# 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) |
| 检索 | Lucene 10(core/queryparser/analysis-common/highlighter) |
| 其他 | 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` | 读取当前/用户所选工作空间信息 |
### 硬约束(不要放宽)
- 结果集上限 **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 ` 围栏 / 裸 ``·`