☰
第 1 章学习笔记 · Agent Harness 与最小运行结构
2026/10/17 17:50:39 网站建设 项目流程

1. 三层架构:Runtime / Framework / Harness

三层各管什么

层

包

版本

职责

一句话记法

Runtime

langgraph

1.2.13

把「调模型 → 执行工具 → 再调模型」编译成可执行的图,管状态流转和终止条件

管循环

Framework

langchain/langchain-core

1.4.3 / 1.6.6

模型封装、消息类型、工具定义、Schema 生成

管零件

Harness

deepagents

0.7.22

在 LangGraph 之上预装文件系统、子 Agent、待办清单

管预装

关键理解

  • 三层是自底向上层层构建,不是互相替代。四个包在同一个进程里共存。
  • create_agent和create_deep_agent不是两个框架。后者只是在前者基础上多装配了一批默认能力。

验证代码


2. 最小 Agent 的构造:create_agent的三个参

三个参数的含义

参数

作用

换掉会怎样

model

决定用哪个模型

换模型 → 工具选择能力、回复风格全变

tools

模型能调用的工具清单

不传 → 模型只能靠嘴回答,无法行动

system_prompt

每次调用都带上的行为约束

不写 → 模型倾向于只聊天不调工具

system_prompt和用户问题的区别(重要)

  • system_prompt:每一次模型调用都自动带上,是持续生效的约束
  • 用户问题:只在invoke时传进去,只影响这一轮

图节点怎么读

model和tools是执行步骤。模型要工具就走到tools,工具执行完回到model,循环直到模型不再请求工具。__start__/__end__是入口和出口标记。


3. 工具:从 Python 函数到模型看得懂的 Schema

核心认知

模型看不见你的 Python 代码。它看到的是一份由框架自动生成的 JSON 说明书(Schema)。

生成依据只有三样:

你函数里的

变成 Schema 里的

作用

类型标注amount: float

参数的type(float→number)

决定模型能传什么形状的值

docstring 正文

工具的description

模型靠这句话决定什么时候该用它

Args:下的逐行说明

每个参数的description

决定模型怎么填这个参数

默认值to_currency: str = "CNY"

该参数从required消失,并带default

模型可以不传它

定义一个工具(不需要装饰器)

普通函数放进tools=[...]就行。框架读函数名、类型标注、docstring。

打印 Schema

注意required里只有两个名字——to_currency因为有默认值,自动从必填清单消失。

为什么默认值是接口设计问题

参数该不该给默认值,直接决定模型的自由度:

  • 全给默认值 → 模型会偷懒不做判断
  • 全不给 → 模型被必填项卡住,只能反过来追问用户

docstring 那句话更关键:它决定了模型在什么场景下想起这个工具。工具好不好用,一半看实现,一半看这句描述。


4. 一次invoke的完整生命周期

四条消息(live 实测原样输出)

消息类型对照表

#

类型

角色

决定性字段

1

HumanMessage

用户输入

content

2

AIMessage

模型请求调工具

tool_calls(content是空字符串)

3

ToolMessage

工具真实执行结果

content/tool_call_id/status

4

AIMessage

模型基于结果作答

content,且tool_calls为空

两个AIMessage怎么区分

看有没有tool_calls:

  • 有 → 模型在"下单",content是空的
  • 没有 → 模型在"交货",这轮结束

为什么第 2 条的content是空的

两次输出通道分离的设计:模型一次可以输出给用户看的自然语言和给框架看的结构化调用请求。塞进同一个字符串字段会让解析变成字符串匹配游戏(引号、换行、嵌套括号都能让它崩)。拆成两个字段后,判断退化成布尔检查。

为什么tool_call_id必须是随机串

一次invoke里模型可以同时请求多个工具,返回顺序不保证(并发执行、耗时差异、重试都会打乱)。按位置配对在单工具场景能蒙对,一旦并发就是错的。

更重要的:随机 ID模型伪造不了。模型能编任意content,但编不出和真实请求匹配的随机 ID。这是区分"工具真跑了"和"模型自己说了算"的物理判据。

