diff --git a/notes/pics/agent-cs-design.png b/notes/pics/agent-cs-design.png new file mode 100644 index 00000000..8916fa97 Binary files /dev/null and b/notes/pics/agent-cs-design.png differ diff --git a/notes/大模型应用.md b/notes/大模型应用.md index 7721fbb8..ae72e83d 100644 --- a/notes/大模型应用.md +++ b/notes/大模型应用.md @@ -53,6 +53,7 @@ * [推理服务部署](#推理服务部署) * [稳定性](#稳定性) * [安全](#安全) + * [设计一个客服 Agent](#设计一个客服-agent) * [附录:实测](#附录实测) * [A1 Embedding 相似度与 BM25](#a1-embedding-相似度与-bm25) * [A2 提示词对工具调用的影响](#a2-提示词对工具调用的影响) @@ -131,7 +132,7 @@ Token 数决定了两件事: 2. 向量依次经过几十个结构相同的层,Qwen2.5-7B 有 28 层。每层先用注意力从前文收集信息,再用前馈网络(Feed-Forward Network, FFN)对每个位置单独加工; 3. 最后一个位置的向量经过线性层,得到词表中每个 Token 的分数(logits),再经过 softmax 转为概率。 -每个子层都带有残差连接和归一化。残差连接把子层的输入直接加到输出上,使几十层的网络也能稳定训练;归一化把每个向量的数值缩放到稳定的范围。最初的 Transformer 在残差相加之后做归一化,目前的大模型大多改为在每个子层之前做(Pre-Norm),残差的主干上不再经过归一化,几十层叠加时训练更稳定,并使用只按均方根缩放、计算更简单的 RMSNorm,Qwen2 系列就是这样。 +每个子层都带有残差连接和归一化。残差连接把子层的输入直接加到输出上,使几十层的网络也能稳定训练;归一化把每个向量的数值缩放到稳定的范围。这里用的是 LayerNorm 一类的方法,只用一个 Token 自己向量的各个维度计算统计量,而不像 BatchNorm 那样依赖同一批中的其它样本,所以不受批大小和序列长度影响,训练和推理时的计算也相同。最初的 Transformer 在残差相加之后做归一化,目前的大模型大多改为在每个子层之前做(Pre-Norm),残差的主干上不再经过归一化,几十层叠加时训练更稳定,并使用只按均方根缩放、计算更简单的 RMSNorm,Qwen2 系列就是这样。 三种结构的注意力有两处不同。编码器中每个 Token 都能看到全部 Token;解码器的自注意力带有因果掩码,只能看到前面的 Token,见下文的注意力机制。编码器-解码器结构的解码器每层还多一个交叉注意力(Cross-Attention):Q 来自解码器,K 和 V 来自编码器最后一层的输出,生成每个词时都可以查阅整句原文。 @@ -225,7 +226,7 @@ Q = x W_Q (n, 3584) 降低这部分开销有两类做法。一类让每个 Token 只看一部分 Token,或者改写 softmax,结果与标准注意力不同:例如 Mistral 7B 的滑动窗口注意力中,每层的每个 Token 只看前 4096 个 Token;线性注意力用核函数代替 softmax,利用矩阵乘法的结合律把复杂度降为 O(n)。另一类结果不变,只减少显存的读写,即 FlashAttention。 -FlashAttention 把计算分块放进 GPU 的片上缓存完成,n × n 的分数矩阵从不写回显存,再用在线 softmax 逐块累积,结果与标准注意力完全相同。它减少的是显存的读写而不是计算量,原理和数值例子见[进阶:计算量与 FlashAttention](大模型应用%20-%20进阶.md#2-计算量与-flashattention)。 +FlashAttention 把计算分块放进 GPU 的片上缓存完成,n × n 的分数矩阵从不写回显存,再用在线 softmax 逐块累积,结果在数学上与标准注意力相同,不是近似。它减少的是显存的读写而不是计算量,原理和数值例子见[进阶:计算量与 FlashAttention](大模型应用%20-%20进阶.md#2-计算量与-flashattention)。 ### 自回归生成与 KV Cache @@ -1800,7 +1801,7 @@ Skill 与系统提示词、Agent 的区别: Skill 中的规范只能引导,不能保证模型一定遵守,必须满足的规则还要用脚本或钩子检查,见 [Harness 的引导与反馈](#2-引导与反馈)。模型也不一定会加载 Skill,描述不够贴切时可能直接动手;部署、提交这类有副作用、必须按流程走的 Skill,可以设为只允许用户用斜杠命令(例如 /deploy)显式调用,Claude Code 中在 frontmatter 写 disable-model-invocation: true,它的描述也不再放进目录。 -Agent 是否使用某个 Skill,完全取决于它的 description,所以要像测试[意图识别与路由](#意图识别与路由)一样测试它: +除了用户用斜杠命令直接调用,模型自动选用 Skill 时,是根据目录中的名称和 description 判断它与当前任务是否相关,所以要像测试[意图识别与路由](#意图识别与路由)一样测试它: - 触发测试:准备约 20 条真实的提问,一半应该触发、一半不应该触发。不应该触发的重点选关键词相近但需求不同的,例如“把周报模板翻译成英文”不应该触发生成周报的 Skill。每条运行 3 次左右计算触发率,并留出一部分提问不参与修改,检查改进是否适用于新的说法; - 效果测试:同一批任务分别在有和没有这个 Skill(或者使用旧版本)的情况下运行,能用代码判断的项由脚本检查,其余由人或模型裁判比较,同时记录 Token 消耗和耗时。 @@ -2537,6 +2538,59 @@ OpenAI 的官方 SDK 默认也是这样处理的:对 408、409、429 和 5xx 每次工具调用都记录审计日志:谁发起的任务、调用了哪些工具、参数和结果是什么、谁确认了操作,出了问题可以追溯。 +### 设计一个客服 Agent + +“设计一个智能客服 Agent”这类题要把前面的机制组合成一个能上线的系统,下面仍以前文的电商客服为例,通用的答题顺序见[系统设计题的思路](系统设计基础.md#十三系统设计题的思路)。 + +#### 1. 需求和约束 + +先和面试官确认范围。假设要做的是电商 App 内的售后客服: + +- 功能:查询订单和物流,回答退换货政策和商品问题,办理退款和改地址,处理不了的创建工单或转人工; +- 规模:平时每天 10 万次咨询,大促期间约为平时的 10 倍(假设); +- 体验:用户发出消息后很快能看到进展,不能对着空白等完多轮调用; +- 安全:用户只能查自己的订单,退款金额不能超过实付金额,资金操作必须由用户确认。 + +按高峰小时占全天流量的 6% 计算,平时高峰约 10 万 × 6% ÷ 3600 ≈ 1.7 次咨询/秒,大促约 17 次/秒。每次咨询按前文的例子调用 3 次模型,假设共约 6 秒,由利特尔法则,大促高峰同时在进行的咨询约 17 × 6 ≈ 100 个。模型服务的并发和 Token 限额要按这个量申请或部署,见[大模型服务的高并发](系统设计基础.md#大模型服务的高并发)。 + +多轮调用让首个字出现得晚,所以要把“正在查询订单”这类进展推给前端,见[推送 Agent 的执行过程](#2-推送-agent-的执行过程)。成本也要在设计时估算:按[一次 Agent 任务的费用](#2-一次-agent-任务的费用)中的例子,每天 10 万次对话约 82~270 元,差别在于前缀缓存是否命中;实际的提示词和工具结果更长,费用会成倍增加。 + +#### 2. 架构 + +

+ +- 接入层:从登录会话中取出 user_id,工具都用它鉴权,不作为参数让模型填写,见[限制模型能造成的后果](#2-限制模型能造成的后果); +- 意图路由:退款、改地址这类涉及资金、步骤固定的请求交给工作流,咨询类交给 Agent,置信度低时反问澄清,见[意图识别与路由](#意图识别与路由); +- 工作流:退款由代码按校验、生成申请、用户确认、执行的顺序处理,模型只负责从对话中提取订单号和原因,见 [Agent 与工作流](#agent-与工作流); +- Agent:一个 ReAct 循环,工具有查询订单、查询物流、检索知识库和创建工单,限制最大步数; +- 知识库:退换货政策和商品资料切片后建立索引,用混合检索加重排,见[检索增强生成](#三检索增强生成); +- 短期记忆:摘要、最近几轮和任务状态(例如当前在处理的订单)存放在 Redis 中,见[短期记忆](#1-短期记忆)。 + +长期记忆在这个场景中要克制。订单、地址、退款进度以业务系统为准,每次用工具实时查询,不写进记忆,否则记忆中的旧地址会和数据库冲突;值得跨会话保存的只有少量偏好,例如“希望下午送货”,写入的方法见[长期记忆的写入](#2-长期记忆的写入)。 + +#### 3. 关键取舍 + +用框架还是自研。Agent 循环本身不到二十行,工作量在业务工具、权限、上下文组装和评测上,这些框架替代不了。Java 项目可以用 Spring AI 统一模型接口和注册工具,并关闭自动执行,由应用控制每一轮:每次发给模型的消息完全可控,便于保持前缀稳定以提高缓存命中率、在执行前校验权限和记录审计日志。退款流程要暂停等待用户确认、中断后能恢复,状态存在数据库中即可;分支和人工节点多起来时,再换 LangGraph 这类状态图框架,见[怎样选择](#4-怎样选择)。 + +单 Agent 还是多 Agent。在线客服对延迟和成本敏感,查订单和查物流前后依赖,单个 Agent 加路由就够了,见[适用场景](#3-适用场景)。退款单独成流程主要是为了隔离权限:咨询 Agent 没有退款工具,即使被提示词注入也发起不了退款。转人工相当于一次转交(Handoff),把摘要和任务状态一起交给人工客服,用户不必重复描述。 + +上下文怎么管理。每次调用按规则和工具定义、摘要、最近几轮、任务状态、本轮工具结果的顺序组装:不变的部分放在最前面,提高前缀缓存的命中率;工具结果只保留模型需要的字段,例如物流只返回最新的几条轨迹;历史超出预算时压缩成摘要,见[上下文工程](#上下文工程)。 + +做成工具还是 Skill。要查询数据或产生副作用的能力做成工具,例如查订单、建工单;只说明怎么处理一类问题的内容,例如价保的计算规则、换货的话术,按意图放进对应的提示词。这类说明多到每次都放进去太占上下文时,再像 [Skills](#skills) 那样平时只放名称和描述,需要时读取正文。订单查询要同时给客服和内部运营的助手使用时,可以做成 [MCP](#mcp) 服务端,一次实现、多处接入。 + +#### 4. 上线 + +上线前先有评测集:从历史客服会话中挑出几十条真实任务,按意图分层,既有应该退款的,也有超过期限、应该拒绝的;退款这类写操作检查数据库的最终状态,而不是 Agent 回复了什么,查询和问答检查回复是否与工具结果、知识库一致;路由的准确率单独评测,见[评测](#2-评测)。 + +权限逐步放开。先只上线查询类,退款类先由 Agent 生成处理建议、人工客服确认后执行;小流量灰度时对比转人工率和满意度,没有变差再扩大范围。 + +监控与成本: + +- 记录每一步的模型调用和工具调用,监控任务完成率、转人工率、平均步数、触发最大步数的次数、TTFT 和每次咨询的 Token,见[评测集与线上指标](#4-评测集与线上指标)和[可观测](#5-可观测); +- 查物流这类简单问题交给更小的模型,限制步数和输出长度,保持前缀稳定以提高缓存命中率,见[减少 Token 消耗](#4-减少-token-消耗)。 + +降级按层设计:主模型限流或出错时切到备用模型,见[限流、降级与熔断](#3-限流降级与熔断);备用模型也不可用时,咨询类退回关键词规则和常见问题的固定答案,再转人工;退款的校验、确认和执行不依赖模型,用户仍可以从订单页直接申请。大促前按估算的并发压测一次,确认限流和降级能按预期触发。 + ## 附录:实测 以下实验的环境为 Apple M1 Pro(32 GB)、macOS,PyTorch 与 Hugging Face Transformers,模型从 Hugging Face 下载到本地。