二面深度·补充:架构实战深化(16题)
原始 20 题基础上的深化题目,考察更细颗粒度的架构决策和实战经验 答题要求:四段式 "问题 → 方案 → 取舍 → 数据佐证" 建议每题答题时间:3-5分钟
一、Agent 架构深化(8题)
D1-1. 自研 Agent 架构与 LangGraph、AutoGen 结合使用的场景有哪些?如何实现二者的优势互补?
分档:二面深度
高分答题思路:
混合使用的三种模式:
| 模式 | 结合方式 | 适用场景 | 优势 |
|---|---|---|---|
| 自研骨架 + LangGraph 节点 | 自研框架做编排/监控/容错,复杂子流程用 LangGraph 实现图节点 | 子流程有复杂分支循环,但主流程需要精细控制 | 自研可控 + LangGraph 图表达能力 |
| 自研 + AutoGen 研究探索 | 生产用自研,内部实验/原型用 AutoGen 快速验证多 Agent 协作 | 新功能探索期需要快速验证多 Agent 协作效果 | 生产稳定 + 探索灵活 |
| LangGraph 编排 + 自研工具层 | LangGraph 做流程编排,工具/记忆/监控全部自研 | 团队对 LangGraph 熟悉,但工具体系有定制需求 | 编排效率 + 工具定制化 |
优势互补的关键设计:
- 接口标准化:自研部分和框架部分通过标准接口(输入/输出 Schema)通信,不深度耦合
- 可替换设计:LangGraph 节点可以替换为自研节点,反之亦然,通过适配层转换
- 监控统一:无论用哪部分,trace_id 贯穿,埋点格式统一
坚决不混用的场景:
- 团队对两个框架都不熟 → 引入两个框架的维护成本 > 收益
- 简单线性流程 → 自研已足够,引入框架是负优化
- 高性能要求场景 → 框架叠加增加序列化/抽象开销
数据佐证模板: "我们的方案是自研编排骨架 + LangGraph 做复杂子流程。核心流程的延迟控制在 50ms 以内(自研),复杂子流程用 LangGraph 的图编排快速迭代开发,整体开发效率提升 40%,同时核心链路性能不受影响。"
D1-2. Master+Sub Agent 模式中,Master Agent 出现单点故障时,如何设计容灾方案保障业务不中断?
分档:二面深度
高分答题思路:
单点故障风险分析:
- Master Agent 是所有请求的入口和编排者 → 挂了 = 全站不可用
- Master 持有会话状态和编排上下文 → 挂了 = 进行中的任务丢失
四层容灾方案:
| 层级 | 方案 | 实现细节 |
|---|---|---|
| L1 无状态化 | Master 不持有状态,状态全部存 Redis | 请求带 session_id,任意 Master 实例都能处理 |
| L2 多实例部署 | Master 部署 3+ 实例,负载均衡 | Nginx/K8s Service 做健康检查,异常实例自动摘除 |
| L3 状态持久化 | 编排上下文(状态机快照)实时写 Redis | 实例故障后,新实例从 Redis 恢复上下文继续执行 |
| L4 降级直通 | Master 全挂时,请求直接路由到默认 SubAgent | 用 Nginx fallback 配置,绕过 Master 走预设流程 |
故障切换流程:
Master 实例A 故障
│
▼
健康检查失败(3次连续失败,间隔5s)
│
▼
负载均衡摘除实例A
│
▼
流量转移到实例B/C
│
▼
实例B 从 Redis 读取 session 上下文
│
▼
从中断点继续执行(状态机恢复)
│
▼
实例A 恢复后自动重新加入关键设计细节:
- 会话亲和性:尽量让同一 session 路由到同一 Master 实例(减少 Redis 读取),但故障时自动切换
- 幂等执行:状态恢复后重新执行最后一步时,必须幂等(工具调用可能已执行过)
- 超时兜底:如果切换 + 恢复 > 10s,直接返回降级响应,不等恢复
加分点: "我们设计了一个'影子 Master'机制——正常情况下实例B/C 不接收流量,只做热备(同步 Redis 状态)。实例A 故障时,影子 Master 0ms 切换为主,比'从 Redis 恢复'方案快 10 倍。成本是多一倍实例,但核心场景值得。"
D1-3. 多 Agent 协作中存在信息冗余传递问题,你会通过哪些方式做信息压缩和关键信息提取?
分档:二面深度
高分答题思路:
信息冗余的三种来源:
- 全量传递:Master 把完整上下文传给每个 SubAgent → 大量无关信息
- 结果回传:SubAgent 把完整工具返回传回 Master → 结果可能很长
- 重复传递:多个 SubAgent 需要相同的基础信息,各自重复接收
压缩与提取方案:
| 方案 | 适用场景 | 效果 |
|---|---|---|
| 结构化提取 | 传递前提取关键字段(实体/决策/约束) | 压缩 70-80%,信息损失 < 5% |
| 分层传递 | 基础信息只传一次(存共享池),SubAgent 按需读取 | 避免重复传递 |
| 摘要压缩 | 长文本用 LLM 生成摘要再传递 | 压缩 60-70%,但引入摘要延迟 |
| 差量传递 | 只传增量信息(与上次传递的差异) | 适合多轮协作,首轮仍需全量 |
| 引用机制 | 传 reference_id,SubAgent 需要时再查 | 延迟传递,最小化传输量 |
关键信息提取实现:
class ContextExtractor:
"""从完整上下文中提取关键信息"""
EXTRACTION_RULES = {
"user_query": "always", # 原始用户问题,永远保留
"user_constraints": "always", # 用户明确约束(预算/时间/偏好)
"decisions": "always", # 已确认的决策
"tool_results": "summarize", # 工具结果做摘要
"chitchat": "drop", # 寒暄丢弃
"intermediate_reasoning": "drop", # 中间推理过程丢弃
}
def extract(self, full_context: dict) -> dict:
essential = {}
for field, strategy in self.EXTRACTION_RULES.items():
if strategy == "always":
essential[field] = full_context[field]
elif strategy == "summarize":
essential[field] = self.summarize(full_context[field])
# drop: 不传递
return essential量化效果:
- 原始传递:8K token/Agent → 压缩后:1.5K token/Agent
- SubAgent 推理准确率:从 82% → 84%(上下文更聚焦,减少干扰)
- 推理成本:降低 55%
加分点: "关键洞察是'信息越多反而效果越差'——SubAgent 收到大量无关信息时,LLM 注意力被稀释,关键信息反而被忽略。所以压缩不只是省 token,更是提升推理质量。"
D1-4. 动态 Agent 编队场景下,如何根据任务复杂度自动增减子 Agent 数量,实现资源最优利用?
分档:二面深度
高分答题思路:
复杂度评估模型:
def assess_complexity(query: str, context: dict) -> str:
score = 0
# 维度1:意图数量(多个意图 = 高复杂度)
score += len(detect_intents(query)) * 2
# 维度2:工具数量预估
score += len(estimate_required_tools(query))
# 维度3:历史轮数(多轮积累 = 可能需要更多 Agent)
score += min(context.get("turn_count", 0), 5)
# 维度4:查询长度和信息密度
score += min(len(query) // 100, 3)
# 维度5:是否涉及跨领域
score += 3 if is_cross_domain(query) else 0
if score <= 4: return "simple"
elif score <= 8: return "medium"
else: return "complex"动态编队策略:
| 复杂度 | Agent 配置 | 典型场景 |
|---|---|---|
| Simple | 1 个通用 Agent | 单意图、单工具(如简单问答) |
| Medium | 1 Master + 1-2 SubAgent | 多步骤但同领域(如搜索+总结) |
| Complex | 1 Master + 3-5 SubAgent | 跨领域、多工具协作(如数据分析+报告生成) |
动态调整机制:
- 初始编队:根据复杂度评估结果分配初始 Agent 集
- 运行时扩容:Agent 执行中发现需要额外能力 → 动态启动新 SubAgent
- 运行时缩容:SubAgent 完成任务后立即释放,不保留到会话结束
- 超时降级:复杂度评估 > 阈值但实际执行简单 → 降级为单 Agent
资源优化效果:
- 简单场景(占 60% 流量):单 Agent,资源消耗最低
- 中等场景(30%):2-3 Agent,平衡效果和成本
- 复杂场景(10%):4-6 Agent,保证效果
关键取舍:
- 动态编队增加调度复杂度 → 需要完善的 Agent 注册/发现机制
- 复杂度评估准确率直接影响效果 → 评估错误导致过度编队或编队不足
- 动态启停 Agent 有冷启动延迟 → 预热池保持 1-2 个热 Agent
加分点: "我们统计发现 60% 的请求是简单场景,只需要单 Agent。动态编队后,平均每请求的 Agent 实例数从 3.2 降到 1.8,推理成本降低 40%,而任务完成率保持不变(因为简单场景本来不需要多 Agent)。"
D1-5. Agent 的长短期记忆如何结合使用?长时记忆的存储介质选型依据是什么?
分档:二面深度
高分答题思路:
长短期记忆协同模型:
用户请求进入
│
▼
[短期记忆] ← 当前对话窗口(最近 5-10 轮,存 LLM Context)
│ 检索:直接在 Context 中
│ 写入:每轮对话自动加入
│ 淘汰:超过窗口大小 → 触发摘要
│
▼
[会话记忆] ← 当前会话的关键信息(存 Redis,TTL 24h)
│ 检索:按 session_id 直接读取
│ 写入:每轮提取关键信息写入
│ 淘汰:会话结束 / TTL 过期
│
▼
[长期记忆] ← 跨会话的持久记忆(存向量数据库 + 关系型数据库)
│ 检索:向量相似度 + 关键词混合检索
│ 写入:会话结束时,提取值得长期记住的信息
│ 淘弃:LRU + 重要性评分衰减协同工作流:
- 读记忆:短期记忆(Context 内)→ 会话记忆(Redis)→ 长期记忆(向量库),按需逐层检索
- 写记忆:每轮对话 → 更新短期记忆 → 提取关键信息写会话记忆 → 会话结束时筛选写长期记忆
- 记忆晋升:短期记忆被淘汰时 → 摘要后写入会话记忆 → 会话记忆过期时 → 筛选写入长期记忆
长时记忆存储介质选型:
| 维度 | 向量数据库(Milvus/Faiss) | 关系型数据库(PostgreSQL) | 文档数据库(MongoDB) |
|---|---|---|---|
| 检索方式 | 语义相似度 | 精确匹配/范围查询 | 字段查询+全文索引 |
| 适合存储 | 描述性记忆("用户喜欢简洁风格") | 结构化记忆(用户偏好键值对、决策历史) | 半结构化记忆(对话摘要) |
| 检索精度 | 模糊匹配,适合"相似场景" | 精确匹配,适合"具体查询" | 中等 |
| 写入成本 | 需 Embedding 计算 | 直接写入 | 直接写入 |
| 扩展性 | 高(向量索引优化) | 中(需要分表) | 高 |
选型决策框架:
记忆内容是什么?
├─ 结构化事实(偏好/设置/决策)→ PostgreSQL(精确查询)
├─ 描述性经验(对话摘要/教训/案例)→ 向量数据库(语义检索)
└─ 时序事件(操作日志/行为序列)→ 时序数据库/PostgreSQL混合存储实践(推荐):
- PostgreSQL:用户画像表(结构化偏好、配置)+ 决策历史表
- Milvus:经验记忆(对话摘要的 Embedding)+ 案例库
- Redis:会话级热数据 + 记忆索引(快速判断是否有相关长期记忆)
- 查询时:先查 Redis 索引 → 有则并行查 PG + Milvus → 合并结果
加分点: "记忆不是越多越好。我们做过实验:长期记忆条目从 100 增加到 1000,检索准确率反而从 85% 降到 72%——因为噪声记忆干扰了检索。所以记忆淘汰和记忆写入同样重要,我们用'重要性评分 + 时间衰减 + 访问频率'做综合淘汰。"
D1-6. 跨 Agent 任务交接时,如何保证上下文的连贯性和任务状态的无缝传递?
分档:二面深度
高分答题思路:
交接中的三类风险:
- 信息丢失:上下文摘要遗漏关键字段
- 状态不一致:源 Agent 和目标 Agent 对任务状态的理解不同
- 语义偏移:交接描述的语义与原始用户意图有偏差
无缝传递方案:
1. 结构化交接协议
class HandoffContext(BaseModel):
"""Agent 交接协议"""
# 不可变字段(原始信息,永不丢失)
task_id: str
original_user_query: str
user_constraints: list[str] # 用户明确约束
# 状态字段(当前进展)
current_state: str # 当前状态机节点
completed_steps: list[StepResult] # 已完成步骤及结果
pending_steps: list[Step] # 待执行步骤
# 上下文字段(辅助信息)
key_decisions: list[Decision] # 已确认的决策
key_entities: list[Entity] # 关键实体
tool_results_summary: list[str] # 工具结果摘要
# 元信息
source_agent: str
handoff_reason: str # 为什么交接
confidence_score: float # 交接信息完整度2. 交接校验机制
- 完整性校验:接收方收到交接上下文后,检查 must_have 字段是否齐全
- 语义校验:LLM 快速校验"交接描述与原始 Query 是否一致"
- 状态校验:检查 current_state 是否在目标 Agent 的状态枚举中
3. 交接失败的兜底
- 校验失败 → 接收方拒绝交接,回退到 Master 重新分配
- 交接超时(> 5s)→ Master 介入,决定重试或降级
- 连续交接失败 3 次 → 转人工处理
4. 交接审计
- 每次交接记录:source → target、交接上下文摘要、校验结果
- 支持 Badcase 回溯:如果最终结果异常,可以追踪哪次交接出了问题
数据佐证模板: "我们引入结构化交接协议后,跨 Agent 信息丢失率从 15% 降到 2% 以下。关键是'原始 Query 不可变'+ 'must_have 字段校验'这两条硬约束,解决了 80% 的交接问题。"
D1-7. 设计 Agent 时,如何平衡"自主决策能力"和"人工干预可控性",避免 Agent 做出偏离业务预期的操作?
分档:二面深度
高分答题思路:
自主性分级模型:
| 级别 | 自主程度 | 适用场景 | 干预机制 |
|---|---|---|---|
| L0 完全规则 | Agent 只执行预设流程 | 合规要求高的场景(金融/医疗) | 人工定义所有规则 |
| L1 建议模式 | Agent 给建议,人工确认后执行 | 高风险决策(如发邮件/转账) | 关键操作必须人工 approve |
| L2 受限自主 | Agent 自主执行,但敏感操作需确认 | 一般业务场景 | 敏感操作列表触发确认 |
| L3 完全自主 | Agent 全程自主,事后审计 | 低风险场景(如信息查询) | 事后审计 + 异常告警 |
可控性设计:
1. 操作分级
class OperationLevel(Enum):
SAFE = "safe" # 只读操作,无需确认(搜索/查询)
MODERATE = "moderate" # 写入操作,需记录审计(保存/更新)
SENSITIVE = "sensitive" # 外部影响,需人工确认(发送邮件/发布内容)
CRITICAL = "critical" # 不可逆操作,需双重确认(删除/支付)
# 每个工具标注操作级别
@register_tool("send_email", level=OperationLevel.SENSITIVE)
async def send_email(to, subject, body): ...
@register_tool("search", level=OperationLevel.SAFE)
async def search(query): ...2. 护栏机制
- 工具白名单:Agent 只能调用授权工具
- 参数约束:工具参数有 Schema 约束(如金额上限、收件人白名单)
- 输出审查:Agent 最终输出经安全过滤器检查后再返回用户
- 行为边界:System Prompt 明确"不能做什么",配合后处理校验
3. 人工干预入口
- 实时干预:敏感操作弹窗确认,人工 approve/reject
- 异步审核:Agent 执行后记录审计日志,人工事后抽检
- 紧急停止:管理员一键暂停所有 Agent(kill switch)
4. 偏离检测
- 行为基线:统计 Agent 正常行为的模式(工具调用分布、响应长度等)
- 异常检测:实时行为偏离基线 > 阈值时告警
- 用户反馈回路:收集用户对 Agent 行为的反馈,持续修正
加分点: "核心设计原则是'风险与自主性成反比'——低风险操作给 Agent 充分自主性以提升效率,高风险操作必须有人工 checkpoint。我们的实践是 L2 级别覆盖 80% 场景,既不过度干预影响效率,又能在关键环节兜住底线。"
D1-8. 工具调用链过长导致的响应延迟问题,有哪些优化手段?如何做调用链路的超时管控和链路裁剪?
分档:二面深度
高分答题思路:
延迟问题分析:
- 每个工具调用:网络 RTT + 执行时间 + 结果处理 = 500ms-3s
- 5 步链式调用:2.5s-15s,用户体感差
- LLM 决策时间:每步 500ms-2s,5 步 = 2.5-10s
- 总延迟 = 工具执行 + LLM 决策 = 5-25s
优化手段:
| 手段 | 效果 | 实施难度 |
|---|---|---|
| 并行工具调用 | 无依赖的工具并行执行,延迟降 40-60% | 中(需分析依赖关系) |
| 流式输出 | 首字延迟降 50%+,用户体感大幅改善 | 低 |
| 工具结果缓存 | 相同参数的工具调用缓存结果,命中率 10-20% | 中 |
| 预计算/投机执行 | 预测下一步工具调用,提前执行 | 高 |
| 链路裁剪 | 跳过非必要步骤 | 中 |
| 工具合并 | 多个小工具合并为大工具,减少调用次数 | 中 |
| 异步化 | 长耗时工具异步执行,先返回部分结果 | 中 |
超时管控设计:
全局超时预算:15s
│
├─ LLM 决策预算:每步 2s(共 ~6s for 3 步)
│ └─ 超时 → 跳过当前步骤,用上一步结果继续
│
├─ 工具执行预算:每步 3s
│ └─ 超时 → 重试 1 次 → 仍超时 → 降级/跳过
│
└─ 全局超时:15s
└─ 超时 → 返回已完成部分结果 + 提示"部分结果"链路裁剪策略:
- 必须步骤:核心路径不可裁剪(如必选的工具调用)
- 可选步骤:增强型步骤可裁剪(如结果格式化、额外验证)
- 奢侈步骤:锦上添花的步骤优先裁剪(如多角度验证、风格优化)
class ChainOptimizer:
def optimize(self, planned_chain: list[Step], time_budget: float) -> list[Step]:
essential = [s for s in planned_chain if s.priority == "must"]
optional = [s for s in planned_chain if s.priority == "optional"]
luxury = [s for s in planned_chain if s.priority == "luxury"]
estimated_time = sum(s.estimated_duration for s in essential)
# 时间预算内尽量加入 optional
for step in optional:
if estimated_time + step.estimated_duration <= time_budget:
essential.append(step)
estimated_time += step.estimated_duration
# 剩余时间加入 luxury
for step in luxury:
if estimated_time + step.estimated_duration <= time_budget:
essential.append(step)
estimated_time += step.estimated_duration
return essential数据佐证模板: "我们通过'并行调用 + 链路裁剪 + 超时降级'三管齐下,把 5 步工具调用的平均延迟从 12s 降到 4.5s,P99 从 25s 降到 8s。关键是链路裁剪——70% 的场景只需要 3 步核心调用就能完成任务,额外 2 步是可选的增强。"
二、Prompt 工程深化(4题)
D2-1. 针对多轮对话中用户意图反复变更的场景,如何设计 Prompt 实现意图的实时校准和冲突检测?
分档:二面深度
高分答题思路:
意图校准 Prompt 设计:
你是一个意图追踪器。分析以下对话历史,检测意图变化。
## 对话历史
{conversation_summary}
## 当前用户消息
{current_message}
## 任务
1. 提取当前消息的意图
2. 与历史意图对比,检测是否发生意图漂移
3. 如果检测到意图冲突,输出冲突描述
4. 输出最新的有效意图(以最新用户消息为准,但保留历史约束)
## 输出格式
{
"current_intent": "最新意图",
"intent_changed": true/false,
"conflict_type": "none|constraint_conflict|goal_shift|scope_change",
"conflict_description": "冲突描述(如有)",
"effective_constraints": ["当前生效的所有约束"],
"clarification_needed": "需要向用户确认的问题(如有)"
}冲突检测规则:
- constraint_conflict:新约束与旧约束矛盾(如之前说"便宜的",现在说"要高端的")
- goal_shift:核心目标变化(如从"搜索餐厅"变成"查看天气")
- scope_change:范围扩大或缩小(如从"全北京"变成"朝阳区")
校准策略:
- goal_shift → 以最新目标为准,清除旧目标的上下文
- constraint_conflict → 向用户确认:"您之前提到要 X,现在要 Y,请问以哪个为准?"
- scope_change → 自动更新,不需要确认(这是正常的对话演进)
加分点: "意图校准不是每轮都做——我们用了一个'意图稳定性评分',只在评分低于阈值时触发校准。这样避免过度敏感(用户只是在补充细节就触发冲突检测),也避免不够敏感(真正冲突时没检测到)。"
D2-2. 结构化 Prompt 设计中,如何兼顾"格式约束的严格性"和"模型生成的灵活性",避免格式错误导致任务失败?
分档:二面深度
高分答题思路:
核心矛盾:
- 格式约束太严 → 模型生成僵化,内容质量下降
- 格式约束太松 → 格式错误率高,下游解析失败
三层平衡方案:
| 层次 | 策略 | 效果 |
|---|---|---|
| Prompt 层 | 格式约束与内容生成分离:先自由生成内容,再格式化输出 | 内容质量不受格式约束影响 |
| Schema 层 | 用 JSON Schema 定义"必须字段"和"可选字段" | 核心字段严格,非核心字段灵活 |
| 后处理层 | Pydantic 校验 + 自动修复 + 字段补默认值 | 兜底保障,格式错误不阻塞 |
分离式 Prompt 设计:
## 第一步:自由思考(不受格式约束)
分析用户问题,思考解决方案。
{thinking_section}
## 第二步:结构化输出(严格格式)
基于以上思考,输出以下 JSON 格式:
{
"answer": "你的回答(自然语言)",
"confidence": 0.0-1.0,
"sources": ["引用的信息来源"],
"follow_up": "建议的后续问题(可选)"
}
注意:
- answer 字段可以是自然语言,不受格式限制
- confidence 和 sources 必须符合类型要求
- follow_up 可选,没有就不输出关键设计原则:
- 必填字段最小化:只有下游必须依赖的字段标为 required
- 内容字段不约束格式:answer/reply 等自然语言字段不做格式约束
- 元数据字段严格约束:confidence/tool_name/status 等必须符合类型
- 容错设计:后处理层保证即使格式有错也能继续,不阻塞流程
加分点: "我们的格式错误率从 15% 降到 1% 以下,关键不是 Prompt 写得多好,而是后处理层兜底——Pydantic 校验 + 自动类型转换 + 缺失字段补默认值。Prompt 只负责'引导',不负责'保证'。"
D2-3. 上下文窗口有限的情况下,如何实现历史对话的智能裁剪,优先保留与当前任务相关的关键信息?
分档:二面深度
高分答题思路:
智能裁剪算法:
完整对话历史(10轮,8K token)
│
▼
[Step 1: 重要性评分]
每轮对话评分:
- 含用户明确约束 → 10分
- 含关键决策 → 8分
- 含工具调用结果 → 6分
- 含用户偏好陈述 → 7分
- 纯寒暄/确认 → 1分
- 中间推理过程 → 2分
│
▼
[Step 2: 相关性评分]
与当前 Query 的相关性:
- 语义相似度(Embedding)
- 实体重叠度
- 意图关联度
│
▼
[Step 3: 综合评分]
final_score = 重要性 × 0.6 + 相关性 × 0.4
│
▼
[Step 4: 预算分配]
总预算:3K token
- 保留 Top N 轮(综合评分最高的)
- 低分轮次做摘要(1-2句话)
- 丢弃评分 < 2 的轮次
│
▼
优化后上下文(3K token)实现代码框架:
class ContextPruner:
def prune(self, history: list[Turn], current_query: str,
max_tokens: int = 3000) -> list[Turn]:
# 1. 评分
for turn in history:
turn.importance = self.score_importance(turn)
turn.relevance = self.score_relevance(turn, current_query)
turn.final_score = turn.importance * 0.6 + turn.relevance * 0.4
# 2. 排序
sorted_turns = sorted(history, key=lambda t: t.final_score, reverse=True)
# 3. 预算分配
result = []
remaining_budget = max_tokens
for turn in sorted_turns:
if turn.final_score < 2:
continue # 丢弃
if remaining_budget < turn.token_count:
# 摘要代替
summary = self.summarize(turn)
result.append(Turn(summary, is_summary=True))
remaining_budget -= count_tokens(summary)
else:
result.append(turn)
remaining_budget -= turn.token_count
# 4. 按时间序恢复
result.sort(key=lambda t: t.timestamp)
return result关键设计细节:
- 原始 Query 永不裁剪:用户最初的请求完整保留
- 约束信息永不裁剪:用户明确的偏好/约束(预算/时间/格式要求)
- 摘要标注:被摘要的轮次标记
[摘要],让 LLM 知道这不是原始内容 - 裁剪日志:记录被裁剪的内容,Badcase 分析时可回溯
数据佐证模板: "智能裁剪让我们的上下文从平均 8K token 降到 3K token,而任务完成率只降了 2%(从 88% → 86%)。关键是'约束信息永不裁剪'——被裁掉的都是低信息量的寒暄和中间过程。"
D2-4. 针对复杂任务,如何设计分层 Prompt,实现"拆解任务-执行任务-校验结果"的闭环引导?
分档:二面深度
高分答题思路:
三层闭环 Prompt 架构:
┌─────────────────────────────────────────┐
│ Layer 1: Planner Prompt(任务拆解) │
│ "分析用户需求,拆解为子任务列表" │
│ 输出:[{step, tool, expected_output}] │
└──────────────────┬──────────────────────┘
│
┌──────────────────▼──────────────────────┐
│ Layer 2: Executor Prompt(逐步执行) │
│ "执行第 N 步:{step},使用工具:{tool}" │
│ 输出:工具调用 + 执行结果 │
└──────────────────┬──────────────────────┘
│
┌──────────────────▼──────────────────────┐
│ Layer 3: Validator Prompt(结果校验) │
│ "检查执行结果是否满足:1)用户原始需求 │
│ 2)本步骤预期输出 3)整体任务完成度" │
│ 输出:{passed: bool, issues: [],
│ next_action: "proceed|retry|ask"}│
└──────────────────┬──────────────────────┘
│
通过 → 下一步 / 全部完成
失败 → 重试 / 调整计划 / 反问用户Planner Prompt 核心:
你是任务规划专家。分析用户需求,拆解为可执行的子任务。
用户需求:{user_query}
可用工具:{tool_list}
已知约束:{constraints}
拆解原则:
1. 每步只做一件事
2. 步骤间依赖关系明确
3. 每步有明确的预期输出
4. 预估每步耗时,总耗时不超过预算
输出 JSON:
{
"steps": [
{
"id": 1,
"action": "搜索XX信息",
"tool": "search",
"expected_output": "包含XX的信息列表",
"depends_on": [],
"estimated_time": "2s"
}
],
"total_steps": 3,
"can_parallelize": [1, 2] // 可并行的步骤ID
}Validator Prompt 核心:
你是结果校验专家。检查执行结果是否满足要求。
用户原始需求:{user_query}
当前步骤:{step_description}
预期输出:{expected_output}
实际输出:{actual_output}
校验维度:
1. 完整性:预期输出的关键信息是否都包含
2. 准确性:信息是否正确(与已知事实对比)
3. 充分性:是否足够支撑下一步或最终回答
输出:
{
"passed": true/false,
"completeness": 0.0-1.0,
"issues": ["缺失的关键信息"],
"next_action": "proceed" | "retry" | "replan" | "ask_user",
"suggestion": "如果retry/replan,给出建议"
}闭环控制逻辑:
- Validator 通过 → 执行下一步
- Validator 失败 → 重试当前步骤(最多 2 次)
- 重试仍失败 → 回到 Planner 重新规划
- 重新规划后仍失败 → 反问用户
加分点: "三层 Prompt 的核心价值是'可审计性'——每一步都有明确的输入/输出/校验结果,Badcase 可以精确定位是'计划不合理'还是'执行出错'还是'校验漏了'。这比一锅端的 Prompt 可调试性好 10 倍。"
三、评测与迭代深化(4题)
D3-1. Agent 任务完成率的量化评估标准如何制定?针对不同类型的任务,评估指标有哪些差异?
分档:二面深度
高分答题思路:
任务完成率的定义框架:
| 任务类型 | 完成标准 | 关键指标 | 评估方式 |
|---|---|---|---|
| 问答型 | 回答包含所有关键信息点 | 信息覆盖率、准确率 | LLM-as-Judge + 人工 |
| 工具调用型 | 正确工具 + 正确参数 + 成功执行 | 工具准确率、参数准确率、执行成功率 | 自动化比对 |
| 创作型 | 满足约束 + 质量达标 | 约束满足率、质量评分(1-5) | LLM-as-Judge + 人工 |
| 多轮对话型 | 最终完成 + 过程连贯 | 任务完成率、多轮一致性、平均轮数 | 端到端评测 |
分类型评估指标体系:
通用指标(所有任务):
- 任务完成率:最终回复是否满足用户需求
- 安全合规率:无违规内容
- 用户满意度:点赞/点踩/反馈
问答型特有:
- 信息覆盖率:标注的关键信息点被覆盖的比例
- 准确率:事实性信息的正确率
- 幻觉率:编造不存在信息的比例
工具调用型特有:
- 工具选择准确率:选对了工具
- 参数准确率:参数值正确
- 执行成功率:工具调用成功
- 链路完成率:多步工具调用全部完成
创作型特有:
- 约束满足率:满足用户指定的格式/长度/风格约束
- 创意度:LLM-as-Judge 评分
- 可用性:产出是否可直接使用
多轮对话型特有:
- 多轮一致性:各轮回复无矛盾
- 意图理解准确率:正确理解用户的最终意图
- 平均交互轮数:完成任务的效率评估数据集构建差异:
- 问答型:标注标准答案 + 关键信息点列表
- 工具调用型:标注期望工具序列 + 期望参数
- 创作型:标注约束条件 + 质量评分标准
- 多轮对话型:标注完整对话脚本 + 分支路径
加分点: "我们最初用统一指标评估所有任务,发现创作型任务的'完成率'虚高(因为创作没有标准答案)。后来按任务类型分轨道评估,各轨道有独立的指标体系和数据集,评估准确度显著提升。"
D3-2. Badcase 聚类分析的核心维度有哪些?如何通过聚类结果快速定位高频问题场景?
分档:二面深度
高分答题思路:
聚类维度矩阵:
| 维度 | 聚类方式 | 示例类别 |
|---|---|---|
| 错误类型 | 按根因分类 | 工具选错/参数错误/信息缺失/格式错误/幻觉 |
| 意图类别 | 按用户意图分类 | 搜索/分析/创作/闲聊 |
| 工具组合 | 按涉及的工具分类 | 单工具/多工具/无工具 |
| 复杂度 | 按对话轮数分类 | 单轮/2-3轮/4+轮 |
| 用户画像 | 按用户类型分类 | 新用户/老用户/专业用户 |
聚类分析流程:
Badcase 池(1000条)
│
▼
[多维度标注]
每条 Badcase 标注:错误类型 + 意图 + 工具 + 轮数 + 用户类型
│
▼
[Embedding 聚类]
对 Badcase 文本做 Embedding → K-Means/层次聚类
│
▼
[维度交叉分析]
错误类型 × 意图 = "搜索场景下的工具选错"占 35%
错误类型 × 工具 = "多工具场景的参数错误"占 20%
│
▼
[优先级排序]
按 [频次 × 影响] 排序,Top 3 问题优先修复快速定位高频问题:
- 帕累托分析:通常 20% 的错误类型贡献了 80% 的 Badcase
- 交叉热力图:错误类型 × 意图 的热力图,颜色越深优先级越高
- 趋势分析:某类 Badcase 突增 → 可能是某次发布引入的回归
聚类结果示例:
Top 1 (35%):搜索意图 → 工具选择错误(选了搜索工具但应该用数据库查询)
Top 2 (20%):多工具场景 → 参数缺失(LLM 没有从上下文提取到必要参数)
Top 3 (15%):多轮对话 → 上下文丢失(第4轮忘记了第1轮的约束)加分点: "聚类最大的价值不是发现已知问题,而是发现'未知的高频问题'。我们通过聚类发现了一个意想不到的高频 Badcase:用户用反问句表达需求时,Agent 会误解为确认。这个问题的占比 8%,但人工抽检时很难发现,因为不会主动去看这类表达模式。"
D3-3. Prompt 调优中,如何建立版本管理机制,避免"越调越差",且能快速回滚?
分档:二面深度
高分答题思路:
Prompt 版本管理方案:
Prompt 版本树:
v1.0 (baseline)
├─ v1.1 (调整 Few-shot)
│ └─ v1.1.1 (增加负面示例)
├─ v1.2 (修改 System Prompt)
│ └─ v1.2.1 (回滚:效果下降 3%)
└─ v1.3 (重构输出格式)版本管理机制:
| 要素 | 方案 |
|---|---|
| 版本号 | 语义化版本:major.minor.patch |
| 变更记录 | 每次变更记录:修改内容、修改原因、预期效果 |
| 评测绑定 | 每个版本绑定一组评测结果(准确率/完成率/格式合规率) |
| 灰度发布 | 新版本先灰度 5% → 指标不降 → 扩到 20% → 50% → 100% |
| 快速回滚 | 保留最近 3 个稳定版本,一键回滚 |
防止"越调越差"的策略:
A/B 对比
- 新版本必须在评测集上跑完整指标
- 与上一个稳定版本对比,所有指标不降才能发布
- 如果某个指标降了但其他升了 → 需人工评估 trade-off
回归测试
- 维护"黄金测试集":100 条之前版本表现好的 case
- 新版本必须在黄金测试集上不回归(准确率不降)
变更最小化
- 每次只改一个维度(要么改 System Prompt,要么改 Few-shot,要么改格式)
- 避免"大改"导致无法归因效果变化
效果监控
- 上线后实时监控核心指标
- 指标下降 > 2% → 自动告警 → 评估是否回滚
Prompt 调优流程:
假设 → 修改 Prompt → 跑评测集 → 对比基线
├─ 全面提升 → 灰度发布 → 监控 → 全量
├─ 部分提升部分下降 → 评估 trade-off → 决策
└─ 全面下降 → 回滚 + 分析原因 → 调整假设加分点: "我们把 Prompt 版本管理做成了类似 Git 的系统——分支、合并、PR review、CI(自动评测)。每次 Prompt 变更都需要通过 CI(评测指标不降)才能合并。这把'Prompt 越调越差'的概率从 30% 降到 5% 以下。"
D3-4. 当 Agent 效果遇到瓶颈时,如何通过"Prompt 调优+模型微调+工具扩充"的组合策略实现突破?
分档:二面深度
高分答题思路:
瓶颈诊断框架:
Agent 效果瓶颈
│
▼
[根因分析]
├─ Prompt 已经穷尽优化 → 考虑微调
├─ 缺少必要能力/数据 → 考虑工具扩充
├─ 模型理解能力不足 → 考虑换模型/微调
└─ 检索素材不足 → 考虑知识库扩充组合策略决策矩阵:
| 瓶颈类型 | 组合策略 | 预期效果 | 周期 |
|---|---|---|---|
| 格式/风格不稳定 | Prompt 强化 + 少量 SFT | 格式合规率 85%→98% | 1-2 周 |
| 领域知识不足 | 工具扩充(RAG)+ Prompt 引导 | 知识覆盖率 60%→90% | 2-3 周 |
| 工具选择不准 | Prompt 优化(工具描述重写)+ SFT(工具选择) | 准确率 72%→92% | 3-4 周 |
| 多步推理弱 | SFT(Plan-Solve 推理链)+ Prompt(分步引导) | 完成率 65%→85% | 4-6 周 |
| 综合瓶颈 | Prompt + SFT + 工具 + RAG 全面优化 | 整体提升 15-25% | 6-8 周 |
组合策略执行顺序(ROI 优先):
Phase 1 (Week 1-2): Prompt 调优
- 重写工具描述、优化 Few-shot、改进输出格式
- 预期提升:5-10%
- 成本:最低
Phase 2 (Week 2-3): 工具/知识扩充
- 新增缺失工具、扩充知识库
- 预期提升:5-15%
- 成本:中
Phase 3 (Week 3-6): 模型微调
- 基于 Phase 1-2 的 Badcase 构建微调数据
- SFT 解决 Prompt 无法解决的格式/风格/推理问题
- 预期提升:5-10%
- 成本:最高
Phase 4 (持续): 效果监控 + 迭代
- 上线后持续监控,发现新瓶颈回到 Phase 1关键原则:
- 不要同时改多个维度:无法归因效果提升来自哪个改动
- 每阶段都跑评测:确认本阶段的改动有正向收益
- 微调是最后手段:前两阶段能解决的不用微调
数据佐证模板: "我们的 Agent 准确率卡在 82% 两个月。通过组合策略:Phase 1 Prompt 优化(+4%)→ Phase 2 工具扩充(+6%)→ Phase 3 针对性 SFT(+5%),最终到 97%。关键是每个阶段都先归因再行动,而不是同时全改。"
四、工程底层深化(4题)
D4-1. Python 协程实现 Agent 高并发调度,协程与线程结合使用的场景和注意事项?
分档:二面深度
高分答题思路:
纯协程方案(推荐默认):
async def agent_scheduler(tasks: list[Task]):
semaphore = asyncio.Semaphore(100) # 并发上限
async def run_one(task):
async with semaphore:
return await agent.execute(task)
results = await asyncio.gather(*[run_one(t) for t in tasks])
return results- 优势:单进程内 1000+ 并发,切换开销极小
- 适用:I/O 密集(LLM API 调用、网络请求)
协程 + 线程结合场景:
| 场景 | 为什么需要线程 | 实现方式 |
|---|---|---|
| CPU 密集混合 | Embedding 计算/文本处理阻塞事件循环 | loop.run_in_executor(ThreadPoolExecutor, func) |
| 同步 SDK 调用 | 第三方 SDK 不支持 async | 包装在线程池中调用 |
| 阻塞文件 I/O | 大文件读写阻塞 | aiofiles 或线程池 |
注意事项:
- 线程安全:协程内访问共享变量不需要锁(单线程),但线程池中的代码需要加锁
- GIL 影响:线程池中的 CPU 密集任务受 GIL 限制,真正并行需要多进程
- 资源控制:线程池大小要设上限(
max_workers=10),避免线程爆炸 - 异常传播:线程中的异常不会自动传播到协程,需要手动捕获
# 正确的协程+线程结合模式
executor = ThreadPoolExecutor(max_workers=10)
async def agent_task(query):
# 协程部分:LLM 调用(I/O 密集)
llm_response = await call_llm(query)
# 线程部分:Embedding 计算(CPU 密集)
embedding = await asyncio.get_event_loop().run_in_executor(
executor, compute_embedding, llm_response
)
# 回到协程:结果存储(I/O 密集)
await store_result(embedding)加分点: "我们踩过一个坑——第三方 SDK 的同步调用直接放在协程里,导致整个事件循环阻塞 2 秒,所有并发请求都卡住。解决方案是用
run_in_executor包装同步调用,或者找/写 async 版本的 SDK。"
D4-2. 代码插桩在 Agent 链路监控中的应用?如何通过插桩实现每步操作的可追溯?
分档:二面深度
高分答题思路:
插桩方案:
| 方式 | 实现 | 侵入性 | 灵活性 |
|---|---|---|---|
| 装饰器插桩 | @trace("tool_call") | 低 | 高 |
| 中间件插桩 | 在 Agent 执行管线中插入监控层 | 中 | 中 |
| AST 插桩 | 自动在 AST 层面插入埋点代码 | 无(自动) | 最高 |
| 手动埋点 | 在代码中直接写 logger.info() | 高 | 最高 |
装饰器插桩实现:
def trace(step_name: str):
def decorator(func):
@functools.wraps(func)
async def wrapper(*args, **kwargs):
trace_id = get_current_trace_id()
step_id = f"{trace_id}_{step_name}_{uuid4().hex[:8]}"
# 前置埋点
logger.info({
"trace_id": trace_id,
"step_id": step_id,
"step": step_name,
"phase": "start",
"input": summarize(args),
"timestamp": time.time()
})
start = time.time()
try:
result = await func(*args, **kwargs)
# 后置埋点
logger.info({
"trace_id": trace_id,
"step_id": step_id,
"step": step_name,
"phase": "end",
"output": summarize(result),
"duration_ms": (time.time() - start) * 1000,
"status": "success"
})
return result
except Exception as e:
logger.error({
"trace_id": trace_id,
"step_id": step_id,
"step": step_name,
"phase": "error",
"error": str(e),
"duration_ms": (time.time() - start) * 1000,
"status": "failed"
})
raise
return wrapper
return decorator
# 使用
class SearchAgent:
@trace("search_tool_call")
async def search(self, query):
return await search_api(query)
@trace("search_result_process")
async def process(self, results):
return filter_and_rank(results)可追溯的完整链路:
trace_id: abc123
├─ step: route, duration: 50ms, input: "用户query", output: "search_agent"
├─ step: intent, duration: 80ms, input: "用户query", output: "搜索意图"
├─ step: search_tool_call, duration: 500ms, input: "query", output: "10条结果"
├─ step: search_result_process, duration: 30ms, input: "10条", output: "3条精选"
└─ step: llm_generate, duration: 1500ms, input: "3条+用户query", output: "最终回复"trace_id 传播:
- 使用
contextvars.ContextVar在协程间传递 trace_id - 不需要显式传参,装饰器自动获取
加分点: "插桩不是越多越好——我们曾经每个函数都埋点,日志量爆炸(1GB/天),反而影响性能。后来精简为'关键节点埋点 + 采样详细埋点':关键节点全量埋点(路由/工具/LLM),细节埋点采样 10%。日志量降 70%,排查能力基本不变。"
D4-3. Transformer 位置编码的实现方式有哪些?各自优缺点?如何适配不同长度输入?
分档:二面深度
高分答题思路:
| 方案 | 原理 | 优点 | 缺点 | 代表模型 |
|---|---|---|---|---|
| Sinusoidal | sin/cos 固定编码 | 无参数、可外推 | 效果一般 | 原始 Transformer |
| Learnable PE | 每个位置一个可学习向量 | 效果好 | 不能外推到训练最大长度之外 | GPT-2/BERT |
| ALiBi | Attention 分数加距离偏置 | 无需位置编码、天然外推 | 相对位置表达力稍弱 | BLOOM |
| RoPE | 对 Q/K 做旋转编码相对位置 | 外推性好、计算高效、效果好 | 实现稍复杂 | LLaMA/GPT-4 |
RoPE 详解(当前主流):
- 对 Q 和 K 向量按维度分组,每组做 2D 旋转
- 旋转角度与位置成正比:θ_i = pos / 10000^(2i/d)
- Q·K 的内积自然包含相对位置信息
- 优势:不引入额外参数,支持外推(通过 NTK-aware scaling)
长度适配方案:
| 问题 | 方案 |
|---|---|
| 训练 max_len=2048,推理需要 4096 | RoPE + NTK-aware scaling(调整基频) |
| Learnable PE 不能外推 | 插值法(位置插值 PI)或换 RoPE |
| 超长文本(32K+) | RoPE + 动态 NTK + 滑动窗口注意力 |
加分点: "RoPE 的外推不是无限的——我们的实验显示,从 4K 训练的模型外推到 16K 时效果开始下降。解决方案是在推理时用 NTK-aware scaling 调整旋转基频,可以把有效长度扩展到 32K 而效果几乎无损。"
D4-4. Python 中 Lock 和 RLock 的使用场景?如何避免死锁?
分档:二面深度
高分答题思路:
| 类型 | 特点 | 使用场景 |
|---|---|---|
| Lock | 不可重入,同一线程不能重复 acquire | 保护独立资源 |
| RLock | 可重入,同一线程可重复 acquire(计数器) | 递归函数、嵌套调用 |
Lock vs RLock:
# Lock:同一线程重复 acquire 会死锁
lock = threading.Lock()
def func():
with lock:
with lock: # 死锁!
pass
# RLock:同一线程可重复 acquire
rlock = threading.RLock()
def func():
with rlock:
with rlock: # 正常
passAgent 场景中的使用:
- 工具注册表:多线程注册/查询工具 → 用 Lock
- 会话状态:多线程读写同一 session → 用 Lock
- 缓存更新:双检锁模式 → 用 Lock
死锁的四个必要条件及破坏方式:
| 条件 | 破坏方式 |
|---|---|
| 互斥 | 无法破坏(资源本身就是互斥的) |
| 持有并等待 | 一次性获取所有锁 |
| 不可剥夺 | 获取锁时设超时(lock.acquire(timeout=5)) |
| 循环等待 | 按固定顺序获取锁 |
避免死锁的实践:
- 锁排序:所有代码按固定顺序获取锁
- 超时机制:
lock.acquire(timeout=5),超时走异常处理 - 锁粒度最小化:只锁真正需要保护的代码段
- 避免嵌套锁:能用一把锁的不用两把
- context manager:用
with lock:而非手动 acquire/release(异常安全)
加分点: "在 Agent 场景中,其实大部分并发问题用 asyncio 就能解决——协程是单线程的,不需要锁。只有在协程+线程混合场景(如线程池中访问共享资源)才需要锁。我们的原则是:能不用锁就不用,必须用就只锁最小范围。"
答题策略补充
| 要点 | 说明 |
|---|---|
| 深化题的特点 | 每道题都是"基础题的延伸",答题时先简述基础概念再展开深化 |
| 场景代入 | 尽量用"我们的实践中..."开头,把回答从"理论上可以"变成"我们这样做过" |
| 数据精度 | 数据不需要精确到小数点,但量级要合理(提升 5-20%、延迟降 30-60%) |
| 边界讨论 | 主动提到方案的限制和适用边界,展示全面思考 |