目录

15-AI与智能体

发表于
2 223.6~287.5 分钟 100625

什么是Agent?组成部分有哪些?

原始问法:

  • 什么是Agent?组成部分有哪些?

来源题目:SRC-15-151-448

面试先答

Agent 是一种以大语言模型(LLM)为核心推理引擎、具备自主感知、决策和行动能力的智能系统。与纯大模型对话不同,Agent 能理解用户意图、规划执行步骤、调用外部工具、获取实时信息,并根据执行结果动态调整后续行动。其核心组成包括:大模型(决策大脑)、上下文管理(记忆)、工具系统(行动能力)、规划模块(任务分解)、执行器(行动调度)。Agent 的价值在于突破了大模型"只能生成文本"的限制,使其能在真实环境中完成复杂任务,但同时也引入了不确定性、安全风险和工程复杂度。

核心结论
  • Agent = LLM + 上下文 + 工具 + 规划 + 执行,是让大模型"能做事"的系统封装
  • Agent 的核心能力是"感知-思考-行动"循环(Think-Act-Obsere Loop)
  • Agent 的关键工程挑战在于意图理解准确性、工具调用可靠性、上下文窗口管理和安全防护
1. 是什么

Agent(智能体)是一个以 大语言模型为核心决策引擎 的自主智能系统。它具备以下五个基本组成部分:

组成部分 作用 说明
大模型(Brain) 理解、推理、决策 负责意图识别、任务规划、工具选择和结果总结
上下文管理(Memory) 会话历史、长期记忆 包括短期记忆(当前对话历史)和长期记忆(知识持久化)
工具系统(Tools) 扩展行动能力 通过 Function Call、MCP 等协议调用外部 API、数据库、文件系统
规划模块(Planner) 任务分解与调度 将复杂目标拆解为可执行的子任务序列
执行器(Executor) 工具调用与结果处理 按规划顺序调用工具,将结果反馈给大模型

概念边界:Agent 不同于普通的 ChatBot(仅对话)、不同于 Workflow(固定流程)、不同于 Plugin(被动调用)。Agent 是主动的、自主决策的循环系统。

2. 为什么需要它

纯大模型存在三大局限:

  1. 知识截止:模型训练数据有截止日期,无法获取实时信息
  2. 无法行动:只能生成文本,不能真正操作外部系统(下单、发邮件、查询数据库)
  3. 不可验证:生成内容缺乏事实校验,容易产生幻觉

Agent 通过工具调用和循环执行,解决了上述矛盾:让模型"能查到最新信息"、"能真正动手做事"、"能通过工具结果验证答案"。

3. 底层原理与完整流程

Agent 的核心是 感知-思考-行动(Think-Act-Observe)循环

用户请求 → [感知] 理解意图
         → [思考] 规划步骤、选择工具
         → [行动] 调用工具 API
         → [观察] 获取工具执行结果
         → [思考] 判断是否完成/需要继续
         → 循环直至任务完成

详细流程:

  1. 接收输入:用户请求 + 当前上下文历史
  2. 意图识别:大模型分析用户真正想达成的目标
  3. 任务规划:将目标分解为有序子任务,选择所需工具
  4. 工具调用:通过 Function Call 协议发送调用请求
  5. 结果处理:接收工具返回结果,判断是否满足需求
  6. 循环迭代:如未完成则继续规划-调用循环
  7. 结束条件:大模型判断任务已达成或无法继续
4. 怎么使用
// 伪代码:使用 Agent 框架的典型方式
Agent agent = Agent.builder()
    .model(new ChatModel("gpt-4o"))
    .addTool(new SearchTool())
    .addTool(new DatabaseQueryTool())
    .addTool(new FileReadTool())
    .memory(new InMemorySession())
    .build();

String result = agent.run("帮我查询订单12345的状态,如果已发货就通知用户");
System.out.println(result);
5. 适用场景
  • 需要多步骤推理+工具调用的复杂任务(如数据分析报告生成、自动化运维)
  • 需要实时信息获取的场景(如实时股票查询、天气推荐)
  • 需要操作外部系统的业务流程(如工单创建、邮件发送)
  • 需要自主决策的智能体(如自主编码助手、智能客服)
6. 不适用场景与替代方案
场景 推荐方案
简单问答/纯知识查询 直接用大模型对话
固定流程业务 传统 Workflow/状态机
实时高并发的确定性操作 RPC + 规则引擎
对延迟要求极高 同步接口调用 + 规则匹配
7. 优缺点与技术取舍

优点

  • 自主决策能力强,可处理开放域复杂任务
  • 灵活度高,同一 Agent 可适配多种场景
  • 可扩展性强,新工具即插即用

缺点

  • 不确定高,每轮循环都可能引入错误累积
  • 延迟大,多轮 LLM 调用导致响应慢
  • 成本高,多次 Token 消耗
  • 调试困难,决策路径不透明
8. 常见问题及解决方案
问题 原因 解决方案
Agent 死循环 大模型无法判断终止条件 设置最大轮次 + 终止条件检测
工具调用参数错误 LLM 生成的参数格式不对 入参校验层 + 自动格式修正
结果偏离意图 规划步骤错误或工具不可用 增加人工确认环节 + 降级策略
Token 超限 上下文历史过长 上下文压缩 + 滑动窗口截断
9. 版本差异与实现边界

Agent 是概念而非标准,各框架实现差异大:

  • LangChain/LangGraph:基于状态图的流程编排,支持复杂 Agent 拓扑
  • AutoGen:多 Agent 对话框架,侧重 Agent 间协作
  • Spring AI:Java 生态的 AI 集成框架,2024 年发布 1.0
  • MCP 协议:2024 年由 Anthropic 提出,标准化工具调用协议
10. 常见追问
  • 追问 1:Agent 和 Workflow 的本质区别?
    • Agent 是动态规划,每步决策由 LLM 实时生成;Workflow 是预定义流程,路径固定
  • 追问 2:Agent 如何保证决策可解释?
    • 记录每轮的思考链(CoT)、工具选择理由、终止判断依据,形成可追溯轨迹
11. 易错点
  • ❌ 错误:Agent = 大模型 + 工具
  • ✅ 正确:Agent 是包含大模型、上下文、工具、规划、执行五要素的完整系统
  • ❌ 错误:Agent 一定比 Workflow 好
  • ✅ 正确:确定性场景用 Workflow 更可靠,开放性复杂任务用 Agent
  • ❌ 错误:Agent 不需要人工干预
  • ✅ 正确:高风险场景必须设置人工确认节点
一句话总结

Agent 是以大模型为决策大脑、辅以上下文记忆和工具调用、通过循环推理自主完成复杂任务的智能系统。


Agent和大模型怎么交互、协同?工作流程是什么?

原始问法:

  • Agent和大模型怎么交互、协同?工作流程是什么?

来源题目:SRC-15-151-449

面试先答

Agent 与大模型的交互采用 Prompt-Response-Observation 循环模式:Agent 将用户意图、可用工具、历史上下文组装成 Prompt 发送给大模型,大模型返回文本回复或工具调用指令,Agent 解析响应、执行工具、将结果反馈给大模型,如此循环直至任务完成。二者是**"大脑"与"身体"的协同关系**:大模型负责理解、推理和决策,Agent 负责工具执行、状态管理和流程控制。这种协同的关键在于:每轮交互都是结构化的,Agent 通过 System Prompt 约束大模型的输出格式,确保可解析、可执行。

核心结论
  • Agent 是大模型的"执行层",大模型是 Agent 的"决策层",二者通过结构化消息交互
  • 交互协议(Function Call / Tool Use)是实现可靠协同的核心
  • 完整工作流:用户输入 → Prompt 组装 → LLM 推理 → 响应解析 → 工具执行 → 结果反馈 → 循环/终止
1. 是什么

Agent 与大模型的交互是一种 "大脑-身体"协同架构

  • 大模型(大脑):负责理解意图、规划步骤、生成行动指令、总结结果
  • Agent(身体):负责管理上下文、调度工具、执行操作、验证结果

二者通过结构化消息协议(如 Function Call)进行通信,每一轮交互都是:

  1. Agent 组装消息(用户意图 + 历史 + 工具定义)
  2. 大模型推理输出(文本回复 + 工具调用请求)
  3. Agent 执行工具并反馈结果
  4. 重复直至完成
2. 为什么需要它

没有 Agent 的大模型是"裸"的:

  • 无法调用工具(没有执行通道)
  • 无法管理多轮上下文(没有状态管理)
  • 无法自主循环(没有流程控制)

没有大模型的 Agent 是"死"的:

  • 无法理解自然语言意图
  • 无法处理开放式决策
  • 无法规划复杂任务

二者协同才构成完整的智能系统。

3. 底层原理与完整流程
┌─────────────┐     ┌──────────────┐     ┌─────────────┐
│  1. 用户请求  │────→│  2. Agent    │────→│  3. 大模型   │
└─────────────┘     │   组装 Prompt │     │   推理决策   │
                    └──────────────┘     └──────┬──────┘
                                                │
                                       ┌────────▼────────┐
                                       │ 4. 解析响应      │
                                       │    文本/工具调用  │
                                       └────────┬────────┘
                                                │
                              ┌─────────────────┼─────────────────┐
                              ▼                                   ▼
                    ┌─────────────┐                     ┌─────────────┐
                    │ 5a. 直接回复 │                     │ 5b. 执行工具 │
                    └─────────────┘                     └──────┬──────┘
                                                               │
                                                     ┌─────────▼─────────┐
                                                     │ 6. 结果反馈给模型   │
                                                     │     进入下一轮循环   │
                                                     └───────────────────┘

关键交互协议:Function Call 格式

{
  "role": "assistant",
  "content": null,
  "tool_calls": [{
    "id": "call_abc123",
    "type": "function",
    "function": {
      "name": "search_order",
      "arguments": "{\"order_id\": \"12345\"}"
    }
  }]
}
4. 怎么使用
// 伪代码:模拟 Agent-LLM 交互流程
while (!agent.isTaskComplete()) {
    // Step 1: 组装消息
    List<Message> messages = agent.buildMessages(userInput, history, tools);

    // Step 2: 调用大模型
    LLMResponse response = llm.chat(messages);

    // Step 3: 解析响应
    if (response.hasToolCalls()) {
        // Step 4: 执行工具
        ToolResult result = agent.executeTool(response.getToolCalls());
        // Step 5: 反馈结果
        agent.addMessage(new ToolMessage(result));
    } else {
        // Step 6: 直接回复用户
        agent.finalize(response.getText());
    }
}
5. 适用场景
  • 多轮工具调用场景(如数据分析、报告生成)
  • 需要大模型理解+外部执行的混合任务
  • 开放式问答+操作的复合业务
6. 不适用场景与替代方案
  • 简单对话场景 → 直接用大模型
  • 固定流程 → Workflow 引擎
  • 高频实时操作 → 同步接口 + 规则
7. 优缺点与技术取舍

优点:灵活、可扩展、能处理复杂任务 缺点:每轮交互都有 LLM 延迟和 Token 成本,交互链越长不确定性越高

8. 常见问题及解决方案
问题 解决方案
大模型输出格式不规范 通过 System Prompt 严格约束 + JSON Schema 校验
上下文过长导致 Token 超限 滑动窗口 + 摘要压缩 + 关键信息保留
工具调用链路过长 设置最大轮次限制 + 中间结果验证
9. 版本差异与实现边界
  • 早期 Agent 框架(如 BabyAGI)基于纯 Prompt 驱动
  • LangChain/LangAgent 引入了 Tool 抽象层
  • 2024 年后 MCP 协议标准化了工具调用接口
  • 各云厂商 Agent 平台(AWS Bedrock Agents、Azure AI Agent Service)均采用类似的交互模式
10. 常见追问
  • 追问 1:如何保证每轮交互的可靠性?
    • 通过响应格式约束、入参校验、工具结果验证三层保障
  • 追问 2:如果大模型一直无法正确决策怎么办?
    • 引入人工干预节点、设置超时降级、切换更强大模型
11. 易错点
  • ❌ 错误:Agent 和大模型是同一个东西
  • ✅ 正确:大模型是决策核心,Agent 是围绕大模型的执行框架
  • ❌ 错误:Agent 交互只有一轮
  • ✅ 正确:Agent 的核心是多轮循环交互
一句话总结

Agent 与大模型是"身体-大脑"的协同关系:Agent 管理流程和工具执行,大模型负责理解和决策,二者通过结构化消息循环交互完成复杂任务。


为什么要给大模型引用工具?单纯大模型不行吗?

原始问法:

  • 为什么要给大模型引用工具?单纯大模型不行吗?

来源题目:SRC-15-151-450

面试先答

单纯大模型存在三大硬伤:知识截止、无法行动、不可验证。给大模型引入工具就是为了突破这三大瓶颈。首先,工具让模型能获取实时信息(查天气、查数据库、查最新新闻),突破训练数据的时间限制;其次,工具让模型能真正"动手做事"(发邮件、创建工单、操作文件),从"只会说"变成"会做事";最后,工具的返回结果是客观事实,可以用来验证模型回答的正确性,减少幻觉。虽然 RAG 也能补充知识,但工具是主动行动,这是检索增强做不到的。

核心结论
  • 工具扩展了大模型的三种核心能力:实时获取信息、主动执行操作、结果验证
  • 单纯大模型受限于训练数据截止时间和无执行能力,无法对接真实业务
  • 工具调用是 Agent 与普通 ChatBot 的本质区别
1. 是什么

给大模型引用工具,就是通过 Function Call 机制让大模型在推理过程中调用外部 API 或服务的能力。工具可以是:

  • 数据查询类:数据库查询、搜索引擎、API 接口
  • 操作执行类:发邮件、创建工单、文件读写
  • 计算辅助类:代码执行、数学计算、格式转换
  • 系统集成类:日历、消息、第三方服务对接
2. 为什么需要它

单纯大模型的三大缺陷:

缺陷 具体表现 影响
知识截止 训练数据有截止日,不知道最新发生的事 无法回答实时问题(如"今天的股价")
无法行动 只能生成文本,不能操作外部系统 无法完成实际任务(如"帮我订会议室")
不可验证 无法检验自己的回答是否正确 容易产生幻觉(如编造不存在的API)

引入工具后的改进:

用户:"帮我查一下订单12345现在到哪了"
单纯大模型:(训练数据中没有该订单)→ 可能编造答案
引入工具:调用订单查询API → 返回真实物流信息 → 可信回答
3. 底层原理与完整流程
1. 开发者注册工具 → 定义工具名称、参数、描述
2. Agent 将工具定义以 JSON Schema 格式注入 System Prompt
3. 大模型根据用户请求判断是否需要调用工具
4. 若需要,输出 Function Call 格式的响应
5. Agent 解析响应、执行对应工具
6. 将工具结果(Tool Message)反馈给大模型
7. 大模型基于真实结果继续推理或直接回答
4. 怎么使用
// 伪代码:注册并使用工具
// 1. 定义工具
Tool orderQueryTool = Tool.builder()
    .name("query_order")
    .description("根据订单号查询订单状态和物流信息")
    .parameter("order_id", "string", "订单编号")
    .handler(params -> orderService.queryOrder(params.get("order_id")))
    .build();

// 2. 注册到 Agent
agent.registerTool(orderQueryTool);

// 3. 大模型自动根据需要调用
// 用户:"查一下订单ORD-2024-001的状态"
// 大模型输出:function_call -> query_order({order_id: "ORD-2024-001"})
// Agent 执行工具,将结果返回给大模型
5. 适用场景
  • 需要实时数据查询的场景
  • 需要操作外部系统的业务流程
  • 需要计算能力的场景(代码执行、数据处理)
  • 需要跨系统集成的复杂任务
6. 不适用场景与替代方案
  • 纯知识问答 → 直接大模型 + RAG
  • 高频确定性调用 → 传统 RPC 接口
  • 超低延迟要求 → 同步接口,不经过 LLM 决策
7. 优缺点与技术取舍

优点:突破大模型能力边界,对接真实业务 缺点:引入外部依赖增加不确定性,工具调用增加延迟和成本

8. 常见问题及解决方案
问题 解决方案
大模型选错工具 优化工具描述 + Few-shot 示例
工具参数生成错误 入参校验 + 自动格式修正
工具服务不可用 重试机制 + 降级保底
9. 版本差异与实现边界
  • GPT-3.5 时代:Function Call 是 GPT 专属能力
  • GPT-4/4o:增强了工具调用的并发能力和结构化输出
  • 开源模型(如 Llama 3):也支持类似的 Tool Use 格式
  • MCP 协议:2024 年标准化了工具调用的通信协议,降低了工具集成成本
10. 常见追问
  • 追问 1:RAG 和工具调用有什么区别?
    • RAG 是"读"预存知识,工具是"查"实时数据+做实际操作
  • 追问 2:工具调用的安全性如何保障?
    • 通过权限控制、参数校验、操作审计三层防护
11. 易错点
  • ❌ 错误:有了工具大模型就不会产生幻觉
  • ✅ 正确:工具只在被正确调用时提供验证,模型仍可能选择错误的工具或误解结果
  • ❌ 错误:工具越多 Agent 能力越强
  • ✅ 正确:工具过多会增加模型选择的困惑度,应按需精简
一句话总结

给大模型引入工具,是为了突破其知识截止、无法行动、不可验证的三大固有局限,使其从"只会说"的对话模型升级为"会做事"的智能体。


工具给大模型扩展了什么能力?

原始问法:

  • 工具给大模型扩展了什么能力?

来源题目:SRC-15-151-451

面试先答

工具给大模型扩展了四大核心能力:信息获取能力、行动执行能力、计算辅助能力、系统集成能力。信息获取让模型能查到实时数据(天气、股价、数据库记录),突破训练数据的时间限制;行动执行让模型能操作外部系统(发邮件、建工单、写文件),从"回答问题"变成"完成任务";计算辅助让模型能进行复杂运算(代码执行、大数计算、格式转换),弥补模型精确计算的不足;系统集成让模型能跨平台协作(对接日历、消息、第三方 API),融入现有业务生态。每种能力都对应一类具体工具,构成 Agent 的能力工具箱。

核心结论
  • 工具扩展了四类能力:信息获取、行动执行、计算辅助、系统集成
  • 每类能力对应不同的工具类型和使用场景
  • 工具能力的选择直接影响 Agent 的实际可用性
1. 信息获取能力

让模型突破训练数据截止时间,获取实时/动态信息:

工具类型 示例 解决的问题
搜索引擎 Google Search、Bing Search 实时新闻、最新资讯
数据库查询 MySQL、PostgreSQL 查询 业务数据实时查询
API 接口 天气 API、股票 API 第三方实时数据
文件读取 本地文件、云存储 自有文档内容
2. 行动执行能力

让模型从"说"变成"做",真正操作外部系统:

工具类型 示例 应用场景
消息发送 邮件、短信、钉钉通知 发送通知、告警
工单创建 Jira、GitLab Issue 项目管理
文件写入 创建、编辑、删除文件 文档生成
订单操作 创建/取消订单 业务自动化
3. 计算辅助能力

弥补大模型在精确计算和逻辑推理上的不足:

工具类型 示例 解决的问题
代码执行 Python REPL、沙箱环境 复杂运算、数据处理
数学计算 Wolfram Alpha 精确数学、公式推导
格式转换 JSON↔XML、Markdown↔HTML 数据格式处理
SQL 生成 自然语言转 SQL 数据库操作
4. 系统集成能力

让模型融入企业现有技术栈:

工具类型 示例 应用场景
日历集成 Google Calendar、Outlook 会议安排
版本控制 Git、GitHub 代码管理
消息队列 Kafka、RabbitMQ 异步通知
认证系统 OAuth、SSO 权限管理
5. 底层原理

工具扩展能力的本质是 通过 Function Call 协议将大模型的文本输出转化为结构化的 API 调用

大模型推理 → 判断需要什么能力 → 输出 Function Call
    → Agent 解析 → 执行对应工具 → 返回 Tool Result
    → 大模型基于结果继续推理
6. 适用场景
  • 信息获取型:实时数据查询、资讯聚合
  • 行动执行型:自动化办公、业务流程
  • 计算辅助型:数据分析报告、科学计算
  • 系统集成型:企业内部智能化改造
7. 常见追问
  • 追问 1:如何判断该用哪种工具?
    • 根据任务类型和工具描述,由大模型自动选择;或通过路由规则提前分发
  • 追问 2:工具能力的边界在哪里?
    • 受限于工具本身的 API 能力,Agent 只能做工具支持的事
一句话总结

工具为大模型扩展了信息获取、行动执行、计算辅助和系统集成四大能力,使其从文本生成器升级为能感知环境、执行操作、融合系统的智能体。


什么是Function Call?

原始问法:

  • 什么是Function Call?

来源题目:SRC-15-151-452

面试先答

Function Call 是大模型的一种能力,指模型在生成回复时,可以主动触发调用外部函数(工具),而不是只能生成纯文本。具体来说,模型输出的不是普通文本,而是一个结构化的函数调用请求,包含函数名和参数。Agent 框架接收到这个请求后,执行对应的函数,将执行结果作为"工具消息"反馈给模型,模型再基于结果生成最终回复。这一机制是 Agent 实现"感知-行动-观察"循环的核心技术基础,让大模型从被动回答变成主动调用外部能力。

核心结论
  • Function Call 是大模型输出结构化函数调用指令的能力
  • 它是 Agent 实现工具调用的核心协议
  • 工作流程:模型输出调用请求 → Agent 执行函数 → 结果反馈 → 模型继续推理
1. 是什么

Function Call(函数调用)是指 大模型在生成回复时,能够输出一段结构化的"调用请求"而非普通文本,请求调用外部预注册的函数/工具。

// Function Call 响应示例
{
  "role": "assistant",
  "content": null,
  "function_call": {
    "name": "get_weather",
    "arguments": "{\"city\": \"北京\", \"date\": \"2024-06-01\"}"
  }
}

核心要素

  • 函数名:预注册的工具标识
  • 参数:JSON 格式的调用参数
  • 调用 ID:用于匹配结果
2. 为什么需要它

没有 Function Call,大模型只能输出文本。开发者需要从文本中解析出调用意图和参数,这非常不可靠。Function Call 将"调用请求"标准化为结构化格式,实现了:

  • 可靠的意图识别(无需 NLP 解析自由文本)
  • 规范的参数传递(JSON Schema 校验)
  • 可预测的交互流程(Agent 可程序化处理)
3. 底层原理与完整流程
Step 1: 开发者定义工具的 JSON Schema
        ↓
Step 2: Agent 将 Schema 注入 System Prompt
        ↓
Step 3: 大模型推理时,根据 Schema 生成函数调用
        ↓
Step 4: Agent 检测到 function_call 字段
        ↓
Step 5: Agent 执行对应函数,获取结果
        ↓
Step 6: Agent 将结果封装为 Tool Message 返回
        ↓
Step 7: 大模型基于结果继续推理
4. 怎么使用
// 伪代码:Function Call 的使用方式

// 1. 定义工具 Schema
String toolSchema = """
{
  "name": "get_weather",
  "description": "获取指定城市的天气",
  "parameters": {
    "type": "object",
    "properties": {
      "city": {"type": "string", "description": "城市名称"},
      "date": {"type": "string", "description": "日期 YYYY-MM-DD"}
    },
    "required": ["city"]
  }
}
""";

// 2. 调用大模型时传入工具列表
ChatCompletionResponse response = client.chat(
    messages,
    List.of(toolSchema)  // 注册可用工具
);

// 3. 处理响应
if (response.hasFunctionCall()) {
    FunctionCall call = response.getFunctionCall();
    String result = executeFunction(call);
    // 4. 将结果反馈给模型
    messages.add(new ToolMessage(call.getId(), result));
    // 再次调用模型...
}
5. 适用场景
  • 需要大模型自主选择调用外部 API 的场景
  • 多轮工具调用的复杂任务
  • 需要结构化输出与程序交互的场景
6. 不适用场景与替代方案
  • 固定流程的 API 调用 → 硬编码调用
  • 大模型不需要判断意图 → 路由规则直接分发
  • 简单问答 → 不需要 Function Call
7. 优缺点与技术取舍

优点

  • 结构化输出,可靠可解析
  • 模型自主选择调用,灵活度高
  • 标准化协议,跨平台兼容

缺点

  • 依赖模型理解 Schema 的能力
  • 每轮调用增加延迟
  • 并发调用管理复杂
8. 常见问题及解决方案
问题 解决方案
参数格式错误 严格 JSON Schema + 自动修正
模型不调用工具 在 Prompt 中强化使用说明 + Few-shot 示例
并发调用冲突 串行执行 + 结果汇总
9. 版本差异与实现边界
  • GPT 3.5 Turbo:首次支持 Function Call
  • GPT 4/4o:支持并发多工具调用、增强了参数理解能力
  • Claude 3:支持 Tool Use 协议
  • 开源模型:Llama 3、Mistral 等也支持类似格式
  • MCP 协议:2024 年标准化了工具调用的通信协议
10. 常见追问
  • 追问 1:Function Call 和普通 Prompt 中有什么区别?
    • Function Call 输出是结构化的函数调用,普通 Prompt 输出是自然语言文本
  • 追问 2:如何设计好的工具 Schema?
    • 清晰的描述(让模型知道何时调用)、精确的参数定义、合理的必填字段
11. 易错点
  • ❌ 错误:Function Call = 调用 Java 函数
  • ✅ 正确:Function Call 是大模型的输出格式,Agent 框架解析后执行对应操作
  • ❌ 错误:Function Call 一定会被正确触发
  • ✅ 正确:模型可能不调用或错误调用,需要 Prompt 工程引导
一句话总结

Function Call 是大模型输出结构化函数调用请求的能力,是 Agent 实现"自主决策+工具执行"的核心机制。


什么是MCP?MCP的组成是什么?

原始问法:

  • 什么是MCP?MCP的组成是什么?

来源题目:SRC-15-151-453

面试先答

