欢迎光临
我们一直在努力

大模型开发 - 用纯Java手写一个多功能AI Agent:01 从零实现类Manus智能体

文章目录

  • 引言
  • 一、项目全景:架构与技术选型
    • 1.1 项目结构
    • 1.2 技术选型
  • 二、Agent核心循环:ReAct模式的实现
    • 2.1 BaseAgent:循环骨架
    • 2.2 ToolCallAgent:ReAct的核心引擎
    • 2.3 ManusAgent:具体Agent的组装
  • 三、消息系统:多模态对话的基石
    • 3.1 四种角色的消息设计
    • 3.2 LLM API的封装
  • 四、记忆管理:LLM驱动的上下文过滤
    • 4.1 相关性过滤接口
    • 4.2 LLM相关性过滤器
    • 4.3 工具的动态过滤
  • 五、工具系统:可插拔的能力扩展
    • 5.1 工具接口与基类
    • 5.2 Docker沙箱:安全的代码执行
    • 5.3 浏览器自动化:Playwright驱动
    • 5.4 网页搜索:Tavily集成
  • 六、完整执行流程:一个真实的例子
  • 七、设计模式总结
  • 八、进一步思考
  • 结语

在这里插入图片描述

引言

2024年以来,AI Agent(智能体)成为大模型应用领域最炙手可热的方向。从OpenAI的GPT-4 with Tools,到Anthropic的Claude Computer Use,再到国内Manus、AutoGLM等产品,"让大模型不只是聊天,而是真正地做事"已经成为行业共识。

然而,大多数开发者对AI Agent的理解还停留在概念层面:知道它能调用工具、能自主决策,但对其内部运作机制缺乏深入了解。市面上的Agent框架(如LangChain、Spring AI)虽然降低了开发门槛,但也隐藏了大量实现细节。

本文将通过一个完全不依赖Spring框架的纯Java项目——ai-manus,带你从零理解AI Agent的核心架构。这个项目实现了一个功能完整的多工具智能体,具备文件读写、Docker沙箱代码执行、网页搜索、浏览器自动化等能力,并采用了ReAct(Reasoning + Acting)推理模式和LLM驱动的上下文记忆管理。通过逐层拆解其代码实现,你将真正理解一个AI Agent是如何"思考"和"行动"的。

一、项目全景:架构与技术选型

1.1 项目结构

aimanus/
├── pom.xml
└── src/main/java/com/artisan/
├── ManusApplication.java # 启动入口
├── agent/
│ ├── BaseAgent.java # Agent基类(循环控制)
│ ├── ToolCallAgent.java # 工具调用AgentReAct核心)
│ └── ManusAgent.java # 具体Agent实现(注册工具)
├── model/
│ ├── ModelConfig.java # 模型配置
│ ├── OpenAIClient.java # LLM API客户端
│ ├── Message.java # 消息模型(支持多模态)
│ ├── Memory.java # 记忆管理
│ ├── RelevanceFilter.java # 相关性过滤接口
│ ├── LLMRelevanceFilter.java # 基于LLM的相关性过滤
│ ├── ModelResponse.java # 模型响应
│ ├── Role.java # 角色枚举
│ ├── ToolCall.java # 工具调用模型
│ ├── ToolDefinition.java # 工具定义模型
│ └── Function.java # 函数调用模型
└── tools/
├── Tool.java # 工具接口
├── BaseTool.java # 工具基类
├── ToolCollection.java # 工具注册中心
├── ToolResult.java # 工具执行结果
└── impl/
├── FileWriterTool.java # 文件写入
├── FileReaderTool.java # 文件读取
├── SandboxTool.java # Docker沙箱执行
├── DockerSandbox.java # Docker容器管理
├── TavilySearchTool.java # 网页搜索
└── BrowserTool.java # 浏览器自动化

1.2 技术选型

项目刻意避开了Spring等重型框架,采用"最小依赖"原则:

