从0开始构建Claude Code

学习文档

模型是Agent,代码是harness

Agent = 一个神经网络,学会了感知,推理,行动。(训练好的模型)

核心公式:Agent是模型,代码是Harness

模型(Agent)做决策,做推理,有智能,有目标感。

代码(Harness)提供工具,提供知识,管理上下文,执行决策。
Harness = Tools + knowledge + observation + Action + permissions

类比驾驶者与载具。

模型就是驾驶者,有智能,有判断力,有目标感,决定去哪,怎么去。

而harness就是载具,有方向盘,油门,刹车,导航,传感器,执行驾驶者的决策

你不是在开发agent,你是在构建Harness

Harness工程师的五项工作

  • 1 实现工具:给Agent双手,比如文件读写,Shell,API
  • 2 策划知识:给Agent领域专长,按需加载,不前置塞入。
  • 3 管理上下文:给Agent干净记忆,隔离,压缩,持久化
  • 4 控制权限:给Agent边界,沙箱,审批,信任边界。
  • 5 收集数据:每条行动序列,训练下一代模型的原材料。

为什么拆解Claude Code?

因为他是最优雅的Agent Harness,不是因为某个巧妙的技巧,是因为它没做:

  • 没有强加 僵化的工作流
  • 没有用决策树 替模型判断
  • 没有视图成为Agent本身。
    他单纯只是:提供工具+知识+上下文+权限,然后让开交给大模型自己来。
Claude Code = Harness机制的组合

Agent = Claude (模型)

Harness = Agent Loop(循环) + Tools(工具) + Skill Loading(按需知识) + Context Compression(上下文压缩) + Subagent(子Agent派生) + Task System(任务图) + Worktree Isolation(隔离执行) + Permission Governance(权限治理) + ...

实现细节
  • 1-2 Agent Loop + Tool Use (循环基础:循环+工具派发)
  • 3-6 TodoWrite + SubAgent + Skills + compact (规划与知识:规划+隔离+知识+压缩)
  • 7-8 Tasks + Backgrorund (持久化:任务+后台)
  • 9-12: Trams + protocols + autonomous + worktree (团队协作:邮箱+协议+自治+隔离)

一 Agent Loop

一个循环 +一个工具 = 一个智能体

大模型能推理,但不能触碰真实世界。解决方案:让LLM能自动调用工具并且获取反馈,自动循环,程序驱动,自动闭环。

Agent Loop流程图

User Prompt -> LLM -> Tool -> ToolResult -> LLM-> 可能继续调用工具,可能停止调用,输出。

一个退出条件控制整个流程,如果stop_reason = tool_use,继续循环,执行工具,如果stop_reason = end_turn,结束循环,返回最终文本。

messages[] 累积式消息列表

他就是一个python数组,每轮对话,列表不断增长,这就是Agent的记忆。

代码实现
1 创建tools,这里采用openai风格的tools配置
python 复制代码
# 1 告诉LLM有一个bash的工具,接收一个command参数
TOOLS = [{
    "type": "function",
    "function": {
        "name": "bash",
        "description": "Run a shell command",
        "parameters": {
            "type": "object",
            "properties": {
                "command": {"type": "string"}
            },
            "required": ["command"]
        }
    }
}]
2 工具执行函数
python 复制代码
# 2 工具执行函数 run_bash()
def run_bash(command: str) -> str:
    # 安全过滤:危险命令拦截
    dangerous = ["rm -rf /", "sudo", "shutdown", ]
    if any(d in command for d in dangerous):
        return "Error: Dangerous command blocked"

    # 执行命令,开启子进程执行命令
    import subprocess
    r = subprocess.run(command, shell=True, capture_output=True, text=True, timeout=120)

    # 返回stdout + stderr,截断到50kb
    return (r.stdout + r.stderr).strip()[: 50000]

主要是创建一个子进程来执行对应的bash命令,并且对危险命令进行拦截,然后对打印信息进行截断,防止挤爆上下文