MCP(Model Context Protocol)是 Anthropic 公司在 2024 年提出的一套开放协议,用于标准化大模型与外部工具/数据源之间的通信方式。可以把它理解为 AI 时代的"USB 接口标准"——像 USB 统一了外设连接一样,MCP 统一了大模型与工具的接入方式。MCP 由三个核心组件构成:MCP Client(AI 应用端)、MCP Server(工具提供方)、MCP Protocol(通信协议)。Client 消费 Server 提供的能力,Protocol 定义了双方的消息格式和交互规范。它的价值在于:一套标准接入所有工具,避免了每个 AI 平台都要单独适配的碎片化问题。

核心结论
  • MCP 是 AI 时代的工具/数据接入标准协议,由 Anthropic 2024 年发起
  • 核心三组件:MCP Client、MCP Server、MCP Protocol
  • 解决了多平台工具碎片化对接问题,降低了 AI 应用集成成本
1. 是什么

MCP(Model Context Protocol) 是一个开放协议,定义了大模型应用(Client)与外部工具/数据提供者(Server)之间的标准化通信方式。

设计目标

  • 一套标准,接入所有 AI 平台
  • 工具提供方只需实现一次,就能被所有支持 MCP 的应用使用
  • 类似 HTTP 对 Web 的意义,MCP 致力于成为 AI 应用的"通用连接器"

核心组成

组件 角色 说明
MCP Client 消费者 AI 应用(如 Claude Desktop、Cursor),连接 Server 并使用其能力
MCP Server 提供者 将外部工具/数据封装为 MCP 服务,暴露给 Client 使用
MCP Protocol 协议层 定义 Client-Server 间的消息格式、能力发现、调用规范
2. 为什么需要它

没有 MCP 的问题:

  • 碎片化:每个 AI 平台有自己的工具接入协议,开发者需要重复适配
  • 成本高:同一工具要为不同 AI 平台开发多个版本
  • 扩展难:新 AI 平台出现时,现有工具无法直接复用

MCP 的解决方案:

  • 标准化:一次实现,多端复用
  • 解耦:工具提供方和 AI 应用方独立演进
  • 生态化:形成 AI 工具的开放生态
3. 底层原理与完整流程
┌──────────────┐     MCP Protocol     ┌──────────────┐
│  MCP Client  │◄────────────────────►│  MCP Server  │
│  (AI 应用)   │                      │  (工具提供方) │
└──────────────┘                      └──────────────┘

通信流程:
1. Client → Server: initialize(握手,协商版本)
2. Client → Server: tools/list(获取可用工具列表)
3. Server → Client: 返回工具定义(名称、描述、参数 Schema)
4. Client → Server: tools/call(调用指定工具)
5. Server → Client: 返回执行结果
6. Client → Server: resources/list(获取可用资源)
7. Server → Client: 返回资源列表
8. Client → Server: resources/read(读取资源)
4. 怎么使用
// 伪代码:实现一个 MCP Server

// 1. 创建 MCP Server
MCPServer server = MCPServer.builder()
    .name("weather-server")
    .version("1.0.0")
    .build();

// 2. 注册工具
server.registerTool("get_weather", ToolDef.builder()
    .description("获取城市天气")
    .parameter("city", "string", "城市")
    .handler(params -> weatherService.getWeather(params.get("city")))
    .build());

// 3. 注册资源
server.registerResource("weather_codes",
    ResourceDef.builder()
        .description("天气代码表")
        .mimeType("application/json")
        .content(weatherCodesJson)
        .build());

// 4. 启动 Server
server.start();  // 通过 stdio/SSE 与 Client 通信
5. 适用场景
  • 为 AI 应用提供外部工具/数据的开发者
  • 需要对接多个 AI 平台的企业
  • AI 工具生态的建设者
  • 企业内部 AI 能力的标准化输出
6. 不适用场景与替代方案
  • 仅对接单一 AI 平台 → 可使用平台原生协议
  • 内部工具仅自用 → 无需 MCP 标准
  • 超低延迟要求 → MCP 协议有额外开销
7. 优缺点与技术取舍

优点

  • 一套标准,多端复用
  • 开放生态,社区驱动
  • 解耦 Client 和 Server,独立演进

缺点

  • 协议仍在演进,可能有 breaking changes
  • 相比直接对接有额外协议开销
  • 社区生态还在发展中
8. 常见问题及解决方案
问题 解决方案
Client 不支持 MCP 通过适配桥接(Adapter Bridge)转换协议
Server 实现复杂 使用官方 SDK(支持 Python/TypeScript/Java)
工具调用安全 MCP 支持权限控制和操作审计
9. 版本差异与实现边界
  • 2024 年 6 月:Anthropic 发布 MCP 协议规范
  • 当前版本:Protocol Version 2024-11-05
  • 传输层:支持 stdio 和 SSE(Server-Sent Events)两种传输方式
  • 语言支持:官方 SDK 支持 TypeScript、Python、Java
  • 兼容性:Claude Desktop、Cursor 等已原生支持 MCP
10. 常见追问
  • 追问 1:MCP 和 Function Call 的关系?
    • Function Call 是模型端的能力,MCP 是工具端的协议,二者互补
  • 追问 2:MCP 的安全机制?
    • 支持身份认证、权限控制、操作审计、沙箱隔离
11. 易错点
  • ❌ 错误:MCP 是 Anthropic 的私有协议
  • ✅ 正确:MCP 是开放协议,任何组织和个人都可参与
  • ❌ 错误:MCP 只能用于 Claude
  • ✅ 正确:MCP 独立于模型厂商,任何 AI 应用都可支持
一句话总结

MCP 是 AI 时代的"USB 标准接口",由 Client、Server、Protocol 三组件构成,统一了大模型与外部工具/数据的接入方式。


什么是Skill?和Function Call、MCP的区别是什么?

原始问法:

  • 什么是Skill?和Function Call、MCP的区别是什么?

来源题目:SRC-15-151-454

面试先答

Skill 是 Agent 框架中的一个 高层封装概念,指一组相关能力的集合,通常包含 Prompt 模板、工具调用流程和处理逻辑。可以把 Skill 理解为 Agent 的"技能包"——就像人的技能一样,一个 Skill 可能包含多个步骤和多种工具的组合调用。而 Function Call 是单步的"函数调用指令",MCP 是工具间通信的"协议标准"。三者的关系是:Function Call 是原子调用,MCP 是连接标准,Skill 是封装了多个调用的复合能力。打个比方:Function Call 是"一个按钮",MCP 是"USB 接口",Skill 是"整个厨房料理台"。

核心结论
  • Skill 是 Agent 的高层封装,包含 Prompt + 工具 + 逻辑的复合能力单元
  • Function Call 是原子调用,单步的函数调用指令
  • MCP 是通信协议,标准化的工具接入规范
1. 是什么

Skill(技能) 是 Agent 框架中对一组相关能力的高层封装,通常包含:

  • System Prompt 片段:定义该 Skill 的行为规范
  • 工具定义:该 Skill 可使用的工具集合
  • 执行逻辑:调用这些工具的流程和规则
  • 示例:Few-shot 示例,引导模型正确使用
Skill 示例:"数据分析技能"
├── Prompt:你是一个数据分析专家,擅长处理 CSV 文件...
├── 工具:[文件读取、Python 执行、图表生成]
└── 逻辑:读取数据 → 清洗 → 分析 → 生成报告
2. 三者对比
维度 Function Call MCP Skill
定位 原子调用指令 通信协议标准 高层能力封装
粒度 单步函数调用 Client-Server 交互 多步复合能力
关注点 调用格式 传输规范 业务语义
使用者 Agent 框架 工具开发者 Agent 设计者
类比 一个按钮 USB 接口 整个功能模块
3. 底层原理与完整流程
Skill 内部可以包含多个 Function Call 和 MCP 工具:

Skill "订单处理"
├── Function Call: query_order(order_id)     ← 原子调用
├── MCP Tool: send_email(to, content)        ← 通过 MCP 调用
├── Function Call: create_ticket(description) ← 原子调用
└── 执行逻辑:查询 → 通知 → 建单(步骤编排)
4. 怎么使用
// 伪代码:定义和使用 Skill

// 定义 Skill
Skill dataAnalysisSkill = Skill.builder()
    .name("data_analysis")
    .systemPrompt("你是数据分析专家...")
    .tools(List.of(fileReadTool, pythonExecTool, chartTool))
    .build();

// 使用 Skill
agent.registerSkill(dataAnalysisSkill);
// 用户:"帮我分析这个销售数据文件"
// Agent 加载 data_analysis Skill,使用其中的工具完成分析
5. 适用场景
  • 将常用业务流程封装为可复用能力
  • 为不同领域的 Agent 提供专业技能包
  • 降低 Agent 使用门槛,开箱即用
6. 优缺点与技术取舍

优点:复用性强、语义清晰、降低使用门槛 缺点:封装过度会增加灵活性,Skill 间可能存在能力重叠

7. 常见追问
  • 追问 1:Skill 和 Tool 的关系?
    • Skill 是 Tool 的编排组合,一个 Skill 可以使用多个 Tool
  • 追问 2:Skill 是否可以跨 Agent 共享?
    • 可以,Skill 是独立封装,可以在不同 Agent 间复用
一句话总结

Skill 是 Agent 的"技能包",封装了 Prompt、工具和执行逻辑的复合能力;Function Call 是单步调用指令,MCP 是工具通信标准,三者对应 Agent 系统的不同抽象层级。


MCP和Skill哪个上下文占用大?

原始问法:

  • MCP和Skill哪个上下文占用大?

来源题目:SRC-15-151-455

面试先答

Skill 的上下文占用通常比 MCP 大得多。原因是 Skill 需要将完整的 Prompt 模板、工具定义、执行逻辑和示例代码都注入到 Agent 的上下文窗口中,一个 Skill 可能消耗数千甚至上万 Token。而 MCP 本身只是一个通信协议,它的上下文占用主要是工具的元信息(名称、描述、参数 Schema),这些通常只有几百 Token。换句话说,Skill 是"预加载"到上下文的完整能力包,而 MCP 只是"按需调用"的轻量接口描述。不过,MCP 调用后返回的结果也会占用上下文,这取决于工具返回数据的大小。

核心结论
  • Skill 上下文占用大(Prompt + 工具定义 + 逻辑 + 示例)
  • MCP 上下文占用小(仅工具元信息)
  • 实际项目中应按需选择:Skill 适合固定场景,MCP 适合动态调用
1. 详细分析

Skill 的上下文占用

一个 Skill 的上下文组成:
├── System Prompt: ~500-2000 Tokens
├── 工具定义(JSON Schema): ~200-500 Tokens/工具
├── Few-shot 示例: ~500-2000 Tokens
└── 执行逻辑描述: ~200-500 Tokens
总计:一个 Skill 通常占用 1500-5000 Tokens

MCP 的上下文占用

MCP 工具的上下文组成:
├── 工具名称: ~10 Tokens
├── 工具描述: ~50-200 Tokens
├── 参数 Schema: ~50-200 Tokens
└── (仅此而已)
总计:一个 MCP 工具通常占用 100-500 Tokens
2. 设计权衡
维度 Skill MCP
上下文占用
能力范围 复合能力 单一能力
调用方式 Skill 激活即加载 按需加载
适用场景 固定流程、高频使用 动态调用、低频使用
灵活性 较低(固定封装) 高(按需组合)
3. 最佳实践
  • Skill 适合:高频使用的固定业务流程(如数据分析、报告生成)
  • MCP 适合:低频调用的外部工具(如天气查询、数据库操作)
  • 混合策略:Skill 内部通过 MCP 调用外部工具,既保持 Skill 的封装性,又利用 MCP 的灵活性
4. 常见追问
  • 追问 1:如何优化 Skill 的上下文占用?
    • 精简 Prompt、按需加载工具、使用动态 Skill 切换
  • 追问 2:MCP 工具返回结果太大怎么办?
    • 对结果进行截断、摘要或分页处理
一句话总结

Skill 上下文占用大(封装了完整能力包),MCP 上下文占用小(仅工具元信息),实际项目中应根据使用频率和场景灵活选择。


Agent怎么判断某轮tool call后该不该结束?

原始问法:

  • Agent怎么判断某轮tool call后该不该结束?

来源题目:SRC-15-151-456

面试先答

Agent 判断一轮 Tool Call 后该不该结束,主要有 四种机制:一是 大模型自主判断,在 System Prompt 中要求模型在完成目标时输出特定的结束标记(如 <COMPLETE>),由 Agent 框架检测该标记;二是 显式结束条件,通过代码预设判断规则(如检查工具返回结果是否包含关键字段);三是 最大轮次限制,设置一个上限防止死循环;四是 人工确认节点,在关键步骤后强制要求用户确认。实际项目中通常组合使用这四种机制,既保证任务完成度,又防止无限循环。

核心结论
  • Agent 结束判断的四种核心机制:LLM 自主判断、显式条件、最大轮次、人工确认
  • 通常组合使用多种机制确保可靠性
  • 设计核心是平衡"完成度"和"效率"
1. 是什么

Agent 的结束判断是指在一轮 Tool Call 完成后,系统决定是继续执行下一轮还是终止任务的过程。这是 Agent 循环的关键控制节点。

2. 四种结束判断机制
机制 实现方式 优点 缺点
LLM 自主判断 Prompt 引导模型输出结束标记 灵活,适应开放场景 可能判断错误
显式条件 代码检查结果是否满足预设规则 确定性强 只能覆盖已知场景
最大轮次 设置循环上限(如 10 轮) 防止死循环 可能提前终止未完成任务
人工确认 关键节点暂停等待用户 安全可控 用户体验受影响
3. 底层原理与完整流程
Tool Call 返回结果
    ↓
┌─────────────────────┐
│  1. LLM 分析结果     │ ──→ 输出"继续"或"结束"
└─────────┬───────────┘
          │
          ▼
┌─────────────────────┐
│  2. 检查显式条件     │ ──→ 结果是否满足预设规则
└─────────┬───────────┘
          │
          ▼
┌─────────────────────┐
│  3. 检查最大轮次     │ ──→ 是否已达上限
└─────────┬───────────┘
          │
          ▼
┌─────────────────────┐
│  4. 是否需要人工确认 │ ──→ 关键节点暂停
└─────────┬───────────┘
          │
     ┌────┴────┐
     ▼         ▼
  继续执行   结束任务
4. 怎么使用
// 伪代码:实现多机制结束判断
int maxRounds = 10;
int currentRound = 0;

while (currentRound < maxRounds) {
    currentRound++;

    // 执行 Tool Call
    ToolResult result = agent.executeNextTool();

    // 机制 1: LLM 自主判断
    if (result.containsCompletionMark()) {
        break;
    }

    // 机制 2: 显式条件
    if (result.matches(completionRule)) {
        break;
    }

    // 机制 3: 最大轮次
    if (currentRound >= maxRounds) {
        agent.finalize("已达到最大执行轮次");
        break;
    }

    // 机制 4: 人工确认
    if (result.needsUserConfirmation()) {
        String userInput = agent.waitForUserInput();
        if (userInput.equals("确认完成")) break;
    }
}
5. 适用场景
  • 开放式任务:LLM 自主判断 + 最大轮次
  • 确定性流程:显式条件 + 最大轮次
  • 高风险操作:强制人工确认 + 其他机制
6. 常见问题及解决方案
问题 解决方案
Agent 死循环 最大轮次 + 超时机制
过早结束 显式条件检查 + LLM 结果验证
过晚结束(浪费 Token) 设置最小完成条件 + LLM 结果分析
7. 易错点
  • ❌ 错误:Agent 可以精准判断何时结束
  • ✅ 正确:结束判断需要多种机制组合使用,单一机制不够可靠
  • ❌ 错误:只要 LLM 判断结束就一定正确
  • ✅ 正确:LLM 可能误判,需要显式条件和人工确认兜底
一句话总结

Agent 通过 LLM 自主判断、显式条件、最大轮次、人工确认四种机制组合判断结束时机,在任务完成度和执行效率间取得平衡。


Agent生成代码执行用什么方法?了解过sandbox吗?

原始问法:

  • Agent生成代码执行用什么方法?了解过sandbox吗?

来源题目:SRC-15-151-457

面试先答

Agent 生成代码后不能直接在生产环境执行,通常通过 沙箱(Sandbox) 机制进行安全执行。沙箱是一个隔离的运行环境,具有资源限制(CPU、内存、执行时间)、权限控制(文件访问、网络访问受限)和安全防护(防止恶意代码执行)。常见实现包括:Docker 容器、FireJail、NSJail、E2B 等。执行流程是:Agent 将生成的代码发送到沙箱 → 沙箱在受限环境中执行 → 返回执行结果 → Agent 基于结果继续推理。沙箱的核心价值在于在安全可控的条件下赋予 Agent 代码执行能力,是 Coding Agent 的关键基础设施。

核心结论
  • Agent 代码执行依赖沙箱机制,核心实现包括 Docker、FireJail、E2B 等
  • 沙箱提供资源隔离、权限控制和安全防护
  • 代码执行流程:生成→沙箱执行→结果返回→后续推理
1. 是什么

沙箱(Sandbox) 是一个安全隔离的代码执行环境,具有以下核心特征:

特征 说明
资源限制 CPU 时间、内存大小、磁盘空间限制
权限控制 文件系统、网络、系统调用受限
安全隔离 进程级/系统级隔离,防止恶意代码扩散
超时机制 防止死循环或长时间占用资源
2. 为什么需要它

直接执行大模型生成的代码极其危险:

  • 代码可能包含恶意指令(删除文件、窃取数据)
  • 代码可能有 Bug(死循环、资源耗尽)
  • 代码可能访问敏感资源(数据库、密钥)

沙箱通过隔离和限制,将风险降到可控范围。

3. 常见沙箱实现
实现 原理 特点
Docker 容器 操作系统级虚拟化 隔离性强,启动较慢(秒级)
FireJail Linux Namespace + Cgroup 轻量级,毫秒级启动
NSJail FreeBSD 沙箱 系统级隔离
E2B 云端 SDK 沙箱 远程执行,易于扩展
本地解释器 Python/Node REPL 最简单,隔离性最弱
4. 底层原理与完整流程
Agent 生成代码
    ↓
发送到沙箱服务
    ↓
┌──────────────────────────────┐
│ 沙箱启动(容器/进程隔离)      │
│   ↓                          │
│ 资源限制应用(CPU/内存/超时)  │
│   ↓                          │
│ 权限控制(文件/网络/系统调用)  │
│   ↓                          │
│ 代码执行                      │
│   ↓                          │
│ 捕获输出/异常                 │
│   ↓                          │
│ 销毁沙箱环境                  │
└──────────────────────────────┘
    ↓
返回执行结果给 Agent
5. 怎么使用
// 伪代码:使用沙箱执行代码
Sandbox sandbox = Sandbox.builder()
    .type(SandboxType.E2B)
    .timeout(30, TimeUnit.SECONDS)
    .memoryLimit(512, MemoryUnit.MB)
    .cpuLimit(1.0)
    .allowNetwork(false)
    .allowFileAccess("/tmp/workspace")
    .build();

ExecutionResult result = sandbox.execute(code, language);
if (result.isSuccess()) {
    System.out.println("Output: " + result.getOutput());
} else {
    System.out.println("Error: " + result.getError());
}
6. 适用场景
  • Coding Agent 的代码执行和验证
  • 数据分析 Agent 的代码运行
  • 需要动态执行代码的自动化场景
  • AI 辅助编程的实时反馈
7. 常见追问
  • 追问 1:沙箱如何保证安全?
    • 多层防护:进程隔离 + 资源限制 + 权限控制 + 安全审计
  • 追问 2:沙箱执行的代码能访问生产数据库吗?
    • 通常不行,沙箱网络受限;如有需要通过受控的 MCP 代理间接访问
一句话总结

Agent 通过沙箱安全执行生成的代码,沙箱提供资源隔离、权限控制和安全防护,是 Coding Agent 等需要动态代码执行场景的关键基础设施。


Agent执行代码工具如何设计?报错了怎么办?

原始问法:

  • Agent执行代码工具如何设计?报错了怎么办?

来源题目:SRC-15-151-458

面试先答

设计 Agent 的代码执行工具需要考虑 五个核心维度:一是 安全隔离(通过沙箱限制资源和权限),二是 多语言支持(对接不同编程语言的解释器),三是 结果捕获(标准输出、错误输出、执行状态的完整采集),四是 错误处理(将代码报错转化为可被 Agent 理解的结构化信息),五是 重试与降级(代码出错时自动修正或降级处理)。报错后的处理策略包括:将错误信息结构化反馈给 LLM 让其自行修正、设置重试次数上限、超过阈值则降级为手动处理、记录错误日志用于后续分析改进。

核心结论
  • 代码执行工具设计五维度:安全、多语言、结果捕获、错误处理、重试降级
  • 报错处理:结构化反馈→LLM 自愈→重试→降级→人工
  • 关键设计原则:可恢复、可观测、可降级
1. 工具架构设计
┌─────────────────────────────────────────────────┐
│              Code Execution Tool                 │
├─────────────────────────────────────────────────┤
│  1. 安全层(沙箱)                               │
│     ├── Docker / FireJail / E2B                 │
│     ├── 资源限制(CPU/内存/超时)                 │
│     └── 权限控制(文件/网络/系统调用)            │
├─────────────────────────────────────────────────┤
│  2. 执行层(多语言)                              │
│     ├── Python 解释器                            │
│     ├── Node.js 运行时                           │
│     ├── Java (JDK)                               │
│     └── Shell/Bash                               │
├─────────────────────────────────────────────────┤
│  3. 采集层(结果捕获)                           │
│     ├── 标准输出(stdout)                        │
│     ├── 错误输出(stderr)                       │
│     ├── 执行状态(exit code)                    │
│     └── 执行耗时(timing)                       │
├─────────────────────────────────────────────────┤
│  4. 处理层(错误处理)                           │
│     ├── 错误分类(语法/运行时/资源)              │
│     ├── 结构化错误信息生成                       │
│     └── 重试/降级决策                            │
├─────────────────────────────────────────────────┤
│  5. 观测层(日志与监控)                         │
│     ├── 执行日志记录                             │
│     ├── 错误统计分析                             │
│     └── 性能监控告警                             │
└─────────────────────────────────────────────────┘
2. 报错处理策略
代码执行报错
    ↓
┌─────────────────────┐
│ 1. 错误分类          │
│    ├── 语法错误      │ → 反馈给 LLM 修正
│    ├── 运行时错误    │ → 反馈给 LLM 修正
│    ├── 资源超限      │ → 调整资源限制或简化代码
│    └── 沙箱异常      │ → 基础设施问题,告警
└─────────┬───────────┘
          │
          ▼
┌─────────────────────┐
│ 2. 结构化错误反馈    │
│    {                │
│      "error_type":  │
│      "message":     │
│      "line":        │
│      "suggestion":  │
│    }                │
└─────────┬───────────┘
          │
          ▼
┌─────────────────────┐
│ 3. LLM 自愈(重试)  │ → 最多 N 次(默认 3)
└─────────┬───────────┘
          │
          ▼
    重试成功?──否──→ 降级处理
          │是
          ▼
       返回结果    ├── 简化执行
                    ├── 建议用户手动
                    └── 记录学习(用于 Prompt 优化)
3. 怎么使用
// 伪代码:代码执行工具使用
CodeExecutionTool codeTool = CodeExecutionTool.builder()
    .sandbox(new DockerSandbox("python:3.11-slim"))
    .language(ProgrammingLanguage.PYTHON)
    .maxRetries(3)
    .timeout(30, TimeUnit.SECONDS)
    .onError(ErrorStrategy.AUTO_FIX)
    .build();

ExecutionResult result = codeTool.execute(code);
// result 包含: success, stdout, stderr, exitCode, executionTime

if (!result.isSuccess() && result.getRetryCount() >= 3) {
    // 降级处理
    agent.handoffToUser("代码多次执行失败,建议手动检查");
}
4. 常见追问
  • 追问 1:如何设计"自愈"机制?
    • 将错误信息结构化后反馈给 LLM,Prompt 中明确要求其修正代码后重试
  • 追问 2:如何防止无限重试?
    • 设置最大重试次数 + 重试次数递增等待 + 失败后降级
一句话总结

代码执行工具通过安全沙箱、多语言执行、结果采集、错误处理和重试降级五层架构设计,报错后通过"分类→结构化反馈→LLM 自愈→降级"的策略实现可靠执行。


重试做在tool内还是agent多次调用?

原始问法:

  • 重试做在tool内还是agent多次调用?

来源题目:SRC-15-151-459

面试先答

重试机制的选择取决于 失败原因和重试策略。一般来说,瞬时故障(如网络抖动、超时)应在 Tool 内部重试,因为这属于基础设施层面的问题,不需要 LLM 介入判断;而 逻辑错误(如参数错误、业务规则不满足)应由 Agent 通过多次调用解决,因为这需要 LLM 理解错误原因并调整参数。最佳实践是 分层重试:Tool 内部处理瞬时故障(固定次数、固定间隔),Agent 层面处理逻辑错误(LLM 分析原因、动态调整方案)。关键原则是:能在 Tool 层解决的不上升到 Agent 层,需要 LLM 智能判断的不在 Tool 层硬编码

核心结论
  • 瞬时故障在 Tool 内重试,逻辑错误由 Agent 多次调用解决
  • 采用分层重试策略:基础设施问题底层解决,智能决策问题交给 Agent
  • 关键区分:是否需要 LLM 的智能判断能力
1. 分层重试策略
失败场景分析:
├── 基础设施层(Tool 内部重试)
│   ├── 网络超时/抖动
│   ├── 服务暂时不可用
│   └── 资源临时不可用
│
│   处理:固定次数(2-3次)+ 递增等待(1s, 2s, 4s)
│
└── 智能层(Agent 多次调用)
    ├── 参数格式错误
    ├── 业务规则不满足
    ├── 需要补充信息
    └── 服务返回需要进一步处理
│
    处理:LLM 分析错误 → 调整参数/方案 → 重新调用
2. 对比分析
维度 Tool 内重试 Agent 多次调用
决策方 Tool 框架(规则) LLM(智能判断)
适用场景 瞬时故障 逻辑错误
Token 消耗 无额外消耗 消耗 Token
延迟 低(毫秒级) 高(秒级)
灵活性 固定策略 动态调整
典型实现 Spring Retry、Resilience4j Agent 循环 + LLM 推理
3. 最佳实践
// 伪代码:分层重试设计

