AI_AGENT.md 28 KB

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 / <img src>)用 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/<domain>/{dto,entity,query,vo}/     ★ DTO / 实体统一放这里
└─ module/<domain>/
   ├─ controller/                仅收参 + 调 service
   ├─ service/  service/impl/
   └─ mapper/   (XML 在 resources/mappers/<domain>/*.xml)

<domain> 固定取值:agent call dm external govern graph otg person plat track trans

配套资源:

ai-server/src/main/resources/mappers/<domain>/<Xxx>Mapper.xml

命名规范

  • 查询入参 → common.model.<domain>.query.XxxQuery
  • 出参 DTO → common.model.<domain>.dto.XxxDTO
  • 数据库实体 → common.model.<domain>.entity.Xxx
  • Service 接口 XxxService,实现 XxxServiceImpl
  • 类名后缀:Controller / Service / Mapper / DTO / VO / Entity / Query / Enum / Const

编码规则

  1. Controller 不写业务逻辑,只做参数校验 + 转调 service。
  2. 分页统一用 MyBatis-Plus Page<T>(PaginationInnerInterceptor(DbType.DUCKDB) 已注册)。
  3. 统一返回 Result<T>: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 结构:

{ 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 围栏 / 裸 <html>·<svg> html blocks/HtmlBlock.vue(sandbox iframe)
其余代码围栏 / markdown 正文 code / markdown blocks/CodeBlock.vue / blocks/MarkdownBlock.vue

关键实现约束:因为表格是裸 JSON 而非围栏,utils/scanner.ts 必须做字符级花括号配平扫描 (字符串/转义感知)并按载荷形态校验,不能只按围栏切分。流式期间的增量策略是 「已完成块冻结(尾部 8KB 窗口外)+ 只重扫尾部 + 尾部块按 key 复用对象」, 块 key 用 kind:start(内容只追加 → key 稳定,避免图表实例重建、表格分页被重置)。

改渲染契约(appendRenderPrompt)时必须同步改前端 scanner.ts 与对应块组件。


7. 数据清洗 / 治理(module/dm)

dm/
├─ pre/     PreTask、PreDataListener、DateNumberConverter、Str2NullConverter
├─ clean/
│  ├─ DataCleaner(接口)、CleanResult、CleanCache、DirtyDataHandler、StrategyType
│  ├─ impl/cleaner/   各数据源清洗器(Call*/Trans*/Track*/External*/...)
│  ├─ impl/loader/    数据装载器
│  └─ func/           可复用清洗函数:FunAbs/FunRadix/FunReplace/FunTime/FunMultiplier/...
├─ ai/      非表格文件的 Tika 抽样本 + 大模型识别
│           (FileSampleExtractor / FileRecognitionService / FileAiProfileService)
└─ mapper/  CleanErrorLogMapper、DmMapper、PersonRelEdgeMapper、EdgeConfMapper、
           FileInfoMapper、FileAiProfileMapper
  • 新增一种数据源导入 = 新增一对 Loader + Cleaner(命名对齐 XxxDataLoader / XxxDataCleaner)
  • 清洗错误必须落 CleanErrorLog,不要静默吞掉
  • 可复用变换优先写成 func/Fun* 而不是在 Cleaner 里硬编码
  • 非 xls / xlsx / csv 的文件不落盘:只登记 file_info(FILE_UNSUPPORTED_FAIL)+ file_ai_profile(Tika 文本样本 + 大模型识别的分类/摘要/关键词,供后续数据分析工具按 category 分流)。 识别链路 3 路并发、其余排队(zsjz.file-ai.* 可调),全程 fail-open —— 抽样本失败/模型报错只落 SKIP/FAIL,绝不能让上传接口失败。抽样本必须在请求线程内做 (multipart 临时文件在请求结束被清理)。详见 ai-server/src/main/java/com/zsjz/ai/module/dm/ai/
  • file_ai_profile 带 caseId(绑定到案件上):建档时从 CaseContextHolder 读一次, 写进档案,同时把同一个值传给 FileRecognitionService#submit —— 分两次读万一中间上下文变了, 会落出「档案说 A 案、识别写回 B 案」的脏数据。查询不按 caseId 过滤: 案件范围由 CaseRoutingDataSource 按上下文自动限定,再叠一层只会制造「两边不一致就查不到」的假故障。
  • 改 file_ai_profile 的列要动 4 处:实体 FileAiProfile、CaseDataSourceRegistry#AI_PROFILE_DDL(新库)、 CaseDataSourceRegistry#AI_PROFILE_MIGRATIONS(老库补列)、sql/case_table_1.sql。 ★ CREATE TABLE IF NOT EXISTS 对已存在的表是空操作 —— 老案件库里表已建好, 光改建表语句永远加不上新列,表现是「新环境好好的,老案件一识别就报 Column not found」。 所以新列必须同时写进建表语句和补列清单。FileAiProfileSchemaTest 会按生产顺序跑 DDL + 补列并比对实体注解,漏改一处就红;另有 legacyTableGetsCaseIdColumn 专门模拟 「已有旧版表的老库」验证补列路径。
  • AI 识别只认文本类文件(后缀白名单):唯一事实来源是 FileTypeConstants.AI_RECOGNIZABLE_SUFFIXES,判定走 FileTypeConstants.isAiRecognizable(fileType)。 白名单 ≈ pdf / doc / docx / ppt / pptx / rtf / odt / ods / odp / wps / wpt / dps / dpt / eml / txt / text / md / markdown / log / xml / json / html / htm / sql / tsv / yaml / yml / properties / ini / conf / cfg。 压缩包、图片、音视频、可执行文件、数据库文件、无后缀文件(fileType = "unknown")一律不识别。 ★ 三条硬约定:用白名单不用黑名单(后缀无穷无尽,黑名单漏一个 = 默认识别,白名单漏一个只是 「该识别的没识别」,代价可控);csv / xls / xlsx 绝不加进白名单(它们走表格解析流水线, 根本到不了这条路);守卫放在 FileAiProfileService#register 入口,不是调用方 DmService —— 绕过是静默的(只是多花钱、识别不出东西),不报错就发现不了,只有放在唯一入口才拦得住后续新增的调用点。 加后缀时同步改 FileTypeConstantsTest(whitelistEntriesAreNormalized 会拦住「大写」和「带点号」的条目)
  • 同一批档案还给 AI 用:file 工具组(FileAnalysisTool)提供 list_file_profiles(清单检索)/ stat_file_profiles_by_category(分类统计)/ get_file_sample(读某份材料的正文样本,≤4000 字)。 ★ 三条硬约定:正文绝不进列表(单条样本 4000 字,50 行就是 20 万字,会冲爆上下文, 正文只能按需单份取);命中超 5000 条报错而非截断(截断会让模型以为材料就这么多); 分类/状态认不出必须报错(FileCategoryEnum.parse 认不出会落 OTHER, 直接用它会让「过滤笔录」静默变成「过滤其他」)。

8. 前端约定

目录按业务域划分,与后端 <domain> 基本对齐:

ai-frontend/src/
├─ core/     脚手架通用层:components/、layouts/、api/、hooks/、store/、router
├─ ai/       AI 数据分析应用(api/、utils/、store/、styles/、components/、views/)
├─ case/     案件:数据导入/清洗/治理/人物画像/线索推送
├─ call/     话单分析(night、continuous、sensitive...)
├─ trans/    资金分析
├─ track/    轨迹分析
├─ person/   人员
├─ plat/     平台/模板
├─ dm/       清洗类型定义
└─ graph/    图谱(G6 编辑器、力导向布局 worker、资金流向图)

每个业务域内:api/(请求 + types/dto.ts、types/query.ts)、views/、components/、hooks/、types/。

规则

  1. 优先复用 src/core/components/*(BasicTable / BasicForm / BasicModal / Description / AnalysisPagination 等),不要新造轮子。
  2. 组件用 <script setup lang="ts">,defineProps / defineEmits 显式类型化。
  3. 新增接口先写 src/<domain>/api/types/{dto,query}.ts 的 TS 类型,与后端 DTO 字段保持一致(改后端字段时同步改这里)。
  4. 样式:UnoCSS 原子类 + Less(组件内 <style lang="less" scoped>)。
  5. 只能用 pnpm。

⚠️ 新增页面 / 菜单的四个坑

  1. 菜单图标必须进 UnoCSS 白名单:uno.config.ts 的 content.pipeline.include 只扫 **/*.{vue,tsx,ts},menu.json 不在扫描范围内;而 Icon 组件是运行时拼 i-<collection>:<name> 类名的。所以在 menu.json 里配的图标不会生成 CSS。 新增图标时同步把 i- 前缀的完整类名加入 uno.config.ts 的 safelist (menu.json 里可直接写 "icon": "i-mdi:history",Icon 见到 i- 前缀会原样使用)。
  2. 路由建议走显式映射:src/core/router/helper/routeHelper.ts 的 explicitDynamicViewMap 加一条 '/name/index': () => import('@/<domain>/views/<name>/index.vue')。 默认的 import.meta.glob('**/views/**') 会命中 node_modules 里的同名 views。
  3. 全高页面:布局内容容器 .jeesite-layout-content 有确定像素高度,页面根用 height: 100% 即可撑满;要全出血再配 width: calc(100% + 24px); margin: 0 -12px; (容器 padding: 12px 12px 0)。
  4. 两套响应形态:module/agent 的 /chat/**(含 /chat/results/**)是裸返回, defHttp 默认 transform 会把非 code===200 的结构当错误抛,所以必须传 { isTransformResponse: false };/models/** 是 Result<T> 包装,用默认 transform。 鉴权靠 Sa-Token,token 走 x-token 请求头(后端 sa-token.is-read-cookie=false, 仅 Header 模式)。defHttp 的请求拦截器会自动注入;不走 defHttp 的场景 (fetch / EventSource)必须手动带上 'x-token': getToken()。 ⚠️ 原生 EventSource 无法自定义请求头,SSE 一律用 fetch + body.getReader() 读增量。 未登录时后端返回 HTTP 200 + body.code=401(不是 HTTP 401),前端据此跳登录页。

9. 常用命令

后端

# 编译
mvn -pl ai-server -am clean compile
# 打包(跳过测试)
mvn -pl ai-server -am clean package -DskipTests
# 运行
mvn -pl ai-server spring-boot:run
# 只跑 ai-server 的测试
mvn -pl ai-server test

启动后:http://localhost:8980

前端

cd ai-frontend
pnpm install          # = pnpm bootstrap
pnpm dev              # 开发服务器
pnpm type:check       # vue-tsc --noEmit
pnpm lint:eslint
pnpm lint:prettier
pnpm lint:stylelint
pnpm lint:all         # 全量(含 install)
pnpm build            # 生产构建
pnpm preview:dist     # 预览 :3100

依赖服务

PostgreSQL(需装 pgvector 扩展)、Redis、Neo4j(按需)。


10. 硬性规则:Do / Don't

✅ Do

  • 新业务按 module/<domain>/{controller,service,mapper} + common/model/<domain>/{dto,entity,query,vo} 落位
  • 改 SQL 同步改 resources/mappers/**/*.xml
  • 改后端 DTO 字段 → 同步前端 src/<domain>/api/types/*.ts
  • 改表结构 → 补 sql/ 脚本 + table_info / table_field 元数据
  • 新增 Agent 工具 → 写清 @Tool(description=...) 与 @ToolParam,返回结构化 JSON + 中文说明
  • 大查询一律带 LIMIT / WHERE,分页返回
  • 提交前跑通后端 compile 与前端 type:check

❌ Don't

  • 不要动 ai-server/src/main/resources/app.yml(Solon 遗留,已失效)
  • 不要把密钥 / 真实库密码 / 内网地址硬编码进提交
  • 不要在 Java 里拼接 SQL 字符串(用 MyBatis XML 或 LambdaQueryWrapper)
  • 不要放宽 execute_sql 的只读白名单、行数上限、单元格截断
  • 不要让 render_chart 渲染关系图
  • 不要绕过 Sa-Token 的用户归属校验
  • 不要在仓库里存放原始敏感案情数据
  • 未经要求不要改写 prompts/AGENTS.md / QingJian/agent/AGENTS.md 的人格与输出格式
  • 不要用 npm / yarn 装前端依赖

11. 已知待清理项(改到就顺手修)

  1. common/config/Result.java 顶部有 import io.milvus.param.R; —— 未被使用且 pom 中无 milvus 依赖,属遗留脏引用,建议直接删除。
  2. Result 的 SUCCEED_CODE / FAILURE_CODE 是非 final 的 public static 字段,可被外部篡改,建议改 final 或换成常量/枚举。
  3. common/config/WebConfig.java 与 application.yaml 的 context-path: "!/js/a/" 写法源自 Solon 语法,在 Spring Boot 下语义可疑 —— 已实证并修复(2026-09-15): AbstractServletWebServerFactory.checkContextPath() 会拒绝不合法的 context path。 实测 new TomcatServletWebServerFactory().setContextPath(v)(Spring Boot 3.5.16): !/js/a/ → 抛 IllegalArgumentException;/js/a/ → 同样被拒(尾部斜杠非法);/js/a → 通过。 即原配置会让 ai-server 完全无法启动。已改为 context-path: /js/a, 与前端 ctxPath(/js) + adminPath(/a) 及 defHttp 约定一致。
  4. ai-server 未声明 @MapperScan,确认 mapper 是靠 @Mapper 注解还是 XML 绑定注册;新增 Mapper 时跟随现有写法。
  5. pom.xml 中 testcontainers 等依赖已声明但未见到对应测试,可评估是否裁剪。
  6. src/main/resources/mappers/**/*.xml 全是 com.qingjian.* 命名空间 —— 已修(2026-09-15): Java 侧 0 处引用 com.qingjian,但有 85 个 com.zsjz.* Mapper 接口与 85 个 XML 一一对应, 属包名迁移时漏改 XML,导致 MyBatis 启动即炸 (Failed to parse mapping resource: mappers/ai/SkillMapper.xml → 无法解析 resultType), ai-server 在本仓库根本无法启动。 处理:删除 mappers/ai/(8 个文件,QingJian 旧 AI 模块,现 AI 能力在 module/agent 用 BaseMapper); 其余 77 个文件、160 处 com.qingjian → com.zsjz.ai。 新增 / 迁移 Mapper XML 时务必确认 namespace 与 resultType 用的是 com.zsjz.ai.*。
  7. application.yaml 的 mybatis-plus.type-aliases-package: com.qingjian.ai.server.model.*.entity.* 仍指向不存在的包(当前无实际影响:XML 全用全限定名)。 若要启用别名,先确认 com.zsjz.ai.common.model.*.entity 下无重名简单类名,否则 MyBatis 会因别名冲突启动失败。
  8. AppLoadEndEventListener(CommandLineRunner)依赖平台库结构(table_info / table_field) 与客户端运行时资产(RocksDB 目录、基站库文件)。当前开发库 zsjz-ai 缺平台库结构 (平台表在 etl-2,两个库不重合),故裸跑仓库目录会在启动后置任务阶段失败: PSQLException: 关系 "table_info" 不存在。 完整本地运行需先补齐库结构与客户端资产(见 PathConst)。

12. 提交前检查清单

  • 后端 mvn -pl ai-server -am clean compile 通过
  • 前端 pnpm type:check + pnpm lint:eslint 无错
  • 接口字段变更 → 后端 DTO/VO 与前端 TS 类型同步
  • 表结构变更 → sql/ 补脚本 + 元数据三表同步
  • 新增/修改 Agent 工具 → description 更新,必要时同步提示词
  • 用户级资源接口 → 带 StpUtil.getLoginIdAsLong() 归属校验
  • 无密钥 / 密码 / 敏感数据入库
  • 中文注释齐全,无调试 System.out.println 残留