1. 项目概述
作为一名长期从事AI技术落地的开发者,我最近在探索如何将非结构化文档处理与语义检索相结合。传统的关键词检索(如grep)虽然快速,但召回率低且容易检索到无关内容;而单纯的语义相似度检索又难以处理结构化信息。经过多次实践,我发现将谷歌开源的LangExtract与Milvus向量数据库结合,能够很好地解决这个问题。
这个方案特别适合法律、医疗、教育等对内容准确度要求高的领域。比如在法律场景中,当条文更新时,传统RAG只能召回所有相关文本,而LangExtract可以精确提取日期等信息,帮助律师判断适用的条款版本。在医疗场景中,要查询"2019-2023年50岁以上呼吸科患者CT结果中出现特定肺结节的情况",就需要同时处理时间、年龄等结构化数据和医学影像的非结构化特征。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心工具解析
2.1 LangExtract深度剖析
LangExtract是谷歌近期开源的一个Python库(GitHub star数已达13.3k),它通过LLM从非结构化文本中提取结构化信息。我实际测试后发现几个关键特性特别实用:
-
自定义schema能力:用户可以通过提示词和Few-Shot示例定义要提取的字段。比如处理法律文书时,可以设置"姓名、年龄、籍贯、刑期、罪名"等字段。
-
精确溯源:每个提取结果都关联原始文本位置,最大限度避免模型幻觉。这在医疗记录处理中尤为重要,医生需要确认诊断结论的来源依据。
-
长文本处理:采用分区块+多线程+多轮抽取策略,实测可处理百万字符内容。我们用它处理过整本医学教材的要点提取。
-
数据标准化:自动将提取结果转换为结构化格式(如JSON),并支持HTML可视化。下图展示了一个医疗记录的处理效果:

2.2 Milvus向量数据库优势
Milvus是我们选择的向量数据库,主要考虑以下几点:
- 高性能检索:支持十亿级向量数据的毫秒级查询,这对医疗影像检索等场景至关重要
- 混合查询:可同时执行向量相似度搜索和结构化字段过滤
- 灵活部署:支持从本地文件到分布式集群多种部署方式
- 生态完善:提供Python、Java等多语言SDK,与主流AI框架深度集成
3. 系统搭建实战
3.1 环境准备
建议使用Python 3.8+环境,安装依赖:
bash复制pip install --upgrade pymilvus langextract google-genai requests tqdm pandas
需要准备Gemini API密钥(如使用Gemini模型):
python复制import os
os.environ["GEMINI_API_KEY"] = "your_api_key_here"
3.2 数据处理流程设计
整个系统的工作流程分为四个阶段:
- 文档预处理:清洗原始文本,处理特殊字符和格式
- 信息提取:使用LangExtract提取结构化字段
- 向量化:生成文档的向量表示
- 存储与检索:将结构化数据和向量存入Milvus
3.3 代码实现详解
3.3.1 Milvus集合创建
首先定义集合schema,包含三类字段:
- 主键:唯一标识符
- 文本字段:原始文档内容
- 向量字段:文档的向量表示
- 结构化字段:从文档提取的属性
python复制client = MilvusClient(uri="./milvus_demo.db") # 使用本地文件存储
schema = client.create_schema(
auto_id=False,
enable_dynamic_field=True,
description="文档提取结果存储"
)
# 添加字段
schema.add_field(field_name="id", datatype=DataType.VARCHAR, max_length=100, is_primary=True)
schema.add_field(field_name="document_text", datatype=DataType.VARCHAR, max_length=10000)
schema.add_field(field_name="embedding", datatype=DataType.FLOAT_VECTOR, dim=3072)
# 创建集合
client.create_collection(collection_name="documents", schema=schema)
3.3.2 LangExtract配置
定义提取规则时需要特别注意提示词设计。以医疗记录为例:
python复制prompt = """
从以下医疗记录中提取关键信息:
- 患者基本信息:姓名、年龄、性别
- 诊断结果:主要诊断、次要诊断
- 检查信息:检查类型、检查日期、检查结果
- 治疗方案:药物名称、剂量、用法
要求:
1. 使用原始文本中的确切表述
2. 对日期统一格式为YYYY-MM-DD
3. 对剂量单位统一为mg或ml
"""
examples = [
lx.data.ExampleData(
text="患者张三,男,45岁,主诉头痛。CT显示左侧额叶2cm占位。诊断:脑膜瘤。处方:布洛芬200mg q8h",
extractions=[
lx.data.Extraction(extraction_class="患者信息", extraction_text="张三,男,45岁",
attributes={"姓名": "张三", "性别": "男", "年龄": "45"}),
lx.data.Extraction(extraction_class="诊断结果", extraction_text="脑膜瘤",
attributes={"主要诊断": "脑膜瘤"}),
lx.data.Extraction(extraction_class="检查信息", extraction_text="CT显示左侧额叶2cm占位",
attributes={"检查类型": "CT", "结果": "左侧额叶2cm占位"}),
lx.data.Extraction(extraction_class="治疗方案", extraction_text="布洛芬200mg q8h",
attributes={"药物名称": "布洛芬", "剂量": "200mg", "用法": "q8h"})
]
)
]
3.3.3 文档处理流水线
python复制def process_document(text):
# 信息提取
result = lx.extract(
text_or_documents=text,
prompt_description=prompt,
examples=examples,
model_id="gemini-2.0-flash",
)
# 生成向量
embedding_response = genai_client.models.embed_content(
model="gemini-embedding-001",
contents=[text],
config=EmbedContentConfig(
task_type="RETRIEVAL_DOCUMENT",
output_dimensionality=3072,
),
)
embedding = embedding_response.embeddings[0].values
# 准备存储数据
data = {
"id": str(uuid.uuid4()),
"document_text": text,
"embedding": embedding
}
# 添加提取的属性
for extraction in result.extractions:
class_name = extraction.extraction_class
for attr_name, attr_value in extraction.attributes.items():
data[f"{class_name}_{attr_name}"] = attr_value
return data
4. 查询功能实现
4.1 纯语义搜索
python复制def semantic_search(query_text, limit=5):
# 生成查询向量
query_embedding = genai_client.models.embed_content(
model="gemini-embedding-001",
contents=[query_text],
config=EmbedContentConfig(
task_type="RETRIEVAL_QUERY",
output_dimensionality=3072,
),
).embeddings[0].values
# 执行搜索
results = client.search(
collection_name="documents",
data=[query_embedding],
anns_field="embedding",
limit=limit,
output_fields=["document_text"],
search_params={"metric_type": "COSINE"},
)
return results[0]
4.2 混合查询(语义+过滤)
python复制def hybrid_search(query_text, filter_expr, limit=5):
query_embedding = genai_client.models.embed_content(
model="gemini-embedding-001",
contents=[query_text],
config=EmbedContentConfig(
task_type="RETRIEVAL_QUERY",
output_dimensionality=3072,
),
).embeddings[0].values
results = client.search(
collection_name="documents",
data=[query_embedding],
anns_field="embedding",
limit=limit,
filter=filter_expr,
output_fields=["document_text", "诊断结果_主要诊断", "检查信息_检查类型"],
search_params={"metric_type": "COSINE"},
)
return results[0]
4.3 典型查询示例
- 查找所有CT检查显示肺部结节的患者:
python复制results = hybrid_search(
"肺部结节",
'检查信息_检查类型 == "CT" AND 检查信息_结果 LIKE "%结节%"'
)
- 查找2023年使用过阿司匹林的老年患者:
python复制results = hybrid_search(
"老年患者用药",
'治疗方案_药物名称 == "阿司匹林" AND 患者信息_年龄 >= "60" AND 检查信息_检查日期 >= "2023-01-01"'
)
5. 性能优化技巧
5.1 LangExtract调优
- 示例数量:Few-Shot示例建议3-5个,太少会导致提取不准,太多会增加处理时间
- 区块大小:长文本建议设置chunk_size=2000,chunk_overlap=200
- 并行度:设置max_workers=CPU核心数*2可充分利用计算资源
5.2 Milvus配置建议
- 索引类型:数据量<100万用IVF_FLAT,>100万用HNSW
- 查询参数:nprobe=10-50(精度与性能的平衡)
- 内存管理:定期调用flush()确保数据持久化
5.3 系统级优化
- 批量处理:累积一定数量文档后批量处理,减少API调用
- 缓存机制:对常见查询结果缓存5-10分钟
- 异步处理:将提取和向量化任务放入队列异步执行
6. 常见问题排查
6.1 提取结果不准确
现象:提取的字段值不正确或缺失
排查步骤:
- 检查提示词是否明确指定了字段格式
- 验证示例是否覆盖了各种情况
- 确认原始文本质量(特殊字符、格式混乱会影响效果)
6.2 查询性能下降
现象:搜索响应时间变长
解决方案:
- 检查Milvus集合的索引状态
- 优化nprobe参数(从10开始逐步增加)
- 考虑升级硬件或使用分布式部署
6.3 内存不足
现象:处理大文档时内存溢出
处理方法:
- 减小LangExtract的chunk_size
- 启用逐块处理模式(stream=True)
- 增加SWAP空间或使用内存更高效的模型
7. 应用场景扩展
7.1 法律文书分析
通过提取"当事人、案由、判决结果"等字段,实现:
- 类案检索:找相似判例
- 法官倾向分析:统计某法官的判决特点
- 法律条文更新追踪:标记受影响的案件
7.2 医疗科研
提取"患者特征、治疗方案、疗效"等数据,用于:
- 病例相似度匹配:找适合对照研究的病例
- 药物效果分析:统计特定药物的疗效
- 疾病特征挖掘:发现症状与检查结果的关联
7.3 教育领域
处理教学资料提取"知识点、难度等级、关联概念",支持:
- 个性化学习路径推荐
- 试题相似度检测
- 教学资源智能分类
8. 实战经验分享
在实际部署过程中,我总结了以下几点经验:
-
字段设计要前瞻:初期就要规划好可能用到的查询条件,避免后期重构。比如医疗场景中,除了基本诊断信息,我们还预留了"并发症、过敏史"等字段。
-
数据版本控制:当修改提取规则时,建议新建集合而非修改已有schema,便于AB测试和回滚。
-
混合检索策略:对于关键业务查询,建议同时执行语义搜索和精确过滤,然后融合结果。我们使用的加权公式是:
code复制最终得分 = 语义相似度 * 0.7 + 字段匹配度 * 0.3 -
监控指标:必须监控提取准确率、查询延迟、召回率等核心指标。我们设置了一个定时任务,每周自动抽样评估。
这个方案在医疗场景落地后,医生检索相关病例的时间从平均15分钟缩短到2分钟,且结果相关性显著提升。最关键的是,系统能够理解"查找术后感染但未使用抗生素的患者"这类复杂查询,这是传统检索系统难以实现的。