// Tool 层:处理瞬时故障
@Retryable(
    value = {TimeoutException.class, ServiceUnavailableException.class},
    maxAttempts = 3,
    backoff = @Backoff(delay = 1000, multiplier = 2)
)
public ToolResult callTool(ToolRequest request) {
    return toolClient.execute(request);
}

// Agent 层:处理逻辑错误
while (retryCount < MAX_AGENT_RETRIES) {
    ToolResult result = tool.execute(request);

    if (result.isLogicError()) {
        // 需要 LLM 分析并调整
        String correctedParams = llm.analyzeAndFix(
            result.getError(),
            request.getParams()
        );
        request.setParams(correctedParams);
        retryCount++;
    } else if (result.isSuccess()) {
        break;
    } else {
        // 未知错误,直接失败
        throw new AgentExecutionException(result.getError());
    }
}
4. 常见追问
  • 追问 1:如果 Tool 内重试也失败了怎么办?
    • 抛出异常给 Agent,由 Agent 决定是否进一步重试或降级
  • 追问 2:如何避免重试风暴?
    • 设置全局重试上限 + 熔断器(失败率过高时自动停止)
一句话总结

瞬时故障在 Tool 内重试解决(固定策略、零 Token 成本),逻辑错误由 Agent 通过多次调用解决(LLM 智能判断、有 Token 成本),二者分层协作实现高效可靠的重试机制。


如果一轮tool call返回结果非常多,怎么设计?

原始问法:

  • 如果一轮tool call返回结果非常多,怎么设计?

来源题目:SRC-15-151-460

面试先答

当 Tool Call 返回结果非常多时(如返回上万条数据库记录、超长文本文件),需要 四种策略组合处理:一是 结果分页,Tool 支持分页参数,Agent 按需获取后续页面;二是 摘要截断,Tool 返回前 N 条或摘要信息,由 Agent 判断是否需要更多;三是 按需检索,Tool 提供过滤/搜索接口,Agent 通过精细化查询缩小范围;四是 结果缓存,将大结果存储在外部,仅返回引用 ID,Agent 需要时再按需获取。核心原则是 避免将大结果直接注入 LLM 上下文,通过分层获取和按需访问控制 Token 消耗。

核心结论
  • 大结果处理四策略:分页、摘要截断、按需检索、结果缓存
  • 核心原则:不将大结果直接注入 LLM 上下文
  • 需要 Tool 设计时就考虑结果的分层返回能力
1. 设计方案

策略一:分页返回

// Tool 支持分页参数
ToolResult result = tool.queryOrders(
    QueryRequest.builder()
        .pageSize(20)      // 每页大小
        .pageNum(1)        // 当前页
        .filter("status=paid")
        .build()
);

// Agent 根据结果决定是否翻页
if (result.hasMore()) {
    // 获取下一页
    ToolResult nextPage = tool.queryOrders(result.nextPageRequest());
}

策略二:摘要 + 详情

// Tool 返回摘要和详情引用
ToolResult result = tool.search("订单数据");
// result.getSummary() → "共找到 1,234 条订单,前 20 条已返回"
// result.getTotalCount() → 1234
// result.getDetailRef() → "ref:order_detail_abc" (引用 ID)
// Agent 需要详情时再通过 ref 获取
ToolResult detail = tool.getDetail("ref:order_detail_abc");

策略三:按需检索

// Tool 提供精细化查询接口
ToolResult result = tool.queryOrders(
    QueryRequest.builder()
        .filter("status=paid AND amount>1000")
        .sortBy("create_time", SortOrder.DESC)
        .fields("order_id", "status", "amount")  // 只返回需要的字段
        .build()
);

策略四:结果缓存

// 大结果存储在外部,仅返回引用
ToolResult result = tool.exportData("orders_2024");
// result.getCacheId() → "cache_abc123"
// result.getExpireTime() → "2024-06-02T10:00:00"
// Agent 需要时通过引用 ID 异步获取
2. 完整处理流程
Tool 返回大结果
    ↓
┌─────────────────────┐
│ 1. 判断结果量级      │
│    ├── 小(<1000 Token)→ 直接返回
│    ├── 中(1000-10K)→ 摘要截断
│    └── 大(>10K)→ 分页/缓存
└─────────┬───────────┘
          │
          ▼
┌─────────────────────┐
│ 2. 返回分层结果      │
│    ├── 摘要/前 N 条  │
│    ├── 总数/分页信息 │
│    └── 引用 ID       │
└─────────┬───────────┘
          │
          ▼
┌─────────────────────┐
│ 3. Agent 按需获取    │
│    ├── 需要更多 → 翻页│
│    ├── 需要详情 → 引用│
│    └── 不需要 → 结束  │
└─────────────────────┘
3. 常见追问
  • 追问 1:如何设计 Tool 的结果返回能力?
    • 预留分页参数、支持字段选择、提供摘要模式、结果可缓存
  • 追问 2:缓存的结果怎么清理?
    • 设置过期时间 + 访问统计 + 定期清理机制
一句话总结

大结果处理通过分页、摘要截断、按需检索、结果缓存四策略组合,核心原则是避免将大结果直接注入 LLM 上下文。


Coding agent会有哪些模块?

原始问法:

  • Coding agent会有哪些模块?

来源题目:SRC-15-151-461

面试先答

Coding Agent 通常包含 七大核心模块:一是 需求理解模块,解析用户意图、识别编程任务类型;二是 代码规划模块,将任务分解为编码步骤、选择技术方案;三是 代码生成模块,基于规划生成代码片段或完整文件;四是 代码执行模块,在沙箱中运行代码验证正确性;五是 错误修复模块,分析执行错误并自动修正代码;六是 项目管理模块,管理文件结构、依赖关系、代码风格;七是 交互协作模块,与用户沟通、确认关键决策。这些模块协同工作,形成"理解→规划→生成→执行→修复→交付"的完整编码流水线。

核心结论
  • Coding Agent 七大模块:需求理解、代码规划、代码生成、代码执行、错误修复、项目管理、交互协作
  • 核心流水线:理解→规划→生成→执行→修复→交付
  • 关键能力:自主规划 + 代码生成 + 自动修复 + 项目感知
1. 模块架构
┌─────────────────────────────────────────────────┐
│              Coding Agent 架构                    │
├─────────────────────────────────────────────────┤
│                                                  │
│  ┌─────────────┐   ┌─────────────┐              │
│  │ 需求理解模块  │──→│ 代码规划模块  │              │
│  └─────────────┘   └──────┬──────┘              │
│                           │                      │
│                           ▼                      │
│  ┌─────────────┐   ┌─────────────┐              │
│  │ 项目管理模块  │──→│ 代码生成模块  │              │
│  └─────────────┘   └──────┬──────┘              │
│                           │                      │
│                           ▼                      │
│  ┌─────────────┐   ┌─────────────┐              │
│  │ 错误修复模块  │←──│ 代码执行模块  │              │
│  └─────────────┘   └─────────────┘              │
│                           │                      │
│                           ▼                      │
│                    ┌─────────────┐              │
│                    │ 交互协作模块  │              │
│                    └─────────────┘              │
│                                                  │
└─────────────────────────────────────────────────┘
2. 各模块详解
模块 核心职责 关键技术
需求理解 解析用户意图、识别任务类型 NLU、任务分类、上下文分析
代码规划 分解任务、选择技术方案 任务分解、技术选型、依赖分析
代码生成 生成代码片段/文件 LLM 代码生成、模板引擎、AST 操作
代码执行 编译/运行/测试代码 沙箱环境、构建工具、测试框架
错误修复 分析错误、修正代码 错误分类、LLM 修正、自动补丁
项目管理 文件/依赖/风格管理 文件系统、包管理器、Lint 工具
交互协作 与用户沟通、确认决策 对话界面、确认节点、进度展示
3. 典型工作流
用户:"帮我写一个 Spring Boot 的用户 CRUD 接口"
    ↓
需求理解:识别为 Web API 开发任务
    ↓
代码规划:
  1. 创建 User 实体类
  2. 创建 UserRepository
  3. 创建 UserService
  4. 创建 UserController
  5. 配置数据库连接
  6. 编写单元测试
    ↓
代码生成:按规划逐步生成各文件
    ↓
项目管理:创建文件结构、添加依赖
    ↓
代码执行:编译 + 运行 + 测试
    ↓
错误修复:编译错误 → 修正 → 重新执行
    ↓
交互协作:向用户展示结果、确认是否需要调整
4. 常见追问
  • 追问 1:Coding Agent 如何保证代码风格一致?
    • 通过代码风格配置(如 EditorConfig)+ Lint 工具 + Few-shot 示例引导
  • 追问 2:Coding Agent 能处理多大规模的项目?
    • 取决于上下文窗口和规划能力,当前适合单文件/单模块级别,大规模项目需多 Agent 协作
一句话总结

Coding Agent 由需求理解、代码规划、代码生成、代码执行、错误修复、项目管理、交互协作七大模块组成,形成完整的自动化编码流水线。


多个agents怎么相互协作?

原始问法:

  • 多个agents怎么相互协作?

来源题目:SRC-15-151-462

面试先答

多个 Agent 协作主要有 三种模式编排模式(Orchestration)对话模式(Conversation)层级模式(Hierarchy)。编排模式由一个主控 Agent 分配任务给子 Agent,适合流程明确的场景;对话模式中 Agent 之间平等对话、协商解决问题,适合开放域讨论;层级模式建立上下级关系,上级 Agent 监督下级 Agent 执行。实现关键包括 Agent 间通信协议(消息传递、共享黑板)、任务分配策略(按能力/负载分配)、冲突解决机制(投票、优先级、仲裁)。典型案例是 AutoGen 的多 Agent 对话框架和 MetaGPT 的角色扮演协作。

核心结论
  • 多 Agent 协作三模式:编排(主控分配)、对话(平等协商)、层级(上下级管理)
  • 关键技术:通信协议、任务分配、冲突解决
  • 适用场景:复杂项目、多领域协作、并行处理
1. 三种协作模式
模式 架构 适用场景 代表框架
编排模式 主控 Agent → 子 Agent 流程明确的任务 LangGraph、CrewAI
对话模式 Agent ↔ Agent 平等对话 开放域讨论、协商 AutoGen、ChatDev
层级模式 上级 Agent → 下级 Agent 大项目分级管理 MetaGPT、CAMEL
2. 编排模式详解
┌─────────────────────────────────┐
│        Orchestrator Agent        │
│  (主控:任务分配、结果汇总)      │
└────────┬──────────┬──────────────┘
         │          │
    ┌────▼───┐  ┌───▼────┐  ┌────────┐
    │Agent A │  │Agent B │  │Agent C │
    │(前端)  │  │(后端)  │  │(测试)  │
    └────────┘  └────────┘  └────────┘
         │          │          │
         └──────────┴──────────┘
                    │
              结果汇总
3. 对话模式详解
┌──────────┐ 提议  ┌──────────┐
│ Agent A  │──────→│ Agent B  │
│(产品经理)│       │(架构师)  │
└────┬─────┘       └────┬─────┘
     │                  │
     │  ←──── 反对 ────│
     │                  │
     ▼                  ▼
┌──────────┐ 协商  ┌──────────┐
│ Agent C  │──────→│ Agent D  │
│(开发者)  │       │(测试)    │
└──────────┘       └──────────┘
     │                  │
     └──────── 达成共识 ────────┘
4. 通信机制

方案一:消息传递

agentA.sendMessage(agentB,
    Message.builder()
        .type(MessageType.TASK_REQUEST)
        .content("请实现用户认证接口")
        .build()
);

方案二:共享黑板

// 所有 Agent 共享一个知识黑板
blackboard.post("task_board",
    TaskBoard.builder()
        .addTask("auth_api", Status.IN_PROGRESS)
        .addResult("auth_api", "完成 JWT 认证实现")
        .build()
);
// Agent 从黑板读取其他 Agent 的进度
5. 冲突解决
冲突类型 解决策略
资源竞争 优先级分配 + 队列调度
意见分歧 投票表决 + 仲裁机制
任务重叠 去重检测 + 任务拆分
结果不一致 交叉验证 + 人工介入
6. 常见追问
  • 追问 1:多 Agent 协作的效率如何?
    • 并行处理可提升效率,但通信开销和协调成本也会增加
  • 追问 2:如何避免多 Agent 之间的冲突?
    • 通过明确的角色定义、任务边界、冲突解决机制组合保障
一句话总结

多 Agent 通过编排、对话、层级三种模式协作,核心技术是通信协议、任务分配和冲突解决,适用于复杂项目的并行开发和多领域协作。

Agent间通信和状态管理怎么设计?

原始问法:

  • Agent间通信和状态管理怎么设计?

来源题目:SRC-15-151-463

面试先答

多 Agent 系统的通信和状态管理需要 一套完整的设计方案。通信层面主要有两种模式:消息传递(Agent 间直接发送结构化消息,支持异步和同步)和 共享黑板(所有 Agent 读写同一个共享状态空间,实现间接通信)。状态管理层面需要解决 状态持久化(会话/任务状态存储)、状态同步(多 Agent 间状态一致性)、状态隔离(不同任务/会话的状态隔离)三个核心问题。推荐的设计是:用消息传递做实时通信、用共享黑板做状态共享、用 Redis/数据库做状态持久化,三者组合使用。

核心结论
  • 通信模式:消息传递(直接通信)+ 共享黑板(间接通信)
  • 状态管理三要素:持久化、同步、隔离
  • 推荐组合:消息传递 + 共享黑板 + 持久化存储
1. 通信设计

消息传递模式

// Agent 间直接消息传递
MessageBus messageBus = new MessageBus();

// 发送消息
agent.send(Message.builder()
    .id(UUID.randomUUID().toString())
    .from("agent-001")
    .to("agent-002")
    .type(MessageType.TASK_RESULT)
    .content("用户模块开发完成")
    .payload(Map.of("files", List.of("User.java", "UserController.java")))
    .timestamp(LocalDateTime.now())
    .build());

// 接收消息
messageBus.subscribe("agent-002", message -> {
    if (message.getType() == MessageType.TASK_RESULT) {
        // 处理任务结果
    }
});

共享黑板模式

// 所有 Agent 共享的状态空间
Blackboard blackboard = new Blackboard();

// Agent A 写入状态
blackboard.write("task:user-module",
    TaskState.builder()
        .status(Status.IN_PROGRESS)
        .progress(0.6)
        .artifacts(List.of("User.java"))
        .updatedAt(LocalDateTime.now())
        .build()
);

// Agent B 读取状态
TaskState state = blackboard.read("task:user-module");
if (state.getProgress() > 0.5) {
    // 开始测试准备
}

两种模式对比

维度 消息传递 共享黑板
通信方式 点对点直接通信 读写间接通信
实时性 高(实时推送) 中(需轮询或通知)
可追溯性 需记录消息 天然可追溯
复杂度 高(需寻址和路由) 低(统一入口)
适用场景 实时协作、指令传递 状态共享、进度同步
2. 状态管理设计

状态持久化

// 使用 Redis 持久化 Agent 状态
StateStore stateStore = RedisStateStore.builder()
    .host("localhost")
    .port(6379)
    .keyPrefix("agent:state:")
    .ttl(24, TimeUnit.HOURS)
    .build();

// 保存状态
stateStore.save("session-001", AgentState.builder()
    .sessionId("session-001")
    .currentTask("用户认证模块")
    .completedTasks(List.of("实体创建", "Repository"))
    .context(Map.of("tech_stack", "Spring Boot"))
    .build()
);

// 恢复状态
AgentState state = stateStore.load("session-001");

状态同步

// 状态变更通知机制
public class StateSyncManager {
    private final List<StateListener> listeners = new CopyOnWriteArrayList<>();

    public void registerListener(String agentId, StateListener listener) {
        listeners.add(listener);
    }

    public void broadcast(StateChangeEvent event) {
        // 通知所有相关 Agent
        listeners.stream()
            .filter(l -> l.isInterestedIn(event.getChannel()))
            .forEach(l -> l.onStateChange(event));
    }
}

状态隔离

// 按 session_id 隔离状态
public class IsolatedStateManager {
    private final Map<String, AgentState> sessionStates = new ConcurrentHashMap<>();

    public AgentState getState(String sessionId) {
        return sessionStates.computeIfAbsent(sessionId,
            id -> AgentState.init(id)
        );
    }

    public void updateState(String sessionId, AgentState state) {
        sessionStates.put(sessionId, state);
    }

    public void cleanExpired() {
        // 清理过期会话
    }
}
3. 完整架构
┌─────────────────────────────────────────────────────┐
│                 通信与状态管理架构                    │
├─────────────────────────────────────────────────────┤
│                                                     │
│  ┌──────────────┐     ┌──────────────┐             │
│  │  Agent A     │     │  Agent B     │             │
│  └──────┬───────┘     └──────┬───────┘             │
│         │                    │                     │
│         ▼                    ▼                     │
│  ┌──────────────────────────────────────┐          │
│  │           消息传递层(Message Bus)    │          │
│  │  点对点通信 / 广播 / 订阅              │          │
│  └──────────────────────────────────────┘          │
│         │                    │                     │
│         ▼                    ▼                     │
│  ┌──────────────────────────────────────┐          │
│  │           共享黑板层(Blackboard)     │          │
│  │  状态共享 / 进度同步 / 结果汇总        │          │
│  └──────────────────────────────────────┘          │
│         │                    │                     │
│         ▼                    ▼                     │
│  ┌──────────────────────────────────────┐          │
│  │           状态持久层(State Store)    │          │
│  │  Redis / DB / 文件存储                │          │
│  └──────────────────────────────────────┘          │
│                                                     │
└─────────────────────────────────────────────────────┘
4. 常见追问
  • 追问 1:如何处理状态冲突?
    • 通过乐观锁(版本号)、分布式锁、状态合并策略解决
  • 追问 2:Agent 崩溃后如何恢复状态?
    • 从持久化存储(Redis/DB)加载状态,恢复到崩溃前的检查点
一句话总结

多 Agent 通信采用消息传递+共享黑板组合模式,状态管理通过持久化、同步、隔离三要素保障,形成可靠的协同工作体系。


Agent效果评估怎么做?

原始问法:

  • Agent效果评估怎么做?

来源题目:SRC-15-151-464

面试先答

Agent 效果评估需要 从五个维度构建完整的评估体系任务完成度(Agent 是否成功完成用户目标,最核心指标)、过程质量(决策路径是否合理、工具调用是否正确、步数是否最优)、响应质量(生成内容的准确性、相关性、完整性)、效率指标(Token 消耗、执行时间、成本)、安全指标(是否产生有害输出、是否违规操作)。评估方法结合 自动评估(定义评估指标和规则,批量执行评估用例)和 人工评估(对开放性任务进行人工打分)。常用评估框架包括 LangSmith、Langfuse、ARES 等。

核心结论
  • Agent 评估五维度:任务完成度、过程质量、响应质量、效率指标、安全指标
  • 评估方法:自动评估 + 人工评估结合
  • 关键挑战:开放性任务的"完成"定义模糊、过程质量难以量化
1. 评估维度体系
维度 指标 说明
任务完成度 成功率、目标达成率 是否完成用户指定的任务
过程质量 工具调用准确率、步数合理性、决策正确性 执行过程是否高效正确
响应质量 答案准确性、信息相关性、格式规范性 最终输出的质量
效率指标 Token 消耗、执行时间、API 调用次数 成本和效率
安全指标 违规操作数、有害输出率、权限控制合规性 安全性
2. 核心评估指标详解

任务完成度(最核心)

// 评估 Agent 是否完成任务
public class TaskCompletionEvaluator {

    public EvaluationResult evaluate(AgentTask task, AgentResult result) {
        boolean completed = checkTaskCompletion(task, result);
        List<String> missingSteps = findMissingSteps(task, result);
        List<String> errors = findErrors(result);

        return EvaluationResult.builder()
            .taskId(task.getId())
            .completed(completed)
            .completionRate(calculateCompletionRate(task, result))
            .missingSteps(missingSteps)
            .errors(errors)
            .score(calculateScore(completed, missingSteps, errors))
            .build();
    }

    private double calculateScore(boolean completed,
            List<String> missing, List<String> errors) {
        double score = completed ? 1.0 : 0.5;
        score -= missing.size() * 0.1;
        score -= errors.size() * 0.15;
        return Math.max(0, score);
    }
}

过程质量评估

public class ProcessQualityEvaluator {

    public ProcessEvaluation evaluate(List<AgentStep> steps) {
        int correctToolCalls = countCorrectToolCalls(steps);
        int totalToolCalls = steps.size();
        double toolAccuracy = totalToolCalls > 0
            ? (double) correctToolCalls / totalToolCalls
            : 0.0;

        int optimalSteps = calculateOptimalSteps(steps);
        int actualSteps = steps.size();
        double stepEfficiency = optimalSteps > 0
            ? (double) optimalSteps / actualSteps
            : 1.0;

        return ProcessEvaluation.builder()
            .toolCallAccuracy(toolAccuracy)
            .stepEfficiency(stepEfficiency)
            .decisionQuality(evaluateDecisions(steps))
            .build();
    }
}

效率指标评估

public class EfficiencyEvaluator {

    public EfficiencyMetrics evaluate(AgentExecution execution) {
        return EfficiencyMetrics.builder()
            .totalTokens(execution.getTotalTokens())
            .inputTokens(execution.getInputTokens())
            .outputTokens(execution.getOutputTokens())
            .toolResultTokens(execution.getToolResultTokens())
            .executionTimeMs(execution.getDuration().toMillis())
            .toolCallCount(execution.getToolCalls().size())
            .estimatedCost(calculateCost(execution))
            .build();
    }
}
3. 评估方法

自动评估

  • 定义评估用例(输入 + 预期输出/行为)
  • 设定评估规则(关键词匹配、格式校验、API 调用验证)
  • 批量执行并统计指标
  • 适用于:结构化任务(如 API 调用、数据查询)

人工评估

  • 对 Agent 输出进行人工打分
  • 评估维度:有用性、准确性、完整性、创造性
  • 使用打分标准(1-5 分)
  • 适用于:开放性任务(如写作、分析)

混合评估

自动评估(量化指标)
    ↓
人工评估(质量判断)
    ↓
加权汇总 → 综合评分
4. 评估框架
框架 特点 适用场景
LangSmith LangChain 官方,深度集成 LangChain 项目
Langfuse 开源、多框架支持 多框架混合项目
ARES 专注 LLM 评估 LLM 输出质量评估
Ragas RAG 专用评估 RAG 系统评估
自建 定制化评估 特殊业务需求
5. 评估流程
1. 定义评估用例集
    ├── 覆盖典型场景(60%)
    ├── 覆盖边界场景(30%)
    └── 覆盖异常场景(10%)
    ↓
2. 批量执行 Agent
    ↓
3. 自动评估
    ├── 检查任务完成度
    ├── 统计效率指标
    ├── 检测安全问题
    ↓
4. 人工评估
    ├── 抽样评估质量
    ├── 开放性问题打分
    ↓
5. 汇总报告
    ├── 整体评分
    ├── 各维度得分
    ├── 改进建议
6. 常见追问
  • 追问 1:开放性任务如何评估完成度?
    • 通过多维度验证:结果合理性、用户满意度、关键要素覆盖度
  • 追问 2:如何构建评估用例集?
    • 从真实用户需求出发,覆盖典型/边界/异常场景,定期更新
7. 易错点
  • ❌ 错误:Agent 评估只看结果
  • ✅ 正确:需要同时评估结果、过程、效率、安全
  • ❌ 错误:评估只靠人工
  • ✅ 正确:自动评估量化指标 + 人工评估质量判断相结合
一句话总结

Agent 效果评估从任务完成度、过程质量、响应质量、效率指标、安全指标五维度展开,通过自动量化评估和人工质量评估结合构建完整的评估体系。

RAG和传统搜索的区别是什么?

原始问法:

  • RAG和传统搜索的区别是什么?

来源题目:SRC-15-152-465

面试先答

RAG(Retrieval-Augmented Generation,检索增强生成)与传统搜索的核心区别在于 "搜索"和"生成"的融合方式。传统搜索(如 Elasticsearch、Google Search)基于关键词匹配返回文档列表,由用户自行阅读理解;RAG 则是先检索相关文档,再将检索结果作为上下文提交给大模型,由模型 基于检索内容直接生成答案。本质区别有三:一是传统搜索返回的是"文档",RAG 返回的是"答案";二是传统搜索依赖关键词精确匹配,RAG 用语义向量进行相似度匹配,能理解自然语言的深层含义;三是传统搜索不经过大模型,RAG 将检索与生成深度融合,答案更精准、更符合上下文需求。

核心结论
  • RAG = 检索(Retrieval)+ 生成(Generation),检索为生成提供上下文
  • 传统搜索返回文档列表,RAG 返回基于检索内容的生成答案
  • RAG 用语义向量检索替代关键词匹配,理解能力更强
1. 对比分析
维度 传统搜索 RAG
核心流程 关键词匹配 → 返回文档列表 语义检索 → 大模型生成答案
检索方式 倒排索引、BM25、关键词匹配 向量嵌入、余弦相似度、混合检索
输出形式 文档列表(需用户阅读) 直接答案(已整合总结)
语义理解 依赖关键词精确匹配 理解自然语言深层含义
可解释性 返回原文,用户自行判断 引用来源,可追溯
适用场景 文档查找、关键词定位 知识问答、智能客服、报告生成
2. 流程对比

传统搜索流程

用户输入 → 关键词提取 → 倒排索引匹配 → 返回文档列表 → 用户阅读

RAG 流程

用户问题 → 向量化 → 语义检索 → 返回相关文档
    → 将文档+问题提交给 LLM → LLM 生成答案 + 引用来源
3. RAG 的核心优势
用户问:"如何配置 Spring Boot 的数据库连接池?"

传统搜索:
  → 返回 10 篇文档("Spring Boot HikariCP 配置"、"数据库连接池详解"...)
  → 用户需逐一阅读找到答案

RAG:
  → 检索到最相关的 3 段内容
  → 大模型生成:
    "Spring Boot 默认使用 HikariCP 作为连接池,配置方式如下:
     在 application.yml 中设置 spring.datasource.hikari.maximum-pool-size=10...
     (引用:Spring Boot 官方文档第 8.3 节)"
4. 常见追问
  • 追问 1:RAG 能否完全替代传统搜索?
    • 不能。RAG 适合问答场景,传统搜索在文档查找、关键词定位等场景仍有优势
  • 追问 2:RAG 的检索和生成哪个更重要?
    • 同等重要。检索质量决定了答案的"证据"质量,生成质量决定了答案的"表达"质量
一句话总结

RAG 将语义检索和大模型生成深度融合,用"检索+生成"的方式直接给出答案,而传统搜索仅返回文档列表供用户自行查找。


为什么不直接用关键词检索?

原始问法:

  • 为什么不直接用关键词检索?

来源题目:SRC-15-152-466

面试先答

关键词检索(如 BM25、倒排索引)在精确匹配场景下表现优秀,但在 大模型问答场景下存在四大不足:一是 语义鸿沟问题,用户的自然语言提问可能用了与文档不同的表达方式,关键词匹配无法命中;二是 召回不完整,同义词、近义词、相关概念的文档无法被检索到;三是 排序不合理,仅靠词频排序无法衡量文档与问题的实际语义相关性;四是 无理解能力,关键词检索不理解文档内容,无法将检索结果整合成答案。在 RAG 场景下,语义检索通过向量嵌入将文本映射到高维空间,通过向量相似度匹配实现语义级别的召回,有效解决了这些问题。

核心结论
  • 关键词检索在语义理解、召回完整性、排序合理性上存在不足
  • 语义检索(向量检索)通过嵌入映射解决了语义鸿沟问题
  • RAG 场景下关键词检索应与语义检索结合使用(混合检索)
1. 关键词检索的局限

局限一:语义鸿沟

用户问:"如何优化 Java 程序的启动速度?"

关键词检索匹配的文档:
  ✅ "Java 启动性能调优"(精确匹配)
  ❌ "Spring Boot 容器初始化优化"(相关但关键词不同)
  ❌ "JVM 类加载机制详解"(相关但关键词不同)
  ❌ "减少 Bean 初始化时间"(相关但表达方式不同)

局限二:同义词问题

用户问:"分布式锁的实现方式?"

关键词检索可能遗漏:
  ❌ "分布式互斥锁"(同义词"互斥锁"≠"锁")
  ❌ "Redis SETNX 实现并发控制"(用"并发控制"代替"锁")
  ❌ "Zookeeper 临时节点原理"(技术方案名称不含"锁")

局限三:排序问题

查询:"Redis 缓存穿透解决方案"

排序靠前的文档:
  1. "Redis 缓存穿透、击穿、雪崩详解"(词频高,但内容不聚焦)
  2. "布隆过滤器实战"(词频低,但恰好是解决方案)
  → 用户可能错过最相关的文档
2. 语义检索如何解决
语义检索通过 Embedding 将文本映射到高维向量:

"如何优化 Java 程序的启动速度?" → 向量 A
"Spring Boot 容器初始化优化" → 向量 B(与 A 余弦相似度高)
"JVM 类加载机制详解" → 向量 C(与 A 余弦相似度中等)
"减少 Bean 初始化时间" → 向量 D(与 A 余弦相似度高)

通过向量相似度计算,即使关键词不同,语义相近的文档也能被召回
3. 最佳实践:混合检索
// 混合检索:关键词 + 语义
public List<Document> hybridSearch(String query) {
    // 关键词检索(保留精确匹配能力)
    List<ScoredDoc> keywordResults = keywordSearch(query, topK);

    // 语义检索(补充语义召回)
    List<ScoredDoc> semanticResults = semanticSearch(query, topK);

    // 融合排序(RRF 或加权融合)
    return reciprocalRankFusion(keywordResults, semanticResults);
}
4. 常见追问
  • 追问 1:那关键词检索还有用吗?
    • 有用。在精确匹配、专有名词、代码片段等场景仍是首选,应与语义检索结合
  • 追问 2:如何评估检索质量?
    • 使用 Recall@K、MRR、NDCG 等指标,结合人工标注评估
一句话总结

关键词检索无法解决语义鸿沟、同义词和排序问题,在 RAG 场景下应使用语义检索或混合检索来提升召回质量。


RAG混合检索策略怎么做?

原始问法:

  • RAG混合检索策略怎么做?

来源题目:SRC-15-152-467

面试先答

RAG 混合检索是将 关键词检索(精确匹配)和语义检索(向量相似度)相结合 的检索策略,目的是兼取两者之长。具体实现有三种方式:串行融合(先关键词后语义或反之)、并行融合(两路检索同时进行,结果合并排序)和 加权融合(为不同检索方式分配权重)。常用的融合算法包括 RRF(Reciprocal Rank Fusion)Combined Score学习排序(LTR)。最佳实践是先用 Dense Retrieval(语义)做主要召回,用 BM25(关键词)补充精确匹配,通过 RRF 合并结果。

核心结论
  • 混合检索 = 关键词检索 + 语义检索,互补优势
  • 融合策略:串行、并行、加权
  • 推荐算法:RRF(Reciprocal Rank Fusion)
1. 三种融合方式

串行融合

用户查询
    ↓
关键词检索(精确匹配)
    ↓ 未命中
语义检索(补充召回)
    ↓
返回结果

并行融合

用户查询
    ↓
┌──────────┬──────────┐
│          │          │
▼          ▼          ▼
关键词    语义     元数据过滤
检索      检索      (时间/权限)
│          │          │
└──────────┴──────────┘
    ↓
  融合排序
    ↓
  返回结果

加权融合

// RRF 融合算法
public double rrfScore(int rank, double k) {
    return 1.0 / (k + rank);
}

public List<Document> hybridSearch(String query, double keywordWeight, double semanticWeight) {
    List<ScoredDoc> keywordResults = keywordSearch(query);
    List<ScoredDoc> semanticResults = semanticSearch(query);

    // RRF 融合
    Map<String, Double> scores = new HashMap<>();
    for (int i = 0; i < keywordResults.size(); i++) {
        String docId = keywordResults.get(i).getId();
        scores.merge(docId, rrfScore(i + 1, 60) * keywordWeight, Double::sum);
    }
    for (int i = 0; i < semanticResults.size(); i++) {
        String docId = semanticResults.get(i).getId();
        scores.merge(docId, rrfScore(i + 1, 60) * semanticWeight, Double::sum);
    }

    return scores.entrySet().stream()
        .sorted(Map.Entry.<String, Double>comparingByValue().reversed())
        .map(e -> getDocument(e.getKey()))
        .collect(Collectors.toList());
}
2. 融合算法对比
算法 原理 优点 缺点
RRF 排名倒数求和 简单有效,无需训练 不考虑原始分数
Combined Score 分数加权求和 利用原始分数信息 需分数归一化
LTR 机器学习排序 最精确 需要训练数据
CSS 条件概率融合 理论完善 实现复杂
3. 调优策略
  • 权重调整:根据场景调整关键词和语义的权重(如法律文本侧重关键词,客服对话侧重语义)
  • 动态切换:简单查询用关键词,复杂查询用语义,混合查询用融合
  • 多粒度检索:同时检索段落级和文档级粒度,提供更精准的上下文
4. 常见追问
  • 追问 1:RRF 中的 k 参数如何选择?
    • 通常取 60(Cormac 等人论文推荐),可根据数据集微调
  • 追问 2:混合检索一定比纯语义好吗?
    • 不一定。在强语义匹配场景(如概念定义),纯语义可能更优;在精确匹配场景(如报错信息),关键词更优
一句话总结

混合检索通过并行或串行方式融合关键词检索和语义检索的结果,结合 RRF 等融合算法兼取精确匹配和语义理解的优势。


RAG召回效果如何评估?

原始问法:

  • RAG召回效果如何评估?

来源题目:SRC-15-152-468

面试先答

RAG 召回效果评估需要 从检索质量和生成质量两个层面 进行。检索质量的核心指标包括 Recall@K(前 K 个结果中命中相关文档的比例,最关键的召回指标)、Precision@K(前 K 个结果中相关文档的比例)、MRR(第一个相关文档的排名倒数均值)、NDCG(考虑多相关性等级的排序质量)。生成质量的评估关注答案的准确性、完整性和引用正确性。评估方法是:先准备标注好的"查询-相关文档"数据集,运行检索系统获取结果,计算上述指标,与基线方法对比。

核心结论
  • 检索质量指标:Recall@K、Precision@K、MRR、NDCG
  • 生成质量指标:答案准确性、完整性、引用正确性
  • 评估流程:标注数据集 → 运行检索 → 计算指标 → 对比分析
1. 检索质量指标
指标 公式 含义 关注点
Recall@K |相关文档∩TopK| / |相关文档| 召回率 不漏文档
Precision@K |相关文档∩TopK| / K 精确率 少错文档
MRR 1/|Q| × Σ 1/rank_i 平均倒数排名 首个相关文档位置
NDCG@K DCG/IDCG 归一化折损累计增益 整体排序质量
2. 评估实现
public class RetrievalEvaluator {

    public EvaluationResult evaluate(List<Query> queries,
            RetrievalSystem system) {

        double totalRecall = 0;
        double totalPrecision = 0;
        double totalMRR = 0;

        for (Query query : queries) {
            List<Document> results = system.search(query.getText(), 10);
            Set<String> relevantIds = query.getRelevantDocIds();

            int relevantFound = 0;
            for (int i = 0; i < results.size(); i++) {
                if (relevantIds.contains(results.get(i).getId())) {
                    relevantFound++;
                    totalMRR += 1.0 / (i + 1);
                }
            }

            totalRecall += (double) relevantFound / relevantIds.size();
            totalPrecision += (double) relevantFound / results.size();
        }

        int n = queries.size();
        return EvaluationResult.builder()
            .recallAtK(totalRecall / n)
            .precisionAtK(totalPrecision / n)
            .mrr(totalMRR / n)
            .build();
    }
}
3. 生成质量评估
维度 评估方法 说明
答案准确性 人工打分 + 自动评估 答案是否正确
答案完整性 关键点覆盖率 是否覆盖所有要点
引用正确性 引用文档是否相关且正确 可追溯性
幻觉检测 判断是否有超出检索内容的生成 忠实度
4. 评估流程
Step 1: 准备标注数据集
  ├── 查询集合(覆盖典型/边界/异常场景)
  ├── 每个查询的相关文档标注
  └── 每个查询的标准答案(可选)
    ↓
Step 2: 运行检索系统
  ├── 执行所有查询
  ├── 记录 TopK 结果
  └── 保存检索日志
    ↓
Step 3: 计算检索指标
  ├── Recall@K、Precision@K
  ├── MRR、NDCG
  └── 与基线方法对比
    ↓
Step 4: 评估生成质量
  ├── 答案准确性打分
  ├── 引用正确性检查
  └── 幻觉检测
    ↓
Step 5: 生成评估报告
5. 常见追问
  • 追问 1:没有标注数据怎么办?
    • 使用 LLM 自动标注、人工标注小样本后扩展、使用弱监督方法
  • 追问 2:Recall@K 和 Precision@K 如何取舍?
    • 取决于场景:问答系统侧重 Recall(宁可多召回),搜索侧重 Precision
一句话总结

RAG 召回效果通过 Recall@K、Precision@K、MRR、NDCG 等检索指标和答案准确性等生成指标进行系统化评估,核心是构建标注数据集并运行量化指标计算。


Chunk切片粒度太大/太小有什么问题?

原始问法:

  • Chunk切片粒度太大/太小有什么问题?

来源题目:SRC-15-152-469

面试先答

Chunk 切片粒度的选择直接影响 RAG 系统的检索质量和答案质量。粒度过大会导致:一是检索精度下降,大 Chunk 包含过多无关内容,稀释了核心语义;二是上下文浪费,每个 Chunk 占用大量 Token,限制了返回的文档数量;三是噪声干扰,大段无关信息干扰大模型生成答案。粒度过小会导致:一是语义不完整,一个完整的语义单元被切碎,检索到的片段缺乏上下文;二是召回困难,小片段的向量表示不稳定,相似度匹配不准确;三是拼接成本,需要额外逻辑将多个小片段拼接。理想的 Chunk 粒度是 语义完整+长度适中,通常 200-500 Token。

核心结论
  • Chunk 粒度需平衡语义完整性和检索精度
  • 过大:语义稀释、Token 浪费、噪声干扰
  • 过小:语义破碎、召回不准、拼接困难
  • 理想粒度:200-500 Token,保持语义单元完整
1. 粒度影响分析
粒度 检索精度 语义完整 Token 效率 典型问题
过大(>1000 Token) 噪声干扰、上下文浪费
适中(200-500 Token) 需按语义边界切分
过小(<100 Token) 语义破碎、召回不准
2. 切片策略

固定长度切片

// 简单但可能切断语义
public List<Chunk> fixedChunk(String text, int chunkSize, int overlap) {
    List<Chunk> chunks = new ArrayList<>();
    for (int i = 0; i < text.length(); i += chunkSize - overlap) {
        int end = Math.min(i + chunkSize, text.length());
        chunks.add(new Chunk(text.substring(i, end)));
    }
    return chunks;
}

语义切片(推荐)

public List<Chunk> semanticChunk(String text) {
    List<Chunk> chunks = new ArrayList<>();
    List<String> paragraphs = splitByParagraphs(text);

    StringBuilder currentChunk = new StringBuilder();
    for (String para : paragraphs) {
        if (currentChunk.length() + para.length() > MAX_CHUNK_SIZE) {
            chunks.add(new Chunk(currentChunk.toString()));
            currentChunk = new StringBuilder(para);
        } else {
            currentChunk.append(para);
        }
    }
    if (currentChunk.length() > 0) {
        chunks.add(new Chunk(currentChunk.toString()));
    }
    return chunks;
}

层级切片

文档 → 章节 → 段落 → 句子
(按文档组织结构分层切片,检索时可选择粒度)
3. 常见追问
  • 追问 1:代码文件如何切片?
    • 按函数/类切片,保持代码的语法完整性
  • 追问 2:表格数据如何切片?
    • 按行切片或将表格转为文本描述后切片
一句话总结

Chunk 切片需在语义完整性和检索精度间取得平衡,推荐 200-500 Token 的语义切片,过大会引入噪声,过小会破坏语义。


RAG系统文档频繁更新,向量索引与原文不一致怎么办?

原始问法:

  • RAG系统文档频繁更新,向量索引与原文不一致怎么办?

来源题目:SRC-15-152-470

面试先答

RAG 系统中向量索引与原文不一致是常见问题,特别是在文档频繁更新的场景下。解决方案是 建立索引与原文的联动更新机制,主要包括三种策略:同步更新(文档变更时立即触发向量重新嵌入和索引更新)、异步更新(通过消息队列或定时任务批量更新)、增量更新(只更新变更的 Chunk,而非全量重建)。关键设计包括:为每个 Chunk 维护版本号/哈希值用于变更检测、使用消息队列解耦文档变更与索引更新、设置定期全量校验任务。同时需要考虑最终一致性,允许短暂的不一致窗口。

核心结论
  • 三种更新策略:同步、异步、增量
  • 关键机制:版本号检测、消息队列解耦、定期校验
  • 设计原则:最终一致性优先,避免强一致性的性能代价
1. 更新策略对比
策略 实现 优点 缺点 适用场景
同步更新 文档变更时立即更新索引 实时一致 影响性能 低频更新场景
异步更新 消息队列/定时任务 解耦、高性能 短暂不一致 中高频更新
增量更新 只更新变更 Chunk 高效 实现复杂 高频更新
2. 系统设计
文档变更 → 版本号/哈希变更检测
    ↓
┌─────────────────────────────┐
│ 变更检测层                   │
│ 对比文档版本号/哈希值        │
└───────────┬─────────────────┘
            │
            ▼
┌─────────────────────────────┐
│ 更新调度层                   │
│ 同步/异步/增量策略选择       │
└───────────┬─────────────────┘
            │
            ▼
┌─────────────────────────────┐
│ 索引更新层                   │
│ 重新嵌入 + 向量索引更新      │
└───────────┬─────────────────┘
            │
            ▼
┌─────────────────────────────┐
│ 校验层(定期全量校验)       │
│ 比对原文与索引的版本一致性   │
└─────────────────────────────┘
3. 实现代码
public class IndexSyncService {

    private final DocumentStore documentStore;
    private final VectorStore vectorStore;
    private final EmbeddingService embeddingService;
    private final MessageQueue messageQueue;

    // 增量更新
    public void onDocumentChanged(String docId) {
        Document doc = documentStore.get(docId);
        String newHash = doc.calculateHash();

        // 检测变更
        if (doc.getLastIndexedHash().equals(newHash)) {
            return; // 无变更
        }

        // 找出变更的 Chunk
        List<Chunk> changedChunks = findChangedChunks(doc);

        // 更新变更的 Chunk
        for (Chunk chunk : changedChunks) {
            float[] embedding = embeddingService.embed(chunk.getText());
            vectorStore.upsert(
                docId + ":" + chunk.getId(),
                embedding,
                chunk.getMetadata()
            );
        }

        // 更新版本号
        doc.setLastIndexedHash(newHash);
        documentStore.update(doc);
    }

    // 异步更新(通过消息队列)
    public void asyncSync(String docId) {
        messageQueue.publish("doc-changed", docId);
    }

    // 消费者
    @SqsListener("doc-changed")
    public void onMessage(String docId) {
        onDocumentChanged(docId);
    }
}
4. 常见追问
  • 追问 1:如何处理删除的文档?
    • 向量索引中标记为已删除或物理删除,检索时过滤
  • 追问 2:如何保证不丢更新?
    • 使用 At-Least-Once 投递 + 幂等消费 + 定期全量校验
一句话总结

通过变更检测、增量更新、消息队列解耦和定期校验的组合机制,实现向量索引与原文的最终一致性。


混合检索时,如果关键词匹配但语义不匹配怎么办?

原始问法:

  • 混合检索时,如果关键词匹配但语义不匹配怎么办?

来源题目:SRC-15-152-471

面试先答

混合检索中关键词匹配但语义不匹配的情况很常见,比如搜索"苹果手机"时,关键词检索到了"苹果公司财务报告"。解决方案是 在融合阶段引入语义相关性过滤和重排序。具体做法:一是 语义过滤,对关键词检索结果计算语义相似度,低于阈值的直接丢弃;二是 重排序(Rerank),用交叉编码器(Cross-Encoder)对融合结果进行精排,同时考虑关键词匹配度和语义相似度;三是 加权降权,对关键词匹配但语义低的结果给予较低权重。核心思想是让语义相关性成为排序的主导因素,而非仅仅关键词匹配。

核心结论
  • 关键词匹配但语义不匹配是混合检索的常见问题
  • 解决方案:语义过滤 + 重排序 + 加权降权
  • 推荐使用 Cross-Encoder 做精排,效果最好
1. 解决方案

方案一:语义过滤

public List<ScoredDoc> semanticFilter(String query,
        List<ScoredDoc> keywordResults, double threshold) {
    float[] queryEmbedding = embeddingService.embed(query);

    return keywordResults.stream()
        .filter(doc -> {
            float[] docEmbedding = embeddingService.embed(doc.getText());
            double similarity = cosineSimilarity(queryEmbedding, docEmbedding);
            return similarity > threshold;
        })
        .collect(Collectors.toList());
}

方案二:Rerank(推荐)

public List<ScoredDoc> rerank(String query, List<ScoredDoc> candidates) {
    // Cross-Encoder 同时考虑查询和文档的交互特征
    List<RerankedResult> reranked = crossEncoder.rerank(query, candidates);

    return reranked.stream()
        .sorted(Comparator.comparingDouble(RerankedResult::getScore).reversed())
        .map(r -> candidates.get(r.getOriginalIndex()))
        .collect(Collectors.toList());
}

方案三:融合降权

public double fusedScore(double keywordScore, double semanticScore,
        double semanticWeight) {
    // 语义相关性低时,整体分数大幅降低
    double semanticFactor = semanticScore > 0.5 ? 1.0 : 0.3;
    return keywordScore * semanticFactor * semanticWeight;
}
2. 流程优化
并行检索
├── 关键词检索 → TopK 结果
├── 语义检索 → TopK 结果
│
└── 融合
    ↓
语义过滤(去除语义不相关的关键词结果)
    ↓
Rerank(Cross-Encoder 精排)
    ↓
返回最终结果
3. 常见追问
  • 追问 1:Cross-Encoder 为什么效果更好?
    • 它同时处理 query 和 document 的交互特征,能捕捉更细粒度的相关性
  • 追问 2:Rerank 的性能如何?
    • 比向量检索慢,通常只对融合后的 TopN(如 50-100)进行精排
一句话总结

通过语义过滤、Cross-Encoder 重排序和加权降权,让语义相关性成为最终排序的主导因素,有效处理关键词匹配但语义不匹配的问题。


RAG效果优化有哪些方法?

原始问法:

  • RAG效果优化有哪些方法?

来源题目:SRC-15-152-472

面试先答

RAG 效果优化可以从 检索、增强、生成、评估 四个环节入手。检索层面:优化 Chunk 切片策略、改进嵌入模型、使用混合检索、引入 Rerank 精排;增强层面:添加元数据过滤(时间、权限、来源)、利用 HyDE(假设性文档嵌入)、增加查询改写;生成层面:优化 Prompt 模板、控制检索文档数量和顺序、添加引用约束;评估层面:定期评估召回质量、人工标注反馈、持续迭代优化。关键思路是 每个环节都有可优化空间,整体效果提升来自多环节的累加

核心结论
  • 优化四维度:检索、增强、生成、评估
  • 关键技术:HyDE、Rerank、混合检索、元数据过滤
  • 核心思路:多环节累加优化
1. 检索层优化
优化方向 具体方法 效果
Chunk 策略 语义切片 + 层级索引 提升召回精度
嵌入模型 选更好的嵌入模型(如 BGE-M3) 提升语义理解
混合检索 关键词 + 语义融合 兼顾精确和语义
Rerank Cross-Encoder 精排 提升排序质量
索引优化 HNSW/IVF 加速检索 提升检索速度
2. 增强层优化

HyDE(假设性文档嵌入)

// 让 LLM 先生成一个"假设性答案",再用假设答案做检索
public String hydeSearch(String query) {
    // Step 1: 生成假设性答案
    String hypothetical = llm.generate(
        "根据以下问题生成一个可能的答案:" + query
    );

    // Step 2: 用假设性答案做检索(通常比原问题效果好)
    return vectorSearch(hypothetical);
}

查询改写

public String rewriteQuery(String query) {
    return llm.generate(
        "将以下查询改写为更适合检索的形式," +
        "包含关键词扩展和语义澄清:" + query
    );
}

元数据过滤

public List<Document> searchWithFilter(String query,
        String source, LocalDate fromDate) {
    return vectorStore.search(
        query,
        Filter.builder()
            .eq("source", source)
            .gte("publish_date", fromDate)
            .build()
    );
}
3. 生成层优化

Prompt 优化

System Prompt:
你是一个基于检索内容回答问题的助手。
规则:
1. 只基于提供的上下文回答,不要编造
2. 在答案中标注引用来源 [1][2]
3. 如果上下文不足以回答,明确说明
4. 优先使用最新的信息

Context:
[1] {doc1_content}
[2] {doc2_content}

Question: {user_query}

文档选择与排序

public List<Document> selectDocuments(List<Document> docs, int maxDocs) {
    // 按相关性排序
    docs.sort(Comparator.comparingDouble(Document::getScore).reversed());

    // 去重 + 多样性保证
    List<Document> selected = new ArrayList<>();
    Set<String> seenSources = new HashSet<>();
    for (Document doc : docs) {
        if (selected.size() >= maxDocs) break;
        if (!seenSources.contains(doc.getSource())) {
            selected.add(doc);
            seenSources.add(doc.getSource());
        }
    }
    return selected;
}
4. 常见追问
  • 追问 1:哪个环节优化收益最大?
    • 通常检索层(嵌入模型 + 混合检索)收益最大
  • 追问 2:如何平衡优化效果和成本?
    • 先用简单方法(混合检索+Rerank),效果不足再引入更复杂的方法
一句话总结

RAG 效果优化从检索、增强、生成、评估四维度入手,通过 HyDE、混合检索、Rerank、元数据过滤等技术多环节累加提升。


什么是CoT(思维链)?

原始问法:

  • 什么是CoT(思维链)?

来源题目:SRC-15-152-473

面试先答

CoT(Chain of Thought,思维链)是一种 引导大模型进行分步推理的 Prompt 技术,通过要求模型"一步步思考"来提升复杂推理任务的准确性。核心思想是让模型在生成最终答案前,先生成中间推理步骤,模拟人类解决问题的思维过程。CoT 在数学推理、逻辑推理、多步骤问答等场景效果显著,能将准确率提升 10-40%。实现方式有两种:Zero-shot CoT(在 Prompt 中加入"让我们一步步思考")和 Few-shot CoT(在 Prompt 中提供带推理步骤的示例)。

核心结论
  • CoT 是引导大模型分步推理的 Prompt 技术
  • 核心价值:提升复杂推理任务的准确率
  • 两种实现:Zero-shot CoT 和 Few-shot CoT
1. 是什么

CoT(Chain of Thought)是一种 Prompt Engineering 技术,通过要求大模型在回答问题时 生成中间推理步骤(思维链),而非直接给出答案,从而提升推理准确性。

没有 CoT:
用户:一个商店有 23 个苹果,卖了 17 个,又进货 8 个,还剩多少?
模型:34 个(可能算错)

有 CoT:
用户:一个商店有 23 个苹果,卖了 17 个,又进货 8 个,还剩多少?
模型:让我们一步步思考:
  1. 初始 23 个
  2. 卖了 17 个:23 - 17 = 6 个
  3. 进货 8 个:6 + 8 = 14 个
  4. 答案是 14 个
(准确率显著提升)
2. 实现方式

Zero-shot CoT

Prompt:
问题:{question}
让我们一步步思考,然后给出答案。

Few-shot CoT

Prompt:
示例:
问题:小明有 5 个糖果,给了小红 3 个,又买了 2 个
思考过程:
1. 初始 5 个
2. 给了 3 个:5 - 3 = 2
3. 买了 2 个:2 + 2 = 4
答案:4

