Skip to content

十、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 智能体特性的分层。

关键五层:

  1. 接入层:REST/SSE/WebSocket 接口,限流认证
  2. 编排层:ReAct/Plan-and-Execute/多 Agent 调度
  3. 能力层:工具系统、RAG、记忆
  4. 模型层:多模型路由策略
  5. 基础设施层:向量库、缓存、监控

加分点:能说出 多模型路由策略(按任务复杂度选模型)和 编排层的 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 ​

维度ReActPlan-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 → 循环

实现要点:

  1. Prompt 设计:明确定义 Thought/Action/Observation 格式
  2. 工具描述:精确描述每个工具的用途和参数
  3. 循环控制:最大步数限制、重复动作检测、无效动作检测
  4. 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. 检测层:重复动作检测、状态哈希比对、无进展检测
  3. 兜底层:最大步数限制、超时控制

📝 详细解析 ​

死循环的典型场景 ​

场景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 秒)

面试时要能说出 至少三种检测机制(重复、无进展、循环模式)和 优雅的退出策略(降级处理、返回部分结果)。



最后更新2026-04-26
觉得有帮助?把这个链接转给正在求职的朋友 · 用 Ctrl + K 全站搜索其它题