langchain学习笔记之小样本提示词Few-shot Prompt Template

发布于:2025-02-14 ⋅ 阅读:(16) ⋅ 点赞:(0)

引言

本节将介绍通过小样本提示词 ( Few-shot Prompt Templates ) (\text{Few-shot Prompt Templates}) (Few-shot Prompt Templates)进行提示词追加的方法。

Few-shot Prompt Templates \text{Few-shot Prompt Templates} Few-shot Prompt Templates简单介绍

提示词中添加交互样本的作用是为了模型能够更好地理解用户的想法,从而在回答问题/执行任务中,其结果能够更加满足用户的要求。而小样本提示词模板 ( Few-shot Prompt Templates ) (\text{Few-shot Prompt Templates}) (Few-shot Prompt Templates)旨在使用一组少量的示例来指导模型处理新的输入。

这些少量示例可以用来训练模型,并让模型更好地理解和回答类似的问题。

示例集创建

假设我们提出的问题是:梁思成和林徽因谁的寿命更长 ? ? ? 在对大模型进行提问之前,先准备一个示例数组examples,其里面包含若干个问答样例:
为节省篇幅,这里仅使用两个问答样例。

examples = [
    {
        "question": "穆罕默德·阿里和艾伦·图灵谁的寿命更长?",
        "answer": """
        这里需要跟进问题吗?是的
        跟进: 穆罕默德·阿里去世时多大?
        中间答案: 穆罕默德·阿里去世时74岁
        跟进: 艾伦·图灵去世时多大?
        中间答案: 艾伦·图灵去世时41岁
        最终答案: 穆罕默德·阿里
        """
    },
    {
        "question": "《大白鲨》和《007:皇家赌场》的导演都来自同一个国家吗?",
        "answer": """
        这里需要跟进问题吗?是的
        跟进: 《大白鲨》的导演是谁?
        中间答案: 《大白鲨》的导演是Steven Spielberg
        跟进: Steven Spielberg来自哪里?
        中间答案: 美国
        跟进: 《007:皇家赌场》的导演是谁?
        中间答案: 007:皇家赌场》的导演是Martin Campbell
        跟进: Martin Campbell来自哪里?
        中间答案: 新西兰
        最终答案: 不是
        """
    }
]

观察上述样例,我们发现:在样例的 answer \text{answer} answer部分,用户对模型的回答结果存在具体格式。例如:

  • 开头部分这里需要跟进问题吗?是的 这样一个开头描述;
  • 推理过程中的格式跟进中间答案 等格式字样;
  • 结论部分格式最终答案 格式字样。

创建 ExamplePrompt \text{ExamplePrompt} ExamplePrompt ExampleSelector \text{ExampleSelector} ExampleSelector

创建一个example_prompt,将用户给出的示例格式进行优化,用于后续FewShotPromptTemplate使用:

from langchain.prompts.prompt import PromptTemplate
example_prompt = PromptTemplate(
	input_variables=["question", "answer"],
	template="问题: {question} \n {answer}"
)

原始结果(示例):

{
    "question": "穆罕默德·阿里和艾伦·图灵谁的寿命更长?",
    "answer": """
    这里需要跟进问题吗?是的
    跟进: 穆罕默德·阿里去世时多大?
    中间答案: 穆罕默德·阿里去世时74岁
    跟进: 艾伦·图灵去世时多大?
    中间答案: 艾伦·图灵去世时41岁
    最终答案: 穆罕默德·阿里
    """
}

优化结果

问题: 穆罕默德·阿里和艾伦·图灵谁的寿命更长? 
 
        这里需要跟进问题吗?是的
        跟进: 穆罕默德·阿里去世时多大?
        中间答案: 穆罕默德·阿里去世时74岁
        跟进: 艾伦·图灵去世时多大?
        中间答案: 艾伦·图灵去世时41岁
        最终答案: 穆罕默德·阿里

出现一个新的问题,由于大模型的上下文长度有限,这意味着:输入的提示信息有限。基于该情况,一个朴素的想法是:从冗长示例中选择若干个相似示例作为最终的提示结果

如上例中:问题:梁思成与林徽因谁的寿命更长 ? ? ? 对应的示例明显是第一个:穆罕默德·阿里和艾伦·图灵谁的寿命更长 ? ? ? 因为它们之间描述的语义相似,只是名字部分有所区别。

如何找到与问题相似的示例信息 ? ? ? 需要创建一个示例选择器 ( ExampleSelector ) (\text{ExampleSelector}) (ExampleSelector)来执行这个操作:

from langchain.prompts.example_selector import SemanticSimilarityExampleSelector

一个示例选择器的具体格式示例如下:

