☰
LangChain 时间加权检索:让‘最近的事‘更容易被想起
2026/10/12 6:02:43 网站建设 项目流程
个人主页: > for_ever_love__ <(欢迎各位大佬莅临😊)
其他栏目: > 大模型开发从0到1 <
其他栏目: > iOS项目总结大全 <
其他栏目: > 我想学python了 <
其他栏目: > iOS UI <

文章目录

  • LangChain 时间加权检索:让"最近的事"更容易被想起
    • 一、什么场景需要时间加权
    • 二、核心公式
      • 2.1 decay_rate 怎么理解
    • 三、上手
    • 四、两个必须设置的 metadata 字段
    • 五、importance:给重要记忆加权
    • 六、实战:给 Agent 做长期记忆
    • 七、和普通检索的对比
    • 八、常见坑
    • 九、什么时候别用它
    • 十、小结

LangChain 时间加权检索:让"最近的事"更容易被想起

人的记忆有个特点:刚发生的事记得最清楚,越久远越模糊。
但向量检索是"一视同仁"的——三年前的文档和昨天的文档,
只要语义相近就一样容易被召回。

有些场景这是错的。这篇讲TimeWeightedVectorStoreRetriever怎么用、
它的公式是什么意思,以及哪些场景真的需要它。

一、什么场景需要时间加权

场景需不需要
企业制度文档库(新旧版本共存)需要(新版本优先)
新闻/公告检索需要(最新的更重要)
对话记忆(Agent 记住用户说过的话)非常需要
技术手册、API 文档不太需要(新旧都有效)
历史档案查询不需要(就是要查老的)

判断标准很简单:“更近的是不是更重要”。

二、核心公式

score = 语义相似度 + 时间权重 时间权重 = decay_rate ^ hours_passed

展开一点:

最终分数 = (1 - 时间因素权重) × 语义分 + 时间因素权重 × recency_score recency_score = exp(-decay_rate × hours_passed)

其中hours_passed是"这条内容被访问/创建到现在过了多少小时"。

2.1 decay_rate 怎么理解

decay_rate控制衰减有多快:

decay_rate含义半衰期
0.1很慢约 7 小时
0.01默认,适中约 69 小时(近 3 天)
0.001很慢约 29 天
0.5极快约 1.4 小时

半衰期 ≈ ln(2) / decay_rate 小时。

选值经验:

  • 对话记忆(几分钟到几小时):0.05 ~ 0.1,让旧对话快速淡出;
  • 新闻/公告(以天计):0.005 ~ 0.01;
  • 企业文档(以月计):0.0005 ~ 0.001。

三、上手

fromlangchain_community.vectorstoresimportFAISS# 或其他fromlangchain.retrieversimportTimeWeightedVectorStoreRetrieverfromlangchain_openaiimportOpenAIEmbeddingsfromlangchain_core.documentsimportDocumentimportdatetime embeddings=OpenAIEmbeddings()vectorstore=FAISS.from_texts(["初始文本"],embeddings)retriever=TimeWeightedVectorStoreRetriever(vectorstore=vectorstore,decay_rate=0.01,# 衰减速率k=4,# 返回条数other_score_keys=["importance"],# 可选的额外打分维度)# 添加带时间的文档docs=[Document(page_content="用户说他叫小明",metadata={"last_accessed_at":datetime.datetime.now(),"created_at":datetime.datetime.now()},),Document(page_content="用户喜欢喝咖啡",metadata={"last_accessed_at":datetime.datetime.now()-datetime.timedelta(days=3),"created_at":datetime.datetime.now()-datetime.timedelta(days=3)},),]retriever.add_documents(docs)results=retriever.invoke("用户叫什么")

四、两个必须设置的 metadata 字段

这个 retriever依赖两个时间字段,缺了会报错:

字段含义
created_at文档创建时间
last_accessed_at最后一次被访问的时间

last_accessed_at会在文档被检索到时自动更新——
这就是关键设计:被反复想起的记忆会"保鲜",
像人一样,经常回忆的事不容易忘。

# 内部行为(简化)defget_relevant_documents(query):docs=vectorstore.similarity_search(query,k=self.k*10)fordindocs:# 被命中就刷新访问时间d.metadata["last_accessed_at"]=datetime.now()# 按时间加权重排...

五、importance:给重要记忆加权

除了时间,还可以给每条记忆一个"重要度":

