chore(backup): 保存新版预审开发前的代码与文档基线
verify / test-and-build (push) Canceled after 0s

This commit is contained in:
2026-09-28 16:34:10 +08:00
parent b10093f9d8
commit 4d5a420671
37 changed files with 3890 additions and 10 deletions
@@ -0,0 +1,661 @@
# mini-openclaw 项目技术架构与数据流说明
**面向对象:** 无代码基础的业务、产品、实施与管理同事
**说明版本:** v1.0
**代码基线:** Git 提交 `aec52ec`(2026-09-16)
**适用范围:** 本仓库当前代码,不扩展到通用 LangChain、LangGraph 或其他 OpenClaw 实现
---
## 1. 这份文件要回答什么
这份文件只解释当前项目里的真实实现,重点回答六个问题:
1. 用户输入一句话后,数据先到哪里、再到哪里?
2. 原版 mini-openclaw 怎样组织提示词、历史对话、Skills 和工具?
3. LangChain 与 LangGraph 在本项目里各自承担什么?
4. 大模型一次“回答”为什么可能实际调用多次模型?每次输入和输出是什么?
5. 工具怎样被选择、执行和回传?哪些工具有安全边界?
6. 短期记忆、长期记忆、知识库、运行留痕和兴邦业务快照有什么区别?
为了不误导读者,全文使用三个标记:
- **已实现**:当前代码中存在并能指出具体文件。
- **配置约定**:由提示词或配置规定,但不等于代码自动强制完成。
- **当前边界**:代码尚未实现、依赖缺失或能力有限的地方。
## 2. 一页结论:先建立正确的整体认识
mini-openclaw 不是“一个大模型直接连接数据库然后给答案”。当前项目由两条相互关联但用途不同的数据链组成。
### 2.1 原版通用对话链
```text
浏览器聊天框
→ Next.js 同源代理 /api/core
→ FastAPI /api/chat
→ 读取会话历史与系统提示词
→ LangChain create_agent(底层由 LangGraph 驱动循环)
→ 大模型决定直接回答,或请求调用工具
→ Python 执行被允许的工具
→ 工具结果回到模型
→ 模型继续判断,直至形成最终文本
→ SSE 流式返回浏览器,并保存会话与追踪日志
```
它适合自由问答、文件读取、受限命令、Python 计算、网页抓取和知识库检索。它的主要输出是自然语言。
### 2.2 兴邦业务运行链
```text
业务数据库 / 上传材料 / 人工确认
→ 数据卡受控 SQL 与字段映射
→ 冻结输入快照
→ 确定性 Skills 与规则先计算
→ 可选的受限大模型语义核查
→ 对模型 JSON 做结构、证据和权限校验
→ 形成问题、证据、运行记录与报告草稿
→ 客户经理提交评审池
→ 评审员领取、复核、退回或批准
```
它适合可追溯的对公金租预审与评审。它的主要输出不是聊天文字,而是带编号、版本、证据和状态的业务对象。
### 2.3 两条链的关系
- 两者共用同一个 `AgentManager` 基座和 LangChain Agent 循环。
- 原生聊天拥有通用工具;业务运行只获得当前任务明确允许的只读工具。
- 原生聊天使用会话记忆;业务运行使用冻结快照,不继承聊天记忆,避免把上一项目内容带入下一项目。
- 业务快捷助手的多数命令是可审计的固定意图分派,不是把每句话都交给大模型猜。
## 3. 系统分层:每一层只做一类事
| 层次 | 当前技术 | 主要职责 | 关键代码 |
|---|---|---|---|
| 浏览器界面 | Next.js 14、React 18 | 展示聊天、项目、评审池、报告、时效看板 | `src/app/`、`src/components/` |
| 同源代理 | Next.js Route Handler | 将浏览器请求转发到 8002 后端;生产环境校验访问身份 | `src/app/api/core/[...path]/route.ts`、`src/app/api/risk-agent/[...path]/route.ts` |
| API 层 | FastAPI | 参数校验、权限检查、SSE 输出、业务路由 | `backend/app.py`、`backend/api/` |
| Agent 编排层 | LangChain `create_agent`,LangGraph 运行循环 | 组织模型与工具的多轮调用 | `backend/graph/agent.py` |
| 模型适配层 | `ChatDeepSeek`、`ChatOpenAI` | 调用通用模型或内网业务模型 | `backend/graph/agent.py`、`backend/services/assistant_model.py` |
| 工具层 | LangChain Tools | 终端、Python、网页、文件、知识库或业务只读工具 | `backend/tools/`、`backend/services/assistant_model.py` |
| 业务执行层 | Python 确定性执行器、持久 Worker | 数据读取、计算、规则、模型核查、断点恢复 | `backend/services/assistant_worker.py`、`skill_executor.py` |
| 持久化层 | JSON/JSONL、SQLite、LlamaIndex 索引 | 会话、追踪、项目对象、任务、证据、向量索引 | `backend/sessions/`、`raw_logs/`、`xingbang_data/`、`storage/` |
浏览器并不直接连接大模型、数据库或文件系统。所有能力都必须先经过代理和后端接口。
## 4. 服务启动时发生什么
FastAPI 的入口是 `backend/app.py`。服务启动时按当前代码执行以下动作:
1. 扫描 `backend/skills/**/SKILL.md`,生成 `backend/SKILLS_SNAPSHOT.md`。
2. 初始化 `AgentManager`,加载模型配置、通用工具和会话管理器。
3. 尝试为 `backend/memory/MEMORY.md` 重建或加载向量索引。
4. 根据功能开关启动兴邦项目的 `DurableWorker` 后台线程。
5. 注册聊天、会话、文件、配置以及兴邦项目、评审、报告等 API 路由。
这说明 Skills 清单、模型对象和记忆索引不是每次点击按钮才从零创建;部分基础设施在后端启动时准备好。业务组件则在每次运行入队时冻结,保证运行过程中版本不漂移。
## 5. 原版 mini-openclaw:一条消息的完整旅程
以下按时间顺序解释用户在原生聊天框输入“读取材料并分析风险”之后发生的事情。
### 5.1 浏览器发出请求
前端 `src/lib/api.ts` 将消息、`session_id` 和 `stream=true` 发送到 `/api/core/chat`。
`/api/core` 是 Next.js 同源代理。它只允许一组白名单路径,例如 `chat`、`sessions`、`files`、`skills`、`tokens`、`config` 和 `xingbang`。生产环境中,通用聊天和文件工具只允许已认证管理员;未配置认证时,只允许本机开发访问。
代理把请求转发到 FastAPI 的 `/api/chat`,并且对 SSE 响应直接转发流,不先把整个回答读完。
### 5.2 FastAPI 读取会话
`backend/api/chat.py` 接到请求后,从 `SessionManager` 读取当前会话。
会话文件位于 `backend/sessions/{session_id}.json`,主要包括:
- 会话标题;
- 创建和更新时间;
- 用户与助手消息;
- 压缩后的历史摘要(如果执行过会话压缩)。
送给 Agent 之前,代码会把连续的助手消息合并,并只把普通的用户/助手历史转成模型消息。工具轨迹有单独的 JSONL 留痕,不会原样全部塞回下一轮上下文。
### 5.3 构造系统提示词
`backend/graph/prompt_builder.py` 按固定顺序拼接:
1. `backend/SKILLS_SNAPSHOT.md`:可用 Skills 的名称、说明和路径;
2. `backend/workspace/SOUL.md`:行为风格和原则;
3. `backend/workspace/IDENTITY.md`:助手身份;
4. `backend/workspace/USER.md`:用户背景;
5. `backend/workspace/AGENTS.md`:工作规则;
6. `backend/memory/MEMORY.md`:仅在 RAG 关闭时整份加入。
单个文件最多读取 20,000 个字符。这里要区分:Skills 快照告诉模型“有哪些方法说明”;它本身不会把每个 `SKILL.md` 自动变成 Python 函数。
### 5.4 构造 Agent
`backend/graph/agent.py` 调用:
```python
create_agent(model=model, tools=tools, system_prompt=system_prompt)
```
这里的职责分工是:
- **LangChain** 提供模型适配器、消息类型、工具定义和 `create_agent` 高层接口。
- **LangGraph** 在 LangChain v1 的 `create_agent` 内部承担有状态循环:模型节点 → 工具节点 → 再回模型节点,直到不再产生工具调用。
- **本项目代码** 决定系统提示词、可用工具、历史消息、流式事件、日志和超时。
原版聊天没有在仓库中手写一张复杂的 `StateGraph` 图,而是使用 LangChain 封装好的 Agent 图。因此说“本项目使用 LangGraph”是正确的,但不能误解为仓库中每个页面都自己定义了 LangGraph 节点。
### 5.5 模型第一次收到什么
第一次模型调用的输入可概括为:
```text
系统提示词
+ 已压缩历史摘要(如果有)
+ 既往用户/助手消息
+ 本轮用户消息
+ 本轮 RAG 检索结果(仅 RAG 开启且检索成功时)
+ 工具名称、用途与参数结构
```
模型会输出两类结果之一:
1. **普通文本 token**:表示模型准备直接回答;
2. **tool_calls**:表示模型不直接给最终答案,而是请求执行一个或多个工具,并给出工具参数。
模型只“提出调用请求”,真正执行命令、读取文件或发起 HTTP 请求的是后端 Python 工具代码。
### 5.6 工具调用循环
如果模型输出工具调用,例如 `read_file({path: ...})`,流程是:
1. LangGraph 将控制权转到工具节点;
2. 工具节点按参数调用 Python 函数;
3. Python 函数完成读取或计算,返回文本/对象;
4. 结果包装成 `ToolMessage`;
5. `ToolMessage` 与此前上下文一起再次送入模型;
6. 模型根据工具结果继续回答,或再次请求工具;
7. 当模型只输出文本、不再输出 `tool_calls` 时,本轮结束。
因此一次用户问题可能包含多次大模型调用。例如:
```text
第1次模型:决定读取文件
工具:返回文件内容
第2次模型:发现需要计算
工具:执行 Python 计算
第3次模型:综合文件与计算结果,生成最终回答
```
“三次模型调用”仍然是用户看到的一次对话轮次。
### 5.7 SSE 怎样让页面实时更新
后端把运行过程转换为 Server-Sent Events:
| 事件 | 页面含义 |
|---|---|
| `retrieval` | 长期记忆检索结果已加入上下文 |
| `token` | 新生成的一小段回答文字 |
| `tool_start` | 模型请求的工具开始执行 |
| `tool_end` | 工具执行结束,返回预览结果 |
| `new_response` | 工具调用后,模型开始新一段文字回答 |
| `done` | 本轮完整回答结束 |
| `title` | 首轮对话自动生成了会话标题 |
| `error` | 空回答、超时或调用失败 |
聊天流空闲超过 75 秒或总运行超过 180 秒会终止。代码不会在超时后自动重放整轮,以免有副作用的工具被执行两次。
### 5.8 一轮结束后保存什么
一轮结束后至少有三类记录:
1. **会话记录**:`backend/sessions/{session_id}.json`,供下次对话继续使用;
2. **完整消息轨迹**:`backend/raw_logs/{session_id}.msgs.jsonl`,包含 human、assistant、tool 消息;
3. **原始模型追踪**:`backend/raw_logs/{session_id}.jsonl`,包含每次 LLM 请求、响应、工具调用、token 用量、结束原因和耗时。
页面上的工具结果可能被截短用于展示,但完整 `ToolMessage` 会写入消息轨迹。原始追踪会过滤 `api_key` 字段,但消息正文仍可能包含业务内容,因此该目录应按敏感日志管理。
首轮对话结束后还可能额外调用一次模型生成短标题;这次调用不属于业务回答本身。
## 6. 原版通用工具:模型能做什么
`backend/tools/__init__.py` 当前注册五类通用工具。
### 6.1 `terminal`
- 在后端项目目录作为工作目录执行命令;
- 30 秒超时;
- 返回内容最多约 5,000 字符;
- 对若干危险命令字符串做黑名单检查。
**当前边界:** 这是“工作目录 + 命令黑名单”的约束,不是操作系统级强沙箱。它不能被描述为完全隔离的安全容器。因此生产代理将通用工具限制为管理员使用。
### 6.2 `python_repl`
通过 LangChain Experimental 的 Python REPL 执行计算。适合数据计算和快速验证。
**当前边界:** 包装层没有显示文件系统强隔离,权限仍取决于后端进程账户。
### 6.3 `fetch_url`
- 使用 HTTP GET 获取 URL;
- 15 秒超时;
- JSON 直接返回,HTML 转 Markdown;
- 结果最多约 5,000 字符。
### 6.4 `read_file`
- 读取后端允许根目录内的文件;
- 路径解析后检查是否仍位于根目录;
- 最多返回约 10,000 字符。
### 6.5 `search_knowledge_base`
- 面向 `backend/knowledge` 的通用知识库;
- 通过 LlamaIndex 建立向量索引;
- 每次返回相似度最高的 3 个结果。
**当前边界:** 当前代码实现的是向量检索,不是文档中曾描述的 BM25 + 向量混合检索。仓库当前也没有可见的 `backend/knowledge` 内容时,工具会返回知识库为空。
## 7. Skills 在本项目里到底是什么
Skills 容易被误解为“装上去就能自动执行的插件”。当前项目存在两种不同用法。
### 7.1 原生聊天里的 Skills:方法说明
`backend/tools/skills_scanner.py` 扫描 `backend/skills/**/SKILL.md`:
- YAML 头必须包含 `name` 和 `description`;
- 单文件最大 64 KB;
- 状态为 `archived` 的 Skill 不展示;
- 扫描结果写入 `SKILLS_SNAPSHOT.md`,进入系统提示词。
这类 Skill 主要告诉模型“遇到某类任务应按什么步骤做”。它本身不会自动获得新权限;真正能不能读文件、查网页、执行命令,仍由 `tools` 列表决定。
### 7.2 兴邦业务里的 Skills:冻结的方法 + 受控执行器
业务运行还会把 Skills 组件冻结进运行计划:
- `quality`、`ratio`、`compare`、`required`、`duplicate` 等由 Python 确定性执行;
- `model` 类型才调用大模型;
- `model` Skill 只能使用发布时声明的 `allowed_tools`;
- 被选中的原生 `SKILL.md` 文本在入队时读出并计算哈希,之后文件发生变化也不会影响这次运行。
这使“方法”与“权限”分离:Skill 可以教模型如何核查租赁物,但不能凭文字自行获得 SQL、终端或写文件权限。
## 8. 记忆系统:五种容易混淆的存储
### 8.1 会话短期记忆
位置:`backend/sessions/{session_id}.json`。
用途:让同一聊天会话记住前文。下一轮模型会重新获得历史用户/助手消息。
### 8.2 压缩历史
当会话过长,可以调用压缩接口:
- 旧消息归档到 `backend/sessions/archive/`;
- 摘要写入会话的 `compressed_context`;
- 下次对话把摘要作为第一条助手上下文加入。
摘要不是原消息逐字回放,因此重要事实仍应进入正式项目数据或长期记忆,而不是只依赖压缩摘要。
### 8.3 长期记忆 `MEMORY.md`
位置:`backend/memory/MEMORY.md`。
当前有两种模式:
- **RAG 关闭(当前 `backend/config.json` 为 `false`)**:整份 `MEMORY.md` 直接进入系统提示词;
- **RAG 开启**:将文件切成约 256 token、重叠 32 token 的片段,使用 `BAAI/bge-small-zh` 嵌入,按问题检索前 3 个片段。
索引持久化到 `backend/storage/memory_index`,用 MD5 检测文件变化并重建。索引加载失败时会降级,不应把“启动成功”误认为“长期记忆一定可检索”。
**配置约定:** `workspace/AGENTS.md` 可以要求模型把重要信息写入长期记忆;这是提示词协议。当前没有发现一套独立代码自动把每日聊天全部总结并写入 `MEMORY.md`,所以不能宣传为全自动记忆沉淀。
### 8.4 通用知识库
位置:`backend/knowledge` 和 `backend/storage` 下的知识库索引。
用途:查询制度、材料或知识文档。它与 `MEMORY.md` 是两套独立索引:长期记忆是“助手应该记住的上下文”,知识库是“可以检索的外部资料”。
### 8.5 兴邦业务快照与经验
业务项目不会依赖聊天记忆作为审批依据。它使用 SQLite 中的:
- 项目事实与已选数据来源;
- 不可变输入快照和项目快照;
- 运行步骤、问题、证据、报告和评审决定;
- 已发布经验版本及其适用记录。
这类存储是业务留痕,不是大模型的自由记忆。只有经过发布、权限和适用条件筛选的经验才会进入某次业务运行。
## 9. 兴邦扩展:业务数据怎样进入系统
### 9.1 三类入口
当前业务事实可以来自:
1. **受控数据库数据卡**:管理员配置数据源、参数化 SQL 和字段映射;
2. **上传材料**:TXT、CSV、DOCX、XLSX、PDF、PNG、JPG;
3. **人工确认/补充**:客户经理对解析候选、期间、单位、口径和来源进行确认。
三类入口最终都转换成标准字段,例如:
- `facility.amount`:融资金额;
- `lease.total_value`:租赁物价值;
- `financial.total_liabilities`:财务负债;
- `financial.operating_cashflow`:经营现金流;
- `credit.overdue_count`:逾期记录数。
Skills 和规则只依赖标准字段,不直接依赖源系统的中文列名或表结构。
### 9.2 数据卡怎样读取多张业务表
演示业务库脚本 `backend/backend/xingbang_data/business_database.sql` 包含客户、项目、融资方案、财务报表、租赁物、保险、增信、征信、诉讼、还款来源和材料等多张表。
数据卡配置包含:
- 数据源 ID;
- 只读、参数化 SQL,例如使用 `:project_id`;
- 源字段到标准字段的映射;
- 金额单位、币种、期间和口径。
`assistant_data.py` 执行数据卡后会:
1. 检查演示域与生产域不能混用;
2. 按项目参数读取行;
3. 做字段映射和金额单位换算;
4. 为每个字段生成证据引用;
5. 发现多来源不一致时将字段置空并记录冲突,而不是擅自选一个值;
6. 保存数据集行哈希,便于判断来源是否变化。
业务模型不会直接拿到数据库连接,也没有 SQL 工具。SQL 由数据卡层执行,模型只能读取已经授权并冻结的标准字段和证据。
### 9.3 上传材料怎样解析
`assistant_materials.py` 的约束包括:
- 单文件最大 10 MB;
- 文本总量最大约 30 万字;
- DOCX/XLSX 直接读取压缩包内 XML,不执行宏;
- XLSX 公式只读取已有缓存值,不在服务端重算公式;
- PDF/图片通过抵质押核验模块的页面提取能力读取文本;
- 解析不到的页会产生警告;
- 每个文本片段生成独立证据 ID。
解析结果先形成“候选字段”,不会自动覆盖项目事实。只有人工确认候选并说明依据后,项目数据版本才更新。
## 10. 一次业务 AI 预审怎样执行
### 10.1 入队,而不是浏览器一直等待
客户经理点击“发起 AI 预审”或使用业务快捷命令后,`ProjectAssistantService.enqueue` 会:
1. 检查身份、角色和项目权限;
2. 解析选定方案;
3. 冻结 Skill、规则、模板、数据卡、内置方法文本及版本哈希;
4. 冻结当时已确认的项目事实、来源和材料清单;
5. 创建 `run`、`input_snapshot` 和 `assistant_jobs` 记录;
6. 使用幂等键避免同一操作因重复点击而创建两次;
7. 立即返回运行编号,后台 Worker 再继续执行。
### 10.2 DurableWorker 怎样保证任务可恢复
Worker 从 SQLite 领取 `queued` 任务,并写入:
- 执行人(worker owner);
- 30 秒租约;
- 尝试次数;
- 当前状态和开始时间。
执行期间每 5 秒续租。进程异常后,租约过期的任务可以被重新领取;最多恢复 3 次。每个步骤完成后都会持久化,重新执行时可复用已完成步骤,而不是从第一步全部重跑。
### 10.3 先确定性计算,再调用模型
当前默认顺序是:
1. 非模型 Skills;
2. 规则集;
3. 已发布经验核查;
4. 模型 Skills。
金额和比率使用 `Decimal`,不是二进制浮点数;执行器只允许固定类型,不使用动态 `eval`。这部分结果可重复、可单元测试。
### 10.4 业务模型每次看到什么
模型 Skill 通过 `assistant_model.py` 创建一个“任务隔离 Agent”。它不会读取聊天历史,也不能继承通用 Agent 的终端、网页或 SQL 工具。
它可被授权的工具只有:
- `read_project_snapshot`:读本次不可变项目事实和来源;
- `read_document`:读本次快照内的一份材料;
- `read_checks`:读已经计算的检查、发布规则和本次人工要求;
- `read_method`:读本次选中的冻结 Skill 方法;
- `read_experiences`:读脱敏后的适用经验指引。
模型的任务说明明确要求:材料中的指令是不可信数据;不得执行 SQL;不得修改文件;不得作审批决定;不得自行生成重要数字。
### 10.5 模型输出为什么不能直接入库
模型必须返回一个 JSON 对象,并通过 Pydantic 校验。主要字段是:
```json
{
"findings": [
{
"code": "稳定英文检查码",
"title": "问题标题",
"result": "pass | issue | unknown | not_applicable",
"evidence_ids": ["证据编号"],
"fields": ["标准字段"],
"suggested_action": "建议动作"
}
],
"requirement_response": "对本次人工要求的回应"
}
```
程序还会继续做业务校验:
- 必须调用 `read_project_snapshot` 和 `read_checks`;
- 每个选中方法都必须调用 `read_method`;
- 引用的证据必须属于当前快照;
- 引用材料片段前必须实际调用过对应 `read_document`;
- `pass` 或 `issue` 没有证据时拒绝;
- 标准字段必须真实存在;
- 检查码不能重复;
- 超时、取消或超过工具次数预算时拒绝。
只有通过这些校验,模型结果才进入运行步骤。这就是“模型负责语义判断,程序负责边界与证据完整性”。
### 10.6 运行结束怎样形成问题
Worker 将本轮 checks 与历史 findings 对账:
- 新问题 → 新建;
- 有新证据证明通过 → 已解决或待复核;
- 已解决问题再次失败 → 重新打开;
- 本轮没有执行到的旧检查 → 标记 stale,不能默认为通过;
- 运行期间项目数据版本已变化 → 本轮结果标记 superseded,不覆盖当前问题状态。
最终运行状态可能是 `completed`、`partial`、`failed` 或 `cancelled`。`partial` 明确表示仍有数据错误、步骤未完成或结果已过时,而不是把部分成功包装成全部成功。
## 11. 报告、评审池和人工决定怎样衔接
1. 客户经理用某次已完成/部分完成的预审运行生成报告草稿。
2. 报告保存所用快照、运行、模板和章节版本;数据变化会把旧草稿标记为 stale。
3. 客户经理提交报告后,项目进入评审池,并记录提交时间。
4. 评审员领取项目后,才能写评审决定。
5. 评审员可以对全链路节点或具体问题选择固定结论,并在需要时填写意见。
6. “退回”使项目回到客户经理补件;重新提交保留历史版本。
7. “批准”及评审报告不会由 AI 自动决定,最终动作属于人工角色。
报告导出为 DOCX,但它是结构化业务对象的呈现形式。可追溯链仍是:报告章节 → 运行 → 检查/问题 → 证据 ID → 数据卡字段或材料定位。
## 12. 业务快捷助手与原生聊天为什么回答方式不同
`BusinessAssistant.tsx` 有两个选项卡:
- **业务快捷助手**:调用 `/project-assistant/commands`;
- **mini-openclaw**:嵌入原生 `ChatPanel`。
业务快捷助手当前不是开放式 LLM。后端按关键词识别有限意图,例如:
- 发起预审/独立评审;
- 生成报告;
- 提交评审;
- 查看进度、问题、缺失材料和下一步;
- 显示帮助或要求用户澄清。
所有变更都调用与页面按钮相同的服务方法,所以权限、幂等和留痕一致。复杂自由分析要切到原生 mini-openclaw。这个设计避免业务人员一句模糊自然语言就触发不可预测的业务修改。
## 13. 持久化总表:数据究竟放在哪里
| 数据 | 存储 | 是否进入下一次模型上下文 | 主要用途 |
|---|---|---|---|
| 聊天会话 | `backend/sessions/*.json` | 是,同一 session | 连续对话 |
| 压缩前历史 | `backend/sessions/archive/*.json` | 否,摘要替代 | 审计与节省上下文 |
| 长期记忆 | `backend/memory/MEMORY.md` | 是,整份或检索片段 | 跨会话偏好/约定 |
| 长期记忆向量索引 | `backend/storage/memory_index` | 通过检索间接进入 | 加速相关片段查找 |
| 通用知识库 | `backend/knowledge` + 索引 | 仅工具调用后 | 文档问答 |
| 原始 LLM 追踪 | `backend/raw_logs/*.jsonl` | 默认不进入 | 请求/响应/耗时/用量追踪 |
| 完整消息轨迹 | `backend/raw_logs/*.msgs.jsonl` | 默认不进入 | 工具与消息审计 |
| 兴邦项目对象 | SQLite `assistant_objects` | 业务运行按快照读取 | 项目、运行、证据、报告等 |
| 业务事件 | SQLite `assistant_events` | 否 | 操作审计 |
| 任务队列 | SQLite `assistant_jobs` | 否 | 可恢复后台执行 |
| 幂等记录 | SQLite `assistant_idempotency` | 否 | 防重复提交 |
| 时间事件 | SQLite `business_time_events` | 否 | 阶段耗时与看板 |
| 演示业务源库 | 多表 SQLite | 先经数据卡映射 | 模拟真实源系统 |
## 14. 可追溯性:怎样回答“这句话从哪里来”
### 14.1 原生聊天追溯
可以查看:
- 会话消息;
- 模型每次请求与响应;
- 模型请求了哪个工具及参数;
- 工具返回了什么;
- token 用量、结束原因和耗时。
它能解释“模型怎样得到回答”,但自然语言回答本身不一定逐句绑定证据 ID。
### 14.2 业务预审追溯
业务链额外固定:
- 数据快照 ID 和内容哈希;
- 数据卡、SQL 配置版本及行哈希;
- Skill、规则、模板和经验版本;
- 每一步输入哈希、输出和状态;
- finding 的 `evidence_ids` 和 `basis_ids`;
- 人工确认、退回、批准的操作者与时间;
- 报告引用的运行和快照。
因此业务结论应从项目页面点击证据,而不是去聊天记录里寻找审批依据。
## 15. 权限、隔离与防重复
### 15.1 身份与项目范围
业务 API 根据客户经理、评审员、管理员角色和项目清单授权。Worker 在领取、执行和发布前都会再次检查权限,避免“入队时有权、完成时已无权”仍发布结果。
### 15.2 演示与生产域
组件、数据源和方案有 `demo` / `production` 范围,代码阻止混用。凭据不存入组件正文,只允许服务器环境变量引用。
### 15.3 冻结与版本
运行开始后使用冻结版本,不自动追随新的 Skill、规则或材料。新数据会增加 `data_revision`,旧运行不能覆盖当前状态。
### 15.4 幂等与并发
业务写操作支持幂等键;对象有 revision,旧页面提交会收到冲突并要求刷新。模型并发由信号量限制为 2 个槽位,方案还限制总超时和工具次数。
## 16. 当前实现边界与需要谨慎表述的地方
1. **原版 terminal 不是强沙箱。** 它有黑名单和工作目录,但不是容器级隔离。
2. **记忆 RAG 当前默认关闭。** `config.json` 中为 `false`,所以当前是整份 `MEMORY.md` 注入。
3. **长期记忆并非全自动沉淀。** 提示词可以要求模型写入,但代码没有证明每天自动总结归档。
4. **通用知识库当前是向量检索。** 不应声称现状是 BM25 + 向量混合。
5. **业务快捷助手不是自由大模型。** 它是有限、可审计的命令识别层。
6. **模型不等于审批人。** 业务模型输出核查建议;批准、退回与人工意见由评审员完成。
7. **业务模型不能直连数据库。** 它只读冻结后的标准字段和证据。
8. **原生聊天与业务快照不是同一套记忆。** 聊天中说过的事实不会自动成为项目事实。
9. **RAG 嵌入依赖需实际部署验证。** 代码使用 HuggingFace Embedding,但依赖清单没有明确列出对应 HuggingFace 嵌入包,部署环境需要验证。
10. **JSON 会话文件未显示跨进程文件锁。** 多实例部署原版会话层前需要补充一致性方案;兴邦业务状态已使用 SQLite 事务。
## 17. 用 DEMO-B 串起一条完整演示
以“恒远设备(DEMO-B)”为例:
1. 客户经理进入项目,系统读取项目事实和已选来源。
2. 数据卡按 `project_id=DEMO-B` 查询业务源库,将财务、租赁物等字段映射到标准字段。
3. 客户经理上传设备清单;解析器生成带页/行定位的候选值,不自动覆盖项目。
4. 客户经理确认候选的单位、期间和来源,项目 `data_revision` 增加。
5. 发起 AI 预审后,系统冻结项目事实、材料、Skills、规则和报告模板,写入任务队列。
6. Worker 先做必填、对比、去重和规则计算,再把冻结事实交给受限模型 Agent。
7. 模型通过只读工具读取快照、检查和方法,返回结构化 JSON;程序拒绝无证据或越权引用。
8. 系统形成问题卡,每个问题可查看数据来源、AI 判断、建议动作、规则/Skill 版本。
9. 客户经理补件并重新预审;新运行与旧运行并存,问题状态按证据变化对账。
10. 客户经理生成 AI 预审报告并提交,项目进入评审池,记录提交时间和等待时长。
11. 评审员领取后使用评审员方案独立复核,逐节点作人工判断。
12. 评审员退回时,项目在客户经理工作台重点显示;客户经理补件后重新提交,历史仍保留。
13. 评审员批准并生成评审报告;报告可以回到快照、检查和证据定位。
## 18. 面向小白的术语表
| 术语 | 在本项目中的通俗解释 |
|---|---|
| 大模型 / LLM | 根据上下文生成文字或工具调用请求的模型服务 |
| Prompt | 送给模型的规则、背景和任务说明 |
| Token | 模型读取和生成文字时使用的计量单位,不等于汉字数 |
| LangChain | 连接模型、消息与工具的应用开发层 |
| LangGraph | 让“模型—工具—模型”循环按状态运行的图执行层 |
| Agent | 能根据任务选择是否调用工具、再继续回答的运行体 |
| Tool | 由后端真正执行的函数;模型只能请求调用 |
| Skill | 方法说明或受控业务能力定义,不天然等于工具权限 |
| Rule | 程序按固定条件执行的判断,结果可重复 |
| RAG | 先检索相关片段,再把片段加入模型上下文 |
| Embedding | 把文字转换为可比较相似度的数值向量 |
| Snapshot | 某个时刻冻结的数据副本,后续修改不会改变它 |
| Evidence ID | 指向数据卡字段或材料页/行的证据编号 |
| SSE | 后端持续向浏览器推送小段事件的流式协议 |
| 幂等 | 同一请求重复发送,也只产生一次业务结果 |
| Lease | Worker 领取任务的限时占用权,超时可由别的 Worker 恢复 |
| Stale | 依赖数据已变化,原结果还可查看但不能当作当前结论 |
## 19. 阅读代码的推荐顺序
没有编码经验的同事如果要配合研发核对,可以按以下顺序阅读:
1. `backend/app.py`:看系统启动了哪些能力;
2. `backend/api/chat.py`:看一轮聊天怎样进出;
3. `backend/graph/agent.py`:看模型和工具循环;
4. `backend/graph/prompt_builder.py`:看模型获得哪些长期规则;
5. `backend/tools/`:看原生工具实际做什么;
6. `backend/graph/session_manager.py` 与 `memory_indexer.py`:看记忆;
7. `backend/services/project_assistant.py`:看业务对象怎样创建;
8. `backend/services/assistant_worker.py`:看业务任务怎样逐步执行;
9. `backend/services/assistant_model.py`:看业务模型工具与输出校验;
10. `backend/services/assistant_store.py`:看 SQLite 对象、事件和任务怎样保存。
---
## 20. 最终总结
这个项目的核心不是“让大模型拥有所有权限”,而是把能力拆成三层:
1. **模型负责理解和语义判断**;
2. **工具与确定性代码负责读取、计算和执行**;
3. **快照、证据、版本、权限和人工决定负责把结果变成可审计业务流程**。
原版 mini-openclaw 提供通用 Agent 基座;兴邦改造没有绕开这个基座,而是在它之上收窄工具、冻结输入、校验输出,并把结果写成可追溯的项目对象。理解这条边界,就能同时理解系统为什么“有 AI 的灵活性”,又为什么在对公评审场景中不能让 AI 自由修改事实或替人审批。