Appearance
十、AI 智能体项目实战
本主题题目目录
点击题号跳转;右侧目录会高亮你正在阅读的题目
覆盖 AI 智能体项目架构、多轮对话、工具调用、ReAct/CoT、SSE流式、性能优化,共 20 题(326-345)。
326. 请介绍一下 AI 智能体项目的整体架构设计,有哪些模块?
👔面试官:说说你做的 AI 智能体项目,整体架构是怎么设计的?
🙋♂️我:就是一个 Spring Boot 项目,接了 OpenAI 的 API,然后加了几个功能模块。
👔面试官:「几个功能模块」具体是哪些?模块之间怎么通信?数据流是怎样的?有没有做分层设计?
🙋♂️我:……可能就是 Controller、Service、DAO 三层吧,数据流就是从前端到后端再到模型 API。
这种回答面试官一听就知道你没做过复杂系统。下面我把一个生产级 AI 智能体项目的完整架构讲清楚。
💡 简要回答
AI 智能体项目的典型架构分为五层:
- 接入层:REST API / SSE 流式接口,处理用户请求和响应
- 编排层:智能体编排引擎(ReAct/Plan-and-Execute),负责任务规划和 Agent 调度
- 能力层:工具系统(联网搜索、代码执行、PDF 生成)、RAG 知识库、记忆系统
- 模型层:多模型路由(GPT-4/Claude/国产模型),统一抽象接口
- 基础设施层:向量数据库、缓存、消息队列、监控告警
📝 详细解析
分层架构图
┌─────────────────────────────────────────────────────────┐
│ 接入层 (Presentation Layer) │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ REST API │ │ SSE 流式 │ │ WebSocket │ │
│ │ (同步接口) │ │ (实时响应) │ │ (双向通信) │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
├─────────────────────────────────────────────────────────┤
│ 编排层 (Orchestration Layer) │
│ ┌─────────────────────────────────────────────────┐ │
│ │ Agent 编排引擎 │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │ ReAct │ │ Plan-Exec│ │ Multi-Agent│ │ │
│ │ │ (推理+行动)│ │ (计划+执行)│ │ (多Agent协作)│ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ │ │
│ └─────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────┤
│ 能力层 (Capability Layer) │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ 工具系统 │ │ RAG 知识库 │ │ 记忆系统 │ │
│ │ • 联网搜索 │ │ • 文档索引 │ │ • 短期记忆 │ │
│ │ • PDF生成 │ │ • 向量检索 │ │ • 长期记忆 │ │
│ │ • 代码执行 │ │ • Rerank │ │ • 会话隔离 │ │
│ └────────────┘ └────────────┘ └────────────┘ │
├─────────────────────────────────────────────────────────┤
│ 模型层 (Model Layer) │
│ ┌─────────────────────────────────────────────────┐ │
│ │ 多模型路由网关 │ │
│ │ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │ │
│ │ │ GPT-4 │ │ Claude │ │ Qwen │ │ 本地模型│ │ │
│ │ └────────┘ └────────┘ └────────┘ └────────┘ │ │
│ └─────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────┤
│ 基础设施层 (Infrastructure Layer) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Milvus │ │ Redis │ │ Kafka │ │ Prometheus│ │
│ │ (向量库) │ │ (缓存) │ │ (消息队列)│ │ (监控) │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────┘各层职责详解
接入层:不只是简单的 Controller,还要处理:
- 用户身份认证和权限校验
- 请求限流(防止单个用户刷爆服务)
- SSE 连接管理(连接生命周期、心跳保活)
- 多租户隔离(企业版 SaaS 需要)
编排层:这是智能体的「大脑」,核心职责:
- ReAct 模式:推理 → 行动 → 观察 → 再推理的循环
- Plan-and-Execute:先制定完整计划,再逐步执行
- 多 Agent 协作:调度不同的 Specialist Agent(搜索 Agent、代码 Agent、审核 Agent)
java
// 编排引擎的核心接口
public interface AgentOrchestrator {
// 根据用户意图选择合适的 Agent 或策略
ExecutionPlan plan(UserIntent intent);
// 执行计划,处理 Agent 切换和状态传递
Stream<String> execute(ExecutionPlan plan, SessionContext context);
}能力层:工具系统不是简单的函数集合,要考虑:
- 工具注册和发现(MCP 协议)
- 工具调用权限控制
- 工具执行超时和熔断
- 工具结果缓存
模型层:多模型路由的关键决策:
- 简单任务 → 便宜的小模型(GPT-3.5)
- 复杂推理 → 强能力大模型(GPT-4/Claude-3)
- 敏感数据 → 本地私有化模型
- 实时性要求 → 推理速度快的模型
java
@Component
public class ModelRouter {
public ChatModel route(TaskProfile task) {
if (task.isComplexReasoning()) return gpt4Model;
if (task.containsSensitiveData()) return localModel;
if (task.requiresRealTime()) return fastModel;
return defaultModel;
}
}基础设施层:
- 向量库(Milvus/Pinecone)存储知识库
- Redis 存储会话状态和短期记忆
- 消息队列处理异步任务(PDF 生成、批量索引)
- Prometheus + Grafana 监控 QPS/延迟/错误率
数据流示例
用户问:「帮我查一下竞品 A 的最新动态,整理成报告发到我邮箱」
1. 接入层接收请求,鉴权,创建 SSE 连接
↓
2. 编排层识别意图:搜索 + 整理 + 发邮件(多步骤任务)
↓
3. 编排层启动 ReAct Loop
├─ 第1轮:模型判断需要搜索 → 调用搜索工具
├─ 第2轮:拿到搜索结果,判断需要整理 → 生成报告内容
└─ 第3轮:判断需要发邮件 → 调用邮件发送工具
↓
4. 能力层执行具体工具调用
↓
5. 模型层在每一轮被调用,生成推理和输出
↓
6. 编排层汇总结果,通过 SSE 返回给用户🎯 面试总结
答这道题最容易踩的雷是只说「Controller-Service-DAO 三层」。面试官想听的是针对 AI 智能体特性的分层。
关键五层:
- 接入层:REST/SSE/WebSocket 接口,限流认证
- 编排层:ReAct/Plan-and-Execute/多 Agent 调度
- 能力层:工具系统、RAG、记忆
- 模型层:多模型路由策略
- 基础设施层:向量库、缓存、监控
加分点:能说出 多模型路由策略(按任务复杂度选模型)和 编排层的 ReAct Loop(推理-行动-观察循环)。
327. 如何实现 AI 多轮对话功能?如何解决对话记忆持久化问题?
👔面试官:AI 智能体项目中,多轮对话是怎么实现的?记忆怎么持久化?
🙋♂️我:就是把历史消息都塞进 Prompt 里,持久化就用数据库存起来。
👔面试官:「都塞进 Prompt」?上下文窗口有限,对话长了怎么办?数据库怎么设计表结构?短期记忆和长期记忆怎么区分?
🙋♂️我:……可能就存最近几条吧,具体设计没细想过。
多轮对话和记忆持久化是智能体系统的核心能力。下面把工程实现讲清楚。
💡 简要回答
多轮对话的实现分两层:
短期记忆(上下文窗口):
- 最近 N 轮对话保留在 Prompt 中(通常 5-10 轮)
- 超过限制时做摘要压缩或滑动窗口
长期记忆(向量数据库):
- 重要信息提取后向量化存储
- 新对话时检索相关历史记忆注入 Prompt
持久化方案:Redis 存短期记忆(TTL 过期),向量库 + 关系库存长期记忆。
📝 详细解析
短期记忆:上下文窗口管理
滑动窗口策略:
java
@Component
public class SlidingWindowMemory {
private static final int MAX_MESSAGES = 10; // 保留最近 10 条
public List<Message> maintainWindow(String sessionId) {
List<Message> history = redisTemplate.opsForList()
.range("chat:" + sessionId, 0, -1);
if (history.size() > MAX_MESSAGES) {
// 老消息做摘要,保留摘要 + 最近 N 条完整消息
List<Message> oldMessages = history.subList(0, history.size() - MAX_MESSAGES);
String summary = summarize(oldMessages);
// 更新 Redis:摘要 + 最近消息
redisTemplate.opsForList().trim("chat:" + sessionId,
history.size() - MAX_MESSAGES, -1);
redisTemplate.opsForList().leftPush("chat:" + sessionId,
new SystemMessage("历史摘要:" + summary));
}
return history;
}
}摘要压缩策略:
java
// 当对话太长时,让 LLM 生成摘要
public String summarize(List<Message> messages) {
String conversation = messages.stream()
.map(m -> m.getRole() + ": " + m.getContent())
.collect(Collectors.joining("\n"));
return chatClient.prompt()
.user("请总结以下对话的关键信息(50字以内):\n" + conversation)
.call()
.content();
}长期记忆:向量数据库存储
什么该存长期记忆?
- 用户偏好(「我喜欢简洁的回答」)
- 关键事实(「我是做金融行业的」)
- 任务结果(「上次生成的报告主题是 AI 趋势」)
java
@Component
public class LongTermMemory {
@Autowired
private VectorStore vectorStore;
// 从对话中提取重要信息并存入向量库
public void extractAndStore(String sessionId, List<Message> conversation) {
String prompt = """
从以下对话中提取用户的关键信息(偏好、事实、需求),
每条信息用一句话描述:
%s
提取结果(每行一条):
""".formatted(formatConversation(conversation));
String extracted = chatClient.prompt(prompt).call().content();
// 每条信息向量化后存入
for (String fact : extracted.split("\n")) {
if (fact.isBlank()) continue;
Document doc = new Document(fact);
doc.getMetadata().put("session_id", sessionId);
doc.getMetadata().put("type", "user_fact");
doc.getMetadata().put("timestamp", System.currentTimeMillis());
vectorStore.add(List.of(doc));
}
}
// 检索相关记忆
public List<String> retrieveRelevant(String sessionId, String query) {
return vectorStore.similaritySearch(
SearchRequest.query(query)
.withFilterExpression("session_id == '" + sessionId + "'")
.withTopK(3)
).stream()
.map(Document::getContent)
.toList();
}
}数据库表结构设计
Redis(短期记忆):
Key: chat:{session_id}
Type: List
Value: [Message1, Message2, ...] (JSON 序列化)
TTL: 24h(会话过期清理)向量库(长期记忆):
json
{
"id": "uuid",
"content": "用户是做金融行业的",
"embedding": [0.1, 0.2, ...],
"metadata": {
"session_id": "sess_123",
"type": "user_fact",
"timestamp": 1704067200000
}
}关系库(会话元信息):
sql
CREATE TABLE chat_sessions (
id VARCHAR(64) PRIMARY KEY,
user_id VARCHAR(64) NOT NULL,
title VARCHAR(255), -- 会话标题(自动生成)
model VARCHAR(50), -- 使用的模型
created_at TIMESTAMP,
updated_at TIMESTAMP,
message_count INT, -- 消息数量
INDEX idx_user (user_id, updated_at)
);
CREATE TABLE chat_messages (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
session_id VARCHAR(64),
role ENUM('user', 'assistant', 'system'),
content TEXT,
tokens INT, -- token 数量
created_at TIMESTAMP,
FOREIGN KEY (session_id) REFERENCES chat_sessions(id),
INDEX idx_session (session_id, created_at)
);Spring AI 中的集成
java
@Configuration
public class ChatMemoryConfig {
@Bean
public ChatClient chatClient(ChatModel model,
VectorStore vectorStore,
RedisTemplate<String, Message> redis) {
// 组合两种记忆 Advisor
return ChatClient.builder(model)
.defaultAdvisors(
// 短期记忆:从 Redis 加载最近对话
new RedisChatMemoryAdvisor(redis, 10),
// 长期记忆:从向量库检索相关事实
new LongTermMemoryAdvisor(vectorStore)
)
.build();
}
}会话隔离
java
@Service
public class ChatService {
public Flux<String> chat(String userId, String sessionId, String message) {
// 1. 验证会话归属(防止用户 A 访问用户 B 的会话)
validateSessionOwnership(userId, sessionId);
// 2. 加载该会话的历史
List<Message> history = loadSessionHistory(sessionId);
// 3. 调用模型(自动带上历史上下文)
return chatClient.prompt()
.user(message)
.advisors(a -> a.param(CHAT_MEMORY_CONVERSATION_ID, sessionId))
.stream()
.content();
}
}🎯 面试总结
多轮对话和记忆实现的关键是分层:
短期记忆:
- 位置:Redis List
- 策略:滑动窗口(保留最近 N 条)+ 摘要压缩
- 过期:TTL 24h
长期记忆:
- 位置:向量数据库
- 内容:用户偏好、关键事实、任务结果
- 检索:按语义相似度召回相关记忆
持久化设计:
- Redis:高性能读写,TTL 自动清理
- 向量库:语义检索长期记忆
- 关系库:会话元信息和消息归档
面试时要能说出 短期记忆的滑动窗口策略(防止撑爆上下文)和 长期记忆的提取策略(用 LLM 从对话中抽取关键信息)。
328. 什么是 ReAct?如何基于 ReAct 模式构建具备自主规划能力的 AI 智能体?
👔面试官:说说 ReAct 是什么?怎么用它来构建智能体?
🙋♂️我:ReAct 就是推理加行动,让模型一边思考一边做事情。
👔面试官:ReAct 的循环具体是怎样的?Observation 有什么用?怎么防止 ReAct 无限循环?实际代码怎么实现?
🙋♂️我:……大概就是 Thought → Action → Observation 这样循环,具体代码没写过。
ReAct 是最经典的智能体架构模式,下面把原理和实现讲清楚。
💡 简要回答
ReAct(Reasoning + Acting) 是一种让 LLM 交替进行「推理」和「行动」的范式,通过观察行动结果来调整下一步策略。
核心循环:
Thought(思考当前状态和目标)
↓
Action(选择并执行工具)
↓
Observation(观察行动结果)
↓
[循环直到完成任务或达到最大步数]📝 详细解析
ReAct 的核心思想
传统方式是「一次性生成答案」,ReAct 是「边做边想、边想边做」:
- Thought:分析当前情况,规划下一步
- Action:调用工具(搜索、计算、查数据库)
- Observation:获取工具返回的结果
- 循环:基于新的观察,重新思考
ReAct 示例流程
用户提问:「杭州今天天气怎么样?适合穿什么衣服?」
Step 1 - Thought:
用户想知道杭州的天气和穿衣建议。我需要先查询天气信息。
Step 1 - Action:
调用 get_weather 工具,city="杭州"
Step 1 - Observation:
{"city": "杭州", "temperature": 15, "condition": "多云", "wind": "东北风3级"}
Step 2 - Thought:
杭州今天15度多云,温度适中但有风。需要建议穿薄外套。
Step 2 - Action:
不需要调用工具,可以直接回答。
Step 3 - Final Answer:
杭州今天多云,15°C,东北风3级。建议穿薄外套+长裤,可以带件薄毛衣备用。Spring AI 实现 ReAct
java
@Component
public class ReActAgent {
private final ChatClient chatClient;
private final List<Tool> tools;
private static final int MAX_STEPS = 10; // 防止无限循环
public String execute(String userInput) {
StringBuilder conversation = new StringBuilder();
conversation.append("用户:").append(userInput).append("\n\n");
for (int step = 0; step < MAX_STEPS; step++) {
// 构建 ReAct Prompt
String prompt = buildReActPrompt(conversation.toString());
// 调用模型
String response = chatClient.prompt(prompt).call().content();
// 解析响应
if (response.contains("Final Answer:")) {
return extractFinalAnswer(response);
}
// 提取 Thought 和 Action
String thought = extractThought(response);
String action = extractAction(response);
conversation.append("Thought: ").append(thought).append("\n");
conversation.append("Action: ").append(action).append("\n");
// 执行工具
String observation = executeAction(action);
conversation.append("Observation: ").append(observation).append("\n\n");
}
return "达到最大步数限制,任务未完成";
}
private String buildReActPrompt(String context) {
return """
你是一个智能助手,可以通过思考和使用工具来解决问题。
可用工具:
%s
请按以下格式响应:
Thought: 你的思考过程
Action: 工具名称[参数]
或当任务完成时:
Final Answer: 最终答案
历史:
%s
""".formatted(formatTools(), context);
}
private String executeAction(String action) {
// 解析工具调用,执行并返回结果
// get_weather[杭州] → 调用 weatherTool.query("杭州")
}
}防止无限循环的机制
java
public class ReActAgent {
private static final int MAX_STEPS = 10;
private static final Set<String> executedActions = new HashSet<>();
private String executeWithSafety(String userInput) {
for (int step = 0; step < MAX_STEPS; step++) {
String action = getActionFromModel();
// 1. 检测重复动作
if (executedActions.contains(action)) {
return "检测到重复动作,终止执行";
}
executedActions.add(action);
// 2. 检测无效动作
if (isInvalidAction(action)) {
return "无效动作:" + action;
}
// 3. 执行动作
String observation = executeAction(action);
// 4. 检测是否有进展(Observation 是否有新信息)
if (isNoProgress(observation)) {
return "执行无进展,可能需要调整策略";
}
}
return "达到最大步数限制";
}
}ReAct vs Plan-and-Execute
| 维度 | ReAct | Plan-and-Execute |
|---|---|---|
| 规划方式 | 边执行边规划 | 先完整规划,再执行 |
| 灵活性 | 高,能根据观察调整 | 低,计划一旦定好不易改 |
| 可解释性 | 每一步都有 Thought | 整体计划清晰 |
| 适用场景 | 探索性任务、信息收集 | 确定性任务、流程固定 |
| 风险控制 | 可能陷入循环 | 计划可控,执行稳定 |
实际项目往往两者结合:先用 Plan-and-Execute 制定大纲,再用 ReAct 处理每个步骤的细节。
工程优化技巧
java
// 1. 工具描述要精确,帮助模型选对工具
@Tool(description = "查询天气。当用户询问任何城市的天气、气温、是否下雨时使用。")
// 2. Prompt 中给出 Few-shot 示例
String fewShotExample = """
示例1:
用户:北京天气怎么样?
Thought: 用户想知道北京的天气,我需要查询天气工具。
Action: get_weather[city="北京"]
Observation: {"temperature": 20, "condition": "晴"}
Thought: 已经拿到天气信息,可以直接回答。
Final Answer: 北京今天晴天,20°C。
""";
// 3. Observation 格式标准化
// 所有工具返回 JSON,方便模型解析🎯 面试总结
ReAct 是最基础的智能体架构模式。
核心循环:Thought → Action → Observation → 循环
实现要点:
- Prompt 设计:明确定义 Thought/Action/Observation 格式
- 工具描述:精确描述每个工具的用途和参数
- 循环控制:最大步数限制、重复动作检测、无效动作检测
- Few-shot 示例:给模型看正确的执行轨迹
与 Plan-and-Execute 对比:
- ReAct:灵活、适合探索性任务
- Plan-and-Execute:稳定、适合确定性任务
面试时要能画出 ReAct 循环图 和说出 防止死循环的机制(最大步数、重复检测)。
329. 什么是 CoT 思维链?如何实现 CoT 思维链?
👔面试官:说说 CoT 思维链是什么?它怎么提高 AI 的推理能力?
🙋♂️我:CoT 就是让模型一步一步思考,把推理过程写出来。
👔面试官:CoT 为什么能提高准确率?Few-shot CoT 和 Zero-shot CoT 有什么区别?实现 CoT 有哪些技巧?
🙋♂️我:……可能就是加一句「请逐步思考」?Few-shot 是给例子,Zero-shot 是不给例子?
CoT 是提升 LLM 推理能力的关键技术,下面把原理和实现讲清楚。
💡 简要回答
CoT(Chain-of-Thought) 思维链的核心思想:让模型把推理的中间步骤显式写出来,而不是直接输出最终答案。
为什么有效:
- LLM 是逐个 token 生成的,每步输出成为下一步的输入
- 把中间思考写出来,等于给了模型更多的「计算步骤」
- 复杂问题拆解成多步,每步错误概率降低,整体准确率提升
两种形式:
- Zero-shot CoT:不加示例,只给触发词("Let's think step by step")
- Few-shot CoT:给包含推理过程的示例,让模型模仿
📝 详细解析
CoT 为什么有效?
直觉解释:
直接问:「一个农场有 5 只鸡,每只鸡每天下 2 个蛋,3 天一共多少个蛋?」
不加 CoT,模型可能直接猜一个数:「12」(错的)。
加 CoT,模型输出:
让我逐步计算:
1. 每只鸡每天下 2 个蛋
2. 5 只鸡每天下 5 × 2 = 10 个蛋
3. 3 天一共 10 × 3 = 30 个蛋
答案是 30原理:
- 每步计算只依赖前一步,降低单步复杂度
- 显式推理过程让错误可定位、可验证
- 多 token 生成增加了「思考时间」
Zero-shot CoT
最简单的实现,不需要准备示例:
java
public String zeroShotCoT(String question) {
String prompt = question + "\n\nLet's think step by step.";
return chatClient.prompt(prompt).call().content();
}中文场景:
请一步步思考并回答:
问题:{question}
思考过程:Few-shot CoT
给模型看几个包含推理过程的示例:
java
public String fewShotCoT(String question) {
String prompt = """
以下是几个示例,请按照相同的推理方式回答问题:
示例1:
问题:一个长方形长 5 米,宽 3 米,面积是多少?
回答:长方形的面积 = 长 × 宽 = 5 × 3 = 15 平方米。答案是 15。
示例2:
问题:小明有 15 元,买了 3 支笔,每支 4 元,还剩多少钱?
回答:
1. 买笔花费:3 × 4 = 12 元
2. 剩余:15 - 12 = 3 元
答案是 3 元。
现在请回答:
问题:%s
回答:
""".formatted(question);
return chatClient.prompt(prompt).call().content();
}Self-Consistency CoT
进阶技巧:让模型生成多条推理路径,投票选最一致的答案。
java
public String selfConsistencyCoT(String question, int samples) {
List<String> answers = new ArrayList<>();
// 生成多个答案
for (int i = 0; i < samples; i++) {
// 用 temperature=0.7 引入随机性
String answer = chatClient.prompt(question)
.options(ChatOptions.builder().temperature(0.7).build())
.call()
.content();
answers.add(extractFinalAnswer(answer));
}
// 投票选最常见的答案
return answers.stream()
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
.entrySet().stream()
.max(Map.Entry.comparingByValue())
.map(Map.Entry::getKey)
.orElse("无法确定");
}Tree of Thoughts(ToT):CoT 的进阶
CoT 是「一条路走到底」,ToT 是「多路探索,择优前进」:
问题
|
┌───────┼───────┐
↓ ↓ ↓
Thought1 Thought2 Thought3
| | |
评估得分 评估得分 评估得分
| | |
└───────┬───────┘
↓
选最优继续展开...java
// ToT 的简化实现
public String treeOfThoughts(String problem, int breadth, int depth) {
List<String> currentThoughts = List.of(problem);
for (int d = 0; d < depth; d++) {
List<String> candidates = new ArrayList<>();
// 从每个当前状态展开多个思路
for (String thought : currentThoughts) {
candidates.addAll(generateThoughts(thought, breadth));
}
// 评估并选择最优的继续
currentThoughts = evaluateAndSelect(candidates, breadth);
}
return selectFinalAnswer(currentThoughts);
}CoT 的适用场景
| 场景 | 是否适合 CoT | 原因 |
|---|---|---|
| 数学计算 | ✅ | 需要多步推导 |
| 逻辑推理 | ✅ | 需要中间步骤验证 |
| 代码生成 | ✅ | 需要分解任务步骤 |
| 事实问答 | ❌ | 不需要推理,直接回答 |
| 情感分析 | ❌ | 直觉判断,不需要思考过程 |
| 创意写作 | ❌ | 发散性思维,不需要固定步骤 |
Spring AI 中的 CoT 集成
java
@Component
public class CoTAdvisor implements CallAdvisor {
@Override
public AdvisedRequest adviseBefore(AdvisedRequest request, Map<String, Object> context) {
String original = request.userText();
// 判断是否需要 CoT(复杂问题才需要)
if (isComplexQuestion(original)) {
String enhanced = original + "\n\n请一步步思考:";
return AdvisedRequest.from(request)
.withUserText(enhanced)
.build();
}
return request;
}
private boolean isComplexQuestion(String question) {
// 启发式规则:包含数字、计算、比较、推理关键词
return question.contains("多少") ||
question.contains("计算") ||
question.contains("为什么");
}
}🎯 面试总结
CoT 的核心价值:显式推理过程 → 降低单步复杂度 → 提高整体准确率。
三种形式:
- Zero-shot:加触发词("Let's think step by step")
- Few-shot:给包含推理的示例
- Self-Consistency:多路径生成,投票选答案
进阶:ToT(Tree of Thoughts)多路探索,择优前进。
面试时要能说出 CoT 为什么有效(多 token 生成 = 更多计算步骤)和 适用场景(复杂推理任务)。
330. 在 AI 智能体项目中,你如何解决 Agent Loop 可能出现的死循环问题?
👔面试官:Agent 执行过程中可能出现死循环,你怎么解决?
🙋♂️我:设置一个最大步数限制,超过就停止。
👔面试官:最大步数是最后的兜底手段。怎么在早期就检测到循环?怎么区分「正常多步执行」和「死循环」?
🙋♂️我:……可能看看是否一直在重复同样的动作?
死循环是 Agent 系统的经典问题,下面把完整的防护机制讲清楚。
💡 简要回答
Agent Loop 死循环的防护分三层:
- 预防层:工具描述精确化,减少模型选错工具的概率
- 检测层:重复动作检测、状态哈希比对、无进展检测
- 兜底层:最大步数限制、超时控制
📝 详细解析
死循环的典型场景
场景1 - 工具选择错误:
模型想查天气,但调用了计算器工具 → 返回错误 → 模型再试 → 还是错 → 循环
场景2 - 参数错误:
模型调用搜索,query 参数为空 → 返回无结果 → 模型再搜 → 还是空 → 循环
场景3 - 循环依赖:
Step1: 搜索「A 公司的 CEO」→ 得到「张三"
Step2: 搜索「张三的公司」→ 得到「A 公司"
Step3: 再次搜索「A 公司的 CEO」→ 死循环
场景4 - 目标模糊:
用户说「帮我整理一下资料」→ 模型不知道具体要做什么 → 反复尝试不同工具防护机制实现
1. 最大步数限制(最后的兜底)
java
public class SafeAgentExecutor {
private static final int MAX_STEPS = 15;
public ExecutionResult execute(Task task) {
for (int step = 0; step < MAX_STEPS; step++) {
StepResult result = executeStep(task);
if (result.isComplete()) {
return ExecutionResult.success(result.getOutput());
}
}
return ExecutionResult.failure("达到最大步数限制");
}
}2. 重复动作检测
java
public class LoopDetector {
private final Set<String> executedActions = new HashSet<>();
private final List<String> actionHistory = new ArrayList<>();
public boolean isLooping(String action) {
String actionHash = hashAction(action);
// 检测完全重复的动作
if (executedActions.contains(actionHash)) {
return true;
}
// 检测循环模式(A→B→A→B)
actionHistory.add(action);
if (actionHistory.size() >= 4) {
String last4 = String.join("->",
actionHistory.subList(actionHistory.size() - 4, actionHistory.size()));
if (last4.matches("(.+)->(.+)->\\1->\\2")) {
return true; // 检测到 A→B→A→B 模式
}
}
executedActions.add(actionHash);
return false;
}
}3. 无进展检测
java
public class ProgressTracker {
private String lastObservation = "";
private int noProgressCount = 0;
public boolean hasProgress(String newObservation) {
// 对比本次和上次观察结果,看是否有新信息
double similarity = calculateSimilarity(lastObservation, newObservation);
if (similarity > 0.9) { // 相似度超过 90%,认为无进展
noProgressCount++;
if (noProgressCount >= 3) {
return false; // 连续 3 步无进展
}
} else {
noProgressCount = 0; // 有进展,重置计数
}
lastObservation = newObservation;
return true;
}
}4. 目标偏离检测
java
public class GoalDriftDetector {
private final String originalGoal;
private final ChatClient chatClient;
public boolean isOnTrack(String currentThought) {
String prompt = """
原始目标:%s
当前思考:%s
请判断当前思考是否偏离了原始目标。
只回答"是"或"否":
""".formatted(originalGoal, currentThought);
String result = chatClient.prompt(prompt).call().content().trim();
return result.contains("否"); // 返回 true 表示未偏离
}
}5. 工具描述的精确化(预防)
java
// ❌ 差的描述
@Tool(description = "查询数据")
public String query(String keyword) { ... }
// ✅ 好的描述
@Tool(description = """
搜索互联网信息。
使用场景:需要获取实时信息、新闻、百科知识时。
参数 keyword: 搜索关键词,不能为空。
注意:不要用于查询本地数据库内容。
""")
public String searchWeb(@ToolParam(description = "搜索关键词,2-10个字") String keyword) { ... }完整的 Safe Agent Loop
java
@Component
public class SafeAgentExecutor {
private static final int MAX_STEPS = 15;
private static final int MAX_TIME_SECONDS = 60;
@Autowired private LoopDetector loopDetector;
@Autowired private ProgressTracker progressTracker;
@Autowired private GoalDriftDetector goalDriftDetector;
public ExecutionResult safeExecute(Task task) {
long startTime = System.currentTimeMillis();
for (int step = 0; step < MAX_STEPS; step++) {
// 1. 超时检查
if (System.currentTimeMillis() - startTime > MAX_TIME_SECONDS * 1000) {
return ExecutionResult.failure("执行超时");
}
// 2. 获取模型决策
AgentDecision decision = getDecision(task);
// 3. 重复动作检查
if (loopDetector.isLooping(decision.getAction())) {
return ExecutionResult.failure("检测到循环执行");
}
// 4. 目标偏离检查
if (!goalDriftDetector.isOnTrack(decision.getThought())) {
return ExecutionResult.failure("执行偏离目标");
}
// 5. 执行动作
String observation = executeAction(decision.getAction());
// 6. 进展检查
if (!progressTracker.hasProgress(observation)) {
return ExecutionResult.failure("执行无进展");
}
// 7. 完成检查
if (isComplete(observation)) {
return ExecutionResult.success(observation);
}
}
return ExecutionResult.failure("达到最大步数限制");
}
}用户友好的退出策略
当检测到死循环时,不应该直接报错,而是:
java
public ExecutionResult handleFailure(String reason, Task task) {
// 1. 记录失败原因
log.warn("Agent 执行异常: {}, 任务: {}", reason, task.getId());
// 2. 尝试降级处理
if (canFallback(task)) {
return executeFallback(task); // 切换到简单策略重试
}
// 3. 返回已收集的部分结果
String partialResult = progressTracker.getCollectedInfo();
return ExecutionResult.partialSuccess(partialResult,
"执行过程中遇到" + reason + ",以下是已收集的信息:");
}🎯 面试总结
Agent 死循环防护的三层体系:
预防层:
- 工具描述精确化,减少模型误选
- Prompt 中明确禁止重复调用
检测层:
- 重复动作检测(哈希比对)
- 循环模式检测(A→B→A→B)
- 无进展检测(Observation 相似度)
- 目标偏离检测(LLM 判断)
兜底层:
- 最大步数限制(15-20 步)
- 超时控制(60 秒)
面试时要能说出 至少三种检测机制(重复、无进展、循环模式)和 优雅的退出策略(降级处理、返回部分结果)。