Skip to content

Commit 04c3059

Browse files
authored
add tool call to chat completion agent (autogenhub#35)
* add tool call to chat completion agent * refactor function executor; tool executor in chat completion agent * update example * update orchestrator chat demo * handle function execution result message type * format * temp fix for examples. * fix * update chat completion agent
1 parent 2dc7af8 commit 04c3059

File tree

15 files changed

+516
-361
lines changed

15 files changed

+516
-361
lines changed

examples/orchestrator.py

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
import argparse
2+
import asyncio
3+
import json
4+
import logging
5+
import os
6+
from typing import Annotated, Callable
7+
8+
import openai
9+
from agnext.agent_components.function_executor._impl.in_process_function_executor import (
10+
InProcessFunctionExecutor,
11+
)
12+
from agnext.agent_components.model_client import OpenAI
13+
from agnext.agent_components.types import SystemMessage
14+
from agnext.application_components import (
15+
SingleThreadedAgentRuntime,
16+
)
17+
from agnext.chat.agents.chat_completion_agent import ChatCompletionAgent
18+
from agnext.chat.agents.oai_assistant import OpenAIAssistantAgent
19+
from agnext.chat.patterns.orchestrator_chat import OrchestratorChat
20+
from agnext.chat.types import TextMessage
21+
from agnext.core import Agent, AgentRuntime
22+
from agnext.core.intervention import DefaultInterventionHandler, DropMessage
23+
from tavily import TavilyClient
24+
from typing_extensions import Any, override
25+
26+
logging.basicConfig(level=logging.WARNING)
27+
logging.getLogger("agnext").setLevel(logging.DEBUG)
28+
29+
30+
class LoggingHandler(DefaultInterventionHandler): # type: ignore
31+
send_color = "\033[31m"
32+
response_color = "\033[34m"
33+
reset_color = "\033[0m"
34+
35+
@override
36+
async def on_send(self, message: Any, *, sender: Agent | None, recipient: Agent) -> Any | type[DropMessage]: # type: ignore
37+
if sender is None:
38+
print(f"{self.send_color}Sending message to {recipient.name}:{self.reset_color} {message}")
39+
else:
40+
print(
41+
f"{self.send_color}Sending message from {sender.name} to {recipient.name}:{self.reset_color} {message}"
42+
)
43+
return message
44+
45+
@override
46+
async def on_response(self, message: Any, *, sender: Agent, recipient: Agent | None) -> Any | type[DropMessage]: # type: ignore
47+
if recipient is None:
48+
print(f"{self.response_color}Received response from {sender.name}:{self.reset_color} {message}")
49+
else:
50+
print(
51+
f"{self.response_color}Received response from {sender.name} to {recipient.name}:{self.reset_color} {message}"
52+
)
53+
return message
54+
55+
56+
def software_development(runtime: AgentRuntime) -> OrchestratorChat: # type: ignore
57+
developer = ChatCompletionAgent(
58+
name="Developer",
59+
description="A developer that writes code.",
60+
runtime=runtime,
61+
system_messages=[SystemMessage("You are a Python developer.")],
62+
model_client=OpenAI(model="gpt-4-turbo"),
63+
)
64+
65+
tester_oai_assistant = openai.beta.assistants.create(
66+
model="gpt-4-turbo",
67+
description="A software tester that runs test cases and reports results.",
68+
instructions="You are a software tester that runs test cases and reports results.",
69+
)
70+
tester_oai_thread = openai.beta.threads.create()
71+
tester = OpenAIAssistantAgent(
72+
name="Tester",
73+
description="A software tester that runs test cases and reports results.",
74+
runtime=runtime,
75+
client=openai.AsyncClient(),
76+
assistant_id=tester_oai_assistant.id,
77+
thread_id=tester_oai_thread.id,
78+
)
79+
80+
def search(query: Annotated[str, "The search query."]) -> Annotated[str, "The search results."]:
81+
"""Search the web."""
82+
client = TavilyClient(api_key=os.getenv("TAVILY_API_KEY"))
83+
result = client.search(query) # type: ignore
84+
if result:
85+
return json.dumps(result, indent=2, ensure_ascii=False) # type: ignore
86+
return "No results found."
87+
88+
function_executor = InProcessFunctionExecutor(functions=[search])
89+
90+
product_manager = ChatCompletionAgent(
91+
name="ProductManager",
92+
description="A product manager that performs research and comes up with specs.",
93+
runtime=runtime,
94+
system_messages=[
95+
SystemMessage("You are a product manager good at translating customer needs into software specifications."),
96+
SystemMessage("You can use the search tool to find information on the web."),
97+
],
98+
model_client=OpenAI(model="gpt-4-turbo"),
99+
function_executor=function_executor,
100+
)
101+
102+
planner = ChatCompletionAgent(
103+
name="Planner",
104+
description="A planner that organizes and schedules tasks.",
105+
runtime=runtime,
106+
system_messages=[SystemMessage("You are a planner of complex tasks.")],
107+
model_client=OpenAI(model="gpt-4-turbo"),
108+
)
109+
110+
orchestrator = ChatCompletionAgent(
111+
name="Orchestrator",
112+
description="An orchestrator that coordinates the team.",
113+
runtime=runtime,
114+
system_messages=[
115+
SystemMessage("You are an orchestrator that coordinates the team to complete a complex task.")
116+
],
117+
model_client=OpenAI(model="gpt-4-turbo"),
118+
)
119+
120+
return OrchestratorChat(
121+
"OrchestratorChat",
122+
"A software development team.",
123+
runtime,
124+
orchestrator=orchestrator,
125+
planner=planner,
126+
specialists=[developer, product_manager, tester],
127+
)
128+
129+
130+
async def run(message: str, user: str, scenario: Callable[[AgentRuntime], OrchestratorChat]) -> None: # type: ignore
131+
runtime = SingleThreadedAgentRuntime(before_send=LoggingHandler())
132+
chat = scenario(runtime)
133+
response = runtime.send_message(TextMessage(content=message, source=user), chat)
134+
while not response.done():
135+
await runtime.process_next()
136+
print((await response).content) # type: ignore
137+
138+
139+
if __name__ == "__main__":
140+
parser = argparse.ArgumentParser(description="Run a orchestrator demo.")
141+
choices = {"software_development": software_development}
142+
parser.add_argument(
143+
"--scenario",
144+
choices=list(choices.keys()),
145+
help="The scenario to demo.",
146+
default="software_development",
147+
)
148+
parser.add_argument(
149+
"--user",
150+
default="Customer",
151+
help="The user to send the message. Default is 'Customer'.",
152+
)
153+
parser.add_argument("--message", help="The message to send.", required=True)
154+
args = parser.parse_args()
155+
asyncio.run(run(args.message, args.user, choices[args.scenario]))

0 commit comments

Comments
 (0)