ollama-client 1.3.0 → 1.4.0

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.
Files changed (178) hide show
  1. checksums.yaml +4 -4
  2. data/.env.example +11 -0
  3. data/API_CONTRACT.md +79 -8
  4. data/CHANGELOG.md +45 -0
  5. data/CONTRIBUTING.md +8 -0
  6. data/README.md +205 -273
  7. data/ROADMAP.md +23 -0
  8. data/docs/API_GAPS.md +18 -141
  9. data/docs/ARCHITECTURE.md +141 -0
  10. data/docs/AREAS_FOR_CONSIDERATION.md +13 -1
  11. data/docs/CLOUD.md +19 -0
  12. data/docs/CONSOLE_IMPROVEMENTS.md +20 -1
  13. data/docs/ECOSYSTEM_GEMS.md +41 -0
  14. data/docs/ECOSYSTEM_STRATEGY.md +151 -0
  15. data/docs/GETTING_STARTED.md +16 -8
  16. data/docs/INTEGRATION_TESTING.md +23 -5
  17. data/docs/PRODUCTION_FIXES.md +15 -3
  18. data/docs/QUICK_START.md +1 -1
  19. data/docs/README.md +1 -0
  20. data/docs/RUBYLLM_ADOPTION_MATRIX.md +273 -0
  21. data/docs/adr/001-openai-boundary.md +12 -0
  22. data/docs/adr/002-transport-abstraction.md +12 -0
  23. data/docs/adr/003-response-normalization.md +12 -0
  24. data/docs/adr/004-mock-transport.md +16 -0
  25. data/docs/adr/005-error-taxonomy.md +16 -0
  26. data/docs/adr/006-stream-runtime.md +16 -0
  27. data/docs/ecosystem/BOUNDARIES.md +19 -0
  28. data/docs/ecosystem/DEPENDENCY_GRAPH.md +14 -0
  29. data/docs/ecosystem/DESIGN_PRINCIPLES.md +9 -0
  30. data/docs/ecosystem/EXISTING_REPOS.md +31 -0
  31. data/docs/ecosystem/EXPERIMENTAL_LABS.md +24 -0
  32. data/docs/ecosystem/OVERVIEW.md +28 -0
  33. data/docs/ecosystem/RELEASE_ORDER.md +17 -0
  34. data/docs/observability/README.md +8 -0
  35. data/docs/rails/README.md +7 -0
  36. data/docs/rfcs/0001-stream-runtime.md +6 -0
  37. data/docs/rfcs/0002-schema-system.md +6 -0
  38. data/docs/rfcs/0003-observability-hooks.md +6 -0
  39. data/docs/rfcs/0004-async-runtime.md +6 -0
  40. data/docs/rfcs/README.md +16 -0
  41. data/docs/runtime/ERROR_CONTRACT.md +14 -0
  42. data/docs/runtime/SCHEMA_CONTRACT.md +28 -0
  43. data/docs/runtime/STREAM_CONTRACT.md +17 -0
  44. data/docs/runtime/STREAM_RUNTIME.md +20 -0
  45. data/docs/runtime/TRANSPORT_CONTRACT.md +26 -0
  46. data/docs/schema/README.md +8 -0
  47. data/docs/schema/STRUCTURED_OUTPUTS.md +22 -0
  48. data/docs/streaming/README.md +8 -0
  49. data/docs/testing/README.md +7 -0
  50. data/docs/testing/REPLAY_SYSTEM.md +17 -0
  51. data/docs/transport/README.md +7 -0
  52. data/examples/README.md +44 -0
  53. data/examples/basic_chat.rb +9 -0
  54. data/examples/cloud_models.rb +162 -0
  55. data/examples/embeddings.rb +10 -0
  56. data/examples/free_catalog.json +290 -0
  57. data/examples/generate.rb +9 -0
  58. data/examples/streaming.rb +12 -0
  59. data/examples/structured_tools.rb +90 -0
  60. data/examples/tool_calling_direct.rb +101 -0
  61. data/examples/tool_dto_example.rb +94 -0
  62. data/exe/ollama-client +5 -1
  63. data/lib/ollama/agent/executor.rb +243 -0
  64. data/lib/ollama/agent/messages.rb +31 -0
  65. data/lib/ollama/agent/planner.rb +45 -0
  66. data/lib/ollama/api_key_pool.rb +61 -0
  67. data/lib/ollama/attachment.rb +74 -0
  68. data/lib/ollama/capabilities.rb +1 -1
  69. data/lib/ollama/chat_response.rb +33 -0
  70. data/lib/ollama/client/chat/request_preparer.rb +82 -0
  71. data/lib/ollama/client/chat.rb +58 -68
  72. data/lib/ollama/client/chat_stream_processor.rb +85 -25
  73. data/lib/ollama/client/generate/request_preparer.rb +118 -0
  74. data/lib/ollama/client/generate/response_formatter.rb +95 -0
  75. data/lib/ollama/client/generate.rb +83 -165
  76. data/lib/ollama/client/model_management.rb +182 -84
  77. data/lib/ollama/client/openai_compat.rb +185 -0
  78. data/lib/ollama/client/raw.rb +66 -0
  79. data/lib/ollama/client/tool_intent.rb +38 -0
  80. data/lib/ollama/client/web_search.rb +39 -0
  81. data/lib/ollama/client.rb +83 -35
  82. data/lib/ollama/config.rb +122 -17
  83. data/lib/ollama/embeddings.rb +67 -29
  84. data/lib/ollama/errors.rb +55 -1
  85. data/lib/ollama/events.rb +55 -0
  86. data/lib/ollama/generate_stream_handler.rb +23 -4
  87. data/lib/ollama/http_error_handler.rb +42 -0
  88. data/lib/ollama/messages.rb +109 -0
  89. data/lib/ollama/middleware/cache.rb +73 -0
  90. data/lib/ollama/middleware/logger.rb +74 -0
  91. data/lib/ollama/middleware/metrics.rb +84 -0
  92. data/lib/ollama/middleware/tracing.rb +101 -0
  93. data/lib/ollama/middleware.rb +39 -0
  94. data/lib/ollama/model_profile.rb +1 -1
  95. data/lib/ollama/openai.rb +16 -0
  96. data/lib/ollama/options.rb +63 -1
  97. data/lib/ollama/params.rb +139 -0
  98. data/lib/ollama/parsers/base.rb +24 -0
  99. data/lib/ollama/parsers/chat.rb +22 -0
  100. data/lib/ollama/parsers/embeddings.rb +23 -0
  101. data/lib/ollama/parsers/generate.rb +38 -0
  102. data/lib/ollama/parsers/list_running.rb +15 -0
  103. data/lib/ollama/parsers/show_model.rb +14 -0
  104. data/lib/ollama/parsers/version.rb +15 -0
  105. data/lib/ollama/pipeline.rb +169 -0
  106. data/lib/ollama/plugins.rb +83 -0
  107. data/lib/ollama/policies/auto_pull.rb +69 -0
  108. data/lib/ollama/policies/base.rb +70 -0
  109. data/lib/ollama/policies/capability_validation.rb +88 -0
  110. data/lib/ollama/policies/fallback.rb +57 -0
  111. data/lib/ollama/policies/rate_limit.rb +115 -0
  112. data/lib/ollama/policies/repair_json.rb +137 -0
  113. data/lib/ollama/policies/retry/strategies/exponential.rb +27 -0
  114. data/lib/ollama/policies/retry/strategies/fixed.rb +27 -0
  115. data/lib/ollama/policies/retry/strategies/jitter.rb +30 -0
  116. data/lib/ollama/policies/retry/strategies/linear.rb +27 -0
  117. data/lib/ollama/policies/retry.rb +152 -0
  118. data/lib/ollama/policies/schema_repair.rb +180 -0
  119. data/lib/ollama/policies/timeout.rb +43 -0
  120. data/lib/ollama/policies.rb +24 -0
  121. data/lib/ollama/prompt.rb +86 -0
  122. data/lib/ollama/prompt_adapters/base.rb +3 -2
  123. data/lib/ollama/prompt_adapters/gemma4.rb +22 -14
  124. data/lib/ollama/prompts/tool_planner.rb +35 -0
  125. data/lib/ollama/providers/base.rb +70 -0
  126. data/lib/ollama/providers/llama_cpp.rb +131 -0
  127. data/lib/ollama/providers/ollama.rb +54 -0
  128. data/lib/ollama/providers/openai.rb +134 -0
  129. data/lib/ollama/providers.rb +28 -0
  130. data/lib/ollama/rate_limit_handler.rb +48 -0
  131. data/lib/ollama/request.rb +169 -0
  132. data/lib/ollama/response.rb +3 -2
  133. data/lib/ollama/responses/base.rb +11 -0
  134. data/lib/ollama/responses/chat.rb +11 -0
  135. data/lib/ollama/responses/embeddings.rb +14 -0
  136. data/lib/ollama/responses/generate.rb +22 -0
  137. data/lib/ollama/schema_dsl.rb +97 -0
  138. data/lib/ollama/schema_validator.rb +90 -53
  139. data/lib/ollama/schemas/tool_intent.json +15 -0
  140. data/lib/ollama/schemas/tool_intent.rb +16 -0
  141. data/lib/ollama/serializers/base.rb +44 -0
  142. data/lib/ollama/serializers/chat.rb +42 -0
  143. data/lib/ollama/serializers/embeddings.rb +36 -0
  144. data/lib/ollama/serializers/generate.rb +47 -0
  145. data/lib/ollama/streaming_observer.rb +22 -0
  146. data/lib/ollama/testing.rb +103 -0
  147. data/lib/ollama/tool/function/parameters/property.rb +72 -0
  148. data/lib/ollama/tool/function/parameters.rb +101 -0
  149. data/lib/ollama/tool/function.rb +78 -0
  150. data/lib/ollama/tool.rb +60 -0
  151. data/lib/ollama/tool_dsl.rb +93 -0
  152. data/lib/ollama/tool_intent.rb +19 -0
  153. data/lib/ollama/transport/base.rb +42 -0
  154. data/lib/ollama/transport/mock.rb +48 -0
  155. data/lib/ollama/transport/net_http.rb +76 -0
  156. data/lib/ollama/transport/request.rb +20 -0
  157. data/lib/ollama/transport/response.rb +41 -0
  158. data/lib/ollama/transport.rb +26 -0
  159. data/lib/ollama/version.rb +1 -1
  160. data/lib/ollama_client.rb +35 -0
  161. data/script/live_branch_smoke/chat_exercises.rb +232 -0
  162. data/script/live_branch_smoke/generation_exercises.rb +128 -0
  163. data/script/live_branch_smoke/model_exercises.rb +105 -0
  164. data/script/live_branch_smoke/utility_exercises.rb +375 -0
  165. data/script/live_branch_smoke.rb +159 -174
  166. data/test_all_features.rb +315 -0
  167. metadata +158 -24
  168. data/.cursor/.gitignore +0 -1
  169. data/RELEASE_NOTES_v0.2.6.md +0 -41
  170. data/devagent_proper.rb +0 -430
  171. data/docs/TESTING.md +0 -508
  172. data/examples/agent_loop.rb +0 -120
  173. data/examples/failure_modes/invalid_json_repair.rb +0 -42
  174. data/examples/production/rails_agent.rb +0 -62
  175. data/market.jpg +0 -0
  176. data/print_capabilities.rb +0 -20
  177. data/schema.json +0 -1
  178. data/test_tool.rb +0 -26
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6d553fa8a5ab841efb4e5c8e8369543618eca89b6930365a62f4af21a75df317
4
- data.tar.gz: 455ef12da68ec09b33b32dea2404dfbf48ea4b087d96a8e140f681d1845377bf
3
+ metadata.gz: cb8311c8125a276a71ebb5793eaa3db0f12819a5c2860fdcfc684bbd2cc070a4
4
+ data.tar.gz: 6deb2ccaa92539928327cf56169764b2a3e96aba2f31effaf1ad5ab9ee615483
5
5
  SHA512:
6
- metadata.gz: 211a89b870ec9e95c341f27cbe7f15d081a0537314ca578d388ee2a8677c16c58e4f6709cc33bb30cb2a58a478a058fc7f9c172af35a405f57372ce154e8fc72
7
- data.tar.gz: 600cc892452d4e867dcd93bd6995a93aed8c69b5d1dc21600134c513b6ad65c1fd7d3fb13b18be2e8d4ee20ce9f5a74cb86afa5e30ffd7fb8ee588bd08109c1a
6
+ metadata.gz: 10af6c797b73974edb149e4daa7d7c81f856456fe17c9906feb0ee18215c4baf775b70be8bf88f5a1dc2ae182c3a29e24713f9fc6ba4a18a0b170804f51e62bb
7
+ data.tar.gz: a985ebf3f9f9b8ac52def574b0a160f8a639280c1f18c019ba75135548089caa12e7b3f9c292cd06f93463421d86658cea5e25388b305930d5ae385699b228ab
data/.env.example ADDED
@@ -0,0 +1,11 @@
1
+ # Ollama local server by default. Use https://ollama.com for Ollama Cloud.
2
+ OLLAMA_BASE_URL=http://localhost:11434
3
+
4
+ # Single Ollama Cloud API key fallback.
5
+ OLLAMA_API_KEY=
6
+
7
+ # Optional comma-separated Ollama Cloud API key pool. Takes precedence over OLLAMA_API_KEY.
8
+ OLLAMA_API_KEYS=
9
+
10
+ # Set to true/1 to round-robin initial keys across concurrent request threads.
11
+ ENABLE_MULTI_KEY_CONCURRENCY=false
data/API_CONTRACT.md CHANGED
@@ -1,6 +1,6 @@
1
- # API Contract — v1.3.0
1
+ # API Contract — v1.4.0
2
2
 
3
- This document defines the **public API surface** of `ollama-client` v1.3.0.
3
+ This document defines the **public API surface** of `ollama-client` v1.4.0.
4
4
  Everything listed here is guaranteed stable until `v2.0.0` (unless explicitly marked as *may evolve* in minor releases).
5
5
 
6
6
  ## Public Methods
@@ -28,7 +28,9 @@ client = Ollama::Client.new(config: Ollama::Config.new)
28
28
 
29
29
  | Method | Signature | Returns |
30
30
  |---|---|---|
31
- | `generate` | `(prompt:, schema: nil, model: nil, strict: config.strict_json, return_meta: false, system: nil, images: nil, think: nil, return_reasoning: false, keep_alive: nil, suffix: nil, raw: nil, options: nil, hooks: {})` | `String` (no schema) or `Hash` (with schema) |
31
+ | `generate` | `(prompt:, context: nil, schema: nil, model: nil, strict: config.strict_json, return_meta: false, system: nil, images: nil, think: nil, return_reasoning: false, keep_alive: nil, suffix: nil, raw: nil, options: nil, hooks: {}, tools: nil)` | `String` (no schema) or `Hash` (with schema) |
32
+
33
+ `context:` accepts the `context` array returned by a previous `generate` call (or via `return_meta: true`) for `/api/generate`-side conversational memory, independent of `chat`'s message history.
32
34
 
33
35
  When `think: true` and `return_reasoning: true`, the return value is a `Hash` with:
34
36
 
@@ -43,13 +45,28 @@ When `think: true` and `return_reasoning: true`, the return value is a `Hash` wi
43
45
  | `list_model_names` | `()` | `Array<String>` |
44
46
  | `list_running` / `ps` | `()` | `Array<Hash>` |
45
47
  | `show_model` | `(model:, verbose: false)` | `Hash` |
46
- | `pull` | `(model_name)` | `true` |
48
+ | `pull` | `(model_name, insecure: false, stream: false, hooks: {})` | `Hash` (final status) |
47
49
  | `delete_model` | `(model:)` | `true` |
48
50
  | `copy_model` | `(source:, destination:)` | `true` |
49
- | `create_model` | `(model:, from:, system: nil, template: nil, license: nil, parameters: nil, messages: nil, quantize: nil, stream: false)` | `Hash` |
50
- | `push_model` | `(model:, insecure: false, stream: false)` | `Hash` |
51
+ | `create_model` | `(model:, from: nil, modelfile: nil, path: nil, system: nil, template: nil, license: nil, parameters: nil, messages: nil, quantize: nil, stream: false)` | `Hash` |
52
+ | `push_model` | `(model:, insecure: false, stream: false, hooks: {})` | `Hash` (final status) |
53
+ | `blob_exists?` | `(digest:)` | `Boolean` |
54
+ | `create_blob` | `(digest:, content:)` | `true` |
55
+ | `load_model` | `(model:, keep_alive: "5m")` | `true` |
56
+ | `unload_model` | `(model:)` | `true` |
51
57
  | `version` | `()` | `String` |
52
- | `embeddings` | _(attr_reader)_ | `Ollama::Embeddings` instance |
58
+ | `embeddings` | *(attr_reader)* | `Ollama::Embeddings` instance |
59
+
60
+ `pull` and `push_model` accept `hooks: { on_progress: ->(status) { ... } }`, invoked once per streamed NDJSON status line (`stream: true`) with the parsed status `Hash`.
61
+
62
+ #### Web Search (Ollama Cloud)
63
+
64
+ Require `config.base_url = "https://ollama.com"` and `config.api_key` / `OLLAMA_API_KEY`.
65
+
66
+ | Method | Signature | Returns |
67
+ |---|---|---|
68
+ | `web_search` | `(query:, max_results: nil)` | `Array<Hash>` (`"title"`, `"url"`, `"content"`) |
69
+ | `web_fetch` | `(url:)` | `Hash` (`"title"`, `"content"`, `"links"`) |
53
70
 
54
71
  ### `Ollama::Embeddings`
55
72
 
@@ -122,7 +139,7 @@ All attributes are read/write via `attr_accessor`:
122
139
  |---|---|---|---|
123
140
  | `base_url` | `String` | `"http://localhost:11434"` | Ollama server URL |
124
141
  | `api_key` | `String, nil` | `nil` | Optional Bearer token for Ollama Cloud (`https://ollama.com`) |
125
- | `model` | `String` | `"llama3.2:3b"` | Default model for generation |
142
+ | `model` | `String` | `"qwen3.5:4b"` | Default model for generation |
126
143
  | `timeout` | `Integer` | `30` | HTTP read/open timeout in seconds |
127
144
  | `retries` | `Integer` | `2` | Max retry attempts |
128
145
  | `strict_json` | `Boolean` | `true` | Enable JSON validation + repair |
@@ -131,6 +148,60 @@ All attributes are read/write via `attr_accessor`:
131
148
  | `num_ctx` | `Integer` | `8192` | Context window size |
132
149
  | `on_response` | `Proc/nil` | `nil` | Global response callback |
133
150
 