retriever=TimeWeightedVectorStoreRetriever(vectorstore=vectorstore,decay_rate=0.01,k=4,other_score_keys=["importance"],)retriever.add_documents([Document(page_content="用户说他叫小明",metadata={"last_accessed_at":datetime.datetime.now(),"created_at":datetime.datetime.now(),"importance":0.9,# 名字很重要},),Document(page_content="用户提到今天天气不错",metadata={"last_accessed_at":datetime.datetime.now(),"created_at":datetime.datetime.now(),"importance":0.1,# 闲聊不重要},),])

最终分数 = 语义 × (1-w) + 时间 × w + importance 加成。

典型用法:

内容importance
用户姓名、身份、偏好0.8 ~ 1.0
用户提出的具体需求0.6 ~ 0.8
一般对话0.1 ~ 0.3

六、实战:给 Agent 做长期记忆

这是它最典型的应用场景。

importdatetimefromlangchain.retrieversimportTimeWeightedVectorStoreRetrieverfromlangchain_community.vectorstoresimportFAISSfromlangchain_openaiimportOpenAIEmbeddings,ChatOpenAIfromlangchain_core.documentsimportDocument embeddings=OpenAIEmbeddings()vs=FAISS.from_texts(["记忆库初始化"],embeddings)memory=TimeWeightedVectorStoreRetriever(vectorstore=vs,decay_rate=0.05,k=5,other_score_keys=["importance"],)defremember(content:str,importance:float=0.5):now=datetime.datetime.now()memory.add_documents([Document(page_content=content,metadata={"created_at":now,"last_accessed_at":now,"importance":importance},)])defrecall(question:str)->str:docs=memory.invoke(question)return"\n".join(d.page_contentfordindocs)# 对话中remember("用户叫小明,是一名后端工程师",importance=0.9)remember("用户在做一个 RAG 项目",importance=0.7)remember("用户说今天有点累",importance=0.2)# 几轮之后print(recall("用户是做什么的?"))# → "用户在做一个 RAG 项目"(近期 + 重要度中等,排前面)

相比ConversationBufferMemory(全量保留历史)的优势:

  • 上下文不会无限增长;
  • 只召回和当前问题相关的记忆;
  • 旧的、不重要的记忆会自然淡出。

七、和普通检索的对比

# 同样的文档,一个是一年前的,一个是昨天的old=Document("项目用 PostgreSQL",metadata={...一年前...})new=Document("项目改用 MySQL 了",metadata={...昨天...})# 普通检索:问"项目用什么数据库"# → 可能两条都召回,甚至先召回 PostgreSQL(语义更接近"数据库")# 时间加权:问"项目用什么数据库"# → MySQL 排前面(更新)

注意:它不会把旧文档过滤掉,只是降权。
如果你要的是"只要最新的版本",
那应该用 metadata 过滤(version == latest),
而不是时间加权——别用错了工具。

八、常见坑

现象原因解法
报缺少last_accessed_atmetadata 没设这两个字段添加时务必带上
时间加权没效果decay_rate 太小调到 0.01 以上试试
全被新文档霸占decay_rate 太大调小
想过滤掉旧版本用错工具了应该用 metadata 过滤
结果里出现"初始化文本"建库时的占位内容没删建库后清空,或用真实内容初始化

最后一个坑很容易中:FAISS.from_texts(["初始文本"])里的
"初始文本"会一直留在库里。建议初始化后清掉,
或者用第一条真实记忆来初始化。

九、什么时候别用它

情况理由
文档价值与时间无关白白增加复杂度
需要精确过滤"最新版本"该用 metadata 过滤
数据量很小直接全量给模型就行
用 FAISS 之外的库部分库的时间字段处理有差异,先验证

它不是必需品。大部分 RAG 场景用普通向量检索 + metadata 过滤就够了。
只有"记忆"类场景(对话记忆、新闻流)才真正需要它。

十、小结

  • 时间加权解决"越近越重要"的排序需求,
    公式:语义分 + 时间权重,时间权重按decay_rate ^ hours衰减;
  • decay_rate:对话记忆 0.05~0.1,新闻 0.005~0.01,企业文档 0.0005~0.001;
  • metadata 必须带created_at和last_accessed_at,
    后者会在命中时自动刷新(常被想起的记忆更"保鲜");
  • 可选的importance字段让重要记忆不被时间冲淡;
  • 典型场景:Agent 长期记忆(替代全量历史,避免上下文膨胀);
  • ⚠️它只降权不过滤,要"只要最新版"请用 metadata 过滤。

下一篇讲元数据过滤与多租户隔离——生产环境怎么保证 A 公司看不到 B 公司的数据。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询