前端和小白都能看懂的 LangChain Model 模块核心实战指南

前言

注,langchain 更新速度很快,本文所有案例都经过试验,在 2025年12月可用!并且所有案例的 python 和 node.js 版本我都测试通过。

这是「写给小白学 AI」系列的第 5 篇文章,这个系列专门为刚接触人工智能的前端开发者和编程新手打造。如果你错过了之前的精彩内容,可以在这里补课:

同时也欢迎加入国内目前最好的 react headless 组件库项目

学习提示:为了让不同技术背景的同学都能跟上节奏,本文的代码示例会同时提供 Python 和 Node.js 版本,你可以选择自己熟悉的语言来实践。

langchain 跟 coze(扣子) 这种可视化制作 ai agent 的平台不同,langchain 是用代码制作 ai agent,coze 是相当于低代码平台制作 ai agent。

而学习 langchain 的核心就是学习它各种模块如何使用,最终会用这些模块做一些 ai agent 案例。所以接下来我们看看 langchain 核心模块有哪些。

langchain 核心模块

接下来讲 langchain 如何制作 ai agent 的本质,就是介绍如何调用 langchain 核心模块的 api。其中它的核心模块包括:

  • Model
  • Chains
  • Memory
  • Agents
  • Retrieval
  • Calllbacks

其实核心概念不止这些,可以查看官网,如下图,左侧是比较重要的概念,我们的系列文章主要是想在最短时间,走通changchain部分核心模块,其它模块会穿插在其中讲。

我们刚开始肯定不理解模块的具体作用,这个千万别在意,我们接下来关于 langchain 的文章都是解释这几个模块的用法。我们先从一个简单的 hello world 案例了解一下,如何在 langchain 中调用大模型,然后开始进入 Model 模块。

hello world 案例的 python 代码如下(后面会有代码解释,先看案例):

注意,没有 python 环境的,请查阅网上资料安装对应 python 代码运行环境。

首先安装必要的依赖

python 复制代码
pip install -U langchain langchain-openai python-dotenv

然后示例代码如下

python 复制代码
import dotenv
from langchain_openai import ChatOpenAI
import os

dotenv.load_dotenv()

llm = ChatOpenAI(
    model_name=os.getenv("MODEL_NAME"),
    temperature=0,
    api_key=os.getenv("OPENAI_API_KEY"),
    base_url=os.getenv("OPENAI_BASE_URL"),
)

response = llm.invoke("你好!")
print(response)

node.js 代码:

安装依赖:

bash 复制代码
npm install dotenv langchain @langchain/core @langchain/openai

创建 test.mjs 文件(.mjs 结尾的目的是用 es6 module 加载模块)

接着看 demo 代码:

javascript 复制代码
import dotenv from "dotenv";
import { ChatOpenAI } from "@langchain/openai";

dotenv.config(); // 加载 .env 文件

// 初始化 LLM
const llm = new ChatOpenAI({
  modelName: process.env.MODEL_NAME,
  temperature: 0,
  openAIApiKey: process.env.OPENAI_API_KEY,
  baseUrl: process.env.OPENAI_BASE_URL,
});

async function main() {
  const response = await llm.invoke("你好!");
  console.log(response);
}

main();

首先解释一下这些库:

  • dotenv 是用来加载环境变量的,外部会有一个.env 文件,可以书写环境变量,例如 我的 .env 文件如下,(其中 OPENAI_API_KEY 你要换成你自己的,这个是需要付费的,我购买的是 deepseek 的api, 充值 10 元就可以用很久,练习足够了):
ini 复制代码
OPENAI_API_KEY="sk-xxxx"
OPENAI_BASE_URL="https://api.deepseek.com"
MODEL_NAME="deepseek-chat"

例如上面定义了 OPENAI_BASE_URL 环境变量,在 python 文件中,我就可以通过

python 复制代码
# 加载 .env 中配置的环境变量
dotenv.load_dotenv()
# 通过 os 模块得到刚才加载的的环境变量的值
os.getenv("OPENAI_BASE_URL")

在 node.js中是

javascript 复制代码
dotenv.config(); // 加载 .env 文件
# 通过 process.env 模块得到刚才加载的的环境变量的值
process.env.OPENAI_BASE_URL
  • ChatOpenAI 就是调用 chatgpt api 的 langchain 的包,因为 deepseek 大体兼容 OpenAI 的 api (deepseek 也有自己独有的包,我们只是练习,可以忽略这个问题)

Model 模块

接下来我们进入到第一个 Model 模块的讲解。

Model 模块的作用是什么?

Model 模块的作用本质是抽象与统一对各类大模型(LLM、Chat Model、Embedding Model 等)的调用接口。简单来说就是不同的大模型有不同的 api,在 langchain 这里把调用它们的方式统一在一起。当然也有细微差别,例如 openai 有 openai 对应的 model 的包,Claude 有 Claude 对应的 model 包。但使用方式几乎都是一样的 。

并且返回的格式几乎也是一样的。这就是 Model 最基本的作用。

刚才在我们的 hello world 代码中,其实我们已经用了 Model 模块了。代码如下:

复制代码
llm.invoke

这个 invoke 就是 Model 模块其中一个核心的方法,简单来说就是一问一答, invoke 函数调用一次, 大模型回答一次。当然 Model 的功能远不止如此。

例如我们看到很多大模型的官网,我们问问题的时候,都是一个字一个字吐出来的,这种流式数据,在 Model 中,需要调用 stream 方法。我们后续介绍。

好了,简单理解 Model 就是用来调大模型 api 以及接受大模型返回数据的模块即可!

invoke 示例

我们刚才已经介绍了,这里再次复习一下,这里注意,我们可以链式调用:

python 案例

python 复制代码
import dotenv
from langchain_openai import ChatOpenAI
import os

dotenv.load_dotenv()

llm = ChatOpenAI(
    model_name=os.getenv("MODEL_NAME"),
    temperature=0,
    api_key=os.getenv("OPENAI_API_KEY"),
    base_url=os.getenv("OPENAI_BASE_URL"),
)

response = llm.invoke("你好!")
print(response)

node.js 案例

