ollama-client 1.1.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 (187) hide show
  1. checksums.yaml +4 -4
  2. data/.env.example +11 -0
  3. data/API_CONTRACT.md +113 -14
  4. data/CHANGELOG.md +60 -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 +33 -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 +2 -2
  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 +68 -97
  72. data/lib/ollama/client/chat_stream_processor.rb +174 -0
  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 +86 -245
  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 +139 -35
  82. data/lib/ollama/config.rb +122 -17
  83. data/lib/ollama/embeddings.rb +67 -29
  84. data/lib/ollama/errors.rb +56 -1
  85. data/lib/ollama/events.rb +55 -0
  86. data/lib/ollama/generate_stream_handler.rb +80 -0
  87. data/lib/ollama/history_sanitizer.rb +62 -0
  88. data/lib/ollama/http_error_handler.rb +42 -0
  89. data/lib/ollama/json_fragment_extractor.rb +89 -0
  90. data/lib/ollama/messages.rb +109 -0
  91. data/lib/ollama/middleware/cache.rb +73 -0
  92. data/lib/ollama/middleware/logger.rb +74 -0
  93. data/lib/ollama/middleware/metrics.rb +84 -0
  94. data/lib/ollama/middleware/tracing.rb +101 -0
  95. data/lib/ollama/middleware.rb +39 -0
  96. data/lib/ollama/model_profile.rb +155 -0
  97. data/lib/ollama/multimodal_input.rb +77 -0
  98. data/lib/ollama/openai.rb +16 -0
  99. data/lib/ollama/options.rb +63 -1
  100. data/lib/ollama/params.rb +139 -0
  101. data/lib/ollama/parsers/base.rb +24 -0
  102. data/lib/ollama/parsers/chat.rb +22 -0
  103. data/lib/ollama/parsers/embeddings.rb +23 -0
  104. data/lib/ollama/parsers/generate.rb +38 -0
  105. data/lib/ollama/parsers/list_running.rb +15 -0
  106. data/lib/ollama/parsers/show_model.rb +14 -0
  107. data/lib/ollama/parsers/version.rb +15 -0
  108. data/lib/ollama/pipeline.rb +169 -0
  109. data/lib/ollama/plugins.rb +83 -0
  110. data/lib/ollama/policies/auto_pull.rb +69 -0
  111. data/lib/ollama/policies/base.rb +70 -0
  112. data/lib/ollama/policies/capability_validation.rb +88 -0
  113. data/lib/ollama/policies/fallback.rb +57 -0
  114. data/lib/ollama/policies/rate_limit.rb +115 -0
  115. data/lib/ollama/policies/repair_json.rb +137 -0
  116. data/lib/ollama/policies/retry/strategies/exponential.rb +27 -0
  117. data/lib/ollama/policies/retry/strategies/fixed.rb +27 -0
  118. data/lib/ollama/policies/retry/strategies/jitter.rb +30 -0
  119. data/lib/ollama/policies/retry/strategies/linear.rb +27 -0
  120. data/lib/ollama/policies/retry.rb +152 -0
  121. data/lib/ollama/policies/schema_repair.rb +180 -0
  122. data/lib/ollama/policies/timeout.rb +43 -0
  123. data/lib/ollama/policies.rb +24 -0
  124. data/lib/ollama/prompt.rb +86 -0
  125. data/lib/ollama/prompt_adapters/base.rb +33 -0
  126. data/lib/ollama/prompt_adapters/deepseek.rb +12 -0
  127. data/lib/ollama/prompt_adapters/gemma4.rb +71 -0
  128. data/lib/ollama/prompt_adapters/generic.rb +9 -0
  129. data/lib/ollama/prompt_adapters/qwen.rb +12 -0
  130. data/lib/ollama/prompt_adapters.rb +24 -0
  131. data/lib/ollama/prompts/tool_planner.rb +35 -0
  132. data/lib/ollama/providers/base.rb +70 -0
  133. data/lib/ollama/providers/llama_cpp.rb +131 -0
  134. data/lib/ollama/providers/ollama.rb +54 -0
  135. data/lib/ollama/providers/openai.rb +134 -0
  136. data/lib/ollama/providers.rb +28 -0
  137. data/lib/ollama/rate_limit_handler.rb +48 -0
  138. data/lib/ollama/request.rb +169 -0
  139. data/lib/ollama/response.rb +22 -2
  140. data/lib/ollama/responses/base.rb +11 -0
  141. data/lib/ollama/responses/chat.rb +11 -0
  142. data/lib/ollama/responses/embeddings.rb +14 -0
  143. data/lib/ollama/responses/generate.rb +22 -0
  144. data/lib/ollama/schema_dsl.rb +97 -0
  145. data/lib/ollama/schema_validator.rb +90 -53
  146. data/lib/ollama/schemas/tool_intent.json +15 -0
  147. data/lib/ollama/schemas/tool_intent.rb +16 -0
  148. data/lib/ollama/serializers/base.rb +44 -0
  149. data/lib/ollama/serializers/chat.rb +42 -0
  150. data/lib/ollama/serializers/embeddings.rb +36 -0
  151. data/lib/ollama/serializers/generate.rb +47 -0
  152. data/lib/ollama/stream_event.rb +31 -0
  153. data/lib/ollama/streaming_observer.rb +22 -0
  154. data/lib/ollama/testing.rb +103 -0
  155. data/lib/ollama/tool/function/parameters/property.rb +72 -0
  156. data/lib/ollama/tool/function/parameters.rb +101 -0
  157. data/lib/ollama/tool/function.rb +78 -0
  158. data/lib/ollama/tool.rb +60 -0
  159. data/lib/ollama/tool_dsl.rb +93 -0
  160. data/lib/ollama/tool_intent.rb +19 -0
  161. data/lib/ollama/transport/base.rb +42 -0
  162. data/lib/ollama/transport/mock.rb +48 -0
  163. data/lib/ollama/transport/net_http.rb +76 -0
  164. data/lib/ollama/transport/request.rb +20 -0
  165. data/lib/ollama/transport/response.rb +41 -0
  166. data/lib/ollama/transport.rb +26 -0
  167. data/lib/ollama/version.rb +1 -1
  168. data/lib/ollama-client.rb +4 -0
  169. data/lib/ollama_client.rb +40 -0
  170. data/script/live_branch_smoke/chat_exercises.rb +232 -0
  171. data/script/live_branch_smoke/generation_exercises.rb +128 -0
  172. data/script/live_branch_smoke/model_exercises.rb +105 -0
  173. data/script/live_branch_smoke/utility_exercises.rb +375 -0
  174. data/script/live_branch_smoke.rb +303 -0
  175. data/test_all_features.rb +315 -0
  176. metadata +184 -21
  177. data/.cursor/.gitignore +0 -1
  178. data/RELEASE_NOTES_v0.2.6.md +0 -41
  179. data/devagent_proper.rb +0 -430
  180. data/docs/TESTING.md +0 -508
  181. data/examples/agent_loop.rb +0 -120
  182. data/examples/failure_modes/invalid_json_repair.rb +0 -42
  183. data/examples/production/rails_agent.rb +0 -62
  184. data/market.jpg +0 -0
  185. data/print_capabilities.rb +0 -20
  186. data/schema.json +0 -1
  187. data/test_tool.rb +0 -26
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 35dd7d6fe83f10045ed0ea1efa14f5ffc7547b8f132a5a504ad5fcbad40843ec
4
- data.tar.gz: ca238782d4431c3c0beeee0615dca25699d479a66fe44c88509f8905803656a6
3
+ metadata.gz: cb8311c8125a276a71ebb5793eaa3db0f12819a5c2860fdcfc684bbd2cc070a4
4
+ data.tar.gz: 6deb2ccaa92539928327cf56169764b2a3e96aba2f31effaf1ad5ab9ee615483
5
5
  SHA512:
