009、记忆模块(三):知识图谱的构建与检索增强

昨天深夜调一个对话Agent,遇到个典型问题:用户问“帮我推荐几个适合带孩子周末去的地方,要能学点知识”。系统检索了最近的几条对话记录,返回了“科技馆、自然博物馆”,看起来还行对吧?但用户紧接着补了一句“上次去过的那个恐龙展厅就别提了”。问题来了——系统完全没意识到,上周用户刚在聊天里提过“上周带孩子去了自然博物馆的恐龙展”。这种场景下,单纯的向量检索就像只记住了碎片化的对话快照,却理不清事件之间的关联。这时候,就该知识图谱上场了。

为什么向量检索不够用

很多刚做Agent的兄弟容易陷入一个误区:把所有记忆都塞进向量数据库,靠相似度搜索搞定一切。实际跑起来会发现,有些关系它根本抓不住。比如“用户上周去了自然博物馆”和“用户不想再去恐龙展”这两条信息,在向量空间里可能距离很远,但逻辑上紧密关联。纯向量检索容易漏掉这种跨句子的逻辑链条。

更麻烦的是时间线和事件演进。用户三个月前说“孩子在学钢琴”,上周说“钢琴课停了”,今天问“有什么音乐类的活动推荐”——理想的回答应该避开钢琴推荐,但很多系统会把三个月前的“钢琴”记录也拉出来,导致推荐失效。这种时间衰减和状态变更,向量检索处理起来很吃力。

知识图谱怎么在Agent里落地

别被“知识图谱”这个词吓到,在Agent的上下文里,我们不需要搞成企业级那种庞然大物。核心就三块:实体、关系、属性。拿刚才的场景举例:

# 简单示例,实际结构会更复杂些
memory_graph = {
    "entities": {
        "user": {"type": "person", "attributes": {"has_child": True}},
        "natural_museum": {"type": "location", "attributes": {"category": "museum"}},
        "dinosaur_exhibition": {"type": "event", "attributes": {"status": "visited"}}
    },
    "relations": [
        {"subject": "user", "predicate": "visited", "object": "dinosaur_exhibition", "timestamp": "2024-03-10"},
        {"subject": "dinosaur_exhibition", "predicate": "located_at", "object": "natural_museum"}
    ]
}

实际实现时,我习惯用两个存储:向量库存对话原文和嵌入,图数据库(或者简单点用内存结构)存实体关系。每次对话解析出实体后,先在图里查一遍关联上下文,再结合向量检索的结果做融合。这个融合策略是关键,后面会细说。

构建流程里的坑点

从非结构化文本抽实体关系,现在大模型确实好用,但别直接裸用。我吃过亏——早期让LLM每句话都输出JSON-LD格式的关系三元组,结果成本飙涨,还引入大量噪声。后来改成两级触发:先用规则判断这句话是否包含“状态变更”“时间绑定”“否定偏好”这类关键模式,只有命中时才调用关系抽取。成本降了七八成,准确率反而上去了。

时间处理是另一个暗坑。用户说“上周三”“过年那会儿”“下个月”,这些相对时间必须锚定到绝对时间戳。我有个固定工具函数专门干这个,每次对话开始先注入当前时间上下文。更重要的是,所有带时间的关系都要存两个版本:一个存原始表述(“上周三”),一个存计算后的绝对时间(“2024-03-06”)。后面做时间推理时省大事儿。

检索增强的实际配方

光有图还不够,怎么和现有向量检索结合才是重点。我的当前方案是个三层漏斗:

第一层,向量检索照常跑,从对话历史里捞相关片段。
第二层,从这些片段里提取实体名,去知识图谱里拉关联子图。
第三层,把子图转换成文本描述,和向量结果一起喂给LLM做合成。

def retrieve_with_graph(query, vector_db, graph_db, top_k=5):
    # 第一层:向量检索
    vector_results = vector_db.similarity_search(query, k=top_k*2)
    
    # 第二层:实体识别和子图提取
    entities = extract_entities(query + " " + " ".join([r.content for r in vector_results]))
    subgraph = graph_db.get_related_subgraph(entities, depth=2)
    
    # 第三层:构建增强上下文
    graph_context = describe_subgraph(subgraph)  # 把子图转成自然语言描述
    vector_context = "\n".join([r.content for r in vector_results[:top_k]])
    
    return f"{vector_context}\n\n背景知识:{graph_context}"

这里有个细节:子图转文本描述时,别简单罗列三元组。我最初用“(用户, 参观, 恐龙展)”这种格式,LLM理解起来有歧义。后来改成短句描述:“用户曾在2024年3月参观过恐龙展,该展览位于自然博物馆”。可读性好了,后续合成质量明显提升。

性能权衡的经验之谈

知识图谱听着高大上,但加进实时对话系统就得考虑延迟。我的经验是,别追求图谱的完备性——Agent记忆模块里的图,本质是个辅助检索的索引。几点实操建议:

  1. 实体数量控制住:只跟踪人、地点、产品、事件这些核心类型。别什么都往上加,否则维护成本吃不消。
  2. 关系类型预定义:根据场景定死十几种关系,比如“参观”“喜欢”“讨厌”“购买”“位于”。开放抽取容易乱套。
  3. 定期剪枝:用户三个月没提的实体,降到低优先级;半年没提的可以考虑归档。不然图越滚越大,检索速度下降明显。
  4. 缓存策略:用户当前会话里提取的实体关系,放内存缓存里,别每次都读写底层存储。

另外,知识图谱的构建质量极度依赖实体识别。如果你们团队有标注能力,建议在核心实体类型上微调一个小模型,比纯用通用NER准得多。我们自己在“产品型号”“技术术语”这些领域实体上微过,准确率从70%拉到92%,图谱噪声少了一大半。

最后聊聊落地心态

做这功能容易陷入完美主义陷阱——总想构建一个完美反映现实世界的知识图谱。但实际在Agent场景里,图谱只是记忆的辅助索引。它的核心价值不是“准确表示世界”,而是“帮助检索到相关记忆”。有时候,即使图谱里某个关系抽错了(比如把“讨厌”抽成“喜欢”),只要这个错误不影响当前对话的检索结果,其实可以容忍。当然,基础实体别错得太离谱。

另一个反直觉的点:知识图谱在简单场景里可能看不出优势,甚至显得累赘。但当对话轮次超过十轮、涉及多个实体状态变更时,效果差就显出来了。建议先在小规模复杂场景上验证价值,再决定投入多少资源。

最近在试的新方向是让图谱也支持向量检索——把子图结构编码成嵌入,和文本嵌入一起做多模态检索。初期效果还行,但推理成本上去了。技术总是这样,没有银弹,只有权衡。先跑通最简单可用的方案,再慢慢迭代,可能是大多数团队的最优路径。

下次聊聊记忆模块的另一个变种:如何用决策树管理用户偏好和对话状态。那个更偏业务逻辑,但和知识图谱配合好了,能让Agent显得“记性”特别好。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值