依赖版本用途
OkHttp3 4.12.0 HTTP客户端,调用LLM API
Jackson 2.16.1 JSON序列化/反序列化
Lombok 1.18.30 减少样板代码
docker-java 3.3.6 Docker容器管理
langchain4j-tavily 0.36.2 Tavily网页搜索
Playwright 1.55.0 浏览器自动化

这种选择的好处是:你看到的每一行代码都是Agent逻辑本身,没有框架魔法的干扰,非常适合学习和理解Agent的运作原理。

二、Agent核心循环:ReAct模式的实现

ReAct(Reasoning + Acting)是当前AI Agent最主流的推理范式。其核心思想是:大模型在每一步先进行推理(Thought),然后决定执行什么动作(Action),观察执行结果(Observation),再进入下一轮推理。这个项目通过三层Agent继承体系优雅地实现了这一模式。

2.1 BaseAgent:循环骨架

BaseAgent是所有Agent的抽象基类,它定义了Agent执行的基本骨架——一个有限步数的循环:

public abstract class BaseAgent {

protected final Memory memory;
private final int maxStep;
protected String systemPrompt;

public String run(String prompt) {
// 1. 初始化:将系统提示词和用户输入加入记忆
memory.addMessage(Message.systemMessage(systemPrompt));
memory.addMessage(Message.userMessage(prompt));

int currentStep = 0;
StringBuilder allStepResult = new StringBuilder();

// 2. 核心循环:最多执行maxStep步
while (currentStep < maxStep) {
StepResult stepResult = step(prompt); // 子类实现
allStepResult.append(stepResult.output).append("/n");

if (!stepResult.isShouldContinue()) {
break; // Agent认为任务完成,退出循环
}
currentStep++;
}

return allStepResult.toString();
}

// 由子类实现的单步执行逻辑
protected abstract StepResult step(String currentQuery);
}

这里有几个关键设计决策:

  • 最大步数限制(maxStep=10):防止Agent陷入无限循环,这是所有Agent系统必备的安全阀。
  • StepResult双字段设计:output记录当前步的输出,shouldContinue标识是否需要继续执行。大模型通过返回finish_reason="stop"来告知Agent任务已完成。
  • Memory贯穿全程:所有消息(系统提示、用户输入、助手回复、工具结果)都存入Memory,保证上下文连贯性。

2.2 ToolCallAgent:ReAct的核心引擎

ToolCallAgent是整个系统的灵魂所在,它实现了ReAct循环的单步逻辑:

@Override
protected StepResult step(String currentQuery) {
// 1. 从记忆中获取上下文消息(带相关性过滤)
List<Message> contextMessages = memory.getMessages(currentQuery);

// 2. 获取与当前查询相关的工具定义(带相关性过滤)
List<ToolDefinition> toolDefinitions =
toolCollection.getRelevantToolDefinitions(currentQuery);

// 3. 调用大模型,传入上下文消息和可用工具
ModelResponse modelResponse =
openAIClient.chat(contextMessages, toolDefinitions);

// 4. 大模型决定调用工具
if (modelResponse.hasToolCalls()) {
// 将工具调用请求存入记忆
Message assistantMessage = Message.assistantMessage(modelResponse.getContent());
assistantMessage.setToolCalls(convertToToolCalls(modelResponse.getToolCalls()));
memory.addMessage(assistantMessage);

// 执行工具并返回结果
return handleToolCalls(modelResponse.getToolCalls());
}

// 5. 大模型不调用工具,直接返回文本
if (modelResponse.getContent() != null && !modelResponse.getContent().isBlank()) {
memory.addMessage(Message.assistantMessage(modelResponse.getContent()));
}

// 6. 判断是否结束
if (modelResponse.getFinishReason().equals("stop")) {
return StepResult.builder()
.shouldContinue(false)
.output("大模型认为任务已经执行结束")
.build();
}

return StepResult.builder()
.shouldContinue(true)
.output(modelResponse.getContent())
.build();
}

让我们拆解这个方法中蕴含的设计智慧:

上下文管理:每次调用LLM前,先通过memory.getMessages(currentQuery)获取经过相关性过滤的消息,避免上下文窗口溢出。

