riffer 0.7.0 → 0.8.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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/.agents/architecture.md +113 -0
  3. data/.agents/code-style.md +42 -0
  4. data/.agents/providers.md +46 -0
  5. data/.agents/rdoc.md +51 -0
  6. data/.agents/testing.md +56 -0
  7. data/.release-please-manifest.json +1 -1
  8. data/AGENTS.md +21 -308
  9. data/CHANGELOG.md +10 -0
  10. data/README.md +21 -112
  11. data/Rakefile +1 -1
  12. data/docs/01_OVERVIEW.md +106 -0
  13. data/docs/02_GETTING_STARTED.md +128 -0
  14. data/docs/03_AGENTS.md +226 -0
  15. data/docs/04_TOOLS.md +251 -0
  16. data/docs/05_MESSAGES.md +173 -0
  17. data/docs/06_STREAM_EVENTS.md +191 -0
  18. data/docs/07_CONFIGURATION.md +195 -0
  19. data/docs_providers/01_PROVIDERS.md +168 -0
  20. data/docs_providers/02_AMAZON_BEDROCK.md +196 -0
  21. data/docs_providers/03_ANTHROPIC.md +211 -0
  22. data/docs_providers/04_OPENAI.md +157 -0
  23. data/docs_providers/05_TEST_PROVIDER.md +163 -0
  24. data/docs_providers/06_CUSTOM_PROVIDERS.md +304 -0
  25. data/lib/riffer/agent.rb +97 -43
  26. data/lib/riffer/config.rb +20 -12
  27. data/lib/riffer/core.rb +7 -7
  28. data/lib/riffer/helpers/class_name_converter.rb +6 -3
  29. data/lib/riffer/helpers/dependencies.rb +18 -0
  30. data/lib/riffer/helpers/validations.rb +9 -0
  31. data/lib/riffer/messages/assistant.rb +23 -1
  32. data/lib/riffer/messages/base.rb +15 -0
  33. data/lib/riffer/messages/converter.rb +15 -5
  34. data/lib/riffer/messages/system.rb +8 -1
  35. data/lib/riffer/messages/tool.rb +45 -2
  36. data/lib/riffer/messages/user.rb +8 -1
  37. data/lib/riffer/messages.rb +7 -0
  38. data/lib/riffer/providers/amazon_bedrock.rb +8 -4
  39. data/lib/riffer/providers/anthropic.rb +209 -0
  40. data/lib/riffer/providers/base.rb +17 -12
  41. data/lib/riffer/providers/open_ai.rb +7 -1
  42. data/lib/riffer/providers/repository.rb +9 -4
  43. data/lib/riffer/providers/test.rb +25 -7
  44. data/lib/riffer/providers.rb +6 -0
  45. data/lib/riffer/stream_events/base.rb +13 -1
  46. data/lib/riffer/stream_events/reasoning_delta.rb +15 -1
  47. data/lib/riffer/stream_events/reasoning_done.rb +15 -1
  48. data/lib/riffer/stream_events/text_delta.rb +14 -1
  49. data/lib/riffer/stream_events/text_done.rb +14 -1
  50. data/lib/riffer/stream_events/tool_call_delta.rb +18 -11
  51. data/lib/riffer/stream_events/tool_call_done.rb +22 -12
  52. data/lib/riffer/stream_events.rb +9 -0
  53. data/lib/riffer/tool.rb +57 -25
  54. data/lib/riffer/tools/param.rb +19 -16
  55. data/lib/riffer/tools/params.rb +28 -22
  56. data/lib/riffer/tools.rb +5 -0
  57. data/lib/riffer/version.rb +1 -1
  58. data/lib/riffer.rb +21 -21
  59. metadata +34 -1
data/README.md CHANGED
@@ -2,26 +2,7 @@
2
2
 
3
3
  The all-in-one Ruby framework for building AI-powered applications and agents.
4
4
 
