附录:具有知识图谱的时间代理
本笔记本包含“具有知识图谱的时间代理”食谱的附录。
在本附录中,您将找到更深入的“原型到生产”部分。
A. 原型到生产
A.1. 存储和检索海量图数据
A.1.1. 数据量和模式复杂性
随着数据集扩展到数百万甚至数十亿个节点和边,管理性能和可维护性变得至关重要。这需要对模式设计和数据分区进行深思熟虑的方法:
-
面向增长和变更的模式设计
清晰地定义核心实体类型(例如,
Person
、Organization
、Event
)和关系。在设计模式时要考虑版本控制和灵活性,从而能够以最小的停机时间进行未来的模式演进。 -
分片和分区
使用高基数字段(例如时间戳或唯一实体 ID)进行分区,以在数据量增长时保持查询性能。这对于时间感知数据尤其重要。例如:
```sql CREATE TABLE statements ( statement_id UUID PRIMARY KEY, entity_id UUID NOT NULL, text TEXT NOT NULL, valid_from TIMESTAMP NOT NULL, valid_to TIMESTAMP, status VARCHAR(16) DEFAULT 'active', embedding VECTOR(1536), ... ) PARTITION BY RANGE (valid_from); ```
A.1.2. 时间有效性和版本控制
在我们的时间知识图中,每个语句都包含时间标记(例如,valid_from
、valid_to
)。
-
无损保留历史记录
避免删除或覆盖记录。而是通过设置
status
(例如,inactive
)将过时的事实标记为非活动状态。 -
优化时间访问
索引时间字段(
valid_from
、valid_to
)以支持对当前和历史状态的高效查询。
示例:无损更新
与其删除或覆盖记录,不如更新其状态并关闭其有效性窗口:
UPDATE statements
SET status = 'inactive', valid_to = '2025-03-15T00:00:00Z'
WHERE statement_id = '...' AND entity_id = '...';
A.1.3. 索引和语义搜索
时间索引
为了支持高效的时间查询,请在 valid_from
和 valid_to
上创建 B 树索引。'B 树' 索引是一种将数据排序以在对数时间内促进快速查找、范围查询和有序扫描的树状数据结构。它是许多关系数据库中的默认索引类型。
CREATE INDEX ON statements (valid_from);
CREATE INDEX ON statements (valid_to);
使用 pgvector 进行语义搜索
在 PostgreSQL 中存储向量嵌入(通过 pgvector
扩展)可以通过相似性检索实现基于语义搜索。这遵循一个两步过程:
- 存储代表文本语义含义的高维向量。这些可以使用 OpenAI 的
text-embedding-3-small
和text-embedding-3-large
等嵌入模型创建 - 使用近似最近邻(ANN)在大规模上进行高效的相似性匹配
pgvector 提供了几种不同的索引选项,每种都有不同的用途。这些索引选项以及深入的实现步骤在 pgvector 的 GitHub 存储库的 README 中有更详细的描述。 |
索引类型 |
构建时间 |
查询速度 |
内存使用 |
准确性 |
推荐规模 |
备注 |
---|---|---|---|---|---|---|
flat |
最小 |
慢 (线性扫描) |
低 |
100% (精确) |
非常小 (< 100 K 向量) |
无近似索引 — 扫描所有向量。最适合小集合上的精确召回 |
ivfflat |
中等 |
调优后速度快 |
中等 |
高 (可调) |
小到中等 (100 K–200 M) |
使用倒排文件索引。查询时间参数控制权衡 |
ivfpq |
高 |
非常快 |
低 (量化) |
略低于 ivfflat |
中到大 (1 M–500 M) |
将倒排文件与乘积量化相结合以降低内存使用 |
hnsw |
最高 |
最快 (尤其是在大规模上) |
高 (内存中) |
非常高 |
大到非常大 (100 M–数十亿+) |
构建分层可导航图。适用于延迟敏感、大规模系统 |
向量索引的调优参数
ivfflat
lists
: 分区数(例如 100)probes
: 查询时扫描的分区数(例如 10-20),控制召回率与延迟
ivfpq
subvectors
: 量化的块数(例如 16)bits
: 每个块的位数(例如 8)probes
: 与ivfflat
相同
hnsw
M
: 每个节点的连接数(例如 16)ef_construction
: 构建时动态候选列表大小(例如 200)ef_search
: 查询时候选池(例如 64-128)
最佳实践
flat
用于调试或小数据集ivfflat
当您想要可调的准确性与良好的速度时ivfpq
当内存效率至关重要时hnsw
当优化大规模集合的最低延迟时
生态系统中的其他向量数据库选项
| 向量数据库 | 主要特点 | 优点 | 缺点 |
向量数据库 | 主要特点 | 优点 | 缺点 |
---|---|---|---|
Pinecone | 完全托管,无服务器;支持 HNSW 和 SPANN | 自动扩展,有 SLA 支持,易于集成 | 供应商锁定;规模化成本增加 |
Weaviate | GraphQL API,内置编码和向量化模块 | 混合查询(元数据 + 向量),模块化 | 生产部署需要 Kubernetes |
Milvus | 支持 GPU 索引;IVF、HNSW、ANNOY | 大规模高性能,动态索引 | 操作复杂性;独立系统 |
Qdrant | 轻量级,实时更新,有效负载过滤 | 设置简单,良好的混合查询支持 | 缺乏原生关系连接;集群中最终一致性 |
Vectara | 托管,具有语义排名和重新排名功能 | 强大的相关性功能;易于集成 | 专有;索引控制有限 |
选择正确的向量存储
|
规模 |
推荐 |
详细信息 |
---|---|---|
中小规模 (少于 1 亿向量) |
PostgreSQL + pgvector 使用 ivfflat 索引 |
通常足以满足中等工作负载。推荐设置:lists = 100–200 ,probes = 10–20 。 |
大规模 (1 亿 – 10 亿+ 向量) |
Milvus 或 Qdrant |
适用于高吞吐量工作负载,尤其是在需要 GPU 加速索引或亚毫秒延迟时。 |
混合场景 |
PostgreSQL 用于元数据 + 专用向量数据库 |
使用 PostgreSQL 存储实体元数据,并使用向量数据库(例如 Milvus、Qdrant)进行相似性搜索。使用 CDC 管道(例如 Debezium)同步嵌入。 |
有关更多详细信息,请查看 OpenAI 关于向量数据库的食谱。
持久磁盘存储和备份
在某些情况下,尤其是在需要高可用性或跨重启的状态恢复时,将状态持久化到可靠的磁盘存储并实施备份策略可能是有价值的。
如果关注持久性,请考虑使用持久磁盘并定期备份或将状态同步到外部存储。虽然并非所有部署都需要,但在一致性和容错性很重要的环境中,它可以提供宝贵的数据丢失或操作中断的保障。
A.2. 管理和修剪数据集
A.2.1. TTL(生存时间)和存档策略
制定清晰的策略来确定哪些事实应无限期保留(例如,监管机构要求的法律记录),哪些可以在规定期限后存档(例如,一年多前的社交媒体来源的事实)。
关键实践包括:
-
自动化存档作业
设置一个后台任务,定期查询具有例如
valid_to < NOW() - INTERVAL 'X days'
的记录,并将它们移动到存档表中以供长期存储。 -
特定来源的保留策略
按数据源或实体类型定制保留期限。例如,政府出版物等高权威来源可能比抓取的报纸标题或用户生成内容等不太可靠的数据需要更长的保留时间。
A.2.2. 相关性评分和智能修剪
随着知识图谱的增长,许多事实的效用将会下降。为了保持图的焦点并最大化性能:
-
索引相关性分数
引入一个数字
relevance_score
列(或多列),其中包含诸如时新性、来源可信度和生产查询频率等指标。 -
自动化修剪逻辑
安排一个例行作业来修剪或存档低于预定相关性阈值的事实。
高级相关性图缩减
在扩展时有效减小知识图的大小非常重要。2024 年的一项调查将技术分为稀疏化、粗粒化和浓缩——所有这些都旨在缩小图的大小同时保留任务关键语义。这些方法在大规模 KG 上提供了显著的运行时和内存收益。
示例实现模式:
-
为每个三元组评分
计算一个复合
relevance_score
,例如:relevance_score = β1 * recency_score + β2 * source_trust_score + β3 * retrieval_count
其中:
recency_score
:从valid_from
指数衰减source_trust_score
:来源域信任值retrieval_count
:生产查询频率
-
应用缩减策略
- 稀疏化:根据中心性、谱相似性或嵌入保留等标准选择并保留最相关的边或节点
- 粗粒化:将低重要性或语义相似的节点分组到超级节点中,并聚合它们的特征和连接
- 浓缩:从头开始构建任务优化的迷你图
-
在影子模式下验证
在将生产流量路由到修剪后的图之前,记录并比较修剪后的图与原始图的输出。
-
定期重新评分
重新计算相关性(例如,每晚一次),以确保新访问或频繁访问的事实重新浮出水面。
A.3. 在摄取管道中实现并发
从原型到生产通常需要您将线性处理管道转换为并发、可扩展的管道。与其顺序处理文档(文档 → 分块 → 语句提取 → 实体提取 → 语句失效 → 实体解析),不如实现一个分阶段的管道,其中每个阶段都可以独立扩展。
使用一系列专用阶段设计管道,每个阶段都有自己的队列和工作池。这允许您独立扩展瓶颈,并在不同负载下保持系统可靠性。
-
批量分块
首先,使用 Redis 或 Amazon SQS 等作业队列收集批次(例如 100-500 个)文档。并行处理这些文档,将每个文档拆分为相应的块。分块阶段通常应优化 I/O 并行化,因为文档读取通常是瓶颈。然后,您可以使用批量插入操作将块及其相应元数据存储在
chunk_store
表中,以最大程度地减少开销。 -
语句和实体提取
以批次(例如 50-100 个)拉取块,并通过并行 API 请求将它们发送到您选择的 LLM(例如 GPT-4.1-mini)。实现带有信号量或其他方法的速率限制,以安全地保持在 OpenAI 的 API 限制内,同时最大化吞吐量。我们在关于[如何处理速率限制](https://cookbook.openai.com/examples/how_to_handle_rate_limits)的食谱中更详细地介绍了速率限制。提取后,您可以将它们写入数据库中的相关表。
然后,您可以类似地将我们刚刚提取的语句分组到批次中,然后以类似的方式运行实体提取过程,然后再将它们存储起来。
-
语句失效
按相关实体集群(例如,与“Acme Corp.”等特定实体相关的的所有语句)对提取的语句 ID 进行分组。将每个集群并行发送到您的 LLM(例如 GPT-4.1-mini),以评估哪些语句已过时或被取代。使用模型的输出来更新
statements
表中的status
字段 — 例如,设置status = 'inactive'
。并行化失效作业以提高性能,并考虑安排定期扫描以确保一致性。 -
实体解析
获取新提取的实体提及批次,并使用模型的嵌入端点计算嵌入。将它们插入到
entity_registry
表中,为每个表分配一个临时或规范的entity_id
。使用pgvector
执行近似最近邻(ANN)搜索以识别近重复项或别名。然后,您可以将实体更新到entities
表中,并解析规范 ID,确保下游任务引用统一的表示。
批量处理的优势
-
吞吐量 – 批量处理减少了单个 API 调用和数据库事务的开销。
-
并行性 – 每个阶段都可以水平扩展:您可以运行多个分块、提取、失效等工作进程,每个进程从队列中读取。
-
反压和可靠性 – 如果一个阶段变慢(例如,在数据突然激增期间语句失效),上游阶段可以在队列中缓冲更多项目,直到容量释放。
A.4. 最大限度地减少令牌成本
A.4.1. 提示缓存
通过记忆对脆弱的子提示的响应来避免冗余的 API 调用。
实施策略:
- 缓存频繁查询:例如,对相同语句重复的提示,如“从该语句中提取实体”
- 使用哈希键:使用语句文本的 MD5 哈希生成唯一的缓存键:
md5(statement_text)
- 存储选项:Redis 用于可扩展的持久性,或内存 LRU 缓存用于简单性和速度
- 绕过 API 调用:如果在缓存中找到语句,则跳过 API 调用
A.4.2. 服务层:Flex
在 OpenAI Responses SDK 中使用 service_tier=flex
参数来启用部分完成并降低成本。
API 配置:
{
"model": "o4-mini",
"prompt": "<your prompt>",
"service_tier": "flex"
}
成本效益:
- 只收取生成的令牌费用,不收取提示令牌费用
- 对于短提取(例如,单句实体列表),成本最多可降低 40%
您可以在 Flex 处理的 API 文档 中了解有关 Flex 处理功能的更多信息以及如何使用它。
A.4.3. 最大限度地减少“健谈”
在可能的情况下,用更有效的替代方案替换昂贵的文本生成调用。
替代方法:
- 使用嵌入端点(令牌成本更低)结合 pgvector 最近邻搜索
- 不要问模型“哪个现有语句最相似?”,而是计算一次嵌入并在 Postgres 中直接查询
- 此方法对于语义相似性任务特别有效
优点:
- 每个操作的成本更低
- 更快的查询响应时间
- 减少了相似性搜索的 API 依赖性
A.5. 扩展和生产化我们的检索代理
一旦填充了图,您就需要一种机制来大规模回答多跳查询。这需要:
-
代理架构
- 控制器代理(前端):接收用户问题(例如,“哪些事件导致了 Acme Corp. 的 IPO?”),然后将其分解为子问题或遍历步骤。
- 遍历工作代理:每个工作代理可以执行本地图遍历(例如,“查找 Acme Corp. 在 2020-2025 年之间具有 EventType = Acquisition 的所有事实”),可能在图的不同分区上并行执行。
-
并行子图提取
- 按实体 ID 哈希(例如,模 16)对图进行分区。对于给定的查询,确定哪些分区可能包含相关边,然后将遍历任务并行分派给每个工作代理。
- 工作代理返回部分子图(节点 + 边),控制器代理将它们合并。
-
链式 LLM 推理
对于多跳问题,控制器可以使用部分子图提示模型(例如 GPT-4.1),并询问“我应该遍历下一个哪个边?”这允许动态、上下文感知的遍历,而不是盲目的广度优先搜索。
-
缓存和记忆化
对于频繁查询或子图模式,将结果缓存起来(例如,在 Redis 或 Postgres 物化视图中),其 TTL 等于事实的
valid_to
日期,以便后续请求命中缓存而不是重新遍历。 -
负载均衡和自动缩放
在 Kubernetes 集群中部署具有水平 Pod 自动缩放器的遍历工作代理。使用 CPU 和内存指标(以及平均队列长度)在高峰使用期间进行扩展。
A.6. 安全保障
A.6.1 多层输出验证
运行轻量级验证管道以确保输出符合预期。其中可以包含一些示例:
- 检查日期是否符合
ISO-8601
- 验证实体类型是否符合您的受控词汇表(例如,如果模型输出意外标签,则标记为手动审核)
- 部署一个“健全性检查”函数调用到一个更小、更便宜的模型,以验证输出的一致性(例如,“这个语句是否能正确解析为事实?是/否。”)
A.6.2. 审计日志和监控
- 实现具有可配置详细程度级别(例如,debug、info、warn、error)的结构化日志记录
- 存储输入预处理步骤、中间输出和最终结果,并进行完整跟踪,例如通过 OpenAI 的跟踪 提供的跟踪
- 跟踪令牌吞吐量、延迟和错误率
- 在可能的情况下监控数据质量指标,例如文档或语句覆盖率、时间分辨率速率等
- 衡量与业务相关的指标,例如用户数量、平均消息量和用户满意度
A.7. 提示优化
-
角色设定
为模型引入角色设定是提高性能的有效方法。一旦您缩小了正在为其开发提示的组件的专业范围,就可以在系统提示中创建一个角色设定,以帮助塑造模型的行为。我们在规划模型中使用了这个方法,创建了一个类似以下的系统提示:
initial_planner_system_prompt = ( "你是领先的金融公司 ABC 公司的一员,ABC 公司是全球最大的金融公司之一。 " "由于您在公司任职时间长且享有盛誉,因此各种股票研究团队经常会向您咨询他们正在进行的研究任务的指导。 " "您的专业知识在 ABC 公司专有的收益电话会议记录知识库领域尤其强大。该知识库包含从各公司收益电话会议记录中提取的详细信息,并标明了这些陈述何时有效或曾经有效。您是提供有关如何使用此知识图谱来回答其研究查询的说明方面的专家。 \n" )
角色设定提示可以比这更详细和具体,但这应该能说明它在实践中的样子。
-
少样本提示和思维链
对于提取任务,例如语句提取,简洁的少样本提示(2-5 个示例)通常比零样本提示具有更高的精度,而成本仅略有增加。
例如,对于时间协调任务,引导模型进行比较逻辑的思维链方法更合适。这可以看起来像:
示例 1:[旧事实],[新事实] → 失效 示例 2:[旧事实],[新事实] → 共存 现在:[旧事实],[新事实] →
-
动态提示和上下文管理
您还可以利用其他 LLM 或更结构化的方法来修剪和准备将动态传递给提示的材料。我们在上面构建检索器工具时看到了一个例子,其中
timeline_generation
工具在将检索到的材料传递回中央协调器之前对其进行排序。清理上下文或在运行中压缩上下文的步骤对于更长的查询也非常有效。
-
模板库和 A/B 测试
在版本控制的目录中维护一组提示模板(例如,
prompts/statement_extraction.json
、prompts/entity_extraction.json
),以便您能够审核过去的更改并在必要时回滚。您可以使用 OpenAI 的可重用提示来实现此目的。在 OpenAI 仪表板中,您可以开发[可重用提示](https://platform.openai.com/docs/guides/text#reusable-prompts)以在 API 请求中使用。这使您能够构建和评估提示,部署更新和改进的版本,而无需更改代码。通过定期对管道中提取的事实进行采样,使用替代提示重新运行它们,并比较性能分数(您可以在单独的评估工具中跟踪此指标),来自动化 A/B 测试。
跟踪关键绩效指标 (KPI),例如提取延迟、错误率和失效准确性。
如果任何指标超出阈值(例如,失效准确性低于 90%),则触发警报并回滚到以前的提示版本。