Skip to content

Commit 641d83f

Browse files
authored
add illustrator-critics demo; handle multi-modal messages in agents; refactored examples to use Textual app; equality comparison for AgentId (#87)
* add illustrator-critics demo; handle multi-modal messages in agents * fix * Refactored examples to use Textual app. Add equality comparison for AgentId
1 parent edb939f commit 641d83f

14 files changed

+423
-266
lines changed

examples/chat_room.py

+47-61
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
"""This is an example of a chat room with AI agents. It demonstrates how to use the
2-
`TypeRoutedAgent` class to create custom agents that can use custom message types,
3-
and interact with other using event-based messaging without an orchestrator."""
4-
51
import argparse
62
import asyncio
73
import json
8-
from dataclasses import dataclass
4+
import logging
5+
import os
6+
import sys
7+
8+
sys.path.append(os.path.abspath(os.path.dirname(__file__)))
99

1010
from agnext.application import SingleThreadedAgentRuntime
1111
from agnext.chat.memory import BufferedChatMemory, ChatMemory
@@ -14,18 +14,7 @@
1414
from agnext.components import TypeRoutedAgent, message_handler
1515
from agnext.components.models import ChatCompletionClient, OpenAI, SystemMessage
1616
from agnext.core import AgentRuntime, CancellationToken
17-
from colorama import Fore, Style, init
18-
19-
20-
# Define a custom message type for chat room messages.
21-
@dataclass
22-
class ChatRoomMessage(TextMessage): # type: ignore
23-
pass
24-
25-
26-
sep = "-" * 50
27-
28-
init(autoreset=True)
17+
from utils import TextualChatApp, TextualUserAgent, start_runtime
2918

3019

3120
# Define a custom agent that can handle chat room messages.
@@ -38,7 +27,6 @@ def __init__( # type: ignore
3827
background_story: str,
3928
memory: ChatMemory, # type: ignore
4029
model_client: ChatCompletionClient, # type: ignore
41-
color: str = Style.RESET_ALL,
4230
) -> None: # type: ignore
4331
super().__init__(name, description, runtime)
4432
system_prompt = f"""Your name is {name}.
@@ -58,10 +46,9 @@ def __init__( # type: ignore
5846
self._system_messages = [SystemMessage(system_prompt)]
5947
self._memory = memory
6048
self._client = model_client
61-
self._color = color
6249

6350
@message_handler() # type: ignore
64-
async def on_chat_room_message(self, message: ChatRoomMessage, cancellation_token: CancellationToken) -> None: # type: ignore
51+
async def on_chat_room_message(self, message: TextMessage, cancellation_token: CancellationToken) -> None: # type: ignore
6552
# Save the message to memory as structured JSON.
6653
from_message = TextMessage(
6754
content=json.dumps({"sender": message.source, "content": message.content}), source=message.source
@@ -77,7 +64,7 @@ async def on_chat_room_message(self, message: ChatRoomMessage, cancellation_toke
7764
assert isinstance(raw_response.content, str)
7865

7966
# Save the response to memory.
80-
await self._memory.add_message(ChatRoomMessage(source=self.metadata["name"], content=raw_response.content))
67+
await self._memory.add_message(TextMessage(source=self.metadata["name"], content=raw_response.content))
8168

8269
# Parse the response.
8370
data = json.loads(raw_response.content)
@@ -86,76 +73,75 @@ async def on_chat_room_message(self, message: ChatRoomMessage, cancellation_toke
8673

8774
# Publish the response if needed.
8875
if respond is True or str(respond).lower().strip() == "true":
89-
await self._publish_message(ChatRoomMessage(source=self.metadata["name"], content=str(response)))
90-
print(f"{sep}\n{self._color}{self.metadata['name']}:{Style.RESET_ALL}\n{response}")
76+
await self._publish_message(TextMessage(source=self.metadata["name"], content=str(response)))
77+
78+
79+
class ChatRoomUserAgent(TextualUserAgent): # type: ignore
80+
"""An agent that is used to receive messages from the runtime."""
81+
82+
@message_handler # type: ignore
83+
async def on_chat_room_message(self, message: TextMessage, cancellation_token: CancellationToken) -> None: # type: ignore
84+
await self._app.post_runtime_message(message)
9185

9286

9387
# Define a chat room with participants -- the runtime is the chat room.
94-
def chat_room(runtime: AgentRuntime) -> None: # type: ignore
95-
_ = ChatRoomAgent(
88+
def chat_room(runtime: AgentRuntime, app: TextualChatApp) -> None: # type: ignore
89+
_ = ChatRoomUserAgent(
90+
name="User",
91+
description="The user in the chat room.",
92+
runtime=runtime,
93+
app=app,
94+
)
95+
alice = ChatRoomAgent(
9696
name="Alice",
9797
description="Alice in the chat room.",
9898
runtime=runtime,
9999
background_story="Alice is a software engineer who loves to code.",
100100
memory=BufferedChatMemory(buffer_size=10),
101101
model_client=OpenAI(model="gpt-4-turbo"), # type: ignore
102-
color=Fore.CYAN,
103102
)
104-
_ = ChatRoomAgent(
103+
bob = ChatRoomAgent(
105104
name="Bob",
106105
description="Bob in the chat room.",
107106
runtime=runtime,
108107
background_story="Bob is a data scientist who loves to analyze data.",
109108
memory=BufferedChatMemory(buffer_size=10),
110109
model_client=OpenAI(model="gpt-4-turbo"), # type: ignore
111-
color=Fore.GREEN,
112110
)
113-
_ = ChatRoomAgent(
111+
charlie = ChatRoomAgent(
114112
name="Charlie",
115113
description="Charlie in the chat room.",
116114
runtime=runtime,
117115
background_story="Charlie is a designer who loves to create art.",
118116
memory=BufferedChatMemory(buffer_size=10),
119117
model_client=OpenAI(model="gpt-4-turbo"), # type: ignore
120-
color=Fore.MAGENTA,
121118
)
119+
app.welcoming_notice = f"""Welcome to the chat room demo with the following participants:
120+
1. 👧 {alice.metadata['name']}: {alice.metadata['description']}
121+
2. 👱🏼‍♂️ {bob.metadata['name']}: {bob.metadata['description']}
122+
3. 👨🏾‍🦳 {charlie.metadata['name']}: {charlie.metadata['description']}
122123
124+
Each participant decides on its own whether to respond to the latest message.
123125
124-
async def get_user_input(prompt: str) -> str:
125-
loop = asyncio.get_event_loop()
126-
return await loop.run_in_executor(None, input, prompt)
126+
You can greet the chat room by typing your first message below.
127+
"""
127128

128129

129-
async def main(user_name: str, wait_seconds: int) -> None:
130+
async def main() -> None:
130131
runtime = SingleThreadedAgentRuntime()
131-
chat_room(runtime)
132-
while True:
133-
# TODO: allow user to input at any time while runtime is running.
134-
# Get user input and send messages to the chat room.
135-
# TODO: use Textual to build the UI.
136-
user_input = await get_user_input(f"{sep}\nYou:\n")
137-
if user_input.strip():
138-
# Publish user message if it is not empty.
139-
await runtime.publish_message(ChatRoomMessage(source=user_name, content=user_input))
140-
# Wait for agents to respond.
141-
while runtime.unprocessed_messages:
142-
await runtime.process_next()
143-
await asyncio.sleep(wait_seconds)
132+
app = TextualChatApp(runtime, user_name="You")
133+
chat_room(runtime, app)
134+
asyncio.create_task(start_runtime(runtime))
135+
await app.run_async()
144136

145137

146138
if __name__ == "__main__":
147-
parser = argparse.ArgumentParser(description="Run a chat room simulation.")
148-
parser.add_argument(
149-
"--user-name",
150-
type=str,
151-
default="Host",
152-
help="The name of the user who is participating in the chat room.",
153-
)
154-
parser.add_argument(
155-
"--wait-seconds",
156-
type=int,
157-
default=5,
158-
help="The number of seconds to wait between processing messages.",
159-
)
139+
parser = argparse.ArgumentParser(description="Chat room demo with self-driving AI agents.")
140+
parser.add_argument("--verbose", action="store_true", help="Enable verbose logging.")
160141
args = parser.parse_args()
161-
asyncio.run(main(args.user_name, args.wait_seconds))
142+
if args.verbose:
143+
logging.basicConfig(level=logging.WARNING)
144+
logging.getLogger("agnext").setLevel(logging.DEBUG)
145+
handler = logging.FileHandler("chat_room.log")
146+
logging.getLogger("agnext").addHandler(handler)
147+
asyncio.run(main())

examples/coder_reviewer.py

+43-18
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,28 @@
1+
import argparse
12
import asyncio
3+
import logging
4+
import logging.handlers
5+
import os
6+
import sys
7+
8+
sys.path.append(os.path.abspath(os.path.dirname(__file__)))
29

310
from agnext.application import SingleThreadedAgentRuntime
411
from agnext.chat.agents import ChatCompletionAgent
512
from agnext.chat.memory import BufferedChatMemory
613
from agnext.chat.patterns import GroupChatManager
7-
from agnext.chat.types import TextMessage
814
from agnext.components.models import OpenAI, SystemMessage
915
from agnext.core import AgentRuntime
16+
from utils import TextualChatApp, TextualUserAgent, start_runtime
1017

1118

12-
def coder_reviewer(runtime: AgentRuntime) -> None:
19+
def coder_reviewer(runtime: AgentRuntime, app: TextualChatApp) -> None:
20+
_ = TextualUserAgent(
21+
name="Human",
22+
description="A human user that provides a problem statement.",
23+
runtime=runtime,
24+
app=app,
25+
)
1326
coder = ChatCompletionAgent(
1427
name="Coder",
1528
description="An agent that writes code",
@@ -30,8 +43,13 @@ def coder_reviewer(runtime: AgentRuntime) -> None:
3043
system_messages=[
3144
SystemMessage(
3245
"You are a code reviewer. You focus on correctness, efficiency and safety of the code.\n"
33-
"Provide reviews only.\n"
34-
"Output only 'APPROVE' to approve the code and end the conversation."
46+
"Respond using the following format:\n"
47+
"Code Review:\n"
48+
"Correctness: <Your comments>\n"
49+
"Efficiency: <Your comments>\n"
50+
"Safety: <Your comments>\n"
51+
"Approval: <APPROVE or REVISE>\n"
52+
"Suggested Changes: <Your comments>"
3553
)
3654
],
3755
model_client=OpenAI(model="gpt-4-turbo"),
@@ -44,24 +62,31 @@ def coder_reviewer(runtime: AgentRuntime) -> None:
4462
participants=[coder.id, reviewer.id], # The order of the participants indicates the order of speaking.
4563
memory=BufferedChatMemory(buffer_size=10),
4664
termination_word="APPROVE",
47-
on_message_received=lambda message: print(f"{'-'*80}\n{message.source}: {message.content}"),
4865
)
66+
app.welcoming_notice = f"""Welcome to the coder-reviewer demo with the following roles:
67+
1. 🤖 {coder.metadata['name']}: {coder.metadata['description']}
68+
2. 🧐 {reviewer.metadata['name']}: {reviewer.metadata['description']}
69+
The coder will write code to solve a problem, and the reviewer will review the code.
70+
The conversation will end when the reviewer approves the code.
71+
Let's get started by providing a problem statement.
72+
"""
4973

5074

5175
async def main() -> None:
5276
runtime = SingleThreadedAgentRuntime()
53-
coder_reviewer(runtime)
54-
runtime.publish_message(
55-
TextMessage(
56-
content="Write a Python script that find near-duplicate paragraphs in a directory of many text files. "
57-
"Output the file names, line numbers and the similarity score of the near-duplicate paragraphs. ",
58-
source="Human",
59-
)
60-
)
61-
# Start the runtime.
62-
while True:
63-
await runtime.process_next()
64-
await asyncio.sleep(1)
77+
app = TextualChatApp(runtime, user_name="You")
78+
coder_reviewer(runtime, app)
79+
asyncio.create_task(start_runtime(runtime))
80+
await app.run_async()
6581

6682

67-
asyncio.run(main())
83+
if __name__ == "__main__":
84+
parser = argparse.ArgumentParser(description="Coder-reviewer pattern for code writing and review.")
85+
parser.add_argument("--verbose", action="store_true", help="Enable verbose logging.")
86+
args = parser.parse_args()
87+
if args.verbose:
88+
logging.basicConfig(level=logging.WARNING)
89+
logging.getLogger("agnext").setLevel(logging.DEBUG)
90+
handler = logging.FileHandler("coder_reviewer.log")
91+
logging.getLogger("agnext").addHandler(handler)
92+
asyncio.run(main())

examples/illustrator_critics.py

+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import argparse
2+
import asyncio
3+
import logging
4+
import os
5+
import sys
6+
7+
sys.path.append(os.path.abspath(os.path.dirname(__file__)))
8+
9+
import openai
10+
from agnext.application import SingleThreadedAgentRuntime
11+
from agnext.chat.agents import ChatCompletionAgent, ImageGenerationAgent
12+
from agnext.chat.memory import BufferedChatMemory
13+
from agnext.chat.patterns.group_chat_manager import GroupChatManager
14+
from agnext.components.models import OpenAI, SystemMessage
15+
from agnext.core import AgentRuntime
16+
from utils import TextualChatApp, TextualUserAgent, start_runtime
17+
18+
19+
def illustrator_critics(runtime: AgentRuntime, app: TextualChatApp) -> str: # type: ignore
20+
_ = TextualUserAgent(
21+
name="User",
22+
description="A user looking for illustration.",
23+
runtime=runtime,
24+
app=app,
25+
)
26+
descriptor = ChatCompletionAgent(
27+
name="Descriptor",
28+
description="An AI agent that provides a description of the image.",
29+
runtime=runtime,
30+
system_messages=[
31+
SystemMessage(
32+
"You create short description for image. \n"
33+
"In this conversation, you will be given either: \n"
34+
"1. Request for new image. \n"
35+
"2. Feedback on some image created. \n"
36+
"In both cases, you will provide a description of a new image to be created. \n"
37+
"Only provide the description of the new image and nothing else. \n"
38+
"Be succinct and precise."
39+
),
40+
],
41+
memory=BufferedChatMemory(buffer_size=10),
42+
model_client=OpenAI(model="gpt-4-turbo", max_tokens=500),
43+
)
44+
illustrator = ImageGenerationAgent(
45+
name="Illustrator",
46+
description="An AI agent that generates images.",
47+
runtime=runtime,
48+
client=openai.AsyncOpenAI(),
49+
model="dall-e-3",
50+
memory=BufferedChatMemory(buffer_size=1),
51+
)
52+
critic = ChatCompletionAgent(
53+
name="Critic",
54+
description="An AI agent that provides feedback on images given user's requirements.",
55+
runtime=runtime,
56+
system_messages=[
57+
SystemMessage(
58+
"You are an expert in image understanding. \n"
59+
"In this conversation, you will judge an image given the description and provide feedback. \n"
60+
"Pay attention to the details like the spelling of words and number of objects. \n"
61+
"Use the following format in your response: \n"
62+
"Number of each object type in the image: <Type 1 (e.g., Husky Dog)>: 1, <Type 2>: 2, ...\n"
63+
"Feedback: <Your feedback here> \n"
64+
"Approval: <APPROVE or REVISE> \n"
65+
),
66+
],
67+
memory=BufferedChatMemory(buffer_size=2),
68+
model_client=OpenAI(model="gpt-4-turbo"),
69+
)
70+
_ = GroupChatManager(
71+
name="GroupChatManager",
72+
description="A chat manager that handles group chat.",
73+
runtime=runtime,
74+
memory=BufferedChatMemory(buffer_size=5),
75+
participants=[illustrator.id, critic.id, descriptor.id],
76+
termination_word="APPROVE",
77+
)
78+
79+
app.welcoming_notice = f"""You are now in a group chat with the following agents:
80+
81+
1. 🤖 {descriptor.metadata['name']}: {descriptor.metadata.get('description')}
82+
2. 🤖 {illustrator.metadata['name']}: {illustrator.metadata.get('description')}
83+
3. 🤖 {critic.metadata['name']}: {critic.metadata.get('description')}
84+
85+
Provide a prompt for the illustrator to generate an image.
86+
"""
87+
88+
89+
async def main() -> None:
90+
runtime = SingleThreadedAgentRuntime()
91+
app = TextualChatApp(runtime, user_name="You")
92+
illustrator_critics(runtime, app)
93+
asyncio.create_task(start_runtime(runtime))
94+
await app.run_async()
95+
96+
97+
if __name__ == "__main__":
98+
parser = argparse.ArgumentParser(description="Illustrator-critics pattern for image generation demo.")
99+
parser.add_argument("--verbose", action="store_true", help="Enable verbose logging.")
100+
args = parser.parse_args()
101+
if args.verbose:
102+
logging.basicConfig(level=logging.WARNING)
103+
logging.getLogger("agnext").setLevel(logging.DEBUG)
104+
handler = logging.FileHandler("illustrator_critics.log")
105+
logging.getLogger("agnext").addHandler(handler)
106+
asyncio.run(main())

0 commit comments

Comments
 (0)