跳到主要内容

代理系统设计指南

基于 COunt 实践和业界最佳实践的综合指南

本指南深入解析代理系统的设计原则、提示词工程技巧和多代理协作模式,结合了业界成熟代理系统的常见设计理念。

目录


核心设计原则

1. 上下文优先原则

最重要的因素是为模型提供最佳上下文。这是模型执行任务的主要信号。

优先级排序:
1. 用户提供的信息(最高优先级)
2. 系统状态和环境信息
3. 历史对话和工具调用记录
4. 通用指令和约束

实践建议

  • 当不确定时,倾向于提供更多信息
  • 现代模型擅长在大型提示中找到相关信息
  • 不要担心提示词长度,上下文窗口足够大

2. 完整世界观原则

帮助模型建立完整的世界观,解释它所处的环境和可用资源。

Coun t 实现示例

# 来自 backend/modules/agent/subagent.py
def _build_subagent_prompt(self, task: str, enable_skills: bool = False) -> str:
workspace_path = str(self.workspace.expanduser().resolve())

prompt = f"""# 子代理 (Subagent)

你是主代理创建的子代理,专门负责完成特定任务。

## 你的任务
{task}

## 工作规则
1. **专注任务**: 只完成分配的任务,不做其他事情
2. **简洁高效**: 最终响应会报告给主代理,保持简洁但信息完整
3. **不要闲聊**: 不要发起对话或承担额外任务
4. **彻底完成**: 确保任务完整完成,提供清晰的结果总结

## 可用能力
- 读写工作空间文件
- 执行 Shell 命令
- 网络搜索和抓取网页
- 使用所有标准工具

## 工作空间
{workspace_path}
"""
return prompt

3. 一致性原则

确保提示词的所有组件(系统提示、工具定义、工具输出)保持一致。

反例

# 反例:不一致的设计
system_prompt = "当前目录是 /home/user/project"
# 但 execute_command 工具的默认 cwd 是 /tmp

正例

# 正例:一致的设计
system_prompt = f"当前目录是 {workspace_path}"
# execute_command 工具的默认 cwd 也是 workspace_path

4. 用户视角对齐

将模型与用户的视角对齐,提供用户关心的信息。

可包含的状态信息

  • 用户当前时间和时区
  • 用户当前位置
  • IDE 状态(打开的文件、光标位置)
  • 用户活动历史

注意:动态状态不要放在系统提示中,而应该在用户消息中更新,以保持提示缓存有效。


提示词工程最佳实践

1. 详尽但不冗余

原则:模型受益于详尽的提示,不要担心长度。

示例:CountBot 的记忆总结提示词

# 来自 backend/modules/agent/prompts.py
CONVERSATION_TO_MEMORY_PROMPT = """你是一个对话总结器。将下面的对话总结为简洁的记忆条目。

要求:
1. 输出格式: 一行文本,多个事项用中文分号(;)分隔
2. 只记录有长期价值的事实信息:
- 用户明确表达的偏好和习惯
- 重要决策和结论
- 项目配置和技术细节
- 用户要求记住的内容
3. 不要记录:
- 寒暄、确认、重复内容
- 一次性查询结果(天气、新闻、搜索结果)
- 工具执行的中间过程
- 闲聊和测试内容
4. 每个事项必须包含具体信息(名称、数字、时间、地点等)
5. 如果对话没有值得长期记录的信息,输出: 无需记录

对话内容:
{messages}

输出(一行,事项用;分隔):"""

关键特点

  • 明确的输出格式要求
  • 详细的正面和负面示例
  • 具体的判断标准
  • 边界情况处理("无需记录")

2. 避免过拟合特定示例

模型是强大的模式匹配器,会紧抓提示中的细节。

安全做法

  • 告诉模型"不要做什么"通常是安全的
  • 注意:提供"要做什么"的具体示例可能导致过拟合
  • 使用多样化的示例进行测试

