本地大模型(llama.cpp + Qwen-7B)工具调用 Agent 的基础设施:在按会话隔离的 workspace 沙箱之上,把 Agent 做成可服务化、可观测、可恢复、可审计的工程系统。
English (condensed) | 中文完整版(当前)
本仓库的全部数值都来自随仓库提供的原始日志、CSV 与 JSONL 产物;无法证明或未执行的部分单列在「已知限制」一节。
直接用 open(path) 读写文件的 Agent 有三个真实问题,本仓库逐项给出了实现与实测:
- 没有文件边界:工具能读
.env、项目源码、系统文件,也能往任意路径写。 → 白名单解析(Path.resolve()+is_relative_to)+ 会话级沙箱根。 - 多会话互相污染:同一个进程里不同用户的对话历史与文件会串。
→ 会话存储(内存字典 + SQLite)+
thread_id = session_id+ 独立会话目录。 - 小模型工具调用不可靠:格式错乱、"假装执行"、参数缺失。 → GBNF 约束解码把动作锁进枚举,格式非法输出无法生成;语义安全交给工具白名单兜底。
| 能力 | 实测 |
|---|---|
| 路径白名单沙箱 | 同一批 8 条越权路径:改造前允许 7/8 → 改造后允许 1/8(唯一允许项为正向对照) |
| 多会话隔离 | 5/5 通过(跨会话不串历史、不串文件;重启后按 session_id 恢复 thread_id) |
| 安全网关 | 14/14 通过(越权拒绝、跨会话绝对路径拒绝、搜索超时 5s + 重试 2 次 + 兜底 JSON 降级) |
| 真实 GBNF 工具调用 | llama-server 真实联调 29/30 = 96.67% 综合合法率;校验器精确率/召回率 100% |
| 全链路可观测 | 369 事件 / 85 trace / 25 会话;总体 P95 28.93 ms |
| 检查点中断恢复 | 两个独立进程:PHASE-1 中断退出(pid 62548)→ PHASE-2 从 SQLite 恢复完成(pid 40980) |
| 单元/集成测试 | pytest -q agent/tests → 2 passed(仓库自包含,克隆后可直接跑) |
flowchart TB
subgraph C["调用方"]
CLI["CLI: agent_core/run.py"]
HC["HTTP 客户端"]
end
subgraph I["Agent Infra (agent/)"]
MW["TraceSessionMiddleware (纯 ASGI)<br/>trace_id / session_id / X-Trace-ID"]
API["FastAPI<br/>/v1/agent/run | /v1/session/{id} | /healthz"]
SS["SessionStore<br/>内存字典 + SQLite"]
G["LangGraph 主图 + SqliteSaver"]
GW["安全网关 call_tool<br/>白名单校验 + 审计日志 + 降级"]
TR["Trace JSONL"]
end
subgraph K["基础 Agent (agent_core/)"]
SP["safe_path.py<br/>Path.resolve + is_relative_to"]
TL["tools.py<br/>read_file / write_file / search_web"]
end
LLM["llama-server (GBNF 约束解码)"]
WS["会话文件沙箱<br/>workspace/sessions/{session_id}/"]
HC --> MW --> API --> G
API --> SS
G --> GW --> SP --> WS
GW --> TL
G -.-> LLM
MW --> TR
G --> TR
前置:Python 3.9+;可选一个已编译的 llama.cpp(用于真实模型;没有则走确定性 mock)。
pip install -r agent/requirements.txt
# 配置:复制模板(本地开发用 127.0.0.1,容器内由 Dockerfile 的 ENV 提供)
copy agent_core\.env.example agent_core\.env
# 启动服务(默认 mock 模式也能跑通全部编排/隔离/日志/恢复链路)
cd agent
uvicorn app.main:app --host 127.0.0.1 --port 8000调用:
curl -X POST http://127.0.0.1:8000/v1/agent/run `
-H "Content-Type: application/json" -H "X-Session-ID: demo-1" `
-d "{\"input\": \"把\\\"hello\\\"写入文件 a.txt\"}"接真实模型时,先把 llama-server 起在本机 8080(示例:RTX 4060 8GB,显存占用约 6.3 GB):
llama-server.exe -m <model>.gguf --host 127.0.0.1 --port 8080 `
-ngl 99 -c 4096 -np 1 -ctk q8_0 -ctv q8_0 -fa on --metricsAGENT_LLM_MODE 取值 auto(默认,探测到服务就用真实模型)/ llama / mock。
├── README.md / README.en.md 本文件与英文精简版
├── README_all.md 实现说明(架构、核心实现、实测数据、接口、复现命令)
├── audit_before.md 改造前文件访问边界审计(含真实越权证据)
├── agent/ Agent Infra 主体
│ ├── app/ config / tracing / sessions / security / llm / graph / main
│ ├── tests/ 会话隔离、安全网关、GBNF 合法率(含 30 条用例)
│ ├── scripts/ gen_traffic / analyze_trace / run_resume_demo / gen_grammar
│ ├── grammar/ 手写 grammar + llama.cpp 官方转换器生成的 GBNF + JSON Schema
│ ├── logs/ 实测 trace 与安全审计日志(样本)
│ └── README_agent.md 模块说明、接口、已知边界
├── agent_core/ 基础 Agent(被 app 引用的运行时依赖)
├── audit/ 前后对比探针与真实对比数据
├── backup/ 改造前原始实现(仅作对照证据,内含警示,请勿用于生产)
├── docker/ Dockerfile(非 root、只挂载 workspace)+ 安装指引
├── figures/ 图表与生成/校验脚本、清单
├── llama_server/ 启动/就绪/联调/关闭日志
├── shared/ 测试与验证记录
└── workspace/ workspace 隔离设计决策
- 白名单根:
AGENT_WORKSPACE_ROOT/sessions/{session_id},工具只能在此子树内读写; - 判定方式:
Path.resolve()解析..与符号链接后,is_relative_to(会话根)校验; - 可审计原因码:
PROJECT_SOURCE_DENIED、ENV_CONFIG_FILE、WINDOWS_SYSTEM_DIR、SYSTEM_ACCOUNT_FILE、OUT_OF_WORKSPACE、DRIVE_RELATIVE_PATH、NOT_FOUND、CONTROL_CHAR; - 审计日志:每次工具调用(放行与拒绝)都写
agent/logs/security_gateway.log(JSON Lines); - 失败可区分:路径拒绝返回原因码,而不是依赖解码失败等偶然因素。
部署注意:本服务默认只监听
127.0.0.1且没有鉴权,请勿直接暴露到公网; llama-server 自身也会打印CORS is set to allow all origins警告。若要在内网使用,请自行在前置代理上增加认证与来源限制。
- 服务化:
POST /v1/agent/run、GET|DELETE /v1/session/{id}、GET /healthz; - 多会话隔离:
X-Session-ID头 >session_idCookie > 自动生成 uuid4 并Set-Cookie; - 持久化:会话元数据/历史/工具调用记录入 SQLite,重启后按
session_id恢复thread_id; - 全链路 trace:JSONL 事件(
trace_id, session_id, thread_id, node_name, input, output, start_time, end_time, elapsed_ms, error)+ P95/错误率统计脚本; - 中断恢复:LangGraph
interrupt()+SqliteSaver,两阶段两进程可复现; - GBNF 工具调用:grammar 由 llama.cpp 官方
json_schema_to_grammar.py生成,配 30 条用例的合法率测试与自实现子集匹配器。
| 节点耗时 | 节点错误率 |
|---|---|
![]() |
![]() |
| 越权路径改造前后 | GBNF 合法率 |
|---|---|
![]() |
![]() |
图表全部由 figures/make_figures.py 从仓库内真实 CSV/JSONL 生成,清单见 figures/manifest.md。
| 文档 | 内容 |
|---|---|
README_all.md |
实现说明:架构、核心实现(沙箱/隔离/网关/日志/恢复/GBNF)、实测数据、接口、复现命令 |
agent/README_agent.md |
Agent Infra 模块说明、接口、测试与已知边界 |
workspace/workspace_decision.md |
workspace 隔离设计决策(直接读写项目根 → 收敛白名单) |
audit_before.md |
改造前文件访问边界审计(含真实越权证据) |
shared/validation.md |
测试与验证结果 |
docker/DOCKER_SETUP_GUIDE.md |
Docker 安装指引(Docker Desktop 或 WSL2 + docker-ce 两条路径) |
- Docker 沙箱未实测:开发机未安装 Docker 且未启用 WSL,容器内验证待环境具备(Dockerfile 与指引已随仓库提供,构建上下文已就绪)。等价的本机路径白名单与会话隔离已实测。
- 真实模型合法率为 96.67%:唯一失败用例是一次
max_tokens截断(模型重复生成导致 JSON 未闭合),不是 GBNF 失效。 - mock 模式的合法率不代表模型能力:mock 用例集按设计含 11 条非法输出,该模式用于验证校验链路(校验器精确率/召回率 100%);真实模型指标以 llama-server 联调为准。
agent/logs/下的 trace 与审计日志是实测样本,重新运行会追加内容。
本仓库不重分发任何第三方源码,只把它们作为运行时依赖:
- llama.cpp(MIT)——本地推理服务;
agent/grammar/tool_call_generated.gbnf由其官方examples/json_schema_to_grammar.py生成; - LangGraph / LangChain(MIT)——状态图与 SQLite 检查点;
- FastAPI / Starlette / Uvicorn(MIT)——HTTP 服务层;
- jsonschema(MIT)——工具调用载荷校验。
本项目以 MIT 许可发布。




