[Nodejs+LangChain+Ollama] 2.提示词实践
本文介绍了提示词(prompt)在语言模型开发中的应用。提示词模板类似于短信/邮件模板,包含指令、问答示例和问题。文章详细讲解了如何创建提示词模板、聊天消息提示词模板,以及使用MessagePlaceholder动态插入消息。重点介绍了Few-shot prompt模板,通过少量示例指导模型处理新输入,并展示了示例选择器(SemanticSimilarityExampleSelector)的应用,
Prompt templates
语言模型以文本作为输入 - 这个文本通常被称作为提示词(prompt)。在开发过程中,对于提示词通常不能直接硬编码,不利于提示词管理,二十通过提示词模板进行维护,类似开发过程中遇到的短信模板、邮件模板等。
什么是提示词?
提示词模板本质上跟平时大家是用的邮件模板、短信模板没有什么区别,就是一个字符串模板,模板可以包含一组模板参数,通过模板参数值可以替换模板对应的参数。
一个提示词模板可以包含下面的内容:
- 发给大语言模型(LLM)的指令。
- 一组问答示例,以提醒AI以什么格式返回请求。
- 发给语言模型的问题。
创建提示词模板
可以是用PromptTemplate
类创建简单的提示词。提示词模板可以内嵌任意数量的模板参数,然后通过参数值格式化模板内容。
import { ChatPromptTemplate } from "@langchain/core/prompts";
import { ChatOllama } from '@langchain/ollama';
// 初始化模型
const llm = new ChatOllama({
model: 'qwen3:0.6b'
});
// 设置提示词
const prompt = ChatPromptTemplate.fromMessages([
['system', '你是一个世界级幽默大师'],
['human', '你好'],
['ai', '你好'],
['human', '{input}']
]);
// 调用模型
const run = async () => {
const result = await prompt.formatMessages({ input: '我看你是没见过黑手,敢不敢来跟我比划比划' })
const response = await llm.invoke(result)
console.log(response.content)
};
run()
聊天消息提示词模板
聊天模型(Chat Modal)以聊天消息列表作为输入,这个聊天消息列表的消息内容也可以通过提示词进行管理。这些聊天消息与原始字符串不同,因为每个消息都与“角色(role)”相关联。
例如:在OpenAI的Chat Completion API中,Openai的聊天模型,给不同的聊天消息定义了三种角色类型分别是助手(assistant)、人类(human)或系统(system)角色:
- 助手(Assistant)消息指的是当前消息是AI回答的内容。
- 人类(user)消息指的是你发给AI的内容。
- 系统(system)消息通常是用来给AI身份进行描述。
MessagePlaceholder
这个提示词模板负责在特定位置添加消息列表。在上面的ChatPromptTemplate中,我们看到了如何格式化两条消息,每条消息都是一个字符串。但是,如果我们希望用户传入一个消息列表,我们将其插入到特定位置,该怎么办?这就是您是用MessagesPlaceholder的方式。
import { HumanMessage } from "@langchain/core/messages";
import { ChatPromptTemplate, MessagesPlaceholder } from "@langchain/core/prompts";
import { ChatOllama } from '@langchain/ollama';
// 初始化模型
const llm = new ChatOllama({
model: 'qwen3:0.6b'
});
// 设置提示词
const prompt = ChatPromptTemplate.fromMessages([
['system', '你是一个世界级的生活助理,你的名字叫贾维斯。'],
new MessagesPlaceholder('msgs')
]);
// 调用模型
const run = async () => {
const result = await prompt.formatMessages({
msgs: [
new HumanMessage('贾维斯,明天天气怎么样?'),
new HumanMessage('贾维斯,明天去参加国会几天出发?'),
new HumanMessage('贾维斯,三太太生的男孩女孩?'),
new HumanMessage('贾维斯,公司最近亏损怎么样?'),
]
})
const response = await llm.invoke(result)
console.log(response.content)
};
run()
这将生成5条消息,这对于将一系列消息插入到特定位置非常有用。另一种实现相同效果的替代方法是,不直接是用MessagesPlaceholder
类,而是:
const prompt = ChatPromptTemplate.fromMessages([
['system', '你是一个世界级的生活助理,你的名字叫贾维斯。'],
['placeholder', '{msgs}'],
]);
Few-shot prompt templates
提示词中包含交互样本的作用是为了帮助模型更好地理解用户的意图,从而更好地回答问题或执行任务。小样本提示模板指使用一组少量的示例来知道模型处理新的输入。这些示例可以用来训练模型,以便模型可以更好地理解和回答类似的问题。
例子:
Q: 什么是无限月读?
A: 无限月读是动漫中一种虚构的忍术。
Q: 什么是意大利面拌24号混凝土?
A: 未知。
Q: 什么是语言模型?
A:
告诉模型根据,Q是问题,A是答案,按这种格式进行问答交互。
下面讲解的就是LanChain针对在提示词中插入少量交互样本提供的工具类。
使用示例集
下面定义一个examples示例数组,里面包含一组问答样例。
import { HumanMessage } from "@langchain/core/messages";
import { ChatPromptTemplate, FewShotPromptTemplate, MessagesPlaceholder, PromptTemplate } from "@langchain/core/prompts";
import { ChatOllama } from '@langchain/ollama';
// 初始化模型
const llm = new ChatOllama({
model: 'qwen3:0.6b'
});
// 示例数据
const examples = [
{
'question': '宇智波鼬跟宇智波佐助的那一战,宇智波鼬放水了吗?',
'answer': `
这里需要跟进问题吗:是的。
跟进:宇智波佐助是谁?
中间答案:宇智波佐助是宇智波鼬的弟弟。
跟进:宇智波鼬会什么?
中间答案:最强武器(十拳剑、八咫镜)、别天神等幻术、天照。
跟进:宇智波鼬打赢了吗?
中间答案:宇智波鼬没有打赢,他被佐助打败了。
跟进:如何没打赢?
中间答案:宇智波鼬未使用最强武器(十拳剑、八咫镜)攻击佐助,未使用别天神等幻术,鼬完美地利用了这一劣势,他让佐助以为自己是因为生病和虚弱才无法取胜。
所以最终答案是:宇智波鼬在整个战斗中严重放水。
`,
},
{
'question': '如果创造了一个琳的世界带土真的会快乐吗?',
'answer': `
这里需要跟进问题吗:是的。
跟进:宇智波带土想要创造一个有琳的世界的根源是什么?
中间答案:根源是他无法接受琳的死亡,认为现实世界是地狱,企图通过月之眼计划创造一个没有痛苦的幻术世界来逃避现实。
跟进:月之眼计划创造的“琳”是真实的吗?
中间答案:不是真实的,那是根据带土记忆创造的、没有自主意识的幻影。
跟进:真正的琳会希望带土这样做吗?
中间答案:不会。真正的琳善良勇敢,为了保护同伴自愿牺牲,她绝不会认可带土为了一己私欲而抹杀整个现实世界。
跟进:带土的痛苦仅仅是因为失去琳吗?
中间答案:不完全是。更深层的痛苦源于他对“没能保护同伴的自己”的否定和无法原谅。
所以最终答案是:不会。即使创造了一个有琳的世界,带土也绝不会获得真正的快乐。因为他得到的只是一个虚假的幻影,这无法弥补他内心的失败感,更背叛了琳真正的意志,最终只会带来更深邃的空虚和痛苦。
`,
}
];
const examples_prompt = new PromptTemplate({
inputVariables: ['question', 'answer'],
template: '问题:{question}\n{answer}',
});
// 少样本提示模板
const prompt = new FewShotPromptTemplate({
examples,// 作为模型回答问题的参考
examplePrompt: examples_prompt,// 它决定了每个示例在最终提示中的呈现方式
suffix: '问题:{input}',// 在看完所有示例后,现在需要回答的是这个新问题
inputVariables: ['input'],// 列出了模板中需要替换的变量名
});
// 调用模型
const run = async () => {
const response = await llm.invoke(await prompt.format({ input: '如果第三次忍界大战,日向宁次没死,雏田还会跟鸣人结婚吗?' }))
console.log(response.content)
};
run()
示例选择器
将示例提供给ExampleSelector
这里重用前一部分中的示例集和提示词模板(prompt templae)。但是,不会将示例直接提供给FewShotPromptTemplate
对象,把全部示例插入到提示词中,而是将它们提供给一个ExampleSelector
对象,插入部分示例。简而言之就是可以通过输入的语言查找到最相似的那条数据。
这里我们使用SemanticSimilarityExampleSelector
类。该类根据与输入的相似性选择小样本示例。它使用嵌入模型计算输入和小样本示例之间的相似性,然后使用向量数据库执行相似的搜索,获取跟输入相似的示例。
- 提示:这里涉及向量计算、向量数据库,在AI领域这两个主要用于数据相似度搜索,例如:查询相似文章内容、相似的图片、视频等等,这里先简单了解一下就行。
例子:
前提先下载 nomic-embed-text:v1.5
嵌入模型,用于语义相似度计算。
ollama run nomic-embed-text:v1.5
import { SemanticSimilarityExampleSelector } from "@langchain/core/example_selectors";
import { OllamaEmbeddings } from '@langchain/ollama';
import { MemoryVectorStore } from "langchain/vectorstores/memory";
// 初始化嵌入模型(用于语义相似度计算)
const embeddings = new OllamaEmbeddings({
model: 'nomic-embed-text:v1.5'
});
// 示例数据
const examples = [
{
'question': '宇智波鼬跟宇智波佐助的那一战,宇智波鼬放水了吗?',
'answer': `
这里需要跟进问题吗:是的。
跟进:宇智波佐助是谁?
中间答案:宇智波佐助是宇智波鼬的弟弟。
跟进:宇智波鼬会什么?
中间答案:最强武器(十拳剑、八咫镜)、别天神等幻术、天照。
跟进:宇智波鼬打赢了吗?
中间答案:宇智波鼬没有打赢,他被佐助打败了。
跟进:如何没打赢?
中间答案:宇智波鼬未使用最强武器(十拳剑、八咫镜)攻击佐助,未使用别天神等幻术,鼬完美地利用了这一劣势,他让佐助以为自己是因为生病和虚弱才无法取胜。
所以最终答案是:宇智波鼬在整个战斗中严重放水。
`,
},
{
'question': '如果创造了一个琳的世界带土真的会快乐吗?',
'answer': `
这里需要跟进问题吗:是的。
跟进:宇智波带土想要创造一个有琳的世界的根源是什么?
中间答案:根源是他无法接受琳的死亡,认为现实世界是地狱,企图通过月之眼计划创造一个没有痛苦的幻术世界来逃避现实。
跟进:月之眼计划创造的“琳”是真实的吗?
中间答案:不是真实的,那是根据带土记忆创造的、没有自主意识的幻影。
跟进:真正的琳会希望带土这样做吗?
中间答案:不会。真正的琳善良勇敢,为了保护同伴自愿牺牲,她绝不会认可带土为了一己私欲而抹杀整个现实世界。
跟进:带土的痛苦仅仅是因为失去琳吗?
中间答案:不完全是。更深层的痛苦源于他对“没能保护同伴的自己”的否定和无法原谅。
所以最终答案是:不会。即使创造了一个有琳的世界,带土也绝不会获得真正的快乐。因为他得到的只是一个虚假的幻影,这无法弥补他内心的失败感,更背叛了琳真正的意志,最终只会带来更深邃的空虚和痛苦。
`,
}
];
// 调用模型
const run = async () => {
// 创建示例选择器
const example_selector = await SemanticSimilarityExampleSelector.fromExamples(
examples,// 示例数据
embeddings,// 嵌入模型
MemoryVectorStore,// 内存向量数据库
{
k: 1,// 指定要选择的示例数量
}
);
const selected_examples = await example_selector.selectExamples({ question: '佐助跟鸣人的终结谷之战中佐助放水了吗?' })
console.log("最相似的示例是:", selected_examples)
};
run()
将示例选择器提供给FewShotPromptTemplate
最后创建一个FewShotPromptTemplate
对象。根据前面的example_selector示例选择器,选择一个跟问题相似的例子。简而语之就是查找到最相似的那个示例生成提示词。
import { SemanticSimilarityExampleSelector } from "@langchain/core/example_selectors";
import { FewShotPromptTemplate, PromptTemplate } from "@langchain/core/prompts";
import { ChatOllama, OllamaEmbeddings } from '@langchain/ollama';
import { MemoryVectorStore } from "langchain/vectorstores/memory";
// 初始化模型
const llm = new ChatOllama({
model: 'qwen3:0.6b'
});
// 初始化嵌入模型(用于语义相似度计算)
const embeddings = new OllamaEmbeddings({
model: 'nomic-embed-text:v1.5'
});
// 示例数据
const examples = [
{
'question': '宇智波鼬跟宇智波佐助的那一战,宇智波鼬放水了吗?',
'answer': `
这里需要跟进问题吗:是的。
跟进:宇智波佐助是谁?
中间答案:宇智波佐助是宇智波鼬的弟弟。
跟进:宇智波鼬会什么?
中间答案:最强武器(十拳剑、八咫镜)、别天神等幻术、天照。
跟进:宇智波鼬打赢了吗?
中间答案:宇智波鼬没有打赢,他被佐助打败了。
跟进:如何没打赢?
中间答案:宇智波鼬未使用最强武器(十拳剑、八咫镜)攻击佐助,未使用别天神等幻术,鼬完美地利用了这一劣势,他让佐助以为自己是因为生病和虚弱才无法取胜。
所以最终答案是:宇智波鼬在整个战斗中严重放水。
`,
},
{
'question': '如果创造了一个琳的世界带土真的会快乐吗?',
'answer': `
这里需要跟进问题吗:是的。
跟进:宇智波带土想要创造一个有琳的世界的根源是什么?
中间答案:根源是他无法接受琳的死亡,认为现实世界是地狱,企图通过月之眼计划创造一个没有痛苦的幻术世界来逃避现实。
跟进:月之眼计划创造的“琳”是真实的吗?
中间答案:不是真实的,那是根据带土记忆创造的、没有自主意识的幻影。
跟进:真正的琳会希望带土这样做吗?
中间答案:不会。真正的琳善良勇敢,为了保护同伴自愿牺牲,她绝不会认可带土为了一己私欲而抹杀整个现实世界。
跟进:带土的痛苦仅仅是因为失去琳吗?
中间答案:不完全是。更深层的痛苦源于他对“没能保护同伴的自己”的否定和无法原谅。
所以最终答案是:不会。即使创造了一个有琳的世界,带土也绝不会获得真正的快乐。因为他得到的只是一个虚假的幻影,这无法弥补他内心的失败感,更背叛了琳真正的意志,最终只会带来更深邃的空虚和痛苦。
`,
}
];
const examples_prompt = new PromptTemplate({
inputVariables: ['question', 'answer'],
template: '问题:{question}\n{answer}',
});
// 调用模型
const run = async () => {
// 创建示例选择器
const example_selector = await SemanticSimilarityExampleSelector.fromExamples(
examples,// 示例数据
embeddings,// 嵌入模型
MemoryVectorStore,// 内存向量数据库
{
k: 1,// 指定要选择的示例数量
}
);
const prompt = new FewShotPromptTemplate({
exampleSelector: example_selector,
examplePrompt: examples_prompt,// 它决定了每个示例在最终提示中的呈现方式
suffix: '问题:{input}',// 在看完所有示例后,现在需要回答的是这个新问题
inputVariables: ['input'],// 列出了模板中需要替换的变量名
});
const result = await prompt.invoke({
input: '佐助跟鸣人的终结谷之战中佐助放水了吗?',
});
console.log(result)
};
run()
更多推荐
所有评论(0)