Metadata-Version: 2.4
Name: arvel-ai
Version: 0.2.0
Summary: One stable, typed, async API over many AI providers — plus an MCP server — for the arvel framework.
License-Expression: MIT
License-File: LICENSE
Keywords: ai,any-llm,arvel,gateway,llm,mcp,openai
Requires-Python: >=3.14
Requires-Dist: arvel
Requires-Dist: msgspec
Provides-Extra: all
Requires-Dist: any-llm-sdk[all]>=1.21; extra == 'all'
Provides-Extra: anthropic
Requires-Dist: any-llm-sdk[anthropic]>=1.21; extra == 'anthropic'
Provides-Extra: any-llm
Requires-Dist: any-llm-sdk>=1.21; extra == 'any-llm'
Provides-Extra: atlascloud
Requires-Dist: any-llm-sdk[atlascloud]>=1.21; extra == 'atlascloud'
Provides-Extra: azure
Requires-Dist: any-llm-sdk[azure]>=1.21; extra == 'azure'
Provides-Extra: azureanthropic
Requires-Dist: any-llm-sdk[azureanthropic]>=1.21; extra == 'azureanthropic'
Provides-Extra: azureopenai
Requires-Dist: any-llm-sdk[azureopenai]>=1.21; extra == 'azureopenai'
Provides-Extra: bedrock
Requires-Dist: any-llm-sdk[bedrock]>=1.21; extra == 'bedrock'
Provides-Extra: cascadia
Requires-Dist: any-llm-sdk[cascadia]>=1.21; extra == 'cascadia'
Provides-Extra: cerebras
Requires-Dist: any-llm-sdk[cerebras]>=1.21; extra == 'cerebras'
Provides-Extra: cohere
Requires-Dist: any-llm-sdk[cohere]>=1.21; extra == 'cohere'
Provides-Extra: dashscope
Requires-Dist: any-llm-sdk[dashscope]>=1.21; extra == 'dashscope'
Provides-Extra: databricks
Requires-Dist: any-llm-sdk[databricks]>=1.21; extra == 'databricks'
Provides-Extra: deepinfra
Requires-Dist: any-llm-sdk[deepinfra]>=1.21; extra == 'deepinfra'
Provides-Extra: deepseek
Requires-Dist: any-llm-sdk[deepseek]>=1.21; extra == 'deepseek'
Provides-Extra: fireworks
Requires-Dist: any-llm-sdk[fireworks]>=1.21; extra == 'fireworks'
Provides-Extra: gemini
Requires-Dist: any-llm-sdk[gemini]>=1.21; extra == 'gemini'
Provides-Extra: github
Requires-Dist: any-llm-sdk[github]>=1.21; extra == 'github'
Provides-Extra: gmi
Requires-Dist: any-llm-sdk[gmi]>=1.21; extra == 'gmi'
Provides-Extra: groq
Requires-Dist: any-llm-sdk[groq]>=1.21; extra == 'groq'
Provides-Extra: huggingface
Requires-Dist: any-llm-sdk[huggingface]>=1.21; extra == 'huggingface'
Provides-Extra: inception
Requires-Dist: any-llm-sdk[inception]>=1.21; extra == 'inception'
Provides-Extra: llama
Requires-Dist: any-llm-sdk[llama]>=1.21; extra == 'llama'
Provides-Extra: llamacpp
Requires-Dist: any-llm-sdk[llamacpp]>=1.21; extra == 'llamacpp'
Provides-Extra: llamafile
Requires-Dist: any-llm-sdk[llamafile]>=1.21; extra == 'llamafile'
Provides-Extra: lmstudio
Requires-Dist: any-llm-sdk[lmstudio]>=1.21; extra == 'lmstudio'
Provides-Extra: minimax
Requires-Dist: any-llm-sdk[minimax]>=1.21; extra == 'minimax'
Provides-Extra: mistral
Requires-Dist: any-llm-sdk[mistral]>=1.21; extra == 'mistral'
Provides-Extra: moonshot
Requires-Dist: any-llm-sdk[moonshot]>=1.21; extra == 'moonshot'
Provides-Extra: mzai
Requires-Dist: any-llm-sdk[mzai]>=1.21; extra == 'mzai'
Provides-Extra: nebius
Requires-Dist: any-llm-sdk[nebius]>=1.21; extra == 'nebius'
Provides-Extra: neosantara
Requires-Dist: any-llm-sdk[neosantara]>=1.21; extra == 'neosantara'
Provides-Extra: ollama
Requires-Dist: any-llm-sdk[ollama]>=1.21; extra == 'ollama'
Provides-Extra: openai
Requires-Dist: any-llm-sdk[openai]>=1.21; extra == 'openai'
Provides-Extra: openrouter
Requires-Dist: any-llm-sdk[openrouter]>=1.21; extra == 'openrouter'
Provides-Extra: otari
Requires-Dist: any-llm-sdk[otari]>=1.21; extra == 'otari'
Provides-Extra: perplexity
Requires-Dist: any-llm-sdk[perplexity]>=1.21; extra == 'perplexity'
Provides-Extra: portkey
Requires-Dist: any-llm-sdk[portkey]>=1.21; extra == 'portkey'
Provides-Extra: qiniu
Requires-Dist: any-llm-sdk[qiniu]>=1.21; extra == 'qiniu'
Provides-Extra: requesty
Requires-Dist: any-llm-sdk[requesty]>=1.21; extra == 'requesty'
Provides-Extra: sagemaker
Requires-Dist: any-llm-sdk[sagemaker]>=1.21; extra == 'sagemaker'
Provides-Extra: sambanova
Requires-Dist: any-llm-sdk[sambanova]>=1.21; extra == 'sambanova'
Provides-Extra: telnyx
Requires-Dist: any-llm-sdk[telnyx]>=1.21; extra == 'telnyx'
Provides-Extra: together
Requires-Dist: any-llm-sdk[together]>=1.21; extra == 'together'
Provides-Extra: vertexai
Requires-Dist: any-llm-sdk[vertexai]>=1.21; extra == 'vertexai'
Provides-Extra: vertexaianthropic
Requires-Dist: any-llm-sdk[vertexaianthropic]>=1.21; extra == 'vertexaianthropic'
Provides-Extra: vllm
Requires-Dist: any-llm-sdk[vllm]>=1.21; extra == 'vllm'
Provides-Extra: voyage
Requires-Dist: any-llm-sdk[voyage]>=1.21; extra == 'voyage'
Provides-Extra: watsonx
Requires-Dist: any-llm-sdk[watsonx]>=1.21; extra == 'watsonx'
Provides-Extra: xai
Requires-Dist: any-llm-sdk[xai]>=1.21; extra == 'xai'
Provides-Extra: zai
Requires-Dist: any-llm-sdk[zai]>=1.21; extra == 'zai'
Description-Content-Type: text/markdown

