关于 ReasonGraph Cloud memory
ReasonGraph 是面向 AI Agent 的图式持久记忆,可从文本提取实体和因果关系,并连接不同文档与会话中的事实。它适用于需要跨来源发现、因果追溯及时间查询的 Agent,也提供 HTTP 和 MCP 记忆服务。
主要功能
- 01从文本提取实体与因果关系
- 02连接不同来源的事实
- 03跨会话共享记忆并处理矛盾
- 04追溯原因、影响及反事实
- 05混合检索与多跳图遍历
- 06支持内存、SQLite 和 PostgreSQL 后端
适用场景
- 01关联独立文档中的信息以构建推理链
- 02在多个 Agent 会话间保留和发现事实
- 03查询某个时间点已知的事实
- 04分析事件的根因和后续影响
MCP 相关
ReasonGraph 提供可部署的 HTTP 与 MCP 记忆服务,供 Agent 添加和查询记忆。可连接托管云端服务,也可在本地运行 MCP 服务。
配置
远程服务无需本地安装。选择你使用的客户端,复制配置或使用一键安装。
https://memory.primaxiom.ai/mcpclaude mcp add --transport http ai-primaxiom-memory-reasongraph https://memory.primaxiom.ai/mcp{"mcpServers":{"ai-primaxiom-memory-reasongraph":{"type":"http","url":"https://memory.primaxiom.ai/mcp"}}}在 Cursor 中打开{"servers":{"ai-primaxiom-memory-reasongraph":{"type":"http","url":"https://memory.primaxiom.ai/mcp"}}}在 VS Code 中打开{"mcpServers":{"ai-primaxiom-memory-reasongraph":{"type":"http","url":"https://memory.primaxiom.ai/mcp"}}}{"mcpServers":{"ai-primaxiom-memory-reasongraph":{"serverUrl":"https://memory.primaxiom.ai/mcp"}}}命令在终端中运行;JSON 粘贴到对应客户端的 MCP 配置后重启。需要授权的服务会在首次连接时提示登录。
https://memory.primaxiom.ai/mcp需要授权工具
详细介绍
ReasonGraph
面向 AI Agent 的图谱式记忆:它会摄取事实,自动提取实体和因果关系,并在独立文档 以及 Agent 会话之间发现联系,同时支持冲突解决、时间回溯、因果追踪和反事实推理。
为什么选择 ReasonGraph?
标准 RAG 会检索与查询相似的文档。ReasonGraph 是一种持久、可更新的记忆,能够发现独立写入的事实之间的联系。
将文本传入 add_texts() 时,ReasonGraph 会自动提取实体(通过 GLiNER)和因果关系(通过专用因果模型),并将它们作为节点和带类型的边存入图谱。共享实体或因果链的事实会彼此连接——即使它们从未互相提及。随后,多跳遍历会沿着这些联系构建跨越多个来源的推理链。
除了检索,它还可用作 Agent 记忆:作用域/会话(Agent 可通过共享实体探索彼此的记忆)、矛盾解决(新事实会软性取代与之矛盾的内容)、时间回溯(query(as_of=...))、因果追踪(trace_effects / root_causes / causal_chain)、反事实推理(what_if),以及可直接部署的 HTTP 和 MCP MemoryService。
零配置,默认设置强大。 ReasonGraph() 会自动选择最佳可用的实体提取器、因果模型、嵌入模型和重排序器——下方的评测数据均来自这些默认设置。若要使用 SOTA 因果模型(约 0.70 F1),请运行 pip install reasongraph[causal],图谱会自动使用该模型。配置部分是可选的深入内容,并非必读。
60 秒上手
Claude Code / Cursor / 任意 MCP 客户端,托管版(欧盟,不调用大模型):
claude mcp add --transport http memory https://memory.primaxiom.ai/mcp \
--header "Authorization: Bearer rgm_YOUR_KEY"
本地 MCP 服务(stdio,所有内容都在你的机器上,SQLite):
pip install "reasongraph[service,gliner,fastembed,sqlite]"
claude mcp add memory -- reasongraph-mcp
Python,进程内:
pip install "reasongraph[all]"
from reasongraph import ReasonGraph
graph = ReasonGraph()
graph.initialize_sync()
graph.add_texts_sync(["TSMC is building a chip fab in Phoenix, Arizona.",
"Arizona ordered water cuts for industrial users in Maricopa County."])
print(graph.discover_sync("water and chips")) # a path: water cuts -> Arizona -> TSMC fab
任意语言,通过 HTTP(可自托管或使用托管版):
curl -X POST https://memory.primaxiom.ai/sessions/notes/memory \
-H "Authorization: Bearer rgm_YOUR_KEY" -H "Content-Type: application/json" \
-d '{"text": "Apple sources M-series chips from TSMC in Arizona."}'
可直接复制使用的 Agent 示例(Groq/兼容 OpenAI 的研究 Agent、共享一份记忆的两个 Agent、使用持久记忆的 Claude Code、LangGraph)位于
examples/agents/。
托管版:ReasonGraph Cloud
memory.primaxiom.ai 将此库作为服务运行:注册后即可获取免费密钥(每月 10k 次请求)、远程 MCP 端点、浏览器控制台和 Playground。 提取过程由 PrimAxiom 运营的服务器上的小型模型执行(目前位于欧盟);只有在你请求生成综合答案时,事实才会发送给大模型提供商。抢先体验中。
JavaScript 和 TypeScript
非 Python 编写的 Agent 可通过无依赖客户端(clients/typescript)与托管服务通信。它使用平台的 fetch,因此可在 Node 18+、
Bun、Deno、Cloudflare Workers 和浏览器中运行。npm install reasongraph。
import { Memory, memoryTools } from "reasongraph";
const mem = new Memory({ apiKey: process.env.REASONGRAPH_API_KEY });
await mem.remember("scout", "TSMC is building a chip fab in Phoenix, Arizona.");
const facts = await mem.discover("What could disrupt the Phoenix fab?");
对于无需框架、直接调用函数的 Agent,memoryTools(mem) 会返回附带执行器的 OpenAI 风格工具定义:remember、recall 和 why。
安装
pip install reasongraph[all] # everything included
或者只安装你需要的内容:
pip install reasongraph # core: in-memory backend, NER extraction, embeddings
pip install reasongraph[gliner] # + GLiNER entity extraction + hybrid causal (default, recommended)
pip install reasongraph[causal] # + SOTA span-pointer causal model (~0.70 F1) + hybrid fallback
pip install reasongraph[gliner2] # + GLiNER2 alternative (single model does entities + causal)
pip install reasongraph[sqlite] # + SQLite backend with sqlite-vec
pip install reasongraph[postgres] # + PostgreSQL + pgvector backend
pip install reasongraph[service] # + HTTP + MCP memory service
pip install reasongraph[fastembed] # + pure-ONNX embedder / reranker (faster cold start)
跨来源发现
两份报告讨论不同主题。来源 A 介绍 TSMC 的半导体工厂。来源 B 介绍亚利桑那州的水资源危机。两者都未提及对方讨论的主题。
import asyncio
from reasongraph import ReasonGraph
source_a = [ # Tech industry report
"TSMC announced plans to build a $40 billion semiconductor fabrication plant in Phoenix, Arizona.",
"The Phoenix fab requires 10 million gallons of purified water daily to cool wafers during the chip etching process.",
"TSMC signed a long-term supply agreement with Apple to manufacture next-generation M-series processors at the Arizona facility.",
"Construction delays at the Phoenix site pushed first production to late 2025, raising concerns among TSMC's major customers.",
]
source_b = [ # Environmental report -- never mentions TSMC, semiconductors, or chips
"Arizona declared a water emergency after Lake Mead dropped to its lowest level since the 1930s, threatening water supply for millions.",
"The Arizona Department of Water Resources ordered mandatory water cuts for all industrial users in Maricopa County, where Phoenix is located.",
"Intel paused expansion of its Chandler, Arizona chip plant citing water availability concerns and rising operational costs.",
"Apple warned investors that component shortages from its Asian and North American suppliers could impact iPhone production timelines through 2026.",
]
async def main():
async with ReasonGraph() as graph:
await graph.add_texts(source_a)
await graph.add_texts(source_b)
results = await graph.query("How does the Arizona water crisis affect semiconductor manufacturing?")
for i, text in enumerate(results, 1):
source = "A" if text in source_a else "B"
print(f"{i}. [Source {source}] {text}")
asyncio.run(main())
1. [Source B] Intel paused expansion of its Chandler, Arizona chip plant citing water availability concerns and rising operational costs.
2. [Source B] The Arizona Department of Water Resources ordered mandatory water cuts for all industrial users in Maricopa County, where Phoenix is located.
3. [Source A] The Phoenix fab requires 10 million gallons of purified water daily to cool wafers during the chip etching process.
4. [Source B] Arizona declared a water emergency after Lake Mead dropped to its lowest level since the 1930s.
5. [Source A] TSMC announced plans to build a $40 billion semiconductor fabrication plant in Phoenix, Arizona.
6. [Source A] TSMC signed a long-term supply agreement with Apple to manufacture M-series processors at the Arizona facility.
结果来自两个来源。没有任何单份文档包含这条推理链。以下是其底层工作原理:
ReasonGraph 会从每段文本中提取实体和因果关系(需要实体与因果关系提取器,例如 pip install reasongraph[gliner] 或 [all]):
| 文本(节选) | 实体 | 因果关系 |
|---|---|---|
| TSMC 将在亚利桑那州凤凰城建设晶圆厂…… | TSMC、凤凰城、亚利桑那州 | -- |
| 凤凰城晶圆厂需要 1,000 万加仑水…… | 凤凰城 | -- |
| TSMC 与 Apple 的供应协议…… | TSMC、Apple、亚利桑那州 | -- |
| 凤凰城工地的施工延误…… | TSMC、凤凰城 | 施工延误 -> 首次生产 |
| 亚利桑那州水资源紧急状况、米德湖…… | 亚利桑那州、米德湖 | 米德湖水位下降 -> 水资源紧急状况 |
| 马里科帕县强制限水…… | 亚利桑那州水资源部、凤凰城、马里科帕县 | -- |
| Intel 暂停亚利桑那州芯片工厂项目…… | Intel、钱德勒、亚利桑那州 | -- |
| Apple 警告零部件短缺…… | Apple | 零部件短缺 -> iPhone 生产时间表 |
两个来源中都出现了三个实体,从而形成桥接节点:
| 桥接实体 | 来源 A 的关联 | 来源 B 的关联 |
|---|---|---|
| 亚利桑那州 | TSMC 晶圆厂、TSMC 与 Apple 的协议 | 水资源紧急状况、Intel 暂停项目、限水 |
| 凤凰城 | TSMC 晶圆厂、用水、延误 | 对工业用户限水 |
| Apple | TSMC 供应协议 | 零部件短缺警告 |
查询遍历路径:
水资源危机查询 -> 通过嵌入向量查找两个来源中的相关文本 -> 沿着 Arizona 和 Phoenix 实体边,发现 TSMC 的高耗水晶圆厂 -> 沿着 TSMC 供应协议中的 Apple 实体边,找到 Apple 的零部件短缺警告。因果关系 Lake Mead dropped -> water emergency 将环境诱因与工业影响联系起来。
完整演示:uv run python examples/cross_source_discovery.py
快速入门
使用内置数据集
from reasongraph import ReasonGraph
graph = ReasonGraph()
graph.initialize_sync()
graph.load_dataset_sync("financial")
results = graph.query_sync("What caused the 2008 financial crisis?")
for i, text in enumerate(results, 1):
print(f"{i}. {text}")
graph.close_sync()
输出——一条连贯的推理链,而不只是关键词匹配:
1. Lehman Brothers filed for bankruptcy in September 2008 after massive MBS losses.
2. Loose lending standards fueled a housing price bubble across the United States.
3. Lehman's collapse triggered a global credit freeze as interbank lending stopped.
4. Mortgage-backed securities built on subprime loans collapsed when defaults surged.
5. The U.S. government enacted TARP, a $700 billion bailout to stabilize the financial system.
6. Banks issued subprime mortgages to borrowers with poor credit histories.
异步 API
import asyncio
from reasongraph import ReasonGraph
async def main():
async with ReasonGraph() as graph:
await graph.load_dataset("financial")
results = await graph.query("What caused the 2008 crisis?")
for text in results:
print(text)
asyncio.run(main())
功能
- 跨来源发现——通过共享实体和因果关系,关联独立文档中的事实
- 自动提取——添加内容时会提取实体(默认使用 GLiNER
gliner_small-v2.5)和因果关系(因果关系专用 span-pointer / 混合模型),默认均已启用;未安装gliner时,会依次回退到 GLiNER2 和 BERT NER - Agent 记忆——支持作用域/会话、跨会话发现、矛盾解决(软替代)、时间回溯(
as_of)、语义去重和自动遗忘 - 因果推理——追踪下游影响、根本原因和有向因果路径;支持提出反事实问题
what_if - 混合搜索——结合嵌入相似度、关键词(三元组)匹配,或同时使用两者
- 多跳遍历——沿图边遍历,发现相互关联的推理链
- 交叉编码器重排——在每一跳使用交叉编码器重新排序结果(默认使用
ms-marco-MiniLM-L-6-v2,托管服务中使用多语言 mMARCO) - 记忆服务——提供开箱即用的 HTTP + MCP 服务,供 Agent 共享和查询记忆
- 内置数据集——加载精选推理图,即刻使用
- 异步优先——原生异步 API,并提供同步便捷封装
- 可插拔后端——内存(默认零配置)、SQLite 或 PostgreSQL(搭配 pgvector)
因果评估案例
tests/data/causal_cases.jsonl(40 个已审核案例)、tests/data/causal_cases_batch2.jsonl
(另有 40 个,8 个领域各 5 个,其中 12 个为非英语案例)、tests/data/causal_cases_batch3.jsonl(另有 60 个:
德语、西班牙语、法语各 20 个)以及 tests/data/causal_cases_batch4_trnl.jsonl(土耳其语 20 个、
荷兰语 20 个)均包含带有标准链条的多来源原因类问题。运行 python tests/eval_causal_cases.py --cases tests/data/causal_cases_batch2.jsonl。
使用默认模型在 batch2 上的基准结果:链条恢复率 100%,顺序正确率 47%,答案正确率 100%,
因果链正确率 65%。
模型
每个模型插槽都可替换;以下是默认配置,也是 ReasonGraph Cloud 使用的配置。 这些模型都很小,可在 CPU 上运行。
| 步骤 | 库默认配置 | ReasonGraph Cloud | 备注 |
|---|---|---|---|
| 句子切分 | 关闭(使用 "sat" 或 "regex" 设置 split= 可启用) | SaT sat-3l-sm(wtpsplit) | 在杂乱文本上的边界恢复率为 84%,而正则切分器为 40% |
| 实体 | GLiNER gliner-community/gliner_small-v2.5 | 相同 | 零样本、多语言;在 6 种语言的检查中召回率为 97%,每次调用约 19 ms。REASONGRAPH_ENTITY_NORMALIZE=1(或 canonicalizer=EntityNormalizer())会将表面形式(“Sabah”/“sabah”、“Bulk Export's”/“Bulk Export”)合并为同一节点;REASONGRAPH_ENTITY_CONTAINMENT=1(或 link_contained_entities=True)还会通过索引首词查找,将事实关联到已有实体:该实体的完整词序列前缀匹配或扩展该事实中的实体(“malzeme”/“malzeme eksikliği”)。这两项默认均关闭:在 80 个案例的繁忙租户评估中,测得它们未提升根因召回率,且实体包含关联在 79% 的情况下跨越了无关案例;目前保留供实验使用 |
| 原因 → 结果 | Berk/causal-span-pointer-v2(微调版 mDeBERTa-v3,开放权重) | v3(目前不公开;目的从句方向准确率 97%,而原为 20%,CNC 0.705),以及阈值为 0.1 的令牌门控 | 在 CausalNewsCorpus 开发集上的 F1 为 0.70;门控可防止普通陈述进入因果图。REASONGRAPH_CAUSAL_ONNX=hf://owner/repo/file.onnx 可通过 onnxruntime 运行同一模型,在 CPU 上速度提升 2 倍,且跨度结果相同 |
| 嵌入 | all-MiniLM-L12-v2 | paraphrase-multilingual-MiniLM-L12-v2(fastembed) | 如果事实不全是英语,请切换 |
| 跨度关联 | 嵌入余弦相似度 ≥ span_link_threshold(0.85) | 测量后使用跨度关联交叉编码器(REASONGRAPH_SPAN_LINKER,logit ≥ REASONGRAPH_SPAN_LINK_LOGIT);可关联余弦相似度遗漏的改述跳步(在留出案例中,零错误关联时恢复率为 17/20,而非 7/20) | |
| 重排序器 | cross-encoder/ms-marco-MiniLM-L-6-v2 | cross-encoder/mmarco-mMiniLMv2-L12-H384-v1 | 在我们的评估中,多语言重排序器将德语发现率从 62% 提升到 88% |
| 矛盾检查 | 关闭(resolve_conflicts=True 需要解析器) | 在 llama.cpp 上运行 Berk/reasongraph-extractor-1.7b(微调版 Qwen3 1.7B,开放权重),并使用嵌入预筛选 | 在人工核验的配对数据上 F1 为 0.95;使用 2 个 CPU 线程时,每对约 0.4 s |
| 对话 / 文本回答 | 无(自行提供 call_model) | 外部提供商,目前是在 Groq 上运行的 gpt-oss-120b | 唯一使用大模型的步骤,且仅在使用对话功能或提问时才会运行 |
各模型插槽的评估脚本位于 tests/ 中(eval_causal_extraction.py、
eval_causal_cases.py),结果列在下方对应选项旁边。
内置数据集
| 数据集 | 描述 |
|---|---|
syllogisms | 经典三段论推理链 |
causal | 带实体标注的因果推理 |
taxonomy | 层级概念分类体系 |
financial | 金融危机因果链(2008 年金融危机、互联网泡沫、通胀、欧元区) |
medical | 医学因果链(心脏病、糖尿病、传染病、癌症) |
analysis_patterns | 数据分析推理:场景检测、技术选择、实现模式 |
graph.load_dataset_sync("financial")
搜索模式
embedding(默认)会根据含义查找种子。hybrid 会将其与词级三元组通道(pg_trgm 严格词相似度,在 Postgres 上由索引辅助)融合:即使嵌入模型从未见过问题中的名称、代码、数字或复合词,也能匹配包含这些内容的事实。keyword 仅使用三元组通道,适用于查找已知术语。
记忆循环也接受相同选项(MemoryLoop(search_mode="hybrid"),或
REASONGRAPH_LOOP_SEARCH=hybrid),并且默认仍为 embedding。
**实测结果,无需猜测:**在因果根因评测中(180 个案例、6 种语言、1 个繁忙租户),hybrid 没有帮助,反而略有影响:根因召回率从 9% 降至 7%,英语从 15% 降至 8%,p95 延迟增加 22%。原因是结构性的,而非调优失败:根因的表述与问题截然不同,因此匹配问题中的词会找到链条中段的填充信息,进而与根因争夺上下文预算。Hybrid 真正适用的场景(精确名称、代码、零件编号、标识符)确实存在,但这是一个查找任务,而该评测并未衡量这一点。对于查找型工作负载,请启用它;对于“为什么”类问题,请将其关闭。
遍历范围和方向
hops 限定遍历深度。causal_hops=(forward, backward) 按方向限定深度:
forward 沿着原因 -> 结果(后果)前进,backward 沿着结果 -> 原因(导致当前情况的因素)回溯。
实体桥接不受影响。“为什么”是一个回溯问题,因此非对称分配遍历深度,可能找到对称遍历遗漏的根因:
loop = MemoryLoop(graph, session="chat", causal_hops=(3, 2)) # or REASONGRAPH_CAUSAL_HOPS=3,2
默认值为对称(None),在我们自己的因果评测中,拆分深度完全没有影响:
由于整体 hops 限制会先起作用,其中没有任何路径会在一个方向上经过超过两条因果边,因此按方向设置上限从未触发。只有当你的图中确实存在单向长链(纯粹的原因到原因链条,至少 4 跳)时,才应使用此选项。我们也测量了提高 hops 的效果:深度设为 5,在 180 个案例中只多答对 2 个,但数据库工作量约翻倍,因此默认值保持为 3。
关联同一事件的两种表述
同一事件可能有两种写法:一句话说“成本有所降低”,下一句则说“成本降低”。通用检索嵌入模型的余弦相似度无法将它们关联起来;一旦关联失败,因果链就会断裂,而这正是根因容易丢失的地方,且已有实测证据。
这项判断有独立的用途,因此可以使用独立的模型。span_linker 接受以下任一形式:
ReasonGraph(span_linker="bi:my-org/same-event-multilingual") # a similarity model, scored by cosine
ReasonGraph(span_linker="my-org/same-event-cross-encoder") # a cross-encoder, scored pairwise
也可以使用具有相同取值的 REASONGRAPH_SPAN_LINKER。两者看到的都只有方向感知候选项:效果片段只会与原因片段进行比较。span_link_top_k 用于设置在进行判断前,一个片段要与多少个近邻进行比较。
默认值为 6。过去只要配置了关联器,就会将此值扩大到 10;实测证明这是错误的:
在 341 个案例中,更宽的候选列表会让遍历过程涌入大量相似项,使改写链条的收益减半,并把普通链条的小幅收益变成小幅损失。将其调窄是这项工作中效果最大的一项改进,而且没有任何成本。
默认情况下,关联器会替换嵌入模型自身的余弦相似度,这意味着它也可能丢失余弦相似度原本正确识别的关联。span_link_floor 会让它改为补充:保留余弦相似度原本识别的所有关联,关联器只负责处理余弦相似度处于下限与阈值之间的配对。
ReasonGraph(span_linker="bi:my-org/same-event", span_link_floor=0.6) # or REASONGRAPH_SPAN_LINK_FLOOR
在我们自己的语料库中,下限在所有设置下都证明是无效的:在关联器的候选集合中,余弦相似度与训练模型在下限发挥作用的区间内从未出现分歧,因此没有任何关联需要保留或剔除。我们仍保留此选项,因为其他语料库完全可能存在这种分歧;但未经测量,不要指望它能带来帮助。
将长对话保留在上下文窗口内
让长对话得以延续的有三件事:近期消息的原文、对更早内容的摘要,以及对后来证明相关的更早内容进行回忆。循环默认会完成第三件事,前两件则在会话中配置。
loop = MemoryLoop(
graph, session="support-chat",
max_history_tokens=6000, # fold once the transcript passes this
keep_tail_tokens=2000, # this much of the newest talk stays word for word
summarizer=my_model, # fn(messages, max_tokens=None) -> str; omit and old turns are dropped
summary_length_target=False, # cap the summary at its share of the budget; costs detail
summarize_in_background=True, # summarise after the reply, never before it
)
为什么按 Token 预算,而不是保留最近 N 条消息。 固定数量两头都不合适:十次只有一行的交流不算什么,而十段粘贴的堆栈跟踪就会撑爆窗口。预算统计的是实际占用的内容。
为什么一次折叠一整块。 当对话记录超出预算时,尾部之前的所有内容会变成一份摘要,尾部则保留原文。按块而不是按消息生成摘要,意味着摘要器很少运行;而从未达到预算的对话则完全不需要生成摘要。下一次折叠会将先前的摘要与新变旧的消息一并纳入,因此摘要会合并,而不是层层叠加。
摘要会占用多少预算,以及为什么默认不设上限。 摘要和原文尾部共享 max_history_tokens,因此统计尾部后剩余的空间就是摘要可用的空间:loop.summary_budget_tokens。没有任何机制能强制模型遵守这个预算。在 qwen2.5 7B 上进行了 8 次折叠测试,每次分配 110 个 Token,并保留一份后续回复可能需要的 17 项详情:
| 折叠 8 次后的摘要 | 保留的详情 | |
|---|---|---|
| 未指定长度(默认) | 374 个 Token | 17 项中的 17 项 |
summary_length_target=True | 228 个 Token | 17 项中的 11 项 |
指定长度后,模型会尽量接近目标,并通过删减详情来达到要求;不指定长度时,它会保留所有内容,反而超出分配的空间。两种方式都各有取舍,选择权在你:默认设置会保留详情,而当提示词必须适配时,summary_length_target=True 会将 summary_budget_tokens 作为 max_tokens 传给摘要器。无论哪种方式,只接受 messages 的摘要器都能正常工作。一个模型、一份对话记录、一个随机种子——值得在你自己的环境中重新测量。
摘要器不会处于关键路径上。 折叠时会使用会话已有的摘要,并记录仍待摘要的内容。启用 summarize_in_background 后,工作会在回复发送后进行,因此摘要会延后一轮才生成;不启用时,折叠会等待摘要完成。无论哪种方式,即使摘要器运行缓慢、不可用或没有返回内容,对话仍能正常进行,因为旧轮次的措辞丢失尚可接受,而回复被阻塞则不可接受。
摘要不是什么。 摘要是模型改述的内容,没有来源支撑,因此会作为独立的系统消息出现,不会与有来源依据的回忆事实混在一起。而且,摘要在这里没有其他场景那么重要:从窗口中移出的消息已由 observe 存为事实,因此在相关时会按语义被重新找回。摘要是为了保持连贯,而不是为了记忆。
回指的笔记
人们会通过指代来串联因果,而不是重复表述:“这导致结账出错”,“因此我们回滚了”。抽取时一次读取一句话,所以“这”指向它看不到的内容,联系也就丢失了。在真实的事故复盘中,句子之间约有五分之一的因果联系属于这种情况。
ReasonGraph(resolve_back_references=True) # or REASONGRAPH_RESOLVE_BACK_REFERENCES=1
以回指开头并断言因果关系的句子,会与前一条笔记建立关联。这种表达方式因语言而异,而且往往根本不是代词:德语和荷兰语会将指代包含在副词中(“dadurch”“daardoor”),土耳其语则用格后缀标记(“bu nedenle”“bundan dolayı”);而像“as a result”或“por ello”这样的连接词本身就能断言这种联系。
默认关闭,下面如实说明当前情况。 在生成的案例中,它能找回三分之一原本会丢失的联系,而且不会错误关联;对现有评估则毫无影响。尚未确定的是它在常规流量中的准确率,因此请谨慎开启并进行检查。这是规则无法解决的两个限制:西班牙语和土耳其语经常省略主语,因此没有标记可供查找;而土耳其语倾向于在单个句子中表达因果,所以一开始就较少有这类联系需要找回。
经过测量,你可以跳过我们试过的方法。 通用交叉编码器的表现 不如 纯余弦相似度。另一方面,专门训练的同一事件模型在两端表述不同的案例中,将根因召回率提高了三倍(从 1.6% 提高到 4.9%),但对原本不需要它的案例略有影响(从 6.0% 降至 5.0%)。这种权衡是否值得,取决于你的文本:当约四分之一的因果联系在两端的表述不同时,收益就能抵消成本;而在普通领域笔记中,大约三分之二的联系属于这种情况,因此通常能带来约三倍的收益。在假定它适用之前,请先衡量你自己的数据构成。
大规模场景下的可重复答案
在一个拥有 100k 条事实的租户上进行测量后,结果令人放心:运行中的服务已经可以稳定复现结果。 针对同一个索引,对同一个问题提问两次,每次、每个版本得到的事实都相同。结果中的顺序也固定,且没有额外成本。
会发生漂移的是重建索引。由于搜索是近似的,使用相同数据独立构建的两个索引会返回略有不同的邻居。在该租户上,80 个问题中有 30 个在两次全新构建之间结果不同,其中 4 个甚至改变了根因,因此这不仅仅是表面差异。
要让两次重建结果一致,需要使用精确搜索,这会使召回延迟增加约 3.6 倍。这是迁移设置,不是服务设置:
REASONGRAPH_PG_DETERMINISTIC=1 # exact search: rebuild-stable, much slower
REASONGRAPH_PG_EF_SEARCH=<n> # a wider candidate window, if you want it too
服务时保持两项设置关闭。重建存储并需要让答案与被替换的存储一致时,才开启第一项。
分层遍历
召回会从种子节点开始向外遍历图。每一层通过一次后端调用获取(nearest_neighbors_many),而不是每个节点调用一次,因为在网络环境中,召回的成本取决于往返次数:测量发现,每次召回要往返 110 次,数据库每次往返耗时 5 ms,几乎使召回延迟增加了四倍。无法批量处理的后端会沿用逐项循环的默认实现,因此这一点对使用者是透明的。
在一次召回中,事实的作用域、因果关系和时间戳只会获取一次(ReasonGraph.request_cache(),由 MemoryLoop.recall 启用):一次召回会多次遍历有重叠的事实,而在网络环境中,每次重复读取都意味着一次往返。请求结束时缓存会被丢弃,因此不同请求之间不会出现过期数据,并且只会缓存只读元数据。
对于会运行多个写入方的部署(例如 API 加抽取工作进程),并发处理会按键顺序执行节点和边的 upsert,避免写入方在相同的行上死锁;架构创建会通过咨询锁进行串行化;瞬时写入失败(死锁、序列化失败、已中止的流水线)会在退避后重试。
# Pure embedding similarity (default)
results = graph.query_sync("credit freeze", search_mode="embedding")
# Pure keyword/trigram matching
results = graph.query_sync("credit freeze", search_mode="keyword")
# Hybrid: Reciprocal Rank Fusion of embedding + trigram rankings
results = graph.query_sync("credit freeze", search_mode="hybrid")
# Tune the RRF smoothing constant (default 60, lower = more weight to top ranks)
results = graph.query_sync("credit freeze", search_mode="hybrid", rrf_k=30)
实体与因果关系抽取
实体抽取和因果关系抽取是两项独立且默认同时启用的能力。安装了 gliner 时,add_text() / add_texts() 会使用 gliner_small-v2.5 抽取实体(速度快、支持多语言、实体召回率最高);若未安装,则回退到 GLiNER2,然后是 BERT NER。可通过 extractor 参数按调用覆盖,例如使用 gliner_large-v2.5 获得更高的精确率。
from reasongraph import ReasonGraph, NERExtractor, GLiNER2Extractor
graph = ReasonGraph()
graph.initialize_sync()
# Default: GLiNER gliner_small-v2.5 for entities (+ the default causal model),
# falling back to GLiNER2 then BERT NER
entities = graph.add_text_sync("Apple released the iPhone in 2007.")
print(entities) # ['Apple', 'iPhone']
# Explicit: force BERT NER even if a GLiNER model is installed
entities = graph.add_text_sync("Apple released the iPhone in 2007.", extractor=NERExtractor())
# Explicit: GLiNER2 with custom entity types
gliner = GLiNER2Extractor(entity_types=["company", "product", "date"])
entities = graph.add_text_sync("Apple released the iPhone in 2007.", extractor=gliner)
# Conversational memory: ChatExtractor also captures preference/plan/topic,
# so "hard techno" or "visit" become bridgeable nodes -- not just people/places
from reasongraph import ChatExtractor
entities = graph.add_text_sync(
"I love hard techno and plan to visit Berlin.", extractor=ChatExtractor()
) # ['Berlin', 'hard techno', 'visit']
# Any callable works
entities = graph.add_text_sync("some text", extractor=lambda t: ["custom"])
因果推理(默认开启)
因果关系是核心功能,因此默认启用因果关系抽取(每次调用时可通过 causal=False 选择退出)。有向的原因->结果关系会成为图中的一等类型化边(label="causes"),与匿名实体桥接边相区分;discover() 会按事实返回这些关系:
graph.add_text_sync("Heavy rainfall caused severe flooding.")
# -> typed edge heavy rainfall --causes--> severe flooding
for fact in graph.discover_sync("flooding"):
print(fact["content"], fact["causes"]) # [{'cause': 'Heavy rainfall', 'effect': 'severe flooding'}]
因果链追踪
由于原因->结果边是有向的一等边,你可以遍历因果图——这是扁平向量存储无法做到的。追踪下游影响、回溯根本原因,或查找两个事实之间的有向因果路径:
graph.add_texts_sync([
"Heavy rainfall caused flooding.",
"Flooding caused power outages.",
"Power outages caused hospital disruptions.",
])
graph.trace_effects_sync("Heavy rainfall caused flooding.")["terminals"]
# e.g. -> ['hospital disruptions'] # downstream impact
graph.trace_causes_sync("Power outages caused hospital disruptions.")["terminals"]
# e.g. -> ['rainfall'] # upstream causes (same as root_causes_sync)
graph.causal_chain_sync("Heavy rainfall caused flooding.",
"Power outages caused hospital disruptions.")
# -> ordered causal hops, each cited to the fact that asserted it
具体跨度取决于因果抽取器;当一个事实的结果跨度与下一个事实的原因跨度匹配时,就会连接成一跳。每一跳都会标记断言它的事实、其作用域以及 cross_session 标志;配置了 conflict_resolver 后,默认会跳过已退役(被取代)的事实(include_superseded=True 则会保留它们)。可通过 max_depth(默认值为 6)和 max_visited(默认值为 1000)进行调整。Agent 可通过 trace_memory MCP 工具和 /trace HTTP 端点使用此功能。
反事实:如果某个事实为假,会破坏什么
因果边是一等要素,因此你可以反向查询追踪结果:
如果某个事实为假,哪些下游影响会失效? what_if 会假设性地剪除一个事实(不会修改图),重新遍历可达性,并报告哪些影响跨度失去了所有因果支持,哪些则通过备用路径得以保留。只有被剪除事实单独支持的边才会移除——其他事实也能解释的影响仍然成立。
graph.what_if_sync("Flooding caused power outages.")
# {
# 'pruned': 'Flooding caused power outages.',
# 'origin': 'Flooding caused power outages.', # walk start (== pruned unless origin= given)
# 'pruned_edges': [{'cause': 'flooding', 'effect': 'power outages'}],
# 'collapsed': [ # lost their only causal path
# {'span': 'power outages', 'fact': 'Flooding caused power outages.', 'depth': 0, ...},
# {'span': 'hospital disruptions', 'fact': 'Power outages caused hospital disruptions.', 'depth': 1, ...},
# ],
# 'survived': [], # spans an alternate path rescued
# }
传入 origin=,可测量相对于上游事实的失效情况;传入
direction='causes',则可查看哪些上游原因会变得孤立。该功能通过
what_if_memory MCP 工具和 /what_if HTTP 端点提供。
默认因果提取器会选择当前最佳后端。安装了
causal-span-model 软件包时,它会使用跨度指针模型
(CausalPointerExtractor):一个经过微调的 mDeBERTa-v3,在 Causal News Corpus Subtask-2 官方评分器上的 F1 分数约为 0.70,优于组织方的 0.627 基线、混合方案和少样本 LLM 基线(约为 0.24-0.41)。它使用英语训练,但推理时支持多语言(支持脚本感知分段,已在 es/fr/de/pt/tr/ru/ar 及 zh/ja 上验证),并内置因果门控,因此遇到非因果文本时不会返回结果。
内置门控可以替换为解耦嵌入门控:一个基于句子嵌入的小型分类器(在 causal-span-model 中使用 scripts/train_embed_gate.py 训练;训练结果会保存为 .joblib)。它每个句子的耗时为 1 毫秒,可使用任意负样本组合重新训练,而无需改动跨度头;在我们的因果评测中,它生成的边更少、更精准。传入本地路径或
hf://owner/repo/file.joblib 引用即可:
ReasonGraph(causal_extractor=CausalPointerExtractor(
model="Berk/causal-span-pointer-v2", gate_threshold=1.0, # built-in gate off
embed_gate="hf://Berk/causal-span-pointer-v2/embed_gate_mlp.joblib",
embed_gate_threshold=0.9)) # keep P(causal) >= 0.9
causal_chain 还会关联以不同措辞描述同一事件的事实(“the system
throttles performance” -> “Throttling performance”,或一个简单的根事实,其词语会在下一个原因跨度中再次出现),因此即使措辞发生变化,定向链仍能保留,无需设置 span_link_threshold。trace_causes(..., bridge=True) 也会以相同方式回溯:从原因跨度追溯到描述同一事件的其他事实的影响跨度(“the billing
job ran twice whenever a payment retried” -> “The billing job ran twice”);对于自身没有关系的简单事实,则追溯到描述该事实的影响跨度,因此,某人记录的症状(“the billing job started double-charging”)会指向之后记录的解释。
否则,它会回退到混合方案(HybridCausalExtractor):快速、无模型的多语言线索扫描可处理方向正确的显式和倒装措辞;不含因果连接词的句子(隐式因果关系)则交由 gliner-relex-multi 处理(Apache-2.0、mDeBERTa、约 100 种语言)。在包含四种情况(显式 / 多语言 / 隐式 / 倒装)的探测集上,混合方案的定向关系对召回率达到 100%,而任一组成部分单独使用时为 61-79%——两者各自弥补对方的盲点;且大多数句子不会调用模型,因此平均成本较低。可使用 tests/bench_causal_extractors.py 重现结果。
from reasongraph import CausalPointerExtractor, HybridCausalExtractor, GlinerRelexExtractor
ReasonGraph() # best available (pointer if installed, else hybrid)
ReasonGraph(causal_extractor=CausalPointerExtractor()) # force the span-pointer model
ReasonGraph(causal_extractor=HybridCausalExtractor()) # force the hybrid
ReasonGraph(causal_extractor=GlinerRelexExtractor()) # relex model only
ReasonGraph(causal_extractor=False) # disable causal extraction
pip install reasongraph[causal] 会同时安装指针模型
(causal-span-model)和混合方案(gliner),因此图默认会使用 SOTA 指针模型,并在需要时自动回退到混合方案。如果两者都不可用,默认行为是发出一次警告,而不是静默丢弃因果关系;如果无法解析任何因果提取器,add_text(..., causal=True) 会抛出异常。
摄取时的句子切分
管线中的每个模型都是使用单句训练的,因此将整段文字作为一个事实推送,会同时影响实体、因果跨度和检索效果(在我们的 39 个案例因果评测中,链召回率从 79% 降至 10%)。传入一个切分器,即可将每段文本拆分为每句一个事实:
ReasonGraph(sentence_splitter="sat") # Segment-any-Text, 85 languages: pip install reasongraph[split]
ReasonGraph(sentence_splitter="regex") # dependency-free fallback (punctuation + newlines)
graph.add_texts([paragraph], split=True) # or per call; split=False keeps a text whole
服务会读取 REASONGRAPH_SPLIT_SENTENCES=sat|regex;推送操作接受 split: true/false。
深度记忆集成:记忆循环
无需工具,也无需编写提示词:封装任意聊天模型,每次交互都会成为记忆,而相关内容会在下次调用前自动返回。
from reasongraph import ReasonGraph, MemoryLoop
graph = ReasonGraph() # or your Postgres-backed graph
loop = MemoryLoop(graph, session="support-chat", max_facts=8)
history = [{"role": "user", "content": "Why did the Rotterdam warehouse lose power?"}]
reply, context = loop.chat_sync(call_model, history, system="You are a careful assistant.")
# call_model is any fn(messages) -> str: OpenAI-compatible, Claude, Ollama, llama.cpp
# context.facts -> what was recalled (with sources and cause->effect links)
# context.roots -> for why-questions, the root cause(s) the chain walks back to;
# they are spelled out in the injected block so a small model
# answers with the root, not only the nearest cause
# the question and the reply are now remembered in "support-chat"
循环也会记住对话本身。这些已存储的轮次会像其他事实一样被召回,但会在其他所有内容之后进行排序:否则,你刚刚提出的问题和先前的“我不知道”会得分最高,占满所有位置。
graph.forget(scopes) 会擦除一个会话(或一个租户,或一次测试运行):事实以及仅由它们关联的实体都会被删除;在其他地方也出现过的句子会解除关联,而不是删除。
loop.messages(history) 会返回消息列表,并在其中注入一条包含召回事实的系统消息,方便你自行调用模型;loop.observe(user, assistant) 会存储一次交互。选项:max_facts / max_chars(上下文预算)、min_score(不添加无关内容)、rerank_min(在此基础上应用可选的交叉编码器阈值:余弦相似度无法区分“主题相同”和“回答了这个问题”,重排序器可以;使用默认重排序器时为 -4)、extend_query(当一个“为什么”问题的因果链以召回事实未明确陈述的根本原因结束时,再发起一次有针对性的查询,获取其背后的直接事实)、bridge_causes(默认开启:因果追踪会跨越对同一事件使用不同措辞的事实,并从直接症状出发;像“下次账单处理前有什么需要注意的吗?”这样的模糊问题就会以此为起点;在一个包含 15 起事件的繁忙记忆库中,使用小型生产环境嵌入模型时,这让此类问题召回根本原因的比例从 33% 提升到 73%)、observe_user / observe_assistant、redact(在存储前删除或改写文本的函数)、resolve_conflicts。托管服务通过 POST /chat 提供相同的循环。Agent 示例:examples/agents/memory_loop_agent.py。
LangChain 和 LangGraph
pip install langchain-reasongraph(LangChain 合作伙伴风格的包,包含 LangChain 标准的检索器测试;源码位于 packages/langchain-reasongraph)或
pip install "reasongraph[langchain]" 会添加三个适配器,可与本地的
ReasonGraph 或通过 reasongraph.client.MemoryClient 使用的托管服务配合:
from reasongraph.integrations.langchain import ReasonGraphRetriever, with_memory, memory_tools
retriever = ReasonGraphRetriever(target=graph) # documents = the facts the graph connects
model = with_memory(ChatOpenAI(...), graph, session="support-chat") # recall in, exchange remembered
tools = memory_tools(graph, session="agent") # remember / recall / discover for agents
文档会在 metadata 中携带来源、获取该事实所经由的名称,以及原因->结果链接。完整示例:examples/agents/langchain_memory.py;使用这些工具的 LangGraph Agent:examples/agents/langgraph_agent.py。
用于链和 Agent 的记忆类
ReasonGraphMemory 采用经典的 BaseMemory 结构(load_memory_variables /
save_context),因此可直接用于任何接受 LangChain 记忆的场景。每轮中,Agent 会看到两项内容:memory,图谱与输入相关联的事实及其来源和原因->结果链接;以及 history,按令牌预算折叠的对话记录,其中最早的轮次合并为一个滚动摘要,最新轮次则逐字保留。
from reasongraph.integrations.langchain import ReasonGraphMemory
from reasongraph.loop import make_summarizer
memory = ReasonGraphMemory(target=graph, session="ops-chat",
max_history_tokens=2000, keep_tail_tokens=800,
summarizer=make_summarizer(lambda msgs: llm.invoke(msgs).content))
seen = memory.load_memory_variables({"input": "Which version is staging on, and why?"})
seen["memory"] # "- We rolled staging back to 2.3.1 because the certificate expired. [ops-chat]\n because: ..."
seen["history"] # "Summary: ...\nHuman: How do I speed up rollbacks?\nAI: Keep the previous image warm..."
memory.save_context({"input": "..."}, {"output": "..."})
折叠只决定模型会看到什么,不会删除任何内容:离开窗口的轮次仍然是事实,在再次相关时会通过 memory 返回。摘要会在 save_context 之后生成,绝不会在一轮对话中途生成;即使没有摘要器,也会遵守预算。ReasonGraphChatMessageHistory 会将相同的对话记录作为 BaseChatMessageHistory 提供给 RunnableWithMessageHistory。逐轮展示 Agent 所见内容的演练:examples/agents/langchain_memory_class.py。
快速推理(可选,纯 ONNX)
默认配置已经达到下方的评测质量;此项优化仅用于提升速度、减少内存占用。每个模型槽位都可插拔,因此你可以将 PyTorch 默认模型换成针对 CPU 优化的 ONNX 模型,并保持同等或更好的质量。 在包含 32 个案例的混合领域评测中测得:
from reasongraph import ReasonGraph, FastEmbedEmbedder, FastEmbedReranker
graph = ReasonGraph(
embed_model=FastEmbedEmbedder("sentence-transformers/all-MiniLM-L6-v2"),
rerank_model=FastEmbedReranker("Xenova/ms-marco-MiniLM-L-6-v2"),
)
- 重排器 →
Xenova/ms-marco-MiniLM-L-6-v2:这是默认重排器的 ONNX 版本,因此评分(和评测质量)完全相同,但冷启动时间从约 2.4 秒降至约 0.03 秒。 - 嵌入模型 →
all-MiniLM-L6-v2(ONNX):加载速度约提升 2.3 倍,评测质量相同或更好。 - 完整 ONNX 流水线:冷启动速度约提升 3 倍,RAM 占用减少约 23%,且质量相同或更好;每次查询的延迟会增加(约从 12 毫秒升至 100 毫秒)。当冷启动和内存比热启动延迟更重要时,这是不错的取舍。
- 可选用多语言嵌入模型(
paraphrase-multilingual-MiniLM-L12-v2);代价是英语质量会略有下降。
需要运行 pip install reasongraph[fastembed]。使用 tests/bench_pipeline.py 对任意配置进行基准测试。
选择抽取器(可选)
你无需选择——默认配置(实体使用 gliner_small-v2.5,并搭配默认因果模型)是推荐且经过基准测试的方案。本节说明了该默认配置的依据,以及特殊情况下可选的方案;如果有特定需求,可通过 extractor 参数更换实体模型(使用 tests/bench_extractors.py 复现这些数据):
GlinerExtractor(默认)——采用转换并缓存的 ONNX 推理的 GLiNER v1 零样本方案(实体类型快速灵活;仅抽取实体,因果关系由单独的默认因果模型抽取)。默认使用gliner-community/gliner_small-v2.5,在 10 种语言的 WikiANN 基准测试中,其实体召回率领先(86%,GLiNER2 为 74%),速度约为 67 ms/次调用,占用约 2.2 GB;而且与 GLiNER2 不同,它在韩语、阿拉伯语、土耳其语和俄语上也表现出色。检查点版本影响很大:较旧的urchade/gliner_multi-v2.1得分约为 12%,因此请固定模型版本,并使用tests/bench_ner_multilingual.py进行基准测试。GLiNER2Extractor——一个模型即可在一次处理中抽取实体类型和因果关系。当你想用一个模型同时完成两者时可选用它,但它体量最大(加载较慢,占用约 4.6 GB),多语言实体召回率也较低。OnnxTokenClassifierExtractor——运行任意导出为 ONNX 的 BIO 词元分类模型,根据模型自身的id2label解码实体。速度快(约 30 ms/次调用),搭配合适的模型即可支持多语言;标签方案由模型决定,因此无需更改代码即可接入专用地点模型或自定义通用 NER 模型。
规模对比(同一 WikiANN 基准测试)——模型并非越大越好:
| model | 推理 | RAM | 召回率 | 精确率 | F1 |
|---|---|---|---|---|---|
gliner_small-v2.5 | 67 ms | 2.2 GB | 86% | 73% | 79% |
gliner_medium-v2.5 | 73 ms | 2.7 GB | 84% | 75% | 79% |
gliner_large-v2.5 | 142 ms | 4.8 GB | 86% | 84% | 85% |
knowledgator/gliner-x-base | 151 ms | 4.2 GB | 87% | 79% | 83% |
| GLiNER2 | 250 ms | 4.8 GB | 74% | 84% | 79% |
Small 的召回率与 large 不相上下;large 以更大的体量换来了更高的精确率(F1 最佳)。Medium 处于劣势,不建议选用。large-v2.5 全面胜过 GLiNER2(精确率相同、召回率更高、速度更快,在阿拉伯语和韩语上的表现也强得多)。knowledgator/gliner-x-base(支持 20 多种语言)的召回率和精确率略高于 small-v2.5,但需要 stanza 和 langdetect(运行时会下载各语言对应的模型),速度慢约 7 倍,而且在 WikiANN 中文重建数据上的表现并无优势,因此默认仍采用 small-v2.5;只有在精确率比延迟更重要时,才选用 x-base。
通过 ONNX 运行 gliner_small-v2.5(GlinerExtractor(onnx=True))可将推理时间从约 67 ms 缩短至约 12 ms/次调用,同时保持召回率——这是速度最快且多语言高召回的选项(首次使用时会缓存转换结果)。
简要建议:高召回且兼顾速度和内存时,选择 gliner_small-v2.5(设置 onnx=True 可达到约 12 ms/次调用);追求整体质量最佳、且在多语言表现上全面优于 GLiNER2 时,选择 gliner_large-v2.5;只有需要抽取因果关系时才选择 GLiNER2;地点密集型场景追求最快速度时,选择 place ONNX。
作用域
作用域是事实上的自由文本标签("user-alice"、"topic-economy"、"session-42"),不是分区。图谱保持共享:一条事实可同时带有多个作用域,多跳推理也会沿着所有作用域中的共享实体继续进行。query() 上的作用域只会缩小搜索的起点范围;遍历仍会到达其他作用域中的关联事实。
await graph.add_texts(alice_facts, scopes=["user-alice"])
await graph.add_texts(economy_facts, scopes=["topic-economy"])
# One fact can belong to several scopes at once
await graph.add_texts(shared, scopes=["user-alice", "topic-economy"])
# Seeds come from user-alice; reasoning still bridges into topic-economy facts
results = await graph.query("Will it get harder to afford a home?", scopes=["user-alice"])
将相同内容添加到新的作用域时,会合并标签(不会移除旧标签)。如果要设置严格边界,让查询只能访问自身作用域内的事实,请传入 isolate=True(参见多租户与生产环境)。
完整示例:uv run python examples/scoped_reasoning.py
后端
默认情况下,ReasonGraph() 使用纯 Python 内存后端(MemoryBackend)。它可以在任何环境中使用,除了 numpy 外无需其他依赖项。若要持久化,请传入文件路径,以 JSON 格式保存或加载:
from reasongraph import ReasonGraph, MemoryBackend
# In-memory only (default)
graph = ReasonGraph()
# In-memory with JSON file persistence (loads on init, saves on close)
graph = ReasonGraph(backend=MemoryBackend(file_path="graph.json"))
SQLite 后端
对于较大的图或并发访问,请使用 SQLite 后端搭配 sqlite-vec 进行向量搜索。需要运行 pip install reasongraph[sqlite]。
from reasongraph import ReasonGraph
from reasongraph.backends import SqliteBackend
graph = ReasonGraph(backend=SqliteBackend(db_path="graph.db"))
PostgreSQL 后端
from reasongraph import ReasonGraph
from reasongraph.backends import PostgresBackend
graph = ReasonGraph(backend=PostgresBackend(database_url="postgresql://user:pass@localhost/db"))
需要运行 pip install reasongraph[postgres],并在数据库中启用 pgvector 和 pg_trgm 扩展。
评估:混合领域推理
我们将所有 6 个内置数据集加载到单个图中(约 130 个文本节点、约 104 个实体节点、约 280 条边),以评估推理质量,并测试该库能否追踪正确的因果链、三段论证明、分类层级和数据分析模式,同时不受其他领域无关事实的干扰。
32 个测试用例模拟了 Agent 风格的查询,例如 “我想了解导致 2008 年金融危机的原因”、“胰岛素抵抗如何导致肾衰竭?” 或 “我有两列数值,请检查它们是否相关”,并检查返回的推理链是否符合预期的真实情况。
各领域结果(混合搜索,top_k=5、hops=4、rerank_top_k=4):
| 领域 | 用例数 | 链完整度 | Recall@5 | Precision@5 | 领域准确率 |
|---|---|---|---|---|---|
| 因果 | 5 | 100% | 100% | 92% | 100% |
| 金融 | 6 | 100% | 82% | 60% | 100% |
| 医疗 | 5 | 100% | 92% | 76% | 92% |
| 三段论 | 5 | 100% | 100% | 92% | 85% |
| 分类体系 | 3 | 100% | 83% | 53% | 92% |
| 分析模式 | 8 | 96% | 75% | 45% | 96% |
| 总体 | 32 | 99% | 88% | 68% | 95% |
32/32 个用例通过(链完整度 >= 50%)。拆分重排会优先考虑链的延续(文本到文本的边),而非桥接发现(实体到文本的边),使遍历保持聚焦。
搜索模式对比:
| 模式 | 链完整度 | Recall@5 | Precision@5 | 领域准确率 |
|---|---|---|---|---|
| 嵌入 | 99% | 88% | 68% | 95% |
| 关键词 | 0% | 0% | 0% | 0% |
| 混合 | 99% | 88% | 68% | 95% |
仅关键词模式的得分为 0%,因为评估查询是自然语言问题,与数据集中的陈述句无法通过子字符串匹配。这是预期结果——关键词搜索适用于已知词查找,而非问答。
复现:uv run python tests/eval_financial_reasoning.py
API 参考
ReasonGraph(backend=None, embed_model=None, rerank_model=None, forget_after=30, forget_every=None, synthesizer=None, causal_extractor=None, isolate_traversal=False, conflict_resolver=None)
causal_extractor:None会延迟构建当前可用的最佳因果提取器(若已安装causal-span-model,则使用 span-pointer 模型,否则使用混合提取器);False会禁用因果提取;可调用对象或带有extract_causal的对象会被使用。isolate_traversal:图级默认设置,用于指定限定范围的查询是否将遍历限制在其范围内(多租户)。关闭时会保留跨范围发现;可通过query(..., isolate=...)针对每次调用进行覆盖。conflict_resolver:启用写入时的矛盾解决(软替代)和读取时的已退役事实过滤(参见多租户与生产环境)。
摄取
| 方法 | 描述 |
|---|---|
add_nodes(nodes, scopes=None) | 将 (content, type) 元组添加到图中 |
add_edges(edges) | 添加 (from, to) 或 (from, to, label) 内容边(例如,标签为 "causes") |
add_text(text, extractor=None, scopes=None, causal_extractor=None, causal=None, dedup_threshold=None, resolve_conflicts=None) | 添加文本并提取实体和因果关系;causal=False 禁用因果提取,True 强制启用(不可用时会报错);dedup_threshold 会丢弃近似重复项(并合并其作用域);设置 conflict_resolver 后,resolve_conflicts 会对存在矛盾的事实进行软替代 |
add_texts(texts, extractor=None, causal_extractor=None, scopes=None, causal=None, dedup_threshold=None, resolve_conflicts=None) | add_text 的批量形式(默认启用因果提取) |
检索
| 方法 | 描述 |
|---|---|
query(query, top_k=5, hops=4, rerank_top_k=4, search_mode="embedding", rrf_k=60, recency_weight=0.0, scopes=None, isolate=None, include_superseded=False, as_of=None) | 搜索并遍历图。recency_weight 取值范围为 [0,1],用于将时效性纳入排名;scopes 会缩小种子范围(除非设置 isolate=True,否则遍历仍会跨越作用域);as_of=<datetime> 可回溯到指定时间当时的状态;include_superseded=True 会保留已退役的事实 |
query_detailed(...) | 与 query 的签名相同,但每个命中项会返回 {content, score, created_at, scopes},用于阈值筛选和去重 |
discover(query, top_k=5, hops=4, search_mode="embedding", rrf_k=60, scopes=None, max_results=10, max_visited=1000, isolate=None, include_superseded=False) | 类似 query,但会返回关联路径——说明每条事实如何通过桥接实体关联回种子,并标注作用域、标记跨会话链接,以及列出每条事实的有向 causes 关系。遍历在访问 max_visited 个节点后停止 |
answer(query, use_discover=True, top_k=5, hops=4, search_mode="embedding", scopes=None, max_results=10) | 通过可插拔的 synthesizer,将检索到的事实和路径改写为逻辑连贯的自由文本 |
因果推理
| 方法 | 描述 |
|---|---|
trace_effects(content, max_depth=6, scopes=None, isolate=None, include_superseded=False, max_visited=1000) | 向前追踪因果关系:返回 {origin, chain, terminals},展示下游影响 |
trace_causes(content, ...) | 向后追踪因果关系:找出导致 content 的原因 |
root_causes(content, ...) | 返回 content 背后的根因跨度(向后遍历的终端节点) |
causal_chain(from_content, to_content, max_depth=6, scopes=None, isolate=None, include_superseded=False) | 返回连接两条事实的有序因果跳转;若不存在则返回 None |
what_if(content, origin=None, direction="effects", max_depth=6, scopes=None, isolate=None, include_superseded=False, max_visited=1000) | 反事实分析:剪除某条事实,并报告下游跨度中哪些已collapsed、哪些仍survived |
更新、遗忘与时间
| 方法 | 描述 |
|---|---|
delete(content, purge_orphans=False) | 按完全匹配的内容移除节点及其关联边;purge_orphans=True 还会移除因此悬空的实体 |
supersede(old_content, new_text, extractor=None, purge_orphans=False) | 替换过时事实:添加 new_text,然后删除 old_content |
supersession_history(content) | 审查某条事实的 {supersedes, superseded_by} 信息 |
delete_stale() | 移除在 forget_after 天内未被访问的节点 |
maybe_forget() | 限流版 delete_stale():最多每隔 forget_every 秒清理一次(当 forget_every 为 None 时不执行任何操作) |
数据集与检查
| 方法 | 描述 |
|---|---|
load_dataset(name) | 加载内置数据集 |
get_all_nodes(scopes=None) / get_all_edges() | 检查图的内容(节点可按作用域筛选) |
生命周期为 initialize() / close(),也可以使用 async with ReasonGraph() as graph:。所有方法都是异步的;每个方法都有一个参数相同的 _sync 对应方法(例如 query_sync、what_if_sync、add_text_sync)。 |
embed_model 接受模型名称(str)、SentenceTransformer,或任何可对文本进行编码的对象/可调用对象。编码器必须接受 str(返回一个向量)或 list[str](为每段文本返回一个向量);numpy、torch 或 list 类型的输出均可接受。这样,宿主就可以复用已在运行的嵌入器,而不必再加载一套:
def encode(text_or_texts):
# reuse your own embedding library; return list[float] or list[list[float]]
...
graph = ReasonGraph(embed_model=encode)
Agent 记忆服务
一个可直接使用的服务能将 reasongraph 转变为供多个 Agent 共享且可发现的记忆。知识会话是作用域:Agent 将记忆推送到自己的会话中,查询从该会话开始,但遍历会跨越所有会话——因此 Agent 可以通过共享实体发现彼此记忆之间的联系。pip install reasongraph[service]。
from reasongraph.service import MemoryService
from reasongraph.backends import PostgresBackend
service = MemoryService(backend=PostgresBackend("postgresql:///memory"),
synthesizer=my_small_llm) # synthesizer is optional
await service.push("research-bot", "TSMC is building a chip fab in Arizona.")
await service.push("news-bot", "Arizona declared a water emergency.")
# research-bot discovers news-bot's fact via the shared 'Arizona' entity
paths = await service.discover("Arizona", session="research-bot")
answer = await service.answer("Arizona", session="research-bot") # logical free text
可通过 HTTP(reasongraph.service.http.create_app)或 MCP(reasongraph.service.mcp_server.create_mcp)提供服务——HTTP 的 query / discover 端点接受 synthesize 标志,启用后会添加自由文本 answer。演示 uv run python examples/agent_memory_service.py 会在五个 Agent 会话中推送一个涵盖经济 / 供应链 / 能源 / 健康 / 政策的世界,并展示一次市场查询如何触达其他 Agent 记录的台湾干旱和芯片晶圆厂。
HTTP 端点:POST /sessions/{session}/memory(以及 /batch;传入 resolve_conflicts: true 可检查附近事实是否存在矛盾)、/query(支持用于时间回溯的 as_of 和 include_superseded)、/discover、/causal_chain、/supersede、/delete、/history、/trace、/what_if、/forget、GET /sessions、/stats,以及 /health 和 /ready 探测端点。
MCP 工具:push_memory、query_memory、query_memory_detailed、discover_connections、causal_chain_memory、trace_memory、what_if_memory、answer、update_memory、delete_memory、memory_history、forget_stale、list_sessions。
合成器
answer() 和 synthesize 标志会将检索到的事实及其连接路径改写为逻辑连贯的自由文本。核心部分不依赖模型——可传入任意 callable(query, context) -> str,或使用随附的适配器之一:
from reasongraph import TemplateSynthesizer, PromptSynthesizer, TransformersSynthesizer
ReasonGraph(synthesizer=TemplateSynthesizer()) # deterministic, no model
ReasonGraph(synthesizer=PromptSynthesizer(my_generate)) # bring any LLM: generate(prompt) -> str
ReasonGraph(synthesizer=TransformersSynthesizer()) # local small LLM (Qwen2.5-0.5B-Instruct)
PromptSynthesizer 会构建提示词(问题 + 事实 + 跨会话桥接信息),并调用你的 generate(同步或异步);TransformersSynthesizer 则会在 sentence-transformers 已加载的 torch/transformers 技术栈上,于本地运行一个小型指令模型。
部署
由环境变量驱动的入口点会配置后端、嵌入器和合成器。使用 Docker 运行基于 Postgres 的堆栈:
docker compose up --build # Postgres (pgvector) + the service on :8000
curl localhost:8000/stats
或者直接提供服务(pip install reasongraph[service] 会添加 reasongraph-serve
控制台脚本和 ASGI 工厂):
REASONGRAPH_BACKEND=postgres \
REASONGRAPH_DATABASE_URL=postgresql:///memory \
REASONGRAPH_SYNTHESIZER=template \
reasongraph-serve # or: uvicorn reasongraph.service.app:create_app_from_env --factory
| 变量 | 默认值 | 用途 |
|---|---|---|
REASONGRAPH_BACKEND | memory | memory | sqlite | postgres |
REASONGRAPH_DATABASE_URL | -- | Postgres URL、sqlite 路径或 memory JSON 路径 |
REASONGRAPH_EMBED_MODEL | 内置 | 模型名称;纯 ONNX 模型使用前缀 fastembed: |
REASONGRAPH_SYNTHESIZER | template | none | template | transformers |
REASONGRAPH_SYNTH_MODEL | 内置 | transformers 合成器使用的指令模型 |
REASONGRAPH_FORGET_AFTER / REASONGRAPH_FORGET_EVERY | 30 / 关闭 | 自动遗忘窗口(天)和清理间隔(秒)。设置间隔后,服务会在后台任务中运行清理。 |
REASONGRAPH_ISOLATE | 关闭 | 将遍历限制在查询会话内(多租户)。关闭时会保留跨会话发现。 |
REASONGRAPH_RESOLVE_CONFLICTS | 关闭 | 使用默认的 NLI 解析器启用矛盾消解(软覆盖)。 |
REASONGRAPH_API_KEY | -- | 设置后,数据端点需要此密钥(Authorization: Bearer 或 X-API-Key);/health 和 /ready 仍保持开放。 |
REASONGRAPH_DEFER_EXTRACT | 关闭 | 在后台工作进程中(而非事件循环中)运行实体/因果关系提取,使推送请求立即返回。 |
REASONGRAPH_SPAN_LINK_THRESHOLD | 关闭 | 余弦阈值(例如 0.85);高于此值时,新的原因/结果跨度会通过 same_as 关联到现有因果跨度,从而让 trace_* / causal_chain 能跨越对同一事件采用不同措辞的事实。 |
REASONGRAPH_CAUSAL_MODEL | berk/causal-span-pointer-mdeberta | 跨度指针模型的 HF 仓库 ID 或本地目录。 |
REASONGRAPH_CAUSAL_GATE_THRESHOLD | 0.5 | 内置门控阈值:P(non-causal) 高于此值时,指针会弃权;1.0 会将其关闭。 |
REASONGRAPH_CAUSAL_EMBED_GATE | 关闭 | 嵌入门控分类器的路径或 hf://owner/repo/file.joblib;低于 REASONGRAPH_CAUSAL_EMBED_GATE_THRESHOLD(默认值 0.9)的文本不会生成关系。 |
REASONGRAPH_DEDUP_THRESHOLD | 关闭 | 余弦阈值(例如 0.95);高于此值时,推送的事实会被视为现有事实的释义:合并作用域,不存储新内容。 |
REASONGRAPH_HOST / REASONGRAPH_PORT | 0.0.0.0 / 8000 | reasongraph-serve 的绑定地址和端口。 |
实际使用多 Agent 并发时,请使用持久化后端(PostgresBackend)。
多租户与生产环境
默认情况下,图是共享的:限定范围的查询会从其会话开始,但遍历会跨越多个会话,这就是跨会话发现功能。若要在租户之间设置保密边界,请启用遍历隔离,使查询只能访问自身会话中的事实:
ReasonGraph(isolate_traversal=True) # graph-wide default
graph.query("...", scopes={"tenant-a"}, isolate=True) # or per query
其他生产环境控制项:
- 身份验证:
create_app(service, api_key="...")(或REASONGRAPH_API_KEY)会保护所有数据端点;MCP 服务也会公开相同的工具。 - 结构化结果:
query_detailed(...)(以及 HTTPdetailed标志/query_memory_detailedMCP 工具)会返回{content, score, created_at, scopes},用于阈值筛选、去重以及“于 记住”。 - 删除:
delete(content, purge_orphans=True)/supersede(..., purge_orphans=True)也会删除因删除操作而失去关联的实体(被遗忘权);共享实体会保留。通过delete_memory/update_memoryMCP 工具公开。 - 语义去重:
add_text(..., dedup_threshold=0.95)会丢弃近似重复的重述内容,而不是不断累积,并将范围合并到保留的事实上。 - 矛盾解决:
ReasonGraph(conflict_resolver=NLIConflictResolver())(或REASONGRAPH_RESOLVE_CONFLICTS=1)会软性取代新事实所矛盾的事实——supersedes边会让旧事实不再出现在默认的query/discover检索结果中,同时保留其审计记录,并可通过include_superseded=True检索;supersession_history(fact)会显示事实之间的替代关系。解析器可插拔(任何具有contradictions(new, candidates)方法的对象均可):NLIConflictResolver交叉编码器无需 LLM,但可能会将互补信息误判为矛盾(它会将“在慕尼黑工作”与“住在柏林”视为矛盾);LLMConflictResolver(generate)则可接入任意 LLM,判断更精准。软性取代有意设计为可逆:重新断言某个事实会使其恢复,而query(..., as_of=<datetime>)可以穿越时间,查询当时仍然有效的内容(每个事实都带有created_at/invalid_at有效时间区间)。通过memory_historyMCP 工具和/history端点向 Agent 公开。 - 运行状况:
/health(存活状态)和/ready(就绪状态)供编排探针使用。 - Postgres 会创建 HNSW 余弦索引,因此向量搜索会由索引加速,而非进行顺序扫描。
- 速度:
tests/bench_speed.py会在可配置的图规模和后端下测量写入吞吐量和读取延迟(query/discover/trace,p50/p95;使用--fake可进行无需模型的计时)。具有参考意义的真实模型内存运行数据:query 约 14ms,discover 约 7ms,因果追踪 p50 约 3ms。
与我们合作
想在自己的数据上使用 ReasonGraph、将其作为托管服务使用(memory.primaxiom.ai),或将其集成到你的 Agent 中?PrimAxiom Labs 为企业构建并运行 AI 系统:先进行简短的付费需求评估,再开展为期四周的试点项目。 电子邮件:[email protected] · primaxiom.ai
许可证
MIT
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。