Skip to content

Commit 5b8460b

Browse files
committed
Add the missing Subagent
1 parent 644fa7a commit 5b8460b

File tree

1 file changed

+93
-0
lines changed

1 file changed

+93
-0
lines changed
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
from __future__ import annotations
2+
3+
from dataclasses import dataclass, field
4+
from typing import Callable
5+
6+
from coagent.core import Address, BaseAgent
7+
8+
from .context import RunContext
9+
from .messages import (
10+
InputHistory,
11+
InputMessage,
12+
MessageOutputItem,
13+
OutputMessage,
14+
ToolCallItem,
15+
ToolCallOutputItem,
16+
ToolCallProgressItem,
17+
)
18+
19+
20+
@dataclass
21+
class Subagent:
22+
type: str
23+
"""The type of the subagent."""
24+
25+
tool_name: str | None = field(default=None, init=False)
26+
"""The name of the subagent tool."""
27+
28+
tool_description: str | None = field(default=None, init=False)
29+
"""A description of the subagent tool."""
30+
31+
def as_tool(
32+
self, name: str | None = None, description: str | None = None
33+
) -> Subagent:
34+
self.tool_name = name
35+
self.tool_description = description
36+
return self
37+
38+
39+
class SubagentTool:
40+
"""A wrapper class that allows an agent to be used as a tool by a host agent."""
41+
42+
def __init__(self, host_agent: BaseAgent, subagent: Subagent):
43+
self.host_agent: BaseAgent = host_agent
44+
self.subagent: Subagent = subagent
45+
46+
def as_tool(self) -> Callable:
47+
async def run(ctx: RunContext, input: str) -> str:
48+
addr = Address(name=self.subagent.type, id=self.host_agent.address.id)
49+
msg = InputHistory(messages=[InputMessage(role="user", content=input)])
50+
result = await self.host_agent.channel.publish(
51+
addr, msg.encode(), stream=True
52+
)
53+
54+
final_output = ""
55+
parent_tool_name = ctx.full_tool_name
56+
57+
try:
58+
async for chunk in result:
59+
msg = OutputMessage.decode(chunk)
60+
i = msg.item
61+
match i:
62+
case MessageOutputItem():
63+
final_output += i.raw_item.content[0].text
64+
65+
case ToolCallItem():
66+
# Use the full tool name that includes its parent agent (as tool) in the stream events.
67+
# This is pretty useful for debugging and understanding the tool call hierarchy.
68+
if parent_tool_name:
69+
i.raw_item.name = (
70+
f"{parent_tool_name}/{i.raw_item.name}"
71+
)
72+
73+
final_output = "" # Reset tool output on intermediate updates. We only want the final output.
74+
ctx.put_event(i)
75+
76+
case ToolCallProgressItem():
77+
final_output = ""
78+
ctx.put_event(i)
79+
80+
case ToolCallOutputItem():
81+
final_output = ""
82+
ctx.put_event(i)
83+
except Exception as exc:
84+
return f"Error: {exc}"
85+
else:
86+
return final_output
87+
88+
run.__name__ = (
89+
self.subagent.tool_name
90+
or f"{self.subagent.type}.{self.host_agent.address.id}"
91+
)
92+
run.__doc__ = self.subagent.tool_description or ""
93+
return run

0 commit comments

Comments
 (0)