问题:{question}
思考过程:

Self-Consistency CoT

// 多次采样,取多数答案
public String selfConsistencyCoT(String question, int samples) {
    Map<String, Integer> answerCounts = new HashMap<>();
    for (int i = 0; i < samples; i++) {
        String response = llm.coTGenerate(question);
        String answer = extractAnswer(response);
        answerCounts.merge(answer, 1, Integer::sum);
    }
    return answerCounts.entrySet().stream()
        .max(Map.Entry.comparingByValue())
        .getKey();
}
3. 适用场景
  • 数学推理(计算、应用题)
  • 逻辑推理(智力题、判断题)
  • 多步骤问答(需综合多条信息)
  • 代码生成(分步实现)
4. 局限性
  • 简单问题不需要 CoT(增加延迟和 Token 消耗)
  • 推理链条过长可能出错
  • 模型可能生成看似合理但错误的推理步骤
5. 常见追问
  • 追问 1:CoT 和 RAG 的关系?
    • CoT 是推理增强,RAG 是知识增强,可结合使用
  • 追问 2:如何让 CoT 更有效?
    • 提供高质量示例、控制推理链条长度、使用 Self-Consistency
一句话总结

CoT 通过引导大模型生成分步推理过程,有效提升复杂推理任务的准确性,是 Prompt Engineering 中的核心技术。


RAG如何解决大模型幻觉?怎么体现/评估?

原始问法:

  • RAG如何解决大模型幻觉?怎么体现/评估?

来源题目:SRC-15-152-474

面试先答

RAG 通过 "检索增强" 从根本上减少大模型幻觉。大模型幻觉的根源是训练数据中的知识过时或缺失,模型只能"编造"答案。RAG 的解决思路是:不让模型依赖内部知识,而是通过检索提供最新、可靠的外部知识作为上下文,让模型"基于证据回答"而非"凭记忆回答"。具体机制包括:在 Prompt 中注入检索到的权威文档、要求模型标注引用来源、约束模型只基于提供内容回答。评估方面通过 忠实度评估(答案是否完全基于检索内容)和 引用准确率(引用来源是否正确相关)两个核心指标衡量效果。

核心结论
  • RAG 通过提供外部权威知识减少幻觉
  • 机制:检索增强 + 引用约束 + Prompt 指令
  • 评估:忠实度 + 引用准确率
1. 幻觉类型与 RAG 解决方案
幻觉类型 表现 RAG 解决方案
事实幻觉 编造不存在的事实/数据 提供权威文档作为知识来源
引用幻觉 编造引用来源 要求模型引用真实的检索文档
逻辑幻觉 推理逻辑错误 提供正确的推理上下文
过度泛化 超出知识范围回答 约束模型"只基于提供内容回答"
2. RAG 防幻觉机制
用户提问
    ↓
检索权威文档(知识注入)
    ↓
构建 Prompt:
  System: 只基于提供的上下文回答,不编造信息。
  Context: [检索到的文档内容]
  Question: 用户问题
  Instruction: 如果上下文不足,说明"根据现有信息无法回答"
    ↓
模型生成答案(基于检索内容)
    ↓
验证答案引用(确保引用来源真实)
3. 评估方法

忠实度评估

public class FaithfulnessEvaluator {

    public FaithfulnessResult evaluate(String answer,
            List<Document> retrievedDocs) {
        // 将答案切分为原子陈述
        List<String> claims = extractClaims(answer);

        int supportedClaims = 0;
        List<String> unsupportedClaims = new ArrayList<>();

        for (String claim : claims) {
            // 检查每个陈述是否被检索文档支持
            boolean supported = checkSupport(claim, retrievedDocs);
            if (supported) {
                supportedClaims++;
            } else {
                unsupportedClaims.add(claim);
            }
        }

        double faithfulness = (double) supportedClaims / claims.size();

        return FaithfulnessResult.builder()
            .faithfulness(faithfulness)
            .totalClaims(claims.size())
            .supportedClaims(supportedClaims)
            .unsupportedClaims(unsupportedClaims)
            .build();
    }
}

引用准确率评估

public class CitationEvaluator {

    public CitationResult evaluate(String answer,
            List<Document> retrievedDocs) {
        // 提取答案中的引用
        List<Citation> citations = extractCitations(answer);

        int correctCitations = 0;
        List<Citation> incorrectCitations = new ArrayList<>();

        for (Citation citation : citations) {
            // 检查引用是否对应正确的文档
            if (citation.isValid(retrievedDocs)) {
                correctCitations++;
            } else {
                incorrectCitations.add(citation);
            }
        }

        double accuracy = citations.isEmpty() ? 1.0
            : (double) correctCitations / citations.size();

        return CitationResult.builder()
            .citationAccuracy(accuracy)
            .totalCitations(citations.size())
            .correctCitations(correctCitations)
            .build();
    }
}
4. 进一步减少幻觉的技巧
  • Prompt 约束:明确要求"只基于提供的上下文回答"
  • 拒答机制:当检索内容不足时,允许模型回答"无法回答"
  • 引用约束:要求答案中的每个论点都有引用支持
  • 温度设置:降低 temperature 减少创造性但错误的输出
  • 多轮验证:生成后用 LLM 交叉验证答案正确性
5. 常见追问
  • 追问 1:RAG 能完全消除幻觉吗?
    • 不能。如果检索本身召回了错误文档,模型仍会基于错误文档生成"正确回答"
  • 追问 2:检索质量差时怎么办?
    • 优化检索策略(混合检索、Rerank)、增加拒答概率、引入人工审核
一句话总结

RAG 通过检索注入外部权威知识,让模型"基于证据回答"而非"凭记忆回答",通过忠实度和引用准确率评估效果,大幅减少但无法完全消除幻觉。

常用哪些大模型?模型选型的考虑因素有哪些?

原始问法:

  • 常用哪些大模型?模型选型的考虑因素有哪些?

来源题目:SRC-15-153-475

面试先答

当前主流大模型可分为 三大阵营闭源商用模型(GPT-4o/4、Claude 3.5/3、Gemini Pro/Ultra)、开源模型(Llama 3/3.1、Mistral、Qwen 系列)、国内模型(文心一言、通义、千问、混元)。选型需要综合考虑 六大因素:一是能力需求(推理、代码、多模态等);二是成本预算(Token 价格、调用费用);三是数据安全(是否允许数据外传、是否需要私有化部署);四是响应延迟(实时性要求);五是上下文窗口大小;六是生态工具链(是否支持 Function Call、RAG 等)。没有最好的模型,只有最适合场景的模型。

核心结论
  • 三大阵营:闭源商用、开源、国内模型
  • 选型六因素:能力、成本、安全、延迟、上下文、生态
  • 核心原则:场景匹配优先,不盲目追求最新最强
1. 主流模型概览
类别 代表模型 核心优势
闭源高端 GPT-4o/4、Claude 3.5 Opus 推理能力强、支持多模态
闭源性价比 GPT-4o-mini、Claude 3 Haiku 速度快、成本低
开源旗舰 Llama 3.1 70B、Qwen 2.5 72B 可私有化、社区活跃
开源轻量 Phi-3、Gemma 2、Mistral 7B 本地化部署、低资源需求
国内模型 千问 Max、文心一言 4.0、混元 中文优化、合规支持
2. 选型决策流程
需求分析
    ↓
┌─────────────────────┐
│ 1. 能力需求评估      │
│   - 推理/代码/多模态?│
│   - 中文/英文?       │
│   - 准确率要求?      │
└─────────┬───────────┘
          ↓
┌─────────────────────┐
│ 2. 安全与合规        │
│   - 数据可外传?      │
│   - 需要私有化?      │
│   - 行业合规要求?    │
└─────────┬───────────┘
          ↓
┌─────────────────────┐
│ 3. 成本与性能        │
│   - 预算范围?        │
│   - 延迟要求?        │
│   - 吞吐量?          │
└─────────┬───────────┘
          ↓
    模型选型决策
3. 常见场景推荐
场景 推荐模型 理由
企业级问答 GPT-4o / Claude 3.5 Sonnet 能力全面、RAG 支持好
代码辅助 GPT-4o / Claude 3.5 Opus 代码生成能力强
本地化部署 Llama 3.1 / Qwen 2.5 开源可私有化
低成本场景 GPT-4o-mini / Haiku 性价比高
国内合规 千问 / 文心一言 数据不出境、中文优化
边缘设备 Phi-3 / Gemma 2 轻量级、可本地运行
4. 常见追问
  • 追问 1:闭源 vs 开源如何选择?
    • 数据敏感→开源私有化;追求效果→闭源;预算有限→开源
  • 追问 2:模型多久换一次?
    • 根据业务需求和模型迭代节奏,通常半年评估一次
一句话总结

大模型选型需综合能力、成本、安全、延迟、上下文和生态六大因素,按场景匹配而非盲目追求最强。


大模型API调用原理是什么?流式输出怎么实现?

原始问法:

  • 大模型API调用原理是什么?流式输出怎么实现?

来源题目:SRC-15-153-476

面试先答

大模型 API 调用本质是 HTTP 请求-响应 的封装,客户端发送包含 Prompt 和参数的 JSON 请求到 API 端点,服务端执行推理后返回生成结果。调用流程包括:客户端组装请求(消息、模型名、参数)→ HTTP 请求发送 → 服务端排队与推理 → 结果返回。流式输出 通过 Server-Sent Events(SSE) 实现:服务端按 Token 粒度逐步生成,每生成一个 Token 就通过 SSE 推送给客户端,客户端实时接收并渲染。核心好处是用户无需等待全部生成完成即可看到输出,大幅改善体验。Java 中可用 OkHttp 或 Spring WebFlux 的 Flux 处理 SSE 流。

核心结论
  • API 调用 = HTTP 请求-响应的封装
  • 流式输出 = SSE(Server-Sent Events)按 Token 粒度逐步推送
  • 核心价值:提升用户体验,支持实时渲染
1. API 调用流程
┌──────────┐    HTTP POST     ┌──────────────┐
│  客户端   │─────────────────→│  API 网关     │
│          │                 │  (鉴权/路由)  │
└────┬─────┘                 └──────┬───────┘
     │                              │
     │                   ┌──────────▼──────────┐
     │                   │    模型服务           │
     │                   │  (推理引擎 + GPU)    │
     │                   └──────────┬──────────┘
     │                              │
     │                   推理完成,返回结果
     │                              │
     │←────────── HTTP Response ────┘
     │
     ▼
  客户端处理结果

请求结构

{
  "model": "gpt-4o",
  "messages": [
    {"role": "system", "content": "你是一个助手"},
    {"role": "user", "content": "你好"}
  ],
  "temperature": 0.7,
  "max_tokens": 1000,
  "stream": false
}
2. 流式输出实现

SSE 协议

服务端按 Token 粒度推送:

data: {"choices":[{"delta":{"content":"你"}}]}

data: {"choices":[{"delta":{"content":"好"}}]}

data: {"choices":[{"delta":{"content":"!"}}]}

data: [DONE]

Java 实现

// 使用 OkHttp 调用流式 API
OkHttpClient client = new OkHttpClient();

RequestBody body = RequestBody.create(
    jsonPayload,
    MediaType.parse("application/json")
);

Request request = new Request.Builder()
    .url("https://api.openai.com/v1/chat/completions")
    .header("Authorization", "Bearer " + apiKey)
    .post(body)
    .build();

// 流式处理
try (Response response = client.newCall(request).execute()) {
    BufferedSource source = response.body().source();
    while (!source.exhausted()) {
        String line = source.readUtf8Line();
        if (line.startsWith("data: ")) {
            String data = line.substring(6);
            if ("[DONE]".equals(data)) break;
            // 解析并处理每个 Token
            processToken(data);
        }
    }
}

Spring WebFlux 实现

// 使用 WebClient 处理流式响应
WebClient client = WebClient.create();

Flux<String> tokenStream = client.post()
    .uri("/v1/chat/completions")
    .contentType(MediaType.APPLICATION_JSON)
    .bodyValue(requestBody)
    .retrieve()
    .bodyToFlux(String.class);

tokenStream.subscribe(
    token -> {
        // 实时渲染 Token
        appendToUI(token);
    },
    error -> log.error("Stream error", error),
    () -> log.info("Stream completed")
);
3. 常见追问
  • 追问 1:SSE 和 WebSocket 的区别?
    • SSE 是单向(服务端→客户端),WebSocket 是双向;SSE 基于 HTTP 更简单
  • 追问 2:流式输出如何处理中断?
    • 客户端断连后服务端停止生成;可通过断点续传恢复
一句话总结

大模型 API 基于 HTTP 请求-响应,流式输出通过 SSE 协议按 Token 粒度逐步推送给客户端,实现实时响应体验。


写提示词的方法论是什么?对提示词工程的理解?

原始问法:

  • 写提示词的方法论是什么?对提示词工程的理解?

来源题目:SRC-15-153-477

面试先答

提示词工程(Prompt Engineering)是一门 与大模型有效沟通的艺术和科学,核心目标是通过精心设计的输入引导模型生成高质量输出。方法论可以总结为 CREATE 原则Clear(清晰明确的指令)、Role(设定专业角色)、Example(提供示例)、Aspect(指定输出维度)、Tone(调整语气风格)、Elaborate(细化要求)。关键技巧包括:使用 System Prompt 设定行为边界、Few-shot 提供示例引导、结构化输出要求、分步推理(CoT)、迭代优化。本质上是通过优化输入信号来最大化模型输出质量。

核心结论
  • 提示词工程 = 与大模型沟通的方法论,核心是 CREATE 原则
  • 关键技巧:角色设定、Few-shot、结构化输出、CoT、迭代优化
  • 本质:优化输入信号,最大化输出质量
1. CREATE 原则详解
原则 说明 示例
Clear 清晰明确的指令 "将以下中文翻译为英文,保持原意"
Role 设定专业角色 "你是一名资深 Java 架构师"
Example 提供 Few-shot 示例 "示例:输入:1+1 输出:2。现在计算:3+5"
Aspect 指定输出维度 "从性能、安全性、可维护性三个方面分析"
Tone 调整语气风格 "用通俗易懂的口语化风格回答"
Elaborate 细化要求 "代码需添加注释,变量使用驼峰命名"
2. 提示词结构模板
# System Prompt(设定身份和行为规则)
你是一名{role},擅长{skill}。
规则:
1. {rule1}
2. {rule2}

# Context(提供背景信息)
背景:{context}

# Instruction(明确任务指令)
请{task_description}

# Example(提供示例,可选)
示例:
输入:{example_input}
输出:{example_output}

# Output Format(指定输出格式)
输出格式:{format_spec}

# User Input(用户输入)
{user_input}
3. 高级技巧

Few-shot 示例

请将以下技术术语解释为通俗易懂的比喻:

示例:
术语:JVM
解释:JVM 就像一个"翻译官",把 Java 代码翻译成电脑能听懂的指令

术语:HashMap
解释:

结构化输出约束

请以 JSON 格式输出,包含以下字段:
{
  "answer": "详细答案",
  "confidence": 0.0-1.0的置信度,
  "sources": ["引用来源列表"]
}

分步引导(CoT)

请一步步思考以下问题,然后给出最终答案:
问题:{question}
思考过程:
4. 迭代优化流程
1. 写初始 Prompt
    ↓
2. 测试典型用例
    ↓
3. 分析输出问题
    ├── 指令不清晰 → 简化指令
    ├── 输出不符合格式 → 添加格式约束
    ├── 知识不足 → 添加背景信息
    └── 风格不对 → 调整角色/语气
    ↓
4. 优化 Prompt
    ↓
5. 重新测试
    ↓
6. 迭代至满意
5. 常见追问
  • 追问 1:提示词工程和传统编程的区别?
    • 编程是确定性的逻辑,提示词工程是概率性的引导
  • 追问 2:如何评估 Prompt 质量?
    • 通过准确率、完整性、相关性等指标,批量测试用例评估
一句话总结

提示词工程通过 CREATE 六大原则和角色设定、Few-shot、结构化输出等技巧,系统化地引导大模型生成高质量输出,是 AI 应用开发的核心技能。


上下文工程的理解?和提示词工程的区别?

原始问法:

  • 上下文工程的理解?和提示词工程的区别?

来源题目:SRC-15-153-478

面试先答

上下文工程(Context Engineering) 是比提示词工程更宽泛的概念,指 管理和优化大模型接收的所有信息 的工程实践。提示词工程关注的是 Prompt 本身的编写技巧,是上下文工程的一个子集。上下文工程涵盖的范围更广,包括:提示词设计(Prompt Engineering)、历史对话管理(选择/压缩/过滤历史)、外部知识注入(RAG 检索结果、知识库查询)、工具调用上下文(工具定义和执行结果)、多模态信息处理(图片、文件等)。简单说:提示词工程管"写什么话",上下文工程管"给模型看什么信息"。上下文工程是 Agent 系统设计的核心能力。

核心结论
  • 上下文工程 > 提示词工程,前者是后者的超集
  • 上下文工程关注给模型看什么信息,提示词工程关注写什么 Prompt
  • 上下文工程是 Agent 系统设计的核心,涉及多维度信息管理
1. 对比分析
维度 提示词工程 上下文工程
关注点 Prompt 编写技巧 所有输入信息的管理
范围 窄(Prompt 本身) 宽(全部信息输入)
内容 指令、角色、示例 Prompt + 历史 + 知识 + 工具 + 多模态
目标 提升输出质量 优化信息输入质量
关系 上下文工程的子集 包含提示词工程
2. 上下文工程的组成
┌─────────────────────────────────────────────────┐
│              上下文工程                          │
├─────────────────────────────────────────────────┤
│                                                  │
│  ┌──────────────┐                               │
│  │ 1. 提示词设计  │ ← 提示词工程                  │
│  └──────────────┘                               │
│                                                  │
│  ┌──────────────┐                               │
│  │ 2. 历史管理   │ 选择/压缩/隔离历史对话          │
│  └──────────────┘                               │
│                                                  │
│  ┌──────────────┐                               │
│  │ 3. 知识注入   │ RAG 检索结果、知识库            │
│  └──────────────┘                               │
│                                                  │
│  ┌──────────────┐                               │
│  │ 4. 工具上下文  │ 工具定义、执行结果             │
│  └──────────────┘                               │
│                                                  │
│  ┌──────────────┐                               │
│  │ 5. 多模态处理  │ 图片、文件、代码等             │
│  └──────────────┘                               │
│                                                  │
└─────────────────────────────────────────────────┘
3. 历史对话管理策略
public class ContextManager {

    // 策略一:滑动窗口(保留最近 N 轮)
    public List<Message> slidingWindow(List<Message> history, int windowSize) {
        int start = Math.max(0, history.size() - windowSize);
        return history.subList(start, history.size());
    }

    // 策略二:摘要压缩(旧对话压缩为摘要)
    public List<Message> summarizeAndCompress(List<Message> history) {
        // 旧对话 → LLM 生成摘要 → 摘要作为上下文
        String summary = llm.summarize(history.subList(0, history.size() - 5));
        List<Message> result = new ArrayList<>();
        result.add(new SystemMessage("对话摘要:" + summary));
        result.addAll(history.subList(history.size() - 5, history.size()));
        return result;
    }

    // 策略三:关键信息提取(只保留关键信息)
    public List<Message> extractKeyInfo(List<Message> history) {
        // 提取用户偏好、决策、关键数据
        return history.stream()
            .filter(msg -> msg.isKeyInformation())
            .collect(Collectors.toList());
    }
}
4. 常见追问
  • 追问 1:上下文工程的核心挑战?
    • Token 窗口限制、信息选择质量、实时性保证
  • 追问 2:如何评估上下文质量?
    • 通过任务完成率、答案准确率、Token 利用率等指标间接评估
一句话总结

上下文工程管理大模型接收的所有信息,是比提示词工程更宽泛的概念,涵盖 Prompt 设计、历史管理、知识注入、工具上下文和多模态处理。


如何避免不同用户上下文互相污染?

原始问法:

  • 如何避免不同用户上下文互相污染?

来源题目:SRC-15-153-479

面试先答

避免不同用户上下文污染的核心是 建立严格的上下文隔离机制,主要有四种方案:会话级隔离(每个用户会话独立存储上下文)、用户级隔离(按用户 ID 隔离所有历史和状态)、租户级隔离(多租户场景下按租户隔离)、请求级隔离(每次请求独立处理,不依赖历史)。实现上通过 Session ID / User ID 作为隔离键上下文存储按 ID 分区上下文数据结构设计时就考虑隔离来保证。同时需要注意 Token 化的上下文标识传递,确保请求路由到正确的上下文空间。

核心结论
  • 隔离方案:会话级、用户级、租户级、请求级
  • 核心机制:ID 隔离 + 存储分区 + 标识传递
  • 设计原则:默认隔离,按需共享
1. 隔离策略
隔离级别 隔离键 适用场景 实现方式
会话级 Session ID 单用户单次对话 每个 Session 独立存储
用户级 User ID 单用户多会话 按用户聚合历史
租户级 Tenant ID SaaS 多租户 按租户完全隔离
请求级 Request ID 无状态请求 不存储历史
2. 实现方案

Session 隔离

public class IsolatedContextStore {

    private final Map<String, List<Message>> sessionContexts = new ConcurrentHashMap<>();

    // 获取用户会话上下文
    public List<Message> getContext(String sessionId) {
        return sessionContexts.getOrDefault(sessionId, new ArrayList<>());
    }

    // 更新上下文
    public void addMessage(String sessionId, Message message) {
        sessionContexts.computeIfAbsent(sessionId, k -> new ArrayList<>())
            .add(message);
    }

    // 清理过期会话
    @Scheduled(fixedRate = 3600000) // 每小时
    public void cleanExpiredSessions() {
        // 清理超过 24 小时未活跃的会话
        sessionContexts.entrySet().removeIf(
            entry -> isExpired(entry.getKey())
        );
    }
}

Token 传递

// 请求中携带 Session ID
@RestController
public class AgentController {

    @PostMapping("/chat")
    public ChatResponse chat(
            @RequestBody ChatRequest request,
            @RequestHeader("X-Session-Id") String sessionId) {

        // 使用 Session ID 隔离上下文
        List<Message> context = contextStore.getContext(sessionId);
        context.add(new UserMessage(request.getMessage()));

        // 调用 Agent
        AgentResponse response = agent.run(context);

        // 保存上下文
        context.add(new AssistantMessage(response.getText()));
        contextStore.addMessage(sessionId, new AssistantMessage(response.getText()));

        return ChatResponse.from(response);
    }
}

Redis 存储隔离

// 使用 Redis 的 Key 前缀隔离
public class RedisContextStore {

    private static final String KEY_PREFIX = "agent:context:";

    public List<Message> getContext(String sessionId) {
        String key = KEY_PREFIX + sessionId;
        String json = redisTemplate.opsForValue().get(key);
        return json != null ? deserialize(json) : new ArrayList<>();
    }

    public void saveContext(String sessionId, List<Message> messages) {
        String key = KEY_PREFIX + sessionId;
        redisTemplate.opsForValue().set(key, serialize(messages), 24, TimeUnit.HOURS);
    }
}
3. 常见追问
  • 追问 1:用户切换设备如何保持上下文?
    • 使用 User ID 做全局标识,Session ID 做设备级标识
  • 追问 2:上下文数据安全如何保障?
    • 传输加密(HTTPS)、存储加密、访问控制
一句话总结

通过 Session ID / User ID 作为隔离键,在存储层、数据结构层和传输层建立多级隔离机制,确保不同用户上下文互不干扰。


保存/隔离/选择/压缩上下文的方法有哪些?

原始问法:

  • 保存/隔离/选择/压缩上下文的方法有哪些?

来源题目:SRC-15-153-480

面试先答

上下文的 保存、隔离、选择、压缩 是一个完整的管理体系。保存 指将上下文存储在持久化介质(Redis、数据库、文件)中,支持会话恢复;隔离 指按 Session/User/Tenant ID 分区存储,防止不同上下文污染;选择 指从全量历史中挑选与当前任务最相关的上下文(滑动窗口、关键信息提取、语义检索);压缩 指将旧对话压缩为摘要或结构化信息,节省 Token 消耗。实际项目中通常组合使用:持久化保存 → ID 隔离 → 相关性选择 → 摘要压缩,形成完整的上下文生命周期管理。

核心结论
  • 四要素:保存(持久化)、隔离(防污染)、选择(提相关)、压缩(省 Token)
  • 生命周期:创建→保存→隔离→选择→压缩→清理
  • 核心挑战:平衡信息量和 Token 限制
1. 上下文保存
存储方式 适用场景 优点 缺点
Redis 会话级缓存 高性能、自动过期 不适合长期存储
关系型数据库 用户级历史 持久化、可查询 性能较低
向量数据库 语义检索 支持相关性查找 成本较高
文件存储 归档历史 低成本、大容量 查询不便
2. 上下文选择策略
public class ContextSelector {

    // 策略一:滑动窗口
    public List<Message> selectByWindow(List<Message> all, int lastN) {
        return all.subList(Math.max(0, all.size() - lastN), all.size());
    }

    // 策略二:关键词过滤
    public List<Message> selectByKeyword(List<Message> all, String keyword) {
        return all.stream()
            .filter(msg -> msg.getContent().contains(keyword))
            .collect(Collectors.toList());
    }

    // 策略三:语义检索
    public List<Message> selectBySemantic(List<Message> all, String query, int topK) {
        // 将历史消息向量化,按语义相似度选择
        return semanticSearch(all, query, topK);
    }

    // 策略四:时间窗口
    public List<Message> selectByTimeRange(List<Message> all,
            LocalDateTime from, LocalDateTime to) {
        return all.stream()
            .filter(msg -> msg.getTimestamp().isAfter(from)
                && msg.getTimestamp().isBefore(to))
            .collect(Collectors.toList());
    }
}
3. 上下文压缩策略
public class ContextCompressor {

    // 策略一:LLM 摘要压缩
    public CompressedContext summarize(List<Message> oldMessages) {
        String summary = llm.generate(
            "请总结以下对话的关键信息:\n" +
            formatMessages(oldMessages)
        );
        return new CompressedContext(summary, oldMessages.size());
    }