3 关键的循环
python 复制代码
# 3 agent loop
def agent_loop(messages: list):
    round_num = 0
    while True:
        round_num += 1
        print(f"\n========== 第 {round_num} 轮 ==========")

        # 1 调用 LLM
        print(f"[1] 调用 LLM,当前 messages 共 {len(messages)} 条")
        response = client.chat.completions.create(model="glm-4.5-air", messages=messages, tools=TOOLS)
        msg = response.choices[0].message
        finish_reason = response.choices[0].finish_reason
        print(f"[2] LLM 返回,finish_reason={finish_reason}")
        print(f"    content: {msg.content}")
        if msg.tool_calls:
            for tc in msg.tool_calls:
                print(f"    tool_call: {tc.function.name}({tc.function.arguments})")

        # 2 追加助手响应(含 tool_calls 字段)
        assistant_msg = {"role": "assistant", "content": msg.content}
        if msg.tool_calls:
            assistant_msg["tool_calls"] = [
                {
                    "id": tc.id,
                    "type": "function",
                    "function": {"name": tc.function.name, "arguments": tc.function.arguments}
                } for tc in msg.tool_calls
            ]
        messages.append(assistant_msg)
        print(f"[3] 已追加 assistant 消息到 messages")

        # 3 不调用工具就结束
        if finish_reason != "tool_calls":
            print(f"[4] 无工具调用,agent 结束\n")
            return

        # 4 执行每个工具调用,逐条追加 tool 消息
        print(f"[4] 开始执行工具调用,共 {len(msg.tool_calls)} 个")
        for tc in msg.tool_calls:
            if tc.function.name == "bash":
                args = json.loads(tc.function.arguments)
                print(f"    执行 bash: {args['command']}")
                output = run_bash(args["command"])
                print(f"    执行结果: {output[:200]}")
                messages.append({
                    "role": "tool",
                    "tool_call_id": tc.id,
                    "content": output
                })
                print(f"    已追加 tool 结果到 messages")

        # 回到while循环
        print(f"[5] 工具执行完毕,进入下一轮")
  • 首先我们调用模型,传入我们的问题,模型会返回对应的信息,比如是否需要调用工具等,将返回内容插入messages数组,防止丢失记忆。
  • 判断是否调用工具,不调用则表示结束。
  • 调用则循环tool_calls,逐一调用工具,如上,使用run_bash执行命令,并且将其返回结果加到messages中,作为tool信息。
  • 然后继续循环。

测试:

python 复制代码
if __name__ == "__main__":
    agent_loop(messages=[{
        "role": "system",
        "content": "你是一个专业的私人助手,在需要的时候调用工具,完成用户的需求"
    }, {"role": "user", "content": "帮我创建一个hello.py文件"}])

打印结果:

text 复制代码
========== 第 1 轮 ==========
[1] 调用 LLM,当前 messages 共 2 条
[2] LLM 返回,finish_reason=tool_calls
    content: 
我来帮你创建一个hello.py文件,包含一个简单的"Hello, World!"程序:

    tool_call: bash({"command":"echo 'print(\"Hello, World!\")' > hello.py"})
[3] 已追加 assistant 消息到 messages
[4] 开始执行工具调用,共 1 个
    执行 bash: echo 'print("Hello, World!")' > hello.py
    执行结果: 
    已追加 tool 结果到 messages
[5] 工具执行完毕,进入下一轮

========== 第 2 轮 ==========
[1] 调用 LLM,当前 messages 共 4 条
[2] LLM 返回,finish_reason=tool_calls
    content: 

    tool_call: bash({"command":"cat hello.py"})
[3] 已追加 assistant 消息到 messages
[4] 开始执行工具调用,共 1 个
    执行 bash: cat hello.py
    执行结果: print("Hello, World!")
    已追加 tool 结果到 messages
[5] 工具执行完毕,进入下一轮

========== 第 3 轮 ==========
[1] 调用 LLM,当前 messages 共 6 条
[2] LLM 返回,finish_reason=stop
    content: 
已经成功为您创建了hello.py文件!文件内容包含了一个简单的Python脚本,会打印"Hello, World!"。

您可以运行这个文件来测试:

python hello.py


或者如果您使用Python 3:

python3 hello.py

[3] 已追加 assistant 消息到 messages
[4] 无工具调用,agent 结束

如上,我们通过一个while循环+工具列表/工具调用+一个finish_reason,则完成了一个最简单的智能体。

二 Tool Use工具使用

上面第一节我们只用了bash,只用bash有什么问题?

  • cat读取不可预测,可能撑爆上下文。
  • sed编辑文件遇到特殊字符就崩溃
  • 安全风险,每次bash调用都是不受约束的安全面。

举个例子,如果只用cat读取,文件如果有10w行,可能会撑爆上下文,如果使用read_file工具我们可以限制limit行数,这就是专用工具的价值。

解决方案:给每个操作定义专用工具
  • bash执行命令
  • read_file 读取文件
  • write_file 写入文件
  • edit_file 编辑文件

