smith-agents 0.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 +7 -0
- data/CHANGELOG.md +139 -0
- data/CODE_OF_CONDUCT.md +128 -0
- data/LICENSE +21 -0
- data/README.md +226 -0
- data/Rakefile +14 -0
- data/UPSTREAM_PROPOSAL.md +141 -0
- data/docs/CONFIGURATION.md +123 -0
- data/docs/PATTERNS.md +492 -0
- data/docs/PERSISTENCE.md +169 -0
- data/docs/TOOLS_AND_GUARDRAILS.md +140 -0
- data/docs/workflow_claim.md +58 -0
- data/exe/smith +7 -0
- data/lib/generators/smith/install/install_generator.rb +22 -0
- data/lib/generators/smith/install/templates/smith.rb.tt +44 -0
- data/lib/smith/agent/lifecycle.rb +264 -0
- data/lib/smith/agent/registry.rb +128 -0
- data/lib/smith/agent.rb +259 -0
- data/lib/smith/artifacts/file.rb +59 -0
- data/lib/smith/artifacts/memory.rb +75 -0
- data/lib/smith/artifacts/scoped_store.rb +29 -0
- data/lib/smith/artifacts.rb +5 -0
- data/lib/smith/budget/ledger.rb +42 -0
- data/lib/smith/budget.rb +5 -0
- data/lib/smith/cli.rb +82 -0
- data/lib/smith/context/observation_masking.rb +19 -0
- data/lib/smith/context/session.rb +42 -0
- data/lib/smith/context/state_injection.rb +24 -0
- data/lib/smith/context.rb +61 -0
- data/lib/smith/doctor/check.rb +12 -0
- data/lib/smith/doctor/checks/baseline.rb +84 -0
- data/lib/smith/doctor/checks/configuration.rb +56 -0
- data/lib/smith/doctor/checks/durability.rb +103 -0
- data/lib/smith/doctor/checks/live.rb +55 -0
- data/lib/smith/doctor/checks/models_registry.rb +66 -0
- data/lib/smith/doctor/checks/openai_api_mode.rb +51 -0
- data/lib/smith/doctor/checks/persistence.rb +99 -0
- data/lib/smith/doctor/checks/persistence_capabilities.rb +60 -0
- data/lib/smith/doctor/checks/persistence_registry.rb +82 -0
- data/lib/smith/doctor/checks/rails.rb +39 -0
- data/lib/smith/doctor/checks/serialization.rb +78 -0
- data/lib/smith/doctor/installer.rb +103 -0
- data/lib/smith/doctor/printer.rb +62 -0
- data/lib/smith/doctor/report.rb +39 -0
- data/lib/smith/doctor.rb +53 -0
- data/lib/smith/errors.rb +191 -0
- data/lib/smith/event.rb +11 -0
- data/lib/smith/events/.keep +0 -0
- data/lib/smith/events/bus.rb +60 -0
- data/lib/smith/events/step_completed.rb +11 -0
- data/lib/smith/events/subscription.rb +24 -0
- data/lib/smith/events.rb +5 -0
- data/lib/smith/guardrails/runner.rb +44 -0
- data/lib/smith/guardrails/url_verifier.rb +7 -0
- data/lib/smith/guardrails.rb +35 -0
- data/lib/smith/models/inference.rb +199 -0
- data/lib/smith/models/normalizer.rb +186 -0
- data/lib/smith/models/profile.rb +39 -0
- data/lib/smith/models.rb +132 -0
- data/lib/smith/persistence_adapters/active_record_store.rb +99 -0
- data/lib/smith/persistence_adapters/cache_store.rb +79 -0
- data/lib/smith/persistence_adapters/memory.rb +105 -0
- data/lib/smith/persistence_adapters/rails_cache.rb +20 -0
- data/lib/smith/persistence_adapters/redis_store.rb +136 -0
- data/lib/smith/persistence_adapters/retry.rb +42 -0
- data/lib/smith/persistence_adapters.rb +112 -0
- data/lib/smith/pricing.rb +65 -0
- data/lib/smith/providers/openai/responses.rb +315 -0
- data/lib/smith/providers/openai/routing.rb +67 -0
- data/lib/smith/providers/openai/tools_extensions.rb +106 -0
- data/lib/smith/railtie.rb +9 -0
- data/lib/smith/tasks/doctor.rake +38 -0
- data/lib/smith/tool/budget_enforcement.rb +33 -0
- data/lib/smith/tool/capability_builder.rb +18 -0
- data/lib/smith/tool/capture.rb +22 -0
- data/lib/smith/tool/compatibility.rb +72 -0
- data/lib/smith/tool/policy.rb +40 -0
- data/lib/smith/tool.rb +171 -0
- data/lib/smith/tools/think.rb +25 -0
- data/lib/smith/tools/url_fetcher.rb +16 -0
- data/lib/smith/tools/web_search.rb +17 -0
- data/lib/smith/tools.rb +5 -0
- data/lib/smith/trace/logger.rb +46 -0
- data/lib/smith/trace/memory.rb +53 -0
- data/lib/smith/trace/open_telemetry.rb +57 -0
- data/lib/smith/trace.rb +89 -0
- data/lib/smith/types.rb +16 -0
- data/lib/smith/version.rb +5 -0
- data/lib/smith/workflow/artifact_integration.rb +41 -0
- data/lib/smith/workflow/budget_integration.rb +105 -0
- data/lib/smith/workflow/claim.rb +118 -0
- data/lib/smith/workflow/data_volume_policy.rb +36 -0
- data/lib/smith/workflow/deadline_enforcement.rb +100 -0
- data/lib/smith/workflow/deterministic_execution.rb +53 -0
- data/lib/smith/workflow/deterministic_step.rb +57 -0
- data/lib/smith/workflow/dsl.rb +223 -0
- data/lib/smith/workflow/durability.rb +369 -0
- data/lib/smith/workflow/evaluator_optimizer.rb +220 -0
- data/lib/smith/workflow/event_integration.rb +24 -0
- data/lib/smith/workflow/execution.rb +127 -0
- data/lib/smith/workflow/execution_frame.rb +166 -0
- data/lib/smith/workflow/guardrail_integration.rb +40 -0
- data/lib/smith/workflow/nested_execution.rb +69 -0
- data/lib/smith/workflow/orchestrator_worker.rb +145 -0
- data/lib/smith/workflow/parallel.rb +50 -0
- data/lib/smith/workflow/parallel_execution.rb +75 -0
- data/lib/smith/workflow/persistence.rb +358 -0
- data/lib/smith/workflow/pipeline.rb +117 -0
- data/lib/smith/workflow/router.rb +53 -0
- data/lib/smith/workflow/transition.rb +208 -0
- data/lib/smith/workflow.rb +555 -0
- data/lib/smith.rb +254 -0
- data/script/profile_tool_results.rb +94 -0
- data/sig/smith.rbs +4 -0
- metadata +258 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# Upstream proposal: Capability profiles + before_complete hook for RubyLLM
|
|
2
|
+
|
|
3
|
+
## Motivation
|
|
4
|
+
|
|
5
|
+
Smith (a workflow-first multi-agent orchestration library built on RubyLLM) currently maintains a "shadow registry" of model capabilities — pattern-based provider rules describing how each provider shapes its API payload (Anthropic Opus 4.7+ uses adaptive thinking; OpenAI gpt-5 family needs `/v1/responses` for tools+thinking; Gemini 2.5+ accepts `thinking_budget`; etc.). When the underlying RubyLLM client doesn't know these distinctions, Smith's normalizer rewrites the chat object's `@temperature`, `@thinking`, and `@params` ivars before the request leaves.
|
|
6
|
+
|
|
7
|
+
This works but requires Smith to:
|
|
8
|
+
|
|
9
|
+
1. Maintain a parallel capability registry (`Smith::Models::Inference`)
|
|
10
|
+
2. Mutate RubyLLM-owned chat ivars directly (`@temperature`, `@thinking`) because RubyLLM 1.15 has no `Chat#without_thinking` / `#without_temperature` public API
|
|
11
|
+
3. Vendor PR #770's `/v1/responses` adapter ahead of upstream merge so gpt-5 family can use tools + reasoning_effort together
|
|
12
|
+
|
|
13
|
+
A cleaner design lives upstream in RubyLLM. This proposal describes three additive changes that would let Smith retire ~400 lines of vendored code and ~6 monkey-patches.
|
|
14
|
+
|
|
15
|
+
## Proposed RubyLLM API
|
|
16
|
+
|
|
17
|
+
### 1. `RubyLLM::Chat#without_thinking` and `#without_temperature`
|
|
18
|
+
|
|
19
|
+
Smith currently does `chat.instance_variable_set(:@thinking, nil)` and `chat.instance_variable_set(:@temperature, nil)` because there's no public way to clear these. `with_thinking` requires at least one of `effort:` or `budget:`. Add the no-arg clearers:
|
|
20
|
+
|
|
21
|
+
```ruby
|
|
22
|
+
module RubyLLM
|
|
23
|
+
class Chat
|
|
24
|
+
def without_thinking
|
|
25
|
+
@thinking = nil
|
|
26
|
+
self
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def without_temperature
|
|
30
|
+
@temperature = nil
|
|
31
|
+
self
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Small additive change. Smith retires its only remaining `instance_variable_set` calls.
|
|
38
|
+
|
|
39
|
+
### 2. `RubyLLM::Capabilities::Profile` + `Model::Info#capabilities`
|
|
40
|
+
|
|
41
|
+
Add a structured capability profile to model info:
|
|
42
|
+
|
|
43
|
+
```ruby
|
|
44
|
+
module RubyLLM
|
|
45
|
+
module Capabilities
|
|
46
|
+
Profile = Data.define(
|
|
47
|
+
:thinking_shape, # :budget_tokens | :reasoning_effort | :adaptive | nil
|
|
48
|
+
:accepts_temperature,
|
|
49
|
+
:tools_with_thinking_native,
|
|
50
|
+
:tools_with_thinking_route # :responses | nil for OpenAI
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
# Public registration API. Idempotent.
|
|
54
|
+
def self.register(model_id, profile)
|
|
55
|
+
registry[model_id.to_s] = profile
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def self.find(model_id)
|
|
59
|
+
registry[model_id.to_s]
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def self.registry
|
|
63
|
+
@registry ||= {}
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
class Model::Info
|
|
68
|
+
attr_reader :capabilities # RubyLLM::Capabilities::Profile?
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
RubyLLM's `models.json` could ship default capability profiles for the models it bundles. Smith would migrate its `Smith::Models::Inference` defaults upstream as a `Capabilities.default_rules` table.
|
|
74
|
+
|
|
75
|
+
### 3. `RubyLLM::Provider.before_complete` hook
|
|
76
|
+
|
|
77
|
+
Add an extension point for per-request shaping:
|
|
78
|
+
|
|
79
|
+
```ruby
|
|
80
|
+
module RubyLLM
|
|
81
|
+
class Provider
|
|
82
|
+
# Hosts register normalizers that run AFTER chat construction but
|
|
83
|
+
# BEFORE render_payload. The hook receives the chat and the
|
|
84
|
+
# capabilities profile of the resolved model.
|
|
85
|
+
def self.before_complete(&block)
|
|
86
|
+
normalizers << block
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def self.normalizers
|
|
90
|
+
@normalizers ||= []
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Existing complete(...) signature unchanged. Internally invokes
|
|
94
|
+
# the registered normalizers before render_payload.
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## What Smith looks like after upstream lands
|
|
100
|
+
|
|
101
|
+
```ruby
|
|
102
|
+
# lib/smith.rb (post-upstream)
|
|
103
|
+
# Register Smith's library-shipped pattern rules into RubyLLM's catalog
|
|
104
|
+
Smith::Models::Inference.rules.each do |rule|
|
|
105
|
+
RubyLLM::Capabilities.register_rule(
|
|
106
|
+
provider: rule.provider,
|
|
107
|
+
matcher: rule.matcher,
|
|
108
|
+
profile: rule.to_profile("anyone").to_h.except(:model_id)
|
|
109
|
+
)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Register Smith's normalizer as a public RubyLLM hook
|
|
113
|
+
RubyLLM::Provider.before_complete do |chat, profile|
|
|
114
|
+
Smith::Models::Normalizer.apply!(chat, profile: profile) if profile
|
|
115
|
+
end
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Smith's `Models` registry, the `Smith::Agent.chat()` override, and the `lib/smith/providers/openai/routing.rb` vendor patch all retire. What remains in Smith: the capability defaults table and the normalizer's translation logic — still Smith-owned (orchestration concerns), just consumed through a public RubyLLM hook.
|
|
119
|
+
|
|
120
|
+
## Retirement checklist
|
|
121
|
+
|
|
122
|
+
Once the upstream API ships and Smith adopts it, the following files retire:
|
|
123
|
+
|
|
124
|
+
- `lib/smith/models.rb` — becomes a thin wrapper around `RubyLLM::Capabilities`
|
|
125
|
+
- `lib/smith/models/profile.rb` — replaced by `RubyLLM::Capabilities::Profile`
|
|
126
|
+
- `lib/smith/agent.rb#chat` override — replaced by `RubyLLM::Provider.before_complete` hook
|
|
127
|
+
- `lib/smith/providers/openai/routing.rb` — replaced when PR #770 merges (independent track)
|
|
128
|
+
- `lib/smith/providers/openai/responses.rb` — same
|
|
129
|
+
- `lib/smith/providers/openai/tools_extensions.rb` — same
|
|
130
|
+
|
|
131
|
+
What stays Smith-owned (orchestration concerns, not provider-API concerns):
|
|
132
|
+
|
|
133
|
+
- `lib/smith/models/inference.rb` — pattern rules table; registers itself into RubyLLM via `Capabilities.register_rule`
|
|
134
|
+
- `lib/smith/models/normalizer.rb` — translation logic; registered via `Provider.before_complete`
|
|
135
|
+
- `lib/smith/tool/compatibility.rb` — tool-side compatibility checks
|
|
136
|
+
- The agent / workflow / tool DSLs
|
|
137
|
+
|
|
138
|
+
## Tracking
|
|
139
|
+
|
|
140
|
+
- RubyLLM PR #770 (OpenAI `/v1/responses` support) is the related upstream track. Smith's vendored `Smith::Providers::OpenAI::Responses` retires when #770 merges.
|
|
141
|
+
- This proposal (capability profiles + before_complete) is a separate, additive RubyLLM RFC. Once accepted, Smith files a migration PR to consume it.
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# Configuration
|
|
2
|
+
|
|
3
|
+
There are three different configuration scopes.
|
|
4
|
+
|
|
5
|
+
### 1. Global runtime configuration: `Smith.configure`
|
|
6
|
+
|
|
7
|
+
Use this for shared runtime services:
|
|
8
|
+
|
|
9
|
+
- artifact backend
|
|
10
|
+
- tracing
|
|
11
|
+
- pricing catalog
|
|
12
|
+
- logger
|
|
13
|
+
|
|
14
|
+
### 2. Agent configuration: `Smith::Agent`
|
|
15
|
+
|
|
16
|
+
Use agent classes for invocation behavior:
|
|
17
|
+
|
|
18
|
+
- `model`
|
|
19
|
+
- `tools`
|
|
20
|
+
- `instructions`
|
|
21
|
+
- `temperature`
|
|
22
|
+
- `thinking`
|
|
23
|
+
- `budget`
|
|
24
|
+
- `guardrails`
|
|
25
|
+
- `output_schema`
|
|
26
|
+
- `data_volume`
|
|
27
|
+
- `fallback_models`
|
|
28
|
+
- `register_as`
|
|
29
|
+
|
|
30
|
+
### 3. Workflow configuration: `Smith::Workflow`
|
|
31
|
+
|
|
32
|
+
Use workflow classes for orchestration behavior:
|
|
33
|
+
|
|
34
|
+
- `initial_state`
|
|
35
|
+
- `state`
|
|
36
|
+
- `transition`
|
|
37
|
+
- `pipeline`
|
|
38
|
+
- `budget`
|
|
39
|
+
- `max_transitions`
|
|
40
|
+
- `guardrails`
|
|
41
|
+
- `context_manager`
|
|
42
|
+
|
|
43
|
+
### If You Are Unsure Where Something Goes
|
|
44
|
+
|
|
45
|
+
- "Which model should this agent use?" -> agent class
|
|
46
|
+
- "How do I store artifacts or emit traces?" -> `Smith.configure`
|
|
47
|
+
- "What happens after this step succeeds or fails?" -> workflow class
|
|
48
|
+
- "How many tokens/cost/tool calls can this one invocation use?" -> agent budget
|
|
49
|
+
- "How much total budget can the whole workflow consume?" -> workflow budget
|
|
50
|
+
- "Which provider credentials should the app use?" -> RubyLLM, not Smith
|
|
51
|
+
|
|
52
|
+
### Full `Smith.configure` Example
|
|
53
|
+
|
|
54
|
+
```ruby
|
|
55
|
+
Smith.configure do |config|
|
|
56
|
+
config.artifact_store = Smith::Artifacts::Memory.new
|
|
57
|
+
config.artifact_retention = 3600
|
|
58
|
+
config.artifact_encryption = :none
|
|
59
|
+
config.artifact_tenant_isolation = false
|
|
60
|
+
|
|
61
|
+
config.trace_adapter = Smith::Trace::Logger
|
|
62
|
+
config.trace_transitions = true
|
|
63
|
+
config.trace_tool_calls = true
|
|
64
|
+
config.trace_token_usage = true
|
|
65
|
+
config.trace_cost = true
|
|
66
|
+
config.trace_fields = {
|
|
67
|
+
transition: %i[transition from to],
|
|
68
|
+
tool_call: %i[tool duration]
|
|
69
|
+
}
|
|
70
|
+
config.trace_content = false
|
|
71
|
+
config.trace_retention = 86_400
|
|
72
|
+
config.trace_tenant_isolation = false
|
|
73
|
+
|
|
74
|
+
config.pricing = {
|
|
75
|
+
"gpt-4.1-nano" => {
|
|
76
|
+
input_cost_per_token: 0.0000001,
|
|
77
|
+
output_cost_per_token: 0.0000004
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
config.logger = Logger.new($stdout)
|
|
82
|
+
end
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### What Each `Smith.configure` Setting Is For
|
|
86
|
+
|
|
87
|
+
| Setting | What it controls | Typical first use |
|
|
88
|
+
| --- | --- | --- |
|
|
89
|
+
| `artifact_store` | Where large handoff payloads are stored | Start with `Smith::Artifacts::Memory.new` |
|
|
90
|
+
| `artifact_retention` | Default retention window for artifact expiry checks | Set once you have a cleanup policy |
|
|
91
|
+
| `artifact_encryption` | Metadata-level encryption policy flag | Leave at default until you wire a real backend |
|
|
92
|
+
| `artifact_tenant_isolation` | Require namespaced artifact writes | Enable in multi-tenant systems |
|
|
93
|
+
| `trace_adapter` | Where structural traces go | Use `Smith::Trace::Memory` or `Smith::Trace::Logger` first |
|
|
94
|
+
| `trace_transitions` | Emit transition traces | Usually leave on |
|
|
95
|
+
| `trace_tool_calls` | Emit tool call traces | Usually leave on |
|
|
96
|
+
| `trace_token_usage` | Emit usage traces | Useful for budget visibility |
|
|
97
|
+
| `trace_cost` | Emit cost traces | Useful once pricing is configured |
|
|
98
|
+
| `trace_fields` | Allowlist structural trace fields | Use when you want tighter trace output |
|
|
99
|
+
| `trace_content` | Whether content appears in traces | Leave `false` first |
|
|
100
|
+
| `trace_retention` | Trace retention policy hook | Useful when traces leave memory |
|
|
101
|
+
| `trace_tenant_isolation` | Trace multi-tenant isolation flag | Enable in multi-tenant systems |
|
|
102
|
+
| `pricing` | Best-known model-call cost catalog | Add once you care about `total_cost` |
|
|
103
|
+
| `logger` | Smith's runtime logger | Usually the first setting to add |
|
|
104
|
+
| `persistence_adapter` | Adapter for durable workflow state | `:redis`, `:rails_cache`, `:active_record`, `:memory`, or a custom object |
|
|
105
|
+
| `persistence_options` | Per-adapter options (client, namespace, model, columns) | See "Built-In Persistence Adapters" |
|
|
106
|
+
| `persistence_ttl` | Global TTL for persisted state (Integer/Float seconds; nil = no expiry) | Set when long-tail abandoned workflows accumulate in storage |
|
|
107
|
+
| `persistence_retry_policy` | Exponential-backoff policy for transient adapter I/O failures | Defaults to `{ attempts: 3, base_delay: 0.1, max_delay: 1.0 }` |
|
|
108
|
+
| `test_mode` | Auto-select `:memory` adapter when `persistence_adapter` is nil | Enable in `spec_helper.rb` to skip Redis/cache wiring in tests |
|
|
109
|
+
| `openai_api_mode` | `:auto` routes (gpt-5 family + tools + thinking) via `/v1/responses` using Smith's vendored Responses adapter (sync only; streaming over `/v1/responses` is not yet supported); `:off` drops incompatible tools instead | Leave `:auto` (default) unless you need streaming with the (gpt-5 + tools + thinking) combo, in which case set `:off` for graceful tool-dropping |
|
|
110
|
+
| `trace_normalizer` | Emit `:normalizer_decision` trace events from `Smith::Models::Normalizer` | Useful when debugging cross-provider request shaping |
|
|
111
|
+
| `ruby_llm_model_registry` | `:database` to require an AR-backed RubyLLM model registry; `:bundled` for the JSON fallback | Leave at default unless you've migrated to DB-backed |
|
|
112
|
+
|
|
113
|
+
### Recommended First Additions
|
|
114
|
+
|
|
115
|
+
Add settings in this order:
|
|
116
|
+
|
|
117
|
+
1. `config.logger`
|
|
118
|
+
2. `config.trace_adapter`
|
|
119
|
+
3. `config.artifact_store`
|
|
120
|
+
4. `config.pricing`
|
|
121
|
+
|
|
122
|
+
Do not start by configuring every advanced switch at once.
|
|
123
|
+
|