向量库标签自己变了
InMemoryVectorStore 的标签在外部编辑后写回库内,检查共享的嵌套 metadata;入库与结果注释分别隔离,保留正式更新和过滤规则。
文档已加入 LangChain 的 InMemoryVectorStore,应用只在检索结果的 metadata['tags'] 列表里加了一项,再读库时却发现原文档也带上了它。没有调用更新方法时,可以检查入库文档与返回结果是否共享了嵌套元数据对象。
这条路径有 langchain-core 1.6.9 的反馈,已核对的正式 1.6.10 仍直接保存 doc.metadata,读取时也没有为嵌套列表或字典建立完整隔离。不能仅凭版本号增大,就认为元数据已自动深拷贝。
看起来是新 Document,里面却仍共享对象
InMemoryVectorStore 的同步、异步入库方法保存文档的文本、向量与元数据。元数据并不是一份独立的深拷贝;调用方继续原地修改同一份嵌套列表,可能直接改变库内状态。
按 ID 读取、相似度检索和过滤回调会构造 Document,但新对象不代表其中每一层容器都是新的。返回结果里的 tags 列表、嵌套的统计字典,仍可能指向库保存的对象。浅拷贝外层字典也无法隔离这些内层引用。
区分原地修改与重新绑定。例如 tags.append('seen') 改的是共享列表;给调用方的 metadata 重新赋一份新字典,则不一定改动旧对象。只用后一种赋值做对照,可能漏掉实际问题。
这种变化也可能改变后续 filter 的结果。应比较同一文档 ID 在入库后、修改调用方对象后、修改检索结果后各自的元数据,不能只以相似度分数判断库内容有没有变化。
入库和结果注释都需要隔离
应用确实需要在外部继续修改文档时,可以在入库前创建深拷贝;需要给检索结果加展示标签时,也先复制结果,再编辑副本。下面仅说明两个边界,使用已有的 documents、store 和查询变量:
from copy import deepcopy
store.add_documents(deepcopy(documents))
results = store.similarity_search(query, k=3)
editable_results = deepcopy(results)
k=3 是示例查询数量,按原检索策略保留实际值。入库与查询会调用已配置的 embedding,可能访问外部服务;应合入原有流程,不能把整段当成无副作用的诊断命令反复执行。
需要在 editable_results 上添加 UI 标签或统计信息。只复制入库侧,随后修改原检索结果里的嵌套对象,仍没有覆盖出库侧;只复制检索结果,入库后继续修改原始文档,也仍可能写回库内。
这个做法适合可深拷贝的文档与普通列表、字典元数据。元数据含自定义对象、文件句柄或需要共享的资源时,应按明确的数据契约整理,不能声称 deepcopy 会复制所有资源。过滤回调也应只读,不在它收到的 Document 上累计统计或补标签。
元数据隔离不代替权限设计
若 tenant 等过滤字段已经被修改,需要根据可信记录核对并恢复,给后续对象加深拷贝不会自动修好库里的旧值。不要靠清空所有文档来消除某一个对象引用问题。
验收应覆盖入库后修改原始嵌套值、修改返回结果副本,以及后续同 ID 读取与过滤。库内元数据应保持预定值,只有通过应用正式更新流程的修改才进入存储。
深拷贝只是对象隔离。共享内存向量库的租户权限、并发修改和正式更新入口,仍需要应用自己的约束;不能把这项修正当成完整的多租户安全方案。