6
- metadata.gz: 51ece11f047b2ed872a0860b04d3e0cc2cfbbf443259deef5edddbf8511ac94318d8e7ea3ccfb6251945e1d38abe96fc31b366d6905c9ed45cc452530ad01b61
7
- data.tar.gz: c5f8cf9f1296768c13740e4462abe3bbe0aaa48e936da720a061c55f6c9456362e32b7a1aa5fcaac09c630e0e4beac36b3990e6673367434defb234eb2895dc4
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,7 +1,7 @@
1
- # API Contract — v1.0.0
1
+ # API Contract — v1.4.0
2
2
 
3
- This document defines the **public API surface** of `ollama-client` v1.0.
4
- Everything listed here is guaranteed stable until `v2.0.0`.
3
+ This document defines the **public API surface** of `ollama-client` v1.4.0.
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
7
7
 
@@ -11,17 +11,26 @@ Everything listed here is guaranteed stable until `v2.0.0`.
11
11
  client = Ollama::Client.new(config: Ollama::Config.new)
12
12
  ```
13
13
 
14
+ #### Client — model profiles (v1.3+)
15
+
16
+ | Method | Signature | Returns |
17
+ |---|---|---|
18
+ | `profile` | `(model_name)` | `Ollama::ModelProfile` |
19
+ | `history_sanitizer` | `(model_name_or_profile, trace_store: nil)` | `Ollama::HistorySanitizer` |
20
+
14
21
  #### Chat
15
22
 
16
23
  | Method | Signature | Returns |
17
24
  |---|---|---|
18
- | `chat` | `(messages:, model: nil, format: nil, tools: nil, stream: nil, think: nil, keep_alive: nil, options: nil, logprobs: nil, top_logprobs: nil, hooks: {})` | `Ollama::Response` |
25
+ | `chat` | `(messages:, model: nil, format: nil, tools: nil, stream: nil, think: nil, keep_alive: nil, options: nil, logprobs: nil, top_logprobs: nil, hooks: {}, profile: :auto, inputs: nil)` | `Ollama::Response` |
19
26
 
20
27
  #### Generate
21
28
 
22
29
  | Method | Signature | Returns |
23
30
  |---|---|---|
24
- | `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.
25
34
 
26
35
  When `think: true` and `return_reasoning: true`, the return value is a `Hash` with:
27
36
 
@@ -36,13 +45,28 @@ When `think: true` and `return_reasoning: true`, the return value is a `Hash` wi
36
45
  | `list_model_names` | `()` | `Array<String>` |
37
46
  | `list_running` / `ps` | `()` | `Array<Hash>` |
38
47
  | `show_model` | `(model:, verbose: false)` | `Hash` |
39
- | `pull` | `(model_name)` | `true` |
48
+ | `pull` | `(model_name, insecure: false, stream: false, hooks: {})` | `Hash` (final status) |
40
49
  | `delete_model` | `(model:)` | `true` |
41
50
  | `copy_model` | `(source:, destination:)` | `true` |
42
- | `create_model` | `(model:, from:, system: nil, template: nil, license: nil, parameters: nil, messages: nil, quantize: nil, stream: false)` | `Hash` |
43
- | `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` |
44
57
  | `version` | `()` | `String` |
45
- | `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"`) |
46
70
 
47
71
  ### `Ollama::Embeddings`
48
72
 
@@ -81,6 +105,22 @@ Returned by `chat`. Wraps the API response with accessor methods:
81
105
  | `tool_calls` | `Array<ToolCall>` | Function calls |
82
106
  | `images` | `Array<String>` | Base64 images |
83
107
 
108
+ ### `Ollama::GenerateStreamHandler` (v1.3+)
109
+
110
+ Used internally by `generate` when streaming; stable for advanced callers who process raw `Net::HTTPResponse` bodies.
111
+
112
+ | Method | Signature | Returns |
113
+ |---|---|---|
114
+ | `.call` | `(response, hooks, accumulator)` | `nil` — mutates `accumulator` (`String`) with decoded `response` tokens; invokes `hooks` |
115
+
116
+ ### `Ollama::JsonFragmentExtractor` (v1.3+)
117
+
118
+ | Method | Signature | Returns |
119
+ |---|---|---|
120
+ | `.call` | `(text)` | `String` — a balanced JSON object or array substring (parse with `JSON.parse` if you need Ruby values) |
121
+
122
+ Raises `Ollama::InvalidJSONError` when `text` is blank or no balanced JSON fragment can be extracted.
123
+
84
124
  ### `Ollama::Options`
85
125
 
86
126
  Type-safe runtime options passed via `options:` parameter:
@@ -99,7 +139,7 @@ All attributes are read/write via `attr_accessor`:
99
139
  |---|---|---|---|
100
140
  | `base_url` | `String` | `"http://localhost:11434"` | Ollama server URL |
101
141
  | `api_key` | `String, nil` | `nil` | Optional Bearer token for Ollama Cloud (`https://ollama.com`) |
102
- | `model` | `String` | `"llama3.2:3b"` | Default model for generation |
142
+ | `model` | `String` | `"qwen3.5:4b"` | Default model for generation |
103
143
  | `timeout` | `Integer` | `30` | HTTP read/open timeout in seconds |
104
144
  | `retries` | `Integer` | `2` | Max retry attempts |
