

今天的工作核心不是让模型“能说”,而是让它具备两种工程级品质:
这两个品质合在一起,才让系统从“像在聊天”升级为“像在思考”。
📌 一句话总结:RAG 的价值不只是“回答”,而是“可验证的回答”。
单纯向量检索擅长“语义相似”,但对“硬关键词、标题、编号、任务名”并不总是稳定;反过来,全文检索擅长“字面命中”,却容易错过同义改写。
因此采用 双路并行召回:
检索从“押题”变成“覆盖面更全的候选集生成”。
混合检索的关键不只是“两路都搜”,而是“如何合并”。今天采用 RRF(Reciprocal Rank Fusion):不纠结两路分数的量纲差异,而是看排名共识,让“多路都靠前”的结果自然上浮。
核心逻辑(只保留核心公式):
def rrf(rank: int, k: int = 60) -> float:
return 1.0 / (k + max(1, rank))

日记类内容有一个天然结构:日期是主索引。仅靠语义召回容易被“主题相近但日期不同”的内容干扰。
因此把日期线索当作“硬锚点”,在召回阶段优先注入与日期匹配的候选片段,确保面对“某天发生了什么”这类问题时能直达目标内容。

没有证据链,RAG 容易退化成:
因此在回答完成后输出一个结构化 sources 列表(Top 3~5),每条至少包含:
content/snippet:片段摘要(便于快速预览)filename:来源文件名score:融合后的最终得分url/path:可定位原文的位置或链接这让“回答质量”从主观体验变成可以被客观检查的事实。
把一次请求拆解为多个阶段(历史、改写、Embedding、检索、生成),记录耗时与检索模式(例如 hybrid / keyword-only)。
今天进一步确认了一个适合个人博客的“双螺旋分工”:
职责边界清晰后,迭代会更快:后端专注“更准”,前端专注“更懂用户”。

flowchart LR
U[用户问题] --> H[拉取最近对话历史]
H --> R[Query Rewrite]
R -->|Keyword| K[FTS / Keyword 召回]
R -->|Embedding| E[Embedding 服务]
E --> V[Vector 召回]
K --> F[RRF 融合排序]
V --> F
F --> A[日期锚点注入/置顶]
A --> C[构建上下文 Context Window]
C --> L[LLM 流式生成]
A --> S[提取 Top3~5 sources\ncontent/filename/score/path/url]
L --> OUT[输出 Answer Stream]
S --> OUT
OUT --> LOG[记录日志\nretrieved_context + latency + 模式]