chore: initialize sandbox and overwrite remote content
Some checks failed
Pre-commit / run (ubuntu-latest) (push) Has been cancelled
Deploy Sphinx documentation to Pages / build_en (ubuntu-latest, 3.10) (push) Has been cancelled
Deploy Sphinx documentation to Pages / build_zh (ubuntu-latest, 3.10) (push) Has been cancelled
Python Unittest Coverage / test (macos-15, 3.10) (push) Has been cancelled
Python Unittest Coverage / test (macos-15, 3.11) (push) Has been cancelled
Python Unittest Coverage / test (macos-15, 3.12) (push) Has been cancelled
Python Unittest Coverage / test (ubuntu-latest, 3.10) (push) Has been cancelled
Python Unittest Coverage / test (ubuntu-latest, 3.11) (push) Has been cancelled
Python Unittest Coverage / test (ubuntu-latest, 3.12) (push) Has been cancelled
Python Unittest Coverage / test (windows-latest, 3.10) (push) Has been cancelled
Python Unittest Coverage / test (windows-latest, 3.11) (push) Has been cancelled
Python Unittest Coverage / test (windows-latest, 3.12) (push) Has been cancelled

This commit is contained in:
codex-bot
2026-03-02 22:32:27 +08:00
commit a64378956a
584 changed files with 93604 additions and 0 deletions

View File

@@ -0,0 +1,66 @@
# High-code Deployment of a Routing Agent
This example demonstrates how to deploy a multi-agent system using AgentScope. The system is composed of a main
routing agent equipped with a tool function named `create_worker` to dispatch tasks to specialized worker agents.
Specifically, the routing agent is deployed as a chat endpoint in server hold by the `Quart` library.
Once receiving an input request, we
- set up a routing agent
- load the session state if any
- invoke the routing agent to handle the request, and return the streaming response
- save the session state
# Example Structure
```
planning_agent/
├── main.py # Entry point to start the Quart server with routing agent
├── tool.py # Tool function to create worker agents
└── test_post.py # Preset test script to send requests to the server
```
## Note
1. The printing messages from sub-agent/worker agents is converted to the streaming response of the tool
function `create_worker`, meaning the sub-agent won't be exposing to the user directly.
2. The sub-agent in `tool.py` is equipped with the following tools. For GitHub and AMap tools, they will be activated only
if the corresponding environment variables are set.
You can customize the toolset by modifying the `tool.py` file.
| Tool | Description | Required Environment Variable |
|-----------------------|-----------------------------------------------------|-------------------------------|
| write/view text files | Read and write text files | - |
| Playwright MCP server | Automate browser actions using Microsoft Playwright | - |
| GitHub MCP server | Access GitHub repositories and data | GITHUB_TOKEN |
| AMap MCP server | Access AMap services for location-based tasks | GAODE_API_KEY |
3. Optionally, you can also expose the sub-agent's response to the user by modifying the `tool.py` file.
## Quick Start
Install the latest agentscope and Quart packages:
```bash
pip install agentscope quart
```
Ensure you have set `DASHSCOPE_API_KEY` in your environment for DashScope LLM API, or change the used model in
both `main.py` and `tool.py` (Remember to change the formatter correspondingly).
Set the environment variables for GitHub and AMap tools if needed.
Run the Quart server:
```bash
python main.py
```
In another terminal, run the test script to send a request to the server:
```bash
python test_post.py
```

View File

