02_LangChain整合DeepSeek提示词工程应用实践
温馨提示:本教程为《LangChain系列实战》,LangChain是一个开源的Python AI应用开发框架,它提供了构建基于大模型的AI应用所需的模块和工具。通过LangChain,开发者可以轻松地与大型语言模型(LLM)集成,完成文本生成、问答、翻译、对话等任务。LangChain降低了AI应用开发的门槛,让任何人都可以基于LLM构建属于自己的创意应用。
点击学习:《01_LangChain整合DeepSeek快速入门》
一、Prompt template(提示词模板)
语言模型以文本作为输入 - 这个文本通常被称为提示词(prompt)。在开发过程中,对于提示词通常不能直接硬编码,不利于提示词管理,而是通过提示词模板进行维护,类似开发过程中遇到的短信模板、邮件模板等等。
二、什么是提示词模板?
提示词模板本质上跟平时大家使用的邮件模板、短信模板没什么区别,就是一个字符串模板,模板可以包含模板参数,通过模板参数值可以替换模板对应的参数。
一个提示词模板可以包含下面内容:
- 发给大预言模型(LLM)的指令
- 一组问答示例,以提醒AI以什么格式返回请求
- 发给语言模型的问题
三、创建一个提示词模板(prompt template)
可以使用PromptTemplate类创建简单的提示词。提示词模板可以内嵌任意数量的模板参数,然后通过参数值格式化模板内容。
创建字符串提示词模板例子:
from langchain.prompts import PromptTemplate # 定义一个提示模板,包含adjective和content两个模板变量,模板变量使用使用{}包括起来 prompt_template = PromptTemplate.from_template( "给我讲一个关于{content}的{adjective}笑话。" ) # 通过模板参数格式化提示模板 result = prompt_template.format(content="冷", adjective="猴子") print(result) |
运行结果:
D:\anaconda3\envs\langchain\python.exe D:\PycharmProjects\langchain\prompt\prompt_template.py 给我讲一个关于冷的猴子笑话。 |
四、聊天消息提示词模板(chat prompt template)
聊天模型(Chat Model)以聊天消息列表作为输入,这个聊天消息列表的消息内容也可以通过提示词模板进行管理。这些聊天消息与原始字符串不同,因为每个消息都与“角色(role)”相关联。
例如,在OpenAI的Chat Completion API中,Openai的聊天模型,给不同的聊天消息定义了三种角色类型分别是助手(assistant)、人类(user)或系统(system)角色:
- 助手(assistant)消息指的是当前消息是AI回答的内容
- 人类(user)消息指的是你发给AI的内容
- 系统(system)消息通常是用来给AI省份进行描述
创建聊天消息模板例子:
# 导入langchain提示词模板 from langchain_core.prompts import ChatPromptTemplate # 通过一个消息数组创建聊天消息模板 # 数组每一个元素代表一条消息,每个消息元组,第一个元素代表消息角色(也成为消息类型),第二个元素代表消息内容 # 消息角色:system:代表系统信息,human:代表用户信息,ai:代表LLM返回的消息内容 # 下面消息定义了2个模板参数name和user_input chat_template = ChatPromptTemplate.from_messages( [ ("system", "你是一位人工智能助手,你的名字是{name}"), ("human", "你好!"), ("ai", "我很好,谢谢!"), ("human", "{user_input}"), ] ) # 通过模板参数格式化模板内容 messages = chat_template.format_messages(name="LangChain", user_input="你的名字叫什么?") print(messages) |
运行结果:
D:\anaconda3\envs\langchain\python.exe D:\PycharmProjects\langchain\prompt\chat_prompt.py [SystemMessage(content='你是一位人工智能助手,你的名字是LangChain', additional_kwargs={}, response_metadata={}), HumanMessage(content='你好!', additional_kwargs={}, response_metadata={}), AIMessage(content='我很好,谢谢!', additional_kwargs={}, response_metadata={}), HumanMessage(content='你的名字叫什么?', additional_kwargs={}, response_metadata={})] |
另外一种消息格式例子(实际开发推荐使用这种方式):
from langchain.prompts import HumanMessagePromptTemplate from langchain_core.messages import SystemMessage from langchain_core.prompts import ChatPromptTemplate # 使用 langchain 定义的SystemMessage、HumanMessagePromptTemplate等工具类定义消息,跟前面的列子类似,下面定义了两条消息 chat_template = ChatPromptTemplate.from_messages( [ SystemMessage( content="你是一个乐于助人的助手,可以润色内容,使其看起来更简单易读。" ), HumanMessagePromptTemplate.from_template("{text}"), ] ) # 使用模板参数格式化模板 messages = chat_template.format_messages(text="我不喜欢吃好吃的东西") print(messages) |
运行结果:
D:\anaconda3\envs\langchain\python.exe D:\PycharmProjects\langchain\prompt\chat_prompt_other.py [SystemMessage(content='你是一个乐于助人的助手,可以润色内容,使其看起来更简单易读。', additional_kwargs={}, response_metadata={}), HumanMessage(content='我不喜欢吃好吃的东西', additional_kwargs={}, response_metadata={})] |
通常我们不会直接使用format_messages函数格式化提示模板(prompt template)内容,而是交给LangChain框架自动处理。
五、MessagesPlaceholder
这个提示模板负责在特定位置添加消息列表。在上面的ChatPromptTemplate中,我们看到了如何格式化两条消息,每条消息都是一个字符串。但是,如果我们希望用户传入一个消息列表,我们将其插入到特定位置,该怎么办?这就是使用MessagesPlaceholder的方式。
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder from langchain_core.messages import HumanMessage, SystemMessage prompt_template = ChatPromptTemplate.from_messages( [ SystemMessage( content="You are a helpful assistant" ), # 可以传入一组消息 MessagesPlaceholder("messages"), ] ) result = prompt_template.invoke( {"messages":[HumanMessage(content="hi!"), HumanMessage(content="how are you?")]}, ) print(result) |
运行结果:
D:\anaconda3\envs\langchain\python.exe D:\PycharmProjects\langchain\prompt\messages_placeholder.py messages=[SystemMessage(content='You are a helpful assistant', additional_kwargs={}, response_metadata={}), HumanMessage(content='hi!', additional_kwargs={}, response_metadata={}), HumanMessage(content='how are you?', additional_kwargs={}, response_metadata={})] |
这将生成两条消息,第一条是系统消息,第二条是我们传入的HumanMessage。如果我们传入了5条消息,那么总共会生成6条消息(系统消息加上传入的5条消息)。这对于将一系列消息插入到特定位置非常有用。另一种实现相同效果的替代方法是,不直接使用MessagesPlaceholder类,而是:
from langchain_core.prompts import ChatPromptTemplate prompt_template = ChatPromptTemplate.from_messages( [ ("system", "You are a helpful assistant"), # 可以传入一组消息 ("placeholder", "{messages}"), ] ) result = prompt_template.invoke( {"messages":["hi!", "how are you?"]}, ) print(result) |
运行结果:
D:\anaconda3\envs\langchain\python.exe D:\PycharmProjects\langchain\prompt\messages_placeholder.py messages=[SystemMessage(content='You are a helpful assistant', additional_kwargs={}, response_metadata={}), HumanMessage(content='hi!', additional_kwargs={}, response_metadata={}), HumanMessage(content='how are you?', additional_kwargs={}, response_metadata={})] |
六、提示词追加示例(Few-shot prompt templates)
提示词中包含交互样本的作用是为了帮助模型更好地理解用户的意图,从而更好地回答或执行任务。小样本提示模板是指使用一组少量的示例来指导模型处理新的输入。这些示例可以用来训练模型,以便模型可以更好地理解和回答类似的问题。
使用场景:可以有效地减少大模型幻觉问题
Q:什么是蝙蝠侠? A:蝙蝠侠是一个虚构的漫画人物。 Q:什么是torsalplexity? A:未知。 Q:什么是语言模型? A: |
告诉模型根据,Q是问题,A是答案,按这种格式进行问答交互。
下面讲解的就是LangChain针对在提示词中插入少量交互样本提供的工具类。
1、使用示例集
(1)将示例和格式化程序提供给FewShotPromptTemplate
下面定义一个examples示例数组,里面包含一组问答样例。通过FewShotPromptTemplate对象,批量插入示例内容。
from langchain.prompts.few_shot import FewShotPromptTemplate from langchain.prompts.prompt import PromptTemplate examples = [ { "question": "谁的寿命更长,缪罕莫德•阿里还是艾伦•图灵?", "answer": """ 这里需要跟进问题吗:是的。 跟进:缪罕莫德•阿里去世时多大? 中间答案:缪罕莫德•阿里去世时74岁。 跟进:艾伦•图灵去世时多大? 中间答案:艾伦•图灵去世时41岁。 所以最终答案是:缪罕莫德•阿里 """, }, { "question": "craigslist的创始人是什么时候出生的?", "answer": """" 这里需要跟进问题吗:是的。 跟进:craigslist的创始人是谁? 中间答案:craigslist由Craig Newmark创立。 跟进:Craig Newmark是什么时候出生的? 中间答案:Craig Newmark于1952年12月6日出生。 所以最终答案是:1952年12月6日 """, }, { "question": "《大白鲨》和《皇家赌场》的导演都来自同一个国家吗?", "answer": """" 这里需要跟进问题吗:是的。 跟进:《大白鲨》的导演是谁? 中间答案:《大白鲨》的导演是Steven Spielberg。 跟进:Steven Spielberg来自哪里? 中间答案:美国。 跟进:《皇家赌场》的导演是谁? 中间答案:《皇家赌场》的导演是Martin Cambell。 跟进:Martin Cambell来自哪里? 中间答案:新西兰。 所以最终答案是:不是 """, }, ] example_prompt = PromptTemplate( input_variables=["question", "answer"], template="问题:{question}\n答案:{answer}\n", ) # 接受examples示例数组参数,通过example_prompt提示词模板批量渲染示例内容 # suffix和input_variables参数用于在提示词模板最后追加内容,input_variables用于定义suffix中包含的模板参数 prompt = FewShotPromptTemplate( examples=examples, example_prompt=example_prompt, suffix="问题:{input}\n答案:", input_variables=["input"], example_separator="\n\n", ) print(prompt.format(input="乔治•华盛顿的父亲是谁?")) |
运行结果:
D:\anaconda3\envs\langchain\python.exe D:\PycharmProjects\langchain\prompt\few_shot_examples.py 问题:谁的寿命更长,缪罕莫德•阿里还是艾伦•图灵? 答案: 这里需要跟进问题吗:是的。 跟进:缪罕莫德•阿里去世时多大? 中间答案:缪罕莫德•阿里去世时74岁。 跟进:艾伦•图灵去世时多大? 中间答案:艾伦•图灵去世时41岁。 所以最终答案是:缪罕莫德•阿里
问题:craigslist的创始人是什么时候出生的? 答案:" 这里需要跟进问题吗:是的。 跟进:craigslist的创始人是谁? 中间答案:craigslist由Craig Newmark创立。 跟进:Craig Newmark是什么时候出生的? 中间答案:Craig Newmark于1952年12月6日出生。 所以最终答案是:1952年12月6日
问题:《大白鲨》和《皇家赌场》的导演都来自同一个国家吗? 答案:" 这里需要跟进问题吗:是的。 跟进:《大白鲨》的导演是谁? 中间答案:《大白鲨》的导演是Steven Spielberg。 跟进:Steven Spielberg来自哪里? 中间答案:美国。 跟进:《皇家赌场》的导演是谁? 中间答案:《皇家赌场》的导演是Martin Cambell。 跟进:Martin Cambell来自哪里? 中间答案:新西兰。 所以最终答案是:不是
问题:乔治•华盛顿的父亲是谁? 答案: |
(2)创建小样本示例的格式化程序
通过PromptTemplate对象,简单的在提示词模板中插入样例。
from langchain.prompts.prompt import PromptTemplate examples = [ { "question": "谁的寿命更长,缪罕莫德•阿里还是艾伦•图灵?", "answer": """ 这里需要跟进问题吗:是的。 跟进:缪罕莫德•阿里去世时多大? 中间答案:缪罕莫德•阿里去世时74岁。 跟进:艾伦•图灵去世时多大? 中间答案:艾伦•图灵去世时41岁。 所以最终答案是:缪罕莫德•阿里 """, }, { "question": "craigslist的创始人是什么时候出生的?", "answer": """" 这里需要跟进问题吗:是的。 跟进:craigslist的创始人是谁? 中间答案:craigslist由Craig Newmark创立。 跟进:Craig Newmark是什么时候出生的? 中间答案:Craig Newmark于1952年12月6日出生。 所以最终答案是:1952年12月6日 """, }, { "question": "《大白鲨》和《皇家赌场》的导演都来自同一个国家吗?", "answer": """" 这里需要跟进问题吗:是的。 跟进:《大白鲨》的导演是谁? 中间答案:《大白鲨》的导演是Steven Spielberg。 跟进:Steven Spielberg来自哪里? 中间答案:美国。 跟进:《皇家赌场》的导演是谁? 中间答案:《皇家赌场》的导演是Martin Cambell。 跟进:Martin Cambell来自哪里? 中间答案:新西兰。 所以最终答案是:不是 """, }, ] example_prompt = PromptTemplate( input_variables=["question", "answer"], template="问题:{question}\n答案:{answer}\n", ) # 提取examples示例集合的一个示例的内容,用于格式化模板内容 print(example_prompt.format(**examples[0])) |
运行结果:
D:\anaconda3\envs\langchain\python.exe D:\PycharmProjects\langchain\prompt\07_few_shot_fromat.py 问题:谁的寿命更长,缪罕莫德•阿里还是艾伦•图灵? 答案: 这里需要跟进问题吗:是的。 跟进:缪罕莫德•阿里去世时多大? 中间答案:缪罕莫德•阿里去世时74岁。 跟进:艾伦•图灵去世时多大? 中间答案:艾伦•图灵去世时41岁。 所以最终答案是:缪罕莫德•阿里 |
2、使用示例选择器
(1)将示例提供给ExampleSelector
这里重用前一部分中的示例集和提示词模板(prompt template)。但是,不会将示例直接提供给FewShotPromptTemplate对象,把全部示例插入到提示词中,而是将它们提供给一个ExampleSelector对象,插入部分示例。
这里我们使用SemanticSimilarityExampleSelector类。该类根据与输入的相似性选择小样本示例。它使用嵌入模型计算输入和小样本示例之间的相似性,然后使用向量数据库执行相似搜索,获取跟输入相似的示例。
提示:这里涉及向量计算,向量数据库,在AI领域这两个主要用于数据相似度搜索,例如:查询相似文章内容、相似的图片、视频等等,这里先简单了解下就行。
安装 Chroma 向量数据库依赖 langchain_community
pip install langchain-community pip install chromadb |
匹配跟问题相似的例子
from langchain.prompts.example_selector import SemanticSimilarityExampleSelector from langchain_community.vectorstores import Chroma from langchain_ollama import OllamaEmbeddings examples = [ { "question": "谁的寿命更长,缪罕莫德•阿里还是艾伦•图灵?", "answer": """ 这里需要跟进问题吗:是的。 跟进:缪罕莫德•阿里去世时多大? 中间答案:缪罕莫德•阿里去世时74岁。 跟进:艾伦•图灵去世时多大? 中间答案:艾伦•图灵去世时41岁。 所以最终答案是:缪罕莫德•阿里 """, }, { "question": "craigslist的创始人是什么时候出生的?", "answer": """" 这里需要跟进问题吗:是的。 跟进:craigslist的创始人是谁? 中间答案:craigslist由Craig Newmark创立。 跟进:Craig Newmark是什么时候出生的? 中间答案:Craig Newmark于1952年12月6日出生。 所以最终答案是:1952年12月6日 """, }, { "question": "乔治•华盛顿的祖父母中的母亲是谁?", "answer": """" 这里需要跟进问题吗:是的。 跟进:乔治•华盛顿的母亲是谁? 中间答案:乔治•华盛顿的的母亲是Mary Ball Washington。 跟进:Mary Ball Washington的父亲是谁? 中间答案:Mary Ball Washington的父亲是Joseph Ball。 所以最终答案是:Joseph Ball """, }, { "question": "《大白鲨》和《皇家赌场》的导演都来自同一个国家吗?", "answer": """" 这里需要跟进问题吗:是的。 跟进:《大白鲨》的导演是谁? 中间答案:《大白鲨》的导演是Steven Spielberg。 跟进:Steven Spielberg来自哪里? 中间答案:美国。 跟进:《皇家赌场》的导演是谁? 中间答案:《皇家赌场》的导演是Martin Cambell。 跟进:Martin Cambell来自哪里? 中间答案:新西兰。 所以最终答案是:不是 """, }, ] # 使用语义相似性示例选择器 example_selector = SemanticSimilarityExampleSelector.from_examples( # 这是可供选择的示例列表 examples, # 这是用于生成嵌入的嵌入类,该嵌入用于衡量语义相似性 OllamaEmbeddings( base_url="http://127.0.0.1:11434/", model="deepseek-r1:1.5b", ), # 这是用于存储嵌入和执行相似性检索的VectorStore Chroma, # 这是要生成的示例的数量 k=1, ) # 选择与输入相似的示例 question = "乔治•华盛顿的父亲是谁?" selected_examples = example_selector.select_examples({"question": question}) print(f"最相似的示例:{question}") for example in selected_examples: print("\\n") for k, v in example.items(): print(f"{k}: {v}") |
运行结果:
D:\anaconda3\envs\langchain\python.exe D:\PycharmProjects\langchain\prompt\08_few_shot_selector.py 最相似的示例:乔治•华盛顿的父亲是谁? \n answer: " 这里需要跟进问题吗:是的。 跟进:乔治•华盛顿的母亲是谁? 中间答案:乔治•华盛顿的的母亲是Mary Ball Washington。 跟进:Mary Ball Washington的父亲是谁? 中间答案:Mary Ball Washington的父亲是Joseph Ball。 所以最终答案是:Joseph Ball
question: 乔治•华盛顿的祖父母中的母亲是谁? |
(2)将示例选择器提供给FewShotPromptTemplate
最后,创建一个FewShotPromptTemplate对象,根据前面的example_selector示例选择器,选择一个跟问题相似的列子。
from langchain.prompts.example_selector import SemanticSimilarityExampleSelector from langchain_community.vectorstores import Chroma from langchain_ollama import OllamaEmbeddings from langchain.prompts.few_shot import FewShotPromptTemplate from langchain.prompts.prompt import PromptTemplate examples = [ { "question": "谁的寿命更长,缪罕莫德•阿里还是艾伦•图灵?", "answer": """ 这里需要跟进问题吗:是的。 跟进:缪罕莫德•阿里去世时多大? 中间答案:缪罕莫德•阿里去世时74岁。 跟进:艾伦•图灵去世时多大? 中间答案:艾伦•图灵去世时41岁。 所以最终答案是:缪罕莫德•阿里 """, }, { "question": "craigslist的创始人是什么时候出生的?", "answer": """" 这里需要跟进问题吗:是的。 跟进:craigslist的创始人是谁? 中间答案:craigslist由Craig Newmark创立。 跟进:Craig Newmark是什么时候出生的? 中间答案:Craig Newmark于1952年12月6日出生。 所以最终答案是:1952年12月6日 """, }, { "question": "乔治•华盛顿的祖父母中的母亲是谁?", "answer": """" 这里需要跟进问题吗:是的。 跟进:乔治•华盛顿的母亲是谁? 中间答案:乔治•华盛顿的的母亲是Mary Ball Washington。 跟进:Mary Ball Washington的父亲是谁? 中间答案:Mary Ball Washington的父亲是Joseph Ball。 所以最终答案是:Joseph Ball """, }, { "question": "《大白鲨》和《皇家赌场》的导演都来自同一个国家吗?", "answer": """" 这里需要跟进问题吗:是的。 跟进:《大白鲨》的导演是谁? 中间答案:《大白鲨》的导演是Steven Spielberg。 跟进:Steven Spielberg来自哪里? 中间答案:美国。 跟进:《皇家赌场》的导演是谁? 中间答案:《皇家赌场》的导演是Martin Cambell。 跟进:Martin Cambell来自哪里? 中间答案:新西兰。 所以最终答案是:不是 """, }, ] # 使用语义相似性示例选择器 example_selector = SemanticSimilarityExampleSelector.from_examples( # 这是可供选择的示例列表 examples, # 这是用于生成嵌入的嵌入类,该嵌入用于衡量语义相似性 OllamaEmbeddings( base_url="http://127.0.0.1:11434/", model="deepseek-r1:1.5b", ), # 这是用于存储嵌入和执行相似性检索的VectorStore Chroma, # 这是要生成的示例的数量 k=1, ) # 选择与输入相似的示例 example_prompt = PromptTemplate( input_variables=["question", "answer"], template="问题: {question}\\n{answer}", ) # 获取提示词模板 prompt = FewShotPromptTemplate( # 选择语义相似性示例 example_selector=example_selector, example_prompt=example_prompt, # 这是用于提示的模板 suffix="问题: {input}", # 这是提示的输入变量 input_variables=["input"], ) print(prompt.format(input="乔治•华盛顿的父亲是谁?")) |
运行结果:
D:\anaconda3\envs\langchain\python.exe D:\PycharmProjects\langchain\prompt\09_few_shot_selector_param.py 问题: 乔治•华盛顿的祖父母中的母亲是谁?\n" 这里需要跟进问题吗:是的。 跟进:乔治•华盛顿的母亲是谁? 中间答案:乔治•华盛顿的的母亲是Mary Ball Washington。 跟进:Mary Ball Washington的父亲是谁? 中间答案:Mary Ball Washington的父亲是Joseph Ball。 所以最终答案是:Joseph Ball
问题: 乔治•华盛顿的父亲是谁? |