记录开发ai智能问答系统总结

我开发了一个ai智能问答系统,功能是可创建知识库,文档存入知识库,用户提问,ai检索知识库的相关内容回答。所用到的东西是百炼平台,postgreSql,腾讯云cos,系统有点庞大,想要源码和有问题的可以联系我,qq邮箱:1328113017@qq.com

一、数据库与存储层

1. PostgreSQL vector类型不存在

  • 现象 :执行 ALTER TABLE ... TYPE vector(1536)CAST(... AS vector)时报 type "vector" does not exist

  • 根因 :pgvector 扩展未安装,或安装后当前数据库会话的 search_path未包含扩展所在的 schema(通常是 public)。

  • 解决

    • 安装 pgvector 扩展(宝塔环境需手动编译安装,或确认软件商店已安装)。

    • 用超级用户执行 CREATE EXTENSION IF NOT EXISTS vector SCHEMA public;

    • 设置数据库默认搜索路径:ALTER DATABASE intellikb SET search_path = intellikb_schema, public;

    • 或在 JDBC URL 中通过 options=-c%20search_path=intellikb_schema,public显式传递。

2. MyBatis-Plus 插入返回 rows affected = null

  • 现象documentChunkMapper.insert(chunk)返回 null,且数据未入库。

  • 根因 :MyBatis-Plus 实体类 @TableName未指定 schema,导致插入到错误的 schema(如 public);或 XML 映射文件与注解方法冲突。

  • 解决

    • 实体类添加 @TableName(value = "document_chunk", schema = "intellikb_schema")

    • 删除冲突的 XML 文件,统一使用注解。

    • 改用 JdbcTemplate执行原生 SQL,彻底绕开 MyBatis-Plus 的映射问题。

