Class: Agents::AgentTool

Inherits:
Tool
  • Object
show all
Defined in:
lib/agents/agent_tool.rb

Overview

AgentTool wraps an agent as a tool, enabling agent-to-agent collaboration without conversation handoffs. This implementation constrains wrapped agents for safety and predictability.

Key constraints:

  1. Wrapped agents cannot perform handoffs (empty registry)

  2. Limited turn count to prevent infinite loops

  3. Isolated context (only shared state, no conversation history)

  4. Always returns to calling agent

Examples:

Customer support copilot with specialized agents

conversation_agent = Agent.new(
  name: "ConversationAnalyzer",
  instructions: "Extract order IDs and customer intent from conversation history"
)

actions_agent = Agent.new(
  name: "ShopifyActions",
  instructions: "Perform Shopify operations",
  tools: [shopify_tool]
)

copilot = Agent.new(
  name: "SupportCopilot",
  tools: [
    conversation_agent.as_tool(
      name: "analyze_conversation",
      description: "Extract key info from conversation history"
    ),
    actions_agent.as_tool(
      name: "shopify_action",
      description: "Perform Shopify operations like refunds"
    )
  ]
)

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods inherited from Tool

#execute

Constructor Details

#initialize(agent:, name: nil, description: nil, output_extractor: nil) ⇒ AgentTool

Initialize an AgentTool that wraps an agent as a callable tool

Parameters:

  • agent (Agents::Agent)

    The agent to wrap as a tool

  • name (String, nil) (defaults to: nil)

    Override the tool name (defaults to snake_case agent name)

  • description (String, nil) (defaults to: nil)

    Override the tool description

  • output_extractor (Proc, nil) (defaults to: nil)

    Custom proc to extract/transform the agent’s output



51
52
53
54
55
56
57
58
# File 'lib/agents/agent_tool.rb', line 51

def initialize(agent:, name: nil, description: nil, output_extractor: nil)
  @wrapped_agent = agent
  @tool_name = name || transform_agent_name(agent.name)
  @tool_description = description || "Execute #{agent.name} agent"
  @output_extractor = output_extractor

  super()
end

Instance Attribute Details

#output_extractorObject (readonly)

Returns the value of attribute output_extractor.



40
41
42
# File 'lib/agents/agent_tool.rb', line 40

def output_extractor
  @output_extractor
end

#tool_descriptionObject (readonly)

Returns the value of attribute tool_description.



40
41
42
# File 'lib/agents/agent_tool.rb', line 40

def tool_description
  @tool_description
end

#tool_nameObject (readonly)

Returns the value of attribute tool_name.



40
41
42
# File 'lib/agents/agent_tool.rb', line 40

def tool_name
  @tool_name
end

#wrapped_agentObject (readonly)

Returns the value of attribute wrapped_agent.



40
41
42
# File 'lib/agents/agent_tool.rb', line 40

def wrapped_agent
  @wrapped_agent
end

Instance Method Details

#descriptionObject



64
65
66
# File 'lib/agents/agent_tool.rb', line 64

def description
  @tool_description
end

#nameObject



60
61
62
# File 'lib/agents/agent_tool.rb', line 60

def name
  @tool_name
end

#perform(tool_context, input:) ⇒ Object

Execute the wrapped agent with constraints to prevent handoffs and recursion



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/agents/agent_tool.rb', line 69

def perform(tool_context, input:)
  # Create isolated context for the wrapped agent
  isolated_context = create_isolated_context(tool_context.context)

  # Execute with explicit constraints:
  # 1. Empty registry prevents handoffs
  # 2. Low max_turns prevents infinite loops
  # 3. Isolated context prevents history access
  result = Runner.new.run(
    @wrapped_agent,
    input,
    context: isolated_context,
    registry: {}, # CONSTRAINT: No handoffs allowed
    max_turns: 3  # CONSTRAINT: Limited turns for tool execution
  )

  return "Agent execution failed: #{result.error.message}" if result.error

  # Extract output
  if @output_extractor
    @output_extractor.call(result)
  else
    result.output || "No output from #{@wrapped_agent.name}"
  end
rescue StandardError => e
  "Error executing #{@wrapped_agent.name}: #{e.message}"
end