javascript 复制代码
import dotenv from "dotenv";
import { ChatOpenAI } from "@langchain/openai";

dotenv.config(); // 加载 .env 文件

// 初始化 LLM
const llm = new ChatOpenAI({
  modelName: process.env.MODEL_NAME,
  temperature: 0,
  openAIApiKey: process.env.OPENAI_API_KEY,
  baseUrl: process.env.OPENAI_BASE_URL,
});

async function main() {
  const response = await llm.invoke("你好!");
  console.log(response);
}

main();

需要注意,其中 ChatOpenAI 就是 Model 中用来调用 Chatgpt 的包。 invoke 方法的特性就是一次性使用,没有上下轮记忆功能,也就是第一次调 invoke 和 第二次调 invoke 没什么关系。(除非你把第一次 invoke 的内容作为第二次的历史发给大模型)。

我们上面定义的 invoke 的参数是字符串,它还支持一些别的类型。接下来我们一一介绍:

消息类型

除了字符串,langchain 还提供了一些消息类型,例如:

  • SystemMessage: 设定整体行为规范、角色定位、目标约束。例如:

    • 你是一个资深翻译专家
    • 输出 JSON 格式
  • HumanMessage:代表用户的自然语言输入,是用户真实提问或要求。也就是我们的问题。例如:

    • 1 + 1 等于几?
    • 鸟会飞吗?
  • AIMessage: 表示模型的先前回复,用于保留上下文。我们知道 invoke 没有记忆功能(大模型本身没有记忆功能)。我们如果想让其有记忆,就需要把之前的问题和结果在 invoke 函数中插入,而插入的类型,我们可以使用 AIMessage 来做。

  • ToolMessage 或者叫 FunctionMessage,模型调用工具(API Function)的结构化结果,例如:

    • 模型调用"搜索天气"后,我们的工具调用网络 api 去查询到底天气如何,假设返回:"北京 23 度,多云",该结果被包装成 ToolMessage,再输入给模型推理下一步逻辑。
    • 这个方式就是很多文章说到的 ai agent 的概念,叫 MCP。

我们举个例子:

python例子如下:

python 复制代码
import dotenv
from langchain_openai import ChatOpenAI
from langchain.messages import HumanMessage, SystemMessage
import os

dotenv.load_dotenv()

llm = ChatOpenAI(
    model_name=os.getenv("MODEL_NAME"),
    temperature=0,
    api_key=os.getenv("OPENAI_API_KEY"),
    base_url=os.getenv("OPENAI_BASE_URL"),
)

system_message = SystemMessage(content="你是一个数学方面的专家")
human_message = HumanMessage(content="1+1等于多少?")

messages = [system_message, human_message]

response = llm.invoke(messages)
print(response)

node.js例子如下:

javascript 复制代码
import dotenv from "dotenv";
import { ChatOpenAI } from "@langchain/openai";
import { HumanMessage, SystemMessage } from "langchain";

dotenv.config(); // 加载 .env 文件

// 初始化 LLM
const llm = new ChatOpenAI({
  modelName: process.env.MODEL_NAME,
  temperature: 0,
  openAIApiKey: process.env.OPENAI_API_KEY,
  baseUrl: process.env.OPENAI_BASE_URL,
});

const messages = [
  new SystemMessage("你是一个数学方面的专家"),
  new HumanMessage("1+1等于多少?"),
];

async function main() {
  const response = await llm.invoke(messages);
  console.log(response);
}

main();

可以看到,说白了,之前提问,我们直接用字符串,现在只不过用 SystemMessage 和 HumanMessage 实例化我们的问题而已,然后把问题区分了类型而已

  • SystemMessage:全局性的关键信息,或者说背景信息
  • HumanMessage:用户提问

流式响应

之前调用 invoke 方法,我们会等一会儿,接收到数据是一次性,例如我之前调用deepseek返回格式如下:

swift 复制代码
{
  "id": "953a9be0-f578-4477-9c63-b1ffcda1e3ee",
  "content": "这是一个非常基础的数学问题。  \n\n根据算术的基本定义:  \n\\[\n1 + 1 = 2\n\\]  \n\n如果你是在二进制系统中,那么:  \n\\[\n1_2 + 1_2 = 10_2\n\\]  \n(即十进制中的 2)  \n\n如果你是在布尔代数中,有时 \\(1 + 1 = 1\\)(逻辑或运算)。  \n\n不过,通常默认在十进制算术下,答案是 **2**。",
  "additional_kwargs": {},
  xxx省略其它参数。
}

以上是 node.js 的返回结果,是 json 格式,然后,我们可以看到 content 是一次性返回的。

有时候我们希望自己的 ai agent 返回数据能够一个字一个字的往外吐,跟很多大模型的官网一致。在langchain 中需要调用 stream 方法,我们上案例:

python例子如下:

python 复制代码
import dotenv
from langchain_openai import ChatOpenAI
from langchain.messages import HumanMessage, SystemMessage
import os

dotenv.load_dotenv()

llm = ChatOpenAI(
    model_name=os.getenv("MODEL_NAME"),
    temperature=0,
    api_key=os.getenv("OPENAI_API_KEY"),
    base_url=os.getenv("OPENAI_BASE_URL"),
)

system_message = SystemMessage(content="你是一个数学方面的专家")
human_message = HumanMessage(content="1+1等于多少?")


for chunk in llm.stream([system_message, human_message]):
    print(chunk.content, end="", flush=True)
print("\n---done---")

注意,在 python 中,需要使用 for xx in 语法来读取数据流。

node.js例子如下:

javascript 复制代码
import dotenv from "dotenv";
import { ChatOpenAI } from "@langchain/openai";
import { HumanMessage, SystemMessage } from "langchain";

dotenv.config(); // 加载 .env 文件

// 初始化 LLM
const llm = new ChatOpenAI({
  modelName: process.env.MODEL_NAME,
  temperature: 0,
  openAIApiKey: process.env.OPENAI_API_KEY,
  baseUrl: process.env.OPENAI_BASE_URL,
});

const messages = [
  new SystemMessage("你是一个数学方面的专家"),
  new HumanMessage("1+1等于多少?"),
];

