WeKnora 贡献指南(犀牛鸟竞赛导向)
调研日期:2025-06-22 WeKnora commit: 5f7ed2f(2026-06-22)未 Docker 实测,以源码/docs 为准
一句话定位
本篇是犀牛鸟竞赛 可执行贡献路径——不重复 精读: WeKnora 已有的架构分析,聚焦”怎么选切入点、怎么提第一个 PR、怎么从零做到高价值贡献”。
RAG 管线段标注
本篇主要涉及 Ingest - Parse/Chunk - Retrieve - Generate 全段的贡献视角——每段在哪改、改什么、门槛多高、评审怎么看。
1. 犀牛鸟竞赛的评审标准
结论先行:评审不看 PR 数量,看理解深度 + 技术方案可行性 + 独立解决问题的能力。
1.1 评审维度拆解
想象你在参加一场厨艺比赛。评委不会因为你切了 100 根葱花就给你高分——他们看的是:你理解这道菜的口味逻辑吗?你的食材搭配方案合理吗?刀工出问题你能自己补救吗?
犀牛鸟评审的逻辑一模一样:
| 评审维度 | 权重(推断) | 具体表现 | 常见误区 |
|---|---|---|---|
| 项目理解深度 | 最高 | 跑通 demo、读过源码、能说清架构设计取舍 | 只读了 README 就开始提 PR |
| Proposal 质量 | 高 | 技术方案可行、时间规划合理(以周为单位)、体现独立思考 | Proposal 写得像读书笔记,没有自己的方案 |
| 提前联系导师 | 高 | 展示准备工作和思考,带着具体问题去沟通 | 从不联系导师,闭门造车 |
| GitHub 活跃记录 | 中 | 有实际 PR 贡献,review 他人 PR 也能加分 | 只提 trivial PR(修 typo)凑数 |
| 解决问题的韧性 | 中 | 遇到卡壳能独立排查,不直接放弃 | 遇到问题就等导师手把手教 |
| 技术含量 | 中 | 贡献涉及核心逻辑而非纯 UI 微调 | 全部贡献集中在改按钮颜色 |
往年经验详见 往年经验帖,评审维度的详细案例见该帖”评审看重什么”一节。
1.2 从评审反推贡献策略
评审最看重”理解深度”,所以贡献策略的核心不是”PR 越多越好”,而是”每个 PR 都能证明我理解了什么”。
具体做法:
- 第一个 PR 必须跑通全栈——证明你能部署、能定位问题、能验证修复
- PR 描述写清楚 why——不只写”修复了 #1633”,而是写”问题根因是搜索回调返回值在 null case 下未处理,导致 undefined 渲染。修复方式是在回调函数中增加 null guard”
- 选涉及核心逻辑的 issue——改检索管线的一个参数,比改 10 个前端 typo 更有说服力
- 做别人不愿做的苦活——写 eval 脚本、补测试、做 benchmark——这些没人做但维护者极其需要
1.3 与 GSoC 评审的异同
| 维度 | 犀牛鸟 | GSoC |
|---|---|---|
| 评审主体 | 项目导师 + 腾讯开源委员会 | 项目导师 |
| 中期评估 | 有(8 月初) | 有(mid-term) |
| 最终评估 | PR 合入 + 书面报告 | 代码合入 |
| 对 Proposal 的重视 | 极高(直接决定是否入围) | 高 |
| 导师沟通 | 非常重要(导师推荐权重高) | 重要 |
1.4 从评审看”深度贡献”的定义
犀牛鸟评审对”深度贡献”有非常具体的期待——不是”改了很多代码”,而是”对项目有可衡量的正向影响”。以下三个层次,评审会依次考察:
层次一:能跑起来(必要条件) 本地部署成功、能复现 issue、能跑测试。这一步淘汰掉大部分”只看了 README”的参与者。如果你连项目都跑不起来,评审不会相信你理解了项目。
层次二:能定位问题(区分条件)
不只是修 bug,而是能说清楚 bug 的根因。比如修复 #1633,不只是加一个 null check——而是能解释”搜索回调在异步 Promise 链中,当 API 返回空结果时,回调函数的参数结构从 {data: [...]} 变成了 {data: null},下游的 .map() 调用在 null 上抛出 TypeError,导致 undefined 被渲染到页面”。这种根因分析能力,评审极其看重。
层次三:能提出方案(加分条件) 发现了现有架构的设计局限,并提出可行的改进方案。比如发现 WeKnora 的单轮检索无法处理复杂查询,提出多跳查询分解方案——这就是层次三的贡献。大多数参与者停留在层次二,做到层次三的往往是最终获奖者。
2. 环境矩阵
| 组件 | 版本要求 | 说明 |
|---|---|---|
| Go | 1.21+ | 核心后端(internal/ 全部);门槛最高 |
| Node.js / React+TS | 18+ | 前端主力切入点(frontend/ 目录) |
| Python | 3.10+ | 文档解析微服务(docreader/,约513K) |
| Docker / Docker Compose | 最新 | 全栈本地部署(docker/、deploy/) |
| LLM API Key | 必需 | 检索/Rerank/Agent 对话均依赖 |
| 向量数据库 | 按配置 | 支持 10 种后端(Milvus/Qdrant/PgVector 等) |
注意:仓库今天(2026-06-22)有重大结构变动——原来的 client/ Vue 前端已重构为 frontend/ React+TS 前端,新增 mcp-server/(MCP Server 实现)和 miniprogram/(微信小程序)。
2.1 环境选择策略
面对四种技术栈(Go / React+TS / Python / Docker),新手最常犯的错误是”什么都想学”——既想学 Go 后端又想学前端又想学 Python docreader。结果什么都只学了皮毛,哪个方向都做不出有说服力的贡献。
正确的做法是:选定一个技术栈,做到深度足以提 PR。具体选择逻辑:
- 如果你已经会 TypeScript / React:从
frontend/入手,这是最快的出 PR 路径 - 如果你已经会 Python 且对 NLP 有兴趣:从
docreader/入手,表格/图片理解是高价值方向 - 如果你想挑战 Go 后端:先花 1-2 周过一遍 Go 基础(推荐”Go by Example”),然后从
internal/retrieval/的某个子包开始精读 - 如果你是纯新手:先从文档/部署指南贡献开始,边做边学
2.2 LLM API Key 的选择
WeKnora 的几乎所有功能都依赖 LLM API——检索时的 query embedding、Rerank 时的 Cross-Encoder 打分、Agent 对话的生成。选择 API 时需要考虑:
| 场景 | 推荐选择 | 原因 |
|---|---|---|
| 低成本实验 | DeepSeek API | 价格极低,中文理解好 |
| 稳定生产 | OpenAI GPT-4o-mini | 稳定性强,文档齐全 |
| 腾讯生态 | 混元 API | 与 WeKnora 同属腾讯,兼容性可能更好 |
| 本地无网络依赖 | Ollama + Qwen2 | 完全本地,零成本,但质量较低 |
注意:Rerank 功能对模型质量很敏感。用低质量模型做 Rerank,0.6/0.3/0.1 的权重分配可能不再合理——模型分的权重可能需要从 0.6 降到 0.4,基础分从 0.3 升到 0.5。这是路径 C(评估框架集成)要验证的假设。
3. 当前仓库结构(对照已有精读更新)
cmd/ -- Go 入口 main
internal/
|-- eventmanager/ -- 事件总线(Plugin 注册/派发)
|-- plugin/ -- 插件管线
|-- retrieval/ -- bm25 / vector / knowledge_graph / fusion
|-- rerank/ -- 复合 Rerank
|-- chunking/ -- 自适应分块(4 级降级)
|-- wiki/ -- 自维护 Wiki(inspector.go 巡检)
frontend/ -- React+TS 前端(推荐切入点)
mcp-server/ -- MCP Server 实现(新增)
docreader/ -- Python 文档解析引擎
client/ -- Go SDK / 客户端库
cli/ -- 命令行工具
miniprogram/ -- 微信小程序(新增)
docker/ / deploy/ / helm/ -- 部署配置
docs/ -- 文档
skills/ / packages/ / examples/ -- 扩展 / 示例
3.1 目录导航策略
这个仓库很大,直接全读是不现实的。以下是按贡献目标的导航策略:
想改检索管线(路径 A / D 的核心):
- 从
internal/retrieval/开始,这是检索的入口包 - 读
bm25/理解关键词检索如何工作 - 读
vector/理解向量检索如何工作 - 读
knowledge_graph/理解图谱检索如何工作——这是最复杂的一路 - 读
fusion/理解 RRF 如何合并三路结果 - 读
rerank/理解复合 Rerank 的三信号设计
想改分块策略(路径 B / E 的核心):
- 从
internal/chunking/开始 - 找到
adaptive.go——4 级自适应分块的入口 - 理解各级分块的触发条件和降级逻辑
- 对于路径 E,还要看
docreader/的 Python 代码
想加评估(路径 C 的核心):
- 从
internal/wiki/开始——理解自维护 Wiki 的巡检逻辑 - 读
inspector.go——理解巡检如何发现知识库问题 - 在
internal/plugin/中找到合适的扩展点 - 设计 EvalPlugin 与现有 Wiki 巡检的集成方式
想改前端(快速出 PR):
- 从
frontend/开始 - 看
package.json了解技术栈和脚本 - 看
src/components/找到对应组件 - 用
npm run dev启动开发服务器
完整的源码精读路径见 精读: WeKnora 的”学习建议”章节。
4. Issue 地图(2026-06-22 gh 核实)
4.1 高新手友好度(推荐前 5)
| Issue # | 标题 | 类型 | 为何适合 |
|---|---|---|---|
| #1633 | 共享空间添加人员弹窗——角色搜索返回 undefined | Bug | 典型前端 bug,搜索回调返回值处理错误,范围小 |
| #1353 | 知识库 tag 编辑在列表视图和批量操作栏缺失 | Bug | UI 组件缺失,纯前端工作,边界清晰 |
| #1565 | 知识库添加文件报错时不展示错误信息 | Enhancement | 纯前端错误提示改善,范围极小 |
| #1417 | 浏览器插件保存时修改标题无效 | Bug | 浏览器扩展小 bug,字段传递问题 |
| #1444 | wiki 模式实体里图片有的无法加载 | Bug | 图片 URL 处理问题,前端定位 |
4.2 中等友好度(有一定理解成本)
| Issue # | 标题 | 类型 | 为何有空间 |
|---|---|---|---|
| #1606 | IM 思考过程输出作为可配置项 | Enhancement | 加配置开关,前端+配置层 |
| #1716 | Cannot Save Agent With Selected Knowledge Bases | Bug | 前端表单提交 bug,需理解 Agent 配置 |
| #1723 | KB 更新 API 缺少 VLMConfig 字段 | Bug | Go API 层加字段,改动可控 |
| #1725 | PaddleOCR-VL 表格 HTML 影响分块效果 | Bug | 需理解分块逻辑但问题清晰 |
| #1676 | 对话支持指定具体文档(非只能指定知识库) | Enhancement | 前端 UI + 后端 API 参数扩展 |
4.3 竞争情报
- 仓库 无 CONTRIBUTING.md,无实际使用 good-first-issue 标签的 issue
- 当前 open issues: 约 40+(含 [Question] 类)
- Open PRs: 15 个,来自多位活跃贡献者(FFFFFFpy、coconut-yc、langcaiye、jack-wang-176)
- 建议:选定 issue 后先在评论区留言表明意向,观察维护者响应速度
4.4 Issue 选择策略
选 issue 不是看哪个简单就选哪个——而是看哪个 issue 能最大化你的”理解深度证明”。以下是选择逻辑:
第一周(破冰期):选一个高新手友好度的前端 bug。目标不是做多有技术含量的贡献,而是证明你能跑通项目、能定位问题、能提 PR。推荐 #1633 或 #1565。
第二周起(深入期):根据你选定的深度路径(A-E),选择与路径直接相关的 issue。这些 issue 可能不在”高新手友好度”列表中,但它们让你在解决实际问题的过程中深入理解相关模块。
Issue 与路径的映射:
| Issue | 关联路径 | 深入方向 |
|---|---|---|
| #1725 PaddleOCR-VL 表格 HTML 影响分块 | 路径 B / E | 理解分块逻辑 + docreader |
| #1723 KB 更新 API 缺少 VLMConfig 字段 | 路径 E | 理解 Go API 层 + 多模态配置 |
| #1676 对话支持指定具体文档 | 路径 A | 理解检索 API + 查询参数扩展 |
| #1606 IM 思考过程输出作为可配置项 | 路径 C | 理解配置层 + 可观测性 |
4.5 竞争格局分析
了解其他贡献者在做什么,可以帮助你避开”红海”——同一个 issue 被多个人同时修复,维护者只会合入一个。
当前活跃贡献者方向(基于 Open PRs 分析):
| 贡献者 | 方向 | 对你的影响 |
|---|---|---|
| FFFFFpy | 前端功能增强 | 如果你要做前端贡献,关注他的 PR 避免冲突 |
| coconut-yc | Go 后端 bug 修复 | 检索/分块相关 bug 可能已被他修了 |
| langcaiye | 文档 / 部署 | 文档类贡献可能与他重叠 |
| jack-wang-176 | MCP Server | 如果你做路径 A,关注他的 MCP 改动 |
策略建议:
- 不要和活跃贡献者抢同一个 issue——除非你能做得明显更好
- 关注他们的 PR 评论,了解维护者对代码风格和架构的偏好
- 如果某个 PR 长时间没有合入(>2 周),说明可能有设计争议——在评论区学习争议点,避免犯同样的错误
5. WeKnora 贡献路径详解
这是本篇的核心。不重复 精读: WeKnora 的架构分析,聚焦”每条路径具体改什么文件、写什么代码、怎么测”。
先给全局地图:
flowchart TD
START[选定贡献路径] --> A{技术栈偏好?}
A -->|前端| B[前端 Bug / UI]
A -->|Go 后端| C{深度目标?}
A -->|Python| D[docreader 解析]
A -->|评估/文档| E[Benchmark / Eval]
C -->|检索编排| F[路径 A: Agentic RAG]
C -->|分块优化| G[路径 B: 自适应分块]
C -->|评估闭环| H[路径 C: 评估框架]
C -->|图谱增强| I[路径 D: 知识图谱]
C -->|多模态| J[路径 E: 多模态支持]
B --> K[快速出 PR]
D --> K
E --> K
F --> L[高价值深度贡献]
G --> L
H --> L
I --> L
J --> L
路径 A:Agentic RAG 集成——多跳查询分解
路径总览与选择建议
五条路径该怎么选?核心原则是”你的技术栈匹配度 x 路径的竞赛价值 x 你能投入的时间”。
| 路径 | 技术栈 | 竞赛价值 | 时间投入 | 推荐人群 |
|---|---|---|---|---|
| A: Agentic RAG | Go + LLM prompt | 极高(最前沿方向) | 3-4 周 | 有 Go 基础 + 对 Agent 感兴趣 |
| B: 自适应分块 | Go + LLM prompt | 高 | 2-3 周 | 有 Go 基础 + 对 NLP 感兴趣 |
| C: 评估框架 | Go + 评估理论 | 极高(维护者最需要) | 3-4 周 | 有 Go 基础 + 对评估/测试感兴趣 |
| D: 知识图谱 | Go + 图论 | 高(WeKnora 独有特性) | 3-4 周 | 有 Go 基础 + 对图算法感兴趣 |
| E: 多模态 | Python + Go | 高(实际问题驱动) | 2-3 周 | 会 Python + 对多模态感兴趣 |
个人推荐排序(假设你有基本的 Go 基础):
- 路径 C(评估框架)——维护者最需要、竞争最小、竞赛评估时最容易量化效果
- 路径 A(Agentic RAG)——最前沿、最能体现”理解深度”、但风险也最高
- 路径 D(知识图谱)——WeKnora 独有特性,做了没人能竞争
如果你只有 Python 基础:路径 E 是唯一选择,但 #1725 issue 保证了这是一个实际问题。
各路径的竞赛得分预期
不同路径在竞赛评审中的预期得分不同。这不是因为某些路径”更好”,而是因为不同路径展示”理解深度”的容易程度不同:
| 路径 | 容易量化效果 | 展示架构理解 | 维护者需求度 | 竞争者数量 | 综合得分预期 |
|---|---|---|---|---|---|
| A: Agentic RAG | 中(需要对比实验) | 极高(改核心管线) | 中(方向有争议) | 少 | 中-高 |
| B: 自适应分块 | 高(分块质量可量化) | 高(改分块逻辑) | 中 | 少 | 中-高 |
| C: 评估框架 | 极高(RAGAS 四指标直接出数) | 高(改 Wiki 巡检 + Plugin) | 极高(无人做) | 极少 | 高 |
| D: 知识图谱 | 高(图谱精度可量化) | 极高(改核心差异化特性) | 高 | 极少 | 高 |
| E: 多模态 | 中(需要图表类 benchmark) | 中(改 docreader 较独立) | 中 | 中 | 中 |
核心洞察:路径 C 的”容易量化效果”和”维护者需求度”都是极高的——这意味着你做完之后,可以用数字说”我让 WeKnora 的评估能力从零变成了可用”,这个效果比”我加了一个功能”有力得多。
定位与类比
WeKnora 当前是”单轮固定管线”——用户问一个问题,三路全查,RRF 融合,Rerank 精排,生成答案。如果用户问的是复杂问题,比如”WeKnora 的知识图谱算法和 GraphRAG 有什么区别?各自适合什么场景?”——这个问题其实包含两个子问题,但 WeKnora 只做一次检索,可能两边都搜不深。
类比:你现在去图书馆问管理员”中国明朝和英国都铎王朝的税收制度有什么区别?”管理员要么给你找中国的、要么给你找英国的,很难一次把两边都找齐。但如果有一个聪明的研究助理,他会先分解问题——”明朝税收制度是什么”“都铎王朝税收制度是什么”——分别查找,再综合比较。这就是多跳查询分解。
类比边界:不是所有查询都需要分解。”WeKnora 支持哪些向量数据库?”这种简单问题分解反而增加延迟和成本。
设计文档大纲
# Agentic RAG: Multi-hop Query Decomposition for WeKnora
## 1. 背景与动机
- 当前 WeKnora 检索是单轮固定管线
- 复杂查询(对比/因果/多条件)的召回率不足
- 参考: IRCoT 论文、LangGraph Multi-step Retrieval
## 2. 设计方案
### 2.1 查询分解器 (QueryDecomposer)
- 输入: 用户原始查询
- 输出: 1-N 个子查询 + 合成策略
- 实现: LLM-based decomposition (prompt engineering)
### 2.2 并行子查询检索
- 每个子查询独立走三路检索 + RRF + Rerank
- 使用 goroutine 并行执行
### 2.3 结果合成
- 按合成策略合并子查询结果
- 去重(同一 chunk 可能被多个子查询召回)
- 重排序(可选二次 Rerank)
### 3. EventManager 集成
- 新增 QueryDecomposed 事件
- 新增 SubQueryRetrieved 事件
- FusionPlugin 扩展:支持多轮融合
### 4. 配置与降级
- decomposition.enabled: bool(默认关闭)
- decomposition.max_hops: int(默认 3)
- 降级: 分解失败时 fallback 到原始单轮检索
关键代码修改点
1. 新增 internal/retrieval/decomposer/ 包
// decomposer.go - 查询分解器核心逻辑
type Decomposer struct {
llmClient LLMClient
maxHops int
enabled bool
}
// Decompose 将复杂查询分解为子查询列表
// 关键设计: 分解失败时返回原始查询(降级策略)
func (d *Decomposer) Decompose(ctx context.Context, query string) ([]SubQuery, error) {
if !d.enabled {
return []SubQuery{{Query: query, Strategy: "direct"}}, nil
}
prompt := buildDecompositionPrompt(query)
resp, err := d.llmClient.Chat(ctx, prompt)
if err != nil {
// 降级: LLM 调用失败, 返回原始查询
return []SubQuery{{Query: query, Strategy: "direct"}}, nil
}
subQueries, err := parseDecomposition(resp)
if err != nil || len(subQueries) == 0 {
return []SubQuery{{Query: query, Strategy: "direct"}}, nil
}
if len(subQueries) > d.maxHops {
subQueries = subQueries[:d.maxHops]
}
return subQueries, nil
}
2. 修改 internal/eventmanager/ 事件定义
在 events.go 中新增:
QueryDecomposed EventType = "query_decomposed" // 新增
SubQueryRetrieved EventType = "subquery_retrieved" // 新增
AllSubQueriesDone EventType = "all_subqueries_done" // 新增
3. 新增 DecomposerPlugin 注册到 EventManager
type DecomposerPlugin struct {
decomposer *Decomposer
}
func (p *DecomposerPlugin) SubscribedEvents() []EventType {
return []EventType{"QueryReceived"}
}
func (p *DecomposerPlugin) Handle(event Event) (Event, error) {
subQueries, err := p.decomposer.Decompose(event.Ctx, event.Query)
if err != nil {
return event, err // 降级: 返回原始事件
}
return Event{
Type: "QueryDecomposed",
Data: SubQueryData{Queries: subQueries, Original: event.Query},
}, nil
}
func (p *DecomposerPlugin) Priority() int {
return -1 // 在 BM25/Vector/Graph 之前执行
}
4. 修改 FusionPlugin 支持多子查询结果合并
在 internal/retrieval/fusion/ 中扩展 Fusion 逻辑,新增 MultiQueryFuse() 方法处理来自不同子查询的检索结果,做去重和加权合并。
测试策略
| 测试层级 | 内容 | 工具 |
|---|---|---|
| 单元测试 | Decomposer.Decompose() 分解正确性 | Go testing + table-driven |
| 单元测试 | 降级逻辑(LLM 超时/返回格式错误) | Mock LLMClient |
| 集成测试 | 完整 EventManager 流程:Query → Decompose → SubQuery → Retrieve → Fuse | Docker compose |
| 端到端测试 | 对比”有分解”vs”无分解”的检索质量 | RAGAS eval script |
| 性能测试 | 多子查询并行检索的延迟 vs 单查询延迟 | pprof + benchmark |
关键测试用例设计:
// table-driven 测试
tests := []struct{
name string
query string
wantN int // 期望分解出几个子查询
fallback bool // 是否期望降级
}{
{"简单查询不分解", "WeKnora支持哪些向量数据库", 1, false},
{"对比查询分解为2", "BM25和向量检索的区别", 2, false},
{"三段式查询分解为3", "WeKnora的架构、检索和评估分别是什么", 3, false},
{"LLM失败降级", "anything", 1, true},
}
预期影响与工作量
| 维度 | 估算 |
|---|---|
| 代码行数 | 约 800-1200 行(Go) |
| 涉及文件 | 新增 decomposer/ 包 + 修改 eventmanager/ + fusion/ + config/ |
| 工作量 | 3-4 周(含测试和文档) |
| 对检索质量的影响 | 复杂查询 Context Recall 预期提升 15-25% |
| 风险 | 分解 prompt 质量不稳定;多子查询增加 LLM 调用成本 |
Agentic RAG 的六种模式详见 Agentic RAG 模式,WeKnora 当前的 EventManager 架构详见 精读: WeKnora 的 EventManager 章节。
Prompt 工程要点
路径 A 的核心是 LLM prompt 的质量——分解查询的 prompt 如果不好,整个功能就是摆设。以下是设计分解 prompt 时需要考虑的关键点:
Prompt 设计三原则:
-
明确输出格式:不要让 LLM 自由发挥输出格式。要求它输出 JSON 或结构化文本,解析才不会出错。WeKnora 的其他 LLM 调用(如 Rerank)都有严格的输出格式要求,分解 prompt 也不例外。
-
提供判断依据:不要只说”把复杂查询分解为简单查询”,而是给出判断标准——什么样的查询需要分解?什么样的不需要?例如:”如果查询包含’对比’‘区别’‘A 和 B’等多主体关键词,则分解为独立子查询;如果查询只涉及单一主题,则不分解。”
-
限制分解深度:明确规定最多分解为 N 个子查询(默认 3),防止 LLM 过度分解导致成本失控。
推荐的分解 Prompt 模板:
你是一个查询分解器。你的任务是将复杂的用户查询分解为多个简单子查询。
规则:
1. 如果查询只涉及一个主题,直接返回原始查询
2. 如果查询包含多个可独立回答的子问题,将每个子问题提取为独立子查询
3. 最多分解为 3 个子查询
4. 每个子查询必须是可以独立检索的完整问题
5. 如果无法确定是否需要分解,倾向于不分解
输出格式(严格遵守):
{"decompose": true/false, "sub_queries": ["子查询1", "子查询2"], "strategy": "compare/sequence/parallel"}
示例:
输入:"BM25 和向量检索有什么区别?"
输出:{"decompose": true, "sub_queries": ["BM25 检索的原理和特点", "向量检索的原理和特点"], "strategy": "compare"}
输入:"WeKnora 支持哪些向量数据库?"
输出:{"decompose": false, "sub_queries": [], "strategy": "direct"}
Prompt 迭代策略:先用 10-20 个测试查询验证 prompt 的分解质量,对不合理的分解结果迭代修改 prompt。这个迭代过程本身就是一个有价值的贡献——你可以把测试查询集和 prompt 版本记录下来,作为 Proposal 和答辩的素材。
路径 B:自适应分块增强——LLM 辅助语义分块
定位与类比
WeKnora 的 4 级自适应分块中,第 4 级(语义级)使用相邻句子的向量相似度来判断切分点。这个方法有一个已知的缺陷:当同一个论点用不同风格表达时(比如”这个方法的优点是高效”vs”其时间复杂度仅为 O(n log n)”),向量相似度可能低于阈值导致误切。
类比:你听讲座时,讲师先用通俗语言解释一个概念,然后给出数学公式——两者说的是同一件事,但如果只看”用词相似度”,它们看起来完全不同。一个聪明的听众(LLM)能判断这两段其实讲的是同一个论点,不应该在中间断开。
类比边界:LLM 辅助分块的代价是每个 chunk 的分块时间从毫秒级增加到秒级——对大规模文档库可能不可接受。适合作为”高质量模式”在文档首次摄入时使用,不适合实时场景。
设计文档大纲
# LLM-Assisted Semantic Chunking Enhancement
## 1. 背景与动机
- 当前语义级分块仅依赖向量相似度,对"同义不同词"的段落误切率高
- 实测案例: 技术文档中"方法优势"段与"性能数据"段被误切
- 参考: LumberChunker (2024), Dense X Retrieval
## 2. 设计方案
### 2.1 双阶段分块
- Stage 1: 传统向量相似度分块(快速,低精度)
- Stage 2: LLM 判定边界合理性(慢速,高精度)
- 两者结合: Stage 1 粗切 → Stage 2 精调
### 2.2 LLM Boundary Judge
- 输入: 两个相邻 chunk 的文本
- 输出: 是否应该切分 (0-1 分数) + 理由
- Prompt: 结构化判定 prompt
### 2.3 自适应策略选择
- 文档 < 10K tokens: 可选 LLM 辅助
- 文档 > 10K tokens: 仅向量分块(成本考量)
- 用户可配置: chunking.semantic.llm_assisted = true/false
### 3. 实现
- 在 ChunkerPlugin 中增加 post-processing 步骤
- 复用现有 LLM 配置(rerank 模型即可,无需新模型)
关键代码修改点
1. 新增 internal/chunking/llm_judge.go
type LLMJudge struct {
client LLMClient
threshold float64 // 切分判定阈值, 默认 0.5
}
// JudgeBoundary 判定两个相邻 chunk 是否应该切分
// 返回 0-1 分数, > threshold 表示应该切分
func (j *LLMJudge) JudgeBoundary(ctx context.Context, chunkA, chunkB string) (float64, error) {
prompt := fmt.Sprintf(
"判断以下两段文本是否属于同一个论述主题。\n"+
"段落A: %s\n段落B: %s\n"+
"如果两段讨论同一主题, 回答 MERGE; 否则回答 SPLIT。\n"+
"给出0到1的分数, 1表示应该切分, 0表示应该合并。",
chunkA, chunkB,
)
resp, err := j.client.Chat(ctx, prompt)
if err != nil {
return j.threshold, nil // 降级: 默认保持 Stage 1 结果
}
return parseScore(resp), nil
}
2. 修改 internal/chunking/adaptive.go
在 AdaptiveChunker.Chunk() 方法中增加 Stage 2 后处理:
func (c *AdaptiveChunker) Chunk(ctx context.Context, doc Document) ([]Chunk, error) {
// Stage 1: 原有向量相似度分块
chunks, err := c.semanticChunk(doc)
if err != nil {
return nil, err
}
// Stage 2: LLM 辅助精调(可配置关闭)
if c.config.Semantic.LLMAssisted {
chunks, err = c.llmRefine(ctx, chunks)
if err != nil {
// LLM 失败时降级到 Stage 1 结果
log.Warn("LLM refinement failed, using Stage 1 chunks", "err", err)
}
}
return chunks, nil
}
3. 新增配置项
在 internal/config/chunking.go 中增加:
type SemanticConfig struct {
Threshold float64 `yaml:"threshold"` // 现有
Overlap int `yaml:"overlap"` // 现有
LLMAssisted bool `yaml:"llm_assisted"` // 新增
JudgeModel string `yaml:"judge_model"` // 新增: 使用的 LLM 模型
MaxDocTokens int `yaml:"max_doc_tokens"` // 新增: 超过此大小不启用 LLM
}
测试策略
| 测试层级 | 内容 |
|---|---|
| 单元测试 | LLMJudge.JudgeBoundary() 的 prompt 构造和解析 |
| 单元测试 | 降级逻辑(LLM 超时/返回非数字) |
| 对比测试 | 同一文档: 纯向量分块 vs LLM 辅助分块的 chunk 边界质量 |
| 评估测试 | 用 RAGAS 评估两种分块策略下的检索质量差异 |
| 性能测试 | LLM 辅助分块的额外耗时(预期: 单 chunk +1-3s) |
关键测试用例:构造一个”同义不同词”的技术文档段落,验证纯向量分块会误切、LLM 辅助分块不误切。
预期影响与工作量
| 维度 | 估算 |
|---|---|
| 代码行数 | 约 500-800 行(Go) |
| 涉及文件 | 新增 llm_judge.go + 修改 adaptive.go + config/ |
| 工作量 | 2-3 周 |
| 对检索质量的影响 | 语义级分块的 Context Precision 预期提升 10-15% |
| 风险 | LLM 调用成本增加;大文档场景不可用 |
WeKnora 当前分块策略的完整分析见 精读: WeKnora 的”4 级自适应分块策略”章节。
路径 C:评估框架集成——RAGAS EvalPlugin
定位与类比
WeKnora 当前没有任何内建评估工具。维护者不知道一次检索变更到底让质量变好了还是变差了——就像医生给病人开了药但没有任何化验指标,只能凭”感觉好像好了”来判断。
RAGAS EvalPlugin 的目标:给 WeKnora 装上”体检仪器”——每次检索都自动跑四维评估(Faithfulness / Answer Relevancy / Context Precision / Context Recall),让质量变化可量化、可归因。
类比边界:评估本身消耗 LLM 调用(RAGAS 用 LLM 判定相关性),所以不能对每次用户查询都跑全量评估。应该做采样评估或离线评估。
WeKnora 架构约束与设计哲学
在开始设计 EvalPlugin 之前,必须理解 WeKnora 的架构约束——否则你的设计可能被维护者以”不符合项目架构”为由拒绝:
约束一:所有功能必须是 Plugin WeKnora 的核心设计哲学是”EventManager + Plugin”。任何新功能都必须以 Plugin 形式注册到 EventManager,监听已有事件或新增事件。直接在主流程中加代码是不被接受的——你必须证明你的功能可以热插拔。
约束二:降级策略是硬要求 WeKnora 是企业级产品,不能因为一个新功能导致整个系统不可用。所有新功能必须有降级策略——当新功能依赖的外部服务(如 LLM)不可用时,系统必须能退回到原始行为继续运行。
约束三:配置驱动,默认关闭
新功能必须有一个配置开关,且默认关闭。这是为了让现有用户升级时不受影响。只有明确配置了 evaluation.enabled = true 的用户才会启用评估功能。
约束四:异步优先 评估功能不应该阻塞检索流程。用户检索一个结果不应该因为”评估还没跑完”而等待。评估应该异步执行,结果异步存储。
理解这些约束后,EvalPlugin 的设计方向就非常清晰了:它必须是一个 Plugin,监听检索完成事件,异步执行评估,降级时跳过评估,通过配置开关控制。
设计文档大纲
# RAGAS EvalPlugin for WeKnora
## 1. 背景与动机
- WeKnora 无内建评估工具
- 竞品对比: Dify 有标注回复功能, LlamaIndex 有 RAGAS 集成
- 当前社区缺少 WeKnora 端到端 benchmark
- 参考: RAGAS 论文, deep-dive-rag-evaluation-methodology
## 2. 设计方案
### 2.1 EvalPlugin 架构
- 注册为 EventManager Plugin
- 监听 RerankCompleted 事件(检索完成时触发评估)
- 采样策略: 可配置采样率 (如 10% 的查询做评估)
### 2.2 评估指标
- Faithfulness: 答案忠实度
- Answer Relevancy: 答案相关性
- Context Precision: 上下文精确度
- Context Recall: 上下文召回率 (需要 ground truth)
### 2.3 评估结果存储
- 写入 WeKnora 的 wiki/ 巡检系统
- 低分查询触发知识库自维护的"覆盖缺口检测"
- 评估结果可导出为 JSON/CSV 供分析
### 2.4 集成 RAGAS
- 方案一: Go 调用 RAGAS Python 包 (CGo / subprocess)
- 方案二: 纯 Go 实现 RAGAS 四指标 (用 Go HTTP 调用 LLM)
- 推荐: 方案二, 避免 Python 依赖
关键代码修改点
1. 新增 internal/plugin/eval/ 包
type EvalPlugin struct {
sampler *Sampler // 采样器
evaluator *RAGASEvaluator
storage EvalStorage
sampleRate float64 // 采样率, 默认 0.1 (10%)
}
func (p *EvalPlugin) SubscribedEvents() []EventType {
return []EventType{"RerankCompleted"}
}
func (p *EvalPlugin) Handle(event Event) (Event, error) {
// 采样判断: 不是每次都评估
if !p.sampler.ShouldSample(p.sampleRate) {
return event, nil // 不评估, 原样传递
}
// 异步评估, 不阻塞检索流程
go func() {
result := p.evaluator.Evaluate(event.Ctx, EvalInput{
Query: event.Query,
Contexts: event.Chunks,
Answer: event.Answer,
})
p.storage.Save(event.Ctx, result)
}()
return event, nil
}
2. 新增 internal/eval/ragas.go
type RAGASEvaluator struct {
llmClient LLMClient
}
type EvalResult struct {
Query string
Faithfulness float64 // 0-1
AnswerRelevancy float64 // 0-1
ContextPrecision float64 // 0-1
ContextRecall float64 // 0-1 (需要 ground truth, 无 GT 时为 -1)
Timestamp time.Time
}
// Evaluate 端到端评估一次检索结果
// 核心设计: ContextRecall 需要 ground truth, 无 GT 时跳过
func (e *RAGASEvaluator) Evaluate(ctx context.Context, input EvalInput) EvalResult {
result := EvalResult{Query: input.Query, Timestamp: time.Now()}
// Faithfulness: 检查答案每个 claim 是否被 context 支持
result.Faithfulness = e.evaluateFaithfulness(ctx, input)
// Answer Relevancy: 答案是否回答了问题
result.AnswerRelevancy = e.evaluateAnswerRelevancy(ctx, input)
// Context Precision: 检索的 chunks 中相关比例
result.ContextPrecision = e.evaluateContextPrecision(ctx, input)
// Context Recall: 需要ground truth, 无GT时标记 -1
if input.GroundTruth != "" {
result.ContextRecall = e.evaluateContextRecall(ctx, input)
} else {
result.ContextRecall = -1
}
return result
}
3. 扩展 Wiki 巡检——评估结果反馈
修改 internal/wiki/inspector.go,新增”低分查询触发覆盖缺口检测”:
// 在巡检流程中增加: 查询评估结果中 Faithfulness < 0.6 的
// 说明知识库在该主题上覆盖不足, 触发覆盖缺口检测
func (i *WikiInspector) DetectCoverageGaps(ctx context.Context) []Gap {
lowScoreQueries := i.evalStorage.QueryByFaithfulness(ctx, 0.6)
var gaps []Gap
for _, q := range lowScoreQueries {
gaps = append(gaps, Gap{
Topic: q.Query,
Reason: fmt.Sprintf("Faithfulness=%.2f", q.Faithfulness),
Suggestion: "补充相关文档或更新过时内容",
})
}
return gaps
}
测试策略
| 测试层级 | 内容 |
|---|---|
| 单元测试 | RAGASEvaluator 四个指标的计算逻辑 |
| 单元测试 | 采样器的采样率控制 |
| 集成测试 | EvalPlugin 注册到 EventManager 后的完整流程 |
| 对比测试 | 与 Python RAGAS 包的评估结果对比(误差 < 5%) |
| 端到端测试 | 构造中文 QA 数据集,跑完整评估流水线 |
关键测试用例:构造一个”幻觉答案”的 case(答案包含 context 中不存在的信息),验证 Faithfulness < 0.5。
预期影响与工作量
| 维度 | 估算 |
|---|---|
| 代码行数 | 约 1000-1500 行(Go) |
| 涉及文件 | 新增 eval/ + plugin/eval/ + 修改 wiki/ + config/ |
| 工作量 | 3-4 周 |
| 对项目的影响 | 填补 WeKnora 评估空白,极高维护者需求 |
| 风险 | RAGAS 指标的 Go 实现与 Python 版本一致性 |
RAGAS 四指标详解和消融实验设计见 RAG 评估方法论,WeKnora 复合 Rerank 的评估含义见该文第 4 节。
路径 D:知识图谱增强——LLM 增强 PMI 图谱
定位与类比
WeKnora 的知识图谱使用 PMI(点互信息)构建——纯统计方法,只看实体共现频率。PMI 的优势是不需要训练模型、不需要标注数据;劣势是”共现不等于相关”——两个实体在同一篇文档中出现,可能只是偶然。
类比:PMI 就像通过观察”谁和谁经常一起出现在同一张合照里”来推断人际关系。大多数时候这很有效——经常一起拍照的人大概率是朋友。但有时候两个陌生人恰好参加了同一场活动被拍进同一张照片,PMI 就会误判他们有关系。如果有一个”认识所有人”的社交达人(LLM),他能判断”这两个人虽然出现在同一张照片里,但一个是嘉宾一个是工作人员,不是朋友”——这就是 LLM 增强 PMI 的核心思路。
类比边界:LLM 不是万能的。对于专业领域(如特定行业的术语关系),通用 LLM 可能判断错误。PMI 的统计信号在大量数据下仍然可靠。
深度技术分析:PMI 的数学原理与局限
要理解为什么 LLM 增强 PMI 有价值,需要先理解 PMI 的数学原理。PMI(Pointwise Mutual Information,点互信息)衡量的是两个事件一起发生的概率比它们各自独立发生时高多少:
\[PMI(A, B) = \log_2\left(\frac{P(A,B)}{P(A) \cdot P(B)}\right)\]直觉解释:如果实体 A 和实体 B 完全独立出现,PMI=0;如果它们总是一起出现,PMI 为正值;如果它们倾向于不同时出现,PMI 为负值。WeKnora 使用 PMI 来决定图谱中两个节点之间是否应该建边——PMI 高于阈值的实体对会被连接。
PMI 的三大局限:
局限一:低频共现的噪声。如果实体 A 和实体 B 各自只出现了一次,而且恰好出现在同一篇文档中,PMI 会给它们一个非常高的值——因为 P(A,B) = P(A) = P(B),PMI 达到最大值。但实际上这只是一次偶然共现。这就好比两个人只被拍进过同一张照片,就说他们关系很紧密——显然不合理。
局限二:高频共现的稀释。有些实体非常常见(比如”服务器”、”数据库”),它们几乎会跟所有实体共现。PMI 虽然通过分母 P(A)*P(B) 做了归一化,但在语料不够大的时候,高频实体的 PMI 仍然偏高。这就像一个社交达人跟谁都会被拍进合照——仅凭共现频率无法判断他跟谁是真正的好友。
局限三:缺乏关系类型。PMI 只告诉你”这两个实体有关系”,但不告诉你”是什么关系”。因果关系(A 导致 B)和并列关系(A 和 B 都是 C 的子类)在 PMI 看来没有区别。然而在知识图谱检索中,关系类型非常重要——用户问”为什么 A 会发生”,你需要沿因果关系链(causes)追踪,而不是沿并列关系链追踪。
LLM 增强 PMI 的核心价值就在于弥补这三个局限:LLM 可以判断共现是否有语义基础(解决局限一和二),LLM 可以标注关系类型(解决局限三)。
双信号融合的调参策略
最终关系权重 = alpha * PMI_norm + (1-alpha) * LLM_score,其中 alpha 的选择决定了 PMI 和 LLM 的相对权重。
alpha 的选择不是固定的,需要根据数据特点调整:
- 语料规模大(>10万文档):PMI 统计信号更可靠,alpha 偏高(0.7-0.8)。大数据量下 PMI 的噪声被平滑了。
- 语料规模小(<1万文档):PMI 噪声大,LLM 的语义判断更可靠,alpha 偏低(0.3-0.5)。
- 专业领域语料:PMI 可能在专业术语上失效(因为训练语料中这些术语的共现模式与专业领域不同),alpha 应该偏低。
- 通用领域语料:PMI 和 LLM 都比较可靠,alpha 取中(0.5-0.6)。
实际操作中,建议用网格搜索(grid search)在验证集上搜索最优 alpha——选取一组已知正确关系的实体对作为验证集,调整 alpha 使验证集上的 F1 最高。
LLM 验证的 Prompt 工程关键点
LLM 验证的效果高度依赖 prompt 设计。以下是一些实践中的关键点:
关键点一:提供充分的共现上下文。不要只给 LLM 两个实体名——给它包含两个实体共现的原文片段。否则 LLM 只能凭常识判断,无法区分”数据库”和”备份”在一篇运维文档中是因果关系(数据库故障导致需要备份恢复)还是只是并列提到。
关键点二:要求结构化输出。让 LLM 输出 JSON 格式(关系类型 + 置信度 + 判断理由),而不是自由文本。结构化输出便于程序解析,也能减少 LLM 输出的不确定性。
关键点三:设置”不确定”选项。不要让 LLM 强制选择”有关系”或”没关系”——给它第三个选项”不确定”。当 LLM 输出”不确定”时,保留 PMI 原始值作为权重,不做 LLM 修正。
关键点四:批量验证降低成本。不要一条一条地调用 LLM——把 10-20 个实体对打包成一个 prompt,一次调用验证多条关系。这样可以把 API 调用成本降低到原来的十分之一。
图谱构建性能优化
LLM 验证是异步的,但不意味着不需要考虑性能。假设一个知识库有 5000 个实体,PMI 候选边可能有 10 万条——即使批量验证,也需要 5000-10000 次 LLM 调用。
优化策略:
- 按 PMI 值排序,优先验证高分边——PMI 最高的边最有可能是真实关系,先验证这些可以最快提升图谱质量
- 设置 PMI 阈值过滤——PMI 低于某个阈值的边直接丢弃,不需要 LLM 验证
- 缓存 LLM 结果——相同的实体对不重复验证
- 增量验证——新文档加入时只验证新增的候选边,不重新验证所有边
设计文档大纲
# LLM-Enhanced PMI Knowledge Graph
## 1. 背景与动机
- PMI 图谱的"共现≠相关"问题
- 低频共现对(PMI 噪声大)和高频共现但无语义关系的实体对
- 参考: GraphRAG (Microsoft), LLM-based Relation Extraction
## 2. 设计方案
### 2.1 PMI + LLM 双信号
- PMI 计算候选关系(现有逻辑不变)
- LLM 对 PMI 候选关系做二次验证
- 最终关系权重 = alpha * PMI + (1-alpha) * LLM_score
### 2.2 LLM 验证 Prompt
- 输入: 实体A, 实体B, 共现上下文
- 输出: 关系类型 + 置信度 (0-1)
- 关系类型: causes / enables / contradicts / related_to
### 2.3 增量更新
- 新文档加入时, PMI 增量更新(现有逻辑)
- LLM 验证异步执行, 不阻塞索引流程
- 验证结果更新图谱权重
### 2.4 图谱查询增强
- 支持关系类型过滤查询
- 支持推理链解释(为什么从A跳到B)
关键代码修改点
1. 新增 internal/retrieval/knowledge_graph/llm_verifier.go
type LLMVerifier struct {
client LLMClient
alpha float64 // PMI 权重, 默认 0.6
}
type Relation struct {
EntityA string
EntityB string
Type string // causes / enables / contradicts / related_to
Confidence float64 // 0-1
PMI float64
FinalScore float64 // alpha*PMI_norm + (1-alpha)*LLM_score
}
func (v *LLMVerifier) Verify(ctx context.Context, entityA, entityB string, cooccurrenceContext string) (Relation, error) {
prompt := fmt.Sprintf(
"判断以下两个实体之间是否存在有意义的语义关系。\n"+
"实体A: %s\n实体B: %s\n上下文: %s\n"+
"如果有关系, 输出关系类型(causes/enables/contradicts/related_to)和置信度(0-1)。\n"+
"如果只是偶然共现, 输出 NONE 和置信度 0。",
entityA, entityB, cooccurrenceContext,
)
resp, err := v.client.Chat(ctx, prompt)
if err != nil {
return Relation{Type: "unknown", Confidence: 0.5}, nil // 降级
}
return parseRelation(resp), nil
}
2. 修改 internal/retrieval/knowledge_graph/builder.go
在图谱构建流程中增加 LLM 验证步骤:
func (b *GraphBuilder) BuildWithVerification(ctx context.Context, pmiEdges []PMIEdge) error {
for _, edge := range pmiEdges {
// 现有: 直接用 PMI 值作为边权重
// 新增: LLM 验证 + 双信号融合
relation, err := b.verifier.Verify(ctx, edge.EntityA, edge.EntityB, edge.Context)
if err != nil {
continue // 验证失败, 保留 PMI 边
}
// 双信号融合
pmiNorm := normalizePMI(edge.PMI) // 归一化到 0-1
finalScore := b.verifier.alpha*pmiNorm + (1-b.verifier.alpha)*relation.Confidence
if finalScore > b.config.EdgeThreshold {
b.graph.AddEdge(edge.EntityA, edge.EntityB, Edge{
Weight: finalScore,
Type: relation.Type,
Verified: true,
})
}
}
return nil
}
3. 增强图谱查询——支持关系类型过滤
修改 internal/retrieval/knowledge_graph/retriever.go:
type GraphQuery struct {
Entities []string
MaxHops int
RelationType string // 新增: 可按关系类型过滤
}
func (r *GraphRetriever) Traverse(ctx context.Context, query GraphQuery) ([]GraphNode, error) {
// ... 现有 BFS 逻辑 ...
// 新增: 关系类型过滤
if query.RelationType != "" {
neighbors = filterByRelationType(neighbors, query.RelationType)
}
return results, nil
}
测试策略
| 测试层级 | 内容 |
|---|---|
| 单元测试 | LLMVerifier.Verify() 的 prompt 和解析逻辑 |
| 单元测试 | 双信号融合公式的正确性 |
| 对比测试 | PMI-only vs PMI+LLM 图谱的查询质量(同一查询集) |
| 评估测试 | 知识图谱路的 Context Recall 变化 |
| 人工评估 | 抽样验证 LLM 标注的关系类型是否正确 |
关键测试用例:构造一个”高频共现但无语义关系”的实体对(如”服务器”和”午饭”在同一篇运维日志中共现),验证 PMI-only 会建立边但 PMI+LLM 不会。
预期影响与工作量
| 维度 | 估算 |
|---|---|
| 代码行数 | 约 800-1200 行(Go) |
| 涉及文件 | 新增 llm_verifier.go + 修改 builder.go + retriever.go + config/ |
| 工作量 | 3-4 周 |
| 对检索质量的影响 | 图谱路 Context Precision 预期提升 15-20% |
| 风险 | LLM 验证增加图谱构建时间;专业领域 LLM 判断不准 |
PMI 图谱构建的完整技术分析见 精读: WeKnora 的”第三路:知识图谱推理检索”章节,Graph-augmented RAG 模式见 Agentic RAG 模式 的”模式 3”。
路径组合策略
五条路径不是孤立选择的——它们之间存在协同关系,组合实现能产生更大的竞赛价值。
推荐组合一:路径 C + 路径 A(评估驱动的 Agentic RAG)
先做路径 C(评估框架),为 WeKnora 建立评估基础设施。然后做路径 A(Agentic RAG),用评估框架量化多跳查询分解的效果提升。这样你的贡献不仅有”做了什么”,还有”证明它有效”——这是评审最看重的。
推荐组合二:路径 C + 路径 D(评估驱动的图谱增强)
先做路径 C,再做路径 D。用评估框架量化 LLM 增强 PMI 图谱对检索质量的提升。路径 D 的改动更局部(仅涉及 knowledge_graph/ 包),风险比路径 A 低。
推荐组合三:路径 E + 路径 B(多模态 + 分块优化)
路径 E 改 docreader(Python),路径 B 改 chunking(Go)。两者在分块逻辑上天然衔接——E 产生更好的 chunk 输入,B 优化 chunk 边界。且技术栈互补,展示你的全栈能力。
不推荐的组合:
- 路径 A + 路径 D:两者都涉及
internal/retrieval/,修改范围重叠严重,冲突风险高 - 路径 B + 路径 C:两者都改 chunking + wiki,虽然不冲突但工作量叠加太大
路径间的技术依赖关系详见 Agentic RAG 模式 的”模式演进光谱”。
路径 E:多模态支持——图像/表格理解增强
定位与类比
WeKnora 的文档解析引擎 docreader/ 目前对 PDF 中的图片和表格理解有限。PaddleOCR-VL 可以提取表格为 HTML,但 HTML 表格直接送入分块管线会导致分块效果差(issue #1725 就报了这个问题)。
类比:你读一篇论文,里面有一个实验结果表格。如果你只把表格的每个数字当作独立文本,RAG 检索到”95.3%”时完全不知道这是哪个实验的哪个指标。一个聪明的读者会把表格理解为一个整体:”这是一组对比实验,方法 A 准确率 95.3%,方法 B 准确率 92.1%”——然后基于这个理解来回答问题。
类比边界:多模态理解依赖 VLM(视觉语言模型),模型质量和推理成本是硬约束。不是所有表格都需要 VLM 理解——结构简单的表格用规则提取即可。
深度技术分析:表格理解的技术难点
表格理解看似简单——把 HTML 转成 Markdown 不就行了?但实际操作中有大量技术细节:
难点一:合并单元格。HTML 表格中的 colspan 和 rowspan 在 Markdown 中没有直接对应。一个跨 3 列的表头在 Markdown 中怎么表示?常见做法是把合并单元格拆成多个相同内容的单元格,但这会丢失”这是一个合并表头”的语义信息。
难点二:嵌套表格。有些文档中的表格内部还嵌套了小表格(比如一个单元格里有子弹列表)。Markdown 完全不支持嵌套表格,必须展平或拆分。
难点三:超大表格的检索问题。一个 50 行的表格如果作为一个 chunk,RAG 检索时可能因为太长而被截断;如果按行拆分,每一行缺少列头信息,检索到的”95.3%”完全不知道指的是哪一列。WeKnora 当前就遇到了这个问题(issue #1725)。
解决超大表格的核心思路是”按行组拆分,每组保留列头”——把 50 行的表格按语义分组(比如每 5-10 行一组),每组前面加上列头行。这样每个 chunk 既有足够的信息量,又不会太长。
但”按语义分组”本身也是个难题——如何判断哪些行属于同一组?简单策略是固定行数分组;更好的策略是根据表格内容的语义断点分组(比如当某一列的值发生跳变时分组)。
图片理解的技术难点
图片理解比表格理解更复杂,因为图片的多样性远高于表格:
类型一:流程图。流程图的关键信息是步骤的顺序和分支条件。VLM 需要理解箭头的方向、判断框的条件、流程的起止点。
类型二:架构图。架构图的关键信息是组件之间的关系(调用、依赖、数据流)。VLM 需要识别不同类型的框(服务、数据库、队列)和它们之间的连线。
类型三:截图。截图通常是软件界面的抓取。关键信息是界面上的文字和布局。VLM 需要 OCR 能力来提取文字,但 OCR 已经在 docreader 中实现了。
类型四:照片。照片中可能包含实物场景,这类图片在技术文档中较少见,但在产品文档中可能出现。VLM 需要描述照片中的关键对象和场景。
不同类型的图片需要不同的 VLM prompt——流程图要问”这个流程的步骤顺序是什么”,架构图要问”这个架构有哪些组件、它们怎么连接”。一个通用的”请描述这张图片”prompt 效果不好。
实现时可以先用一个分类器判断图片类型,再根据类型选择专门的 prompt。分类器可以是简单的规则(比如检测图片中是否有箭头形状),也可以用 VLM 做分类。
多模态 Chunk 的检索策略
三种 chunk(text / table / image)统一参与 RRF 融合,但它们的检索特性很不同:
- text chunk:直接做 BM25 + 向量检索,这是 WeKnora 已有的逻辑
- table chunk:BM25 对 Markdown 表格效果还行(因为关键词明确),但向量检索对表格效果较差(因为表格的语义密度高,一句话可能包含大量信息)
- image chunk:BM25 完全依赖 VLM 生成的描述——如果描述不准确,检索就失败。向量检索也是基于描述文本的 embedding
这意味着 image chunk 的检索质量上限取决于 VLM 的描述质量。如果 VLM 把一个流程图描述为”一个包含多个方框和箭头的图片”(太笼统),那任何查询都检索不到这个图片。如果 VLM 描述为”用户注册流程:填写表单 → 邮箱验证 → 创建账户”,那”用户注册”和”邮箱验证”都能检索到。
因此,VLM 的 prompt 设计是路径 E 中最关键的技术决策——它直接决定了图片信息的检索可达性。
设计文档大纲
# Multi-modal Understanding Enhancement for WeKnora
## 1. 背景与动机
- Issue #1725: PaddleOCR-VL 表格 HTML 影响分块效果
- 图片中的信息(流程图、架构图、截图)完全丢失
- 参考: GPT-4V / Qwen-VL / LLaVA 多模态理解
## 2. 设计方案
### 2.1 表格理解
- PaddleOCR-VL 提取表格 HTML → 转换为 Markdown 表格
- 大表格(>10行)按行组拆分, 每组保留列头
- 表格摘要: 用 LLM 生成表格的一句话摘要, 作为 chunk 的上下文
### 2.2 图片理解
- 检测文档中的图片(已有 PaddleOCR 支持)
- 用 VLM 生成图片描述
- 图片描述作为独立 chunk 参与检索
- 保留图片→原文的映射关系
### 2.3 多模态 chunk 结构
- 新增 chunk_type 字段: text / table / image
- table chunk: markdown 表格 + 摘要
- image chunk: VLM 描述 + 原图引用
- 检索时三种 chunk 统一参与 RRF
### 2.4 VLM 集成
- 支持多种 VLM 后端: Qwen-VL / GPT-4V / 本地模型
- 统一接口: VLMClient.Describe(image) → string
- 降级: VLM 不可用时跳过图片理解
关键代码修改点
1. 修改 docreader/ 的表格处理
在 docreader/processors/table_processor.py 中增加 HTML→Markdown 转换和摘要生成:
class TableProcessor:
def __init__(self, llm_client=None):
self.llm_client = llm_client
def process(self, table_html: str, context: str = "") -> TableChunk:
# Step 1: HTML → Markdown
md_table = self.html_to_markdown(table_html)
# Step 2: 大表格拆分
if self.row_count(md_table) > 10:
sub_tables = self.split_by_row_groups(md_table)
else:
sub_tables = [md_table]
# Step 3: LLM 生成摘要(可选)
summary = ""
if self.llm_client:
summary = self.generate_summary(md_table, context)
return TableChunk(
markdown=md_table,
summary=summary,
sub_tables=sub_tables,
)
def generate_summary(self, table_md: str, context: str) -> str:
prompt = (
f"用一句话总结以下表格的关键信息。保留具体数字。\n"
f"上下文: {context}\n表格:\n{table_md}"
)
return self.llm_client.chat(prompt)
2. 新增 docreader/processors/image_processor.py
class ImageProcessor:
def __init__(self, vlm_client=None):
self.vlm_client = vlm_client
def process(self, image_bytes: bytes, context: str = "") -> ImageChunk:
description = ""
if self.vlm_client:
description = self.vlm_client.describe(image_bytes, context)
else:
description = "[图片](VLM 未启用,无法生成描述)"
return ImageChunk(
description=description,
has_vlm=self.vlm_client is not None,
)
3. 修改 internal/chunking/ 支持多模态 chunk 类型
在 Go 后端的 chunk 结构中增加类型字段:
type ChunkType string
const (
ChunkTypeText ChunkType = "text"
ChunkTypeTable ChunkType = "table"
ChunkTypeImage ChunkType = "image"
)
type Chunk struct {
ID string
Content string
Type ChunkType // 新增
Metadata map[string]interface{}
// table 特有
TableSummary string
// image 特有
ImageRef string // 原图引用
}
测试策略
| 测试层级 | 内容 |
|---|---|
| 单元测试 | HTML→Markdown 转换正确性 |
| 单元测试 | 大表格拆分逻辑 |
| 集成测试 | 图片→VLM 描述→chunk→检索完整流程 |
| 对比测试 | 有/无 VLM 描述的图片检索召回率 |
| 评估测试 | 表格类问题的 Answer Relevancy 提升 |
关键测试用例:构造一个包含流程图的 PDF,验证 VLM 生成的描述能被检索到并正确回答”这个流程的第一步是什么”。
预期影响与工作量
| 维度 | 估算 |
|---|---|
| 代码行数 | 约 600-1000 行(Python + Go) |
| 涉及文件 | docreader/processors/ + internal/chunking/ + config/ |
| 工作量 | 2-3 周 |
| 对检索质量的影响 | 含图表文档的检索质量显著提升(量化需 benchmark) |
| 风险 | VLM 调用成本高;不同 VLM 质量差异大 |
Issue #1725 是此路径的直接触发点,多模态 RAG 的行业趋势见 RAG 知识库全景。
6. 从零到 PR 的完整流程
这一节覆盖从 fork 仓库到 PR 被合入的全流程。细节决定成败——很多初学者不是技术不行,而是流程不对导致 PR 被忽略。
6.1 Fork 与本地部署
# 1. Fork 仓库(GitHub 页面点 Fork 按钮)
# 2. Clone 你 fork 的仓库
git clone https://github.com/YOUR_USERNAME/WeKnora.git
cd WeKnora
# 3. 添加上游仓库(保持同步)
git remote add upstream https://github.com/Tencent/WeKnora.git
# 4. Docker 全栈部署(首次可能需要 20-40 分钟拉镜像)
cd docker
cp .env.example .env
# 编辑 .env: 填入 LLM API Key, 选择向量数据库后端
docker compose up -d
# 5. 验证部署
docker compose ps # 所有服务应该 running
curl http://localhost:8080/api/v1/health # 应返回 200
# 打开 http://localhost:3000 看前端页面
常见部署问题和解决方案:
| 问题 | 原因 | 解决 |
|---|---|---|
| Milvus 启动失败 | 内存不足(至少需 4G) | 换 pgvector 后端,资源需求低 |
| docreader 报错 | Python 依赖缺失 | pip install -r docreader/requirements.txt |
| LLM API 超时 | 网络问题 | 检查 API Key 和网络代理配置 |
| 前端白屏 | Node 版本不对 | nvm use 18 切到正确版本 |
6.2 创建 Feature Branch
# 始终从最新的 main 创建分支
git fetch upstream
git checkout main
git merge upstream/main
# 创建 feature 分支(命名规范很重要!)
# fix: 修 bug
# feat: 新功能
# docs: 文档
# refactor: 重构
git checkout -b feat/multi-hop-query-decomposition
# 或者修 bug
git checkout -b fix/shared-space-role-search-undefined
分支命名规范:观察仓库已有 PR 的命名风格。WeKnora 目前观察到的前缀有 fix: / feat: / docs:。不要用 feature/ 或 bugfix/ 这种冗长形式——跟着项目现有风格走。
6.3 开发与本地验证
# Go 后端开发
cd internal/retrieval/decomposer # 假设你在做路径 A
go mod tidy # 拉依赖
go test ./... # 跑已有测试, 确保没有破坏
go test -v -run TestDecompose # 跑你的新测试
# 前端开发
cd frontend
npm install
npm run dev # 开发服务器
npm run test # 前端测试
npm run lint # 代码风格检查(必须过!)
# Python 开发(docreader)
cd docreader
pip install -e .
pytest tests/
6.4 提交代码
# 每个提交做一件事
git add internal/retrieval/decomposer/decomposer.go
git commit -m "feat: add query decomposer for multi-hop retrieval
- Add Decomposer struct with LLM-based query decomposition
- Support fallback to single query when decomposition fails
- Add configuration for max_hops and enabled flag
Refs: #issue-number"
# 推送到你的 fork
git push origin feat/multi-hop-query-decomposition
提交信息规范:
| 规则 | 正确 | 错误 |
|---|---|---|
| 前缀 | feat: add... |
add... |
| 时态 | add(祈使句) |
added / adds |
| 范围 | feat(retrieval): add decomposer |
feat: 修改了很多东西 |
| 长度 | 标题 < 50 字符 | 标题写 3 行 |
| 关联 | Refs: #1633 |
不提 issue |
6.5 创建 Pull Request
PR 描述模板(WeKnora 没有 PR 模板,但观察已有 PR 的写法):
## 变更说明
简要描述做了什么、为什么做、怎么做。
## 关联 Issue
Fixes #1633
## 变更类型
- [ ] Bug 修复
- [x] 新功能
- [ ] 重构
- [ ] 文档
## 测试
- [x] 单元测试通过
- [x] 手动验证
- [ ] 截图/GIF 演示(UI 变更必须附)
## 自查清单
- [x] 代码风格符合项目规范(gofmt / eslint)
- [x] 不包含敏感信息(API Key / 密码)
- [x] 不包含无关变更(一个 PR 做一件事)
6.6 Code Review 应对
| 情况 | 做法 |
|---|---|
| 维护者要求修改 | 尽快修改并 push,不要关闭 PR 重开 |
| 有冲突 | git fetch upstream && git rebase upstream/main |
| 长时间无响应 | 在 PR 下友善地 ping(间隔 > 3 天) |
| 被拒绝 | 问清楚原因,不要灰心,改了再提 |
| 建议”换个方式实现” | 先在 PR 里讨论方案,达成一致后再改 |
6.7 Code Review 中的常见反馈模式
理解维护者通常给什么反馈,可以提前规避,减少 review 轮次:
反馈一:”请增加降级策略” 所有涉及 LLM 调用的功能,维护者都会问”LLM 不可用时怎么办”。提前在代码中写好降级逻辑(如 fallback 到原始行为),并在 PR 描述中说明。
反馈二:”请补充测试” WeKnora 的测试覆盖率可能不完善,但新代码必须有测试。提交 PR 前自查:至少有正常路径测试、边界条件测试、错误路径测试各一个。
反馈三:”请遵循现有命名规范”
Go 社区有明确的命名规范:缩写全大写(HTTP 而非 Http)、接口名以 er 结尾(Retriever、Evaluator)、布尔变量以 is/has/can 开头。提交前检查命名是否符合规范。
反馈四:”请拆分为更小的 PR” 如果一个 PR 修改了超过 5 个文件,维护者可能要求拆分。每个 PR 做一件事——一个 bug 修复、一个功能添加、一次重构。
反馈五:”这个功能需要配置开关” 新功能默认不应该改变现有行为。维护者通常要求新功能有一个配置开关,默认关闭。这样即使新功能有 bug,也不会影响现有用户。
6.8 PR 合入后的跟踪
PR 合入不是终点——你需要在合入后持续关注:
- 监控是否引入新 bug——合入后几天内关注 issue 列表,看是否有人报告与你相关的问题
- 跟进后续优化——维护者可能在你 PR 的基础上做进一步优化,了解他们的改动思路
- 在社区中建立声誉——合入的 PR 是你最好的名片,后续提新方案时维护者更容易信任你
往年参与者的具体 PR 经验见 往年经验帖。
7. Go 语言新手注意事项
WeKnora 后端约 800+ 个 Go 文件,对 Go 新手来说理解成本不低。这里列出 WeKnora 代码库中常见的 Go 模式——不是 Go 语法教程,而是”读 WeKnora 源码时你会反复遇到什么”。
7.0 读 Go 源码的方法论
在讲具体模式之前,先说方法论。Go 代码的阅读策略和其他语言不同——Go 没有 class 继承,没有泛型(1.18 之前),代码组织主要靠 package 和 interface。
第一步:从 main.go 开始。cmd/ 下的 main.go 是程序入口,它会告诉你系统启动时做了哪些初始化——哪些 Plugin 被注册、哪些配置被加载、哪些服务被启动。
第二步:找 interface 定义。Go 的 interface 通常定义在包的根文件中(如 retrieval.go),具体实现放在子目录中(如 bm25/、vector/、knowledge_graph/)。先读 interface 理解抽象,再读实现理解细节。
第三步:追踪数据流。从 QueryReceived 事件开始,追踪一个查询如何经过各个 Plugin,最终返回结果。在代码中搜索 Emit("QueryReceived") 找到事件触发点,搜索 SubscribedEvents() 找到所有订阅者。
第四步:读测试。Go 的测试文件(*_test.go)是理解代码行为的最好文档。好的测试用例覆盖了典型输入、边界条件、错误处理。WeKnora 的测试可能不完善,但已有的测试仍然很有参考价值。
推荐阅读顺序(按难度递增):
| 包 | 预计阅读时间 | 核心要理解的东西 |
|---|---|---|
cmd/main.go |
30 分钟 | 系统启动流程 |
internal/config/ |
1 小时 | 配置结构和管理方式 |
internal/retrieval/bm25/ |
2 小时 | 最直观的检索实现 |
internal/retrieval/vector/ |
2 小时 | 向量检索的 Go 实现 |
internal/retrieval/fusion/ |
2 小时 | RRF 融合逻辑 |
internal/eventmanager/ |
3 小时 | 事件总线的核心设计 |
internal/chunking/ |
3 小时 | 4 级自适应分块 |
internal/rerank/ |
2 小时 | 复合 Rerank |
internal/retrieval/knowledge_graph/ |
4 小时 | PMI 图谱构建和查询 |
internal/wiki/ |
3 小时 | 自维护 Wiki 巡检 |
7.1 错误处理:if err != nil 是你的日常
Go 没有 try-catch。错误处理是每个函数调用后的 if err != nil。WeKnora 代码库中这种模式无处不在:
result, err := someFunction()
if err != nil {
return fmt.Errorf("failed to do X: %w", err) // 用 %w 包装, 保留错误链
}
新手常见错误:忽略错误返回值。在 WeKnora 中,永远不要用 _ 忽略 error——即使你觉得不会出错。维护者会在 review 时要求你处理。
7.2 接口隐式实现
WeKnora 的 Plugin 系统基于 Go 的接口隐式实现——不需要 implements 关键字,只要你的 struct 实现了接口的所有方法,它就自动满足接口。
// 接口定义(在 eventmanager 包中)
type Plugin interface {
Name() string
SubscribedEvents() []EventType
Handle(event Event) (Event, error)
Priority() int
}
// 你的实现(在另一个包中)
type MyPlugin struct{}
func (p *MyPlugin) Name() string { return "my-plugin" }
func (p *MyPlugin) SubscribedEvents() []EventType { return []EventType{"QueryReceived"} }
func (p *MyPlugin) Handle(event Event) (Event, error) { /* ... */ }
func (p *MyPlugin) Priority() int { return 0 }
// MyPlugin 自动满足 Plugin 接口, 无需声明
var _ Plugin = (*MyPlugin)(nil) // 编译期接口检查(推荐写法)
7.3 goroutine 和 channel
WeKnora 大量使用 goroutine 做并发。三路检索并行就是用 goroutine 实现的。
// 并行执行三路检索
func (r *Retriever) Retrieve(ctx context.Context, query string) ([]Result, error) {
var wg sync.WaitGroup
results := make(chan []Result, 3) // 带缓冲 channel
// BM25
wg.Add(1)
go func() {
defer wg.Done()
bm25Result := r.bm25.Search(query)
results <- bm25Result
}()
// Vector
wg.Add(1)
go func() {
defer wg.Done()
vectorResult := r.vector.Search(query)
results <- vectorResult
}()
// Graph
wg.Add(1)
go func() {
defer wg.Done()
graphResult := r.graph.Traverse(query)
results <- graphResult
}()
wg.Wait()
close(results)
// 收集结果
var allResults []Result
for r := range results {
allResults = append(allResults, r...)
}
return allResults, nil
}
新手坑:
| 错误 | 正确 |
|---|---|
忘记 wg.Wait() 导致 goroutine 泄漏 |
用 defer wg.Done() 配合 wg.Wait() |
| 不用 channel 直接写共享变量 | 用 channel 通信,或用 sync.Mutex 保护 |
| goroutine 里 panic 导致整个程序崩溃 | 用 recover() 捕获 panic |
7.4 context.Context 贯穿始终
Go 的 context.Context 是请求级别的生命周期管理。WeKnora 中几乎所有函数的第一个参数都是 ctx context.Context。
// 用 ctx 控制超时和取消
ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()
result, err := retriever.Search(ctx, query)
if err == context.DeadlineExceeded {
// 超时处理
}
新手容易忽略 ctx 的传递——在调用链中间”断掉” ctx 会导致超时机制失效。WeKnora 维护者会严格检查 ctx 是否正确传递。
7.5 结构体标签(struct tag)
WeKnora 大量使用 struct tag 做配置映射和 JSON 序列化:
type Config struct {
Enabled bool `yaml:"enabled" json:"enabled"`
MaxHops int `yaml:"max_hops" json:"maxHops"`
Threshold float64 `yaml:"threshold" json:"threshold"`
}
新增配置项时必须同时写 yaml 和 json tag——前者用于配置文件,后者用于 API 响应。忘写 tag 会导致配置不生效,而且不会报错(Go 的坑)。
7.6 依赖管理:Go Modules
go mod tidy # 清理未使用的依赖
go mod vendor # 生成 vendor 目录(部分项目需要)
go sumdb # 校验依赖完整性
WeKnora 使用 Go Modules。新增外部依赖时:先考虑是否真的需要——WeKnora 维护者对引入新依赖非常谨慎,会要求你说明为什么不能用标准库或项目内已有实现。
7.7 代码风格与 Lint
# 必须通过
gofmt -w . # 格式化
go vet ./... # 静态分析
golangci-lint run ./... # 综合lint(WeKnora可能已配置)
# 推荐通过
go test -race ./... # 竞态检测
go test -cover ./... # 覆盖率
PR 提交前必须过 gofmt 和 go vet。golangci-lint 如果项目有配置文件则必须全部通过。
7.8 Go 测试模式
WeKnora 使用 Go 标准库的 testing 包做测试。理解以下测试模式对贡献至关重要:
Table-driven 测试——Go 社区最推荐的测试模式,WeKnora 代码库中大量使用:
func TestDecomposer(t *testing.T) {
tests := []struct {
name string
query string
wantN int
wantErr bool
}{
{"简单查询", "WeKnora支持哪些向量数据库", 1, false},
{"对比查询", "BM25和向量检索的区别", 2, false},
{"空查询", "", 1, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := decomposer.Decompose(ctx, tt.query)
if (err != nil) != tt.wantErr {
t.Errorf("Decompose() error = %v, wantErr %v", err, tt.wantErr)
}
if len(got) != tt.wantN {
t.Errorf("Decompose() got %d queries, want %d", len(got), tt.wantN)
}
})
}
}
Mock 接口——Go 的 interface 天然支持 mock。在测试中用 mock 替换真实依赖:
// MockLLMClient 替换真实 LLM 调用
type MockLLMClient struct {
Response string
Err error
}
func (m *MockLLMClient) Chat(ctx context.Context, prompt string) (string, error) {
return m.Response, m.Err
}
// 在测试中使用 mock
func TestDecomposer_Fallback(t *testing.T) {
mockClient := &MockLLMClient{Err: errors.New("timeout")}
d := &Decomposer{llmClient: mockClient, enabled: true}
got, err := d.Decompose(ctx, "test query")
if err != nil {
t.Errorf("expected fallback, got error: %v", err)
}
if len(got) != 1 || got[0].Query != "test query" {
t.Errorf("expected fallback to original query, got %v", got)
}
}
子测试和并行测试——Go 支持 t.Parallel() 标记可并行执行的测试,加速测试套件执行:
func TestRetrieval(t *testing.T) {
tests := []struct{ name string; query string }{
{"simple", "test"},
{"complex", "compare A and B"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel() // 标记可并行
// 测试逻辑...
})
}
}
WeKnora 的测试覆盖率目前可能不完善——补测试本身就是高价值贡献。维护者非常欢迎”为现有代码补测试”的 PR。
7.9 WeKnora 代码库中的常见坑
在阅读和修改 WeKnora 源码时,以下是一些容易踩的坑:
坑一:internal/ 包的循环依赖
Go 不允许循环导入——包 A 导入包 B,包 B 就不能再导入包 A。WeKnora 的 internal/ 下有很多包,新增代码时很容易引入循环依赖。解决方法:把共享的类型定义提取到一个独立的 internal/types/ 包中,让其他包都依赖这个类型包。
坑二:goroutine 中的闭包捕获 在 for 循环中启动 goroutine 时,如果闭包捕获了循环变量,所有 goroutine 可能会使用同一个变量值。必须通过参数传递来解决:
// 错误:所有 goroutine 都用同一个 query
for _, query := range queries {
go func() {
result := search(query) // bug: query 可能总是最后一个值
}()
}
// 正确:通过参数传递
for _, query := range queries {
go func(q string) {
result := search(q)
}(query)
}
坑三:配置热更新 WeKnora 支持部分配置的热更新(不重启服务即可生效)。如果你新增了配置项,需要确认它是否需要支持热更新。如果需要,要在配置变更的监听逻辑中注册你的配置回调。
坑四:中文分词对 BM25 的影响 修改 BM25 相关代码时,要注意中文分词的特殊性。英文按空格分词很简单,但中文的”知识图谱”到底是一个词还是两个词,取决于分词器的词典。WeKnora 使用了自定义词典,修改 BM25 代码时不要破坏分词器的正常工作。
坑五:向量数据库的兼容性 WeKnora 支持 10 种向量数据库后端。如果你修改了向量检索相关代码,必须确保在所有 10 种后端上都能正常工作——至少在 pgvector 和 Milvus 两个最常用的后端上测试过。
8. 与 mentor 沟通策略
在犀牛鸟竞赛中,与项目导师的沟通质量直接影响你的 Proposal 是否被采纳、是否能入围。但沟通不是”越频繁越好”,而是”越精准越好”。
8.1 沟通节奏
类比:你去医院看病。最好的病人是:清楚描述症状、看过相关资料、准备好回答医生的问题。最差的病人是:什么准备都没有、问”我哪里不舒服你来猜”。
与 mentor 沟通也一样:
| 阶段 | 时机 | 沟通方式 | 内容 |
|---|---|---|---|
| 探索期 | 报名后 1 周 | Issue 评论 | “我对这个 issue 感兴趣,初步看了一下代码,原因可能是 X,方向对吗?” |
| 准备期 | 第一周 PR 后 | Issue 评论 / 邮件 | “我已经提交了 #xxx 修复这个 bug,另外我观察到 Y 这个问题,是否有计划解决?” |
| Proposal 期 | 7 月中 | 邮件 / Issue | 完整技术方案,包含设计文档 + 时间线 + 已有贡献证明 |
| 实战期 | 8 月 | PR Comment / 邮件 | 技术问题 + 进展同步(频率 < 每周 2 次) |
8.2 提方案的正确姿势
错误示范:
“我觉得 WeKnora 应该支持多跳查询分解,因为这样检索质量会更好。”
这个”方案”没有任何信息量——”会更好”好多少?怎么实现?多久能做出来?维护者看到这种模糊提议,第一反应是”又一个空想家”。
正确示范:
“我在使用 WeKnora 时发现,复杂查询(如对比类问题)的 Context Recall 只有约 0.45,远低于简单查询的 0.78。原因在于当前单轮检索无法覆盖对比查询的两个方面。
我提议新增查询分解层(QueryDecomposer),具体方案如下:
- 新增
internal/retrieval/decomposer/包,用 LLM 将复杂查询分解为子查询- 每个子查询独立走三路检索+RRF+Rerank
- 结果合并时做去重和可选二次 Rerank
- 降级策略:分解失败时 fallback 到原始单轮检索
预估工作量 3-4 周。我已经 fork 了仓库并跑通了本地部署,提了 #1633 的修复 PR。
完整设计文档:[链接]
请问这个方向是否与项目路线图一致?有什么需要调整的吗?”
区别在于:
| 维度 | 错误示范 | 正确示范 |
|---|---|---|
| 问题定义 | “会更好” | “Context Recall 从 0.45 到目标值” |
| 方案细节 | 无 | 文件级修改点 + 降级策略 |
| 可行性证明 | 无 | 已有 PR + 本地部署 |
| 工作量估算 | 无 | 3-4 周 |
| 请求方式 | 模糊建议 | 具体可评审的方案 |
8.3 常见沟通陷阱
| 陷阱 | 表现 | 替代做法 |
|---|---|---|
| “小作文” | 一封邮件写 2000 字 | 分段写,每段一个主题,先结论再展开 |
| “伸手党” | “这个怎么装?那个怎么跑?” | 先查文档和 Issue,提问时附上你试过什么 |
| “幽灵” | 提完 PR 后消失 2 周 | 每周至少同步一次进展 |
| “过度沟通” | 每天发 3 条消息问进度 | 汇总问题一次问,频率 < 每周 2 次 |
| “防御心态” | review 意见 = 攻击 | review 意见 = 免费的技术指导,认真学习 |
8.4 何时该问,何时不该问
该问:
- 方案方向是否与项目路线图一致(避免做无用功)
- 不确定的设计取舍(两三个方案各有利弊,需要维护者决策)
- PR 提交后 5 天以上无响应(友善地 ping)
不该问:
- 可以通过读源码/文档找到答案的问题
- 纯粹的语言/工具使用问题(”Go 怎么写泛型?”——这不是 mentor 的工作)
- 尚未尝试就问”能不能这样做”——先做再说
8.5 Proposal 写作实战
Proposal 是犀牛鸟竞赛中最重要的文档——它直接决定你是否能入围。一份好的 Proposal 需要包含三个核心部分:
第一部分:技术方案(约 60% 篇幅)
技术方案不是”我想做什么”,而是”我怎么做、为什么这样做、还有什么替代方案”。
标准结构:
- 问题定义——用数据说话。不是”检索质量不够好”,而是”在 XX 数据集上,复杂查询的 Context Recall 为 0.45,简单查询为 0.78”
- 方案设计——文件级修改点 + 关键数据结构 + 降级策略
- 替代方案对比——至少考虑 2 个替代方案,说明为什么选当前方案
- 风险与缓解——列出最大的 3 个风险及应对措施
第二部分:时间规划(约 20% 篇幅)
以周为单位,每周末有可验证的交付物。留 1 周 buffer。不要把计划排得太满——竞赛期间你一定会遇到意想不到的问题。
示例:
- W1-W2:实现核心逻辑 + 单元测试(交付:可运行的 feature branch)
- W3:集成测试 + 端到端测试(交付:通过所有测试)
- W4:性能优化 + 文档(交付:PR 就绪)
- W5:buffer + 修 review 意见
第三部分:开源经历(约 20% 篇幅)
列出你已经对 WeKnora 做的贡献——合入的 PR、参与的讨论、写的设计文档。这是证明你”已经准备好了”的最有力证据。没有这部分,Proposal 就是纸上谈兵。
8.6 与维护者沟通的特殊技巧
WeKnora 是腾讯 IMA 团队维护的开源项目,维护者可能有以下特点:
- 工作语言可能中英混合——技术讨论用英文更自然
- 响应时间可能较长——这是企业级开源项目的常态,不是对你有意见
- 对架构一致性非常敏感——你的方案必须和现有 EventManager+Plugin 架构保持一致
具体技巧:
- PR 比 issue 更有说服力——不要只在 issue 里讨论,直接提一个 WIP(Work In Progress)PR,让维护者看到代码
- 先小后大——先做一个小 PR 建立信任,再提大方案
- 参考已有 PR——看看维护者对其他 PR 的 review 意见,了解他们的代码偏好
- 中文社区互动——WeKnora 有微信群/企业微信群,在里面积极回答其他用户的问题也能积累信任
8.7 沟通中的文化差异与应对
开源项目的维护者可能来自不同文化背景,沟通风格差异很大:
风格一:直截了当型(常见于欧美维护者)。他们说”这个方案不好”就是真的觉得不好,不是在试探你。应对方式:不要觉得被冒犯,直接问”您觉得哪里有问题?有什么替代建议吗?”
风格二:委婉建议型(常见于亚洲维护者)。他们说”这个方案可以考虑一下其他方向”,实际上是在说”这个方案我不看好”。应对方式:主动追问,确认他们的真实意思,不要以为只是随意建议而忽略。
风格三:技术沉默型。有些维护者很少说话,但每次说话都很有分量。他们不回复不代表没看到——可能只是在思考。应对方式:给足够的时间(5-7 天),然后友善地 ping 一次。
无论哪种风格,有一条通用原则:永远不要在公开场合(GitHub issue/PR)与维护者争论。如果你不同意某个 review 意见,私下沟通(邮件/微信),或者在 PR 中用”请教”的语气表达你的想法——”我理解您的建议是 X,但我在想 Y 这个方向是否也可以考虑,因为 Z”。
8.8 从沟通中提取技术洞察
与维护者的每一次交流都是学习机会。维护者对你的方案提的每一个问题,都暗示了他们关心的技术维度。把这些维度记录下来,它们就是你写 Proposal 时必须覆盖的点。
例如,如果维护者问你”LLM 不可用时怎么办”,说明他们非常关心系统的健壮性——你的 Proposal 中必须有专门的”降级策略”章节。如果维护者问”这个功能会影响现有性能吗”,说明他们关心性能——你的 Proposal 中必须有性能评估。
维护者问的每一个问题,都是 Proposal 大纲的一个条目。这不是巧合——维护者的问题反映了项目的核心价值观。
9. 时间线规划——3 个月竞赛节奏
结合 选题策略 的时间线和 WeKnora 的贡献特性,以下是 3 个月的规划。
9.1 全局时间线
gantt
title 犀牛鸟竞赛 - WeKnora 贡献时间线
dateFormat YYYY-MM-DD
section Phase 1: 探索
Fork + 部署 :p1a, 2026-06-23, 2d
读源码核心包 :p1b, after p1a, 5d
提第一个前端 PR :p1c, after p1a, 3d
section Phase 2: 深入
选定深度路径 :p2a, 2026-07-01, 2d
写设计文档 :p2b, after p2a, 5d
实现核心逻辑 :p2c, after p2b, 10d
section Phase 3: Proposal
写 Proposal :p3a, 2026-07-15, 5d
联系导师 :p3b, after p3a, 3d
section Phase 4: 实战
完整实现 + 测试 :p4a, 2026-08-01, 15d
修 review 意见 :p4b, after p4a, 7d
写评估报告 :p4c, after p4b, 5d
9.2 逐周计划
Phase 1:探索与破冰(6/23 - 6/30)
| 天 | 任务 | 产出 |
|---|---|---|
| Day 1 | Fork + Clone + Docker 部署 | 本地跑通全栈 |
| Day 2 | 读 internal/eventmanager/ + internal/retrieval/ |
笔记:理解事件总线架构 |
| Day 3 | 读 frontend/ 结构,定位 #1633 对应组件 |
本地复现 bug |
| Day 4 | 修复 #1633,提 PR | 第一个 PR |
| Day 5 | 读 internal/chunking/ + internal/retrieval/knowledge_graph/ |
笔记:分块和图谱逻辑 |
| Day 6-7 | 选定深度路径(A-E 之一),写初步设计文档 | 设计文档初稿 |
Phase 2:深入贡献(7/1 - 7/15)
| 周 | 任务 | 产出 |
|---|---|---|
| W2 | 实现深度路径的核心逻辑 + 单元测试 | 核心代码 + 测试 |
| W3 | 集成测试 + 端到端测试 + 修 bug | 可运行的功能分支 |
| W3 | 准备 Proposal 材料 | Proposal 初稿 |
关键检查点:W2 结束时,核心逻辑应该能通过编译和单元测试。如果还做不到,说明方案太复杂或理解不够——需要调整范围。
Phase 3:Proposal 提交(7/15 - 7/31)
| 周 | 任务 | 产出 |
|---|---|---|
| W4 | 完善 Proposal + 联系导师 | 提交 Proposal |
| W5 | 继续 PR 贡献 + 社区活跃 | 2-3 个 PR 合入 |
Proposal 三要素(参考 往年经验帖):
- 技术方案:设计文档 + 代码修改范围 + 测试策略
- 时间规划:以周为单位的 8 周计划(留 1 周 buffer)
- 开源经历:已有的 PR 合入记录 + 设计文档
Phase 4:课题实战(8/1 - 9/10)
| 周 | 任务 | 产出 |
|---|---|---|
| W6-W7 | 完整实现 + 性能优化 | Feature branch 完成 |
| W8 | 修 code review 意见 + 文档 | PR 合入 |
| W9 | 评估报告 + 答辩准备 | 最终报告 |
9.3 风险预案
| 风险 | 概率 | 预案 |
|---|---|---|
| Docker 部署失败 | 中 | 先用 dev 模式跑单个服务,不全栈部署 |
| 第一个 PR 被拒 | 中 | 同时准备 2-3 个备选 issue |
| 深度路径实现遇到技术困难 | 高 | 降级到简单版本,先交核心功能再迭代 |
| 维护者响应慢 | 高 | 同时做 2 个方向,哪个先得到响应就推哪个 |
| Proposal 未入围 | 中 | 继续贡献,争取优秀贡献者证书 |
9.4 每阶段里程碑与验收标准
每个阶段结束时,你应该能回答”我做了什么”——这不仅是自我检查,也是 Proposal 和答辩时的核心素材。
Phase 1 验收标准(6/30 前):
- 本地全栈部署成功,能正常使用 WeKnora 的检索和对话功能
- 至少提交 1 个 PR(修 bug 或改善 UI)
- 精读了至少 2 个核心包(如
eventmanager/+retrieval/),有笔记 - 确定了深度贡献路径(A-E 之一)
Phase 2 验收标准(7/15 前):
- 深度路径的核心逻辑实现完毕,本地编译通过
- 单元测试覆盖率 > 60%
- 设计文档已写好,包含方案、替代方案、降级策略
- 与至少 1 位项目维护者有过技术交流
Phase 3 验收标准(7/31 前):
- Proposal 已提交
- 累计至少 2-3 个 PR 合入
- 设计文档获得维护者正面反馈
Phase 4 验收标准(9/10 前):
- Feature PR 已合入(或已被批准待合入)
- RAGAS 评估结果可用(如果有路径 C)
- 最终报告已提交,包含量化效果数据
9.5 时间管理的常见陷阱
陷阱一:”先学完再动手” 这是最常见的错误。Go 不需要”学完”才能开始——边做边学,遇到不懂的语法再查。你不需要理解 Go 的所有特性才能修一个前端 bug。
陷阱二:”一个方向钻到底” 如果你选的深度路径在第二周发现行不通(比如 LLM prompt 质量不稳定),不要继续死磕。及时切换到路径 C 或 B,风险更低。
陷阱三:”完美主义” PR 不需要完美才能提。先提一个 WIP PR,让维护者看到方向是否正确,再逐步完善。等”完美”了再提,可能已经错过了竞赛窗口。
陷阱四:”只做代码不做文档” 竞赛评估不只看代码——还看你的理解深度和表达能力。每完成一个功能,花 1-2 小时写技术文档。这 1-2 小时的投入在答辩时回报巨大。
10. 检索管线”贡献地图”
想改哪一段,就去读哪个目录:
flowchart LR
A[想改 RRF 融合] --> B["internal/retrieval/fusion/"]
C[想改 Rerank] --> D["internal/rerank/"]
E[想改 Wiki 自维护] --> F["internal/wiki/"]
G[想改分块策略] --> H["internal/chunking/"]
I[想改知识图谱] --> J["internal/retrieval/knowledge_graph/"]
K[想改前端 UI] --> L["frontend/"]
M[想改文档解析] --> N["docreader/"]
O[想加 MCP 能力] --> P["mcp-server/"]
11. 与 DB-Agent-Memory 的关系
WeKnora 是 RAG 引擎(”Agent 查什么”),DB-Agent-Memory 是记忆引擎(”Agent 记什么”)。两者同属腾讯犀牛鸟开源项目,但定位完全不同:
- WeKnora:从外部文档中检索知识,批量更新,共享知识库
- DB-Agent-Memory:从对话交互中提炼记忆,实时更新,per-user 个性化
两者重叠区域在于”知识图谱”——WeKnora 从文档构建图谱,DB-Agent-Memory 在 L1 层提炼原子事实。潜在冲突:Agent 从对话学到的事实 vs 文档记录不一致时谁优先?
完整决策树见 B 线 - Memory 与 RAG 边界 腾讯生态分工见 B 线 - 腾讯记忆生态
12. 双赛道策略建议(WeKnora vs DB-Agent-Memory)
| 维度 | WeKnora | DB-Agent-Memory |
|---|---|---|
| 语言 | Go(高门槛)+ React+TS(中低门槛) | TypeScript(中门槛) |
| 新手引导 | 无 CONTRIBUTING / 无 good-first-issue | 待确认 |
| 竞争 | 14.3K star,竞争大 | 相对较新,竞争可能小 |
| 推荐切入 | 前端 bug/UI,逐步深入 | 核心 TypeScript 模块 |
| 策略 | 快速贡献,积累信任,争取深度 issue | 直接参与核心设计 |
结论:若竞赛允许双项目,可以 WeKnora 前端做”快速出成果”,DB-Agent-Memory 做”深度贡献”并行推进。
12.1 双赛道并行的时间分配
如果选择双赛道并行,时间分配是关键。以下是一个可行的分配方案:
| 时间段 | WeKnora 投入 | DB-Agent-Memory 投入 | 说明 |
|---|---|---|---|
| W1-W2 | 70% | 30% | 先把 WeKnora 前端 PR 快速做出来 |
| W3-W4 | 40% | 60% | 转向 DB-Agent-Memory 核心贡献 |
| W5-W8 | 30% | 70% | 侧重 DB-Agent-Memory,WeKnora 维持小 PR |
这个分配的逻辑是:WeKnora 前端是”快钱”——快速出 PR 建立存在感;DB-Agent-Memory 是”长线投资”——核心 TypeScript 模块的深度贡献在竞赛评估时更有分量。
12.2 两项目的协同效应
WeKnora 和 DB-Agent-Memory 的贡献经验可以互相转化:
- 在 WeKnora 中理解了 RAG 管线 → 在 DB-Agent-Memory 中理解”Agent 什么时候需要查外部知识”
- 在 WeKnora 中学了 Go 的并发模式 → 理解 TypeScript 的 async/await 更容易(概念同源)
- 在 DB-Agent-Memory 中理解了”记忆分层” → 在 WeKnora 的 Wiki 自维护中理解”知识也需要分层管理”
Memory 与 RAG 的边界详见 B 线 - Memory 与 RAG 边界,腾讯内部记忆生态的分工见 B 线 - 腾讯记忆生态。
13. 风险与诚实评估
- 无 good-first-issue、无 CONTRIBUTING.md — 新手完全缺乏官方引导
[Issue] - 252 个 open issues — 但大量是 [Question](用户使用问题),真正可贡献的 bug/enhancement 约 30-40 个
- 活跃贡献者已占位 — 已有 15 个 open PR,热门 issue 可能被抢
- 前端重构 —
client/到frontend/(Vue 到 React+TS),旧 issue 描述可能与当前代码不匹配 - Go 核心门槛高 — 想做检索/图谱/Rerank 需通读约 800 个 Go 文件
- LLM 调用成本 — 路径 A/B/D/E 都需要 LLM 调用,API Key 成本需评估
- 降级策略是硬要求 — 所有涉及 LLM 的功能都必须有降级方案,否则维护者不会接受
13.1 风险的优先级排序与应对
不是所有风险都一样重要。按”发生概率 x 影响程度”排序:
| 优先级 | 风险 | 概率 | 影响 | 应对 |
|---|---|---|---|---|
| 最高 | Go 后端门槛高,读不懂代码 | 高 | 致命 | 先从前端 PR 入手,Go 代码一天读一个包 |
| 最高 | 维护者不响应 PR | 中 | 致命 | 同时准备 2-3 个 PR,不把鸡蛋放一个篮子 |
| 高 | 深度路径技术方案不可行 | 中 | 高 | 每周评估进展,不行就切换路径 |
| 高 | LLM API 成本超预算 | 中 | 中 | 用 DeepSeek / Ollama 降低成本 |
| 中 | 前端重构导致 issue 失效 | 中 | 低 | 提 PR 前先确认 issue 在新代码中仍存在 |
| 低 | 竞争者抢了你想做的 issue | 低 | 低 | 做深度路径不会被抢——那是你提出的新方案 |
13.2 技术债务与隐性成本
除了上述显性风险外,WeKnora 作为大型开源项目还有一些隐性成本需要考虑:
隐性成本一:代码库的文档缺口。WeKnora 的代码注释和文档可能不完善——很多设计决策只在代码中体现,没有文档说明。这意味着你需要花大量时间阅读源码来理解”为什么要这样设计”,而不能只看文档。
隐性成本二:配置项的复杂性。WeKnora 的配置文件选项非常多(向量数据库后端、LLM 提供商、分块策略、Rerank 模型等),每个选项组合可能产生不同的行为。调试问题时,首先要排除配置问题——”你的配置对吗”是最常见的排查起点。
隐性成本三:测试数据的准备。做检索质量评估需要测试数据集——标准的问答对+上下文。WeKnora 可能没有提供标准的评估数据集,你需要自己构造。构造一个高质量的评估数据集本身就是一项不小的工程——至少需要 50-100 个问答对,覆盖简单查询、复杂查询、对比查询等不同类型。
隐性成本四:PR 排队时间。WeKnora 的维护者可能同时在处理多个 PR。你的 PR 从提交到被 review 可能需要 3-7 天,从 review 到合入可能又需要 3-7 天。这个等待时间在做时间规划时必须考虑进去。
13.3 诚实评估:我是否应该选 WeKnora
选择 WeKnora 作为竞赛项目之前,诚实地问自己:
你满足以下至少两个条件吗?
- 有基本的 Go 语言经验(至少写过 HTTP server 或 CLI 工具)
- 有基本的 React+TypeScript 经验(至少用过 hooks 和状态管理)
- 理解 RAG 的基本概念(向量检索、embedding、chunking)
- 对信息检索或 NLP 有兴趣,愿意深入学
- 每周能投入至少 15 小时
如果只满足 0-1 个条件:WeKnora 可能不是最佳选择,考虑 Dify(Python)或 TDesign(前端) 如果满足 2-3 个条件:WeKnora 是合理选择,从前端 PR 入手逐步深入 如果满足 4-5 个条件:WeKnora 是好选择,可以直接挑战路径 A 或 D
14. 总结:从第一天到第一个深度 PR 的行动清单
以下是从零开始的完整行动路径,按天排列。严格执行这个清单,3 个月内你一定能做出有说服力的贡献。
Day 1-2:环境搭建
- Fork + Clone WeKnora
- Docker Compose 部署,确保全栈可运行
- 配置 LLM API Key,测试检索和对话功能
Day 3-4:源码精读
- 读
cmd/main.go了解启动流程 - 读
internal/eventmanager/理解事件总线 - 记笔记:用自己的话写出 EventManager 的工作原理
Day 5-7:第一个 PR
- 在 Issue 列表中选择 #1633 或 #1565
- 在 Issue 下留言表明贡献意向
- 本地定位 bug,修复,提 PR
- PR 描述写清楚 why(根因分析)
Day 8-14:深度路径选择 + 设计文档
- 精读与目标路径相关的核心包
- 写设计文档初稿(问题定义 + 方案 + 替代方案 + 降级策略)
- 在 Issue 或邮件中向维护者提出方案,征求意见
Day 15-28:核心实现
- 实现核心逻辑 + 单元测试
- 每周末评估进展:是否在正轨上?是否需要切换路径?
- 修之前 PR 的 review 意见
Day 29-35:Proposal 提交
- 完善 Proposal 三部分(技术方案 + 时间规划 + 开源经历)
- 联系导师,展示已有贡献和设计文档
- 提交 Proposal
Day 36-80:课题实战
- 完整实现深度路径
- 用 RAGAS 评估效果(如果有路径 C)
- 修 code review 意见
- 写最终报告
关键心态:
- 不要等”准备好了”再开始——边做边学
- 不要追求完美——先做到”能用”,再做到”好用”
- 不要一个人闭门造车——持续与维护者和社区互动
- 不要只做代码——文档、测试、评估同样是高价值贡献
14.1 答辩准备的关键要点
竞赛最终的答辩环节是你展示成果的窗口。以下是答辩准备中容易忽略但非常重要的几个点:
要点一:量化你的贡献。不要说”我优化了检索质量”——说”我将复杂查询的 Context Recall 从 0.45 提升到 0.72,提升幅度 60%”。数字是最有说服力的证据。因此在实现过程中就要注意收集数据——每次改进前后都要跑评估,记录指标变化。
要点二:展示你的思考过程。评审不只看结果,还看你怎么得到结果的。遇到问题时你怎么分析的?考虑了哪些替代方案?为什么选择了当前方案?这些思考过程体现了你的技术深度。在设计文档和日常笔记中记录这些思考,答辩时才能讲清楚。
要点三:诚实面对不足。没有任何方案是完美的。如果你主动说出”这个方案在 XX 场景下效果不好,原因是 YY,未来可以通过 ZZ 改进”,评审会认为你对问题有深入理解——这比回避问题要好得多。
要点四:用演示代替纯文字。一个 3 分钟的现场演示比 10 页幻灯片更有冲击力。提前准备好演示环境,确保网络稳定、数据就绪、功能可操作。同时准备一个演示视频作为备选——万一现场出了问题,可以播放视频。
要点五:与开源社区的协作故事。竞赛不只是技术比拼,也是协作能力的展示。讲一讲你怎么与维护者沟通、怎么根据 review 意见修改、怎么帮助其他贡献者——这些故事让评审看到你不只是会写代码,还会合作。
读完你应能
- 说出犀牛鸟评审最看重的维度,并解释为什么”深度贡献”比 PR 数量更重要
- 画出 WeKnora 的 EventManager → Retriever → Reranker 管线,标注你计划修改的位置
- 在 Mac(无 Docker)环境下列出你可以执行的三类贡献操作
- 为路径 A-E 各写一句话的 PR 标题,体现你对对应模块的理解
- 解释 WeKnora 的 Go plugin 机制如何约束你的代码组织方式
15. 交叉引用
| 相关页面 | 关系 |
|---|---|
| 精读: WeKnora | 本篇的架构基础——EventManager/RRF/Rerank/分块的完整技术分析 |
| Agentic RAG 模式 | 路径 A 的理论基础——6 种 RAG 编排模式 |
| RAG 评估方法论 | 路径 C 的理论基础——RAGAS 四指标 + 消融实验设计 |
| RAG 知识库全景 | WeKnora 在 5 项目比较中的定位 |
| Agent 与 RAG 平台集成 | MCP 集成模式——WeKnora 如何被外部 Agent 调用 |
| RAG 平台选型决策树 | 什么场景选 WeKnora |
| Memory vs RAG 边界 | D 线(RAG)与 B 线(Memory)的分工与重叠 |
| 腾讯记忆生态 | WeKnora 在腾讯 DB-Agent-Memory 生态中的位置 |
| 选题策略 | 犀牛鸟竞赛全局策略 + 时间线 |
| 往年经验帖 | 评审标准 + Proposal 攻略 + 往届参与者经验 |
证据等级
| 来源 | 标签 |
|---|---|
gh issue list 2026-06-22 |
[Issue] |
| WeKnora 源码目录结构 | [源码] |
| deep-dive-weknora.md 架构分析 | [已有] |
| agentic-rag-patterns.md 六种模式 | [已有] |
| rag-evaluation-methodology.md RAGAS 指标 | [已有] |
| rag-knowledge-landscape.md 5 项目横评 | [已有] |
| rag-agent-integration.md MCP 集成 | [已有] |
| B 线 memory-vs-rag-boundary | [已有] |
| strategy.md 时间线 | [已有] |
| past-experience.md 评审标准 | [已有] |
| 未 Docker 实测 | 以源码/docs 为准 |