构建提示词

在基于大语言模型(LLM)的应用程序开发中,构建提示词是最基本和最重要的部分,好的提示词能让LLM明确目标,并按照我们的预期输出内容。LangChain对提示词相关的功能进行了封装,我们可以基于框架的API组装符合LLM应用最佳实践的提示词。

PromptTemplate 提示词模板

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")

FewShotPromptTemplate Few-Shot提示词模板

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)

PipelinePromptTemplate 组合模板

假如我们有多个模板对象,它们可能是PromptTemplateFewShotPromptTemplate或是其它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_templatefew_shot_prompt这两个变量组合到了一起,并拼接进了final_prompt_template中,最后拼接的结果存储在pipline_prompt_template里。

使用Mustache模板引擎

有时我们输入的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我们很容易能够实现和大语言模型的多轮对话。有关ChatPromptTemplate我们放在下一节实现聊天机器人中介绍。

补充:在LangChain中,对于大语言模型(LLM)的封装类其实主要有两种,前面我们用到的是BaseLLM,它是对LLM输入输出的基本封装,而BaseChatModel则主要用于多轮对话,它处理的数据是“消息”,BaseChatModel通常需要配合ChatPromptTemplate使用。

LangChainHub

LangChain维护了一个提示词的仓库LangChainHub,对于一些复杂的提示词,我们可以参考这里学习如何编写效果最好。

注意:LangChainHub的官方推荐使用方式例子如下。

from langchain import hub
prompt = hub.pull("资源名")

然而,实际开发中非常不推荐使用这种方式,“提示词”就是个模板字符串,这么简单的东西还要从远程加载就有点搞笑了,简直是“大炮打蚊子”,而且实际开发中我们可能需要对提示词进行许多调整,这样直接加载的方式不方面我们修改,建议直接把相关文本内容复制到代码中即可。

作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。
Copyright © 2017-2024 Gacfox All Rights Reserved.
Build with NextJS | Sitemap