async function main() {
  const stream = await llm.stream(messages);
  for await (const chunk of stream) {
    process.stdout.write(chunk.text);
  }
}

main();

这里 node.js 使用 for await ... of 语法来遍历返回的数据流。我们对这个语法简单介绍一下:

for await...of 用于遍历异步的 Iterator 接口, 我们简单举个例子来说明基本用法:

javascript 复制代码
// 模拟异步数据源
async function* getAsyncData() {
  const data = [1, 2, 3, 4, 5];
  for (const item of data) {
    // 模拟异步操作
    await new Promise(resolve => setTimeout(resolve, 100));
  }
}

async function processData() {
  console.log('开始处理...');
  
  for await (const value of getAsyncData()) {
    console.log(`处理结果: ${value}`);
    // 1秒后输出: 处理结果: 1
    // 2秒后输出: 处理结果: 2
    // ...
  }
  
  console.log('处理完成!');
}

processData();

其中注意我们打印数据使用的是:

arduino 复制代码
process.stdout.write(chunk.text);

而不是 console.log 的原因是,console.log 会让每次后端返回的数据后面加个换行符,为了不让其自动加换行符,我们用了 process.stdout.write api,相当于不加换行符的 console.log

批量调用

除了 invoke, stream 方法,langchain 还有提供一个 batch 方法,这个方法主要是用来批量调用的,简单来说就是同时发多个消息。我们举个例子:

python例子如下:

python 复制代码
import dotenv
from langchain_openai import ChatOpenAI
from langchain.messages import HumanMessage, SystemMessage
import os

dotenv.load_dotenv()

llm = ChatOpenAI(
    model_name=os.getenv("MODEL_NAME"),
    temperature=0,
    api_key=os.getenv("OPENAI_API_KEY"),
    base_url=os.getenv("OPENAI_BASE_URL"),
)

system_message = SystemMessage(content="你是一个数学方面的专家")
human_message = HumanMessage(content="1+1等于多少?")

message1 = [system_message, human_message]

system_message1 = SystemMessage(content="你是一个语文方面的专家")
human_message1 = HumanMessage(content="请问"草长莺飞"出自哪首诗?")

message2 = [system_message1, human_message1]

response = llm.batch([message1, message2])
print(response)

返回结果一个列表,我们上面发了两个信息,所以列表长度就是 2,相当于以前 invoke 是一个一个发消息,batch 是批量一起发。

node.js例子如下:

javascript 复制代码
import dotenv from "dotenv";
import { ChatOpenAI } from "@langchain/openai";
import { HumanMessage, SystemMessage } from "langchain";

dotenv.config(); // 加载 .env 文件

// 初始化 LLM
const llm = new ChatOpenAI({
  modelName: process.env.MODEL_NAME,
  temperature: 0,
  openAIApiKey: process.env.OPENAI_API_KEY,
  baseUrl: process.env.OPENAI_BASE_URL,
});

const messages1 = [
  new SystemMessage("你是一个数学方面的专家"),
  new HumanMessage("1+1等于多少?"),
];

const messages2 = [
  new SystemMessage("你是一个语文方面的专家"),
  new HumanMessage("请问"草长莺飞"出自哪首诗?"),
];

async function main() {
  const response = await llm.batch([messages1, messages2]);
  console.log("response: ", response);
}

main();

返回结果是一个数组,包含 messages1, messages2 分别返回的信息。相当于以前 invoke 是一个一个发消息,batch 是批量一起发。

ainvoke 和 astream

这两个 api 只在 python 版本中有。为什么呢?

invoke 是同步调用,ainvoke 是异步调用,异步就是不阻塞当前的代码,而 node.js 版本的 invoke 跟 python 默认是同步的调用完全不一样,node.js 中默认 invoke 是 promise,返回的也是 promise,所以默认就是异步的。所以不需要这两个 api。

接下来我们学习一下如何更复杂的方式跟 ai 提问。Model 组件提供了提示词模板功能。我们先看看什么是提示词模板。

提示词模板

我们先简单介绍一下 langchain 里的提示词模板是什么。

简单来说就是用于将动态变量嵌入固定文本模板,从而生成最终给模型的提示(prompt)。核心思想是"模板化+变量替换"。

举一个例子(以下是 node.js 案例):

  • 首先定义模板字符串

    • 定义固定文本和占位符
    • 占位符通常用 {变量名} 表示

    示例:

    css 复制代码
    你是一个数学专家,请帮我计算: {expression}
  • 输入变量(input_variables)

    • 模板中需要填充的变量
    • LangChain 会要求用户在生成最终 prompt 时提供这些变量
    ini 复制代码
    const template = "你是一个数学专家,请帮我计算: {expression}";
    const variables = { expression: "1+1" };

以下是 node.js 案例

  • 生成最终提示

    • 使用模板 + 变量 → 最终 prompt 文本
    • LangChain 会通过 PromptTemplate.format(variables) 或类似方法生成
    javascript 复制代码
    import { PromptTemplate } from "langchain";
    
    const prompt = new PromptTemplate({
      // 字符串模板
      template: "你是一个数学专家,请帮我计算: {expression}",
      // 意思是要填充到 template 的变量是 expression
      inputVariables: ["expression"]
    });
    
    const finalPrompt = prompt.format({ expression: "1+1" });
    console.log(finalPrompt); // 输出: 你是一个数学专家,请帮我计算: 1+1

其中生成模板的类型有好几种,如下(先列出来,后面一一介绍):

  • PromptTemplate: 最基础的文本模板,支持将变量插入到固定文本中。
  • FewShotPromptTemplate:用于 Few-Shot 学习场景,向模型提供示例问答,让模型理解期望格式,主要特点是可以往里面加 example,也就是案例。
  • ChatPromptTemplate(聊天模板):构建多角色聊天场景的模板(系统消息、用户消息、助手消息),这个模板中还可以嵌套一些多种消息类型,包括:SystemMessagePromptTemplateHumanMessagePromptTemplateAIMessagePromptTemplate

PromptTemplate

