Add aimlapi.com as a named LLM provider - #1
Open
Lookoff-AIMLAPI wants to merge 3 commits into
Open
Conversation
Users can already reach aimlapi.com through ChatOpenAI(base_url=...), but that route has no env var, no discoverability, and - the part that actually costs users money - no entry in _get_pricing_model_name(), so a gateway-routed openai/gpt-4o-mini is priced against upstream OpenAI's rate card in the token cost display. Registering it as a named provider is the only way to get that carve-out, mirroring what OrcaRouter did. The provider is a near-verbatim sibling of browser_use/llm/orcarouter, with two deliberate differences: - Unset model params are omitted from the request instead of being sent as explicit nulls. The gateway validates optional fields strictly and answers `"top_p": null` with a 400; the OpenAI SDK serialises a None argument exactly that way, so the copied shape failed on every call made with default settings. - Attribution headers (HTTP-Referer / X-Title naming browser-use, plus the two X-AIMLAPI-* channel headers) are merged into default_headers and scoped to the api.aimlapi.com origin, so they cannot ride a request to a proxy configured through base_url. Caller-supplied headers win on a key clash, and the shared constant is never mutated. ChatOpenRouter already sets HTTP-Referer, so this is an existing mechanism rather than new machinery. Model ids used in the example, the docs and the tests were checked against the live catalog (ids and aliases) rather than copied from another gateway's list.
Moves aimlapi.com to the front of the hand-ordered provider lists: the Quick Reference table, the table of contents and the provider sections in skills/open-source/references/models.md, the commented key list in .env.example, and the Chat* entries in the two lazy-import registries. This is partnership placement, not a functional change, and it is deliberately isolated in one commit so it can be dropped before any upstream PR. Nothing here alters behaviour; the TYPE_CHECKING import blocks are left alone because ruff/isort orders them. No "Recommended" badge is claimed. The repo has that concept, but it is bound to accuracy figures in "Recommendations by Use Case" that we have no benchmark for, and AGENTS.md reserves it: "always default to and recommend the model `ChatBrowserUse`".
The placeholder part_browseruse was a readable stand-in chosen before the partner was registered. Registration mints the id server-side, so the real value is part_DtfcGF9FcEYD50B1yIkFL8a6. 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.
Adds
ChatAIMLAPIas a first-class LLM provider, wired the same way as the OrcaRouter integration that merged upstream on 2026-08-31 (browser-use#5451).Why
Users can already reach aimlapi.com today with
ChatOpenAI(base_url='https://api.aimlapi.com/v1'), so this is not about making it possible. What a named provider buys is:TokenCost._get_pricing_model_name()has no idea a gateway is in front, so a gateway-routedopenai/gpt-4o-miniis priced against upstream OpenAI's rate card. Theaimlapi/carve-out is the only way to stop that, and it is exactly what OrcaRouter and OpenRouter already have.What's included
browser_use/llm/aimlapi/chat.py—ChatAIMLAPI(provider='aimlapi'), an OpenAI-compatibleBaseChatModelmirroringChatOrcaRouter(text +json_schemastructured-output paths), default base URLhttps://api.aimlapi.com/v1, key from the constructor orAIMLAPI_API_KEYand never fromOPENAI_API_KEY.browser_use/llm/aimlapi/serializer.py—AIMLAPIMessageSerializer, a thin delegate to the OpenAI message serializer.browser_use/llm/__init__.pyandbrowser_use/__init__.py(TYPE_CHECKING stubs +_LAZY_IMPORTS+__all__), sofrom browser_use import ChatAIMLAPIworks.browser_use/tokens/service.py— pricing namespaced toaimlapi/<model>..env.example—# AIMLAPI_API_KEY=.skills/open-source/references/models.md— provider row and section, display nameaimlapi.com.examples/models/aimlapi.py,tests/ci/test_aimlapi.py(10 tests).Two deliberate differences from the OrcaRouter template
1. Unset model params are omitted rather than sent as explicit nulls. A verbatim copy of the OrcaRouter provider failed on every call made with default settings:
ChatOrcaRouterpassestemperature=self.temperature, top_p=self.top_p, seed=self.seedunconditionally, and the OpenAI SDK serialises aNoneargument as a literalnullon the wire. OpenAI itself accepts that; the aimlapi.com gateway validates optional fields strictly and rejects it._get_request_params()drops unset keys instead, which is the portable form. A regression test covers it. Note that this is invisible to a unit-test suite — it only shows up on a real request.2. Attribution headers.
HTTP-RefererandX-Titlename browser-use (the calling application), following the conventionChatOpenRouteralready implements with itshttp_refererparameter;X-AIMLAPI-Source/X-AIMLAPI-Partner-IDare the aimlapi.com channel headers. They are:default_headerswin on a key clash;api.aimlapi.comorigin — pointbase_urlat a proxy or another vendor and no attribution is sent at all.Tests assert all three properties plus the partner-id shape (
^part_[A-Za-z0-9]{1,64}$); a malformed id is dropped silently server-side, so a typo would otherwise be invisible.Model ids
Every id shipped here (
anthropic/claude-sonnet-4.6in the example/docs,openai/gpt-4o-miniin tests) was checked against the live catalog —GET https://api.aimlapi.com/v1/models?include=all, filtered totype == "openai/chat-completions", matched against ids and aliases, and confirmed to advertise thestructured_outputcapability that browser-use's agent loop depends on. No ids were copied from another gateway's provider list.Placement commit
The second commit,
chore(aimlapi): fork-only placement — do not send upstream, moves aimlapi.com to the front of the hand-ordered lists (docs table, ToC, provider sections,.env.example, the two lazy-import registries). It is isolated so it can be dropped before any upstream PR. It changes no behaviour. TheTYPE_CHECKINGimport blocks are left in ruff/isort order.No "Recommended" badge is claimed for aimlapi.com. The repo does have that concept, but it is tied to benchmark accuracy figures under "Recommendations by Use Case" that we have no measurement for, and
AGENTS.mdreserves it: "always default to and recommend the modelChatBrowserUse".Verification
uv build— clean, before and after.tests/ci/test_orcarouter.py,test_openrouter_token_cost.py,tests/ci/models/,test_llm_retries.py,test_llm_output_truncation.py,test_fallback_llm.py):tests/ci/test_aimlapi.pyadded: 123 passed, 7 skipped, 0 failed (+10 new)ruff check,ruff format,pyright,codespell,pyupgrade, …), perAGENTS.md.api.aimlapi.comwithanthropic/claude-sonnet-4.6:'AIMLAPI OK', usageprompt=16 completion=8 total=24json_schemastructured output: valid Pydantic round-trip, usageprompt=219 completion=115 total=334use_vision=True(so screenshots really went over the wire), task "Go to https://example.com and report the exact text of the page's main heading": completed in 1 step,is_done=True,is_successful=True, final resultExample Domain, 2 invocations / 11,622 tokens, correctly attributed toanthropic/claude-sonnet-4.6.Not verified
TokenCostresolves prices throughget_openrouter_model_pricing, which knows nothing aboutaimlapi/<model>, so cost shows as0.0— the same behaviour OrcaRouter has today. The carve-out's job is to prevent a wrong number, not to produce a right one; wiring real per-model prices (the catalog does expose them under?include=pricing) would be a separate change.tests/ciwas run, not the full browser matrix, which CI shards across many jobs.