ai-agent-book 精选快照(<2MB 代码与文档,来自 github.com/bojieli/ai-agent-book)
Build latest book artifacts / build (push) Canceled after 0s
dependency resolution / resolve (3.11) (push) Canceled after 0s
dependency resolution / resolve (3.13) (push) Canceled after 0s
deploy-pages / build (push) Canceled after 0s
deploy-pages / deploy (push) Canceled after 0s
i18n consistency check / check (push) Canceled after 0s
provider adoption tests / test (chapter2/context-compression) (push) Canceled after 0s
provider adoption tests / test (chapter2/prompt-injection) (push) Canceled after 0s
provider adoption tests / test (chapter2/system-hint) (push) Canceled after 0s
provider adoption tests / test (chapter3/log-sanitization) (push) Canceled after 0s
web-search-agent tests / test (push) Canceled after 0s
web-search-agent tests / agentbook (push) Canceled after 0s

This commit is contained in:
2026-08-20 13:12:50 +00:00
commit b119135836
10275 changed files with 3284984 additions and 0 deletions
@@ -0,0 +1,393 @@
# Building and Running Agents
In AWorld's design, both Workflows and Multi-Agent Systems (MAS) are complex systems built around Agents as the core
component. Using the most common llm_agent as an example, this tutorial provides detailed guidance on:
1. How to quickly build an Agent
2. How to customize an Agent
This document is divided into two parts to explain AWorld's design philosophy.
## Part 1: Quick Agent Setup
### Declaring an Agent
```python
from aworld.agents.llm_agent import Agent
# Assign a name to your agent
agent = Agent(name="my_agent")
```
### Configuring LLM
#### Method 1: Using Environment Variables
```python
import os
## Set up LLM service using environment variables
os.environ["LLM_PROVIDER"] = "openai" # Choose from: openai, anthropic, azure_openai
os.environ["LLM_MODEL_NAME"] = "gpt-4"
os.environ["LLM_API_KEY"] = "your-api-key"
os.environ["LLM_BASE_URL"] = "https://api.openai.com/v1" # Optional for OpenAI
```
#### Method 2: Using AgentConfig
```python
import os
from aworld.agents.llm_agent import Agent
from aworld.config.conf import AgentConfig
agent_config = AgentConfig(
llm_provider=os.getenv("LLM_PROVIDER", "openai"),
llm_model_name=os.getenv("LLM_MODEL_NAME"),
llm_base_url=os.getenv("LLM_BASE_URL"),
llm_api_key=os.getenv("LLM_API_KEY"),
)
agent = Agent(name="my_agent", conf=agent_config)
```
#### Method 3: Using Shared ModelConfig
When multiple agents use the same LLM service, you can specify a shared ModelConfig:
```python
import os
from aworld.agents.llm_agent import Agent
from aworld.config.conf import AgentConfig, ModelConfig
# Create a shared model configuration
model_config = ModelConfig(
llm_provider=os.getenv("LLM_PROVIDER", "openai"),
llm_model_name=os.getenv("LLM_MODEL_NAME"),
llm_base_url=os.getenv("LLM_BASE_URL"),
llm_api_key=os.getenv("LLM_API_KEY"),
)
# Use the shared model config in agent configuration
agent_config = AgentConfig(
llm_config=model_config,
)
agent = Agent(name="my_agent", conf=agent_config)
```
### Configuring Prompts
```python
from aworld.agents.llm_agent import Agent
import os
from aworld.config.conf import AgentConfig, ModelConfig
model_config = ModelConfig(
llm_provider=os.getenv("LLM_PROVIDER", "openai"),
llm_model_name=os.getenv("LLM_MODEL_NAME"),
llm_base_url=os.getenv("LLM_BASE_URL"),
llm_api_key=os.getenv("LLM_API_KEY"),
)
agent_config = AgentConfig(
llm_config=model_config,
)
# Define your system prompt
system_prompt = """You are a helpful AI assistant that can assist users with various tasks.
You should be polite, accurate, and provide clear explanations."""
agent = Agent(
name="my_agent",
conf=agent_config,
system_prompt=system_prompt
)
```
### Configuring Tools
#### Local Tools
```python
from aworld.agents.llm_agent import Agent
import os
from aworld.config.conf import AgentConfig, ModelConfig
from aworld.core.tool.func_to_tool import be_tool
model_config = ModelConfig(
llm_provider=os.getenv("LLM_PROVIDER", "openai"),
llm_model_name=os.getenv("LLM_MODEL_NAME"),
llm_base_url=os.getenv("LLM_BASE_URL"),
llm_api_key=os.getenv("LLM_API_KEY"),
)
agent_config = AgentConfig(
llm_config=model_config,
)
system_prompt = """You are a helpful agent with access to various tools."""
# Define a local tool using the @be_tool decorator
@be_tool(tool_name='greeting_tool', tool_desc="A simple greeting tool that returns a hello message")
def greeting_tool() -> str:
return "Hello, world!"
agent = Agent(
name="my_agent",
conf=agent_config,
system_prompt=system_prompt,
tool_names=['greeting_tool']
)
```
#### MCP (Model Context Protocol) Tools
```python
from aworld.agents.llm_agent import Agent
import os
from aworld.config.conf import AgentConfig, ModelConfig
model_config = ModelConfig(
llm_provider=os.getenv("LLM_PROVIDER", "openai"),
llm_model_name=os.getenv("LLM_MODEL_NAME"),
llm_base_url=os.getenv("LLM_BASE_URL"),
llm_api_key=os.getenv("LLM_API_KEY"),
)
agent_config = AgentConfig(
llm_config=model_config,
)
system_prompt = """You are a helpful agent with access to file system operations."""
# Configure MCP servers
mcp_config = {
"mcpServers": {
"GorillaFileSystem": {
"type": "stdio",
"command": "python",
"args": ["examples/BFCL/mcp_tools/gorilla_file_system.py"],
},
}
}
agent = Agent(
name="my_agent",
conf=agent_config,
system_prompt=system_prompt,
mcp_servers=list(mcp_config.get("mcpServers", {}).keys()),
mcp_config=mcp_config
)
```
#### Agent as Tool
```python
from aworld.agents.llm_agent import Agent
import os
from aworld.config.conf import AgentConfig, ModelConfig
model_config = ModelConfig(
llm_provider=os.getenv("LLM_PROVIDER", "openai"),
llm_model_name=os.getenv("LLM_MODEL_NAME"),
llm_base_url=os.getenv("LLM_BASE_URL"),
llm_api_key=os.getenv("LLM_API_KEY"),
)
agent_config = AgentConfig(
llm_config=model_config,
)
system_prompt = """You are a helpful agent that can delegate tasks to other specialized agents."""
# Create a specialized tool agent
tool_agent = Agent(name="tool_agent", conf=agent_config)
# Create the main agent that can use the tool agent
agent = Agent(
name="my_agent",
conf=agent_config,
system_prompt=system_prompt,
agent_names=['tool_agent']
)
```
## Part 2: Customizing Agents
### Customizing Agent Input
Override the `init_observation()` function to customize how your agent processes initial observations:
```python
async def init_observation(self, observation: Observation) -> Observation:
# You can add extended information from other agents or third-party storage
# For example, enrich the observation with additional context
observation.metadata = {"timestamp": time.time(), "source": "custom"}
return observation
```
### Customizing Model Input
Override the `async_messages_transform()` function to customize how messages are transformed before being sent to the
model:
```python
async def async_messages_transform(self,
image_urls: List[str] = None,
observation: Observation = None,
message: Message = None,
**kwargs) -> List[Dict[str, Any]]:
"""
Transform input data into the format expected by the LLM.
Args:
image_urls: List of images encoded using base64
observation: Observation from the environment
message: Event received by the Agent
"""
messages = []
# Add system context
if hasattr(self, 'system_prompt'):
messages.append({"role": "system", "content": self.system_prompt})
# Add user message
if message and message.content:
messages.append({"role": "user", "content": message.content})
# Add images if present
if image_urls:
for img_url in image_urls:
messages.append({
"role": "user",
"content": [{"type": "image_url", "image_url": {"url": img_url}}]
})
return messages
```
### Customizing Model Logic
Override the `invoke_model()` function to implement custom model logic:
```python
async def invoke_model(self,
messages: List[Dict[str, str]] = [],
message: Message = None,
**kwargs) -> ModelResponse:
"""Custom model invocation logic.
You can use neural networks, rule-based systems, or any other business logic.
"""
# Example: Use a custom model or business logic
if self.use_custom_logic:
# Your custom logic here
response_content = self.custom_model.predict(messages)
else:
# Use the default LLM
response_content = await self.llm_client.chat_completion(messages)
return ModelResponse(
id=f"response_{int(time.time())}",
model=self.model_name,
content=response_content,
tool_calls=None # Set if tool calls are present
)
```
### Customizing Model Output
Create a custom `ModelOutputParser` class and specify it using the `model_output_parser` parameter:
```python
from aworld.models.model_output_parser import ModelOutputParser
class CustomOutputParser(ModelOutputParser[ModelResponse, AgentResult]):
async def parse(self, resp: ModelResponse, **kwargs) -> AgentResult:
"""Custom parsing logic based on your model's API response format."""
# Extract relevant information from the model response
content = resp.content
tool_calls = resp.tool_calls
# Create your custom AgentResult
result = AgentResult(
content=content,
tool_calls=tool_calls,
metadata={"parsed_at": time.time()}
)
return result
# Use the custom parser
agent = Agent(
name="my_agent",
conf=agent_config,
model_output_parser=CustomOutputParser()
)
```
### Customizing Agent Response
Override the `async_post_run()` function to customize how your agent responds:
```python
from aworld.core.message import Message
class CustomMessage(Message):
def __init__(self, content: str, custom_field: str = None):
super().__init__(content=content)
self.custom_field = custom_field
async def async_post_run(self,
policy_result: List[ActionModel],
policy_input: Observation,
message: Message = None) -> Message:
"""
Customize the agent's response after processing.
"""
# Process the policy result and create a custom response
response_content = f"Processed {len(policy_result)} actions"
custom_field = "custom_value"
return CustomMessage(
content=response_content,
custom_field=custom_field
)
```
### Custom Response Parsing
If the framework doesn't support your response structure, you can create a custom response parser:
```python
from aworld.runners import HandlerFactory
from aworld.runners.default_handler import DefaultHandler
# Define a custom handler name
custom_name = "custom_handler"
@HandlerFactory.register(name=custom_name)
class CustomHandler(DefaultHandler):
def is_valid_message(self, message: Message):
"""Check if this handler should process the message."""
return message.category == custom_name
async def _do_handle(self, message: Message) -> AsyncGenerator[Message, None]:
"""Custom message processing logic."""
if not self.is_valid_message(message):
return
# Implement your custom message processing logic here
processed_message = self.process_custom_message(message)
yield processed_message
# Use the custom handler
agent = Agent(
name="my_agent",
conf=agent_config,
event_handler_name=custom_name
)
```
**Important Note:** The `custom_name` variable value must remain consistent across your handler registration and agent
configuration.
@@ -0,0 +1,88 @@
# Environment
Mainly providing MCP servers in an independent environment to support high concurrency applications of MCP servers.
## Add MCP Servers
If you only use the built-in MCP servers, you only need to deploy them.
If there is a new MCP server and you want to use it independently, you can use the same directory structure
as [gaia-mcp-server](../../env/gaia-mcp-server), and refer to the code structure and implementation of
[hello_world](../../env/gaia-mcp-server/mcp_servers/hello_world).
```
your_mcp_server/
.dockerignore
Dockfile
mcp_servers/
.env
.gitignore
mcp_config.py
build_mcp_tool_schema.py
init_env.sh
your_tool/
src/
.python-version
pyproject.toml
```
The `mcp_config` variable in `mcp_config.py` is a standard MCP configuration structure.
Before deployment, it is necessary to run `build_mcp_tool_schema.py` to generate `mcp_tool_schema.json`.
`.env` is the environment configuration file for MCP servers.
## Depolyment
### Local Docker Deployment
#### Prerequisites
Ensure Docker and Docker Compose are properly installed and operational:
```bash
# Verify Docker installation
docker --version
docker compose --version
# Verify Docker daemon is running
docker ps
docker compose ps
```
**Step 1: Launch VirtualPC MCP Server**
```bash
sh run-docker.sh
```
Monitor the terminal output for any errors during startup.
**Step 2: Connect to VirtualPC MCP Server**
Use the following configuration to connect to the VirtualPC MCP Server:
```json
{
"virtualpc-mcp-server": {
"type": "streamable-http",
"url": "http://localhost:8000/mcp",
"headers": {
"Authorization": "Bearer your token",
"MCP_SERVERS": "readweb-server,browser-server"
},
"timeout": 6000,
"sse_read_timeout": 6000,
"client_session_timeout_seconds": 6000
}
}
```
**Note**: The Bearer token is your own. The `MCP_SERVERS` header specifies the MCP server
scope for your current connection, which should be a subset of server names defined in
`mcp_servers/mcp_config.py`.
### Kubernetes Cluster Deployment
For production deployments and RL training scenarios, Kubernetes cluster deployment is recommended.
Detailed instructions will be provided in future updates.
@@ -0,0 +1,18 @@
# Installation
## AWorld Agent
### Prerequisites
- Python 3.10+
### Install
```shell
git clone https://github.com/inclusionAI/AWorld && cd AWorld
pip install .
```
## AWorld Env
TODO
## AWorld Train
TODO
@@ -0,0 +1,166 @@
# Building and Running Multi-Agent Systems (MAS)
In the AWorld framework, similar to Workflow Construction, the fundamental building block for MAS is the Agent. By introducing the Swarm concept, users can easily, quickly, and efficiently build complex Multi-Agent Systems. In summary:
1. **Workflow in AWorld**: Static, pre-defined execution flows
2. **MAS in AWorld**: Dynamic, real-time decision-making execution flows
This design ensures unified underlying capabilities (i.e., Agent, Graph-based Topology) while maintaining extensibility.
## Quick MAS Construction
Similar to Workflows, we can easily define communication networks between Agents through topology. The key difference is that by using `build_type=GraphBuildType.HANDOFF`, we allow dynamic decision-making for inter-agent calling relationships:
1. `agent1` can selectively decide to call `agent2` and `agent3`; the number of calls is also dynamic (once or multiple times)
2. `agent2` can selectively decide to call `agent3`; the number of calls is also dynamic (once or multiple times)
```python
from aworld.config.conf import AgentConfig
from aworld.agents.llm_agent import Agent
from aworld.core.agent.swarm import Swarm, GraphBuildType
from aworld.runner import Runners
# Configure agents
agent_conf = AgentConfig(...)
agent1 = Agent(name="agent1", conf=agent_conf)
agent2 = Agent(name="agent2", conf=agent_conf)
agent3 = Agent(name="agent3", conf=agent_conf)
# Create swarm with dynamic handoff topology
swarm = Swarm(
topology=[(agent1, agent2), (agent2, agent3), (agent1, agent3)],
build_type=GraphBuildType.HANDOFF
)
# Run the swarm
Runners.run(input="your question", swarm=swarm)
```
### Specifying Entry Agent
Since MAS is essentially a Graph by definition, different Agents can accept external input. We can specify which Agent receives the query using the `root_agent` parameter.
```python
swarm = Swarm(
topology=[(agent1, agent2), (agent2, agent3), (agent1, agent3)],
build_type=GraphBuildType.HANDOFF,
root_agent=[agent1]
)
```
### Dynamic Routing
When the `policy()` function decides which agent to call next, for special cases, Agents may need customized routing based on specific business rules. You can override the handler in the corresponding Agent:
```python
# Handler name consistency must be maintained
agent = Agent(..., event_handler_name="your_handler_name")
```
```python
from aworld.core.handler import HandlerFactory, DefaultHandler
from aworld.core.message import Message
from typing import AsyncGenerator
@HandlerFactory.register(name="your_handler_name")
class YourHandler(DefaultHandler):
def is_valid_message(self, message: Message) -> bool:
return message.category == "your_handler_name"
async def _do_handle(self, message: Message) -> AsyncGenerator[Message, None]:
if not self.is_valid_message(message):
return
# The type of data is generally ActionModel, but can be customized
data = message.payload
if "clause1" in data:
# Handle clause1 logic
pass
elif "clause2" in data:
# Handle clause2 logic
pass
```
You can refer to the implementation of `DefaultTaskHandler` in AWorld.
#### Two Examples of Overriding Routing: ReAct and Plan-Execute
##### ReAct
```python
@HandlerFactory.register(name='react')
class ReactHandler(AgentHandler):
def is_valid_message(self, message: Message):
if message.category != 'react':
return False
return True
async def _do_handle(self, message: Message) -> AsyncGenerator[Message, None]:
yield message
```
##### Plan-Execute
Compared to ReAct, agent2 and agent3 can execute in parallel simultaneously.
```python
from aworld.core.common import Observation
from aworld.core.event.base import AgentMessage
from aworld.logs.util import logger
@HandlerFactory.register(name='plan_execute')
class PlanExecuteHandler(AgentHandler):
def is_valid_message(self, message: Message):
if message.category != 'plan_execute':
return False
return True
async def _do_handle(self, message: Message) -> AsyncGenerator[Message, None]:
logger.info(f"PlanExecuteHandler|handle|taskid={self.task_id}|is_sub_task={message.context._task.is_sub_task}")
content = message.payload
# Parse model plan
plan = parse_plan(content[0].policy_info)
logger.info(f"PlanExecuteHandler|plan|{plan}")
# Execute steps
output, context = execution_steps(plan.steps)
# Send event message, notify the next processing agent
new_plan_input = Observation(content=output)
yield AgentMessage(
session_id=message.session_id,
payload=new_plan_input,
sender=self.name(),
receiver=self.swarm.communicate_agent.id(),
headers={'context': context}
)
```
For more details, refer to the examples.
## Combination and Recursion of MAS and Workflow
Same or different types of Swarms can be deeply nested, providing multi-level Swarms with different interaction mechanisms to support complex multi-agent interactions. For example, when creating a travel itinerary planner, using a combination of Workflow + MAS, where Workflow provides deterministic processes and MAS handles multi-source information retrieval and integration.
```python
from aworld.config.conf import AgentConfig
from aworld.agents.llm_agent import Agent
from aworld.core.agent.swarm import Swarm, GraphBuildType
# Configure agents
agent_conf = AgentConfig(...)
# Create five agents
rewrite = Agent(name="rewrite", conf=agent_conf)
plan = Agent(name="plan", conf=agent_conf)
search = Agent(name="search", conf=agent_conf)
summary = Agent(name="summary", conf=agent_conf)
report = Agent(name="report", conf=agent_conf)
# Construct a MAS
mas = Swarm(
topology=[(plan, search), (plan, summary)],
build_type=GraphBuildType.HANDOFF,
root_agent=[plan]
)
# Construct a combination of a workflow with the MAS team
combination = Swarm(
topology=[(rewrite, mas), (mas, report)],
root_agent=[rewrite]
)
```
@@ -0,0 +1,70 @@
We use the classic graph syntax to describe workflows in AWorld.
The following are the basic scenarios for constructing agent workflows.
## Agent Native Workflow
### Sequential
```python
"""
Sequential Agent Pipeline: agent1 → agent2 → agent3
Executes agents in sequence where each agent's output becomes
the next agent's input, enabling multi-step collaborative processing.
"""
swarm = Swarm([(agent1, agent2), (agent2, agent3)], root_agent=[agent1])
result: TaskResponse = Runners.run(input=question, swarm=swarm)
```
### Parallel
```python
"""
Parallel Agent Execution with Barrier Synchronization
Input ──┬─→ agent1 ──┐
│ ├──→ agent3 (barrier wait)
└─→ agent2 ──┘
- agent1 and agent2 execute in parallel
- agent3 acts as a barrier, waiting for both agents
- agent3 processes combined outputs from agent1 and agent2
"""
swarm = Swarm([(agent1, agent3), (agent2, agent3)], root_agent=[agent1, agent2])
result: TaskResponse = Runners.run(input=question, swarm=swarm)
```
### Parallel Multi-Path
```python
"""
Parallel Multi-Path Agent Execution
Input ──→ agent1 ──┬──→ agent2 ──┐
│ │
└──→ agent3 ←─┘ (barrier wait for agent1 & agent2)
- Single input enters only through agent1
- agent1 distributes to both agent2 and agent3
- agent2 processes and feeds agent3
- agent3 waits for both agent1 and agent2 completion
- agent3 synthesizes outputs from both agent1 and agent2
"""
swarm = Swarm([(agent1, agent2), (agent1, agent3), (agent2, agent3)], root_agent=[agent1])
result: TaskResponse = Runners.run(input=question, swarm=swarm)
```
## Task Native Workflow
Task native workflow is further implemented for Isolating the agent runtimes and environments,
in the distributed or other easy-to-overlap scenarios.
Task native workflow is further implemented for isolating agent runtimes and environments,
particularly useful in distributed or other scenarios where tool-isolation is required.
```python
task1 = Task(input="my question", agent=agent1)
task2 = Task(agent=agent2)
task3 = Task(agent=agent3)
tasks = [task1, task2, task3]
result: Dict[str, TaskResponse] = Runners.run_task(tasks, RunConfig(sequence_dependent=True))
```
@@ -0,0 +1,9 @@
// Check if the current page is the home page, if so hide the edit link
if (window.location.pathname === '/' ||
window.location.pathname.endsWith('/index.html') ||
window.location.pathname.endsWith('/AWorld/')) {
const editLink = document.querySelector('.wy-breadcrumbs-aside a[href*="index.md"]');
if (editLink) {
editLink.style.display = 'none';
}
}
@@ -0,0 +1,81 @@
# coding: utf-8
# Copyright (c) 2025 inclusionAI.
import os
from collections import OrderedDict
from typing import List
import yaml
docs = "docs"
black_keys = ["Index"]
black_values = ["index.md"]
file_priority = {"Quickstart": ["Install", "Agent Construction", "Workflow Construction",
"Multi-agent System Construction", "Environment"]
}
dir_order = ["Quickstart", "Tutorials"]
def scan_path(path: str) -> List[dict]:
items = scan(path)
res = []
for k, v in items.items():
# root path
if k in black_keys and v in black_values:
continue
# files in dir
final_map = OrderedDict()
for file in file_priority.get(k, []):
if file in v:
final_map[file] = v[file]
v.pop(file)
final_map.update(v)
res.append({k: dict(final_map)})
return res
def scan(path: str) -> dict:
items = {}
for name in sorted(os.listdir(path)):
p = os.path.join(path, name)
if name.startswith("."):
continue
if os.path.isdir(p):
children = scan(p)
if children:
items[name] = children
elif name.endswith(".md"):
words = os.path.splitext(name)[0].split('_')
key = ' '.join([w.capitalize() for w in words])
items[key] = os.path.relpath(p, docs).replace(os.sep, "/")
return items
if __name__ == '__main__':
outline = scan_path(docs)
cfg = {
"site_name": "AWorld Docs",
"site_url": "https://github.com/inclusionAI/AWorld",
"repo_url": "https://github.com/inclusionAI/AWorld",
"edit_uri": "tree/main/docs/",
"copyright": "\u00A9 Copyright 2025 inclusionAI AWorld Team.",
"extra_javascript": ["js/hide-home-edit.js"],
"theme": "readthedocs",
"nav": outline,
}
index_content = ["# Welcome to AWorlds Documentation!"]
# standard structure
for line in outline:
for k, v in line.items():
index_content.append(f"## {k}")
for s_k, s_v in v.items():
index_content.append(f"[{s_k}]({s_v})")
with open("index.md", 'w') as index_file:
index_file.write("\n\n".join(index_content))
with open('mkdocs.yml', 'w') as outfile:
yaml.safe_dump(cfg, outfile, sort_keys=False, allow_unicode=True)