验证迭代的一般思路
以调用和发挥大模型的大模型开发相较于传统的AI开发更注重验证迭代。
在使用LLM构建应用程序时,可能会经历以下流程,首先,你会在一到三个样本的小样本中调整 Prompt ,尝试使其在这些样本上起效。随后,当你对系统进行进一步测试时,可能会遇到一些棘手的例子,这些例子无法通过 Prompt 或者算法解决。这就是使用 LLM 构建应用程序的开发者所面临的挑战。在这种情况下,你可以将这些额外的几个例子添加到你正在测试的集合中,有机地添加其他难以处理的例子。最终,你会将足够多的这些例子添加到你逐步扩大的开发集中,以至于手动运行每一个例子以测试 Prompt 变得有些不便。然后,你开始开发一些用于衡量这些小样本集性能的指标,例如平均准确度。这个过程的有趣之处在于,如果你觉得你的系统已经足够好了,你可以随时停止,不再进行改进。实际上,很多已经部署的应用程序就在第一步或第二步就停下来了,而且它们运行得非常好。
解决bad case
在少样例中找到 bad case
有了bad case,还需要修改prompt使之提高效果。
构造思维链
大模型可以很好地理解并执行指令,但模型本身有一些能力的限制,例如大模型的幻觉等,可以通过构造思维链,将prompt构造成一系列步骤来尽量减少其能力限制。例如构造一个两步的思维链,要求模型在第二步做出反思,以尽可能消除大模型的幻觉问题。
template_v4 = """
请你依次执行以下步骤:
① 使用以下上下文来回答最后的问题。如果你不知道答案,就说你不知道,不要试图编造答案。
你应该使答案尽可能详细具体,但不要偏题。如果答案比较长,请酌情进行分段,以提高答案的阅读体验。
如果答案有几点,你应该分点标号回答,让答案清晰具体。
上下文:
{context}
问题:
{question}
有用的回答:
② 基于提供的上下文,反思回答中有没有不正确或不是基于上下文得到的内容,如果有,修改回答"""
QA_CHAIN_PROMPT = PromptTemplate(input_variables=["context","question"],
template=template_v4)
qa_chain = RetrievalQA.from_chain_type(llm,
retriever=vectordb.as_retriever(),
return_source_documents=True,
chain_type_kwargs={"prompt":QA_CHAIN_PROMPT})
question = "我们应该如何去构造一个LLM项目"
result = qa_chain({"query": question})
print(result["result"])
增加一个指令解析
用户的提问中可能包含有更多要求。但LLM并没有理解到。
针对该问题,一个存在的解决方案是,在我们的检索 LLM 之前,增加一层 LLM 来实现指令的解析,将用户问题的格式要求和问题内容拆分开来。这样的思路其实就是目前大火的 Agent 机制的雏形,即针对用户指令,设置一个 LLM(即 Agent)来理解指令,判断指令需要执行什么工具,再针对性调用需要执行的工具,其中每一个工具可以是基于不同 Prompt Engineering 的 LLM,也可以是例如数据库、API 等。LangChain 中其实有设计 Agent 机制,但本教程中我们就不再赘述了,这里只基于 OpenAI 的原生接口简单实现这一功能。
import openai
# 一个封装 OpenAI 接口的函数,参数为 Prompt,返回对应结果
def get_completion(prompt, model="gpt-3.5-turbo", temperature = 0):
'''
prompt: 对应的提示词
model: 调用的模型,默认为 gpt-3.5-turbo(ChatGPT),有内测资格的用户可以选择 gpt-4
'''
messages = [{"role": "user", "content": prompt}]
response = openai.ChatCompletion.create(
model=model,
messages=messages,
temperature=temperature, # 模型输出的温度系数,控制输出的随机程度
)
# 调用 OpenAI 的 ChatCompletion 接口
return response.choices[0].message["content"]
question = 'LLM的分类是什么?给我返回一个 Python List'
# 首先将格式要求与问题拆分
input_lst_s = get_completion(prompt_input.format(question))
rule, new_question = eval(input_lst_s)
# 接着使用拆分后的问题调用检索链
result = qa_chain({"query": new_question})
result_context = result["result"]
# 接着调用输出格式解析
response = get_completion(prompt_output.format(new_question, result_context, rule))
response
可以看到,经过如上步骤,我们就成功地实现了输出格式的限定。当然,在上面代码中,核心为介绍 Agent 思想,事实上,不管是 Agent 机制还是 Parser 机制(也就是限定输出格式),LangChain 都提供了成熟的工具链供使用,欢迎感兴趣的读者深入探讨,此处就不展开讲解了。