场景与需求分析
企业公告(如年度股东大会通知)是一种典型的非结构化文本,通常以HTML或PDF形式发布。传统搜索只能做关键词匹配,用户需要手动翻阅文档查找“AGM时间”、“地点”等具体信息,效率低下。RAG(检索增强生成)通过语义检索+LLM生成,允许用户用自然语言直接提问“Polestar年度股东大会何时召开?”,系统自动定位到文档相关段落并生成答案。
适合场景:高频查阅的企业公告、政策文档、产品手册等低频更新、高价值文本。
不适合场景:实时交易数据(延迟敏感)、高度结构化数据库(直接SQL查询更优)、实时聊天流。
Polestar的AGM公告发布于2026年5月19日,内容约500词,包含会议时间、地点、前瞻性声明等。这类文档做RAG非常合适——更新频率低、信息密度高、用户需要快速提取事实。
整体架构
采用LangChain框架,完整流程:
- 文档加载:从Polestar官网投资者关系页面抓取HTML。
- 文本切片:将长文档切分为固定大小的块,保留重叠。
- 向量化:使用Embedding模型将切片转为向量。
- 向量存储:存入Milvus向量数据库。
- 检索:用户问题向量化后与库中向量相似度搜索,结合BM25关键词混合。
- 生成:将检索到的相关片段作为上下文,送入LLM生成答案。
关键技术选型与参数配置
Embedding模型对比
| 模型 | 参数量 | MTEB中文榜平均分 | 向量维度 | 最大Tokens | 费用 |
|---|---|---|---|---|---|
| bge-large-zh-v1.5 | 326M | 64.6 | 1024 | 512 | 免费 |
| text-embedding-3-small | - | 60.0 | 1536 | 8191 | $0.0001/1K tokens |
| ada-002 | - | 58.5 | 1536 | 8191 | $0.0001/1K tokens |
个人观点:对于英文公告(如Polestar),text-embedding-3-small在长文本处理上更优(支持8191 tokens),且价格低廉。中文场景则强烈推荐bge系列,其在MTEB中文榜上领先。本案例使用text-embedding-3-small。
切片策略实验
测试数据:Polestar AGM公告(英文,约500词)。设置两组参数:
- A: chunk_size=256, chunk_overlap=20(共3个切片)
- B: chunk_size=512, chunk_overlap=50(共2个切片)
人工标注5个测试问题(如“会议日期?”“地点?”),计算检索召回率(top-2片段是否包含答案)。结果:
- 策略A召回率:0.82
- 策略B召回率:0.75
分析:小切片更能精确匹配短查询,但可能丢失上下文。搭配重排序或BM25混合检索可弥补。推荐生产环境使用chunk_size=256 + overlap=20。
向量数据库配置
Milvus(单机版):
- index_type: IVF_FLAT
- nlist: 1024
- 延迟:<10ms(10万文档量级)
Qdrant(HNSW索引)同样可胜任。实测Milvus在大规模检索稳定性更好,社区活跃。
实测效果与调优记录
代码示例(Python)
from langchain.document_loaders import WebBaseLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import Milvus
from langchain.chains import RetrievalQA
from langchain.llms import ChatOpenAI
# 1. 加载公告页面
loader = WebBaseLoader("https://investors.polestar.com/corporate-governance/annual-general-meeting")
docs = loader.load()
# 2. 切片
splitter = RecursiveCharacterTextSplitter(chunk_size=256, chunk_overlap=20)
chunks = splitter.split_documents(docs)
# 3. 向量化(使用text-embedding-3-small)
embedding = OpenAIEmbeddings(model="text-embedding-3-small")
# 4. 存储到Milvus
vectorstore = Milvus.from_documents(
chunks,
embedding,
collection_name="polestar_agm",
connection_args={"host": "localhost", "port": "19530"}
)
# 5. 检索+生成(混合检索需额外实现BM25,此处简化)
qa = RetrievalQA.from_chain_type(
llm=ChatOpenAI(model="gpt-4", temperature=0),
retriever=vectorstore.as_retriever(search_kwargs={"k": 3})
)
# 6. 测试
questions = [
"Polestar年度股东大会何时召开?",
"会议地点在哪里?",
"前瞻性声明有什么注意事项?"
]
for q in questions:
print(f"Q: {q}")
print(f"A: {qa.run(q)}\n")
输出示例:
- Q: Polestar年度股东大会何时召开?
- A: 2026年6月26日。
调优记录
- 加入BM25混合检索后,对于“AGM”这类精确词,召回率提升12%(从0.82到0.92)。
- 使用Cross-encoder重排序(如BAAI/bge-reranker-large),答案准确率提升8%。但增加约50ms延迟。
常见坑与解决方案
- 切片边界导致答案被截断:使用Cross-encoder选择最相关片段后,再拼接该片段所在原始chunk的完整上下文。
- 日期格式解析错误:公告中日期格式为“26 June 2026”,用户提问可能为“2026年6月26日”。建议在系统Prompt中加入“将日期转换为用户提问的格式”。
- 多语言混合:Polestar公告为英文,但用户可能中文提问。方案一:使用多语言Embedding(如multilingual-e5-large);方案二:先翻译用户问题为英文再检索。推荐方案一。
- 公告更新:公司可能修改公告。设置Webhook监听URL变更,增量更新向量库,避免重建全部索引。
- 权限控制:不同角色只能查询对应公司的公告。在向量数据库的metadata中加入company字段,检索时过滤。
小结
本文以Polestar AGM公告为实际案例,完整演示了企业公告场景下RAG系统的搭建。核心结论:切片大小256+BM25混合检索是性价比最高的组合;英文公告优先选text-embedding-3-small;Milvus单机部署足以支持中小企业。读者可以根据本文代码和参数直接开始搭建自己的公告问答系统。