Skip to content

feat(wizard): add aimlapi.com as an LLM provider - #1

Open
Lookoff-AIMLAPI wants to merge 3 commits into
mainfrom
feat/aimlapi-provider
Open

feat(wizard): add aimlapi.com as an LLM provider#1
Lookoff-AIMLAPI wants to merge 3 commits into
mainfrom
feat/aimlapi-provider

Conversation

@Lookoff-AIMLAPI

Copy link
Copy Markdown
Member

Fixes #

Why

DeerFlow's model layer is pure config, so aimlapi.com already works today for
anyone who hand-writes the YAML — use: langchain_openai:ChatOpenAI plus a
base_url is the whole integration. What is missing is discoverability: a
user setting DeerFlow up through scripts/wizard has no way to find the gateway,
and config.example.yaml has no worked example to copy. Every other
OpenAI-compatible gateway in the wizard (OpenRouter, OrcaRouter, Atlas Cloud) is
already reachable that way.

aimlapi.com is an OpenAI-compatible aggregator: one key in front of 350+ chat
models (OpenAI, Anthropic, Google, DeepSeek, Qwen, Kimi, GLM, Llama, …), so it
covers the same "one key, many vendors" need as the Volcengine Coding Plan entry
does for the China-hosted set.

What changed

  • Setup wizard gains an aimlapi.com provider. One LLMProvider entry in
    scripts/wizard/providers.py, the same shape as the openrouter /
    orcarouter entries: langchain_openai:ChatOpenAI, AIMLAPI_API_KEY,
    base_url: https://api.aimlapi.com/v1, four selectable models with
    openai/gpt-5-5 as the default.
  • config.example.yaml gains a commented stanza under models:, next to
    the OpenRouter example — one plain entry and one PatchedChatOpenAI variant
    for reasoning models, so reasoning_content survives multi-turn tool calls.
  • The provider sends default_headers identifying DeerFlow as the calling
    application (HTTP-Referer / X-Title pointing at this repo, plus the
    gateway's own two attribution headers). default_headers is already an
    allow-listed passthrough in deerflow/models/factory.py, and
    claude_provider.py already merges into it, so this uses machinery the repo
    has rather than adding any.
  • LLMProvider.extra_config_for() now copies a nested default_headers map.
    Its docstring already promised not to mutate the shared provider-level
    extra_config, but dict() is shallow, so a caller editing the returned
    config's header map would have rewritten the shared provider definition for
    every later wizard run in the same process.
  • Four tests in backend/tests/test_setup_wizard.py, modelled on
    test_openrouter_defaults_are_preserved.

No default is changed: this adds an option to the wizard menu, it does not
change what an existing install does.

On the model ids

Each id was checked against the live catalog as an id or an alias, not copied
from another aggregator's provider list — that is how dead ids spread. All four
shipped ids are present as ids and are type: openai/chat-completions:

id catalog capabilities
openai/gpt-5-5 (default) tools, vision, reasoning, structured_output, parallel_tool_calls, file_input, streaming
anthropic/claude-sonnet-4.6 as above, plus web_search
google/gemini-2.5-flash tools, vision, structured_output, parallel_tool_calls, audio, streaming
deepseek/deepseek-chat tools, structured_output, parallel_tool_calls, file_input, streaming — no vision

supports_vision: true sits on the provider with a model_vision_overrides
entry turning it off for deepseek/deepseek-chat, the same mechanism the MiniMax
entries use for M2.7.

Version numbers use the dotted spelling (anthropic/claude-sonnet-4.6). The
gateway routes the dashed spelling too, but only the dotted id currently
publishes its capability flags, so the dotted form is what the example documents.

Nothing was removed, so no removal needed justifying.

Surface area

  • Frontend UI
  • Backend API
  • Agents / LangGraph
  • Sandbox
  • Skills
  • Dependencies — none; langchain-openai is already a dependency
  • Default behavior change — no; this adds a menu option, it does not
    change any existing default
  • Docs / tests / CI only — plus the wizard registry entry, which is
    inert until a user selects it

Validation

Backend, on this branch:

cd backend && make lint          # ruff check: All checks passed! / 1317 files already formatted
cd backend && make format        # no changes
cd backend && make test

Baseline — full suite on a pristine main checkout, before any edit:

13690 passed, 99 skipped, 1 deselected, 19 warnings in 850.38s   (exit 0)

After — the full suite could not be completed on this branch, and that is a
property of the machine, not of the diff. The same command that took 850s on the
pristine tree was still at ~4% after 30 minutes on a host that had reached a load
average of ~120 from unrelated concurrent work; an earlier attempt deadlocked in
a thread wait at 16% and had to be killed. What was run instead, on this branch,
green in 14.6s:

pytest -m "not live" tests/test_setup_wizard.py tests/test_config_version.py \
  tests/test_model_factory.py tests/test_support_bundle.py \
  tests/test_app_config_reload.py tests/test_doctor.py tests/test_verification_config.py
=> 283 passed

Those are the suites that cover what this diff touches: the wizard registry and
writer, config.example.yaml's version/parity checks, the model factory that
consumes default_headers, and the support bundle that redacts them.
tests/test_setup_wizard.py alone goes from 49 to 53 tests, all passing.

Someone should re-run the full backend suite on a quiet machine before merging.
The change is three files, adds no import and no dependency, and the entry is
inert until a user selects it, so a regression elsewhere is unlikely — but
"unlikely" is not the same as measured, and this PR does not claim it.

Also ran python scripts/check_agent_guidance.py --base-ref origin/main --head-ref HEAD24 AGENTS.md, 0 errors, 0 warnings.

Frontend is untouched, so its suites were not run.

Live inference through the code path this PR adds

Green tests do not prove a provider works, so this was exercised end to end —
wizard.providerswizard.writer.build_minimal_config → generated
config.yamldeerflow.models.factory.create_chat_model() → a real request.
No mock, no raw curl. Generated model entry:

name: gpt-5-5
display_name: aimlapi.com
use: langchain_openai:ChatOpenAI
model: openai/gpt-5-5
api_key: $AIMLAPI_API_KEY
base_url: https://api.aimlapi.com/v1
request_timeout: 600.0
max_retries: 2
max_tokens: 8192
temperature: 0.7
supports_vision: true
default_headers:
  HTTP-Referer: https://github.com/bytedance/deer-flow
  X-Title: DeerFlow
  X-AIMLAPI-Partner-ID: part_deerflow
  X-AIMLAPI-Source: agent/deer-flow

Completion:

'DeerFlow aimlapi live check OK'
usage: {'input_tokens': 18, 'output_tokens': 23, 'total_tokens': 41,
        'output_token_details': {'reasoning': 5}}

Tool calling, via chat.bind_tools([get_weather]):

[{"name": "get_weather", "args": {"city": "Reykjavik"},
  "id": "call_IDFnuvwtVrcEcejYggaTfceJ", "type": "tool_call"}]

Both requests went to https://api.aimlapi.com/v1/chat/completions carrying
http-referer, x-title, x-aimlapi-partner-id and x-aimlapi-source, and
authorization (redacted here). The outgoing bodies were captured and asserted:

body keys: ['max_completion_tokens', 'messages', 'model', 'stream', 'temperature']
null-valued body keys: none

That last assertion matters: this gateway returns HTTP 400 for an explicit
temperature: null / top_p: null / seed: null, which is exactly what an
OpenAI-compatible client emits for an unset optional if the config leaves those
keys out and the client serialises them anyway. Two things make that safe here —
create_chat_model builds settings with model_dump(exclude_none=True), and the
provider config carries a real temperature: 0.7 — and the generated-config test
pins the second.

What was not verified

  • Vision. supports_vision was taken from the gateway's published capability
    flags per model, not exercised with an actual image.
  • Thinking/reasoning replay through PatchedChatOpenAI. That variant is
    documented in config.example.yaml but is not what the wizard writes, and it
    was not run.
  • The frontend model-selector.tsx provider-slug union is untouched. It is a
    logo lookup terminated by | (string & {}), so it is cosmetic and gates
    nothing; adding a slug there would need a logo asset and is out of scope.
  • The full backend suite on this branch — see above.

AI assistance

Tool(s) used: to be completed by the submitting author before this is
offered upstream — this PR targets our fork, and the repository's PR template
asks for a first-person disclosure that only a human author can sign.

How you used it: see above.

  • I've read and understand every line of this change and take responsibility
    for it — it's not unreviewed AI output.

Note for the reviewer: the last commit,
chore(aimlapi): fork-only placement — do not send upstream, moves the entry to
the top of LLM_PROVIDERS. It is deliberately isolated so it can be dropped with
a single revert before any upstream submission. No badge or "recommended" label
was added — this repo has no such concept and inventing one would change the
wizard's vocabulary rather than add a provider.

aimlapi and others added 3 commits September 3, 2026 05:30
DeerFlow's model layer is pure config, so aimlapi.com already works today for
anyone who hand-writes the YAML — langchain_openai:ChatOpenAI plus a base_url is
all it needs. What is missing is discoverability: a user setting DeerFlow up
through the wizard has no way to find it, and there is no worked example to copy
from config.example.yaml. This is the same one-dataclass-entry shape the other
OpenAI-compatible gateways here use (openrouter, orcarouter).

Model ids were each checked against the live catalog as an id or an alias rather
than copied from another aggregator's list, which is how dead ids spread. The
gateway routes both dotted and dashed spellings of a version, but only the dotted
one currently publishes its capability flags, so the dotted spelling is what the
example and the entry use.

default_headers identifies DeerFlow as the calling application, the way
HTTP-Referer/X-Title already do for OpenRouter-style gateways. It lives inside
this provider's extra_config rather than in any shared default, so it can only
ever be written onto a model entry pointing at this base_url and cannot ride a
request to a different provider. extra_config_for() now copies that nested map
as well, so a caller editing a returned config cannot reach back and rewrite the
shared provider definition — its docstring already promised that, but the copy
was one level deep.

The partner-id and source header values are covered by a test because a
malformed value is accepted by the gateway and then silently ignored: nothing
fails at runtime, so a typo would never surface anywhere else.
LLM_PROVIDERS is hand-ordered and the wizard renders it in list order, so
position in this list is position in the menu a first-time user sees. Moving the
aimlapi.com entry to the front is a placement decision for our own distribution,
not a change any upstream maintainer asked for, so it is isolated in its own
commit and can be dropped with a single revert before this work is offered
upstream. Nothing else depends on the ordering: the entry is selected by name
everywhere it is referenced, and no test asserts an index.

No badge is added. This repo has no "recommended"/featured concept in the wizard
or anywhere else, and inventing one to point at ourselves would be a change to
the wizard's vocabulary rather than an added provider.
The placeholder part_deerflow was a readable stand-in chosen before the
partner was registered. Registration mints the id server-side, so the
real value is part_91MTARtiTI8JdCgPOt0ioAKF. A wrong or unknown partner id is accepted with a
200 and silently not attributed, so this would not have surfaced at runtime.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant