旅行助手 Agent+SFT 测试题答案
一、概念理解
1. Agent vs 普通 LLM
核心区别:普通 LLM 只能生成文本,Agent = LLM + 工具调用 + 决策循环。
Agent 的工作循环:
- 感知:接收用户输入
- 决策:LLM 判断意图,决定是否需要调用工具
- 行动:调用工具(API、数据库等)获取外部信息
- 观察:接收工具返回结果
- 回复/继续:如果信息足够则生成最终回答,否则继续循环调用更多工具
2. SFT 的作用
基座模型虽有语言能力,但不知道:
- 何时该调用工具(判断意图)
- 调用哪个工具(从5个中选择)
- 传什么参数(构造正确的 JSON)
- 如何串联多个工具(如酒店推荐→评价的链式调用)
- 何时该拒绝回答
SFT 通过大量标注好的对话示范,让模型学会以上所有技能,本质是行为克隆。
3. LoRA 原理
LoRA 核心思想:不直接更新大矩阵 W,而是学习一个低秩分解 ΔW = A × B,推理时 W’ = W + ΔW。
计算:
- (a) 全量微调:1024 × 1024 = 1,048,576 个参数
- (b) LoRA:A(1024×32) + B(32×1024) = 32,768 + 32,768 = 65,536 个参数
- (c) 节省倍数:1,048,576 / 65,536 = 16 倍
4. RAG vs SFT
| RAG | SFT | |
|---|---|---|
| 解决的问题 | 知识问题——模型不知道的信息 | 能力问题——模型不会的行为 |
| 本项目中 | 提供 200+ 城市的旅行攻略内容 | 教模型学会调用工具的格式和时机 |
互补关系:SFT 教模型"我应该调用 search_travel_guide 来查攻略",RAG 确保这个函数能从向量库中检索到有用的攻略内容。没有 SFT,模型不会调工具;没有 RAG,工具返回空内容。
5. Function Calling 完整流程
- 系统把工具定义(JSON Schema)和用户消息一起发给 LLM
- LLM 分析用户意图,输出结构化的 JSON(工具名 + 参数),而非自然语言
- 外部程序(非 LLM)解析这个 JSON
- 外部程序调用对应的真实函数/API,获取结果
- 把工具结果以
{"role": "tool", "content": result}的形式追加到对话中 - 再次调用 LLM,LLM 基于工具结果生成最终回答
关键:LLM 自己不执行任何函数,它只是输出调用指令,实际执行由外部程序完成。
6. RRF 混合检索
RRF 公式:rrf_score(d) = Σ 1/(k + rank_i)
文档 A 的计算:
- 向量检索排名 2:1/(60+2) = 1/62 ≈ 0.01613
- 关键词检索排名 5:1/(60+5) = 1/65 ≈ 0.01538
- RRF 分数 = 0.01613 + 0.01538 ≈ 0.03151
RRF 的优势:不需要归一化不同检索方式的分数(向量相似度和 TF 分数量纲不同),只看排名,简单有效。
二、项目理解
7. 数据流水线
| 步骤 | 脚本 | 说明 |
|---|---|---|
| 1 | generate_dataset.py | 基于100个城市模板 + LLM 生成1010条原始数据 |
| 2 | convert_dataset_final_fixed.py | 用 TravelAssistant 实际执行工具调用,生成完整对话 |
| 3 | merge_json_files.py | 合并所有 batch 文件 |
| 4 | split_dataset.py | 划分训练集和测试集 |
| 5 | conversation_splitter.py | 拆分多轮对话 + 最后一轮 3x 增强 |
| 6 | 最终输出 | merged_train_final_multiturn_v2.json(19MB) |
8. 五大工作流
(a) 工作流 3(酒店推荐)最复杂,原因:
- 需要连续调用两个工具:先
recommend_hotels再get_hotel_reviews - 两次调用之间不能中断(不能先回复用户再查评价)
- 模型需要学会自动链式调用,而非等待用户下一轮指令
(b) 工作流 5(拒绝)重要性:
- 防止模型对非旅行问题"幻觉回答"
- 定义了模型的能力边界
- 如果没有拒绝样本,模型可能对任何问题都尝试调用工具,造成误调用和资源浪费
9. “只训练最后一轮"策略
配合方式:
conversation_splitter.py把一条 N 轮对话拆成 N 个训练样本- 每个样本的最后一轮 assistant 都不同(分别是第1轮、第2轮…第N轮)
- 训练时
only_last_assistant=True只计算每个样本最后一轮的 loss
为什么比训练所有轮次更好:
- 避免重复梯度:第1轮 assistant 在样本1中已经被当作最后一轮训练过,在样本2中再训练一次是浪费
- 聚焦当前轮:每个样本只关注"在给定上下文下,下一步应该怎么做”
- 结合 3x 增强,最终的完整回答获得更大权重
10. 工具定义
(a) search_mode 可选值:"vector"(向量搜索)、"keyword"(关键词搜索)、"hybrid"(混合搜索),默认 "hybrid"
(b) query_route 的唯一必填参数:end_location(终点地址)。起点可以使用默认坐标。
(c) 用 num_days 而非 end_date 的原因:
- 更直觉:用户说"玩3天"比说"到4月3日"更常见
- 避免日期计算错误:LLM 计算日期差容易出错
- API 兼容:和风天气 API 按天数查询更方便
11. 数据分布设计
(a) 旅行规划占比最高(450/1010 ≈ 44.6%)原因:
- 这是最核心、最常用的功能
- 涉及两个工具的链式调用(攻略+天气),复杂度高
- 需要更多样本让模型学好
(b) 设计"信息不足需追问"的数据原因:
- 真实场景中用户经常不说完整信息(如只说"我想旅游",不说去哪)
- 教模型学会主动提问补全信息,而不是瞎猜
- 这提升了用户体验和安全性(不会根据不完整信息胡乱调工具)
三、代码分析
12. LoRA 配置分析
(a) lora_alpha / r = 2 是缩放因子。实际更新量 = ΔW × (alpha/r) = ΔW × 2。比值越大,LoRA 层的影响越大。2 是经验推荐值,平衡了学习能力和稳定性。
(b) 同时覆盖注意力层和 FFN 层的原因:
- 注意力层(q/k/v/o_proj):学习"关注什么"——即理解何时该调用工具
- FFN 层(gate/up/down_proj):学习"生成什么"——即输出正确的工具参数 JSON
- 只改注意力层不够,因为工具调用需要精确的输出格式
(c) r 从 32 改为 4 的影响:
- 参数量减少 8 倍,训练更快
- 但学习容量大幅降低,可能无法学会 5 个工具的完整调用模式
- 简单任务(如拒绝)可能还行,但复杂的链式调用(酒店工作流)可能退化
13. 训练超参数
(a) 有效 batch size = per_device_train_batch_size × gradient_accumulation_steps = 1 × 8 = 8
(b) gradient_checkpointing:
- 作用:前向传播时不保存所有中间激活值,反向传播时重新计算,用时间换显存
- 代价:训练速度慢约 20-30%(因为需要重新计算)
- 好处:显存占用减少约 60%,使得单卡能训练更大模型
(c) LoRA 学习率更大的原因:
- LoRA 只训练一小部分新增参数(A 和 B 矩阵),这些参数是从零初始化的
- 全量微调是在已有权重上微调,太大的学习率会破坏预训练知识
- LoRA 的低秩结构天然限制了更新幅度,所以可以用更大的学习率而不会过拟合
14. Agent 循环代码
| |
15. 数据去重
(a) 需要去重的原因:LLM 在生成工具调用时可能在多轮中重复调用同一个工具(相同参数),特别是在酒店工作流中,模型可能重复调用 recommend_hotels。
(b) 去重 key 的构造:f"{tool_call.function.name}:{tool_call.function.arguments}",即工具名 + 参数字符串的拼接。只有完全相同的调用才会被去重。
(c) 不去重的后果:
- 训练数据中包含冗余的工具调用
- 模型学会"一个问题调用两次同样的工具"这种错误模式
- 推理时浪费 API 调用,增加延迟和成本
- 可能导致重复结果混入最终回答
四、优化方案
16. RAG 优化
优化方向 1:分块策略
- 当前:每个城市一整篇攻略存为一条记录
- 改进:按段落/章节分块(如"景点"、“美食”、“交通"分开),每块 300-500 字,相邻块有 50 字重叠
- 理由:整篇文档太长(可能上千字),检索时无法精确定位用户关心的部分。分块后,查"杭州美食"只返回美食相关段落,减少无关信息干扰。
优化方向 2:加入 Reranker
- 当前:RRF 融合后直接返回 top-k
- 改进:RRF 初筛 top-20 → Reranker(如 bge-reranker-v2)精排 → 返回 top-5
- 理由:RRF 只看排名不看内容,Reranker 能基于 query-document 语义精确打分。
其他可选方向:
- 动态调整检索阈值(基于 query 置信度)
- 定期更新攻略数据保持时效性
- 使用开源 Embedding(如 BGE-M3)降低 API 成本
17. 推理部署优化
(a) 延迟优化:
- 工具并行调用:旅行规划中
search_travel_guide和get_weather_info可以并行执行(当前是串行),减少约 50% 的工具调用等待时间 - 或者:使用 KV Cache + 投机解码加速推理
(b) 吞吐量优化:
- 使用 vLLM 部署模型,支持 Continuous Batching(连续批处理),多个用户请求共享 GPU 计算
- PagedAttention 避免 KV Cache 的显存碎片
(c) 容错/降级:
- 工具调用失败时的降级策略:如天气 API 超时 → 返回"建议查看当地天气预报"而非报错
- RAG 检索无结果时 → LLM 基于自身知识生成通用建议,并提示"信息可能不完全准确”
- 设置工具调用超时和重试机制
18. 评估体系设计
(a) 评估指标(至少 3 个):
- 工具选择准确率:模型选对了正确的工具(如旅行规划应该选
search_travel_guide) - 参数准确率:工具参数是否正确(如城市名、日期格式、天数)
- 调用完整率:是否调用了所有必需的工具(如酒店工作流需要连续调两个)
- 拒绝准确率:非旅行问题是否正确拒绝
- 端到端回答质量:最终回答是否合理整合了工具结果(可用 LLM-as-Judge)
(b) 测试集样本类型:
- 各工作流的标准样本(正例)
- 信息不完整的样本(测试追问能力)
- 边界样本(半旅行半非旅行的问题)
- 纯非旅行样本(测试拒绝能力)
- 需要链式调用的复杂样本
(c) 判断 Function Calling “正确"的标准:
- 工具名完全匹配
- 所有必填参数都存在且格式正确
- 参数值语义正确(如用户说"北京”,参数里也是"北京"而非"上海")
- 不应该有多余的工具调用
五、综合设计题
19. 扩展 book_ticket 工具
(a) JSON Schema 定义:
| |
(b) 需要修改的文件:
all_tools.json— 添加上述工具定义tools/目录 — 新建book_ticket.py,实现订票 API 调用travel_assistant_funcall_fixed.py— 添加工作流 6(订票流程),在系统提示中描述何时触发generate_dataset.py— 新增订票场景的数据生成模板convert_dataset_final_fixed.py— 添加对book_ticket工具的执行逻辑
(c) 训练数据需求:
- 直接订票:200 条(“帮我订明天北京到上海的机票”)
- 需追问:50 条(“帮我订票” → “请问您要从哪里出发?")
- 与旅行规划组合:100 条(“帮我规划北京游,顺便订机票”)
- 拒绝不合理请求:30 条(“帮我订去火星的票”)
(d) 新工作流设计:
是的,需要定义工作流 6:订票服务:
- 提取出发地、目的地、日期、交通方式
- 如信息不完整,主动追问
- 调用
book_ticket查询票务信息 - 返回可选班次、价格、时间
- (可选)与工作流 1 联动:规划完行程后自动建议订票