# arvel-ai

**One stable API over many AI providers — plus a secured MCP server — for
the [arvel](https://pypi.org/project/arvel/) framework.**

Your app talks to one contract: `AI.chat`, `AI.stream`, `AI.structured`, `AI.embed`. Which provider
actually serves the request — Anthropic, OpenAI, a LiteLLM proxy, a local vLLM or Ollama — is a
config choice, not a code change. No provider SDK type (`httpx`, `any_llm`) ever
crosses into your code; the boundary is enforced by the import-linter, not just intended.

```bash
uv add arvel-ai                 # gateway (openai_compatible + fake) + MCP server
uv add 'arvel-ai[anthropic]'    # + the any-llm driver and your provider's SDK, one extra
```

One extra per provider — `anthropic`, `openai`, `gemini`, `bedrock`, `mistral`, `ollama`,
`groq`, … ([full list](docs/getting-started.md)); `arvel-ai[all]` installs every provider.

> Need durable, multi-step orchestration? That's a separate package —
> [`arvel-workflow`](https://pypi.org/project/arvel-workflow/) (Temporal-backed) — not part of the gateway.

Installing registers the provider automatically — `app.make("ai")` and the `AI` facade work with
zero wiring.

```python
from arvel_ai import AI

reply = await AI.chat("Summarize this review in one line", model="fast")
print(reply.text)

async for delta in AI.stream("Write a product description"):
    ...                                        # TextDelta / ToolCallDelta / StreamEnd

copy = await AI.structured(ProductCopy, "Write copy for red wool socks")
#      a typed, validated ProductCopy — not a dict
```

## What's in the box

- **The gateway** — `chat` / `stream` (SSE) / `structured` (typed output) / `embed`, over a stable
  msgspec contract and one `AiError` taxonomy. Swap providers in config; your code never changes.
- **Model aliases** — code says `model="fast"`/`"smart"`; ops maps those to real ids, so a retired
  model is a one-line config edit, not a code hunt.
- **A secured MCP server** — expose your app's functions to AI agents over the Model Context
  Protocol, with token or OIDC auth (RFC 8707 audience binding). Off by default.
- **First-class fakes** — `AI.fake()` tests AI code with no network, the same way you test mail.
- **A health-checked resource** — the gateway reports on `/health` and the startup log via a real
  probe: a wrong or missing key shows as `failed`, not a false `ok`.

## Point it at a provider

The gateway speaks the OpenAI HTTP format, and both Anthropic and OpenAI expose OpenAI-compatible
endpoints — so you need no proxy in front:

```python
# config/ai.py
from arvel import env

ai = {
    "default": "openai_compatible",
    "models": {"fast": "claude-haiku-4-5", "smart": "claude-opus-4-8"},
    "drivers": {
        "openai_compatible": {
            "base_url": env("AI_GATEWAY_URL", "https://api.anthropic.com/v1"),
            "api_key_env": "AI_API_KEY",       # the NAME of the env var, never the key itself
            "model": env("AI_MODEL_FAST", "claude-haiku-4-5"),
        },
    },
}
```

Keys live in environment variables only — config holds the env var *name*, never the secret.

## Documentation

| Guide | What's in it |
|-------|--------------|
| [Getting Started](docs/getting-started.md) | install → configure → first call → first test, end to end |
| [The Gateway](docs/gateway.md) | `chat`/`stream`/`structured`/`embed`, tools, drivers, aliases, errors, observability, testing |
| [MCP Server](docs/mcp.md) | expose tools to agents, token & OIDC auth, the security model |
| [Configuration](docs/configuration.md) | the complete `config("ai")` reference — every key and default |

## Requirements

- Python **3.14+**
- **arvel** (installed with the package)
- Optional engines: `arvel-ai[<provider>]` (the any-llm driver — one extra per provider, see
  [Getting Started](docs/getting-started.md)); OIDC MCP auth needs `arvel[jwt]`

## License

MIT — see [LICENSE](LICENSE).
