RAG 这个词很容易被说得太大。对 StarryRAG 来说,它没有试图让模型“记住整个互联网”,只做了一件比较具体的事:从个人博客文章里找出几段相关内容,把它们放进提示词,再让模型据此回答。
完整链路可以压缩成:
博客页面
-> 抓取和正文提取
-> 文本切分
-> Embedding
-> Qdrant 检索
-> RRF 重排
-> LLM 流式回答每一步都可能影响最终答案。模型本身只是最后一环。
1. 先把博客页面变成干净文本
知识库入口是 sitemap 和 RSS。爬虫发现 URL 后,项目会用 Jsoup 读取页面,再优先选择文章正文区域。页面导航、评论区和站点脚注不应该混进知识库,否则模型会把菜单文字当成文章内容。
当前博客文章的路径统一使用 archives 规则:
site:
base-url: https://starryv.top
include-patterns:
- ".*/archives/.*"这条规则看起来简单,但它解决了一个实际问题:sitemap 里可能同时有标签页、分页页和文章页。如果不先过滤,导入结果会膨胀,召回内容也会变杂。
网站 URL 还要做归一化。站点生成的链接有时带有 80 端口,而 HTTPS 反代实际监听 443。抓取器会去掉不必要的端口,避免同一篇文章产生多个来源地址。
2. 切分不是越碎越好
当前默认切分参数是:
chunk:
size: 600
overlap: 100600 和 100 不是模型的硬性要求,而是针对博客正文的折中:
块太大,检索命中后会带入很多无关内容。
块太小,标题和解释容易被拆开,模型看不到完整上下文。
保留 100 个字符重叠,可以减少边界处的信息断裂。
代码块、列表和段落不一定适合完全相同的切分策略。文章里如果有一大段 Java 代码,按字符切开后,单独检索到的片段可能无法编译,也很难解释。后续如果文章类型增加,可以给代码块和正文分别设计切分规则。
3. 召回阶段只负责找候选
用户提问后,RetrievalService 先把问题编码成向量,再向 Qdrant 查询最多 8 个结果:
retrieve:
top-k: 8
min-score: 0.0这里的 top-k 是候选数量,不是最终一定会引用 8 篇文章。后续会做重排,最终提示词里仍然只保留有限的上下文。
当前默认关闭 HyDE:
hyde:
enabled: falseHyDE 的做法是先用一个非流式模型,根据用户问题生成一段“假设性文档”,再对假设文档做 Embedding 检索。它有机会改善抽象问题的召回,但会多一次 LLM 请求和一次向量化请求。对个人博客问答来说,首响应延迟往往比这一点召回收益更明显,所以先关闭更实际。
如果确实遇到“用户的问法和文章表达差别很大”的问题,可以在服务器环境变量里打开 HyDE,再比较召回结果,而不是默认给每个请求都增加一轮调用。
4. RRF 解决什么问题
向量相似度擅长找语义接近的内容,但对专有名词、类名和路径不一定稳定。比如用户问:
项目里的 QdrantEmbeddingStoreImpl 做了什么?
这时,文本中出现的类名本身很有价值。
StarryRAG 使用内置 RRF,把向量排序和简单词法排序合并。它不依赖额外的重排模型,成本低,也容易排查:
向量排序:按相似度排名
词法排序:按查询词在候选文本中的命中情况排名
RRF:把两个排名转换成分数后合并如果配置 Cohere,也可以切换到 Cohere 重排。但在默认配置下,RRF 已经足够支撑这个项目,至少不会因为又增加一个外部接口而让排障链条变长。
5. 来源要进入上下文,也要返回给前端
只在提示词里告诉模型“请引用来源”不够。模型有时会忘记,或者把 URL 写错。因此项目做了两层处理。
第一层是在上下文中显式写入来源:
- Source: https://starryv.top/archives/example
- Content: 文章片段内容第二层是在 SSE 的 done 事件中返回来源数组:
{
"type": "done",
"sources": [
"https://starryv.top/archives/example"
]
}前端把来源数组渲染到回答底部,用户可以直接打开文章。即使模型没有在正文中写链接,回答仍然有出处。
来源 URL 还需要做协议校验。widget 只允许 http 和 https,避免把未经处理的字符串直接当成链接插入页面。
6. 不相关时要承认没有找到
RAG 系统最容易犯的错,不是偶尔答错,而是明明没有检索到内容,却让模型继续编。
系统提示词要求模型只根据参考资料回答。如果候选为空,项目会进入规则兜底。兜底回答的内容来自 resources/knowledge 和 rules.json,而不是临时猜测。
这条边界看起来保守,但个人博客场景不适合让助手替博主补全不存在的经历。回答短一点没关系,来源和事实边界要清楚。
7. RAG 调优应该看哪几个指标
不要只看“感觉回答更聪明了”。可以记录:
首个 token 的延迟。
检索接口耗时。
候选片段的来源和相似度。
最终回答是否使用了正确文章。
空召回比例。
缓存命中比例。
用户是否点击了参考文章。
这些数据能帮助区分问题到底在 Embedding、切分、重排,还是生成模型。否则调参很容易变成“换一个模型再试试”。