犀牛鸟 2026 研究笔记 estelledc.github.io

Ch14: 动手实验室——本地跑通 WeKnora 与最小 RAG

Part 6: 进阶专题 前置章节:Ch06: WeKnora 架构深度剖析 后续章节:Ch15: Memory 与 RAG 边界 返回目录:README


结论先行:读懂 RAG 最快的方式不是再读一章,而是 用 90 分钟跑通两条线:① Docker 拉起 WeKnora 完成一次文档问答;② 用 ~80 行 Python 实现「嵌入 + 向量检索 + LLM 生成」最小管线。后者帮你建立「WeKnora 里每个模块在干什么」的肌肉记忆。

实验 A:WeKnora 全栈(竞赛必做)

环境要求

资源 最低 推荐
内存 8 GB 16 GB
磁盘 20 GB 40 GB(含镜像)
LLM DeepSeek / OpenAI / Ollama 任一 API Key 本地 Ollama + Qwen2

步骤

# 1. 克隆(以官方仓库为准,分支可能更新)
git clone https://github.com/Tencent/WeKnora.git
cd WeKnora

# 2. 配置环境变量(示例:DeepSeek)
cp .env.example .env
# 编辑 .env:填入 LLM_API_KEY、EMBEDDING 相关配置

# 3. 启动全栈
docker compose up -d

# 4. 验证
# 浏览器打开 compose 中映射的 Web 端口(常见 8080/3000,以 README 为准)
# 创建知识库 → 上传一份 Markdown/PDF → 提问 → 检查引用来源

验收清单

常见问题

现象 可能原因 处理
容器 OOM 内存不足 关其他服务 / 调低 compose 内存限制
Embedding 失败 Key 或模型名错误 .env 与官方文档模型列表
中文乱码 文件编码 上传 UTF-8 Markdown 先试
检索无结果 分块未完成 等索引任务结束再问

更细的模块地图见 Ch06WeKnora 竞赛贡献指南 环境矩阵。


实验 B:80 行最小 RAG(理解原理)

不依赖 WeKnora,用 Python 复现核心链路:分块 → 嵌入 → 向量检索 → 拼 prompt → 调 LLM

"""minimal_rag.py — 教学用最小 RAG,非生产代码"""
import os
from openai import OpenAI

client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])

def chunk_text(text: str, size: int = 400, overlap: int = 80) -> list[str]:
    chunks, start = [], 0
    while start < len(text):
        chunks.append(text[start : start + size])
        start += size - overlap
    return chunks

def embed(texts: list[str]) -> list[list[float]]:
    resp = client.embeddings.create(model="text-embedding-3-small", input=texts)
    return [d.embedding for d in resp.data]

def cosine(a, b):
    import math
    dot = sum(x * y for x, y in zip(a, b))
    na = math.sqrt(sum(x * x for x in a))
    nb = math.sqrt(sum(x * x for x in b))
    return dot / (na * nb + 1e-9)

def retrieve(query: str, chunks: list[str], vectors: list[list[float]], k=3):
    qv = embed([query])[0]
    scored = sorted(
        zip(chunks, vectors),
        key=lambda x: -cosine(qv, x[1]),
    )
    return [c for c, _ in scored[:k]]

def answer(query: str, context_chunks: list[str]) -> str:
    context = "\n\n---\n\n".join(context_chunks)
    resp = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[
            {"role": "system", "content": "仅根据给定上下文回答;不知道就说不知道。"},
            {"role": "user", "content": f"上下文:\n{context}\n\n问题:{query}"},
        ],
    )
    return resp.choices[0].message.content

if __name__ == "__main__":
    doc = open("sample.md", encoding="utf-8").read()
    chunks = chunk_text(doc)
    vectors = embed(chunks)
    q = "WeKnora 的三路检索是哪三路?"
    hits = retrieve(q, chunks, vectors)
    print(answer(q, hits))

实验 B 延伸任务

任务 对应 WeKnora 模块 预计时间
把固定分块改成按 \n\n 段落切 自适应分块 30 min
加 BM25(rank_bm25 库)与向量结果 RRF 融合 hybrid_search + RRF 1 h
打印 top-3 相似度分数,观察「相关但不够」的 chunk Rerank 动机 20 min
用 RAGAS 或人工检查 faithfulness Ch08 评估 1 h

实验 C:对照 WeKnora 行为(可选)

同一文档、同一问题,分别在 实验 B 脚本WeKnora UI 中提问,记录:

对比项 最小 RAG WeKnora
召回 chunk 内容    
是否含 BM25/图谱路 仅向量 三路 + RRF
答案是否带引用 需自实现 原生
延迟    

写 5 行结论:WeKnora 相对最小 RAG 多做了哪三步——这可直接用于 proposal 或 PR 描述中的「问题理解」。


与竞赛路径的衔接

完成实验后 下一步
实验 A 通过 WeKnora 实战 case-1785 → patterns → #1248
实验 B 延伸 2 完成 考虑 hybrid/RRF 相关 open issue
实验 C 有笔记 写入 evidence-inventory 作为「已理解 RAG 管线」证据

章末自检


延伸阅读