🔥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版)提供了- Zod、 JSON 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在结构化输出这方面能力还是较差。
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)
还有种方式,很多教程都有提到——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)。
但是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 架构中)中,ToolStrategy 和 ProviderStrategy 是实现**结构化输出(Structured Output)**的两种核心策略。
简单来说,它们的区别在于“是利用模型原生的结构化能力,还是通过‘欺骗’模型调用工具来间接实现结构化”。
createAgent的参数 responseFormat 支持下面4个类型值:
ProviderStrategy使用提供者原生的结构化输出ToolStrategy使用工具调用以获得结构化输出schemaZod对象 - 根据模型能力自动选择最佳策略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 个步骤):
- 定义函数:你在调用 API 时,提供一份“说明书”(JSON Schema),告诉模型你有几个函数、它们的作用是什么、需要什么参数。
- 模型判断:用户提问(如“帮我查下明天的北京天气”)。模型发现这匹配了你定义的函数,于是返回一个特殊的“函数调用请求” JSON。
- 程序执行:你的后端代码解析这个 JSON,实际运行对应的函数(如去访问气象局 API),并拿到结果。
- 结果反馈:你将函数的运行结果(如“北京明天多云转晴,20度”)发回给模型。
- 自然回复:模型根据这个真实数据,组织语言给用户一个自然的最终回答。
关键特性
- 并行调用 (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可调用的工具了。name、description和schema 定义了这个工具的名称、用途和参数含义,用来暴露给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}
*/
}
模型输出的结果如图:
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" } },
);
}