feat(llms,embeddings): add aimlapi.com as a named LLM and embedding provider - #1
Open
Lookoff-AIMLAPI wants to merge 3 commits into
Open
feat(llms,embeddings): add aimlapi.com as a named LLM and embedding provider#1Lookoff-AIMLAPI wants to merge 3 commits into
Lookoff-AIMLAPI wants to merge 3 commits into
Conversation
mem0 is a memory library, so a gateway is only usable here if it can serve the embedding path as well as the LLM path. Reaching aimlapi.com through the existing `litellm` provider covers chat but not embeddings — LiteLLM ships no embedding route for its `aiml` provider and answers "Unmapped LLM provider for this endpoint" — and reaching it through `openai` + `openai_base_url` leaves the provider unnamed in configs and docs. Hence a named provider in both categories, sharing one connection module. The two providers are thin wrappers over the OpenAI SDK because the endpoint is OpenAI-compatible, which is the same shape xai/deepseek/sarvam already use. Three details are not cosmetic: - Unset options are omitted from the chat payload rather than passed as None. The API answers 400 to an explicit `null` on `tools`, `temperature`, `top_p`, `seed`, `response_format` and others instead of reading it as "unset". The OpenAI SDK serialises a None-valued keyword straight through, so a provider that forwards None succeeds on a first turn and fails on the turn where an agent loop clears its tools. - The embedder sends `input` as strings. The endpoint rejects the pre-tokenised integer-array form with a 400 naming `input`, which is what LangChain's OpenAIEmbeddings sends by default; going through mem0's own OpenAI-SDK embedder avoids it, and the docs page says so for anyone routing this gateway through the `langchain` embedder instead. - Attribution headers are attached per client and only when the resolved host is api.aimlapi.com, so a user who repoints the provider at their own gateway does not forward them to a third party. HTTP-Referer/X-Title name Mem0, matching how the OpenAI provider already labels OpenRouter traffic. `defaultHeaders` is threaded through the TypeScript OpenAI LLM and embedder, which had no way to set request headers; it is spread only when present, so every other provider's client construction is unchanged.
Moves the aimlapi.com entry to the head of every hand-ordered, user-facing provider list: the two docs card groups, the two docs.json nav groups, the LLM.md provider lists and the two llms.txt sections. No behaviour changes and no code changes — docs ordering only. This is deliberately isolated so it can be dropped before the change is offered upstream, where self-promotion in someone else's provider list is not ours to make. mem0 has no "recommended"/featured badge concept in these lists, so none was invented. The Python allowlists and the factory maps are left in append position: their order is not user-visible and reordering them would be noise in the diff.
The placeholder part_mem0 was a readable stand-in chosen before the partner was registered. Registration mints the id server-side, so the real value is part_JNAROikm3sdRqpewzcZxLgrK. 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.
Linked Issue
Not applicable — this PR targets the fork's own
main. Upstream requiresCloses #<issue>on an issue carrying theacceptedlabel; see Upstream gatesbelow before this is offered to
mem0ai/mem0.Description
Adds
aimlapias a named provider in both the LLM and the embeddingcategories, in the Python SDK and the TypeScript SDK.
mem0 is a memory library, so a gateway is only useful here if it serves the
embedding path as well as the chat path. Two existing routes fall short:
provider: "litellm"with anaiml/...model covers chat only. LiteLLM shipsllms/aiml/chat/andimage_generation/but noembedding/route, so anaiml/<embedding model>call fails withUnmapped LLM provider for this endpoint. Verified against upstream LiteLLM's tree.provider: "openai"withopenai_base_urlworks but leaves the providerunnamed in configs, docs and the factory, and silently shares the OpenAI env
vars.
So this is a named provider on both sides, sharing one small connection module
(
mem0/utils/aimlapi.py,mem0-ts/src/oss/src/utils/aimlapi.ts).What was added
mem0/llms/aimlapi.pyAimlapiLLM(LLMBase)over the OpenAI SDKmem0/embeddings/aimlapi.pyAimlapiEmbedding(EmbeddingBase)mem0/configs/llms/aimlapi.pyAimlapiConfigwithaimlapi_base_urlmem0/utils/aimlapi.pydrop_nonemem0/llms/configs.py,mem0/embeddings/configs.pymem0/utils/factory.pyLlmFactory+EmbedderFactoryentriesmem0/configs/embeddings/base.pyaimlapi_base_urlparametermem0-ts/.../llms/aimlapi.ts,.../embeddings/aimlapi.tsmem0-ts/.../utils/factory.tscase "aimlapi"in both factoriesmem0-ts/.../types/index.ts,llms/openai.ts,embeddings/openai.tsdefaultHeaderspass-throughtests/llms/test_aimlapi.py,tests/embeddings/test_aimlapi_embeddings.pymem0-ts/.../tests/aimlapi.test.ts,factory.unit.test.tsdocs/**,LLM.md,docs/llms.txtThree details that are not cosmetic
1. Unset options are omitted from the payload, never sent as
null.The API answers
400to an explicitnullontools,temperature,top_p,seed,tool_choice,response_format,stream,stream_options,parallel_tool_calls,max_tokensandmax_completion_tokenson the strictestmodels, rather than reading it as "unset". Which models are strict varies —
google/gemini-2.5-flashacceptsnulleverywhere,openai/gpt-5-minirejectstools: null(confirmed live:400,error.details[].path = "tools"). TheOpenAI SDK serialises a
None-valued keyword straight through, so a providercopied from a working OpenAI-compatible one succeeds on turn 1 and fails on the
turn where an agent loop clears its tools — with a fully green test suite.
drop_none()sits on the request path andtest_unset_parameters_are_omitted_not_nulledasserts noNonereaches the wire.2. The embedder sends strings, not token ids.
POST /v1/embeddingsrejects the pre-tokenised integer-array form ofinputwith
400anderror.details[].path = "input". That is exactly what LangChain'sOpenAIEmbeddingssends by default, so anyone routing this gateway throughmem0's
langchainembedder will hit it. mem0's own OpenAI-SDK embedder sendsstrings, this one mirrors it, a test asserts the input elements are strings, and
the docs page carries the warning for the LangChain route.
3. Attribution headers are scoped to the origin and built per client.
X-AIMLAPI-Partner-ID/X-AIMLAPI-Source/HTTP-Referer/X-Titleareattached through the OpenAI SDK's
default_headers, but only when the resolvedhost is
api.aimlapi.com— a user who repoints the provider at their own gateway(
aimlapi_base_url,AIMLAPI_API_BASE) getsNone.HTTP-Referer/X-Titlename Mem0, matching how
mem0/llms/openai.pyalready labels OpenRoutertraffic. Caller headers win on a key clash, and the module constant is never
mutated. A malformed partner id is dropped silently by the gateway and earns
nothing, so its shape is asserted in a test
(
^part_[A-Za-z0-9]{1,64}$) in both languages rather than discovered inproduction.
defaultHeadershad to be threaded through the TypeScriptOpenAILLMandOpenAIEmbedder, which had no way to set request headers. It is spread onlywhen present, so every other provider's client construction is byte-identical.
Defaults
openai/gpt-5-miniopenai/text-embedding-3-small, 1536 dims — matches mem0's existingdefault
embedding_model_dims, so the default vector-store collection worksunchanged.
AIMLAPI_API_KEY; base URL overrideAIMLAPI_API_BASE/aimlapi_base_url/ TSbaseURL.Both defaults were checked against
GET /v1/models(present asids) andcalled live — catalog membership alone is not proof: the catalog omits ids that
serve traffic and has been observed listing at least one that 404s.
Type of Change
AI Assistance
To be completed by the human author before any upstream submission — upstream's
template asks about the code, and upstream's CONTRIBUTING says the author must be
able to explain the diff without an AI tool.
Breaking Changes
None. Every change outside the new files is additive: three allowlist/registry
entries per language, one new optional parameter on
BaseEmbedderConfig, oneoptional field on the two TS config interfaces.
Test Coverage
Baseline vs after
Python (
pytest tests/ --continue-on-collection-errors, local env missing someoptional extras —
azure-identity,sentence-transformers,vertexai,chromadb,faiss— which accounts for every error and failure in both columns):The failure and error sets are identical line-for-line; the +21 is the new tests.
TypeScript (
pnpm run test):Builds:
uv build→mem0ai-2.0.20-py3-none-any.whl, exit 0.pnpm run build→ exit 0.ruff check mem0/ tests/→ clean.ruff format --checkon the six new/changed Python files → clean.isort --check-onlyflags 18 pre-existing files, none of them mine.python scripts/check-llms-txt-coverage.py→ in sync.Live calls
Python, through
Memory.from_configwithaimlapifor both the LLM and theembedder (qdrant on disk):
TypeScript, through
LLMFactory.create("aimlapi")/EmbedderFactory.create("aimlapi")on the built
dist/ossbundle:Notes for a future upstream PR
The second commit (
chore(aimlapi): fork-only placement) must be dropped.It only moves the aimlapi.com entry to the head of the hand-ordered,
user-facing docs lists (two card groups, two
docs.jsonnav groups,LLM.md,two
llms.txtsections). Reordering someone else's provider list is not oursto do upstream. mem0 has no "recommended"/featured badge concept in those
lists, so none was invented. The Python allowlists and factory maps are left in
append position in the first commit — their order is not user-visible.
Whether the attribution headers ship upstream in v1 or as a follow-up is a
commercial call, not a technical one. They are two dict keys plus the
origin guard; removing them means deleting
_ATTRIBUTION_HEADERS/ATTRIBUTION_HEADERSand passingdefault_headers=None.Upstream gates, verbatim from
CONTRIBUTING.md:.github/workflows/pr-gate.ymlenforces the first one by closing the PRwithin a minute and reopening it when the label lands.
Things worth knowing that are not fixed here
max_tokensdoes not reliably bound reasoning tokens on this gateway, andcompletion_tokensexcludes reasoning tokens on some models(
google/gemini-2.5-flashreturned prompt 12 / completion 3 / total 86 /reasoning 71 in probing). mem0 does not meter spend, so nothing breaks, but the
docs page says not to treat
max_tokensas a cost ceiling.modelfield echoed back does not always match the id requested(
openai/gpt-5-5→gpt-5.5-2026-04-23). mem0 does not pin or record theserving model, so this is informational.
Checklist