feat(wizard): add aimlapi.com as an LLM provider - #1
Open
Lookoff-AIMLAPI wants to merge 3 commits into
Open
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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:ChatOpenAIplus abase_urlis the whole integration. What is missing is discoverability: auser setting DeerFlow up through
scripts/wizardhas no way to find the gateway,and
config.example.yamlhas no worked example to copy. Every otherOpenAI-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
aimlapi.comprovider. OneLLMProviderentry inscripts/wizard/providers.py, the same shape as theopenrouter/orcarouterentries:langchain_openai:ChatOpenAI,AIMLAPI_API_KEY,base_url: https://api.aimlapi.com/v1, four selectable models withopenai/gpt-5-5as the default.config.example.yamlgains a commented stanza undermodels:, next tothe OpenRouter example — one plain entry and one
PatchedChatOpenAIvariantfor reasoning models, so
reasoning_contentsurvives multi-turn tool calls.default_headersidentifying DeerFlow as the callingapplication (
HTTP-Referer/X-Titlepointing at this repo, plus thegateway's own two attribution headers).
default_headersis already anallow-listed passthrough in
deerflow/models/factory.py, andclaude_provider.pyalready merges into it, so this uses machinery the repohas rather than adding any.
LLMProvider.extra_config_for()now copies a nesteddefault_headersmap.Its docstring already promised not to mutate the shared provider-level
extra_config, butdict()is shallow, so a caller editing the returnedconfig's header map would have rewritten the shared provider definition for
every later wizard run in the same process.
backend/tests/test_setup_wizard.py, modelled ontest_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:capabilitiesopenai/gpt-5-5(default)anthropic/claude-sonnet-4.6google/gemini-2.5-flashdeepseek/deepseek-chatsupports_vision: truesits on the provider with amodel_vision_overridesentry turning it off for
deepseek/deepseek-chat, the same mechanism the MiniMaxentries use for M2.7.
Version numbers use the dotted spelling (
anthropic/claude-sonnet-4.6). Thegateway 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
langchain-openaiis already a dependencychange any existing default
inert until a user selects it
Validation
Backend, on this branch:
Baseline — full suite on a pristine
maincheckout, before any edit: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:
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 thatconsumes
default_headers, and the support bundle that redacts them.tests/test_setup_wizard.pyalone 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 HEAD→24 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.providers→wizard.writer.build_minimal_config→ generatedconfig.yaml→deerflow.models.factory.create_chat_model()→ a real request.No mock, no raw
curl. Generated model entry:Completion:
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/completionscarryinghttp-referer,x-title,x-aimlapi-partner-idandx-aimlapi-source, andauthorization(redacted here). The outgoing bodies were captured and asserted:That last assertion matters: this gateway returns HTTP 400 for an explicit
temperature: null/top_p: null/seed: null, which is exactly what anOpenAI-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_modelbuilds settings withmodel_dump(exclude_none=True), and theprovider config carries a real
temperature: 0.7— and the generated-config testpins the second.
What was not verified
supports_visionwas taken from the gateway's published capabilityflags per model, not exercised with an actual image.
PatchedChatOpenAI. That variant isdocumented in
config.example.yamlbut is not what the wizard writes, and itwas not run.
model-selector.tsxprovider-slug union is untouched. It is alogo lookup terminated by
| (string & {}), so it is cosmetic and gatesnothing; adding a slug there would need a logo asset and is out of scope.
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.
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 tothe top of
LLM_PROVIDERS. It is deliberately isolated so it can be dropped witha 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.