Dispatch Map-字典分发,使用python字典,替换if else链。

那LLM怎么知道该用哪个工具呢,答案就是每个工具的description。描述写的越清晰,llm就能更近准选中,如果模糊,llm就会执行bash兜底

实现

首先,我们需要创建一个路径访问,而且需要限制他访问的路径

python 复制代码
# 工作目录沙箱,LLM 只能访问此目录内的文件
WORKDIR = Path(__file__).parent.resolve()
# 4 创建路径沙箱,防止llm读取其他隐私文件
def safe_path(p: str) -> Path:
    # 解析绝对路径
    path = (WORKDIR / p).resolve()
    # is_relative_to检测边界
    if not path.is_relative_to(WORKDIR):
        raise ValueError(f"Path escapes workspace: {p}")
    return path

如上,超出当前工作目录的就限制掉。

然后创建我们的工具

python 复制代码
# 5 定义三个新工具
def run_read(path, limit=None):
    text = safe_path(path).read_text()
    lines = text.splitlines()
    if limit and limit < len(lines):
        lines = lines[:limit]
    return "\n".join(lines)[:50000]


def run_write(path, content):
    fp = safe_path(path)
    # parents=True:如果中间层目录不存在也一并创建(类似 mkdir -p)
    # exist_ok=True:目录已存在时不报错,静默跳过
    fp.parent.mkdir(parents=True, exist_ok=True)
    fp.write_text(content)
    return f"Wrote {len(content)} bytes to {fp.name}"


def run_edit(path, old_text, new_text):
    fp = safe_path(path);
    content = fp.read_text();
    if old_text not in content:
        return "Error: Text not found"
    fp.write_text(content.replae(old_text, new_text, 1))

三个工具支持读写改。

接着,我们要定义对应的分发字典,类似于js的对象,策略模式。

python 复制代码
# 6 定义分发字典
TOOL_HANDLERS = {
    "bash": run_bash,
    "read_file": run_read,
    "write_file": run_write,
    "edit_file": run_edit
}

然后给我们的工具Schema增加三种工具

