PROJECT 02
PaiCLI · Java Agent CLI
一个跑在终端里的 Java AI Agent:你提需求,模型自己决定读哪个文件、 搜什么关键字、执行什么命令,看结果再决定下一步,直到任务完成 —— 整个过程可审批、可回滚、可审计。
01
为什么做它
市面上的 AI 编程助手,很多还停在「把代码贴进对话框」这一步。但真实工程里, 模型需要的是能自己去看:读文件、跑命令、看报错, 再根据结果调整下一步。
所以我实现了一个本地可执行的 Agent 运行时,把工具调用、上下文管理、 安全边界都做成可审计的东西 —— 而不是一个黑盒对话框。
和我另一个项目(企业级 RAG 知识库)正好互补:那个偏应用层的后端工程, 这个偏底层的 Agent 架构。
02
核心机制
ReAct 主循环与工具编排
「思考 → 工具调用 → 结果回灌 → 再推理」的自主循环,
退出由模型自决而不是固定轮数;
多工具并行执行(并发上限 4),通过下标对齐保证结果严格按原始请求顺序回灌,
满足 tool_call_id 配对协议;
内置死循环检测(工具名 + 参数签名连续 3 轮一致即中止)与 50 轮硬上限双重兜底。
上下文工程与自动压缩
LLM 是无状态的,每轮要重发全量历史。这里实现了按模型窗口派生的动态压缩阈值,
自动保留最近 3 个 user 轮次,并强制把切分点落在 user 消息边界
—— 避免切断 tool_call / tool_result 的配对。
摘要生成失败时放弃压缩,保证历史完整性。
MCP 协议与工具动态扩展
支持 stdio 与 Streamable HTTP 两种传输,工具以 mcp__{server}__{tool}
的形式动态注册,响应 tools/list_changed 通知热更新;
环境变量延迟到单个 server 启动时展开,实现故障隔离
—— 一个 MCP server 起不来,不阻塞主流程。
三层安全防线
HITL 人工审批 → PathGuard 路径围栏 → CommandGuard 命令黑名单纵深防御。
审批支持「全部放行 / 拒绝 / 跳过 / 修改参数后执行」;
路径校验基于 toRealPath() 解析真实路径,防符号链接逃逸;
危险操作写审计日志并对 token / password 等敏感参数脱敏。
Side-Git 快照与回滚
基于 JGit 建立独立的 GIT_DIR 快照,不污染项目的 .git,
支持按轮次回滚 —— 模型改坏代码时能退回上一步。
多模式执行
除默认 ReAct 外,还有 Plan-and-Execute (生成计划 → DAG 依赖建模 → DFS 环检测 → 拓扑顺序并行调度) 与 Multi-Agent(规划 / 执行 / 审查三角色,仅执行者持有工具权限)。 三条路径共享同一套工具注册表与记忆模块。
多模型适配
基于统一接口 + 抽象模板基类接入 7 个模型 provider,并针对性处理能力差异:
thinking 模式要随历史回带 reasoning_content、
部分网络下长流会被网关重置需要强制 HTTP/1.1、
不支持图片的 provider 自动降级为文本提示。
03
我实现的核心模块
1. Agent 主循环与工具编排引擎
负责三件事:把模型的工具调用决策翻译成真实的本地执行、把执行结果按协议回灌给模型、
以及在模型"卡住"时兜底。并行工具的执行结果必须按原始调用顺序回填,
否则 tool_call_id 配不上,模型会收到错乱的历史。
2. 上下文与记忆体系
按模型窗口比例分配 Token 预算,设计三类记忆(对话 / 事实 / 工具结果)的注入与淘汰策略; 压缩时强制在 user 消息边界切分,宁可少压缩一点,也不切断工具调用配对。
3. MCP 协议接入
实现 stdio 子进程与 Streamable HTTP 两种传输的客户端,工具动态注册与列表热更新; 清洗工具 schema 里的嵌套引用与超长描述,降低模型调用失败率。
4. 安全防线与快照回滚
三层拦截按 HITL → PathGuard → CommandGuard 顺序执行,
用户无法批准被策略拒绝的请求;基于 JGit 的快照独立于项目仓库,支持按轮次回退。
04
几个关键的设计取舍
如果从任意位置切,很容易把一次
tool_call 和它的 tool_result
切到两半,模型收到一个「有调用没有结果」的历史,就会开始乱猜。这类问题不会报错,
只会让回答质量变差 —— 最难排查的那种。
线程池做不到持久化:服务一重启,没处理完的任务就丢了。而在 Agent 场景里, 一次任务可能已经花了不少 token,丢掉等于白花钱。
toRealPath()?只校验字符串前缀是能被绕过的:项目里放一个指向
/etc 的符号链接,
路径字符串看起来还在项目内,实际已经跑出去了。
再高收益很小,但本地文件读写和命令执行会互相干扰,出问题的概率明显上升。 保守一点,换来的是结果可复现。
05
技术栈
Java 17MavenOkHttp (SSE 流式) JacksonJLine 4SQLite JGitJavaParserJsoup MCP 协议Ollama Embedding