主要参数包括:

  • template:定义提示词模板字符串,其中包含文本和变量占位符,例如 {name}
  • input_variable: 指定了制定了模板中使用的变量名称,在调用模板的时候被替换
  • partial_variable: 用于定义模板中一些固定的变量名,也就是提前替换模板中的占位符

先来个案例:

python 版本:

python 复制代码
from langchain_core.prompts import PromptTemplate

prompt = PromptTemplate(
    template="你是一个{role},你的名字是: {name}",
    input_variables=["role", "name"],
    partial_variables={"name": "张三"},
)

result = prompt.format(role="数学专家")
print("result", result)  # 输出: 你是一个数学专家,你的名字是: 张三

node.js 版本:

javascript 复制代码
import { PromptTemplate } from "@langchain/core/prompts";

const prompt = new PromptTemplate({
  template: "你是一个{role},你的名字是: {name}",
  inputVariables: ["role", "name"],
  partialVariables: { name: "张三" },
});
async function runPrompt() {
  const result = await prompt.format({ role: "数学专家" });
  console.log("result", result); // 输出: 你是一个数学专家,你的名字是: 张三
}
runPrompt();

可以看到

  • template 定义了一个模板,通过 {xxx} 来告诉你能插入什么变量在其中
  • inputVariables,就是告诉能插入的变量名包括哪些
  • partialVariables,直接插入默认的变量名

最终通过调用 format 方法,来给模板插入变量,最终完成字符串的拼接。

同时,你可以还可以链式调用:

javascript 复制代码
import { PromptTemplate } from "@langchain/core/prompts";


async function runPrompt() {
  const prompt = await new PromptTemplate({
    template: "你是一个{role},你的名字是: {name}",
    inputVariables: ["role", "name"],
  }).partial({ name: "张三" });

  // 注意上面的 partial 的链式调用,partial 方法是帮我们提前填充了数据
  const result = await prompt.format({ role: "数学专家" });
  console.log("result", result); // 输出: 你是一个数学专家,你的名字是: 张三
}
runPrompt();

如何利用这个模板传给大模型呢?

因为我们发现实质上 format 方法其实最终返回的只是一个字符串,字符串如何发给大模型呢?之前是不是介绍了 Model 模块有个 invoke 方法,是不是调用后就传给大模型了。这下就联系起来上下文的知识了。

多轮对话与上下文记忆

上面我们说了,之前 invoke 的调用是一次性的,第一次提问和第二次提问是没有关系的是什么意思呢,我们用伪代码简单举个例子(node.js 代码):

javascript 复制代码
import dotenv from "dotenv";
import { ChatOpenAI } from "@langchain/openai";
import { HumanMessage, SystemMessage } from "langchain";

dotenv.config(); // 加载 .env 文件

// 第一个问题是告诉大模型我叫小智
const message1 = [new SystemMessage("我叫小智")];

const message2 = [new HumanMessage("我叫什么?")];

const llm = new ChatOpenAI({
  modelName: process.env.MODEL_NAME,
  temperature: 0,
  openAIApiKey: process.env.OPENAI_API_KEY,
  baseUrl: process.env.OPENAI_BASE_URL,
});

async function runPrompt() {
  const response1 = await llm.invoke(message1);

  const response2 = await llm.invoke(message2);

  console.log("Response to message1:", response2.content);
}
runPrompt();

返回的结果如下(我问的deepseek):

我是DeepSeek,一个AI助手,由深度求索公司创造。我很乐意成为你的朋友和助手,无论你有什么问题、想法还是需要帮助的地方,都可以随时告诉我。

你看,完全不记得我们第一个问题了。如何让 ai 记住呢?

我们拿上面的代码改造一下

javascript 复制代码
import dotenv from "dotenv";
import { ChatOpenAI } from "@langchain/openai";
import { HumanMessage, SystemMessage, AIMessage } from "langchain";

dotenv.config(); // 加载 .env 文件

// 第一个问题是告诉大模型我叫小智
const message1 = [new SystemMessage("我叫小智")];

const message2 = [new HumanMessage("我叫什么?")];

const llm = new ChatOpenAI({
  modelName: process.env.MODEL_NAME,
  temperature: 0,
  openAIApiKey: process.env.OPENAI_API_KEY,
  baseUrl: process.env.OPENAI_BASE_URL,
});

async function runPrompt() {
  const response1 = await llm.invoke(message1);
  // AIMessage 就是 ai 回答的内容
  const aiMessage = new AIMessage(response1.content);
  // 将 AI 的回答也加入到上下文中
  const response2 = await llm.invoke([...message1, aiMessage, ...message2]);

  console.log("Response to message1:", response2.content);
}

runPrompt();

这下我们看看deepseek如何回答:

你刚才告诉我,你的名字是 小智 !😊

如果这不是你真实的名字,或者你想让我用其他名字称呼你,随时告诉我哦~

我会记住的!

这下就相当于有上下文记忆能力了。

有人就会好奇,你说大模型没记忆能力是不是搞错了,明明我们在用这些大模型官网的时候,都有呀。其实这些大模型的聊天类型的产品,算是 ai agent 应用了,不是简单的大模型调用。

ChatPromptTemplate

之前的 PromptTemplate 本质上生成的是一个 字符串,接下来我们看看更复杂的模板 ChatPromptTemplate。它是创建聊天消息列表的提示模板。它比普通的 PromptTemplate 更是适合处理多角色,多轮次对话。

  • 它支持例如 System/Human/Ai 等不同角色的消息模板
  • 历史对话维护

我们先来个简单的案例,看看如何使用:

先看 python 代码:

python 复制代码
import dotenv
from langchain_openai import ChatOpenAI
from langchain.messages import HumanMessage, SystemMessage
from langchain_core.prompts import ChatPromptTemplate
import os

prompt = ChatPromptTemplate(
    messages=[
        ("system", "你是一个英语老师,你的名字是: {name}"),
        ("human", "我的问题是{question}"),
    ],
    input_variables=["name", "question"],
)

res = prompt.invoke(
    input={"name": "李四", "question": "What is the capital of France?"}
)
print("res", res)

返回内容是

