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.
- checksums.yaml +4 -4
- data/.env.example +11 -0
- data/API_CONTRACT.md +79 -8
- data/CHANGELOG.md +45 -0
- data/CONTRIBUTING.md +8 -0
- data/README.md +205 -273
- data/ROADMAP.md +23 -0
- data/docs/API_GAPS.md +18 -141
- data/docs/ARCHITECTURE.md +141 -0
- data/docs/AREAS_FOR_CONSIDERATION.md +13 -1
- data/docs/CLOUD.md +19 -0
- data/docs/CONSOLE_IMPROVEMENTS.md +20 -1
- data/docs/ECOSYSTEM_GEMS.md +41 -0
- data/docs/ECOSYSTEM_STRATEGY.md +151 -0
- data/docs/GETTING_STARTED.md +16 -8
- data/docs/INTEGRATION_TESTING.md +23 -5
- data/docs/PRODUCTION_FIXES.md +15 -3
- data/docs/QUICK_START.md +1 -1
- data/docs/README.md +1 -0
- data/docs/RUBYLLM_ADOPTION_MATRIX.md +273 -0
- data/docs/adr/001-openai-boundary.md +12 -0
- data/docs/adr/002-transport-abstraction.md +12 -0
- data/docs/adr/003-response-normalization.md +12 -0
- data/docs/adr/004-mock-transport.md +16 -0
- data/docs/adr/005-error-taxonomy.md +16 -0
- data/docs/adr/006-stream-runtime.md +16 -0
- data/docs/ecosystem/BOUNDARIES.md +19 -0
- data/docs/ecosystem/DEPENDENCY_GRAPH.md +14 -0
- data/docs/ecosystem/DESIGN_PRINCIPLES.md +9 -0
- data/docs/ecosystem/EXISTING_REPOS.md +31 -0
- data/docs/ecosystem/EXPERIMENTAL_LABS.md +24 -0
- data/docs/ecosystem/OVERVIEW.md +28 -0
- data/docs/ecosystem/RELEASE_ORDER.md +17 -0
- data/docs/observability/README.md +8 -0
- data/docs/rails/README.md +7 -0
- data/docs/rfcs/0001-stream-runtime.md +6 -0
- data/docs/rfcs/0002-schema-system.md +6 -0
- data/docs/rfcs/0003-observability-hooks.md +6 -0
- data/docs/rfcs/0004-async-runtime.md +6 -0
- data/docs/rfcs/README.md +16 -0
- data/docs/runtime/ERROR_CONTRACT.md +14 -0
- data/docs/runtime/SCHEMA_CONTRACT.md +28 -0
- data/docs/runtime/STREAM_CONTRACT.md +17 -0
- data/docs/runtime/STREAM_RUNTIME.md +20 -0
- data/docs/runtime/TRANSPORT_CONTRACT.md +26 -0
- data/docs/schema/README.md +8 -0
- data/docs/schema/STRUCTURED_OUTPUTS.md +22 -0
- data/docs/streaming/README.md +8 -0
- data/docs/testing/README.md +7 -0
- data/docs/testing/REPLAY_SYSTEM.md +17 -0
- data/docs/transport/README.md +7 -0
- data/examples/README.md +44 -0
- data/examples/basic_chat.rb +9 -0
- data/examples/cloud_models.rb +162 -0
- data/examples/embeddings.rb +10 -0
- data/examples/free_catalog.json +290 -0
- data/examples/generate.rb +9 -0
- data/examples/streaming.rb +12 -0
- data/examples/structured_tools.rb +90 -0
- data/examples/tool_calling_direct.rb +101 -0
- data/examples/tool_dto_example.rb +94 -0
- data/exe/ollama-client +5 -1
- data/lib/ollama/agent/executor.rb +243 -0
- data/lib/ollama/agent/messages.rb +31 -0
- data/lib/ollama/agent/planner.rb +45 -0
- data/lib/ollama/api_key_pool.rb +61 -0
- data/lib/ollama/attachment.rb +74 -0
- data/lib/ollama/capabilities.rb +1 -1
- data/lib/ollama/chat_response.rb +33 -0
- data/lib/ollama/client/chat/request_preparer.rb +82 -0
- data/lib/ollama/client/chat.rb +58 -68
- data/lib/ollama/client/chat_stream_processor.rb +85 -25
- data/lib/ollama/client/generate/request_preparer.rb +118 -0
- data/lib/ollama/client/generate/response_formatter.rb +95 -0
- data/lib/ollama/client/generate.rb +83 -165
- data/lib/ollama/client/model_management.rb +182 -84
- data/lib/ollama/client/openai_compat.rb +185 -0
- data/lib/ollama/client/raw.rb +66 -0
- data/lib/ollama/client/tool_intent.rb +38 -0
- data/lib/ollama/client/web_search.rb +39 -0
- data/lib/ollama/client.rb +83 -35
- data/lib/ollama/config.rb +122 -17
- data/lib/ollama/embeddings.rb +67 -29
- data/lib/ollama/errors.rb +55 -1
- data/lib/ollama/events.rb +55 -0
- data/lib/ollama/generate_stream_handler.rb +23 -4
- data/lib/ollama/http_error_handler.rb +42 -0
- data/lib/ollama/messages.rb +109 -0
- data/lib/ollama/middleware/cache.rb +73 -0
- data/lib/ollama/middleware/logger.rb +74 -0
- data/lib/ollama/middleware/metrics.rb +84 -0
- data/lib/ollama/middleware/tracing.rb +101 -0
- data/lib/ollama/middleware.rb +39 -0
- data/lib/ollama/model_profile.rb +1 -1
- data/lib/ollama/openai.rb +16 -0
- data/lib/ollama/options.rb +63 -1
- data/lib/ollama/params.rb +139 -0
- data/lib/ollama/parsers/base.rb +24 -0
- data/lib/ollama/parsers/chat.rb +22 -0
- data/lib/ollama/parsers/embeddings.rb +23 -0
- data/lib/ollama/parsers/generate.rb +38 -0
- data/lib/ollama/parsers/list_running.rb +15 -0
- data/lib/ollama/parsers/show_model.rb +14 -0
- data/lib/ollama/parsers/version.rb +15 -0
- data/lib/ollama/pipeline.rb +169 -0
- data/lib/ollama/plugins.rb +83 -0
- data/lib/ollama/policies/auto_pull.rb +69 -0
- data/lib/ollama/policies/base.rb +70 -0
- data/lib/ollama/policies/capability_validation.rb +88 -0
- data/lib/ollama/policies/fallback.rb +57 -0
- data/lib/ollama/policies/rate_limit.rb +115 -0
- data/lib/ollama/policies/repair_json.rb +137 -0
- data/lib/ollama/policies/retry/strategies/exponential.rb +27 -0
- data/lib/ollama/policies/retry/strategies/fixed.rb +27 -0
- data/lib/ollama/policies/retry/strategies/jitter.rb +30 -0
- data/lib/ollama/policies/retry/strategies/linear.rb +27 -0
- data/lib/ollama/policies/retry.rb +152 -0
- data/lib/ollama/policies/schema_repair.rb +180 -0
- data/lib/ollama/policies/timeout.rb +43 -0
- data/lib/ollama/policies.rb +24 -0
- data/lib/ollama/prompt.rb +86 -0
- data/lib/ollama/prompt_adapters/base.rb +3 -2
- data/lib/ollama/prompt_adapters/gemma4.rb +22 -14
- data/lib/ollama/prompts/tool_planner.rb +35 -0
- data/lib/ollama/providers/base.rb +70 -0
- data/lib/ollama/providers/llama_cpp.rb +131 -0
- data/lib/ollama/providers/ollama.rb +54 -0
- data/lib/ollama/providers/openai.rb +134 -0
- data/lib/ollama/providers.rb +28 -0
- data/lib/ollama/rate_limit_handler.rb +48 -0
- data/lib/ollama/request.rb +169 -0
- data/lib/ollama/response.rb +3 -2
- data/lib/ollama/responses/base.rb +11 -0
- data/lib/ollama/responses/chat.rb +11 -0
- data/lib/ollama/responses/embeddings.rb +14 -0
- data/lib/ollama/responses/generate.rb +22 -0
- data/lib/ollama/schema_dsl.rb +97 -0
- data/lib/ollama/schema_validator.rb +90 -53
- data/lib/ollama/schemas/tool_intent.json +15 -0
- data/lib/ollama/schemas/tool_intent.rb +16 -0
- data/lib/ollama/serializers/base.rb +44 -0
- data/lib/ollama/serializers/chat.rb +42 -0
- data/lib/ollama/serializers/embeddings.rb +36 -0
- data/lib/ollama/serializers/generate.rb +47 -0
- data/lib/ollama/streaming_observer.rb +22 -0
- data/lib/ollama/testing.rb +103 -0
- data/lib/ollama/tool/function/parameters/property.rb +72 -0
- data/lib/ollama/tool/function/parameters.rb +101 -0
- data/lib/ollama/tool/function.rb +78 -0
- data/lib/ollama/tool.rb +60 -0
- data/lib/ollama/tool_dsl.rb +93 -0
- data/lib/ollama/tool_intent.rb +19 -0
- data/lib/ollama/transport/base.rb +42 -0
- data/lib/ollama/transport/mock.rb +48 -0
- data/lib/ollama/transport/net_http.rb +76 -0
- data/lib/ollama/transport/request.rb +20 -0
- data/lib/ollama/transport/response.rb +41 -0
- data/lib/ollama/transport.rb +26 -0
- data/lib/ollama/version.rb +1 -1
- data/lib/ollama_client.rb +35 -0
- data/script/live_branch_smoke/chat_exercises.rb +232 -0
- data/script/live_branch_smoke/generation_exercises.rb +128 -0
- data/script/live_branch_smoke/model_exercises.rb +105 -0
- data/script/live_branch_smoke/utility_exercises.rb +375 -0
- data/script/live_branch_smoke.rb +159 -174
- data/test_all_features.rb +315 -0
- metadata +158 -24
- data/.cursor/.gitignore +0 -1
- data/RELEASE_NOTES_v0.2.6.md +0 -41
- data/devagent_proper.rb +0 -430
- data/docs/TESTING.md +0 -508
- data/examples/agent_loop.rb +0 -120
- data/examples/failure_modes/invalid_json_repair.rb +0 -42
- data/examples/production/rails_agent.rb +0 -62
- data/market.jpg +0 -0
- data/print_capabilities.rb +0 -20
- data/schema.json +0 -1
- data/test_tool.rb +0 -26
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
# RubyLLM → ollama-client Adoption Matrix
|
|
2
|
+
|
|
3
|
+
**Purpose:** decide, feature by feature, what `ollama-client` should take from RubyLLM, what it should
|
|
4
|
+
take *and improve*, what it should refuse, and what belongs in an ecosystem gem — so the next build
|
|
5
|
+
phase is driven by a decision, not by opportunity.
|
|
6
|
+
|
|
7
|
+
**Positioning premise:** RubyLLM is breadth (one interface across ~12 providers). `ollama-client` is
|
|
8
|
+
depth (the complete Ruby runtime for one provider). Adoption is therefore about **ergonomics parity**,
|
|
9
|
+
not **scope parity**. Anything RubyLLM does *because* it must abstract many providers is a candidate
|
|
10
|
+
for rejection.
|
|
11
|
+
|
|
12
|
+
**Sources**
|
|
13
|
+
|
|
14
|
+
- RubyLLM public API: [rubyllm.com](https://rubyllm.com/) — [chat](https://rubyllm.com/chat/),
|
|
15
|
+
[tools](https://rubyllm.com/tools/), [models](https://rubyllm.com/models/),
|
|
16
|
+
[streaming](https://rubyllm.com/streaming/), [rails](https://rubyllm.com/rails/),
|
|
17
|
+
[configuration](https://rubyllm.com/configuration/) (surface as of RubyLLM ~1.15, incl. `RubyLLM::Agent`).
|
|
18
|
+
- `ollama-client` current state: this repository at `v1.3.0` (`lib/ollama/version.rb`), ~5.8k LOC in `lib/`,
|
|
19
|
+
public surface frozen by [`API_CONTRACT.md`](../API_CONTRACT.md).
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Verdict legend
|
|
24
|
+
|
|
25
|
+
| Verdict | Meaning |
|
|
26
|
+
|---|---|
|
|
27
|
+
| **COPY** | Adopt the concept close to as-is. RubyLLM's shape is right and provider-neutral. |
|
|
28
|
+
| **IMPROVE** | Adopt the concept, but ship a strictly better version using Ollama-specific depth. |
|
|
29
|
+
| **REJECT** | Do not build. Either it is a multi-provider artifact, or Ollama has no such capability. |
|
|
30
|
+
| **ECOSYSTEM** | Right idea, wrong gem. Belongs in `ollama-rails` / `ollama-agent` / `ollama-testing` / `ollama-observability`. |
|
|
31
|
+
| **HAVE** | Already exists here, at parity or better. No work beyond docs. |
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## A. Chat & conversation
|
|
36
|
+
|
|
37
|
+
| # | RubyLLM capability | RubyLLM API | ollama-client today | Verdict | Target |
|
|
38
|
+
|---|---|---|---|---|---|
|
|
39
|
+
| A1 | Stateful conversation object | `chat = RubyLLM.chat(model:)`; `chat.ask("…")` twice keeps history | **None.** `chat` is a stateless one-shot on `Client` (`lib/ollama/client/chat.rb:29`) returning `Ollama::Response`. Callers hand-manage `messages:` arrays | **COPY** | `Ollama::Chat` — `client.chat(model:)` with no `messages:` returns a `Chat`; `chat.ask` appends and returns `Ollama::Response`. Highest-leverage single item in this document |
|
|
40
|
+
| A2 | Backwards-compatible entry point | n/a (RubyLLM never had a hash API) | `chat(messages:, …)` is contract-frozen until v2.0 | **IMPROVE** | Overload, don't replace: `messages:` present → today's exact behavior; `messages:` absent → `Ollama::Chat`. Zero contract breakage |
|
|
41
|
+
| A3 | System prompt helper | `with_instructions(text, append: false)` | Only `Ollama::Messages#system` (`lib/ollama/messages.rb:29`) | **COPY** | `chat.with_instructions` delegating to `Messages#system` (which already does replace-or-append) |
|
|
42
|
+
| A4 | Fluent config builders | `with_model`, `with_temperature`, `with_params`, `with_headers` | Per-call kwargs + `Ollama::Options` + per-client `Config` | **IMPROVE** | Add the fluent set *plus* Ollama-native builders RubyLLM cannot have: `with_think(:high)`, `with_keep_alive("30m")`, `with_num_ctx(32_768)`, `with_options(Ollama::Options)` |
|
|
43
|
+
| A5 | Conversation history access | `messages`, `add_message`, `reset_messages!` | `Ollama::Messages` builder exists but is not attached to a conversation | **COPY** | `chat.messages` returns the `Messages` builder; `add_message`, `reset!` |
|
|
44
|
+
| A6 | History hygiene across turns | Not addressed — RubyLLM replays raw history | `HistorySanitizer` + `ModelProfile#history_policy` (`:exclude_thoughts`) already exist (`lib/ollama/history_sanitizer.rb`, `lib/ollama/model_profile.rb:123`) | **HAVE / exceed** | Wire the existing sanitizer into `Ollama::Chat` by default. This is depth RubyLLM structurally cannot match — it does not know that Gemma-4 thinking blocks must be dropped from replayed history |
|
|
45
|
+
| A7 | Message/response object | `response.content`, `.model_id`, `.raw`, `.tokens.{input,output,cache_read,thinking}`, `.cost.total` | `Ollama::Response` with `content`, `usage`, `latency_ms`, `total_duration`, `eval_count`, `logprobs` (`lib/ollama/response.rb`) | **IMPROVE** | Keep `usage`. **Reject `cost`** (local inference has no per-token price) and spend that slot on `tokens_per_second`, `load_duration_ms`, `prompt_eval_rate` — the numbers that actually matter locally |
|
|
46
|
+
| A8 | Lifecycle callbacks | `before_message`, `after_message`, `before_tool_call`, `after_tool_result` (legacy `on_*` deprecated) | `hooks: {on_token:, on_thought:, on_tool_call:, on_error:, on_complete:}` (contract-frozen) + separate `StreamEvent` struct + separate `StreamingObserver` | **IMPROVE** | Do **not** add a third callback system. Collapse to one typed `Ollama::StreamEvent` bus (`lib/ollama/stream_event.rb` already defines the taxonomy), with `hooks:` retained as the frozen adapter over it |
|
|
47
|
+
| A9 | Prompt caching | `cache_prompts`, cache token accounting | n/a — Ollama has no prompt-cache billing surface | **REJECT** | Ollama's equivalent lever is `keep_alive` + preload, already covered by A4/E5 |
|
|
48
|
+
| A10 | Reusable assistant preset | `class X < RubyLLM::Agent; model …; instructions …; tools … end` | `Ollama::Agent::Executor`, `Ollama::Agent::Planner` in core (`lib/ollama/agent/`) | **ECOSYSTEM** | Agent presets → `ollama-agent`. Core ships `Ollama::Chat` only. See §L for the executor question |
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## B. Tools
|
|
53
|
+
|
|
54
|
+
| # | RubyLLM capability | RubyLLM API | ollama-client today | Verdict | Target |
|
|
55
|
+
|---|---|---|---|---|---|
|
|
56
|
+
| B1 | Class-based tool definition | `class X < RubyLLM::Tool; description …; param …; def execute(**) end` | `Ollama::ToolDSL` with `description`, `tool_name`, `input do … end`, `output do … end`, `call { }` (`lib/ollama/tool_dsl.rb`) | **IMPROVE** | Keep the schema DSL (it is better — typed `input`/`output` blocks). Replace the confusing `call { }` "constructor" block with a plain `#execute(**kwargs)` instance method. `to_tool_hash` stays |
|
|
57
|
+
| B2 | Nested/complex params | `params do object :w do … end; array :p, of: :string end` | `SchemaDSL` has `object`/`array` but `object` takes a raw `properties:` hash — no nested block (`lib/ollama/schema_dsl.rb:61`) | **COPY** | Add block form to `SchemaDSL#object` and `of:` to `#array`. Small change, removes the last reason to hand-write JSON Schema |
|
|
58
|
+
| B3 | Signature inference | v1.15+: builds schema from `execute` kwargs when no `param` given | Exists but buried in `Agent::Executor#infer_parameters` and types everything as `"string"` | **IMPROVE** | Promote inference to `ToolDSL`, and infer types from Ruby defaults/`Data` members rather than defaulting to string |
|
|
59
|
+
| B4 | Attaching tools | `with_tool(T)`, `with_tools(A, B)` | `chat(tools: [T.to_tool_hash])` — user must serialize manually | **COPY** | `chat.with_tool(T)` accepting a `ToolDSL` subclass, `Ollama::Tool` DTO, or raw hash |
|
|
60
|
+
| B5 | Tool call parsing | Provider-normalized | `Response::Message::ToolCall` + `Function#arguments` with JSON-string fallback (`lib/ollama/response.rb:165`) — plus `ToolIntent` fallback for models that emit tool calls as prose | **HAVE / exceed** | Nothing. `lib/ollama/client/tool_intent.rb` has no RubyLLM equivalent |
|
|
61
|
+
| B6 | Halt the loop | `halt "…"` skips the model's post-tool commentary | None | **COPY** | Cheap, high value once a loop exists (§L) |
|
|
62
|
+
| B7 | Tool choice / call count | `choice: :auto\|:required\|:none\|Tool`, `calls: :many\|:one` | None | **PARTIAL / verify** | `/api/chat` does not expose `tool_choice`; the OpenAI-compat endpoint does. Ship `choice:` only where the provider supports it (`lib/ollama/providers/openai.rb`), and emulate `:none` by omitting `tools:`. Do not fake `:required` |
|
|
63
|
+
| B8 | Concurrent tool execution | `concurrency: :threads \| :fibers` | None | **ECOSYSTEM** | Scheduling policy → `ollama-agent` |
|
|
64
|
+
| B9 | Automatic execution loop | Core runs the tool loop inside `ask` | `Agent::Executor` (in core, undocumented in `API_CONTRACT.md`) | **CONTESTED** | See §L — this is the one boundary decision that must be made before A1 ships |
|
|
65
|
+
| B10 | Argument safety | Docs-only guidance ("treat as untrusted") | `Executor` does keyword/positional coercion + alias guessing (`directory`→`path`) | **IMPROVE** | Validate tool arguments against the declared `input` schema *before* dispatch using the existing `SchemaValidator`, and drop the alias guessing — it is a silent-wrong-behavior generator |
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## C. Structured output
|
|
70
|
+
|
|
71
|
+
| # | RubyLLM capability | RubyLLM API | ollama-client today | Verdict | Target |
|
|
72
|
+
|---|---|---|---|---|---|
|
|
73
|
+
| C1 | Schema class DSL | `class S < RubyLLM::Schema; string :name; number :price; array :f do string end; end` | `Ollama::SchemaDSL.define { … }.to_h` — block/instance form, not a subclassable class | **COPY** | `class TradeSignal < Ollama::Schema` with the same field methods. Keep `SchemaDSL.define` as the anonymous form |
|
|
74
|
+
| C2 | Attaching a schema | `chat.with_schema(S)` → `response.content` is a parsed Hash | `generate(schema:)` → Hash; `chat(format:)` → raw String the caller must `JSON.parse` | **IMPROVE** | `chat.with_schema(S)`. Non-negotiable per `CLAUDE.md`: `generate` keeps its String-vs-Hash rule unchanged; the new typed path lives on `Chat` |
|
|
75
|
+
| C3 | Validation & repair | Provider-side JSON mode; no repair loop | `strict_json` + `SchemaValidator` + repair-prompt retry — **but only on `generate`**. `chat(format:)` bypasses validation and repair entirely | **IMPROVE** | **Real defect, not just a gap.** Route `chat(format:)`/`with_schema` through the same validate→repair→retry pipeline. Closing this is worth more than any DX item in this table |
|
|
76
|
+
| C4 | Typed result objects | Returns a Hash | Returns a Hash | **IMPROVE / differentiate** | `signal.direction` not `hash["direction"]`. Schema class instantiates a typed object with coercion. This is the flagship differentiator called out in the strategy |
|
|
77
|
+
| C5 | Incremental / streaming parse | None | `JsonFragmentExtractor` extracts balanced JSON from partial text (`lib/ollama/json_fragment_extractor.rb`) | **HAVE → extend** | Feed the extractor from the stream so partial structured output can render live. RubyLLM has no answer here |
|
|
78
|
+
| C6 | Enum / constraint coverage | `string :x, enum: […]` | `enum`, `minimum`, `maximum`, `description`, `default`, `optional` | **HAVE** | Add `pattern`, `format`, `minItems` for completeness |
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## D. Streaming
|
|
83
|
+
|
|
84
|
+
| # | RubyLLM capability | RubyLLM API | ollama-client today | Verdict | Target |
|
|
85
|
+
|---|---|---|---|---|---|
|
|
86
|
+
| D1 | Block streaming | `chat.ask("…") { \|chunk\| print chunk.content }` | `hooks: {on_token:}` — works, but the block form is what people expect | **COPY** | `chat.ask(…) { \|chunk\| }`; presence of a block implies `stream: true`, exactly as `hooks:` does today (`lib/ollama/client/chat.rb:74`) |
|
|
87
|
+
| D2 | Chunk object | `RubyLLM::Chunk` with `content`, `role`, `model_id`, `tool_calls`, `tokens`, `thinking` | `StreamEvent(type:, data:, model:)` with `thought?`/`answer?`/`tool_call?`/`terminal?` predicates and `to_jsonl` | **HAVE / exceed** | Typed event *taxonomy* beats an untyped chunk — reasoning is separated from answer at the type level. Just expose it publicly |
|
|
88
|
+
| D3 | Final message after streaming | `ask` returns the complete message even when streaming | `chat` returns assembled `Ollama::Response` via `ChatStreamProcessor` | **HAVE** | — |
|
|
89
|
+
| D4 | Stream cancellation | **None documented** | None | **IMPROVE / exceed** | `stream = chat.stream("…")`; `stream.cancel`, `stream.closed?`, `stream.metrics`, `stream.each_chunk`. This is a clear "exceed RubyLLM" slot and is already scoped in `docs/rfcs/0001-stream-runtime.md` |
|
|
90
|
+
| D5 | Streaming errors | Raises `RubyLLM::Error` subclasses mid-stream | `Ollama::StreamError` from `{"error":…}` NDJSON lines, `on_error` hook | **HAVE** | — |
|
|
91
|
+
| D6 | Turbo/ActionCable bridge | Documented Rails pattern | None | **ECOSYSTEM** | `ollama-rails` |
|
|
92
|
+
| D7 | Async / fibers | Works under `Async`; concurrency options in tools | None; `docs/rfcs/0004-async-runtime.md` drafted | **ECOSYSTEM (later core)** | Keep `Net::HTTP` core; async runtime as `ollama-stream` or an opt-in transport adapter — the `Transport::Base` boundary already supports this |
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## E. Models & runtime — *the differentiation zone*
|
|
97
|
+
|
|
98
|
+
| # | RubyLLM capability | RubyLLM API | ollama-client today | Verdict | Target |
|
|
99
|
+
|---|---|---|---|---|---|
|
|
100
|
+
| E1 | Model registry object | `RubyLLM.models` → collection | `list_models` → `Array<Hash>` (`lib/ollama/client/model_management.rb:233`) | **IMPROVE** | `client.models` → `Ollama::ModelCollection`; `client.model("qwen3:8b")` → `Ollama::Model` |
|
|
101
|
+
| E2 | Registry filters | `all`, `chat_models`, `embedding_models`, `by_provider`, `by_family`, `find` | None (raw array) | **COPY** | `.running`, `.embedding`, `.vision`, `.tools`, `.thinking`, `.by_family`, `.find` — filters keyed off `Capabilities` + `ModelProfile`, not a static catalog |
|
|
102
|
+
| E3 | Model metadata source | **Static catalog** refreshed from models.dev + `refresh!` + `save_to_json` | Live `/api/tags` + `/api/show`, with `Capabilities.for` inference (`lib/ollama/capabilities.rb`) | **IMPROVE / exceed** | Live-first is the whole thesis. `model.capabilities` should prefer the server's own `/api/show` capability list and fall back to name/family heuristics — today `Capabilities.for` is heuristics-only |
|
|
103
|
+
| E4 | Model attributes | `context_window`, `max_tokens`, `supports_vision?`, `supports_functions?`, `family`, pricing | `ModelProfile` (static family table, `lib/ollama/model_profile.rb:14`) + capability hash | **IMPROVE** | `Ollama::Model#name/family/parameters/quantization/context_window/capabilities` — sourced from `/api/show` `model_info`, which carries real per-model context length and quantization. Static tables become fallback only |
|
|
104
|
+
| E5 | Runtime state | **Impossible for RubyLLM** | `list_running`/`ps`, `load_model`, `unload_model` exist but return hashes | **IMPROVE / exceed** | `model.running?`, `.loaded_at`, `.expires_at`, `.vram`, `.memory`, `.load`, `.unload`, `.performance.tokens_per_second`. This is the `/models` command in the AI Runtime Shell, and no multi-provider SDK can offer it |
|
|
105
|
+
| E6 | Lifecycle operations | `refresh!` only | `pull`, `push_model`, `delete_model`, `copy_model`, `create_model`, `blob_exists?`, `create_blob` | **HAVE / exceed** | Re-expose as instance methods on `Ollama::Model` (`model.pull`, `model.copy("prod")`) — same HTTP calls, better surface |
|
|
106
|
+
| E7 | Pricing / cost tracking | `input_price_per_million`, `cost.total` | None | **REJECT** | Meaningless locally. Replace with the E5 performance surface |
|
|
107
|
+
| E8 | Alias resolution | `aliases.json` maps `claude-sonnet` → versioned id | None — `qwen3` vs `qwen3:8b` is the caller's problem | **COPY (small)** | Resolve bare names against installed tags; raise a typed error listing near-matches instead of a bare 404 |
|
|
108
|
+
| E9 | Unknown-model escape | `assume_model_exists: true` | 404 → auto-pull → retry (contract-guaranteed) | **HAVE / exceed** | — |
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## F. Other modalities
|
|
113
|
+
|
|
114
|
+
| # | RubyLLM capability | RubyLLM API | ollama-client today | Verdict | Target |
|
|
115
|
+
|---|---|---|---|---|---|
|
|
116
|
+
| F1 | Embeddings | `RubyLLM.embed(text)` → `Embedding` with `vectors`, `model`, `input_tokens` | `client.embeddings.embed(model:, input:)` → raw `Array<Float>`; `model:` is required | **COPY** | `client.embed("text")` using a new `config.default_embedding_model`; return an `Ollama::Embedding` object (`vectors`, `model`, `dimensions`, `usage`) that still `to_ary`s to the raw vector for compatibility |
|
|
117
|
+
| F2 | Attachments | `chat.ask("what is this?", with: "image.png")` — auto-detects type | `Ollama::Attachment.file/base64/url` + `MultimodalInput` + `inputs:` kwarg | **COPY (ergonomics only)** | Add `with:` sugar over the existing typed machinery. Keep `inputs:` as the explicit form |
|
|
118
|
+
| F3 | Image generation | `RubyLLM.paint` | None | **REJECT** | Ollama does not generate images |
|
|
119
|
+
| F4 | Transcription | `RubyLLM.transcribe` | None | **REJECT** | Not an Ollama endpoint |
|
|
120
|
+
| F5 | Moderation | `RubyLLM.moderate` | None | **REJECT** | Not an Ollama endpoint. A safety-model convention could live in `ollama-agent` |
|
|
121
|
+
| F6 | Thinking / reasoning | Surfaced as a `thinking` chunk field only | `think: true\|"high"\|"medium"\|"low"`, per-family `think_trigger` (`:flag` vs `:system_prompt_tag`), separated reasoning stream, `UnsupportedThinkingModel` | **HAVE / exceed** | Document it as a headline feature. `PromptAdapters` handling Gemma-4's tag-based thinking is genuinely unmatched |
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## G. Configuration, transport, errors
|
|
126
|
+
|
|
127
|
+
| # | RubyLLM capability | RubyLLM API | ollama-client today | Verdict | Target |
|
|
128
|
+
|---|---|---|---|---|---|
|
|
129
|
+
| G1 | Global config | `RubyLLM.configure { \|c\| … }` | `OllamaClient.config` + per-client `Config` (per-client is the documented default) | **HAVE** | Keep per-client primacy — `CLAUDE.md` forbids mutating global config with live clients. Thread-safety here is already better |
|
|
130
|
+
| G2 | Isolated contexts | `RubyLLM.context { \|c\| … }` → independent, thread-safe | `Config.new` + `Client.new(config:)` — the same thing, less ceremony | **HAVE** | Documentation only |
|
|
131
|
+
| G3 | Retry tuning | `max_retries`, `retry_interval`, `retry_backoff_factor`, `retry_interval_randomness` | `config.retries` only; backoff hardcoded `2 ** attempt`, no jitter | **COPY** | Add interval/factor/jitter knobs. Contract-safe: `API_CONTRACT.md` explicitly permits retry-timing changes and new config attributes with compatible defaults |
|
|
132
|
+
| G4 | Logging | `logger`, `log_file`, `log_level`, `log_stream_debug` | `config.on_response` callback only | **COPY (core) + ECOSYSTEM** | Minimal `config.logger` in core; OTel/metrics/exporters → `ollama-observability` |
|
|
133
|
+
| G5 | Proxy / connection | `http_proxy`, Faraday connection options | `http_connection_options` (ssl + timeouts) | **COPY** | Proxy support + connection pooling (already on `ROADMAP.md`) |
|
|
134
|
+
| G6 | Transport swap | Faraday middleware, not a documented public seam | `Transport::Base`/`NetHTTP`/`Mock` factory (`lib/ollama/transport.rb`) + `client.use(policy)` middleware stack (`lib/ollama/policies/`) | **HAVE / exceed** | Formalize the middleware stack in `API_CONTRACT.md` |
|
|
135
|
+
| G7 | Raw escape hatch | `with_params`, `response.raw` | `client.raw.get/post/delete` (`lib/ollama/client/raw.rb`) + `client.openai` facade | **HAVE / exceed** | — |
|
|
136
|
+
| G8 | Error taxonomy | `Error`, `BadRequest`, `Unauthorized`, `RateLimit`, `ServerError`, `ModelNotFound`, … | 11 typed errors with documented retryability + recovery guarantees | **HAVE / exceed** | — |
|
|
137
|
+
| G9 | Multi-key failover | None | `ApiKeyPool` + round-robin + rate-limit rotation (`lib/ollama/api_key_pool.rb`) | **HAVE / exceed** | Relevant to Ollama Cloud; keep and document |
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## H. Rails & test/observability tooling
|
|
142
|
+
|
|
143
|
+
| # | RubyLLM capability | RubyLLM API | ollama-client today | Verdict | Target |
|
|
144
|
+
|---|---|---|---|---|---|
|
|
145
|
+
| H1 | Persistence | `acts_as_chat`, `acts_as_message`, `acts_as_tool_call`, `acts_as_model` | None | **ECOSYSTEM** | `ollama-rails`. Hard rule stands: core imports nothing from Rails |
|
|
146
|
+
| H2 | Generators | `ruby_llm:install`, `:chat_ui`, `:load_models` | None | **ECOSYSTEM** | `ollama-rails` |
|
|
147
|
+
| H3 | Empty-assistant-message persistence pattern | Core Rails behavior; documented as blocking `validates :content, presence: true` | n/a | **REJECT the pattern** | Do not inherit this design flaw. `ollama-rails` should persist on completion, or persist a distinct `pending` state |
|
|
148
|
+
| H4 | Background jobs / Turbo | Documented patterns | None | **ECOSYSTEM** | `ollama-rails` |
|
|
149
|
+
| H5 | Deterministic test doubles | VCR in RubyLLM's own suite; no shipped public test API | `Transport::Mock` shipped in-gem; `docs/testing/REPLAY_SYSTEM.md` drafted | **HAVE → ECOSYSTEM** | `Ollama::Testing` helpers (`stub_chat`, `stub_stream`, `stub_error`) per `IMPROVE.md` Phase 5, then extract to `ollama-testing`. A shipped mock transport is a real advantage — RubyLLM users reach for WebMock themselves |
|
|
150
|
+
| H6 | Observability | Logging only | `on_response` hook, `Response#usage`, `#latency_ms` | **ECOSYSTEM** | `ollama-observability` (OTel spans, token metrics) |
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## I. Where ollama-client already exceeds RubyLLM
|
|
155
|
+
|
|
156
|
+
No adoption work — these are the marketing surface and should be lifted into the README's lead section:
|
|
157
|
+
|
|
158
|
+
1. **Thinking/reasoning as a first-class, per-family concern** — `think:` levels, `think_trigger` (API flag vs. system-prompt tag), reasoning separated from answer in the stream, `UnsupportedThinkingModel`.
|
|
159
|
+
2. **Prompt adapters per model family** — Gemma-4/Qwen/DeepSeek/generic (`lib/ollama/prompt_adapters/`). RubyLLM cannot do this without becoming Ollama-specific.
|
|
160
|
+
3. **History sanitization policies** — dropping thoughts from replayed history is a correctness issue in multi-turn agent loops that RubyLLM leaves to the user.
|
|
161
|
+
4. **Structured-output repair loop** — validate → repair-prompt → retry, contract-guaranteed. RubyLLM trusts provider JSON mode.
|
|
162
|
+
5. **Tool-intent recovery** — parsing tool calls from models that emit them as prose.
|
|
163
|
+
6. **Auto-pull on 404** — a guaranteed recovery behavior that only makes sense for a local runtime.
|
|
164
|
+
7. **Model lifecycle** — `ps`, `load`/`unload`, `keep_alive`, `create`/`copy`/`push`/blobs.
|
|
165
|
+
8. **Shipped mock transport** and an explicit `API_CONTRACT.md`.
|
|
166
|
+
9. **Multi-key failover** for Ollama Cloud.
|
|
167
|
+
10. **Typed stream event taxonomy** with JSONL tracing built in.
|
|
168
|
+
|
|
169
|
+
## J. Where RubyLLM is straightforwardly ahead
|
|
170
|
+
|
|
171
|
+
Stated plainly, because the matrix above is otherwise easy to read as self-congratulation:
|
|
172
|
+
|
|
173
|
+
1. **No conversation object.** `chat.ask` twice is the single most expected thing in a modern LLM SDK, and it does not exist here (A1).
|
|
174
|
+
2. **Tools do not execute.** `ToolDSL` produces a schema; nothing wires it to a callable in the public API (B1/B4/B9).
|
|
175
|
+
3. **Structured output on `chat` is unvalidated** and returns a raw string (C2/C3).
|
|
176
|
+
4. **No streaming block form** — hooks are more powerful and less familiar (D1).
|
|
177
|
+
5. **Models are hashes**, not objects (E1).
|
|
178
|
+
6. **Embeddings require an explicit `model:`** every call and return bare arrays (F1).
|
|
179
|
+
7. **No Rails story at all** (H1–H4).
|
|
180
|
+
|
|
181
|
+
Items 1–6 are all fixable additively, without touching a frozen signature.
|
|
182
|
+
|
|
183
|
+
## K. Ollama API coverage gaps (independent of RubyLLM)
|
|
184
|
+
|
|
185
|
+
Depth positioning means "100% Ollama API coverage" must be literally true. Open items to verify against
|
|
186
|
+
the current Ollama API docs before claiming it:
|
|
187
|
+
|
|
188
|
+
- ~~`web_search` / `web_fetch` cloud endpoints~~ — **done**: `client.web_search(query:, max_results:)` and `client.web_fetch(url:)` (`lib/ollama/client/web_search.rb`, documented in `API_CONTRACT.md`).
|
|
189
|
+
- `/api/embeddings` (legacy singular) alongside `/api/embed` — currently only the plural path is used. Low priority: the singular route is Ollama's deprecated legacy alias and `/api/embed` is the documented current endpoint.
|
|
190
|
+
- `/api/generate` `context` parameter (deprecated but present), `suffix` (FIM) — confirmed: `context`, `suffix`, and `raw` are all threaded through `Client::Generate` and `Params::Generate`.
|
|
191
|
+
- ~~Streamed `create`/`pull`/`push` progress: `hooks[:on_progress]` exists but is not in `API_CONTRACT.md`~~ — **done**: documented. (`create_model` itself still has no streaming hook — only `pull`/`push_model` do.)
|
|
192
|
+
- `/api/show` `model_info` fields (real context length, quantization, parameter count) are fetched but discarded — they are the raw material for E4. Still open; tracked as roadmap item E4, not a missing-endpoint gap.
|
|
193
|
+
|
|
194
|
+
## L. The one contested boundary: who runs the tool loop?
|
|
195
|
+
|
|
196
|
+
The strategy says `ollama-client` owns tool *schema, parsing, validation, normalization*, and
|
|
197
|
+
`ollama-agent` owns *execution loops, permissions, scheduling, policies*. RubyLLM puts the loop in core.
|
|
198
|
+
Meanwhile `Ollama::Agent::Executor` already sits in this gem's `lib/`, undeclared in `API_CONTRACT.md`.
|
|
199
|
+
**UPDATE (2026-08): `Ollama::Agent::Executor` is now declared public — it is required by the default
|
|
200
|
+
load path, `#run` uses the current `chat()` API (tool loop included), and string or symbol tool keys
|
|
201
|
+
are both accepted. The strategic question below still stands for future `ollama-agent` work, but the
|
|
202
|
+
loop-in-core default is now implemented.**
|
|
203
|
+
|
|
204
|
+
**Recommendation: put a bounded loop in core, and keep policy out.**
|
|
205
|
+
|
|
206
|
+
Core (`ollama-client`) ships:
|
|
207
|
+
|
|
208
|
+
- `chat.with_tool(T)` and `chat.ask(…)` resolving tool calls automatically up to `max_tool_rounds:` (default small, e.g. 5), raising a typed `Ollama::ToolLoopExhausted` past that.
|
|
209
|
+
- Synchronous, in-order execution. Argument validation against the declared schema. `halt` support.
|
|
210
|
+
|
|
211
|
+
`ollama-agent` owns: permissions and sandboxing, concurrency/scheduling, retries and replanning,
|
|
212
|
+
memory, multi-agent orchestration, human-in-the-loop approval, budget enforcement.
|
|
213
|
+
|
|
214
|
+
Rationale: without execution, `with_tool` is a lie — the user still writes the loop, and the "most Ruby
|
|
215
|
+
developers don't need `ollama-agent`" goal fails on the most common use case. A bounded, policy-free
|
|
216
|
+
loop is a *protocol completion*, not an agent framework. `Agent::Executor` then either becomes the
|
|
217
|
+
private implementation of that loop or moves out to `ollama-agent` at the same time — it should not
|
|
218
|
+
remain a public-ish class in core with no contract.
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## M. Prioritized build order
|
|
223
|
+
|
|
224
|
+
Derived strictly from the matrix. Every P0/P1 item is additive; none touches a signature frozen by
|
|
225
|
+
`API_CONTRACT.md`.
|
|
226
|
+
|
|
227
|
+
### P0 — Correctness (ship before any ergonomics)
|
|
228
|
+
|
|
229
|
+
| Item | Ref | Why first |
|
|
230
|
+
|---|---|---|
|
|
231
|
+
| Route `chat(format:)` through validate → repair → retry | C3 | Two structured-output paths with different reliability guarantees is a defect, and the gem's core claim is deterministic structured output |
|
|
232
|
+
| Prefer `/api/show` server capabilities over name heuristics | E3 | `Capabilities.for` currently guesses from model names; the server knows |
|
|
233
|
+
| Decide the tool-loop boundary and document it | L | Blocks P1 tool work and `Agent::Executor`'s fate |
|
|
234
|
+
|
|
235
|
+
### P1 — Ergonomics parity (the RubyLLM lesson)
|
|
236
|
+
|
|
237
|
+
| Item | Ref |
|
|
238
|
+
|---|---|
|
|
239
|
+
| `Ollama::Chat` stateful conversation + `ask` (+ history sanitizer wired in) | A1, A2, A5, A6 |
|
|
240
|
+
| `with_instructions` / `with_model` / `with_temperature` / `with_think` / `with_keep_alive` | A3, A4 |
|
|
241
|
+
| Block streaming `chat.ask(…) { \|chunk\| }` over the existing NDJSON path | D1 |
|
|
242
|
+
| `class T < Ollama::Tool` with `#execute`; `chat.with_tool(T)`; bounded loop; `halt` | B1, B4, B6, B9 |
|
|
243
|
+
| `class S < Ollama::Schema`; `chat.with_schema(S)` → typed object | C1, C2, C4 |
|
|
244
|
+
| `client.embed("text")` + `default_embedding_model` + `Ollama::Embedding` | F1 |
|
|
245
|
+
| `ask(…, with: "image.png")` sugar | F2 |
|
|
246
|
+
|
|
247
|
+
### P2 — Depth that exceeds RubyLLM
|
|
248
|
+
|
|
249
|
+
| Item | Ref |
|
|
250
|
+
|---|---|
|
|
251
|
+
| `Ollama::Model` + `Ollama::ModelCollection` with live filters and lifecycle methods | E1, E2, E4, E6 |
|
|
252
|
+
| Runtime introspection: `running?`, `expires_at`, `vram`, `performance.tokens_per_second` | E5 |
|
|
253
|
+
| `Ollama::Stream` with `cancel`, `closed?`, `metrics`, `each_chunk` | D4 |
|
|
254
|
+
| Incremental structured-output parsing off the stream | C5 |
|
|
255
|
+
| Nested schema blocks, tool signature inference, schema-validated tool arguments | B2, B3, B10 |
|
|
256
|
+
| Retry/jitter knobs, `config.logger`, proxy support, model alias resolution | G3, G4, G5, E8 |
|
|
257
|
+
|
|
258
|
+
### P3 — Ecosystem extraction
|
|
259
|
+
|
|
260
|
+
`ollama-testing` (H5) → `ollama-rails` (H1–H4) → `ollama-observability` (H6) → `ollama-agent` boundary
|
|
261
|
+
finalized (A10, B8, L) → `ollama-stream` / async (D7).
|
|
262
|
+
|
|
263
|
+
## N. API contract impact
|
|
264
|
+
|
|
265
|
+
| Change class | Contract effect |
|
|
266
|
+
|---|---|
|
|
267
|
+
| `Ollama::Chat`, `Ollama::Model`, `Ollama::Schema`, `Ollama::Embedding`, `Ollama::Stream` | New classes — additive, no impact |
|
|
268
|
+
| `client.chat` with no `messages:` returning a `Chat` | New arity path; `messages:`-present behavior byte-identical. Permitted ("new optional keyword arguments") — but call it out explicitly in the contract |
|
|
269
|
+
| `chat(format:)` gaining validation/repair | **Behavior change.** Today it returns unvalidated content; after, it can raise `SchemaViolationError`. Requires a minor-version note, and arguably a `strict:` opt-out on `chat` to mirror `generate` |
|
|
270
|
+
| `Ollama::Response#cost` | Never — explicitly rejected (E7) |
|
|
271
|
+
| `config.retry_interval`, `config.logger`, `config.default_embedding_model`, `config.http_proxy` | Additive with compatible defaults — permitted |
|
|
272
|
+
| `Ollama::ToolLoopExhausted` | New error under the existing hierarchy — permitted |
|
|
273
|
+
| `Agent::Executor` | Must be either declared public with a contract, made private, or moved to `ollama-agent`. Currently undefined status. **UPDATE (2026-08): declared public and wired into the default load path; `#run` now uses the current `chat()` API.** |
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# ADR-001: OpenAI Compatibility Boundary
|
|
2
|
+
|
|
3
|
+
## Context
|
|
4
|
+
OpenAI request/response semantics can contaminate Ollama-native runtime APIs.
|
|
5
|
+
|
|
6
|
+
## Decision
|
|
7
|
+
OpenAI compatibility is optional and loaded separately.
|
|
8
|
+
|
|
9
|
+
## Consequences
|
|
10
|
+
- cleaner core semantics
|
|
11
|
+
- better long-term extensibility
|
|
12
|
+
- easier extraction to dedicated gem
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# ADR-002: Transport Abstraction
|
|
2
|
+
|
|
3
|
+
## Context
|
|
4
|
+
Direct HTTP coupling prevents adapter evolution and deterministic testing.
|
|
5
|
+
|
|
6
|
+
## Decision
|
|
7
|
+
All runtime HTTP execution goes through transport adapters.
|
|
8
|
+
|
|
9
|
+
## Consequences
|
|
10
|
+
- adapter extensibility (`:net_http`, `:mock`, future async)
|
|
11
|
+
- cleaner request pipeline layering
|
|
12
|
+
- improved testability
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# ADR-003: Normalized Transport Response
|
|
2
|
+
|
|
3
|
+
## Context
|
|
4
|
+
Runtime logic was coupled to backend-specific response objects.
|
|
5
|
+
|
|
6
|
+
## Decision
|
|
7
|
+
Introduce transport response object with normalized fields.
|
|
8
|
+
|
|
9
|
+
## Consequences
|
|
10
|
+
- transport-neutral policies and hooks
|
|
11
|
+
- easier observability
|
|
12
|
+
- incremental migration with compatibility shims
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# ADR-004: Mock Transport
|
|
2
|
+
|
|
3
|
+
## Context
|
|
4
|
+
Deterministic CI and failure simulation require decoupling tests from live Ollama servers.
|
|
5
|
+
|
|
6
|
+
## Decision
|
|
7
|
+
Introduce a `:mock` transport adapter in core transport architecture.
|
|
8
|
+
|
|
9
|
+
## Alternatives rejected
|
|
10
|
+
- Global monkeypatching HTTP
|
|
11
|
+
- Ad-hoc stubs per test
|
|
12
|
+
|
|
13
|
+
## Consequences
|
|
14
|
+
- deterministic request/response tests
|
|
15
|
+
- foundation for replay/latency/failure simulation
|
|
16
|
+
- enables test tooling without runtime coupling
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# ADR-005: Typed Error Taxonomy
|
|
2
|
+
|
|
3
|
+
## Context
|
|
4
|
+
Generic runtime errors made retry/policy behavior inconsistent and hard to classify.
|
|
5
|
+
|
|
6
|
+
## Decision
|
|
7
|
+
Define typed runtime/transport errors and centralized response-to-error mapping.
|
|
8
|
+
|
|
9
|
+
## Alternatives rejected
|
|
10
|
+
- status-code checks at call sites
|
|
11
|
+
- adapter-specific error hierarchies
|
|
12
|
+
|
|
13
|
+
## Consequences
|
|
14
|
+
- consistent retry-safe classification
|
|
15
|
+
- clearer policy layering
|
|
16
|
+
- improved observability and diagnostics
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# ADR-006: Stream Runtime Ownership
|
|
2
|
+
|
|
3
|
+
## Context
|
|
4
|
+
Streaming semantics were at risk of diverging across integrations and experimental repos.
|
|
5
|
+
|
|
6
|
+
## Decision
|
|
7
|
+
`ollama-client` owns canonical stream contracts (lifecycle, cancellation, buffering, ordering).
|
|
8
|
+
|
|
9
|
+
## Alternatives rejected
|
|
10
|
+
- per-integration stream semantics
|
|
11
|
+
- transport-specific user-facing stream APIs
|
|
12
|
+
|
|
13
|
+
## Consequences
|
|
14
|
+
- ecosystem-wide stream consistency
|
|
15
|
+
- cleaner adapter and integration layering
|
|
16
|
+
- reduced drift risk as streaming features expand
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Ecosystem Boundaries
|
|
2
|
+
|
|
3
|
+
## Belongs in `ollama-client`
|
|
4
|
+
|
|
5
|
+
- transport
|
|
6
|
+
- streaming contracts/runtime
|
|
7
|
+
- retries/policy orchestration
|
|
8
|
+
- schema/runtime contracts
|
|
9
|
+
- model lifecycle
|
|
10
|
+
- observability hooks
|
|
11
|
+
|
|
12
|
+
## Must stay out of `ollama-client`
|
|
13
|
+
|
|
14
|
+
- vector DB integrations
|
|
15
|
+
- RAG pipelines
|
|
16
|
+
- memory systems
|
|
17
|
+
- workflow engines
|
|
18
|
+
- chain builders
|
|
19
|
+
- prompt template frameworks
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Dependency Graph
|
|
2
|
+
|
|
3
|
+
Allowed direction:
|
|
4
|
+
|
|
5
|
+
`ollama-client` <- extensions
|
|
6
|
+
|
|
7
|
+
Examples:
|
|
8
|
+
- `ollama-openai` -> `ollama-client`
|
|
9
|
+
- `ollama-agent` -> `ollama-client`
|
|
10
|
+
- `ollama-rails` -> `ollama-client`
|
|
11
|
+
|
|
12
|
+
Disallowed:
|
|
13
|
+
- `ollama-client` -> any extension
|
|
14
|
+
- circular extension dependencies
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Existing Repositories and Contract Ownership
|
|
2
|
+
|
|
3
|
+
This ecosystem uses existing repos as **experimental proving grounds**, not canonical runtime authorities.
|
|
4
|
+
|
|
5
|
+
## Role Mapping
|
|
6
|
+
|
|
7
|
+
| Repository | Primary Role | Reusable Concepts | Must NOT Own |
|
|
8
|
+
|---|---|---|---|
|
|
9
|
+
| `ollama-client` | Canonical runtime kernel | transport contracts, stream semantics, error taxonomy, schema contracts, observability hooks | n/a |
|
|
10
|
+
| `ollama_agent` | Orchestration experimentation | planner patterns, tool execution loops, memory interface ideas | transport contracts, stream/runtime semantics, error taxonomy |
|
|
11
|
+
| `agent-runtime` | Higher-level execution runtime | lifecycle orchestration, scheduling concepts, workflow state transitions | inference transport, canonical response/error contracts |
|
|
12
|
+
|
|
13
|
+
## Dependency Direction
|
|
14
|
+
|
|
15
|
+
```text
|
|
16
|
+
ollama-client
|
|
17
|
+
↑
|
|
18
|
+
ollama-openai
|
|
19
|
+
↑
|
|
20
|
+
ollama-agent
|
|
21
|
+
↑
|
|
22
|
+
agent-runtime
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Disallowed:
|
|
26
|
+
- `ollama-client` depending on orchestration/runtime repos.
|
|
27
|
+
- Upstream repos owning low-level inference contracts.
|
|
28
|
+
|
|
29
|
+
## Rule
|
|
30
|
+
|
|
31
|
+
Experiment in higher-level repos, then stabilize and standardize contracts in `ollama-client`.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Experimental Labs Policy
|
|
2
|
+
|
|
3
|
+
## Principle
|
|
4
|
+
|
|
5
|
+
Experiment there, stabilize here.
|
|
6
|
+
|
|
7
|
+
## Roles
|
|
8
|
+
|
|
9
|
+
| Repository | Role |
|
|
10
|
+
|---|---|
|
|
11
|
+
| `ollama-client` | Stable runtime kernel and canonical contract owner |
|
|
12
|
+
| `ollama_agent` | Orchestration experimentation |
|
|
13
|
+
| `agent-runtime` | Execution/runtime experimentation |
|
|
14
|
+
|
|
15
|
+
## Not owned by experimental repos
|
|
16
|
+
|
|
17
|
+
- transport contracts
|
|
18
|
+
- stream semantics
|
|
19
|
+
- retry semantics
|
|
20
|
+
- response models
|
|
21
|
+
- schema runtime semantics
|
|
22
|
+
- observability semantics
|
|
23
|
+
|
|
24
|
+
These are canonicalized in `ollama-client`.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Ecosystem Overview
|
|
2
|
+
|
|
3
|
+
## Gems
|
|
4
|
+
|
|
5
|
+
- `ollama-client` — runtime kernel
|
|
6
|
+
- `ollama-openai` — OpenAI compatibility translation
|
|
7
|
+
- `ollama-stream` — advanced streaming runtime
|
|
8
|
+
- `ollama-observability` — telemetry and diagnostics
|
|
9
|
+
- `ollama-schema` — typed schema/repair runtime
|
|
10
|
+
- `ollama-testing` — mock/replay/failure simulation
|
|
11
|
+
- `ollama-rails` — Railtie + realtime Rails helpers
|
|
12
|
+
- `ollama-agent` — optional orchestration primitives
|
|
13
|
+
- `ollama-router` — routing and fallback logic
|
|
14
|
+
|
|
15
|
+
## Principles
|
|
16
|
+
|
|
17
|
+
- Small deterministic core
|
|
18
|
+
- Optional integrations
|
|
19
|
+
- No abstraction soup
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
## See also
|
|
23
|
+
|
|
24
|
+
- `docs/ecosystem/EXISTING_REPOS.md`
|
|
25
|
+
- `docs/runtime/TRANSPORT_CONTRACT.md`
|
|
26
|
+
- `docs/runtime/STREAM_CONTRACT.md`
|
|
27
|
+
- `docs/runtime/ERROR_CONTRACT.md`
|
|
28
|
+
- `docs/rfcs/README.md`
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Release Order
|
|
2
|
+
|
|
3
|
+
## Phase 1
|
|
4
|
+
- `ollama-openai`
|
|
5
|
+
- `ollama-testing`
|
|
6
|
+
|
|
7
|
+
## Phase 2
|
|
8
|
+
- `ollama-stream`
|
|
9
|
+
- `ollama-observability`
|
|
10
|
+
|
|
11
|
+
## Phase 3
|
|
12
|
+
- `ollama-schema`
|
|
13
|
+
- `ollama-rails`
|
|
14
|
+
|
|
15
|
+
## Phase 4
|
|
16
|
+
- `ollama-agent`
|
|
17
|
+
- `ollama-router`
|
data/docs/rfcs/README.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# RFC Process
|
|
2
|
+
|
|
3
|
+
Use RFCs for ecosystem-impacting runtime proposals before implementation.
|
|
4
|
+
|
|
5
|
+
## Initial candidates
|
|
6
|
+
|
|
7
|
+
- `0001-stream-runtime.md`
|
|
8
|
+
- `0002-schema-system.md`
|
|
9
|
+
- `0003-observability-hooks.md`
|
|
10
|
+
- `0004-async-runtime.md`
|
|
11
|
+
|
|
12
|
+
## Guideline
|
|
13
|
+
|
|
14
|
+
- Proposals are discussed first.
|
|
15
|
+
- Canonical contracts land in `ollama-client` docs + ADRs.
|
|
16
|
+
- Implementation in other gems must follow accepted runtime contracts.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Error Contract
|
|
2
|
+
|
|
3
|
+
`ollama-client` owns typed runtime error taxonomy and response-to-error mapping.
|
|
4
|
+
|
|
5
|
+
## Principles
|
|
6
|
+
|
|
7
|
+
- Typed errors over generic exceptions
|
|
8
|
+
- Deterministic mapping from transport responses
|
|
9
|
+
- Retry-safe classification lives in core runtime contracts
|
|
10
|
+
|
|
11
|
+
## Direction
|
|
12
|
+
|
|
13
|
+
Higher-level repos should consume and extend behavior via policy, not redefine
|
|
14
|
+
base runtime error classes for transport/inference semantics.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Schema Contract
|
|
2
|
+
|
|
3
|
+
`ollama-client` owns canonical schema/runtime semantics for deterministic structured generation.
|
|
4
|
+
|
|
5
|
+
## Scope
|
|
6
|
+
|
|
7
|
+
- Typed schema declarations
|
|
8
|
+
- Validation and coercion rules
|
|
9
|
+
- Repair strategy contracts
|
|
10
|
+
- Streaming structured output constraints
|
|
11
|
+
|
|
12
|
+
## Required semantics
|
|
13
|
+
|
|
14
|
+
- Deterministic parse contract: valid structured result or typed failure
|
|
15
|
+
- Coercion behavior must be explicit and documented
|
|
16
|
+
- Validation errors must be typed and machine-readable
|
|
17
|
+
- Repair paths must be bounded and observable
|
|
18
|
+
|
|
19
|
+
## Streaming requirements
|
|
20
|
+
|
|
21
|
+
- Incremental parse support
|
|
22
|
+
- Partial AST assembly guarantees
|
|
23
|
+
- Stream-time validation hooks
|
|
24
|
+
- Recovery behavior for malformed partial JSON
|
|
25
|
+
|
|
26
|
+
## Ownership rule
|
|
27
|
+
|
|
28
|
+
Higher-level gems may compose convenience APIs, but must not redefine core schema semantics.
|