# AI Agent 协作指南 · zsjz-ai(清鉴)
> 本文件是**仓库级**约定,供 AI 编码助手(WorkBuddy / Claude Code / Cursor / Copilot 等)在本仓库内改代码时遵守。
>
> ⚠️ 别和下面两个文件搞混 —— 它们是**产品运行时的 AI 人格提示词**,不是给编码助手看的:
> - `ai-server/src/main/resources/prompts/AGENTS.md`
> - `QingJian/agent/AGENTS.md`
>
> 改那两个文件 = 改产品里 AI 研判专家的人设与话术;改本文件 = 改编码助手的行为约定。
---
## 1. 项目一句话定位
**清鉴(QingJian)** —— 面向纪检监察 / 经侦机关的智能资金数据研判平台。
业务闭环:
```
多源异构数据导入(话单 CDR / 银行账单 / 第三方支付 / 基站轨迹 / 外部库表)
→ 清洗治理(dm 模块)
→ 建模分析(通话·资金·轨迹·人物画像·图谱)
→ 可视化(ECharts / G6 关系图 / 离线栅格地图)
→ AI 对话式研判(AgentScope Agent + RAG + SQL 工具)
```
领域术语(写注释/命名时沿用它,不要自创):
`资金回流`、`闭环交易`、`快进快出`、`通联异常`、`时空伴随`、`白手套`、`攻守同盟`。
---
## 2. 仓库结构
```
zsjz-ai/
├─ pom.xml # 父 POM(packaging=pom),Spring Boot 3.5.16,仅聚合 ai-server
├─ ai-server/ # 后端:Spring Boot 3 + MyBatis-Plus + AgentScope
├─ ai-frontend/ # 前端:Vue3 + Vite7 + TS + Ant Design Vue(JeeSite 脚手架血统)
├─ sql/ # 建表 / 初始化脚本(*.sql)
├─ QingJian/ # 运行期工作空间素材(agent 提示词、skills、conf、db、license)
├─ cmd/ lib/ pmtiles.exe # 本机工具(regInfo.exe 授权、mbtiles→pmtiles 转换等)
└─ enc.md readme.md
```
`QingJian/` 不是源码目录,是**运行时数据目录**(含 `.db`、license 证书、导入模板),改动需谨慎。
---
## 3. 技术栈
### 后端(ai-server)
| 项 | 值 |
|---|---|
| JDK | **25** |
| 框架 | Spring Boot 3.5.16 |
| 持久层 | MyBatis-Plus 3.5.17 + dynamic-datasource 4.5.0 |
| 主库 | **PostgreSQL** + pgvector(master) |
| 分析库 | **DuckDB**(slave,按工作空间切换) |
| 缓存 | Redis |
| 图库 | Neo4j(配置存在,非全链路强依赖) |
| 鉴权 | Sa-Token 1.46 |
| Agent | **AgentScope 2.0.1**(harness + dashscope/ollama/openai + mem0 + rag-simple + redis) |
| 检索 | Elasticsearch 8.18(spring-boot-starter-data-elasticsearch,单索引+caseId 隔离) |
| 其他 | fesod-sheet(Excel)、Hutool、Guava、MapStruct 1.6.3、commons-compress、JNA |
| 启动类 | `com.zsjz.ai.App` |
| 端口 | `8980` |
> `ai-server/src/main/resources/app.yml` 是 **Solon 时代遗留配置,已不生效**,只作历史参考,不要照它改。
> 生效配置是 `application.yaml` + `application-dev.yaml`。
### 前端(ai-frontend)
- Vue 3.5 + TypeScript 5.9 + Vite 7 + **pnpm 10**(`preinstall: only-allow pnpm`,禁止 npm/yarn)
- Ant Design Vue 4.2.6、Pinia 2.3、vue-i18n 11、UnoCSS 66
- 图可视化:**G6 5**、X6、`@relation-graph/vue`;图表 **ECharts 6**
- 地图:**maplibre-gl + pmtiles**(离线栅格底图)
- Markdown:vditor、streamdown-vue、markdown-it
- Node 要求:`>=20.19 || >=22.12`
**接口前缀 `/js`**,开发环境由 `.env.development` 代理到 `http://127.0.0.1:8980`:
```
VITE_PROXY = [["/js","http://127.0.0.1:8980/js",false]]
VITE_GLOB_API_URL_PREFIX = /js
VITE_GLOB_ADMIN_PATH = /a
```
请求最终落到 **`/js/a/...`**(`urlPrefix(/js)` + `adminPath(/a)`),
与后端 `server.servlet.context-path: /js/a` 严格对应 —— 两者必须同步改。
调接口时用 `url: adminPath + '/xxx'`(`defHttp` 会自动补 `/js`);
不走 `defHttp` 的场景(`fetch` / `
`)用
`glob.apiUrl + glob.urlPrefix + glob.adminPath + path`。
---
## 4. 后端分层与落位约定
包根:`com.zsjz.ai`
```
com.zsjz.ai
├─ App.java 启动类
├─ common/ 跨模块复用
│ ├─ config/ Result、WebConfig、MybatisPlusConfig、LicenseFilter、
│ │ DuckdbUnpooledDataSource、BigDecimal 序列化器
│ ├─ base/ Query、TreeNode、BasicColumn、Cleaned、ErrorEnum
│ ├─ enums/ constants/ utils/
│ └─ model//{dto,entity,query,vo}/ ★ DTO / 实体统一放这里
└─ module//
├─ controller/ 仅收参 + 调 service
├─ service/ service/impl/
└─ mapper/ (XML 在 resources/mappers//*.xml)
```
`` 固定取值:`agent` `call` `dm` `external` `govern` `graph` `otg` `person` `plat` `track` `trans`
配套资源:
```
ai-server/src/main/resources/mappers//Mapper.xml
```
### 命名规范
- 查询入参 → `common.model..query.XxxQuery`
- 出参 DTO → `common.model..dto.XxxDTO`
- 数据库实体 → `common.model..entity.Xxx`
- Service 接口 `XxxService`,实现 `XxxServiceImpl`
- 类名后缀:Controller / Service / Mapper / DTO / VO / Entity / Query / Enum / Const
### 编码规则
1. **Controller 不写业务逻辑**,只做参数校验 + 转调 service。
2. 分页统一用 MyBatis-Plus `Page`(`PaginationInnerInterceptor(DbType.DUCKDB)` 已注册)。
3. **统一返回 `Result`**:`Result.succeed(data)` / `Result.failure(msg)`。
> 存量 Controller(如 `CallNightController`)有直接返回裸对象的历史写法;**新增代码一律用 `Result`**,存量代码按需渐进改造。
4. 金额/比率用 `BigDecimal`(已配 `BigDecimalSerializer` / `BigDecimalDeserializer`),禁用 double。
5. 鉴权取当前用户:`StpUtil.getLoginIdAsLong()`。**会话、消息、模型配置等用户级资源必须带 userId 做归属校验**,不能只按 id 查。
6. 跨库访问用 `@DS("slave")`(参考 `SqlAnalysisTool` 依赖的 `SqlQueryMapper`);不标注则走 master。
7. 注释用中文,Javadoc 写清「做什么 + 为什么」,不写废话。
8. 新增枚举尽量实现 `HasInnerEnum`,与存量保持一致。
---
## 5. 数据层要点
| 数据源 | 用途 |
|---|---|
| **master**(PostgreSQL + pgvector) | 平台元数据、`table_info`/`table_field`、Agent 会话/消息、模型与 Provider 配置、RAG 索引状态、向量检索 |
| **slave**(DuckDB) | **按工作空间切换**的案情库;Agent 的 `execute_sql` 只在这里跑 |
| Redis | 会话/缓存、Sa-Token、mem0 |
| Neo4j | 图谱(配置在 `application-dev.yaml`) |
- 表/字段元数据三表链路:`meta_raw_sheet` → `table_info` → `table_field`
- 建表脚本放 `sql/`,新增表结构务必同步补脚本 **和** 元数据三表
- `mapUnderscoreToCamelCase` 已开启
### ⚠️ 环境配置
`application-dev.yaml` 内含**本机数据库密码 / 内网 IP**,属于开发便利配置。
- 不要把真实凭证提交到公开仓库
- 不要在生产 profile 复用 dev 配置
---
## 6. Agent 子系统(`module/agent`)★ 核心
### 架构
AgentScope `HarnessAgent` + `Toolkit` + 中间件:
```
controller/ AgentChatController(REST + SSE,裸返回)
AgentModelController(模型/厂商 CRUD,Result 包装)
AgentResultController(execute_sql 结果集分页,裸返回)
AgentPythonFileController(Python 产出图片文件服务)
service/ AgentChatService、AgentModelService、AgentModelProviderService
tools/ SqlAnalysisTool、RagSchemaSearchTool、PythonAnalysisTool、
GraphRenderTool、WorkspaceInfoTool
intent/ 意图识别(IntentService / IntentMiddleware / IntentEnum)
followup/ 追问生成(FollowupService / FollowupMiddleware)
rag/ RagSchemaService、EmbeddingModelFactory、RagProperties
python/ PythonExecutor(沙箱执行)
sql/ SqlResultStore(结果集缓存,供前端分页;按登录用户归属)
prompt/ PromptLoader、PromptHelper
scaffold/ WorkspaceScaffolder(工作空间初始化)
mapper/ Agent*Mapper、SqlQueryMapper、RagVectorMapper
entity/ AgentChatSession、AgentMessage、AgentModel、AgentModelProvider、RagIndexStatus
```
### 工具清单(`@Tool` 注解)
| 工具 | 说明 |
|---|---|
| `execute_sql` | 在当前工作空间 DuckDB 上执行**只读** SQL,返回 resultId + 列定义 + 当前页 + 总行数 |
| `list_tables` | 列出工作空间全部表(中英文名、字段类型与中文注释) |
| `search_table_schema` | 基于 pgvector 的表结构语义检索 |
| `render_chart` | 校验并返回 ECharts 图表配置(**禁止**用于关系图) |
| `render_graph` | 校验并返回力导向关系图谱 JSON —— 关系图**只能**走这里 |
| `execute_python` | 在沙箱中执行 Python 脚本分析,产出图片经 `/py/files/**` 访问 |
| `get_current_workspace` / `get_user_selected_workspace` | 读取当前/用户所选工作空间信息 |
| `get_call_records` | 通话原始明细(`CallRecordService#getCallRecord`) |
| `stat_call_night_summary` / `stat_call_night_detail` | 夜间通话一/二层(`CallNightService`) |
| `stat_call_continuous_summary` / `stat_call_continuous_detail` | 持续联系一/二层(`CallContinuousService`) |
| `stat_call_sensitive_summary` / `stat_call_sensitive_detail` | 敏感日期联系一/二层(`CallSensitiveService`) |
#### 业务域工具分组(`AgentToolRegistry`,当前实际注册 67 个)
只读 service 由 `AgentToolRegistry` 统一注册为 **6 个工具组**,默认只装备 `person` 组,
其余组由模型调用 `reset_equipped_tools` 按需切换 —— 避免每轮把全部 JSON Schema 塞进上下文。
| 组名 | 数量 | 覆盖能力 | 工具类 / 入参类 |
|---|---|---|---|
| `person`(**默认装备**) | 24 | 人员枚举与分组、通话/交易画像、常联系/常交易 TOP、汇总、运营商归属、银行卡、交易趋势、9 类亲密度行为 | `PersonAnalysisTool` / `PersonToolSpecs` |
| `call` | 7 | 通话明细、夜间/连续/敏感通话的汇总与明细 | `CallAnalysisTool` / `CallToolSpecs` |
| `trans` | 16 | 交易明细、大额、共同/相互交易、代持卡、现金流、连续交易、快速资金流转、理财、定期存款、频次、敏感交易 | `TransAnalysisTool` / `TransToolSpecs` |
| `track` | 16 | 基站轨迹、境外本地昼夜活动、快递、碰面、同住、同行出行 | `TrackAnalysisTool` / `TrackToolSpecs` |
| `graph` | 1 | 关系图谱取数(`get_case_graph`) | `GraphAnalysisTool` / `GraphToolSpecs` |
| `file` | 3 | 非表格材料档案:清单检索、分类统计、读正文样本 | `FileAnalysisTool` / `FileToolSpecs` |
> ⚠️ 上表是**当前实际值**,不是设计稿:`otg`(全文检索,`OtgAnalysisTool`)**未注册**
> (`OTG_TOOLS` 为空、`createGroup` 整段注释),`trans` 的 `TRANS_TOOLS` 清单也还没补全,
> `graph` 的明细类工具同样没实现。改动工具清单时同步更新
> `AgentToolRegistryTest#TOTAL_BUSINESS_TOOLS` 与 `AgentScopeMcpToolProviderTest` 的数量下限。
> 分组机制要点(`AgentToolRegistry` 注释里有完整说明):
> - 组必须是 `ToolGroupScope.META`,否则 `reset_equipped_tools` 会拒绝("not manageable by this tool")
> 且组名不会出现在该工具 `to_activate` 的 enum 里;
> - 未激活组里的工具**不进** `getToolSchemas(activeGroups)`,模型完全看不到;
> - 新会话的初始激活组 = `ReActAgent` 在 build 时捕获的 `toolkit.getActiveGroups()`,
> 所以「默认装备哪一组」只由 `AgentToolRegistry#DEFAULT_ACTIVE_GROUPS` 决定;
> - `reset_equipped_tools` 是**全量替换**语义(不在 `to_activate` 里的组一律卸载),
> `AgentService#appendRenderPrompt` 里已提醒模型「把还要用的组一并列出,用完切回」。
> - 已知限制:`ReActAgent` 在会话恢复/结束时用共享 `Toolkit` 的全局 activeGroups 做 `set/get`,
> 同一 agent 实例的并发会话理论上可能互相覆盖全局状态(每轮实际按会话状态过滤,影响有限)。
> 通话分析 7 件套由 `CallAnalysisTool` 包装 `module/call` 的既有 service,**只做适配不改业务**,
> 保证「页面上的结论」与「AI 的结论」同口径;返回形态与 `execute_sql` 一致
> (`resultId` + `columns`/`rows`/`totalRows`,前端 `DataTableBlock` 直接渲染可翻页表格),
> 单次取数上限 **10,000 行**(比 `execute_sql` 收紧,因 `SqlResultStore` 是 LRU 50 的共享缓存),
> 超限返回可读报错而不是静默截断。入参是专用 POJO(`CallToolSpecs`),
> **不暴露** `orderKey`/`sort`/`tableName`(Mapper XML 里是 `${}` 拼接,属注入面)。
> 其余 5 个域同构:入参 `*ToolSpecs`、出参 `ToolResultTable`、上限 10,000 行。
>
> **唯一例外**:`get_case_graph` 返回 `{nodeList, edgeList}` 结构化 JSON(不表格化,因为
> `render_graph` 要的就是这个结构),且边数超过 500 时按权重降序截断并附
> `truncated` / `totalEdges` / `note`,防止 1 万条边的 JSON(约 30 万 token)冲爆上下文。
### 硬约束(不要放宽)
- 结果集上限 **100,000 行**,超出要求模型加 `LIMIT` / `WHERE`
- `page_size` 默认 50,上限 500
- 单元格字符串截断 **500** 字符
- 只读白名单首词:`select / with / pragma / describe / show / explain`,**禁止多语句(含分号)**
- `render_chart` 会硬拦截 `series.type == "graph"`,强制改走 `render_graph`
### SSE 流式协议
`POST /chat/stream`,事件按序:`tool_call` → `tool_input` → `tool_result` → `token`(多次) → `done` / `error`
payload 结构:
```ts
{ type, data?, toolCallId?, toolName?, toolInput?, toolResult?, error?, sessionKey? }
```
前端消费方:`ai-frontend/src/ai/api/`(`chatApi.ts` / `types.ts`)。**改动协议必须前后端同步**。
### 提示词
- 运行时系统提示词:`ai-server/src/main/resources/prompts/AGENTS.md`
- 未经明确要求,**不要重写人格设定与输出格式**(【异常概览】/【资金链路分析】/【通联行为画像】/【时空碰撞结果】/【研判结论与建议】),那是产品行为的一部分。
### 前端渲染管线(`ai-frontend/src/ai/`)
后端 `AgentService#appendRenderPrompt` 强制的输出契约 → 前端块的映射:
| AI 输出形式 | 前端块 | 组件 |
|---|---|---|
| **裸 JSON 混在 markdown 正文**(`execute_sql` 结果,**禁止围栏**) | `table` | `blocks/DataTableBlock.vue`(按 resultId 调 `/chat/results/{id}` 翻页) |
| ` ```echarts ` 围栏(ECharts option JSON) | `echarts` | `blocks/EChartsBlock.vue` |
| ` ```graph ` 围栏(`{title,nodes,edges,categories}`) | `graph` | `blocks/GraphBlock.vue`(`@relation-graph/vue`) |
| ` ```html ` 围栏 / 裸 ``·`