从零构建AI智能体:架构设计与代码实现
想亲手打造会思考、能调用工具的 AI 智能体?本文带你从架构到代码实操。解析 OpenManus 分层架构,用 Java 和 Spring AI,从基类到工具调用层逐步实现。含工具开发、循环检测等实用模块,助你掌握核心技术,拥有专属智能体!
从零构建AI智能体:架构设计与代码实现
引言
在上一篇文章中,我们介绍了AI智能体的概念和核心技术。本文将带领读者实际动手构建一个智能体系统,从架构设计到代码实现,全面解析智能体的开发流程。我们将参考OpenManus开源项目的架构,使用Java和Spring AI框架来构建一个具备工具调用能力的智能体。
OpenManus架构解析
OpenManus采用分层代理架构,通过层层继承实现功能扩展。其核心设计遵循"从整体到局部"的组织方式,实现了高度模块化和可扩展性。
核心架构层次
OpenManus通过继承关系构建了清晰的功能层次结构:
- BaseAgent:提供基础的状态管理和执行循环
- ReActAgent:实现"思考-行动-观察"循环模式
- ToolCallAgent:集成工具调用能力
- Manus:最终实现的智能体实例,集成多种专用工具
这种分层架构使得开发者可以在不同层次扩展或定制智能体功能,满足不同应用场景的需求。
架构UML类图
下面是OpenManus架构的UML类图,清晰展示了系统各组件的关系:
组件交互流程
以下是智能体系统各组件的交互流程图:
构建智能体:步骤详解
步骤一:定义数据模型
首先,我们需要定义智能体的状态模型,用于控制执行流程:
/**
* 代理执行状态枚举
*/
public enum AgentState {
IDLE, // 空闲状态
RUNNING, // 运行中状态
FINISHED, // 已完成状态
ERROR // 错误状态
}
步骤二:实现BaseAgent基类
BaseAgent作为所有智能体的基类,负责管理状态和执行流程:
/**
* 代理基类,管理状态和执行流程
*/
@Data
@Slf4j
public abstract class BaseAgent {
// 核心属性
private String name;
private String systemPrompt;
private String nextStepPrompt;
// 状态与执行控制
private AgentState state = AgentState.IDLE;
private int currentStep = 0;
private int maxSteps = 10;
// LLM与记忆
private ChatClient chatClient;
private List<Message> messageList = new ArrayList<>();
/**
* 运行代理
*/
public String run(String userPrompt) {
// 校验
if (state != AgentState.IDLE || StrUtil.isBlank(userPrompt)) {
throw new RuntimeException("无法执行: " + (state != AgentState.IDLE ? "状态错误" : "空提示词"));
}
// 执行
state = AgentState.RUNNING;
messageList.add(new UserMessage(userPrompt));
List<String> results = new ArrayList<>();
try {
// 步骤循环
for (int i = 0; i < maxSteps && state != AgentState.FINISHED; i++) {
currentStep = i + 1;
log.info("执行步骤 {}/{}", currentStep, maxSteps);
String stepResult = step();
results.add("Step " + currentStep + ": " + stepResult);
}
// 检查终止条件
if (currentStep >= maxSteps) {
state = AgentState.FINISHED;
results.add("终止: 达到最大步骤 (" + maxSteps + ")");
}
return String.join("\n", results);
} catch (Exception e) {
state = AgentState.ERROR;
log.error("执行错误", e);
return "执行错误: " + e.getMessage();
} finally {
cleanup();
}
}
/**
* 运行代理(流式输出)
*/
public SseEmitter runStream(String userPrompt) {
SseEmitter emitter = new SseEmitter(300000L); // 5分钟超时
CompletableFuture.runAsync(() -> {
try {
// 校验
if (state != AgentState.IDLE || StrUtil.isBlank(userPrompt)) {
String error = "错误: " + (state != AgentState.IDLE ? "无法从状态运行: " + state : "空提示词");
emitter.send(error);
emitter.complete();
return;
}
// 执行
state = AgentState.RUNNING;
messageList.add(new UserMessage(userPrompt));
// 步骤循环
for (int i = 0; i < maxSteps && state != AgentState.FINISHED; i++) {
currentStep = i + 1;
log.info("执行步骤 {}/{}", currentStep, maxSteps);
String stepResult = step();
String result = "Step " + currentStep + ": " + stepResult;
emitter.send(result);
}
// 检查终止条件
if (currentStep >= maxSteps) {
state = AgentState.FINISHED;
emitter.send("执行结束: 达到最大步骤 (" + maxSteps + ")");
}
emitter.complete();
} catch (Exception e) {
state = AgentState.ERROR;
log.error("执行错误", e);
try {
emitter.send("执行错误: " + e.getMessage());
emitter.complete();
} catch (IOException ex) {
emitter.completeWithError(ex);
}
} finally {
cleanup();
}
});
// 事件处理
emitter.onTimeout(() -> {
state = AgentState.ERROR;
cleanup();
log.warn("SSE连接超时");
});
emitter.onCompletion(() -> {
if (state == AgentState.RUNNING) {
state = AgentState.FINISHED;
}
cleanup();
log.info("SSE连接完成");
});
return emitter;
}
/**
* 定义单个执行步骤
*/
public abstract String step();
/**
* 清理资源
*/
protected void cleanup() {
// 子类可重写
}
}
BaseAgent类采用模板方法模式,定义了执行流程的骨架,但将具体的step方法留给子类实现。核心功能包括:
- 状态管理:通过AgentState枚举控制执行状态
- 执行循环:实现了step-by-step的迭代执行方式
- 异常处理:捕获并处理执行过程中的异常
- 资源清理:提供cleanup方法用于释放资源
步骤三:实现ReActAgent类
ReActAgent继承自BaseAgent,实现了思考-行动循环模式:
/**
* ReAct模式代理(思考-行动循环)
*/
@EqualsAndHashCode(callSuper = true)
@Data
@Slf4j
public abstract class ReActAgent extends BaseAgent {
/**
* 思考决策阶段
* @return 是否需要执行行动
*/
public abstract boolean think();
/**
* 执行行动阶段
* @return 行动执行结果
*/
public abstract String act();
/**
* 单步执行思考和行动
*/
@Override
public String step() {
try {
// 先思考后行动
boolean shouldAct = think();
if (!shouldAct) {
return "思考完成 - 无需行动";
}
return act();
} catch (Exception e) {
log.error("步骤执行失败", e);
return "步骤执行失败: " + e.getMessage();
}
}
}
ReActAgent类将BaseAgent的step方法分解为think和act两个抽象方法,体现了ReAct模式的核心思想:
- think:思考决策阶段,返回布尔值决定是否需要执行行动
- act:行动执行阶段,实际执行操作并返回结果
这种设计使得智能体的推理和行动逻辑分离,便于定制和扩展。
步骤四:实现ToolCallAgent类
ToolCallAgent继承自ReActAgent,具体实现了工具调用能力:
/**
* 工具调用代理,实现think和act方法处理工具调用
*/
@EqualsAndHashCode(callSuper = true)
@Data
@Slf4j
public class ToolCallAgent extends ReActAgent {
private final ToolCallback[] availableTools;
private ChatResponse toolCallChatResponse;
private final ToolCallingManager toolCallingManager;
private final ChatOptions chatOptions;
public ToolCallAgent(ToolCallback[] availableTools) {
super();
this.availableTools = availableTools;
this.toolCallingManager = ToolCallingManager.builder().build();
// 禁用Spring AI内置工具调用,自行管理
this.chatOptions = DashScopeChatOptions.builder()
.withProxyToolCalls(true)
.build();
}
/**
* 思考阶段:分析当前状态并确定要调用的工具
*/
@Override
public boolean think() {
// 添加下一步提示词
if (StrUtil.isNotBlank(getNextStepPrompt())) {
getMessageList().add(new UserMessage(getNextStepPrompt()));
}
// 调用AI获取工具选择
try {
Prompt prompt = new Prompt(getMessageList(), this.chatOptions);
ChatResponse chatResponse = getChatClient().prompt(prompt)
.system(getSystemPrompt())
.tools(availableTools)
.call()
.chatResponse();
this.toolCallChatResponse = chatResponse;
// 解析响应
AssistantMessage assistantMessage = chatResponse.getResult().getOutput();
List<AssistantMessage.ToolCall> toolCallList = assistantMessage.getToolCalls();
// 记录选择
String result = assistantMessage.getText();
log.info("{}的思考: {}", getName(), result);
log.info("{}选择了{}个工具", getName(), toolCallList.size());
if (toolCallList.size() > 0) {
String toolCallInfo = toolCallList.stream()
.map(tc -> String.format("工具: %s, 参数: %s", tc.name(), tc.arguments()))
.collect(Collectors.joining("\n"));
log.info(toolCallInfo);
}
// 没有工具调用时记录助手消息
if (toolCallList.isEmpty()) {
getMessageList().add(assistantMessage);
return false;
}
return true;
} catch (Exception e) {
log.error("{}思考过程错误: {}", getName(), e.getMessage());
getMessageList().add(new AssistantMessage("处理错误: " + e.getMessage()));
return false;
}
}
/**
* 行动阶段:执行选定的工具
*/
@Override
public String act() {
if (!toolCallChatResponse.hasToolCalls()) {
return "没有工具需要调用";
}
// 执行工具调用
Prompt prompt = new Prompt(getMessageList(), this.chatOptions);
ToolExecutionResult result = toolCallingManager.executeToolCalls(prompt, toolCallChatResponse);
// 更新消息上下文
setMessageList(result.conversationHistory());
ToolResponseMessage response = (ToolResponseMessage) CollUtil.getLast(result.conversationHistory());
// 检查是否调用了终止工具
boolean terminated = response.getResponses().stream()
.anyMatch(r -> r.name().equals("doTerminate"));
if (terminated) {
setState(AgentState.FINISHED);
}
// 格式化结果
String results = response.getResponses().stream()
.map(r -> "工具[" + r.name() + "]结果: " + r.responseData())
.collect(Collectors.joining("\n"));
log.info(results);
return results;
}
}
ToolCallAgent类实现了think和act方法,是智能体与工具系统交互的核心:
- think方法:使用LLM决定调用哪些工具
- act方法:执行工具调用并处理结果
- 工具管理:管理可用工具集合,实现工具选择和执行
下面是工具调用流程的示意图:
步骤五:实现最终智能体LenManus
LenManus继承自ToolCallAgent,是实际可用的智能体实例:
/**
* 超级智能体,具备自主规划能力
*/
@Component
public class LenManus extends ToolCallAgent {
public LenManus(ToolCallback[] allTools, ChatModel dashscopeChatModel) {
super(allTools);
// 基础配置
this.setName("lenManus");
this.setMaxSteps(20);
// 提示词设置
this.setSystemPrompt(
"You are LenManus, an all-capable AI assistant, aimed at solving any task presented by the user. " +
"You have various tools at your disposal that you can call upon to efficiently complete complex requests."
);
this.setNextStepPrompt(
"Based on user needs, proactively select the most appropriate tool or combination of tools. " +
"For complex tasks, you can break down the problem and use different tools step by step to solve it. " +
"After using each tool, clearly explain the execution results and suggest the next steps. " +
"If you want to stop the interaction at any point, use the `terminate` tool/function call."
);
// 初始化对话客户端
this.setChatClient(
ChatClient.builder(dashscopeChatModel)
.defaultAdvisors(new MyLoggerAdvisor())
.build()
);
}
}
LenManus类主要配置了智能体的属性和行为:
- 系统提示词:定义智能体的角色和能力
- 下一步提示词:引导智能体在每个步骤的决策
- 最大步数:控制执行的最大迭代次数
- 对话客户端:设置与LLM交互的客户端
步骤六:开发工具系统
工具系统是智能体能力扩展的关键。下面是一个终止工具的实现示例:
/**
* 终止工具 - 允许智能体自主结束任务
*/
@Slf4j
public class TerminateTool {
/**
* 终止当前任务
*/
@Tool(description = "Use this tool to terminate the current task when you believe it is complete or cannot proceed further", returnDirect = false)
public String doTerminate(
@ToolParam(description = "Reason for terminating the task") String reason) {
log.info("任务终止: {}", reason);
return "任务已终止: " + reason;
}
}
一个文件操作工具示例:
/**
* 文件操作工具
*/
@Slf4j
public class FileOperationTool {
/**
* 读取文件内容
*/
@Tool(description = "Read content from a file", returnDirect = false)
public String readFile(
@ToolParam(description = "Path to the file to read") String filePath) {
try {
File file = new File(filePath);
if (!file.exists()) {
return "错误: 文件不存在";
}
if (!file.isFile() || !file.canRead()) {
return "错误: 无法读取文件";
}
String content = FileUtil.readString(file, StandardCharsets.UTF_8);
return "文件内容:\n" + content;
} catch (Exception e) {
log.error("读取文件失败", e);
return "读取文件失败: " + e.getMessage();
}
}
/**
* 写入文件内容
*/
@Tool(description = "Write content to a file", returnDirect = false)
public String writeFile(
@ToolParam(description = "Path to the file to write") String filePath,
@ToolParam(description = "Content to write to the file") String content) {
try {
File file = new File(filePath);
File parent = file.getParentFile();
if (parent != null && !parent.exists()) {
if (!parent.mkdirs()) {
return "错误: 无法创建目录";
}
}
FileUtil.writeString(content, file, StandardCharsets.UTF_8);
return "文件已成功写入: " + filePath;
} catch (Exception e) {
log.error("写入文件失败", e);
return "写入文件失败: " + e.getMessage();
}
}
}
智能体循环检测与优化
为了避免智能体陷入循环,我们可以实现循环检测和处理机制:
/**
* 检查代理是否陷入循环
*/
protected boolean isStuck() {
if (messageList.size() < 2) {
return false;
}
// 获取最后一条助手消息
AssistantMessage lastAssistantMessage = null;
for (int i = messageList.size() - 1; i >= 0; i--) {
if (messageList.get(i) instanceof AssistantMessage) {
lastAssistantMessage = (AssistantMessage) messageList.get(i);
break;
}
}
if (lastAssistantMessage == null || lastAssistantMessage.getText() == null
|| lastAssistantMessage.getText().isEmpty()) {
return false;
}
// 计算重复内容出现次数
int duplicateCount = 0;
String lastContent = lastAssistantMessage.getText();
for (int i = messageList.size() - 2; i >= 0; i--) {
Message msg = messageList.get(i);
if (msg instanceof AssistantMessage) {
AssistantMessage assistantMsg = (AssistantMessage) msg;
if (lastContent.equals(assistantMsg.getText())) {
duplicateCount++;
if (duplicateCount >= this.duplicateThreshold) {
return true;
}
}
}
}
return false;
}
/**
* 处理陷入循环的状态
*/
protected void handleStuckState() {
String stuckPrompt = "观察到重复响应。请考虑新的策略,避免重复已尝试过的无效路径。";
this.nextStepPrompt = stuckPrompt + "\n" + (this.nextStepPrompt != null ? this.nextStepPrompt : "");
log.warn("检测到智能体陷入循环状态。添加额外提示: {}", stuckPrompt);
}
循环检测机制主要通过比较历史消息来识别重复响应,当检测到循环时,通过添加特殊提示词引导智能体跳出循环。
循环检测与处理流程图
测试智能体
创建单元测试验证智能体功能:
@SpringBootTest
class LenManusTest {
@Resource
private LenManus lenManus;
@Test
public void run() {
String userPrompt = """
我的另一半居住在广东广州,请帮我找到 5 公里内合适的约会地点,
并结合一些网络图片,制定一份详细的约会计划,
并以 PDF 格式输出""";
String answer = lenManus.run(userPrompt);
Assertions.assertNotNull(answer);
}
}
测试示例展示了如何使用智能体解决复杂任务,包括地点搜索、图片查找和PDF生成。
扩展功能:PDF生成能力
为智能体添加PDF生成工具,扩展其能力边界:
/**
* PDF生成工具
*/
public class PDFGenerationTool {
// Markdown标题正则
private static final Pattern HEADER_PATTERN = Pattern.compile("^(#{1,6})\\s+(.+)$");
// Markdown图片正则
private static final Pattern IMAGE_PATTERN = Pattern.compile("!\\[(.*?)\\]\\((.*?)\\)");
/**
* 生成PDF文件
*/
@Tool(description = "Generate a PDF file with given content and images, supports markdown image syntax  and headers with # symbols", returnDirect = false)
public String generatePDF(
@ToolParam(description = "Name of the file to save the generated PDF") String fileName,
@ToolParam(description = "Content to be included in the PDF, supports markdown image syntax  and headers with # symbols") String content) {
String fileDir = FileConstant.FILE_SAVE_DIR + "/pdf";
String filePath = fileDir + "/" + fileName;
try {
FileUtil.mkdir(fileDir);
try (PdfWriter writer = new PdfWriter(filePath);
PdfDocument pdf = new PdfDocument(writer);
Document document = new Document(pdf)) {
PdfFont font = PdfFontFactory.createFont("STSongStd-Light", "UniGB-UCS2-H");
document.setFont(font);
processContent(content, document, font);
}
return "PDF生成成功,保存路径: " + filePath;
} catch (IOException e) {
return "生成PDF时出错: " + e.getMessage();
}
}
/**
* 处理内容
*/
private void processContent(String content, Document document, PdfFont font) {
String[] lines = content.split("\n");
StringBuilder textBuffer = new StringBuilder();
for (String line : lines) {
Matcher headerMatcher = HEADER_PATTERN.matcher(line);
if (headerMatcher.matches()) {
if (textBuffer.length() > 0) {
processTextWithImages(textBuffer.toString(), document);
textBuffer.setLength(0);
}
String headerMarker = headerMatcher.group(1);
String headerText = headerMatcher.group(2);
addHeader(document, headerText, headerMarker.length(), font);
continue;
}
textBuffer.append(line).append("\n");
}
if (textBuffer.length() > 0) {
processTextWithImages(textBuffer.toString(), document);
}
}
/**
* 添加标题
*/
private void addHeader(Document document, String headerText, int level, PdfFont font) {
Paragraph header = new Paragraph(headerText);
float fontSize = 24f;
switch (level) {
case 1: fontSize = 24f; break;
case 2: fontSize = 20f; break;
case 3: fontSize = 18f; break;
case 4: fontSize = 16f; break;
case 5: fontSize = 14f; break;
case 6: fontSize = 12f; break;
}
header.setFont(font)
.setFontSize(fontSize)
.setBold()
.setTextAlignment(TextAlignment.LEFT);
// 添加适当的间距
header.setMarginTop(20f);
header.setMarginBottom(10f);
document.add(header);
}
/**
* 处理包含图片的文本
*/
private void processTextWithImages(String content, Document document) {
Matcher matcher = IMAGE_PATTERN.matcher(content);
int lastEnd = 0;
while (matcher.find()) {
String textBefore = content.substring(lastEnd, matcher.start());
if (!textBefore.isEmpty()) {
document.add(new Paragraph(textBefore));
}
String imageUrl = matcher.group(2);
try {
// 使用iText的ImageDataFactory加载远程图像
Image image = new Image(ImageDataFactory.create(new URL(imageUrl)));
// 设置图片宽度和自动缩放
image.setWidth(document.getPdfDocument().getDefaultPageSize().getWidth() * 0.8f);
image.setAutoScale(true);
// 添加图片标题(如果有)
String imageCaption = matcher.group(1);
if (StrUtil.isNotBlank(imageCaption)) {
// 创建包含图片和标题的垂直布局
Div imageContainer = new Div();
imageContainer.setTextAlignment(TextAlignment.CENTER);
imageContainer.add(image);
// 添加图片标题
Paragraph caption = new Paragraph(imageCaption)
.setFontSize(10f)
.setItalic()
.setTextAlignment(TextAlignment.CENTER);
imageContainer.add(caption);
document.add(imageContainer);
} else {
// 直接添加图片
document.add(image);
}
} catch (Exception e) {
document.add(new Paragraph("无法加载图片: " + imageUrl + " (" + e.getMessage() + ")"));
}
lastEnd = matcher.end();
}
if (lastEnd < content.length()) {
document.add(new Paragraph(content.substring(lastEnd)));
}
}
}
PDF生成工具支持Markdown语法,能够处理文本格式和图片嵌入,为智能体提供了输出结构化文档的能力。下面是PDF生成的流程图:
项目文件结构示例
一个完整的智能体项目通常包含以下文件结构:
src/
├── main/
│ ├── java/
│ │ └── com/
│ │ └── example/
│ │ └── agent/
│ │ ├── Application.java // 应用程序入口
│ │ ├── config/
│ │ │ └── AgentConfiguration.java // 智能体配置
│ │ ├── controller/
│ │ │ └── AgentController.java // API控制器
│ │ ├── model/
│ │ │ ├── AgentState.java // 状态枚举
│ │ │ ├── Message.java // 消息模型
│ │ │ └── ToolResult.java // 工具结果模型
│ │ ├── agent/
│ │ │ ├── BaseAgent.java // 基础智能体
│ │ │ ├── ReActAgent.java // ReAct智能体
│ │ │ ├── ToolCallAgent.java // 工具调用智能体
│ │ │ └── LenManus.java // 最终智能体实现
│ │ ├── tools/
│ │ │ ├── FileOperationTool.java // 文件操作工具
│ │ │ ├── PDFGenerationTool.java // PDF生成工具
│ │ │ ├── TerminateTool.java // 终止工具
│ │ │ ├── WebSearchTool.java // 网络搜索工具
│ │ │ └── ImageSearchTool.java // 图片搜索工具
│ │ └── utils/
│ │ ├── FileUtil.java // 文件工具类
│ │ └── PromptUtil.java // 提示词工具类
│ └── resources/
│ ├── application.yml // 应用配置
│ └── static/ // 静态资源
└── test/
└── java/
└── com/
└── example/
└── agent/
└── LenManusTest.java // 智能体测试类
结语
通过本文的实践,我们完成了一个具备工具调用能力的AI智能体系统,从架构设计到代码实现全面解析了开发流程。这种模块化、分层的架构设计使得智能体系统具有高度的灵活性和可扩展性,能够适应各种应用场景。
在实际应用中,开发者可以根据需求进一步扩展智能体的功能,例如:
- 知识库集成:连接向量数据库,实现专业领域知识支持
- 多模态交互:支持图像、音频等多模态输入和输出
- 持久化记忆:实现长期记忆存储,保持上下文连贯性
- 自适应提示词:根据任务复杂度动态调整提示词策略
在下一篇文章中,我们将介绍如何进一步扩展智能体的功能,实现更复杂的业务场景,例如专业领域智能体和多智能体协作系统。
参考资料:
- OpenManus开源项目:https://github.com/FoundationAgents/OpenManus
- Spring AI文档:https://docs.spring.io/spring-ai/reference/
- iText PDF库:https://itextpdf.com/
- 智能体设计模式:https://martinfowler.com/articles/patterns-of-distributed-systems/
作者:lenyan
GitHub:lenyanjgk · GitHub
CSDN:lenyan~-CSDN博客
火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。
更多推荐
所有评论(0)