持久化、重载与文档生命周期
保存索引不等于保存了全部运行配置。重载必须选择与建库时相同的embedding模型,外部向量库还可能单独持久化,需要同时管理docstore、index store和vector store。
下面独立运行,使用临时目录,退出后自动清理,不覆盖既有索引。
from tempfile import TemporaryDirectory
from llama_index.core import Document, VectorStoreIndex, StorageContext, load_index_from_storage
from llama_index.core.embeddings import MockEmbedding
embed = MockEmbedding(embed_dim=8)
with TemporaryDirectory() as folder:
index = VectorStoreIndex.from_documents([Document(text='A compact policy.', id_='p1')], embed_model=embed)
index.storage_context.persist(persist_dir=folder)
loaded = load_index_from_storage(StorageContext.from_defaults(persist_dir=folder), embed_model=embed)
assert 'p1' in loaded.ref_doc_info
hits = loaded.as_retriever(similarity_top_k=1).retrieve('policy')
assert len(hits) == 1
loaded.delete_ref_doc('p1', delete_from_docstore=True)
assert 'p1' not in loaded.ref_doc_info
print('persist reload delete passed')这里验证默认内存存储的文件持久化,不代表所有外部向量后端都支持相同删除、过滤和事务语义。删除后仍要检验检索结果与外部记录,不能只检查docstore元数据。线上替换索引时构建新版本再切活动引用,避免读到半次更新。
存储manifest应包含核心与集成包版本、embedding、切分器、元数据schema、数据版本。相同维度不同模型重载通常不会给出明确错误,却会损害召回。把manifest校验放在加载前,比等待业务问题暴露更可靠。
存量代码如何维护
旧例子常用ServiceContext集中配置,现代代码用Settings或组件局部参数。迁移说明解释两者对应关系。维护旧项目先锁住能运行的依赖,再定位llm、embedding、splitter来自何处;不要一边升级核心包一边更换模型。
Settings是进程级默认配置,在多租户并发请求里临时修改会让请求相互影响。优先向Index、Query Engine传局部实例,初始化一次的共享只读配置才适合全局默认。核心包与LLM/embedding/向量库集成包分开安装,出现导入失败时先检查对应集成依赖,不靠改导入路径碰运气。
练习
编写manifest兼容性检查,模型相同但维度改变、维度相同但模型改变都必须拒绝。
完成后查看本章答案。
维护0.9的ServiceContext,再比较0.14
遇到仍在运行的0.9系统,先在原版本完成配置修改,而不是先要求迁移。下面独立环境固定 llama-index==0.9.48 setuptools==80.9.0;后者保留旧框架导入所需pkg_resources。与现代环境分开,避免两代同名包混装。Python3.12创建新venv后安装这两个依赖,保存legacy.py运行:
from llama_index import ServiceContext, VectorStoreIndex, Document
from llama_index.token_counter.mock_embed_model import MockEmbedding
from llama_index.llms.mock import MockLLM
from llama_index.node_parser import SentenceSplitter
def build(size):
context=ServiceContext.from_defaults(llm=MockLLM(),embed_model=MockEmbedding(embed_dim=8),
node_parser=SentenceSplitter(chunk_size=size,chunk_overlap=0))
index=VectorStoreIndex.from_documents([Document(text=' '.join(['refund within seven days.']*30))],service_context=context)
return index
small,large=build(32),build(128)
assert len(small.docstore.docs)>len(large.docstore.docs)
r=small.as_query_engine(similarity_top_k=1).query('refund?')
assert len(r.source_nodes)==1
print('ServiceContext chunks',len(small.docstore.docs),len(large.docstore.docs))实际输出6个与2个节点。把chunk_size从32改成128是在旧API上完成的真实维护:chunk_overlap必须小于chunk_size,原ServiceContext把parser/embedding/LLM集中注入。若只修改全局默认值,而索引已经持有旧context,不能假定新配置会追溯修改旧节点。
然后在0.14.6环境运行同样输入,显式把transformation与embed_model传给索引、LLM传给查询引擎:
from llama_index.core import VectorStoreIndex, Document
from llama_index.core.embeddings import MockEmbedding
from llama_index.core.llms import MockLLM
from llama_index.core.node_parser import SentenceSplitter
def build(size):
return VectorStoreIndex.from_documents([Document(text=' '.join(['refund within seven days.']*30))],
embed_model=MockEmbedding(embed_dim=8),transformations=[SentenceSplitter(chunk_size=size,chunk_overlap=0)])
small,large=build(32),build(128)
assert len(small.docstore.docs)>len(large.docstore.docs)
assert len(small.as_query_engine(llm=MockLLM(),similarity_top_k=1).query('refund?').source_nodes)==1
print('modern chunks',len(small.docstore.docs),len(large.docstore.docs))当前两个固定环境均得到6/2节点与一个来源节点。这只是本输入的行为比较,不能推出所有旧索引可原封重载。迁移还需要检查node ID、metadata排除字段、embedding维度及持久化格式;必要时从原文重新建索引并双读对照。ServiceContext被拆分后更容易避免无关全局设置串扰,仍要明确每个组件在哪一层注入。
维护练习:在旧环境把overlap从0改为8,保持size=32,比较边界句是否重复;不要先迁移。随后将同样参数用于现代环境,比较节点文本列表而不是随机节点ID。完成后查看维护练习答案。
下一章:查询组合、后处理与可观测性。