Skip to content

Agent 学习 (3月31日报)

约 1287 字大约 4 分钟

日报Agent

2026-03-31

  • 学习了agent

Agent 循环

"One loop & Bash is all you need" -- 一个工具 + 一个循环 = 一个智能体。

问题

大模型可以帮你推理代码,但是它不能帮你直接跑测试,生成文件,每一次工具的调用实际上都是你手动把结果粘贴回大模型,在这其中你自己粘贴回去的过程就是循环,为了解决来回复制的这个麻烦问题,我们就有了以下流程

解决方案

+--------+      +-------+      +---------+
|  User  | ---> |  LLM  | ---> |  Tool   |
| prompt |      |       |      | execute |
+--------+      +---+---+      +----+----+
                    ^                |
                    |   tool_result  |
                    +----------------+
                    (loop until stop_reason != "tool_use")
                    
                    
while stop_reason == "tool_use":
	response = LLM(messages, tools)
	execute tools
	append results
	
This is the core loop: feed tool results back to the model
until the model decides to stop. Production agents layer
policy, hooks, and lifecycle controls on top.

实现

完整的函数如下

def agent_loop(query):
  # 用户 prompt 作为第一条消息 
    messages = [{"role": "user", "content": query}]
    while True:
  # 将消息和工具定义一起发给 LLM 
        response = client.messages.create(
            model=MODEL, system=SYSTEM, messages=messages,
            tools=TOOLS, max_tokens=8000,
        )
  # 追加助手响应。检查 stop_reason -- 如果模型没有调用工具,则结束
        messages.append({"role": "assistant", "content": response.content})

        if response.stop_reason != "tool_use":
            return
  # 执行每个工具调用,收集结果,作为 user 消息追加,回到第 2 步
        results = []
        for block in response.content:
            if block.type == "tool_use":
                output = run_bash(block.input["command"])
                results.append({
                    "type": "tool_result",
                    "tool_use_id": block.id,
                    "content": output,
                })
        messages.append({"role": "user", "content": results})

Tools

加一个工具, 只加一个 handler" -- 循环不用动, 新工具注册进 dispatch map 就行

问题

只有 bash ,所有操作都走 shell,那么就会有以下问题 Agent 如果要查看代码文件,他只能使用 cat 指令

如果要查看的文件有几万行代码,会直接撑爆。 Agent 想要查看文末的 Bug ,但是因为 `s01` 中所写的暴力防撑爆系统(截断前 5000 个字符)无法获取不到文末内容,导致死循环

如果 Agent 想要修改文件里的一行代码,纯 bash 环境下它只能用 sedawk 或者 echo "代码" > file 代码里充满了引号 "、单引号 '、美元符号 $、反斜杠 \。当大模型试图把这些复杂的代码拼装成一行 sed 命令时,极大概率会写错转义符 导致报错 另外 bash 的权限太大了,很容易导致其他的问题

解决方案

+--------+      +-------+      +------------------+
|  User  | ---> |  LLM  | ---> | Tool Dispatch    |
| prompt |      |       |      | {                |
+--------+      +---+---+      |   bash: run_bash |
                    ^           |   read: run_read |
                    |           |   write: run_wr  |
                    +-----------+   edit: run_edit |
                    tool_result | }                |
                                +------------------+

The dispatch map is a dict: {tool_name: handler_function}.
One lookup replaces any if/elif chain.

实现

每个工具有一个处理函数。路径沙箱防止逃逸工作区

# 接受传入路径,检查路径是否安全
def safe_path(p: str) -> Path:
# 合并路径,计算绝对路径
    path = (WORKDIR / p).resolve()
# 防止逃逸出工作区
    if not path.is_relative_to(WORKDIR):
        raise ValueError(f"Path escapes workspace: {p}")
    return path
# 优化读取操作
def run_read(path: str, limit: int = None) -> str:
    text = safe_path(path).read_text()
    lines = text.splitlines()
# 按行数阅读
    if limit and limit < len(lines):
        lines = lines[:limit]
# 限制字符
    return "\n".join(lines)[:50000]

dispatch map 将工具名映射到处理函数

# 本质调度中心
TOOL_HANDLERS = {
    "bash":       lambda **kw: run_bash(kw["command"]),
    "read_file":  lambda **kw: run_read(kw["path"], kw.get("limit")),
    "write_file": lambda **kw: run_write(kw["path"], kw["content"]),
    "edit_file":  lambda **kw: run_edit(kw["path"], kw["old_text"],
                                        kw["new_text"]),
}

循环中按名称查找处理函数。循环体本身与 s01 完全一致

# 遍历 LLM 返回的内容
for block in response.content:
# 过滤工具调用相关内容出来
    if block.type == "tool_use":
# 找到对应的处理函数
        handler = TOOL_HANDLERS.get(block.name)
        output = handler(**block.input) if handler \
            else f"Unknown tool: {block.name}"
# 结果打包返回
        results.append({
            "type": "tool_result",
            "tool_use_id": block.id,
            "content": output,
        })

加工具 = 加 handler + 加 schema。循环永远不变

ToDoWrite

"没有计划的 agent 走哪算哪" -- 先列步骤再动手, 完成率翻倍

问题

在多步任务中,模型会丢失任务
因为上下文膨胀会导致系统提示的影响力逐渐被稀释。现在的 LLM 底层都是基于 Transformer 框架的,核心是 注意力机制

  • 注意力分配: 当模型阅读上下文时,它会给每一个词分配一个“注意力权重”
  • 近因效应(Recency Bias): 模型天生更容易关注最近发生的事情(即对话历史的最末端)
  • 稀释效应: 一开始,你的 System Prompt(比如:“你是一个严谨的重构专家,必须按步骤来”)在上下文中很显眼。但当中间塞进了几万字的工具执行日志后,System Prompt 被挤到了极遥远的“古代”。模型在计算注意力分布时,给系统提示词分配的权重变得微乎其微,它“忘了”自己的最初人设 一个 10 步重构可能做完 1-3 步就开始即兴发挥, 因为 4-10 步已经被挤出注意力了

实现原理

# TodoManager 存储带状态的项目。同一时间只允许一个 in_progress
class TodoManager:
    def update(self, items: list) -> str:
        validated, in_progress_count = [], 0
        for item in items:
            status = item.get("status", "pending")
            if status == "in_progress":
                in_progress_count += 1
            validated.append({"id": item["id"], "text": item["text"],
                              "status": status})
        if in_progress_count > 1:
            raise ValueError("Only one task can be in_progress")
        self.items = validated
        return self.render()
# 模型连续 3 轮以上不调用 `todo` 时注入提醒
if rounds_since_todo >= 3 and messages:
    last = messages[-1]
    if last["role"] == "user" and isinstance(last.get("content"), list):
        last["content"].insert(0, {
            "type": "text",
            # xml 标签
            "text": "<reminder>Update your todos.</reminder>",
        })