大家好,又到了技术分享时间。这段时间一直在关注 AI 应用工程化落地,尤其是“AI 软件工厂”这个概念被频繁提及。很多团队已经不再满足于写几个 Prompt、调一下 API,而是开始认真思考:AI 应用能不能像传统软件一样,用统一的规范、标准的设计模式、流水线化的研发流程来构建?
这一期直播的核心主题正好落在了这个交叉点上:AI 软件工厂里的设计模式。我们聊了很多工程实践层面的问题,比如多 Agent 系统怎么编排、主从模式是不是把子 Agent 当成一种特殊的 Tool 来调用、状态机在设计 AI 业务流程时有多大价值,以及经典的 Java、C++ 设计模式还有没有参考意义。
这篇文章会把直播里的核心思路整理成一套可落地的学习框架,既有概念拆解,也有工程案例和代码思路。适合正在做 AI 应用开发、智能体设计、大模型平台建设的同学,也适合那些从传统后端转 AI 方向、想建立系统化认知的开发者。
1. AI 软件工厂到底是什么
1.1 从软件工厂到 AI 软件工厂
传统软件工程里,我们谈“软件工厂”,想的往往是一套标准化、模块化、可复用的生产体系:统一的技术栈、统一的代码规范、统一的构建发布流程。核心思想是把软件研发从手工作坊模式,变成可管理的工业流水线模式。
AI 软件工厂延续了这个思想,但目标对象不再是普通业务代码,而是包含大模型能力的智能应用。它需要考虑几个新东西:
- 模型链路如何管理(模型选型、Prompt、上下文、微调)。
- Agent 如何编排(单 Agent、多 Agent、主从协作)。
- 工具如何使用(Function Call、外部 API、内部服务)。
- 结果如何评测与回归(模型输出不像代码那样有确定性)。
- 安全与成本如何控制(Token 消耗、权限边界、内容合规)。
换句话说,AI 软件工厂解决的不只是“怎么写出一个调用大模型的接口”,而是“怎么稳定、高效、可控地生产和维护一批 AI 应用”。
1.2 为什么需要设计模式
设计模式是前人总结的、在特定场景下反复被验证的解决方案模板。在传统后端开发中,单例、工厂、策略、观察者这些模式帮我们解决了大量重复设计问题。
到了 AI 应用开发里,设计模式依然有效,但形态发生了变化:
- 经典的策略模式,可以对应不同模型的切换。
- 经典的工厂模式,可以对应不同 Agent 的创建。
- 经典的责任链模式,可以对应 Prompt 预处理、模型调用、结果后处理的流水线。
- 经典的状态机模式,可以对应多轮对话中的业务流程流转。
- 新兴的主从 Agent 模式,则是把控制逻辑和任务执行逻辑分离。
所以,这一期直播的核心观点是:不要把 AI 应用开发当成一种全新的、无规律可循的魔法,而要用软件工程的方法论去约束它。设计模式恰恰是连接传统工程经验和 AI 工程实践的桥梁。
2. AI 软件工厂中的设计模式全景
2.1 经典设计模式的 AI 化复用
先看经典设计模式在 AI 场景中的对应关系。为了方便理解,我用表格整理如下:
| 经典模式 | 传统场景 | AI 场景示例 |
|---|---|---|
| 工厂模式 | 创建复杂对象 | 根据配置创建不同类型的 Agent、LLM 客户端 |
| 策略模式 | 多算法替换 | 切换不同大模型或不同 Prompt 策略 |
| 模板方法模式 | 固定流程骨架 | 定义“接收意图→检索知识→生成回复→校验输出”的固定流程 |
| 责任链模式 | 多处理器串行处理 | Prompt 前置处理、敏感信息过滤、模型调用、后置格式化 |
| 观察者模式 | 状态变更通知 | Agent 执行状态上报给监控系统 |
| 状态机模式 | 状态复杂流转 | 多轮对话流程、审批流程、任务状态流转 |
| 代理模式 | 为对象提供访问控制 | 对 LLM 调用做缓存、限流、日志增强 |
| 组合模式 | 树形结构处理 | 复杂任务拆分为子任务树,多 Agent 分层执行 |
这些不是生搬硬套,而是解决真实问题的思路迁移。比如代理模式,在 AI 应用里非常实用:你不想每次请求都直接打大模型 API,可以先走一层缓存代理,命中了就返回缓存,没命中再调用模型。
2.2 新兴的 AI Agent 设计模式
除了经典模式,AI 原生场景下也沉淀出了一些新的设计模式,比如:
- ReAct 模式:推理 + 行动交替进行。
- Plan-and-Execute 模式:先做任务规划,再逐个执行。
- Multi-Agent 协作模式:多个 Agent 分工协作。
- 主从 Agent 模式:一个主控 Agent 管理多个子 Agent。
- Tool-as-Agent 模式:把子 Agent 包装成 Tool,由主 Agent 调度。
这里重点说一下主从模式和 Tool 调用的关系。
直播里有个观点很到位:本质上可以把 Sub-Agent 看作一种另类的 Tool 进行调用。什么意思呢?
传统 Function Call 里,Tool 是一个函数或 API,输入是参数,输出是结果。而一个子 Agent,也可以被包装成类似的接口:输入一个任务描述,输出一个处理结果。这时,子 Agent 对外暴露的“接口”就是自然语言。主 Agent 不需要关心子 Agent 内部用了什么模型、走了什么链路,只需要像调用一个工具一样调用它。
这种设计的价值在于:
- 主 Agent 的控制逻辑更简单,不需要理解每个子任务的内部实现。
- 子 Agent 可以独立迭代、独立测试。
- 复用性变强,同一个子 Agent 可以被不同的主 Agent 调用。
但代价也很明显:语言接口不像代码接口那样稳定。子 Agent 偶尔会理解错指令、输出格式不稳定,所以需要引入结构化输出、评测机制和容错机制。
3. 环境准备与基础工程结构
在开始写代码之前,先明确一下技术选型。因为 AI 领域版本迭代非常快,不建议直接照抄某个固定版本,这里我给出一个相对通用的工程思路。
3.1 技术栈建议
- 语言:Java 17 或 Python 3.10 以上,看你团队的技术基础。
- AI 框架:Spring AI、LangChain4j 或 LangChain。Java 团队推荐前两者,Python 团队用 LangChain 生态更方便。
- 模型接入:OpenAI 兼容接口,或国内云厂商的大模型 API。
- 编排工具:Spring AI 的 ChatClient、LangChain4j 的 AiServices。
- 存储:Redis(会话缓存)、关系型数据库或向量数据库。
- 构建工具:Maven 或 Gradle。
这里要特别说明一下:不同框架的 API 差异较大,本文示例以 Spring AI 的思路为主,但具体类名和方法需要根据你引入的版本调整。核心是理解设计思路,而不是死记 API。
3.2 一个最小的 AI 软件工厂项目结构
我习惯把 AI 工程代码按职责拆成下面几层:
ai-factory-demo ├── pom.xml ├── src/main/java/com/example/aifactory │ ├── AiFactoryApplication.java │ ├── controller │ │ └── ChatController.java │ ├── agent │ │ ├── MasterAgent.java │ │ ├── SubAgent.java │ │ └── AgentFactory.java │ ├── tool │ │ ├── ToolRegistry.java │ │ └── WeatherTool.java │ ├── llm │ │ ├── LlmClient.java │ │ └── LlmClientFactory.java │ ├── strategy │ │ └── PromptStrategy.java │ ├── state │ │ └── TaskStateMachine.java │ └── common │ └── Result.java └── src/main/resources └── application.yml这里面可以很清晰地看到设计模式的影子:
AgentFactory对应工厂模式,负责创建不同类型的 Agent。LlmClientFactory对应工厂模式 + 策略模式,负责创建不同模型的客户端。ToolRegistry对应注册器模式,统一管理所有可调用工具。TaskStateMachine对应状态机模式,管理任务流转。MasterAgent和SubAgent对应主从协作模式。
先动手搭一个最简骨架,后面逐步填充。
4. 核心设计模式实战拆解
4.1 用工厂模式管理 Agent 创建
AI 应用里,Agent 类型会越来越多:写作助手、代码审查助手、数据抽取助手、客服助手……如果直接在业务代码里挨个 new,后期很难维护。工厂模式正好派上用场。
先定义一个 Agent 接口和一个基础实现:
// 文件路径:src/main/java/com/example/aifactory/agent/Agent.java public interface Agent { String getName(); String execute(String task); }再定义一个工厂,根据名称创建对应的 Agent:
// 文件路径:src/main/java/com/example/aifactory/agent/AgentFactory.java @Component public class AgentFactory { private final Map<String, Agent> agentMap = new ConcurrentHashMap<>(); public AgentFactory(List<Agent> agents) { for (Agent agent : agents) { agentMap.put(agent.getName(), agent); } } public Agent getAgent(String name) { Agent agent = agentMap.get(name); if (agent == null) { throw new IllegalArgumentException("未找到 Agent: " + name); } return agent; } }这里其实是“注册式工厂”,Spring 启动时会把所有 Agent 实现类自动收集进来。调用方不需要知道 Agent 的具体实现类,只需要传入名称即可。
核心好处:
- 新增 Agent 不需要改调用方代码,只需要新增一个实现类。
- Agent 之间的依赖关系由 Spring 容器统一管理。
- 后续可以通过配置文件动态决定启用哪些 Agent。
4.2 用策略模式切换大模型和 Prompt
策略模式在 AI 工程里非常常见。你的项目可能同时接入了多个大模型,不同模型擅长不同任务,或者你希望同一个模型走不同的 Prompt 模板。
先定义一个 LLM 调用策略接口:
// 文件路径:src/main/java/com/example/aifactory/strategy/LlmStrategy.java public interface LlmStrategy { String call(String prompt); String modelName(); }再实现两个策略:
// 文件路径:src/main/java/com/example/aifactory/strategy/FastModelStrategy.java @Component public class FastModelStrategy implements LlmStrategy { @Override public String call(String prompt) { // 调用相对轻量的模型 return "Fast model result for: " + prompt; } @Override public String modelName() { return "fast-model"; } }// 文件路径:src/main/java/com/example/aifactory/strategy/ReasoningModelStrategy.java @Component public class ReasoningModelStrategy implements LlmStrategy { @Override public String call(String prompt) { // 调用推理能力更强的模型 return "Reasoning model result for: " + prompt; } @Override public String modelName() { return "reasoning-model"; } }然后通过一个上下文类来动态选择策略:
// 文件路径:src/main/java/com/example/aifactory/strategy/LlmContext.java @Component public class LlmContext { private final Map<String, LlmStrategy> strategyMap = new ConcurrentHashMap<>(); public LlmContext(List<LlmStrategy> strategies) { for (LlmStrategy strategy : strategies) { strategyMap.put(strategy.modelName(), strategy); } } public String execute(String modelName, String prompt) { LlmStrategy strategy = strategyMap.get(modelName); if (strategy == null) { throw new IllegalArgumentException("不支持的模型: " + modelName); } return strategy.call(prompt); } }实际场景里,你还可以把 Prompt 模板也做成策略的一部分,比如同一个模型,在编写代码时使用“代码专家”模板,在写文案时使用“营销文案”模板。这样可以把模型选择、Prompt 选择都收敛到策略层,业务层只关心“我要什么结果”,不关心“用什么模型怎么拼 Prompt”。
4.3 用代理模式给 LLM 调用增加缓存与限流
直接调用大模型 API 有三个问题:慢、贵、可能失败。代理模式可以在不改变调用接口的前提下,给模型调用增加缓存、限流、重试等能力。
先定义一个简单的 LLM 客户端接口:
// 文件路径:src/main/java/com/example/aifactory/llm/LlmClient.java public interface LlmClient { String chat(String prompt); }定义一个具体的客户端实现,真正调用模型 API:
// 文件路径:src/main/java/com/example/aifactory/llm/OpenAiLlmClient.java @Component("openAiLlmClient") public class OpenAiLlmClient implements LlmClient { @Override public String chat(String prompt) { // 这里替换为真实的大模型 API 调用 return "模拟模型返回结果"; } }再定义一个带缓存能力的代理客户端:
// 文件路径:src/main/java/com/example/aifactory/llm/CachingLlmClient.java @Component("cachingLlmClient") public class CachingLlmClient implements LlmClient { private final LlmClient delegate; private final Cache<String, String> cache = Caffeine.newBuilder() .expireAfterWrite(10, TimeUnit.MINUTES) .maximumSize(1000) .build(); public CachingLlmClient(@Qualifier("openAiLlmClient") LlmClient delegate) { this.delegate = delegate; } @Override public String chat(String prompt) { String cached = cache.getIfPresent(prompt); if (cached != null) { return cached; } String result = delegate.chat(prompt); cache.put(prompt, result); return result; } }这里用到了 Caffeine 作为本地缓存。实际生产环境可以换成 Redis,并且注意缓存 key 的设计。如果 Prompt 很长,建议对 Prompt 做哈希后作为 key,避免 Redis 中存过大的 key。
代理模式的好处是透明:调用方还是拿LlmClient接口,感知不到缓存逻辑的存在。后续想升级成带限流、带熔断的代理,只需要再加一层代理类即可。
4.4 用状态机模式管理任务流转
AI Agent 执行任务时,经常出现多步骤状态切换。比如编码助手任务可能经历:
- 待执行
- 分析中
- 生成代码中
- 检查中
- 已完成
- 失败
如果不用状态机,你会在代码里写很多 if-else 判断,而且很容易漏掉异常路径。
这里给出一个轻量状态机示例。
先定义状态枚举和事件枚举:
// 文件路径:src/main/java/com/example/aifactory/state/TaskState.java public enum TaskState { PENDING, ANALYZING, GENERATING, REVIEWING, COMPLETED, FAILED }// 文件路径:src/main/java/com/example/aifactory/state/TaskEvent.java public enum TaskEvent { START, ANALYZE_DONE, GENERATE_DONE, REVIEW_DONE, FAIL }再定义状态流转规则和状态机:
// 文件路径:src/main/java/com/example/aifactory/state/TaskStateMachine.java @Component public class TaskStateMachine { private final Map<TaskState, Map<TaskEvent, TaskState>> transitions = new HashMap<>(); public TaskStateMachine() { // 定义合法流转 transitions.put(TaskState.PENDING, Map.of( TaskEvent.START, TaskState.ANALYZING )); transitions.put(TaskState.ANALYZING, Map.of( TaskEvent.ANALYZE_DONE, TaskState.GENERATING, TaskEvent.FAIL, TaskState.FAILED )); transitions.put(TaskState.GENERATING, Map.of( TaskEvent.GENERATE_DONE, TaskState.REVIEWING, TaskEvent.FAIL, TaskState.FAILED )); transitions.put(TaskState.REVIEWING, Map.of( TaskEvent.REVIEW_DONE, TaskState.COMPLETED, TaskEvent.FAIL, TaskState.FAILED )); } public TaskState next(TaskState current, TaskEvent event) { Map<TaskEvent, TaskState> stateTransitions = transitions.get(current); if (stateTransitions == null) { throw new IllegalStateException("当前状态不允许流转: " + current); } TaskState nextState = stateTransitions.get(event); if (nextState == null) { throw new IllegalStateException("当前状态 " + current + " 不响应事件 " + event); } return nextState; } }这样,Agent 的主循环就非常清晰了:
TaskState current = TaskState.PENDING; current = stateMachine.next(current, TaskEvent.START); // 执行分析... current = stateMachine.next(current, TaskEvent.ANALYZE_DONE); // 生成内容... current = stateMachine.next(current, TaskEvent.GENERATE_DONE); // 评审... current = stateMachine.next(current, TaskEvent.REVIEW_DONE);状态机带来的最大收益是可维护性和可测试性。每个流转规则都是显式定义的,出了问题可以快速定位;新增状态和事件不需要大范围改动,只需要修改状态机内部配置。
5. 多 Agent 协作设计模式实战
5.1 主从模式:把子 Agent 当成 Tool 来调用
这一期直播里,讨论很热烈的部分就是多 Agent 设计模式,尤其是主从模式。大家达成的共识是:主从模式本质上就是把 Sub-Agent 当作一种特殊的 Tool 进行调用。这个思路能帮我们设计出清晰、可扩展的多 Agent 系统。
先看主 Agent 的设计思路:
// 文件路径:src/main/java/com/example/aifactory/agent/MasterAgent.java @Component public class MasterAgent { private final AgentFactory agentFactory; public MasterAgent(AgentFactory agentFactory) { this.agentFactory = agentFactory; } public String handleTask(String task) { // 第一步:分析任务,决定由哪个子 Agent 处理 String targetAgentName = decideAgent(task); // 第二步:像调用工具一样调用子 Agent Agent subAgent = agentFactory.getAgent(targetAgentName); String result = subAgent.execute(task); // 第三步:对结果进行汇总校验 return validateAndWrap(result); } private String decideAgent(String task) { // 真实场景这里可以由 LLM 根据意图识别来决定 if (task.contains("代码")) { return "code-review-agent"; } if (task.contains("文案")) { return "copywriting-agent"; } return "default-agent"; } private String validateAndWrap(String result) { // 校验子 Agent 输出是否满足要求 return "校验通过,最终结果:" + result; } }这个写法里,主 Agent 做的事情主要就是:
- 意图判断。
- 选择子 Agent。
- 调用子 Agent。
- 校验结果。
把子 Agent 设计成 Tool 风格后,主 Agent 不再需要理解子 Agent 内部复杂的 Prompt 和模型配置,两者之间的契约就是一句话的任务描述和一个结构化的输出结果。
再看子 Agent 的 Tool 化封装思路:
// 文件路径:src/main/java/com/example/aifactory/agent/ToolStyleSubAgent.java @Component("code-review-agent") public class ToolStyleSubAgent implements Agent { @Override public String getName() { return "code-review-agent"; } @Override public String execute(String task) { // 内部执行代码审查逻辑 // 可以包括:读取代码、规则匹配、LLM 分析、生成审查意见 return "发现 3 个潜在问题:1... 2... 3..."; } }从外部看,子 Agent 和普通 Tool 几乎没有区别。主 Agent 调用它,就像调用一个sendEmail()或queryWeather()函数一样简单。
这样的设计有很明显的优势:
- 子 Agent 可以独立开发、独立测试。
- 主 Agent 不关心子 Agent 内部实现,降低耦合。
- 同一个子 Agent 可以被多个主 Agent 复用。
- 将来可以把子 Agent 替换成普通工具函数,只要保持相同的输入输出契约即可。
5.2 编排模式:多个子 Agent 流水线协作
有些任务无法由一个子 Agent 独立完成,需要多个子 Agent 按顺序或并行协作。这就需要有编排能力。
一个常见的流水线场景是“知识库问答 + 内容生成”:
- 检索 Agent 负责从向量数据库检索相关资料。
- 摘要 Agent 负责对检索结果做精简。
- 写作 Agent 负责基于摘要生成最终回答。
代码思路如下:
// 文件路径:src/main/java/com/example/aifactory/agent/WorkflowAgent.java @Component public class WorkflowAgent { private final AgentFactory agentFactory; public WorkflowAgent(AgentFactory agentFactory) { this.agentFactory = agentFactory; } public String process(String question) { Agent retriever = agentFactory.getAgent("retriever-agent"); Agent summarizer = agentFactory.getAgent("summarizer-agent"); Agent writer = agentFactory.getAgent("writer-agent"); String rawMaterials = retriever.execute(question); String summary = summarizer.execute(rawMaterials); return writer.execute(question + ",参考资料:" + summary); } }这种编排模式的本质是数据流驱动:前一个 Agent 的输出成为后一个 Agent 的输入。为了让这个过程更稳定,通常需要:
- 给每个 Agent 的结果设计统一的包装结构,比如包含
code、data、message字段。 - 在关键节点加入格式校验。
- 给每个步骤增加超时和重试机制。
- 将每一步的输入输出记录到日志或数据库中,便于追踪和评测。
5.3 多 Agent 协作中的上下文传递
多 Agent 系统最容易踩的坑是上下文丢失。每个子 Agent 被调用时,如果只拿到一句话任务描述,往往无法产出高质量结果。
实践中有两种做法比较靠谱:
- 显式传参:把上下文信息拼接到任务描述里,比如“以下是检索到的资料,请基于这些资料回答问题”。这种方式简单直接,但要注意上下文长度限制。
- 共享存储:使用 Redis 或向量数据库存储中间态数据,子 Agent 通过任务 ID 读取。这种方式适合复杂任务,但增加了系统复杂度。
我的建议是:简单任务用显式传参,复杂任务用共享存储 + 任务 ID 关联。设计接口时,可以让Agent.execute()接收一个TaskContext对象,里面包含任务 ID、原始输入、历史中间结果等。
// 文件路径:src/main/java/com/example/aifactory/common/TaskContext.java public class TaskContext { private String taskId; private String input; private Map<String, Object> intermediates = new HashMap<>(); // getter/setter 省略 }这样每个子 Agent 都可以从上下文中取到所需信息,也可以把中间结果写回去,链路之间不再只是简单的字符串拼接。
6. 一个可落地的 AI 软件工厂小案例
6.1 案例目标
做一个小而完整的案例:一个智能代码审查助手。它接收一段代码,通过多 Agent 协作完成“基础检查 → 规则审查 → 结果汇总”三个步骤,并把整个流程用设计模式组织起来。
6.2 项目依赖
以 Spring Boot 3 + Spring AI 为例,pom.xml里核心依赖如下:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter</artifactId> <version>按实际版本配置</version> </dependency>如果你使用的是 LangChain4j,则依赖改为:
<dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j</artifactId> <version>按实际版本配置</version> </dependency>6.3 核心代码流程
先定义结果包装类:
// 文件路径:src/main/java/com/example/aifactory/common/Result.java public class Result<T> { private int code; private String message; private T data; public static <T> Result<T> ok(T data) { Result<T> result = new Result<>(); result.code = 200; result.message = "success"; result.data = data; return result; } public static <T> Result<T> error(String message) { Result<T> result = new Result<>(); result.code = 500; result.message = message; return result; } // getter/setter 省略 }定义 Agent 接口:
// 文件路径:src/main/java/com/example/aifactory/agent/Agent.java public interface Agent { String execute(String input); }定义三个子 Agent:
// 文件路径:src/main/java/com/example/aifactory/agent/BasicCheckAgent.java @Component("basic-check-agent") public class BasicCheckAgent implements Agent { @Override public String execute(String code) { // 模拟基础检查:检查代码长度、空指针风险等 return "基础检查通过,代码行数 120 行,未发现空指针风险"; } }// 文件路径:src/main/java/com/example/aifactory/agent/RuleCheckAgent.java @Component("rule-check-agent") public class RuleCheckAgent implements Agent { @Override public String execute(String code) { // 模拟规则审查:检查命名规范、异常处理 return "规则审查发现:方法命名符合规范,但缺少必要的异常处理"; } }// 文件路径:src/main/java/com/example/aifactory/agent/SummaryAgent.java @Component("summary-agent") public class SummaryAgent implements Agent { @Override public String execute(String checkResults) { // 汇总审查结果 return "审查结论:整体代码质量中等,建议补全异常处理逻辑"; } }主 Agent 负责编排:
// 文件路径:src/main/java/com/example/aifactory/agent/CodeReviewAgent.java @Component public class CodeReviewAgent { private final AgentFactory agentFactory; public CodeReviewAgent(AgentFactory agentFactory) { this.agentFactory = agentFactory; } public String review(String code) { Agent basic = agentFactory.getAgent("basic-check-agent"); Agent rule = agentFactory.getAgent("rule-check-agent"); Agent summary = agentFactory.getAgent("summary-agent"); String basicResult = basic.execute(code); String ruleResult = rule.execute(code); String combined = basicResult + "\n" + ruleResult; return summary.execute(combined); } }控制器层接收请求:
// 文件路径:src/main/java/com/example/aifactory/controller/CodeReviewController.java @RestController @RequestMapping("/api/review") public class CodeReviewController { private final CodeReviewAgent codeReviewAgent; public CodeReviewController(CodeReviewAgent codeReviewAgent) { this.codeReviewAgent = codeReviewAgent; } @PostMapping("/code") public Result<String> reviewCode(@RequestBody String code) { String reviewResult = codeReviewAgent.review(code); return Result.ok(reviewResult); } }6.4 运行与验证
启动 Spring Boot 应用后,发送 POST 请求:
curl -X POST http://localhost:8080/api/review/code \ -H "Content-Type: text/plain" \ -d "public class Demo { ... }"预期返回:
{ "code": 200, "message": "success", "data": "审查结论:整体代码质量中等,建议补全异常处理逻辑" }这个示例展示的并不是多么复杂的 AI 能力,而是展示了一种工程化的组织方式。真实项目中,你可以把每个 Agent 里的模拟逻辑替换成大模型调用、规则引擎或静态代码扫描工具,整体结构不需要变。
7. Agent 工具抽象与安全管理
7.1 Tool 的统一注册与发现
多 Agent 系统里,工具会越来越多。如果不做统一管理,主 Agent 的 Prompt 里会塞满各种工具定义,很难维护。
建议设计一个ToolRegistry:
// 文件路径:src/main/java/com/example/aifactory/tool/ToolRegistry.java @Component public class ToolRegistry { private final Map<String, Tool> toolMap = new ConcurrentHashMap<>(); public ToolRegistry(List<Tool> tools) { for (Tool tool : tools) { toolMap.put(tool.getName(), tool); } } public Tool getTool(String name) { return toolMap.get(name); } public List<String> getAllToolNames() { return new ArrayList<>(toolMap.keySet()); } }工具定义接口可以包含名称、描述、参数格式、执行方法。描述信息会被拼进 Prompt,帮助 Agent 判断什么时候该用这个工具。
7.2 工具调用的安全边界
接入了工具之后,安全问题会迅速暴露出来。比如,Agent 被诱导调用一个删除文件的工具,或者把内部 API 暴露给了外部用户。
工程实践中必须坚持几个原则:
- 最小权限原则:给 Agent 的工具权限,应该只覆盖它完成任务所需的最小范围。
- 人工确认重要操作:删除、写库、转账等重要操作,不能由模型单方面决定,必须加入人工确认环节。
- 输入校验与过滤:不能把用户输入直接透传给工具,要做参数白名单校验、路径规范化、长度限制。
- 操作审计:每次工具调用都要记录操作者、参数、结果、时间,便于事后追溯。
- 敏感信息保护:不要将数据库密码、API Key 等敏感信息放进模型可读取的上下文里。
这些边界规则和传统后端安全没有本质区别,只是在 AI 场景下更不可控,所以要更严格。
8. AI 应用的可观测性与评测设计
8.1 日志与追踪
AI 应用的问题定位比传统应用难得多。同一个 Prompt,昨天返回正常,今天可能就变了。所以从第一天开始就要把可观测性纳入工程体系。
需要记录的维度:
- 请求 IP、用户 ID、会话 ID。
- Prompt 原文和模型输出。
- 模型名称、Token 消耗、耗时。
- 工具调用链路和参数。
- 重试次数、缓存命中情况。
建议为每个请求生成一个traceId,贯穿整个调用链。日志格式可以采用 JSON 结构,方便接入 ELK、Loki 或云厂商日志服务。
8.2 离线评测与回归
模型的输出不像代码有确定性的正确结果,所以需要建立评测集,定期跑回归测试。
一个简单的评测指标可以包括:
- 回答是否包含关键知识点。
- 输出的格式是否符合预期。
- 是否出现敏感内容。
- 用户模拟测试的满意度打分。
可以把评测设计成一个独立的 Job,每天定时跑一批测试样例,统计通过率。如果某个配置变更后通过率明显下降,就可以快速发现并回滚。
9. 常见问题与排查思路
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| 模型返回结果很不稳定 | Prompt 缺少结构化约束 | 使用 JSON Schema 或输出格式模板,增加 few-shot 示例 |
| 多 Agent 链路经常失败 | 上下文丢失或格式不匹配 | 引入 TaskContext,统一结果包装结构,增加校验 |
| 调用大模型越来越慢 | 上下文过长、Token 太多 | 加缓存、做摘要压缩、限制上下文长度 |
| 工具被模型误调用 | 工具描述不清晰或 Prompt 引导不足 | 优化工具描述,增加调用条件说明,增加权限拦截 |
| 测试环境正常,生产环境异常 | 模型版本或配置不一致 | 统一模型版本管理,配置走配置中心,避免环境漂移 |
| Token 费用超预期 | 没有缓存、上下文无限增长 | 使用缓存代理模式,控制 Prompt 长度,设置告警 |
9.1 多 Agent 协作非常慢怎么办
这是直播中很多人问的问题。多 Agent 串行调用多个模型,本来就会增加大量延迟。
优化的手法主要有:
- 可并行的子任务并行执行。
- 对子 Agent 结果做缓存。
- 简单任务不拆分成多 Agent,单 Agent 直接完成。
- 用更快的模型处理子任务,用强模型做最终汇总。
9.2 子 Agent 输出不稳定怎么办
本质上,子 Agent 的输入输出是自然语言,不稳定是常态。解决思路有以下几条:
- 定义严格的输出格式,尽量让模型输出结构化内容。
- 增加一轮“格式修正”调用,或者用代码校验并重新请求。
- 为关键场景准备一些 few-shot 示例。
- 如果某个子 Agent 反复失败,考虑用固定代码逻辑代替模型判断。
10. 工程化最佳实践与架构建议
10.1 从传统设计模式中借鉴思想
AI 工程不是从零开始的独立学科,传统设计模式依然是很好的思考工具。看到一个问题时,先别急着写 Prompt,先想想它在传统工程里对应哪个问题:
- 对象创建复杂?用工厂。
- 算法可以替换?用策略。
- 流程固定但步骤有变?用模板方法。
- 请求需要附加能力?用代理和责任链。
- 状态变化多?用状态机。
这种“迁移式思维”能帮助团队快速建立统一的工程语言。
10.2 AI 工程特有的设计原则
除了经典设计模式,AI 软件工厂还有几个特有的设计原则:
- 考虑失败的默认值:模型输出失败时,要回退到什么结果?不要让链路直接崩溃。
- 控制不确定性的范围:能用代码判断的,不要让模型来做决定。比如格式校验、规则判断、权限校验。
- Prompt 即代码:Prompt 要像代码一样走版本管理、评审和发布流程。
- 分阶段引入智能:简单逻辑先用规则引擎,解决不了再上模型,降低成本提高稳定性。
- 接口稳定优先:Agent 之间的接口最好用结构化的数据对象,而不是纯文本拼接。
10.3 推荐的分层架构
一个中等规模的 AI 应用,建议按下面层次组织:
接入层(Controller / API) 业务编排层(Master Agent / Workflow Agent) 能力层(子 Agent / Tool / LLM 客户端) 基础层(模型访问、缓存、日志、配置、存储)接入层负责参数校验、鉴权和限流;业务编排层负责理解用户意图、拆解任务;能力层提供具体的原子能力;基础层是通用支撑。严格分层之后,每一层的职责都很清楚,测试和迭代也更加容易。
10.4 关于配置与发布
AI 应用涉及模型参数、Prompt 模板、工具开关等大量配置。建议把这些配置放入配置中心,配合灰度发布。比如新 Prompt 先在小比例流量上验证,确认无明显问题后再全量放开。如果临时出现问题,第一动作应该是回滚配置,而不是紧急发版。
配置项命名建议遵循统一规范,比如:
agent.code-review.prompt-template=xxx agent.code-review.model=fast-model agent.code-review.enabled=true这样既方便管理,又便于在配置中心里按前缀检索。
11. 从直播延伸到日常实践
这一期直播内容很丰富,如果只记住几个关键词,我建议是:工厂、策略、代理、状态机、主从编排、工具化 Agent、可观测性、评测回归。
设计模式在 AI 软件工厂里的应用不是一层不变的教条。你今天用主从模式组织两个 Agent,明天可能发现其中一个小任务根本不需要 Agent,用一个普通函数更合适;你今天用状态机管理三个状态,明天可能发现状态太细导致代码繁琐,需要合并状态。关键在于把设计模式当成工具箱,而不是枷锁。
接下来可以按这条路线继续深入:
- 先跑通一个单 Agent 的最小应用,理解模型调用的基本流程。
- 再给 Agent 接上工具,理解 Function Call 的交互方式。
- 然后尝试用主从模式组织多个 Agent,体会任务拆解与结果汇总。
- 最后把评测、日志、缓存、状态管理等工程能力补齐。
如果这篇笔记对你有帮助,可以收藏备用,或者在实际项目中按里面的思路先搭一版原型。你在多 Agent 编排或者设计模式落地时踩过哪些坑,也欢迎在评论区分享,下一期直播我们可以继续展开讨论。