from langchain_community.vectorstores import DashVector
from langchain_community.embeddings import DashScopeEmbeddings

example_selector = SemanticSimilarityExampleSelector.from_examples(
    examples=examples,
    embeddings=DashScopeEmbeddings(),
    vectorstore_cls=DashVector,
    vectorstore_kwargs={
        "endpoint": DASHVECTOR_ENDPOINT,
        "api_key": DASHVECTOR_API_KEY
    },
    k=1
)

这是一个通过计算问题与各示例样本之间的语义相似度的方式来获取示例样本的选择器。其中:

  • examples:可供选择的示例列表
  • embedding:用于生成 embedding \text{embedding} embedding的类,由于我们使用的 Qwen \text{Qwen} Qwen模型,因此使用DashScopeEmbedding将问题以及示例中的文字进行向量化处理
  • vectorstore_cls:与 Qwen \text{Qwen} Qwen匹配,使用DashVector Embedding \text{Embedding} Embedding结果进行存储以及相似度检索工作的vectorstore类。
  • vectorstore_kwargs:阿里云的向量检索服务需要申请相应的api-keyend-points使向量检索正常运行。
    其中api-key可以从阿里云百炼中进行申请; end-points可以在阿里云的向量检索服务中创建 Cluster \text{Cluster} Cluster进行获取,有免费使用额度。
  • k:表示example_selector要生成的示例数。

这个选择器就可以通过语义相似度找出与问题相关的示例信息:

select_res = example_selector.select_examples({"question": question_input})

在本例中如果将question_input中加入上述问题:梁思成与林徽因谁的寿命更长 ? ? ?,其最终返回的是examples中的第一个样本。

  • 由于example_selector中参数k=1的缘故,因而select_examples方法返回的select_res列表中仅包含一个样本,结合我们提供的示例长度,以及 llm \text{llm} llm支持的上下文长度,适当地调整k值,以便得到更满足要求的问答结果。
{'question': '穆罕默德·阿里和艾伦·图灵谁的寿命更长?', 'answer': '\n        这里需要跟进问题吗?是的\n        跟进: 穆罕默德·阿里去世时多大?\n        中间答案: 穆罕默德·阿里去世时74岁\n        跟进: 艾伦·图灵去世时多大?\n        中间答案: 艾伦·图灵去世时41岁\n        最终答案: 穆罕默德·阿里\n        '}

创建 FewShotPromptTemplate \text{FewShotPromptTemplate} FewShotPromptTemplate

至此,已经通过example_selector找到相似的示例信息,需要将示例信息与问题进行整合,并生成新的prompt