工具动态筛选:不是每次都把所有5个工具都传给大模型,而是通过getRelevantToolDefinitions根据当前查询动态选择最相关的工具,减少干扰提升决策质量。

消息链维护:严格遵循OpenAI Tool Calling协议——先将assistant的tool_calls消息存入记忆,再存入对应的tool执行结果消息。这个消息链的完整性是大模型正确理解上下文的关键。

工具执行的核心逻辑在handleToolCalls方法中:

private StepResult handleToolCalls(List<Object> toolCalls) {
StringBuilder allResults = new StringBuilder();
for (Object toolCallObj : toolCalls) {
try {
JsonNode toolCallNode = objectMapper.valueToTree(toolCallObj);
String toolCallId = toolCallNode.get("id").asText();
String toolName = toolCallNode.get("function").get("name").asText();
String argumentsJson = toolCallNode.get("function").get("arguments").asText();

// 解析参数并执行工具
Map<String, Object> arguments =
objectMapper.readValue(argumentsJson, Map.class);
ToolResult result = toolCollection.executeTool(toolName, arguments);

// 将工具结果封装为toolMessage存入记忆
String resultContent = result.hasError()
? "Error: " + result.getError()
: result.getOutput().toString();

Message toolMessage = Message.toolMessage(
resultContent, toolName, toolCallId, result.getBase64Image());
memory.addMessage(toolMessage);
} catch (Exception e) {
// 错误也要存入记忆,让大模型知道发生了什么
Message errorMessage = Message.toolMessage(
"工具执行失败: " + e.getMessage(), "unknown",
UUID.randomUUID().toString());
memory.addMessage(errorMessage);
}
}
return StepResult.builder().shouldContinue(true).output(allResults.toString()).build();
}

注意这里的一个细节:工具执行完成后,shouldContinue始终为true。这意味着工具执行只是Agent的一个中间步骤,执行完工具后需要将结果反馈给大模型,由大模型决定下一步做什么——可能继续调用其他工具,也可能认为任务已完成。这就是ReAct循环的精髓。

2.3 ManusAgent:具体Agent的组装

ManusAgent是最终面向用户的Agent实现,负责注册工具和配置系统提示词:

public class ManusAgent extends ToolCallAgent {

private final static String SYSTEM_PROMPT = """
# 角色定义
你是Manus,一个多功能的AI代理,能够使用可用的工具处理各种任务。

# 规则
– 工作目录:{workspace}
– Sandbox里面不使用工作目录
– 利用Sandbox执行代码时,直接把代码内容传给Sandbox,而不是把代码脚本文件传给Sandbox
– 一次只能执行一个工具
""";

public ManusAgent(OpenAIClient openAIClient) {
super(openAIClient, null, null);

// 注册5个工具
ToolCollection toolCollection = new ToolCollection();
toolCollection.addTool(new FileWriterTool());
toolCollection.addTool(new FileReaderTool());
toolCollection.addTool(new SandboxTool());
toolCollection.addTool(new TavilySearchTool());
toolCollection.addTool(new BrowserTool());
this.toolCollection = toolCollection;

// 创建工作区目录并注入到系统提示词
Path workspaceRoot = getProjectRoot().resolve("workspace");
Files.createDirectories(workspaceRoot);
this.systemPrompt = SYSTEM_PROMPT.replace("{workspace}",
workspaceRoot.toString());
}
}

系统提示词的设计值得关注:它明确了Agent的角色定位、工作目录规范以及工具使用规则。特别是"一次只能执行一个工具"这条规则,简化了工具执行的并发复杂度。

三、消息系统:多模态对话的基石

3.1 四种角色的消息设计

public enum Role {
SYSTEM("system"), // 系统提示词
USER("user"), // 用户输入
ASSISTANT("assistant"), // 大模型回复(含tool_calls)
TOOL("tool"); // 工具执行结果
}

Message类是整个消息系统的核心,它需要兼容OpenAI Chat Completions API的消息格式:

public class Message {
private Role role;
private String content;
private List<ToolCall> toolCalls; // assistant消息携带的工具调用
private String name; // tool消息的工具名
private String toolCallId; // tool消息关联的调用ID
private String base64Image; // 多模态图片数据
}

这里有一个精妙的设计——base64Image字段使得消息天然支持多模态。当浏览器工具截图后,截图数据以Base64编码附在toolMessage中传回;大模型在下一轮接收到这个多模态消息时,就能"看到"截图内容并进行分析。

在OpenAIClient中,多模态消息的转换逻辑如下:

if (message.getBase64Image() != null) {
// 构造多模态content数组
List<Map<String, Object>> content = new ArrayList<>();
content.add(Map.of("type", "text", "text", message.getContent()));
content.add(Map.of(
"type", "image_url",
"image_url", Map.of("url", "data:image/jpeg;base64," + message.getBase64Image())
));
apiMessage.put("content", content);
} else {
apiMessage.put("content", message.getContent());
}

3.2 LLM API的封装

OpenAIClient使用OkHttp3直接调用OpenAI兼容API(本项目实际对接的是阿里云DashScope的通义千问模型):

public class OpenAIClient {
private final OkHttpClient httpClient;

public OpenAIClient(ModelConfig modelConfig) {
this.httpClient = new OkHttpClient.Builder()
.connectTimeout(Duration.ofSeconds(30))
.readTimeout(Duration.ofMinutes(5)) // 长超时,等待大模型推理
.writeTimeout(Duration.ofMinutes(5))
.build();
}

public ModelResponse chat(List<Message> messages, List<ToolDefinition> tools) {
Map<String, Object> requestBody = new HashMap<>();
requestBody.put("model", modelConfig.getModel());
requestBody.put("messages", convertMessagesToApiFormat(messages));

// 仅在有工具时传入tools参数
if (tools != null && !tools.isEmpty()) {
requestBody.put("tools", convertToolsToApiFormat(tools));
}
// … 发送请求并解析响应
}
}

工具定义的转换遵循OpenAI Function Calling标准格式:

private List<Map<String, Object>> convertToolsToApiFormat(List<ToolDefinition> tools) {
return tools.stream().map(tool -> Map.of(
"type", "function",
"function", Map.of(
"name", tool.getName(),
"description", tool.getDescription(),
"parameters", tool.getParameters() // JSON Schema格式
)
)).toList();
}

四、记忆管理:LLM驱动的上下文过滤

随着Agent执行步骤增多,消息历史会快速膨胀。如果不加处理,很快就会超出大模型的上下文窗口。本项目提供了一个创新的解决方案——用LLM自身来评估消息的相关性。

4.1 相关性过滤接口

public interface RelevanceFilter {
List<Message> filter(List<Message> messages, String currentQuery, int maxMessages);
double calculateRelevance(Message message, String currentQuery);
}

Memory类在获取消息时会自动应用过滤:

public List<Message> getMessages(String currentQuery) {
if (relevanceFilter != null) {
return relevanceFilter.filter(messages, currentQuery, 5); // 最多保留5条
}
return messages;
}

4.2 LLM相关性过滤器

LLMRelevanceFilter是这个机制的核心实现。它对每条非系统消息调用LLM进行语义相关性评分:

public List<Message> filter(List<Message> messages, String currentQuery, int maxMessages) {
// 系统消息始终保留
List<Message> systemMessages = messages.stream()
.filter(msg -> msg.getRole() == Role.SYSTEM)
.toList();

// 为每条非系统消息计算相关性得分
List<MessageScore> scoredMessages = nonSystemMessages.stream()
.map(msg -> new MessageScore(msg, calculateRelevance(msg, currentQuery)))
.sorted((a, b) -> Double.compare(b.score, a.score))
.toList();

// 保留系统消息 + 得分最高的N条消息
List<Message> result = new ArrayList<>(systemMessages);
int remainingSlots = maxMessages systemMessages.size();
result.addAll(scoredMessages.stream().limit(remainingSlots).map(ms -> ms.message).toList());

return result;
}

评分时构造的提示词非常讲究,提供了明确的评分标准:

private String buildRelevancePrompt(String messageContent, String query) {
return String.format(
"请评估以下消息内容与查询的相关性,返回0.0到1.0之间的数字评分:\\n\\n" +
"查询:%s\\n\\n消息内容:%s\\n\\n" +
"评估标准:\\n" +
"1.0 – 高度相关:消息直接回答查询或包含查询的核心信息\\n" +
"0.7-0.9 – 相关:消息与查询主题相关,包含有用信息\\n" +
"0.4-0.6 – 部分相关:消息与查询有一定关联\\n" +
"0.1-0.3 – 微弱相关:消息与查询只有很少关联\\n" +
"0.0 – 不相关:消息与查询完全无关\\n\\n" +
"请只返回数字评分,不要包含其他文字说明:",
query, messageContent
);
}

同时,代码还实现了健壮的评分解析逻辑,能处理LLM返回数字或文本描述两种情况:

private double parseRelevanceScore(String content) {
// 优先尝试解析数字
String numberStr = trimmed.replaceAll("[^0-9.].*", "");
if (!numberStr.isEmpty()) {
return Double.parseDouble(numberStr);
}

// 回退:从文本关键词推断
if (lowerContent.contains("高度相关")) return 0.9;
if (lowerContent.contains("相关")) return 0.7;
if (lowerContent.contains("部分")) return 0.5;
if (lowerContent.contains("微弱")) return 0.2;
if (lowerContent.contains("不相关")) return 0.0;

return 0.0;
}

4.3 工具的动态过滤

同样的相关性过滤机制也应用于工具选择。ToolCollection会将每个工具的"名称+描述"包装成消息,让LLM评估其与当前查询的相关性:

public List<ToolDefinition> getRelevantToolDefinitions(String query) {
List<ToolScore> scoredTools = allTools.stream()
.map(tool -> {
String toolText = tool.getName() + " " + tool.getDescription();
Message message = Message.assistantMessage(toolText);
double relevance = relevanceFilter.calculateRelevance(message, query);
return new ToolScore(tool, relevance);
})
.filter(ts -> ts.score >= 0.3) // 相关性阈值
.sorted((a, b) -> Double.compare(b.score, a.score))
.toList();

// 安全兜底:如果没有工具超过阈值,返回全部
if (relevantTools.isEmpty()) {
return allTools;
}
return relevantTools;
}

这个设计有一个重要的安全阈值——0.3。当没有任何工具的相关性超过0.3时,系统会回退到返回所有工具,避免Agent"无工具可用"的死锁状态。

五、工具系统:可插拔的能力扩展

5.1 工具接口与基类

工具系统采用经典的接口-抽象类-实现类三层设计:

public interface Tool {
String getName();
String getDescription();
Map<String, Object> getParametersSchema(); // JSON Schema
ToolResult execute(Map<String, Object> parameters);

default ToolDefinition toDefinition() {
return new ToolDefinition(getName(), getDescription(), getParametersSchema());
}
}

BaseTool提供了构建JSON Schema参数定义的辅助方法:

public abstract class BaseTool implements Tool {
// 参数Schema构建器
protected Map<String, Object> stringParam(String description) { ... }
protected Map<String, Object> boolParam(String description) { ... }
protected Map<String, Object> intParam(String description) { ... }
protected Map<String, Object> enumParam(String description, List<String> values) { ... }

// 参数安全提取
protected String getString(Map<String, Object> parameters, String key) { ... }
protected Boolean getBoolean(Map<String, Object> parameters, String key) { ... }
protected Integer getInteger(Map<String, Object> parameters, String key) { ... }

// Schema组装
protected Map<String, Object> buildSchema(
Map<String, Map<String, Object>> properties, List<String> required) { ... }
}

ToolResult支持三种返回形态——纯文本、错误信息、带图片的多模态结果:

public class ToolResult {
private final Object output;
private final String error;
private final String base64Image; // 支持截图等多模态输出

public static ToolResult success(Object output) { ... }
public static ToolResult success(Object output, String base64Image) { ... }
public static ToolResult error(String error) { ... }
}

5.2 Docker沙箱:安全的代码执行

SandboxTool和DockerSandbox配合实现了在Docker容器内安全执行用户代码的能力。

容器配置强调安全隔离:

public static class SandboxSettings {
public static String image = "python:3.12-slim";
public static String workDir = "/workspace";
public static String memoryLimit = "512m"; // 内存限制512MB
public static double cpuLimit = 1.0; // CPU限制1核
public static int timeout = 300; // 超时5分钟
public static boolean networkEnabled = false; // 禁用网络访问
}

禁用网络是一个关键的安全决策——防止恶意代码外传数据或发起攻击。

支持多语言代码执行,使用Heredoc方式传递代码,避免了复杂的字符转义问题:

private String buildCodeExecutionCommand(String code, String language) {
switch (language.toLowerCase()) {
case "python":
return buildHeredocCommand(code, "python3");
case "bash":
return code; // Bash直接执行
case "node":
return buildHeredocCommand(code, "node");
case "java":
// Java需要先写文件、编译、再执行
String javaHeredoc = buildHeredocToFile(code, "/tmp/Main.java");
return javaHeredoc + " && cd /tmp && javac Main.java && java Main";
default:
throw new IllegalArgumentException("Unsupported language: " + language);
}
}

private String buildHeredocCommand(String code, String interpreter) {
String delimiter = "OPENMANUS_CODE_EOF_" + System.currentTimeMillis();
return String.format("%s << '%s'\\n%s\\n%s", interpreter, delimiter, code, delimiter);
}

使用时间戳生成唯一的Heredoc分隔符(OPENMANUS_CODE_EOF_1718…),确保分隔符不会与代码内容冲突。

5.3 浏览器自动化:Playwright驱动

BrowserTool基于Playwright实现了完整的浏览器操作能力,支持7种操作:

操作说明
navigate 导航到URL,等待网络空闲
click 通过CSS选择器点击元素
type 在输入框中输入文本
screenshot 全页面截图,返回Base64编码
get_content 提取页面标题、URL和文本内容
scroll 上下左右滚动页面
wait 等待元素出现或页面加载

浏览器采用懒加载模式,只在首次使用时初始化:

public ToolResult execute(Map<String, Object> parameters) {
// 首次使用时才创建浏览器实例
if (browser == null) {
initializeBrowser();
}
// …
}

private void initializeBrowser() {
Playwright playwright = Playwright.create();
browser = playwright.chromium().launch(
new BrowserType.LaunchOptions().setHeadless(false)); // 非无头模式

context = browser.newContext(new Browser.NewContextOptions()
.setViewportSize(1920, 1080)
.setUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) …"));

currentPage = context.newPage();
}

截图功能是多模态能力的关键入口——Agent可以截图后让大模型"看到"页面内容:

private ToolResult handleScreenshot(Map<String, Object> parameters) {
byte[] screenshot = currentPage.screenshot(new Page.ScreenshotOptions()
.setFullPage(true)
.setType(ScreenshotType.PNG));

String base64Screenshot = Base64.getEncoder().encodeToString(screenshot);
return ToolResult.success("截图成功", base64Screenshot); // 携带Base64图片
}

5.4 网页搜索:Tavily集成

TavilySearchTool通过langchain4j的Tavily封装实现网页搜索:

public ToolResult execute(Map<String, Object> parameters) {
String query = getString(parameters, "query");
WebSearchResults results = searchEngine.search(query);

List<Map<String, Object>> searchResults = results.results().stream()
.map(result -> Map.of(
"title", result.title(),
"url", result.url(),
"snippet", result.snippet()
))
.toList();

return ToolResult.success(response);
}

六、完整执行流程:一个真实的例子

让我们通过入口程序的示例任务,完整追踪Agent的执行过程:

String prompt = """
1. 创建一个名为'test_page.html'的HTML文件并添加内容
2. 使用file://协议在浏览器中打开本地文件
3. 给打开的页面截图
4. 告诉截图中的内容
"""
;
manusAgent.run(prompt);