3. 工具调用的限制和处理

已知限制

  1. 模型可能选择错误的工具(即使有最佳提示)
  2. 面对多个相似工具时,模型倾向选择简单的
  3. 模型可能违反工具定义的约定(参数类型、范围、必需参数)

最佳实践

# 建议:验证输入,返回友好的错误信息
def execute_tool(tool_name, arguments):
try:
validate_arguments(arguments)
return tool.execute(arguments)
except ValidationError as e:
# 不要抛出异常,返回错误说明
return f"工具调用错误: 缺少必需参数 {e.param}。请提供该参数后重试。"

工具去重:CountBot 的实现

# 来自 backend/modules/agent/subagent.py
# 去除并行工具调用中的重复项
seen_sigs: set[str] = set()
deduped: list = []
for _tc in tool_calls_buffer:
_sig = f"{_tc.name}:{json.dumps(_tc.arguments, sort_keys=True)}"
if _sig not in seen_sigs:
seen_sigs.add(_sig)
deduped.append(_tc)
else:
logger.warning(f"[Subagent] skipping duplicate tool call: {_tc.name}")
tool_calls_buffer = deduped

4. 提示词位置的重要性

注意力分布

用户消息 > 提示开头 > 提示中间

实践建议

  • 重要指令放在用户消息中
  • 次要指令放在系统提示开头
  • 背景信息可以放在中间

5. 威胁和共情有时有效

# 通常有效
"正确完成此任务,否则会造成严重后果"
"请仔细检查你的工作"

# 通常无效
"拜托了!"
"!!!重要!!!"

子代理系统设计

架构概览

┌─────────────────────────────────────┐
│ 主代理 (Main Agent) │
│ - 用户交互 │
│ - 会话管理 │
│ - 记忆系统 │
└──────────────┬──────────────────────┘

│ create_task()

┌─────────────────────────────────────┐
│ SubagentManager │
│ - 任务队列管理 │
│ - 异步执行调度 │
│ - 状态持久化 │
└──────────────┬──────────────────────┘

│ execute_task()

┌─────────────────────────────────────┐
│ SubagentTask │
│ - 独立上下文 │
│ - 工具调用 │
│ - 进度通知 │
└─────────────────────────────────────┘

任务生命周期

class TaskStatus(Enum):
PENDING = "pending" # 已创建,等待执行
RUNNING = "running" # 正在执行
COMPLETED = "completed" # 成功完成
FAILED = "failed" # 执行失败
CANCELLED = "cancelled" # 用户取消

状态转换图

PENDING ──execute_task()──> RUNNING ──success──> COMPLETED
├──error────> FAILED
└──cancel───> CANCELLED

子代理的能力边界

子代理可以做

  • 读写工作空间文件
  • 执行 Shell 命令(受限)
  • 网络搜索和抓取
  • 调用所有标准工具
  • 访问技能系统(如果启用)

子代理不能做

  • 直接向用户发送消息
  • 创建其他子代理
  • 访问主代理的对话历史
  • 修改主代理的状态

技能系统集成

CountBot 支持将技能文档动态注入到子代理提示词中:

if enable_skills and self.skills:
skills_summary = self.skills.build_skills_summary()
if skills_summary:
prompt += f"""

## 可用技能(Skills)

**重要**: 技能不是工具!技能是包含命令行调用示例的文档,
需要先读取文档,再使用 exec 工具执行其中的命令。

以下技能已启用,需要时使用 read_file 工具读取完整内容:

{skills_summary}

**正确使用流程**:
1. 用户提到某个功能(如"生成图片"、"查天气")
2. 使用 read_file 读取对应技能文档
3. 阅读文档中的命令行示例
4. 使用 exec 工具执行文档中的命令
"""

进度通知机制

CountBot 实现了双通道通知:

  1. TaskNotificationHandler(用于 spawn 工具)
  2. event_callback(用于 workflow 系统)
