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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 58e85067049af7ceef18c133c58b3f562b53d62421cdafb61bf8bdc5eec4ebe8
|
|
4
|
+
data.tar.gz: 9d9d0cc7ec214d7734b09d0d179269119d516f716069ed8fa7c15e477cd97c66
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1ba8b9dccdbdd29fdbc1afbc48e42c492c4cc8be47cb34ca763abf0bba558093480b0d016461a1a1215d9e1fe4278586de536d762cb0f8f05fa0f4c4d63a8dfd
|
|
7
|
+
data.tar.gz: ce5edabd959da8b8c68184146bbbed0489b620c4ffad7f9e6e335c0b23348fa96cd11c8d0c79188b0dab74f693b9d14e1225e6a23d2afbe9b955e97fd8e63838
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.1.1]
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **Conductor Agents** (`require 'conductor/agents'`): define agents in Ruby and run them on a Conductor server (#29)
|
|
13
|
+
- `Agent` with instructions, model, tools, guardrails, handoffs, memory, termination conditions and prompt templates
|
|
14
|
+
- `tool def` DSL that turns a method into a worker tool with a schema derived from keyword defaults, plus factories for HTTP, OpenAPI, MCP, human, agent, media, PDF and RAG tools
|
|
15
|
+
- Regex, LLM and custom guardrails with `raise`, `retry`, `fix` and `human` failure modes
|
|
16
|
+
- Sequential, parallel, hierarchical and swarm multi-agent patterns, handoffs and human-in-the-loop approval
|
|
17
|
+
- `AgentRuntime` with streaming, status polling, secrets resolution and automatic worker registration
|
|
18
|
+
- Examples under `examples/agents/` and documentation under `docs/agents/`
|
|
19
|
+
- Agent playback integration tests driven by the shared Conductor HTTP and MCP playback services (#29)
|
|
20
|
+
- Integration (e2e) test suite now runs against a Conductor OSS stack on every PR and gates the Orkes cloud run (#26)
|
|
21
|
+
|
|
8
22
|
## [0.1.0]
|
|
9
23
|
|
|
10
24
|
### Added
|
|
@@ -139,4 +153,5 @@ end
|
|
|
139
153
|
- Supports both OSS Conductor and Orkes Cloud
|
|
140
154
|
- Ruby 2.6+ compatible (Ruby 3+ recommended for Ractor support)
|
|
141
155
|
|
|
156
|
+
[0.1.1]: https://github.com/conductor-oss/ruby-sdk/releases/tag/v0.1.1
|
|
142
157
|
[0.1.0]: https://github.com/conductor-oss/ruby-sdk/releases/tag/v0.1.0
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Port of python-sdk/examples/agents/01_basic_agent.py.
|
|
4
|
+
# Run: bundle exec ruby -Ilib examples/agents/01_basic_agent.rb
|
|
5
|
+
require 'conductor/agents'
|
|
6
|
+
|
|
7
|
+
module Example01BasicAgent
|
|
8
|
+
include Conductor::Agents
|
|
9
|
+
extend Conductor::Agents::Tools
|
|
10
|
+
|
|
11
|
+
def self.build(model: ENV.fetch('CONDUCTOR_AGENT_LLM_MODEL', 'openai/gpt-4o-mini'))
|
|
12
|
+
agent = Agent.new(
|
|
13
|
+
name: "greeter",
|
|
14
|
+
model: model,
|
|
15
|
+
instructions: "You are a friendly assistant. Keep responses brief."
|
|
16
|
+
)
|
|
17
|
+
agent
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.run(runtime: Conductor::Agents.runtime, input: $stdin, output: $stdout)
|
|
21
|
+
agent = build
|
|
22
|
+
executions = []
|
|
23
|
+
execution = runtime.call_async(agent, "Say hello and tell me a fun fact about Python.")
|
|
24
|
+
output.puts execution.result(timeout: 180)
|
|
25
|
+
executions << execution
|
|
26
|
+
executions
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
if $PROGRAM_NAME == __FILE__
|
|
31
|
+
begin
|
|
32
|
+
Example01BasicAgent.run
|
|
33
|
+
ensure
|
|
34
|
+
Conductor::Agents.shutdown
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Port of python-sdk/examples/agents/02a_simple_tools.py.
|
|
4
|
+
# Run: bundle exec ruby -Ilib examples/agents/02a_simple_tools.rb
|
|
5
|
+
require 'conductor/agents'
|
|
6
|
+
|
|
7
|
+
module Example02aSimpleTools
|
|
8
|
+
include Conductor::Agents
|
|
9
|
+
extend Conductor::Agents::Tools
|
|
10
|
+
|
|
11
|
+
def get_weather(city: String)
|
|
12
|
+
{ city: city, temp_f: 72, condition: "Sunny" }
|
|
13
|
+
end
|
|
14
|
+
tool :get_weather, description: "Get the current weather for a city."
|
|
15
|
+
|
|
16
|
+
def get_stock_price(symbol: String)
|
|
17
|
+
{ symbol: symbol, price: 182.50, change: "+1.2%" }
|
|
18
|
+
end
|
|
19
|
+
tool :get_stock_price, description: "Get the current stock price for a ticker symbol."
|
|
20
|
+
|
|
21
|
+
def self.build(model: ENV.fetch('CONDUCTOR_AGENT_LLM_MODEL', 'openai/gpt-4o-mini'))
|
|
22
|
+
agent = Agent.new(
|
|
23
|
+
name: "weather_stock_agent",
|
|
24
|
+
model: model,
|
|
25
|
+
tools: [self[:get_weather], self[:get_stock_price]],
|
|
26
|
+
instructions: "You are a helpful assistant. Use tools to answer questions."
|
|
27
|
+
)
|
|
28
|
+
agent
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.run(runtime: Conductor::Agents.runtime, input: $stdin, output: $stdout)
|
|
32
|
+
agent = build
|
|
33
|
+
executions = []
|
|
34
|
+
execution = runtime.call_async(agent, "What's the weather like in San Francisco?")
|
|
35
|
+
output.puts execution.result(timeout: 180)
|
|
36
|
+
executions << execution
|
|
37
|
+
executions
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
if $PROGRAM_NAME == __FILE__
|
|
42
|
+
begin
|
|
43
|
+
Example02aSimpleTools.run
|
|
44
|
+
ensure
|
|
45
|
+
Conductor::Agents.shutdown
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Port of python-sdk/examples/agents/02c_tool_retry_config.py.
|
|
4
|
+
# Run: bundle exec ruby -Ilib examples/agents/02c_tool_retry_config.rb
|
|
5
|
+
require 'conductor/agents'
|
|
6
|
+
|
|
7
|
+
module Example02cToolRetryConfig
|
|
8
|
+
include Conductor::Agents
|
|
9
|
+
extend Conductor::Agents::Tools
|
|
10
|
+
|
|
11
|
+
def call_external_api(query: String)
|
|
12
|
+
{ result: "Data for: #{query}", source: "external_api" }
|
|
13
|
+
end
|
|
14
|
+
tool :call_external_api, description: "Call an unreliable external API that may need aggressive retries.", retry_policy: "exponential_backoff", retry_count: 5, retry_delay_seconds: 1
|
|
15
|
+
|
|
16
|
+
def query_database(sql: String)
|
|
17
|
+
{ rows: [{ id: 1, value: sql }], count: 1 }
|
|
18
|
+
end
|
|
19
|
+
tool :query_database, description: "Run a database query with fixed-interval retries for transient connection issues." , retry_policy: "fixed", retry_count: 3, retry_delay_seconds: 5
|
|
20
|
+
|
|
21
|
+
def process_data(data: String)
|
|
22
|
+
{ processed: data, status: "ok" }
|
|
23
|
+
end
|
|
24
|
+
tool :process_data, description: "Process data locally — light retries with linear backoff." , retry_policy: "linear_backoff", retry_count: 2, retry_delay_seconds: 2
|
|
25
|
+
|
|
26
|
+
def self.build(model: ENV.fetch('CONDUCTOR_AGENT_LLM_MODEL', 'openai/gpt-4o-mini'))
|
|
27
|
+
agent = Agent.new(
|
|
28
|
+
name: "retry_config_demo",
|
|
29
|
+
model: model,
|
|
30
|
+
tools: [self[:call_external_api], self[:query_database], self[:process_data]],
|
|
31
|
+
instructions: "You help users fetch and process data. Use the appropriate tool for each request."
|
|
32
|
+
)
|
|
33
|
+
agent
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def self.run(runtime: Conductor::Agents.runtime, input: $stdin, output: $stdout)
|
|
37
|
+
agent = build
|
|
38
|
+
executions = []
|
|
39
|
+
execution = runtime.call_async(agent, "Look up the latest Python release info from the API.")
|
|
40
|
+
output.puts execution.result(timeout: 180)
|
|
41
|
+
executions << execution
|
|
42
|
+
executions
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
if $PROGRAM_NAME == __FILE__
|
|
47
|
+
begin
|
|
48
|
+
Example02cToolRetryConfig.run
|
|
49
|
+
ensure
|
|
50
|
+
Conductor::Agents.shutdown
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Port of python-sdk/examples/agents/04_http_and_mcp_tools.py.
|
|
4
|
+
# Run: bundle exec ruby -Ilib examples/agents/04_http_and_mcp_tools.rb
|
|
5
|
+
require 'conductor/agents'
|
|
6
|
+
|
|
7
|
+
module Example04HttpAndMcpTools
|
|
8
|
+
include Conductor::Agents
|
|
9
|
+
extend Conductor::Agents::Tools
|
|
10
|
+
|
|
11
|
+
def format_report(title: String, body: String)
|
|
12
|
+
{ report: "=== #{title} ===\n#{body}\n#{'=' * (title.length + 8)}" }
|
|
13
|
+
end
|
|
14
|
+
tool :format_report, description: "Format a title and body into a structured report."
|
|
15
|
+
|
|
16
|
+
def self.build(model: ENV.fetch('CONDUCTOR_AGENT_LLM_MODEL', 'openai/gpt-4o-mini'))
|
|
17
|
+
reverse_api = Tool.http(
|
|
18
|
+
"reverse_string",
|
|
19
|
+
"http://localhost:3001/api/string/reverse",
|
|
20
|
+
description: "Reverse a string using the HTTP API",
|
|
21
|
+
method: "POST",
|
|
22
|
+
headers: { "Authorization" => "Bearer ${HTTP_TEST_API_KEY}" },
|
|
23
|
+
credentials: ["HTTP_TEST_API_KEY"],
|
|
24
|
+
input_schema: { "type" => "object", "properties" => { "text" => { "type" => "string", "description" => "Text to reverse" } }, "required" => ["text"] }
|
|
25
|
+
)
|
|
26
|
+
mcp_test_tools = Tool.mcp(
|
|
27
|
+
"http://localhost:3001/mcp",
|
|
28
|
+
name: "mcp_test_tools",
|
|
29
|
+
description: "Deterministic test tools via MCP — math, string, collection, encoding, hash, datetime, validation, and conversion operations.",
|
|
30
|
+
headers: { "Authorization" => "Bearer ${MCP_TEST_API_KEY}" },
|
|
31
|
+
credentials: ["MCP_TEST_API_KEY"]
|
|
32
|
+
)
|
|
33
|
+
agent = Agent.new(
|
|
34
|
+
name: "http_tools_demo",
|
|
35
|
+
model: model,
|
|
36
|
+
tools: [self[:format_report], reverse_api, mcp_test_tools],
|
|
37
|
+
instructions: "You can reverse strings and format reports. When asked to reverse a string, use reverse_string first, then format_report with the result."
|
|
38
|
+
)
|
|
39
|
+
agent
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def self.run(runtime: Conductor::Agents.runtime, input: $stdin, output: $stdout)
|
|
43
|
+
agent = build
|
|
44
|
+
executions = []
|
|
45
|
+
execution = runtime.call_async(agent, "Reverse the string 'hello world' and add 33 and 21 append the result to that string, then write a report with the result.")
|
|
46
|
+
output.puts execution.result(timeout: 180)
|
|
47
|
+
executions << execution
|
|
48
|
+
executions
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
if $PROGRAM_NAME == __FILE__
|
|
53
|
+
begin
|
|
54
|
+
Example04HttpAndMcpTools.run
|
|
55
|
+
ensure
|
|
56
|
+
Conductor::Agents.shutdown
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Port of python-sdk/examples/agents/05_handoffs.py.
|
|
4
|
+
# Run: bundle exec ruby -Ilib examples/agents/05_handoffs.rb
|
|
5
|
+
require 'conductor/agents'
|
|
6
|
+
|
|
7
|
+
module Example05Handoffs
|
|
8
|
+
include Conductor::Agents
|
|
9
|
+
extend Conductor::Agents::Tools
|
|
10
|
+
|
|
11
|
+
def check_balance(account_id: String)
|
|
12
|
+
{ account_id: account_id, balance: 5432.10, currency: "USD" }
|
|
13
|
+
end
|
|
14
|
+
tool :check_balance, description: "Check the balance of a bank account."
|
|
15
|
+
|
|
16
|
+
def lookup_order(order_id: String)
|
|
17
|
+
{ order_id: order_id, status: "shipped", eta: "2 days" }
|
|
18
|
+
end
|
|
19
|
+
tool :lookup_order, description: "Look up the status of an order."
|
|
20
|
+
|
|
21
|
+
def get_pricing(product: String)
|
|
22
|
+
{ product: product, price: 99.99, discount: "10% off" }
|
|
23
|
+
end
|
|
24
|
+
tool :get_pricing, description: "Get pricing information for a product."
|
|
25
|
+
|
|
26
|
+
def self.build(model: ENV.fetch('CONDUCTOR_AGENT_LLM_MODEL', 'openai/gpt-4o-mini'))
|
|
27
|
+
billing_agent = Agent.new(
|
|
28
|
+
name: "billing",
|
|
29
|
+
model: model,
|
|
30
|
+
instructions: "You handle billing questions: balances, payments, invoices.",
|
|
31
|
+
tools: [self[:check_balance]]
|
|
32
|
+
)
|
|
33
|
+
technical_agent = Agent.new(
|
|
34
|
+
name: "technical",
|
|
35
|
+
model: model,
|
|
36
|
+
instructions: "You handle technical questions: order status, shipping, returns.",
|
|
37
|
+
tools: [self[:lookup_order]]
|
|
38
|
+
)
|
|
39
|
+
sales_agent = Agent.new(
|
|
40
|
+
name: "sales",
|
|
41
|
+
model: model,
|
|
42
|
+
instructions: "You handle sales questions: pricing, products, promotions.",
|
|
43
|
+
tools: [self[:get_pricing]]
|
|
44
|
+
)
|
|
45
|
+
support = Agent.new(
|
|
46
|
+
name: "support",
|
|
47
|
+
model: model,
|
|
48
|
+
instructions: "Route customer requests to the right specialist: billing, technical, or sales.",
|
|
49
|
+
agents: [billing_agent, technical_agent, sales_agent],
|
|
50
|
+
strategy: :handoff
|
|
51
|
+
)
|
|
52
|
+
support
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def self.run(runtime: Conductor::Agents.runtime, input: $stdin, output: $stdout)
|
|
56
|
+
support = build
|
|
57
|
+
executions = []
|
|
58
|
+
execution = runtime.call_async(support, "What's the balance on account ACC-123?")
|
|
59
|
+
output.puts execution.result(timeout: 180)
|
|
60
|
+
executions << execution
|
|
61
|
+
executions
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
if $PROGRAM_NAME == __FILE__
|
|
66
|
+
begin
|
|
67
|
+
Example05Handoffs.run
|
|
68
|
+
ensure
|
|
69
|
+
Conductor::Agents.shutdown
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Port of python-sdk/examples/agents/06_sequential_pipeline.py.
|
|
4
|
+
# Run: bundle exec ruby -Ilib examples/agents/06_sequential_pipeline.rb
|
|
5
|
+
require 'conductor/agents'
|
|
6
|
+
|
|
7
|
+
module Example06SequentialPipeline
|
|
8
|
+
include Conductor::Agents
|
|
9
|
+
extend Conductor::Agents::Tools
|
|
10
|
+
|
|
11
|
+
def self.build(model: ENV.fetch('CONDUCTOR_AGENT_LLM_MODEL', 'openai/gpt-4o-mini'))
|
|
12
|
+
researcher = Agent.new(
|
|
13
|
+
name: "researcher",
|
|
14
|
+
model: model,
|
|
15
|
+
instructions: "You are a researcher. Given a topic, provide key facts and data points. Be thorough but concise. Output raw research findings."
|
|
16
|
+
)
|
|
17
|
+
writer = Agent.new(
|
|
18
|
+
name: "writer",
|
|
19
|
+
model: model,
|
|
20
|
+
instructions: "You are a writer. Take research findings and write a clear, engaging article. Use headers and bullet points where appropriate."
|
|
21
|
+
)
|
|
22
|
+
editor = Agent.new(
|
|
23
|
+
name: "editor",
|
|
24
|
+
model: model,
|
|
25
|
+
instructions: "You are an editor. Review the article for clarity, grammar, and tone. Make improvements and output the final polished version."
|
|
26
|
+
)
|
|
27
|
+
pipeline = researcher >> writer >> editor
|
|
28
|
+
pipeline
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.run(runtime: Conductor::Agents.runtime, input: $stdin, output: $stdout)
|
|
32
|
+
pipeline = build
|
|
33
|
+
executions = []
|
|
34
|
+
execution = runtime.call_async(pipeline, "The impact of AI agents on software development in 2025")
|
|
35
|
+
output.puts execution.result(timeout: 180)
|
|
36
|
+
executions << execution
|
|
37
|
+
executions
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
if $PROGRAM_NAME == __FILE__
|
|
42
|
+
begin
|
|
43
|
+
Example06SequentialPipeline.run
|
|
44
|
+
ensure
|
|
45
|
+
Conductor::Agents.shutdown
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Port of python-sdk/examples/agents/07_parallel_agents.py.
|
|
4
|
+
# Run: bundle exec ruby -Ilib examples/agents/07_parallel_agents.rb
|
|
5
|
+
require 'conductor/agents'
|
|
6
|
+
|
|
7
|
+
module Example07ParallelAgents
|
|
8
|
+
include Conductor::Agents
|
|
9
|
+
extend Conductor::Agents::Tools
|
|
10
|
+
|
|
11
|
+
def self.build(model: ENV.fetch('CONDUCTOR_AGENT_LLM_MODEL', 'openai/gpt-4o-mini'))
|
|
12
|
+
market_analyst = Agent.new(
|
|
13
|
+
name: "market_analyst",
|
|
14
|
+
model: model,
|
|
15
|
+
instructions: "You are a market analyst. Analyze the given topic from a market perspective: market size, growth trends, key players, and opportunities."
|
|
16
|
+
)
|
|
17
|
+
risk_analyst = Agent.new(
|
|
18
|
+
name: "risk_analyst",
|
|
19
|
+
model: model,
|
|
20
|
+
instructions: "You are a risk analyst. Analyze the given topic for risks: regulatory risks, technical risks, competitive threats, and mitigation strategies."
|
|
21
|
+
)
|
|
22
|
+
compliance_checker = Agent.new(
|
|
23
|
+
name: "compliance",
|
|
24
|
+
model: model,
|
|
25
|
+
instructions: "You are a compliance specialist. Check the given topic for compliance considerations: data privacy, regulatory requirements, and industry standards."
|
|
26
|
+
)
|
|
27
|
+
analysis = Agent.new(
|
|
28
|
+
name: "analysis",
|
|
29
|
+
model: model,
|
|
30
|
+
agents: [market_analyst, risk_analyst, compliance_checker],
|
|
31
|
+
strategy: :parallel
|
|
32
|
+
)
|
|
33
|
+
analysis
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def self.run(runtime: Conductor::Agents.runtime, input: $stdin, output: $stdout)
|
|
37
|
+
analysis = build
|
|
38
|
+
executions = []
|
|
39
|
+
execution = runtime.call_async(analysis, "Launching an AI-powered healthcare diagnostic tool in the US market")
|
|
40
|
+
output.puts execution.result(timeout: 180)
|
|
41
|
+
executions << execution
|
|
42
|
+
executions
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
if $PROGRAM_NAME == __FILE__
|
|
47
|
+
begin
|
|
48
|
+
Example07ParallelAgents.run
|
|
49
|
+
ensure
|
|
50
|
+
Conductor::Agents.shutdown
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Port of python-sdk/examples/agents/09_human_in_the_loop.py.
|
|
4
|
+
# Run: bundle exec ruby -Ilib examples/agents/09_human_in_the_loop.rb
|
|
5
|
+
require 'conductor/agents'
|
|
6
|
+
|
|
7
|
+
module Example09HumanInTheLoop
|
|
8
|
+
include Conductor::Agents
|
|
9
|
+
extend Conductor::Agents::Tools
|
|
10
|
+
|
|
11
|
+
def check_balance(account_id: String)
|
|
12
|
+
{ account_id: account_id, balance: 15000.00 }
|
|
13
|
+
end
|
|
14
|
+
tool :check_balance, description: "Check the balance of an account."
|
|
15
|
+
|
|
16
|
+
def transfer_funds(from_acct: String, to_acct: String, amount: Float)
|
|
17
|
+
{ status: "completed", from: from_acct, to: to_acct, amount: amount }
|
|
18
|
+
end
|
|
19
|
+
tool :transfer_funds, description: "Request a funds transfer; runtime pauses for human approval before execution." , approval_required: true
|
|
20
|
+
|
|
21
|
+
def self.build(model: ENV.fetch('CONDUCTOR_AGENT_LLM_MODEL', 'openai/gpt-4o-mini'))
|
|
22
|
+
agent = Agent.new(
|
|
23
|
+
name: "banker",
|
|
24
|
+
model: model,
|
|
25
|
+
tools: [self[:check_balance], self[:transfer_funds]],
|
|
26
|
+
instructions: "You are a banking assistant. Use check_balance for balance inquiries. When asked to transfer money, first check the balance, then call transfer_funds to request the transfer. The runtime will pause for human approval before the transfer executes."
|
|
27
|
+
)
|
|
28
|
+
agent
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.run(runtime: Conductor::Agents.runtime, input: $stdin, output: $stdout)
|
|
32
|
+
agent = build
|
|
33
|
+
agent.on_approval do |request|
|
|
34
|
+
output.puts "Approval requested: #{request.tool_calls}"
|
|
35
|
+
response = {}
|
|
36
|
+
request.response_schema.fetch('properties').each do |field, schema|
|
|
37
|
+
output.print "#{schema['description'] || schema['title'] || field}: "
|
|
38
|
+
answer = input.gets
|
|
39
|
+
raise EOFError, "No response supplied for #{field}" unless answer
|
|
40
|
+
|
|
41
|
+
response[field] = schema['type'] == 'boolean' ? %w[y yes].include?(answer.strip.downcase) : answer.strip
|
|
42
|
+
end
|
|
43
|
+
request.respond(response)
|
|
44
|
+
end
|
|
45
|
+
executions = []
|
|
46
|
+
execution = runtime.call_async(agent, "Transfer $500 from ACC-789 to ACC-456. Check the balance first.")
|
|
47
|
+
output.puts execution.result(timeout: 180)
|
|
48
|
+
executions << execution
|
|
49
|
+
executions
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
if $PROGRAM_NAME == __FILE__
|
|
54
|
+
begin
|
|
55
|
+
Example09HumanInTheLoop.run
|
|
56
|
+
ensure
|
|
57
|
+
Conductor::Agents.shutdown
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Port of python-sdk/examples/agents/09c_hitl_streaming.py.
|
|
4
|
+
# Run: bundle exec ruby -Ilib examples/agents/09c_hitl_streaming.rb
|
|
5
|
+
require 'conductor/agents'
|
|
6
|
+
|
|
7
|
+
module Example09cHitlStreaming
|
|
8
|
+
include Conductor::Agents
|
|
9
|
+
extend Conductor::Agents::Tools
|
|
10
|
+
|
|
11
|
+
def check_service(service_name: String)
|
|
12
|
+
{ service: service_name, status: "unhealthy", uptime: "0m" }
|
|
13
|
+
end
|
|
14
|
+
tool :check_service, description: "Check the health of a service."
|
|
15
|
+
|
|
16
|
+
def restart_service(service_name: String)
|
|
17
|
+
{ service: service_name, status: "restarted", new_uptime: "0m" }
|
|
18
|
+
end
|
|
19
|
+
tool :restart_service, description: "Restart a service. Safe operation, no approval needed."
|
|
20
|
+
|
|
21
|
+
def delete_service_data(service_name: String, data_type: String)
|
|
22
|
+
{ service: service_name, data_type: data_type, status: "deleted" }
|
|
23
|
+
end
|
|
24
|
+
tool :delete_service_data, description: "Delete service data. Destructive — requires human approval." , approval_required: true
|
|
25
|
+
|
|
26
|
+
def self.build(model: ENV.fetch('CONDUCTOR_AGENT_LLM_MODEL', 'openai/gpt-4o-mini'))
|
|
27
|
+
agent = Agent.new(
|
|
28
|
+
name: "ops_agent",
|
|
29
|
+
model: model,
|
|
30
|
+
tools: [self[:check_service], self[:restart_service], self[:delete_service_data]],
|
|
31
|
+
instructions: "You are an operations assistant. Work through the request one tool call at a time, in this order:\n1. Check the service with check_service.\n2. If it is unhealthy, restart it with restart_service.\n3. Last, if the user asked you to clear or delete data, call delete_service_data.\nA human approves the deletion, not you — delete_service_data pauses for that approval by itself, so never ask for approval in your own reply."
|
|
32
|
+
)
|
|
33
|
+
agent
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def self.run(runtime: Conductor::Agents.runtime, input: $stdin, output: $stdout)
|
|
37
|
+
agent = build
|
|
38
|
+
agent.on_approval do |request|
|
|
39
|
+
output.puts "Approval requested: #{request.tool_calls}"
|
|
40
|
+
response = {}
|
|
41
|
+
request.response_schema.fetch('properties').each do |field, schema|
|
|
42
|
+
output.print "#{schema['description'] || schema['title'] || field}: "
|
|
43
|
+
answer = input.gets
|
|
44
|
+
raise EOFError, "No response supplied for #{field}" unless answer
|
|
45
|
+
|
|
46
|
+
response[field] = schema['type'] == 'boolean' ? %w[y yes].include?(answer.strip.downcase) : answer.strip
|
|
47
|
+
end
|
|
48
|
+
request.respond(response)
|
|
49
|
+
end
|
|
50
|
+
executions = []
|
|
51
|
+
on_event = ->(event) { output.puts "[#{event['event']}] #{event['data']}" }
|
|
52
|
+
execution = runtime.call_async(agent, "The payments service is down. Check it, restart it, and clear its stale cache data.", on_event: on_event)
|
|
53
|
+
output.puts execution.result(timeout: 180)
|
|
54
|
+
executions << execution
|
|
55
|
+
executions
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
if $PROGRAM_NAME == __FILE__
|
|
60
|
+
begin
|
|
61
|
+
Example09cHitlStreaming.run
|
|
62
|
+
ensure
|
|
63
|
+
Conductor::Agents.shutdown
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Port of python-sdk/examples/agents/103_plan_and_compile.py.
|
|
4
|
+
# Run: bundle exec ruby -Ilib examples/agents/103_plan_and_compile.rb
|
|
5
|
+
require 'conductor/agents'
|
|
6
|
+
|
|
7
|
+
module Example103PlanAndCompile
|
|
8
|
+
include Conductor::Agents
|
|
9
|
+
extend Conductor::Agents::Tools
|
|
10
|
+
|
|
11
|
+
def factorial(n: Integer)
|
|
12
|
+
return "ERROR: n must be in [0, 20], got #{n}" unless (0..20).cover?(n)
|
|
13
|
+
|
|
14
|
+
(1..n).reduce(1, :*).to_s
|
|
15
|
+
end
|
|
16
|
+
tool :factorial, output_schema: { 'type' => 'string' }, description: "Compute n! and return it as a string.\n\nArgs:\n n: Non-negative integer. Capped at 20 to keep things sane."
|
|
17
|
+
|
|
18
|
+
def write_summary(text: String)
|
|
19
|
+
text
|
|
20
|
+
end
|
|
21
|
+
tool :write_summary, output_schema: { 'type' => 'string' }, description: "Persist a short summary string. Returns it back for the validator."
|
|
22
|
+
|
|
23
|
+
def check_summary(text: String, min_chars: Integer)
|
|
24
|
+
JSON.generate(passed: text.length >= min_chars, length: text.length, min_chars: min_chars)
|
|
25
|
+
end
|
|
26
|
+
tool :check_summary, output_schema: { 'type' => 'string' }, description: "Return JSON ``{passed, length, min_chars}`` for the validator.\n\nArgs:\n text: The summary to check.\n min_chars: Minimum acceptable length in characters."
|
|
27
|
+
|
|
28
|
+
def self.build(model: ENV.fetch('CONDUCTOR_AGENT_LLM_MODEL', 'openai/gpt-4o-mini'))
|
|
29
|
+
planner_instructions = "You are a math-explainer planner. Plan a workflow that:\n\n1. Computes factorials of 1, 2, 3, 4, 5 in PARALLEL using ``factorial`` (static args).\n2. Writes a short prose summary about factorial growth using ``write_summary``\n (use a ``generate`` block — the LLM produces the ``text`` arg at run time).\n3. Validates the summary is at least 30 characters via ``check_summary``,\n with ``success_condition: \"$.passed === true\"``.\n"
|
|
30
|
+
harness = Conductor::Agents.plan_execute(
|
|
31
|
+
name: "plan_and_compile_demo",
|
|
32
|
+
tools: [self[:factorial], self[:write_summary], self[:check_summary]],
|
|
33
|
+
planner_instructions: planner_instructions,
|
|
34
|
+
fallback_instructions: "The plan failed. Use the available tools to recover.",
|
|
35
|
+
fallback_max_turns: 4,
|
|
36
|
+
model: model
|
|
37
|
+
)
|
|
38
|
+
harness
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def self.run(runtime: Conductor::Agents.runtime, input: $stdin, output: $stdout, topic: 'factorials')
|
|
42
|
+
harness = build
|
|
43
|
+
executions = []
|
|
44
|
+
execution = runtime.call_async(harness, "Topic: #{topic}")
|
|
45
|
+
output.puts execution.result(timeout: 180)
|
|
46
|
+
compiled = find_plan_and_compile_output(runtime, execution.execution_id)
|
|
47
|
+
raise 'No PLAN_AND_COMPILE task found in the workflow tree' unless compiled
|
|
48
|
+
raise "Plan compilation failed: #{compiled['error']}" if compiled['error']
|
|
49
|
+
|
|
50
|
+
output.puts "Compiled workflow: #{compiled['workflowName']}"
|
|
51
|
+
output.puts "Stats: #{compiled['stats']}"
|
|
52
|
+
Array(compiled.dig('workflowDef', 'tasks')).each do |task|
|
|
53
|
+
output.puts "#{task['type']}: #{task['taskReferenceName']}"
|
|
54
|
+
end
|
|
55
|
+
executions << execution
|
|
56
|
+
executions
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def self.find_plan_and_compile_output(runtime, execution_id)
|
|
60
|
+
client = Conductor::Client::WorkflowClient.new(runtime.configuration)
|
|
61
|
+
pending = [execution_id]
|
|
62
|
+
visited = []
|
|
63
|
+
until pending.empty?
|
|
64
|
+
id = pending.pop
|
|
65
|
+
next if visited.include?(id)
|
|
66
|
+
|
|
67
|
+
visited << id
|
|
68
|
+
workflow = client.get_workflow(id)
|
|
69
|
+
workflow.tasks.each do |task|
|
|
70
|
+
return task.output_data if task.task_type == 'PLAN_AND_COMPILE'
|
|
71
|
+
|
|
72
|
+
pending << task.sub_workflow_id if task.sub_workflow_id
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
nil
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
if $PROGRAM_NAME == __FILE__
|
|
80
|
+
begin
|
|
81
|
+
Example103PlanAndCompile.run(topic: ARGV.empty? ? 'factorials' : ARGV.join(' '))
|
|
82
|
+
ensure
|
|
83
|
+
Conductor::Agents.shutdown
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Port of python-sdk/examples/agents/10_guardrails.py.
|
|
4
|
+
# Run: bundle exec ruby -Ilib examples/agents/10_guardrails.rb
|
|
5
|
+
require 'conductor/agents'
|
|
6
|
+
|
|
7
|
+
module Example10Guardrails
|
|
8
|
+
include Conductor::Agents
|
|
9
|
+
extend Conductor::Agents::Tools
|
|
10
|
+
|
|
11
|
+
def get_order_status(order_id: String)
|
|
12
|
+
{ order_id: order_id, status: "shipped", tracking: "1Z999AA10123456784", estimated_delivery: "2026-02-22" }
|
|
13
|
+
end
|
|
14
|
+
tool :get_order_status, description: "Look up the current status of an order."
|
|
15
|
+
|
|
16
|
+
def get_customer_info(customer_id: String)
|
|
17
|
+
{ customer_id: customer_id, name: "Alice Johnson", email: "alice@example.com",
|
|
18
|
+
card_on_file: "4532-0150-1234-5678", membership: "gold" }
|
|
19
|
+
end
|
|
20
|
+
tool :get_customer_info, description: "Retrieve customer details including payment info on file."
|
|
21
|
+
|
|
22
|
+
def self.pii_guardrail
|
|
23
|
+
Guardrail.new(name: "no_pii", position: :output, on_fail: :retry) do |content|
|
|
24
|
+
pii = /\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b|\b\d{3}-\d{2}-\d{4}\b/
|
|
25
|
+
if pii.match?(content)
|
|
26
|
+
GuardrailResult.new(passed: false, message: "Your response contains PII (credit card or SSN). Redact all card numbers and SSNs before responding.")
|
|
27
|
+
else
|
|
28
|
+
GuardrailResult.new(passed: true)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def self.build(model: ENV.fetch('CONDUCTOR_AGENT_LLM_MODEL', 'openai/gpt-4o-mini'))
|
|
34
|
+
no_pii = pii_guardrail
|
|
35
|
+
agent = Agent.new(
|
|
36
|
+
name: "support_agent",
|
|
37
|
+
model: model,
|
|
38
|
+
tools: [self[:get_order_status], self[:get_customer_info]],
|
|
39
|
+
instructions: "You are a customer support assistant. Use the available tools to answer questions about orders and customers. Always include all details from the tool results in your response.",
|
|
40
|
+
guardrails: [no_pii]
|
|
41
|
+
)
|
|
42
|
+
agent
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def self.run(runtime: Conductor::Agents.runtime, input: $stdin, output: $stdout)
|
|
46
|
+
agent = build
|
|
47
|
+
executions = []
|
|
48
|
+
execution = runtime.call_async(agent, "I need a full summary: What's the status of order ORD-42, and what's the profile for customer CUST-7?")
|
|
49
|
+
output.puts execution.result(timeout: 180)
|
|
50
|
+
executions << execution
|
|
51
|
+
executions
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
if $PROGRAM_NAME == __FILE__
|
|
56
|
+
begin
|
|
57
|
+
Example10Guardrails.run
|
|
58
|
+
ensure
|
|
59
|
+
Conductor::Agents.shutdown
|
|
60
|
+
end
|
|
61
|
+
end
|