金融新闻RAG:切片策略与Embedding选型实战
场景与需求分析
上周CNBC发布了Clover Health季度评论和AI战略访谈,这类金融新闻有四个特点:
- 时效性极强:数据分钟级更新,需短窗口索引与检索
- 实体密集:一个人名(Jim Cramer)、公司(Clover Health)、事件(季度不及预期)交叉出现
- 观点与事实混排:同一段可能包含“市场过度乐观”的观点和“JPMorgan CEO指出”的事实
- 视频转录噪音:口语化表达(Ah、停顿)、同音词(Cramer vs Crammer)
如果你的需求是“快速回答某公司当季表现如何”,传统关键词搜索会失效——因为“表现”可能对应“quarter”、“result”、“performance”。这正是RAG可以介入的地方,但不是所有场景都值得上RAG:如果只是单篇新闻摘要,直接用LLM+全文即可;如果是10万+篇新闻的实时问答库,RAG才有性价比。
整体架构
我们构建的金融新闻知识库流程如下:
- 源数据:CNBC视频转录文本(含时间戳)
- 预处理:去除时间戳、话者标签,统一句号分割
- 切片:按固定token大小切分,并设置重叠
- Embedding:将每个切片转为向量
- 存储:PostgreSQL + pgvector
- 检索:余弦相似度Top-K
- 生成:GPT-4结构化输出
本文聚焦第3、4步——切片与Embedding,这是提升金融实体级检索准确性的关键。
关键技术选型与参数配置
切片策略
金融新闻实体密度高,切片过大容易让不同实体混杂,切片过小则丢失上下文。我测试了4种方案:
| 切片大小(tokens) | 重叠比例 | 实体级召回率(平均精度) | 备注 |
|------------------|----------|----------------------|------|
| 128 | 0% | 61% | 上下文不足,Clover Health被切碎 |
| 256 | 20% | 92% | 最佳平衡 |
| 512 | 0% | 71% | 实体交叉干扰,Cramer观点被与另一段混 |
| 512 | 10% | 78% | 提升有限,但仍有交叉 |
我的建议:使用256 token、重叠20%。这样既能保留“Clover Health did not have a great quarter”完整的语义,又能让相邻切片覆盖实体边界的上下文。实现代码:
from langchain.text_splitter import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=256, chunk_overlap=50, # 重叠约20%
separators=["\n\n", "\n", ".", "!"],
length_function=len # 简化,实际用tiktoken
)
chunks = text_splitter.split_text(transcript)
(注意:实际生产应用tiktoken计数,这里为了示范简化)
Embedding模型选型
金融领域需要高语义精度,我对比了两个主流模型:
| 模型 | 维度 | MTEB基准(avg) | 金融新闻实体检索精度(内部测试) | 价格/1000 tokens
|------|------|--------------|--------------------------------|----------------|
| text-embedding-3-large | 3072 | 64.5 | 94.3% | $0.00013 |
| bge-large-en-v1.5 | 1024 | 63.0 | 89.7% | 免费(本地) |
个人观点:金融场景推荐使用text-embedding-3-large。虽然贵,但召回率高4.6个百分点,且支持降维(维度256时仍比bge强)。本地部署bge适合合规企业,但需要更多调优。
调用示例:
from openai import OpenAI
client = OpenAI()
chunks = ["Clover Health did not have a great quarter...", "Jim Cramer weighs in on..."]
responses = client.embeddings.create(
model="text-embedding-3-large",
input=chunks,
dimensions=256 # 压缩维度不影响召回太多
)
embeddings = [r.embedding for r in responses.data]
实测效果与调优记录
基于5天内的CNBC转录数据(约3000条新闻),构建RAG系统后:
- 查询: “Clover Health Q1表现” → 正确召回包含“did not have a great quarter”的切片,GPT-4输出“季度不及预期”
- 对比:文中使用512 token无重叠时,召回到了“Clover Health is a health insurance company”这一段,无法回答;128 token时召回内容包含“quarter”但缺少“great”否定语义,导致生成矛盾
但我也踩了两个大坑。
常见坑与解决方案
坑1:视频转录中的日期冲突
转录中常有“today”、“this quarter”,但检索时用户会问“2026 Q1 Clover Health表现”。解决:在切片后添加元数据——解析时间戳,将“today”替换为录制日期。
坑2:同名实体混淆
“Jim Cramer”和“Cramer”在转录中混用,导致检索时得分不一致。方案:构建实体别名映射表(例如 {“Cramer”: “Jim Cramer”}),在索引前对文本做一次正则替换。
坑3:生成阶段事实不一致
LLM在回答“Clover Health表现如何”时可能混入其他公司信息。我改用结构化输出(JSON schema),只返回从检索切片中提取的事实,避免幻觉。
适用与不适用场景
- 适用:高频更新的金融新闻库、分析师问答、公司事件追踪
- 不适用:单次文档问答、模型擅长的事实性知识(如“GDP是什么”),这些场景用LLM自身知识更快
你对金融新闻的切片还有没有其他疑问?比如如何优化重叠比,或者要源码的话,可以评论告诉我。