ini 复制代码
messages=[SystemMessage(content='你是一个英语老师,你的名字是: 李四', additional_kwargs={}, response_metadata={}), HumanMessage(content='我的问题是What is the capital of France?', additional_kwargs={}, response_metadata={})]

可以看到内容跟 PromptTemplate 结果大不一样,返回的是一个列表(python把数组叫列表),然后包含一个SystemMessage,包含一个 HumanMessage。

以下是 node.js 案例

typescript 复制代码
import dotenv from "dotenv";
import { ChatOpenAI } from "@langchain/openai";
import { HumanMessage, SystemMessage } from "langchain";
import { ChatPromptTemplate } from "@langchain/core/prompts";

dotenv.config();

const prompt = ChatPromptTemplate.fromMessages([
  ["system", "你是一个英语老师,你的名字是: {name}"],
  ["human", "我的问题是{question}"],
]);

async function printResult() {
  const res = await prompt.invoke({
    name: "李四",
    question: "What is the capital of France?",
  });
  console.log("res: ", res);
}

printResult();

得到的结果如下:

javascript 复制代码
ChatPromptValue {
  // ... 省略无用信息
  messages: [
    SystemMessage {
      "content": "你是一个英语老师,你的名字是: 李四",
      "additional_kwargs": {},
      "response_metadata": {}
    },
    HumanMessage {
      "content": "我的问题是What is the capital of France?",
      "additional_kwargs": {},
      "response_metadata": {}
    }
  ]
}

可以看到最终结果是一个 messages 数组然后包含一个 SystemMessage,包含一个 HumanMessage。注意返回的是 ChatPromptValue 类型,跟之前 PromptTemplate 的 format 方法返回的 str 是不一样的。

小小总结一下

  • invoke 方法:传入字典(对象),返回 ChatPromptValue 类型
  • format 方法:传入变量的值(对象),返回字符串类型。

其实还有两种调用方式,跟上面是类似的方式,我们这里做个简单的介绍,如何调用以及返回的类型

  • python: format_message(), node.js formatMessage()
  • python: format_prompt(), node.js fromPromt()

这个很简单,我们只拿 node.js 举例,注意下面的 prompt.formatMessages 方法:

javascript 复制代码
const prompt = ChatPromptTemplate.fromMessages([
  ["system", "你是一个英语老师,你的名字是: {name}"],
  ["human", "我的问题是{question}"],
]);

async function printResult() {
  const res = await prompt.formatMessages({
    name: "李四",
    question: "What is the capital of France?",
  });
  console.log("res: ", res);
}

printResult();

返回的就是一个数组,内容为

css 复制代码
[  SystemMessage {    "content": "你是一个英语老师,你的名字是: 李四",    "additional_kwargs": {},    "response_metadata": {}  },  HumanMessage {    "content": "我的问题是What is the capital of France?",    "additional_kwargs": {},    "response_metadata": {}  }]

注意下面的 rompt.formatPromptValue 方法:

javascript 复制代码
const prompt = ChatPromptTemplate.fromMessages([
  ["system", "你是一个英语老师,你的名字是: {name}"],
  ["human", "我的问题是{question}"],
]);

async function printResult() {
  const res = await prompt.formatPromptValue({
    name: "李四",
    question: "What is the capital of France?",
  });
  console.log("res: ", res);
}