from langchain.prompts.few_shot import FewShotPromptTemplate
prompt = FewShotPromptTemplate(
        example_selector=example_selector,
        example_prompt=example_prompt,
        suffix=f"问题:{question_input}",

其中:

  • example_selector:上面建立的样本选择器
  • example_prompt:将选择出的样本进行使用example_prompt进行格式上的优化;
  • suffix:输入我们要提问的问题question_input。在FewShotPromptTemplatequestion_input执行了两个动作:一个是提供给example_selector找出语义相似的样本信息(example_selector自身不包含问题信息);另一个是将格式优化好的prompt信息与问题进行整合。

最终生成的prompt表示如下:

print(prompt.format(input="梁思成和林徽因谁的寿命更长"))

"""
问题: 穆罕默德·阿里和艾伦·图灵谁的寿命更长? 
 
        这里需要跟进问题吗?是的
        跟进: 穆罕默德·阿里去世时多大?
        中间答案: 穆罕默德·阿里去世时74岁
        跟进: 艾伦·图灵去世时多大?
        中间答案: 艾伦·图灵去世时41岁
        最终答案: 穆罕默德·阿里
        

问题:梁思成和林徽因谁的寿命更长?
"""

将上述结果分成 3 3 3个部分,前 2 2 2个部分是example_selector找到的语义相似的示例样本并使用example_prompt优化后的效果;最后一个部分是我们提出的问题。上述结果作为最终的 prompt \text{prompt} prompt与大模型进行交互。

比对结果

如果没有example_selector产生的信息,仅将问题部分作为prompt与大模型进行交互,对应结果表示如下:

"""
问题:梁思成和林徽因谁的寿命更长?
"""
梁思成(1901420日-197219日),享年70岁。
林徽因(1904610日-195541日),享年50岁。
因此,梁思成的寿命更长。

很明显,语义没有任何问题,只是格式并不是我们想要的格式;如果使用example_selectorFewShotPromptTemplate处理后的prompt,它的对应结果表示如下:

"""
问题: 穆罕默德·阿里和艾伦·图灵谁的寿命更长? 
 
        这里需要跟进问题吗?是的
        跟进: 穆罕默德·阿里去世时多大?
        中间答案: 穆罕默德·阿里去世时74岁
        跟进: 艾伦·图灵去世时多大?
        中间答案: 艾伦·图灵去世时41岁
        最终答案: 穆罕默德·阿里
        

问题:梁思成和林徽因谁的寿命更长?
"""

为了回答这个问题,我们需要了解梁思成和林徽因各自的寿命。

跟进: 梁思成去世时多大?
中间答案: 梁思成出生于1901420日,去世于197219日,享年70岁。

跟进: 林徽因去世时多大?
中间答案: 林徽因出生于1904610日,去世于195541日,享年51岁。

最终答案: 梁思成的寿命更长。

这次结果的语义正确,并且格式上也和示例中的格式非常相似。这是因为处理后的prompt llm \text{llm} llm提供了更丰富的上下文信息

完整代码

"""
样本交互:
若Human单独地提出一个prompt,assistant的回答结果 -> 可能是[宽泛]的
这里的[宽泛]是指:回答结果的语义/格式和Human想要的结果之间存在差异

朴素策略:
通过一系列对话的方式引导assistant反馈给我们更精准/更满足条件的答案
[一系列对话]会给assistant提供上下文的记忆(memory)

或者说,在prompt之前,Human主动提供一些示例 -> 供assistant参考
可以看作是一种[微型的]finetune操作
"""
from langchain.prompts.few_shot import FewShotPromptTemplate
from langchain.prompts.prompt import PromptTemplate
from langchain.prompts.example_selector import SemanticSimilarityExampleSelector
from langchain_community.vectorstores import DashVector
from langchain_community.embeddings import DashScopeEmbeddings
from langchain_community.llms import Tongyi

# 自行申请相应的api-key和end-point
DASHVECTOR_API_KEY = 'YOUR_DASHVECTOR_API_KEY'
DASHVECTOR_ENDPOINT = 'YOUR_DASHVECTOR_ENDPOINT'

examples = [
    {
        "question": "穆罕默德·阿里和艾伦·图灵谁的寿命更长?",
        "answer": """
        这里需要跟进问题吗?是的
        跟进: 穆罕默德·阿里去世时多大?
        中间答案: 穆罕默德·阿里去世时74岁
        跟进: 艾伦·图灵去世时多大?
        中间答案: 艾伦·图灵去世时41岁
        最终答案: 穆罕默德·阿里
        """
    },
    {
        "question": "《大白鲨》和《007:皇家赌场》的导演都来自同一个国家吗?",
        "answer": """
        这里需要跟进问题吗?是的
        跟进: 《大白鲨》的导演是谁?
        中间答案: 《大白鲨》的导演是Steven Spielberg
        跟进: Steven Spielberg来自哪里?
        中间答案: 美国
        跟进: 《007:皇家赌场》的导演是谁?
        中间答案: 007:皇家赌场》的导演是Martin Campbell
        跟进: Martin Campbell来自哪里?
        中间答案: 新西兰
        最终答案: 不是
        """
    }
]

def langchain_few_shot_interaction(question_input):

    example_selector = SemanticSimilarityExampleSelector.from_examples(
        examples=examples,
        embeddings=DashScopeEmbeddings(),
        vectorstore_cls=DashVector,
        vectorstore_kwargs={
            "endpoint": DASHVECTOR_ENDPOINT,
            "api_key": DASHVECTOR_API_KEY
        },
        k=1
    )

    example_prompt = PromptTemplate(
        input_variables=["question", "answer"],
        template="问题: {question} \n {answer}"
    )
    prompt = FewShotPromptTemplate(
        example_selector=example_selector,
        example_prompt=example_prompt,
        suffix=f"问题:{question_input}",
    )
    return (example_selector.select_examples({"question": question_input}),
            prompt.format(input=question_input))


def console(question_input, model_input):

    select_res, template = langchain_few_shot_interaction(question_input)
    template_original = question_input
    prompt_original = PromptTemplate.from_template(template_original)
    prompt = PromptTemplate.from_template(template)

    # 仅将question_input作为prompt
    # chain_original = prompt_original | model_input
    # result = chain_original.stream({
    #     "question": question_input
    # })

    chain = prompt | model_input
    result = chain.stream({
        "question": question_input
    })
    return result

if __name__ == '__main__':
    q = "梁思成和林徽因谁的寿命更长?"
    llm = Tongyi(
        model_name="tongyi-7b-chinese",
        temperature=0.5,
        max_tokens=100,
    )
    res = console(q, llm)
    for chunk in res:
        print(chunk, end="", flush=True)