# 工具调用通知
if handler:
await handler.notify_tool_call(
tool_call.name,
tool_call.arguments,
tool_call_id=tool_call.id,
)

if task.event_callback:
await task.event_callback("tool_call", tool_call.name, tool_call.arguments)

# 工具结果通知
if handler:
await handler.notify_tool_result(
tool_call.name,
result,
task.progress,
tool_call_id=tool_call.id,
)

if task.event_callback:
await task.event_callback("tool_result", tool_call.name, result)

多代理协作模式

CountBot 支持三种多代理协作模式:

1. Pipeline 模式(流水线)

特点:代理按顺序执行,每个代理的输出作为下一个代理的输入。

Agent 1 → Agent 2 → Agent 3 → 最终结果

适用场景

  • 需要明确步骤顺序的任务
  • 每个步骤依赖前一步的输出
  • 例如:研究 → 分析 → 撰写报告

配置示例

{
"name": "内容创作流水线",
"mode": "pipeline",
"agents": [
{
"id": "researcher",
"role": "研究专家",
"system_prompt": "你是一个专业的研究员,擅长收集和整理信息。",
"task": "深入研究给定主题,收集关键信息和数据"
},
{
"id": "analyst",
"role": "分析师",
"system_prompt": "你是一个数据分析专家,擅长从数据中提取洞察。",
"task": "分析研究结果,提取关键洞察和趋势"
},
{
"id": "writer",
"role": "内容撰写者",
"system_prompt": "你是一个专业的内容创作者,擅长将复杂信息转化为易读的文章。",
"task": "基于分析结果撰写一篇结构清晰、引人入胜的文章"
}
]
}

2. Graph 模式(有向无环图)

特点:代理之间有依赖关系,但不是严格的线性顺序。

    Agent 1
/ \
Agent 2 Agent 3
\ /
Agent 4

适用场景

  • 部分任务可以并行执行
  • 某些代理依赖多个前置代理的输出
  • 例如:前端和后端开发可以并行,最后集成测试

配置示例

{
"name": "全栈开发图",
"mode": "graph",
"agents": [
{
"id": "architect",
"role": "架构师",
"task": "设计系统架构",
"depends_on": []
},
{
"id": "frontend",
"role": "前端开发",
"task": "实现用户界面",
"depends_on": ["architect"]
},
{
"id": "backend",
"role": "后端开发",
"task": "实现 API 和业务逻辑",
"depends_on": ["architect"]
},
{
"id": "tester",
"role": "测试工程师",
"task": "集成测试和质量保证",
"depends_on": ["frontend", "backend"]
}
]
}

3. Council 模式(议会)

特点:多个代理从不同视角分析同一问题,可选交叉评审。

Agent 1 (视角A) ──┐
Agent 2 (视角B) ──┼──> 综合决策
Agent 3 (视角C) ──┘

适用场景

  • 需要多角度评估的决策
  • 代码审查、方案评审
  • 复杂问题的多维度分析

配置示例

{
"name": "代码审查委员会",
"mode": "council",
"cross_review": true,
"agents": [
{
"id": "security",
"role": "安全专家",
"system_prompt": "你是安全专家,专注于发现安全漏洞和风险。",
"perspective": "安全性"
},
{
"id": "performance",
"role": "性能专家",
"system_prompt": "你是性能优化专家,关注代码效率和资源使用。",
"perspective": "性能"
},
{
"id": "maintainability",
"role": "可维护性专家",
"system_prompt": "你是代码质量专家,关注可读性、可维护性和最佳实践。",
"perspective": "可维护性"
}
]
}

交叉评审机制

cross_review: true 时,每个代理不仅提供自己的分析,还会评审其他代理的观点。


工具调用优化

1. 迭代限制

防止无限循环,设置最大迭代次数:

# 从配置获取最大迭代次数,默认15
max_iterations = 15
if self.config_loader:
config = self.config_loader.config
max_iterations = config.model.max_iterations