151
+ ### Raw Escape Hatch
152
+
153
+ `client.raw` — direct HTTP access for endpoints without a dedicated method. Auth, retries, and error
154
+ mapping (`handle_http_error`) are applied the same as typed methods; the response body is JSON-parsed.
155
+
156
+ | Method | Signature | Returns |
157
+ |---|---|---|
158
+ | `raw.get` | `(path, query: nil)` | `Hash` |
159
+ | `raw.post` | `(path, payload: {}, query: nil)` | `Hash` |
160
+ | `raw.delete` | `(path, payload: nil, query: nil)` | `Hash` |
161
+
162
+ ### OpenAI Compatibility Facade
163
+
164
+ `client.openai` — wraps `chat`, `generate`, `embeddings`, and `list_models` in OpenAI Chat Completions
165
+ API request/response shapes, for code written against OpenAI-shaped SDKs.
166
+
167
+ | Method | Signature | Returns |
168
+ |---|---|---|
169
+ | `openai.models.list` | `()` | `Hash` (`{"object"=>"list", "data"=>[...]}`) |
170
+ | `openai.embeddings.create` | `(model:, input:, **opts)` | `Hash` (`{"object"=>"list", "data"=>[...], "model"=>...}`) |
171
+ | `openai.chat.completions.create` | `(model:, messages:, tools: nil, temperature: nil, top_p: nil, **)` | `Hash` (OpenAI chat completion shape) |
172
+ | `openai.completions.create` | `(model:, prompt:, temperature: nil, top_p: nil, **)` | `Hash` (OpenAI text completion shape) |
173
+
174
+ ## Policy Middleware (v1.4+)
175
+
176
+ `client.use(policy_class, **options)` attaches production-behavior middleware to the request
177
+ pipeline (chain order = registration order). All policies live under `Ollama::Policies::` and
178
+ wrap non-streaming requests; HTTP failures surface inside the chain as typed errors
179
+ (`Errors.from_response`), so policies observe 404/429/5xx responses.
180
+
181
+ | Policy | Options | Behavior |
182
+ |---|---|---|
183
+ | `Policies::Retry` | `max_attempts:`, `strategy:` (`:exponential`/`:linear`/`:fixed`/`:jitter`), `base_delay:`, `max_delay:`, `jitter:`, `retryable_errors:`, `hooks:` | Retries network errors and HTTP 429/5xx with backoff |
184
+ | `Policies::Timeout` | `connect_timeout:`, `read_timeout:`, `write_timeout:`, `hooks:` (`:on_timeout`) | Annotates `env[:timeouts]`; fires `:on_timeout` hook |
185
+ | `Policies::AutoPull` | `enabled:`, `allowed_patterns:` (glob), `hooks:` (`:before_pull`, `:after_pull`) | On 404, pulls the requested model once and retries the request |
186
+ | `Policies::Fallback` | `models:` (ordered list), `fallback_on:`, `hooks:` | Re-issues the request against each fallback model until one succeeds |
187
+ | `Policies::RateLimit` | `requests_per_second:`, `requests_per_minute:`, `burst:`, `hooks:` | Token-bucket throttling before dispatch |
188
+ | `Policies::CapabilityValidation` | `enabled:`, `cache:`, `cache_ttl:`, `hooks:` (`:capability_missing`) | Raises `UnsupportedCapabilityError` when the model profile lacks a requested capability (tools/thinking/vision/structured output) |
189
+ | `Policies::RepairJson` | `max_repairs:`, `strategies:` (`:balanced`, `:extract_object`), `hooks:` | Repairs malformed JSON response bodies |
190
+ | `Policies::SchemaRepair` | `max_repairs:`, `strict:`, `hooks:` | Validates structured output against the request `format` schema and repairs violations (missing fields, type mismatches, extras) |
191
+
192
+ ## Agent Executor (v1.4+)
193
+
194
+ `Ollama::Agent::Executor` (required by the default load path) runs the chat + tool-calling loop:
195
+
196
+ | Method | Signature | Returns |
197
+ |---|---|---|
198
+ | `Executor#run` | `(system:, user:)` | `String` — final assistant content |
199
+ | `Executor#messages` | `()` | `Array<Hash>` — conversation history (system, user, assistant, tool turns) |
200
+
201
+ Constructor: `Executor.new(client, tools: { "name" => callable_or_tool }, max_steps: 20, stream: nil)`.
202
+ Tool keys may be strings or symbols. Pass `stream:` an `Ollama::StreamingObserver` to receive
203
+ `:token`, `:tool_call_detected`, and `:state` events.
204
+
134
205
  ## Error Classes
135
206
 
136
207
  All errors inherit from `Ollama::Error < StandardError`.
