igniter 0.4.5 → 0.5.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/README.md +217 -0
- data/docs/APPLICATION_V1.md +253 -0
- data/docs/CAPABILITIES_V1.md +207 -0
- data/docs/CONSENSUS_V1.md +477 -0
- data/docs/CONTENT_ADDRESSING_V1.md +221 -0
- data/docs/DATAFLOW_V1.md +274 -0
- data/docs/MESH_V1.md +732 -0
- data/docs/NODE_CACHE_V1.md +324 -0
- data/docs/PROACTIVE_AGENTS_V1.md +293 -0
- data/docs/SERVER_V1.md +200 -1
- data/docs/SKILLS_V1.md +213 -0
- data/docs/STORE_ADAPTERS.md +41 -13
- data/docs/TEMPORAL_V1.md +174 -0
- data/docs/TOOLS_V1.md +347 -0
- data/docs/TRANSCRIPTION_V1.md +403 -0
- data/examples/README.md +37 -0
- data/examples/consensus.rb +239 -0
- data/examples/dataflow.rb +308 -0
- data/examples/elocal_webhook.rb +1 -0
- data/examples/llm_tools.rb +237 -0
- data/examples/mesh.rb +239 -0
- data/examples/mesh_discovery.rb +267 -0
- data/examples/mesh_gossip.rb +162 -0
- data/examples/ringcentral_routing.rb +1 -1
- data/lib/igniter/agents/ai/alert_agent.rb +111 -0
- data/lib/igniter/agents/ai/chain_agent.rb +127 -0
- data/lib/igniter/agents/ai/critic_agent.rb +163 -0
- data/lib/igniter/agents/ai/evaluator_agent.rb +193 -0
- data/lib/igniter/agents/ai/evolution_agent.rb +286 -0
- data/lib/igniter/agents/ai/health_check_agent.rb +122 -0
- data/lib/igniter/agents/ai/observer_agent.rb +184 -0
- data/lib/igniter/agents/ai/planner_agent.rb +210 -0
- data/lib/igniter/agents/ai/router_agent.rb +131 -0
- data/lib/igniter/agents/ai/self_reflection_agent.rb +175 -0
- data/lib/igniter/agents/observability/metrics_agent.rb +130 -0
- data/lib/igniter/agents/pipeline/batch_processor_agent.rb +131 -0
- data/lib/igniter/agents/proactive_agent.rb +208 -0
- data/lib/igniter/agents/reliability/retry_agent.rb +99 -0
- data/lib/igniter/agents/scheduling/cron_agent.rb +110 -0
- data/lib/igniter/agents.rb +56 -0
- data/lib/igniter/application/app_config.rb +32 -0
- data/lib/igniter/application/autoloader.rb +18 -0
- data/lib/igniter/application/generator.rb +157 -0
- data/lib/igniter/application/scheduler.rb +109 -0
- data/lib/igniter/application/yml_loader.rb +39 -0
- data/lib/igniter/application.rb +174 -0
- data/lib/igniter/capabilities.rb +68 -0
- data/lib/igniter/compiler/validators/dependencies_validator.rb +50 -2
- data/lib/igniter/compiler/validators/remote_validator.rb +2 -0
- data/lib/igniter/consensus/cluster.rb +183 -0
- data/lib/igniter/consensus/errors.rb +14 -0
- data/lib/igniter/consensus/executors.rb +43 -0
- data/lib/igniter/consensus/node.rb +320 -0
- data/lib/igniter/consensus/read_query.rb +30 -0
- data/lib/igniter/consensus/state_machine.rb +58 -0
- data/lib/igniter/consensus.rb +58 -0
- data/lib/igniter/content_addressing.rb +133 -0
- data/lib/igniter/contract.rb +12 -0
- data/lib/igniter/dataflow/aggregate_operators.rb +147 -0
- data/lib/igniter/dataflow/aggregate_state.rb +77 -0
- data/lib/igniter/dataflow/diff.rb +37 -0
- data/lib/igniter/dataflow/diff_state.rb +81 -0
- data/lib/igniter/dataflow/incremental_collection_result.rb +39 -0
- data/lib/igniter/dataflow/window_filter.rb +48 -0
- data/lib/igniter/dataflow.rb +65 -0
- data/lib/igniter/dsl/contract_builder.rb +71 -7
- data/lib/igniter/executor.rb +60 -0
- data/lib/igniter/extensions/capabilities.rb +39 -0
- data/lib/igniter/extensions/content_addressing.rb +5 -0
- data/lib/igniter/extensions/dataflow.rb +117 -0
- data/lib/igniter/extensions/mesh.rb +31 -0
- data/lib/igniter/fingerprint.rb +43 -0
- data/lib/igniter/integrations/llm/config.rb +48 -4
- data/lib/igniter/integrations/llm/executor.rb +221 -28
- data/lib/igniter/integrations/llm/providers/anthropic.rb +37 -4
- data/lib/igniter/integrations/llm/providers/openai.rb +34 -5
- data/lib/igniter/integrations/llm/transcription/providers/assemblyai.rb +200 -0
- data/lib/igniter/integrations/llm/transcription/providers/base.rb +122 -0
- data/lib/igniter/integrations/llm/transcription/providers/deepgram.rb +162 -0
- data/lib/igniter/integrations/llm/transcription/providers/openai.rb +102 -0
- data/lib/igniter/integrations/llm/transcription/transcriber.rb +145 -0
- data/lib/igniter/integrations/llm/transcription/transcript_result.rb +29 -0
- data/lib/igniter/integrations/llm.rb +37 -1
- data/lib/igniter/memory/agent_memory.rb +104 -0
- data/lib/igniter/memory/episode.rb +29 -0
- data/lib/igniter/memory/fact.rb +27 -0
- data/lib/igniter/memory/memorable.rb +90 -0
- data/lib/igniter/memory/reflection_cycle.rb +96 -0
- data/lib/igniter/memory/reflection_record.rb +28 -0
- data/lib/igniter/memory/store.rb +115 -0
- data/lib/igniter/memory/stores/in_memory.rb +136 -0
- data/lib/igniter/memory/stores/sqlite.rb +284 -0
- data/lib/igniter/memory.rb +80 -0
- data/lib/igniter/mesh/announcer.rb +55 -0
- data/lib/igniter/mesh/config.rb +45 -0
- data/lib/igniter/mesh/discovery.rb +39 -0
- data/lib/igniter/mesh/errors.rb +31 -0
- data/lib/igniter/mesh/gossip.rb +47 -0
- data/lib/igniter/mesh/peer.rb +21 -0
- data/lib/igniter/mesh/peer_registry.rb +51 -0
- data/lib/igniter/mesh/poller.rb +77 -0
- data/lib/igniter/mesh/router.rb +109 -0
- data/lib/igniter/mesh.rb +85 -0
- data/lib/igniter/metrics/collector.rb +131 -0
- data/lib/igniter/metrics/prometheus_exporter.rb +104 -0
- data/lib/igniter/metrics/snapshot.rb +8 -0
- data/lib/igniter/metrics.rb +37 -0
- data/lib/igniter/model/aggregate_node.rb +34 -0
- data/lib/igniter/model/collection_node.rb +3 -2
- data/lib/igniter/model/compute_node.rb +13 -0
- data/lib/igniter/model/remote_node.rb +18 -2
- data/lib/igniter/node_cache.rb +231 -0
- data/lib/igniter/replication/bootstrapper.rb +61 -0
- data/lib/igniter/replication/bootstrappers/gem.rb +32 -0
- data/lib/igniter/replication/bootstrappers/git.rb +39 -0
- data/lib/igniter/replication/bootstrappers/tarball.rb +56 -0
- data/lib/igniter/replication/expansion_plan.rb +38 -0
- data/lib/igniter/replication/expansion_planner.rb +142 -0
- data/lib/igniter/replication/manifest.rb +45 -0
- data/lib/igniter/replication/network_topology.rb +123 -0
- data/lib/igniter/replication/node_role.rb +42 -0
- data/lib/igniter/replication/reflective_replication_agent.rb +238 -0
- data/lib/igniter/replication/replication_agent.rb +87 -0
- data/lib/igniter/replication/role_registry.rb +73 -0
- data/lib/igniter/replication/ssh_session.rb +77 -0
- data/lib/igniter/replication.rb +54 -0
- data/lib/igniter/runtime/execution.rb +18 -0
- data/lib/igniter/runtime/input_validator.rb +6 -2
- data/lib/igniter/runtime/resolver.rb +254 -16
- data/lib/igniter/runtime/stores/redis_store.rb +41 -4
- data/lib/igniter/server/client.rb +44 -1
- data/lib/igniter/server/config.rb +13 -6
- data/lib/igniter/server/handlers/event_handler.rb +4 -0
- data/lib/igniter/server/handlers/execute_handler.rb +6 -0
- data/lib/igniter/server/handlers/liveness_handler.rb +20 -0
- data/lib/igniter/server/handlers/manifest_handler.rb +34 -0
- data/lib/igniter/server/handlers/metrics_handler.rb +51 -0
- data/lib/igniter/server/handlers/peers_handler.rb +115 -0
- data/lib/igniter/server/handlers/readiness_handler.rb +47 -0
- data/lib/igniter/server/http_server.rb +54 -17
- data/lib/igniter/server/router.rb +54 -21
- data/lib/igniter/server/server_logger.rb +52 -0
- data/lib/igniter/server.rb +6 -0
- data/lib/igniter/skill/feedback.rb +116 -0
- data/lib/igniter/skill/output_schema.rb +110 -0
- data/lib/igniter/skill.rb +218 -0
- data/lib/igniter/temporal.rb +84 -0
- data/lib/igniter/tool/discoverable.rb +151 -0
- data/lib/igniter/tool.rb +52 -0
- data/lib/igniter/tool_registry.rb +144 -0
- data/lib/igniter/version.rb +1 -1
- data/lib/igniter.rb +17 -0
- metadata +122 -1
data/docs/TOOLS_V1.md
ADDED
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
# Igniter::Tool — AI-Callable Tools — v1
|
|
2
|
+
|
|
3
|
+
`Igniter::Tool` bridges declarative Igniter primitives with LLM function-calling APIs.
|
|
4
|
+
Each tool is an `Igniter::Executor` subclass enriched with metadata — so it can be
|
|
5
|
+
used both as a standard compute node in any `Igniter::Contract` graph AND as an
|
|
6
|
+
AI-callable function with auto-generated JSON schemas for Anthropic / OpenAI.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Overview
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
Igniter::Tool < Igniter::Executor
|
|
14
|
+
│
|
|
15
|
+
├── description "..." ← for LLM context
|
|
16
|
+
├── param :name, type:, ... ← auto-generates JSON Schema
|
|
17
|
+
├── requires_capability :x ← capability guard (enforced before call)
|
|
18
|
+
│
|
|
19
|
+
├── to_schema(:anthropic) ← Anthropic tool definition Hash
|
|
20
|
+
├── to_schema(:openai) ← OpenAI tool definition Hash
|
|
21
|
+
│
|
|
22
|
+
└── def call(**kwargs) ← implementation
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Defining a tool
|
|
28
|
+
|
|
29
|
+
```ruby
|
|
30
|
+
require "igniter/tool"
|
|
31
|
+
|
|
32
|
+
class DatabaseLookup < Igniter::Tool
|
|
33
|
+
description "Look up a product by SKU in the catalog"
|
|
34
|
+
|
|
35
|
+
param :sku, type: :string, required: true, desc: "Product SKU identifier"
|
|
36
|
+
param :fields, type: :array, default: nil, desc: "Fields to return (nil = all)"
|
|
37
|
+
|
|
38
|
+
requires_capability :database_read # LLM executor must declare this capability
|
|
39
|
+
|
|
40
|
+
def call(sku:, fields: nil)
|
|
41
|
+
product = ProductCatalog.find(sku)
|
|
42
|
+
fields ? product.slice(*fields) : product
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Supported param types
|
|
48
|
+
|
|
49
|
+
| Symbol | JSON type |
|
|
50
|
+
|--------|-----------|
|
|
51
|
+
| `:string` | `"string"` |
|
|
52
|
+
| `:integer` | `"integer"` |
|
|
53
|
+
| `:float` | `"number"` |
|
|
54
|
+
| `:boolean` | `"boolean"` |
|
|
55
|
+
| `:array` | `"array"` |
|
|
56
|
+
| `:object` | `"object"` |
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Schema generation
|
|
61
|
+
|
|
62
|
+
```ruby
|
|
63
|
+
DatabaseLookup.tool_name # => "database_lookup" (ClassName → snake_case)
|
|
64
|
+
|
|
65
|
+
# Intermediate format (used internally by Igniter, processed by normalize_tools)
|
|
66
|
+
DatabaseLookup.to_schema
|
|
67
|
+
# => { name: "database_lookup", description: "...", parameters: { ... } }
|
|
68
|
+
|
|
69
|
+
# Provider-specific final formats
|
|
70
|
+
DatabaseLookup.to_schema(:anthropic)
|
|
71
|
+
# => { name: "database_lookup", description: "...", input_schema: { ... } }
|
|
72
|
+
|
|
73
|
+
DatabaseLookup.to_schema(:openai)
|
|
74
|
+
# => { type: "function", function: { name: "database_lookup", ... } }
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## `Igniter::ToolRegistry` — discovery and schema export
|
|
80
|
+
|
|
81
|
+
```ruby
|
|
82
|
+
require "igniter/tool_registry"
|
|
83
|
+
|
|
84
|
+
# Global registration (typically in an initializer)
|
|
85
|
+
Igniter::ToolRegistry.register(Calculator, DatabaseLookup, SendEmail)
|
|
86
|
+
|
|
87
|
+
# Discovery
|
|
88
|
+
Igniter::ToolRegistry.all # => [Calculator, DatabaseLookup, SendEmail]
|
|
89
|
+
Igniter::ToolRegistry.find("calculator") # => Calculator
|
|
90
|
+
|
|
91
|
+
# Capability filtering — only tools the agent is authorized to call
|
|
92
|
+
Igniter::ToolRegistry.tools_for(capabilities: [:database_read])
|
|
93
|
+
# => [Calculator, DatabaseLookup] (SendEmail needs :email_send)
|
|
94
|
+
|
|
95
|
+
# Schema export
|
|
96
|
+
Igniter::ToolRegistry.schemas # intermediate, all tools
|
|
97
|
+
Igniter::ToolRegistry.schemas(:anthropic) # Anthropic format, all
|
|
98
|
+
Igniter::ToolRegistry.schemas(:openai, capabilities: [:database_read]) # filtered
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Capability guard
|
|
104
|
+
|
|
105
|
+
`requires_capability` declares what the calling agent must be allowed to do.
|
|
106
|
+
The guard runs **before `call`** — if the agent lacks a required capability,
|
|
107
|
+
`Igniter::Tool::CapabilityError` is raised immediately.
|
|
108
|
+
|
|
109
|
+
```ruby
|
|
110
|
+
class SendEmail < Igniter::Tool
|
|
111
|
+
description "Send an email to a recipient"
|
|
112
|
+
param :to, type: :string, required: true
|
|
113
|
+
param :subject, type: :string, required: true
|
|
114
|
+
param :body, type: :string, required: true
|
|
115
|
+
requires_capability :email_send
|
|
116
|
+
|
|
117
|
+
def call(to:, subject:, body:)
|
|
118
|
+
EmailService.deliver(to: to, subject: subject, body: body)
|
|
119
|
+
{ sent: true }
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Direct guard check
|
|
124
|
+
SendEmail.new.call_with_capability_check!(
|
|
125
|
+
allowed_capabilities: [:email_send],
|
|
126
|
+
to: "user@example.com", subject: "Hello", body: "..."
|
|
127
|
+
)
|
|
128
|
+
# => { sent: true }
|
|
129
|
+
|
|
130
|
+
SendEmail.new.call_with_capability_check!(
|
|
131
|
+
allowed_capabilities: [], # missing :email_send
|
|
132
|
+
to: "user@example.com", ...
|
|
133
|
+
)
|
|
134
|
+
# => Igniter::Tool::CapabilityError:
|
|
135
|
+
# Tool "send_email" requires capabilities [:email_send] but agent only has []
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
The agent's capabilities come from the `Igniter::Executor.capabilities` DSL
|
|
139
|
+
inherited by `LLM::Executor`:
|
|
140
|
+
|
|
141
|
+
```ruby
|
|
142
|
+
class SupportAgent < Igniter::LLM::Executor
|
|
143
|
+
capabilities :database_read, :email_send # what this agent may do
|
|
144
|
+
tools DatabaseLookup, SendEmail
|
|
145
|
+
...
|
|
146
|
+
end
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## LLM::Executor — automatic tool-use loop
|
|
152
|
+
|
|
153
|
+
When `tools` DSL contains `Igniter::Tool` subclasses, `#complete` runs an
|
|
154
|
+
automatic loop:
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
LLM request + tool schemas
|
|
158
|
+
│
|
|
159
|
+
▼
|
|
160
|
+
┌─────────────────┐
|
|
161
|
+
│ LLM response │── text only ──► return text
|
|
162
|
+
└─────────────────┘
|
|
163
|
+
│ tool_use blocks
|
|
164
|
+
▼
|
|
165
|
+
CapabilityGuard.check! ← raises CapabilityError if cap missing
|
|
166
|
+
│
|
|
167
|
+
▼
|
|
168
|
+
Tool#call(**arguments) ← error text returned if StandardError
|
|
169
|
+
│
|
|
170
|
+
▼
|
|
171
|
+
Append :tool_results message
|
|
172
|
+
│
|
|
173
|
+
└──► repeat (up to max_tool_iterations)
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
```ruby
|
|
177
|
+
class ProductAssistant < Igniter::LLM::Executor
|
|
178
|
+
provider :anthropic
|
|
179
|
+
model "claude-haiku-4-5-20251001"
|
|
180
|
+
system_prompt "You are a product assistant. Use tools when needed."
|
|
181
|
+
|
|
182
|
+
tools Calculator, DatabaseLookup, SendEmail
|
|
183
|
+
capabilities :database_read, :email_send # authorizes these tools
|
|
184
|
+
max_tool_iterations 8 # default: 10
|
|
185
|
+
|
|
186
|
+
def call(question:)
|
|
187
|
+
complete(question)
|
|
188
|
+
# complete() automatically:
|
|
189
|
+
# 1. Sends tool schemas to Anthropic API
|
|
190
|
+
# 2. Handles tool_use responses in a loop
|
|
191
|
+
# 3. Returns final text when LLM stops calling tools
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
assistant = ProductAssistant.new
|
|
196
|
+
answer = assistant.call(question: "What's the price of SKU-001 and apply 15% discount?")
|
|
197
|
+
puts answer
|
|
198
|
+
# "Widget Pro (SKU-001) costs $29.99. With a 15% discount, the final price is $25.49."
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Error handling in the loop
|
|
202
|
+
|
|
203
|
+
| Situation | Behaviour |
|
|
204
|
+
|-----------|-----------|
|
|
205
|
+
| Tool raises `CapabilityError` | Re-raised immediately — loop stops |
|
|
206
|
+
| Tool raises any other error | Error message string returned as tool result; LLM can recover |
|
|
207
|
+
| Unknown tool name in response | `"Unknown tool: ..."` returned as tool result |
|
|
208
|
+
| Loop exceeds `max_tool_iterations` | `Igniter::LLM::ToolLoopError` raised |
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## Tool as a Contract compute node
|
|
213
|
+
|
|
214
|
+
`Tool < Executor` — full backward compatibility. Use a tool anywhere an executor is used:
|
|
215
|
+
|
|
216
|
+
```ruby
|
|
217
|
+
class PriceReport < Igniter::Contract
|
|
218
|
+
define do
|
|
219
|
+
input :sku
|
|
220
|
+
|
|
221
|
+
compute :product, with: :sku, call: DatabaseLookup
|
|
222
|
+
compute :discount, with: :product, call: DiscountCalculator
|
|
223
|
+
compute :report, with: [:product, :discount], call: ReportFormatter
|
|
224
|
+
|
|
225
|
+
output :report
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
report = PriceReport.new(sku: "SKU-002")
|
|
230
|
+
report.resolve_all
|
|
231
|
+
puts report.result.report
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
The tool's `requires_capability` is not enforced in Contract graphs — it only
|
|
235
|
+
applies when the tool is invoked through the LLM tool-use path (via
|
|
236
|
+
`call_with_capability_check!`). Regular `call` (Executor protocol) is unrestricted.
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## Provider message format
|
|
241
|
+
|
|
242
|
+
The tool-use loop produces provider-agnostic messages that each provider's
|
|
243
|
+
`normalize_messages` converts:
|
|
244
|
+
|
|
245
|
+
```ruby
|
|
246
|
+
# Executor sends (provider-agnostic)
|
|
247
|
+
{ role: "assistant", content: "", tool_calls: [{ id: "id1", name: "calculator", arguments: { expression: "2+2" } }] }
|
|
248
|
+
{ role: :tool_results, results: [{ id: "id1", name: "calculator", content: "4" }] }
|
|
249
|
+
|
|
250
|
+
# Anthropic receives
|
|
251
|
+
{ "role" => "assistant", "content" => [{ "type" => "tool_use", "id" => "id1", ... }] }
|
|
252
|
+
{ "role" => "user", "content" => [{ "type" => "tool_result", "tool_use_id" => "id1", "content" => "4" }] }
|
|
253
|
+
|
|
254
|
+
# OpenAI receives
|
|
255
|
+
{ "role" => "assistant", "tool_calls" => [{ "id" => "id1", "type" => "function", ... }] }
|
|
256
|
+
{ "role" => "tool", "tool_call_id" => "id1", "content" => "4" }
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
Each provider handles its own format in `normalize_messages`.
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
## Full example
|
|
264
|
+
|
|
265
|
+
```ruby
|
|
266
|
+
require "igniter/tool"
|
|
267
|
+
require "igniter/tool_registry"
|
|
268
|
+
require "igniter/integrations/llm"
|
|
269
|
+
|
|
270
|
+
class SearchWeb < Igniter::Tool
|
|
271
|
+
description "Search the internet for current information"
|
|
272
|
+
param :query, type: :string, required: true
|
|
273
|
+
param :max_results, type: :integer, default: 5
|
|
274
|
+
requires_capability :web_access
|
|
275
|
+
|
|
276
|
+
def call(query:, max_results: 5)
|
|
277
|
+
WebSearchClient.search(query, limit: max_results)
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
class WriteReport < Igniter::Tool
|
|
282
|
+
description "Write a markdown report to a file"
|
|
283
|
+
param :filename, type: :string, required: true
|
|
284
|
+
param :content, type: :string, required: true
|
|
285
|
+
requires_capability :filesystem_write
|
|
286
|
+
|
|
287
|
+
def call(filename:, content:)
|
|
288
|
+
File.write(filename, content)
|
|
289
|
+
{ written: true, path: filename }
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
# Register globally
|
|
294
|
+
Igniter::ToolRegistry.register(SearchWeb, WriteReport)
|
|
295
|
+
|
|
296
|
+
# LLM executor with auto-loop
|
|
297
|
+
class ResearchAgent < Igniter::LLM::Executor
|
|
298
|
+
provider :anthropic
|
|
299
|
+
model "claude-sonnet-4-6"
|
|
300
|
+
system_prompt "Research assistant. Search, synthesize, write reports."
|
|
301
|
+
|
|
302
|
+
tools SearchWeb, WriteReport
|
|
303
|
+
capabilities :web_access, :filesystem_write
|
|
304
|
+
max_tool_iterations 10
|
|
305
|
+
|
|
306
|
+
def call(topic:, output_file:)
|
|
307
|
+
complete("Research '#{topic}' and write a report to #{output_file}")
|
|
308
|
+
end
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
# In a Contract pipeline
|
|
312
|
+
class ResearchPipeline < Igniter::Contract
|
|
313
|
+
runner :thread_pool, pool_size: 3
|
|
314
|
+
|
|
315
|
+
define do
|
|
316
|
+
input :topics # Array<String>
|
|
317
|
+
input :output_dir
|
|
318
|
+
|
|
319
|
+
compose :report1, with: [:topics, :output_dir], contract: Class.new(Igniter::Contract) {
|
|
320
|
+
define do
|
|
321
|
+
input :topics
|
|
322
|
+
input :output_dir
|
|
323
|
+
compute :result, with: [:topics, :output_dir], call: ResearchAgent
|
|
324
|
+
output :result
|
|
325
|
+
end
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
output :report1
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
---
|
|
334
|
+
|
|
335
|
+
## Files
|
|
336
|
+
|
|
337
|
+
| File | Purpose |
|
|
338
|
+
|------|---------|
|
|
339
|
+
| `lib/igniter/tool.rb` | `Igniter::Tool` base class — DSL, schema, capability guard |
|
|
340
|
+
| `lib/igniter/tool_registry.rb` | Global registry + capability-filtered discovery |
|
|
341
|
+
| `lib/igniter/integrations/llm/executor.rb` | Auto tool-use loop in `#complete`, `max_tool_iterations` |
|
|
342
|
+
| `lib/igniter/integrations/llm/providers/anthropic.rb` | Tool message normalization (Anthropic format) |
|
|
343
|
+
| `lib/igniter/integrations/llm/providers/openai.rb` | Tool message normalization (OpenAI format) |
|
|
344
|
+
| `spec/igniter/tool_spec.rb` | Tool unit tests (40 examples) |
|
|
345
|
+
| `spec/igniter/tool_registry_spec.rb` | Registry tests (20 examples) |
|
|
346
|
+
| `spec/igniter/integrations/llm_tool_loop_spec.rb` | Loop + guards tests (mock provider, 27 examples) |
|
|
347
|
+
| `examples/llm_tools.rb` | Full demo |
|