iteration = 0
while iteration < max_iterations:
iteration += 1
# 执行工具调用循环
...

2. 工具调用去重

并行工具调用时,避免重复执行相同的调用:

# 使用签名去重
seen_sigs: set[str] = set()
deduped: list = []
for tc in tool_calls_buffer:
sig = f"{tc.name}:{json.dumps(tc.arguments, sort_keys=True)}"
if sig not in seen_sigs:
seen_sigs.add(sig)
deduped.append(tc)
else:
logger.warning(f"跳过重复的工具调用: {tc.name}")

3. 工具输出截断

对于长输出,智能截断以保留最有用的信息:

# 反例:简单截断后缀
output = long_output[:1000]

# 正例:保留前缀和后缀,截断中间
def smart_truncate(text, max_length=1000):
if len(text) <= max_length:
return text

prefix_len = max_length // 2
suffix_len = max_length - prefix_len - 20

return (
text[:prefix_len] +
"\n... [中间部分已截断] ...\n" +
text[-suffix_len:]
)

原因:命令输出的有用信息通常在开头(命令执行情况)和结尾(错误堆栈、最终结果)。

4. 错误处理和恢复

# 建议:友好的错误返回
def execute_tool(tool_name, arguments):
try:
return tool.execute(arguments)
except FileNotFoundError as e:
return f"错误: 文件 '{e.filename}' 不存在。请检查路径是否正确。"
except PermissionError as e:
return f"错误: 没有权限访问 '{e.filename}'。请检查文件权限。"
except Exception as e:
return f"工具执行失败: {str(e)}。请检查参数并重试。"

关键:不要抛出异常到代理层,而是返回描述性的错误信息,让模型自行恢复。


实战案例

案例 1:文件批量处理子代理

场景:用户需要批量重命名项目中的文件。

主代理提示词

用户请求: "将 src/ 目录下所有 .js 文件重命名为 .ts"

创建子代理处理此任务:
- 任务标签: "批量重命名文件"
- 任务描述: "扫描 src/ 目录,将所有 .js 文件重命名为 .ts,保持文件名不变"

子代理执行流程

  1. 使用 list_dir 工具列出 src/ 目录
  2. 过滤出所有 .js 文件
  3. 对每个文件使用 exec 工具执行 mv 命令
  4. 返回重命名结果摘要

子代理提示词(自动生成):

# 子代理 (Subagent)

你是主代理创建的子代理,专门负责完成特定任务。

## 你的任务
扫描 src/ 目录,将所有 .js 文件重命名为 .ts,保持文件名不变

## 工作规则
1. **专注任务**: 只完成分配的任务,不做其他事情
2. **简洁高效**: 最终响应会报告给主代理,保持简洁但信息完整
3. **不要闲聊**: 不要发起对话或承担额外任务
4. **彻底完成**: 确保任务完整完成,提供清晰的结果总结

## 可用能力
- 读写工作空间文件
- 执行 Shell 命令
- 使用所有标准工具

## 工作空间
/home/user/project

## 完成标准
任务完成后,提供清晰的总结:
- 完成了什么
- 遇到的问题(如果有)

案例 2:多代理代码审查

场景:对一个 Pull Request 进行全面审查。

Agent Team 配置

{
"name": "PR 审查团队",
"mode": "council",
"cross_review": true,
"enable_skills": false,
"agents": [
{
"id": "security_reviewer",
"role": "安全审查员",
"system_prompt": "你是安全专家。审查代码时关注:\n- SQL 注入、XSS 等常见漏洞\n- 敏感信息泄露\n- 认证和授权问题\n- 加密和数据保护\n\n提供具体的安全建议和修复方案。",
"perspective": "安全性"
},
{
"id": "performance_reviewer",
"role": "性能审查员",
"system_prompt": "你是性能优化专家。审查代码时关注:\n- 算法复杂度\n- 数据库查询效率\n- 内存使用\n- 并发和异步处理\n\n提供性能优化建议和预期收益。",
"perspective": "性能"
},
{
"id": "quality_reviewer",
"role": "质量审查员",
"system_prompt": "你是代码质量专家。审查代码时关注:\n- 代码可读性和命名规范\n- 设计模式和架构\n- 测试覆盖率\n- 文档完整性\n\n提供代码质量改进建议。",
"perspective": "代码质量"
}
]
}