@@ -0,0 +1,129 @@
# -*- coding: utf-8 -*-
"""The server that holds agent service."""
import json
import os
from typing import AsyncGenerator
from quart import Quart, Response, request
from tool import create_worker
from agentscope.pipeline import stream_printing_messages
from agentscope.session import JSONSession
from agentscope.agent import ReActAgent
from agentscope.formatter import DashScopeChatFormatter
from agentscope.message import Msg
from agentscope.model import DashScopeChatModel
from agentscope.tool import Toolkit
app = Quart(__name__)
async def handle_input(
msg: Msg,
user_id: str,
session_id: str,
) -> AsyncGenerator[str, None]:
"""Handle the input message and yield response chunks.
Args:
msg (`Msg`):
The input message from the user.
user_id (`str`):
The user ID.
session_id (`str`):
The session ID.
Yields:
`str`:
A response message in dict format by `Msg().to_dict()`.
"""
toolkit = Toolkit()
toolkit.register_tool_function(
create_worker,
)
# Init JSONSession to save and load the state
session = JSONSession(save_dir="./sessions")
agent = ReActAgent(
name="Friday",
# pylint: disable=line-too-long
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.
## Core Mission
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.
### Important Constraints
1. DO NOT TRY TO SOLVE THE SUBTASKS DIRECTLY yourself.
2. Always follow the plan sequence.
3. DO NOT finish the plan until all subtasks are finished.
""", # noqa: E501
model=DashScopeChatModel(
model_name="qwen3-max",
api_key=os.environ.get("DASHSCOPE_API_KEY"),
),
formatter=DashScopeChatFormatter(),
toolkit=toolkit,
)
# Load the session state if exists
await session.load_session_state(
session_id=f"{user_id}-{session_id}",
agent=agent,
)
async for msg, _ in stream_printing_messages(
agents=[agent],
coroutine_task=agent(msg),
):
# Transform the message into a dict string and yield it
data = json.dumps(msg.to_dict(), ensure_ascii=False)
yield f"data: {data}\n\n"
# Save the session state
await session.save_session_state(
session_id=f"{user_id}-{session_id}",
agent=agent,
)
@app.route("/chat_endpoint", methods=["POST"])
async def chat_endpoint() -> Response:
"""A simple chat endpoint that streams responses."""
# Parse the user_id, session_id and user message from the request body
data = await request.get_json()
user_id = data.get("user_id")
session_id = data.get("session_id")
# We use textual input here, you can extend it to support other types
user_input = data.get("user_input")
# If the user_id or session_id is missing, return 400
if not user_id or not session_id:
return Response(
f"user_id and session_id are required, got user_id: {user_id}, "
f"session_id: {session_id}",
status=400,
)
return Response(
handle_input(
Msg(
"user",
user_input,
"user",
),
user_id,
session_id,
),
mimetype="text/event-stream",
)
if __name__ == "__main__":
app.run(
port=5000,
debug=True,
)

View File

@@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
"""Send the post request to get the response from the agent"""
import requests
def send_post(user_query: str) -> None:
"""Send the post request to the agent endpoint and print the response."""
res = requests.post(
url="http://127.0.0.1:5000/chat_endpoint",
json={
"user_id": "test_user",
"session_id": "test_session",
"user_input": user_query,
},
stream=True,
)
res.raise_for_status()
for chunk in res.iter_content(chunk_size=None):
if chunk:
print(repr(chunk.decode("utf-8")))
print("The first request response:")
# We first tell who we are in the first request
send_post("Hi, Alice!")
print("\n\nThe second request response:")
# Test if the session is loaded correctly
send_post("Do you know my name?")
print("\n\nThe third request response:")
send_post("Help me to write a hello world in Python")

View File

@@ -0,0 +1,226 @@
# -*- coding: utf-8 -*-
"""The tool functions used in the planner example."""
import asyncio
import json
import os
from collections import OrderedDict
from typing import AsyncGenerator
from pydantic import BaseModel, Field
from agentscope.agent import ReActAgent
from agentscope.formatter import DashScopeChatFormatter
from agentscope.mcp import HttpStatelessClient, StdIOStatefulClient
from agentscope.message import Msg, TextBlock
from agentscope.model import DashScopeChatModel
from agentscope.pipeline import stream_printing_messages
from agentscope.tool import (
ToolResponse,
Toolkit,
write_text_file,
insert_text_file,
view_text_file,
)
class ResultModel(BaseModel):
"""
The result model used for the sub worker to summarize the task result.
"""
success: bool = Field(
description="Whether the task was successful or not.",
)
message: str = Field(
description=(
"The specific task result, should include necessary details, "
"e.g. the file path if any file is generated, the deviation, "
"and the error message if any."
),
)
def _convert_to_text_block(msgs: list[Msg]) -> list[TextBlock]:
# Collect all the content blocks
blocks: list = []
# Convert tool_use block into text block for streaming tool response
for _ in msgs:
for block in _.get_content_blocks():
if block["type"] == "text":
blocks.append(block)
elif block["type"] == "tool_use":
blocks.append(
TextBlock(
type="text",
text=f"Calling tool {block['name']} ...",
),
)
return blocks
async def create_worker(
task_description: str,
) -> AsyncGenerator[ToolResponse, None]:
"""Create a sub-worker to finish the given task.
Args:
task_description (`str`):
The description of the task to be done by the sub-worker, should
contain all the necessary information.
Returns:
`AsyncGenerator[ToolResponse, None]`:
An async generator yielding ToolResponse objects.
"""
toolkit = Toolkit()
# Gaode MCP client
if os.getenv("GAODE_API_KEY"):
toolkit.create_tool_group(
group_name="amap_tools",
description="Map-related tools, including geocoding, routing, and "
"place search.",
)
client = HttpStatelessClient(
name="amap_mcp",
transport="streamable_http",
url=f"https://mcp.amap.com/mcp?key={os.environ['GAODE_API_KEY']}",
)
await toolkit.register_mcp_client(client, group_name="amap_tools")
else:
print(
"Warning: GAODE_API_KEY not set in environment, skipping Gaode "
"MCP client registration.",
)
# Browser MCP client
toolkit.create_tool_group(
group_name="browser_tools",
description="Web browsing related tools.",
)
browser_client = StdIOStatefulClient(
name="playwright-mcp",
command="npx",
args=["@playwright/mcp@latest"],
)
await browser_client.connect()
await toolkit.register_mcp_client(
browser_client,
group_name="browser_tools",
)
# GitHub MCP client
if os.getenv("GITHUB_TOKEN"):
toolkit.create_tool_group(
group_name="github_tools",
description="GitHub related tools, including repository "
"search and code file retrieval.",
)
github_client = HttpStatelessClient(
name="github",
transport="streamable_http",
url="https://api.githubcopilot.com/mcp/",
headers={"Authorization": f"Bearer {os.getenv('GITHUB_TOKEN')}"},
)
await toolkit.register_mcp_client(
github_client,
group_name="github_tools",
)
else:
print(
"Warning: GITHUB_TOKEN not set in environment, skipping GitHub "
"MCP client registration.",
)
# Basic read/write tools
toolkit.register_tool_function(write_text_file)
toolkit.register_tool_function(insert_text_file)
toolkit.register_tool_function(view_text_file)
# Create a new sub-agent to finish the given task
sub_agent = ReActAgent(
name="Worker",
sys_prompt=f"""You're an agent named Worker.
## Your Target
Your target is to finish the given task with your tools.
## IMPORTANT
You MUST use the `{ReActAgent.finish_function_name}` to generate the final answer after finishing the task.
""", # noqa: E501 # pylint: disable=C0301
model=DashScopeChatModel(
model_name="qwen3-max",
api_key=os.environ["DASHSCOPE_API_KEY"],
),
enable_meta_tool=True,
formatter=DashScopeChatFormatter(),
toolkit=toolkit,
max_iters=20,
)
# disable the console output of the sub-agent
sub_agent.set_console_output_enabled(False)
# Collect the execution process content
msgs = OrderedDict()
# Wrap the sub-agent in a coroutine task to obtain the final
# structured output
result = []
async def call_sub_agent() -> None:
msg_res = await sub_agent(
Msg(
"user",
content=task_description,
role="user",
),
structured_model=ResultModel,
)
result.append(msg_res)
# Use stream_printing_message to get the streaming response as the
# sub-agent works
async for msg, _ in stream_printing_messages(
agents=[sub_agent],
coroutine_task=call_sub_agent(),
):
msgs[msg.id] = msg
# Collect all the content blocks
yield ToolResponse(
content=_convert_to_text_block(
list(msgs.values()),
),
stream=True,
is_last=False,
)
# Expose the interruption signal to the caller
if msg.metadata and msg.metadata.get("_is_interrupted", False):
raise asyncio.CancelledError()
# Obtain the last message from the coroutine task
if result:
yield ToolResponse(
content=[
*_convert_to_text_block(
list(msgs.values()),
),
TextBlock(
type="text",
text=json.dumps(
result[0].metadata,
indent=2,
ensure_ascii=False,
),
),
],
stream=True,
is_last=True,
)
await browser_client.close()