printResult(

返回:

css 复制代码
ChatPromptValue {
  // ...忽略其它属性
  messages: [
    SystemMessage {
      "content": "你是一个英语老师,你的名字是: 李四",
      "additional_kwargs": {},
      "response_metadata": {}
    },
    HumanMessage {
      "content": "我的问题是What is the capital of France?",
      "additional_kwargs": {},
      "response_metadata": {}
    }
  ]
}

好像上面的 prompt.formatPromptValue 和 prompt.invoke 方法传参和返回值都差不多,但一般情况都会用 invoke。

invoke 返回值可以使用到后面我们学习到的 chain 相关的方法(模型链),而 prompt.formatPromptValue 仅仅是返回 ChatPromptValue 类型的数据而已。

我们再来一个好玩的,了解即可, 就是 toString 方法,可以将 ChatPromptValue 类型转化为字符串,如下,注意 res.toString():

javascript 复制代码
const prompt = ChatPromptTemplate.fromMessages([
  ["system", "你是一个英语老师,你的名字是: {name}"],
  ["human", "我的问题是{question}"],
]);

async function printResult() {
  const res = await prompt.formatPromptValue({
    name: "李四",
    question: "What is the capital of France?",
  });
  console.log("res: ", res.toString());
}

printResult(); 

// 返回两行字符串,如下:
// System: 你是一个英语老师,你的名字是: 李四
// Human: 我的问题是What is the capital of France?

同理,还可以提取里面的 message,注意下面的 res.toChatMessages() 方法,能直接返回 message 消息数组:

javascript 复制代码
const prompt = ChatPromptTemplate.fromMessages([
  ["system", "你是一个英语老师,你的名字是: {name}"],
  ["human", "我的问题是{question}"],
]);

async function printResult() {
  const res = await prompt.formatPromptValue({
    name: "李四",
    question: "What is the capital of France?",
  });
  console.log("res: ", res.toChatMessages());
}

最终上我们其实得到的就是一个提示词模板,然后将其传入大模型的 invoke 方法就可以问大模型问题了。

MessagePlaceholder

MessagePlaceholder 就是一个占位符,什么意思呢,一看案例马上明白:

python 案例如下:

python 复制代码
import dotenv
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.messages import HumanMessage
import os

prompt = ChatPromptTemplate.from_messages(
    [
        ("system", "你是一个知识渊博的助手,擅长回答各种各样的问题。"),
        MessagesPlaceholder("history"),
    ]
)

res = prompt.invoke({"history": [HumanMessage(content="请问地球到月球的距离是多少?")]})


print("res", res)

是不是很简单,说白了,就是一个占位符,在prompt.invoke的时候传消息,而不是在 ChatPromptTemplate.from_messages 初始化的时候传消息。

node.js 代码如下:

javascript 复制代码
import { HumanMessage } from "langchain";
import {
  ChatPromptTemplate,
  MessagesPlaceholder,
} from "@langchain/core/prompts";

const prompt = ChatPromptTemplate.fromMessages([
  ["system", "你是一个英语老师,你的名字是: {name}"],
  new MessagesPlaceholder({ variableName: "history" }),
]);

async function printResult() {
  const res = await prompt.invoke({
    name: "小2",
    history: [new HumanMessage("What is the capital of France?")],
  });
  console.log("res: ", res);
}

printResult();

解释同上,也就是 MessagesPlaceholder 就是一个占位符,在 prompt.invoke 的时候传消息,而不是在 ChatPromptTemplate.from_messages 初始化的时候传消息。

FewShotPromptTemplate

我们知道,给大模型的问题当中,如果有一些案例,会极大的提高大模型回答的准确率,langchain 也提供了专门书写案例的提示词模板。

我们先介绍第一种最简单的跟提供案例相关的模板:FewShotPromptTemplate,FewShotPromptTemplate 必须要结合之前我们说的 PrompTemplate 一起使用。

使用方式有点绕,我们一步步的来解释,使用方式如下(使用方式 node.js 和python的都用python代码表示一下,后面具体案例会把两种代码都贴出来)

python 复制代码
few_shot_prompt = FewShotPromptTemplate(
    examples=examples,
    example_prompt=prompt,
    suffix="input:{input} output:",  # 声明在示例后面的提示词模板
    input_variables=["input"],
)
  • examples 代表你给大模型传入的样例是什么

例如:

python 复制代码
examples = [
    {"input": "我是小明", "output": "小明"},
    {
        "input": "我是小红",
        "output": "小红",
    },
    {
        "input": "我叫小刚",
        "output": "小刚",
    },
]

我们的案例其实就是从 input 的文字中,提取出名字。

  • example_prompt,也就是我们的提示词模板,这个模板是跟上面的 examples 相关的,也就是必须包含 examples 中的字典的 key,在我们案例里是需要包含 input 和 output 。案例如下:
python 复制代码
from langchain_core.prompts import PromptTemplate, FewShotPromptTemplate

prompt = PromptTemplate.from_template(
    template="input: {input} output:{output}",
)

最终 在 FewShotPromptTemplate 中会把上面的案例内容填充进去,也就是自动得到如下文字

lua 复制代码
input: 我是小明 output:小明\n\ninput: 我是小红 output:小红\n\ninput: 我叫小刚 output:小刚

好了,别忘了上面的 FewShotPromptTemplate 的用法,我

python 复制代码
few_shot_prompt = FewShotPromptTemplate(
    examples=examples,
    example_prompt=prompt,
    suffix="input:{input} output:",  # 声明在示例后面的提示词模板
    input_variables=["input"],
)

suffix 就是我们刚才说的,生成的模板字符串是

lua 复制代码
input: 我是小明 output:小明\n\ninput: 我是小红 output:小红\n\ninput: 我叫小刚 output:小刚

然后,suffix 会在后面拼接,得到(注意最后)

arduino 复制代码
'input: 我是小明 output:小明\n\ninput: 我是小红 output:小红\n\ninput: 我叫小刚 output:小刚\n\ninput:我叫小华 output:'

你可能奇怪了,为什么上面有个我叫小华 output:,明明 suffix 写的是 input:{input} output:,其实是suffix 中的 input 被变量替换了,如何替换呢?在调用的时候,如下:

ini 复制代码
res = few_shot_prompt.invoke({"input": "我叫小华"})

此时会把参数替换 suffix 中的占位符。

综合上面,我们来写完整示例:

python 版本:

python 复制代码
import dotenv
from langchain_openai import ChatOpenAI
from langchain_core.prompts import PromptTemplate, FewShotPromptTemplate
from langchain_core.messages import HumanMessage
import os

prompt = PromptTemplate.from_template(
    template="input: {input} output:{output}",
)

examples = [
    {"input": "我是小明", "output": "小明"},
    {
        "input": "我是小红",
        "output": "小红",
    },
    {
        "input": "我叫小刚",
        "output": "小刚",
    },
]

few_shot_prompt = FewShotPromptTemplate(
    examples=examples,
    example_prompt=prompt,
    suffix="input:{input} output:",  # 声明在示例后面的提示词模板
    input_variables=["input"],
)

res = few_shot_prompt.invoke({"input": "我叫小华"})
print(res)

返回结果如下:

ini 复制代码
text='input: 我是小明 output:小明\n\ninput: 我是小红 output:小红\n\ninput: 我叫小刚 output:小刚\n\ninput:我叫小华 output:'

接着看 node.js 版本:

javascript 复制代码
import dotenv from "dotenv";
import { ChatOpenAI } from "@langchain/openai";
import { HumanMessage } from "langchain";
import { PromptTemplate, FewShotPromptTemplate } from "@langchain/core/prompts";

dotenv.config();

const prompt = PromptTemplate.fromTemplate("input: {input} output:{output}");

const examples = [
  { input: "我是小明", output: "小明" },
  {
    input: "我是小红",
    output: "小红",
  },
  {
    input: "我叫小刚",
    output: "小刚",
  },
];

async function printResult() {
  const few_shot_prompt = new FewShotPromptTemplate({
    examples,
    examplePrompt: prompt,
    suffix: "input:{input} output:",
    inputVariables: ["input"],
  });
  const res = await few_shot_prompt.invoke({ input: "我叫小华" });
  console.log("res: ", res);
}

printResult();

返回结果如下:

css 复制代码
StringPromptValue {
  lc_serializable: true,
  lc_kwargs: {
    value: '\n' +
      '\n' +
      'input: 我是小明 output:小明\n' +
      '\n' +
      'input: 我是小红 output:小红\n' +
      '\n' +
      'input: 我叫小刚 output:小刚\n' +
      '\n' +
      'input:我叫小华 output:'
  },
  lc_namespace: [ 'langchain_core', 'prompt_values' ],
  value: '\n' +
    '\n' +
    'input: 我是小明 output:小明\n' +
    '\n' +
    'input: 我是小红 output:小红\n' +
    '\n' +
    'input: 我叫小刚 output:小刚\n' +
    '\n' +
    'input:我叫小华 output:'
}

可以看到,实际上 value 的值就是拼接了 example 和我们自己的提问.

FewShotChatMessagePromptTemplate

FewShotChatMessagePromptTemplate 也是提供示例的模板,主要配合的是 ChatPromptTemplate 一起使用。

注意:FewShotChatMessagePromptTemplate 里我们只传了 examples 和 example_prompt 参数 我们举例:

python 版本:

python 复制代码
import dotenv
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate, FewShotChatMessagePromptTemplate
from langchain_core.messages import HumanMessage
import os

prompt = ChatPromptTemplate.from_messages([("human", "{input}"), ("ai", "{output}")])

examples = [
    {"input": "我是小明", "output": "小明"},
    {
        "input": "我是小红",
        "output": "小红",
    },
    {
        "input": "我叫小刚",
        "output": "小刚",
    },
]

few_shot_prompt = FewShotChatMessagePromptTemplate(
    examples=examples,
    example_prompt=prompt,
)

res = few_shot_prompt.invoke({})
print(res)

返回内容如下:

ini 复制代码
messages=[HumanMessage(content='我是小明', additional_kwargs={}, response_metadata={}), AIMessage(content='小明', additional_kwargs={}, response_metadata={}), HumanMessage(content='我是小红', additional_kwargs={}, response_metadata={}), AIMessage(content='小红', additional_kwargs={}, response_metadata={}), HumanMessage(content='我叫小刚', additional_kwargs={}, response_metadata={}), AIMessage(content='小刚', additional_kwargs={}, response_metadata={})]

其实就是将 examples 的变量,替换 [("human", "{input}"), ("ai", "{output}")] 模板

node.js 示例:

javascript 复制代码
import dotenv from "dotenv";
import { ChatOpenAI } from "@langchain/openai";
import { HumanMessage } from "langchain";
import {
  ChatPromptTemplate,
  FewShotChatMessagePromptTemplate,
} from "@langchain/core/prompts";

dotenv.config();

const prompt = ChatPromptTemplate.fromMessages([
  ["human", "{input}"],
  ["ai", "{output}"],
]);
const examples = [
  { input: "我是小明", output: "小明" },
  {
    input: "我是小红",
    output: "小红",
  },
  {
    input: "我叫小刚",
    output: "小刚",
  },
];

async function printResult() {
  const few_shot_prompt = new FewShotChatMessagePromptTemplate({
    examples,
    examplePrompt: prompt,
    inputVariables: [],
  });
  const res = await few_shot_prompt.invoke({});
  console.log("res: ", res);
}

printResult();

返回:

javascript 复制代码
ChatPromptValue {
  // ... 删除其它不相关属性。、
  messages: [
    HumanMessage {
      "content": "我是小明",
      "additional_kwargs": {},
      "response_metadata": {}
    },
    AIMessage {
      "content": "小明",
      "additional_kwargs": {},
      "response_metadata": {},
      "tool_calls": [],
      "invalid_tool_calls": []
    },
    HumanMessage {
      "content": "我是小红",
      "additional_kwargs": {},
      "response_metadata": {}
    },
    AIMessage {
      "content": "小红",
      "additional_kwargs": {},
      "response_metadata": {},
      "tool_calls": [],
      "invalid_tool_calls": []
    },
    HumanMessage {
      "content": "我叫小刚",
      "additional_kwargs": {},
      "response_metadata": {}
    },
    AIMessage {
      "content": "小刚",
      "additional_kwargs": {},
      "response_metadata": {},
      "tool_calls": [],
      "invalid_tool_calls": []
    }
  ]
}

Output Parsers

接下来是 Model 最后一个部分,叫输出解析器。因为实际上 AI 返回的格式,我们在不同的环境下可能需要不同的格式,例如直接返回给用户,我们希望使用字符串的格式,例如返回给数据库,可能需要 json 的格式,我们解析后存入数据库。

这里列举比较常用的输出解析器的类型:

  • StrOutputParser:字符串解析器
  • JsonOutputPrser:JSON解析器,确保输出符合特定的JSON对象格式
  • XMLOutputParser: XML解析器,允许以XML格式从大模型返回
  • CommaSeparatedListOutputParser: CSV解析器,模型的输出以逗号分割,以列表的形式返回输出
  • DatetimeOutputParser: 日期时间解析器,可用于将大模型输出解析为日期时间

我们先来看看最简单的字符串解析器怎么用

StrOutputParser

python 代码:

python 复制代码
import dotenv
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate, FewShotChatMessagePromptTemplate
from langchain_core.messages import HumanMessage
from langchain_core.output_parsers import StrOutputParser
import os

dotenv.load_dotenv()

llm = ChatOpenAI(
    model_name=os.getenv("MODEL_NAME"),
    temperature=0,
    api_key=os.getenv("OPENAI_API_KEY"),
    base_url=os.getenv("OPENAI_BASE_URL"),
)

res = llm.invoke("你是谁")

print("res:", res)

print("res.content:", res.content)

parser = StrOutputParser()

print("parser.invoke(res):", parser.invoke(res))

注意上面三个打印的内容,

第一个 print("res:", res) 返回

ini 复制代码
res: content='你好!我是DeepSeek,由深度求索公司创造的AI助手!省略其它文字。。。' additional_kwargs={} 省略其它属性}

