学习笔记 · Obsidian
LangChain TypeScript:Agent、Model、Message 与 Tool
Agent loop 的真实边界
createAgent 组装的是一个循环:模型读取 state 中的消息和可用工具,选择直接回答或发起一个/多个 tool call;工具结果作为 ToolMessage 回到模型,直到模型不再调用工具。它返回 compiled LangGraph,因此可以 invoke、stream、checkpoint、interrupt,并接受 middleware。
Agent 不是权限系统。模型“能看到工具”与用户“被授权执行工具”是两件事:危险动作需要工具自身鉴权、参数校验、幂等、超时和审计,并在合适位置加 HITL。
Agent 配置
核心配置包括:
model:provider-qualified 字符串或已初始化 model;tools:静态工具集合,或由 middleware 动态暴露的工具;systemPrompt:字符串、SystemMessage,也可由 middleware 动态构造;responseFormat:结构化最终结果;stateSchema/contextSchema:扩展持久 state 与本次调用 context;middleware:围绕 agent/model/tool 的可组合控制层;checkpointer/store:短期线程状态与跨线程长期记忆。
自定义 state 应通过 reducer 定义合并语义;messages 是 append-oriented 通道,不能把并发更新当普通对象覆盖。持久会话必须给稳定的 thread_id,请求级依赖通过 context 传入,不要塞进消息正文。
Model:统一接口不等于能力完全一致
initChatModel("provider:model") 便于切换 provider,也可以直接构造 provider class 获取特有选项。统一接口覆盖 invoke/stream、tool calling、structured output、消息和 usage;但多模态、reasoning、server-side tools、native structured output、token accounting 和错误类型仍依赖 provider。
重要行为:
- JS chat model 的默认
maxRetries当前为 6,主要针对网络错误、429 和 5xx;401/404 不属于可通过重试解决的错误。 - 生产重试必须再加总时限、指数退避/jitter、请求幂等和上游并发控制,不能把 SDK 默认值当完整韧性策略。
- Model profiles 在
langchain >= 1.1引入且仍标为 beta,用于描述 tool calling、structured output 等能力;不能仅凭 model 名字长期假设能力不变。 withStructuredOutput、bindTools等会返回包装后的 model;测试替身也应覆盖这些路径。- 缓存只适合可安全复用的纯读取响应;涉及用户权限、时效数据或副作用时需设计 cache key、TTL 与隔离。
文档部分段落出现 max_retries、use_responses_api 等 Python 风格写法。TypeScript 实现应以 TS snippet、JavaScript reference 和编译器为准。
Message:协议对象,不只是字符串
常见类型:
| 类型 | 用途 |
|---|---|
SystemMessage | 系统行为和全局约束 |
HumanMessage | 用户输入 |
AIMessage | 模型文本、reasoning、tool calls、usage 等 |
ToolMessage | 对特定 tool_call_id 的执行结果 |
content 可以是文本,也可以是 provider/native 内容块;标准化视图通过 contentBlocks 访问 text、reasoning、image、audio、file、citation、tool call 等块。需要 provider 原始字段时放在对应 metadata,而业务层应优先消费标准块,减少锁定。
工具消息协议最重要的不变量是:每个 AIMessage.tool_calls 必须有且只有一个匹配 ID 的 ToolMessage。并行调用也不能漏项、重复或错配;裁剪/摘要消息历史时必须把 tool call 和结果视为原子组。
JavaScript 消息 API 的命名并不统一:
AIMessage.tool_calls、ToolMessage.tool_call_id保留 snake_case;message.contentBlocks使用 camelCase;- message-like 输入可用
{ role, content },但复杂消息优先构造明确 class; - 不要先
JSON.stringify再把字符串当 message 传回模型。
Tool:模型可见契约 + 服务端执行边界
典型工具由 tool(handler, { name, description, schema }) 创建。schema 首选 Zod,因为它同时提供 TypeScript 推断与运行时校验;也支持 JSON Schema,但 Quickstart 明确提醒:直接 JSON Schema 输入并不自动获得同等运行时验证,执行前仍要校验不可信参数。
高质量工具应满足:
- 名称短且稳定,跨 provider 时优先
snake_case; - description 说明何时用、何时不用,而不只是复述函数名;
- schema 范围窄、枚举明确、字段有描述;
- 返回给模型的是完成任务所需的最小结果,不泄露内部对象和凭证;
- 外部副作用可鉴权、幂等、超时、取消、审计和恢复。
工具 handler 可读取 ToolRuntime,获得 context、state、store、stream writer 和当前 toolCallId。它可以返回:
- string / object:成为普通工具结果;
- 多模态内容:文本、图片等块;
Command:在返回结果同时更新 graph state;- 直接返回模式
returnDirect:跳过后续 model turn。
returnDirect 的细节是:若同一 AI turn 并行调用多个工具,只有当全部已调用工具都声明直接返回时,agent 才真正短路。不能把它当单个工具的强制全局终止开关。
动态工具的双层要求
运行时注册工具时,模型侧与执行侧必须同步:
wrapModelCall把本次允许的动态工具交给模型;wrapToolCall能根据 tool call 找到并执行该动态实现。
只完成第一层会出现“模型会调用但 executor 找不到”;只完成第二层则模型不知道工具存在。动态工具还必须按 tenant/user/context 做授权过滤,避免共享 registry 泄漏能力。
TypeScript 对 Python 的差异摘要
- 工具输入通常用 Zod,不是 Pydantic/Annotated。
- 配置字段多为 camelCase,但消息 wire fields 仍可能 snake_case。
ToolRuntime、state/context schema 和 agent 泛型可提供编译期约束;这不能替代边界处的运行时校验。- Promise/async iterator 统一承载异步,不使用 Python 的
ainvoke/astream命名模式。 - JS 文档特别说明多工具全部
returnDirect才能短路,并提供与前端共享的 headless tool 定义方式。
逐页覆盖
| 页面 | 学习结论 |
|---|---|
| Agents | agent loop、system prompt、state/context schema、动态模型/工具、structured output 和错误处理入口。 |
| Models | 初始化、invoke/stream、参数、tool binding、profiles、retry、cache 与 provider 差异。 |
| Messages | message types、content blocks、tool-call protocol、trim/filter/merge 和序列化语义。 |
| Tools | Zod schema、ToolRuntime、返回类型、Command、returnDirect、动态工具和生产约束。 |