5
- [![Gem Version](https://badge.fury.io/rb/riffer.svg)](https://badge.fury.io/rb/riffer) ⚠️ Work in progress
6
-
7
- ## Overview
8
-
9
- Riffer is a comprehensive Ruby framework designed to simplify the development of AI-powered applications and agents. It provides a complete toolkit for integrating artificial intelligence capabilities into your Ruby projects.
10
-
11
- Key concepts:
12
-
13
- - **Agents** – orchestrate messages, LLM calls, and tool execution (`Riffer::Agent`).
14
- - **Tools** – define callable functions that agents can use to interact with external systems (`Riffer::Tool`).
15
- - **Providers** – adapters that implement text generation and streaming (`Riffer::Providers::*`).
16
- - **Messages** – typed message objects for system, user, assistant, and tool messages (`Riffer::Messages::*`).
17
-
18
- ## Features
19
-
20
- - Minimal, well-documented core for building AI agents
21
- - Tool calling support with parameter validation
22
- - Provider abstraction (OpenAI, Amazon Bedrock) for pluggable providers
23
- - Streaming support and structured stream events
24
- - Message converters and helpers for robust message handling
5
+ [![Gem Version](https://badge.fury.io/rb/riffer.svg)](https://badge.fury.io/rb/riffer)
25
6
 
26
7
  ## Requirements
27
8
 
@@ -41,121 +22,49 @@ Or add to your application's Gemfile:
41
22
  gem 'riffer'
42
23
  ```
43
24
 
44
- Install the development branch directly from GitHub:
45
-
46
- ```ruby
47
- gem 'riffer', git: 'https://github.com/janeapp/riffer.git'
48
- ```
49
-
50
25
  ## Quick Start
51
26
 
52
- Basic usage with the OpenAI provider:
53
-
54
27
  ```ruby
55
28
  require 'riffer'
56
29
 
57
- # Configure OpenAI API key (recommended to use ENV)
30
+ # Configure your provider
58
31
  Riffer.configure do |config|
59
32
  config.openai.api_key = ENV['OPENAI_API_KEY']
60
33
  end
61
34
 
35
+ # Define an agent
62
36
  class EchoAgent < Riffer::Agent
63
- model 'openai/gpt-5-mini' # provider/model
37
+ model 'openai/gpt-4o'
64
38
  instructions 'You are an assistant that repeats what the user says.'
65
39
  end
66
40
 
41
+ # Use the agent
67
42
  agent = EchoAgent.new
68
43
  puts agent.generate('Hello world')
69
- # => "Hello world"
70
- ```
71
-
72
- Streaming example:
73
-
74
- ```ruby
75
- agent = EchoAgent.new
76
- agent.stream('Tell me a story').each do |event|
77
- print event.content
78
- end
79
- ```
80
-
81
- ### Provider & Model Options
82
-
83
- Agents support two optional configuration methods for passing options through to the underlying provider:
84
-
85
- ```ruby
86
- class MyAgent < Riffer::Agent
87
- model 'openai/gpt-4o'
88
- instructions 'You are a helpful assistant.'
89
-
90
- # Options passed directly to the provider client (e.g., OpenAI::Client)
91
- provider_options api_key: ENV['CUSTOM_OPENAI_KEY']
92
-
93
- # Options passed to the model invocation (e.g., reasoning, temperature)
94
- model_options reasoning: 'medium'
95
- end
96
- ```
97
-
98
- - `provider_options` - Hash of options passed to the provider client during instantiation
99
- - `model_options` - Hash of options passed to `generate_text` / `stream_text` calls
100
-
101
- ### Tools
102
-
103
- Tools allow agents to interact with external systems. Define a tool by extending `Riffer::Tool`:
104
-
105
- ```ruby
106
- class WeatherLookupTool < Riffer::Tool
107
- description "Provides current weather information for a specified city."
108
-
109
- params do
110
- required :city, String, description: "The city to look up"
111
- optional :units, String, default: "celsius", enum: ["celsius", "fahrenheit"]
112
- end
113
-
114
- def call(context:, city:, units: nil)
115
- weather = WeatherService.lookup(city, units: units || "celsius")
116
- "The weather in #{city} is #{weather.temperature} #{units}."
117
- end
118
- end
119
- ```
120
-
121
- Register tools with an agent using `uses_tools`:
122
-
123
- ```ruby
124
- class WeatherAgent < Riffer::Agent
125
- model 'openai/gpt-4o'
126
- instructions 'You are a helpful weather assistant.'
127
-
128
- uses_tools [WeatherLookupTool]
129
- end
130
-
131
- agent = WeatherAgent.new
132
- puts agent.generate("What's the weather in Toronto?")
133
44
  ```
134
45
 
135
- Tools can also be dynamically resolved at runtime. The lambda receives `tool_context` when it accepts a parameter, enabling conditional tool resolution based on the current user or request:
46
+ ## Documentation
136
47
 
137
- ```ruby
138
- class DynamicAgent < Riffer::Agent
139
- model 'openai/gpt-4o'
48
+ For comprehensive documentation, see the [docs](docs/) directory:
140
49
 
141
- uses_tools ->(context) {
142
- tools = [WeatherLookupTool]
143
- tools << AdminTool if context&.dig(:current_user)&.admin?
144
- tools
145
- }
146
- end
50
+ - [Overview](docs/01_OVERVIEW.md) - Core concepts and architecture
51
+ - [Getting Started](docs/02_GETTING_STARTED.md) - Installation and first steps
52
+ - [Agents](docs/03_AGENTS.md) - Building AI agents
53
+ - [Tools](docs/04_TOOLS.md) - Creating tools for agents
54
+ - [Messages](docs/05_MESSAGES.md) - Message types and formats
55
+ - [Stream Events](docs/06_STREAM_EVENTS.md) - Streaming responses
56
+ - [Configuration](docs/07_CONFIGURATION.md) - Framework configuration
57
+ - [Providers](docs_providers/01_PROVIDERS.md) - LLM provider adapters
147
58
 
148
- agent = DynamicAgent.new
149
- agent.generate("Do admin things", tool_context: { current_user: current_user })
150
- ```
59
+ ### API Reference
151
60
 
152
- Pass context to tools using `tool_context`:
61
+ Generate the full API documentation with:
153
62
 
154
- ```ruby
155
- agent.generate("Look up my city", tool_context: { user_id: current_user.id })
63
+ ```bash
64
+ bundle exec rake docs
156
65
  ```
157
66
 
158
- The `context` keyword argument is passed to every tool's `call` method, allowing tools to access shared state like user information, database connections, or API clients.
67
+ Then open `doc/index.html` in your browser.
159
68
 
160
69
  ## Development
161
70
 
@@ -202,4 +111,4 @@ Licensed under the MIT License. See `LICENSE.txt` for details.
202
111
 
203
112
  ## Maintainers
204
113
 
205
- - Jake Bottrall https://github.com/bottrall
114
+ - Jake Bottrall - https://github.com/bottrall
data/Rakefile CHANGED
@@ -14,7 +14,7 @@ RDoc::Task.new do |rdoc|
14
14
  rdoc.main = "README.md"
15
15
 
16
16
  # Explicitly include top-level docs and the library
17
- rdoc.rdoc_files.include("README.md", "CHANGELOG.md", "LICENSE.txt")
17
+ rdoc.rdoc_files.include("README.md", "CHANGELOG.md", "LICENSE.txt", "docs/**/*.md", "docs_providers/**/*.md")
18
18
  rdoc.rdoc_files.include("lib/**/*.rb")
19
19
 
20
20
  # Use Markdown where available and ensure UTF-8
@@ -0,0 +1,106 @@
1
+ # Overview
2
+
3
+ Riffer is a Ruby framework for building AI-powered applications and agents. It provides a complete toolkit for integrating Large Language Models (LLMs) into your Ruby projects.
4
+
5
+ ## Core Concepts
6
+
7
+ ### Agent
8
+
9
+ The Agent is the central orchestrator for AI interactions. It manages messages, calls the LLM provider, and handles tool execution.
10
+
11
+ ```ruby
12
+ class MyAgent < Riffer::Agent
13
+ model 'openai/gpt-4o'
14
+ instructions 'You are a helpful assistant.'
15
+ end
16
+ ```
17
+
18
+ See [Agents](03_AGENTS.md) for details.
19
+
20
+ ### Tool
21
+
22
+ Tools are callable functions that agents can invoke to interact with external systems. They have structured parameter definitions and automatic validation.
23
+
24
+ ```ruby
25
+ class WeatherTool < Riffer::Tool
26
+ description "Gets the weather for a city"
27
+
28
+ params do
29
+ required :city, String, description: "The city name"
30
+ end
31
+
32
+ def call(context:, city:)
33
+ WeatherAPI.fetch(city)
34
+ end
35
+ end
36
+ ```
37
+
38
+ See [Tools](04_TOOLS.md) for details.
39
+
40
+ ### Provider
41
+
42
+ Providers are adapters that connect to LLM services. Riffer supports:
43
+
44
+ - **OpenAI** - GPT models via the OpenAI API
45
+ - **Amazon Bedrock** - Claude and other models via AWS Bedrock
46
+ - **Test** - Mock provider for testing
47
+
48
+ See [Providers](providers/01_PROVIDERS.md) for details.
49
+
50
+ ### Messages
51
+
52
+ Messages represent the conversation between user and assistant. Riffer uses strongly-typed message objects:
53
+
54
+ - `Riffer::Messages::System` - System instructions
55
+ - `Riffer::Messages::User` - User input
56
+ - `Riffer::Messages::Assistant` - LLM responses
57
+ - `Riffer::Messages::Tool` - Tool execution results
58
+
59
+ See [Messages](05_MESSAGES.md) for details.
60
+
61
+ ### Stream Events
62
+
63
+ When streaming responses, Riffer emits typed events:
64
+
65
+ - `TextDelta` - Incremental text chunks
66
+ - `TextDone` - Complete text
67
+ - `ToolCallDelta` - Incremental tool call arguments
68
+ - `ToolCallDone` - Complete tool call
69
+
70
+ See [Stream Events](06_STREAM_EVENTS.md) for details.
71
+
72
+ ## Architecture
73
+
74
+ ```
75
+ User Request
76
+ |
77
+ v
78
+ +------------+
79
+ | Agent | <-- Manages conversation flow
80
+ +------------+
81
+ |
82
+ v
83
+ +------------+
84
+ | Provider | <-- Calls LLM API
85
+ +------------+
86
+ |
87
+ v
88
+ +------------+
89
+ | LLM | <-- Returns response
90
+ +------------+
91
+ |
92
+ v
93
+ +------------+
94
+ | Tool? | <-- Execute if tool call present
95
+ +------------+
96
+ |
97
+ v
98
+ Response
99
+ ```
100
+
101
+ ## Next Steps
102
+
103
+ - [Getting Started](02_GETTING_STARTED.md) - Quick start guide
104
+ - [Agents](03_AGENTS.md) - Agent configuration and usage
105
+ - [Tools](04_TOOLS.md) - Creating tools
106
+ - [Configuration](07_CONFIGURATION.md) - Global configuration
@@ -0,0 +1,128 @@
1
+ # Getting Started
2
+
3
+ This guide walks you through installing Riffer and creating your first AI agent.
4
+
5
+ ## Installation
6
+
7
+ Add Riffer to your Gemfile:
8
+
9
+ ```ruby
10
+ gem 'riffer'
11
+ ```
12
+
13
+ Then run:
14
+
15
+ ```bash
16
+ bundle install
17
+ ```
18
+
19
+ Or install directly:
20
+
21
+ ```bash
22
+ gem install riffer
23
+ ```
24
+
25
+ ## Provider Setup
26
+
27
+ Riffer requires an LLM provider. Install the provider gem for your chosen service:
28
+
29
+ ### OpenAI
30
+
31
+ ```ruby
32
+ gem 'openai'
33
+ ```
34
+
35
+ Configure your API key:
36
+
37
+ ```ruby
38
+ Riffer.configure do |config|
39
+ config.openai.api_key = ENV['OPENAI_API_KEY']
40
+ end
41
+ ```
42
+
43
+ ### Amazon Bedrock
44
+
45
+ ```ruby
46
+ gem 'aws-sdk-bedrockruntime'
47
+ ```
48
+
49
+ Configure your credentials:
50
+
51
+ ```ruby
52
+ Riffer.configure do |config|
53
+ config.amazon_bedrock.region = 'us-east-1'
54
+ # Optional: Use bearer token auth instead of IAM
55
+ config.amazon_bedrock.api_token = ENV['BEDROCK_API_TOKEN']
56
+ end
57
+ ```
58
+
59
+ ## Creating Your First Agent
60
+
61
+ Define an agent by subclassing `Riffer::Agent`:
62
+
63
+ ```ruby
64
+ require 'riffer'
65
+
66
+ Riffer.configure do |config|
67
+ config.openai.api_key = ENV['OPENAI_API_KEY']
68
+ end
69
+
70
+ class GreetingAgent < Riffer::Agent
71
+ model 'openai/gpt-4o'
72
+ instructions 'You are a friendly assistant. Greet the user warmly.'
73
+ end
74
+
75
+ agent = GreetingAgent.new
76
+ response = agent.generate('Hello!')
77
+ puts response
78
+ # => "Hello! It's wonderful to meet you..."
79
+ ```
80
+
81
+ ## Streaming Responses
82
+
83
+ Use `stream` for real-time output:
84
+
85
+ ```ruby
86
+ agent = GreetingAgent.new
87
+
88
+ agent.stream('Tell me a story').each do |event|
89
+ case event
90
+ when Riffer::StreamEvents::TextDelta
91
+ print event.content
92
+ when Riffer::StreamEvents::TextDone
93
+ puts "\n[Done]"
94
+ end
95
+ end
96
+ ```
97
+
98
+ ## Adding Tools
99
+
100
+ Tools let agents interact with external systems:
101
+
102
+ ```ruby
103
+ class TimeTool < Riffer::Tool
104
+ description "Gets the current time"
105
+
106
+ def call(context:)
107
+ Time.now.strftime('%Y-%m-%d %H:%M:%S')
108
+ end
109
+ end
110
+
111
+ class TimeAgent < Riffer::Agent
112
+ model 'openai/gpt-4o'
113
+ instructions 'You can tell the user the current time.'
114
+ uses_tools [TimeTool]
115
+ end
116
+
117
+ agent = TimeAgent.new
118
+ puts agent.generate("What time is it?")
119
+ # => "The current time is 2024-01-15 14:30:00."
120
+ ```
121
+
122
+ ## Next Steps
123
+
124
+ - [Agents](03_AGENTS.md) - Agent configuration options
125
+ - [Tools](04_TOOLS.md) - Creating tools with parameters
126
+ - [Messages](05_MESSAGES.md) - Message types and history
127
+ - [Stream Events](06_STREAM_EVENTS.md) - Streaming event types
128
+ - [Providers](providers/01_PROVIDERS.md) - Provider-specific guides
data/docs/03_AGENTS.md ADDED
@@ -0,0 +1,226 @@
1
+ # Agents
2
+
3
+ Agents are the central orchestrator in Riffer. They manage the conversation flow, call LLM providers, and handle tool execution.
4
+
5
+ ## Defining an Agent
6
+
7
+ Create an agent by subclassing `Riffer::Agent`:
8
+
9
+ ```ruby
10
+ class MyAgent < Riffer::Agent
11
+ model 'openai/gpt-4o'
12
+ instructions 'You are a helpful assistant.'
13
+ end
14
+ ```
15
+
16
+ ## Configuration Methods
17
+
18
+ ### model
19
+
20
+ Sets the provider and model in `provider/model` format:
21
+
22
+ ```ruby
23
+ class MyAgent < Riffer::Agent
24
+ model 'openai/gpt-4o' # OpenAI
25
+ # or
26
+ model 'amazon_bedrock/anthropic.claude-3-sonnet-20240229-v1:0' # Bedrock
27
+ # or
28
+ model 'test/any' # Test provider
29
+ end
30
+ ```
31
+
32
+ ### instructions
33
+
34
+ Sets system instructions for the agent:
35
+
36
+ ```ruby
37
+ class MyAgent < Riffer::Agent
38
+ model 'openai/gpt-4o'
39
+ instructions 'You are an expert Ruby programmer. Provide concise answers.'
40
+ end
41
+ ```
42
+
43
+ ### identifier
44
+
45
+ Sets a custom identifier (defaults to snake_case class name):
46
+
47
+ ```ruby
48
+ class MyAgent < Riffer::Agent
49
+ model 'openai/gpt-4o'
50
+ identifier 'custom_agent_name'
51
+ end
52
+
53
+ MyAgent.identifier # => "custom_agent_name"
54
+ ```
55
+
56
+ ### uses_tools
57
+
58
+ Registers tools the agent can use:
59
+
60
+ ```ruby
61
+ class MyAgent < Riffer::Agent
62
+ model 'openai/gpt-4o'
63
+ uses_tools [WeatherTool, TimeTool]
64
+ end
65
+ ```
66
+
67
+ Tools can also be resolved dynamically with a lambda:
68
+
69
+ ```ruby
70
+ class MyAgent < Riffer::Agent
71
+ model 'openai/gpt-4o'
72
+
73
+ uses_tools ->(context) {
74
+ tools = [PublicTool]
75
+ tools << AdminTool if context&.dig(:user)&.admin?
76
+ tools
77
+ }
78
+ end
79
+ ```
80
+
81
+ ### provider_options
82
+
83
+ Passes options to the provider client:
84
+
85
+ ```ruby
86
+ class MyAgent < Riffer::Agent
87
+ model 'openai/gpt-4o'
88
+ provider_options api_key: ENV['CUSTOM_OPENAI_KEY']
89
+ end
90
+ ```
91
+
92
+ ### model_options
93
+
94
+ Passes options to each LLM request:
95
+
96
+ ```ruby
97
+ class MyAgent < Riffer::Agent
98
+ model 'openai/gpt-4o'
99
+ model_options reasoning: 'medium', temperature: 0.7
100
+ end
101
+ ```
102
+
103
+ ## Instance Methods
104
+
105
+ ### generate
106
+
107
+ Generates a response synchronously:
108
+
109
+ ```ruby
110
+ agent = MyAgent.new
111
+
112
+ # With a string prompt
113
+ response = agent.generate('Hello')
114
+
115
+ # With message objects/hashes
116
+ response = agent.generate([
117
+ {role: 'user', content: 'Hello'},
118
+ {role: 'assistant', content: 'Hi there!'},
119
+ {role: 'user', content: 'How are you?'}
120
+ ])
121
+
122
+ # With tool context
123
+ response = agent.generate('Look up my orders', tool_context: {user_id: 123})
124
+ ```
125
+
126
+ ### stream
127
+
128
+ Streams a response as an Enumerator:
129
+
130
+ ```ruby
131
+ agent = MyAgent.new
132
+
133
+ agent.stream('Tell me a story').each do |event|
134
+ case event
135
+ when Riffer::StreamEvents::TextDelta
136
+ print event.content
137
+ when Riffer::StreamEvents::TextDone
138
+ puts "\n"
139
+ when Riffer::StreamEvents::ToolCallDone
140
+ puts "[Tool: #{event.name}]"
141
+ end
142
+ end
143
+ ```
144
+
145
+ ### messages
146
+
147
+ Access the message history after a generate/stream call:
148
+
149
+ ```ruby
150
+ agent = MyAgent.new
151
+ agent.generate('Hello')
152
+
153
+ agent.messages.each do |msg|
154
+ puts "#{msg.role}: #{msg.content}"
155
+ end
156
+ ```
157
+
158
+ ### on_message
159
+
160
+ Registers a callback to receive messages as they're added during generation:
161
+
162
+ ```ruby
163
+ agent.on_message do |message|
164
+ case message.role
165
+ when :assistant
166
+ puts "[Assistant] #{message.content}"
167
+ when :tool
168
+ puts "[Tool:#{message.name}] #{message.content}"
169
+ end
170
+ end
171
+ ```
172
+
173
+ Multiple callbacks can be registered. Returns `self` for method chaining:
174
+
175
+ ```ruby
176
+ agent
177
+ .on_message { |msg| persist_message(msg) }
178
+ .on_message { |msg| log_message(msg) }
179
+ .generate('Hello')
180
+ ```
181
+
182
+ Works with both `generate` and `stream`. Only emits agent-generated messages (Assistant, Tool), not inputs (System, User).
183
+
184
+ ## Class Methods
185
+
186
+ ### find
187
+
188
+ Find an agent class by identifier:
189
+
190
+ ```ruby
191
+ agent_class = Riffer::Agent.find('my_agent')
192
+ agent = agent_class.new
193
+ ```
194
+
195
+ ### all
196
+
197
+ List all agent subclasses:
198
+
199
+ ```ruby
200
+ Riffer::Agent.all.each do |agent_class|
201
+ puts agent_class.identifier
202
+ end
203
+ ```
204
+
205
+ ## Tool Execution Flow
206
+
207
+ When an agent receives a response with tool calls:
208
+
209
+ 1. Agent detects `tool_calls` in the assistant message
210
+ 2. For each tool call:
211
+ - Finds the matching tool class
212
+ - Validates arguments against the tool's parameter schema
213
+ - Calls the tool's `call` method with `context` and arguments
214
+ - Creates a Tool message with the result
215
+ 3. Sends the updated message history back to the LLM
216
+ 4. Repeats until no more tool calls
217
+
218
+ ## Error Handling
219
+
220
+ Tool execution errors are captured and sent back to the LLM:
221
+
222
+ - `unknown_tool` - Tool not found in registered tools
223
+ - `validation_error` - Arguments failed validation
224
+ - `execution_error` - Tool raised an exception
225
+
226
+ The LLM can use this information to retry or respond appropriately.