检查函数(照搬课程实现)

三个断言对应三个独立环节:请求正确 → 执行成功 → 对话收尾。

为什么"有返回消息"不算证据:参数校验失败同样会产生ToolMessage,只是status不是success。


5. Harness 白送的东西:10 个工具从哪来

同一个echo工具,两个 API 注册后的清单:

9 个白送工具的用途分组

组

工具

干什么

找

ls/glob/grep

列目录、按文件名匹配、按内容检索

读

read_file

读文件(图片/音频/PDF 走多模态打包)

改

write_file/edit_file/delete

写入、精确替换、删除

派活

task

委派子 Agent

执行

execute

跑 shell 命令

★ 本章最重要的一句话

「工具已注册」不等于「工具已调用」。

实测数据:注册 10 个,实际调用 1 个。那 9 个在本次任务里一个都没被碰过。

工具清单只描述能力边界,描述不了实际行为。

  • 判断"能做什么" → 看工具清单
  • 判断"做了什么" → 看消息列表里的ToolMessage

图节点也多了东西

多出来的是 Harness 装配的中间件节点。


6. offline 与 live:两种运行模式

offline

live

模型来源

脚本模型(ScriptedChatModel),应答预先写死

真实模型 API

是否读.env

不读

读

是否发网络请求

不发

发

工具函数与框架执行

真实执行

真实执行

证明了什么

机制成立

真实模型会正确选工具

怎么从数据上区分(这个最有价值)

两种模式都打印passed,光看日志分不出来。可靠判据是调用 ID 的形态:

模式

调用 ID

offline

ch01-echo—— 脚本里写死的,跑一百次都一样

live

call_43ca3a45a58748d5a7581326—— 服务端生成,每次不同

ID 随机与否,是"是不是真实模型"最硬的间接证据。


7. 源码里值得记住的常量与设计

工具按「只读 / 可变」分组

为什么这么分:只读工具之间没有副作用依赖,可以并行批量调用;可变工具会改状态,必须串行。分类结果直接决定并发策略。

大结果卸载(第一道防线)

工具返回值超过阈值,就从对话里卸载出去换成摘要。

排除名单

为什么文件工具被排除(源码注释给了三条理由):

  1. 自带截断 —— 结果本身就不会超限
  1. 卸载后会死循环 —— 模型看不到结果就再调一次,无限重复
  1. 永不超限的轻量操作

这是"框架替你做决定"的典型样本。知道它存在,能解释一类诡异现象:为什么某个工具返回了很长内容,上下文里却没被摘要掉。


8. 易错点清单

①os.environ[...]的语义

方括号语义是"取出一个名为xxx 的环境变量"。把密钥塞进去 = 让 Python 找一个名叫sk-7MA7xxxxxxxx的环境变量,必然KeyError。

② 密钥写进源码文件 = 公开

一旦文件进公开仓库,密钥就不再是秘密。正确做法:

三项配置同时提供,任何 OpenAI 兼容接口都能接入,不用改框架代码:

③ 中文 Windows 控制台编码

④ PyCharm 解释器的「名字」和「路径」不是一个东西

我看到Python 3.13 (agent-learn)这个名字,以为指向D:\agent-learn。实际它的homePath是D:/miniconda3/envs/yolo26/python.exe。

排查解释器问题,永远看路径,不看名字。


9. 自查清单

这一章学没学会,用这几个问题验:

#

问题

能答出来吗

1

三层各自负责什么?为什么说不是替代关系?

2

create_agent的三个参数分别是什么?换掉哪个会有什么后果?

3

两个AIMessage怎么区分?第 2 条的content为什么是空的?

4

ToolMessage里哪个字段能证明工具真的执行了?为什么模型伪造不了?

5

Deep Agent 白送几个工具?本次任务实际用了几个?这说明什么?

6

怎么从数据上判断一次运行是 offline 还是 live?

7

工具三要素分别变成 Schema 里的哪个字段?默认值会造成什么后果?

8

os.environ["sk-xxx"]错在哪?正确写法是什么?

9

为什么文件工具被排除在"大结果卸载"之外?

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询