python 复制代码
TOOLS = [
    {
        "type": "function",
        "function": {
            "name": "bash",
            "description": "执行一条 shell 命令,返回 stdout + stderr",
            "parameters": {
                "type": "object",
                "properties": {
                    "command": {"type": "string", "description": "要执行的 shell 命令"}
                },
                "required": ["command"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "read_file",
            "description": "读取文件内容,可选限制返回行数",
            "parameters": {
                "type": "object",
                "properties": {
                    "path": {"type": "string", "description": "相对于工作目录的文件路径"},
                    "limit": {"type": "integer", "description": "最多返回的行数,不传则返回全部"}
                },
                "required": ["path"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "write_file",
            "description": "将内容写入文件,文件不存在则创建,已存在则覆盖",
            "parameters": {
                "type": "object",
                "properties": {
                    "path": {"type": "string", "description": "相对于工作目录的文件路径"},
                    "content": {"type": "string", "description": "要写入的文本内容"}
                },
                "required": ["path", "content"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "edit_file",
            "description": "替换文件中第一处匹配的文本片段",
            "parameters": {
                "type": "object",
                "properties": {
                    "path": {"type": "string", "description": "相对于工作目录的文件路径"},
                    "old_text": {"type": "string", "description": "要被替换的原始文本"},
                    "new_text": {"type": "string", "description": "替换后的新文本"}
                },
                "required": ["path", "old_text", "new_text"]
            }
        }
    }
]

descrption和paremters要写好,描述写得好,llm才更容易懂。

最后替换

python 复制代码
# 3 agent loop
def agent_loop(messages: list):
    round_num = 0
    while True:
        round_num += 1
        print(f"\n========== 第 {round_num} 轮 ==========")

       
        # 调用 LLM
        print(f"[1] 调用 LLM,当前 messages 共 {len(messages)} 条")
        response = client.chat.completions.create(model="glm-4.5-air", messages=messages, tools=TOOLS)
        msg = response.choices[0].message
        finish_reason = response.choices[0].finish_reason
        print(f"[2] LLM 返回,finish_reason={finish_reason}")
        print(f"    content: {msg.content}")
        print(f"    LLM想法: {msg.reasoning_content}")

        # 2 追加助手响应(含 tool_calls 字段)
        assistant_msg = {"role": "assistant", "content": msg.content}
        if msg.tool_calls:
            assistant_msg["tool_calls"] = [
                {
                    "id": tc.id,
                    "type": "function",
                    "function": {"name": tc.function.name, "arguments": tc.function.arguments}
                } for tc in msg.tool_calls
            ]
        messages.append(assistant_msg)
        print(f"[3] 已追加 assistant 消息到 messages")

        # 3 不调用工具就结束
        if finish_reason != "tool_calls":
            print(f"[4] 无工具调用,agent 结束 {messages}\n")
            return

        # 4 执行每个工具调用,逐条追加 tool 消息
        print(f"[4] 开始执行工具调用,共 {len(msg.tool_calls)} 个")
        for tc in msg.tool_calls:
            handler = TOOL_HANDLERS.get(tc.function.name)
            # handler = handelr if handelr else TOOL_HANDLERS["bash"]
            args = json.loads(tc.function.arguments)
            print(f"    执行 {tc.function.name}: {args}")
            handler = handler or TOOL_HANDLERS["bash"]
            output = handler(**args)
            print(f"    执行结果: {output[:200]}")
            messages.append({
                "role": "tool",
                "tool_call_id": tc.id,
                "content": output
            })
            print(f"    已追加 tool 结果到 messages")

        # 回到while循环
        print(f"[5] 工具执行完毕,进入下一轮")

主要就是

python 复制代码
 			handler = TOOL_HANDLERS.get(tc.function.name)
            # handler = handelr if handelr else TOOL_HANDLERS["bash"]
            args = json.loads(tc.function.arguments)
            print(f"    执行 {tc.function.name}: {args}")
            handler = handler or TOOL_HANDLERS["bash"]
            output = handler(**args)

handler通过分发字典获取。最后看执行结果

python 复制代码
if __name__ == "__main__":
    agent_loop(messages=[{
        "role": "system",
        "content": "你是一个专业的私人助手,在需要的时候调用工具,完成用户的需求"
    }, {"role": "user", "content": "帮我创建一个greet函数并且测试他"}])

第一轮:

text 复制代码
========== 第 1 轮 ==========
[0] 当前 messages 共 2 条:
  [0] system: 你是一个专业的私人助手,在需要的时候调用工具,完成用户的需求
  [1] user: 帮我创建一个greet函数并且测试他

[1] 调用 LLM,当前 messages 共 2 条
[2] LLM 返回,finish_reason=tool_calls
    content: 
我来帮你创建一个greet函数并测试它。首先创建一个Python文件:

    LLM想法: 
用户想要我创建一个greet函数并测试它。我需要:
1. 创建一个包含greet函数的文件
2. 写一些测试代码来测试这个函数

我先创建一个Python文件,包含一个简单的greet函数,然后添加一些测试代码。
[3] 已追加 assistant 消息到 messages
[4] 开始执行工具调用,共 1 个
    执行 write_file: {'path': 'greet_function.py', 'content': 'def greet(name="World"):\n    """\n    一个简单的问候函数\n    \n    Args:\n        name (str): 要问候的名字,默认为"World"\n    \n    Returns:\n        str: 问候语\n    """\n    return f"Hello, {name}!"\n\n# 测试代码\nif __name__ == "__main__":\n    # 测试默认问候\n    print("测试默认问候:")\n    print(greet())  # 应该输出: Hello, World!\n    \n    # 测试带参数的问候\n    print("\\n测试带参数的问候:")\n    print(greet("Alice"))  # 应该输出: Hello, Alice!\n    print(greet("Bob"))    # 应该输出: Hello, Bob!\n    \n    # 测试不同类型的问候\n    print("\\n测试不同类型的问候:")\n    print(greet("Python"))  # 应该输出: Hello, Python!\n    print(greet("World"))   # 应该输出: Hello, World!\n    \n    print("\\n所有测试完成!")'}
    执行结果: Wrote 599 bytes to greet_function.py
    已追加 tool 结果到 messages
[5] 工具执行完毕,进入下一轮

llm需要调用工具 write_file,参数是path,content

第二轮

python 复制代码
========== 第 2 轮 ==========
[0] 当前 messages 共 4 条:
  [0] system: 你是一个专业的私人助手,在需要的时候调用工具,完成用户的需求
  [1] user: 帮我创建一个greet函数并且测试他
  [2] assistant → tool_calls: write_file({"path":"greet_function.py","content":"def greet(n)
  [3] tool(id=call_-7762415718): Wrote 599 bytes to greet_function.py

[1] 调用 LLM,当前 messages 共 4 条
[2] LLM 返回,finish_reason=tool_calls
    content: 

    LLM想法: 
现在我已经创建了一个包含greet函数的Python文件。接下来我需要测试这个函数,通过运行这个Python文件来验证它是否工作正常。
[3] 已追加 assistant 消息到 messages
[4] 开始执行工具调用,共 1 个
    执行 bash: {'command': 'python greet_function.py'}
    执行结果: /bin/sh: python: command not found
    已追加 tool 结果到 messages
[5] 工具执行完毕,进入下一轮

第二轮llm知道已经创建文件后,他需要测试,执行bath,参数是python greet_funciton.py,结果报错了,因为我装的是python3,所以报错信息也追加到了message。

第三轮:

python 复制代码
========== 第 3 轮 ==========
[0] 当前 messages 共 6 条:
  [0] system: 你是一个专业的私人助手,在需要的时候调用工具,完成用户的需求
  [1] user: 帮我创建一个greet函数并且测试他
  [2] assistant → tool_calls: write_file({"path":"greet_function.py","content":"def greet(n)
  [3] tool(id=call_-7762415718): Wrote 599 bytes to greet_function.py
  [4] assistant → tool_calls: bash({"command":"python greet_function.py"})
  [5] tool(id=call_-7762394587): /bin/sh: python: command not found

[1] 调用 LLM,当前 messages 共 6 条
[2] LLM 返回,finish_reason=tool_calls
    content: 

    LLM想法: None
[3] 已追加 assistant 消息到 messages
[4] 开始执行工具调用,共 1 个
    执行 bash: {'command': 'python3 greet_function.py'}
    执行结果: 测试默认问候:
Hello, World!

测试带参数的问候:
Hello, Alice!
Hello, Bob!

测试不同类型的问候:
Hello, Python!
Hello, World!

llm知道bash命令报错后,又重新调用bash函数,参数是python3 xx,这会执行可以正常输出了,将输出结果加到messages,

最后一轮

python 复制代码
[5] 工具执行完毕,进入下一轮

========== 第 4 轮 ==========
[0] 当前 messages 共 8 条:
  [0] system: 你是一个专业的私人助手,在需要的时候调用工具,完成用户的需求
  [1] user: 帮我创建一个greet函数并且测试他
  [2] assistant → tool_calls: write_file({"path":"greet_function.py","content":"def greet(n)
  [3] tool(id=call_-7762415718): Wrote 599 bytes to greet_function.py
  [4] assistant → tool_calls: bash({"command":"python greet_function.py"})
  [5] tool(id=call_-7762394587): /bin/sh: python: command not found
  [6] assistant → tool_calls: bash({"command":"python3 greet_function.py"})
  [7] tool(id=call_-7762402077): 测试默认问候:\nHello, World!\n\n测试带参数的问候:\nHello, Alice!\nHello, Bob!\n\n测试不同类型的问候:\nHello, Python!\nHello, World!\n

[1] 调用 LLM,当前 messages 共 8 条
[2] LLM 返回,finish_reason=stop
    content: 
			完成!我已经为你创建了一个包含 `greet` 函数的 Python 文件并成功测试了它。
			函数已经创建并测试成功,可以正常使用了!</think>
			我已经成功创建了一个 `greet` 函数并进行了测试!
			你可以使用:
			- `greet()` - 默认问候 "Hello, World!"
			- `greet("你的名字")` - 问候指定的人,如 `greet("张三")` 会输出 "Hello, 张三!"
    LLM想法: None
[3] 已追加 assistant 消息到 messages
[4] 无工具调用,agent 结束

最后一轮当llm知道上一轮的python3 xxx运行成功后,就会直接结束返回结果。

现在我们没动循环,只是单纯加了几个工具,就可以让llm的能力更加强壮。

以后如果需要加一个工具,只需要:

  • 1 定义一个工具函数
  • 2 分发字典加上该工具
  • 3 TOOLS加上该工具,记得descrpiton等描述需要详细让llm知道哦。

三 TodoWrite 计划先行

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

上述我们给了大模型多个工具,如果我们给他一个复杂的任务。LLM可能会:

  • 重复步骤,做完的事情又做一遍,浪费token
  • 跳过步骤:当调用工具多了之后,上下文变多的话,大模型会跳过关键步骤,结果不完整。
  • 跑偏发散:上下文变多了之后,容易做着做着就去干别的,偏离目标。

具体表现在:如果有10步重构的步骤,前三步可以正常做,到第四步开始就会即兴发挥,因为工具的结果不断填满上下文,系统提示的影响力逐渐被稀释。

解决方案:给Agent一个待办清单工具

没有todo工具的时候

  • 接到任务,直接开干
  • 做完一步,想起下一步
  • 做到中间,忘了还有什么
  • 对话变长,完全迷失
    有了todo工具后,
  • llm接到任务,会先列计划,
  • 然后逐项完成,标记完成
  • 任何时候,都会知道进度
  • 有条不絮,不遗漏步骤。
架构

llm循环完全不懂,tool工具增加一个todo工具。

然后新增一个TodoManager todo状态管理,每次agent调用的时候都会更新TodoManager的状态。

此外还有一个Nag Reminder机制,要是连续三轮不更新计划,系统就会强制提示大模型,需要调用todo工具。

每个任务有三个状态

代办: 【】

进行中:【>】

已完成: 【x】

关键约束

同一时间,只能有一个任务在进行中,其他任务不能并行执行,这不是限制,而是保障,多个同时进行的话,agent可能在多个任务之间反复跳转。

代码实现

首先需要创建一个TodoManager类

python 复制代码
# 定义todo工具
class TodoManager:
    items: list

    def __init__(self):
        self.items = []

    # 检验text非空,检验status合法,in_progress <= 1
    def update(self, items: list) -> str:
        if len(items) > 20:
            raise ValueError("Max 20 todos allowed")
        validated = []
        in_progress_count = 0
        for i, item in enumerate(items):
            text = str(item.get("text")).strip()
            status = str(item.get("status", "pending"))
            item_id = str(item.get("id", str(i + 1)))
            # 检测text
            if not text:
                raise ValueError("text required")
            # 检测状态
            if status not in ("pending", "in_progress", "completed"):
                raise ValueError(f"Item {item_id}: invalid status '{status}'")
            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 in_progress")
        self.items = validated
        return self.render()

    # 渲染 todo 列表为文本
    def render(self) -> str:
        if not self.items:
            return "No todos";
        lines = []
        for item in self.items:
            marker = {
                "pending": "[ ]",
                "in_progress": "[>]",
                "completed": "[x]"
            }[item["status"]]
            lines.append(f"{marker} #{item["id"]} {item["text"]}")
        done = sum(1 for t in self.items if t["status"] == "completed")
        lines.append(f"({done}/{len(self.items)} completed)")
        return "\n".join(lines);


TODO = TodoManager()

主要有两个方法,update就是工具的执行函数,主要是检测状态等是否符合,判断只能有一个任务在执行中,其次render主要是渲染todo列表为文本。

往分发字典增加工具

python 复制代码
# 6 定义分发字典
TOOL_HANDLERS = {
    "bash": run_bash,
    "read_file": run_read,
    "write_file": run_write,
    "edit_file": run_edit,
    # 新增分发字典
    "todo": TODO.update,

}

接着是往TOOLS增加一个工具描述schema

json 复制代码
 {
        "type": "function",
        "function": {
            "name": "todo",
            "description": "更新待办任务列表,每次传入完整的任务列表来替换当前状态;同一时间只能有一个任务处于 in_progress",
            "parameters": {
                "type": "object",
                "properties": {
                    "items": {
                        "type": "array",
                        "description": "完整的任务列表,替换当前所有 todo",
                        "items": {
                            "type": "object",
                            "properties": {
                                "id": {"type": "string", "description": "任务唯一编号,如 '1'、'2'"},
                                "text": {"type": "string", "description": "任务内容描述"},
                                "status": {
                                    "type": "string",
                                    "enum": ["pending", "in_progress", "completed"],
                                    "description": "任务状态:pending=待办,in_progress=进行中,completed=已完成"
                                }
                            },
                            "required": ["id", "text", "status"]
                        }
                    }
                },
                "required": ["items"]
            }
        }
    }

然后在我们的循环加几行代码即可。

python 复制代码
# 3 agent loop
def agent_loop(messages: list):
    round_num = 0
    # 检测todo
    rounds_since_to = 0
    while True:
        round_num += 1
        print(f"\n========== 第 {round_num} 轮 ==========")

      

        # 调用 LLM
        print(f"[1] 调用 LLM,当前 messages 共 {len(messages)} 条")
        response = client.chat.completions.create(model="glm-4.5-air", messages=messages, tools=TOOLS)
       ......

        # 2 追加助手响应(含 tool_calls 字段)
        

        # 3 不调用工具就结束
        if finish_reason != "tool_calls":
            print(f"[4] 无工具调用,agent 结束\n")
            return

        # 4 执行每个工具调用,逐条追加 tool 消息
        used_todo = False # 新增内容!!!!!
        print(f"[4] 开始执行工具调用,共 {len(msg.tool_calls)} 个")
        for tc in msg.tool_calls:
            if tc.function.name == "todo": # 判断是否调用todo工具
                used_todo = True # 新增内容!!!!!
           ......
         

        rounds_since_to = 0 if used_todo else rounds_since_to + 1  # 重置计数器
	
        if rounds_since_to >= 3:# 超过三轮没调用,加入一个messages提醒llm调用todo工具。
            messages.append({"role": "user", "content": "<reminder>Update your todos.</reminder>"})
        # 回到while循环
        print(f"[5] 工具执行完毕,进入下一轮")

判断大模型知否超过三个循环没有调用todo了,是的话强制加入一条messages让其调用todo工具。

测试:

python 复制代码
if __name__ == "__main__":
    agent_loop(messages=[{
        "role": "system",
        "content": "你是一个专业的私人助手,在需要的时候调用工具,完成用户的需求"
    }, {"role": "user", "content": "构建一个爬虫脚本,爬取https://httpbin.org/html"}])

结果:

python 复制代码
========== 第 1 轮 ==========
[1] 调用 LLM,当前 messages 共 2 条
[2] LLM 返回,finish_reason=tool_calls
    content: 
我来帮您构建一个爬虫脚本。首先让我规划一下任务步骤:

[3] 已追加 assistant 消息到 messages
[4] 开始执行工具调用,共 1 个
    执行 todo: {'items': [{'id': '1', 'text': '分析目标网站结构和需求', 'status': 'pending'}, {'id': '2', 'text': '设计爬虫脚本架构和技术栈', 'status': 'pending'}, {'id': '3', 'text': '创建爬虫脚本主文件', 'status': 'pending'}, {'id': '4', 'text': '实现HTTP请求功能', 'status': 'pending'}, {'id': '5', 'text': '实现HTML解析功能', 'status': 'pending'}, {'id': '6', 'text': '实现数据存储功能', 'status': 'pending'}, {'id': '7', 'text': '添加错误处理和异常处理', 'status': 'pending'}, {'id': '8', 'text': '测试爬虫脚本功能', 'status': 'pending'}]}
    执行结果: [ ] #1 分析目标网站结构和需求
			[ ] #2 设计爬虫脚本架构和技术栈
			[ ] #3 创建爬虫脚本主文件
			[ ] #4 实现HTTP请求功能
			[ ] #5 实现HTML解析功能
			[ ] #6 实现数据存储功能
			[ ] #7 添加错误处理和异常处理
			[ ] #8 测试爬虫脚本功能
(0/8 completed)
    已追加 tool 结果到 messages
[5] 工具执行完毕,进入下一轮

可以看到,已经执行了todo工具,制定了8个待办任务。

python 复制代码
========== 第 6 轮 ==========
[1] 调用 LLM,当前 messages 共 12 条
[2] LLM 返回,finish_reason=tool_calls
    content: 
现在创建爬虫脚本主文件:

[3] 已追加 assistant 消息到 messages
[4] 开始执行工具调用,共 1 个
    执行 write_file: {'path': 'spider.py', 'content': '......'}
    执行结果: Wrote 4535 bytes to spider.py
    已追加 tool 结果到 messages
[5] 工具执行完毕,进入下一轮

到第六轮的时候,已经开始创建文件,这时候工具只会调用write_file,下一轮的时候

python 复制代码
========== 第 7 轮 ==========
[1] 调用 LLM,当前 messages 共 14 条
[2] LLM 返回,finish_reason=tool_calls
    content: 

[3] 已追加 assistant 消息到 messages
[4] 开始执行工具调用,共 1 个
    执行 todo: {'items': [{'id': '1', 'text': '分析目标网站结构和需求', 'status': 'completed'}, {'id': '2', 'text': '设计爬虫脚本架构和技术栈', 'status': 'completed'}, {'id': '3', 'text': '创建爬虫脚本主文件', 'status': 'completed'}, {'id': '4', 'text': '实现HTTP请求功能', 'status': 'completed'}, {'id': '5', 'text': '实现HTML解析功能', 'status': 'completed'}, {'id': '6', 'text': '实现数据存储功能', 'status': 'completed'}, {'id': '7', 'text': '添加错误处理和异常处理', 'status': 'completed'}, {'id': '8', 'text': '测试爬虫脚本功能', 'status': 'in_progress'}]}
    执行结果: [x] #1 分析目标网站结构和需求
[x] #2 设计爬虫脚本架构和技术栈
[x] #3 创建爬虫脚本主文件
[x] #4 实现HTTP请求功能
[x] #5 实现HTML解析功能
[x] #6 实现数据存储功能
[x] #7 添加错误处理和异常处理
[>] #8 测试爬虫脚本功能
(7/8 completed)
    已追加 tool 结果到 messages
[5] 工具执行完毕,进入下一轮

又会调用todo工具,同步todo列表,如上,已经在执行第八步了

接下来的三轮,llm要测试爬虫脚本 bash工具,然后要执行两轮read_file工具判断生成的内容是否正确,在第10轮的时候,因为已经超过三轮没有调用todo了,我们强制塞入了一个message

python 复制代码
========== 第 10 轮 ==========
[1] 调用 LLM,当前 messages 共 20 条
[2] LLM 返回,finish_reason=tool_calls
    content: 

[3] 已追加 assistant 消息到 messages
[4] 开始执行工具调用,共 1 个
    执行 read_file: {'path': 'moby_dick_content.txt'}
    执行结果: 标题: xxx
			来源: xxx
			爬取时间: xxx
			字数: xx
			字符数: xxx
			...
已追加 tool 结果到 messages
超过三轮没有todo了,需要提示llm

所以在第11轮的时候

python 复制代码
========== 第 11 轮 ==========
[1] 调用 LLM,当前 messages 共 23 条
[2] LLM 返回,finish_reason=tool_calls
    content: 

[3] 已追加 assistant 消息到 messages
[4] 开始执行工具调用,共 1 个
    执行 todo: {'items': [{'id': '1', 'text': '分析目标网站结构和需求', 'status': 'completed'}, {'id': '2', 'text': '设计爬虫脚本架构和技术栈', 'status': 'completed'}, {'id': '3', 'text': '创建爬虫脚本主文件', 'status': 'completed'}, {'id': '4', 'text': '实现HTTP请求功能', 'status': 'completed'}, {'id': '5', 'text': '实现HTML解析功能', 'status': 'completed'}, {'id': '6', 'text': '实现数据存储功能', 'status': 'completed'}, {'id': '7', 'text': '添加错误处理和异常处理', 'status': 'completed'}, {'id': '8', 'text': '测试爬虫脚本功能', 'status': 'completed'}]}
    执行结果: [x] #1 分析目标网站结构和需求
[x] #2 设计爬虫脚本架构和技术栈
[x] #3 创建爬虫脚本主文件
[x] #4 实现HTTP请求功能
[x] #5 实现HTML解析功能
[x] #6 实现数据存储功能
[x] #7 添加错误处理和异常处理
[x] #8 测试爬虫脚本功能
(8/8 completed)
    已追加 tool 结果到 messages
[5] 工具执行完毕,进入下一轮

执行了todo工具,对todo列表进行完善处理。

核心要点
  • TodoManager 结构化的进度追踪
  • 单in_progress 强制顺序聚焦
  • Nag Reminder 问责压力,不更新就催。
相关推荐
第一程序员3 小时前
Python与前端集成:构建全栈应用
python·github
程序员小远3 小时前
Jmeter基础:Jmeter聚合报告详解
自动化测试·软件测试·python·测试工具·jmeter·测试用例·压力测试
凌波粒3 小时前
D2L学习笔记:安装、张量与数据处理
笔记·python·学习·pandas
言之。3 小时前
Claude Code架构与设计原理深度解析(AI编程Agent核心课)
架构·ai编程
小村儿4 小时前
连载加餐01-claude code 源码泄漏 ---一起吃透 Claude Code,告别 AI coding 迷茫
前端·后端·ai编程
副露のmagic4 小时前
字符串章节 leetcode 思路&实现
windows·python·leetcode
BackCatK Chen4 小时前
Python安装与VSCode配置完整教程(每步带截图说明)
vscode·python·python安装·vscode配置·python 3.9.10·嵌入式环境配置
子昕4 小时前
Claude Code 源码意外泄露,我连夜拆了个底朝天:29 个子系统、6 层压缩、100+ 隐藏命令
ai编程
带刺的坐椅4 小时前
SolonCode CLI v2026.4.5 发布(编码智能体)
ai·llm·ai编程·cli·claudecode·opencode·sloncode