Skip to content

Commit 4d93ff5

Browse files
committed
Add skills code review agent example
1 parent cbb6979 commit 4d93ff5

64 files changed

Lines changed: 8901 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Code Review Agent — LLM 配置样例(复制为 .env 后按需修改)
2+
#
3+
# 所有变量均为可选;缺省即降级为 FakeLlm(不调用任何模型)。
4+
# 支持任意 OpenAI 兼容端点:OpenAI / Azure OpenAI / 本地 vLLM / 内部 LLM 网关。
5+
6+
# 是否启用真实 LLM 二次研判(也可通过 CLI --enable-llm 覆盖)
7+
# 取值:true | false
8+
LLM_ENABLED=false
9+
10+
# 供应商标识(仅用于记录/诊断,不影响端点选择)
11+
LLM_PROVIDER=openai
12+
13+
# 真实模型 API Key;留空 = 禁用真实模型
14+
LLM_API_KEY=
15+
16+
# OpenAI 兼容的 Chat Completions 端点基址
17+
# OpenAI: https://api.openai.com/v1
18+
# Azure: https://<res>.openai.azure.com/openai
19+
# 本地 vLLM: http://localhost:8000/v1
20+
LLM_BASE_URL=https://api.openai.com/v1
21+
22+
# 模型名(对应端点上的 deployment / model id)
23+
LLM_MODEL=gpt-4o-mini
24+
25+
# 采样温度(越低越确定,适合判定任务)
26+
LLM_TEMPERATURE=0.1
27+
28+
# 单次响应最大 token 数
29+
LLM_MAX_TOKENS=1024
30+
31+
# 请求超时(秒)
32+
LLM_TIMEOUT=30
Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
# Code Review Agent
2+
3+
> 基于 tRPC-Agent Skill 体系的自动代码评审 Agent 原型。输入 git diff / PR patch / 本地变更,输出结构化 findings,并将审查任务、拦截记录、监控摘要、结果写入 SQLite,支持评测、监控、回放。
4+
5+
## 架构设计
6+
7+
这个示例的目标不是让 LLM 直接评论代码,而是把可复用 Skill、沙箱执行、Filter 治理、结构化结果、持久化存储和监控审计串成一条可验证的代码评审链路。输入可以是 unified diff、PR patch 或本地 git 工作区变更;输出是结构化 findings、人工复核项、Filter 拦截摘要、沙箱执行摘要、监控指标和双格式报告。
8+
9+
整体采用六层流水线。L1 输入解析层负责把 `--diff-file`、`--repo-path` 或 fixture 解析成 ChangeSet,提取文件、hunk、上下文和候选行号。L2 Skill 加载层通过 `skill_load` 读取 `skills/code-review/SKILL.md`、规则文档和脚本清单,让规则和执行入口从 Agent 编排中解耦。L3 Filter 治理层在任何脚本进入沙箱前做前置决策,拦截高风险脚本、禁止路径、非白名单网络访问和超预算执行,并把 `deny` / `needs_human_review` 写入数据库和报告。L4 沙箱执行层默认走 Container 或 Cube/E2B runtime,本地只作为开发与 dry-run fallback;每次执行都带超时、输出大小限制、环境变量白名单、脱敏和失败记录。L5 去重结构化层按 `(file, line, category)` 合并重复诊断,并根据置信度把结果分入 findings、warnings 或 needs_human_review。L6 存储输出层把 task、input diff、sandbox run、filter block、finding、monitor summary 和最终 report 写入 SQLite,并生成 `review_report.json` 与 `review_report.md`。
10+
11+
数据流是单向的:CLI 输入先变成 ChangeSet,再加载 code-review Skill 和规则;脚本清单先过 Filter,允许后才进入沙箱执行;沙箱产出的原始诊断统一进入去重和分流;最终结果落库并渲染报告。异常分支也走同一条审计链:Filter 拒绝不会进入沙箱,但会记录拦截原因;沙箱超时、失败或输出截断不会让任务崩溃,而是记录失败并继续生成可查询报告;dry-run 使用 fake runner 跑同一套规则路径,保证没有真实模型 API Key 时也能验证解析、沙箱记录、落库和报告链路。
12+
13+
数据库围绕 `review_task` 建模,其他表都通过 `task_id` 关联。`input_diff` 保存输入摘要,`sandbox_run` 保存 runtime、脚本、状态、耗时、退出码、输出大小、超时和脱敏数量,`finding` 保存结构化问题和 bucket,`filter_block` 保存治理拦截,`monitor_summary` 保存耗时、调用次数、拦截次数、severity 分布和异常类型分布,`review_report` 保存最终报告路径与摘要。这样按 task id 可以回放一次完整评审。
14+
15+
Skill 本身只负责声明和承载可复用评审能力:`SKILL.md` 声明入口、规则列表和沙箱策略,`rules/` 覆盖安全风险、异步错误、资源泄漏、测试缺失、敏感信息泄漏、数据库连接或事务生命周期等类别,`scripts/` 提供 diff 解析、规则检查、去重和脱敏。Agent 编排层只负责加载 Skill、执行治理、调度沙箱、落库和渲染报告。
16+
17+
监控审计贯穿全链路。每次 review 都记录总耗时、沙箱耗时、工具调用次数、Filter 拦截次数、finding 数量、严重级别分布和异常类型分布;这些字段同时进入数据库和报告,便于后续评测、监控和问题回放。
18+
19+
### 目录结构
20+
21+
```
22+
skills_code_review_agent/
23+
├── .env # 运行配置(LLM 等,安全默认,不提交密钥)
24+
├── .env.example # 配置模板
25+
├── run_agent.py # CLI 薄入口(load_dotenv → 调用 agent.agent.main)
26+
├── README.md
27+
├── agent/ # 所有内部模块
28+
│ ├── __init__.py
29+
│ ├── agent.py # 编排管线(CLI 解析 + 六层流水线)
30+
│ ├── db/ # Phase-0 存储层(SQLite + ORM)
31+
│ ├── filters/ # Filter 治理(高危/禁路径/网络/预算)
32+
│ ├── llm/ # 可选真实 LLM 二次研判(默认关闭)
33+
│ ├── sandbox/ # 沙箱运行时(local/container/cube + 透明回退)
34+
│ └── telemetry/ # OTel 埋点
35+
├── skills/ # SDK skill 定义(FsSkillRepository 按 base_dir 加载)
36+
│ └── code-review/
37+
│ ├── SKILL.md
38+
│ ├── rules/ # 6 类规则文件 (security/async/resource/tests/sensitive/db)
39+
│ └── scripts/ # 静态分析脚本(run_checks/dedupe/mask_secrets/parse_diff)
40+
├── tests/ # 8 个测试文件,175 用例
41+
├── examples/ # 生成的报告产物
42+
└── docs/ # 各阶段概述文档
43+
```
44+
45+
---
46+
47+
## 1. 运行环境
48+
49+
必须使用隔离 venv(原因见下),不要用系统 Python:
50+
51+
```bash
52+
# 隔离 venv(Windows 示例路径)
53+
VENV="C:/Users/douzhenyu/.workbuddy/binaries/python/envs/cr_agent/Scripts/python.exe"
54+
55+
cd E:/agent/trpc-agent-python/examples/skills_code_review_agent
56+
```
57+
58+
**关键依赖约束**
59+
60+
- 必须安装 `python-magic-bin`(不是 `python-magic`)。Windows 上缺 `magic1.dll` 会导致 SDK `import code_executors` 直接 segfault。
61+
- SDK 存储为 async 优先,所有存储 / 沙箱调用都要 `await`
62+
- **路径用真实文件系统路径**,别用 Git Bash 的 `/tmp`(aiosqlite 在 Windows 上认不出,会静默失败)。
63+
64+
依赖清单(核心):`SQLAlchemy` `aiosqlite` `pydantic` `opentelemetry-sdk` `tzlocal` `PyYAML` `jinja2` `python-magic-bin` `docker`(可选,容器运行时预留)。
65+
66+
---
67+
68+
## 2. CLI 参数
69+
70+
| 参数 | 必填 | 说明 | 默认 |
71+
|------|------|------|------|
72+
| `--diff-file PATH` | 三选一(互斥) | 解析一个 unified diff 文件 ||
73+
| `--repo-path PATH` | 三选一 | 在仓库里跑 `git diff HEAD`(暂存 + 未暂存) ||
74+
| `--fixture NAME` | 三选一 | 内置样本:`clean` / `security` ||
75+
| `--skill-dir PATH` || code-review skill 目录 | `skills/code-review` |
76+
| `--db-path PATH` || SQLite 库路径 | `cr_agent.db` |
77+
| `--mode dry-run\|real` || 执行模式 | `dry-run` |
78+
| `--dry-run` || `--mode dry-run` 快捷 flag ||
79+
| `--output-dir DIR` || `review_report.json` + `.md` 输出目录 | `.` |
80+
| `--telemetry` || 开启 OTel,span 打到 stdout ||
81+
| `--print-changeset` || 额外打印解析后的 ChangeSet JSON ||
82+
| `--require-sandbox` || real 模式强制使用 SKILL.md 声明的 `default_runtime`(如 `container`);若不可用直接报错,不静默回退 `local` ||
83+
| `--enable-llm` || 开启真实 LLM 二次研判(也可用 `.env``LLM_ENABLED`||
84+
| `--llm-env PATH` || 指定 `.env` 文件路径(默认项目根 `.env`||
85+
86+
输入三选一是 **required** 的——不传任何一个会直接报错。
87+
88+
---
89+
90+
## 3. 两种模式
91+
92+
| | dry-run | real |
93+
|---|---|---|
94+
| 执行方式 | 进程内直接 `import run_checks` |`default_runtime` 选后端(先试 `container`,不可用时透明回退 `local`|
95+
| 安全边界 | 不生效(没进沙箱) | 超时 / 输出上限 / env 白名单 / 脱敏全生效 |
96+
| Filter 治理 | 生效(deny 仍拦截) | 生效 |
97+
| 是否需要 Key |||
98+
| 用途 | CI 预览、快速验证、评测 | 生产评审(**独立容器沙箱**,更隔离、更安全) |
99+
100+
> **独立沙箱(PRD 强制)**`real` 模式 + `default_runtime: container` 时,检查脚本在**全新 docker 容器**内执行——`network_mode=none`(无网络)、不继承宿主机环境变量、每次评审起一个容器用完即毁。这正是 PRD §6.2 要求的生产隔离方案,已在本机(docker 已装)实证:整条链路在容器内跑通、`sandbox_run.runtime` 记录为 `container`、密钥脱敏生效。
101+
> `--require-sandbox` 可在生产环境**强制**该契约:请求 `container` 但不可用就直接报错,不静默降级到 `local`(local 仅作 dry-run / 开发 fallback,PRD 明确"不能作为生产方案")。
102+
103+
> 评审核心为**确定性静态分析引擎**(规则 + AST),**默认不调用任何 LLM / 模型 API**,因此两条路径默认都不需要 API Key。
104+
> 所谓 “real” 仅表示 “在隔离沙箱里执行” vs “直接进程内执行”。
105+
> 如需真实模型二次研判,见 [§4.5 LLM 二次研判(可选)](#45-llm-二次研判可选);未配置时自动降级为 no-op,链路不受影响。
106+
107+
---
108+
109+
## 4. 配置改在哪
110+
111+
**沙箱安全边界**`skills/code-review/SKILL.md``sandbox:` frontmatter:
112+
113+
```yaml
114+
sandbox:
115+
default_runtime: container # real 模式优先用容器;docker 不可用自动回退 local
116+
fallback: local
117+
timeout_s: 30 # 超时秒数,超了降级为空
118+
max_output_bytes: 1048576 # 1MB 输出上限,超出截断
119+
env_whitelist: [PATH, HOME, LANG] # 只透传这些环境变量进沙箱
120+
```
121+
122+
**检测规则** → `skills/code-review/rules/*.md`(6 类:security / async_errors / resource_leak / missing_tests / sensitive_info / db_lifecycle)。改规则直接改这些 md,无需动代码。
123+
124+
**脱敏正则** → `skills/code-review/scripts/mask_secrets.py`(AWS / OpenAI / GitHub / password / 私钥 / 连接串 + 熵检测)。
125+
126+
---
127+
128+
## 4.5 LLM 二次研判(可选)
129+
130+
在 dedupe 之后、落库之前,可用**真实 LLM** 对低置信度的 `needs_human_review` 档位做二次研判:
131+
132+
- **real(确认)** → 更新置信度、给 `source` 打 `llm` 标、把模型解释追加进 `recommendation`,并按新置信度重新分桶(≥0.8 进 findings / ≥0.6 进 warnings)。
133+
- **false_positive(误报)** → 直接丢弃。
134+
- **调用失败 / 无 Key / 未启用** → 自动降级为 no-op,原 findings 原样落库。
135+
136+
### 配置(`.env`)
137+
138+
复制 `.env.example` 为 `.env`,按需填写:
139+
140+
```bash
141+
LLM_ENABLED=true # 或 CLI --enable-llm 临时开启
142+
LLM_API_KEY=sk-... # 留空 = 禁用真实模型
143+
LLM_BASE_URL=https://api.openai.com/v1 # 任意 OpenAI 兼容端点(Azure/本地vLLM/内部网关)
144+
LLM_MODEL=gpt-4o-mini
145+
LLM_TEMPERATURE=0.1
146+
LLM_MAX_TOKENS=1024
147+
LLM_TIMEOUT=30
148+
```
149+
150+
- 任意 OpenAI 兼容端点都能用:OpenAI、Azure OpenAI、**DeepSeek**(`https://api.deepseek.com`,`LLM_MODEL=deepseek-chat`)、本地 vLLM、内部 LLM 网关,只改 `LLM_BASE_URL` / `LLM_API_KEY`。
151+
- **无 Key 时完全不调用模型**,解析 / 沙箱 / 落库链路(含 `--dry-run`)照常工作,所有测试可无 Key 跑通。
152+
- 喂给模型的 diff 会先经 `mask_secrets` 脱敏,密钥不会外泄。
153+
- **已实证**(DeepSeek `deepseek-chat`):低置信度 `needs_human_review` 项(规则 SEC005 `verify=False`,置信度 0.5)被模型研判为 `real`、置信度 0.95 → 提级进 `findings` 桶、`source` 标 `rule+llm`、解释写入 `recommendation`;高/中置信度项不受影响。验证 fixture 为 `llm_probe`。
154+
- 真实网络路径有门控集成测试 `tests/test_phase7_llm_live.py`:`CR_LLM_LIVE=1` 且有 `LLM_API_KEY` 时才跑,避免污染无 Key 的 CI。
155+
156+
### 启用方式
157+
158+
```bash
159+
# 方式一:.env 里 LLM_ENABLED=true 后直接跑
160+
$VENV run_agent.py --diff-file ./my_change.diff --mode real --db-path ./cr.db
161+
162+
# 方式二:CLI 临时开启(覆盖 .env)
163+
$VENV run_agent.py --fixture security --enable-llm --llm-env ./path/to/.env
164+
```
165+
166+
---
167+
168+
## 5. 真实环境用法
169+
170+
```bash
171+
# (A) 本地 diff 文件 → 生产沙箱模式,报告输出到 ./reports
172+
$VENV run_agent.py \
173+
--diff-file ./my_change.diff \
174+
--mode real \
175+
--output-dir ./reports \
176+
--db-path ./cr_prod.db
177+
178+
# (B) 审一个 Git 仓库的未提交改动(CI 门禁,dry-run 够用)
179+
$VENV run_agent.py \
180+
--repo-path /path/to/repo \
181+
--dry-run \
182+
--telemetry \
183+
--db-path ./cr_ci.db
184+
185+
# (C) 评测 / 演示用内置样本
186+
$VENV run_agent.py --fixture security --dry-run --output-dir ./out
187+
```
188+
189+
跑完会生成:
190+
191+
- `./reports/review_report.json` — 八段式结构化报告(机器可读)
192+
- `./reports/review_report.md` — 八段式报告(人读)
193+
- `cr_prod.db` 里写入 `review_task` / `input_diff` / `finding` / `sandbox_run` / `filter_block` / `monitor_summary` / `review_report`
194+
195+
### 按 task_id 查询完整记录
196+
197+
```python
198+
import sqlite3
199+
c = sqlite3.connect("cr_prod.db"); c.row_factory = sqlite3.Row
200+
t = c.execute("SELECT * FROM review_task WHERE id=?", (task_id,)).fetchone()
201+
findings = c.execute("SELECT * FROM finding WHERE task_id=?", (task_id,)).fetchall()
202+
# ... sandbox_run / filter_block / monitor_summary / review_report 同理
203+
```
204+
205+
---
206+
207+
## 6. 示例产物
208+
209+
- [`examples/review_report.json`](./examples/review_report.json) — 一份真实的八段式报告样例(fixture=security 跑出)。
210+
211+
---
212+
213+
## 7. 已知边界
214+
215+
- **容器隔离(独立沙箱,已实证)**:`ContainerRuntime` 是 SDK `ContainerClient` 的真实适配(docker 进程隔离,`network_mode=none`、不继承宿主机 env),`CubeRuntime` 是远程 Cube/E2B 的真实适配;两者在 docker/cube 不可用时抛 `RuntimeUnavailable`,由 agent 透明回退到 `LocalRuntime`(或 `--require-sandbox` 下直接报错)。`sandbox_run.runtime` 记录实际落地的后端(`local`/`container`/`cube`)。
216+
- 本机 docker 已装,已端到端实证:整条 real 链路在容器内跑通、检出 + 脱敏生效、`runtime=container` 正确落库(见 `tests/test_phase3_container_sandbox.py`,docker 不可用时自动 skip)。
217+
- 绕开 SDK `ContainerClient` 的 stdin 执行路径 bug(其 `_exec_run_with_stdin` 有 `self.container.container.id` 笔误 + 关闭 socket 后复用连接池报错);本 runtime 改用 base64 把脚本与输入写进容器文件、再以容器内 `python x.py < _input.json` 重定向执行,全程走稳健的 no-stdin `exec_run` 分支。SDK 该笔误已就地修复(`trpc_agent_sdk/code_executors/container/_container_cli.py`)。
218+
- **并发**:多任务建议每条评审一个 `task_id`;不要多个进程写同一个 `.db` 文件。
219+
- **Windows 路径**:所有 `--db-path` / `--output-dir` / `--diff-file` 用正斜杠或盘符绝对路径。
220+
221+
---
222+
223+
## 8. 测试
224+
225+
```bash
226+
# 全量回归(8 个阶段 + LLM + 容器沙箱)
227+
for p in phase0_foundation phase1_input_skill phase2_rules_engine \
228+
phase3_sandbox_filter phase3_container_sandbox telemetry \
229+
phase4_dedupe phase5_orchestration phase7_llm cr_agent; do
230+
$VENV examples/skills_code_review_agent/tests/test_${p}.py
231+
done
232+
```
233+
234+
当前累计 **178 用例 / 0 失败**(含 docker 门控的容器独立沙箱测试;无 docker 时该测试自动 skip)。
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
"""Code Review Agent package.
2+
3+
Thin re-export surface so callers can do::
4+
5+
import agent
6+
await agent._async_main(ns) # or agent.main(...)
7+
8+
without reaching into ``agent.agent`` directly. The heavy lifting lives in
9+
``agent.agent`` (orchestration), with ``db/``, ``filters/``, ``llm/``,
10+
``sandbox/`` and ``telemetry/`` as sub-packages.
11+
"""
12+
13+
from .agent import main
14+
from .agent import _async_main
15+
from .agent import skill_load
16+
from .agent import load_diff
17+
from .agent import persist_changeset
18+
from .agent import FIXTURES
19+
20+
__all__ = [
21+
"main",
22+
"_async_main",
23+
"skill_load",
24+
"load_diff",
25+
"persist_changeset",
26+
"FIXTURES",
27+
]

0 commit comments

Comments
 (0)