Agent 开发2(4月7日报)
- 继续学习 agent 把之前写的笔记补到这
子 Agent
"大任务拆小, 每个小任务干净的上下文" -- 子智能体用独立 messages[], 不污染主对话
问题
在前面的代码我们得知,Agent 的记忆是通过 message.append()来维持的
LLM 本身是无状态的,以我们必须把之前聊过的所有话、执行过的所有命令和结果,打包成一个的 messages 数组,每次都完整地发给它
因此如果 当 Agent 去寻找这个项目用了什么测试框架 ,连续调用了 5 次 read_file 工具,读取了 package.json、tox.ini、tests/ 目录下的几个大文件。这 5 个文件的源代码加起来可能有上万个字符;这些过程会被永久地塞进主 Agent 的 messages 数组里。随着它继续干其他活,这个数组会越来越胖,就会导致以下问题:
- API 费用大爆炸
- 上下文超载
- 注意力稀释 因此 我们需要 子 Agent 来解决以上的问题
解决方案
Parent agent Subagent
+------------------+ +------------------+
| messages=[...] | | messages=[] | <-- fresh
| | dispatch | |
| tool: task | ----------> | while tool_use: |
| prompt="..." | | call tools |
| | summary | append results |
| result = "..." | <---------- | return last text |
+------------------+ +------------------+
Parent context stays clean. Subagent context is discarded.工作原理
父智能体有一个 task 工具。子智能体拥有除 task 外的所有基础工具 (禁止递归生成)
# 拼接
PARENT_TOOLS = CHILD_TOOLS + [
{"name": "task",
# 生成一个带有全新上下文的子智能体
"description": "Spawn a subagent with fresh context.",
"input_schema": {
"type": "object",
"properties": {"prompt": {"type": "string"}},
"required": ["prompt"],
}},
]子智能体 message = [] , 运行自己的循环。只有最终文本返回给智能体
def run_subagent(prompt: str) -> str:
# 上下文物理隔绝
sub_messages = [{"role": "user", "content": prompt}]
for _ in range(30): # safety limit 防止死循环
response = client.messages.create(
model=MODEL, system=SUBAGENT_SYSTEM, # 专属提示词
messages=sub_messages,
tools=CHILD_TOOLS, max_tokens=8000, # 工具箱传入
)
sub_messages.append({"role": "assistant",
"content": response.content})
if response.stop_reason != "tool_use": # 任务完结判定
break
results = [] # 与主 Agent 的思路一致
for block in response.content:
if block.type == "tool_use":
handler = TOOL_HANDLERS.get(block.name)
output = handler(**block.input)
results.append({"type": "tool_result",
"tool_use_id": block.id,
"content": str(output)[:50000]})
sub_messages.append({"role": "user", "content": results})
# 信息蒸馏
return "".join(
b.text for b in response.content if hasattr(b, "text")
) or "(no summary)"技能
"用到什么知识, 临时加载什么知识" -- 通过 tool_result 注入, 不塞 system prompt
问题
你希望智能体遵循特定领域的工作流: git 约定、测试模式、代码审查清单。全塞进系统提示太浪费 -- 10 个技能, 每个 2000 token, 就是 20,000 token, 大部分跟当前任务毫无关系
解决方案
System prompt (Layer 1 -- always present):
+--------------------------------------+
| You are a coding agent. |
| Skills available: |
| - git: Git workflow helpers | ~100 tokens/skill
| - test: Testing best practices |
+--------------------------------------+
When model calls load_skill("git"):
+--------------------------------------+
| tool_result (Layer 2 -- on demand): |
| <skill name="git"> |
| Full git workflow instructions... | ~2000 tokens
| Step 1: ... |
| </skill> |
+--------------------------------------+Layer 1 : 系统提示中放技能名称(低成本) Layer 2 :tool_result 中按需放完整内容
工作原理
每个技能是一个目录, 包含 SKILL.md 文件和 YAML frontmatter
skills/
pdf/
SKILL.md # ---\n name: pdf\n description: Process PDF files\n ---\n ...
code-review/
SKILL.md # ---\n name: code-review\n description: Review code\n ---\n ...- 将大模型的 prompt 和 python 业务代码彻底物理解耦
- 方便添加新的 skills
SkillLoader 递归扫描 SKILL.md 文件, 用目录名作为技能标识
class SkillLoader:
# 传入路径,扫描 skills 注册 meta 字典
def __init__(self, skills_dir: Path):
self.skills = {}
# rglob 递归搜索,找出路径下所有 SKILL.mds
# 使用 sorted 防御性编程,去除操作系统的随机性等
# 保证了 Agent 行为的绝对确定性
for f in sorted(skills_dir.rglob("SKILL.md")):
text = f.read_text()
# 文档切片,上半部分为 meta 字典,下半部分为 body 字符串
# python 中 _ 开头意味着私有方法
meta, body = self._parse_frontmatter(text)
# 查找字典内的 name 如果没有则去拿父文件夹的文件名作为 name
name = meta.get("name", f.parent.name)
self.skills[name] = {"meta": meta, "body": body}
# 遍历上一步的词典,提取 skill 名和简介,拼接为字符串
def get_descriptions(self) -> str:
lines = []
# 遍历 skills 打包好的 tuple
for name, skill in self.skills.items():
desc = skill["meta"].get("description", "")
lines.append(f" - {name}: {desc}")
return "\n".join(lines)
# Layer2 核心
def get_content(self, name: str) -> str:
# 查字典:去 self.skills 里找键为 "code-review" 的数据包
skill = self.skills.get(name)
# 防幻觉
if not skill:
return f"Error: Unknown skill '{name}'."
# 格式化字符串,拼接数据
# 它取出 skill 里的 'body'
return f"<skill name=\"{name}\">\n{skill['body']}\n</skill>"第一层写入系统提示。第二层不过是 dispatch map 中的又一个工具
SYSTEM = f"""You are a coding agent at {WORKDIR}.
Skills available:
{SKILL_LOADER.get_descriptions()}"""
TOOL_HANDLERS = {
# ...base tools...
"load_skill": lambda **kw: SKILL_LOADER.get_content(kw["name"]),
}模型知道有哪些技能 (便宜), 需要时再加载完整内容 (贵)