执行流程如下:

┌─────────────────────────────────────────────────────────┐
Step 0: 初始化 │
Memory: [SystemMessage, UserMessage]
└──────────────────────────┬──────────────────────────────┘

┌─────────────────────────────────────────────────────────┐
Step 1: LLM推理 → 决定调用 write_file 工具 │
Action: write_file(path="workspace/test_page.html",
│ content="<html>…</html>")
Memory: + [AssistantMsg(tool_calls), ToolMsg(成功)]
└──────────────────────────┬──────────────────────────────┘

┌─────────────────────────────────────────────────────────┐
Step 2: LLM推理 → 决定调用 browser.navigate │
Action: browser(action="navigate",
│ url="file:///path/to/workspace/test_page.html")
Memory: + [AssistantMsg(tool_calls), ToolMsg(导航成功)]
└──────────────────────────┬──────────────────────────────┘

┌─────────────────────────────────────────────────────────┐
Step 3: LLM推理 → 决定调用 browser.screenshot │
Action: browser(action="screenshot")
Memory: + [AssistantMsg(tool_calls),
ToolMsg(截图成功 + base64Image)]
└──────────────────────────┬──────────────────────────────┘

┌─────────────────────────────────────────────────────────┐
Step 4: LLM推理 → 分析截图内容,返回文本描述 │
Response: "截图中显示了一个HTML页面,内容包括…"
│ finish_reason: "stop" → 任务完成,退出循环 │
└─────────────────────────────────────────────────────────┘

在这个过程中,Agent展现了以下能力:

  • 任务规划:将复合任务自动拆解为多个步骤
  • 工具选择:根据当前步骤选择最合适的工具
  • 结果反馈:每次工具执行后将结果传回LLM进行下一步推理
  • 多模态理解:能够"看到"截图内容并进行描述
  • 自主终止:完成所有子任务后主动结束
  • 七、设计模式总结

    回顾整个项目,我们可以提炼出以下核心设计模式:

    设计模式应用位置作用
    模板方法 BaseAgent.run() + step() 固定执行骨架,子类实现单步逻辑
    策略模式 RelevanceFilter接口 可插拔的上下文过滤策略
    工厂方法 Message.userMessage()等 统一消息创建
    注册表模式 ToolCollection 集中管理工具的注册和查找
    懒加载 BrowserTool/DockerSandbox 按需初始化重量级资源
    适配器模式 OpenAIClient 将内部模型适配为OpenAI API格式

    八、进一步思考

    这个项目虽然是学习性质,但其架构设计完全可以作为生产级Agent系统的基础。以下是一些可以进一步优化的方向:

  • 流式输出:当前使用同步HTTP调用,可改为SSE流式响应,提升用户体验。
  • 并行工具执行:当前系统提示词限制"一次只能执行一个工具",实际上OpenAI API支持在一次响应中返回多个tool_calls,可以并行执行。
  • 持久化记忆:当前Memory是内存级别的,可引入向量数据库实现长期记忆。
  • 更细粒度的错误恢复:当工具执行失败时,可以引入重试机制或备选方案。
  • 安全增强:对FileWriter/FileReader添加路径白名单限制,防止任意文件读写。
  • 结语

    通过这个纯Java实现的AI Agent项目,我们深入理解了Agent的三个核心机制:

    • ReAct循环:推理与行动的交替执行,是Agent"思考-行动-观察"的基本范式
    • 工具调用协议:大模型通过Function Calling标准接口与外部工具交互
    • 上下文管理:在有限的上下文窗口内,通过相关性过滤保留最有价值的信息

    AI Agent不是魔法,它本质上是一个以LLM为决策引擎、以工具为执行手段、以消息链为上下文的自动化循环系统。理解了这个本质,你就能根据自己的业务需求,构建出真正有用的智能体应用。


    在这里插入图片描述

    赞(0)
    未经允许不得转载:171主机测评 » 大模型开发 - 用纯Java手写一个多功能AI Agent:01 从零实现类Manus智能体
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址