    // 策略二:结构化提取
    public StructuredContext extractStructure(List<Message> messages) {
        Map<String, Object> structure = new HashMap<>();
        structure.put("user_preferences", extractPreferences(messages));
        structure.put("key_decisions", extractDecisions(messages));
        structure.put("entities", extractEntities(messages));
        return new StructuredContext(structure);
    }

    // 策略三:Token 预算分配
    public List<Message> allocateBudget(List<Message> messages, int maxTokens) {
        // 系统提示 + 摘要 + 最近对话
        List<Message> result = new ArrayList<>();
        result.add(getSystemPrompt());
        result.add(getSummary(messages));
        result.addAll(getRecentMessagesWithinBudget(messages, maxTokens));
        return result;
    }
}
4. 常见追问
  • 追问 1:压缩后信息丢失怎么办?
    • 采用分层压缩:保留详细摘要 + 关键原文片段
  • 追问 2:如何设计压缩策略?
    • 动态调整:上下文充足时少压缩,紧张时多压缩
一句话总结

通过保存、隔离、选择、压缩四要素的组合策略,实现上下文的完整生命周期管理,在信息量和 Token 限制之间取得平衡。


如何持久化上下文?

原始问法:

  • 如何持久化上下文?

来源题目:SRC-15-153-481

面试先答

上下文持久化的核心是 将对话历史和状态存储到可持久化介质中,支持会话恢复和跨设备同步。主要方案包括:Redis + 数据库组合(Redis 存热数据、数据库存冷数据)、纯数据库方案(简单场景直接用 MySQL/PostgreSQL)、向量数据库 + 关系数据库(支持语义检索的持久化)。持久化的关键设计包括:存储结构设计(消息表 + 会话表 + 元数据表)、自动过期机制(TTL + 定期清理)、读写性能优化(批量写入、异步持久化)。核心挑战是平衡性能和可靠性,通常采用"内存缓存 + 异步持久化"的组合模式。

核心结论
  • 持久化方案:Redis + 数据库、纯数据库、向量库 + 关系库
  • 关键设计:存储结构、自动过期、性能优化
  • 推荐模式:内存缓存 + 异步持久化
1. 存储结构设计
-- 会话表
CREATE TABLE conversation (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    session_id VARCHAR(64) NOT NULL UNIQUE,
    user_id VARCHAR(64) NOT NULL,
    title VARCHAR(255),
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    INDEX idx_user_id (user_id)
);

-- 消息表
CREATE TABLE message (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    conversation_id BIGINT NOT NULL,
    role ENUM('system', 'user', 'assistant', 'tool') NOT NULL,
    content TEXT NOT NULL,
    metadata JSON,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    INDEX idx_conversation_id (conversation_id)
);

-- 上下文元数据表
CREATE TABLE context_metadata (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    session_id VARCHAR(64) NOT NULL,
    context_summary TEXT,
    key_facts JSON,
    entities JSON,
    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    UNIQUE KEY uk_session_id (session_id)
);
2. 持久化实现
@Service
public class ContextPersistenceService {

    private final ConversationRepository conversationRepo;
    private final MessageRepository messageRepo;
    private final RedisTemplate<String, List<Message>> redisTemplate;

    // 异步持久化(写入消息后立即返回)
    @Async
    public void persistMessage(String sessionId, Message message) {
        // 1. 更新 Redis 缓存(热数据)
        String redisKey = "context:" + sessionId;
        List<Message> cached = redisTemplate.opsForList().range(redisKey, 0, -1);
        cached.add(message);
        redisTemplate.opsForList().rightPush(redisKey, message);

        // 2. 异步写入数据库(冷数据)
        Conversation conversation = conversationRepo
            .findBySessionId(sessionId)
            .orElseGet(() -> createConversation(sessionId));

        MessageEntity entity = MessageEntity.from(message, conversation.getId());
        messageRepo.save(entity);
    }

    // 启动时恢复上下文
    public List<Message> restoreContext(String sessionId) {
        // 优先从 Redis 读取
        List<Message> cached = redisTemplate.opsForList()
            .range("context:" + sessionId, 0, -1);
        if (cached != null && !cached.isEmpty()) {
            return cached;
        }

        // Redis 没有,从数据库恢复
        return messageRepo.findBySessionIdOrderByCreatedAtAsc(sessionId)
            .stream()
            .map(MessageEntity::toMessage)
            .collect(Collectors.toList());
    }
}
3. 自动清理策略
@Scheduled(fixedRate = 86400000) // 每天执行
public void cleanExpiredContexts() {
    // 清理超过 30 天的会话
    LocalDateTime threshold = LocalDateTime.now().minusDays(30);
    List<Conversation> expired = conversationRepo
        .findByUpdatedAtBefore(threshold);

    for (Conversation conv : expired) {
        // 删除消息和会话
        messageRepo.deleteByConversationId(conv.getId());
        conversationRepo.delete(conv);

        // 清理 Redis 缓存
        redisTemplate.delete("context:" + conv.getSessionId());
    }
}
4. 常见追问
  • 追问 1:如何保证持久化不丢数据?
    • 同步写数据库 + 异步优化,关键消息同步写入
  • 追问 2:大量历史数据如何处理?
    • 冷热分离:热数据存 Redis,冷数据归档或清理
一句话总结

通过 Redis 缓存 + 数据库持久化的组合模式,配合合理的存储结构和清理策略,实现上下文的高效可靠持久化。


多轮prompt中当前prompt怎么利用之前的prompt?

原始问法:

  • 多轮prompt中当前prompt怎么利用之前的prompt?

来源题目:SRC-15-153-482

面试先答

多轮对话中当前 Prompt 利用之前 Prompt 的核心是 上下文传递机制,主要通过 三种方式完整历史拼接(将所有历史消息按顺序拼接在当前 Prompt 前面,模型自行理解上下文)、摘要+关键信息保留(旧对话压缩为摘要,保留最近几轮完整对话)、结构化上下文注入(将历史中的关键信息提取为结构化数据注入当前 Prompt)。实现上通过维护消息列表,每次调用时将历史消息和当前用户消息一起发送给模型。关键设计是控制上下文长度,在信息完整性和 Token 限制之间取得平衡。

核心结论
  • 多轮 Prompt 利用历史的三种方式:完整拼接、摘要保留、结构化注入
  • 核心机制:消息列表维护 + 上下文长度控制
  • 关键挑战:平衡信息完整性和 Token 限制
1. 三种利用方式

方式一:完整历史拼接

// 直接将所有历史消息拼接
List<Message> messages = new ArrayList<>();
messages.add(new SystemMessage("你是一个助手"));
messages.addAll(conversationHistory); // 所有历史
messages.add(new UserMessage(currentInput));

ChatResponse response = llm.chat(messages);

方式二:摘要 + 最近对话

// 旧对话压缩为摘要 + 最近 N 轮完整保留
List<Message> buildContext(List<Message> history,
        Message current, int keepLastN) {
    List<Message> context = new ArrayList<>();

    // 系统提示
    context.add(new SystemMessage("你是一个助手"));

    // 历史摘要
    if (history.size() > keepLastN) {
        String summary = summarizeHistory(
            history.subList(0, history.size() - keepLastN)
        );
        context.add(new SystemMessage("历史摘要:" + summary));
    }

    // 最近对话
    int start = Math.max(0, history.size() - keepLastN);
    context.addAll(history.subList(start, history.size()));

    // 当前消息
    context.add(current);
    return context;
}

方式三:结构化上下文注入

// 从历史中提取关键信息,结构化注入
Map<String, Object> extractedContext = extractKeyInfo(history);

String enhancedPrompt = """
    基于以下上下文信息回答问题:
    用户偏好:%s
    之前的决策:%s
    关键数据:%s

    当前问题:%s
    """.formatted(
        extractedContext.get("preferences"),
        extractedContext.get("decisions"),
        extractedContext.get("key_data"),
        currentInput
    );
2. 上下文窗口管理
public class WindowManager {

    private static final int MAX_TOKENS = 4000;
    private static final int SUMMARY_THRESHOLD = 3000;

    public List<Message> manageWindow(List<Message> messages) {
        int currentTokens = countTokens(messages);

        if (currentTokens <= MAX_TOKENS) {
            return messages; // 直接返回
        }

        // 超过阈值,触发压缩
        List<Message> result = new ArrayList<>();
        result.add(messages.get(0)); // 保留 System Prompt

        // 计算需要压缩多少
        int keepTokens = MAX_TOKENS - countTokens(
            messages.get(0),
            messages.get(messages.size() - 1)
        );

        // 保留最近的消息,压缩中间的
        List<Message> toCompress = new ArrayList<>();
        List<Message> toKeep = new ArrayList<>();

        int midPoint = findSplitPoint(messages, keepTokens);
        toCompress.addAll(messages.subList(1, midPoint));
        toKeep.addAll(messages.subList(midPoint, messages.size()));

        // 压缩 + 拼接
        result.add(new SystemMessage("历史摘要:" + summarize(toCompress)));
        result.addAll(toKeep);
        return result;
    }
}
3. 常见追问
  • 追问 1:如何避免历史信息丢失?
    • 摘要时保留关键信息点,重要场景不压缩完整保留
  • 追问 2:System Prompt 在多轮中如何处理?
    • 通常固定在最前面,不参与压缩
一句话总结

多轮对话通过完整历史拼接、摘要保留和结构化注入三种方式利用之前的 Prompt,核心是在上下文窗口内最大化信息完整性。


Prompt管理:存什么?怎么存?相似度高怎么处理?

原始问法:

  • Prompt管理:存什么?怎么存?相似度高怎么处理?

来源题目:SRC-15-153-483

面试先答

Prompt 管理的核心是 存储有效 Prompt、快速检索复用、避免重复建设存什么:存经过验证的高质量 Prompt 模板、对应的场景描述和效果评估数据。怎么存:通过 Prompt 注册表(存储元数据和版本)+ 向量索引(支持语义检索)+ 分类标签(支持分类浏览)的组合方案。相似度高的处理:通过 embedding 计算语义相似度,对相似 Prompt 进行聚类和版本管理,当新 Prompt 与已有 Prompt 相似度超过阈值时,提示复用或合并。核心目标是提升 Prompt 的复用率和迭代效率。

核心结论
  • 存储内容:高质量 Prompt 模板 + 场景描述 + 效果数据 + 版本信息
  • 存储方案:注册表 + 向量索引 + 分类标签
  • 相似处理:embedding 相似度检测 + 聚类 + 版本管理
1. 存储内容设计
public class PromptTemplate {
    private String id;
    private String name;
    private String template;          // Prompt 模板
    private String scenario;         // 适用场景描述
    private List<String> tags;       // 分类标签
    private Map<String, Object> metadata; // 元数据
    private double avgScore;         // 平均效果分
    private int usageCount;          // 使用次数
    private String version;          // 版本号
    private List<String> versions;   // 历史版本 ID
    private float[] embedding;       // 向量嵌入(用于相似度计算)
    private LocalDateTime createdAt;
    private LocalDateTime updatedAt;
}
2. 存储方案

Prompt 注册表

@Service
public class PromptRegistry {

    private final Map<String, PromptTemplate> prompts = new ConcurrentHashMap<>();
    private final VectorStore vectorStore;
    private final EmbeddingService embeddingService;

    // 注册新 Prompt
    public String register(PromptTemplate template) {
        // 1. 检测相似度
        float[] embedding = embeddingService.embed(template.getTemplate());
        List<SimilarPrompt> similar = findSimilar(embedding, 0.85f);

        if (!similar.isEmpty()) {
            // 相似度超过阈值,提示复用
            template.setSimilarPrompts(
                similar.stream().map(SimilarPrompt::getId).toList()
            );
        }

        // 2. 生成 ID 并存储
        String id = generateId(template.getName());
        template.setId(id);
        template.setEmbedding(embedding);
        prompts.put(id, template);

        // 3. 存入向量索引
        vectorStore.upsert(id, embedding, template.getMetadata());

        return id;
    }

    // 相似度检测
    public List<SimilarPrompt> findSimilar(float[] embedding, double threshold) {
        return vectorStore.search(embedding, 10).stream()
            .filter(result -> result.getScore() > threshold)
            .map(result -> new SimilarPrompt(
                result.getId(),
                result.getScore()
            ))
            .collect(Collectors.toList());
    }

    // 按场景/标签检索
    public List<PromptTemplate> findByScenario(String scenario) {
        return prompts.values().stream()
            .filter(p -> p.getScenario().contains(scenario)
                || p.getTags().contains(scenario))
            .sorted(Comparator.comparingInt(PromptTemplate::getUsageCount).reversed())
            .collect(Collectors.toList());
    }
}
3. 相似度处理策略
新 Prompt 提交
    ↓
计算 Embedding
    ↓
与已有 Prompt 比对相似度
    ↓
┌─────────────────────────────┐
│ 相似度 < 阈值 (0.7)         │
│ → 注册为新 Prompt            │
└─────────────┬───────────────┘
              ↓
┌─────────────────────────────┐
│ 0.7 ≤ 相似度 < 0.9          │
│ → 标记为相似,人工审核       │
│ → 可作为已有 Prompt 的新版本 │
└─────────────┬───────────────┘
              ↓
┌─────────────────────────────┐
│ 相似度 ≥ 0.9                │
│ → 拒绝注册,建议复用         │
│ → 自动关联到已有 Prompt      │
└─────────────────────────────┘
4. 版本管理
public class PromptVersionManager {

    // 创建新版本
    public String createVersion(String promptId, String newTemplate) {
        PromptTemplate original = registry.get(promptId);

        // 保存旧版本
        String oldVersionId = promptId + "-v" + original.getVersion();
        registry.registerVersion(oldVersionId, original);

        // 更新为新版本
        original.setTemplate(newTemplate);
        original.setVersion(incrementVersion(original.getVersion()));
        original.getVersions().add(oldVersionId);
        registry.update(original);

        return original.getId();
    }

    // 获取指定版本
    public PromptTemplate getVersion(String versionId) {
        return registry.get(versionId);
    }
}
5. 常见追问
  • 追问 1:Prompt 效果如何评估?
    • 通过 A/B 测试、用户反馈、任务完成率等指标持续评估
  • 追问 2:如何构建 Prompt 分类体系?
    • 按场景(客服/代码/分析)、按任务(生成/分类/提取)、按领域(金融/医疗/通用)多维分类
一句话总结

Prompt 管理通过注册表+向量索引+分类标签存储和检索高质量模板,通过 embedding 相似度检测处理重复,通过版本管理支持迭代,核心目标是提升复用率和迭代效率。

向量数据库如何选型?

原始问法:

    1. 向量数据库如何选型?

来源题目:SRC-15-154-484

面试先答

向量数据库选型需要从 五大维度 综合评估:数据规模(量级决定存储架构,百万级选 Milvus/Qdrant,亿级选 Weaviate/Pinecone)、检索性能(QPS、延迟、召回率,核心看 ANN 索引算法选择)、功能需求(混合检索、元数据过滤、多模态支持、实时更新)、部署模式(云托管 vs 私有化部署 vs 嵌入式)、生态集成(与 LangChain/LlamaIndex 等框架集成度)。主流产品对比:Pinecone(全托管、简单易用)、Weaviate(混合检索强、支持多模态)、Milvus(开源、大规模场景)、Qdrant(Rust 编写、高性能)、FAISS(Facebook 开源库、嵌入式集成)。

核心结论
  • 选型五维度:数据规模、检索性能、功能需求、部署模式、生态集成
  • 主流产品:Pinecone(托管)、Weaviate(混合检索)、Milvus(大规模)、Qdrant(高性能)、FAISS(嵌入式)
  • 核心原则:匹配业务场景优先,不过度追求功能
1. 主流产品对比
产品 核心优势 适用场景 部署模式
Pinecone 全托管、零运维 中小规模快速上线 云托管
Weaviate 混合检索、多模态 企业级 RAG 系统 云/私有化
Milvus 开源、亿级数据 大规模数据场景 私有化
Qdrant Rust 编写、高性能 对性能要求极高 私有化
FAISS Facebook 开源库 嵌入式集成、研究场景 嵌入式
pgvector PostgreSQL 插件 使用 PG 的现有系统 嵌入式
2. 选型决策流程
Step 1: 确定数据规模
    ├── < 100万 → FAISS / pgvector(嵌入式)
    ├── 100万 ~ 1000万 → Qdrant / Weaviate
    └── > 1000万 → Milvus / Weaviate 集群
    ↓
Step 2: 确定部署需求
    ├── 不想运维 → Pinecone(全托管)
    ├── 需要私有化 → Milvus / Qdrant / Weaviate
    └── 嵌入式 → FAISS / pgvector
    ↓
Step 3: 确定检索特性
    ├── 纯语义检索 → 所有产品都支持
    ├── 混合检索(关键词+语义)→ Weaviate / Milvus
    ├── 元数据过滤 → Weaviate / Qdrant / Milvus
    └── 多模态 → Weaviate / Pinecone
    ↓
Step 4: 确定性能要求
    ├── 高 QPS + 低延迟 → Qdrant / Milvus
    ├── 平衡型 → Weaviate / Pinecone
    └── 研究/学习 → FAISS
3. 关键评估指标
  • 召回率(Recall):ANN 索引 vs 暴力搜索的结果比值,要求 ≥ 95%
  • 查询延迟(Latency):P99 延迟,要求 < 50ms
  • QPS(Queries Per Second):并发查询吞吐量
  • 索引构建速度:全量索引构建时间
  • 增量更新延迟:新数据写入到可检索的时间
4. 常见追问
  • 追问 1:向量数据库和传统数据库的区别?
    • 传统数据库存结构化数据,向量数据库存 embedding 向量,支持语义相似度检索
  • 追问 2:什么时候用 pgvector 而不是独立向量数据库?
    • 数据量不大(< 百万)、已有 PostgreSQL 基础设施、需要复杂事务支持时
一句话总结

向量数据库选型需综合数据规模、检索性能、功能需求、部署模式和生态集成五大维度,根据业务场景匹配最合适的产品。


检索召回速度怎么优化?

原始问法:

    1. 检索召回速度怎么优化?

来源题目:SRC-15-154-485

面试先答

检索召回速度优化需要从 全链路 入手,涵盖 索引优化、查询优化、缓存策略、硬件加速 四个层面。索引优化 是核心:选择合适的 ANN 算法(HNSW 高召回、IVF 高速度、PQ 高压缩比)、调整索引参数(M、efSearch、nprobe)。查询优化:向量预归一化、批量查询、并行检索、限制候选集大小。缓存策略:热点向量缓存、最近查询结果缓存、分层缓存(L1 内存、L2 Redis、L3 磁盘)。硬件加速:GPU/TPU 加速向量计算、SIMD 指令优化、SSD 提升 I/O。实际优化需要根据场景在召回率和速度之间做 trade-off。

核心结论
  • 四大优化方向:索引优化、查询优化、缓存策略、硬件加速
  • 核心手段:ANN 索引调参 + 多级缓存
  • Trade-off:召回率 vs 速度,需要根据场景平衡
1. 索引优化

ANN 算法选择

算法 代表实现 核心优势 适用场景
HNSW Milvus/Qdrant 高召回率、稳定 中小规模、精度要求高
IVF FAISS/Weaviate 高速度、可扩展 大规模、低延迟要求
PQ FAISS/Milvus 高压缩比、低内存 内存受限、超大规模
ScaNN Google/FAISS 高精度、高速度 通用场景

HNSW 参数调优

// HNSW 核心参数
HNSWConfig config = HNSWConfig.builder()
    .setM(16)              // 每层最大连接数,越大召回越高
    .setEfConstruction(200) // 构建时搜索宽度
    .setEfSearch(50)        // 查询时搜索宽度,越大召回越高但越慢
    .build();

// 调优策略:
// 追求速度 → M=8, efSearch=16
// 平衡型 → M=16, efSearch=50
// 追求召回 → M=32, efSearch=100

IVF 参数调优

// IVF 核心参数
IVFConfig config = IVFConfig.builder()
    .setNlist(256)   // 聚类中心数,越大分片越多、查询越快
    .setNprobe(16)   // 查询时搜索的聚类数,越大召回越高但越慢
    .build();

// 调优策略:
// 速度优先 → nlist=1024, nprobe=4
// 平衡型 → nlist=256, nprobe=16
// 召回优先 → nlist=256, nprobe=64
2. 查询优化
public class OptimizedSearchService {

    // 1. 向量预归一化
    public float[] normalizeVector(float[] vector) {
        float norm = 0;
        for (float v : vector) norm += v * v;
        norm = (float) Math.sqrt(norm);
        if (norm > 0) {
            for (int i = 0; i < vector.length; i++) {
                vector[i] /= norm;
            }
        }
        return vector;
    }

    // 2. 批量并行查询
    public List<SearchResult> batchSearch(List<float[]> queries) {
        return queries.parallelStream()
            .map(q -> vectorStore.search(normalizeVector(q), topK))
            .flatMap(List::stream)
            .collect(Collectors.toList());
    }

    // 3. 限制候选集 + 重排序
    public List<SearchResult> searchWithRerank(float[] query,
            int candidateSize, int finalTopK) {
        // 第一阶段:粗召回(大候选集)
        List<SearchResult> candidates = vectorStore.search(query, candidateSize);

        // 第二阶段:精排(重排序)
        return reranker.rerank(query, candidates, finalTopK);
    }
}
3. 缓存策略
public class LayeredCache {

    private final Cache l1Cache;  // 内存缓存(Caffeine)
    private final RedisTemplate<String, List<SearchResult>> l2Cache;

    public List<SearchResult> search(float[] query, String cacheKey) {
        // L1: 内存缓存
        List<SearchResult> cached = l1Cache.get(cacheKey);
        if (cached != null) return cached;

        // L2: Redis 缓存
        cached = l2Cache.opsForValue().get(cacheKey);
        if (cached != null) {
            l1Cache.put(cacheKey, cached);
            return cached;
        }

        // L3: 向量数据库查询
        List<SearchResult> result = vectorStore.search(query, 100);

        // 写入缓存
        l1Cache.put(cacheKey, result);
        l2Cache.opsForValue().set(cacheKey, result, 30, TimeUnit.MINUTES);

        return result;
    }
}
4. 常见追问
  • 追问 1:如何平衡召回率和速度?
    • 通过 efSearch/nprobe 等参数调节,在可接受的速度下最大化召回率
  • 追问 2:冷热数据如何处理?
    • 热数据缓存预热,冷数据延迟加载或归档
一句话总结

检索召回速度优化从索引、查询、缓存、硬件四方面入手,核心是选择合适的 ANN 算法和参数,配合多级缓存实现全链路优化。


ES千万级数据取top100,内部怎么执行?

原始问法:

    1. ES千万级数据取top100,内部怎么执行?

来源题目:SRC-15-154-486

面试先答

ES 千万级数据取 Top 100 的内部执行流程分为 五个阶段Query 阶段(客户端发送查询到协调节点)→ DFS 阶段(协调节点广播到所有分片,每个分片本地计算 Top N 候选和全局统计)→ Merge 阶段(协调节点汇总各分片的候选集,按评分排序)→ Fetch 阶段(根据最终 Top 100 的文档 ID,到对应分片拉取完整文档数据)→ Return 阶段(返回结果给客户端)。核心设计是 分片并行 + 两阶段查询:第一阶段轻量查询(只返回 ID 和评分),第二阶段按需拉取详情,避免大量数据传输。

核心结论
  • ES 查询五步:Query → DFS → Merge → Fetch → Return
  • 核心机制:分片并行 + 两阶段查询(轻量召回 + 按需拉取)
  • 关键优化:分片路由、DFS 优化、Fetch 并行
1. 完整执行流程
客户端                协调节点               分片1          分片2          分片3
  │                    │                    │              │              │
  │── 1.Query ────────→│                    │              │              │
  │                    │                    │              │              │
  │                    │── 2.DFS(查询广播) ──→│              │              │
  │                    │                    │              │              │
  │                    │    ┌───────────────┴──────────────┘              │
  │                    │    │                                              │
  │                    │    ↓                                              │
  │                    │── 2.DFS(查询广播) ───────────────────────────────→│
  │                    │                    │              │              │
  │                    │                    │              │←── 3.局部TopN ──│
  │                    │                    │←── 3.局部TopN ──│              │
  │                    │←── 3.局部TopN ────│              │              │
  │                    │                    │              │              │
  │                    │    ┌───────────────┴──────────────┐              │
  │                    │    │                                              │
  │                    │    ↓                                              │
  │                    │── 4.Merge(全局排序取Top100)                       │
  │                    │                    │              │              │
  │                    │── 5.Fetch(按需拉取详情) ──────────────────────→│
  │                    │                    │              │              │
  │                    │←── 5.Fetch(按需拉取详情) ───────────────────────│
  │                    │                    │              │              │
  │←── 6.Return ───────│                    │              │              │
  │                    │                    │              │              │
2. 关键步骤详解

DFS 阶段(Distributed Frequency Search)

每个分片执行:
1. 本地计算 BM25 评分
2. 取本地 Top N(默认 10 条)
3. 返回:文档 ID + 评分 + 全局统计(词频、文档数)

协调节点汇总:
1. 合并所有分片的 Top N 候选
2. 计算全局 BM25 评分(使用全局统计值)
3. 按全局评分排序,取 Top 100 的文档 ID 列表

Fetch 阶段

协调节点:
1. 根据 Top 100 文档 ID 列表,确定每个文档所在分片
2. 并行发送 Fetch 请求到对应分片
3. 分片返回完整文档数据
4. 协调节点汇总返回给客户端
3. 性能优化点
优化点 说明 效果
路由优化 按路由键分片,避免全分片扫描 减少分片扫描数量
Top N 调优 增大分片级 Top N(10→50),提升召回 更高召回率
DFS 缓存 缓存 DFS 阶段的统计信息 减少重复计算
Fetch 并行 并行拉取多分片文档 减少等待时间
Doc Values 启用 Doc Values 加快排序和聚合 提升排序性能
分片数 设置合理分片数(= 数据节点数 × 3) 最大化并行度
4. 常见追问
  • 追问 1:Top N 调大或调小的影响?
    • 调大:提升召回率但增加计算开销;调小:降低开销但可能漏召
  • 追问 2:为什么不直接在每个分片取 Top 100?
    • 会导致网络开销巨大,且合并后仍需重新排序
一句话总结

ES 千万级取 Top 100 通过分片并行的两阶段查询实现:第一阶段各分片本地取 Top N 返回 ID+评分,第二阶段协调节点合并排序后按需拉取详情,实现高效分布式检索。


检索这块做过效果上的优化吗?有什么优化案例?

原始问法:

    1. 检索这块做过效果上的优化吗?有什么优化案例?

来源题目:SRC-15-154-487

面试先答

(注:以下为虚构案例,需替换成真实项目经历)我在之前的 RAG 系统项目中做过 检索效果的系统性优化,核心目标是将 Top 5 召回率从 72% 提升到 93%。主要做了 四个优化Chunk 粒度优化(从固定 500 字符改为语义切分 + 50% 重叠,召回率 +8%)、混合检索策略(BM25 关键词 + 向量语义 + 重排序,召回率 +12%)、Query 扩展(同义词扩展 + 子问题分解,召回率 +5%)、效果评估闭环(构建 2000 条标注数据集,每周评估迭代)。最终效果:Top 5 召回率 72% → 93%,用户满意度从 68% → 89%。

核心结论
  • (虚构案例)四维度优化:Chunk 粒度、混合检索、Query 扩展、评估闭环
  • 效果提升:Top 5 召回率 72% → 93%
  • 核心思路:数据驱动 + 多策略组合 + 持续迭代
1. 优化案例(虚构,需替换)

背景:某企业知识库 RAG 系统,用户反映回答不准确,经排查发现检索环节 Top 5 召回率仅 72%。

优化一:Chunk 粒度调整

// 优化前:固定 500 字符切分
public List<Chunk> fixedChunk(String text) {
    return splitByFixedSize(text, 500);
}

// 优化后:语义切分 + 50% 重叠
public List<Chunk> semanticChunk(String text) {
    List<Chunk> chunks = new ArrayList<>();
    List<String> sentences = splitIntoSentences(text);

    StringBuilder currentChunk = new StringBuilder();
    int overlapSize = 0;

    for (int i = 0; i < sentences.size(); i++) {
        String sentence = sentences.get(i);
        currentChunk.append(sentence);

        // 达到目标大小或遇到语义边界
        if (currentChunk.length() >= TARGET_SIZE
                || isSemanticBoundary(sentence)) {
            chunks.add(new Chunk(currentChunk.toString(), i));

            // 50% 重叠:保留最后几句
            overlapSize = Math.max(3, sentences.size() / 10);
            currentChunk = new StringBuilder();
            for (int j = i - overlapSize + 1; j <= i; j++) {
                currentChunk.append(sentences.get(j));
            }
        }
    }
    return chunks;
}

优化二:混合检索

public class HybridRetriever {

    private final BM25Retriever bm25Retriever;
    private final VectorRetriever vectorRetriever;
    private final Reranker reranker;

    public List<Document> retrieve(String query, int topK) {
        // 1. BM25 关键词检索(取 topK*2)
        List<Document> keywordResults = bm25Retriever.search(query, topK * 2);

        // 2. 向量语义检索(取 topK*2)
        List<Document> semanticResults = vectorRetriever.search(query, topK * 2);

        // 3. 融合去重(RRF 算法)
        List<Document> fused = rrfFusion(
            keywordResults, semanticResults, 60
        );

        // 4. 重排序(Cross-Encoder 精排)
        return reranker.rerank(query, fused, topK);
    }

    // Reciprocal Rank Fusion
    private List<Document> rrfFusion(List<Document> list1,
            List<Document> list2, int k) {
        Map<String, Double> scores = new HashMap<>();

        for (int i = 0; i < list1.size(); i++) {
            scores.merge(list1.get(i).getId(),
                1.0 / (k + i + 1), Double::sum);
        }
        for (int i = 0; i < list2.size(); i++) {
            scores.merge(list2.get(i).getId(),
                1.0 / (k + i + 1), Double::sum);
        }

        return scores.entrySet().stream()
            .sorted(Map.Entry.<String, Double>comparingByValue().reversed())
            .map(e -> getDocumentById(e.getKey()))
            .collect(Collectors.toList());
    }
}

优化三:Query 扩展

public class QueryExpander {

    private final LlmService llmService;
    private final ThesaurusService thesaurusService;

    public String expand(String originalQuery) {
        // 1. 同义词扩展
        List<String> synonyms = thesaurusService.getSynonyms(originalQuery);

        // 2. LLM 生成子问题
        String subQuestions = llmService.generate(
            "将以下问题分解为 3-5 个子问题:\n" + originalQuery
        );

        // 3. 组合扩展
        return String.join(" OR ",
            Stream.concat(
                Stream.of(originalQuery),
                synonyms.stream(),
                Stream.of(subQuestions.split("\n"))
            ).collect(Collectors.toList())
        );
    }
}

优化四:评估闭环

public class EvaluationPipeline {

    // 构建标注数据集
    private static final List<QueryLabel> GROUND_TRUTH = loadLabels(
        "evaluation_dataset.json"  // 2000 条标注数据
    );

    public EvaluationResult evaluate() {
        int correctTop1 = 0, correctTop5 = 0, correctTop10 = 0;

        for (QueryLabel item : GROUND_TRUTH) {
            List<Document> results = retriever.retrieve(item.getQuery(), 10);
            Set<String> retrievedIds = results.stream()
                .map(Document::getId).collect(Collectors.toSet());

            if (results.get(0).getId().equals(item.getRelevantId())) {
                correctTop1++;
            }
            if (retrievedIds.contains(item.getRelevantId())) {
                correctTop5++;
            }
        }

        return EvaluationResult.builder()
            .top1Recall((double) correctTop1 / GROUND_TRUTH.size())
            .top5Recall((double) correctTop5 / GROUND_TRUTH.size())
            .top10Recall((double) correctTop10 / GROUND_TRUTH.size())
            .build();
    }
}
2. 优化效果
优化项 Top 5 召回率 提升
基线 72% -
+ Chunk 优化 80% +8%
+ 混合检索 88% +8%
+ Query 扩展 91% +3%
+ 持续迭代 93% +2%
3. 常见追问
  • 追问 1:优化过程中遇到的最大挑战?
    • Chunk 粒度和检索精度的平衡,需要反复实验找到最优点
  • 追问 2:如何向面试官证明这是你的真实经历?
    • 用具体的数据指标、代码实现和决策过程来说明
一句话总结

检索效果优化需要从 Chunk 粒度、混合检索、Query 扩展和评估闭环四方面系统性推进,用数据驱动的方式持续迭代,最终实现召回率的显著提升。(以上为虚构案例,需替换成真实项目经历)

工具调用失败怎么办?

原始问法:

    1. 工具调用失败怎么办?

来源题目:SRC-15-155-488

面试先答

Agent 工具调用失败是常态,需要 分层容错处理即时重试(针对网络抖动等瞬时故障,指数退避重试 2-3 次)、降级策略(重试失败后切换到备用工具,如主数据库失败→只读副本)、保底方案(所有重试和降级都失败时,返回友好提示并建议用户手动操作)、错误上报(记录失败原因和上下文,供后续分析和模型微调)。核心设计原则是 "不让模型看到错误堆栈",而是将技术错误转化为自然语言反馈,让模型理解并尝试其他方案。同时需要区分 可重试错误(超时、网络抖动)和 不可重试错误(参数错误、业务规则不满足),对不同错误类型采取不同策略。

核心结论
  • 四层容错:即时重试 → 降级切换 → 保底方案 → 错误上报
  • 核心原则:将技术错误转为自然语言反馈,不让模型看到堆栈
  • 关键设计:区分可重试和不可重试错误,差异化处理
1. 错误分类
public enum ToolErrorType {
    // 可重试错误
    NETWORK_TIMEOUT("网络超时", true),
    CONNECTION_RESET("连接重置", true),
    RATE_LIMIT("限流", true),
    TEMPORARY_UNAVAILABLE("服务暂时不可用", true),

    // 不可重试错误
    INVALID_PARAMS("参数错误", false),
    PERMISSION_DENIED("权限不足", false),
    BUSINESS_RULE("业务规则不满足", false),
    RESOURCE_NOT_FOUND("资源不存在", false),
    UNKNOWN("未知错误", false);

    private final String message;
    private final boolean retryable;
}
2. 容错处理实现
@Service
public class ToolCallExecutor {

    private static final int MAX_RETRIES = 3;
    private static final long INITIAL_BACKOFF_MS = 100;

    private final Map<String, Tool> toolRegistry;
    private final ErrorReporter errorReporter;

    public ToolResult executeWithFallback(ToolCall call) {
        Tool tool = toolRegistry.get(call.getToolName());

        // 1. 主工具尝试
        ExecutionResult result = executeWithRetry(tool, call);
        if (result.isSuccess()) {
            return result.toToolResult();
        }

        // 2. 降级工具尝试
        Tool fallbackTool = tool.getFallback();
        if (fallbackTool != null) {
            ExecutionResult fallbackResult = executeWithRetry(fallbackTool, call);
            if (fallbackResult.isSuccess()) {
                return fallbackResult.toToolResult();
            }
        }

        // 3. 保底方案:返回自然语言提示
        String friendlyMessage = buildFallbackMessage(result.getError());
        errorReporter.report(call, result);
        return ToolResult.failure(friendlyMessage);
    }

    private ExecutionResult executeWithRetry(Tool tool, ToolCall call) {
        int attempts = 0;
        long backoffMs = INITIAL_BACKOFF_MS;

        while (attempts < MAX_RETRIES) {
            try {
                return tool.execute(call.getParams());
            } catch (Exception e) {
                attempts++;
                ToolErrorType errorType = classifyError(e);

                if (!errorType.isRetryable() || attempts >= MAX_RETRIES) {
                    return ExecutionResult.failure(errorType, e);
                }

                // 指数退避
                sleep(backoffMs);
                backoffMs *= 2;
            }
        }
        return ExecutionResult.failure(ToolErrorType.UNKNOWN,
            new RuntimeException("Max retries exceeded"));
    }

    private String buildFallbackMessage(ExecutionError error) {
        return switch (error.getType()) {
            case NETWORK_TIMEOUT -> "抱歉,操作暂时超时,请稍后重试或尝试其他方式";
            case INVALID_PARAMS -> "参数可能有误,请确认后重试";
            case PERMISSION_DENIED -> "权限不足,无法执行此操作";
            case BUSINESS_RULE -> "操作不符合业务规则";
            default -> "操作失败,请稍后重试";
        };
    }
}
3. 常见追问
  • 追问 1:重试策略如何选择?
    • 可重试错误用指数退避,不可重试错误直接返回
  • 追问 2:如何避免重试风暴?
    • 指数退避 + 最大重试次数 + 断路器模式
一句话总结

工具调用失败通过即时重试、降级切换、保底方案和错误上报四层容错处理,核心是将技术错误转化为自然语言反馈,让 Agent 有机会尝试其他方案。


怎么溯源工具调用失败?是意图识别错、入参错还是工具掉线?

原始问法:

    1. 怎么溯源工具调用失败?是意图识别错、入参错还是工具掉线?

来源题目:SRC-15-155-489

面试先答

溯源工具调用失败需要 分层排查,从 Agent 决策层 → 工具调用层 → 工具实现层 逐层定位。第一步:看 Agent 日志,确认是否正确识别意图(Tool Selection),如果选了错误工具或没选工具 → 意图识别问题。第二步:看参数日志,确认 Agent 生成的入参是否符合工具 Schema 定义(Parameter Validation),如果参数缺失或类型错误 → 入参问题。第三步:看工具执行日志,如果参数正确但执行失败 → 工具实现问题(业务逻辑、数据库、外部依赖)。第四步:看基础设施,如果工具本身无异常 → 网络/服务发现/配置等基础设施问题。核心是建立完整的链路追踪日志。

核心结论
  • 分层溯源:Agent 决策层 → 工具调用层 → 工具实现层 → 基础设施层
  • 快速定位:完整链路追踪日志 + 结构化错误分类
  • 关键能力:日志收集、实时监控、根因分析
1. 分层排查流程
工具调用失败
    ↓
┌─────────────────────────────┐
│ Layer 1: Agent 决策层        │
│ - 正确选择了工具?            │
│ - 正确理解了用户意图?        │
│ → 查看 tool_selection 日志   │
└─────────────┬───────────────┘
              ↓
       意图识别正确?
        ↙          ↘
      Yes            No
      ↓               ↓
┌─────────────────────────────┐
│ Layer 2: 参数生成层          │
│ - 参数是否符合 Schema?      │
│ - 是否有缺失/类型错误?      │
│ → 查看 param_validation 日志 │
└─────────────┬───────────────┘
              ↓
       参数生成正确?
        ↙          ↘
      Yes            No
      ↓               ↓
┌─────────────────────────────┐
│ Layer 3: 工具执行层          │
│ - 业务逻辑是否正常?        │
│ - 外部依赖是否可用?        │
│ → 查看 tool_execution 日志   │
└─────────────┬───────────────┘
              ↓
       工具执行成功?
        ↙          ↘
      Yes            No
      ↓               ↓
┌─────────────────────────────┐
│ Layer 4: 基础设施层          │
│ - 网络是否正常?            │
│ - 服务发现是否正常?        │
│ - 配置是否正确?            │
└─────────────────────────────┘
2. 链路追踪实现
@Service
public class ToolCallTracer {

    private final TraceRepository traceRepo;

    public void traceToolCall(ToolCallContext context) {
        Trace trace = Trace.builder()
            .traceId(UUID.randomUUID().toString())
            .sessionId(context.getSessionId())
            .userId(context.getUserId())
            .toolName(context.getToolName())
            .intent(context.getRecognizedIntent())
            .params(context.getParams())
            .selectionConfidence(context.getSelectionConfidence())
            .status(context.getStatus())
            .errorMessage(context.getErrorMessage())
            .errorType(classifyError(context))
            .timestamp(LocalDateTime.now())
            .build();

        traceRepo.save(trace);
    }

    // 错误分类器
    private String classifyError(ToolCallContext context) {
        if (!context.isToolSelected()) {
            return "INTENT_MISRECOGNITION";  // 意图识别错误
        }
        if (!context.isParamValid()) {
            return "PARAMETER_ERROR";        // 入参错误
        }
        if (!context.isToolHealthy()) {
            return "TOOL_UNAVAILABLE";       // 工具掉线
        }
        if (!context.isBusinessLogicOk()) {
            return "BUSINESS_LOGIC_ERROR";   // 业务逻辑错误
        }
        return "UNKNOWN";
    }
}
3. 快速定位看板
@RestController
@RequestMapping("/api/trace")
public class TraceController {

    private final ToolCallTracer tracer;

    // 按 Trace ID 查询完整链路
    @GetMapping("/{traceId}")
    public TraceDetail getTrace(@PathVariable String traceId) {
        return tracer.getFullTrace(traceId);
    }

    // 按 Session 查询所有调用
    @GetMapping("/session/{sessionId}")
    public List<Trace> getSessionTraces(
            @PathVariable String sessionId,
            @RequestParam(required = false) String errorType) {
        return tracer.getBySession(sessionId, errorType);
    }

    // 统计分析
    @GetMapping("/stats")
    public TraceStats getStats(
            @RequestParam LocalDateTime from,
            @RequestParam LocalDateTime to) {
        return tracer.getErrorStats(from, to);
    }
}
4. 常见追问
  • 追问 1:如何减少意图识别错误?
    • 优化 System Prompt、增加 Few-shot 示例、引入意图分类器做二次确认
  • 追问 2:如何快速定位线上问题?
    • 通过 Trace ID 全链路追踪 + 实时监控大盘 + 结构化日志
一句话总结

工具调用失败溯源通过分层排查(Agent 决策→参数生成→工具执行→基础设施),配合完整链路追踪日志和结构化错误分类,能够快速定位是意图识别、入参还是工具本身的问题。


Agent系统可观测平台有哪些?LangSmith和Langfuse的区别?

原始问法:

    1. Agent系统可观测平台有哪些?LangSmith和Langfuse的区别?

来源题目:SRC-15-155-490

面试先答

Agent 系统可观测平台主要有 LangSmith、Langfuse、PromptLayer、Arize、Helicone 等,它们提供 链路追踪、成本监控、效果评估、调试分析 四大核心能力。LangSmith 是 LangChain 官方产品,深度集成 LangChain 生态,优势在于 端到端的 Agent 调试(可视化 ReAct 循环、Playground 实时交互)和 数据集评估Langfuse 是独立的开源可观测平台,优势在于 与框架无关(支持任何 LLM 应用)、轻量级数据可自托管。简单选择建议:使用 LangChain 技术栈 → LangSmith;非 LangChain 技术栈或重视数据隐私 → Langfuse。

核心结论
  • 主流平台:LangSmith、Langfuse、PromptLayer、Arize、Helicone
  • 核心能力:链路追踪、成本监控、效果评估、调试分析
  • LangSmith vs Langfuse:前者深度集成 LangChain,后者框架无关且开源
1. 平台能力对比
能力 LangSmith Langfuse PromptLayer Arize
链路追踪 ✅ 深度集成 ✅ 通用 ✅ 基础 ✅ AI 原生
成本监控
效果评估 ✅ 数据集评估 ✅ 评分机制 ✅ 模型对比
Playground ✅ 实时交互
开源
自托管
框架支持 LangChain 为主 通用 通用 通用
2. LangSmith 核心功能
// LangSmith 集成(LangChain 项目)
@Configuration
public class LangSmithConfig {

    @Bean
    public Runnable langSmithTracer() {
        return LangSmithTracer.builder()
            .projectName("my-agent-project")
            .apiKey(apiKey)
            .build();
    }

    // 自动追踪 Agent 执行
    @Bean
    public Agent tracedAgent(ChatModel model, List<Tool> tools) {
        return Agent.builder()
            .model(model)
            .tools(tools)
            .tracer(langSmithTracer())
            .build();
    }
}

LangSmith 界面功能

  • Trace View:可视化 Agent 的 ReAct 循环,每一步思考和工具调用
  • Playground:实时调试 Prompt 和参数,对比不同模型效果
  • Datasets:创建测试数据集,批量评估 Agent 效果
  • Evaluations:自动化评估 Agent 输出质量(正确率、相关性等)
  • Curation:标注和修复低质量的 Agent 轨迹
3. Langfuse 核心功能
// Langfuse 集成(框架无关)
@Configuration
public class LangfuseConfig {

    @Bean
    public LangfuseClient langfuseClient() {
        return Langfuse.builder()
            .publicKey(publicKey)
            .secretKey(secretKey)
            .host("https://cloud.langfuse.com")  // 自托管地址
            .build();
    }

    // 手动追踪任意 LLM 调用
    public String traceAgentExecution(TraceContext context) {
        return langfuseClient.trace(
            Trace.builder()
                .name("agent-execution")
                .sessionId(context.getSessionId())
                .userId(context.getUserId())
                .input(context.getInput())
                .build()
        ).getId();
    }

    // 记录 LLM 调用详情
    public void recordLlmCall(String traceId, LlmCall call) {
        langfuseClient.generation(
            Generation.builder()
                .traceId(traceId)
                .model(call.getModel())
                .prompt(call.getPrompt())
                .completion(call.getResponse())
                .cost(call.getCost())
                .latency(call.getLatency())
                .build()
        );
    }
}
4. 选型建议
技术栈判断
    ↓
┌─────────────────────────┐
│ 使用 LangChain/LlamaIndex│
│ → LangSmith(深度集成)  │
└───────────┬─────────────┘
            ↓
┌─────────────────────────┐
│ 非 LangChain 技术栈      │
├─────────────────────────┤
│ 数据需要自托管?         │
│ → Yes: Langfuse(开源)  │
│ → No: LangSmith / Langfuse│
└───────────┬─────────────┘
            ↓
┌─────────────────────────┐
│ 需要 Playground 调试?   │
│ → Yes: LangSmith        │
│ → No: Langfuse / 其他   │
└─────────────────────────┘
5. 常见追问
  • 追问 1:可观测平台和 APM 工具(Jaeger、Zipkin)的区别?
    • APM 面向传统应用,可观测平台面向 LLM/Agent 应用,理解 LLM 特有概念(Token、Prompt、Tool Call)
  • 追问 2:自建可观测平台需要什么?
    • 至少需要:LLM 调用日志、工具调用日志、评估框架、可视化看板
一句话总结

LangSmith 和 Langfuse 是 Agent 可观测领域的两大主流平台,前者深度集成 LangChain 生态,后者框架无关且开源,根据技术栈和数据隐私需求选择即可。


工具重试不成功怎么办?保底工具策略?

原始问法:

    1. 工具重试不成功怎么办?保底工具策略?

来源题目:SRC-15-155-491

面试先答

当工具重试全部失败后,需要 三级保底策略第一级:同类型替代工具(如 MySQL 失败→PostgreSQL,Redis 主库失败→只读从库)、第二级:降级能力(如完整报告生成失败→简化版摘要,如文件上传失败→返回下载链接)、第三级:人工介入(返回友好错误信息 + 建议用户操作 + 自动创建工单通知运维)。设计原则是 核心能力不降级、辅助能力可降级:支付等核心操作必须返回明确失败,搜索结果等辅助操作可以返回部分结果。同时需要 熔断机制:连续 N 次失败后自动停止调用该工具,一段时间内直接走降级路径。

核心结论
  • 三级保底:同类型替代 → 能力降级 → 人工介入
  • 设计原则:核心能力不降级,辅助能力可降级
  • 熔断机制:连续失败自动停止调用,走降级路径
1. 保底策略架构
用户请求
    ↓
┌─────────────────────────────┐
│ 主工具调用                    │
│ (自动重试 2-3 次)           │
└─────────────┬───────────────┘
              ↓ 失败
┌─────────────────────────────┐
│ Level 1: 同类型替代工具       │
│ MySQL → PostgreSQL           │
│ Redis 主 → Redis 从          │
│ 搜索 API A → 搜索 API B      │
└─────────────┬───────────────┘
              ↓ 失败
┌─────────────────────────────┐
│ Level 2: 能力降级            │
│ 完整报告 → 摘要报告           │
│ 实时数据 → 缓存数据           │
│ 完整功能 → 基础功能           │
└─────────────┬───────────────┘
              ↓ 失败
┌─────────────────────────────┐
│ Level 3: 人工介入            │
│ → 返回友好错误信息            │
│ → 建议用户操作               │
│ → 创建工单通知运维           │
└─────────────────────────────┘
2. 实现代码
@Service
public class GuaranteedToolExecutor {

    private final CircuitBreaker circuitBreaker;
    private final AlertService alertService;

    public ToolResult execute(ToolCall call) {
        String toolName = call.getToolName();

        // 检查熔断状态
        if (circuitBreaker.isOpen(toolName)) {
            return executeFallback(call);
        }

        // Level 0: 主工具 + 重试
        ToolResult result = executeWithRetry(call);
        if (result.isSuccess()) return result;

        // Level 1: 替代工具
        ToolResult alternative = tryAlternativeTool(call);
        if (alternative.isSuccess()) {
            logFallback(call, "alternative_tool");
            return alternative;
        }

        // Level 2: 能力降级
        ToolResult degraded = tryDegradedCapability(call);
        if (degraded.isSuccess()) {
            logFallback(call, "degraded_capability");
            return degraded;
        }

        // Level 3: 人工介入
        circuitBreaker.recordFailure(toolName);
        alertService.createTicket(call, result.getError());
        return buildHumanInterventionResult(call);
    }

    // 替代工具映射
    private ToolResult tryAlternativeTool(ToolCall call) {
        Map<String, String> alternatives = Map.of(
            "mysql_query", "postgresql_query",
            "redis_get", "redis_get_replica",
            "search_web", "search_web_backup"
        );
        String altTool = alternatives.get(call.getToolName());
        if (altTool != null) {
            return executeWithRetry(call.toBuilder().toolName(altTool).build());
        }
        return ToolResult.failure("No alternative tool available");
    }

    // 降级能力映射
    private ToolResult tryDegradedCapability(ToolCall call) {
        Map<String, DegradedAction> degradedMap = Map.of(
            "generate_report", new DegradedAction("generate_summary",
                "生成简化版摘要报告"),
            "realtime_data", new DegradedAction("get_cached_data",
                "使用最近缓存的数据"),
            "full_search", new DegradedAction("basic_search",
                "使用基础搜索功能")
        );
        DegradedAction action = degradedMap.get(call.getToolName());
        if (action != null) {
            ToolResult result = executeWithRetry(
                call.toBuilder().toolName(action.getToolName()).build()
            );
            if (result.isSuccess()) {
                result.setMessage(
                    result.getMessage() + "\n(已降级:" + action.getDescription() + ")"
                );
            }
            return result;
        }
        return ToolResult.failure("No degraded capability available");
    }
}
3. 熔断器实现
@Component
public class CircuitBreaker {

    private final Map<String, CircuitState> states = new ConcurrentHashMap<>();
    private static final int FAILURE_THRESHOLD = 5;
    private static final long RECOVERY_TIMEOUT_MS = 60000;

    public boolean isOpen(String toolName) {
        CircuitState state = states.get(toolName);
        if (state == null) return false;

        if (state.getFailureCount() >= FAILURE_THRESHOLD) {
            long elapsed = System.currentTimeMillis() - state.getLastFailureTime();
            if (elapsed > RECOVERY_TIMEOUT_MS) {
                state.setState(CircuitState.HALF_OPEN);
                return false;
            }
            return true; // 熔断中
        }
        return false;
    }

    public void recordFailure(String toolName) {
        states.computeIfAbsent(toolName, k -> new CircuitState())
            .recordFailure();
    }

    public void recordSuccess(String toolName) {
        states.computeIfAbsent(toolName, k -> new CircuitState())
            .recordSuccess();
    }
}
4. 常见追问
  • 追问 1:降级后如何通知用户?
    • 在返回结果中明确标注"已降级",说明原因和影响
  • 追问 2:如何防止降级变成常态?
    • 设置监控告警,连续降级时自动通知运维
一句话总结