data/CHANGELOG.md CHANGED
@@ -5,6 +5,51 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.4.0] - 2026-08-18
9
+
10
+ ### Added
11
+ - `Ollama::Policies::*` middleware is now **wired and public**: `client.use(Ollama::Policies::Retry, ...)` etc. attaches retry/timeout/auto-pull/fallback/rate-limit/capability-validation/JSON-repair/schema-repair policies to the request pipeline. The Rack-style `call(request, env)`/`@app` scaffolding was converted to the pipeline's `around` contract, and HTTP failures now surface as typed errors *inside* the chain (`Transport::Base#call` → `Errors.from_response`) so policies can observe 404/429/5xx responses. Documented in `API_CONTRACT.md`.
12
+ - `Ollama::Agent::Executor` is wired into the default load path and `#run` now uses the current `chat()` API (streaming via `hooks:`) instead of the removed `chat_raw`; tool keys may be strings or symbols.
13
+ - `SchemaViolationError` now carries structured `violations` (field/type data), raised by `SchemaValidator` and consumed by `SchemaRepair`.
14
+ - `Ollama::Schemas.tool_intent` is now the default schema for `client.generate_tool_intent`.
15
+ - `list_running`, `show_model`, and `version` now parse through `Parsers::{ListRunning,ShowModel,Version}` (previously dead files).
16
+ - Multi-API-key Ollama Cloud failover via `Ollama::Config#api_keys`, `OLLAMA_API_KEYS`, and automatic HTTP 429 rotation with `Ollama::RateLimitExhaustedError` when every key remains rate-limited.
17
+ - `ENABLE_MULTI_KEY_CONCURRENCY` / `Ollama::Config#enable_multi_key_concurrency` for thread-safe round-robin initial key distribution across concurrent requests.
18
+ - `Ollama::Client#web_search` and `#web_fetch` — Ollama Cloud `/api/web_search` and `/api/web_fetch` endpoints (see `API_CONTRACT.md`).
19
+
20
+ ### Changed
21
+ - Repositioned gem identity from "agent-first" to "Ruby AI SDK for Ollama" in gemspec and README. No API, behavior, or config changes.
22
+
23
+ ### Removed
24
+ - Genuinely dead duplicates with zero references: `lib/ollama/parsers/{list_models,model_management}.rb` and `lib/ollama/serializers/vision.rb`.
25
+
26
+ ### Fixed
27
+ - Repository-wide dead-code/bug audit: 37 of 110 `lib/` files were never `require`d anywhere in the gem's load chain, so nothing ever caught several of them raising on load. All 110 files now load cleanly (enforced by `spec/ollama/all_files_load_spec.rb`).
28
+ - `Ollama::Middleware::{Cache,Logger,Metrics,Tracing}` each reopened `Ollama::Middleware` (a class) as `module Middleware`, raising `TypeError` on load — the `client.use Ollama::Middleware::Logger` example in README.md has never worked. Fixed the reopening, two broken `require_relative` paths, and a missing `require "digest"`. `Tracing#before_request` also unconditionally called `request.headers`/`request.with_headers`, which `Ollama::Request` doesn't implement — guarded behind `respond_to?`.
29
+ - `Ollama::Tool` (and `Tool::Function`/`Parameters`/`Property`) was never required by `lib/ollama_client.rb`, so `examples/tool_calling_direct.rb`, `tool_dto_example.rb`, and `structured_tools.rb` raised `NameError` immediately. Added to the default load path.
30
+ - `Ollama::Agent::Executor#tool_definitions` had a stray, copy-pasted code fragment from `#infer_parameters` appended after its real `.map` block, referencing undefined locals — always raised `NameError`. Removed the fragment; the method now returns its intended array.
31
+ - Fixed the two broken example scripts that use `Ollama::Agent`/`Ollama::Tool` to require what they need, and rewrote `tool_calling_direct.rb`'s use of the removed `chat_raw`/`allow_chat:` API to the current `chat()` (which already returns a full `Ollama::Response`, including `tool_calls`).
32
+ - `lib/ollama/policies/*` (Retry, Timeout, AutoPull, RepairJson, SchemaRepair, Fallback, RateLimit, CapabilityValidation): fixed three broken `require_relative` paths and a `Retry`/`Retry::Strategies` naming collision that raised `TypeError: superclass mismatch`. Subsequently wired into the pipeline as `client.use` middleware — see the Added section above.
33
+ - Removed the now-redundant top-level `lib/ollama/openai_compat.rb` (zero references anywhere; `Ollama::Client` already includes `OpenAICompat` directly). `lib/ollama/openai.rb`'s `require "ollama/openai"` (documented in README) is kept for backwards compatibility — it's a harmless no-op now, since `client.openai` already works without it.
34
+ - `Ollama::Agent::Executor#run` still calls `Client#chat_raw`, a pre-refactor method that no longer exists (superseded by `chat()`), and is deliberately **not** added to the default load path — see the note in `lib/ollama/agent/executor.rb`: `docs/RUBYLLM_ADOPTION_MATRIX.md` section L tracks whether tool-execution loops belong in core or a separate `ollama-agent` gem as an open question. **Resolved in the Added section above: wired into the default load path on the current `chat()` API.**
35
+ - Confirmed genuinely dead with zero references anywhere (code, docs, examples): `lib/ollama/parsers/{list_models,list_running,model_management,show_model,version}.rb`, `lib/ollama/serializers/vision.rb`, `lib/ollama/schemas/tool_intent.rb`. Since resolved: `list_running`/`show_model`/`version` parsers are wired into model management, `Schemas.tool_intent` is the default intent schema, and the remaining dead duplicates were removed (see Added/Removed above).
36
+
37
+ ### Documentation
38
+ - `API_CONTRACT.md`: documented `hooks: { on_progress: }` on `pull`/`push_model`, and the full `create_model`/`pull` keyword signatures (previously only partially listed).
39
+
40
+ ### Testing
41
+ - Added `spec/ollama/vcr/` — VCR-cassette specs replaying real, recorded Ollama Cloud responses for `chat` (incl. tools, `think:`, `format:`, streaming), `generate` (incl. `schema:`, `context`, streaming), `list_models`, `show_model`, `version`, `web_search`, and `web_fetch`. Cassettes are committed under `spec/cassettes/` and replay with no network access or API key required; see `spec/cassettes/README.md` for scope, rationale, and the re-recording workflow. `embeddings` and model-management mutation endpoints (`pull`/`push`/`create`/`delete`/`copy`) remain WebMock-only — Ollama Cloud has no embedding models and rejects those endpoints for a regular API key.
42
+ - A few real-response findings surfaced by recording: `chat(format:)` doesn't enforce/repair schema compliance the way `generate(schema:)` does (confirms the known gap in `docs/RUBYLLM_ADOPTION_MATRIX.md` C3); `generate`'s `context` field can be absent (`nil`) for Cloud-hosted chat-tuned models rather than always populated; `Ollama::Capabilities.for` doesn't yet recognize the `gptoss` family, so `show_model`'s derived capability hash reports `tools`/`thinking` as `false` for `gpt-oss:20b` even though the server's own `capabilities` array says otherwise (confirms `docs/RUBYLLM_ADOPTION_MATRIX.md` E3). No code changes made for these — they're documented via the new specs' comments, not fixed, since fixing them is separate roadmap work.
43
+
44
+ ### CI/CD
45
+ - CI now also runs against Ruby 3.4, and splits `rspec`/`rubocop` into separate steps so failures are distinguishable at a glance.
46
+ - Added a `build` job that builds the gem, installs it, and verifies the public API loads — catches packaging regressions on every PR instead of only at release time.
47
+ - Added a `dependency-audit` job (`bundler-audit`) that fails the build on known CVEs in `Gemfile.lock`; fixed three vulnerable transitive dependencies it found (`erb`, `json`, `concurrent-ruby`, `addressable`).
48
+ - Added a `ci` gate job aggregating `test`/`build`/`dependency-audit` for a single required branch-protection check.
49
+ - Added a weekly CodeQL security-analysis workflow for Ruby.
50
+ - Added Dependabot for weekly Bundler and GitHub Actions dependency updates.
51
+ - `release.yml` now runs the full test suite and RuboCop before building/publishing a tagged gem.
52
+
8
53
  ## [1.3.0] - 2026-04-20
9
54
 
10
55
  ### Added
data/CONTRIBUTING.md CHANGED
@@ -18,6 +18,14 @@ bundle install
18
18
  bundle exec rake
19
19
  ```
20
20
 
21
+ ## Testing
22
+
23
+ Most specs stub HTTP with WebMock. `spec/ollama/vcr/*.rb` instead replays real, pre-recorded Ollama
24
+ Cloud responses via VCR cassettes committed under `spec/cassettes/` — see
25
+ `spec/cassettes/README.md` for what's covered, why, and how to re-record after a behavior change.
26
+ Neither needs a live server or an API key to run normally; VCR cassette recording is the one
27
+ exception, and only when adding or refreshing a `spec/ollama/vcr/` scenario.
28
+
21
29
  ## What to include in a PR
22
30
 
23
31
  - Clear description of *why* the change exists (not just what changed)