diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 36918030..26fe5a90 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -398,6 +398,8 @@ jobs: - uses: actions/checkout@v4 - name: ProviderName generator drift (checked-in files must match generator output) run: python3 scripts/gen_provider_names.py --check + - name: Providers doc generator drift (docs/api/providers.md must match generator output) + run: python3 scripts/gen_providers_doc.py --check - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2 - uses: actions/setup-node@v4 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7d30e33a..ce84ddbb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,7 +8,7 @@ to set up a development environment, run the tests, and submit changes. ``` aimux/ ├── aimux-core/ # Core abstractions: LanguageModel / Provider / Message / StreamPart -├── aimux-providers/ # 325 provider implementations + cassettes +├── aimux-providers/ # provider implementations + cassettes (counts: docs/api/providers.md) ├── aimux-stream/ # SSE / NDJSON stream parsing ├── aimux-provider-utils/ # HTTP utilities: retry, backoff, error parsing, API-key loading ├── aimux-ffi/ # C ABI (opaque handle + JSON + push callback) for non-native bindings @@ -72,6 +72,9 @@ aimux distinguishes three kinds of providers: provider-specific behavior. 3. **Modality-specific** — speech, image, video, transcription, etc. +Step-by-step checklists (with the generator and CI rules) live in +[docs/contributing/adding-a-provider.md](docs/contributing/adding-a-provider.md). + Before submitting a provider, read `rfc/0006-provider-development.md` for the minimum acceptance criteria, core contracts, and required tests. diff --git a/README.md b/README.md index 731a120f..e636bc1b 100644 --- a/README.md +++ b/README.md @@ -4,12 +4,12 @@ aimux banner

-> **A unified LLM access layer written in Rust. One API for 329 AI providers.** +> **A unified LLM access layer written in Rust. One API for [327 AI providers](docs/api/providers.md).** [![CI](https://github.com/arcships/aimux/actions/workflows/ci.yml/badge.svg)](https://github.com/arcships/aimux/actions/workflows/ci.yml) [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) [![Rust](https://img.shields.io/badge/rust-stable-orange.svg)](https://www.rust-lang.org/) -[![Providers](https://img.shields.io/badge/providers-329-green.svg)](docs/api/providers.md) +[![Providers](https://img.shields.io/badge/providers-327-green.svg)](docs/api/providers.md) [![Bindings](https://img.shields.io/badge/bindings-8-9cf.svg)](bindings/) [![crates.io](https://img.shields.io/crates/v/aimux-core)](https://crates.io/crates/aimux-core) [![npm](https://img.shields.io/npm/v/@arcships/aimux)](https://www.npmjs.com/package/@arcships/aimux) @@ -30,12 +30,13 @@ difference: aimux is an access layer, those are orchestration layers. ## Why aimux -- **329 provider modules** — 251 registry-backed OpenAI-compatible - (unified `provider(name, ...)` entry) + 10 native protocol - implementations (OpenAI, Anthropic, Google, Bedrock, Vertex, Azure, Cohere, - Mistral, xAI, Anthropic-AWS) + 68 standalone/modality/local/search providers - (OpenRouter, DeepSeek, Ollama, vLLM, ElevenLabs, KlingAI, Tavily, …). - Full list: [docs/api/providers.md](docs/api/providers.md). +- **327 providers** (as of 2026-09-24) — 251 registry-backed OpenAI-compatible + (unified `provider(name, ...)` entry) + 76 typed providers (native protocol + implementations such as OpenAI/Anthropic/Google/Bedrock/Vertex, local + engines like Ollama/vLLM, and speech/image/video/search modality providers). + Counts by category and the full list live in + [docs/api/providers.md](docs/api/providers.md) — generated, and the single + source of truth for provider counts (only the badge above repeats the total). - **Unified, object-safe interface** — the `LanguageModel` trait supports `Box` so providers are interchangeable without changing call sites. - **Full multimodal** — text, streaming, tool calling, embeddings, image, @@ -49,7 +50,7 @@ difference: aimux is an access layer, those are orchestration layers. fallback (RFC-0021); `MoaModel` aggregates parallel reference models mixture-of-agents style (RFC-0022). Both are plain `LanguageModel`s. - **Config-driven provider registry** — `provider-registry.json` describes - each of the 251 OpenAI-compatible providers (base URL, env var, profile + each registry-backed OpenAI-compatible provider (base URL, env var, profile quirks: top_k, tools, response_format, streaming usage, max_tokens key); one unified `provider(name, ...)` entry in every binding. - **Fast and small** — Rust core, release profile tuned for binary size @@ -96,7 +97,7 @@ middleware, and telemetry per request). ``` aimux/ ├── aimux-core # Core abstractions: LanguageModel / Provider / Message / StreamPart -├── aimux-providers # 329 provider implementations (251 registry-backed + native) +├── aimux-providers # Provider implementations — registry-backed + typed (docs/api/providers.md) ├── aimux-stream # SSE / NDJSON stream parsing ├── aimux-provider-utils # One-exchange HTTP helpers, response handlers, API-key loading ├── aimux-ffi # C ABI (opaque handles + JSON results + owned aimux_error_t *) for non-native bindings @@ -120,7 +121,7 @@ cargo add aimux-core aimux-providers | Crate | Description | crates.io | |-------|-------------|-----------| | `aimux-core` | Core abstractions: `LanguageModel` / `Provider` / `Message` / `StreamPart` | [crates.io](https://crates.io/crates/aimux-core) | -| `aimux-providers` | 325 provider implementations | [crates.io](https://crates.io/crates/aimux-providers) | +| `aimux-providers` | Provider implementations — [registry-backed + typed](docs/api/providers.md) | [crates.io](https://crates.io/crates/aimux-providers) | | `aimux-stream` | SSE / NDJSON stream parsing | [crates.io](https://crates.io/crates/aimux-stream) | | `aimux-provider-utils` | One-exchange HTTP helpers and typed response handlers | [crates.io](https://crates.io/crates/aimux-provider-utils) | | `aimux-ffi` | C ABI for non-native bindings | [crates.io](https://crates.io/crates/aimux-ffi) | @@ -283,8 +284,9 @@ let model = provider_from_env("deepseek", "deepseek-chat", None)?; // model usage is identical — it's all dyn LanguageModel ``` -All 251 OpenAI-compatible providers are registry-backed: `provider(name, ...)` -in every binding, with typed `ProviderName` (enum/union/consts per language). +All registry-backed OpenAI-compatible providers share one entry: +`provider(name, ...)` in every binding, with typed `ProviderName` +(enum/union/consts per language). The retired per-provider shell types (`XxxConfig`/`XxxProvider`) are gone — see [docs/API.md](docs/API.md#providers). @@ -294,17 +296,19 @@ see [docs/API.md](docs/API.md#providers). ## Provider coverage -| Type | Count | Examples | -|------|:-----:|----------| -| Native protocol | 10 | OpenAI, Anthropic, Google, Bedrock, Vertex, Azure, Cohere, Mistral, xAI, Anthropic-AWS | -| OpenAI-compatible (registry) | 251 | Groq, Fireworks, Together, Perplexity, Ollama Cloud, DeepSeek, Alibaba Tongyi, Zhipu, Baidu, Tencent, Moonshot, SiliconFlow… | -| OpenAI-compatible (standalone + Vertex-hosted) | 35 | OpenRouter, Hugging Face, Ollama, vLLM, SGLang, Llama.cpp, LiteLLM Proxy, Vertex-hosted DeepSeek/Qwen/Llama… | -| Speech / transcription | 10 | ElevenLabs, Deepgram, AssemblyAI, AWS Polly, Cartesia, Hume, Gladia, RevAI, LMNT, Fal | -| Image / video | 8 | Black Forest Labs, Replicate, Luma, Prodia, KlingAI, Recraft, Stability, RunwayML | -| Embeddings / rerank / search | 13 | Voyage, Jina, Tavily, Exa, Firecrawl, Serper, SearXNG, You.com… | -| Other (Responses API, Bedrock/Mantle) | 2 | generic Responses API wrapper, Bedrock Mantle | +Counts by category live in +[docs/api/providers.md](docs/api/providers.md#totals) — generated from the +registry and `lib.rs`, and the single source of truth. The rough shape of +the roster: -Full list: [rfc/0004-provider-inventory.md](rfc/0004-provider-inventory.md). +- **Native protocol** — OpenAI, Anthropic, Google, Bedrock, Vertex, Azure, Cohere, Mistral, xAI, Anthropic-AWS, Voyage, Codex, OpenRouter +- **OpenAI-compatible (registry-backed)** — Groq, Fireworks, Together, Perplexity, Ollama Cloud, DeepSeek, Alibaba Tongyi, Zhipu, Baidu, Tencent, Moonshot, SiliconFlow… +- **OpenAI-compatible (standalone + Vertex-hosted)** — Hugging Face, Ollama, vLLM, SGLang, Llama.cpp, LiteLLM Proxy, Vertex-hosted DeepSeek/Qwen/Llama… +- **Speech / transcription** — ElevenLabs, Deepgram, AssemblyAI, AWS Polly, Cartesia, Hume, Gladia, RevAI, LMNT, Fal +- **Image / video** — Black Forest Labs, Replicate, Luma, Prodia, KlingAI, Recraft, Stability, RunwayML +- **Embeddings / rerank / search** — Voyage, Jina, Tavily, Exa, Firecrawl, Serper, SearXNG, You.com… + +Full list with per-category counts: [docs/api/providers.md](docs/api/providers.md). ## Language bindings @@ -338,7 +342,8 @@ Tests run on cassette playback — no network and no keys. See |-----|----------| | [docs/API.md](docs/API.md) | **API overview** — shared reference + links to per-language guides | | [docs/api/reference.md](docs/api/reference.md) | **API reference** — public types & functions lookup | -| [docs/api/providers.md](docs/api/providers.md) | **Provider list** — all 325 providers with entry points (generated) | +| [docs/contributing/adding-a-provider.md](docs/contributing/adding-a-provider.md) | **Adding a provider** — the three-case checklist (registry row / new protocol / single modality) | +| [docs/api/providers.md](docs/api/providers.md) | **Provider list** — every provider with entry points and the canonical counts (generated) | | [docs/api/](docs/api/) | **Per-language API guides** — Node.js, Python, Rust, Go, C/C++, Swift, Kotlin, Flutter | | [docs/error-model.md](docs/error-model.md) | **错误模型** — 跨语言错误形态与兼容性约定 | | [docs/PROJECT-OVERVIEW.md](docs/PROJECT-OVERVIEW.md) | Project overview, design decisions, benchmarks | diff --git a/docs/API.md b/docs/API.md index b60eea45..59659077 100644 --- a/docs/API.md +++ b/docs/API.md @@ -1,6 +1,7 @@ # aimux API Documentation -> Unified LLM service access layer — one API to access 325 AI providers +> Unified LLM service access layer — one API to access every provider +> ([providers.md](api/providers.md) carries the count) ## Table of Contents @@ -63,7 +64,7 @@ model)` (Go), `Model.openai(apiKey, modelId)` (Java, Kotlin, Flutter), Multimodal, local-inference and search providers have their own constructors too — full list: [reference.md](api/reference.md). -### OpenAI-compatible (251) +### OpenAI-compatible (registry-backed) One function in every binding: @@ -85,7 +86,7 @@ provider(name, api_key?, model_id, config?) // all languages ``` 字符串形式(`provider("groq", ...)`)在全部语言中同样可用——两种写法等价。 -- Full list (251, name / env var / base URL): [providers.md](api/providers.md) +- Full list (name / env var / base URL): [providers.md](api/providers.md) - Custom endpoint: registry name + `base_url` override, or the OpenAI constructor with a base URL diff --git a/docs/PROJECT-OVERVIEW.md b/docs/PROJECT-OVERVIEW.md index 6f227c29..a5442aca 100644 --- a/docs/PROJECT-OVERVIEW.md +++ b/docs/PROJECT-OVERVIEW.md @@ -19,7 +19,7 @@ aimux does not do agent loops, RAG, or orchestration — it focuses solely on un | Metric | Value | |------|------| | Rust code | 144,500+ lines | -| AI providers | 329 (251 registry-backed OpenAI-compatible + 10 native protocols + 38 standalone/local/speech/image/video) | +| AI providers | 327 (251 registry-backed OpenAI-compatible + 76 typed; counts by category in [api/providers.md](api/providers.md), generated) | | Modality traits | 8 (text/embedding/image/video/speech/transcription/reranking/search) | | Test cassettes | 2,650 recorded replays | | Test files | 118 | @@ -102,7 +102,7 @@ The comparison between aimux and the OpenAI official SDK is truly equivalent — | Trait | Capability | Example Providers | |-------|------|-------------| -| `LanguageModel` | Text generation + streaming + tool calling | OpenAI / Anthropic / Google / DeepSeek / 325 providers | +| `LanguageModel` | Text generation + streaming + tool calling | OpenAI / Anthropic / Google / DeepSeek / all providers ([providers.md](api/providers.md)) | | `EmbeddingModel` | Vector embeddings | OpenAI / Cohere / Voyage / generic-compatible | | `ImageModel` | Image generation | Black Forest Labs / Replicate / Fal / KlingAI | | `VideoModel` | Video generation | Google Veo / Replicate | @@ -113,15 +113,18 @@ The comparison between aimux and the OpenAI official SDK is truly equivalent — ### Categorization of providers -| Type | Count | Representatives | -|------|:---:|------| -| Native protocol | 11 | OpenAI, Anthropic, Google, Bedrock, Vertex, Azure, Cohere, Mistral, xAI, DeepSeek | -| OpenAI-compatible (registry) | 251 | Groq, Fireworks, Together, Perplexity, Ollama, OpenRouter, Alibaba Tongyi, Zhipu, Baidu, Tencent, iFlytek, Moonshot AI, SiliconFlow… | -| Voice/transcription | 7 | ElevenLabs, Deepgram, AssemblyAI, Cartesia… | -| Image/video | 8 | Black Forest Labs, Replicate, Fal, KlingAI… | -| Search | 11 | Tavily, Exa, Serper, Firecrawl… | +Counts by category live in [api/providers.md](api/providers.md#totals) +(generated — the single source of truth). Representatives: + +| Type | Representatives | +|------|------| +| Native protocol | OpenAI, Anthropic, Google, Bedrock, Vertex, Azure, Cohere, Mistral, xAI, Codex, OpenRouter | +| OpenAI-compatible (registry) | Groq, Fireworks, Together, Perplexity, Ollama, Alibaba Tongyi, Zhipu, Baidu, Tencent, Moonshot AI, SiliconFlow… | +| Voice/transcription | ElevenLabs, Deepgram, AssemblyAI, Cartesia… | +| Image/video | Black Forest Labs, Replicate, Fal, KlingAI… | +| Search | Tavily, Exa, Serper, Firecrawl… | -See [rfc/0004-provider-inventory.md](../rfc/0004-provider-inventory.md) for the full list. +See [api/providers.md](api/providers.md) for the full list with counts. All registry providers are accessed via the unified `provider(name, ...)` entry point — see [API.md](API.md#providers). @@ -158,10 +161,10 @@ aimux/ │ ├── ImageModel / VideoModel │ ├── SpeechModel / TranscriptionModel │ └── RerankingModel / SearchModel -├── aimux-providers # 325 provider implementations -│ ├── 11 native protocols # standalone model + convert, handles provider-specific differences -│ ├── 251 OpenAI compatible # registry-backed: provider-registry.json + provider(name, ...) entry (RFC-0017 phase 4) -│ └── modalities/search # voice / image / video / search implementations +├── aimux-providers # provider implementations (canonical counts: docs/api/providers.md) +│ ├── native protocols # standalone model + convert, handles provider-specific differences +│ ├── OpenAI compatible # registry-backed: provider-registry.json + provider(name, ...) entry (RFC-0017 phase 4) +│ └── modalities/search # voice / image / video / search implementations ├── aimux-stream # SSE / NDJSON streaming parsing ├── aimux-provider-utils # One-exchange HTTP helpers, response handlers, API-key loading ├── aimux-ffi # C ABI (FFI infrastructure, shared by all bindings) @@ -270,10 +273,10 @@ Fundamental difference: **aimux is an access layer, LangChain/Mastra is an orche Your app └── Orchestration layer (LangChain / Mastra / custom loop) └── Access layer (aimux) ← here - └── 325 AI providers + └── AI providers (count: docs/api/providers.md) ``` -aimux does not compete with LangChain; instead it serves as the layer beneath LangChain — LangChain handles the agent loop / RAG / chain, while aimux handles unified access to 325 providers. Running the access layer in Rust delivers performance and memory behavior far exceeding a JS-implemented access layer. +aimux does not compete with LangChain; instead it serves as the layer beneath LangChain — LangChain handles the agent loop / RAG / chain, while aimux handles unified access to every provider ([providers.md](api/providers.md)). Running the access layer in Rust delivers performance and memory behavior far exceeding a JS-implemented access layer. ### aimux vs rig / rust-genai @@ -405,7 +408,7 @@ Not a single trick, but systematic design choices: | Does not do | Zod validation / middleware / telemetry | Does | Extra CPU per request | | Compilation | AOT compiled to native code | JIT | Cold start + steady state | -### 3. The way to unify 325 providers +### 3. The way to unify all providers Don't write an independent model for each provider — that would explode. Use `OpenAICompatProfile` to describe the differences: @@ -420,7 +423,7 @@ let profile = OpenAICompatProfile { }; ``` -The 11 native protocols have independent models + convert (handling differences such as Anthropic message format / Google generateContent / Bedrock SigV4), while the 251 OpenAI-compatible providers are registry-backed (provider-registry.json + unified provider(name, ...) entry, RFC-0017 phase 4). +The native protocol providers have independent models + convert (handling differences such as Anthropic message format / Google generateContent / Bedrock SigV4), while the OpenAI-compatible providers are registry-backed (provider-registry.json + unified provider(name, ...) entry, RFC-0017 phase 4). ### 4. Recorded testing with 2650 cassettes @@ -436,7 +439,7 @@ aimux's type design directly targets Vercel AI SDK V4 provider types — the `Ge ### Completed -- [x] 329 providers integrated (10 native + 251 registry-backed OpenAI-compatible + voice/image/video/search) +- [x] 327 providers integrated (registry-backed OpenAI-compatible + native/modality typed; counts by category in [providers.md](api/providers.md), generated) - [x] 8 modality traits (text/embedding/image/video/speech/transcription/reranking/search) - [x] 7 language bindings (Node/Python/Swift/Kotlin/Flutter/C/Rust) - [x] 2650 cassette recorded tests diff --git a/docs/README.md b/docs/README.md index 11df171f..c490bfd2 100644 --- a/docs/README.md +++ b/docs/README.md @@ -8,7 +8,7 @@ Public documentation for aimux — a unified LLM access layer written in Rust. |-----|----------| | [API.md](API.md) | **API overview** — features, shared reference tables, factory functions, coverage matrix | | [api/reference.md](api/reference.md) | **API reference** — all public types & functions, with sources | -| [api/providers.md](api/providers.md) | **Provider list** — all 325 providers with entry points (generated) | +| [api/providers.md](api/providers.md) | **Provider list** — every provider with entry points and the canonical counts (generated) | | [api/gaps.md](api/gaps.md) | **Binding API gaps** — per-binding missing API tracking (Swift/Kotlin/Flutter multimodal, Go base_url, search factories, C ABI `_with_base`), with C ABI function mapping and reference implementations | | [api/](api/) | **Per-language guides** — Node.js, Python, Rust, Go, C/C++, Swift, Kotlin, Flutter, Java | | [error-model.md](error-model.md) | **错误模型** — 错误来源、跨语言映射、所有权与兼容性约定 | diff --git a/docs/api/providers.md b/docs/api/providers.md index 68dc55a5..5639cdd7 100644 --- a/docs/api/providers.md +++ b/docs/api/providers.md @@ -2,8 +2,33 @@ > **GENERATED** by `scripts/gen_providers_doc.py` — do not edit by hand. > Regenerate with: `python scripts/gen_providers_doc.py` - -**251 registry-backed OpenAI-compatible providers** (construct via `provider(name, ...)` / `ProviderName`) + **78 non-registry providers** (construct via the typed factories listed below). +> CI verifies with `--check`; the totals below are the one source of +> truth for provider counts (#177). + +## Totals + +| category | count | +|----------|-------| +| Registry-backed OpenAI-compatible (`provider(name, ...)` / `ProviderName`) | 251 | +| Non-registry: Native protocol providers | 13 | +| Non-registry: OpenAI-compatible thin wrappers (second batch) | 5 | +| Non-registry: Speech-only providers (TTS) | 4 | +| Non-registry: Transcription-only providers (STT) | 5 | +| Non-registry: Image-only providers | 4 | +| Non-registry: Video-only providers | 1 | +| Non-registry: Generic Responses API wrapper | 1 | +| Non-registry: Bulk-generated thin-wrapper providers | 16 | +| Non-registry: Modality-specific providers (non-language, e.g. rerank-only) | 1 | +| Non-registry: AWS Polly speech (TTS) provider — SigV4 authenticated, speech modality only | 1 | +| Non-registry: Recraft image provider (OpenAI Images-compatible + Recraft extension fields) | 1 | +| Non-registry: Stability image provider (image modality only) | 1 | +| Non-registry: Video-only provider (runwayml) | 1 | +| Non-registry: P1 thin-wrapper providers (provider-research batch) | 1 | +| Non-registry: Vertex AI MaaS partner-model providers (OpenAI-compatible thin wrappers) | 10 | +| Non-registry: Search-only providers (web search modality) | 11 | +| **Total providers** | **327** | + +**251 registry-backed OpenAI-compatible providers** (construct via `provider(name, ...)` / `ProviderName`) + **76 non-registry providers** (construct via the typed factories listed below). ## Registry-backed (OpenAI-compatible) — 251 @@ -265,12 +290,10 @@ These providers are **not** name-addressable: `provider("anthropic", ...)` fails with `NoSuchProvider`. Use the typed entry points below (Rust type names; per-binding constructors: see [reference.md](reference.md)). -### Native protocol providers +### Native protocol providers — 13 | module | typed entry points | |--------|--------------------| -| `replay` | — | -| `catalogue` | `Catalogue` | | `anthropic` | `AnthropicConfig` / `AnthropicProvider` | | `anthropic_aws` | `AnthropicAwsConfig` / `AnthropicAwsProvider` | | `azure` | `AzureConfig` / `AzureProvider` | @@ -285,7 +308,7 @@ These providers are **not** name-addressable: `provider("anthropic", ...)` fails | `openrouter` | `OpenRouterConfig` / `OpenRouterProvider` | | `xai` | `XAIConfig` / `XAIProvider` | -### OpenAI-compatible thin wrappers (second batch) +### OpenAI-compatible thin wrappers (second batch) — 5 | module | typed entry points | |--------|--------------------| @@ -295,7 +318,7 @@ These providers are **not** name-addressable: `provider("anthropic", ...)` fails | `mistralrs` | `MistralrsConfig` / `MistralrsProvider` | | `ollama` | `OllamaConfig` / `OllamaProvider` | -### Speech-only providers (TTS) +### Speech-only providers (TTS) — 4 | module | typed entry points | |--------|--------------------| @@ -304,7 +327,7 @@ These providers are **not** name-addressable: `provider("anthropic", ...)` fails | `hume` | `HumeConfig` / `HumeProvider` | | `lmnt` | `LMNTConfig` / `LMNTProvider` | -### Transcription-only providers (STT) +### Transcription-only providers (STT) — 5 | module | typed entry points | |--------|--------------------| @@ -314,7 +337,7 @@ These providers are **not** name-addressable: `provider("anthropic", ...)` fails | `gladia` | `GladiaConfig` / `GladiaProvider` | | `revai` | `RevaiConfig` / `RevaiProvider` | -### Image-only providers +### Image-only providers — 4 | module | typed entry points | |--------|--------------------| @@ -323,19 +346,19 @@ These providers are **not** name-addressable: `provider("anthropic", ...)` fails | `prodia` | `ProdiaConfig` / `ProdiaProvider` | | `replicate` | `ReplicateConfig` / `ReplicateProvider` | -### Video-only providers +### Video-only providers — 1 | module | typed entry points | |--------|--------------------| | `klingai` | `KlingAIConfig` / `KlingAIProvider` | -### Generic Responses API wrapper +### Generic Responses API wrapper — 1 | module | typed entry points | |--------|--------------------| | `open_responses` | `OpenResponsesConfig` / `OpenResponsesProvider` | -### Bulk-generated thin-wrapper providers +### Bulk-generated thin-wrapper providers — 16 | module | typed entry points | |--------|--------------------| @@ -356,43 +379,43 @@ These providers are **not** name-addressable: `provider("anthropic", ...)` fails | `vllm` | `VllmConfig` / `VllmProvider` | | `xinference` | `XinferenceConfig` / `XinferenceProvider` | -### Modality-specific providers (non-language, e.g. rerank-only) +### Modality-specific providers (non-language, e.g. rerank-only) — 1 | module | typed entry points | |--------|--------------------| | `jina_ai` | `JinaAiConfig` / `JinaAiProvider` | -### AWS Polly speech (TTS) provider — SigV4 authenticated, speech modality only +### AWS Polly speech (TTS) provider — SigV4 authenticated, speech modality only — 1 | module | typed entry points | |--------|--------------------| | `aws_polly` | `AwsPollyConfig` / `AwsPollyProvider` | -### Recraft image provider (OpenAI Images-compatible + Recraft extension fields) +### Recraft image provider (OpenAI Images-compatible + Recraft extension fields) — 1 | module | typed entry points | |--------|--------------------| | `recraft` | `RecraftConfig` / `RecraftProvider` | -### Stability image provider (image modality only) +### Stability image provider (image modality only) — 1 | module | typed entry points | |--------|--------------------| | `stability` | `StabilityConfig` / `StabilityProvider` | -### Video-only provider (runwayml) +### Video-only provider (runwayml) — 1 | module | typed entry points | |--------|--------------------| | `runwayml` | `RunwaymlConfig` / `RunwaymlProvider` | -### P1 thin-wrapper providers (provider-research batch) +### P1 thin-wrapper providers (provider-research batch) — 1 | module | typed entry points | |--------|--------------------| | `bedrock_mantle` | `BedrockMantleConfig` / `BedrockMantleProvider` | -### Vertex AI MaaS partner-model providers (OpenAI-compatible thin wrappers). Each wraps the shared OpenAIProvider against the Vertex AI MaaS OpenAPI endpoint, authenticating with a Google Cloud Bearer token +### Vertex AI MaaS partner-model providers (OpenAI-compatible thin wrappers). Each wraps the shared OpenAIProvider against the Vertex AI MaaS OpenAPI endpoint, authenticating with a Google Cloud Bearer token — 10 | module | typed entry points | |--------|--------------------| @@ -407,7 +430,7 @@ These providers are **not** name-addressable: `provider("anthropic", ...)` fails | `vertex_ai_qwen_models` | `VertexAiQwenModelsConfig` / `VertexAiQwenModelsProvider` | | `vertex_ai_zai_models` | `VertexAiZaiModelsConfig` / `VertexAiZaiModelsProvider` | -### Search-only providers (web search modality) +### Search-only providers (web search modality) — 11 | module | typed entry points | |--------|--------------------| @@ -422,3 +445,4 @@ These providers are **not** name-addressable: `provider("anthropic", ...)` fails | `tavily` | `TavilyConfig` / `TavilyProvider` | | `tinyfish` | `TinyfishConfig` / `TinyfishProvider` | | `you_com` | `YouComConfig` / `YouComProvider` | + diff --git a/docs/api/reference.md b/docs/api/reference.md index ab1b5e71..3e4133a3 100644 --- a/docs/api/reference.md +++ b/docs/api/reference.md @@ -16,7 +16,7 @@ | `ReasoningEffort` | [types.rs](../../aimux-core/src/types.rs) | [ReasoningEffort.ts](../../bindings/node/src/types/ReasoningEffort.ts) | 7 levels, passed through verbatim | | `AbortSignal` | [abort_signal.rs](../../aimux-core/src/abort_signal.rs) | — (runtime handle) | Rust: options field; Node: `AbortBridge` + JS `AbortSignal` | | `ProviderOptions` | [provider.rs](../../aimux-providers/src/provider.rs) | `ProviderConfig` (per binding) | `base_url` / `headers` / `organization` / `project` / `max_retries` / `body_overrides` | -| `ProviderName` | [provider_name.rs](../../aimux-providers/src/provider_name.rs) | [ProviderName.ts](../../bindings/node/src/types/ProviderName.ts) | one constant per registry provider (251); also generated for Go / Python / Java / Kotlin / Swift / Dart (`scripts/gen_provider_names.py`) | +| `ProviderName` | [provider_name.rs](../../aimux-providers/src/provider_name.rs) | [ProviderName.ts](../../bindings/node/src/types/ProviderName.ts) | one constant per registry provider (count: [providers.md](providers.md)); also generated for Go / Python / Java / Kotlin / Swift / Dart (`scripts/gen_provider_names.py`) | | `GenerateTextResult` | [generate.rs](../../aimux-core/src/generate.rs) | [GenerateTextResult.ts](../../bindings/node/src/types/GenerateTextResult.ts) | `text`, `tool_calls`, `usage`, `warnings`, `raw` | | `StreamTextResult` | [generate.rs](../../aimux-core/src/generate.rs) | — | use `StreamPart` while iterating | | `StreamPart` | [stream_part.rs](../../aimux-core/src/stream_part.rs) | [StreamPart.ts](../../bindings/node/src/types/StreamPart.ts) | TextDelta / ToolCallDelta / Finish / … | @@ -86,7 +86,7 @@ Per-modality entry points are trait methods on the model types |----------|-------| | `generateText(model, prompt, options?, signal?)` / `streamText(...)` | typed wrappers; `signal` = `AbortSignal` | | `openai()` / `anthropic()` / `deepseek()` / `google()` | native typed path; `deepseek()` is registry-backed | -| `provider(name, apiKey?, modelId, config?)` | registry-backed (251); typed `ProviderName` | +| `provider(name, apiKey?, modelId, config?)` | registry-backed (rows in [providers.md](providers.md)); typed `ProviderName` | | `openaiEmbedding` / `cohereEmbedding` / `googleEmbedding` | embeddings | | `openaiSpeech` / `openaiTranscription` | speech / STT | | `openaiImage` / `googleImage` / `googleVideo` | image / video | @@ -133,8 +133,9 @@ model constructors (`openai_new`, `anthropic_new`, …), `*_generate`, ## Providers -The complete list — 251 registry-backed (name / display / env var / base URL) -and 76 typed-factory providers grouped by category — is in +The complete list — registry-backed (name / display / env var / base URL) +and typed-factory providers grouped by category, with the canonical +counts — is in [providers.md](providers.md). It is generated from `aimux-providers/src/provider_registry.json` + `aimux-providers/src/lib.rs`: `python scripts/gen_providers_doc.py`. diff --git a/docs/contributing/adding-a-provider.md b/docs/contributing/adding-a-provider.md new file mode 100644 index 00000000..da09fb1e --- /dev/null +++ b/docs/contributing/adding-a-provider.md @@ -0,0 +1,146 @@ +# Adding a provider + +Three cases cover every provider in aimux today. Find yours, follow the +checklist, and CI verifies the generated parts automatically. + +Related: [CONTRIBUTING.md](../../CONTRIBUTING.md) (dev setup), +[RFC-0017](../../rfc/0017-provider-config-dx.md) (the registry), +[RFC-0003](../../rfc/0003-test-cassette.md) (cassettes), +[#166](https://github.com/arcships/aimux/issues/166) (the roadmap that will +simplify some steps below — noted per step). + +## Which case am I in? + +| Case | The vendor speaks… | You add | Example | +|---|---|---|---| +| 1. OpenAI-compatible | OpenAI Chat Completions wire format, own base URL | one registry row + derived cassettes | `deepinfra` | +| 2. A new protocol | its own HTTP API for text generation | a `src//` module implementing `do_generate` / `do_stream` | `cohere` | +| 3. Single modality | any API, but only one non-text modality | a typed shell + one modality model | `serper` (search), `lmnt` (speech) | + +If the vendor is OpenAI-compatible **and** needs quirks beyond a `profile` +flag (different auth header, non-standard error body, …), it is case 2, not +case 1. + +## Case 1 — OpenAI-compatible vendor (registry row) + +1. Add one row to `aimux-providers/src/provider_registry.json` (file is + sorted by `name`; follow the flat 5-key shape): + + ```json + { "name": "example", "display": "Example", "env_var": "EXAMPLE_API_KEY", + "base_url": "https://api.example.com/v1", "profile": {} } + ``` + + `profile` carries the known quirks — `supports_top_k`, `supports_tools`, + `supports_response_format`, `stream_usage_key`, `max_tokens_key` — see + the rows that already use one (e.g. `groq`). Anything beyond body-shape + flags rides in per-call `body_overrides` (RFC-0017); if the quirk changes + auth or error shapes, it is case 2. + +2. Run both generators and commit their output: + + ```sh + python scripts/gen_provider_names.py # ProviderName in 8 languages + python scripts/gen_providers_doc.py # docs/api/providers.md (totals + list) + ``` + + CI runs both with `--check` in the `contract-tests` job and fails if the + committed output is stale. + +3. Derive replay cassettes: add a tuple to `PROVIDERS` in + `scripts/generate_thin_wrapper_cassettes.py`, then run it. It derives + `thin_wrapper_nonstream.json` / `thin_wrapper_stream.json` under + `aimux-providers/tests/cassettes//` from **real OpenAI recordings** + (rewriting request path and model id — not fake data), because a + registry-backed provider's requests are byte-for-byte OpenAI shape. + +4. Nothing else: `provider("example", ...)` now works in every binding, and + env-var key loading follows `env_var` automatically. + +> Roadmap note: #166 B2 turns the 33 standalone thin wrappers into registry +> rows and makes `conformance_test.rs` iterate the registry; the +> cassette-derivation step above is then replaced by that suite. + +## Case 2 — a new protocol + +1. New directory `aimux-providers/src//`: + + - `model.rs` — `Provider` implementing `LanguageModel`: + `do_generate` / `do_stream` (plus other modality traits if the vendor + offers them: embeddings, image, transcription…); + - `convert.rs` — unified message format ⇄ vendor wire format, both + directions, including the error-body shape; + - `types.rs` — vendor request/response structs (`#[serde(default)]` on + optional fields). + + Reference implementations, smallest to largest: `mistral/`, `cohere/`, + `openai/`, `anthropic/`, `google/`, `bedrock/` (event stream + SigV4). + +2. Wire it up in `aimux-providers/src/lib.rs` **under the right section + comment** — the comment is what `gen_providers_doc.py` reads as the + category (and the totals table): + + ```rust + pub mod ; + pub use ::{Config, Provider}; + ``` + +3. Record real cassettes under `tests/cassettes//` (RFC-0003; no + network, no keys at test time) and add a `_conformance` module to + `tests/conformance_test.rs` — `do_generate_returns_text` and + `do_stream_returns_parts` against those cassettes, mirroring the existing + 20 modules. + +4. Note: protocol providers are **not** name-addressable today — callers use + the typed factories (`CohereProvider::new(...)`), and + `provider("cohere", ...)` fails with `NoSuchProvider`. A registry row with + a `protocol` field arrives with #166 B1 (the `Protocol` enum + + `from_resolved`); until then, do not add protocol providers to the + registry JSON. + +## Case 3 — single-modality vendor + +1. One file `aimux-providers/src/.rs` (see `serper.rs` for search, + `lmnt.rs` for speech; `cartesia/` for a vendor with two modalities): + + - `Config { api_key, base_url }` with `new` / `with_base_url` / + `from_env`; + - `Provider` — a shell whose only job is returning the modality + model (`search_model()`, `speech_model()`, `image_model()`, …) and + reporting its `name()`; + - the modality model implementing the trait's operation + (`SearchModel::do_search`, `SpeechModel::do_speak`, …) as: build the + request body → send through the provider-utils HTTP helpers → map the + response into aimux types. Implement **only** the transform functions; + transport, retry and timeouts are not your problem (Core owns them). + +2. `pub mod` + `pub use` in `lib.rs` under the modality's section comment + (search-only, speech-only, image-only, video-only, …). + +3. Cassettes + unit tests asserting the mapped result shapes. + +## Generator and script rules + +- **A generator may stay in `scripts/` only if its output carries a + "GENERATED — do not edit" header and CI runs it with `--check`.** Today + that is `gen_provider_names.py` and `gen_providers_doc.py` (the + `contract-tests` job). `gen_ts_types.py` regenerates the ts-rs TypeScript + types through `cargo test -p aimux-core --lib export` (the export tests + are the gate). +- `docs/api/providers.md` is the single source of truth for provider counts + (#177). Never hand-write a provider count anywhere else — link to the + page (the README states the total once, with a date, next to that link). +- One-off scripts carry their retirement condition in the roadmap (#166): + `generate_thin_wrapper_cassettes.py` is deleted once conformance tests + iterate the registry (A1/B2); `convert_cassettes.py`, + `convert_pydantic_ai.py`, `extract_litellm_bases.py`, + `scan_litellm_urls.py` are import/audit one-offs that can be deleted after + their data lands; `responses_similarity_audit.py` served RFC-0012 §3.5. + +## Before opening the PR + +- [ ] Generators run, output committed (CI `--check` green). +- [ ] Cassettes committed; `cargo test -p aimux-providers` passes offline. +- [ ] Case 2: conformance module added. Case 3: unit tests for the mapped shapes. +- [ ] `docs/api/providers.md` regenerated — the totals table reflects your addition. +- [ ] No provider-count literals introduced anywhere else. diff --git a/rfc/0002-provider-improvements.md b/rfc/0002-provider-improvements.md index 8898a8ab..defb75dc 100644 --- a/rfc/0002-provider-improvements.md +++ b/rfc/0002-provider-improvements.md @@ -1,5 +1,7 @@ # Provider Adapter Layer Improvements +> **Status**: IMPLEMENTED (2026-07-28 — OpenAICompatProfile 差异描述 + 145 个 thin wrapper 集成,见下方 Implementation Progress;"配置即数据"的后续演进为 [RFC-0017](0017-provider-config-dx.md) 的 provider registry;cassette 测试独立为 [RFC-0003](0003-test-cassette.md)) + ## Current Problems Thin-wrapper providers differ only in URL and environment variables, with no customization points. DeepSeek's reasoning field is dropped directly, as the code comments themselves admit. diff --git a/rfc/0003-test-cassette.md b/rfc/0003-test-cassette.md index afc7acd4..e094f84b 100644 --- a/rfc/0003-test-cassette.md +++ b/rfc/0003-test-cassette.md @@ -1,5 +1,7 @@ # Test cassette proposal +> **Status**: IMPLEMENTED (in force — cassette 回放是 provider 测试的标准路径,`aimux-providers/tests/cassettes/` 持续扩充;录制/回放机制的补全跟踪见 #167) + ## Goal Tests do not depend on the network or keys. When running tests, replay cassette files instead of making real calls to provider APIs. diff --git a/rfc/0004-provider-inventory.md b/rfc/0004-provider-inventory.md index 420cf2d6..79dce644 100644 --- a/rfc/0004-provider-inventory.md +++ b/rfc/0004-provider-inventory.md @@ -1,5 +1,7 @@ # Provider Inventory and Implementation Status +> **Status**: SUPERSEDED (2026-07-28 快照,数字不再维护 — 现行事实来源为 [provider_registry.json](../aimux-providers/src/provider_registry.json)([RFC-0017](0017-provider-config-dx.md));与 models.dev 的对账跟踪见 #171) + ## 1. Providers Currently Implemented in aimux **A total of 172 provider modules** (as of 2026-07-28). diff --git a/rfc/0005-protocol-conversion.md b/rfc/0005-protocol-conversion.md index f9df36b3..efdf477f 100644 --- a/rfc/0005-protocol-conversion.md +++ b/rfc/0005-protocol-conversion.md @@ -1,5 +1,7 @@ # Protocol Conversion and Adapter Layer Design +> **Status**: ACCEPTED (调研定案 2026-07-28 — 不做跨协议转换,那是网关的职责;aimux 只做"统一接口调用各 provider"的 SDK。适配层差异表达的落地路线为 [RFC-0002](0002-provider-improvements.md) 的配置描述结构,后由 [RFC-0017](0017-provider-config-dx.md) registry 延续) + > Scanned 104 projects under reference/, recording each project's protocol-conversion logic and provider adapter layer design. > The focus is not the provider list (see [0004-provider-inventory.md](0004-provider-inventory.md)), but **how to unify different providers' protocols**. diff --git a/rfc/0005-rename-to-aimux.md b/rfc/0005-rename-to-aimux.md index 43e9fa8d..670dec0e 100644 --- a/rfc/0005-rename-to-aimux.md +++ b/rfc/0005-rename-to-aimux.md @@ -1,6 +1,6 @@ # RFC 0005: Rename to aimux -> **Status**: Decided plan, pending execution +> **Status**: EXECUTED (2026-08 完成 — 仓库 / crates / FFI 符号均为 aimux,无旧名残留;本文正文亦随之批量替换,故部分前后文读作同义反复) > **Decision**: Error type `AiMuxError` · repository name `aimux` · brand positioning "inspired by Vercel AI SDK" · script batch execution + verification ## 1. Background and Motivation diff --git a/rfc/0009-request-resilience.md b/rfc/0009-request-resilience.md index 1617f7fa..ba36bb0e 100644 --- a/rfc/0009-request-resilience.md +++ b/rfc/0009-request-resilience.md @@ -5,7 +5,7 @@ > **Scope**: `aimux-provider-utils` references three specific design points from catcher (connection-pool config, jitter backoff, fixed timeouts) and implements request-layer optimization using reqwest natively + the existing retry.rs, without introducing a catcher-http dependency > **Related**: [RFC-0002](0002-provider-improvements.md) provider adapter layer improvements, [RFC-0003](0003-test-cassette.md) test cassette plan > -> **Superseded in part by [RFC-0031](0031-ai-sdk-request-pipeline.md)**: +> **Superseded in part by [RFC-0031](../docs/ai-sdk-request-pipeline.md)**: > shared client/pool/connect-timeout remain, but HTTP-layer retry and fixed > whole-response timeout no longer describe the current architecture. Retry > and operation deadlines now belong to Core; provider-utils performs one diff --git a/rfc/0016-align-with-aisdk.md b/rfc/0016-align-with-aisdk.md index 2f72a554..489598ee 100644 --- a/rfc/0016-align-with-aisdk.md +++ b/rfc/0016-align-with-aisdk.md @@ -1,12 +1,12 @@ # RFC-0016: 对齐 Vercel AI SDK 能力缺口 -> **Status**: DRAFT (pending review) — 实施状态与下游 wrapper 影响见 [§7](#7-实施状态截至-2026-08-02)(2026-08-02 更新) +> **Status**: IMPLEMENTED (2026-09-24 核定) — §2 缺口绝大多数落地:H1/H2/H3、M1-M7、M9-M12 经 #99(2026-08-13,M6 proxy/M7 聚合/M11 流聚合/M12 generateObject)、#164(2026-09-12,请求管线对齐:重试分层/错误域/超时)、#165(工具输入解析边界)落地;M13 经核不做、H4 明确不做;工具调用修复由 [RFC-0035](0035-host-side-tool-call-repair.md) 扩展。未落地余项(M8 StreamPart 变体等)逐条追踪见 [§7.2](#72-未落地清单逐条追踪) > **Date**: 2026-08-01 > **Scope**: 系统对比 aimux 0.1.2 与 Vercel AI SDK (`@ai-sdk/openai` / `@ai-sdk/openai-compatible` / `@ai-sdk/provider` V4) 的接口与实现,识别能力缺口,并按优先级规划补齐路径 > **Related**: [RFC-0009](0009-request-resilience.md) request resilience(retry/timeout,本 RFC 的 abortSignal/timeout 缺口与之相关),[RFC-0014](0014-logging.md) 统一日志体系(可观测性缺口依赖本 RFC 的 span 树) > > **Request-pipeline sections are superseded by -> [RFC-0031](0031-ai-sdk-request-pipeline.md)**: retry/timeout ownership moved +> [RFC-0031](../docs/ai-sdk-request-pipeline.md)**: retry/timeout ownership moved > to Core, provider-utils now exposes one-exchange API helpers plus response > handlers, and the old `send_timed` / `send_stream_timed` implementation was > removed. RFC-0016 M3's first-SSE-event peek is intentionally retained as @@ -190,12 +190,12 @@ aimux 的 `OpenAIResponsesModel`([responses/mod.rs](../aimux-providers/src/opena > 逐项用户影响、范围修正与实施排序见 [§7.8](#78-中优先级缺口复审2026-08-02双-review-后逐项核定)(2026-08-02 核定:M6 缩窄为 proxy、M7 实锤差距、M11/M12 澄清、M13 拆分) - ~~**M2** includeRawChunks~~ — 已落地,见 [§7.1](#71-已落地相对本-rfc-起草时)(2026-08-05) - ~~**M3** logprobs 请求~~ — 已落地,见 [§7.1](#71-已落地相对本-rfc-起草时)(2026-08-05) -- **M6** 自定义 fetch / proxy — 仍是进程级共享 reqwest client -- **M7** 顶层结果聚合 — `GenerateTextResult` 仅 text/tool_calls([generate.rs:96](../aimux-core/src/generate.rs#L96));reasoning/files/sources 在 `raw.content`,无 `responseMessages` +- ~~**M6** 自定义 fetch / proxy~~ — 已落地(缩窄形态:proxy 配置):`aimux_init_proxy` / `ProxyConfig`(FFI + bindings 透传),env 代理始终自动生效;自定义 fetch 经核不做(§7.8 范围修正)(#99,2026-08-13) +- ~~**M7** 顶层结果聚合~~ — 已落地:`GenerateTextResult` / `StreamTextResultAggregated` 顶层携带 `reasoning`/`reasoning_text`/`sources`/`files`/`response_messages`([result.rs](../aimux-core/src/result.rs))(#99,2026-08-13) - **M8** 缺 variant — `tool-approval-request`/`custom`/`reasoning-file` 均无;`source` 无 document 子类型([stream_part.rs:156](../aimux-core/src/stream_part.rs#L156)) - ~~**M10** usage.raw~~ — 已落地,见 [§7.1](#71-已落地相对本-rfc-起草时)(2026-08-05) -- **M11** streamText 聚合器 — Node 仍 yield 原始 StreamPart JSON([lib.rs:63](../bindings/node/src/lib.rs#L63));core 仅 `StreamTextResult::text()` -- **M12** 结构化 output / generateObject — 无 +- ~~**M11** streamText 聚合器~~ — 已落地:`consume_stream_text` → `StreamTextResultAggregated`(FFI + bindings)(#99,2026-08-13) +- ~~**M12** 结构化 output / generateObject~~ — 已落地:`generate_object` + `GenerateObjectResult`(core/FFI/bindings)(#99,2026-08-13) - ~~**M13** 生命周期 callbacks~~ — **经核对不做**(2026-08-14)。onStart/onEnd 为编排层语法糖,用户外包调用(非流式)或 `consume()`(流式,§7.1)零成本等价。AI SDK 该设计服务于其多步循环 + telemetry 生态(参见 `GenerateTextStartEvent` 携带 `activeTools`/`toolOrder`/`steps`、与 OTel dispatcher 绑定);前者 aimux 不做(H4 §7.5),后者已有 RFC-0014 `tracing` span 覆盖。做它需 FFI 宿主回调 wire 改动 + 8 语言 wrapper(类别②最高成本),收益无新能力。 **低优先级(2026-08-14 核对 AI SDK 源码后逐项核定):** diff --git a/rfc/0020-external-provider-config.md b/rfc/0020-external-provider-config.md index 29f2bff5..a0a7819b 100644 --- a/rfc/0020-external-provider-config.md +++ b/rfc/0020-external-provider-config.md @@ -1,6 +1,6 @@ # RFC-0020: 外部 Provider 配置 -> **Status**: DRAFT (pending review) +> **Status**: IMPLEMENTED (运行时 overlay 落地于 #97(2026-08-12)— `provider::register_provider` 运行时注册/覆盖条目,查找顺序 overlay → 内置 registry;FFI `aimux_register_providers` 及 Node/Python 透传) > **Date**: 2026-08-05 > **Scope**: `aimux-providers` 新增运行时覆盖层,允许外部(配置文件 / 编程式 API)注册、覆盖 OpenAI 兼容 provider 条目;各 binding 薄透传 > **Related**: [RFC-0017](0017-provider-config-dx.md) 配置 DX(本 RFC 落地其 §363 预留的"用户覆盖内置条目"语义)、[RFC-0019](0019-session-affinity.md) 会话亲和、[调研报告](../docs/external-provider-config-research.md) diff --git a/rfc/0021-composite-model-routing.md b/rfc/0021-composite-model-routing.md index b83b9275..7218810c 100644 --- a/rfc/0021-composite-model-routing.md +++ b/rfc/0021-composite-model-routing.md @@ -1,6 +1,6 @@ # RFC-0021: Composite Model 与 Model 路由 -> **Status**: DRAFT (pending review) +> **Status**: IMPLEMENTED (RouterModel + composite 骨架落地于 #100(2026-08-14);后续随 #164 请求管线对齐更新) > **Date**: 2026-08-05 > **Scope**: `aimux-core` 新增 composite model 基础设施(实现 `LanguageModel` trait 的组合模型)+ `RouterModel`(规则路由 / fallback / 可插拔策略)+ 内置策略(RuleRouter / LLM 分类器 / 视觉分流) > **Related**: [RFC-0022](0022-moa-single-fanout.md) MoA(共用 composite 骨架)、[RFC-0016](0016-align-with-aisdk.md) AISDK 对齐、[RFC-0005](0005-protocol-conversion.md) 定位边界 diff --git a/rfc/0022-moa-single-fanout.md b/rfc/0022-moa-single-fanout.md index 37a6c091..226e28c6 100644 --- a/rfc/0022-moa-single-fanout.md +++ b/rfc/0022-moa-single-fanout.md @@ -1,6 +1,6 @@ # RFC-0022: MoA 单次扇出聚合 -> **Status**: DRAFT (pending review) +> **Status**: IMPLEMENTED (MoaModel 落地于 #100(2026-08-14),与 RFC-0021 共用 composite 骨架;后续随 #164 请求管线对齐更新) > **Date**: 2026-08-05 > **Scope**: `aimux-core` 新增 `MoaModel`——实现 `LanguageModel` trait 的 Mixture-of-Agents 单次扇出聚合模型(reference models 并行跑 → 输出拼进 aggregator prompt → aggregator 跑 → 返回),不含 agent loop > **Related**: [RFC-0021](0021-composite-model-routing.md) Composite Model 骨架(共用)、[RFC-0016](0016-align-with-aisdk.md) H4 边界(不做多步循环) diff --git a/rfc/0027-list-models-coverage.md b/rfc/0027-list-models-coverage.md index 01c93754..664fe6bb 100644 --- a/rfc/0027-list-models-coverage.md +++ b/rfc/0027-list-models-coverage.md @@ -1,5 +1,6 @@ # RFC-0027 list_models 覆盖跟踪 +> **Status**: IMPLEMENTED (真 LLM provider 292/292 覆盖,基线 2026-08-06;本页为基线快照,新增 provider 的覆盖随 registry 演进,不再逐家更新) > 目标:覆盖全部**真 LLM provider**(有 chat/completions 能力、语义上有"模型列表"的 provider)。 > modality-only(speech/image/embed/search)不在范围,保留 trait 默认 `Unsupported`。 > diff --git a/scripts/gen_providers_doc.py b/scripts/gen_providers_doc.py index c61c4112..ad4db61b 100644 --- a/scripts/gen_providers_doc.py +++ b/scripts/gen_providers_doc.py @@ -5,14 +5,24 @@ - aimux-providers/src/provider_registry.json (registry of OpenAI-compatible entries) - aimux-providers/src/lib.rs (non-registry modules + categories) +A `pub mod` in lib.rs counts as a provider module iff its `pub use` re-exports +a typed surface (`*Provider` / `*Config` / `*Model`). Registry machinery +(`provider`, `provider_name`), `replay` and `catalogue` export none of these +and are excluded without a hand-kept list. + +The generated page carries the provider totals (registry rows, per-category +typed providers, grand total); other docs reference it instead of repeating +numbers (#177). + Usage: - python scripts/gen_providers_doc.py - # writes docs/api/providers.md, prints the module count for verification + python scripts/gen_providers_doc.py # writes docs/api/providers.md + python scripts/gen_providers_doc.py --check # exit 1 if the page is stale """ import json import pathlib import re +import sys ROOT = pathlib.Path(__file__).resolve().parent.parent REGISTRY = ROOT / "aimux-providers" / "src" / "provider_registry.json" @@ -117,6 +127,17 @@ def module_exports(module): return exports +def is_provider_module(module): + """True when the module re-exports a typed provider surface. + + A provider module exposes `*Provider` / `*Config` / `*Model` types. + Registry machinery (`provider`, `provider_name`), `replay` and + `catalogue` re-export none of these, so they are excluded without a + hand-kept deny-list (#177). + """ + return any(e.endswith(("Provider", "Config", "Model")) for e in module_exports(module)) + + def display_from_exports(exports): """Derive a human name: AnthropicProvider / AnthropicConfig -> Anthropic.""" for prefix in ("ProviderConfig", "Provider", "Config", "Model"): @@ -126,29 +147,52 @@ def display_from_exports(exports): return None -def main(): +def build_page(): + """Return (page_text, totals) without touching the filesystem.""" registry = load_registry() - sections = parse_lib_rs() + sections = [ + (title, [m for m in mods if is_provider_module(m)]) + for title, mods in parse_lib_rs() + ] + sections = [(title, mods) for title, mods in sections if mods] registry_names = {e["name"] for e in registry} - lines = [] - w = lines.append - # Overlap sanity check: a module name should not also be a registry entry. for _, mods in sections: for m in mods: if m in registry_names: print(f"WARNING: module `{m}` also exists in provider_registry.json") + non_registry = sum(len(mods) for _, mods in sections) + total = len(registry) + non_registry + + lines = [] + w = lines.append + w("# aimux providers") w("") w("> **GENERATED** by `scripts/gen_providers_doc.py` — do not edit by hand.") w("> Regenerate with: `python scripts/gen_providers_doc.py`") + w("> CI verifies with `--check`; the totals below are the one source of") + w("> truth for provider counts (#177).") + w("") + w("## Totals") + w("") + w("| category | count |") + w("|----------|-------|") + w(f"| Registry-backed OpenAI-compatible (`provider(name, ...)` / `ProviderName`) | {len(registry)} |") + for title, mods in sections: + # First sentence only — some lib.rs section comments run on; the full + # title stays on the section heading below. The lookahead keeps + # abbreviations such as "e.g." from ending the "sentence". + short = re.split(r"(?<=[.!?])\s(?=[A-Z0-9])", title, maxsplit=1)[0].rstrip(".") + w(f"| Non-registry: {short} | {len(mods)} |") + w(f"| **Total providers** | **{total}** |") w("") w( f"**{len(registry)} registry-backed OpenAI-compatible providers** " f"(construct via `provider(name, ...)` / `ProviderName`) + " - f"**{sum(len(m) for _, m in sections) - 2} non-registry providers** " + f"**{non_registry} non-registry providers** " "(construct via the typed factories listed below)." ) w("") @@ -169,10 +213,7 @@ def main(): ) w("") for title, mods in sections: - mods = [m for m in mods if m not in ("provider", "provider_name")] - if not mods: - continue - w(f"### {title}") + w(f"### {title} — {len(mods)}") w("") w("| module | typed entry points |") w("|--------|--------------------|") @@ -186,13 +227,42 @@ def main(): w(f"| `{mod}` | {cols} |") w("") - out = "\n".join(lines) - OUT.write_text(out, encoding="utf-8") + page = "\n".join(lines) + "\n" + totals = { + "registry": len(registry), + "non_registry": non_registry, + "total": total, + } + return page, totals - total = len(registry) + sum(len(m) for _, m in sections) - 2 + +def main() -> int: + check = "--check" in sys.argv[1:] + page, totals = build_page() + if check: + on_disk = OUT.read_text(encoding="utf-8") if OUT.is_file() else None + if on_disk != page: + print( + f"STALE: {OUT.relative_to(ROOT)} — run " + "scripts/gen_providers_doc.py and commit the result", + file=sys.stderr, + ) + return 1 + print( + f"{OUT.relative_to(ROOT)} up to date " + f"(registry={totals['registry']} non-registry={totals['non_registry']} " + f"total={totals['total']})" + ) + return 0 + OUT.write_text(page, encoding="utf-8") print(f"wrote {OUT}") - print(f"registry={len(registry)} non-registry modules={sum(len(m) for _, m in sections) - 2} total={total}") + print( + f"registry={totals['registry']} " + f"non-registry modules={totals['non_registry']} " + f"total={totals['total']}" + ) + return 0 if __name__ == "__main__": - main() + sys.exit(main())