在基于大语言模型(LLM)的应用程序开发中,构建提示词是最基本和最重要的部分,好的提示词能让LLM明确目标,并按照我们的预期输出内容。LangChain对提示词相关的功能进行了封装,我们可以基于框架的API组装符合LLM应用最佳实践的提示词。
LangChain中,PromptTemplate
是最基础的提示模板,其中可以包含占位符,通过填充占位符合成最终提示词,下面是一个例子。
from langchain.globals import set_debug, set_verbose
from langchain_core.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_ollama import OllamaLLM
set_debug(True)
set_verbose(True)
llm = OllamaLLM(model='llama3.1:8b', temperature=1)
prompt_template = PromptTemplate.from_template("Translate {text} into {language}.")
chain = prompt_template | llm | StrOutputParser()
result = chain.invoke({"text": "Hello, my name is Aiko.", "language": "Spanish"})
print(result)
提示词模板可以使用PromptTemplate.from_template()
创建,参数是一个包含占位符的字符串。我们可以将这个PromptTemplate
对象组装到“链”中,当对“链”调用invoke()
函数时,我们可以传入相关参数。运行上面代码,我们可以看到提示词模板的填充效果。
补充:如果我们暂时不想把提示词组装到“链”上运行,可以直接调用format()
方法查看效果,该方法接收所有占位变量的命名参数。
prompt_template.format(text="Hello, my name is Aiko.", language="Spanish")
Few-Shot提示词指的是LLM在进行推理任务时,通过给定少量示例来供其参考如何完成该任务,下面例子中,我们的examples
变量就包含了一组例子,例子中的回答给了LLM许多提示信息,每个例子的回答前还都添加了一个猫猫Emoji,LLM大概率也会按照相同的格式进行回答。
from langchain.globals import set_debug, set_verbose
from langchain_core.prompts import PromptTemplate, FewShotPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_ollama import OllamaLLM
set_debug(True)
set_verbose(True)
llm = OllamaLLM(model='llama3.1:8b', temperature=1)
examples = [
{"question": "Who are you?", "answer": "🐱 I am Aiko, a helpful AI assistant."},
{"question": "What is your name?", "answer": "🐱 My name is Aiko."},
{"question": "What can you do for me?", "answer": "🐱 I can generate text."},
]
few_shot_prompt = FewShotPromptTemplate(
examples=examples,
example_prompt=PromptTemplate.from_template(template="Question: {question}\nAnswer: {answer}"),
suffix="User Question: {input}"
)
chain = few_shot_prompt | llm | StrOutputParser()
result = chain.invoke({"input": "Hi, who r u ?"})
print(result)
假如我们有多个模板对象,它们可能是PromptTemplate
、FewShotPromptTemplate
或是其它LangChain的模板类型,如何将它们拼接到一起组合使用?PipelinePromptTemplate
提供了这种方法。
from langchain.globals import set_debug, set_verbose
from langchain_core.prompts import PromptTemplate, FewShotPromptTemplate, PipelinePromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_ollama import OllamaLLM
set_debug(True)
set_verbose(True)
llm = OllamaLLM(model='llama3.1:8b', temperature=1)
prompt_template = PromptTemplate.from_template("You are a helpful AI assistant.")
examples = [
{"question": "Who are you?", "answer": "🐱 I am Aiko, a helpful AI assistant."},
{"question": "What is your name?", "answer": "🐱 My name is Aiko."},
{"question": "What can you do for me?", "answer": "🐱 I can generate text."},
]
few_shot_prompt = FewShotPromptTemplate(
examples=examples,
example_prompt=PromptTemplate.from_template(template="Question: {question}\nAnswer: {answer}"),
suffix=""
)
final_prompt_template = PromptTemplate.from_template("""
{introduction}
Example Q&A:
{example_qa}
User question: {input}
""")
pipline_prompt_template = PipelinePromptTemplate(
final_prompt=final_prompt_template,
pipeline_prompts=[
("introduction", prompt_template),
("example_qa", few_shot_prompt),
],
input_variables=["input"]
)
chain = pipline_prompt_template | llm | StrOutputParser()
result = chain.invoke({"input": "Hi, who r u ?"})
print(result)
上面例子代码中,我们将prompt_template
和few_shot_prompt
这两个变量组合到了一起,并拼接进了final_prompt_template
中,最后拼接的结果存储在pipline_prompt_template
里。
有时我们输入的Prompt会异常复杂,其中可能包含判断和循环逻辑,生成这样一个Prompt需要一个模板引擎来实现。LangChain的早期版本集成了广泛使用的Jinja2模板引擎,但最新版本换成了比较奇葩的Mustache,不过用法还是差不多的。下面例子构建的Prompt相对复杂一些,它实现了一个类似“角色扮演”的功能,在PromptTemplate
中我们使用模板引擎遍历了一个数组npc_data
,并读取了数组元素的属性。
from langchain.globals import set_debug, set_verbose
from langchain_core.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_ollama import OllamaLLM
set_debug(True)
set_verbose(True)
prompt_template = PromptTemplate.from_template(
"""
{{#npc_data}}
Name: {{name}}
Age: {{age}}
Desc: {{desc}}
{{/npc_data}}
Now you play the role of {{npc}}. You print what {{npc}} says and surround his or her actions with `*`.
Reply with the format below:
{{npc}}: What {{npc}} say.*{{npc}}'s action and psychological activities*
===
{{player}}: {{input}}
""", template_format='mustache')
llm = OllamaLLM(model='llama3.1:8b', temperature=1)
chain = prompt_template | llm | StrOutputParser()
npc_data = [
{'name': 'Tom', 'age': '18', 'desc': 'a worker in a factory'},
{'name': 'Jerry', 'age': '17', 'desc': 'a high school student'},
]
result = chain.invoke({'npc_data': npc_data, 'npc': 'Jerry', 'player': 'Tom', 'input': 'Who are you?' })
print(result)
我们构建Prompt时指定了template_format
属性,它的默认值是f-string
,仅能实现一些基础的字符串替换。这里我们将其指定为mustache
,即使用Mustache模板引擎,此时PromptTemplate
就能正确渲染为我们需要的内容了。
了解上面内容后,我们可以发现其实模板引擎才是最强大的,像Few-Shot提示词模板、Pipeline组合模板本身都可以通过模板引擎快速实现,不过实际开发中,我们还是尽量利用LangChain封装好的工具类,这样能让我们构建的提示词基本保持在一个最佳实践的范围内,如果LangChain内置的功能实在实现不了,再考虑使用模板引擎。当然,有些人也可能认为LangChain封装了太多内容,屏蔽了底层信息的同时也丧失的灵活性,这是严重的“过度设计”,我们这里就先不纠结这个问题了,具体开发时根据我们的喜好选择即可。
ChatPromptTemplate
的用法比较灵活,它不仅支持模板占位符,还支持传入对话历史消息,基于ChatPromptTemplate
我们很容易能够实现和大语言模型的多轮对话。有关ChatPromptTemplate
我们放在下一节实现聊天机器人
中介绍。
补充:在LangChain中,对于大语言模型(LLM)的封装类其实主要有两种,前面我们用到的是BaseLLM
,它是对LLM输入输出的基本封装,而BaseChatModel
则主要用于多轮对话,它处理的数据是“消息”,BaseChatModel
通常需要配合ChatPromptTemplate
使用。
LangChain维护了一个提示词的仓库LangChainHub,对于一些复杂的提示词,我们可以参考这里学习如何编写效果最好。
注意:LangChainHub的官方推荐使用方式例子如下。
from langchain import hub
prompt = hub.pull("资源名")
然而,实际开发中非常不推荐使用这种方式,“提示词”就是个模板字符串,这么简单的东西还要从远程加载就有点搞笑了,简直是“大炮打蚊子”,而且实际开发中我们可能需要对提示词进行许多调整,这样直接加载的方式不方面我们修改,建议直接把相关文本内容复制到代码中即可。