llmshim 0.3.3 → 0.3.4
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.
- checksums.yaml +4 -4
- data/README.md +87 -10
- data/lib/llmshim/client.rb +8 -0
- data/lib/llmshim/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: dc7d1f66fec1e50c72a93b0470ef6df88d0b61be7fbc3828972280cda625caa1
|
|
4
|
+
data.tar.gz: 614251f70adf8b39377307c04698fa09db451266006e590628e18b8c8a4fad9c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 68b3fcc22b3371f110a6c5dcd123bbedb4e673345fd9ec7ff4b919eb374dffbec070cc55913fa82cf8ab9d4afbe7d445f99a926700465147c521057f5689039d
|
|
7
|
+
data.tar.gz: e0bf20f098bf17496665d105abd69d79bff92513dc11141c64fadef4a753e4f9a57fc9ac41e9347a81490eeaa48b54bfe520160ead394c4559969b5104b0def1
|
data/README.md
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
A thin, dependency-free Ruby client for the [llmshim](https://github.com/sanjay920/llmshim)
|
|
4
4
|
multi-provider LLM proxy. Send OpenAI-style chat requests to a running llmshim
|
|
5
|
-
proxy and let it translate to OpenAI, Anthropic, Google Gemini,
|
|
5
|
+
proxy and let it translate to OpenAI, Anthropic, Google Gemini, xAI, OpenRouter,
|
|
6
|
+
vLLM, or SGLang.
|
|
6
7
|
|
|
7
8
|
This gem talks to a proxy over plain HTTP — it does **not** spawn the Rust
|
|
8
9
|
binary. Start the proxy separately (`llmshim proxy`, default `http://localhost:3000`).
|
|
@@ -38,10 +39,22 @@ gem install ./llmshim-*.gem
|
|
|
38
39
|
llmshim proxy # listens on 0.0.0.0:3000 by default
|
|
39
40
|
```
|
|
40
41
|
|
|
41
|
-
Configure provider
|
|
42
|
-
`OPENAI_API_KEY` / `ANTHROPIC_API_KEY` / `GEMINI_API_KEY` / `XAI_API_KEY`
|
|
42
|
+
Configure provider credentials for the proxy via `llmshim configure` or
|
|
43
43
|
environment variables. The Ruby client never sees your keys — the proxy holds them.
|
|
44
44
|
|
|
45
|
+
| Provider | Model string form | Env vars |
|
|
46
|
+
| ---------- | --------------------------------------- | ------------------------------------- |
|
|
47
|
+
| OpenAI | `openai/gpt-5.6-sol` | `OPENAI_API_KEY` |
|
|
48
|
+
| Anthropic | `anthropic/claude-sonnet-5` | `ANTHROPIC_API_KEY` |
|
|
49
|
+
| Gemini | `gemini/gemini-3.5-flash` | `GEMINI_API_KEY` |
|
|
50
|
+
| xAI | `xai/grok-4.5` | `XAI_API_KEY` |
|
|
51
|
+
| OpenRouter | `openrouter/anthropic/claude-sonnet-4.5` | `OPENROUTER_API_KEY` |
|
|
52
|
+
| vLLM | `vllm/<served-model>` | `VLLM_BASE_URL` (+ optional `VLLM_API_KEY`) |
|
|
53
|
+
| SGLang | `sglang/<served-model>` | `SGLANG_BASE_URL` (+ optional `SGLANG_API_KEY`) |
|
|
54
|
+
|
|
55
|
+
vLLM and SGLang are self-hosted (local or remote) OpenAI-compatible servers;
|
|
56
|
+
point the proxy at them with `VLLM_BASE_URL` / `SGLANG_BASE_URL`.
|
|
57
|
+
|
|
45
58
|
## Quickstart
|
|
46
59
|
|
|
47
60
|
```ruby
|
|
@@ -49,10 +62,12 @@ require "llmshim"
|
|
|
49
62
|
|
|
50
63
|
client = Llmshim::Client.new(base_url: "http://localhost:3000")
|
|
51
64
|
|
|
52
|
-
resp = client.chat(model: "claude-sonnet-
|
|
65
|
+
resp = client.chat(model: "anthropic/claude-sonnet-5", messages: "What is Rust?")
|
|
53
66
|
puts resp.content # => "Rust is a systems programming language..."
|
|
54
67
|
puts resp.provider # => "anthropic"
|
|
68
|
+
puts resp.latency_ms # => 1234 (round-trip latency in ms)
|
|
55
69
|
puts resp.usage.total_tokens
|
|
70
|
+
puts resp.usage.reasoning_tokens # thinking tokens billed (nil if none)
|
|
56
71
|
```
|
|
57
72
|
|
|
58
73
|
`messages:` accepts a single string (treated as one user message) or an array
|
|
@@ -71,6 +86,12 @@ resp = client.chat(
|
|
|
71
86
|
)
|
|
72
87
|
```
|
|
73
88
|
|
|
89
|
+
### Streaming vs. non-streaming
|
|
90
|
+
|
|
91
|
+
`chat` is non-streaming and returns a single `Llmshim::ChatResponse`. Passing
|
|
92
|
+
`stream: true` to `chat` raises `ArgumentError` — the proxy would emit SSE that
|
|
93
|
+
`chat` cannot parse. Use `stream` (below) for token-by-token output.
|
|
94
|
+
|
|
74
95
|
### Module-level convenience
|
|
75
96
|
|
|
76
97
|
A shared default client (base URL from `LLMSHIM_BASE_URL`, else `http://localhost:3000`):
|
|
@@ -89,7 +110,7 @@ puts resp.content
|
|
|
89
110
|
`done`, `error`.
|
|
90
111
|
|
|
91
112
|
```ruby
|
|
92
|
-
client.stream(model: "claude-sonnet-
|
|
113
|
+
client.stream(model: "anthropic/claude-sonnet-5", messages: "Write a haiku") do |event|
|
|
93
114
|
case event.type
|
|
94
115
|
when "reasoning" then print event.text # thinking tokens
|
|
95
116
|
when "content" then print event.text # answer tokens
|
|
@@ -114,7 +135,7 @@ text = events.select(&:content?).map(&:text).join
|
|
|
114
135
|
|
|
115
136
|
```ruby
|
|
116
137
|
resp = client.chat(
|
|
117
|
-
model: "anthropic/claude-sonnet-
|
|
138
|
+
model: "anthropic/claude-sonnet-5",
|
|
118
139
|
messages: "What's the weather in SF?",
|
|
119
140
|
tools: [
|
|
120
141
|
{ type: "function",
|
|
@@ -123,10 +144,10 @@ resp = client.chat(
|
|
|
123
144
|
properties: { city: { type: "string" } } } } }
|
|
124
145
|
],
|
|
125
146
|
tool_choice: "auto",
|
|
126
|
-
#
|
|
127
|
-
provider_config: { thinking: { type: "
|
|
147
|
+
# Provider-specific controls, namespaced under x-<provider> (see below):
|
|
148
|
+
provider_config: { "x-anthropic" => { thinking: { type: "enabled", budget_tokens: 4000 } } },
|
|
128
149
|
# Try these models if the primary fails with a retryable error:
|
|
129
|
-
fallback: ["openai/gpt-5.
|
|
150
|
+
fallback: ["openai/gpt-5.6-sol", "gemini/gemini-3.5-flash"]
|
|
130
151
|
)
|
|
131
152
|
|
|
132
153
|
resp.message.tool_calls.each do |tc|
|
|
@@ -136,7 +157,63 @@ end
|
|
|
136
157
|
|
|
137
158
|
`tools` and `tool_choice` are folded into `provider_config` (passed straight
|
|
138
159
|
through to the provider). `max_tokens`, `temperature`, `top_p`, `top_k`,
|
|
139
|
-
`stop`, and `
|
|
160
|
+
`stop`, `reasoning_effort`, and `reasoning_mode` are folded into the request
|
|
161
|
+
`config`.
|
|
162
|
+
|
|
163
|
+
### Provider passthrough (`provider_config`)
|
|
164
|
+
|
|
165
|
+
`provider_config` merges at the **request root**, so native provider controls
|
|
166
|
+
must be namespaced under an `x-<provider>` key — a bare `thinking:` at the top
|
|
167
|
+
level is ignored. Use the namespace matching the target provider:
|
|
168
|
+
|
|
169
|
+
| Provider | Namespace |
|
|
170
|
+
| ---------- | --------------- |
|
|
171
|
+
| OpenAI | `x-openai` |
|
|
172
|
+
| Anthropic | `x-anthropic` |
|
|
173
|
+
| Gemini | `x-gemini` |
|
|
174
|
+
| OpenRouter | `x-openrouter` |
|
|
175
|
+
| vLLM | `x-vllm` |
|
|
176
|
+
| SGLang | `x-sglang` |
|
|
177
|
+
|
|
178
|
+
```ruby
|
|
179
|
+
# Anthropic extended thinking:
|
|
180
|
+
provider_config: { "x-anthropic" => { thinking: { type: "enabled", budget_tokens: 4000 } } }
|
|
181
|
+
|
|
182
|
+
# OpenRouter provider routing (also accepts `models`, `transforms`):
|
|
183
|
+
provider_config: { "x-openrouter" => { provider: { sort: "throughput" } } }
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
`provider_config` also carries a few root-level keys the proxy understands
|
|
187
|
+
directly (not namespaced):
|
|
188
|
+
|
|
189
|
+
- `tools` / `tool_choice` — folded in for you from the `tools:`/`tool_choice:` kwargs.
|
|
190
|
+
- `response_format` — e.g. `{ type: "json_object" }` for structured output.
|
|
191
|
+
- `reasoning_summary` — request a summary of the model's reasoning.
|
|
192
|
+
|
|
193
|
+
```ruby
|
|
194
|
+
provider_config: {
|
|
195
|
+
response_format: { type: "json_object" },
|
|
196
|
+
reasoning_summary: "auto"
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Reasoning controls
|
|
201
|
+
|
|
202
|
+
Two provider-agnostic knobs live in `config` (pass them as top-level kwargs):
|
|
203
|
+
|
|
204
|
+
- `reasoning_effort` — one of `none`, `low`, `medium`, `high`, `xhigh`, `max`.
|
|
205
|
+
Clamped to the nearest tier the target model supports.
|
|
206
|
+
- `reasoning_mode` — `standard` or `pro`. `pro` is native on OpenAI gpt-5.6/-pro
|
|
207
|
+
models and emulated as a one-tier effort bump elsewhere.
|
|
208
|
+
|
|
209
|
+
```ruby
|
|
210
|
+
resp = client.chat(
|
|
211
|
+
model: "openai/gpt-5.6-sol",
|
|
212
|
+
messages: "Prove that sqrt(2) is irrational.",
|
|
213
|
+
reasoning_effort: "high",
|
|
214
|
+
reasoning_mode: "pro"
|
|
215
|
+
)
|
|
216
|
+
```
|
|
140
217
|
|
|
141
218
|
## Models and health
|
|
142
219
|
|
data/lib/llmshim/client.rb
CHANGED
|
@@ -43,7 +43,15 @@ module Llmshim
|
|
|
43
43
|
# @param opts [Hash] see #build_body (max_tokens, temperature, top_p, top_k,
|
|
44
44
|
# stop, reasoning_effort, config, provider_config, tools, tool_choice, fallback)
|
|
45
45
|
# @return [Llmshim::ChatResponse]
|
|
46
|
+
# @raise [ArgumentError] if +stream: true+ is passed — /v1/chat would emit
|
|
47
|
+
# SSE, which this method cannot parse. Use #stream for streaming.
|
|
46
48
|
def chat(model:, messages:, **opts)
|
|
49
|
+
if opts[:stream]
|
|
50
|
+
raise ArgumentError,
|
|
51
|
+
"chat() returns a single ChatResponse and cannot stream. " \
|
|
52
|
+
"Use #stream (or Llmshim.stream) for streaming responses."
|
|
53
|
+
end
|
|
54
|
+
|
|
47
55
|
body = build_body(model, messages, opts)
|
|
48
56
|
hash = post_json("/v1/chat", body)
|
|
49
57
|
ChatResponse.from_hash(hash)
|
data/lib/llmshim/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: llmshim
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sanjay Nadhavajhala
|
|
@@ -55,8 +55,8 @@ dependencies:
|
|
|
55
55
|
description: |
|
|
56
56
|
A thin, dependency-free HTTP client for a running llmshim proxy. Send
|
|
57
57
|
OpenAI-style chat requests and let llmshim translate to OpenAI, Anthropic,
|
|
58
|
-
Google Gemini, or
|
|
59
|
-
listing, and health checks. Standard library only.
|
|
58
|
+
Google Gemini, xAI, OpenRouter, vLLM, or SGLang. Supports non-streaming and
|
|
59
|
+
SSE streaming, model listing, and health checks. Standard library only.
|
|
60
60
|
email:
|
|
61
61
|
- sanjay@f2.ai
|
|
62
62
|
executables: []
|