fix agent command

This commit is contained in:
Mery-Sanz 2025-04-24 15:03:13 +02:00 committed by Víctor Mayoral Vilches
parent 483591f36a
commit 56e6fcf1a4
4 changed files with 130 additions and 167 deletions

View File

View File

@ -280,8 +280,9 @@ def run_cai_cli(starting_agent, context_variables=None, stream=False, max_turns=
if commands_handle_command(command, args):
continue # Command was handled, continue to next iteration
# If command wasn't recognized, show error
console.print(f"[red]Unknown command: {command}[/red]")
# If command wasn't recognized, show error (skip for /shell or /s)
if command not in ("/shell", "/s"):
console.print(f"[red]Unknown command: {command}[/red]")
continue
# Process the conversation with the agent

View File

@ -155,50 +155,45 @@ class AgentCommand(Command):
table = Table(title="Available Agents")
table.add_column("#", style="dim")
table.add_column("Name", style="cyan")
table.add_column("Module", style="magenta")
table.add_column("Key", style="magenta")
table.add_column("Module", style="green")
table.add_column("Description", style="green")
table.add_column("Pattern", style="blue")
table.add_column("Model", style="yellow")
# Scan all agents from the agents folder
# Retrieve all registered agents
agents_to_display = get_available_agents()
# Display all agents
for i, (name, agent) in enumerate(agents_to_display.items(), 1):
description = agent.description
if not description and hasattr(agent, 'instructions'):
if callable(agent.instructions):
description = agent.instructions(context_variables={})
else:
description = agent.instructions
# Clean up description - remove newlines and strip spaces
for idx, (agent_key, agent) in enumerate(agents_to_display.items(), start=1):
# Human-friendly name (falls back to the dict key)
display_name = getattr(agent, "name", agent_key)
# Use provided description, otherwise derive from instructions
description = getattr(agent, "description", "") or ""
if not description and hasattr(agent, "instructions"):
instr = agent.instructions
description = instr(context_variables={}) if callable(instr) else instr
if isinstance(description, str):
description = " ".join(description.split())
if len(description) > 50:
description = description[:47] + "..."
# Get the module name for the agent
module_name = get_agent_module(name)
# Module where this agent lives
module_name = get_agent_module(agent_key)
# Get the pattern if it exists
pattern = getattr(agent, 'pattern', '')
if pattern:
pattern = pattern.capitalize()
# Handle model display based on agent type
model_display = self._get_model_display(name, agent)
# Add a row with all collected info
table.add_row(
str(i),
name,
str(idx),
display_name,
agent_key,
module_name,
description,
pattern,
model_display
description
)
console.print(table)
return True
console.print(table)
return True
def handle_select(self, args: Optional[List[str]] = None) -> bool: # pylint: disable=too-many-branches,line-too-long # noqa: E501
"""Handle /agent select command.
@ -210,76 +205,41 @@ class AgentCommand(Command):
"""
if not args:
console.print("[red]Error: No agent specified[/red]")
console.print("Usage: /agent select <name|number>")
console.print("Usage: /agent select <agent_key|number>")
return False
agent_id = args[0]
# Get the list of available agents
agents_to_display = get_available_agents()
agent_list = list(agents_to_display.items()) # preserve order for indexing
# Check if agent_id is a number
if agent_id.isdigit():
index = int(agent_id)
if 1 <= index <= len(agents_to_display):
agent_name = list(agents_to_display.keys())[index - 1]
if 1 <= index <= len(agent_list):
# Get the agent tuple from the list
selected_agent_key, selected_agent = agent_list[index - 1]
agent_name = getattr(selected_agent, "name", selected_agent_key)
agent = selected_agent
else:
console.print(
f"[red]Error: Invalid agent number: {agent_id}[/red]")
console.print(f"[red]Error: Invalid agent number: {agent_id}[/red]")
return False
else:
# Treat as agent name
agent_name = agent_id
if agent_name not in agents_to_display:
console.print(f"[red]Error: Unknown agent: {agent_name}[/red]")
# Treat as agent key
for key, agent_obj in agents_to_display.items():
if key == agent_id:
agent = agent_obj
agent_name = getattr(agent_obj, "name", key)
break
else:
console.print(f"[red]Error: Unknown agent key: {agent_id}[/red]")
return False
# Get the agent
agent = agents_to_display[agent_name]
# Set the agent as the current agent in the REPL
# We need to avoid circular imports, so we'll use a different approach
# to access the client and current_agent variables
# Import the module dynamically to avoid circular imports
if 'cai.repl.repl' in sys.modules:
repl_module = sys.modules['cai.repl.repl']
# Check if client is initialized
if hasattr(repl_module, 'client') and repl_module.client:
# Update the active_agent in the client
repl_module.client.active_agent = agent
# Update the global current_agent variable if it exists
if hasattr(repl_module, 'current_agent'):
repl_module.current_agent = agent
# Update the global agent variable if it exists
if hasattr(repl_module, 'agent'):
repl_module.agent = agent
# Also update the agent variable in the run_demo_loop
# function's frame if possible
try:
for frame_info in inspect.stack():
frame = frame_info.frame
if ('run_demo_loop' in frame.f_code.co_name and
'agent' in frame.f_locals):
frame.f_locals['agent'] = agent
break
except Exception: # pylint: disable=broad-except # nosec
# If this fails, we still have the global current_agent as
# a fallback
pass
console.print(
f"[green]Switched to agent: {agent_name}[/green]")
visualize_agent_graph(agent)
return True
console.print("[red]Error: CAI client not initialized[/red]")
return False
console.print("[red]Error: REPL module not initialized[/red]")
return False
os.environ["CAI_MODEL"] = agent_name
console.print(
f"[green]Switched to agent: {agent_name}[/green]")
visualize_agent_graph(agent)
return True
def handle_info(self, args: Optional[List[str]] = None) -> bool:
"""Handle /agent info command.
@ -292,57 +252,74 @@ class AgentCommand(Command):
"""
if not args:
console.print("[red]Error: No agent specified[/red]")
console.print("Usage: /agent info <name|number>")
console.print("Usage: /agent info <agent_key|number>")
return False
agent_id = args[0]
# Get the list of available agents
# Get available agents
agents_to_display = get_available_agents()
# Check if agent_id is a number
# Resolve agent_id to an agent key (by index or name)
if agent_id.isdigit():
index = int(agent_id)
if 1 <= index <= len(agents_to_display):
agent_name = list(agents_to_display.keys())[index - 1]
else:
console.print(
f"[red]Error: Invalid agent number: {agent_id}[/red]")
idx = int(agent_id)
if not (1 <= idx <= len(agents_to_display)):
console.print(f"[red]Error: Invalid agent number: {agent_id}[/red]")
return False
agent_key = list(agents_to_display.keys())[idx - 1]
else:
# Treat as agent name
agent_name = agent_id
if agent_name not in agents_to_display:
console.print(f"[red]Error: Unknown agent: {agent_name}[/red]")
agent_key = None
for key, ag in agents_to_display.items():
if key == agent_id or getattr(ag, "name", "").lower() == agent_id.lower():
agent_key = key
break
if agent_key is None:
console.print(f"[red]Error: Unknown agent key: {agent_id}[/red]")
return False
# Get the agent
agent = agents_to_display[agent_name]
agent = agents_to_display[agent_key]
# Display agent information
# Display agent information
instructions = agent.instructions
if callable(instructions):
instructions = instructions()
# Prepare agent properties
name = agent.name or agent_key
description = getattr(agent, "description", None) or "N/A"
clean_description = " ".join(line.strip() for line in description.splitlines())
functions = getattr(agent, "functions", [])
parallel = getattr(agent, "parallel_tool_calls", False)
handoff_desc = getattr(agent, "handoff_description", None) or "N/A"
handoffs = getattr(agent, "handoffs", [])
tools = getattr(agent, "tools", [])
guardrails_in = getattr(agent, "input_guardrails", [])
guardrails_out = getattr(agent, "output_guardrails", [])
output_type = getattr(agent, "output_type", None) or "N/A"
hooks = getattr(agent, "hooks", []) or []
# Handle model display based on agent type
model_display = self._get_model_display_for_info(agent_name, agent)
# Create a markdown table for agent details
# Build markdown content for agent info
markdown_content = f"""
# Agent: {agent_name}
# Agent Info: {name}
| Property | Value |
|----------|-------|
| Name | {agent.name} |
| Model | {model_display} |
| Functions | {len(agent.functions)} |
| Parallel Tool Calls | {'Yes' if agent.parallel_tool_calls else 'No'} |
| Property | Value |
|------------------------|-------------------------------|
| Key | {agent_key} |
| Name | {name} |
| Description | {clean_description} |
| Functions | {len(functions)} |
| Parallel Tool Calls | {"Yes" if parallel else "No"} |
| Handoff Description | {handoff_desc} |
| Handoffs | {len(handoffs)} |
| Tools | {len(tools)} |
| Input Guardrails | {len(guardrails_in)} |
| Output Guardrails | {len(guardrails_out)} |
| Output Type | {output_type} |
| Hooks | {len(hooks)} |
## Instructions
{instructions}
"""
"""
console.print(Markdown(markdown_content))
return True

View File

@ -268,87 +268,72 @@ def load_prompt_template(template_path):
except Exception as e:
raise ValueError(f"Failed to load template '{template_path}': {str(e)}")
# Start of Selection
def visualize_agent_graph(start_agent):
"""
Visualize agent graph showing all bidirectional connections between agents.
Uses Rich library for pretty printing.
"""
console = Console() # pylint: disable=redefined-outer-name
console = Console()
if start_agent is None:
console.print("[red]No agent provided to visualize.[/red]")
return
tree = Tree(
f"🤖 {
start_agent.name} (Current Agent)",
guide_style="bold blue")
tree = Tree(f"🤖 {start_agent.name} (Current Agent)", guide_style="bold blue")
# Track visited agents and their nodes to handle cross-connections
visited = {}
visited = set()
agent_nodes = {}
agent_positions = {} # Track positions in tree
position_counter = 0 # Counter for tracking positions
agent_positions = {}
position_counter = 0
def add_agent_node(agent, parent=None, is_transfer=False): # pylint: disable=too-many-branches # noqa: E501
"""Add agent node and track for cross-connections"""
def add_agent_node(agent, parent=None, is_transfer=False):
"""Add an agent node and track for cross-connections."""
nonlocal position_counter
if agent is None:
return None
aid = id(agent)
if aid in visited:
if is_transfer and parent:
original_pos = agent_positions.get(aid)
parent.add(f"[cyan]↩ Return to {agent.name} (Agent #{original_pos})[/cyan]")
return agent_nodes.get(aid)
# Create or get existing node for this agent
if id(agent) in visited:
if is_transfer:
# Add reference with position for repeated agents
original_pos = agent_positions[id(agent)]
parent.add(
f"[cyan]↩ Return to {
agent.name} (Top Level Agent #{original_pos})[/cyan]")
return agent_nodes[id(agent)]
visited[id(agent)] = True
visited.add(aid)
position_counter += 1
agent_positions[id(agent)] = position_counter
agent_positions[aid] = position_counter
# Create node for current agent
if is_transfer:
if is_transfer and parent:
node = parent
elif parent:
node = parent.add(f"[green]{agent.name} (#{position_counter})[/green]")
else:
node = parent.add(
f"[green]{agent.name} (#{position_counter})[/green]") if parent else tree # noqa: E501 pylint: disable=line-too-long
agent_nodes[id(agent)] = node
node = tree
agent_nodes[aid] = node
# Add tools as children
# Add tools
tools_node = node.add("[yellow]Tools[/yellow]")
for fn in getattr(agent, "functions", []):
if callable(fn):
fn_name = getattr(fn, "__name__", "")
if ("handoff" not in fn_name.lower() and
not fn_name.startswith("transfer_to")):
tools_node.add(f"[blue]{fn_name}[/blue]")
for tool in getattr(agent, "tools", []):
tool_name = getattr(tool, "name", None) or getattr(tool, "__name__", "")
tools_node.add(f"[blue]{tool_name}[/blue]")
# Add Handoffs section
# Add handoffs
transfers_node = node.add("[magenta]Handoffs[/magenta]")
for handoff_fn in getattr(agent, "handoffs", []):
if callable(handoff_fn):
try:
next_agent = handoff_fn()
if next_agent:
transfer_node = transfers_node.add(f"🤖 {next_agent.name}")
add_agent_node(next_agent, transfer_node, True)
except Exception:
continue
# Process handoff functions
for fn in getattr(agent, "functions", []): # pylint: disable=too-many-nested-blocks # noqa: E501
if callable(fn):
fn_name = getattr(fn, "__name__", "")
if ("handoff" in fn_name.lower() or
fn_name.startswith("transfer_to")):
try:
next_agent = fn()
if next_agent:
# Show bidirectional connection
transfer = transfers_node.add(
f"🤖 {next_agent.name}") # noqa: E501
add_agent_node(next_agent, transfer, True)
except Exception: # nosec: B112 # pylint: disable=broad-exception-caught # noqa: E501
continue
return node
# Start recursive traversal from root agent
# Start traversal from the root agent
add_agent_node(start_agent)
console.print(tree)
# End of Selectio
def fix_litellm_transcription_annotations():
"""