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
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:
92
examples/agent/meta_planner_agent/README.md
Normal file
92
examples/agent/meta_planner_agent/README.md
Normal file
@@ -0,0 +1,92 @@
|
||||
# Meta Planner Agent Example
|
||||
|
||||
In this example, we demonstrate
|
||||
|
||||
- how to build a planner agent that can decompose complex task into manageable subtasks and orchestrates sub-agents to
|
||||
complete them
|
||||
- how to handle the printing messages of the sub-agents properly in a multi-agent system
|
||||
- how to propagate interrupt events from sub agents to the main planner agent
|
||||
|
||||
Specifically, in [main.py](./main.py), a planner agent is created with the `PlanNotebook` instance to create and manage
|
||||
plans. It's equipped with a tool function named `create_worker` in [tool.py](./tool.py) to create sub-agents
|
||||
dynamically and finish the assigned subtask. The sub-agents are equipped with some basic tools, and some preset
|
||||
MCP servers to enhance their capabilities.
|
||||
|
||||
> We suggest to use AgentScope-Studio to visualize the agent-interactions in this example.
|
||||
|
||||
## Quick Start
|
||||
|
||||
Install agentscope if you haven't already:
|
||||
|
||||
```bash
|
||||
pip install agentscope
|
||||
```
|
||||
|
||||
Make sure you have set your DashScope API key as an environment variable.
|
||||
|
||||
In this example, the sub-agents are equipped with the following MCP servers, set the corresponding environment variables to activate them.
|
||||
If not set, the corresponding MCP will be disabled.
|
||||
For more details about the tools, refer to [tool.py](./tool.py). You can also add or modify the tools as needed.
|
||||
|
||||
| MCP | Description | Environment Variable |
|
||||
|--------------------------|--------------------------------|----------------------|
|
||||
| AMAP MCP | Provide map related services | GAODE_API_KEY |
|
||||
| GitHub MCP | Search and access GitHub repos | GITHUB_TOKEN |
|
||||
| Microsoft Playwright MCP | Web Browser-use MCP server | - |
|
||||
|
||||
Run the example:
|
||||
|
||||
```bash
|
||||
python main.py
|
||||
```
|
||||
|
||||
Then you can ask the planner agent to help you complete a complex task, such as "Conduct research on AgentScope repo".
|
||||
|
||||
Note for simple questions, the planner agent may directly answer without creating sub-agents.
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Handling Sub-agent Output
|
||||
|
||||
In this example, the sub-agents won't print messages to the console directly (by `agent.set_console_output_enable(True)` in tool.py).
|
||||
Instead, its printing messages are streamlined back to the planner agent as the streaming responses of the tool function `create_worker`.
|
||||
By this way, we only expose the planner agent to the user, rather than multiple agents, which provides a better user experience.
|
||||
However, the response of the tool function `create_worker` maybe take too much context length if the sub-agent finishes the given task with a long reasoning-acting process.
|
||||
|
||||
This figure shows how the sub-agent output is displayed as tool streaming response in AgentScope-Studio:
|
||||
|
||||
<details>
|
||||
<summary>Chinese</summary>
|
||||
<p align="center">
|
||||
<img src="./assets/screenshot_zh.jpg"/>
|
||||
</p>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>English</summary>
|
||||
<p align="center">
|
||||
<img src="./assets/screenshot_en.jpg"/>
|
||||
</p>
|
||||
</details>
|
||||
|
||||
|
||||
|
||||
Also, you can choose to expose the sub-agent to the user, and only take the structured results back to the planner agent as the tool result of `create_worker`.
|
||||
|
||||
### Propagating Interrupt Events
|
||||
|
||||
In `ReActAgent`, when the final answer is generated from the `handle_interrupt` function, the metadata field of the return message
|
||||
will contain a `_is_interrupted` key with value `True` to indicate that the agent is interrupted.
|
||||
|
||||
By this field, we can propagate the interrupt event from the sub-agent to the main planner agent in the tool function `create_worker`.
|
||||
For user defined agent classes, you can define your own propagation logic in the `handle_interrupt` function of your agent class.
|
||||
|
||||
### Changing the LLM
|
||||
|
||||
The example is built with DashScope chat model. If you want to change the model in this example, don't forget
|
||||
to change the formatter at the same time! The corresponding relationship between built-in models and formatters are
|
||||
list in [our tutorial](https://doc.agentscope.io/tutorial/task_prompt.html#id1)
|
||||
|
||||
## Further Reading
|
||||
|
||||
- [Plan](https://doc.agentscope.io/tutorial/task_plan.html)
|
||||
BIN
examples/agent/meta_planner_agent/assets/screenshot_en.jpg
Normal file
BIN
examples/agent/meta_planner_agent/assets/screenshot_en.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 227 KiB |
BIN
examples/agent/meta_planner_agent/assets/screenshot_zh.jpg
Normal file
BIN
examples/agent/meta_planner_agent/assets/screenshot_zh.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 162 KiB |
59
examples/agent/meta_planner_agent/main.py
Normal file
59
examples/agent/meta_planner_agent/main.py
Normal file
@@ -0,0 +1,59 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""The planner agent example."""
|
||||
import asyncio
|
||||
import os
|
||||
|
||||
from tool import create_worker
|
||||
|
||||
from agentscope.agent import ReActAgent, UserAgent
|
||||
from agentscope.formatter import DashScopeChatFormatter
|
||||
from agentscope.model import DashScopeChatModel
|
||||
from agentscope.plan import PlanNotebook
|
||||
from agentscope.tool import Toolkit
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
"""The main function."""
|
||||
# Connect to the studio for better visualization (optional)
|
||||
# import agentscope
|
||||
# agentscope.init(
|
||||
# project="meta_planner_agent",
|
||||
# studio_url="http://localhost:3000",
|
||||
# )
|
||||
|
||||
toolkit = Toolkit()
|
||||
toolkit.register_tool_function(create_worker)
|
||||
|
||||
planner = ReActAgent(
|
||||
name="Friday",
|
||||
# pylint: disable=C0301
|
||||
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["DASHSCOPE_API_KEY"],
|
||||
),
|
||||
formatter=DashScopeChatFormatter(),
|
||||
plan_notebook=PlanNotebook(),
|
||||
toolkit=toolkit,
|
||||
max_iters=20,
|
||||
)
|
||||
user = UserAgent(name="user")
|
||||
|
||||
msg = None
|
||||
while True:
|
||||
msg = await planner(msg)
|
||||
msg = await user(msg)
|
||||
if msg.get_text_content() == "exit":
|
||||
break
|
||||
|
||||
|
||||
asyncio.run(main())
|
||||
226
examples/agent/meta_planner_agent/tool.py
Normal file
226
examples/agent/meta_planner_agent/tool.py
Normal 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()
|
||||
Reference in New Issue
Block a user