本地 RAG 知识库
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

18 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

项目概述

AI 智能客服系统,基于 Spring AI Alibaba + 通义千问 + PGVector,支持 RAG 知识库检索、多轮对话、结构化数据提取、知识库全生命周期管理。

构建与运行

# 编译
./mvnw compile

# 运行(端口 9090)
./mvnw spring-boot:run

# 运行测试
./mvnw test

# 运行单个测试类
./mvnw test -Dtest=SupportBotApplicationTests

# 运行单个测试方法
./mvnw test -Dtest=SupportBotApplicationTests#testRag

前提条件: PostgreSQL 12+ 需运行且安装 PGVector 扩展,数据库 support_bot 需存在。knowledge_categoryknowledge_documentai_model_config 等表由 DatabaseInitConfig 自动创建,无需手动建表。

测试说明: 所有测试均为集成测试(@SpringBootTest),需要运行中的 PostgreSQL 和有效的 DashScope API Key。测试类:SupportBotApplicationTests(对话/RAG)、PgVectorVectorStoreConfigTest(向量存储)、QueryTransformerTests(查询重写策略)。无单元测试。

访问地址: 前端管理页面 http://localhost:9090/index.html,API 文档 http://localhost:9090/doc.html(Knife4j)

核心架构决策

主启动类排除了 PgVectorStoreAutoConfiguration

SupportBotApplication.java@SpringBootApplication(exclude = PgVectorStoreAutoConfiguration.class),因为项目在 PgVectorStoreConfig 中手动配置 PgVectorStore Bean(标记 @Primary),不使用自动配置。另有一个 InMemoryVectorStoreConfig 作为开发备选。

Spring AI 集成模式

  • ChatClient Builder: 所有对话通过 ChatClient.builder(chatModelFactory.getChatModel("CHAT")) 构建,ChatModel 由 ChatModelFactory 按 DB 活跃配置动态创建
  • Advisor 链: MessageChatMemoryAdvisor(记忆) → MyLoggerAdvisor(日志) → QuestionAnswerAdvisor(RAG)
  • SSE 流式: 三种实现 — Flux<String>、Flux<ServerSentEvent>、SseEmitter

ChatMemory 持久化

当前使用 DatabaseChatMemory(PostgreSQL 持久化),FileBasedChatMemory(Kryo 序列化)已注释掉。

RAG 双模式

  1. QuestionAnswerAdvisor 模式(生产使用): 预检索优化 + QuestionAnswerAdvisor
  2. RetrievalAugmentationAdvisor 模式(实验性): doChatWithRagEnhance() 仅做基础 RAG 检索,无查询增强

文档处理管道

DocumentService.uploadDocument() 统一流程:文档提取 → MyTokenTextSplitter 分块 → MyKeywordEnricher AI 关键词提取 → pgVectorVectorStore.add() 向量化存储。每个分块的 metadata 中注入 documentIdchunkIndexsourceNametitle 以关联 knowledge_document 表。

预检索查询优化

四种策略在 rag/preretrieval/ 下,由 AssistantApp.doChatWithRagStrategy() 根据 strategy 参数动态选择:REWRITE / TRANSLATION / COMPRESSION / MULTI_QUERY。查询重写器均为 @Component,仅在请求时懒加载 ChatModel,不影响启动。

关键配置

  • application.yml 含 DashScope API Key,已被 .gitignore 排除
  • 模型名称、温度、最大 Token 等参数已全部迁移到前端「AI 大模型配置管理」页面,通过 ai_model_config 表管理,不再在 yml 中配置(yml 仅保留 api-key
  • MyBatis Plus 逻辑删除字段: isDelete,主键策略: assign_id(雪花算法)
  • 雪花 ID 精度问题: KnowledgeDocument.idcategoryIdKnowledgeCategory.idparentId 已添加 @JsonSerialize(using = ToStringSerializer.class),序列化为字符串避免前端 JS 精度丢失。新增 Long ID 字段时务必加上此注解
  • PostgreSQL JSONB 字段使用自定义 PostgresJsonTypeHandler(期望 JSON 对象 '{}',非数组 '[]'
  • 向量维度: 由 knowledge.vector.dimension 配置(默认 1024)。修改后需执行 DROP TABLE IF EXISTS vector_store CASCADE 重建向量表,并重新上传知识库文档。距离类型: COSINE_DISTANCE,索引: HNSW
  • 分块配置: knowledge.chunk.* 配置项(ChunkConfig),默认 chunkSize=200, overlap=100, minChunkSizeChars=10, maxNumChunks=5000, keepSeparator=true
  • 上传校验: ALLOWED_EXTENSIONS 白名单 + 50MB 大小限制(spring.servlet.multipart 配置),前后端双重校验
  • 文档去重: KnowledgeDocument.contentHash 字段(SHA-256),上传时自动计算并查重
  • 数据库自动初始化: DatabaseInitConfig 在启动时检查并创建 knowledge_category/knowledge_document/ai_model_config 等表,对已存在的 knowledge_document 表会自动补加 content_hash 列。注意 knowledge-base.sql 脚本为早期版本,缺少此列,实际以 DatabaseInitConfig 为准

模型配置管理

  • ai_model_config 表: 存储大模型配置,支持多套配置按 App 类型(CHAT / EMBEDDING / RAG_REWRITE)独立管理。所有模型参数(名称、温度、最大Token、API Key、Base URL 等)全部由此表管理,不再依赖 application.yml
  • 激活互斥: 同一 App 类型只能有一个 is_active=true 的配置,激活操作由 Service 层 @Transactional 保证
  • 启动 seed: 首次启动时使用硬编码默认值写入 DB(qwen-turbo / text-embedding-v2),用户可在前端修改。DashScope 自动配置已禁用spring.ai.dashscope.enabled=false),api-key 可选——yml 与 DB 均未配置 api-key 不影响启动,实际调用 AI 功能前在前端「AI 大模型配置管理」页面配置即可
  • 启动校验: ModelConfigLoader 在应用就绪后检查 DB 中每种 App 类型是否有活跃配置,并对 DashScope 提供商比较 DB 与 yml 的 API Key 一致性
  • API Key 脱敏: 前端展示时只显示前 4 位 + **** + 后 4 位
  • ChatModel 运行时切换: 通过 ChatModelFactory 按 DB 活跃配置动态创建/缓存 ChatModel(包括 DashScope,不再复用 yml 自动配置的 Bean),配置变更时立即生效(无需重启)。OpenAI 兼容路径使用自定义 completionsPath(与 EmbeddingModelFactory 的 embeddingsPath 对应),baseUrl 已含版本段的厂商(moonshot /v1、volcengine /api/v3、zhipu /api/paas/v4)设为 /chat/completions,其余使用默认 /v1/chat/completions
  • EmbeddingModel 运行时切换: 通过 EmbeddingModelFactory + DynamicEmbeddingModel 代理,按 DB 活跃配置动态创建/缓存 EmbeddingModel,PgVectorStoreConfigInMemoryVectorStoreConfig 注入 DynamicEmbeddingModel,向量化模型配置变更后无需重启即可生效
  • 多提供商支持: DashScope(通义千问)+ OpenAI 兼容提供商(DeepSeek / 豆包 / Kimi / 智谱 / OpenAI),ChatModel 和 EmbeddingModel 均通过对应 API 手动构建
  • 缓存刷新: 配置增删改激活时 Controller 自动调用 ChatModelFactory.clearCache() + EmbeddingModelFactory.clearCache() + AssistantApp.clearCache()
  • 动态模型列表获取: ModelListFetcher 通过调用各提供商的 /v1/models 兼容端点(DashScope 用 /compatible-mode/v1/models),动态获取可用模型列表。前端填入 API Key + API 地址后,点击「获取模型」即可自动填充模型名称下拉列表(<datalist> 支持搜索选择 + 自定义输入)

依赖版本

  • Spring AI BOM: 1.0.1,统一管理所有 org.springframework.ai 依赖版本
  • spring-ai-alibaba-starter-dashscope: 1.0.0.4(新版 starter,替代老版 spring-ai-alibaba-starter M6.1)
  • spring-ai-openai: BOM 管理(OpenAI 兼容提供商支持)
  • spring-ai-alibaba-starter (M6.1) 已移除,不再使用

EmbeddingModel 架构

  • EmbeddingConfigFixerApplicationListener<ApplicationReadyEvent>,启动时检查 EMBEDDING 配置合理性、校验 EmbeddingModel 实际维度与配置维度是否一致,不一致时 WARN 告警并给出修复步骤。不再强制修正非 DashScope 配置,尊重用户在 DB 中配置的提供商和模型
  • EmbeddingModelFactory:按 DB 活跃配置动态创建/缓存 EmbeddingModel,支持多种提供商
    • DashScope(通义千问):DashScopeEmbeddingModel
    • OpenAI 兼容提供商(DeepSeek / Kimi / 智谱 / OpenAI):通过 OpenAiEmbeddingModel + 自定义 baseUrl + embeddingsPath 创建
    • 豆包文本模型(volcengine + 非 vision):通过 OpenAiEmbeddingModel,embeddingsPath=/embeddings
    • 豆包多模态模型(volcengine + *vision*):通过 VolcengineMultimodalEmbeddingModel,手写 RestClient 直调 /embeddings/multimodal,适配 {type, text} 格式
    • 各厂商 embeddingsPath 映射
      提供商 embeddingsPath 实现类
      dashscope DashScopeEmbeddingModel
      volcengine (vision) /embeddings/multimodal VolcengineMultimodalEmbeddingModel
      volcengine (text) /embeddings OpenAiEmbeddingModel
      moonshot /embeddings OpenAiEmbeddingModel
      zhipu /embeddings OpenAiEmbeddingModel
      deepseek /v1/embeddings OpenAiEmbeddingModel
      openai /v1/embeddings OpenAiEmbeddingModel
    • 注意:各提供商的 embedding 端点兼容性由用户自行验证,向量维度需与 PgVectorStore 的 dimensions 一致
  • DynamicEmbeddingModel:代理类实现 EmbeddingModel 接口,每次调用委托给 Factory,使 VectorStore 无需重建即可热切换
  • 前端ModelConfigManager.js 对 EMBEDDING 类型不再限制 provider,可自由选择任意提供商;EMBEDDING 类型弹窗增加「向量维度」输入框(写入 extraConfig.dimensions

前端架构

  • 技术栈: Vue 3 CDN + ES Module(importmap 引入,无构建工具)
  • 入口: src/main/resources/static/index.htmljs/app.js
  • 组件化: 每个功能模块一个 JS 文件(components/ 目录),导出 Vue 组件定义对象
  • 状态管理: js/store.js 使用 Vue 3 reactive,跨组件共享分类、统计、弹窗状态
  • API 封装: js/api.js 统一封装所有后端调用,API 基址为空字符串(同源部署)
  • SSE 流式: js/utils.jsreadSSEStream() 统一处理三种 SSE 接口
  • 添加新功能: 在 components/ 下新建 JS 组件文件,在 app.js 中导入注册即可

开发规范与踩坑记录

后端:数据库变更必须同步到启动初始化

规则: 任何涉及表结构(建表、增删改列、索引)、初始数据(种子数据、默认配置)的变更,必须同步记录到以下两个文件,否则新部署环境或重建数据库时会丢失变更:

文件 作用 需要同步的内容
DatabaseInitConfig.java 应用启动自动执行(主力) 建表 SQL(CREATE TABLE IF NOT EXISTS)、列迁移(ALTER TABLE ADD COLUMN IF NOT EXISTS)、种子数据(ON CONFLICT ... DO UPDATE
init-database.sql 手动备用脚本 DatabaseInitConfig 保持一致的完整建表 + 索引 + 种子数据

流程: 改完 Entity / Mapper 后,先在 DatabaseInitConfig.init() 中添加对应的初始化逻辑(幂等检查 + safeInit 包裹),再同步更新 init-database.sql

反面案例: knowledge_document.content_hash 列仅在 DatabaseInitConfig 中迁移,未同步到 knowledge-base.sql,导致该脚本变为过时版本。

后端:Long ID 序列化为字符串

规则: 所有雪花算法生成的 Long 类型 ID 字段,必须保证前端收到的是字符串而非数字,防止 JS 超过 Number.MAX_SAFE_INTEGER(2^53)精度丢失。

场景 做法
Entity 字段 @JsonSerialize(using = ToStringSerializer.class) + @TableId(type = IdType.ASSIGN_ID)
原生 JDBC SQL SELECT u.id::TEXT AS id(CAST 为 TEXT),不能直接 SELECT u.id
Controller Map 返回 Map.of("id", user.getId().toString())

反面案例: 用户管理 listUsers 使用原生 SQL 未 CAST,导致新建的用户(雪花 ID 约 19 位)在前端编辑时 ID 被截断,报"用户不存在"。

后端:敏感字段脱敏

规则: API 返回用户对象前,必须将 password 等敏感字段置为 null。不得将 BCrypt 哈希暴露给前端。

前端:弹窗实现统一模式

项目中存在两种弹窗模式,不可混用

模式 实现方式 适用 CSS 类
A: CSS 类切换 :class="{ active: xxx.visible }" .modal-overlay(CSS 定义 display:none + .active { display:flex }
B: 内联样式 + v-if v-if="xxx" style="...display:flex..." .modal-overlay,用内联样式

禁止: 将 .modal-overlayv-if 搭配使用 — v-if 控制 DOM 存在性,但不添加 .active 类,导致弹窗渲染后被 CSS display:none 隐藏,按钮点击无反应。

前端:错误处理显示服务器信息

规则: catch 块必须透传服务器错误信息,禁止吞掉错误只显示泛化提示:

// ✅ 正确
catch (e) { toast(e.message || '操作失败', 'error') }
// ❌ 错误 — 用户和开发者都无法排查
catch (e) { toast('操作失败', 'error') }

API 路由约定

  • AI 对话: /ai/*AiController
  • 模型配置: /model-config/*AiModelConfigController
  • 文档上传: /upload/*DocumentController
  • 文档管理: /document/*DocumentController
  • 批量操作: /document/batch/*DocumentController,用 POST 避免 DELETE+RequestBody 路径冲突)
  • 分类管理: /category/*DocumentController
  • 消息反馈: /feedback/*MessageFeedbackController
  • 敏感词管理: /sensitive-word/*SensitiveWordController
  • FAQ 管理: /faq/*FaqController
  • SDK 认证: /open-api/auth/*AuthController,Token 换取)
  • API Key 角色绑定: /api-key/{id}/rolesApiKeyController,admin 角色)

Filter 优先级

优先级 Filter 路径 说明
HIGHEST + 1 SdkAuthFilter /ai/** SDK JWT 鉴权
HIGHEST + 2 ApiKeyAuthFilter /open-api/** API Key 鉴权
SecurityFilterChain 内 JwtAuthFilter 管理接口 管理后台 JWT

SDK 鉴权架构

采用两段式鉴权:客户端后端用 API Key 换取短期 JWT Token → SDK 携带 Token 请求 /ai/** → SdkAuthFilter 校验放行。SDK JWT 密钥独立于管理后台 JWT(jwt.sdk-secret)。

  • API Key 支持绑定客服角色列表(role_ids JSONB 字段),Token 换取时优先返回绑定的角色
  • 未绑定角色的 API Key 返回所有启用角色(向后兼容)
  • JWT Token 中 sub 为 apiKeyId,rids 为允许的角色 ID 列表
  • 第三方系统接入指南详见 SDK-INTEGRATION.md

P0 阶段新增功能

内容安全过滤(P0-004)

  • DFA 引擎: ContentSafetyService 使用字典树匹配敏感词,volatile + copy-on-write 保证线程安全热加载
  • ContentSafetyAdvisor: 实现 BaseAdvisorgetOrder() 返回 HIGHEST_PRECEDENCE(Advisor 链最外层),before 阶段检查用户输入、after 阶段检查 AI 输出
  • 敏感词级别: level=1 仅脱敏(MASK),level=2 拦截(BLOCK)返回安全提示
  • 审计日志: content_audit_log 表记录所有违规事件,不删除
  • 前端: SensitiveWordManager.js 在系统设置 Tab,支持 CRUD + 批量导入 + 审计日志查看

用户反馈系统(P0-002)

  • 反馈实体: MessageFeedback,按 message_id 唯一索引,重复提交覆盖(upsert 语义)
  • 反馈类型: THUMBS_UP(有帮助)/ THUMBS_DOWN(没帮助),点踩可选原因分类 + 自由文本
  • ChatPanel: 每条 AI 回复下方有 👍/👎 按钮,点击调用 POST /feedback
  • Chat SDK: handleFeedback() 已连接后端 API,同时保留 localStorage 作为乐观 UI 缓存
  • 会话导出: ConversationService.exportConversation() 导出的 TXT 中包含反馈信息

意图识别 + FAQ 精准匹配(P0-003)

  • IntentRouter: LLM 单次调用做意图分类(FAQ/RAG/CHITCHAT),解析失败降级为 RAG
  • FaqMatchEngine: 三级匹配策略 — 精确匹配 → 关键词匹配 → 向量语义匹配(阈值 knowledge.faq.semantic-threshold,默认 0.85)
  • FAQ 向量化: 复用现有 DynamicEmbeddingModel,向量存入 faq_embedding 表,新增/修改 FAQ 时异步计算
  • similar_questions 字段: 使用 String 类型存储 JSON 数组字符串(PostgresJsonTypeHandler 期望对象格式,故不用 typeHandler)
  • 前端: FaqManager.js 在知识库文档管理 Tab,支持 CRUD + 批量 JSON 导入/导出 + 启用/禁用

混合检索 + 重排序引擎(P0-001)

  • SearchMode: 枚举 VECTOR(默认)/ KEYWORD / HYBRID,向后兼容
  • HybridSearchService: 多模式检索核心,KEYWORD 使用 PostgreSQL tsvector 全文检索,HYBRID 使用双路检索 + RRF 融合
  • RrfFusion: RRF 融合算法 score = Σ 1/(k + rank_i),k=60
  • RerankerService: 支持 DashScope + OpenAI 兼容提供商,通过 ai_model_config 表 RERANK 类型配置,超时 3s 自动 fallback
  • vector_store 全文检索: 新增 content_tsvector 列 + GIN 索引 + PostgreSQL 触发器自动维护
  • 前端: DocSearch.js 增加检索模式下拉选择(向量/关键词/混合),结果标注来源模式

已知 TODO

  • DocumentService.updateDocumentMetadata(): Spring AI 无直接更新 vector_store metadata 的 API,向量元数据同步留后续
  • DocumentService.searchDocuments(): Spring AI 1.0.1 的 filter 支持有限,分类过滤暂未实现
  • CompressionQueryRewriter: 当前传入空历史列表
  • MyBatis Plus 3.5.12 的 mybatis-plus-spring-boot3-starter 不含 PaginationInnerInterceptor,分页通过 SQL LIMIT/OFFSET 手动实现
  • PgVectorStoreConfig.dimensions(1024) 硬编码了向量维度,切换非 1024 维的 Embedding 模型时需修改并重建 vector_store 表 → 已修复:维度由 knowledge.vector.dimension 配置,启动时自动检测不匹配并告警