工具重试失败后通过替代工具、能力降级、人工介入三级保底策略确保服务可用,配合熔断器防止故障扩散,核心原则是核心能力不降级、辅助能力可降级。


入参校验怎么做?有什么现成手段?

原始问法:

    1. 入参校验怎么做?有什么现成手段?

来源题目:SRC-15-155-492

面试先答

Agent 工具入参校验需要 多层校验体系,从 模型层 → 框架层 → 工具层 → 服务层 逐层保障。模型层:通过 System Prompt 和 Few-shot 引导模型生成正确参数。框架层:利用 Function Calling 的 Schema 自动校验(参数名、类型、必填项)。工具层:自定义业务规则校验(值范围、格式、业务约束)。服务层:最终的安全校验(注入防护、权限检查、数据合法性)。现成手段包括 JSON Schema 校验、Bean Validation(@Valid)、Hibernate Validator、Spring Validation。核心是 "不信任模型输出",模型生成的参数必须经过严格校验才能执行。

核心结论
  • 四层校验:模型层引导 → 框架层 Schema → 工具层业务规则 → 服务层安全
  • 现成手段:JSON Schema、Bean Validation、Hibernate Validator
  • 核心原则:不信任模型输出,所有参数必须校验
1. 校验体系架构
模型生成参数
    ↓
┌─────────────────────────────┐
│ Layer 1: Prompt 引导         │
│ - System Prompt 说明参数格式 │
│ - Few-shot 提供正确示例     │
└─────────────┬───────────────┘
              ↓
┌─────────────────────────────┐
│ Layer 2: Schema 自动校验     │
│ - JSON Schema 定义参数规范   │
│ - Function Calling 自动校验  │
│ → 类型错误/必填缺失直接拦截  │
└─────────────┬───────────────┘
              ↓
┌─────────────────────────────┐
│ Layer 3: 业务规则校验        │
│ - 值域检查(min/max)       │
│ - 格式检查(正则)          │
│ - 业务约束(交叉校验)      │
└─────────────┬───────────────┘
              ↓
┌─────────────────────────────┐
│ Layer 4: 安全校验            │
│ - SQL 注入防护              │
│ - 权限检查                  │
│ - 数据合法性                │
└─────────────────────────────┘
2. 各层实现

Layer 1: Prompt 引导

// 在 System Prompt 中明确参数格式要求
String systemPrompt = """
    你是一个客服助手。调用工具时必须严格遵守参数格式:

    工具名:query_order
    参数:
    - order_id (string, 必填): 订单号,格式为ORD+10位数字
    - date_range (object, 可选):
        - start_date (string): YYYY-MM-DD
        - end_date (string): YYYY-MM-DD

    示例:
    正确:{"order_id": "ORD1234567890"}
    错误:{"order_id": "123"}  // 缺少ORD前缀
    """;

Layer 2: JSON Schema 校验

public class ToolSchemaValidator {

    private final JsonSchemaFactory schemaFactory;

    public ValidationResult validate(String toolName,
            Map<String, Object> params) {
        JsonNode schema = loadSchema(toolName);
        JsonNode paramsNode = objectMapper.valueToTree(params);

        Set<ValidationMessage> errors = schema.validate(paramsNode);

        if (errors.isEmpty()) {
            return ValidationResult.valid();
        }
        return ValidationResult.invalid(
            errors.stream()
                .map(ValidationMessage::getMessage)
                .collect(Collectors.toList())
        );
    }

    private JsonNode loadSchema(String toolName) {
        String schemaJson = toolSchemaRegistry.get(toolName);
        return objectMapper.readTree(schemaJson);
    }
}

Layer 3: 业务规则校验

public class BusinessRuleValidator {

    public List<String> validate(String toolName,
            Map<String, Object> params) {
        List<String> errors = new ArrayList<>();

        switch (toolName) {
            case "query_order":
                validateOrderId(params.get("order_id"), errors);
                validateDateRange(params.get("date_range"), errors);
                break;
            case "transfer_money":
                validateAmount(params.get("amount"), errors);
                validateAccount(params.get("account"), errors);
                break;
            // ... 其他工具
        }
        return errors;
    }

    private void validateOrderId(Object orderId, List<String> errors) {
        if (orderId == null) {
            errors.add("order_id 是必填项");
            return;
        }
        if (!orderId.toString().matches("ORD\\d{10}")) {
            errors.add("order_id 格式错误,应为ORD+10位数字");
        }
    }

    private void validateAmount(Object amount, List<String> errors) {
        if (amount == null || new BigDecimal(amount.toString()).compareTo(BigDecimal.ZERO) <= 0) {
            errors.add("amount 必须大于 0");
        }
        if (new BigDecimal(amount.toString()).compareTo(new BigDecimal("1000000")) > 0) {
            errors.add("单次转账金额不能超过100万");
        }
    }
}

Layer 4: 安全校验

public class SecurityValidator {

    public void validate(Map<String, Object> params, String userId) {
        // SQL 注入防护
        for (Map.Entry<String, Object> entry : params.entrySet()) {
            if (entry.getValue() instanceof String value) {
                if (containsSqlInjection(value)) {
                    throw new SecurityException("检测到潜在的 SQL 注入风险");
                }
            }
        }

        // 权限检查
        if (!hasPermission(userId, params)) {
            throw new SecurityException("无权限执行此操作");
        }
    }

    private boolean containsSqlInjection(String input) {
        String[] patterns = {
            "(?i)(\\b(SELECT|INSERT|UPDATE|DELETE|DROP)\\b)",
            "(?i)(--|;|\\/\\*)",
            "(?i)(\\b(OR|AND)\\b.*\\b(TRUE|FALSE)\\b)"
        };
        for (String pattern : patterns) {
            if (input.matches(pattern)) return true;
        }
        return false;
    }
}
3. 常见追问
  • 追问 1:校验失败后怎么办?
    • 返回校验错误信息给 Agent,让其修正参数后重试
  • 追问 2:如何设计 Tool Schema?
    • 参考 OpenAPI Spec,定义清晰的参数名、类型、约束、描述
一句话总结

入参校验通过 Prompt 引导、Schema 自动校验、业务规则校验和安全校验四层体系保障,核心是不信任模型输出,所有参数必须经过验证才能执行。

日常使用哪些AI编程工具?

原始问法:

    1. 日常使用哪些AI编程工具?

来源题目:SRC-15-156-493

面试先答

日常使用的 AI 编程工具可以分为 四类IDE 集成类(GitHub Copilot、Cursor、Amazon CodeWhisperer)——最主流,直接在编辑器中补全和生成代码;聊天助手类(ChatGPT、Claude、Codeium)——通过对话形式解决编程问题;CLI 工具类(Aider、AutoGPT、Meta 的 Code Llama)——在命令行中批量生成和修改代码;专业领域类(TestGPT 生成测试、CodeRabbit Code Review、Mintify 生成文档)——针对特定编程环节。我的日常组合是 Copilot 做实时补全 + Cursor 做代码重构 + Claude 做疑难解答,三者配合覆盖不同场景。

核心结论
  • 四类工具:IDE 集成、聊天助手、CLI 工具、专业领域
  • 主流组合:Copilot(补全)+ Cursor(重构)+ Claude(解答)
  • 核心价值:提升编码效率、降低重复劳动、辅助学习
1. 工具分类对比
类别 代表工具 核心能力 适用场景
IDE 集成 GitHub Copilot 行级代码补全、函数生成 日常编码、重复模式
IDE 集成 Cursor 项目级代码理解、重构 代码重构、跨文件修改
IDE 集成 Amazon CodeWhisperer 多语言支持、安全扫描 AWS 技术栈开发
聊天助手 ChatGPT/Claude 代码解释、方案设计 疑难问题、学习新技术
聊天助手 Codeium 免费、多 IDE 支持 预算有限的开发者
CLI 工具 Aider 批量文件修改、Git 集成 大规模重构、批量修改
CLI 工具 AutoGPT 自主完成编程任务 自动化编程任务
专业工具 TestGPT 自动生成单元测试 测试覆盖率提升
专业工具 CodeRabbit 自动化 Code Review 代码质量保障
2. 场景化推荐
日常编码
    ↓
GitHub Copilot → 实时补全(Tab 接受)
    ↓
遇到新 API/框架
    ↓
Claude/ChatGPT → 询问用法、看示例
    ↓
需要重构/跨文件修改
    ↓
Cursor → 选中代码,描述需求,自动修改
    ↓
需要批量修改
    ↓
Aider → 命令行批量替换
    ↓
代码完成
    ↓
TestGPT → 自动生成测试
CodeRabbit → 自动 Review
3. 常见追问
  • 追问 1:Copilot 和 Cursor 的区别?
    • Copilot 是行级补全,Cursor 是项目级理解和操作
  • 追问 2:AI 生成的代码需要审查吗?
    • 必须审查,AI 可能生成看似正确但逻辑有缺陷的代码
一句话总结

日常使用 AI 编程工具覆盖 IDE 集成补全、聊天助手解答、CLI 批量操作和专业领域辅助四大类,根据不同场景组合使用最大化效率。


不同场景怎么切换使用AI编程工具?

原始问法:

    1. 不同场景怎么切换使用AI编程工具?

来源题目:SRC-15-156-494

面试先答

不同场景下 AI 编程工具的选择遵循 "复杂度匹配" 原则:单行/小片段生成 → Copilot(Tab 补全,最快);函数级生成 → Copilot 或 Codeium(自然语言描述函数意图);跨文件重构 → Cursor(选中区域 + 描述需求 + 自动修改);方案设计/疑难解答 → Claude/ChatGPT(对话式,获取思路);批量修改 → Aider(CLI 模式,一次改多个文件);学习新技术 → Claude(生成教程+示例+最佳实践)。关键是 不要用锤子解决螺丝问题:简单补全用 Copilot,复杂重构用 Cursor,不确定的问 Claude。

核心结论
  • 场景匹配原则:复杂度匹配工具能力
  • 选择路径:Copilot(简单)→ Cursor(复杂)→ Claude(不确定)
  • 核心效率:选对工具比用好工具更重要
1. 场景决策矩阵
场景 推荐工具 操作方式 示例
行级补全 Copilot 输入前几个字符,Tab 接受 输入 for → Tab 生成循环
函数生成 Copilot/Cursor 写注释描述意图 → 生成 // 将List按age分组 → 生成 groupingBy
跨文件修改 Cursor 选中代码 + 描述需求 选中 UserService → "将所有方法改为异步"
代码重构 Cursor/Aider 描述重构目标 "将User类拆分为User和UserProfile"
疑难解答 Claude/GPT 对话提问 "为什么 ConcurrentHashMap 会这样?"
学习新技术 Claude/GPT 请求教程 "用通俗语言解释 CompletableFuture"
批量生成 Aider CLI 指令 aider "为所有Service添加@Async注解"
测试生成 TestGPT 选中方法 → 生成测试 选中 calculate() → 生成 JUnit 测试
Code Review CodeRabbit 自动 Review PR 提交 PR → 自动评论建议
2. 实际工作流示例
场景:从 0 到 1 开发一个 Spring Boot 接口

Step 1: 创建 Controller 类
    → Copilot: 输入 @RestController 后自动生成模板
    → 输入方法签名 + 注释,Tab 生成方法体

Step 2: 实现 Service 层
    → Copilot: 接口定义后自动生成实现类骨架
    → 复杂逻辑:描述意图,Copilot 生成初版

Step 3: 需要重构(如将同步改异步)
    → Cursor: 选中整个 Service 类
    → 描述:"将所有方法改为 CompletableFuture 异步返回"
    → Cursor 自动修改相关代码

Step 4: 不确定某个 API 用法
    → Claude: "Spring 的 @Async 注解怎么用?线程池怎么配置?"
    → 获取方案后回到 IDE 实现

Step 5: 生成单元测试
    → TestGPT: 选中 Service 类 → 生成测试
    → 手动补充 Mock 数据和边界用例

Step 6: Code Review
    → CodeRabbit: 提交 PR → 自动 Review
    → 根据建议修改代码
3. 常见追问
  • 追问 1:如何避免过度依赖 AI?
    • 保持代码审查习惯,理解每一行生成的代码
  • 追问 2:团队如何统一工具使用?
    • 制定 AI 编程规范,定期分享最佳实践
一句话总结

根据场景复杂度匹配不同 AI 编程工具:简单补全用 Copilot,复杂操作用 Cursor,疑难解答用 Claude,核心是选对工具而非盲目追求最强工具。


AI Coding的流程是什么?大项目需求怎么解决?

原始问法:

    1. AI Coding的流程是什么?大项目需求怎么解决?

来源题目:SRC-15-156-495

面试先答

AI Coding 的完整流程分为 六步需求理解(明确输入输出和约束)→ 方案设计(选择技术方案和架构)→ 代码生成(按模块/函数逐步生成)→ 代码审查(AI 审查 + 人工审查)→ 测试验证(生成测试 + 运行验证)→ 迭代优化(根据反馈调整)。大项目需求的核心解法是 分而治之:将大需求拆解为小任务(Epic → Story → Task),每个小任务独立走 AI Coding 流程,通过 多轮对话 + 上下文管理 确保各模块一致性。关键是 先设计后编码,用 AI 辅助设计再逐步实现。

核心结论
  • AI Coding 六步:需求→设计→生成→审查→测试→迭代
  • 大项目解法:分而治之 + 上下文管理 + 渐进式实现
  • 核心原则:先设计后编码,结构化拆解
1. 完整流程
Step 1: 需求理解
    ├── 明确功能边界
    ├── 定义输入输出
    └── 识别约束条件
    ↓
Step 2: 方案设计
    ├── 选择技术栈
    ├── 设计模块架构
    ├── 定义接口契约
    └── 数据模型设计
    ↓
Step 3: 代码生成(按模块)
    ├── 3.1 基础设施层(配置、工具类)
    ├── 3.2 数据层(Entity、Repository)
    ├── 3.3 业务层(Service、DTO)
    ├── 3.4 接口层(Controller、API)
    └── 3.5 横切关注点(日志、异常、安全)
    ↓
Step 4: 代码审查
    ├── AI 自动审查(CodeRabbit)
    ├── 人工审查(代码规范、业务逻辑)
    └── 安全审查(注入、越权等)
    ↓
Step 5: 测试验证
    ├── 单元测试(TestGPT 生成 + 补充)
    ├── 集成测试
    └── 运行验证
    ↓
Step 6: 迭代优化
    ├── Bug 修复
    ├── 性能优化
    └── 代码重构
2. 大项目分而治之策略
// 需求拆解示例:开发一个电商订单系统
//
// Epic: 订单管理系统
//   ├── Story 1: 创建订单
//   │   ├── Task 1.1: 定义 Order 数据模型
//   │   ├── Task 1.2: 实现 OrderRepository
//   │   ├── Task 1.3: 实现 OrderService.createOrder()
//   │   └── Task 1.4: 实现 OrderController.POST /orders
//   ├── Story 2: 查询订单
//   │   ├── Task 2.1: 实现 OrderService.queryOrder()
//   │   └── Task 2.2: 实现 OrderController.GET /orders/{id}
//   ├── Story 3: 支付订单
//   │   ├── Task 3.1: 集成支付网关
//   │   └── Task 3.2: 实现支付回调处理
//   └── Story 4: 取消订单
//       └── Task 4.1: 实现取消逻辑和状态流转
3. 上下文管理关键
public class ProjectContextManager {

    // 项目级上下文:确保各模块一致性
    private String projectContext;

    public String buildProjectContext() {
        return """
            项目:电商订单系统
            技术栈:Spring Boot 3 + JPA + Redis
            规范:
            - Controller → Service → Repository 分层
            - DTO/Entity 分离
            - 统一异常处理
            - RESTful API 设计
            - 统一响应封装
            """;
    }

    // 每个模块生成时注入项目上下文
    public String generateWithContext(String module, String requirement) {
        return projectContext + """

            现在实现模块:%s
            需求:%s
            请严格遵循项目规范生成代码。
            """.formatted(module, requirement);
    }
}
4. 常见追问
  • 追问 1:AI 生成的代码质量如何保障?
    • 设计先行 + 分模块生成 + 代码审查 + 测试覆盖
  • 追问 2:大项目中如何避免 AI "忘记"前面的设计?
    • 通过项目级上下文注入 + 模块化迭代 + 定期回顾设计文档
一句话总结

AI Coding 遵循需求→设计→生成→审查→测试→迭代六步流程,大项目通过分而治之拆解为小任务,配合上下文管理确保各模块一致性,核心是先设计后编码。


怎么保证AI生成代码的正确性和质量?

原始问法:

    1. 怎么保证AI生成代码的正确性和质量?

来源题目:SRC-15-156-496

面试先答

保证 AI 生成代码的正确性和质量需要 五道防线第一道:Prompt 约束(在生成时明确代码规范、边界条件、性能要求);第二道:静态分析(IDE 实时检查、SonarQube 扫描、编译错误检测);第三道:单元测试(为 AI 生成的代码编写/生成单元测试,确保逻辑正确);第四道:Code Review(人工审查代码质量、设计合理性、安全性);第五道:运行验证(集成测试、性能测试、安全扫描)。核心原则是 "AI 生成,人来验证",AI 是助手不是替代品,最终质量由人保障。

核心结论
  • 五道防线:Prompt 约束 → 静态分析 → 单元测试 → Code Review → 运行验证
  • 核心原则:AI 生成,人来验证
  • 关键习惯:理解每一行代码、测试覆盖、审查到位
1. 五道防线详解

第一道:Prompt 约束

// 高质量 Prompt 约束示例
String codingPrompt = """
    请生成一个用户认证的 Service 类,要求:

    1. 代码规范:
       - 使用 Lombok 简化代码
       - 方法名驼峰,类名大驼峰
       - 异常使用自定义 BusinessException
       - 添加 Javadoc 注释

    2. 功能要求:
       - register(UserDTO): 注册用户,校验邮箱唯一
       - login(LoginDTO): 登录验证,返回 JWT
       - refreshToken(String): 刷新 Token

    3. 边界条件:
       - 邮箱格式校验
       - 密码强度校验(>=8位,含数字和字母)
       - 登录失败次数限制(5次锁定30分钟)

    4. 性能要求:
       - 密码加密使用 BCrypt
       - 敏感操作添加日志
    """;

第二道:静态分析

<!-- Maven 集成 SonarQube -->
<plugin>
    <groupId>org.sonarsource.scanner.maven</groupId>
    <artifactId>sonar-maven-plugin</artifactId>
    <configuration>
        <sonar.host.url>http://sonarqube:9000</sonar.host.url>
        <sonar.login>${SONAR_TOKEN}</sonar.login>
    </configuration>
</plugin>

第三道:单元测试

// 为 AI 生成的 Service 编写测试
@Test
public void testRegisterSuccess() {
    UserDTO dto = new UserDTO();
    dto.setEmail("test@example.com");
    dto.setPassword("Pass123456");

    assertDoesNotThrow(() -> userService.register(dto));
    User savedUser = userRepository.findByEmail("test@example.com");
    assertNotNull(savedUser);
    assertNotEquals("Pass123456", savedUser.getPassword());
}

@Test
public void testRegisterDuplicateEmail() {
    // 边界条件:重复邮箱
    UserDTO dto = new UserDTO();
    dto.setEmail("existing@example.com");

    assertThrows(BusinessException.class,
        () -> userService.register(dto));
}

@Test
public void testPasswordStrength() {
    // 边界条件:弱密码
    UserDTO dto = new UserDTO();
    dto.setEmail("test@example.com");
    dto.setPassword("123"); // 不符合强度要求

    assertThrows(BusinessException.class,
        () -> userService.register(dto));
}

第四道:Code Review 检查清单

检查项 说明 AI 常见问题
逻辑正确性 业务逻辑是否正确 边界条件遗漏
安全性 注入、越权、泄露 SQL 注入风险
性能 N+1 查询、大对象 未加索引、循环查询
代码规范 命名、格式、注释 命名不一致
设计模式 分层、抽象、耦合 过度耦合或过度设计
异常处理 异常类型、日志 吞掉异常、日志不足

第五道:运行验证

// 集成测试
@SpringBootTest
class UserIntegrationTest {

    @Autowired
    private WebTestClient webClient;

    @Test
    void testFullAuthFlow() {
        // 注册 → 登录 → 刷新 → 登出
        UserDTO user = new UserDTO("test@example.com", "Pass123456");

        webClient.post().uri("/api/users/register")
            .bodyValue(user)
            .exchange()
            .expectStatus().isOk();

        LoginDTO login = new LoginDTO("test@example.com", "Pass123456");
        String token = webClient.post().uri("/api/users/login")
            .bodyValue(login)
            .exchange()
            .expectStatus().isOk()
            .expectBody(LoginResponse.class)
            .returnResult().getResponseBody().getToken();

        // 验证 Token 有效性
        webClient.get().uri("/api/users/profile")
            .header("Authorization", "Bearer " + token)
            .exchange()
            .expectStatus().isOk();
    }
}
2. 常见追问
  • 追问 1:如何区分 AI 生成的代码和自己写的?
    • 无法区分,关键是质量而非来源
  • 追问 2:如何快速审查 AI 代码?
    • 关注边界条件、异常处理、性能问题三个高风险区域
一句话总结

通过 Prompt 约束、静态分析、单元测试、Code Review 和运行验证五道防线,系统性保障 AI 生成代码的正确性和质量,核心是人来做最终验证。


Cursor从设计稿直接生成代码的挑战是什么?

原始问法:

    1. Cursor从设计稿直接生成代码的挑战是什么?

来源题目:SRC-15-156-497

面试先答

(注:以下为基于技术原理的分析,非 Cursor 内部实现细节)从设计稿直接生成代码的核心挑战有 五大类设计稿理解问题(设计稿是视觉表达,缺乏精确的布局坐标、响应式规则、交互状态等代码层面的信息)、技术栈选择(设计稿不知道用 React/Vue/原生还是小程序,需要推断或手动指定)、组件识别(识别按钮、表单、列表等 UI 组件并映射到具体组件库)、交互逻辑缺失(设计稿通常只展示静态页面,缺乏点击、跳转、数据加载等交互描述)、工程化适配(生成的代码需要符合项目的代码规范、目录结构、组件库选择)。当前的解决方案通常需要 人工指定技术栈 + 选择组件库 + 补充交互逻辑,AI 辅助生成骨架代码。

核心结论
  • 五大挑战:设计稿理解、技术栈选择、组件识别、交互逻辑、工程化适配
  • 当前状态:AI 辅助生成骨架,人工补充细节
  • 发展方向:多模态理解 + 设计 Tokens 结构化
1. 挑战详解

挑战一:设计稿理解不精确

设计稿(Figma)表达的是:
- 视觉布局(相对位置、视觉层级)
- 颜色/字体/间距(设计 Tokens)
- 组件形状(圆形按钮、方形卡片)

但缺乏代码层面的精确信息:
- 响应式断点(手机/平板/桌面)
- Flex/Grid 布局方式
- 具体像素值或百分比
- 组件层级和嵌套关系

挑战二:技术栈不确定

设计稿 → ??? 技术栈
    ├── React + Tailwind + shadcn/ui
    ├── Vue 3 + Element Plus
    ├── 小程序 + 原生组件
    └── Flutter + Material

需要用户指定或 AI 推断,但容易出错

挑战三:组件识别与映射

设计稿元素          →  目标组件库
─────────────────────────────────
矩形按钮            →  Button(variant: primary)
输入框              →  Input(type: text)
卡片容器            →  Card + CardHeader + CardBody
列表项              →  List + ListItem
下拉菜单            →  Select / Dropdown
弹窗                →  Modal / Dialog
表单验证            →  Form + FormItem + rules

挑战四:交互逻辑缺失

设计稿通常展示的状态:
- 默认状态(Default)
- 悬停状态(Hover)
- 选中状态(Selected)
- 禁用状态(Disabled)

但缺失的交互逻辑:
- 点击后触发什么?
- 数据从哪里加载?
- 提交后跳转到哪里?
- 表单验证规则是什么?
- 权限控制在哪里?

挑战五:工程化适配

生成的代码需要适配:
- 项目目录结构(src/components、src/pages)
- 路由配置(React Router、Vue Router)
- 状态管理(Redux、Pinia、Zustand)
- API 调用约定(axios 封装、React Query)
- 代码规范(ESLint、Prettier、Husky)
- 国际化支持(i18n)
2. 当前解决方案
设计稿(Figma)
    ↓
Step 1: 导出设计 Tokens
    → 颜色、字体、间距、圆角
    → 生成 CSS Variables / Tailwind Config
    ↓
Step 2: 指定技术栈
    → 用户选择 React/Vue/小程序
    → 指定 UI 组件库(shadcn/ui、Element Plus)
    ↓
Step 3: AI 生成骨架代码
    → 识别组件结构
    → 生成组件骨架和样式
    → 映射到指定组件库
    ↓
Step 4: 人工补充交互
    → 添加事件处理
    → 实现数据绑定
    → 配置路由
    → 添加状态管理
    ↓
Step 5: 工程化调整
    → 移动到正确目录
    → 统一代码规范
    → 集成到项目结构
3. 发展趋势
方向 说明 预期突破
多模态理解 AI 直接理解设计稿的精确布局 减少设计信息丢失
Design Tokens 标准 统一的设计 Token 格式(如 DTC) 精确映射样式
组件库预设 预定义技术栈+组件库映射 减少人工选择
交互自动推断 根据设计模式推断交互逻辑 补充交互缺失
工程化模板 预定义项目工程化模板 自动适配项目结构
4. 常见追问
  • 追问 1:Figma 的代码导出功能和 AI 生成有什么区别?
    • Figma 导出精确但不智能,AI 生成更智能但不够精确,理想是结合使用
  • 追问 2:什么时候用设计稿生成代码?
    • 快速原型验证、重复性页面、UI 基础组件生成
一句话总结

从设计稿生成代码面临设计理解不精确、技术栈不确定、组件识别、交互缺失和工程化适配五大挑战,当前通过 AI 辅助生成骨架代码 + 人工补充细节的模式实现,未来向多模态理解方向发展。


推荐文章

01-Java基础
11-Spring
10-微服务
上一篇 14-系统设计
下一篇 16-项目经验