学习笔记 · Obsidian

LangChain TypeScript:Agent、Model、Message 与 Tool

LangChainLangGraphTypeScript

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 才真正短路。不能把它当单个工具的强制全局终止开关。

动态工具的双层要求

运行时注册工具时,模型侧与执行侧必须同步:

  1. wrapModelCall 把本次允许的动态工具交给模型;
  2. 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 定义方式。

逐页覆盖

页面学习结论
Agentsagent loop、system prompt、state/context schema、动态模型/工具、structured output 和错误处理入口。
Models初始化、invoke/stream、参数、tool binding、profiles、retry、cache 与 provider 差异。
Messagesmessage types、content blocks、tool-call protocol、trim/filter/merge 和序列化语义。
ToolsZod schema、ToolRuntime、返回类型、Command、returnDirect、动态工具和生产约束。

延伸