引言:为什么我要折腾这套系统
最近一直都在探索如何将 AI 工具集成进法律场景中,此前用 dify 搭建的知识库和工作流系统,已经基本消灭了“AI 幻觉”,但 dify 是企业级的,对我个人来说太重了,而且工作流缺乏真正的“agent”的自主决策。自从 OpenClaw 出现,原本是把他当“维修工”去运维 dify+n8n 系统,但最近越用越顺手,于是将一部分法律场景工具逐步迁移到 OpenClaw 上,这个迁移的过程本身就很有意思,并不是简单的跨行业跨专业,现在已经自己进化成一套 IT+ 法律 + 金融 + 自媒体的 AI 集成系统。
这套系统中主要由 5 个角色组成,各自分工不同,既有合作也有互相监督机制。
不同 agent 可以通过“圆桌会议”共同优化一个项目,也可以展开“自由辩论”论证一个方案
更狠的是,我还设置了一个“魔鬼代言人”agent,他是负责对圆桌会议形成的方案唱反调,挑毛病的,这就可以有效避免我和 AI 的自嗨假象。
为了避免各 agent 重复劳动、浪费 token,我还建立了缓存共享系统
为了防止各 agent 上下文污染,我又建立了上下文管理系统
详细教程如下,也欢迎您提出建议
2. 我的业务需求:
1.要有一个 AI 律师,帮我处理法律工作(本地知识库检索、类案检索、法律文书生成等)
2.要有一个 IT 工程师,能写代码、能维护系统。将我的所有需求用技术实现
3.要有一个编辑,能将我使用 AI 工具实现法律场景的需求,写出自媒体文章,发布在自媒体平台。
这不就是一个天然的"多 Agent 系统"吗?
2.1 AI 协作架构
我这套系统是借鉴了 TradingAgents 的架构,TradingAgents 本身是擅长做股票分析的,我是参考了他的分层决策思路,但完全没有用他这套系统,我的思路是设计了 5 个 AI 角色:
老 Q (我)
↓
傻龙 (主助理) — 主角色
↓
┌───────────────┬───────────────┬───────────────┐
↓ ↓ ↓
⚖️ 律师 ✍️ 作家 💻 码农
↓ ↓ ↓
🔍 审核员 — 质量把关
↓
🎓 魔鬼代言人 — 风险评估
各 agent 的功能说明
傻龙:是我的主 agent,负责与我沟通需求和确认验收
律师:负责提出法律需求、处理法律事务
码农:负责将需求用开发技术实现
作家:负责将整个应用成绩写出自媒体文章
审核员:质量控制模块。无论法律文书还是代码质量他都会审核把关
魔鬼代言人:风险评估专家,专门负责"唱反调"!寻找方案漏洞,提出反面意见,防止其他 agent 出现群体思维和确认偏误。
2.2 文件结构 对每个 agent 的功能定义都要单独建立 agents 文件
文件位于/workspace/agents/ 目录下
/root/.openclaw/workspace/
├── agents/ # 角色定义
│ ├── lawyer.md
│ ├── writer.md
│ ├── coder.md
│ ├── reviewer.md
│ └── devils_advocate.md
├── checklists/ # 检查清单
│ ├── lawyer_checklist.md
│ ├── writer_checklist.md
│ └── coder_checklist.md
├── src/cache/ # 缓存模块
│ └── ttl_cache.py
└── cache/ # 缓存数据
└── cache.db
3. 核心代码:sessions_spawn 完整调用逻辑
3.1 基础调用
基本调用就是随时可以单独调用任何一个 agent 出来解决属于他专业知识范畴内的问题
from openclaw import sessions_spawn
# 召唤律师分身
response = sessions_spawn(
task="起草一份民间借贷起诉状",
role="lawyer",
timeoutSeconds=300
)
3.2 并行调用(圆桌会议)
圆桌会议是让各个 agent,用自己专业视角去规划我的某个想法或优化某个环节
比如我要在自媒体平台发变这篇文章,就可以召唤“律师、作家、码农”三个 agent 来讨论怎么样打造一篇“爆款”法律与 AI 融合的文章。
# 同时召唤三分身
tasks = [
{"role": "lawyer", "task": "法律风险分析"},
{"role": "writer", "task": "传播效果分析"},
{"role": "coder", "task": "技术可行性分析"}
]
results = []
for task in tasks:
result = sessions_spawn(
task=task["task"],
role=task["role"],
timeoutSeconds=300
)
results.append(result)
# 汇总结果
final_output = summarize(results)
如果有重大决策问题,可以加入“魔鬼代言人”agent,他是负责对圆桌会议形成的方案唱反调,挑毛病的。这就避免了我和 AI 的自嗨假象。
3.3 超时时间配置
这里根据不同任务场景设置了子 agent 的工作时长,避免他滥用资源
def get_timeout(task_type: str) -> int:
"""根据任务类型获取超时时间"""
timeout_config = {
"simple_query": 60, # 简单查询
"consultation": 180, # 咨询分析
"review": 180, # 审核检查
"scripting": 300, # 脚本编写
"ops": 300, # 系统运维
"complex_dev": 600 # 复杂开发
}
return timeout_config.get(task_type, 300)
4. 角色定义:5 个角色的详细配置
4.1 律师分身 (agents/lawyer.md)
# ⚖️ Lawyer-Agent 律师分身
## 🎯 身份定位
- **专业领域:** 中国法律文书起草、案卷分析、法律检索
- **人格特质:** 严谨、保守、注重证据链完整性
- **思维模式:** 先找法条 → 再套事实 → 最后下结论
## ⚠️ 执业边界
### 可以做
- ✅ 法律文书起草(起诉状、答辩状、代理词等)
- ✅ 案卷分析与证据梳理
- ✅ 法律检索与法条引用
### 禁止做
- ❌ 不给出确定性胜诉承诺
- ❌ 不编造法条或案例
## 🧠 工作流程
1. 接收任务
2. 检索相关法条(必须精确到条款序号)
3. 分析案件事实(区分"事实"与"意见")
4. 起草文书/出具分析
5. 自我审查(检查清单)
6. 交付傻龙汇总
## 📋 输出标准
### 法条引用
- 必须完整:《法律名称》第 X 条第 X 款
- 必须核实有效性
### 事实描述
- 必须标注证据来源
- 必须区分"已证实事实"与"待证事实"
4.2 作家分身 (agents/writer.md)
# ✍️ Writer-Agent 作家分身
## 🎯 身份定位
- **专业领域:** 自媒体创作、洗稿、观点提炼
- **人格特质:** 敏锐、有温度、跨界类比
- **思维模式:** 找核心冲突 → 提炼金句 → 构建叙事弧线
## 📋 输出标准
### 标题
- 必须有吸引力但不夸大
- 必须提供 3-5 个备选
### 正文
- 开头:3 秒内抓住注意力
- 每段:至少 1 个可传播金句
- 结尾:行动号召或深度思考
4.3 码农分身 (agents/coder.md)
# 💻 Coder-Agent 码农分身
## 🎯 身份定位
- **专业领域:** Python 脚本、运维、故障排查
- **人格特质:** 严谨、注重边界、先解释后指令
## ⚠️ 运维边界
### 可以做
- ✅ Python 自动化脚本开发
- ✅ 系统配置检查与优化建议
### 禁止做
- ❌ 不执行危险命令(rm -rf /等)
- ❌ 不修改系统核心配置(/etc/ 下文件)
## 📋 输出标准
### 命令解释
- 每条命令前必须解释作用
- 危险操作前必须**加粗提示**风险
4.4 审核员 (agents/reviewer.md)
# 🔍 Reviewer 审核员
## 🎯 身份定位
- **职责:** 查找低级错误和逻辑漏洞
- **人格:** 挑剔、细节控
- **口头禅:** "让我再检查一遍"
## 🧠 工作流程
接收分身输出 → 逐项检查清单 → 标注问题 → 决策
## 📋 决策权限
- ✅ 放行:无致命问题
- 🔄 要求修改:有严重问题(限 1 次)
- 🚨 上报傻龙:有致命问题未修复
4.5 魔鬼代言人 (agents/devils_advocate.md)
# 🎓 Devil's Advocate 魔鬼代言人
## 🎯 身份定位
- **职责:** 寻找方案漏洞,提出反面意见
- **人格:** 怀疑主义、批判性思维
- **触发条件:** 重大决策、高风险操作
## 🧠 思维工具
1. **假设挑战**:这个方案的核心假设是什么?
2. **最坏情况推演**:最坏情况是什么?
3. **逆向思考**:如果反过来做会怎样?
4. **利益相关者分析**:谁会反对?
## 📋 输出形式
### 🔴 致命风险(必须解决)
### 🟠 严重风险(强烈建议解决)
### 💡 替代方案
### 🌳 决策树
5. 检查清单:审核员如何逐项检查
5.1 律师检查清单 (checklists/lawyer_checklist.md)
# ⚖️ 律师输出检查清单
## 🔴 致命问题(必须修复)
### 法条引用
- [ ] 法条名称是否完整准确?
- [ ] 条款序号是否正确?
- [ ] 法条是否有效?
### 事实描述
- [ ] 是否编造了事实或证据?
- [ ] 是否区分了"已证实事实"与"待证事实"?
## 🟠 严重问题(建议修复)
### 逻辑自洽
- [ ] 结论是否有法条支撑?
- [ ] 推理过程是否跳跃?
### 风险提示
- [ ] 是否提示了诉讼风险?
- [ ] 是否给出了备选方案?
## 🟡 轻微问题(标注即可)
### 表达
- [ ] 是否有错别字?
- [ ] 是否有语病?
5.2 审核员代码实现
class Reviewer:
"""审核员实现"""
def __init__(self, checklist_path: str):
self.checklist = self.load_checklist(checklist_path)
def review(self, content: str, role: str) -> dict:
"""审核输出内容"""
issues = {
"fatal": [],
"serious": [],
"minor": []
}
# 根据角色选择检查清单
checklist = self.checklist[role]
# 逐项检查
for item in checklist:
if not self.check(content, item):
issues[item["level"]].append(item["description"])
# 决策
if issues["fatal"]:
return {"decision": "escalate", "issues": issues}
elif issues["serious"]:
return {"decision": "revise", "issues": issues}
else:
return {"decision": "approved", "issues": issues}
def check(self, content: str, item: dict) -> bool:
"""执行单项检查"""
# 实现具体检查逻辑
pass
6. 缓存实现:从 0 到 1 构建缓存系统
6.1 为什么需要缓存?
早期我遇到过一个问题:同一个任务,律师分析了一遍,作家又分析了一遍,码农再分析一遍——很多基础信息是重复的。
比如"写一篇 AI 法律风险文章",三分身都要先了解:
- AI 是什么
- 有哪些法律风险
- 目标读者是谁
每次都重新分析,浪费时间浪费 token。
6.2 数据库设计
CREATE TABLE cache (
id INTEGER PRIMARY KEY AUTOINCREMENT,
key TEXT UNIQUE NOT NULL,
value BLOB NOT NULL, -- 压缩后的数据
created_at REAL NOT NULL, -- 创建时间戳
ttl INTEGER NOT NULL, -- 生存时间(秒)
category TEXT DEFAULT 'default',
access_count INTEGER DEFAULT 0,
last_access_at REAL
);
CREATE INDEX idx_key ON cache(key);
CREATE INDEX idx_category ON cache(category);
CREATE INDEX idx_expires ON cache(created_at + ttl);
6.3 TTL 缓存完整代码
import sqlite3
import hashlib
import gzip
import json
import time
import threading
from typing import Any, Optional
from contextlib import contextmanager
class TtlCache:
"""带锁、带压缩、带自动过期的 TTL 缓存"""
def __init__(self, db_path: str, max_size: int = 10000):
self.db_path = db_path
self.max_size = max_size
self.locks = {}
self.locks_lock = threading.Lock()
self._init_db()
def _init_db(self):
"""初始化数据库"""
with self._get_cursor() as cursor:
cursor.execute('''
CREATE TABLE IF NOT EXISTS cache (
id INTEGER PRIMARY KEY AUTOINCREMENT,
key TEXT UNIQUE NOT NULL,
value BLOB NOT NULL,
created_at REAL NOT NULL,
ttl INTEGER NOT NULL,
category TEXT DEFAULT 'default',
access_count INTEGER DEFAULT 0,
last_access_at REAL
)
''')
cursor.execute('CREATE INDEX IF NOT EXISTS idx_key ON cache(key)')
cursor.execute('CREATE INDEX IF NOT EXISTS idx_category ON cache(category)')
cursor.execute('CREATE INDEX IF NOT EXISTS idx_expires ON cache(created_at + ttl)')
def _get_lock(self, key: str) -> threading.Lock:
"""获取 key 的锁(防止缓存击穿)"""
with self.locks_lock:
if key not in self.locks:
self.locks[key] = threading.Lock()
return self.locks[key]
def get(self, key: str) -> Optional[Any]:
"""获取缓存"""
lock = self._get_lock(key)
with lock:
now = time.time()
with self._get_cursor() as cursor:
cursor.execute('''
SELECT value FROM cache
WHERE key = ? AND (created_at + ttl) > ?
''', (self._hash(key), now))
row = cursor.fetchone()
if row:
return self._decompress(row['value'])
return None
def set(self, key: str, value: Any, ttl: int = 3600):
"""设置缓存"""
now = time.time()
compressed = self._compress(value)
with self._get_cursor() as cursor:
cursor.execute('''
INSERT OR REPLACE INTO cache
(key, value, created_at, ttl)
VALUES (?, ?, ?, ?)
''', (self._hash(key), compressed, now, ttl))
# 清理过期缓存
cursor.execute(
'DELETE FROM cache WHERE (created_at + ttl) < ?',
(now,)
)
def get_or_set(self, key: str, fetch_func: callable, ttl: int = 3600):
"""获取或设置缓存(原子操作)"""
cached = self.get(key)
if cached is not None:
return cached
value = fetch_func()
self.set(key, value, ttl)
return value
def _compress(self, data: Any) -> bytes:
"""压缩数据"""
json_str = json.dumps(data, ensure_ascii=False)
return gzip.compress(json_str.encode('utf-8'))
def _decompress(self, compressed: bytes) -> Any:
"""解压数据"""
json_str = gzip.decompress(compressed).decode('utf-8')
return json.loads(json_str)
def _hash(self, key: str) -> str:
"""计算 key 的哈希"""
return hashlib.md5(key.encode('utf-8')).hexdigest()
@contextmanager
def _get_cursor(self):
"""获取游标上下文管理器"""
conn = sqlite3.connect(self.db_path, check_same_thread=False, timeout=30)
conn.row_factory = sqlite3.Row
cursor = conn.cursor()
try:
yield cursor
conn.commit()
except Exception as e:
conn.rollback()
raise e
finally:
cursor.close()
6.4 使用示例
from src.cache.ttl_cache import TtlCache
# 初始化缓存
cache = TtlCache("/root/.openclaw/workspace/cache/cache.db")
# 基本使用
cache.set("user_123", {"name": "老 Q"}, ttl=3600)
user_data = cache.get("user_123")
# 使用 get_or_set(推荐)
def fetch_github_data():
return {"stars": 100}
data = cache.get_or_set("github_stars", fetch_github_data, ttl=7200)
7. 性能数据:优化前后的真实对比
7.1 优化前的问题
问题 1:重复分析浪费资源
老 Q 指令 → 律师分析(完整背景)→ 作家分析(完整背景)→ 码农分析(完整背景)
耗时:45 分钟
问题 2:无缓存机制
每次调用 API 都要重新请求
每次数据库查询都要重新执行
7.2 优化后的实现
优化 1:共享上下文
class RoundTable:
"""圆桌会议协调器"""
def __init__(self):
self.shared_context = {}
def analyze(self, task: str):
# 傻龙解析背景(一次)
self.shared_context = self.parse_background(task)
# 三分身并行分析(只分析专业部分)
results = parallel_call([
{"role": "lawyer", "context": self.shared_context},
{"role": "writer", "context": self.shared_context},
{"role": "coder", "context": self.shared_context}
])
return summarize(results)
优化 2:结果缓存
# 缓存常见问题模板
cache.set("complaint_template", template, ttl=86400)
# 缓存脚本
cache.set("backup_script", script, ttl=604800)
# 缓存标题库
cache.set("title_formulas", formulas, ttl=2592000)
7.3 性能数据对比
| 指标 | 优化前 | 优化后 | 提升 |
|---|---|---|---|
| 跨领域分析耗时 | 45 分钟 | 25 分钟 | 44% |
| API 响应时间 | 200-500ms | <1ms(缓存命中) | 99%+ |
| 重复任务耗时 | 20 分钟 | 3 分钟 | 85% |
| 低级错误率 | 15% | 1.2% | 92% |
8. 部署指南:一步步教
8.1 环境准备
# 创建目录
mkdir -p /root/.openclaw/workspace/{agents,checklists,src/cache,cache}
# 确认 Python 版本(需要 3.12+)
python3 --version
8.2 创建角色定义文件
# 律师分身
cat > /root/.openclaw/workspace/agents/lawyer.md << 'EOF'
# ⚖️ Lawyer-Agent 律师分身
## 🎯 身份定位
- **专业领域:** 中国法律文书起草、案卷分析、法律检索
- **人格特质:** 严谨、保守、注重证据链完整性
## ⚠️ 执业边界
### 可以做
- ✅ 法律文书起草(起诉状、答辩状、代理词等)
- ✅ 案卷分析与证据梳理
### 禁止做
- ❌ 不给出确定性胜诉承诺
- ❌ 不编造法条或案例
EOF
# 作家分身
cat > /root/.openclaw/workspace/agents/writer.md << 'EOF'
# ✍️ Writer-Agent 作家分身
# (复制完整定义)
EOF
# 码农分身
cat > /root/.openclaw/workspace/agents/coder.md << 'EOF'
# 💻 Coder-Agent 码农分身
# (复制完整定义)
EOF
# 审核员
cat > /root/.openclaw/workspace/agents/reviewer.md << 'EOF'
# 🔍 Reviewer 审核员
# (复制完整定义)
EOF
# 魔鬼代言人
cat > /root/.openclaw/workspace/agents/devils_advocate.md << 'EOF'
# 🎓 Devil's Advocate 魔鬼代言人
# (复制完整定义)
EOF
8.3 创建检查清单
# 律师检查清单
cat > /root/.openclaw/workspace/checklists/lawyer_checklist.md << 'EOF'
# ⚖️ 律师输出检查清单
## 🔴 致命问题(必须修复)
### 法条引用
- [ ] 法条名称是否完整准确?
- [ ] 条款序号是否正确?
- [ ] 法条是否有效?
EOF
# 作家检查清单
cat > /root/.openclaw/workspace/checklists/writer_checklist.md << 'EOF'
# ✍️ 作家输出检查清单
# (复制完整清单)
EOF
# 码农检查清单
cat > /root/.openclaw/workspace/checklists/coder_checklist.md << 'EOF'
# 💻 码农输出检查清单
# (复制完整清单)
EOF
8.4 部署缓存模块
# 复制缓存代码
cp /root/.openclaw/workspace/src/cache/ttl_cache.py \
/your/project/src/cache/
# 初始化缓存
python3 -c "from src.cache.ttl_cache import init_cache_db; init_cache_db()"
# 测试缓存
python3 -c "
from src.cache.ttl_cache import TtlCache
cache = TtlCache('cache/cache.db')
cache.set('test', {'hello': 'world'})
print(cache.get('test'))
"
8.5 验证部署
# 运行测试脚本
python3 /root/.openclaw/workspace/examples/cache_example.py
# 预期输出:
# ✅ 缓存数据库初始化完成
# 测试 1 - 设置/获取:{'hello': 'world'}
# 测试 2 - 第一次获取:{'data': 'fresh'}
# 测试 2 - 第二次获取(缓存命中):{'data': 'fresh'}
# ✅ 测试完成
9. 踩坑记录:我犯过的错误和解决方案
踩坑 1:法条引用错误
问题: 在一份法律意见书中引用了已经废止的法条。
原因: 没有核实法条有效性。
解决方案: 律师检查清单中加入"法条是否有效"检查项。
踩坑 2:系统迁移数据丢失
问题: 迁移系统时数据丢失 20%。
原因: 没有做完整备份。
解决方案: 魔鬼代言人必须评估"最坏情况",并提供 B 计划。
踩坑 3:缓存击穿
问题: 高并发时缓存失效,大量请求直接打到数据库。
原因: 没有加锁。
解决方案: 每个 key 独立锁,防止并发请求击穿缓存。
踩坑 4:审核员流于形式
问题: 审核员快速扫一眼就放行。
原因: 没有强制检查清单。
解决方案: 审核员必须逐项检查清单,不能跳过。
踩坑 5:分身上下文污染
问题: 上一个任务的上下文影响下一个任务。
原因: 任务结束后没有释放资源。
解决方案: 任务结束后立即执行 subagents(action="kill")。
结语:给想动手的朋友的建议
作为一个法律人,我不需要成为技术专家,但我需要知道如何用技术解决法律问题。
这套系统的核心价值,不是技术有多先进,而是把专业知识结构化。
律师知道怎么引用法条,作家知道怎么制造金句,码农知道怎么标注风险——这些专业知识被固化在角色定义和检查清单里,AI 只需要按图索骥。