第一个 print("res.content:", res.content)

其实就是上面字典中的 content 属性,属于纯文字

除了上面可以获取纯文字,还可以使用 StrOutputParser 获取文字,

其中 print("parser.invoke(res):", parser.invoke(res)) 返回内容跟上面的 print("res.content:", res.content) 是一样的

node.js 案例如下:

javascript 复制代码
import dotenv from "dotenv";
import { ChatOpenAI } from "@langchain/openai";
import { StringOutputParser } from "@langchain/core/output_parsers";

dotenv.config();

const llm = new ChatOpenAI({
  modelName: process.env.MODEL_NAME,
  temperature: 0,
  openAIApiKey: process.env.OPENAI_API_KEY,
  baseUrl: process.env.OPENAI_BASE_URL,
});


async function printResult() {
  const res = await llm.invoke("你是谁?");
  const parser = new StringOutputParser();
  const parserResult = await parser.invoke(res);
  console.log("res: ", res.content);
  console.log("res: ", parserResult);
}


printResult();

注意上面两个个打印的内容, 其中大模型的 res 返回的是一个对象,这个对象有个属性是 content,内容就是大模型返回的文字,所以是字符串。

因此: console.log("res: ", res.content); 返回的是字符串。然后 console.log("res: ", parserResult); 返回的内容一模一样,也是字符串。返回结果如下:

