Skip to content

ConGoRAG v2.0

Choose a tag to compare

@XiaoleC05 XiaoleC05 released this 20 Sep 18:54
· 88 commits to main since this release

v1.0 全量代码经过一轮 15 个维度、带对抗性验证的审查,发现的 36 个缺陷全部修复。每个缺陷有独立 issue(都在 v2.0 里程碑),每个修复带一条回归测试。

本次发布含对外行为变更,升级前请读「行为变更」一节。

修了什么

对话与上下文

  • 历史按最新在前交给模型(RecentMessages 倒序返回,调用链上没有一层重排):现在查询内层取最近 N 条、外层按时间序返回
  • 本轮自己刚写入的消息被当作历史回放:当前提问不再被重复计入预算,空的 assistant 占位行不再进 prompt
  • 回放历史的角色被压平成 user:ctxmgr.Item 补上 Role 并一路传下去,助手自己说过的话不再记在用户头上
  • 检索片段与记忆被拼进 system 消息:改为单独的 user 角色消息 + 定界符,并剥离正文里自带的定界符
  • 摘要水位线跨过仍在流式生成的行:批次截到最后一条已定稿的消息,已完成的回答不再被永久挤出后续 prompt
  • 空 completion 覆盖已有摘要:空值判定后拒绝写入,水位线与旧摘要都保留
  • 压缩器忽略 targetTokens:预算写进提示词,压缩结果不优于被替换区域时整条丢弃

流式与失败语义

  • 客户端断开时清空已生成的部分回答:正文保留,只改状态
  • 数据库故障时 error 帧发不出去、客户端拿到 200 空响应:补一条不依赖落库的兜底帧

文档处理

  • 失败时用已取消的 job ctx 写状态且丢弃错误 → 用脱离取消的 ctx,并补记日志
  • 终态 failed 与 River 重试自相矛盾(重试 25 次全撞在"状态冲突")→ 只有最后一次 attempt 落终态;终态重投返回 nil
  • 整篇文档塞进一次 embedding 请求 → 按 256 分批并校验每批数量
  • 临时文件清理在句柄未关闭时执行(Windows 上是空操作)→ 先关闭再删除;新增 tmp 目录清扫
  • 不校验编码 → 上传挡扩展名、处理前挡非 UTF-8
  • 上传无大小上限 → 见「行为变更」
  • 文档处理继承 River 1 分钟默认超时 → 见「行为变更」

BYOK 与密钥

  • 重复引导会执行 ALTER ... USING NULL 清空全部已存向量 → 见「行为变更」
  • 集成测试自身的清理也清空向量、且断言发现不了 → 改为断言向量存活
  • tokenizer_type 保存时不校验、聊天时才拒绝 → 保存时校验并 trim 落库
  • 主密钥首次生成非原子(api 与 worker 同时启动各装一把不同的密钥,败者加密的数据永久无法解密)→ 临时文件 + fsync + os.Link 抢占 + 回读校验

Agent

  • 客户端断开后 run 永久停在 running → 失败收尾用脱离请求的 ctx;补上 interrupted 终态
  • 无缓冲通道导致 goroutine 与 Eino iterator 每次中断泄漏一个 → 发送可取消
  • 工具错误(如除零)打死整轮 run → 参数级错误降级为 tool result 交回模型
  • 省略 toolNames 时 nil 绑定进 NOT NULL 列返回 500 → 归一化为空切片
  • 一轮里多个并行工具调用写出多条 llm 步骤 → 同一轮只记一次
  • 失败一律报 upstream_llm_error → 保留错误自带的 sentinel

检索与平台

  • HNSW 后置过滤导致 Search 返回少于 topK(甚至零条)→ 在查询内设置 pgvector 扫描参数
  • 周期维护任务继承 60s 默认超时、被截断的一轮仍记为成功 → 显式超时并如实上报
  • isLoopbackHost 无条件按末位冒号截断,无端口 IPv6 回环被判 403 → 改用 net.SplitHostPort

前端

  • 保存 provider 成功后仍被弹回空白引导页(过期的空缓存 + 无效的失效调用)→ 只在"结论还是空"时等待
  • 发消息时用户那条从不进消息缓存,失败时从界面静默消失 → 发送那一刻乐观插入,失败也重拉
  • Agent 运行失败后历史列表不刷新 → 缓存失效从 done 分支挪进 finally
  • 补上前端测试(vitest + jsdom + Testing Library),并让 CI 真的跑它们——此前前端代码在 CI 上没有任何断言门禁

文档与契约

  • README / docs/sse-protocol.md 声称支持 Idempotency-Key 重放,实际没有代码读这个头 → 文档改为描述真实行为
  • openapi.yaml 缺 uploadDocument / deleteDocument / createConversation 会返回的 404 → 补齐
  • Capabilities.ToolCalling 注释声明门控 Agent 可用性,全仓没有一处读它 → 注释改为说清现状
  • make build-web 删除被跟踪的 apps/api/web/.gitkeep(提交后会让新 clone 编译失败)→ 构建后补回占位文件,直接跑 pnpm --filter web build 的路径也覆盖
  • Makefile 的 PATH 用分号拼接,只在 cmd.exe 下成立 → 按平台选分隔符

行为变更

变更 说明
换 embedding 模型会被拒绝(409) 之前重复引导会静默清空全部向量,文档仍显示 ready、检索静默返回零条。现在:列类型已经是目标值就跳过 ALTER;已存向量不属于当前模型时返回 409。代价:换模型需要手工清库——v1.0 没有重嵌入路径,应用内也没有清空向量的入口
上传有 32 MiB 上限 CONGORAG_MAX_UPLOAD_BYTES 可覆盖;写 0 或负数视为没设、退回默认值
文档处理任务超时 30 分钟 之前吃 River 的 1 分钟默认值,一份合法的大文档必然超时。与上传上限相互作用,两条要一起看
失败语义 非最后一次 attempt 不落终态;终态重投返回 nil;客户端断开记 interrupted 而不是 failed
SSE 的 error 帧可以不带 id 唯一那一帧由"事件没能落库"触发,不要拿它推进续传游标(已写进协议文档)

明确没有实现

以下三条本来就是「文档与代码不一致」,按各自的修复方向让两边一致,没有顺手实现功能:

  • Idempotency-Key 重放(SSE 重放语义不简单,属于新功能)
  • Capabilities.ToolCalling 门控(表单默认不勾选,真加门控会让默认配置的用户失去 Agent)
  • 重嵌入 / reindex 路径

验证方式

CI 四个 job 全绿(run #2):Go build / vet / test -race、契约生成物漂移检查、前端 lint + test + 构建 + go:embed、以及在真实 pgvector/pgvector:0.8.6-pg18 上跑的数据库集成测试。

本地另跑:go build ./...、go vet ./...、go test ./...、pnpm --filter web lint、pnpm --filter web test(4 文件 6 用例)、gofmt -l。

每条回归测试都验证过"修复前会失败"——把代码临时退回旧行为跑一遍确认变红,再还原。

已知限制

  • #22 的回归测试只在 Windows 上会失败(POSIX 允许删除仍打开的文件),所以那条"先关闭再删除"的顺序在 Linux CI 上没有门禁,将来被改回去不会被发现。测试注释里写明了。
  • 集成测试在本地永远是 SKIP(没设 CONGORAG_TEST_DB_URL),只有 CI 真正执行它们。涉及 SQL 的改动只能在 CI 上得到验证。
  • 本次提交与 tag 都未签名——本机配置了 GIT_SIGN_COMMITS 但没有可用的 GPG 私钥。