Skip to content

Agent 开发2(4月7日报)

约 1137 字大约 4 分钟

日报Agent

2026-04-07

  • 继续学习 agent 把之前写的笔记补到这

子 Agent

"大任务拆小, 每个小任务干净的上下文" -- 子智能体用独立 messages[], 不污染主对话

问题

在前面的代码我们得知,Agent 的记忆是通过 message.append()来维持的
LLM 本身是无状态的,以我们必须把之前聊过的所有话、执行过的所有命令和结果,打包成一个的 messages 数组,每次都完整地发给它
因此如果 当 Agent 去寻找这个项目用了什么测试框架 ,连续调用了 5 次 read_file 工具,读取了 package.jsontox.initests/ 目录下的几个大文件。这 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"]),
}

模型知道有哪些技能 (便宜), 需要时再加载完整内容 (贵)