Skip to content

feat: add aimlapi.com as an OpenAI-compatible gateway provider - #1

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

feat: add aimlapi.com as an OpenAI-compatible gateway provider#1
Lookoff-AIMLAPI wants to merge 5 commits into
mainfrom
feat/aimlapi-provider

Conversation

@Lookoff-AIMLAPI

Copy link
Copy Markdown
Member

Description

Adds aimlapi.com (AI/ML API) to the provider registry as an OpenAI-compatible
gateway, sends the attribution headers it reads, and fixes a /models de-duplication
bug that its catalog exposes.

Before this, reaching AI/ML API from DeepTutor meant hand-typing a custom profile:
the endpoint, the env var and the capability flags had to be rediscovered by every
user, and nothing told the config loader that an https://api.aimlapi.com/v1 base
belongs to a gateway that routes any model.

Commits

Commit What
feat: add aimlapi.com as an OpenAI-compatible gateway provider ProviderSpec in the gateway block, capability entry, wizard fallback models, registry test
feat: attribute DeepTutor traffic to aimlapi.com by endpoint host Attribution headers, host-scoped, with tests
fix: drop repeated ids from a provider's /models list Order-preserving de-dup in collect_model_names
chore(aimlapi): fork-only placement — do not send upstream Placement only. Drop this commit before offering anything upstream.

Provider registration

ProviderSpec(
    name="aimlapi",
    keywords=("aimlapi",),
    env_key="AIMLAPI_API_KEY",
    display_name="aimlapi.com",
    backend="openai_compat",
    is_gateway=True,
    detect_by_base_keyword="aimlapi",
    default_api_base="https://api.aimlapi.com/v1",
)

Shaped after the four aggregator peers already in that block (openrouter,
orcarouter, edenai, aihubmix). Two deliberate omissions:

  • No detect_by_key_prefix — AI/ML API keys carry no distinguishing prefix, so
    a prefix rule would either never fire or misfire on another vendor's key.
  • No strip_model_prefix — model ids keep their vendor prefix
    (openai/gpt-4o-mini), unlike aihubmix.

Only POST /v1/chat/completions and POST /v1/responses exist upstream; there is no
/v1/completions, and none is declared.

Attribution headers

AI/ML API reads the same HTTP-Referer / X-Title pair OpenRouter does, plus
X-AIMLAPI-Partner-ID and X-AIMLAPI-Source. They are added in
openai_sdk_client_kwargs, right next to the existing
OPENROUTER_ATTRIBUTION_HEADERS block, which is the repo's own precedent for this.

Both HTTP-Referer and X-Title name DeepTutor, not the gateway — they identify
the calling application.

