Agent4J 最佳实践:构建高质量的 Agent 应用
总结 Agent4J 开发中的最佳实践,帮助你构建高质量的 Agent 应用。
Agent4J 最佳实践:构建高质量的 Agent 应用
前言
在前面的文章中,我们学习了 Agent4J 的各种功能。现在让我们总结一些最佳实践,帮助你构建高质量的 Agent 应用。
这些实践来自我自己的经验和社区反馈,希望能帮你少走弯路。
1. Agent 设计
1.1 描述要精准
Agent 的描述会直接影响 LLM 的行为。描述越精准,Agent 越能正确理解自己的角色。
// 不好的描述
agent.setDescription("一个助手");
// 好的描述
agent.setDescription("""
一个资深的 Java 开发工程师,擅长:
1. 代码审查和重构
2. 单元测试编写
3. 性能优化
4. Bug 修复
工作风格:
- 严谨、细致
- 注重代码质量
- 善于解释技术问题
""");
1.2 角色要明确
给 Agent 一个明确的角色,而不是一个通用的助手:
// 不好的角色
agent.setName("Assistant");
// 好好的角色
agent.setName("CodeReviewer");
agent.setName("DatabaseExpert");
agent.setName("DevOpsEngineer");
1.3 工具要精简
只给 Agent 需要的工具,不要一股脑地添加所有工具:
// 不好的做法
agent.getSkills().addAll(BuiltInSkills.all());
// 好的做法
if (needsFileSystem) {
agent.getSkills().add(BuiltInSkills.fileSystem());
}
if (needsCommandExecution) {
agent.getSkills().add(BuiltInSkills.commandExecution());
}
工具越多,LLM 越难选择正确的工具。
2. 任务设计
2.1 任务要具体
给 Agent 的任务越具体,它越能准确执行:
// 不好的任务
session.command("帮我整理代码");
// 好的任务
session.command("""
请帮我整理 src/main/java 目录下的代码:
1. 把 entity 类移到 model 包
2. 把 dao 类移到 repository 包
3. 把 service 类移到 service 包
4. 更新所有 import 语句
""");
2.2 提供上下文
如果任务需要特定的上下文,要在描述中提供:
session.command("""
项目背景:
- 这是一个电商系统,使用 Spring Boot + MyBatis Plus
- 数据库是 MySQL 8.0
- 使用 Java 17
任务:
请根据 order 表的结构,生成对应的实体类和 Mapper
要求:
- 使用 Lombok 注解
- 实现软删除
- 添加创建时间和更新时间字段
""");
2.3 设置约束
明确告诉 Agent 什么可以做,什么不可以做:
session.command("""
请重构 UserService 类。
约束:
- 不要修改公共 API
- 不要删除现有方法
- 保持向后兼容
- 添加必要的注释
""");
3. 错误处理
3.1 实现重试机制
网络请求可能会失败,实现重试机制:
public class RetryHandler implements AgentResultHandler {
private static final int MAX_RETRIES = 3;
private int retryCount = 0;
private String task;
private AgentClientSession session;
public RetryHandler(String task, AgentClientSession session) {
this.task = task;
this.session = session;
}
@Override
public void onMessage(String msg) {
System.out.print(msg);
}
@Override
public void onError(Exception e) {
if (retryCount < MAX_RETRIES) {
retryCount++;
System.out.println("重试第 " + retryCount + " 次...");
session.command(task)
.then(this)
.error(this);
} else {
System.out.println("重试次数用尽,任务失败");
e.printStackTrace();
}
}
}
3.2 处理超时
对于长时间运行的任务,设置超时:
public class TimeoutHandler implements AgentResultHandler {
private final long timeout;
private final ScheduledExecutorService scheduler;
private volatile boolean completed = false;
public TimeoutHandler(long timeoutSeconds) {
this.timeout = timeoutSeconds;
this.scheduler = Executors.newSingleThreadScheduledExecutor();
}
@Override
public void onMessage(String msg) {
if (!completed) {
System.out.print(msg);
}
}
@Override
public void onComplete() {
completed = true;
scheduler.shutdown();
}
public void startTimeout(Runnable onTimeout) {
scheduler.schedule(() -> {
if (!completed) {
completed = true;
onTimeout.run();
}
}, timeout, TimeUnit.SECONDS);
}
}
3.3 记录日志
记录 Agent 的执行过程,便于调试:
public class LoggingHandler implements AgentResultHandler {
private static final Logger logger = LoggerFactory.getLogger(LoggingHandler.class);
@Override
public void onMessage(String msg) {
logger.info("Agent 消息: {}", msg);
System.out.print(msg);
}
@Override
public void onTool(ToolDescriptor tool, ToolStatus status) {
logger.info("工具调用: {} - {}", tool.getName(), status);
}
@Override
public void onPlanStepComplete(Plan plan, int current, int total, String step, String result) {
logger.info("计划步骤完成: {}/{} - {}", current, total, step);
}
@Override
public void onError(Exception e) {
logger.error("Agent 错误", e);
}
}
4. 性能优化
4.1 合理使用 Plan
Plan 的每个步骤都是一个独立的 LLM 调用,会消耗 Token。合理使用 Plan:
// 不好的做法:把简单任务拆分成太多步骤
session.command("""
请执行以下步骤:
1. 读取文件
2. 分析内容
3. 生成报告
4. 保存报告
""");
// 好的做法:简单任务直接执行
session.command("请读取 report.txt 文件,分析内容,生成报告并保存");
4.2 并行处理
对于独立的子任务,使用 Sub-Agent 并行处理:
// 不好的做法:顺序处理
session.command("""
请分析这三个文件:
1. 分析 file1.txt
2. 分析 file2.txt
3. 分析 file3.txt
""");
// 好的做法:并行处理
session.command("""
请同时分析这三个文件:
1. file1.txt
2. file2.txt
3. file3.txt
""");
4.3 缓存结果
对于重复的查询,缓存结果:
public class CachingAgent {
private Map<String, String> cache = new ConcurrentHashMap<>();
private AgentClient agent;
public String query(String key, String question) {
return cache.computeIfAbsent(key, k -> {
StringBuilder result = new StringBuilder();
agent.createSession()
.command(question)
.then(msg -> result.append(msg))
.error(e -> result.append("错误:").append(e.getMessage()));
return result.toString();
});
}
}
5. 安全性
5.1 限制工具权限
对于危险的工具,限制权限:
@ToolInfo(name = "execute_command", description = "执行 Shell 命令")
public class SafeCommandTool implements Tool<CommandParam> {
private static final List<String> FORBIDDEN_COMMANDS = List.of(
"rm -rf",
"sudo",
"chmod 777",
"dd if="
);
@Override
public String execute(CommandParam param) {
String command = param.getCommand();
// 检查是否是禁止的命令
for (String forbidden : FORBIDDEN_COMMANDS) {
if (command.contains(forbidden)) {
return "错误:不允许执行此命令";
}
}
// 执行命令
return executeCommand(command);
}
}
5.2 验证输入
验证 Agent 的输入,防止注入攻击:
public class InputValidator {
public static boolean isValidInput(String input) {
// 检查长度
if (input.length() > 10000) {
return false;
}
// 检查特殊字符
if (input.contains("<script>") || input.contains("javascript:")) {
return false;
}
return true;
}
}
5.3 审计日志
记录所有敏感操作:
public class AuditLogger {
private static final Logger auditLogger = LoggerFactory.getLogger("AUDIT");
public static void logToolCall(String agentName, String toolName, String params) {
auditLogger.info("Agent: {}, Tool: {}, Params: {}", agentName, toolName, params);
}
public static void logFileOperation(String agentName, String operation, String path) {
auditLogger.info("Agent: {}, Operation: {}, Path: {}", agentName, operation, path);
}
}
6. 测试
6.1 单元测试
测试自定义工具:
@Test
public void testWeatherTool() {
WeatherTool tool = new WeatherTool();
WeatherParam param = new WeatherParam();
param.setCity("北京");
String result = tool.execute(param);
assertNotNull(result);
assertTrue(result.contains("北京"));
}
测试 Agent 的完整流程:
@Test
public void testAgentWorkflow() {
LLMModel llm = LLMModel.create(ModelType.OpenAI, "https://token-plan-cn.xiaomimimo.com", "mimo-v-2.5-pro", "your-api-key");
AgentClient agent = new AgentClient();
agent.setName("TestAgent");
agent.setModel(llm);
agent.getTools().add(new MockTool());
AtomicBoolean completed = new AtomicBoolean(false);
StringBuilder result = new StringBuilder();
agent.createSession()
.command("测试任务")
.then(new AgentResultHandler() {
public void onMessage(String msg) {
result.append(msg);
}
public void onComplete() {
completed.set(true);
}
})
.error(e -> fail("不应该出错"));
// 等待完成
await().atMost(30, TimeUnit.SECONDS).untilTrue(completed);
assertNotNull(result.toString());
}
6.3 Mock 测试
使用 Mock 工具测试特定场景:
@ToolInfo(name = "mock_tool", description = "模拟工具")
public class MockTool implements Tool<MockParam> {
private String mockResult;
public MockTool(String mockResult) {
this.mockResult = mockResult;
}
@Override
public String execute(MockParam param) {
return mockResult;
}
}
7. 监控
7.1 性能监控
监控 Agent 的性能指标:
public class PerformanceMonitor {
private static final Map<String, Long> toolExecutionTimes = new ConcurrentHashMap<>();
private static final Map<String, Integer> toolCallCounts = new ConcurrentHashMap<>();
public static void recordToolCall(String toolName, long executionTime) {
toolExecutionTimes.merge(toolName, executionTime, Long::sum);
toolCallCounts.merge(toolName, 1, Integer::sum);
}
public static void printReport() {
System.out.println("工具执行统计:");
toolCallCounts.forEach((tool, count) -> {
long totalTime = toolExecutionTimes.getOrDefault(tool, 0L);
double avgTime = (double) totalTime / count;
System.out.printf(" %s: 调用 %d 次, 平均耗时 %.2f ms%n", tool, count, avgTime);
});
}
}
7.2 Token 使用监控
监控 Token 使用情况:
public class TokenMonitor {
private static final Map<String, Long> tokenUsage = new ConcurrentHashMap<>();
public static void recordUsage(String model, long tokens) {
tokenUsage.merge(model, tokens, Long::sum);
}
public static long getTotalUsage(String model) {
return tokenUsage.getOrDefault(model, 0L);
}
}
7.3 错误监控
监控和统计错误:
public class ErrorMonitor {
private static final Map<String, Integer> errorCounts = new ConcurrentHashMap<>();
public static void recordError(String errorType) {
errorCounts.merge(errorType, 1, Integer::sum);
}
public static void printReport() {
System.out.println("错误统计:");
errorCounts.forEach((error, count) -> {
System.out.printf(" %s: %d 次%n", error, count);
});
}
}
8. 部署
8.1 环境变量
使用环境变量管理配置:
public class Config {
public static String getApiKey() {
return System.getenv("API_KEY");
}
public static String getBaseUrl() {
return System.getenv().getOrDefault("BASE_URL", "https://token-plan-cn.xiaomimimo.com");
}
public static String getBaseUrl() {
return System.getenv().getOrDefault("BASE_URL", "https://token-plan-cn.xiaomimimo.com");
}
public static String getModel() {
return System.getenv().getOrDefault("MODEL", "mimo-v-2.5-pro");
}
}
实现健康检查端点:
@RestController
public class HealthController {
@GetMapping("/health")
public ResponseEntity<Map<String, Object>> health() {
Map<String, Object> status = new HashMap<>();
status.put("status", "UP");
status.put("timestamp", Instant.now());
status.put("version", "1.0.0");
return ResponseEntity.ok(status);
}
}
8.3 优雅关闭
实现优雅关闭:
@Component
public class GracefulShutdown implements DisposableBean {
private static final Logger logger = LoggerFactory.getLogger(GracefulShutdown.class);
@Override
public void destroy() {
logger.info("开始优雅关闭...");
// 等待正在执行的任务完成
// 保存会话状态
// 释放资源
logger.info("优雅关闭完成");
}
}
总结
这些最佳实践来自实际开发经验,希望能帮你构建高质量的 Agent 应用。记住:
- Agent 设计:描述要精准,角色要明确,工具要精简
- 任务设计:任务要具体,提供上下文,设置约束
- 错误处理:实现重试机制,处理超时,记录日志
- 性能优化:合理使用 Plan,并行处理,缓存结果
- 安全性:限制工具权限,验证输入,审计日志
- 测试:单元测试,集成测试,Mock 测试
- 监控:性能监控,Token 使用监控,错误监控
- 部署:环境变量,健康检查,优雅关闭
Agent4J 是一个强大的框架,但只有正确使用,才能发挥它的最大价值。
最后
感谢你阅读这个系列的文章。希望这些内容能帮助你更好地使用 Agent4J。
如果你有任何问题或建议,欢迎在 GitHub 上提出 Issue。
祝你在 Agent 开发的道路上一切顺利!
项目地址:https://github.com/onlyGuo/agent4j
系列文章目录: