在跟各种 聊天模型 交互的时候,在构建聊天信息时,不仅仅包含了像上文中的文本内容,也需要与每条消息关联的角色信息。
例如这条信息是由 人类、AI、还是给 chatbot 指定的 system 信息,这种结构化的消息输入有助于模型更好地理解对话的上下文和流程,从而生成更准确、更自然的回应。
为了方便地构建和处理这种结构化的聊天消息,LangChain.js 提供了专门的结构化提示模板系统,来帮助我们方便地构造这类消息:
| 类名 | 用途 |
|---|---|
ChatPromptTemplate | 构造整个多轮提示词结构 |
SystemMessagePromptTemplate | 设置系统规则 / 行为模式 |
HumanMessagePromptTemplate | 模拟用户输入 |
AIMessagePromptTemplate | 模拟模型输出(上下文中使用) |
角色的概念对 LLM 理解和构建整个对话流程非常重要,相同的内容由不同的 role 发送出来的意义是不同的。
system角色的消息通常用于设置对话的上下文或指定模型采取特定的行为模式。这些消息不会直接显示在对话中,但它们 对模型的行为有指导作用。 可以理解成模型的元信息,权重非常高,在这里有效的构建 prompt 能取得非常好的效果。user角色代表真实用户在对话中的发言。这些消息通常是问题、指令或者评论,反映了用户的意图和需求。assistant角色的消息代表AI模型的回复。这些消息是模型根据system的指示和user的输入生成的。
快速上手#
import { ChatPromptTemplate, SystemMessagePromptTemplate, HumanMessagePromptTemplate,} from '@langchain/core/prompts'
// 构建系统提示const systemPrompt = SystemMessagePromptTemplate.fromTemplate( '你是一位专业导游,负责用中文向游客介绍北京特产。')
// 构建用户提示const humanPrompt = HumanMessagePromptTemplate.fromTemplate('{question}')
// 组合为 ChatPromptTemplateconst chatPrompt = ChatPromptTemplate.fromMessages([systemPrompt, humanPrompt])
// 填充变量,生成最终结构const messages = await chatPrompt.formatMessages({ question: '北京有哪些值得推荐的特产?',})console.log('生成的消息结构:', messages)执行结果如下:
[ SystemMessage { "content": "你是一位中国的专业导游,请使用中文向游客介绍中国的某些地区的特产", "additional_kwargs": {}, "response_metadata": {} }, HumanMessage { "content": "我想问:北京的特产有哪些", "additional_kwargs": {}, "response_metadata": {} }]这是 LangChain.js 中 标准的聊天消息对象结构,每条消息都表示一次对话中的一轮发言(具有角色和内容),它们会传递给聊天模型进行推理。各个字段的意思如下:
| 字段名 | 类型 | 含义与作用 |
|---|---|---|
SystemMessage / HumanMessage | 类实例 | 表示消息的角色类型 |
content | string | 消息正文,即这条消息说了什么 |
additional_kwargs | object | 额外的模型参数(可选),如工具调用、函数定义、引用等 |
response_metadata | object | 模型生成后自动附加的元信息(如 token 用量、日志信息等) |
一个完整的示例:
- 生成结构化提示词
- 将结构化提示词喂给大模型
import { SystemMessagePromptTemplate, HumanMessagePromptTemplate, ChatPromptTemplate,} from '@langchain/core/prompts'import { ChatOllama } from '@langchain/ollama'
function buildPrompt(sysTemplate, userTemplate) { const sysPt = SystemMessagePromptTemplate.fromTemplate(sysTemplate) const hPt = HumanMessagePromptTemplate.fromTemplate(userTemplate) return ChatPromptTemplate.fromMessages([sysPt, hPt])}
// 1. 系统提示词const sysTemplate = '你是一位中国的专业导游,请一定使用中文向游客介绍{location}的{topic}。记住:一定要使用中文来介绍'
// 2. 用户输入提示词const userTemplate = '当前用户的问题:{question}'
// 3. 拿到最终组合的提示词模板const pt = buildPrompt(sysTemplate, userTemplate)
// 4. 做一个填充const result = await pt.formatMessages({ location: '北京', topic: '特产', question: '北京有哪些值得推荐的特产',})
// 5. 创建模型实例const model = new ChatOllama({ model: 'llama3', stream: true, temperature: 0.7,})
const stream = await model.stream(result)
for await (const chunk of stream) { process.stdout.write(chunk.content)}组合多个提示词#
在实际工程中,我们常常需要根据多个变量、多个上下文来源,动态拼接出一个结构复杂、逻辑清晰的 Prompt。此时,如果把所有逻辑都写在一个大模板里,会让 Prompt 难以维护、难以复用。
这就是 PipelinePromptTemplate 的用武之地,它允许我们将多个小的 Prompt 模板,按顺序组合成一个“流水线式”的大模板,既能 模块化构建,又能 复用逻辑片段。
在 PipelinePromptTemplate 中,有两个重要组成部分:
- pipelinePrompts:一个数组,每一项都是一个小模板(Prompt),执行后会产出一个变量,供后续模板使用
- finalPrompt:最终的主模板,引用前面生成的变量,拼装成完整 Prompt
可以想象每个 pipelinePrompt 是一个工序,负责“加工”一段文本,finalPrompt 则是总装线,拼出最终成品。
import { PromptTemplate, PipelinePromptTemplate } from '@langchain/core/prompts'
// 获取当前日期字符串const getDate = () => new Date().toLocaleDateString()
// 1. 创建一个主模板const mainPt = PromptTemplate.fromTemplate( `你是一个智能助理,今天是 {date},主人的信息是 {userInfo}, 请根据上下文完成以下任务: {todo}`)
// 2. 创建子模板const timePl = PromptTemplate.fromTemplate('{date},现在是 {time}')const filledTimePl = await timePl.partial({ date: getDate,})
const userTpl = PromptTemplate.fromTemplate('姓名:{name},性别:{gender}')const taskTpl = PromptTemplate.fromTemplate(` 我想吃 {time} 的 {dish}。 请再次确认我的信息:{userInfo} `)
// 3. 可以将子模板填充到主模板里面const finalPt = new PipelinePromptTemplate({ pipelinePrompts: [ { name: 'date', prompt: filledTimePl, }, { name: 'userInfo', prompt: userTpl, }, { name: 'todo', prompt: taskTpl, }, ], finalPrompt: mainPt,})const result = await finalPt.format({ time: '12:01', name: '张三', gender: '男', dish: '煎蛋',})console.log(result)有了 pipelinePrompts,我们可以极大程度的复用和管理 prompt template,从而让 llm app 的开发更加工程化。