执行流程

  1. 三个审查员并行分析代码
  2. 每个审查员从自己的视角提供反馈
  3. 如果启用交叉评审,审查员之间互相评论
  4. 主代理综合所有反馈,生成最终审查报告

案例 3:技能系统集成

场景:用户请求"生成一张日落的图片"。

主代理识别

  • 检测到用户需要图片生成功能
  • 查找可用技能,发现 image-gen 技能
  • 创建子代理,启用技能系统

子代理提示词(包含技能注入):

## 可用技能(Skills)

**重要**: 技能不是工具!技能是包含命令行调用示例的文档,
需要先读取文档,再使用 exec 工具执行其中的命令。

以下技能已启用:

### image-gen
**描述**: AI 图片生成工具
**文档路径**: skills/image-gen/SKILL.md
**使用场景**: 根据文本描述生成图片

**正确使用流程**:
1. 使用 read_file 读取技能文档: read_file(path='skills/image-gen/SKILL.md')
2. 阅读文档中的命令行示例
3. 使用 exec 工具执行文档中的命令

子代理执行

  1. 调用 read_file('skills/image-gen/SKILL.md')
  2. 阅读文档,了解命令格式
  3. 调用 exec('python skills/image-gen/scripts/generate.py --prompt "日落" --output sunset.png')
  4. 返回生成的图片路径

提示词缓存优化

原则

构建提示词时,确保可以通过追加方式扩展,避免使缓存失效。

不建议的做法

# 每次都在系统提示中包含当前时间
system_prompt = f"""
你是 AI 助手。
当前时间: {datetime.now()}
...
"""

每次时间变化都会使整个提示缓存失效。

推荐做法

# 系统提示保持静态
system_prompt = """
你是 AI 助手。
...
"""

# 动态信息放在用户消息中
user_message = f"""
[系统信息]
当前时间: {datetime.now()}

[用户请求]
{user_input}
"""

CountBot 的实现

# 系统提示保持静态,可以被缓存
messages = [
{"role": "system", "content": static_system_prompt},
]

# 动态信息追加到对话历史
messages.append({
"role": "user",
"content": f"[当前时间: {current_time}]\n\n{user_message}"
})

总结

核心要点

  1. 上下文优先:提供完整、相关的上下文是最重要的
  2. 保持一致:系统提示、工具定义、工具输出要相互一致
  3. 详尽但不冗余:不要害怕长提示,但避免重复信息
  4. 友好的错误处理:返回描述性错误,让模型自行恢复
  5. 工具调用优化:去重、截断、迭代限制
  6. 缓存友好:静态内容放系统提示,动态内容放用户消息

设计检查清单

在设计代理系统时,检查以下项目:

  • 提示词是否提供了完整的上下文?
  • 系统提示、工具定义、工具输出是否一致?
  • 是否明确定义了代理的能力边界?
  • 是否有迭代限制防止无限循环?
  • 工具调用是否有去重机制?
  • 错误处理是否友好且描述性强?
  • 是否考虑了提示词缓存优化?
  • 多代理协作模式是否适合任务特点?
  • 是否有进度通知和状态持久化?
  • 是否有测试用例验证提示词效果?

参考资源

本指南综合了以下来源的最佳实践:

  • CountBot 项目实现(参见 Agent Loop智能体团队
  • Augment Code 的提示词工程技巧
  • Claude Code 的代理设计理念
  • 其他成熟代理系统的架构模式
  • 业界多代理系统研究论文

下一步