conductor_ruby 0.1.0 → 0.1.1
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/CHANGELOG.md +15 -0
- data/examples/agents/01_basic_agent.rb +36 -0
- data/examples/agents/02a_simple_tools.rb +47 -0
- data/examples/agents/02c_tool_retry_config.rb +52 -0
- data/examples/agents/04_http_and_mcp_tools.rb +58 -0
- data/examples/agents/05_handoffs.rb +71 -0
- data/examples/agents/06_sequential_pipeline.rb +47 -0
- data/examples/agents/07_parallel_agents.rb +52 -0
- data/examples/agents/09_human_in_the_loop.rb +59 -0
- data/examples/agents/09c_hitl_streaming.rb +65 -0
- data/examples/agents/103_plan_and_compile.rb +85 -0
- data/examples/agents/10_guardrails.rb +61 -0
- data/examples/agents/13_hierarchical_agents.rb +79 -0
- data/examples/agents/16e_credentials_http_tool.rb +44 -0
- data/examples/agents/17_swarm_orchestration.rb +56 -0
- data/examples/agents/21_regex_guardrails.rb +69 -0
- data/examples/agents/22_llm_guardrails.rb +51 -0
- data/examples/agents/33_external_workers.rb +65 -0
- data/examples/agents/64_swarm_with_tools.rb +71 -0
- data/examples/agents/66_handoff_to_parallel.rb +62 -0
- data/examples/agents/bug_desk.rb +50 -0
- data/examples/agents/catalog.rb +32 -0
- data/examples/agents/dump_agent_configs.rb +30 -0
- data/examples/agents/external_workers.rb +39 -0
- data/examples/agents/golden_agents.rb +385 -0
- data/examples/agents/support_approval.rb +51 -0
- data/examples/agents/weather.rb +27 -0
- data/lib/conductor/agents/agent.rb +324 -0
- data/lib/conductor/agents/callback_handler.rb +64 -0
- data/lib/conductor/agents/config_serializer.rb +246 -0
- data/lib/conductor/agents/errors.rb +26 -0
- data/lib/conductor/agents/guardrail.rb +142 -0
- data/lib/conductor/agents/handoff.rb +94 -0
- data/lib/conductor/agents/memory.rb +75 -0
- data/lib/conductor/agents/plans.rb +22 -0
- data/lib/conductor/agents/prompt_template.rb +23 -0
- data/lib/conductor/agents/runtime/agent_config.rb +52 -0
- data/lib/conductor/agents/runtime/agent_runtime.rb +260 -0
- data/lib/conductor/agents/runtime/approval_request.rb +111 -0
- data/lib/conductor/agents/runtime/dispatch.rb +137 -0
- data/lib/conductor/agents/runtime/execution.rb +265 -0
- data/lib/conductor/agents/runtime/secrets.rb +54 -0
- data/lib/conductor/agents/runtime/sse_client.rb +175 -0
- data/lib/conductor/agents/runtime/status_poller.rb +49 -0
- data/lib/conductor/agents/runtime/system_workers.rb +115 -0
- data/lib/conductor/agents/runtime/tool_call.rb +13 -0
- data/lib/conductor/agents/runtime/tool_registry.rb +164 -0
- data/lib/conductor/agents/termination.rb +192 -0
- data/lib/conductor/agents/tool.rb +283 -0
- data/lib/conductor/agents/tools/schema_builder.rb +190 -0
- data/lib/conductor/agents/tools/secret_scanner.rb +45 -0
- data/lib/conductor/agents/tools.rb +173 -0
- data/lib/conductor/agents.rb +75 -0
- data/lib/conductor/client/agent_client.rb +117 -0
- data/lib/conductor/client/task_client.rb +7 -0
- data/lib/conductor/configuration.rb +43 -10
- data/lib/conductor/exceptions.rb +36 -0
- data/lib/conductor/http/api/agent_resource_api.rb +138 -0
- data/lib/conductor/http/api/scheduler_resource_api.rb +31 -12
- data/lib/conductor/http/api/task_resource_api.rb +15 -0
- data/lib/conductor/http/models/task.rb +10 -2
- data/lib/conductor/http/models/task_def.rb +14 -3
- data/lib/conductor/http/rest_client.rb +9 -0
- data/lib/conductor/orkes/orkes_clients.rb +5 -1
- data/lib/conductor/version.rb +1 -1
- data/lib/conductor/worker/lease_renewer.rb +55 -0
- data/lib/conductor/worker/ractor_task_runner.rb +1 -1
- data/lib/conductor/worker/task_definition_registrar.rb +2 -2
- data/lib/conductor/worker/task_runner.rb +43 -10
- data/lib/conductor/worker/worker.rb +3 -2
- data/lib/conductor/worker/worker_config.rb +2 -1
- data/lib/conductor.rb +3 -1
- metadata +71 -16
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Agents whose serialized agentConfig must match the Python SDK byte for byte
|
|
4
|
+
# (spec/fixtures/agents/configs/*.json, vendored from python-sdk examples/agents/_configs).
|
|
5
|
+
#
|
|
6
|
+
# Used by spec/conductor/agents/contract_spec.rb and by dump_agent_configs.rb.
|
|
7
|
+
# Each example keeps its tools in its own module so that tools with the same name but
|
|
8
|
+
# different descriptions (get_weather in 02 vs 03) do not collide.
|
|
9
|
+
require 'conductor/agents'
|
|
10
|
+
require_relative '103_plan_and_compile'
|
|
11
|
+
|
|
12
|
+
module GoldenAgents
|
|
13
|
+
MODEL = ENV.fetch('CONDUCTOR_AGENT_LLM_MODEL', 'anthropic/claude-sonnet-4-6')
|
|
14
|
+
A = Conductor::Agents
|
|
15
|
+
|
|
16
|
+
module Ex02
|
|
17
|
+
extend A::Tools
|
|
18
|
+
tool def get_weather(city: String)
|
|
19
|
+
{}
|
|
20
|
+
end
|
|
21
|
+
describe :get_weather, 'Get current weather for a city.'
|
|
22
|
+
tool def calculate(expression: String)
|
|
23
|
+
{}
|
|
24
|
+
end
|
|
25
|
+
describe :calculate, 'Evaluate a math expression.'
|
|
26
|
+
tool def send_email(to: String, subject: String, body: String)
|
|
27
|
+
{}
|
|
28
|
+
end
|
|
29
|
+
describe :send_email, 'Send an email.'
|
|
30
|
+
requires_approval :send_email
|
|
31
|
+
self[:send_email].timeout_seconds = 60
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
module Ex03
|
|
35
|
+
extend A::Tools
|
|
36
|
+
tool def get_weather(city: String)
|
|
37
|
+
{}
|
|
38
|
+
end
|
|
39
|
+
describe :get_weather, 'Get current weather data for a city.'
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
module Ex05
|
|
43
|
+
extend A::Tools
|
|
44
|
+
tool def check_balance(account_id: String)
|
|
45
|
+
{}
|
|
46
|
+
end
|
|
47
|
+
describe :check_balance, 'Check the balance of a bank account.'
|
|
48
|
+
tool def lookup_order(order_id: String)
|
|
49
|
+
{}
|
|
50
|
+
end
|
|
51
|
+
describe :lookup_order, 'Look up the status of an order.'
|
|
52
|
+
tool def get_pricing(product: String)
|
|
53
|
+
{}
|
|
54
|
+
end
|
|
55
|
+
describe :get_pricing, 'Get pricing information for a product.'
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
module Ex10
|
|
59
|
+
extend A::Tools
|
|
60
|
+
tool def get_order_status(order_id: String)
|
|
61
|
+
{}
|
|
62
|
+
end
|
|
63
|
+
describe :get_order_status, 'Look up the current status of an order.'
|
|
64
|
+
tool def get_customer_info(customer_id: String)
|
|
65
|
+
{}
|
|
66
|
+
end
|
|
67
|
+
describe :get_customer_info, 'Retrieve customer details including payment info on file.'
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
module Ex19
|
|
71
|
+
extend A::Tools
|
|
72
|
+
tool def search(query: String)
|
|
73
|
+
''
|
|
74
|
+
end
|
|
75
|
+
describe :search, 'Search for information.'
|
|
76
|
+
self[:search].output_schema = { 'type' => 'string' }
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
module Ex21
|
|
80
|
+
extend A::Tools
|
|
81
|
+
tool def get_user_profile(user_id: String)
|
|
82
|
+
{}
|
|
83
|
+
end
|
|
84
|
+
describe :get_user_profile, "Retrieve a user's profile from the database."
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
module Ex45
|
|
88
|
+
extend A::Tools
|
|
89
|
+
tool def search_knowledge_base(query: String)
|
|
90
|
+
{}
|
|
91
|
+
end
|
|
92
|
+
describe :search_knowledge_base, 'Search an internal knowledge base for information.'
|
|
93
|
+
tool def calculate(expression: String)
|
|
94
|
+
{}
|
|
95
|
+
end
|
|
96
|
+
describe :calculate, 'Evaluate a math expression safely.'
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
module Ex47
|
|
100
|
+
extend A::Tools
|
|
101
|
+
tool def get_facts(topic: String)
|
|
102
|
+
{}
|
|
103
|
+
end
|
|
104
|
+
describe :get_facts, 'Get interesting facts about a topic.'
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# 47_callbacks: before_model / after_model hooks
|
|
108
|
+
class MonitorHandler < A::CallbackHandler
|
|
109
|
+
def on_model_start(**_kwargs)
|
|
110
|
+
{}
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def on_model_end(**_kwargs)
|
|
114
|
+
{}
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
EXAMPLES = {
|
|
119
|
+
'01_basic_agent' => lambda {
|
|
120
|
+
A::Agent.new(name: 'greeter', model: MODEL)
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
'02_tools' => lambda {
|
|
124
|
+
A::Agent.new(
|
|
125
|
+
name: 'tool_demo_agent', model: MODEL,
|
|
126
|
+
tools: [Ex02[:get_weather], Ex02[:calculate], Ex02[:send_email]],
|
|
127
|
+
instructions: 'You are a helpful assistant with access to weather, calculator, and email tools.'
|
|
128
|
+
)
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
'03_structured_output' => lambda {
|
|
132
|
+
weather_report = {
|
|
133
|
+
'title' => 'WeatherReport',
|
|
134
|
+
'type' => 'object',
|
|
135
|
+
'properties' => {
|
|
136
|
+
'city' => { 'title' => 'City', 'type' => 'string' },
|
|
137
|
+
'temperature' => { 'title' => 'Temperature', 'type' => 'number' },
|
|
138
|
+
'condition' => { 'title' => 'Condition', 'type' => 'string' },
|
|
139
|
+
'recommendation' => { 'title' => 'Recommendation', 'type' => 'string' }
|
|
140
|
+
},
|
|
141
|
+
'required' => %w[city temperature condition recommendation]
|
|
142
|
+
}
|
|
143
|
+
A::Agent.new(
|
|
144
|
+
name: 'weather_reporter', model: MODEL, tools: [Ex03[:get_weather]], output_type: weather_report,
|
|
145
|
+
instructions: 'You are a weather reporter. Get the weather and provide a recommendation.'
|
|
146
|
+
)
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
'05_handoffs' => lambda {
|
|
150
|
+
billing = A::Agent.new(name: 'billing', model: MODEL, tools: [Ex05[:check_balance]],
|
|
151
|
+
instructions: 'You handle billing questions: balances, payments, invoices.')
|
|
152
|
+
technical = A::Agent.new(name: 'technical', model: MODEL, tools: [Ex05[:lookup_order]],
|
|
153
|
+
instructions: 'You handle technical questions: order status, shipping, returns.')
|
|
154
|
+
sales = A::Agent.new(name: 'sales', model: MODEL, tools: [Ex05[:get_pricing]],
|
|
155
|
+
instructions: 'You handle sales questions: pricing, products, promotions.')
|
|
156
|
+
A::Agent.new(name: 'support', model: MODEL, agents: [billing, technical, sales], strategy: :handoff,
|
|
157
|
+
instructions: 'Route customer requests to the right specialist: billing, technical, or sales.')
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
'06_sequential_pipeline' => lambda {
|
|
161
|
+
researcher = A::Agent.new(
|
|
162
|
+
name: 'researcher', model: MODEL,
|
|
163
|
+
instructions: 'You are a researcher. Given a topic, provide key facts and data points. ' \
|
|
164
|
+
'Be thorough but concise. Output raw research findings.'
|
|
165
|
+
)
|
|
166
|
+
writer = A::Agent.new(
|
|
167
|
+
name: 'writer', model: MODEL,
|
|
168
|
+
instructions: 'You are a writer. Take research findings and write a clear, engaging ' \
|
|
169
|
+
'article. Use headers and bullet points where appropriate.'
|
|
170
|
+
)
|
|
171
|
+
editor = A::Agent.new(
|
|
172
|
+
name: 'editor', model: MODEL,
|
|
173
|
+
instructions: 'You are an editor. Review the article for clarity, grammar, and tone. ' \
|
|
174
|
+
'Make improvements and output the final polished version.'
|
|
175
|
+
)
|
|
176
|
+
researcher >> writer >> editor
|
|
177
|
+
},
|
|
178
|
+
|
|
179
|
+
'07_parallel_agents' => lambda {
|
|
180
|
+
market = A::Agent.new(
|
|
181
|
+
name: 'market_analyst', model: MODEL,
|
|
182
|
+
instructions: 'You are a market analyst. Analyze the given topic from a market perspective: ' \
|
|
183
|
+
'market size, growth trends, key players, and opportunities.'
|
|
184
|
+
)
|
|
185
|
+
risk = A::Agent.new(
|
|
186
|
+
name: 'risk_analyst', model: MODEL,
|
|
187
|
+
instructions: 'You are a risk analyst. Analyze the given topic for risks: ' \
|
|
188
|
+
'regulatory risks, technical risks, competitive threats, and mitigation strategies.'
|
|
189
|
+
)
|
|
190
|
+
compliance = A::Agent.new(
|
|
191
|
+
name: 'compliance', model: MODEL,
|
|
192
|
+
instructions: 'You are a compliance specialist. Check the given topic for compliance considerations: ' \
|
|
193
|
+
'data privacy, regulatory requirements, and industry standards.'
|
|
194
|
+
)
|
|
195
|
+
A::Agent.new(name: 'analysis', model: MODEL, agents: [market, risk, compliance], strategy: :parallel)
|
|
196
|
+
},
|
|
197
|
+
|
|
198
|
+
'08_router_agent' => lambda {
|
|
199
|
+
planner = A::Agent.new(name: 'planner', model: MODEL,
|
|
200
|
+
instructions: 'You create implementation plans. Break down tasks into clear numbered steps.')
|
|
201
|
+
coder = A::Agent.new(name: 'coder', model: MODEL,
|
|
202
|
+
instructions: 'You write code. Output clean, well-documented Python code.')
|
|
203
|
+
reviewer = A::Agent.new(name: 'reviewer', model: MODEL,
|
|
204
|
+
instructions: 'You review code. Check for bugs, style issues, and suggest improvements.')
|
|
205
|
+
A::Agent.new(
|
|
206
|
+
name: 'dev_team', model: MODEL, agents: [planner, coder, reviewer], strategy: :router, router: planner,
|
|
207
|
+
instructions: 'You are the tech lead. Route requests to the right team member: ' \
|
|
208
|
+
'planner for design/architecture, coder for implementation, reviewer for code review.'
|
|
209
|
+
)
|
|
210
|
+
},
|
|
211
|
+
|
|
212
|
+
'10_guardrails' => lambda {
|
|
213
|
+
no_pii = A::Guardrail.new(name: 'no_pii', position: :output, on_fail: :retry) do |content|
|
|
214
|
+
if content =~ /\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b/ || content =~ /\b\d{3}-\d{2}-\d{4}\b/
|
|
215
|
+
A::GuardrailResult.new(passed: false, message: 'Your response contains PII. Redact it.')
|
|
216
|
+
else
|
|
217
|
+
A::GuardrailResult.new(passed: true)
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
A::Agent.new(
|
|
221
|
+
name: 'support_agent', model: MODEL, tools: [Ex10[:get_order_status], Ex10[:get_customer_info]],
|
|
222
|
+
guardrails: [no_pii],
|
|
223
|
+
instructions: 'You are a customer support assistant. Use the available tools to ' \
|
|
224
|
+
'answer questions about orders and customers. Always include all ' \
|
|
225
|
+
'details from the tool results in your response.'
|
|
226
|
+
)
|
|
227
|
+
},
|
|
228
|
+
|
|
229
|
+
'13_hierarchical_agents' => lambda {
|
|
230
|
+
backend = A::Agent.new(
|
|
231
|
+
name: 'backend_dev', model: MODEL,
|
|
232
|
+
instructions: 'You are a backend developer. You design APIs, databases, and server ' \
|
|
233
|
+
'architecture. Provide technical recommendations with code examples.'
|
|
234
|
+
)
|
|
235
|
+
frontend = A::Agent.new(
|
|
236
|
+
name: 'frontend_dev', model: MODEL,
|
|
237
|
+
instructions: 'You are a frontend developer. You design UI components, user flows, ' \
|
|
238
|
+
'and client-side architecture. Provide recommendations with code examples.'
|
|
239
|
+
)
|
|
240
|
+
content = A::Agent.new(
|
|
241
|
+
name: 'content_writer', model: MODEL,
|
|
242
|
+
instructions: 'You are a content writer. You create blog posts, landing page copy, ' \
|
|
243
|
+
'and marketing materials. Write engaging, clear content.'
|
|
244
|
+
)
|
|
245
|
+
seo = A::Agent.new(
|
|
246
|
+
name: 'seo_specialist', model: MODEL,
|
|
247
|
+
instructions: 'You are an SEO specialist. You optimize content for search engines, ' \
|
|
248
|
+
'suggest keywords, and improve page rankings.'
|
|
249
|
+
)
|
|
250
|
+
engineering = A::Agent.new(
|
|
251
|
+
name: 'engineering_lead', model: MODEL, agents: [backend, frontend], strategy: :handoff,
|
|
252
|
+
instructions: 'You are the engineering lead. Route technical questions to the right ' \
|
|
253
|
+
'specialist: backend_dev for APIs/databases/servers, frontend_dev for UI/UX/client-side.'
|
|
254
|
+
)
|
|
255
|
+
marketing = A::Agent.new(
|
|
256
|
+
name: 'marketing_lead', model: MODEL, agents: [content, seo], strategy: :handoff,
|
|
257
|
+
instructions: 'You are the marketing lead. Route marketing questions to the right ' \
|
|
258
|
+
'specialist: content_writer for blog posts/copy, seo_specialist for SEO/keywords/rankings.'
|
|
259
|
+
)
|
|
260
|
+
A::Agent.new(
|
|
261
|
+
name: 'ceo', model: MODEL, agents: [engineering, marketing], strategy: :swarm,
|
|
262
|
+
handoffs: [
|
|
263
|
+
A::Handoff::OnTextMention.new(target: 'engineering_lead', text: 'engineering_lead'),
|
|
264
|
+
A::Handoff::OnTextMention.new(target: 'marketing_lead', text: 'marketing_lead')
|
|
265
|
+
],
|
|
266
|
+
instructions: 'You are the CEO. Route requests to the right department: ' \
|
|
267
|
+
'engineering_lead for technical/development questions, ' \
|
|
268
|
+
'marketing_lead for marketing/content/SEO questions.'
|
|
269
|
+
)
|
|
270
|
+
},
|
|
271
|
+
|
|
272
|
+
'17_swarm_orchestration' => lambda {
|
|
273
|
+
refund = A::Agent.new(
|
|
274
|
+
name: 'refund_specialist', model: MODEL,
|
|
275
|
+
instructions: "You are a refund specialist. Process the customer's refund request. " \
|
|
276
|
+
'Check eligibility, confirm the refund amount, and let them know the ' \
|
|
277
|
+
'timeline. Be empathetic and clear. Do NOT ask follow-up questions -- ' \
|
|
278
|
+
'just process the refund based on what the customer told you.'
|
|
279
|
+
)
|
|
280
|
+
tech = A::Agent.new(
|
|
281
|
+
name: 'tech_support', model: MODEL,
|
|
282
|
+
instructions: "You are a technical support specialist. Diagnose the customer's " \
|
|
283
|
+
'technical issue and provide clear troubleshooting steps.'
|
|
284
|
+
)
|
|
285
|
+
A::Agent.new(
|
|
286
|
+
name: 'support', model: MODEL, agents: [refund, tech], strategy: :swarm, max_turns: 3,
|
|
287
|
+
handoffs: [
|
|
288
|
+
A::Handoff::OnTextMention.new(target: 'refund_specialist', text: 'refund'),
|
|
289
|
+
A::Handoff::OnTextMention.new(target: 'tech_support', text: 'technical')
|
|
290
|
+
],
|
|
291
|
+
instructions: 'You are the front-line customer support agent. Triage customer requests. ' \
|
|
292
|
+
'If the customer needs a refund, transfer to the refund specialist. ' \
|
|
293
|
+
'If they have a technical issue, transfer to tech support. ' \
|
|
294
|
+
'Use the transfer tools available to you to hand off the conversation.'
|
|
295
|
+
)
|
|
296
|
+
},
|
|
297
|
+
|
|
298
|
+
'19_composable_termination_simple' => lambda {
|
|
299
|
+
A::Agent.new(name: 'researcher', model: MODEL, tools: [Ex19[:search]],
|
|
300
|
+
instructions: 'Research the topic and say DONE when you have enough info.',
|
|
301
|
+
termination: A::Termination::TextMention.new('DONE'))
|
|
302
|
+
},
|
|
303
|
+
|
|
304
|
+
'19_composable_termination_or' => lambda {
|
|
305
|
+
A::Agent.new(name: 'chatbot', model: MODEL,
|
|
306
|
+
instructions: "Have a conversation. Say GOODBYE when you're finished.",
|
|
307
|
+
termination: A::Termination::TextMention.new('GOODBYE') | A::Termination::MaxMessage.new(20))
|
|
308
|
+
},
|
|
309
|
+
|
|
310
|
+
'19_composable_termination_and' => lambda {
|
|
311
|
+
A::Agent.new(name: 'deliberator', model: MODEL, tools: [Ex19[:search]],
|
|
312
|
+
instructions: 'Research thoroughly. Only provide your FINAL ANSWER after ' \
|
|
313
|
+
'using the search tool at least twice.',
|
|
314
|
+
termination: A::Termination::TextMention.new('FINAL ANSWER') & A::Termination::MaxMessage.new(5))
|
|
315
|
+
},
|
|
316
|
+
|
|
317
|
+
'19_composable_termination_complex' => lambda {
|
|
318
|
+
complex_stop = A::Termination::StopMessage.new('TERMINATE') |
|
|
319
|
+
(A::Termination::TextMention.new('DONE') & A::Termination::MaxMessage.new(10)) |
|
|
320
|
+
A::Termination::TokenUsage.new(max_total_tokens: 50_000)
|
|
321
|
+
A::Agent.new(name: 'complex_agent', model: MODEL, tools: [Ex19[:search]],
|
|
322
|
+
instructions: 'Research and provide a comprehensive answer.', termination: complex_stop)
|
|
323
|
+
},
|
|
324
|
+
|
|
325
|
+
'21_regex_guardrails' => lambda {
|
|
326
|
+
no_emails = A::RegexGuardrail.new(['[\w.+-]+@[\w-]+\.[\w.-]+'], mode: :block, name: 'no_email_addresses',
|
|
327
|
+
message: 'Response must not contain email addresses. Redact them.',
|
|
328
|
+
position: :output, on_fail: :retry)
|
|
329
|
+
no_ssn = A::RegexGuardrail.new(['\b\d{3}-\d{2}-\d{4}\b'], mode: :block, name: 'no_ssn',
|
|
330
|
+
message: 'Response must not contain Social Security Numbers.',
|
|
331
|
+
position: :output, on_fail: :raise)
|
|
332
|
+
A::Agent.new(name: 'hr_assistant', model: MODEL, tools: [Ex21[:get_user_profile]], guardrails: [no_emails, no_ssn],
|
|
333
|
+
instructions: 'You are an HR assistant. When asked about employees, look up their ' \
|
|
334
|
+
'profile and share ALL the details you find.')
|
|
335
|
+
},
|
|
336
|
+
|
|
337
|
+
'22_llm_guardrails' => lambda {
|
|
338
|
+
safety = A::LlmGuardrail.new(
|
|
339
|
+
MODEL,
|
|
340
|
+
"Reject any content that:\n" \
|
|
341
|
+
"1. Contains medical or legal advice presented as fact\n" \
|
|
342
|
+
"2. Makes promises or guarantees about outcomes\n" \
|
|
343
|
+
"3. Includes discriminatory or biased language\n" \
|
|
344
|
+
"4. Reveals private information about real individuals\n" \
|
|
345
|
+
"\n" \
|
|
346
|
+
'Even if there are disclaimers you should reject',
|
|
347
|
+
name: 'content_safety', position: :output, on_fail: :retry, max_tokens: 10_000
|
|
348
|
+
)
|
|
349
|
+
A::Agent.new(name: 'health_advisor', model: MODEL, guardrails: [safety],
|
|
350
|
+
instructions: 'You are a health information assistant. Provide general health ' \
|
|
351
|
+
'information and solution to the problem. You can prescribe psudo scientific and untested meds ')
|
|
352
|
+
},
|
|
353
|
+
|
|
354
|
+
'45_agent_tool' => lambda {
|
|
355
|
+
researcher = A::Agent.new(name: 'researcher_45', model: MODEL, tools: [Ex45[:search_knowledge_base]],
|
|
356
|
+
instructions: 'You are a research assistant. Use search_knowledge_base to find ' \
|
|
357
|
+
'information about topics. Provide concise summaries.')
|
|
358
|
+
A::Agent.new(name: 'manager_45', model: MODEL, tools: [A::Tool.agent(researcher), Ex45[:calculate]],
|
|
359
|
+
instructions: 'You are a project manager. Use the researcher tool to gather ' \
|
|
360
|
+
'information and the calculate tool for math. Synthesize findings.')
|
|
361
|
+
},
|
|
362
|
+
|
|
363
|
+
'47_callbacks' => lambda {
|
|
364
|
+
A::Agent.new(name: 'monitored_agent_47', model: MODEL, tools: [Ex47[:get_facts]],
|
|
365
|
+
callbacks: [MonitorHandler.new],
|
|
366
|
+
instructions: 'You are a helpful assistant. Use get_facts when asked about topics.')
|
|
367
|
+
},
|
|
368
|
+
|
|
369
|
+
'103_plan_and_compile' => -> { Example103PlanAndCompile.build(model: MODEL) },
|
|
370
|
+
|
|
371
|
+
'52_nested_strategies' => lambda {
|
|
372
|
+
market = A::Agent.new(name: 'market_analyst_52', model: MODEL,
|
|
373
|
+
instructions: 'You are a market analyst. Analyze the market size, growth rate, ' \
|
|
374
|
+
'and key players for the given topic. Be concise (3-4 bullet points).')
|
|
375
|
+
risk = A::Agent.new(name: 'risk_analyst_52', model: MODEL,
|
|
376
|
+
instructions: 'You are a risk analyst. Identify the top 3 risks: regulatory, ' \
|
|
377
|
+
'technical, and competitive. Be concise.')
|
|
378
|
+
research = A::Agent.new(name: 'research_phase_52', model: MODEL, agents: [market, risk], strategy: :parallel)
|
|
379
|
+
summarizer = A::Agent.new(name: 'summarizer_52', model: MODEL,
|
|
380
|
+
instructions: 'You are an executive briefing writer. Synthesize the market analysis ' \
|
|
381
|
+
'and risk assessment into a concise executive summary (1 paragraph).')
|
|
382
|
+
research >> summarizer
|
|
383
|
+
}
|
|
384
|
+
}.freeze
|
|
385
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Streaming + approval: a tool that waits for a human decision before it runs.
|
|
5
|
+
#
|
|
6
|
+
# export CONDUCTOR_SERVER_URL=http://localhost:8080/api
|
|
7
|
+
# bundle exec ruby examples/agents/support_approval.rb
|
|
8
|
+
require_relative '../../lib/conductor/agents'
|
|
9
|
+
include Conductor::Agents
|
|
10
|
+
|
|
11
|
+
module Billing
|
|
12
|
+
def self.refund(order_id, amount)
|
|
13
|
+
"Refunded #{amount} for order #{order_id}"
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
tool def issue_refund(order_id: String, amount: Float)
|
|
18
|
+
{ message: Billing.refund(order_id, amount) }
|
|
19
|
+
end
|
|
20
|
+
requires_approval :issue_refund
|
|
21
|
+
|
|
22
|
+
agent = Agent.new(
|
|
23
|
+
name: 'support',
|
|
24
|
+
model: ENV.fetch('CONDUCTOR_AGENT_LLM_MODEL', 'anthropic/claude-sonnet-4-5'),
|
|
25
|
+
instructions: 'Help with orders.'
|
|
26
|
+
)
|
|
27
|
+
agent.add_tool :issue_refund
|
|
28
|
+
|
|
29
|
+
# Decide approval-required tool calls. `request` exposes the tool's arguments as methods.
|
|
30
|
+
agent.on_approval do |request|
|
|
31
|
+
puts "approval requested: #{request.tool_name} #{request.arguments}"
|
|
32
|
+
request.amount < 100 ? request.approve : request.reject('Needs a manager')
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Blocking
|
|
36
|
+
answer = agent.call_sync('Refund order A-1029, it arrived broken. It cost 49 dollars.')
|
|
37
|
+
puts answer
|
|
38
|
+
|
|
39
|
+
# Non-blocking with a callback when finished
|
|
40
|
+
agent.call_async('Refund order B-2, it cost 4900 dollars.') do |answer, execution|
|
|
41
|
+
puts "finished (#{execution.finish_reason}): #{answer.inspect}"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Non-blocking, poll it yourself
|
|
45
|
+
execution = agent.call_async('Refund order C-3, it cost 12 dollars.')
|
|
46
|
+
sleep 0.5 until execution.done?
|
|
47
|
+
puts execution.result
|
|
48
|
+
puts "finish_reason: #{execution.finish_reason}" # :stop, or :rejected if the tool was rejected
|
|
49
|
+
puts "tool calls: #{execution.tool_calls.inspect}"
|
|
50
|
+
|
|
51
|
+
Conductor::Agents.shutdown
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Tools: an agent with one Ruby method as a tool.
|
|
5
|
+
#
|
|
6
|
+
# export CONDUCTOR_SERVER_URL=http://localhost:8080/api
|
|
7
|
+
# bundle exec ruby examples/agents/weather.rb
|
|
8
|
+
#
|
|
9
|
+
# The model string names a server-side integration ("openai" here) and a model; the SDK
|
|
10
|
+
# never sees the provider key.
|
|
11
|
+
require_relative '../../lib/conductor/agents'
|
|
12
|
+
include Conductor::Agents
|
|
13
|
+
|
|
14
|
+
tool def get_weather(city: String, units: 'metric')
|
|
15
|
+
{ temp_c: 21.0, summary: "Sunny in #{city}" }
|
|
16
|
+
end
|
|
17
|
+
describe :get_weather, 'Get the current weather for a city.'
|
|
18
|
+
|
|
19
|
+
agent = Agent.new(
|
|
20
|
+
name: 'weather',
|
|
21
|
+
model: ENV.fetch('CONDUCTOR_AGENT_LLM_MODEL', 'openai/gpt-4o-mini'),
|
|
22
|
+
instructions: 'Answer weather questions.'
|
|
23
|
+
)
|
|
24
|
+
agent.add_tool :get_weather
|
|
25
|
+
|
|
26
|
+
puts agent.call_sync('Weather in Lisbon?')
|
|
27
|
+
Conductor::Agents.shutdown
|