105
145
  | `strict_json` | `Boolean` | `true` | Enable JSON validation + repair |
@@ -108,6 +148,60 @@ All attributes are read/write via `attr_accessor`:
108
148
  | `num_ctx` | `Integer` | `8192` | Context window size |
109
149
  | `on_response` | `Proc/nil` | `nil` | Global response callback |
110
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
+
111
205
  ## Error Classes
112
206
 
113
207
  All errors inherit from `Ollama::Error < StandardError`.
@@ -122,6 +216,9 @@ All errors inherit from `Ollama::Error < StandardError`.
122
216
  | `Ollama::HTTPError` | Non-200 HTTP response | Depends on status code |
123
217
  | `Ollama::NotFoundError` | HTTP 404 (model not found) | **Auto-handled** — triggers pull |
124
218
  | `Ollama::StreamError` | `{"error": "..."}` in NDJSON stream | **No** — immediate |
219
+ | `Ollama::UnsupportedThinkingModel` | `think:` requested for a model that does not support reasoning | **No** |
220
+ | `Ollama::UnsupportedCapabilityError` | Multimodal or other capability used outside model profile | **No** |
221
+ | `Ollama::ThinkingFormatError` | Reasoning tags in model output could not be parsed | **No** |
125
222
 
126
223
  ## Recovery Behaviors (Guaranteed)
127
224
 
@@ -141,13 +238,15 @@ Passed via `hooks:` parameter on `generate` and `chat`:
141
238
 
142
239
  ```ruby
143
240
  hooks: {
144
- on_token: ->(token) { ... }, # Called per token chunk
145
- on_error: ->(error) { ... }, # Called on stream error
146
- on_complete: -> { ... } # Called when stream finishes
241
+ on_token: ->(token) { ... }, # generate: token string; chat: token string
242
+ on_thought: ->(event) { ... }, # chat only: Ollama::StreamEvent (reasoning)
243
+ on_tool_call: ->(tool_call_hash) { ... }, # chat only: tool call payload from final chunk
244
+ on_error: ->(error) { ... },
245
+ on_complete: -> { ... }
147
246
  }
148
247
  ```
149
248
 
150
- Hooks are **observer-only** — they cannot modify the response. Streaming is auto-enabled when any hook is present.
249
+ Hooks are **observer-only** — they cannot modify the response. For `chat`, streaming is auto-enabled when any hook is present (including `on_thought` or `on_tool_call`). For `generate`, streaming follows the same hook-driven rule as before.
151
250
 
152
251
  ## What Will NOT Change Before v2.0
153
252
 
data/CHANGELOG.md CHANGED
@@ -5,6 +5,66 @@ 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
+
53
+ ## [1.3.0] - 2026-04-20
54
+
55
+ ### Added
56
+ - Model capability layer: `Ollama::ModelProfile`, `Ollama::Capabilities`, `Ollama::PromptAdapters`, `Ollama::MultimodalInput`, `Ollama::HistorySanitizer`, and `Ollama::StreamEvent` for model-aware chat, multimodal ordering, and structured streaming events.
57
+ - `Ollama::Client#profile` and `#history_sanitizer` for resolving profiles and building history sanitizers from a model name or `ModelProfile`.
58
+ - Extended `Ollama::Client#chat` with optional `profile:`, `inputs:`, `logprobs:`, and `top_logprobs:`; chat streaming hooks may include `on_thought` and `on_tool_call` (see `API_CONTRACT.md`).
59
+ - `Ollama::GenerateStreamHandler` — NDJSON streaming consumer for `/api/generate` responses.
60
+ - `Ollama::JsonFragmentExtractor` — extracts a balanced JSON object or array from text that may include leading or trailing prose.
61
+ - `require "ollama-client"` loads the same stack as `require "ollama_client"` (hyphenated gem entrypoint).
62
+ - `dotenv` (~> 2.8) as a runtime dependency; `ollama_client` continues to call `Dotenv.overload` when loaded.
63
+ - `script/live_branch_smoke.rb` — optional live Ollama smoke runner for profiles, chat extensions, generate streaming, embeddings, etc. (see script header for env vars).
64
+
65
+ ### Changed
66
+ - `Client::Generate` delegates streaming body handling to `GenerateStreamHandler` (behavior and hooks contract unchanged).
67
+
8
68
  ## [1.1.0] - 2026-03-17
9
69
 
10
70
  ### 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)