尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

LLM-Cookbook 学习——搭建基于 ChatGPT 的问答系统>搭建一个带评估的端到端问答系统

LLM-Cookbook 学习——搭建基于 ChatGPT 的问答系统>搭建一个带评估的端到端问答系统 一、前言前面几节我们分别学习了很多构建 LLM 应用需要的模块例如Classification对用户输入进行分类Moderation检查输入是否安全Chain of Thought处理复杂问题Chaining Prompts将复杂任务拆成多个步骤Check Outputs检查模型最终生成的答案。但是前面的内容大多是在单独介绍某一个模块。这一节开始把这些模块真正连接起来构建一个完整的End-to-End Question Answering System——端到端问答系统所谓“端到端”可以简单理解成用户提出问题 ↓ 系统自动完成中间所有处理 ↓ 最终返回答案用户不需要关心中间经历了多少个 Prompt、多少次检索和多少次模型调用。这一节实现的客服系统核心流程为用户输入 ↓ 输入安全检查 ↓ 提取商品和商品类别 ↓ 查询商品资料 ↓ LLM 生成回答 ↓ 输出安全检查 ↓ LLM 评价回答质量 ↓ 返回答案 / 转人工客服这已经非常接近一个实际 LLM 应用的基本架构。二、本节最核心的函数process_user_message_ch()这一节最重要的代码就是process_user_message_ch()从名字就可以理解process ↓ 处理 user_message ↓ 用户消息因此它的作用就是接收一条用户消息然后经过完整的问答流程最后返回处理后的回答。函数大致定义为def process_user_message_ch( user_input, all_messages, debugTrue ):这里有三个参数。user_input表示用户当前输入的问题例如user_input 请介绍一下 SmartX ProPhoneall_messages表示之前所有的对话历史它的作用非常重要。如果没有all_messages那么每次用户提问对于模型来说都是一次新的对话。例如用户 SmartX ProPhone 多少钱 助手 899.99 美元。 用户 它支持 5G 吗第二句话中的它到底指什么如果没有前面的聊天记录模型可能不知道。但是通过all_messages把历史对话一起发送给模型模型就能够知道它 SmartX ProPhone因此all_messages是这个系统实现多轮对话的重要基础。debugTrue这个参数表示是否开启调试模式如果debugTrue程序就会不断输出第一步输入通过 Moderation 检查 第二步抽取出商品列表 第三步查找抽取出的商品信息 ……这样做最大的作用就是方便开发者知道程序现在运行到了哪一步。如果系统出现错误就可以快速定位到底是哪一步出问题。三、整个问答系统的七个步骤这一节的代码虽然比较长但其实只需要抓住一个核心process_user_message_ch()就是在按照顺序执行七个步骤。可以先建立一个整体认识用户问题 │ ↓ ① Moderation 输入检查 │ ↓ ② 提取商品和类别 │ ↓ ③ 查询商品信息 │ ↓ ④ 生成回答 │ ↓ ⑤ Moderation 输出检查 │ ↓ ⑥ LLM 自评 │ ┌────┴────┐ │ │ Y N │ │ ↓ ↓ ⑦返回答案 转人工客服下面逐步分析。四、第一步检查用户输入逻辑可以简化成moderation_result moderation(user_input) if moderation_result[flagged]: return 抱歉您的请求不合规也就是用户输入 ↓ Moderation ↓ 是否违规 ┌────┴────┐ │ │ 是 否 │ │ ↓ ↓ 拒绝 继续五、第二步从问题中提取商品和商品类别如果用户输入通过检查程序开始分析用户到底在问哪些商品课程使用utils_zh.find_category_and_product_only(...)来完成这一步。例如用户输入请告诉我 SmartX ProPhone 和 FotoSnap Camera 的信息 另外介绍一下你们的电视。模型需要识别SmartX ProPhone ↓ Phones and Accessories FotoSnap Camera ↓ Cameras and Camcorders TV ↓ Televisions and Home Theater Systems也就是说这一步不是直接回答问题而是在完成信息抽取Information Extraction可以理解为自然语言 ↓ 结构化信息例如原始问题“介绍一下 SmartX ProPhone”经过处理后可能变成类似[ { category: Smartphones, products: [SmartX ProPhone] } ]六、read_string_to_list() 是干什么的接下来课程中又调用utils_zh.read_string_to_list(...)这里很容易产生疑问前面不是已经识别出商品了吗为什么还要再转换因为 LLM 返回的内容本质上通常还是字符串 String看起来即使像[ {category: 手机, products: [SmartX ProPhone]} ]它也可能只是一串文本而不是 Python 真正能够直接操作的list所以read_string_to_list()相当于进行模型输出的字符串 ↓ Python 数据结构 ↓ list这样程序后面才更方便进行遍历、查询和处理。七、第三步根据商品名称查询商品资料拿到商品列表以后接下来调用utils_zh.generate_output_string(...)获取真正的商品信息。例如前面只知道SmartX ProPhone现在需要查出品牌SmartX 型号SX-PP10 屏幕6.1 英寸 存储128GB 摄像头12MP 网络5G 价格899.99 美元 ……这一过程可以理解为用户问题 ↓ 提取商品名称 ↓ 商品数据库 ↓ 找到对应商品资料这一步非常关键。因为我们不希望 LLM 完全依靠自己训练时学到的知识回答。我们希望先找到可信的业务数据再让 LLM 根据这些数据回答。这种思想实际上已经与 RAG 非常接近Retrieval ↓ 检索资料 Generation ↓ 根据资料生成回答八、第四步让 LLM 根据资料生成最终回答拿到商品资料以后就可以真正让 LLM 回答用户问题了。首先定义system_message它负责告诉模型你是谁 你应该用什么语气 你应该怎样回答例如系统设定模型是一家大型电子商店的客户服务助理并要求语气友好 回答简洁 乐于帮助用户这里再次体现了System Message ↓ 规定模型角色和行为而用户真正的问题则放在User Message中。九、messages 为什么包含三种内容这一部分构造的messages很值得理解。整体可以理解成messages [ system_message, 用户的问题, 查询得到的商品资料 ]从模型视角来看System 你是一名电子商店客服。 User 用户问了 SmartX ProPhone。 Assistant / Context 这是与这个问题有关的商品资料。 → 请根据这些信息生成回答。所以 LLM 并不是凭空回答。它实际上同时拥有用户问题 系统规则 商品资料最终final_response就是模型生成的客服答案。十、为什么是 all_messages messages课程中一个非常值得注意的地方是get_completion_from_messages( all_messages messages )这里的不是数学上的加法。因为all_messages和messages都是列表。Python 中list1 list2表示把两个列表拼接起来。例如a [1, 2] b [3, 4] print(a b)得到[1, 2, 3, 4]所以all_messages messages实际上就是以前的聊天历史 当前这一轮消息 ↓ 完整对话上下文最后一起交给 LLM。这就是这个系统实现多轮上下文对话的重要机制。十一、为什么又要更新 all_messages模型回答后all_messages还需要继续更新。原因很简单这一轮对话结束 ↓ 这一轮也成为“历史” ↓ 下一轮模型需要看到例如第一轮 用户SmartX ProPhone 多少钱 AI899.99 美元。下一轮用户它有保修吗如果第一轮已经保存进all_messages模型就可以结合历史判断“它” SmartX ProPhone所以多轮对话本质上就是不断进行新消息 ↓ 加入 history ↓ 下一轮再次发送 history十二、messages[1:] 是什么意思代码中还会看到类似messages[1:]这是 Python 的切片 Slice假设messages [ system, user, product_information ]那么messages[1:]意思就是从下标 1 开始 一直取到最后结果[ user, product_information ]因为 Python 下标从0开始。所以messages[0] → system messages[1] → user messages[2] → product_information这里使用messages[1:]就是把当前这一轮需要保存的内容添加到历史消息中。十三、第五步再次使用 Moderation 检查模型输出这一步正好对应上一节Check Outputs虽然用户输入已经通过 Moderation但这不意味着 LLM 最终生成的回答一定没有问题。因此用户输入检查一次。模型生成final_response之后再检查一次。形成Moderation ↓ 用户输入 → LLM → 模型回答 ↓ Moderation因此系统实际上有入口安全检查 出口安全检查如果最终回答被标记flagged True程序就不会直接把原回答发给用户。而是返回一个更加安全的提示。十四、第六步让模型评价自己的回答安全检查通过以后还没有结束。因为安全 ≠ 回答正确也不代表安全 ≠ 真正回答了用户问题因此系统又进行了一次Response Evaluation即模型评价模型生成的答案程序会把用户问题 刚刚生成的回答再次交给 LLM。然后问这个回答是否足够回答用户的问题要求模型只返回Y或者N这实际上就是第一次调用 LLM ↓ Generator 生成答案 第二次调用 LLM ↓ Evaluator 评价答案因此LLM 不仅负责生成 LLM 还可以负责评价这也是上一节学习的 LLM-as-a-Judge 思想在完整系统中的实际使用。十六、为什么写Y in evaluation_response课程使用类似if Y in evaluation_response:而不是if evaluation_response Y:区别在于要求两边内容必须完全一样。例如evaluation_response Yes那么evaluation_response Y结果是False但是Y in Yes结果True课程这么做的目的是为了应对 LLM 偶尔没有严格按照 Prompt 只输出Y而输出Yes的情况。因此这里的in表示检查某个字符串是否包含在另一个字符串中。例如a in apple得到True十七、第七步回答通过就返回否则转人工经过前面所有步骤后最终进行判断。如果评价结果为Y说明模型认为回答充分解决了用户问题于是直接把 final_response 返回给用户如果结果是N程序不会强行把这个答案发送出去。而是告诉用户当前无法提供合适答案 将转接人工客服。所以最终逻辑就是LLM评价 │ ┌──────┴──────┐ │ │ Y N │ │ ↓ ↓ 返回模型答案 转人工客服这体现了一个非常重要的系统设计思想模型无法可靠完成任务时系统应该允许失败和降级而不是强制让模型给出答案。十八、把七步流程完整串起来到这里就可以把process_user_message_ch()理解成下面这个“大函数”process_user_message_ch() 输入 用户问题 历史对话 ↓ ① 输入 Moderation ↓ ② 识别商品和类别 ↓ ③ 查询商品数据库 ↓ ④ LLM 根据资料回答 ↓ ⑤ 输出 Moderation ↓ ⑥ LLM 判断回答质量 ↓ ⑦ 返回答案 / 转人工 输出 最终回答 更新后的聊天历史它自己并不负责完成所有具体任务而是调用各种不同的小模块 ↓ 按照一定顺序组织它们 ↓ 最终完成整个业务流程这其实已经非常接近 Agent 和 Workflow 中的Orchestration即任务编排。十九、collect_messages_ch() 又是什么完成核心问答逻辑以后课程还定义了collect_messages_ch()这个函数主要负责接收界面上的用户输入并调用前面的问答系统。注意它和process_user_message_ch()功能不一样。可以这样区分collect_messages_ch() ↓ 负责“界面层” process_user_message_ch() ↓ 负责“业务逻辑层”例如用户在输入框打字 ↓ collect_messages_ch() ↓ process_user_message_ch() ↓ 生成回答 ↓ collect_messages_ch() ↓ 把回答显示在网页上二十、global context 是什么意思代码中出现global context这里表示函数内部要使用函数外部定义的context变量。context保存的是聊天历史例如context [ { role: system, content: You are Service Assistant } ]然后每进行一轮聊天用户消息 助手回答都会加入context于是context就越来越长。形成System User 1 Assistant 1 User 2 Assistant 2 User 3 Assistant 3 ……这样就可以实现连续多轮聊天。二十二、使用 Panel 制作简单聊天界面前面虽然已经实现问答系统但是仍然只能print(response)这样使用显然不够直观。所以课程最后使用Panel制作一个简单的图形化聊天界面。首先import panel as pn然后创建输入框pn.widgets.TextInput(...)可以理解成网页上的┌─────────────────────┐ │ 在这里输入问题…… │ └─────────────────────┘再创建按钮pn.widgets.Button(...)类似┌──────────────┐ │ Service │ │ Assistant │ └──────────────┘用户输入问题以后点击按钮点击按钮 ↓ collect_messages_ch() ↓ process_user_message_ch() ↓ 生成回答 ↓ 显示在页面这样一个最基础的聊天机器人界面就搭建出来了。二十三、pn.bind() 是什么意思课程中还有pn.bind(...)可以简单理解成把某个界面操作和某个 Python 函数绑定起来。例如点击按钮 ↓ 触发 collect_messages_ch()用户不是直接调用collect_messages_ch()而是用户点击按钮 ↓ Panel 自动调用函数于是 Python 后端逻辑和网页界面连接了起来。二十六、如果某一步效果不好怎么办课程最后还提出了一个很重要的思想。搭建完系统不意味着项目结束反而意味着现在终于可以开始系统地测试它了。例如测试大量问题以后发现问题1 商品名称经常提取错误那么应该优化第二步如果发现问题2 商品找对了但是回答经常遗漏信息就应该优化第四步 Prompt如果发现问题3 Evaluator 经常错误判断则应该优化第六步评价标准因此整个过程实际上是搭建系统 ↓ 测试 ↓ 发现错误 ↓ 定位错误发生在哪一步 ↓ 修改对应步骤 ↓ 再次测试形成Build ↓ Evaluate ↓ Improve ↓ Evaluate Again二十七、对“评估”这个概念的新理解以前看到Evaluation可能第一反应是准确率是多少但是对于 LLM 系统来说仅仅看一个准确率远远不够。一个完整系统可能需要分别评估输入分类是否正确 ↓ 商品识别是否正确 ↓ 检索资料是否正确 ↓ 生成答案是否正确 ↓ 安全审核是否正确 ↓ Evaluator 是否判断正确也就是说除了评价最终答案还应该评价整个 Pipeline 中的各个中间步骤。这也是后面两章Evaluation Part 1 Evaluation Part 2继续深入讨论的内容。二十八、这一节可以抽象成一个通用 LLM 系统虽然课程演示的是电子产品客服但是去掉具体商品名称以后整个架构其实非常通用用户问题 ↓ 安全检查 ↓ 理解用户需求 ↓ 检索相关资料 ↓ LLM 根据资料回答 ↓ 答案安全检查 ↓ 答案质量评价 ↓ 返回最终结果所以商品数据库完全可以替换成企业知识库 PDF 文档 论文数据库 芯片 Datasheet 法律文档 医疗指南 产品说明书整个系统架构依然成立。二十九、结合 RAG 理解这一节学习到这里以后其实已经可以看到 RAG 的基本影子。RAGRetrieval-Augmented Generation即检索增强生成核心思想用户问题 ↓ Retrieval 检索相关资料 ↓ 将资料放入 Prompt ↓ Generation LLM 根据资料生成回答而本节用户问题 ↓ 提取商品 ↓ 查询商品资料 ↓ LLM 根据商品资料回答实际上已经具有Retrieval Generation两个关键步骤。只不过课程中的数据规模比较小所以使用普通函数查找商品。当数据变成几百份 PDF 几千份文档 几十万段文本以后就需要更加完整的Embedding Vector Database Retrieval LLM也就是后面常见的 RAG 架构。三十、本节代码中几个容易混淆的变量变量含义user_input用户当前输入的问题all_messages历史聊天记录delimiterPrompt 内容分隔符category_and_product_response模型识别出的商品和类别category_and_product_list转换后的 Python 列表product_information查询得到的商品详细资料system_message定义 AI 身份和回答规则messages当前准备发送给模型的消息final_responseLLM 最终生成的客服回答evaluation_responseLLM 对回答质量的评价context图形聊天界面中持续保存的聊天历史debug是否显示各步骤运行信息把这些变量理解清楚以后再阅读process_user_message_ch()就会容易很多。
返回列表