复制代码
你好!我是DeepSeek,由深度求索公司创造的AI助手!😊 ...省略后面文字

JsonOutputPrser

这说白了就是返回 json 格式。而且 JsonOutputPrser 并没有什么黑魔法让大模型输出 json,其实就是简单的告诉大模型,要生成 json 格式,然后不准有注释这类文字信息。

但是实际生产环境,我们肯定要指定字段,比如返回格式是{ name: xx, age: xx },是我们期望的字段,而不是随便返回。所以还需要使用一些类型定义的库来协助。例如 node.js 中要使用到 zod 这个类型定义的库。我们先来看看 node.js 的版本。

以下是 node.js 版本:

首先使用

javascript 复制代码
import dotenv from "dotenv";
import { ChatOpenAI } from "@langchain/openai";
import {
  ChatPromptTemplate,
} from "@langchain/core/prompts";
import {
  StructuredOutputParser,
} from "@langchain/core/output_parsers";
import { z } from "zod";

dotenv.config();

/**
 * 1. 定义结构(强烈推荐)
 */
const UserSchema = z.object({
  name: z.string().describe("用户姓名"),
  age: z.number().describe("用户年龄"),
});

/**
 * 2. 创建 Parser
 * 本质:JsonOutputParser + Zod 校验
 */
const parser = StructuredOutputParser.fromZodSchema(UserSchema);

/**
 * 3. Prompt(必须注入 format instructions)
 */
const prompt = ChatPromptTemplate.fromMessages([
  ["system", "你是一个后端 API,请严格返回 JSON.\n${instructions}"],
  ["human", "{input}"],
]);

/**
 * 4. 模型
 */
const llm = new ChatOpenAI({
  modelName: process.env.MODEL_NAME,
  temperature: 0,
  openAIApiKey: process.env.OPENAI_API_KEY,
  baseUrl: process.env.OPENAI_BASE_URL,
});

async function printResult() {
  const template = await prompt.invoke({
    input: "生成一个用户对象",
    instructions: parser.getFormatInstructions(),
  });
  console.log("template: ", template);
  const res = await llm.invoke(template);
  console.log("res: ", res);
}

printResult();

注意,首先我们要定义类型,例如上面的 UserSchema,然后使用

  • StructuredOutputParser.fromZodSchema(UserSchema) 生成 json 解析器示例,

最最重要的是 parser.getFormatInstructions() 方法。会生成一类字符串,这个函数返回这样的字符串:

css 复制代码
你是一个后端 API,请严格返回 JSON.\n$You must format your output as a JSON value that adheres to a given 后面描述我们字段的类型

这就是 json 解析器的本质。

以下是 python 版本:

python 复制代码
import dotenv
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate, FewShotChatMessagePromptTemplate
from langchain_core.output_parsers import PydanticOutputParser
from pydantic import BaseModel, Field

import os

dotenv.load_dotenv()


# 1. 定义结构 (使用 Pydantic 替代 Zod)
class User(BaseModel):
    name: str = Field(description="用户姓名")
    age: int = Field(description="用户年龄")


# 2. 创建 Parser
# StructuredOutputParser 在 Python 中通常对应 PydanticOutputParser
parser = PydanticOutputParser(pydantic_object=User)

prompt = ChatPromptTemplate.from_messages(
    [
        ("system", "你是一个后端 API,请严格返回 JSON.\n{format_instructions}"),
        ("human", "{input}"),
    ]
)


llm = ChatOpenAI(
    model=os.getenv("MODEL_NAME"),  # type: ignore
    temperature=1,
    api_key=os.getenv("OPENAI_API_KEY"),  # type: ignore
    base_url=os.getenv("OPENAI_BASE_URL"),
)


prompt_value = prompt.invoke(
    {
        "input": "生成一个用户对象",
        "format_instructions": parser.get_format_instructions(),
    }
)
res = llm.invoke(prompt_value)

print("res: ", res)

python 使用 pydantic 库来定义个类型,然后使用 parser = PydanticOutputParser(pydantic_object=User) 创建一个结构化的解析器。

最终调用 parser.get_format_instructions() 方法来返回一个要求大模型返回json格式的字符串。

欢迎一起交流

欢迎大家一起进群讨论前端全栈技术和 ai agent ,一起进步!

相关推荐
Coder个人博客2 小时前
Apollo Canbus 底盘通信模块接口调用流程图与源码分析
人工智能·自动驾驶·apollo
玄微云2 小时前
玄微科技:大健康数智化的 4 个 AI 智能体落地要点
大数据·人工智能·科技·软件需求·门店管理
蓝鲨硬科技2 小时前
黄仁勋“梭哈”的物理AI,正在被中国企业变成现实
人工智能·chatgpt
Coder个人博客2 小时前
Apollo Prediction 预测模块接口调用流程图与源码分析
人工智能·自动驾驶·apollo
wordbaby2 小时前
配置 VS Code / Cursor 保存时自动格式化代码
前端
热爱专研AI的学妹2 小时前
【搭建工作流教程】使用数眼智能 API 搭建 AI 智能体工作流教程(含可视化流程图)
大数据·数据库·人工智能·python·ai·语言模型·流程图
LYFlied2 小时前
Spec Coding:AI时代前端开发的范式革新
前端·人工智能·工程化·spec coding
2401_841495642 小时前
知识工程:人工智能从通用求解到知识驱动的演进基石
人工智能·自然语言处理·知识图谱·语义网络·状态空间·知识工程·自然语言理解
救救孩子把2 小时前
中文命名实体识别(NER)数据集全面整理
人工智能·机器学习·数据集