# reed-agent **Repository Path**: wangwei8899/reed-agent ## Basic Information - **Project Name**: reed-agent - **Description**: reed agent 自研agent项目 - **Primary Language**: Java - **License**: MulanPSL-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-08-15 - **Last Updated**: 2026-10-22 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # ReedAgent 轻量级 Java AI Agent 框架 MVP,对标 Spring AI 核心子集。提供统一的 `ChatModel` 抽象与完整的 Function Calling 链路(注解 → Schema 生成 → 注册 → 反射执行 → 多轮编排),内置 DeepSeek、通义千问(DashScope)适配器,不绑定 Spring。 ## 特性 - **统一对话抽象**:业务代码只面向 `ChatModel` 接口,切换厂商零改动 - **多模型适配器**:DeepSeek、通义千问(DashScope 原生协议) - **声明式工具定义**:`@AiTool` / `@AiParam` 注解自动生成 OpenAI 规范 JSON Schema - **多轮工具编排**:模型调用 → 工具执行 → 结果回填自动循环,内置最大轮次保护 - **零框架依赖**:仅 Jackson + OkHttp + SLF4J,Java 17+ ## 模块结构 | 模块 | 职责 | | --- | --- | | `agent-core` | 核心抽象(消息/请求/响应、`ChatModel`、异常体系)、工具体系(注解/Schema/注册/执行/编排)与内置工具集(文件/检索/Glob/Shell/WebFetch/桌面控制) | | `agent-llm-deepseek` | DeepSeek 对话模型适配(OpenAI v1 兼容协议,默认 `deepseek-v4-pro`) | | `agent-llm-qwen` | 通义千问 DashScope 适配(默认 `qwen-plus`) | | `agent-davsl` | 桌面自动化 DSL(DA-VSL)录制 / 回放引擎 | | `agent-recorder` | 全局键鼠录制器,录制事件转为 DA-VSL 脚本 | | `agent-examples` | 示例 Demo 与端到端集成测试 | 依赖方向单向:适配器与功能库依赖 `agent-core`,`agent-core` 不反向依赖。 ## 快速接入 ### 1. 引入依赖 ```xml com.reed.agent agent-core 0.1.0-SNAPSHOT com.reed.agent agent-tool 0.1.0-SNAPSHOT com.reed.agent agent-deepseek-adapter 0.1.0-SNAPSHOT com.reed.agent agent-qwen-adapter 0.1.0-SNAPSHOT ``` > 需先在本仓库根目录执行 `mvn install` 安装到本地仓库。 ### 2. 创建模型并对话 ```java String apiKey = System.getenv("DEEPSEEK_API_KEY"); ChatModel model = new DeepSeekChatModel( DeepSeekChatOptions.builder().apiKey(apiKey).build()); ChatResponse response = model.call(ChatRequest.of( List.of(ChatMessage.user("你好,介绍一下你自己")))); System.out.println(response.message().content()); ``` 通义千问写法对称: ```java ChatModel model = new QwenChatModel( QwenChatOptions.builder().apiKey(System.getenv("DASHSCOPE_API_KEY")).build()); ``` 可选配置:`baseUrl(...)`、`model(...)`(覆盖默认模型名)、`connectTimeout(...)`、`readTimeout(...)`。 `DouyinSkillDemo` 参数为 `modelName baseUrl [readTimeoutSeconds]`,统一使用 DeepSeek 适配器(OpenAI v1 兼容协议); 超时时间不传时为 60 秒。例如: `mvn -pl agent-examples exec:java "-Dexec.mainClass=com.reed.agent.examples.DouyinSkillDemo" "-Dexec.args=glm-4.7-flash https://open.bigmodel.cn/api/paas/v4 180"`。 不调用大模型、直接验证抖音浏览工具时,先启动外部 CDP 浏览器,再显式指定场景运行 `DouyinBrowserScenarioDemo`: ```bash mvn -pl agent-examples exec:java \ "-Dexec.mainClass=com.reed.agent.examples.DouyinBrowserScenarioDemo" \ "-Dexec.args=work 1" ``` 支持的场景为 `account`、`navigation`、`work`、`comments`、`like`、`comment`、 `interaction`、`batch`、`authorWorks`、`authorUnread` 和 `loop`。普通场景参数依次为起始作者序号、 作者数量和每位作者作品数量;`authorWorks` 的参数为作者序号、作品起始序号和作品数量, `authorUnread` 的参数为作者序号和未看作品数量。`like`、`comment`、`interaction`、`batch` 会执行真实点赞或评论, 评论内容为固定测试文本。 `DouyinDavslDemo` 走「自然语言 → DA-VSL → 零 LLM 回放」管线:以 Playwright Java SDK 直接驱动 Chromium 提供 `browser_*` 通用浏览器工具与抖音语义工具,经 DA-VSL 生成-校验-回放完成抖音关注作者 「未看」作品的评论与点赞,生成阶段使用 DeepSeek 模型(环境变量 `DEEPSEEK_API_KEY`), 浏览器由 Playwright SDK 自管、登录态由持久化用户数据目录 `./playwright-profile` 复用: ```bash mvn -pl agent-examples -am install -DskipTests mvn -pl agent-examples exec:java \ "-Dexec.mainClass=com.reed.agent.examples.DouyinDavslDemo" \ "-Dexec.args=180" ``` 命令行参数为可选 `readTimeoutSeconds`(默认 60 秒),模型与端点使用 DeepSeek 适配器默认值(`deepseek-v4-pro`)。 - 首次运行需安装 Chromium 内核(需网络): `mvn -pl agent-examples exec:java "-Dexec.mainClass=com.microsoft.playwright.CLI" "-Dexec.args=install chromium"`。 - 登录态复用:浏览器以用户数据目录 `./playwright-profile` 启动,复用既有登录态;profile 为空时 首次运行需手动扫码登录,登录态写入该目录供后续会话复用。 - 场景操作边界与安全约束见 `docs/agent-douyin-davsl-scenario.md`。 ### 3. 定义并注册工具 用 `@AiTool` 标注方法、`@AiParam` 描述参数,即可自动生成工具 Schema: ```java public class WeatherTools { @AiTool(description = "查询指定城市的实时天气,返回天气状况、温度与风力") public String getWeather(@AiParam(description = "城市名称,例如:上海") String city) { return city + ":晴,25 度,东南风 3 级,空气质量优"; } } ``` ```java InMemoryToolRegistry registry = new InMemoryToolRegistry(); registry.registerInstance(new WeatherTools()); // 也可按包扫描批量注册(要求工具类有无参构造器) registry.scanPackage("com.example.tools"); ``` ### 4. 使用编排器完成工具调用 ```java FunctionCallingOrchestrator orchestrator = new DefaultFunctionCallingOrchestrator( model, registry, new ReflectiveToolExecutor(registry)); ChatResponse answer = orchestrator.chat( List.of(ChatMessage.user("上海今天天气如何?"))); System.out.println(answer.message().content()); ``` 编排器每轮携带工具定义请求模型,收到工具调用指令后依次执行并以 TOOL 消息回填上下文,直到模型给出最终回答;最大轮次默认 5,可用构造器自定义,并支持传入上下文准备钩子(`ContextPreparer`)在首轮前做一次性上下文装配(技能索引注入即基于该钩子)。 完整可运行示例见 `agent-examples`:`SimpleChatDemo`(双模型普通对话)、`FunctionCallingDemo`(工具调用全链路)、`BuiltinToolsDemo`(内置只读工具问答)、`SkillsDemo`(技能索引渐进披露)。 ## 内置工具(agent-core) 面向 Agent 场景的内置工具集,全部受沙箱约束、默认禁用、按需显式启用: | 启用开关 | 提供的工具 | 说明 | | --- | --- | --- | | `enableFileSystem` | `readFile` / `writeFile` / `listDirectory` / `getCurrentWorkDir` | 沙箱内文件读写与目录浏览 | | `enableGrep` | `searchByContent` / `searchFileNames` | 文件内容检索与文件名查找 | | `enableGlob` | `findFiles` | 按 Glob 模式递归查找文件 | | `enableShell` | `executeCommand` | 执行 Shell 命令(名单管控 + 超时收敛) | | `enableWebFetch` | `fetchPage` | 抓取网页并提取正文文本(SSRF 防护) | | `enableSkills` | `loadSkill` | 技能正文按需读取(配合技能索引渐进披露) | ### 启用内置工具 内置工具集已并入 `agent-core`,引入 `agent-core` 依赖即可使用: ```java Path sandbox = Files.createTempDirectory("reed-agent-sandbox"); InMemoryToolRegistry registry = new InMemoryToolRegistry(); BuiltinToolsConfig config = BuiltinToolsConfig.builder() .workDir(sandbox) .enableFileSystem(true) .enableGrep(true) .build(); BuiltinToolsRegistrar.register(registry, config); ``` `workDir` 为必填项,未配置时注册阶段立即失败;所有开关默认关闭,不启用任何开关则不注册任何工具。 ### Shell 命令管控:白名单优先 `executeCommand` 默认处于内置黑名单兜底模式(Unix/Windows 双平台危险命令名单)。生产环境强烈建议配置命令白名单——白名单非空时仅名单内命令可执行,黑名单仍叠加兜底: ```java SecurityPolicy policy = SecurityPolicy.builder() .commandWhitelist(List.of("mvn", "git", "ls", "dir")) .maxTimeoutSeconds(120) .build(); BuiltinToolsConfig config = BuiltinToolsConfig.builder() .workDir(sandbox) .enableShell(true) .securityPolicy(policy) .build(); ``` ### 沙箱与 SSRF 约束 - **沙箱边界**:文件、检索、Glob 与 Shell 工具统一限制在 `workDir` 内;`../` 越界、沙箱外绝对路径、符号链接逃逸、`.` 开头隐藏路径均抛出 `ToolSecurityException`。 - **命令执行**:不接收 stdin;超时强制终止整个进程树并返回超时标识(不抛异常);输出合并截断 10000 字符;工具超时参数受策略 `maxTimeoutSeconds`(默认 300 秒)收敛。 - **网页抓取**:仅允许 http/https;DNS 解析后校验目标 IP,拒绝私有/环回/链路本地/any/IPv6 ULA 地址;手动跟随重定向并逐跳复检(上限 5 跳);非文本响应拒绝,正文截断 20000 字符。可通过 `SecurityPolicy` 配置域名白名单(非空时仅白名单内域名可访问)与域名黑名单。 - **审计埋点**:所有内置工具调用经 `ToolCallAuditor` 记录日志;`writeFile` / `executeCommand` 等高危操作为 WARN 级别,文件内容等敏感参数自动脱敏。 ### 技能(Skills):技能即文档的渐进披露 技能是可复用的操作知识:部署方以 Markdown 文档沉淀领域流程与操作指南,编排对话时系统仅把技能索引(名称 + 描述)注入首轮上下文,模型按需调用 `loadSkill` 按名获取正文,正文不随索引进入上下文。 **目录约定**:技能目录下每个技能占一个子目录,子目录内以固定文件名 `SKILL.md` 承载;文档以 frontmatter 声明 `name` 与 `description`(仅扁平键值,不依赖 YAML 库),正文为指南内容。坏文档(缺 frontmatter / 缺 name / 正文为空)跳过并告警,技能名重复时后加载者跳过。 ```java InMemoryToolRegistry registry = new InMemoryToolRegistry(); BuiltinToolsConfig config = BuiltinToolsConfig.builder() .workDir(sandbox) .enableSkills(true) .skillsDir(Path.of("skills")) // 启用技能时必填 .build(); Optional preparer = BuiltinToolsRegistrar.register(registry, config); // 装配携带技能索引准备器的编排器;渐进披露多一轮 loadSkill 往返,建议调大轮次 FunctionCallingOrchestrator orchestrator = new DefaultFunctionCallingOrchestrator( model, registry, new ReflectiveToolExecutor(registry), 8, preparer.orElse(null)); ``` - **注册阶段一次性构建不可变索引**,不支持热加载;启用技能而 `skillsDir` 缺失时注册失败。 - **只读边界**:`loadSkill` 仅经技能目录独立只读根校验(规范化 + 符号链接复检),与工作目录沙箱互不相通;正文超 20000 字符截断并提示。 - **信任边界**:技能文档视为部署方提供的可信内容,框架不对正文做内容净化——请勿加载来源不明的技能文档。 ## API Key 配置 通过环境变量提供,框架不会将 Key 打印到日志: | 厂商 | 环境变量 | | --- | --- | | DeepSeek | `DEEPSEEK_API_KEY` | | 通义千问(DashScope) | `DASHSCOPE_API_KEY` | | `DouyinSkillDemo` 使用的 DeepSeek 适配器(OpenAI v1 兼容协议) | `OPENAI_API_KEY` | 错误 Key / 非 2xx 响应统一抛出 `ModelCallException`(携带厂商错误码与信息),不做自动重试。 ## 构建与测试 ```bash # 全量单元测试(JaCoCo 行覆盖率门槛 70%) mvn test # 端到端集成测试(需先设置 API Key 环境变量;按 Key 条件执行,缺 Key 自动跳过) mvn verify -DskipITs=false # Javadoc mvn javadoc:javadoc ``` **编译要求**:本工程全局启用 `-parameters` 编译参数,工具 Schema 生成依赖真实参数名;若在使用方项目中自行编译工具类,请同样开启该参数,否则注册时会因检测到 `arg0` 样式合成参数名而快速失败。 ## 已知限制 - **包扫描仅支持 classpath 目录**:`scanPackage` 通过遍历文件系统目录发现类,不支持从 jar 包内扫描;jar 部署场景请改用 `registerInstance` 手动注册。 - 工具方法仅支持实例方法,不支持静态方法。 - 工具入参支持类型:`String`、`Integer`/`int`、`Long`/`long`、`Double`/`double`、`Boolean`/`boolean`。 - 仅提供同步阻塞调用,无流式输出;不内置重试与限流。 - **内置工具 HTML 提取保真度有限**:`fetchPage` 的正文提取基于正则与标签剥离,仅保证可读文本提取,不保证结构与样式保真;需要结构化数据时请勿依赖该工具。 - **正则检索无 ReDoS 防护**:`searchByContent` 的正则模式直接编译执行用户提供的表达式,不防御恶意模式导致的慢查询(ReDoS),调用方应自行评估与限时。 - **命令黑名单仅作兜底**:内置黑名单用于抬高危险命令的执行成本,不是完整安全边界;生产环境必须使用命令白名单严格管控。 - **技能正文是提示注入面**:技能文档内容直接进入模型上下文,恶意文档可操纵模型行为;框架按"部署方可信内容"定位,不做净化,请只加载可信来源的技能。 - **技能不支持热加载**:索引在注册阶段一次性构建,技能文档变更需重新注册。 - **技能场景轮次预算**:渐进披露固定多一轮 `loadSkill` 往返,编排器最大轮次默认 5 不变,启用技能时建议按需调大。 ## 许可证 见 [LICENSE](LICENSE)。