# agent-sdk **Repository Path**: greycode/agent-sdk ## Basic Information - **Project Name**: agent-sdk - **Description**: Java 环境下最简单的 Agent 框架。没有抽象。没有魔法。仅仅是一个工具调用的 for 循环。 - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-01-24 - **Last Updated**: 2026-06-12 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # bu-agent-sdk-java _Agent 仅仅是一个 for 循环。_ ![Agent Loop](./static/agent-loop.png) Java 环境下最简单的 Agent 框架。没有抽象。没有魔法。仅仅是一个工具调用的 for 循环。 ## 要求 - JDK 21+ - Maven 3.8+ ## 安装 添加到你的 `pom.xml`: ```xml com.buagent agent-sdk 1.0.0-SNAPSHOT ``` ## 快速开始 ### 一行代码(最简方式) ```java import com.buagent.sdk.agent.Agent; // 从环境变量自动检测 LLM(ANTHROPIC_API_KEY、OPENAI_API_KEY 等) String answer = Agent.ask("What is 2 + 2?"); ``` ### 快速构建器(推荐) ```java import com.buagent.sdk.agent.Agent; // 自动检测 LLM,最少配置 // 使用 try-with-resources 确保资源正确释放 try (Agent agent = Agent.quick() .systemPrompt("You are a helpful assistant.") .tools(new MyTools()) // 自动扫描 @AgentTool 方法 .build()) { String result = agent.query("What is 2 + 3?").join(); System.out.println(result); } // Agent 自动关闭 ### 指定提供商构建器 ```java // OpenAI(需要 OPENAI_API_KEY) Agent agent = Agent.withOpenAI() .model("gpt-4o") .systemPrompt("You are helpful.") .build(); // Anthropic(需要 ANTHROPIC_API_KEY) Agent agent = Agent.withAnthropic() .model("claude-sonnet-4-20250514") .systemPrompt("You are helpful.") .build(); // Google(需要 GOOGLE_API_KEY) Agent agent = Agent.withGoogle() .model("gemini-2.0-flash") .systemPrompt("You are helpful.") .build(); ``` ### 完全控制 ```java import com.buagent.sdk.agent.Agent; import com.buagent.sdk.core.exception.TaskComplete; import com.buagent.sdk.tools.core.AgentTool; import com.buagent.sdk.tools.reflect.ToolScanner; import com.buagent.sdk.llm.client.openai.OpenAIChatModel; public class Example { @AgentTool("Add two numbers") public int add(int a, int b) { return a + b; } @AgentTool("Signal task completion") public void done(String message) { throw new TaskComplete(message); } public static void main(String[] args) { // 扫描 @AgentTool 注解的方法 var tools = ToolScanner.scan(new Example()); // 创建 Agent Agent agent = Agent.builder() .llm(new OpenAIChatModel("gpt-4o", System.getenv("OPENAI_API_KEY"))) .tools(tools) .systemPrompt("You are a helpful assistant.") .build(); // 查询 Agent String result = agent.query("What is 2 + 3?").join(); System.out.println(result); } } ``` ## 设计哲学 **苦涩的教训 (The Bitter Lesson):** 所有的价值都在经过强化学习 (RL) 的模型中,而不是你那 10,000 行抽象代码里。 Agent 框架的失败不是因为模型太弱,而是因为它们的动作空间不完整。给予 LLM 尽可能多的自由,然后基于评估 (evals) 进行微调限制。 ## 特性 ### 完成工具模式 (Done Tool Pattern) 朴素的“当没有工具调用时停止”的方法是行不通的。Agents 会过早结束。强制显式完成: ```java @AgentTool("Signal completion") public void done(String message) { throw new TaskComplete(message); } Agent agent = Agent.builder() .llm(llm) .tools(tools) .requireDoneTool(true) // Autonomous mode .build(); ``` ### 临时消息 大型工具输出(浏览器状态、截图)会撑爆上下文。只保留最近的 N 条: ```java @AgentTool(value = "Get browser state", ephemeral = 3) // Keep last 3 only public String getState() { return massiveDomAndScreenshot; } ``` ### 简单的 LLM 原语 跨提供商的统一接口。完全控制: ```java import com.buagent.sdk.llm.client.openai.OpenAIChatModel; // All implement BaseChatModel Agent agent = Agent.builder() .llm(new OpenAIChatModel("gpt-4o", apiKey)) .tools(tools) .build(); ``` ### 依赖注入 FastAPI 风格,类型安全: ```java import com.buagent.sdk.tools.di.DependencyContainer; import com.buagent.sdk.tools.di.Inject; @AgentTool("Query users") public String getUser( int id, @Inject(DatabaseSession.class) DatabaseSession db ) { return db.find(id); } // Configure dependencies DependencyContainer deps = DependencyContainer.create(); deps.register(DatabaseSession.class, () ->new DatabaseSession()); Agent agent = Agent.builder() .llm(llm) .tools(tools) .dependencies(deps) .build(); ``` ### 编程式工具定义 使用流畅的 `Tool.Builder` API 以编程方式定义工具: ```java import com.buagent.sdk.tools.core.Tool; import com.buagent.sdk.tools.core.ToolParamDef; import java.util.concurrent.CompletableFuture; // 简单工具,内联参数 Tool addTool = Tool.builder() .name("add") .description("Add two numbers") .parameter("a", int.class, "First number", true) .parameter("b", int.class, "Second number", true) .executor(args -> { int a = ((Number) args.get("a")).intValue(); int b = ((Number) args.get("b")).intValue(); return CompletableFuture.completedFuture(a + b); }) .build(); // 高级工具,使用 ToolParamDef 获得更多控制 Tool searchTool = Tool.builder() .name("search") .description("Search documents") .parameter(ToolParamDef.builder() .name("query") .type(String.class) .description("Search query") .required(true) .build()) .parameter(ToolParamDef.builder() .name("format") .type(String.class) .description("Output format") .enumValues("json", "xml", "csv") // 枚举约束 .required(false) .defaultValue("json") .build()) .ephemeral(3) // 仅保留最近 3 次输出在上下文中 .executor(args -> CompletableFuture.completedFuture("results...")) .build(); // 与 Agent 一起使用 Agent agent = Agent.quick() .tools(addTool, searchTool) .build(); ``` ### 流式事件 事件按类别组织,便于处理: ```java import com.buagent.sdk.agent.events.*; agent.queryStreamAsync("do something").subscribe(new Flow.Subscriber<>() { @Override public void onNext(AgentEvent event) { // 按类别处理 if (event instanceof StreamingEvent streaming) { // 实时流式块:TextChunkEvent、ThinkingChunkEvent、ToolCallChunkEvent switch (streaming) { case TextChunkEvent e -> System.out.print(e.getDelta()); case ThinkingChunkEvent e -> System.out.print("[thinking] " + e.getDelta()); case ToolCallChunkEvent e -> System.out.print("[tool] " + e.getDelta()); } } else if (event instanceof ToolEvent tool) { // 工具交互:ToolCallEvent、ToolResultEvent switch (tool) { case ToolCallEvent e -> System.out.println("Calling " + e.getTool()); case ToolResultEvent e -> System.out.println(e.getTool() + " -> " + e.getResult()); } } else if (event instanceof ContentEvent content) { // 完整内容:TextEvent、ThinkingEvent、FinalResponseEvent switch (content) { case FinalResponseEvent e -> System.out.println("Done: " + e.getContent()); default -> {} } } else if (event instanceof TerminalEvent terminal) { // 错误:ErrorEvent、StreamingCancelledEvent switch (terminal) { case ErrorEvent e -> System.err.println("Error: " + e.getMessage()); case StreamingCancelledEvent e -> System.out.println("Cancelled: " + e.getReason()); } } // 其他类别:LifecycleEvent、MetadataEvent } // ... 其他订阅者方法 }); ``` **事件类别:** - `StreamingEvent` - 实时流式块(TextChunkEvent、ThinkingChunkEvent、ToolCallChunkEvent) - `ContentEvent` - 完整内容(TextEvent、ThinkingEvent、FinalResponseEvent) - `ToolEvent` - 工具交互(ToolCallEvent、ToolResultEvent) - `LifecycleEvent` - 执行生命周期(StepStartEvent、StepCompleteEvent、MessageStartEvent、MessageCompleteEvent) - `TerminalEvent` - 执行终止(ErrorEvent、StreamingCancelledEvent) - `MetadataEvent` - 补充信息(UsageEvent、HiddenUserMessageEvent) ### 配置分组 使用分组配置简化 Agent 配置: ```java import com.buagent.sdk.agent.config.RetryConfig; import com.buagent.sdk.agent.config.StreamingConfig; // 分组配置(推荐) Agent agent = Agent.builder() .llm(llm) .tools(tools) .retry(RetryConfig.builder() .maxRetries(3) .baseDelay(2.0) .build()) .streaming(StreamingConfig.streaming()) .build(); // 使用工厂方法快速创建 Agent simpleAgent = Agent.simple(llm, "You are a helpful assistant.", tools); // 流式 Agent 构建器 Agent streamingAgent = Agent.streamingBuilder() .llm(llm) .tools(tools) .build(); ``` **RetryConfig** - LLM 重试配置: - `RetryConfig.defaults()` - 默认重试(5 次重试,1-60 秒延迟) - `RetryConfig.noRetry()` - 禁用重试 - `RetryConfig.forRateLimiting()` - 针对速率限制优化(10 次重试,更长延迟) **StreamingConfig** - 流式配置: - `StreamingConfig.defaults()` - 禁用流式 - `StreamingConfig.streaming()` - 启用 LLM 流式输出 - `StreamingConfig.streaming(options)` - 启用并使用自定义选项 ### 生命周期 Hook 在 Agent 执行的关键生命周期点插入自定义逻辑,用于日志记录、审计、验证或控制: ```java import com.buagent.sdk.agent.hook.*; // 创建自定义 Hook AgentHook loggingHook = new AgentHook() { @Override public int priority() { return -100; } // 数值越小优先级越高 @Override public boolean beforeLLMInvoke(BeforeLLMContext context) { System.out.println("LLM 调用 #" + context.getIteration()); context.setPayload("startTime", System.currentTimeMillis()); // 在 Hook 之间传递数据 return true; // 继续执行 (false = 终止) } @Override public boolean afterLLMInvoke(AfterLLMContext context) { long duration = System.currentTimeMillis() - context.getPayload("startTime", Long.class).orElse(0L); System.out.println("LLM 调用完成,耗时 " + duration + "ms"); return true; } @Override public boolean beforeToolExecute(BeforeToolContext context) { System.out.println("执行工具: " + context.getToolName()); return true; } @Override public boolean afterToolExecute(AfterToolContext context) { System.out.println("工具 " + context.getToolName() + " 耗时 " + context.getExecutionTimeMs() + "ms"); return true; } }; // 在 Agent 中使用 Agent agent = Agent.builder() .llm(llm) .tools(tools) .hook(loggingHook) // 单个 Hook .build(); // 或使用 HookManager 管理多个 Hook HookManager hookManager = new HookManager(); hookManager.register(loggingHook); hookManager.register(auditHook); Agent agent = Agent.builder() .llm(llm) .tools(tools) .hookManager(hookManager) // 多个 Hook,按优先级排序执行 .build(); ``` **Hook 特性:** - **4 个生命周期点**:`beforeLLMInvoke`、`afterLLMInvoke`、`beforeToolExecute`、`afterToolExecute` - **优先级排序**:数值越小的优先级越高,越先执行 - **执行控制**:返回 `false` 可终止执行 - **Payload 传递**:通过 `setPayload`/`getPayload` 在 Hook 之间共享数据 - **丰富的上下文**:可访问消息历史、工具信息、Token 用量等 ### XML 工具调用模式 对于不支持原生函数调用的模型(如某些开源模型),或者需要更灵活的工具调用格式时,可以启用 XML 模式。在该模式下,工具定义会被嵌入到系统提示中,LLM 以 XML 格式返回工具调用。 #### 基本用法 ```java import com.buagent.sdk.agent.Agent; import com.buagent.sdk.llm.config.ToolCallingConfig; // 方式一:使用便捷方法启用 XML 模式 Agent agent = Agent.builder() .llm(llm) .tools(tools) .systemPrompt("You are a helpful assistant.") .xmlToolCalling(true) // 启用 XML 工具调用 .build(); // 方式二:使用完整配置对象 ToolCallingConfig config = ToolCallingConfig.builder() .xmlMode(true) .includeUsageExamples(true) // 在系统提示中包含工具使用示例 .build(); Agent agent = Agent.builder() .llm(llm) .tools(tools) .systemPrompt("You are a helpful assistant.") .toolCallingConfig(config) .build(); ``` #### XML 工具调用格式 在 XML 模式下,LLM 会以如下格式调用工具: ```xml read /path/to/file.txt ``` 工具执行结果会以 XML 格式返回给 LLM: ```xml read File contents here... ``` #### 流式 XML 工具调用 XML 模式完全支持流式输出。在流式模式下,框架会实时解析 XML 并发出相应事件: ```java Agent agent = Agent.builder() .llm(llm) .tools(tools) .systemPrompt("You are a helpful assistant.") .xmlToolCalling(true) .llmStreamingEnabled(true) // 启用流式输出 .build(); agent.queryStreamAsync("Read the config file").subscribe(new Flow.Subscriber<>() { @Override public void onNext(AgentEvent event) { switch (event) { case TextChunkEvent e -> { // 实时文本输出(已过滤 XML 工具调用标记) System.out.print(e.getDelta()); } case ToolCallChunkEvent e -> { // XML 解析到完整工具调用时触发 if (e.isComplete()) { System.out.println("Tool call: " + e.getFunctionName()); } } case ToolResultEvent e -> { System.out.println("Result: " + e.getResult()); } default -> {} } } // ... other methods }); ``` #### 适用场景 - **开源模型**:许多开源模型不支持原生函数调用,XML 模式提供了通用的工具调用方案 - **自定义模型**:对于自托管或微调的模型,XML 格式更易于训练和适配 - **调试与可视化**:XML 格式的工具调用更易于阅读和调试 - **跨平台兼容**:不依赖特定 API 的函数调用协议 ## ~150 行代码实现 Claude Code 一个通过依赖注入实现的沙箱化编程助手: ```java import com.buagent.sdk.agent.Agent; import com.buagent.sdk.core.exception.TaskComplete; import com.buagent.sdk.llm.client.openai.OpenAIChatModel; import com.buagent.sdk.tools.core.AgentTool; import com.buagent.sdk.tools.core.ToolParam; import com.buagent.sdk.tools.di.DependencyContainer; import com.buagent.sdk.tools.di.Inject; import com.buagent.sdk.tools.reflect.ToolScanner; // Sandbox context for secure file operations public class SandboxContext { private final Path rootDir; private Path workingDir; public Path resolvePath(String path) { Path resolved = workingDir.resolve(path).normalize(); if (!resolved.startsWith(rootDir)) { throw new SecurityException("Path escapes sandbox: " + path); } return resolved; } } // Claude Code-style tools public class ClaudeCodeTools { @AgentTool("Execute shell command") public String bash( @ToolParam("The command to execute") String command, @Inject(SandboxContext.class) SandboxContext ctx) { ProcessBuilder pb = new ProcessBuilder("bash", "-c", command); pb.directory(ctx.getWorkingDir().toFile()); // ... execute and return output } @AgentTool("Read file contents") public String read( @ToolParam("Path to the file") String filePath, @Inject(SandboxContext.class) SandboxContext ctx) { return Files.readString(ctx.resolvePath(filePath)); } @AgentTool("Write file contents") public String write( @ToolParam("Path to the file") String filePath, @ToolParam("Content to write") String content, @Inject(SandboxContext.class) SandboxContext ctx) { Files.writeString(ctx.resolvePath(filePath), content); return "Wrote " + content.length() + " bytes"; } @AgentTool("Find files by glob pattern") public String glob( @ToolParam("The glob pattern") String pattern, @Inject(SandboxContext.class) SandboxContext ctx) { // ... glob search implementation } @AgentTool("Signal task completion") public String done(@ToolParam("Completion message") String message) { throw new TaskComplete(message); } } public static void main(String[] args) { SandboxContext ctx = SandboxContext.create(Path.of("./sandbox")); DependencyContainer deps = DependencyContainer.create(); deps.register(SandboxContext.class, () -> ctx); var tools = ToolScanner.scan(new ClaudeCodeTools()); Agent agent = Agent.builder() .llm(new OpenAIChatModel("gpt-4o", System.getenv("OPENAI_API_KEY"))) .tools(tools) .dependencies(deps) .systemPrompt("Coding assistant. Working dir: " + ctx.getWorkingDir()) .build(); // Interactive loop while (true) { String task = new Scanner(System.in).nextLine(); agent.queryStreamAsync(task).subscribe(/* event handler */); } } ``` 查看 [`ClaudeCodeExample.java`](./src/main/java/com/buagent/sdk/examples/ClaudeCodeExample.java) 获取包含 grep、edit 和 todo 工具的完整版本。 ## 残酷的真相 每一个抽象都是一种负担。每一个“助手”都是一个故障点。 模型已经变得很好了。真的很好。它们经过了计算机使用、编程、浏览的强化学习训练。它们不需要你的护栏。它们需要: - 一个完整的动作空间 - 一个 for 循环 - 一个显式的退出机制 - 上下文管理 **苦涩的教训:你构建的越少,它工作得越好。** ## 构建 ```bash mvn clean compile ``` ## 许可证 MIT ## 致谢 由 [Browser Use](https://browser-use.com) 构建。灵感来自于对 Claude Code 和 Gemini CLI 的逆向工程。