Skip to content

Commit 03be75e

Browse files
authored
feat(deployment): add example for deploying a routing agent as a chat endpoint (#911)
1 parent b840745 commit 03be75e

File tree

9 files changed

+468
-2
lines changed

9 files changed

+468
-2
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,7 @@ as_studio
391391
- [Session with SQLite](https://github.com/agentscope-ai/agentscope/tree/main/examples/functionality/session_with_sqlite)
392392
- [Stream Printing Messages](https://github.com/agentscope-ai/agentscope/tree/main/examples/functionality/stream_printing_messages)
393393
- [TTS](https://github.com/agentscope-ai/agentscope/tree/main/examples/functionality/tts)
394+
- [High-code Deployment](https://github.com/agentscope-ai/agentscope/tree/main/examples/deployment/planning_agent)
394395
- Agent
395396
- [ReAct Agent](https://github.com/agentscope-ai/agentscope/tree/main/examples/agent/react_agent)
396397
- [Voice Agent](https://github.com/agentscope-ai/agentscope/tree/main/examples/agent/voice_agent)

README_zh.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,7 @@ as_studio
392392
- [基于DB的会话管理](https://github.com/agentscope-ai/agentscope/tree/main/examples/functionality/session_with_sqlite)
393393
- [流式获取打印消息](https://github.com/agentscope-ai/agentscope/tree/main/examples/functionality/stream_printing_messages)
394394
- [TTS](https://github.com/agentscope-ai/agentscope/tree/main/examples/functionality/tts)
395+
- [高代码部署](https://github.com/agentscope-ai/agentscope/tree/main/examples/deployment/planning_agent)
395396
- 智能体
396397
- [ReAct 智能体](https://github.com/agentscope-ai/agentscope/tree/main/examples/agent/react_agent)
397398
- [语音智能体](https://github.com/agentscope-ai/agentscope/tree/main/examples/agent/voice_agent)

examples/deployment/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
This dir contains examples of deploying multi-agent systems with different strategies using AgentScope.
2+
3+
| Example | Multi-agent System | Frontend Displaying |
4+
| --- | --- | --- |
5+
| [planning_agent](./planning_agent) | A planning agent that can create worker agents to handle sub-tasks. | Only exposing the main planner agent |
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# High-code Deployment of a Routing Agent
2+
3+
This example demonstrates how to deploy a multi-agent system using AgentScope. The system is composed of a main
4+
routing agent equipped with a tool function named `create_worker` to dispatch tasks to specialized worker agents.
5+
6+
Specifically, the routing agent is deployed as a chat endpoint in server hold by the `Quart` library.
7+
Once receiving an input request, we
8+
- set up a routing agent
9+
- load the session state if any
10+
- invoke the routing agent to handle the request, and return the streaming response
11+
- save the session state
12+
13+
14+
# Example Structure
15+
16+
```
17+
planning_agent/
18+
├── main.py # Entry point to start the Quart server with routing agent
19+
├── tool.py # Tool function to create worker agents
20+
└── test_post.py # Preset test script to send requests to the server
21+
```
22+
23+
24+
## Note
25+
26+
1. The printing messages from sub-agent/worker agents is converted to the streaming response of the tool
27+
function `create_worker`, meaning the sub-agent won't be exposing to the user directly.
28+
29+
2. The sub-agent in `tool.py` is equipped with the following tools. For GitHub and AMap tools, they will be activated only
30+
if the corresponding environment variables are set.
31+
You can customize the toolset by modifying the `tool.py` file.
32+
33+
| Tool | Description | Required Environment Variable |
34+
|-----------------------|-----------------------------------------------------|-------------------------------|
35+
| write/view text files | Read and write text files | - |
36+
| Playwright MCP server | Automate browser actions using Microsoft Playwright | - |
37+
| GitHub MCP server | Access GitHub repositories and data | GITHUB_TOKEN |
38+
| AMap MCP server | Access AMap services for location-based tasks | GAODE_API_KEY |
39+
40+
41+
3. Optionally, you can also expose the sub-agent's response to the user by modifying the `tool.py` file.
42+
43+
## Quick Start
44+
45+
Install the latest agentscope and Quart packages:
46+
47+
```bash
48+
pip install agentscope quart
49+
```
50+
51+
Ensure you have set `DASHSCOPE_API_KEY` in your environment for DashScope LLM API, or change the used model in
52+
both `main.py` and `tool.py` (Remember to change the formatter correspondingly).
53+
54+
Set the environment variables for GitHub and AMap tools if needed.
55+
56+
Run the Quart server:
57+
58+
```bash
59+
python main.py
60+
```
61+
62+
In another terminal, run the test script to send a request to the server:
63+
64+
```bash
65+
python test_post.py
66+
```
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
# -*- coding: utf-8 -*-
2+
"""The server that holds agent service."""
3+
import json
4+
import os
5+
from typing import AsyncGenerator
6+
7+
from quart import Quart, Response, request
8+
9+
from tool import create_worker
10+
11+
from agentscope.pipeline import stream_printing_messages
12+
from agentscope.session import JSONSession
13+
from agentscope.agent import ReActAgent
14+
from agentscope.formatter import DashScopeChatFormatter
15+
from agentscope.message import Msg
16+
from agentscope.model import DashScopeChatModel
17+
from agentscope.tool import Toolkit
18+
19+
app = Quart(__name__)
20+
21+
22+
async def handle_input(
23+
msg: Msg,
24+
user_id: str,
25+
session_id: str,
26+
) -> AsyncGenerator[str, None]:
27+
"""Handle the input message and yield response chunks.
28+
29+
Args:
30+
msg (`Msg`):
31+
The input message from the user.
32+
user_id (`str`):
33+
The user ID.
34+
session_id (`str`):
35+
The session ID.
36+
37+
Yields:
38+
`str`:
39+
A response message in dict format by `Msg().to_dict()`.
40+
"""
41+
toolkit = Toolkit()
42+
toolkit.register_tool_function(
43+
create_worker,
44+
)
45+
46+
# Init JSONSession to save and load the state
47+
session = JSONSession(save_dir="./sessions")
48+
49+
agent = ReActAgent(
50+
name="Friday",
51+
# pylint: disable=line-too-long
52+
sys_prompt="""You are Friday, a multifunctional agent that can help people solving different complex tasks. You act like a meta planner to solve complicated tasks by decomposing the task and building/orchestrating different worker agents to finish the sub-tasks.
53+
54+
## Core Mission
55+
Your primary purpose is to break down complicated tasks into manageable subtasks (a plan), create worker agents to finish the subtask, and coordinate their execution to achieve the user's goal efficiently.
56+
57+
### Important Constraints
58+
1. DO NOT TRY TO SOLVE THE SUBTASKS DIRECTLY yourself.
59+
2. Always follow the plan sequence.
60+
3. DO NOT finish the plan until all subtasks are finished.
61+
""", # noqa: E501
62+
model=DashScopeChatModel(
63+
model_name="qwen3-max",
64+
api_key=os.environ.get("DASHSCOPE_API_KEY"),
65+
),
66+
formatter=DashScopeChatFormatter(),
67+
toolkit=toolkit,
68+
)
69+
70+
# Load the session state if exists
71+
await session.load_session_state(
72+
session_id=f"{user_id}-{session_id}",
73+
agent=agent,
74+
)
75+
76+
async for msg, _ in stream_printing_messages(
77+
agents=[agent],
78+
coroutine_task=agent(msg),
79+
):
80+
# Transform the message into a dict string and yield it
81+
data = json.dumps(msg.to_dict(), ensure_ascii=False)
82+
yield f"data: {data}\n\n"
83+
84+
# Save the session state
85+
await session.save_session_state(
86+
session_id=f"{user_id}-{session_id}",
87+
agent=agent,
88+
)
89+
90+
91+
@app.route("/chat_endpoint", methods=["POST"])
92+
async def chat_endpoint() -> Response:
93+
"""A simple chat endpoint that streams responses."""
94+
# Parse the user_id, session_id and user message from the request body
95+
data = await request.get_json()
96+
97+
user_id = data.get("user_id")
98+
session_id = data.get("session_id")
99+
100+
# We use textual input here, you can extend it to support other types
101+
user_input = data.get("user_input")
102+
103+
# If the user_id or session_id is missing, return 400
104+
if not user_id or not session_id:
105+
return Response(
106+
f"user_id and session_id are required, got user_id: {user_id}, "
107+
f"session_id: {session_id}",
108+
status=400,
109+
)
110+
111+
return Response(
112+
handle_input(
113+
Msg(
114+
"user",
115+
user_input,
116+
"user",
117+
),
118+
user_id,
119+
session_id,
120+
),
121+
mimetype="text/event-stream",
122+
)
123+
124+
125+
if __name__ == "__main__":
126+
app.run(
127+
port=5000,
128+
debug=True,
129+
)
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# -*- coding: utf-8 -*-
2+
"""Send the post request to get the response from the agent"""
3+
4+
import requests
5+
6+
7+
def send_post(user_query: str) -> None:
8+
"""Send the post request to the agent endpoint and print the response."""
9+
res = requests.post(
10+
url="http://127.0.0.1:5000/chat_endpoint",
11+
json={
12+
"user_id": "test_user",
13+
"session_id": "test_session",
14+
"user_input": user_query,
15+
},
16+
stream=True,
17+
)
18+
19+
res.raise_for_status()
20+
21+
for chunk in res.iter_content(chunk_size=None):
22+
if chunk:
23+
print(repr(chunk.decode("utf-8")))
24+
25+
26+
print("The first request response:")
27+
# We first tell who we are in the first request
28+
send_post("Hi, Alice!")
29+
30+
print("\n\nThe second request response:")
31+
# Test if the session is loaded correctly
32+
send_post("Do you know my name?")
33+
34+
print("\n\nThe third request response:")
35+
send_post("Help me to write a hello world in Python")

0 commit comments

Comments
 (0)