The scoping is deliberately narrower than the OpenRouter check beside it. That one
matches a substring of the URL; this one compares the host of the resolved
endpoint (configured api_base, else the spec's default_api_base) against an exact
allow-list. Tests cover the cases a substring match would get wrong:

  • https://api.aimlapi.com.evil.io/v1 → no headers
  • https://notaimlapi.com/v1 → no headers
  • https://aimlapi.com.attacker.example/v1 → no headers
  • https://gateway.internal.example/aimlapi/v1 → no headers
  • an aimlapi-typed profile pointed at https://proxy.example.com/v1 → no headers

Headers are merged into a per-call dict, never assigned: a profile's own
extra_headers still win on a key clash, and a test asserts the module constant is
unchanged after a call. A further test walks every other ProviderSpec and
asserts none of them emits an X-AIMLAPI-* header.

The partner id fails silently when malformed — an unparseable value is accepted by
the gateway and simply earns nothing — so its shape is asserted in a test
(^part_[A-Za-z0-9]{1,64}$), as is the source (^(web|agent|mcp)/[a-z0-9-]{1,32}$).

/models de-duplication

A /models payload may list one model id once per endpoint family it serves.
GET https://api.aimlapi.com/v1/models returns 936 rows for 785 distinct ids, and
collect_model_names turned each repeat into a separate, identical row in the model
picker — 151 duplicates, indistinguishable to the user. First occurrence now wins, so
payload order is preserved. This is provider-neutral: a payload with no repeats is
unaffected.

Model ids

Every id shipped in the wizard fallback list was checked against the live catalog on
2026-09-03 — against both id and aliases, and filtered to
type == "openai/chat-completions" (353 of the 936 rows). None was copied from
another aggregator's list.

openai/gpt-4o-mini, openai/gpt-5-5, anthropic/claude-sonnet-4.5,
deepseek/deepseek-chat — all four present as chat ids.

Module(s) Affected

  • services
  • tests
  • Other: deeptutor_cli

Verification

Buildpip install -e ".[dev,partners]" on Python 3.12, clean.

Tests — full suite, baseline captured on a pristine checkout of main
(93df3d48) before any change:

passed failed skipped
baseline (93df3d48) 6323 33 48
this branch 6341 33 48

The 33 failures are the identical set in both runs (diff of the sorted FAILED
lines is empty) — pre-existing, in tests/agents/question/test_pipeline.py,
tests/capabilities/, tests/api/test_canonical_route_surface.py,
tests/runtime/test_isolated_worker.py, tests/services/sandbox/test_sandbox.py.
The 18 new passes are the tests added here.

Lint / CI gates, all run locally at the versions CI pins:
ruff==0.16.0 check . clean · ruff format --check . clean ·
import-linter==2.11 lint-imports 3 contracts kept · mypy (hook flags) clean on
every changed non-test file · bandit -c pyproject.toml clean ·
scripts/check_repo_hygiene.py passed · detect-secrets adds no finding that the
pristine files do not already produce.

One real inference call, through the code path added — not a mock, not curl:
LLMConfig(binding="aimlapi") → get_runtime_provider() → OpenAICompatProvider.chat().

resolved base_url : https://api.aimlapi.com/v1/
attribution headers sent:
  HTTP-Referer: https://github.com/HKUDS/DeepTutor
  X-Title: DeepTutor
  X-AIMLAPI-Partner-ID: part_deeptutor
  X-AIMLAPI-Source: agent/deeptutor
chat content      : 'DeepTutor x aimlapi.com OK'
chat usage        : {'prompt_tokens': 19, 'completion_tokens': 8, 'total_tokens': 27}
tool calls        : [{"name": "get_weather", "arguments": {"city": "Paris"}}]

Tool calling exercised in the same run and returns a well-formed call.
Model discovery exercised through cloud_provider.fetch_models against the live
endpoint: 785 names after the de-dup fix, 936 before it.

Not verified

  • Frontend. No web/ file is touched, so npm run check / the web audit job were
    not run. ProviderIcon has no aimlapi entry and falls back to the generic mark,
    which is what edenai, novita, atlascloud and orcarouter already do — no icon
    was vendored rather than inventing one.
  • Non-chat modalities. Embedding, TTS, image and video specs in
    provider_runtime.py are untouched; only the chat surface is claimed.
  • /v1/responses. The spec leaves api_format at auto, so DeepTutor uses Chat
    Completions. The Responses endpoint exists at AI/ML API but is not exercised here.

Checklist

  • My code follows the project's coding standards.
  • I have run the pre-commit toolchain (ruff, ruff-format, bandit, mypy, hygiene, detect-secrets) and fixed any issues.
  • I have added relevant tests for my changes.
  • I have updated the documentation — the registry is self-documenting and README.md carries no provider table to extend.
  • My changes do not introduce any new security vulnerabilities.

Note for whoever takes this upstream

CONTRIBUTING.md is explicit about branch targeting:

Important

Please do not submit PRs directly to main. All contributions should target dev or multi-user.

This PR targets the fork's own default branch (main) per our fork workflow. An
upstream PR must target dev, and must drop the
chore(aimlapi): fork-only placement commit
first.

aimlapi and others added 5 commits September 3, 2026 05:02
DeepTutor could already reach AI/ML API only by typing a "custom" profile by
hand: the endpoint, the env var and the capability flags all had to be
rediscovered by each user, and nothing in the registry told the config loader
that an https://api.aimlapi.com/v1 base belongs to a gateway that routes any
model. Registering it once is what makes env resolution, api_base detection
and the status display work the way they already do for the other
aggregators in the same block.

No detect_by_key_prefix: AI/ML API keys carry no distinguishing prefix, so
the endpoint is the only signal that will not misfire on someone else's key.
No strip_model_prefix: model ids keep their vendor prefix ("openai/gpt-4o-mini"),
unlike aihubmix.

The wizard fallback ids were each checked against
GET https://api.aimlapi.com/v1/models (2026-09-03), filtered to
type == "openai/chat-completions" and matched against both ids and aliases,
rather than copied from another aggregator's list.
AI/ML API reads the same HTTP-Referer / X-Title pair OpenRouter does, plus
X-AIMLAPI-Partner-ID and X-AIMLAPI-Source, to tell which application sent a
request. Without them DeepTutor's traffic is indistinguishable from anyone
else's, which is what the existing OPENROUTER_ATTRIBUTION_HEADERS block
already exists to avoid for the other gateway.

The scoping is deliberately narrower than the OpenRouter check next to it.
That one matches a substring of the URL, which also fires for a proxy whose
path merely contains the vendor name; this one compares the *host* of the
resolved endpoint against an exact allow-list, so the headers cannot ride a
request to api.aimlapi.com.evil.io, to notaimlapi.com, or to a self-hosted
proxy configured under an "aimlapi" binding.

Headers are merged into a per-call dict rather than assigned, so a profile's
own extra_headers still win and the module constant is never mutated. The
partner id fails silently when malformed — an unparseable value is accepted
and simply earns nothing — so its shape is asserted in a test rather than
left to review, alongside a walk over every other ProviderSpec proving none
of them carries an X-AIMLAPI-* header.
A /models payload may list one model id once per endpoint family it serves,
so the same name arrives more than once. AI/ML API returns 936 rows for 785
distinct ids; every repeat became a separate, identical row in the model
picker with no way for the user to tell them apart. First occurrence wins,
so payload order — which providers use to put their preferred models first —
is preserved.
Partnership placement, kept as one commit so it can be dropped wholesale
before anything is offered upstream. Nothing here changes behaviour that a
user can observe beyond ordering:

- aimlapi.com moves to the head of the gateway block in PROVIDERS. The block
  is hand-ordered and the file's own docstring says order controls match
  priority; every gateway in it is distinguished by an unambiguous api_base
  keyword, so the move changes which entry is offered first, not which one is
  detected.
- aimlapi.com is added at the head of FEATURED_LLM_PROVIDERS, the wizard's
  existing hand-picked shortlist. That list is the repo's own "featured"
  mechanism; no new badge concept was invented for it.

The wizard's default selection is untouched — it still falls back to openai
unless the user's current binding is something else.
The placeholder part_deeptutor was a readable stand-in chosen before the
partner was registered. Registration mints the id server-side, so the
real value is part_ItAs0L5uSTvV2dFDOZZaS1BL. 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