Skip to content

Add aimlapi.com as a named LLM provider - #1

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

Add aimlapi.com as a named LLM provider#1
Lookoff-AIMLAPI wants to merge 3 commits into
mainfrom
feat/aimlapi-provider

Conversation

@Lookoff-AIMLAPI

Copy link
Copy Markdown
Member

Adds ChatAIMLAPI as 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:

  • Correct cost accounting. TokenCost._get_pricing_model_name() has no idea a gateway is in front, so a gateway-routed openai/gpt-4o-mini is priced against upstream OpenAI's rate card. The aimlapi/ carve-out is the only way to stop that, and it is exactly what OrcaRouter and OpenRouter already have.
  • Discoverability — env var, docs row, example, autocomplete.
  • A place to hang attribution headers.

What's included

  • browser_use/llm/aimlapi/chat.pyChatAIMLAPI (provider='aimlapi'), an OpenAI-compatible BaseChatModel mirroring ChatOrcaRouter (text + json_schema structured-output paths), default base URL https://api.aimlapi.com/v1, key from the constructor or AIMLAPI_API_KEY and never from OPENAI_API_KEY.
  • browser_use/llm/aimlapi/serializer.pyAIMLAPIMessageSerializer, a thin delegate to the OpenAI message serializer.
  • Registration in both browser_use/llm/__init__.py and browser_use/__init__.py (TYPE_CHECKING stubs + _LAZY_IMPORTS + __all__), so from browser_use import ChatAIMLAPI works.
  • browser_use/tokens/service.py — pricing namespaced to aimlapi/<model>.
  • .env.example# AIMLAPI_API_KEY=.
  • skills/open-source/references/models.md — provider row and section, display name aimlapi.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:

400 PublicApiValidationException
  top_p: Expected number, received null
  seed:  Expected number, received null

ChatOrcaRouter passes temperature=self.temperature, top_p=self.top_p, seed=self.seed unconditionally, and the OpenAI SDK serialises a None argument as a literal null on 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-Referer and X-Title name browser-use (the calling application), following the convention ChatOpenRouter already implements with its http_referer parameter; X-AIMLAPI-Source / X-AIMLAPI-Partner-ID are the aimlapi.com channel headers. They are:

  • merged, never assigned — a caller's own default_headers win on a key clash;
  • built as a fresh dict per client, so the module-level constant is never mutated;
  • scoped to the api.aimlapi.com origin — point base_url at 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.6 in the example/docs, openai/gpt-4o-mini in tests) was checked against the live catalog — GET https://api.aimlapi.com/v1/models?include=all, filtered to type == "openai/chat-completions", matched against ids and aliases, and confirmed to advertise the structured_output capability 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. The TYPE_CHECKING import 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.md reserves it: "always default to and recommend the model ChatBrowserUse".

Verification

  • Build: uv build — clean, before and after.
  • Tests (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):
    • baseline on a pristine checkout: 113 passed, 7 skipped, 0 failed
    • after, with tests/ci/test_aimlapi.py added: 123 passed, 7 skipped, 0 failed (+10 new)
  • Pre-commit: all hooks pass on every changed file (ruff check, ruff format, pyright, codespell, pyupgrade, …), per AGENTS.md.
  • Live inference through the new code path (not a mock, not curl), against api.aimlapi.com with anthropic/claude-sonnet-4.6:
    • plain text: returned 'AIMLAPI OK', usage prompt=16 completion=8 total=24
    • json_schema structured output: valid Pydantic round-trip, usage prompt=219 completion=115 total=334
  • Full agent run, headless Chromium, use_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 result Example Domain, 2 invocations / 11,622 tokens, correctly attributed to anthropic/claude-sonnet-4.6.

Not verified

  • Cost figures are not asserted, only the pricing key. TokenCost resolves prices through get_openrouter_model_pricing, which knows nothing about aimlapi/<model>, so cost shows as 0.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.
  • Only the LLM-related slice of tests/ci was run, not the full browser matrix, which CI shards across many jobs.

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.
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