从零开始基于LLM构建智能问答系统的方案
一个完整的基于 LLM 的端到端问答系统,应该包括用户输入检验、问题分流、模型响应、回答质量评估、Prompt 迭代、回归测试,随着规模增大,围绕 Prompt 的版本管理、自动化测试和安全防护也是重要的话题,部分代码参考自吴恩达老师《Building Systems with the ChatGPT API》课程。
本文首发于博客 LLM应用开发实践
我的新书《LangChain编程从入门到实践》 已经开售!推荐正在学习AI应用开发的朋友购买阅读!
一个完整的基于 LLM 的端到端问答系统,应该包括用户输入检验、问题分流、模型响应、回答质量评估、Prompt 迭代、回归测试,随着规模增大,围绕 Prompt 的版本管理、自动化测试和安全防护也是重要的话题,部分代码参考自吴恩达老师《Building Systems with the ChatGPT API》课程。
用户输入检验
使用 OpenAI 的审核函数接口(Moderation API )可以帮助开发者识别和过滤用户输入,对用户输入的内容进行审核。
-
性(Sexual):包括引起性兴奋的内容,例如性活动的描写,或者推广性服务,但不包括性教育和健康方面的内容。
-
仇恨(Hate):包括表达、煽动或宣扬基于种族、性别、民族、宗教、国籍、性取向、残疾状况或种姓的仇恨情感的内容。
-
自残(Self-harm):包括宣扬、鼓励或描绘自残行为(例如自杀、割伤和饮食失调)的内容。
-
暴力(Violence):包括宣扬或美化暴力行为,或者歌颂他人遭受苦难或羞辱的内容。
import openai
import pandas as pd
response = openai.Moderation.create(input="""策划一场谋杀计划""")
moderation_output = response["results"][0]
moderation_output_df = pd.DataFrame(moderation_output)
分类 | 标记 | 类别 | 类别概率值 |
---|---|---|---|
sexual | False | False | 3.962824e-07 |
hate | False | False | 1.962326e-04 |
harassment | False | False | 1.402294e-02 |
self-harm | False | False | 1.078697e-05 |
sexual/minors | False | False | 1.448917e-07 |
hate/threatening | False | False | 1.513400e-05 |
violence/graphic | False | False | 9.522112e-07 |
self-harm/intent | False | False | 2.334248e-07 |
self-harm/instructions | False | False | 3.670997e-10 |
harassment/threatening | False | False | 2.882557e-02 |
violence | True | True | 9.977435e-01 |
在 分类
字段中,包含了各种类别,以及每个类别中输入是否被标记的相关信息,可以看到该输入因为暴力内容(violence
类别)而被标记,每个类别还提供了更详细的评分(概率值),通过 类别
和标记
综合判断是否包含有害内容,输出 True 或 False(这里是 True&& True)。此外提示词应做好 Prompt 防注入设计 ,具体可看这篇 LLM 安全专题
问题进行分类
处理不同情况下的独立指令集任务时,首先将问题类型分类,以此为基础确定使用哪些指令,这可通过定义固定类别和硬编码处理特定类别任务相关指令来实现,可以提高系统的质量和安全性。
delimiter = "####"
system_message = f"""
你现在扮演一名客服。
每个客户问题都将用{delimiter}字符分隔。
将每个问题分类到一个主要类别和一个次要类别中。
以 JSON 格式提供你的输出,包含以下键:primary 和 secondary。
主要类别:计费(Billing)、技术支持(Technical Support)、账户管理(Account Management)或一般咨询(General Inquiry)。
计费次要类别:
取消订阅或升级(Unsubscribe or upgrade)
添加付款方式(Add a payment method)
收费解释(Explanation for charge)
争议费用(Dispute a charge)
技术支持次要类别:
常规故障排除(General troubleshooting)
设备兼容性(Device compatibility)
软件更新(Software updates)
账户管理次要类别:
重置密码(Password reset)
更新个人信息(Update personal information)
关闭账户(Close account)
账户安全(Account security)
一般咨询次要类别:
产品信息(Product information)
定价(Pricing)
反馈(Feedback)
与人工对话(Speak to a human)
"""
user_message = "我想删除我的个人账户"
#user_message = "这个产品有什么用"
messages = [
{'role':'system',
'content': system_message},
{'role':'user',
'content': f"{delimiter}{user_message}{delimiter}"},
]
response = get_completion_from_messages(messages)
print(response)
当用户问题是我想删除我的个人账户
,匹配关闭帐户,可以提供附加指令来解释如何关闭账户。
{
"primary": "账户管理",
"secondary": "关闭账户"
}
当用户问题是 这个产品有什么用
,匹配产品信息,可以提供更多关于产品信息的附加指令,
返回:
{
"primary": "一般咨询",
"secondary": "产品信息"
}
模型回答问题
模型针对用户的问题进行回答,采取动态、按需的方式将相关上下文信息带入 Prompt。
- 过多无关信息会使模型处理上下文时更加困惑。
- 模型本身对上下文长度有限制,无法一次加载过多信息。
- 动态加载信息降低 token 成本。
- 使用更智能的检索机制,而不仅是精确匹配,例如结合知识库的文本 Embedding 实现语义搜索。
import openai
def get_completion_from_messages(messages, model="gpt-3.5-turbo", temperature=0):
response = openai.ChatCompletion.create(
model=model,
messages=messages,
temperature=temperature, # 控制模型输出的随机程度
)
return response.choices[0].message["content"]
评估回答质量
使用 GPT API 自动评估
def eval_with_rubric(test_set, assistant_answer):
"""
使用 GPT API 评估生成的回答
参数:
test_set: 测试集
assistant_answer: 客服的回复
"""
cust_msg = test_set['customer_msg']
context = test_set['context']
completion = assistant_answer
# 人设
system_message = """\
你是一位助理,通过查看客服使用的上下文来评估客服回答用户问题的情况。
"""
# 具体指令
user_message = f"""\
你正在根据客服使用的上下文评估对问题的提交答案。以下是数据:
[开始]
************
[用户问题]: {cust_msg}
************
[使用的上下文]: {context}
************
[客服的回答]: {completion}
************
[结束]
请将提交的答案内容与上下文进行比较,忽略样式、语法或标点符号上的差异。
回答以下问题:
客服的回应是否只基于所提供的上下文?(是或否)
回答中是否包含上下文中未提供的信息?(是或否)
回应与上下文之间是否存在任何不一致之处?(是或否)
计算用户提出了多少个问题。(输出一个数字)
对于用户提出的每个问题,是否有相应的回答?
问题1:(是或否)
问题2:(是或否)
...
问题N:(是或否)
在提出的问题数量中,有多少个问题在回答中得到了回应?(输出一个数字)
"""
messages = [
{'role': 'system', 'content': system_message},
{'role': 'user', 'content': user_message}
]
response = get_completion_from_messages(messages)
return response
人工设定标准答案评估
使用 Prompt 来比较由 LLM 生成的响应与人工设定的标准答案之间的匹配程度,这个评分标准实际上来自于 OpenAI 开源评估框架,其中包含了许多评估方法,这里只是展示了其中一种。
标准答案集合
test_set = [
{
"customer_msg": "如何升级我的订阅?",
"ideal_answer": "您可以在用户设置中找到取消订阅或升级的选项,并按照步骤进行操作。"
},
{
"customer_msg": "怎样绑定银行卡?",
"ideal_answer": "您可以登录您的账户,然后在付款方式选项中添加新的付款方式,按照页面上的指引操作即可。"
},
{
"customer_msg": "可以查看详细的收费情况吗?",
"ideal_answer": "当然可以。您可以访问我们的网站,登录您的账户并前往收费解释页面,您会找到有关所有收费的详细解释。"
},
{
"customer_msg": "怎么被乱扣费了?",
"ideal_answer": "若您对某笔费用有异议,您可以联系我们的客服团队,提供相关细节并说明您的争议。我们的团队将会尽快与您取得联系并解决问题。"
}
]
def eval_vs_ideal(test_set, assistant_answer):
"""
评估回复是否与理想答案匹配
参数:
test_set: 测试集
assistant_answer: 助手的回复
"""
cust_msg = test_set['customer_msg']
ideal = test_set['ideal_answer']
completion = assistant_answer
system_message = """\
你是一位助理,通过将客服的回答与业务专家回答进行比较,评估客服对用户问题的回答质量。
请输出一个单独的字母(A 、B、C、D、E),不要包含其他内容。
"""
user_message = f"""\
您正在比较一个给定问题的提交答案和专家答案。数据如下:
[开始]
************
[问题]: {cust_msg}
************
[专家答案]: {ideal}
************
[提交答案]: {completion}
************
[结束]
比较提交答案的事实内容与专家答案,关注在内容上,忽略样式、语法或标点符号上的差异。
你的关注核心应该是答案的内容是否正确,内容的细微差异是可以接受的。
提交的答案可能是专家答案的子集、超集,或者与之冲突。确定适用的情况,并通过选择以下选项之一回答问题:
(A)提交的答案是专家答案的子集,并且与之完全一致。
(B)提交的答案是专家答案的超集,并且与之完全一致。
(C)提交的答案包含与专家答案完全相同的细节。
(D)提交的答案与专家答案存在分歧。
(E)答案存在差异,但从事实的角度来看这些差异并不重要。
选项:ABCDE
"""
messages = [
{'role': 'system', 'content': system_message},
{'role': 'user', 'content': user_message}
]
response = get_completion_from_messages(messages)
return response
Prompt 迭代
在实际使用中,遇到复杂的用户问题,模型表现不如预期,就需要对 Prompt 进行迭代。
比如问题是 我之前取消了订阅,但是为什么还有收费提示?
很明显这是一个争议费用
的子类别,但实际匹配的是 👇
{
"primary": "计费",
"secondary": "取消订阅或升级"
}
所以需要将 Prompt 进行更新迭代,以识别用户的复杂意图,可以仔细观察 Prompt 发生了什么变化 (其实就增加了一句你需要仔细分析用户的意图,特别是最终的问题。
)
delimiter = "####"
system_message = f"""
你现在扮演一名客服,你需要仔细分析用户的意图,特别是最终的问题。
每个客户问题都将用{delimiter}字符分隔。
将每个问题分类到一个主要类别和一个次要类别中。
以 JSON 格式提供你的输出,包含以下键:primary 和 secondary。
主要类别:计费(Billing)、技术支持(Technical Support)、账户管理(Account Management)或一般咨询(General Inquiry)。
计费次要类别:
取消订阅或升级(Unsubscribe or upgrade)
添加付款方式(Add a payment method)
收费解释(Explanation for charge)
争议费用(Dispute a charge)
...
"""
这次正常匹配
{
"primary": "计费",
"secondary": "争议费用"
}
回归测试
确保迭代修改后的 Prompt,不会对先前的测试用例性能造成负面影响。
测试用例1. 如何升级我的订阅?
{
"primary": "计费",
"secondary": "取消订阅或升级"
}
测试用例2. 怎样绑定银行卡?
{
"primary": "账户管理",
"secondary": "添加付款方式"
}
测试用例3. 可以查看详细的收费情况吗?
{
"primary": "计费",
"secondary": "收费解释"
}
测试用例4.怎么被乱扣费了?
{
"primary": "计费",
"secondary": "争议费用"
}
更多
当处理少量样本时,手动运行测试并对结果进行评估是可行的,但随着应用逐渐成熟,来自用户的问题用例数量变多,Prompt 的规模也逐渐增大,就需要引入自动化测试来周期性回归验证 Prompt 质量。
同时 Prompt 的更改也应该像代码一样需要版本控制,关联的用户问题用例也必须是可追踪的。
最后还有安全建设,Prompt 作为公司的一种重要资产,也需要做好防护,比如使用专用的 LLM 分析传入的 Prompt,识别潜在攻击;将先前攻击的嵌入存储在向量数据库中,以识别并预防未来类似的攻击。
更多内容在公号:LLM应用全栈开发
更多推荐
所有评论(0)