3. CAST(? AS vector)参数类型无法推断

  • 现象 :执行 CAST(#{queryVec} AS vector)时报 could not determine data type of parameter

  • 根因#{kbId} IS NULL?参数无上下文类型;或 queryVecnull/空字符串。

  • 解决

    • 使用 CAST(#{kbId} AS bigint) IS NULL显式指定类型。

    • 在 Java 层确保 queryVec不为空(Arrays.toString(float[])至少产生 [0.0])。

    • SQL 中加入 AND #{queryVec} IS NOT NULL AND #{queryVec} != ''过滤空值。


二、后端业务逻辑

4. 文档上传后 document_chunk无数据

  • 现象 :上传成功但切片表为空,日志显示 Saving chunk index=...但最终无记录。

  • 根因@Transactional事务回滚------向量生成阶段(generateEmbeddingsForDocument)抛出异常,导致整个事务回滚,之前插入的切片被撤销。

  • 解决

    • 将向量生成改为异步执行(@Async+ 独立事务 REQUIRES_NEW),或捕获异常不往外抛。

    • 先注释掉向量生成步骤,单独测试切片插入,确认问题是否在此。

5. 百炼接口返回 url error/ InvalidParameter

  • 现象 :调用百炼 Chat 或 Embedding 接口时返回 {"code":"InvalidParameter","message":"url error, please check url!"}

  • 根因

    • baseUrl配置错误(如带了 /compatible-mode/v1但与原生端点不匹配)。

    • model参数使用了社区名(如 qwen3.7-plus)而非百炼模型 ID(如 qwen-plus)。

  • 解决

    • 统一使用百炼原生根域名 https://dashscope.aliyuncs.com

    • Chat 模型填 qwen-plus,Embedding 模型填 text-embedding-v2

    • 在 WebClient 中添加 onStatus错误日志,便于调试。

6. SSE 流式响应解析失败

  • 现象 :百炼返回了完整 JSON(非流式格式),但后端只解析 data:前缀的行,导致 Flux 空流,前端进入 error

  • 根因 :百炼 stream: true参数未生效(可能是请求体格式问题),返回了非流式响应。

  • 解决

    • 确认请求体 parameters.streamtrue

    • flatMap中同时支持流式(data: {...})和非流式({...})两种格式。

    • 使用 ServerSentEvent包装响应,前端监听 end事件。


三、前端与交互

7. uni-nav-bar组件不可用

  • 现象 :使用 uni-nav-bar报错或样式异常。

  • 根因:项目使用的是 NutUI 组件库,而非 uni-ui。

  • 解决 :替换为 nut-navbar,注意属性差异(如 left-show@on-click-back)。

8. 图片上传组件进度显示

  • 现象:上传封面或文档时无进度反馈。

  • 根因uni.uploadFile默认不显示进度。

  • 解决 :监听 uploadTask.onProgressUpdate,在组件内展示进度条(progress值驱动宽度动画)。

9. SSE 前端连接进入 error但后端无日志

  • 现象 :传 kbId时前端 EventSource 触发 error,后端控制台无异常。

  • 根因 :后端返回了非 200 状态码(如 500)或响应头缺少 Content-Type: text/event-stream,浏览器拒绝解析。

  • 解决

    • Controller 方法明确指定 produces = MediaType.TEXT_EVENT_STREAM_VALUE

    • 全局异常处理器对 SSE 请求返回合适的错误格式(或直接抛出,让 Spring 默认处理)。

    • 前端增加 onerror日志输出,便于定位。

10. Vue3 响应式数据不更新

  • 现象currentKbNameonKbChange中赋值后,模板未刷新。

  • 根因 :变量未使用 ref包裹,或赋值时忘了 .value

  • 解决 :统一使用 ref定义响应式变量,赋值时加 .value;在异步回调中特别注意。


四、部署与环境

11. 宝塔 PostgreSQL 连接问题

  • 现象psql命令找不到、socket 文件不存在、Navicat 连不上。

  • 根因:宝塔的 PostgreSQL 路径和默认 socket 位置与原生 Linux 不同。

  • 解决

    • 使用宝塔面板内的"终端"功能,或指定 -h 127.0.0.1 -p 端口通过 TCP 连接。

    • 在宝塔软件商店确认 pgvector 是否已安装,未安装则手动编译。

12. 文件上传大小限制

  • 现象Maximum upload size exceeded

  • 根因 :Spring Boot 默认 max-file-size=1MB

  • 解决 :在 application.yml中配置 spring.servlet.multipart.max-file-size=10MBmax-request-size=20MB

13.隔一段时间访问百炼就报Connection reset

问题描述

后端服务通过 WebClient(Reactor Netty)调用百炼(DashScope)API 时,在请求间隔较长(如超过 1 分钟)后 ,下一次请求会抛出 java.io.IOException: Connection reset异常,导致对话或向量生成失败。


根因分析

  1. 连接池复用机制WebClient底层使用 Reactor Netty 的连接池,复用 HTTP 长连接。

  2. 网关空闲断开 :阿里云 SLB / 百炼网关默认空闲超时约为 60 秒,超过该时间会主动 RST 连接。

  3. 连接池未及时感知 :Netty 连接池默认的空闲连接驱逐时间较长(或未配置),导致池中保留的"半死"连接在下次请求时被复用,触发 Connection reset


解决方案(三件套)

1️⃣ 自定义连接池,提前驱逐空闲连接

配置 ConnectionProvider,将 maxIdleTime设为 45 秒(小于网关 60 秒),并开启后台定期扫描:

复制代码
ConnectionProvider.builder("dashscope")
    .maxIdleTime(Duration.ofSeconds(45))
    .evictInBackground(Duration.ofSeconds(30))
    .build();

2️⃣ 为流式调用(Flux)添加重试机制

Connection reset异常进行 1 次指数退避重试,自动重建连接:

复制代码
.retryWhen(Retry.backoff(1, Duration.ofMillis(500))
    .filter(t -> t instanceof IOException 
              || t.getMessage().contains("Connection reset")))

3️⃣ 为同步调用(block)也添加重试

Embedding 等同步请求同样可能触发 RST,使用相同 retryWhen保护:

复制代码
.bodyToMono(String.class)
.retryWhen(Retry.backoff(1, Duration.ofMillis(500))
    .filter(t -> ...))
.block();

效果

  • 空闲连接在 45 秒内被主动清理,永远不会等到网关超时

  • 即使偶发 RST,自动重试一次即可恢复,用户无感。

  • 系统稳定性显著提升,长时间无人提问后再次使用不会报错。


经验教训

  • 调用外部 API 时,连接池的空闲超时配置必须小于网关的超时时间 ,否则必然出现 Connection reset

  • 对于网络抖动或短暂故障,合理使用重试机制(限制次数、指数退避)是提高鲁棒性的有效手段。

  • 生产环境中,建议为每个外部服务独立配置连接池和超时参数,避免相互影响。


五、架构与设计决策

决策点 选择 理由
向量数据库 PostgreSQL + pgvector 与业务库同库,免运维,支持 HNSW 索引
切片策略 固定字符数(500)+ 重叠(50) 简单可控,后期可替换为语义分割
流式输出 SSE(Server-Sent Events) 实现简单,浏览器原生支持
前端 UI 库 NutUI 与项目已有组件库统一
文件存储 腾讯云 COS(服务端上传) 稳定可靠,后期可升级为前端直传 + STS

六、经验教训

  1. 数据库扩展先行 :使用 PostgreSQL 的自定义类型(如 vector)前,务必先确认扩展安装和 search_path配置,否则后续所有操作都会受阻。

  2. 统一 MyBatis 配置 :避免混合使用 XML 和注解,容易冲突;对于复杂类型转换,直接使用 JdbcTemplate更可控。

  3. 事务粒度控制:耗时操作(如调用外部 API 生成向量)不应与核心业务(如切片保存)放在同一事务中,应异步处理或使用独立事务。

  4. SSE 调试技巧 :后端增加 doOnNext/doOnError日志,前端检查 Network 面板的响应体和状态码,快速定位是连接问题还是数据问题。

  5. 响应式数据检查 :Vue3 中遇到模板不更新,优先检查变量是否用 ref/reactive定义,以及赋值时是否加了 .value


七、当前系统能力矩阵

功能 状态
知识库 CRUD
文档上传(txt) + 切片
向量化存储(pgvector)
向量检索(余弦距离)
全局 / 指定知识库问答
SSE 流式输出
封面上传(进度条)
文档列表展示

这份总结涵盖了从数据库、后端、前端到部署的全链路问题。如果你需要针对某个具体问题展开详细的技术文档(如 pgvector 安装步骤、SSE 最佳实践),我可以帮你整理。