BizBuddy 是一个全量开源的企业级智能体平台,基于 Spring Boot 4 + AgentScope Java Harness。0.9.9 不是一个堆功能的版本,它主要补两块基础:检索更准 、记忆可控;同时做了一次工程结构的收敛。
一、这次更新了什么
| 方向 | 主要内容 |
|---|---|
| 知识库 | 按知识库可配 vector / hybrid;hybrid 走向量与 BM25 双路召回,RRF 融合后可选重排 |
| 长期记忆 | 收口为平台托管模式,补齐增量抽取、显式记忆、注入、合并、维护、运行健康、版本快照与恢复 |
| 工程与运维 | 前后端迁入 code/ 目录;e2e 收拢为 e2e/http 与 e2e/ui;移除 SnailJob 依赖,改由应用内调度器承载 |
| 体验 | 资源选择器移除业务工具入口;安全合规配置拆为双页签;补齐钉钉、飞书渠道机器人的图片消息兼容 |
这一版延续项目一贯的做法:功能、回归与双栈验证一起交付,测试脚本随仓库开源。
二、知识库混合检索:向量与 BM25 的互补
纯向量检索擅长"意思相近",但在企业语料里经常失灵:产品型号、错误码、法规条款、工单编号、人名地名,这些词的共同点是------用户会原样复制文档里的词,而向量未必能把它排到第一。反过来,BM25 对精确词很强,对同义表达、口语化提问几乎无能为力。
所以 0.9.9 把两种召回做成可选项,而不是替换关系:
text
查询 → 向量召回 ┐
├→ RRF 融合 → 可选重排 → Top-K
查询 → BM25 召回 ┘
按知识库配置,默认仍然只走向量。 retrieval_mode = vector 时行为与之前完全一致;改成 hybrid 后才会启用双路召回。这个默认值是刻意的:混合检索的代价不是"多一次模型调用",而是多一次向量库检索、多一份正文存储、多一套索引维护,它值得开,但不值得默认全开。
实现上有四个取舍:
1. 用 Milvus 原生 BM25,不引入第二个模型。 稀疏向量由 Milvus 在服务端根据正文自动生成,集合 Schema 里给 content 开启中文分词,再挂一个 BM25 Function。平台侧不额外调用 embedding 模型,混合检索没有新增模型供应商。
2. 独立集合,不动既有 Schema。 每个知识库的 BM25 索引放在 <collection>_bm25 独立集合里。老集合已经在线跑着,改 Schema 意味着迁移风险;独立集合可以按库创建、按库删除,代价是正文多存一份,换来零迁移。
3. 索引有生命周期,状态可见。 bm25_index_status 记录 none / building / ready / failed 四态。从 vector 切到 hybrid 时异步触发一次全量重建,单线程串行、同一知识库重复提交会被跳过,页面提供手动重建入口。
4. 降级写进每一层。 检索是问答的前置步骤,不能成为新的故障点:BM25 集合不存在就直接走纯向量;检索抛异常捕获后打 warn、本次继续;写入失败不影响文档向量化完成,只把状态置 failed;重建失败也不影响既有向量检索。
两个必须说清楚的边界:
- 重建期间不是最佳状态。 BM25 集合可能为空或只有部分文档,此时检索不会报错,但会退化成"向量为主、关键词偶尔补位"。
building状态就是给运维看的。 - 融合分不是置信度。 RRF 分本身很小(第一名也只有 1/61 左右),平台做 min-max 归一化只为展示与跨库排序,它不改顺序,也不是概率。
还有一个尚未完成的动作:混合检索还没有纳入 56 用例的 A/B 报告。 我们完成了功能、回归与双栈验证,但没有同口径数字,所以不写"提升了多少个百分点"。下一步是把 vector / hybrid 做成评测脚本参数,用同一批用例跑出可复现的对比,再决定默认值要不要调整。
三、长期记忆:从框架托管收口为平台托管
长期记忆是企业智能体最容易"看起来能用、实际不敢用"的部分。此前的链路里,框架的自动抽取与自动合并会在三个地方留下缺口:
- 工具体系返回的外部内容可能被原样送进抽取提示词,形成持久的提示注入;
- 合并是整文件覆盖,模型丢事实或人工编辑冲突时无法回滚;
- 注入超预算时截掉尾部,尾部事实模型永远看不到。
0.9.9 把长期记忆收口为平台托管这一种模式:关闭框架的记忆 hooks,抽取、注入、合并、维护全部由平台承担,只保留只读检索工具。
具体是五条链路:
| 链路 | 平台做法 |
|---|---|
| 抽取 | 按消息水位只处理新增的用户/助手最终消息,带少量只读上下文解决指代;输出经净化与校验后写入 memory/YYYY-MM-DD.md |
| 注入 | 显式记忆固定最前,MEMORY.md 按预算截断并显式标记 |
| 合并 | 轮末异步触发,Redis 按用户节流,分批调用记忆模型,每批成功即推进水位 |
| 显式记忆 | remember_user_preference / forget_user_preference 写入独立文件,不受自动合并影响 |
| 维护 | 每日任务接管合并 pending、归档、清理、版本与隔离轮转、审计轮转 |
治理边界决定了这套东西能不能进生产:
- 文件是唯一真值 :
MEMORY.md与每日流水是权威数据,统计与索引都可从文件重建; - 先合并再归档:合并失败则跳过归档,避免不活跃用户最后一批事实彻底脱离合并范围;
- 净化在进入长期记忆之前:合并前做 PII 脱敏、指令特征剔除、精确去重与超长截断,原始流水保留到隔离目录;
- 失败保守:净化、合并、快照任一步失败,宁可不覆盖、不归档,也不丢原文件;
- 可回滚:每次改写前留版本快照,支持查看与恢复;
- 可观测:抽取、合并、失败、截断、归档都有审计,记忆模型的调用计入用量统计。
产品层面,「长期记忆」页拆成三个页签:记忆配置 、记忆内容 (文件清单与编辑、版本历史、显式记忆新增/撤销)、运行健康(按近 1 天 / 7 天展示抽取与合并的审计汇总)。
同样有明确的三条边界:当前仍以单副本为前提,文件工作区 + 进程内节流决定了多副本一致性是后续独立课题;Milvus 记忆索引尚未启用,等框架侧记忆路线明确后再评估;不做跨用户共享记忆与部门级记忆。
四、工程结构:一次"减法"
这次调整不产生新功能,但会直接影响升级:
- 前后端迁入
code/back-end与code/front-end,部署 compose 同步新路径; - e2e 收拢为
e2e/http与e2e/ui:接口断言与浏览器用例分开,入口更清楚; - 移除 SnailJob 依赖 ,定时 Agent 任务改由应用内调度器承载:
ScheduledExecutorService+ 启动事件,不用全局@EnableScheduling;多实例去重靠一条update ... where last_run_time < 本次计划时间的原子抢占;长期记忆每日维护与中断任务回收复用同一模式。
少一个外部依赖,就少一个部署时要解释的组件。对开源项目来说,这是降低落地门槛最直接的方式。
升级时需要注意两点:部署挂载路径要跟着 code/ 调整 ;数据库变更以初始化脚本为准,存量库要按脚本补齐新增列,不要直接复用旧数据卷启动。
五、几个体验细节
- 资源选择器 :任务工作台底栏收敛为一个
+入口,分组为专家、场景、技能、MCP 工具;业务工具不再提供直选入口,避免用户绕过授权体系直接挂载写能力。选中项以标签呈现,改完下一轮生效。 - 安全合规配置双页签:拆成「内容审核」与「PII 脱敏」,两个开关互不影响。内容审核管敏感词与正则的检测处置,PII 脱敏独立生效,不会被前者的总开关顺手关掉。
- 渠道图片兼容:钉钉、飞书机器人对话中的图片消息补上了下载与解析,图片可以正常进入对话链路,而不是被当成一段无法处理的附件。
六、开源
BizBuddy 托管在 Gitee,代码与文档同步开源,同一套代码并列支持 PostgreSQL 与 MySQL 两套部署形态,端到端测试脚本随仓库提供,可以在自己的环境里复跑。
欢迎试用、提 Issue,也欢迎直接拿代码去验证------尤其是检索与记忆这两块,我们更希望被真实语料和真实合规要求挑战。