# java-ai-harness **Repository Path**: jackXUYY/java-ai-harness ## Basic Information - **Project Name**: java-ai-harness - **Description**: 轻量级 Java Agent Harness 框架:零 Spring、最少依赖、开箱即用。 - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-08-21 - **Last Updated**: 2026-08-22 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README --- AIGC: Label: "1" ContentProducer: 001191440300708461136T1XGW3 ProduceID: d9131e1e6ecd94de9637295002cb1e5c_cceef82b9d7e11f1a413525400287e28 ReservedCode1: UIg8TgG3EKajriCQ29bBIqAlTNL42nOzrUa0bfo5UhWvvS7tGnrU5JXxNJbUcccXK8uj9udB6uNelwFi1ArQspV4SzYRvwO40vIpTAk9zMVwQ56E7VVrtRZVC6qoEE6AQyVLQDv6JstvUwqPd8xmwzPzNSLsEoS3dh3KSECKhs6ES88/Dmj2kVlaivU= ContentPropagator: 001191440300708461136T1XGW3 PropagateID: d9131e1e6ecd94de9637295002cb1e5c_cceef82b9d7e11f1a413525400287e28 ReservedCode2: UIg8TgG3EKajriCQ29bBIqAlTNL42nOzrUa0bfo5UhWvvS7tGnrU5JXxNJbUcccXK8uj9udB6uNelwFi1ArQspV4SzYRvwO40vIpTAk9zMVwQ56E7VVrtRZVC6qoEE6AQyVLQDv6JstvUwqPd8xmwzPzNSLsEoS3dh3KSECKhs6ES88/Dmj2kVlaivU= --- # java-ai-harness 轻量级 Java Agent Harness 框架:零 Spring、最少依赖、开箱即用。 面向生产级交付:配置外部化、结构化长期记忆、token 级上下文管理、工具安全防线、事件可观测。 ## 特性 - **零框架依赖**:仅 1 个运行时依赖(Gson),JDK 17 原生 HttpClient - **ReAct 工具调用循环**:内置 LLM → 工具 → LLM 自动循环,支持同步与流式双模式 - **注解式工具注册**:`@AgentTool` + `@ToolParam`,自动生成 JSON Schema - **多模型适配**:兼容 OpenAI 协议(OpenAI / 通义 / 混元 / DeepSeek / Ollama / one-api / 智谱) - **结构化记忆系统**:抽取式沉淀 + 去重合并 + 评分召回 + 容量淘汰,对标 Mem0 / Zep / MemGPT 范式 - **内存向量检索**:默认零依赖本地哈希向量,可选 OpenAI 兼容语义向量;向量 + 关键词混合召回,embedding 失败自动降级 - **token 级上下文管理**:启发式 TokenEstimator 预算裁剪 + LLM 摘要压缩,防超长输出击穿上下文 - **流式 token 观测**:SSE 解析 + UsageDelta 透传,逐轮展示 prompt/completion token - **内置工具**:系统信息 + 文件读写 + 网页抓取,开箱即用 - **多 Agent 编排**:主从派发(MainAgent,实验性)、管线链式(PipelineAgent) - **用户介入(Human-in-the-loop)**:工具级审批,高危工具执行前暂停等待用户确认 - **安全防护**:提示词注入检测、敏感信息脱敏、文件沙箱、高危命令默认不注册 - **可观测性**:事件总线 + 追踪日志 + 轻量指标 - **配置外部化**:`harness.properties` / 环境变量 / 系统属性三级覆盖 ## 快速开始 ```bash # 1. 构建 mvn clean package # 2. 运行终端 Demo(TUI 交互) java -jar target/java-ai-harness-0.1.0.jar ``` 开箱即用:仓库内置的 `src/main/resources/harness.properties` 已配置智谱 `glm-4-flash` 免费端点,构建后即可运行。 > ⚠️ **严禁把 API Key 写死在内置 properties**:该文件随 jar 打包,任何拿到 jar 的人都可见。请通过环境变量或本地覆盖文件注入(见下文配置方式),否则调用会返回 401。 ### 配置方式 配置项优先级(高 → 低):**环境变量 > 系统属性 > 本地 `./harness.properties` > classpath 内置 `harness.properties`**。 **方式一:环境变量**(前缀 `HARNESS_`,配置键中的点号与连字符均转下划线,如 `harness.llm.api-key` ↔ `HARNESS_LLM_API_KEY`) ```bash export HARNESS_LLM_API_KEY=sk-xxx export HARNESS_LLM_BASE_URL=https://api.openai.com/v1 export HARNESS_LLM_MODEL=gpt-4o-mini ``` **方式二:系统属性** ```bash java -Dharness.llm.api-key=sk-xxx \ -Dharness.llm.base-url=https://api.openai.com/v1 \ -Dharness.llm.model=gpt-4o-mini \ -jar target/java-ai-harness-0.1.0.jar ``` **方式三:properties 文件** - 内置默认配置:`src/main/resources/harness.properties`(随 jar 打包,改这里需重新构建); - 本地覆盖配置:工作目录下放一个 `harness.properties` 即可,**无需重新构建**,内容只写要覆盖的项: ```properties harness.llm.base-url=https://api.openai.com/v1 harness.llm.api-key=sk-xxx harness.llm.model=gpt-4o-mini ``` ### 接入其他厂商 框架兼容一切 OpenAI 协议端点,只需改 `base-url` + `model` + `api-key` 三项: | 厂商 | base-url | model 示例 | |------|----------|-----------| | OpenAI | `https://api.openai.com/v1` | `gpt-4o-mini` | | 智谱 | `https://open.bigmodel.cn/api/paas/v4` | `glm-4-flash` | | 通义千问 | `https://dashscope.aliyuncs.com/compatible-mode/v1` | `qwen-plus` | | DeepSeek | `https://api.deepseek.com/v1` | `deepseek-chat` | | 混元 | `https://api.hunyuan.cloud.tencent.com/v1` | `hunyuan-turbos-latest` | | Ollama(本地) | `http://localhost:11434/v1` | `llama3.1` | ## 五步集成 ```java // 1. 初始化(自动加载配置 + 内置工具 + 记忆) Harness harness = Harness.builder().build(); // 2. 注册自定义工具 harness.registerTool(new MyTools()); // 3. 创建 Agent Agent agent = harness.agent("main"); // 4. 同步执行 String answer = agent.run("现在几点了?"); // 5. 流式执行(适合 TUI 逐字展示) agent.stream("帮我查一下系统信息", ev -> { switch (ev) { case StreamEvent.TextDelta td -> System.out.print(td.content()); case StreamEvent.ToolCallStart ts -> System.out.println("\n[调用工具] " + ts.name()); case StreamEvent.ToolResultDelta tr -> System.out.println("[工具结果] " + tr.content()); default -> {} } }); ``` ## 自定义工具 ```java public class MyTools { @AgentTool(name = "weather", description = "查询城市天气") public String weather(@ToolParam("城市名") String city) { return "晴,25°C"; // 你的实现 } } ``` 参数自动映射:String / int / long / double / boolean / 枚举。整数参数兼容 LLM 传浮点字符串(如 `"3000.0"` 自动截断为 `3000`)。 ## 内置工具 框架启动时自动注册以下工具,无需手动添加: | 工具类 | 工具 | 说明 | 审批 | |--------|------|------|------| | `SystemInfoTool` | `get_time` | 当前时间 | - | | | `get_os_info` | 操作系统 / JVM / CPU / 内存 | - | | | `echo` | 原样返回输入 | - | | `FileTool` | `list_dir` | 列出目录条目 | - | | | `read_file` | 读取文本文件(UTF-8,可限长) | - | | | `search_files` | 按文件名关键词递归搜索 | - | | | `file_info` | 文件/目录详情 | - | | | `write_file` | 写入文本文件(自动建目录) | - | | | `delete_file` | 删除文件/空目录 | ✅ 需审批 | | `ShellTool` | `run_command` | 跨平台执行 shell 命令(Windows 走 PowerShell,Linux/macOS 走 `/bin/sh`);命令输出超 8000 字符自动截断标注。**默认不注册**,需 `harness.tool.shell.enabled=true` 启用 | 动态 | | `WebTool` | `http_get` | 抓取网页正文(跟随重定向,内置 SSRF 防护) | - | 审批说明:`delete_file` 属破坏性操作,默认 `requiresApproval=true`;`run_command` 为**动态审批**——普通命令直接执行,命令含删除类操作(`Remove-Item` / `del` / `rm` / `rmdir` 等)时自动要求用户确认;`write_file` **新建文件免审批**,覆盖已存在文件默认拒绝,需先 `delete_file` 删除旧文件(走审批)或配置 `harness.tool.file.allow-overwrite=true`。 ## 工具安全(三道防线) 框架对破坏性操作采用三层防线,全部**默认关闭/默认拒绝**,需要时显式开启: | 防线 | 配置项 | 默认 | 说明 | |------|--------|------|------| | ① 高危命令默认不注册 | `harness.tool.shell.enabled` | `false` | `run_command` 工具默认**不注册**,LLM 无法调用;需显式 `true` 才启用 | | ② 文件沙箱 | `harness.tool.file.work-dir` | 空 | 所有文件工具(list/read/search/info/write/delete)限定在 work-dir 内;空=不限制 | | | `harness.tool.file.allow-any-path` | `false` | `true` 时放行沙箱外路径(逃生口,慎用) | | ③ 覆盖两步确认 | `harness.tool.file.allow-overwrite` | `false` | `write_file` 覆盖已存在文件默认**拒绝**;必须先走 `delete_file` 审批删除旧文件,或显式开启覆盖 | 此外 `ShellTool` 支持构造时传入 `blockedPattern` 正则:命中命令**直接拒绝**(比审批更硬,不弹确认)。`delete_file` 一律走审批回调,未提供 `ApprovalHandler` 时自动拒绝。 ## 记忆系统 结构化长期记忆,对标 Mem0 / Zep / MemGPT 范式,零依赖可落盘。 ### 核心组件 | 组件 | 职责 | |------|------| | `MemoryItem` | 结构化记忆单元:类型(FACT/PREFERENCE/SKILL/GOTCHA/PERSONA)+ 内容 + 重要性 + 时间戳 + 访问计数 | | `MemoryExtractor` | LLM 抽取式沉淀:把 QA 提炼为多条结构化记忆;失败/重试语义的回答在源头拦截(防脏数据回流) | | `MemoryManager` | 统一门面:抽取沉淀 → 去重合并 → 评分召回 → 容量淘汰 | | `MemoryItemStore` | 存储接口:`InMemoryItemStore`(进程内)/ `FileItemStore`(JSON 落盘,旧格式自动重建) | | `MemoryRetriever` | 关键词检索:中文 2-gram 分词,命中率 × 重要性 × 时效 × 频率 | | `MemoryVectorStore` | 内存向量库:id → 归一化向量,余弦 Top-K 检索 | | `EmbeddingModel` | 向量化接口:`HashEmbeddingModel`(默认,零依赖)/ `OpenAiEmbeddingModel`(语义向量) | ### 召回策略 混合召回:向量 Top-N ∪ 关键词命中 → 统一按 `0.6×向量相似度 + 0.4×关键词命中率`(再乘 重要性 × 时效衰减 × 访问频率)排序。 - **默认零依赖**:`HashEmbeddingModel` 用中文 2-gram + 英文 3-gram 哈希到 256 维,离线可用,对词形变体(springboot vs spring)鲁棒; - **可选语义向量**:`harness.memory.embedding=openai` 切换 OpenAI 兼容 /embeddings 接口,复用 LLM 的 base-url 与 API Key; - **失败降级**:embedding 不可用时自动回退纯关键词检索,不阻塞主流程; - **向量不落盘**:纯内存存储,进程重启后对存量记忆自动懒重建; - **容量控制**:默认 500 条,超限淘汰综合分最低的 10%。 ```properties harness.memory.vector.enabled=true # 开启向量检索(默认) harness.memory.embedding=hash # hash(默认) | openai(语义向量) harness.memory.embedding.dimension=256 harness.embedding.model=text-embedding-v3 # openai 模式专用 ``` ## 上下文管理 多轮 ReAct 循环中,历史消息会持续增长,框架提供双层防护: 1. **token 预算裁剪**(`trimMessages`):按 `TokenEstimator` 启发式估算(CJK 1 token/字、ASCII 4 字符/token),超出 `maxHistoryTokens` 时裁剪中间段;最新一条消息超预算也保留,裁剪后强制保留至少一条 user 消息(防 400/1214 报错); 2. **LLM 摘要压缩**(`maybeCompactContext`):上下文超预算 80% 且消息数超保留数时,把中间段旧消息交给 LLM 压缩为 system 摘要(保留关键事实,非简单丢弃),失败静默跳过。 ```properties harness.agent.max-history-messages=40 harness.agent.max-history-tokens=12000 harness.agent.context-compact=true ``` ## 用户介入(Human-in-the-loop) 工具标注 `requiresApproval = true` 后,Agent 执行到该工具时会**暂停**,通过 `ApprovalHandler` 回调等待用户决策: ```java public class MyTools { // 高危工具:删除、支付、写文件等建议开启审批 @AgentTool(name = "deleteFile", description = "删除文件", requiresApproval = true) public String deleteFile(@ToolParam("路径") String path) { return "已删除: " + path; } } // 执行时传入审批处理器(同步阻塞等待用户输入) agent.stream("删除 /tmp/a.txt", printer, (toolName, args) -> { System.out.println("允许调用 " + toolName + " " + args + " ? (y/n)"); return scanner.nextLine().equalsIgnoreCase("y"); }); // 或同步模式:agent.run(input, handler) ``` 行为约定: - 返回 `true` → 执行工具,结果照常回填 LLM; - 返回 `false` → 拒绝执行,回填「用户拒绝」给 LLM,由其换方案或收尾; - **未提供 handler 时,requiresApproval 工具一律拒绝执行**(宁可拒不可静默放行); - 审批请求同时发布 `AgentEvent.ApprovalRequested` 事件,可供日志/审计订阅; - 便捷实现:`ApprovalHandler.allowAll()` / `denyAll()`。 ## 配置项 | 配置 | 默认值 | 说明 | |------|--------|------| | `harness.llm.base-url` | `https://open.bigmodel.cn/api/paas/v4` | OpenAI 兼容端点(内置为智谱) | | `harness.llm.api-key` | 空 | API Key(可用环境变量 `HARNESS_LLM_API_KEY`) | | `harness.llm.model` | `glm-4-flash` | 模型名 | | `harness.llm.temperature` | `0.7` | 采样温度 | | `harness.llm.max-tokens` | `2048` | 最大输出 token | | `harness.llm.supports-tools` | 自动 | 是否启用工具调用;未配置时按模型名启发式推断 | | `harness.agent.max-iterations` | `30` | ReAct 最大循环轮数 | | `harness.agent.max-history-messages` | `40` | 上下文裁剪:最多保留消息条数 | | `harness.agent.max-history-tokens` | `12000` | 上下文裁剪:token 预算(CJK 1 token/字、ASCII 4 字符/token) | | `harness.agent.max-history-chars` | `24000` | 兼容旧配置,未配置 token 预算时按 /2 换算 | | `harness.agent.context-compact` | `true` | 上下文超预算时用 LLM 压缩中间段为摘要 | | `harness.memory.type` | `file` | 记忆类型:`file`(持久化,重启不丢)/ `memory`(纯内存) | | `harness.memory.file` | `harness-memory.json` | 文件记忆落盘路径 | | `harness.memory.extract` | `true` | 是否启用 LLM 抽取式沉淀(false 时整段 FACT 降级) | | `harness.memory.max-items` | `500` | 记忆容量上限,超限淘汰低分条目 | | `harness.memory.vector.enabled` | `true` | 是否开启内存向量检索 | | `harness.memory.embedding` | `hash` | 向量模型:`hash`(零依赖)/ `openai`(语义向量) | | `harness.memory.embedding.dimension` | `256` | 本地哈希向量维度 | | `harness.embedding.model` | `text-embedding-v3` | 远程语义向量模型名(openai 模式) | | `harness.embedding.timeout-seconds` | `30` | 远程 embedding 超时 | | `harness.tool.shell.enabled` | `false` | 是否注册高危命令工具 `run_command` | | `harness.tool.file.work-dir` | 空 | 文件工具沙箱根目录;空=不限制 | | `harness.tool.file.allow-any-path` | `false` | 是否放行沙箱外路径 | | `harness.tool.file.allow-overwrite` | `false` | 是否允许 `write_file` 直接覆盖已存在文件 | | `harness.tool.web.allow-private-network` | `false` | 是否放行 http_get 访问私网/内网地址(SSRF 防护) | | `harness.log.level` | `INFO` | 日志级别 | | `harness.log.file` | 空 | 日志文件路径(空=仅控制台) | | `harness.debug.trace` | `false` | 超级 debug 模式:开启后把每一步(用户输入、LLM 请求/响应、工具调用与结果、去重/审批/记忆注入等)打印到 stdout | ## 运行测试 内置智谱 glm-4-flash 全流程自检(9 项用例:基础问答 / 审批放行 / 审批拒绝 / 系统信息 / 多轮记忆 / 流式 / 指标 / 文件工具 / 网页工具): ```bash mvn compile exec:java -Dexec.mainClass=com.javaai.harness.demo.FullFlowTestRunner -Dexec.classpathScope=runtime ``` 测试结果自动生成 `test-report-glm4flash.md`(用例明细 + 输出摘录 + 指标快照)。 单元/冒烟测试(无需网络,验证记忆向量检索、上下文裁剪等核心机制): ```bash mvn test ``` ## 架构 ``` ┌─────────────────────────────────────────────────┐ │ Harness (门面) │ ├──────────┬──────────┬──────────┬────────────────┤ │ ChatModel│ ToolReg │ Memory │ Metrics │ │ (适配器) │ (注册中心)│ (记忆) │ (指标) │ │ │ │ MemoryManager │ │ │ │ ├ 抽取沉淀/去重/淘汰 │ │ │ │ ├ 关键词检索 │ │ │ │ └ 内存向量检索(混合召回) │ ├──────────┴──────────┴──────────┴────────────────┤ │ Agent (ReAct 循环) │ │ context → LLM → tool? → 执行 → 回填 → 循环 │ │ ├ trimMessages (token 预算裁剪) │ │ └ maybeCompactContext (LLM 摘要压缩) │ ├─────────────────────────────────────────────────┤ │ Orchestrator: MainAgent / PipelineAgent │ │ Security: PromptGuard / SensitiveFilter │ │ Observability: EventBus / TraceLogger │ └─────────────────────────────────────────────────┘ ``` ## 路线图 - [ ] 向量持久化(当前向量仅存内存,重启懒重建;可扩展落盘加速冷启动) - [ ] 记忆分层与跨会话图谱关联 - [ ] Agent 工作流 DSL(YAML 定义 DAG) - [ ] 工具沙箱隔离(进程级) - [ ] 分布式任务队列 ## License MIT *(内容由AI生成,仅供参考)*