Repository navigation
ConGoRAG v2.0
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 私钥。