arova 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- arova-0.1.0/DECISIONS.md +25 -0
- arova-0.1.0/LICENSE +21 -0
- arova-0.1.0/PKG-INFO +160 -0
- arova-0.1.0/PUBLISH.md +25 -0
- arova-0.1.0/README.md +110 -0
- arova-0.1.0/RESEARCH.md +88 -0
- arova-0.1.0/arova/__init__.py +16 -0
- arova-0.1.0/arova/cli.py +76 -0
- arova-0.1.0/arova/client.py +136 -0
- arova-0.1.0/arova/cost.py +60 -0
- arova-0.1.0/arova/providers/__init__.py +17 -0
- arova-0.1.0/arova/providers/anthropic.py +92 -0
- arova-0.1.0/arova/providers/azure_openai.py +48 -0
- arova-0.1.0/arova/providers/base.py +160 -0
- arova-0.1.0/arova/providers/bedrock.py +59 -0
- arova-0.1.0/arova/providers/cohere.py +8 -0
- arova-0.1.0/arova/providers/deepseek.py +8 -0
- arova-0.1.0/arova/providers/gemini.py +101 -0
- arova-0.1.0/arova/providers/groq.py +8 -0
- arova-0.1.0/arova/providers/mistral.py +8 -0
- arova-0.1.0/arova/providers/openai.py +4 -0
- arova-0.1.0/arova/providers/opencompat.py +59 -0
- arova-0.1.0/arova/providers/xai.py +8 -0
- arova-0.1.0/arova/py.typed +0 -0
- arova-0.1.0/arova/router.py +74 -0
- arova-0.1.0/arova/sse.py +115 -0
- arova-0.1.0/arova/stream.py +81 -0
- arova-0.1.0/arova/types.py +129 -0
- arova-0.1.0/pyproject.toml +51 -0
- arova-0.1.0/requirements-dev.txt +7 -0
- arova-0.1.0/requirements.txt +3 -0
- arova-0.1.0/tests/test_cli.py +13 -0
- arova-0.1.0/tests/test_mocked_providers.py +270 -0
- arova-0.1.0/tests/test_providers.py +75 -0
- arova-0.1.0/tests/test_router_cost.py +40 -0
- arova-0.1.0/tests/test_sse.py +33 -0
arova-0.1.0/DECISIONS.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Arova architecture decisions
|
|
2
|
+
|
|
3
|
+
## Small runtime
|
|
4
|
+
|
|
5
|
+
Arova uses `httpx`, `pydantic`, and `anyio` at runtime. Vendor SDKs, web frameworks, proxy servers, and LangChain are excluded so installation remains small and provider adapters stay inspectable.
|
|
6
|
+
|
|
7
|
+
## Stable canonical model
|
|
8
|
+
|
|
9
|
+
The public API is modeled on a provider-neutral chat request and typed stream events. Native adapters translate provider-specific formats at the boundary. This allows applications to preserve one request/response contract while providers evolve their preferred API surfaces.
|
|
10
|
+
|
|
11
|
+
## Universal OpenAI-compatible adapter
|
|
12
|
+
|
|
13
|
+
The long tail is served by `OpenCompatProvider(base_url, api_key, provider_name)`. A new provider that exposes `/chat/completions` does not require an Arova release. Native adapters are reserved for providers whose authentication, payload, or stream semantics materially differ.
|
|
14
|
+
|
|
15
|
+
## No implicit network discovery
|
|
16
|
+
|
|
17
|
+
The bundled price table is static and source-controlled. Arova never makes an extra pricing request during a completion, which keeps latency and failure modes predictable. Unknown prices produce a zero estimate and are explicitly documented as unknown rather than free.
|
|
18
|
+
|
|
19
|
+
## Streaming safety
|
|
20
|
+
|
|
21
|
+
SSE parsing is isolated in `arova.sse`, handles keep-alives and malformed records, and never silently turns an upstream malformed chunk into a successful text response. Tool-call fragments are represented as deltas and can be reassembled after the stream.
|
|
22
|
+
|
|
23
|
+
## Authentication
|
|
24
|
+
|
|
25
|
+
Provider keys are read from conventional environment variables, but every adapter accepts an explicit key. Azure deployment and API-version configuration and Bedrock region configuration are also explicit. AWS IAM signing is intentionally not bundled because adding a cloud SDK would violate the runtime dependency constraint; users can place an authenticated gateway in front of the adapter.
|
arova-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Arova Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
arova-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: arova
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: One API for every LLM provider, with zero ceremony.
|
|
5
|
+
Author: Arova Contributors
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026 Arova Contributors
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
License-File: LICENSE
|
|
28
|
+
Keywords: ai,anthropic,gemini,inference,llm,openai
|
|
29
|
+
Classifier: Development Status :: 3 - Alpha
|
|
30
|
+
Classifier: Intended Audience :: Developers
|
|
31
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
32
|
+
Classifier: Programming Language :: Python :: 3
|
|
33
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
34
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
35
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
36
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
37
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
38
|
+
Requires-Python: >=3.10
|
|
39
|
+
Requires-Dist: anyio<5,>=4
|
|
40
|
+
Requires-Dist: httpx[http2]<1,>=0.27
|
|
41
|
+
Requires-Dist: pydantic<3,>=2.7
|
|
42
|
+
Provides-Extra: dev
|
|
43
|
+
Requires-Dist: build>=1.2; extra == 'dev'
|
|
44
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
45
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
46
|
+
Requires-Dist: respx>=0.21; extra == 'dev'
|
|
47
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
48
|
+
Requires-Dist: twine>=5; extra == 'dev'
|
|
49
|
+
Description-Content-Type: text/markdown
|
|
50
|
+
|
|
51
|
+
# Arova
|
|
52
|
+
|
|
53
|
+
[](https://pypi.org/project/arova/)
|
|
54
|
+
[](https://pypi.org/project/arova/)
|
|
55
|
+
[](LICENSE)
|
|
56
|
+
|
|
57
|
+
> **One API. Every model. Zero ceremony.**
|
|
58
|
+
|
|
59
|
+
Arova is a small, typed Python client for calling major hosted and local language-model providers through one stable interface. It uses native adapters where wire formats differ and one universal OpenAI-compatible adapter for the long tail of endpoints.
|
|
60
|
+
|
|
61
|
+
## Quickstart
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
pip install arova
|
|
65
|
+
export OPENAI_API_KEY=sk-...
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
from arova import completion
|
|
70
|
+
|
|
71
|
+
response = completion(
|
|
72
|
+
"openai/gpt-5.6-luna",
|
|
73
|
+
[{"role": "user", "content": "Explain zero-copy I/O in one paragraph."}],
|
|
74
|
+
)
|
|
75
|
+
print(response.text, response.cost)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
The model prefix selects a provider. A bare model name uses OpenAI by default, and fallback chains can mix providers: `fallbacks=["groq/llama-3.3-70b-versatile", "opencompat/local-model"]`. For asynchronous applications, use `await arova.acompletion(...)` or `async for event in arova.astream(...)`.
|
|
79
|
+
|
|
80
|
+
## Provider coverage
|
|
81
|
+
|
|
82
|
+
Arova includes native adapters for OpenAI, Anthropic, Gemini, Azure OpenAI, Bedrock, Mistral, Cohere, Groq, DeepSeek, and xAI. It also includes `arova.opencompat`, which can target any OpenAI-compatible endpoint by setting `base_url`, model, and key. This covers Together AI, Fireworks AI, OpenRouter, Hugging Face Inference Providers, Ollama, vLLM, LM Studio, Perplexity, Cerebras, SambaNova, NVIDIA NIM, DeepInfra, Novita, and deployment-specific endpoints without adding vendor SDKs. The detailed matrix and source notes are in [RESEARCH.md](RESEARCH.md).
|
|
83
|
+
|
|
84
|
+
| Adapter | Provider examples | Wire format |
|
|
85
|
+
|---|---|---|
|
|
86
|
+
| Native | OpenAI, Anthropic, Gemini, Azure OpenAI, Bedrock, Mistral, Cohere, Groq, DeepSeek, xAI | Provider-specific translation and streaming |
|
|
87
|
+
| `opencompat` | Together, Fireworks, OpenRouter, Ollama, vLLM, LM Studio, Perplexity, Cerebras, SambaNova, self-hosted gateways | `/chat/completions` |
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
from arova.providers.opencompat import OpenCompatProvider
|
|
91
|
+
from arova.types import ChatRequest, Message
|
|
92
|
+
|
|
93
|
+
provider = OpenCompatProvider(
|
|
94
|
+
base_url="https://api.together.xyz/v1",
|
|
95
|
+
api_key="...",
|
|
96
|
+
provider_name="together",
|
|
97
|
+
)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Streaming
|
|
101
|
+
|
|
102
|
+
Streaming yields typed events rather than provider-specific dictionaries. Tool-call arguments may arrive over many deltas and can be reassembled with `assemble_tool_calls`.
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
from arova import Arova, TextDelta, Finish
|
|
106
|
+
|
|
107
|
+
client = Arova()
|
|
108
|
+
for event in client.stream("groq/llama-3.3-70b-versatile", [{"role": "user", "content": "Give me three names for a two-faced API."}]):
|
|
109
|
+
if isinstance(event, TextDelta):
|
|
110
|
+
print(event.text, end="", flush=True)
|
|
111
|
+
elif isinstance(event, Finish):
|
|
112
|
+
print(f"\nfinished: {event.reason}")
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Tool calling and structured output
|
|
116
|
+
|
|
117
|
+
The same request types work across native adapters and compatible endpoints. Provider quirks are translated at the boundary.
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
from arova import completion
|
|
121
|
+
|
|
122
|
+
response = completion(
|
|
123
|
+
"anthropic/claude-sonnet-4.0",
|
|
124
|
+
[{"role": "user", "content": "What is the weather in Paris?"}],
|
|
125
|
+
tools=[{
|
|
126
|
+
"name": "get_weather",
|
|
127
|
+
"description": "Return current weather for a city.",
|
|
128
|
+
"parameters": {
|
|
129
|
+
"type": "object",
|
|
130
|
+
"properties": {"city": {"type": "string"}},
|
|
131
|
+
"required": ["city"],
|
|
132
|
+
},
|
|
133
|
+
}],
|
|
134
|
+
)
|
|
135
|
+
for call in response.tool_calls:
|
|
136
|
+
print(call.name, call.arguments)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
A JSON-schema response can be requested with `response_format={"type": "json_schema", "name": "answer", "schema": {...}}`. Support depends on the upstream model; Arova preserves the request and normalizes the response when the provider supports it.
|
|
140
|
+
|
|
141
|
+
## Retries, fallbacks, and costs
|
|
142
|
+
|
|
143
|
+
Arova retries transient transport failures, 408/409/429 responses, and 5xx responses with jittered exponential backoff. A numeric `Retry-After` header takes precedence. A fallback chain is expressed as model strings, for example `fallbacks=["groq/llama-3.3-70b-versatile", "opencompat/local"]`. Every non-streaming response includes normalized usage and a deterministic `cost` estimate from the bundled static price table. Prices are a source-controlled snapshot, not a billing authority; see [RESEARCH.md](RESEARCH.md).
|
|
144
|
+
|
|
145
|
+
## CLI
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
arova --help
|
|
149
|
+
arova models
|
|
150
|
+
arova cost groq llama-3.3-70b-versatile --input-tokens 1000 --output-tokens 250
|
|
151
|
+
arova chat --model openai/gpt-5.6-luna
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Benchmark-note placeholder
|
|
155
|
+
|
|
156
|
+
A controlled benchmark will compare direct provider calls with Arova using warmed HTTP/2 connections, identical payloads, and separate cold-start measurements. Until that benchmark is added, performance claims are design goals rather than published results. Arova intentionally avoids per-call imports, unnecessary re-validation, mandatory logging, and proxy/server dependencies in the request path.
|
|
157
|
+
|
|
158
|
+
## License
|
|
159
|
+
|
|
160
|
+
Arova is released under the MIT License. See [LICENSE](LICENSE).
|
arova-0.1.0/PUBLISH.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Publishing Arova to PyPI
|
|
2
|
+
|
|
3
|
+
This project intentionally does **not** upload to PyPI. The following procedure is provided for a maintainer who has reviewed the package and configured a PyPI token.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
python -m venv .venv
|
|
7
|
+
. .venv/bin/activate
|
|
8
|
+
python -m pip install --upgrade pip
|
|
9
|
+
pip install -r requirements-dev.txt
|
|
10
|
+
ruff check .
|
|
11
|
+
pytest
|
|
12
|
+
rm -rf dist build *.egg-info
|
|
13
|
+
python -m build
|
|
14
|
+
twine check dist/*
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
After reviewing the generated sdist and wheel, configure a token as an environment variable and publish using the username `__token__`:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
export TWINE_USERNAME=__token__
|
|
21
|
+
export TWINE_PASSWORD=pypi-...
|
|
22
|
+
twine upload dist/*
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Never commit the token. For a release, update the version, review `RESEARCH.md` and the static pricing snapshot, run the full checklist, and verify that `arova/py.typed` is present in the wheel before upload.
|
arova-0.1.0/README.md
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# Arova
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/arova/)
|
|
4
|
+
[](https://pypi.org/project/arova/)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
|
|
7
|
+
> **One API. Every model. Zero ceremony.**
|
|
8
|
+
|
|
9
|
+
Arova is a small, typed Python client for calling major hosted and local language-model providers through one stable interface. It uses native adapters where wire formats differ and one universal OpenAI-compatible adapter for the long tail of endpoints.
|
|
10
|
+
|
|
11
|
+
## Quickstart
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install arova
|
|
15
|
+
export OPENAI_API_KEY=sk-...
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
```python
|
|
19
|
+
from arova import completion
|
|
20
|
+
|
|
21
|
+
response = completion(
|
|
22
|
+
"openai/gpt-5.6-luna",
|
|
23
|
+
[{"role": "user", "content": "Explain zero-copy I/O in one paragraph."}],
|
|
24
|
+
)
|
|
25
|
+
print(response.text, response.cost)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The model prefix selects a provider. A bare model name uses OpenAI by default, and fallback chains can mix providers: `fallbacks=["groq/llama-3.3-70b-versatile", "opencompat/local-model"]`. For asynchronous applications, use `await arova.acompletion(...)` or `async for event in arova.astream(...)`.
|
|
29
|
+
|
|
30
|
+
## Provider coverage
|
|
31
|
+
|
|
32
|
+
Arova includes native adapters for OpenAI, Anthropic, Gemini, Azure OpenAI, Bedrock, Mistral, Cohere, Groq, DeepSeek, and xAI. It also includes `arova.opencompat`, which can target any OpenAI-compatible endpoint by setting `base_url`, model, and key. This covers Together AI, Fireworks AI, OpenRouter, Hugging Face Inference Providers, Ollama, vLLM, LM Studio, Perplexity, Cerebras, SambaNova, NVIDIA NIM, DeepInfra, Novita, and deployment-specific endpoints without adding vendor SDKs. The detailed matrix and source notes are in [RESEARCH.md](RESEARCH.md).
|
|
33
|
+
|
|
34
|
+
| Adapter | Provider examples | Wire format |
|
|
35
|
+
|---|---|---|
|
|
36
|
+
| Native | OpenAI, Anthropic, Gemini, Azure OpenAI, Bedrock, Mistral, Cohere, Groq, DeepSeek, xAI | Provider-specific translation and streaming |
|
|
37
|
+
| `opencompat` | Together, Fireworks, OpenRouter, Ollama, vLLM, LM Studio, Perplexity, Cerebras, SambaNova, self-hosted gateways | `/chat/completions` |
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
from arova.providers.opencompat import OpenCompatProvider
|
|
41
|
+
from arova.types import ChatRequest, Message
|
|
42
|
+
|
|
43
|
+
provider = OpenCompatProvider(
|
|
44
|
+
base_url="https://api.together.xyz/v1",
|
|
45
|
+
api_key="...",
|
|
46
|
+
provider_name="together",
|
|
47
|
+
)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Streaming
|
|
51
|
+
|
|
52
|
+
Streaming yields typed events rather than provider-specific dictionaries. Tool-call arguments may arrive over many deltas and can be reassembled with `assemble_tool_calls`.
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
from arova import Arova, TextDelta, Finish
|
|
56
|
+
|
|
57
|
+
client = Arova()
|
|
58
|
+
for event in client.stream("groq/llama-3.3-70b-versatile", [{"role": "user", "content": "Give me three names for a two-faced API."}]):
|
|
59
|
+
if isinstance(event, TextDelta):
|
|
60
|
+
print(event.text, end="", flush=True)
|
|
61
|
+
elif isinstance(event, Finish):
|
|
62
|
+
print(f"\nfinished: {event.reason}")
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Tool calling and structured output
|
|
66
|
+
|
|
67
|
+
The same request types work across native adapters and compatible endpoints. Provider quirks are translated at the boundary.
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
from arova import completion
|
|
71
|
+
|
|
72
|
+
response = completion(
|
|
73
|
+
"anthropic/claude-sonnet-4.0",
|
|
74
|
+
[{"role": "user", "content": "What is the weather in Paris?"}],
|
|
75
|
+
tools=[{
|
|
76
|
+
"name": "get_weather",
|
|
77
|
+
"description": "Return current weather for a city.",
|
|
78
|
+
"parameters": {
|
|
79
|
+
"type": "object",
|
|
80
|
+
"properties": {"city": {"type": "string"}},
|
|
81
|
+
"required": ["city"],
|
|
82
|
+
},
|
|
83
|
+
}],
|
|
84
|
+
)
|
|
85
|
+
for call in response.tool_calls:
|
|
86
|
+
print(call.name, call.arguments)
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
A JSON-schema response can be requested with `response_format={"type": "json_schema", "name": "answer", "schema": {...}}`. Support depends on the upstream model; Arova preserves the request and normalizes the response when the provider supports it.
|
|
90
|
+
|
|
91
|
+
## Retries, fallbacks, and costs
|
|
92
|
+
|
|
93
|
+
Arova retries transient transport failures, 408/409/429 responses, and 5xx responses with jittered exponential backoff. A numeric `Retry-After` header takes precedence. A fallback chain is expressed as model strings, for example `fallbacks=["groq/llama-3.3-70b-versatile", "opencompat/local"]`. Every non-streaming response includes normalized usage and a deterministic `cost` estimate from the bundled static price table. Prices are a source-controlled snapshot, not a billing authority; see [RESEARCH.md](RESEARCH.md).
|
|
94
|
+
|
|
95
|
+
## CLI
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
arova --help
|
|
99
|
+
arova models
|
|
100
|
+
arova cost groq llama-3.3-70b-versatile --input-tokens 1000 --output-tokens 250
|
|
101
|
+
arova chat --model openai/gpt-5.6-luna
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Benchmark-note placeholder
|
|
105
|
+
|
|
106
|
+
A controlled benchmark will compare direct provider calls with Arova using warmed HTTP/2 connections, identical payloads, and separate cold-start measurements. Until that benchmark is added, performance claims are design goals rather than published results. Arova intentionally avoids per-call imports, unnecessary re-validation, mandatory logging, and proxy/server dependencies in the request path.
|
|
107
|
+
|
|
108
|
+
## License
|
|
109
|
+
|
|
110
|
+
Arova is released under the MIT License. See [LICENSE](LICENSE).
|
arova-0.1.0/RESEARCH.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Arova provider research
|
|
2
|
+
|
|
3
|
+
**Research snapshot:** 26 August 2026. Provider APIs and prices change frequently; this document records the interface assumptions used by Arova 0.1.0 and identifies which rows are implemented natively versus through the universal OpenAI-compatible adapter.
|
|
4
|
+
|
|
5
|
+
## Executive findings
|
|
6
|
+
|
|
7
|
+
Arova should keep a small synchronous core around `httpx`, `pydantic`, and `anyio` rather than depend on vendor SDKs. OpenAI's current documentation centers the Responses API for text, structured output, tools, and multimodal workflows, while Gemini's documentation now recommends the Interactions API for new projects and describes `generateContent` as legacy [1] [2]. Arova 0.1.0 deliberately exposes a stable chat-completion-shaped canonical interface and keeps provider-specific translation inside adapters, so the public API does not change when an upstream provider changes its preferred surface.
|
|
8
|
+
|
|
9
|
+
Anthropic's direct REST API remains a Messages API at `https://api.anthropic.com/v1/messages`, with explicit `anthropic-version` and API-key headers [3]. The adapter therefore maps system messages, tools, tool-use blocks, and Anthropic content-block SSE events explicitly instead of assuming OpenAI wire compatibility.
|
|
10
|
+
|
|
11
|
+
## Provider and endpoint matrix
|
|
12
|
+
|
|
13
|
+
The compatibility column means compatibility with the common OpenAI Chat Completions request/response shape, not that the provider offers every OpenAI feature. A value of **Yes** means a documented compatible route or a widely used compatible gateway surface; **Partial** means compatibility exists but features or authentication differ; **No** means Arova uses a native translation.
|
|
14
|
+
|
|
15
|
+
| Provider | Representative endpoint or base URL | OpenAI-compatible? | Arova route in 0.1.0 | Notes |
|
|
16
|
+
|---|---|---:|---|---|
|
|
17
|
+
| OpenAI | `https://api.openai.com/v1` | Partial | Native OpenAI-compatible adapter | Current docs emphasize Responses API; chat surface remains useful for a stable common denominator [1]. |
|
|
18
|
+
| Azure OpenAI | Azure resource endpoint, deployment route, `api-version` query | Partial | Native Azure adapter | Uses `api-key` and deployment-aware URLs. |
|
|
19
|
+
| Anthropic | `https://api.anthropic.com/v1/messages` | No | Native adapter | Messages API and Anthropic SSE event types [3]. |
|
|
20
|
+
| Google Gemini | `https://generativelanguage.googleapis.com/v1beta` | No | Native adapter | `generateContent` and `streamGenerateContent`; Interactions API is the current forward-looking surface [2]. |
|
|
21
|
+
| Vertex AI | Regional Google Cloud publisher/model endpoints | No | `opencompat` or caller gateway | OAuth/service-account signing is intentionally outside the six-package runtime. |
|
|
22
|
+
| AWS Bedrock | `https://bedrock-runtime.{region}.amazonaws.com` | No | Native Converse adapter | Caller supplies bearer credentials or an authenticated gateway; no `boto3` dependency. |
|
|
23
|
+
| Mistral | `https://api.mistral.ai/v1` | Yes/Partial | Native-default adapter | Chat Completions-shaped surface with Mistral-specific model/tool quirks. |
|
|
24
|
+
| Cohere | `https://api.cohere.com/v2` | Partial | Native-default adapter | v2 Chat has provider-specific content and tool semantics. |
|
|
25
|
+
| Groq | `https://api.groq.com/openai/v1` | Yes | Native-default adapter | OpenAI-compatible endpoint. |
|
|
26
|
+
| DeepSeek | `https://api.deepseek.com/v1` | Yes | Native-default adapter | OpenAI-compatible chat surface. |
|
|
27
|
+
| xAI (Grok) | `https://api.x.ai/v1` | Yes | Native-default adapter | OpenAI-compatible chat surface. |
|
|
28
|
+
| Together AI | `https://api.together.xyz/v1` | Yes | Universal adapter | Supply `base_url`, model, and key. |
|
|
29
|
+
| Fireworks AI | `https://api.fireworks.ai/inference/v1` | Yes | Universal adapter | Supply `base_url`, model, and key. |
|
|
30
|
+
| OpenRouter | `https://openrouter.ai/api/v1` | Yes | Universal adapter | Routes many upstream model families. |
|
|
31
|
+
| Hugging Face Inference Providers | Provider-specific or HF router endpoint | Partial | Universal adapter | Model/provider routing and headers vary. |
|
|
32
|
+
| Ollama | `http://localhost:11434/v1` | Yes | Universal adapter | Local OpenAI-compatible route. |
|
|
33
|
+
| vLLM | Caller-hosted `/v1` | Yes | Universal adapter | OpenAI-compatible server. |
|
|
34
|
+
| LM Studio | `http://localhost:1234/v1` | Yes | Universal adapter | Official docs describe reusing OpenAI clients by changing `base_url` [4]. |
|
|
35
|
+
| Replicate | `https://api.replicate.com/v1` | No | Universal gateway only | Prediction API is not a direct chat-completions wire equivalent. |
|
|
36
|
+
| AI21 | AI21 Studio API base | Partial | Universal gateway only | Model and endpoint availability vary by account. |
|
|
37
|
+
| Writer | Writer API base | Partial | Universal gateway only | Provider-specific auth and model catalog. |
|
|
38
|
+
| Perplexity | `https://api.perplexity.ai` | Yes/Partial | Universal adapter | Chat-shaped API with search-grounded response metadata. |
|
|
39
|
+
| Cerebras | Cerebras inference API base | Yes/Partial | Universal adapter | OpenAI-compatible serving surface for supported models. |
|
|
40
|
+
| SambaNova | SambaNova Cloud API base | Yes/Partial | Universal adapter | OpenAI-compatible serving surface for supported deployments. |
|
|
41
|
+
| NVIDIA NIM | Caller-hosted `/v1` | Yes | Universal adapter | OpenAI-compatible local or hosted inference routes. |
|
|
42
|
+
| DeepInfra | `https://api.deepinfra.com/v1/openai` | Yes | Universal adapter | OpenAI-compatible model gateway. |
|
|
43
|
+
| Anyscale Endpoints | Provider/account-specific | Partial | Universal adapter | Availability and URL depend on deployment. |
|
|
44
|
+
| Baseten | Deployment-specific `/v1` | Partial | Universal adapter | OpenAI-compatible when enabled by deployment. |
|
|
45
|
+
| Modal | Caller-hosted or deployment-specific | Partial | Universal adapter | Endpoint is deployment-specific. |
|
|
46
|
+
| Novita AI | Provider API base | Yes/Partial | Universal adapter | OpenAI-compatible gateway for supported models. |
|
|
47
|
+
| AI Horde | Community inference API | No | Universal gateway only | Queue-based API; not assumed to be low-latency chat. |
|
|
48
|
+
|
|
49
|
+
The implementation strategy is therefore **ten named native adapters plus one universal adapter**. The universal adapter is the extensibility boundary: any provider implementing `/chat/completions` can be used without a new Arova release by supplying `base_url`, `model`, and `api_key`.
|
|
50
|
+
|
|
51
|
+
## Price snapshot
|
|
52
|
+
|
|
53
|
+
Prices below are representative public list prices in USD per one million tokens and are bundled as an auditable static table in `arova.cost`. They are not a billing guarantee. OpenAI's official pricing page explicitly distinguishes input, cached input, cache writes, and output and currently shows the representative flagship values used by this release [5]. Other rows are representative model prices gathered from the linked provider pricing or model documentation and should be refreshed before production cost accounting.
|
|
54
|
+
|
|
55
|
+
| Provider/model | Input $/M | Output $/M | Bundled? |
|
|
56
|
+
|---|---:|---:|---:|
|
|
57
|
+
| OpenAI `gpt-5.6-sol` | 4.00 | 20.00 | Yes |
|
|
58
|
+
| OpenAI `gpt-5.6-terra` | 2.00 | 12.00 | Yes |
|
|
59
|
+
| OpenAI `gpt-5.6-luna` | 0.20 | 1.20 | Yes |
|
|
60
|
+
| Anthropic `claude-opus-4.1` | 15.00 | 75.00 | Yes |
|
|
61
|
+
| Anthropic `claude-sonnet-4.0` | 3.00 | 15.00 | Yes |
|
|
62
|
+
| Gemini `gemini-3.7-pro` | 2.00 | 12.00 | Yes |
|
|
63
|
+
| Gemini `gemini-3.7-flash` | 0.30 | 2.50 | Yes |
|
|
64
|
+
| Mistral `mistral-large-latest` | 2.00 | 6.00 | Yes |
|
|
65
|
+
| Groq `llama-3.3-70b-versatile` | 0.59 | 0.79 | Yes |
|
|
66
|
+
| DeepSeek `deepseek-chat` | 0.28 | 0.42 | Yes |
|
|
67
|
+
| xAI `grok-4` | 3.00 | 15.00 | Yes |
|
|
68
|
+
| OpenRouter, Ollama, vLLM, LM Studio | caller/provider price | caller/provider price | wildcard $0 |
|
|
69
|
+
|
|
70
|
+
A zero wildcard price means "no price is known to Arova," not "the service is always free." Applications should treat an unrecognized or wildcard price as an estimate of zero and replace the table for contractual billing.
|
|
71
|
+
|
|
72
|
+
## LiteLLM complaints and explicit Arova responses
|
|
73
|
+
|
|
74
|
+
A LiteLLM issue reports that the core pip installation includes proxy code and related dependencies, with a reported footprint of approximately 28 MB or more and approximately 12 MB of proxy code alone [6]. Arova addresses this by keeping the runtime dependency set to `httpx`, `pydantic`, `anyio`, and their small transitive requirements; it does not ship a proxy server, FastAPI, Uvicorn, vendor SDKs, LangChain, or a large plugin registry.
|
|
75
|
+
|
|
76
|
+
A separate LiteLLM discussion reports a user's direct-call latency of roughly 1.6–2 seconds increasing to roughly 4–4.5 seconds through a proxy in a TGI setup [7]. This is a single user report, not a controlled benchmark, but it motivates Arova's performance hygiene: module-level client reuse, HTTP/2 and connection pooling, no per-call imports, no network pricing lookup, and no mandatory observability middleware in the hot path.
|
|
77
|
+
|
|
78
|
+
The project also treats streaming correctness as a first-class concern. The `arova.sse` parser accepts keep-alive comments, partial records, multi-line data, `[DONE]`, and malformed chunks without crashing the entire stream; provider adapters then normalize text, reasoning, tool-call deltas, usage, finish, and error events.
|
|
79
|
+
|
|
80
|
+
## References
|
|
81
|
+
|
|
82
|
+
[1]: https://developers.openai.com/api/docs "OpenAI API Platform Documentation"
|
|
83
|
+
[2]: https://ai.google.dev/gemini-api/docs "Google Gemini API Documentation"
|
|
84
|
+
[3]: https://platform.claude.com/docs/en/api/overview "Anthropic Claude API Overview"
|
|
85
|
+
[4]: https://lmstudio.ai/docs/developer/openai-compat "LM Studio OpenAI Compatibility"
|
|
86
|
+
[5]: https://developers.openai.com/api/docs/pricing "OpenAI API Pricing"
|
|
87
|
+
[6]: https://github.com/BerriAI/litellm/issues/15262 "LiteLLM issue: separate proxy dependencies/code from core SDK"
|
|
88
|
+
[7]: https://github.com/BerriAI/litellm/discussions/4298 "LiteLLM discussion: how to reduce proxy latency"
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Arova — one API, every model, zero ceremony."""
|
|
2
|
+
from .client import Arova, acompletion, astream, completion, provider_registry, stream
|
|
3
|
+
from .stream import ErrorEvent, Finish, ReasoningDelta, TextDelta, ToolCallDelta, UsageEvent
|
|
4
|
+
from .types import (
|
|
5
|
+
ChatRequest, ChatResponse, FinishReason, Message, ModelInfo, ProviderCapabilities,
|
|
6
|
+
ResponseFormat, ToolCall, ToolDefinition, Usage,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
__version__ = "0.1.0"
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"Arova", "completion", "acompletion", "stream", "astream", "provider_registry",
|
|
13
|
+
"Message", "ToolCall", "ToolDefinition", "ResponseFormat", "ChatRequest", "ChatResponse", "Usage",
|
|
14
|
+
"FinishReason", "ModelInfo", "ProviderCapabilities", "TextDelta", "ToolCallDelta", "ReasoningDelta",
|
|
15
|
+
"UsageEvent", "Finish", "ErrorEvent",
|
|
16
|
+
]
|
arova-0.1.0/arova/cli.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""Command-line interface for Arova."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import json
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
from .client import Arova, provider_registry
|
|
9
|
+
from .cost import PRICE_TABLE, cost_for_usage
|
|
10
|
+
from .types import Message, Usage
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _parser() -> argparse.ArgumentParser:
|
|
14
|
+
parser = argparse.ArgumentParser(prog="arova", description="One API. Every model. Zero ceremony.")
|
|
15
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
16
|
+
chat = sub.add_parser("chat", help="Start a simple chat REPL")
|
|
17
|
+
chat.add_argument("--model", default="openai/gpt-5.6-luna")
|
|
18
|
+
models = sub.add_parser("models", help="List bundled provider/model price entries")
|
|
19
|
+
models.add_argument("--provider")
|
|
20
|
+
cost = sub.add_parser("cost", help="Calculate cost from token counts")
|
|
21
|
+
cost.add_argument("provider")
|
|
22
|
+
cost.add_argument("model")
|
|
23
|
+
cost.add_argument("--input-tokens", type=int, default=0)
|
|
24
|
+
cost.add_argument("--output-tokens", type=int, default=0)
|
|
25
|
+
cost.add_argument("--cached-input-tokens", type=int, default=0)
|
|
26
|
+
return parser
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _cmd_models(provider: str | None) -> int:
|
|
30
|
+
rows = [price for price in PRICE_TABLE if provider is None or price.provider == provider]
|
|
31
|
+
print("provider\tmodel\tinput/$M\toutput/$M")
|
|
32
|
+
for price in rows:
|
|
33
|
+
print(f"{price.provider}\t{price.model}\t{price.input_per_million:g}\t{price.output_per_million:g}")
|
|
34
|
+
if provider and provider not in provider_registry():
|
|
35
|
+
print(f"Unknown provider: {provider}", file=sys.stderr)
|
|
36
|
+
return 2
|
|
37
|
+
return 0
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _cmd_cost(args: argparse.Namespace) -> int:
|
|
41
|
+
usage = Usage(input_tokens=args.input_tokens, output_tokens=args.output_tokens, cached_input_tokens=args.cached_input_tokens).normalized()
|
|
42
|
+
print(json.dumps({"provider": args.provider, "model": args.model, "usage": usage.model_dump(), "cost_usd": cost_for_usage(args.provider, args.model, usage)}, indent=2))
|
|
43
|
+
return 0
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _cmd_chat(args: argparse.Namespace) -> int:
|
|
47
|
+
print(f"Arova chat — {args.model}. Type /exit to quit.")
|
|
48
|
+
client = Arova()
|
|
49
|
+
messages: list[Message] = []
|
|
50
|
+
try:
|
|
51
|
+
while True:
|
|
52
|
+
line = input("you> ")
|
|
53
|
+
if line.strip() == "/exit":
|
|
54
|
+
return 0
|
|
55
|
+
if not line.strip():
|
|
56
|
+
continue
|
|
57
|
+
messages.append(Message(role="user", content=line))
|
|
58
|
+
response = client.completion(args.model, messages)
|
|
59
|
+
print(f"assistant> {response.text}")
|
|
60
|
+
messages.append(response.message)
|
|
61
|
+
except (EOFError, KeyboardInterrupt):
|
|
62
|
+
print()
|
|
63
|
+
return 0
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def main(argv: list[str] | None = None) -> int:
|
|
67
|
+
args = _parser().parse_args(argv)
|
|
68
|
+
if args.command == "models":
|
|
69
|
+
return _cmd_models(args.provider)
|
|
70
|
+
if args.command == "cost":
|
|
71
|
+
return _cmd_cost(args)
|
|
72
|
+
return _cmd_chat(args)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
if __name__ == "__main__":
|
|
76
|
+
raise SystemExit(main())
|