【TS版 2026 从零学Langchain 1.x】(二)结构化输出和工具调用

66 阅读16分钟

🔥2026从零学Langchain——Python版本:
【Python版 2026 从零学Langchain 1.x】(一)快速开始和LCEL
【Python版 2026 从零学Langchain 1.x】(二)结构化输出和工具调用
【Python版 2026 从零学Langchain 1.x】(三)Agent和Memory

🔥2026从零学Langchain——TS版本:
【TS版 2026 从零学Langchain 1.x】(一)快速开始和LCEL
【TS版 2026 从零学Langchain 1.x】(二)结构化输出和工具调用
【TS版 2026 从零学Langchain 1.x】(三)Agent和Memory

一、结构化输出 - Structured

1. 概念介绍

这里的结构化输出主要是指大模型可以输出符合要求的JSON数据,JSON数据的正确分两方面来看:

  • JSON格式正确。比如{"count":1}正确,而{"count":1不正确。
  • JSON的字段语义正确。比如定义了count字段为数值,那么{"count":1}正确,而{"count":"一"}不正确。

Langchain(ts版)提供了- ZodJSON Schema 2种方式来定义结构,常用的是Zod.

但是早期模型其实不支持JSON格式化输出(API层不提供支持),所以早期的Langchain是通过注入指令/提示词 + 正则提取的方式实现。

到2026年了,很多主流模型都是支持格式化输出了,几乎成了一种标准能力。

2. 返回对象(词典)

🌰例子:我想要了解某个电影的信息,并期望以结构化(普通对象)的数据返回。

import { settings } from "@/config";
import { ChatOpenAI } from "@langchain/openai";
import { z } from "zod";
import { CommaSeparatedListOutputParser } from "@langchain/core/output_parsers";

const model = new ChatOpenAI({
  model: settings.glm_model,
  apiKey: settings.siliconflow_api_key,
  configuration: {
    baseURL: settings.siliconflow_base_url,
  },
  temperature: 0.9,
  maxTokens: 5000,
  timeout: 60_000,
});

const MovieSchema = z
  .object({
    title: z.string().describe("电影名称"),
    year: z.number().int().describe("电影上映时间"),
    director: z.string().describe("电影的导演"),
    rating: z.number().describe("电影的豆瓣评分"),
  })
  .describe("电影的相关信息");

/**
 * 测试结构化输出
 */
async function testStructureClass() {
  const modelWithStructure = model.withStructuredOutput(MovieSchema);
  const response = await modelWithStructure.invoke([
    {
      role: "user",
      content: "介绍下电影《罗小黑战记2》,获取title、year、director、rating信息",
    },
  ]);
  console.log(typeof response); // object
  console.log(response);
  /*
  {
    title: "罗小黑战记2:在那个夏日",
    year: 2023,
    director: "不亦舟",
    rating: 8.8,
  }
  */
}

可以看出,我的提示词里面并没有要求LLM如何提取信息,但是返回结果却是按照我给的Zod模型定义提取了信息。(P.S. 这里的代码,Langchain可没有去做增强我们的提示词的事哦,而是把结构化输出的任务交给了LLM provider)

现在主流LLM都支持结构化输出,看看openAI的文档,这些LLM provider内部会做工程化,完成结构化输出这个能力,并提供API。

LLM provider内部工程化,会使用提示词要求大模型按"格式"输出。此外,一方面对大模型加掩码控制一些不合法的输出,另一方面对输出的结果进行校验(如果不满足Zod的校验,则反馈错误给大模型重试)

最终提取的信息准确度,取决于提示词和LLM的能力。比如介绍下电影《罗小黑战记2》,获取title、year、director、rating信息 会比 介绍下电影《罗小黑战记2》 效果会更好点。

添加环境变量export LANGCHAIN_VERBOSE=true可以控制是否打印Langchain的中间过程。因此可以在.env中间添加下面内容,然后观察示例代码的输出:

# 是否打印langchain的详细日志
LANGCHAIN_VERBOSE=true

另外,我还做了下实验:下面是我用deepseek v3.2 作为LLM, 跑上面代码,打印的中间信息。 一方面,输出时间很长。 另一方面,可以看出LLM返回了一个序列化的JSON数据,title重复了。 说明deepseek v3.2在结构化输出这方面能力还是较差。

image.png

3. 返回列表结构

🌰例子:我希望LLM返回的结果是数组

/**
* 测试结构化输出(数组)
*/
async function testStructureList() {
  // 使用LLM provider API 强制结构化输出
  const modelWithStructure = model.withStructuredOutput(DevProcessListSchema);

  const response = await modelWithStructure.invoke([
    {
      role: "user",
      content: "软件开发的流程是?请给我一个有顺序的字符串数组",
    },
  ]);
  console.log(Array.isArray(response)); //true
  console.log(response); 
  // [ "需求分析", "系统设计", "编码实现", "软件测试", "部署上线", "运维监控", "版本迭代" ]

}

你可以看出model.withStructuredOutput这种方式,LLM是严格返回列表(序列化的json)

image.png

还有种方式,很多教程都有提到——StructuredOutputParser,核心代码如下:

import { CommaSeparatedListOutputParser, StructuredOutputParser } from "@langchain/core/output_parsers";

async function testStructureList() {
 
  // 使用langchain自己的解析器 获取结构化输出(可靠性一般)
  const outputParser = StructuredOutputParser.fromZodSchema(DevProcessListSchema);
  const modelWithStructure = model.pipe(outputParser);

  const response = await modelWithStructure.invoke([
    {
      role: "user",
      content: "软件开发的流程是?请给我一个有顺序的字符串数组",
    },
  ]);
  console.log(Array.isArray(response)); //true
  console.log(response); 
  // [ "需求分析", "系统设计", "编码实现", "软件测试", "部署上线", "运维监控", "版本迭代" ]

}

LLM的实际输出如下,并不是一个序列化的JSON(而是一段markdown)。

image.png

但是Langchain还是能正确给我们返回 一个 数组。 原因在于StructuredOutputParser解析器,解析器会在用户提示词注入格式化指令(也是提示词),然后对结构进行正则匹配拿到数组/对象。 而model.withStructuredOutput则是通过LLM provider提供的API参数,明确获取结构化数据(几乎100%可靠)

@langchain/core/output_parsers 提供了结构化输出的解析器XxxParser都依赖Langchain的指令注入和正则匹配,可靠性一般(很可能直接导致应用瘫痪),生产环境更推荐model.withStructuredOutput

P.S 关于列表生成,首先推荐model.withStructuredOutput。模型无JSON结构输出能力则使用PydanticOutputParser ,而不是CommaSeparatedListOutputParser,因为CommaSeparatedListOutputParser是Langchain根据逗号进行分割和去空格的方式,准确性会存在问题,比如下面:

  • LLM的返回:
"text": "以下是标准软件开发流程(SDLC)的字符串列表:\n\n1. 需求分析\n2. 系统设计\n3. 开发实施\n4. 软件测试\n5. 部署上线\n6. 运维与迭代"
  • langchain解析后返回:
['以下是标准软件开发流程(SDLC)的字符串列表:', '1. 需求分析', '2. 系统设计', '3. 开发实施', '4. 软件测试', '5. 部署上线', '6. 运维与迭代']

4. agent的结构化输出策略

在 LangChain(尤其是在较新版本的 createAgent 或 LangGraph 架构中)中,ToolStrategyProviderStrategy 是实现**结构化输出(Structured Output)**的两种核心策略。

简单来说,它们的区别在于“是利用模型原生的结构化能力,还是通过‘欺骗’模型调用工具来间接实现结构化”。

createAgent的参数 responseFormat 支持下面4个类型值:

  • ProviderStrategy 使用提供者原生的结构化输出
  • ToolStrategy 使用工具调用以获得结构化输出
  • schema Zod对象 - 根据模型能力自动选择最佳策略
  • None 无格式化要求(默认)

代码示例:

async function testStrategy(){
  const ContactInfoSchema = z
    .object({
      name: z.string().describe("The name of the person"),
      email: z.string().describe("The email address of the person"),
      phone: z.string().describe("The phone number of the person"),
    })
    .describe("Contact information for a person.");

  const tools: never[] = [];
  const systemPrompt = "你是信息抽取助手。请根据用户输入抽取联系人信息。";

  const input = {
    messages: [
      {
        role: "user" as const,
        content:
          "从下面文本中提取联系人信息(name/email/phone):张三,邮箱 zhangsan@example.com,电话 13800000000。",
      },
    ],
  };

  const agentAuto = createAgent({
    model,
    tools,
    systemPrompt,
    responseFormat: ContactInfoSchema,
  });

  const agentProvider = createAgent({
    model,
    tools,
    systemPrompt,
    responseFormat: providerStrategy(ContactInfoSchema),
  });

  const agentTool = createAgent({
    model,
    tools,
    systemPrompt,
    responseFormat: toolStrategy(ContactInfoSchema),
  });

  const autoResult = await agentAuto.invoke(input);
  console.log("auto", autoResult.structuredResponse);
  /*
  auto {
    name: "张三",
    email: "zhangsan@example.com",
    phone: "13800000000",
  }
  */
  try {
    const providerResult = await agentProvider.invoke(input);
    console.log("provider", providerResult.structuredResponse);
  } catch (err) {
    console.error("provider failed", err);
  }

  const toolResult = await agentTool.invoke(input);
  console.log("tool", toolResult.structuredResponse);
  /*
  tool {
    name: "张三",
    email: "zhangsan@example.com",
    phone: "13800000000",
  }
  */
}

ToolStrategy vs. ProviderStrategy 的区别

维度ToolStrategy (工具策略)ProviderStrategy (厂商原生策略)
实现机制模拟工具调用:将你需要的输出 Schema 包装成一个“虚构工具”,让模型去调用它。原生 API 支持:直接利用模型厂商提供的结构化输出功能(如 OpenAI 的 JSON Mode 或 Strict Mode)。
兼容性极高:只要模型支持工具调用(Function Calling),就能使用。有限:仅支持提供原生结构化接口的厂商(如 OpenAI, Anthropic, Google)。
可靠性中等:依赖模型对工具参数的遵循能力。最高:厂商在模型底层和 API 层做了强校验,输出更稳定。
使用场景模型不支持原生结构化输出,或需要高度通用的代码实现时。追求最高成功率和严谨的 Schema 校验时。
默认行为在模型不支持原生结构化时作为备选方案(Fallback)。LangChain 在识别到支持的模型时会默认优先选择。

选择建议

  • 什么时候用 ProviderStrategy? 只要你的模型支持(如 GPT-4o, Claude 3.5 Sonnet),永远优先使用 ProviderStrategy。它在底层有更强的约束,能够减少模型胡言乱语(Hallucination)或格式错误的概率。
  • 什么时候用 ToolStrategy? 当你使用的模型较旧、或是某些国产/开源模型仅支持 Function Calling 但没有专门的 JSON Schema 模式时,使用 ToolStrategy 是实现结构化数据提取的唯一可靠途径。

大多数情况下,建议直接传入schema(Zod对象),让Langchain自己选择策略。

二、工具调用 - Tools

1. 工具调用的演变

主流 LLM 实现工具调用的方式经历了三个阶段,这决定了它们对结构化输出的依赖程度:

第一阶段:纯 Prompt 时代的“软约束”

  • 做法:在 Prompt 里写:“如果你想查天气,请输出 JSON 格式:{"action": "weather", "city": "xxx"}”。
  • 现状:这是早期的做法(如 GPT-3 时代)。
  • 问题:模型经常“掉链子”,生成的格式不对,这就往往需要正则来提取函数名和参数。此时,工具调用非常依赖模型的自觉性,解析错误率高。

第二阶段:模型微调的“半强约束”(主流现状)

  • 做法:OpenAI (GPT-3.5/4)、Anthropic (Claude 3/3.5)、Google (Gemini) 对模型进行了专门的工具调用微调。
  • 现状:模型看到 tools 参数时,会进入一种“工具模式”。
  • 依赖关系:虽然模型努力输出结构化 JSON,但由于没有底层的硬性限制,它仍然可能偶尔输出错误的 JSON 结构

第三阶段:语法级别(Grammar)的“硬约束”(即 Structured Outputs)

  • 做法:这是 OpenAI 在 2024 推出的功能(strict: true)。
  • 原理:在模型生成 Token 的每一瞬间,系统会根据 JSON Schema 过滤掉所有不符合语法的 Token。
  • 现状这是工具调用的终极形态。此时,工具调用与结构化输出完全合为一体。如果定义了 Schema,模型物理上不可能输出格式错误的 JSON。

2. Function Calling

OpenAI最早提出Function Calling 的概念和功能。Function Calling 就是一种Tools Calling (在Langchain中所有LLM provider的 "工具使用",包括Function Calling 都被抽象为Tools Calling)。

Function Calling 的流程(5 个步骤):

  1. 定义函数:你在调用 API 时,提供一份“说明书”(JSON Schema),告诉模型你有几个函数、它们的作用是什么、需要什么参数。
  2. 模型判断:用户提问(如“帮我查下明天的北京天气”)。模型发现这匹配了你定义的函数,于是返回一个特殊的“函数调用请求” JSON。
  3. 程序执行:你的后端代码解析这个 JSON,实际运行对应的函数(如去访问气象局 API),并拿到结果。
  4. 结果反馈:你将函数的运行结果(如“北京明天多云转晴,20度”)发回给模型。
  5. 自然回复:模型根据这个真实数据,组织语言给用户一个自然的最终回答。

关键特性

  • 并行调用 (Parallel Function Calling):模型可以一次性决定调用多个函数。例如,问“北京和伦敦天气如何?”,模型会一次性输出两个函数调用指令。
  • 强制/自动模式 (tool_choice):你可以强制模型必须调用某个工具,或者让它根据对话自行判断是否需要。
  • 结构化输出 (Structured Outputs):OpenAI 的最新版本保证了输出的参数百分之百符合你定义的格式要求,极大地提高了生产环境的稳定性。

3. Function Calling vs. MCP的区别

先说总结:==MCP是依赖Function Calling 的能力,是对Function Calling (简称FC) 能力的拓展。FC是一种基础能力,而MCP是一种架构协议(这意味这个更好拓展,有利于系统级别开发)。==

MCP 包含了3部分

  • MCP Host:AI 软件(如 Claude Desktop)。
  • MCP Client:协议层,负责协调。
  • MCP Server:数据源或工具的提供者(如 Google Drive 插件、数据库查询器)。

在FC中,需要定义函数...调用函数,这些都是在定义流程,并确保LLM能完成这一流程。

而MCP,则是明确分工了。MCP Server负责定义工具/函数;MCP Client负责把这些工具/函数 暴露给LLM并识别LLM的结果调用工具;

这个MCP Server从 「主应用」中抽离出来,可以被多个「应用」复用——只要「应用」实现了MCP Client。

因此,MCP还定义了 MCP Client和 MCP Server之间的通信协议。

4. Langchain 的 Tools设计

工具定义和使用

基础使用

这里我先假设一个场景——电影分析
用户希望能准确获取一些《罗小黑》电影的分析,具体来说,希望分析出为什么有人喜欢,有人吐槽。那么就需要真实的影评数据。

首先我们定义一个函数(模拟能从数据库获取 影评 数据)

const getReviews = tool(
  ({ positive }: { positive: boolean }) => {

    const positiveReviews = [
      "原来两三岁的小孩也可以不扯女孩裙子啊;原来不整屎尿屁也可以做出让全场大笑的效果啊;原来女角色也可以不穿超短裙高开叉高跟鞋啊;原来男师父女徒弟也可以不暧昧纯师徒情啊;原来一个动画片里正派之间也可以有不同的价值观啊;原来不喊口号不献祭亲朋好友父老乡亲也能表达反战的思想啊。罗小黑你还是太超前了。",
      "瑕不掩瑜。非常好的一点是,一点儿爹味都没有,不judge任何人(妖精),没有任何人(妖精)需要被打败或悔过。这在中国的大型说教重灾区———国漫中已是十分可贵。",
      "“无限虽然爱装逼,但是他没有跟鹿野搞花千骨,此乃一胜;没有跟罗小黑搞黑猫和他的蓝发师尊,此乃二胜;没有和哪吒搞男同,此乃三胜”",
      "我宣布鹿野是我唯一的姐!太帅了!!!工装裤配T恤,低马尾,非传统女性角色,太帅了5555555希望越来越强,早日拳打无限脚踢各大长老!!! 以及,真是好多场经费爆炸的打斗啊",
    ];
    const negativeReviews = [
      "呃…片方到底懂不懂自己的IP魅力在哪啊!搞什么武器、战争的宏大场面啊,又搞不明白,妥妥露怯!整个剧情就是,稀碎…",
    ];
    return positive ? positiveReviews : negativeReviews;
  },
  {
    name: "get_reviews",
    description: "获取罗小黑电影评论列表",
    schema: z.object({
      positive: z.boolean().describe("是否获取正面评论, true 表示正面,false 表示负面"),
    }),
  },
);

使用 tool 函数包裹你定义的工具函数,并注册成Langchain可调用的工具了。namedescriptionschema 定义了这个工具的名称、用途和参数含义,用来暴露给LLM。

测试 是否能正常调用Tool,代码如下,可以看出LLM识别出我们的意图,然后响应说“你可以发起函数调用”


/**
 * 测试工具调用
 */
async function testToolCalling() {
  
  // const reviews = await getReviews.invoke({ positive: true });
  // console.log(reviews); // 输出reviews数组: ["原来两三岁...",...]

  const tools = [getReviews];
  
  const modelWithTools = model.bindTools(tools);

  const response = await modelWithTools.invoke("请分析罗小黑电影的负面评论原因?");
  const toolCalls = getToolCallsFromResponse(response);
  for (const toolCall of toolCalls) {
    console.log(`Tool: ${toolCall.name}`);
    console.log(`Args: ${JSON.stringify(toolCall.args)}`);
  }
  /* 输出
    Tool: get_reviews
    Args: {"positive":false}
  */
}

模型输出的结果如图:

image.png

tool_calls数组有内容,说明是成功调用了工具,希望我们提供影评数据。下面就带上影评数据进行第二请求。


/**
 * 测试工具调用2(测试多轮对话)
 */
async function testToolCalling2() {
  
  const tools = [getReviews];
  const toolByName = Object.fromEntries(tools.map((t) => [t.name, t])) as Record<
    string,
    (typeof tools)[number]
  >;
  const modelWithTools = model.bindTools(tools);

  const prompt = "请分析罗小黑电影的正面评论原因?";
  const response = await modelWithTools.invoke(prompt);

  const toolMessages: ToolMessage[] = [];
  const toolCalls = getToolCallsFromResponse(response);
  for (const toolCall of toolCalls) {
    console.log(`Tool: ${toolCall.name}`);
    console.log(`Args: ${JSON.stringify(toolCall.args)}`);
    const tool = toolByName[toolCall.name];
    if (!tool) {
      throw new Error(`Unknown tool: ${toolCall.name}`);
    }
    // @ts-expect-error
    const reviews = await tool.invoke(toolCall.args);
    toolMessages.push(
      new ToolMessage({
        content: JSON.stringify(reviews),
        tool_call_id: toolCall.id ?? "",
      }),
    );
  }

  const finalResponse = await modelWithTools.invoke([
    new HumanMessage({ content: prompt }),
    response,
    ...toolMessages,
  ]);
  console.log(finalResponse.content);
  /*输出:
    Tool: get_reviews
    Args: {"positive":true}
    基于获取到的正面评论,我来为您分析罗小黑电影受欢迎的主要原因:
    ## 罗小黑电影正面评论分析
    ### 1. **突破传统套路,创新性强**
    观众普遍认为这部电影"太超前了",主要体现在:
    - **儿童角色塑造**:两三岁的小孩角色不惹麻烦,有良好行为
    - **幽默表现手法**:不依赖粗俗的屎尿屁笑话也能制造全场爆笑效果
        ...
  */
}

这里用ToolMessage 来封装这些影评数据,然后和历史记录一起发送给LLM。

每个由工具返回的 ToolMessage 都包含一个与原始工具调用匹配的 toolCall.id ,这有助于模型将结果与请求关联起来。

下面贴出两次请求的提示词,你可以看到Langchain帮我做了那些事。

{
  "prompts": [
    "Human: 请分析罗小黑电影的正面评论原因?"
  ]
}
{
  "prompts": [
    "Human: 请分析罗小黑电影的正面评论原因?\nAI: 我来帮您获取罗小黑电影的正面评论,然后分析其中的正面评价原因。[{'name': 'get_reviews', 'args': {'positive': True}, 'id': '019bf500873b7d3b26cbb49ba71c4984', 'type': 'tool_call'}]\nTool: [\"原来两三岁的小孩也可以不扯女孩裙子啊;原来不整屎尿屁也可以做出让全场大笑的效果啊;原来女角色也可以不穿超短裙高开叉高跟鞋啊;原来男师父女徒弟也可以不暧昧纯师徒情啊;原来一个动画片里正派之间也可以有不同的价值观啊;原来不喊口号不献祭亲朋好友父老乡亲也能表达反战的思想啊。罗小黑你还是太超前了。\", \"瑕不掩瑜。非常好的一点是,一点儿爹味都没有,不judge任何人(妖精),没有任何人(妖精)需要被打败或悔过。这在中国的大型说教重灾区———国漫中已是十分可贵。\", \"“无限虽然爱装逼,但是他没有跟鹿野搞花千骨,此乃一胜;没有跟罗小黑搞黑猫和他的蓝发师尊,此乃二胜;没有和哪吒搞男同,此乃三胜”\", \"我宣布鹿野是我唯一的姐!太帅了!!!工装裤配T恤,低马尾,非传统女性角色,太帅了5555555希望越来越强,早日拳打无限脚踢各大长老!!! 以及,真是好多场经费爆炸的打斗啊\"]"
  ]
}

P.S. invoke会将Message列表 序列化成字符串,其中ToolMessage就是:

Tool: [工具调用返回的消息]
强制工具使用

默认情况下,模型会根据用户的输入自由选择使用哪个绑定工具。但是,你可能希望强制选择一个工具,确保模型使用特定的工具或给定列表中的任何工具:

model_with_tools = model.bindTools([tool_1], {toolChoice: "tool1"})
并行工具调用

许多模型在适当的情况下支持并行调用多个工具。这使模型能够同时从不同来源收集信息。

你留意到了吗?前面的toolCalls字段是一个数组,意味着可以调用多个工具。那么我们试一试下面的提问(只是把问题改了,看下结果):

"请分析罗小黑电影的正面评论原因和负面评论原因?"

现在问题同时包含“正面原因分析”和“负面原因分析”,LLM会调用两次工具 分别查询出正面评论和负面评论吗?

结论:会。

回答结果如下:

基于获取的评论数据,我来为您分析罗小黑电影的正面和负面评论原因:

## 正面评论的主要原因:
... 

## 负面评论的主要原因:
...
流工具调用

这里直接贴出官方的例子吧~

const stream = await modelWithTools.stream(
    "What's the weather in Boston and Tokyo?"
)
for await (const chunk of stream) {
    // Tool call chunks arrive progressively
    if (chunk.tool_call_chunks) {
        for (const tool_chunk of chunk.tool_call_chunks) {
        console.log(`Tool: ${tool_chunk.get('name', '')}`)
        console.log(`Args: ${tool_chunk.get('args', '')}`)
        }
    }
}

// Output:
// Tool: get_weather
// Args:
// Tool:
// Args: {"loc
// Tool:
// Args: ation": "BOS"}
// Tool: get_time
// Args:
// Tool:
// Args: {"timezone": "Tokyo"}

访问上下文

试想一下,如果下面的get_reviews想要访问一些其他信息,这些信息又不应该暴露给LLM使用,那么如何处理呢?Langchain给出了答案:ToolRuntime

const getReviews = tool(()=>{}, {name: "get_reviews"})

其实很简单,就是给你定义的工具函数传入一个ToolRuntime的参数(句柄),这个句柄可以获取上下文信息。

const ToolContext = z.object({
  userId: z.string().describe("用户ID"),
})

// 定义工具(测试运行时)
const getReviewsWithRuntime = tool(
  async ({ positive }: { positive: boolean }, config: ToolRuntime<any, typeof ToolContext>) => {
    console.log("参数positive", positive);
    console.log("1.查看对话的状态");
    console.log(config.state.messages);
    // [HumanMessage(content='请分析罗小黑电影的正面评论原因?'), AIMessage(content='我来帮您获取罗小黑电影的正面评论并分析其中的原因。'), ...]

    console.log("2.通过context 可以查询user的个人信息");
    console.log(config.context)
    // {
    //   userId: "user123",
    // }

    console.log("3.在长任务中,反馈进度,通常配合langgraph使用");
    const writer = config.writer;
    if(writer){
      writer({ status: "starting", message: "正在处理查询" });
      writer({ status: "progress", message: "完成50%" });
    }

    const positiveReviews = [
      "原来两三岁的小孩也可以不扯女孩裙子啊;原来不整屎尿屁也可以做出让全场大笑的效果啊;原来女角色也可以不穿超短裙高开叉高跟鞋啊;原来男师父女徒弟也可以不暧昧纯师徒情啊;原来一个动画片里正派之间也可以有不同的价值观啊;原来不喊口号不献祭亲朋好友父老乡亲也能表达反战的思想啊。罗小黑你还是太超前了。",
      "瑕不掩瑜。非常好的一点是,一点儿爹味都没有,不judge任何人(妖精),没有任何人(妖精)需要被打败或悔过。这在中国的大型说教重灾区———国漫中已是十分可贵。",
      "“无限虽然爱装逼,但是他没有跟鹿野搞花千骨,此乃一胜;没有跟罗小黑搞黑猫和他的蓝发师尊,此乃二胜;没有和哪吒搞男同,此乃三胜”",
      "我宣布鹿野是我唯一的姐!太帅了!!!工装裤配T恤,低马尾,非传统女性角色,太帅了5555555希望越来越强,早日拳打无限脚踢各大长老!!! 以及,真是好多场经费爆炸的打斗啊",
    ];
    const negativeReviews = [
      "呃…片方到底懂不懂自己的IP魅力在哪啊!搞什么武器、战争的宏大场面啊,又搞不明白,妥妥露怯!整个剧情就是,稀碎…",
    ];
    return positive ? positiveReviews : negativeReviews;
  },
  {
    name: "get_reviews_with_runtime",
    description: "获取罗小黑电影评论列表(演示工具运行时读取 config)",
    schema: z.object({
      positive: z.boolean().describe("是否获取正面评论, true 表示正面,false 表示负面"),
    }),
  },
);


运行agent,调用工具,查看上下文信息打印。


/**
 * 测试工具运行时
 */
async function testToolRuntime() {
  const agent = createAgent({
    model,
    tools: [getReviewsWithRuntime],
    systemPrompt: "你是影评分析助手,请在需要时调用工具获取评论再进行分析。",
    contextSchema: ToolContext
  });

  await agent.invoke(
    {
      messages: [
        {
          role: "user",
          content: "请分析罗小黑电影的负面评论原因?",
        },
      ],
    },
    { context: { userId: "user123" } },
  );
}

附录

代码 - 传送门

langchain_ts 篇2