llmshim 0.3.2__tar.gz → 0.3.4__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.
- {llmshim-0.3.2 → llmshim-0.3.4}/CLAUDE.md +1 -1
- {llmshim-0.3.2 → llmshim-0.3.4}/Cargo.lock +1 -1
- {llmshim-0.3.2 → llmshim-0.3.4}/Cargo.toml +1 -1
- {llmshim-0.3.2 → llmshim-0.3.4}/PKG-INFO +81 -5
- {llmshim-0.3.2 → llmshim-0.3.4}/README.md +1 -1
- {llmshim-0.3.2 → llmshim-0.3.4}/docs/src/guides/reasoning.md +3 -3
- {llmshim-0.3.2 → llmshim-0.3.4}/docs/src/reference/models.md +5 -3
- {llmshim-0.3.2 → llmshim-0.3.4}/llmshim/__init__.py +6 -1
- {llmshim-0.3.2 → llmshim-0.3.4}/llmshim/_client.py +18 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/src/main.rs +4 -2
- {llmshim-0.3.2 → llmshim-0.3.4}/src/models.rs +24 -6
- {llmshim-0.3.2 → llmshim-0.3.4}/src/providers/gemini.rs +8 -4
- {llmshim-0.3.2 → llmshim-0.3.4}/tests/integration_gemini.rs +62 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/tests/unit_gemini.rs +29 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/tests/unit_models.rs +1 -1
- {llmshim-0.3.2 → llmshim-0.3.4}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/.github/workflows/pages.yml +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/.github/workflows/release.yml +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/.gitignore +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/CODE_OF_CONDUCT.md +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/CONTRIBUTING.md +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/LICENSE-APACHE +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/LICENSE-MIT +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/NOTICE +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/SECURITY.md +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/benchmarks/bench.rs +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/benchmarks/bench_python.py +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/benchmarks/loadtest.rs +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/docs/.gitignore +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/docs/book.toml +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/docs/mermaid-init.js +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/docs/mermaid.min.js +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/docs/src/SUMMARY.md +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/docs/src/concepts/contracts.md +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/docs/src/concepts/conversations.md +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/docs/src/concepts/portability.md +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/docs/src/concepts/routing.md +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/docs/src/concepts/translation-flow.md +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/docs/src/guides/fallbacks.md +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/docs/src/guides/images.md +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/docs/src/guides/native-controls.md +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/docs/src/guides/streaming.md +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/docs/src/guides/tools.md +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/docs/src/introduction.md +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/docs/src/proxy/deployment.md +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/docs/src/proxy/http-api.md +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/docs/src/proxy/scaling.md +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/docs/src/reference/api.md +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/docs/src/reference/cli.md +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/docs/src/reference/configuration.md +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/docs/src/reference/errors.md +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/docs/src/reference/providers.md +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/docs/src/reference/request-fields.md +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/docs/src/reference/surfaces.md +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/docs/src/start/choose.md +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/docs/src/start/cli.md +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/docs/src/start/clients.md +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/docs/src/start/configure.md +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/docs/src/start/proxy.md +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/docs/src/start/rust.md +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/examples/chat.rs +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/examples/stream.rs +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/llmshim/_server.py +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/llmshim/types.py +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/pyproject.toml +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/src/client.rs +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/src/config.rs +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/src/env.rs +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/src/error.rs +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/src/fallback.rs +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/src/lib.rs +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/src/log.rs +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/src/provider.rs +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/src/providers/anthropic.rs +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/src/providers/mod.rs +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/src/providers/openai.rs +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/src/providers/openai_compat.rs +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/src/providers/openrouter.rs +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/src/providers/xai.rs +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/src/proxy/convert.rs +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/src/proxy/error.rs +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/src/proxy/handlers.rs +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/src/proxy/mod.rs +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/src/proxy/ratelimit.rs +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/src/proxy/types.rs +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/src/router.rs +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/src/vision.rs +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/tests/integration.rs +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/tests/integration_fallback.rs +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/tests/integration_gemini_tools.rs +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/tests/integration_long_context.rs +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/tests/integration_multimodel.rs +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/tests/integration_openrouter.rs +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/tests/integration_proxy.rs +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/tests/integration_sglang.rs +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/tests/integration_thinking.rs +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/tests/integration_tool_roundtrip.rs +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/tests/integration_vision.rs +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/tests/integration_xai.rs +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/tests/unit_anthropic.rs +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/tests/unit_fallback.rs +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/tests/unit_fast_mode.rs +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/tests/unit_log.rs +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/tests/unit_multimodel.rs +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/tests/unit_openai.rs +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/tests/unit_openai_compat.rs +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/tests/unit_openrouter.rs +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/tests/unit_proxy.rs +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/tests/unit_proxy_convert.rs +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/tests/unit_router.rs +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/tests/unit_sse.rs +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/tests/unit_tools.rs +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/tests/unit_vision.rs +0 -0
- {llmshim-0.3.2 → llmshim-0.3.4}/tests/unit_xai.rs +0 -0
|
@@ -14,7 +14,7 @@ This is a public crate on crates.io. Do NOT make breaking changes to `pub` items
|
|
|
14
14
|
|
|
15
15
|
- **OpenAI:** `gpt-5.6-sol`, `gpt-5.6-terra`, `gpt-5.6-luna`, `gpt-5.5`, `gpt-5.5-pro`, `gpt-5.4`, `gpt-5.4-pro`, `gpt-5.4-mini`, `gpt-5.4-nano`
|
|
16
16
|
- **Anthropic:** `claude-opus-5`, `claude-opus-4-8`, `claude-sonnet-5`, `claude-opus-4-7`, `claude-opus-4-6`, `claude-sonnet-4-6`, `claude-haiku-4-5-20251001`
|
|
17
|
-
- **Gemini:** `gemini-3.5-flash`, `gemini-3.
|
|
17
|
+
- **Gemini:** `gemini-3.7-flash`, `gemini-3.6-flash`, `gemini-3.5-flash`, `gemini-3.5-flash-lite`, `gemini-3.1-flash-lite`
|
|
18
18
|
- **xAI:** `grok-4.6`, `grok-4.5`, `grok-4.3`, `grok-4.20-multi-agent-beta-0309`, `grok-4.20-beta-0309-reasoning`, `grok-4.20-beta-0309-non-reasoning`
|
|
19
19
|
- **OpenRouter:** not enumerated (huge/dynamic catalog) — any `openrouter/<vendor>/<model>` slug routes through, e.g. `openrouter/anthropic/claude-sonnet-4.5`.
|
|
20
20
|
- **vLLM / SGLang:** not enumerated (self-hosted) — any `vllm/<served-model>` or `sglang/<served-model>` routes through to the configured server, e.g. `sglang/Qwen/Qwen3.6-35B-A3B-FP8`.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: llmshim
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.4
|
|
4
4
|
Classifier: Development Status :: 4 - Beta
|
|
5
5
|
Classifier: Intended Audience :: Developers
|
|
6
6
|
Classifier: License :: OSI Approved :: MIT License
|
|
@@ -37,11 +37,30 @@ llmshim.configure(
|
|
|
37
37
|
openai="sk-...",
|
|
38
38
|
gemini="AIza...",
|
|
39
39
|
xai="xai-...",
|
|
40
|
+
openrouter="sk-or-...",
|
|
40
41
|
)
|
|
41
42
|
```
|
|
42
43
|
|
|
43
44
|
Or from the CLI: `llmshim configure`
|
|
44
45
|
|
|
46
|
+
### Self-hosted servers (vLLM / SGLang)
|
|
47
|
+
|
|
48
|
+
vLLM and SGLang are configured via **environment variables** (not
|
|
49
|
+
`config.toml`) — the auto-spawned proxy inherits them from your Python
|
|
50
|
+
process. Set them before your first call:
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
import os
|
|
54
|
+
|
|
55
|
+
os.environ["VLLM_BASE_URL"] = "http://localhost:8000/v1"
|
|
56
|
+
os.environ["VLLM_API_KEY"] = "..." # optional
|
|
57
|
+
os.environ["SGLANG_BASE_URL"] = "http://localhost:30000/v1"
|
|
58
|
+
os.environ["SGLANG_API_KEY"] = "..." # optional
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Then address them via the model string — `vllm/<served-model>` or
|
|
62
|
+
`sglang/<served-model>` (see the model table below).
|
|
63
|
+
|
|
45
64
|
## Chat
|
|
46
65
|
|
|
47
66
|
```python
|
|
@@ -106,17 +125,63 @@ print(f"GPT: {r2['message']['content']}")
|
|
|
106
125
|
|
|
107
126
|
## Reasoning / Thinking
|
|
108
127
|
|
|
128
|
+
Two provider-agnostic knobs control reasoning; both are clamped to the
|
|
129
|
+
nearest tier the target model supports:
|
|
130
|
+
|
|
131
|
+
- `reasoning_effort` — `"none"`, `"low"`, `"medium"`, `"high"`, `"xhigh"`, or `"max"`
|
|
132
|
+
- `reasoning_mode` — `"standard"` (default) or `"pro"` (requests substantially
|
|
133
|
+
more model work; native on OpenAI gpt-5.6/-pro, emulated as an effort bump
|
|
134
|
+
elsewhere)
|
|
135
|
+
|
|
109
136
|
```python
|
|
110
137
|
resp = llmshim.chat(
|
|
111
|
-
"claude-sonnet-
|
|
138
|
+
"claude-sonnet-5",
|
|
112
139
|
"Solve: x^2 - 5x + 6 = 0",
|
|
113
140
|
max_tokens=4000,
|
|
114
141
|
reasoning_effort="high",
|
|
142
|
+
reasoning_mode="pro",
|
|
115
143
|
)
|
|
116
144
|
print(resp["reasoning"]) # thinking content
|
|
117
145
|
print(resp["message"]["content"]) # answer
|
|
118
146
|
```
|
|
119
147
|
|
|
148
|
+
For full native control, bypass the unified mapping with a namespaced
|
|
149
|
+
`provider_config` (see below), e.g.
|
|
150
|
+
`provider_config={"x-anthropic": {"thinking": {"type": "enabled", "budget_tokens": 4000}}}`.
|
|
151
|
+
|
|
152
|
+
## Provider-Specific Controls (`provider_config`)
|
|
153
|
+
|
|
154
|
+
`provider_config` merges into the request **root** and carries anything the
|
|
155
|
+
unified `config` doesn't cover. Native provider controls MUST be **namespaced**
|
|
156
|
+
per provider (`x-anthropic`, `x-openai`, `x-gemini`, `x-openrouter`, `x-vllm`,
|
|
157
|
+
`x-sglang`); it also carries the top-level `tools`, `response_format`, and
|
|
158
|
+
`reasoning_summary` keys.
|
|
159
|
+
|
|
160
|
+
```python
|
|
161
|
+
resp = llmshim.chat(
|
|
162
|
+
"anthropic/claude-sonnet-5",
|
|
163
|
+
"Solve this step by step: 17 * 23",
|
|
164
|
+
max_tokens=4000,
|
|
165
|
+
provider_config={
|
|
166
|
+
# native Anthropic extended-thinking control
|
|
167
|
+
"x-anthropic": {"thinking": {"type": "enabled", "budget_tokens": 4000}},
|
|
168
|
+
# structured output
|
|
169
|
+
"response_format": {"type": "json_object"},
|
|
170
|
+
},
|
|
171
|
+
)
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
OpenRouter routing preferences use the `x-openrouter` namespace:
|
|
175
|
+
|
|
176
|
+
```python
|
|
177
|
+
resp = llmshim.chat(
|
|
178
|
+
"openrouter/anthropic/claude-sonnet-4.5",
|
|
179
|
+
"Hello",
|
|
180
|
+
max_tokens=200,
|
|
181
|
+
provider_config={"x-openrouter": {"provider": {"sort": "throughput"}}},
|
|
182
|
+
)
|
|
183
|
+
```
|
|
184
|
+
|
|
120
185
|
## Tool Use / Function Calling
|
|
121
186
|
|
|
122
187
|
```python
|
|
@@ -147,7 +212,7 @@ resp = llmshim.chat(
|
|
|
147
212
|
"anthropic/claude-sonnet-4-6",
|
|
148
213
|
"Hello",
|
|
149
214
|
max_tokens=100,
|
|
150
|
-
fallback=["openai/gpt-5.
|
|
215
|
+
fallback=["openai/gpt-5.6-sol", "gemini/gemini-3.5-flash"],
|
|
151
216
|
)
|
|
152
217
|
```
|
|
153
218
|
|
|
@@ -220,10 +285,21 @@ billed provider calls; run it only when you deliberately want to hit real APIs.
|
|
|
220
285
|
|
|
221
286
|
| Provider | Models |
|
|
222
287
|
|----------|--------|
|
|
223
|
-
| OpenAI | `gpt-5.5`, `gpt-5.5-pro`, `gpt-5.4`, `gpt-5.4-pro`, `gpt-5.4-mini`, `gpt-5.4-nano` |
|
|
288
|
+
| OpenAI | `gpt-5.6-sol`, `gpt-5.6-terra`, `gpt-5.6-luna`, `gpt-5.5`, `gpt-5.5-pro`, `gpt-5.4`, `gpt-5.4-pro`, `gpt-5.4-mini`, `gpt-5.4-nano` |
|
|
224
289
|
| Anthropic | `claude-opus-5`, `claude-opus-4-8`, `claude-sonnet-5`, `claude-opus-4-7`, `claude-opus-4-6`, `claude-sonnet-4-6`, `claude-haiku-4-5-20251001` |
|
|
225
|
-
| Gemini | `gemini-3.5-flash`, `gemini-3.
|
|
290
|
+
| Gemini | `gemini-3.7-flash`, `gemini-3.6-flash`, `gemini-3.5-flash`, `gemini-3.5-flash-lite`, `gemini-3.1-flash-lite` |
|
|
226
291
|
| xAI | `grok-4.6`, `grok-4.5`, `grok-4.3`, `grok-4.20-multi-agent-beta-0309`, `grok-4.20-beta-0309-reasoning`, `grok-4.20-beta-0309-non-reasoning` |
|
|
227
292
|
|
|
228
293
|
Call `llmshim.models()` for the live list filtered to your configured providers.
|
|
229
294
|
|
|
295
|
+
### OpenRouter & self-hosted (vLLM / SGLang)
|
|
296
|
+
|
|
297
|
+
These providers are addressed by the model string plus environment variables —
|
|
298
|
+
any model the upstream serves is reachable, so they aren't in the table above.
|
|
299
|
+
|
|
300
|
+
| Provider | Address as | Env vars | Native controls |
|
|
301
|
+
|----------|-----------|----------|-----------------|
|
|
302
|
+
| OpenRouter | `openrouter/<vendor>/<model>` (e.g. `openrouter/anthropic/claude-sonnet-4.5`) | `OPENROUTER_API_KEY` (or `llmshim.configure(openrouter=...)`) | `provider_config={"x-openrouter": {...}}` (`provider`, `models`, `transforms`) |
|
|
303
|
+
| vLLM | `vllm/<served-model>` | `VLLM_BASE_URL` (+ optional `VLLM_API_KEY`) | `provider_config={"x-vllm": {...}}` |
|
|
304
|
+
| SGLang | `sglang/<served-model>` | `SGLANG_BASE_URL` (+ optional `SGLANG_API_KEY`) | `provider_config={"x-sglang": {...}}` |
|
|
305
|
+
|
|
@@ -364,7 +364,7 @@ Standard library only. Full docs: [`clients/ruby/README.md`](clients/ruby/README
|
|
|
364
364
|
|----------|--------|-------------------|
|
|
365
365
|
| **OpenAI** | `gpt-5.6-sol`, `gpt-5.6-terra`, `gpt-5.6-luna`, `gpt-5.5`, `gpt-5.5-pro`, `gpt-5.4`, `gpt-5.4-pro`, `gpt-5.4-mini`, `gpt-5.4-nano` | Yes (summaries) |
|
|
366
366
|
| **Anthropic** | `claude-opus-5`, `claude-opus-4-8`, `claude-sonnet-5`, `claude-opus-4-7`, `claude-opus-4-6`, `claude-sonnet-4-6`, `claude-haiku-4-5-20251001` | Yes (full thinking) |
|
|
367
|
-
| **Google Gemini** | `gemini-3.5-flash`, `gemini-3.
|
|
367
|
+
| **Google Gemini** | `gemini-3.7-flash`, `gemini-3.6-flash`, `gemini-3.5-flash`, `gemini-3.5-flash-lite`, `gemini-3.1-flash-lite` | Yes (thought summaries) |
|
|
368
368
|
| **xAI** | `grok-4.6`, `grok-4.5`, `grok-4.3`, `grok-4.20-multi-agent-beta-0309`, `grok-4.20-beta-0309-reasoning`, `grok-4.20-beta-0309-non-reasoning` | No (hidden) |
|
|
369
369
|
|
|
370
370
|
Use a bare model name (auto-detected by prefix) or an explicit `provider/model` string.
|
|
@@ -117,7 +117,7 @@ token budget scaled from `max_tokens` and floored at 1024:
|
|
|
117
117
|
Gemini uses the four-rung
|
|
118
118
|
`generationConfig.thinkingConfig.thinkingLevel` enum:
|
|
119
119
|
|
|
120
|
-
| unified | gemini-3.5-flash / 3-flash-
|
|
120
|
+
| unified | gemini-3.5-flash / 3.6-flash / 3.5-flash-lite / 3.1-flash-lite | gemini-3.7-flash (and gemini-3.1-pro) |
|
|
121
121
|
|---|---|---|
|
|
122
122
|
| `none` | `minimal` (zero thinking tokens) | **`low`** (this model cannot disable thinking) |
|
|
123
123
|
| `low` | `low` | `low` |
|
|
@@ -126,8 +126,8 @@ Gemini uses the four-rung
|
|
|
126
126
|
| `xhigh` | **`high`** | **`high`** |
|
|
127
127
|
| `max` | **`high`** | **`high`** |
|
|
128
128
|
|
|
129
|
-
Gemini 3.1 Pro
|
|
130
|
-
to
|
|
129
|
+
Gemini 3.7 Flash and Gemini 3.1 Pro reject both `minimal` and `thinkingBudget: 0`, so `none`
|
|
130
|
+
clamps to their `low` floor (verified live). Other flash models accept `minimal` and can disable thinking. The legacy integer `thinkingBudget` remains available only
|
|
131
131
|
through `x-gemini.thinkingConfig`.
|
|
132
132
|
|
|
133
133
|
### xAI Responses API
|
|
@@ -14,7 +14,7 @@ the ID and display label.
|
|
|
14
14
|
|
|
15
15
|
## Registered catalog
|
|
16
16
|
|
|
17
|
-
The current registry contains
|
|
17
|
+
The current registry contains 27 entries, newest first within each provider.
|
|
18
18
|
This page mirrors `src/models.rs`; use runtime discovery rather than parsing
|
|
19
19
|
this table in applications.
|
|
20
20
|
|
|
@@ -48,9 +48,11 @@ this table in applications.
|
|
|
48
48
|
|
|
49
49
|
| ID | Display name |
|
|
50
50
|
|---|---|
|
|
51
|
+
| `gemini/gemini-3.7-flash` | Gemini 3.7 Flash |
|
|
52
|
+
| `gemini/gemini-3.6-flash` | Gemini 3.6 Flash |
|
|
51
53
|
| `gemini/gemini-3.5-flash` | Gemini 3.5 Flash |
|
|
52
|
-
| `gemini/gemini-3.
|
|
53
|
-
| `gemini/gemini-3-flash-
|
|
54
|
+
| `gemini/gemini-3.5-flash-lite` | Gemini 3.5 Flash Lite |
|
|
55
|
+
| `gemini/gemini-3.1-flash-lite` | Gemini 3.1 Flash Lite |
|
|
54
56
|
|
|
55
57
|
### xAI
|
|
56
58
|
|
|
@@ -14,6 +14,8 @@ Spec-faithful TypedDicts live in ``llmshim.types`` (also re-exported here) for
|
|
|
14
14
|
static type-checking of requests, responses, and stream events.
|
|
15
15
|
"""
|
|
16
16
|
|
|
17
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
18
|
+
|
|
17
19
|
from llmshim import types
|
|
18
20
|
from llmshim._client import LlmShimError, chat, configure, health, models, stream
|
|
19
21
|
from llmshim.types import (
|
|
@@ -55,4 +57,7 @@ __all__ = [
|
|
|
55
57
|
"ToolCall",
|
|
56
58
|
"Usage",
|
|
57
59
|
]
|
|
58
|
-
|
|
60
|
+
try:
|
|
61
|
+
__version__ = version("llmshim")
|
|
62
|
+
except PackageNotFoundError: # not installed (e.g. running from source tree)
|
|
63
|
+
__version__ = "0.0.0"
|
|
@@ -89,6 +89,7 @@ def configure(
|
|
|
89
89
|
anthropic: Optional[str] = None,
|
|
90
90
|
gemini: Optional[str] = None,
|
|
91
91
|
xai: Optional[str] = None,
|
|
92
|
+
openrouter: Optional[str] = None,
|
|
92
93
|
) -> None:
|
|
93
94
|
"""Configure API keys. Writes to ~/.llmshim/config.toml.
|
|
94
95
|
|
|
@@ -98,6 +99,19 @@ def configure(
|
|
|
98
99
|
Usage:
|
|
99
100
|
import llmshim
|
|
100
101
|
llmshim.configure(anthropic="sk-ant-...", openai="sk-...")
|
|
102
|
+
|
|
103
|
+
Self-hosted servers (vLLM, SGLang) are configured via environment
|
|
104
|
+
variables — NOT config.toml — which the auto-spawned proxy inherits from
|
|
105
|
+
the Python process. Set them before your first call, e.g.::
|
|
106
|
+
|
|
107
|
+
import os
|
|
108
|
+
os.environ["VLLM_BASE_URL"] = "http://localhost:8000/v1"
|
|
109
|
+
os.environ["VLLM_API_KEY"] = "..." # optional
|
|
110
|
+
os.environ["SGLANG_BASE_URL"] = "http://localhost:30000/v1"
|
|
111
|
+
os.environ["SGLANG_API_KEY"] = "..." # optional
|
|
112
|
+
|
|
113
|
+
Then address them via the model string, e.g. ``vllm/<served-model>`` or
|
|
114
|
+
``sglang/<served-model>``.
|
|
101
115
|
"""
|
|
102
116
|
config_dir = Path.home() / ".llmshim"
|
|
103
117
|
config_path = config_dir / "config.toml"
|
|
@@ -125,6 +139,8 @@ def configure(
|
|
|
125
139
|
keys["gemini"] = gemini
|
|
126
140
|
if xai is not None:
|
|
127
141
|
keys["xai"] = xai
|
|
142
|
+
if openrouter is not None:
|
|
143
|
+
keys["openrouter"] = openrouter
|
|
128
144
|
|
|
129
145
|
# Write back
|
|
130
146
|
config_dir.mkdir(parents=True, exist_ok=True)
|
|
@@ -152,6 +168,8 @@ def configure(
|
|
|
152
168
|
os.environ["GEMINI_API_KEY"] = gemini
|
|
153
169
|
if xai:
|
|
154
170
|
os.environ["XAI_API_KEY"] = xai
|
|
171
|
+
if openrouter:
|
|
172
|
+
os.environ["OPENROUTER_API_KEY"] = openrouter
|
|
155
173
|
|
|
156
174
|
# If server is already running, it won't pick up new keys until restart.
|
|
157
175
|
# Force restart on next call.
|
|
@@ -22,9 +22,11 @@ const MODELS: &[(&str, &str)] = &[
|
|
|
22
22
|
("anthropic/claude-opus-4-6", "Claude Opus 4.6"),
|
|
23
23
|
("anthropic/claude-sonnet-4-6", "Claude Sonnet 4.6"),
|
|
24
24
|
("anthropic/claude-haiku-4-5-20251001", "Claude Haiku 4.5"),
|
|
25
|
+
("gemini/gemini-3.7-flash", "Gemini 3.7 Flash"),
|
|
26
|
+
("gemini/gemini-3.6-flash", "Gemini 3.6 Flash"),
|
|
25
27
|
("gemini/gemini-3.5-flash", "Gemini 3.5 Flash"),
|
|
26
|
-
("gemini/gemini-3.
|
|
27
|
-
("gemini/gemini-3-flash-
|
|
28
|
+
("gemini/gemini-3.5-flash-lite", "Gemini 3.5 Flash Lite"),
|
|
29
|
+
("gemini/gemini-3.1-flash-lite", "Gemini 3.1 Flash Lite"),
|
|
28
30
|
("xai/grok-4.6", "Grok 4.6"),
|
|
29
31
|
("xai/grok-4.5", "Grok 4.5"),
|
|
30
32
|
("xai/grok-4.3", "Grok 4.3"),
|
|
@@ -325,6 +325,24 @@ pub const MODELS: &[ModelInfo] = &[
|
|
|
325
325
|
max_output_tokens: Some(64_000),
|
|
326
326
|
capabilities: CAPS_FULL,
|
|
327
327
|
},
|
|
328
|
+
ModelInfo {
|
|
329
|
+
id: "gemini/gemini-3.7-flash",
|
|
330
|
+
provider: "gemini",
|
|
331
|
+
name: "gemini-3.7-flash",
|
|
332
|
+
label: "Gemini 3.7 Flash",
|
|
333
|
+
context_window_tokens: Some(1_048_576),
|
|
334
|
+
max_output_tokens: Some(65_536),
|
|
335
|
+
capabilities: CAPS_STD,
|
|
336
|
+
},
|
|
337
|
+
ModelInfo {
|
|
338
|
+
id: "gemini/gemini-3.6-flash",
|
|
339
|
+
provider: "gemini",
|
|
340
|
+
name: "gemini-3.6-flash",
|
|
341
|
+
label: "Gemini 3.6 Flash",
|
|
342
|
+
context_window_tokens: Some(1_048_576),
|
|
343
|
+
max_output_tokens: Some(65_536),
|
|
344
|
+
capabilities: CAPS_STD,
|
|
345
|
+
},
|
|
328
346
|
ModelInfo {
|
|
329
347
|
id: "gemini/gemini-3.5-flash",
|
|
330
348
|
provider: "gemini",
|
|
@@ -335,19 +353,19 @@ pub const MODELS: &[ModelInfo] = &[
|
|
|
335
353
|
capabilities: CAPS_FULL,
|
|
336
354
|
},
|
|
337
355
|
ModelInfo {
|
|
338
|
-
id: "gemini/gemini-3.
|
|
356
|
+
id: "gemini/gemini-3.5-flash-lite",
|
|
339
357
|
provider: "gemini",
|
|
340
|
-
name: "gemini-3.
|
|
341
|
-
label: "Gemini 3.
|
|
358
|
+
name: "gemini-3.5-flash-lite",
|
|
359
|
+
label: "Gemini 3.5 Flash Lite",
|
|
342
360
|
context_window_tokens: Some(1_048_576),
|
|
343
361
|
max_output_tokens: Some(65_536),
|
|
344
362
|
capabilities: CAPS_STD,
|
|
345
363
|
},
|
|
346
364
|
ModelInfo {
|
|
347
|
-
id: "gemini/gemini-3-flash-
|
|
365
|
+
id: "gemini/gemini-3.1-flash-lite",
|
|
348
366
|
provider: "gemini",
|
|
349
|
-
name: "gemini-3-flash-
|
|
350
|
-
label: "Gemini 3 Flash",
|
|
367
|
+
name: "gemini-3.1-flash-lite",
|
|
368
|
+
label: "Gemini 3.1 Flash Lite",
|
|
351
369
|
context_window_tokens: Some(1_048_576),
|
|
352
370
|
max_output_tokens: Some(65_536),
|
|
353
371
|
capabilities: CAPS_STD,
|
|
@@ -581,11 +581,15 @@ fn transform_response_to_openai(model: &str, resp: &Value) -> Result<Value> {
|
|
|
581
581
|
}))
|
|
582
582
|
}
|
|
583
583
|
|
|
584
|
-
/// Models that cannot turn thinking off: gemini-3.1-pro
|
|
585
|
-
/// thinkingLevel "minimal" and thinkingBudget 0
|
|
586
|
-
///
|
|
584
|
+
/// Models that cannot turn thinking off: gemini-3.1-pro and gemini-3.7-flash
|
|
585
|
+
/// reject thinkingLevel "minimal" (and thinkingBudget 0) — verified live.
|
|
586
|
+
/// Unified effort "none" clamps to "low" for them.
|
|
587
587
|
fn cannot_disable_thinking(model: &str) -> bool {
|
|
588
|
-
model.to_lowercase()
|
|
588
|
+
let m = model.to_lowercase();
|
|
589
|
+
// gemini-3.1-pro and gemini-3.7-flash reject thinkingLevel "minimal" (and
|
|
590
|
+
// thinkingBudget 0), verified live — unified "none" clamps to "low". Other
|
|
591
|
+
// flash models (3.5/3.6/3.5-lite) accept "minimal" and can disable thinking.
|
|
592
|
+
m.contains("3.1-pro") || m.contains("3.7-flash")
|
|
589
593
|
}
|
|
590
594
|
|
|
591
595
|
impl Provider for Gemini {
|
|
@@ -436,3 +436,65 @@ async fn all_three_providers_same_shape() {
|
|
|
436
436
|
);
|
|
437
437
|
}
|
|
438
438
|
}
|
|
439
|
+
|
|
440
|
+
#[tokio::test]
|
|
441
|
+
#[ignore]
|
|
442
|
+
async fn gemini_3_7_flash_completion() {
|
|
443
|
+
if std::env::var("GEMINI_API_KEY").is_err() {
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
let router = router();
|
|
447
|
+
let req = json!({
|
|
448
|
+
"model": "gemini/gemini-3.7-flash",
|
|
449
|
+
"messages": [{"role": "user", "content": "In one short sentence, what is Rust?"}],
|
|
450
|
+
"max_tokens": 2000,
|
|
451
|
+
});
|
|
452
|
+
let resp = llmshim::completion(&router, &req).await.unwrap();
|
|
453
|
+
let content = resp["choices"][0]["message"]["content"]
|
|
454
|
+
.as_str()
|
|
455
|
+
.unwrap_or("");
|
|
456
|
+
assert!(!content.is_empty(), "expected a response, got: {resp}");
|
|
457
|
+
println!("gemini-3.7-flash said: {content}");
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
#[tokio::test]
|
|
461
|
+
#[ignore]
|
|
462
|
+
async fn gemini_3_7_flash_none_clamps_to_low() {
|
|
463
|
+
// gemini-3.7-flash 400s on thinkingLevel "minimal"; llmshim must clamp
|
|
464
|
+
// reasoning_effort "none" to "low". Asserts success, not a 400.
|
|
465
|
+
if std::env::var("GEMINI_API_KEY").is_err() {
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
let router = router();
|
|
469
|
+
let req = json!({
|
|
470
|
+
"model": "gemini/gemini-3.7-flash",
|
|
471
|
+
"messages": [{"role": "user", "content": "Say hi."}],
|
|
472
|
+
"max_tokens": 2000,
|
|
473
|
+
"reasoning_effort": "none",
|
|
474
|
+
});
|
|
475
|
+
let resp = llmshim::completion(&router, &req)
|
|
476
|
+
.await
|
|
477
|
+
.expect("none must clamp to low for gemini-3.7-flash, not 400");
|
|
478
|
+
assert_eq!(resp["object"], "chat.completion");
|
|
479
|
+
println!("gemini-3.7-flash none->low clamp OK");
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
#[tokio::test]
|
|
483
|
+
#[ignore]
|
|
484
|
+
async fn gemini_3_1_flash_lite_completion() {
|
|
485
|
+
if std::env::var("GEMINI_API_KEY").is_err() {
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
let router = router();
|
|
489
|
+
let req = json!({
|
|
490
|
+
"model": "gemini/gemini-3.1-flash-lite",
|
|
491
|
+
"messages": [{"role": "user", "content": "In one short sentence, what is Rust?"}],
|
|
492
|
+
"max_tokens": 2000,
|
|
493
|
+
});
|
|
494
|
+
let resp = llmshim::completion(&router, &req).await.unwrap();
|
|
495
|
+
let content = resp["choices"][0]["message"]["content"]
|
|
496
|
+
.as_str()
|
|
497
|
+
.unwrap_or("");
|
|
498
|
+
assert!(!content.is_empty(), "expected a response, got: {resp}");
|
|
499
|
+
println!("gemini-3.1-flash-lite said: {content}");
|
|
500
|
+
}
|
|
@@ -1529,3 +1529,32 @@ fn request_strips_foreign_reasoning_signature() {
|
|
|
1529
1529
|
assert!(!body.contains("redacted-should-not-leak"));
|
|
1530
1530
|
assert!(!body.contains("reasoning_signature"));
|
|
1531
1531
|
}
|
|
1532
|
+
|
|
1533
|
+
#[test]
|
|
1534
|
+
fn reasoning_none_clamps_per_model_disable_capability() {
|
|
1535
|
+
// gemini-3.7-flash rejects thinkingLevel "minimal" (verified live) -> "none"
|
|
1536
|
+
// clamps to "low". Other flash models accept "minimal" -> "none" stays "minimal".
|
|
1537
|
+
let p = provider();
|
|
1538
|
+
let req = json!({
|
|
1539
|
+
"model": "x",
|
|
1540
|
+
"messages": [{"role": "user", "content": "hi"}],
|
|
1541
|
+
"reasoning_effort": "none",
|
|
1542
|
+
});
|
|
1543
|
+
let r = p.transform_request("gemini-3.7-flash", &req).unwrap();
|
|
1544
|
+
assert_eq!(
|
|
1545
|
+
r.body["generationConfig"]["thinkingConfig"]["thinkingLevel"], "low",
|
|
1546
|
+
"gemini-3.7-flash cannot disable thinking -> none clamps to low"
|
|
1547
|
+
);
|
|
1548
|
+
for m in [
|
|
1549
|
+
"gemini-3.6-flash",
|
|
1550
|
+
"gemini-3.5-flash",
|
|
1551
|
+
"gemini-3.5-flash-lite",
|
|
1552
|
+
"gemini-3.1-flash-lite",
|
|
1553
|
+
] {
|
|
1554
|
+
let r = p.transform_request(m, &req).unwrap();
|
|
1555
|
+
assert_eq!(
|
|
1556
|
+
r.body["generationConfig"]["thinkingConfig"]["thinkingLevel"], "minimal",
|
|
1557
|
+
"{m} can disable thinking -> none maps to minimal"
|
|
1558
|
+
);
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|