# 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 循环。_

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 的逆向工程。