Graph Theory图论

Making the Knowledge Layer a Graph You Actually Traverse将知识层打造为真正可遍历的图

Why retrieval quality should be a property of the system, not of the question's wording? Rebuilding knowledge layer with graph traversal on every query, bitemporal edges, and two-threshold entity resolution.为什么检索质量应该是系统的一种属性,而不是问题措辞的产物?通过在每次查询时进行图遍历、双时态边以及双阈值实体解析来重构知识层。

Miodrag Cekikj
August 20, 202637 min read
Photo by Alina Grubnyak on Unsplash图片来源:Unsplash,摄影师:Alina Grubnyak

In the first article of this series (Designing a Persistent Knowledge Layer That Refuses to Guess), I built a persistent knowledge layer next to a RAG pipeline: an evidence layer for grounding, a structured knowledge layer for accumulated understanding, and an orchestrator that decided which of the two a question needed. I deployed it on Azure, seeded it with a synthetic insurance corpus engineered to break retrieval systems in six specific ways, and measured what happened.在本系列的第一篇文章(《设计一个拒绝猜测的持久化知识层》)中,我构建了一个与 RAG 流水线并行的持久化知识层:用于提供依据的证据层、用于积累理解的结构化知识层,以及一个决定问题需要哪种层的编排器。我将其部署在 Azure 上,并使用一套专门为破坏检索系统而设计的合成保险语料库对其进行测试,随后测量了实际效果。

The architecture held up. The contradiction register refused to answer a question the firm itself had not settled. The effective-date scoping kept a rule that took effect on 1 March from being applied to a loss dated 20 February. The live model walked a four-hop reasoning chain with citations at every step.该架构经受住了考验。矛盾记录器拒绝回答公司尚未定论的问题。生效日期范围限制机制确保了 3 月 1 日生效的规则不会应用于 2 月 20 日发生的损失。实时模型执行了四跳推理链,并在每一步都提供了引用。

But I ended that article with a list of things I would build next, and two of the entries on that list have not left me alone since.但在那篇文章的结尾,我列出了一些接下来要构建的功能,其中有两项一直萦绕在我的心头。

The first was a number. When I ran all twenty-one documents through live extraction on the deployed stack, the model proposed concepts that my alias-based entity resolution could only partially merge. The result was 149 concept objects where the curated graph has 19 - a roughly 7x fragmentation factor. I wrote at the time that this was “the concrete argument for embedding similarity and LLM adjudication in the resolution chain.” It is. But sitting with the number longer, I realized it argues for something bigger. The knowledge layer I built stores relationships, but nothing in the system actually traverses them at retrieval time. The graph is consulted like a filing cabinet, not walked like a graph.第一项是一个数字。当我将所有 21 份文档通过已部署堆栈的实时提取流程处理时,模型提出了许多概念,而我基于别名的实体解析只能部分合并它们。结果是产生了 149 个概念对象,而策划好的图中只有 19 个——碎片化系数约为 7 倍。当时我写道,这是“在解析链中引入嵌入相似度和 LLM 裁决的有力论据”。确实如此。但随着对这个数字的深入思考,我意识到它指向了更宏大的目标。我构建的知识层存储了关系,但系统中没有任何组件在检索时真正遍历它们。这个图被当作文件柜来查阅,而不是像图结构那样被遍历。

The second was an incident, not a number. During live validation I asked the deployed system an unscripted question — why a particular claim had been triaged the way it was, and my first phrasing got intercepted by the contradiction gate, because the claim’s evidence brushed against a contested concept. The gate did its job. But the routing in front of the gate had made a judgment call about how to retrieve, based on keyword markers in my question, and it occurred to me that I had built a system whose retrieval quality depended on how a question happened to be worded.第二项是一个事件,而不是数字。在实时验证期间,我向已部署的系统提出了一个非预设的问题——询问某个特定索赔为何以那种方式进行分类。我的第一种措辞被矛盾拦截器拦截了,因为该索赔的证据触及了一个有争议的概念。拦截器完成了它的工作。但拦截器之前的路由逻辑根据我问题中的关键词标记,对如何检索做出了判断。我突然意识到,我构建的系统,其检索质量竟然取决于问题的措辞方式。

This article is about fixing both. It is the result of the design work I did after previous work was shipped, and it changes my own architecture in a way I want to be upfront about: the query router from Part 1, the wiki-first, evidence-first, hybrid decision I drew as a flowchart and defended in prose is actually the component I am retiring. Not because it was wrong for what Part 1 was doing, but because I now think there is a better answer to the question it was trying to answer, and the better answer also unlocks three capabilities the router never could: real multi-hop traversal, time-aware relationships, and contradictions that are discovered instead of curated.本文旨在解决这两个问题。这是我在上一阶段工作完成后进行设计改进的成果。我想坦诚地说明,它改变了我的架构:我在第一部分中绘制并论证的那个“路由优先、维基优先、证据优先”的混合决策流程图,实际上就是我打算淘汰的组件。这并不是因为它在第一部分中是错误的,而是因为我现在认为,对于它试图回答的问题,有了更好的答案。而这个更好的答案还解锁了路由从未实现的三种能力:真正的多跳遍历、时间感知关系,以及被发现而非人工策划的矛盾。

I will keep the previous approach, and present it in three parts, the same way as before.我将沿用之前的方法,并像之前一样分三个部分进行呈现。

Part I is the design revision. Vendor-neutral, like last time. It covers the principle that replaces the router (retriever versus filter), the always-fused retrieval pipeline, bitemporal edges, ingest-time contradiction detection, and the entity-resolution subsystem that everything else stands on.第一部分是设计修订。和上次一样,保持厂商中立。它涵盖了取代路由器的原则(检索器与过滤器)、始终融合的检索流水线、双时态边、摄入时的矛盾检测,以及所有其他功能所依赖的实体解析子系统。

Part II grows the Azure stack. The resource group from Part 1 is still running, and it turns out to contain most of what this design needs — including a change feed I provisioned and never used. One new engine joins it.第二部分扩展了 Azure 堆栈。第一部分的资源组仍在运行,事实证明它包含了此设计所需的大部分内容——包括一个我已配置但从未使用的变更提要。它将加入一个新的引擎。

Part III walks the insurance corpus through the new design — live. The same Ostermere Mutual documents, the same contradiction, the same claims — and the questions the new architecture can answer that the old one could not. The implementation is deployed on a fresh Azure resource group, and every request and response quoted in Part III was actually sent to and returned by that system, the same standard Part 1 held itself to.第三部分将通过新的设计实时演示保险语料库。使用相同的 Ostermere Mutual 文档、相同的矛盾、相同的索赔,展示新架构能够回答而旧架构无法回答的问题。实现部署在全新的 Azure 资源组上,第三部分中引用的每一个请求和响应,实际上都是由该系统发送和返回的,这与第一部分遵循的标准一致。

What this article deliberately defers: the reflection loop that derives higher-level insights, the full write-path architecture in production depth, the agent-tool layer, and the governance backlog. Each of those deserves its own treatment, and stacking them here would produce a survey instead of an argument.本文有意推迟讨论以下内容:推导更高层次见解的反射循环、生产深度的完整写入路径架构、智能体工具层以及治理积压工作。这些内容每一项都值得单独探讨,如果堆砌在这里,只会变成一份调查报告,而非论证。

Everything in the dataset is synthetic. Ostermere Mutual does not exist. Neither does the regulator, the policy, the claims, the people, the wind zones or the figures. Nothing here is insurance, legal, underwriting or claims advice.数据集中的所有内容均为合成数据。Ostermere Mutual 公司并不存在。监管机构、保单、索赔、人员、风区或数据也都不存在。此处没有任何内容构成保险、法律、承保或索赔建议。

Naming note: current Microsoft documentation uses Microsoft Foundry for the platform previously called Azure AI Foundry. As in Part 1, I use Foundry throughout.命名说明:目前的 Microsoft 文档使用 Microsoft Foundry 来指代之前称为 Azure AI Foundry 的平台。与第一部分一样,我在全文中使用 Foundry。

The complete Part 1 project (application, infrastructure, dataset and the generated Obsidian vault) is available at github.com/mcekikj/persistent-knowledge-layer, under the MIT license — that repository stays frozen as the artifact Part 1 describes. The Part 2 implementation lives in its own repository at github.com/mcekikj/knowledge-graph-fusion.完整的第一部分项目(应用程序、基础设施、数据集和生成的 Obsidian 知识库)可在 github.com/mcekikj/persistent-knowledge-layer 获取,采用 MIT 许可证——该存储库将保持冻结状态,作为第一部分所描述的产物。第二部分的实现位于其自己的存储库 github.com/mcekikj/knowledge-graph-fusion 中。

Contents目录

Part I - The design revision第一部分 - 设计修订

  1. Where Part 1 Ended, Honestly第一部分的真实终点

  2. The Router Was the Weak Link路由器是薄弱环节

  3. Retriever versus Filter检索器与过滤器

  4. Always Fused, Then Grounded始终融合,然后归因

  5. Time Moves Inside the Edges时间在边内部流动

  6. Contradictions: From Curated to Discovered矛盾:从人工策划到自动发现

  7. Entity Resolution: The Subsystem the Graph Stands On实体解析:图所依赖的子系统

  8. One Truth, Projected单一真理的投影

  9. The Ablation Is the Acceptance Test消融实验即验收测试

  10. When Routing Is Still the Right Answer何时路由仍然是正确的答案

Part II - Growing the Azure Stack第二部分 - 扩展 Azure 堆栈

  1. What Is Already Running当前运行的内容

  2. The Second Engine: Cosmos DB for Apache Gremlin第二个引擎:用于 Apache Gremlin 的 Cosmos DB

  3. Teaching the Index About the Graph让索引理解图

  4. The Projection Worker, and the Ontology Ostermere Already Has投影工作者,以及 Ostermere 已有的本体

  5. Reranking the Union对并集进行重排序

  6. What the Additions Cost新增内容的成本

Part III - The Corpus, Traversed第三部分 - 遍历语料库

  1. The Multi-Hop Question, Answered Properly多跳问题的正确解答

  2. The Question Part 1 Could Not Ask第一部分无法提出的问题

  3. Would the System Have Found con-001 on Its Own?系统能自行找到 con-001 吗?

  4. To Sum It All Up总结

References参考文献

Part I - The design revision

1. Where Part 1 ended, honestly1. 第一部分的真实终点

A quick recap for readers joining here, and an honest accounting for readers returning.为新读者提供快速回顾,为回归读者提供诚实的总结。

Part 1’s architecture had three layers. An evidence layer (chunks, vectors, full text in a search index) answered what source material is relevant right now. A knowledge layer (concepts, typed relationships, decisions, comparisons, contradictions in a document store) answered what has the system already worked out. An orchestrator sat in front of both and decided, per question, which layer to consult: wiki-first for definitions and prior decisions, evidence-first for exact wording, hybrid for cross-source synthesis.第一部分的架构有三层。证据层(搜索索引中的分块、向量、全文)回答当前什么源材料是相关的。知识层(文档存储中的概念、类型化关系、决策、比较、矛盾)回答系统已经解决了什么。编排器位于两者之前,针对每个问题决定咨询哪一层:维基优先用于定义和既往决策,证据优先用于精确措辞,混合模式用于跨源综合。

Three things about that design were validated live and survive unchanged into this article:该设计中有三点经过了实时验证,并在本文中保持不变:

  • The contradiction gate. When retrieval touches a concept that two current documents genuinely disagree about, the system returns both positions with their sources and effective dates, names the accountable owner, and does not answer. This is still, in my view, the single most valuable behavior in the whole design, and nothing in this article weakens it.矛盾拦截器。当检索触及两份当前文档存在真正分歧的概念时,系统会返回两种立场及其来源和生效日期,指明负责方,并拒绝回答。在我看来,这仍然是整个设计中最有价值的行为,本文没有任何内容削弱这一点。

  • Effective-date scoping. A question about a claim is answered against the documentation in force on the date of loss, not the documentation in force today.生效日期范围限制。关于索赔的问题是根据损失发生之日生效的文档来回答的,而不是根据今天生效的文档。

  • Provenance. A knowledge page is never a source; it is always traceable to one.出处。知识页面本身从不是来源;它总是可以追溯到来源。

And three things were weaker than the article’s prose, which I said at the time and can now do something about:还有三点比文章的文字描述要弱,我当时就提到了,现在可以做出改进:

  • Entity resolution stopped at alias matching, and the live deployment measured the consequence: 149 machine-extracted concepts against 19 curated ones.实体解析止步于别名匹配,实时部署测量了其后果:149 个机器提取的概念对比 19 个策划的概念。

  • Relationships were stored but not traversed. The wiki search ranked objects by term overlap, the 28 typed relationships in the corpus were rendered on pages and walked by humans in Obsidian, but no query ever followed an edge.关系被存储但未被遍历。维基搜索按术语重叠对对象进行排名,语料库中的 28 种类型化关系呈现在页面上并由人在 Obsidian 中人工查看,但没有任何查询会沿着边进行追踪。

  • The router decided how to retrieve based on keyword markers in the question — and a system whose retrieval strategy depends on question wording has a soft spot exactly where it should be most dependable.路由器根据问题中的关键词标记来决定如何检索——而一个检索策略依赖于问题措辞的系统,其弱点恰恰在于它本应最可靠的地方。

Figure 1 - Part 1’s retrieval, drawn honestly. The two red elements are what this article replaces: a router that decides by question wording, and a relationship store that nothing traverses. Image by author.图 1 - 第一部分的检索,如实绘制。两个红色元素是本文要替换的内容:一个根据问题措辞决定的路由器,以及一个无人遍历的关系存储。图片由作者提供。

The router made sense when I built it. Not every question needs both layers, and sending everything everywhere looked wasteful. So the orchestrator inspected the question for markers — “compare” and “why” suggested synthesis, “exact” and “quote” suggested evidence, and picked a mode.我在构建路由器时,它是有意义的。并非每个问题都需要两层,将所有内容发送到所有地方看起来很浪费。因此,编排器检查问题中的标记——“比较”和“为什么”建议进行综合,“精确”和“引用”建议使用证据,然后选择一种模式。

The problem is not that the heuristics were crude, although they were, and I said so at the time. The problem is structural, and it took the live deployment to make me see it: the router makes retrieval quality a property of the question’s wording instead of a property of the system.问题不在于启发式规则很粗糙(尽管确实如此,我当时也说了),而在于结构性问题。实时部署让我看到了这一点:路由器使检索质量成为问题措辞的属性,而不是系统的属性。

Two users asking for the same fact with different phrasings could take different paths through the system and get differently-grounded answers. A question that deserved relational context would not get it unless it happened to contain a synthesis marker. And when I imagined replacing my keyword heuristics with a classifier, or with the model itself choosing a retrieval tool, the structural problem stayed exactly where it was — I would have been delegating the same judgment call to a smarter judge, when the design question was whether the call should exist at all.两个用户用不同的措辞询问同一个事实,可能会走过系统中的不同路径,并得到基于不同依据的答案。一个本应获得关系上下文的问题,除非恰好包含综合标记,否则将无法获得该上下文。当我设想用分类器或模型本身选择检索工具来替换关键词启发式规则时,结构性问题依然存在——我只是把同样的判断权交给了更聪明的法官,而设计问题在于这种判断是否应该存在。

There is also a quieter cost that I only appreciated when I started designing evaluation for this system. A router multiplies the space you have to test. Every retrieval quality question becomes three questions. Did it work in wiki mode, in evidence mode, in hybrid mode, and every failure investigation starts with “which path did this take?” A fixed pipeline is deterministic, and deterministic things are testable, abatable, and cheap to reason about.还有一个更隐蔽的成本,直到我开始为该系统设计评估时才意识到。路由器成倍增加了需要测试的空间。每个检索质量问题都变成了三个问题。它在维基模式、证据模式、混合模式下是否有效?每次故障调查都从“它走了哪条路径?”开始。固定的流水线是确定性的,而确定性的东西是可测试、可消融且易于推理的。

So the revision starts from a different premise: run everything, always, and let the results compete on evidence. If a question has no useful relational context, the traversal stage contributes little and costs little. If it has no strong semantic matches, the graph anchors carry it. The system does not guess which case it is in, instead, it finds out.因此,修订版从一个不同的前提开始:始终运行所有内容,让结果在证据基础上竞争。如果一个问题没有有用的关系上下文,遍历阶段贡献很小且成本很低。如果它没有强大的语义匹配,图锚点将支撑它。系统不会猜测自己处于哪种情况,而是直接去发现。

That premise immediately raises a fair objection regarding does “run everything” mean every storage technology earns a place in the pipeline? That is the next section, and the answer is no.这个前提立即引发了一个关于“运行所有内容”是否意味着每种存储技术都在流水线中占有一席之地的合理异议。这是下一节的内容,答案是否定的。

3. Retriever versus filter3. 检索器与过滤器

Any system that remembers things eventually faces three shapes of question. 任何记忆事物的系统最终都会面临三种形式的问题。

What is similar to X - a similarity question. 与 X 相似的是什么——相似度问题。

What is connected to X - a relationship question. 与 X 相关的是什么——关系问题。

What was true about X in March, and what changed - a time question.关于 X,3 月份的情况是什么,发生了什么变化——时间问题。

Each shape maps naturally onto a different storage technology — vector indexes, graph databases, time-series stores — and the tempting conclusion is that a serious knowledge system needs all three, wired together behind an orchestrator that fires parallel sub-queries and intersects the results. Architectures like that exist. They work. They also front-load enormous cost. Three engines to operate and secure, cross-store consistency machinery, and a merge problem, fusing rankings from engines whose scores are not comparable. That is genuinely hard to get right. And the parallel-query-and-intersect pattern quietly drops good answers: a document that is a perfect semantic match but falls just outside a time window vanishes from the intersection entirely.每种形式自然地映射到不同的存储技术——向量索引、图数据库、时间序列存储。诱人的结论是,一个严肃的知识系统需要这三者,并将它们连接在一个编排器后面,该编排器触发并行子查询并对结果取交集。这样的架构确实存在,而且有效。但它们预先加载了巨大的成本:需要操作和保护三个引擎,处理跨存储的一致性机制,以及合并排名问题(融合来自不可比较分数的引擎的排名)。要做好这一点真的很难。此外,并行查询和取交集的模式会悄悄丢弃好的答案:一个完美的语义匹配文档,如果刚好落在时间窗口之外,就会完全从交集中消失。

The test that cuts through this is one question per modality: does it produce ranked evidence that must be fused, or does it narrow and reweight someone else’s ranking?切入这一点的测试是每种模态一个问题:它产生需要融合的排名证据,还是缩小并重新加权别人的排名?

  • Semantic and keyword search produce ranked evidence. Retriever.语义和关键词搜索产生排名证据。这是检索器。

  • Time, in this system, never ranks anything on its own. “In force on the date of loss” and “prefer current over superseded” are filters and boosts applied to a relevance ranking. Part 1 already treated time this way — the as_of parameter constrained candidates, it never ordered them. Time is a filter.在本系统中,时间从不单独对任何内容进行排名。“损失日期生效”和“倾向于当前而非被取代”是应用于相关性排名的过滤器和增强项。第一部分已经这样处理时间了——as_of 参数限制了候选者,但从不排序。时间是一个过滤器。

  • The graph sits in between. It does not rank by itself, but it does something no filter does. It expands. It turns one relevant hit into a connected neighborhood, and the paths it returns carry a relational signal that should influence the final ranking.图位于两者之间。它本身不进行排名,但它做了一些过滤器做不到的事情。它进行扩展。它将一个相关的命中结果变成一个连接的邻域,它返回的路径带有应影响最终排名的关系信号。

A modality that filters does not earn an engine, it earns a field and a scoring function. A modality that expands and contributes evidence earns an engine only because the primary engine cannot do the job — and search services genuinely cannot traverse.一种执行过滤的模态不配拥有引擎,它只需要一个字段和一个评分函数。一种扩展并贡献证据的模态,只有在主引擎无法完成工作时才配拥有引擎——而搜索服务确实无法进行遍历。

Figure 2 - The retriever-versus-filter test. It collapses a three-database architecture into two engines and a scoring profile. Image by author.图 2 - 检索器与过滤器测试。它将三数据库架构压缩为两个引擎和一个评分配置文件。图片由作者提供。

Applied to this system, keyword, semantic, and time-as-recency all fit inside one search service. The graph earns a second engine. A time-series database earns nothing, unless the day comes when time produces ranked evidence of its own, trend and anomaly signals, which for a knowledge layer over policy documents may be never.应用于本系统,关键词、语义和作为近因的时间都适合放在一个搜索服务中。图获得第二个引擎。时间序列数据库什么也得不到,除非有一天时间本身能产生排名证据、趋势和异常信号,而对于政策文档之上的知识层来说,这可能永远不会发生。

Two engines, not three. And one commitment that outlives every engine choice: a unified interface. One query object in, one result contract out, and the caller never learns which store answered. That is what lets backends be added, swapped, or removed as query patterns prove them, without anything above the interface changing.两个引擎,而不是三个。还有一个超越任何引擎选择的承诺:统一接口。输入一个查询对象,输出一个结果契约,调用者永远不需要知道是哪个存储回答的。这使得后端可以在查询模式证明其价值时进行添加、交换或移除,而接口之上的任何内容都不需要改变。

4. Always fused, then grounded4. 始终融合,然后归因

With the router gone and the engines chosen, retrieval becomes one fixed pipeline that every query runs, top to bottom, no modes:随着路由器的消失和引擎的选择,检索变成了每个查询都从头到尾运行的固定流水线,没有模式:

Figure 3 - The always-fused pipeline. Both modalities run on every call; the union is reranked together; the output is a connected subgraph, not a bag of chunks. Image by author.图 3 - 始终融合的流水线。两种模态在每次调用时都运行;并集被共同重排序;输出是一个连接的子图,而不是一袋分块。图片由作者提供。

A few of these steps deserve a closer look, because each one fixes a specific weakness from Part 1.其中几个步骤值得仔细研究,因为每一步都修复了第一部分的特定弱点。

Step 1 carries anchors, not just text. Each chunk in the search index stores the IDs of the entities it mentions. A hybrid search result therefore already knows where it enters the graph, no bridging lookup, no name matching at query time. This one schema decision is what makes two engines feel like one system.第 1 步携带锚点,而不仅仅是文本。搜索索引中的每个分块都存储了它提到的实体的 ID。因此,混合搜索结果已经知道它在哪里进入图,无需桥接查找,无需在查询时进行名称匹配。这个架构决策使两个引擎感觉像一个系统。

Step 2 is mandatory, and that is the point. In most graph-augmented designs, traversal is an option, but a mode a router selects or a tool an agent may call. Here it is a fixed stage. When the graph has nothing useful near a query, it contributes little and the search results carry the answer. In this case, the pipeline degrades gracefully instead of branching. And because traversal always runs, its contribution is measurable — you can ablate it, which becomes important in section 9.第 2 步是强制性的,这就是重点。在大多数图增强设计中,遍历是一个选项,要么是路由器选择的模式,要么是智能体可以调用的工具。在这里,它是一个固定阶段。当图中查询附近没有有用的内容时,它贡献很小,搜索结果会承载答案。在这种情况下,流水线会优雅地降级而不是分支。并且因为遍历总是运行,其贡献是可测量的——你可以消融它,这在第 9 节中变得很重要。

Step 4 is one reranker over the whole union. This matters because search scores and graph proximity are not comparable numbers, and gluing two ranked lists together by interleaving is a coin flip dressed as engineering. A single reranking stage that sees both the semantic evidence and the relational paths can do what neither ranking could alone, letting a connected-and-relevant object outrank a slightly-more-similar-but-isolated one.第 4 步是对整个并集进行一次重排序。这很重要,因为搜索分数和图邻近度是不可比较的数字,通过交错将两个排名列表粘在一起就像是披着工程外衣的抛硬币。单个重排序阶段可以看到语义证据和关系路径,它可以做到单一排名做不到的事情,让一个连接且相关的对象排名高于一个稍微相似但孤立的对象。

Step 7 is where Part 1’s gate lives on. Grounding assembles the final bundle: the ranked objects, the relationship paths that connect them, the citations, and, unchanged from Part 1, the contradiction warnings. Fusion replaces the router. It does not replace the gate. If the bundle touches a contested concept, the system still presents both positions, names the owner, and declines to answer as though the matter were settled.第 7 步是第一部分的拦截器存在的地方。归因组装最终的包:排名对象、连接它们的路径、引用,以及与第一部分相同、未改变的矛盾警告。融合取代了路由器。它没有取代拦截器。如果包触及一个有争议的概念,系统仍然会呈现两个立场,指明所有者,并拒绝回答,就像问题尚未解决一样。

The model, in other words, never receives a pile of disconnected snippets. It receives a small, ranked, connected, attributed subgraph rendered as context. Its job shifts from inventing connections to interpreting documented ones, which is precisely the job a language model is good at, and precisely the shift that keeps grounded answers grounded.换句话说,模型从不接收一堆断开连接的片段。它接收一个小型的、已排名、已连接、已归因的子图作为上下文。它的工作从发明连接转变为解释已记录的连接,这正是语言模型擅长的工作,也正是保持归因答案准确的转变。

One implementation note for the skeptical reader: when a query clearly names a concrete anchor (“everything connected to claim CLM-1042”), the orchestrator may invert the order internally — traverse first, then push the connected IDs down as a filter into the vector query. That is a performance optimization, invisible at the interface. There is deliberately no search-only or graph-only mode for a caller to select, because the moment such a mode exists, something upstream has to decide when to use it, and that something is a router wearing a new name.给怀疑论者的一个实现说明:当查询明确命名一个具体锚点(“与索赔 CLM-1042 相关的所有内容”)时,编排器可能会在内部反转顺序——先遍历,然后将连接的 ID 作为过滤器推送到向量查询中。这是一种性能优化,在接口上是不可见的。调用者故意没有搜索专用或图专用模式可供选择,因为一旦存在这种模式,上游的某个东西就必须决定何时使用它,而那个东西就是换了名字的路由器。

5. Time moves inside the edges5. 时间在边内部流动

Part 1 handled time with a single mechanism: sources carried effective dates and supersession links, and the as_of parameter filtered retrieval to the documentation in force on a given date. It worked, for example it kept a March rule from being applied to a February loss, but it worked at the granularity of documents. The knowledge layer’s relationships had no time on them at all. An edge either existed or it did not.第一部分用单一机制处理时间:来源带有生效日期和取代链接,as_of 参数将检索过滤到给定日期生效的文档。它起作用了,例如,它防止了 3 月的规则应用于 2 月的损失,但它是在文档粒度上起作用的。知识层的关系上根本没有时间。边要么存在,要么不存在。

The revision moves time to where the knowledge actually lives: every relationship edge carries a validity window, valid_from, valid_to (open-ended if current), alongside the timestamp when the system learned the fact. Event time and ingest time, both recorded that the model bitemporal databases have used for decades, applied to a knowledge graph’s edges.修订版将时间移到了知识真正存在的地方:每条关系边都带有有效期 valid_from、valid_to(如果是当前的则无截止日期),以及系统学习该事实的时间戳。事件时间和摄入时间,双时态数据库几十年来一直使用的模型,现在应用于知识图谱的边。

When a fact changes, the old edge is not rewritten. It is expired — its valid_to is closed, and a new edge opens. History is preserved by construction, which is the same non-destructive stance Part 1 took toward superseded documents, now applied to individual relationships.当事实发生变化时,旧边不会被重写。它会过期——其 valid_to 被关闭,并打开一条新边。历史通过构建来保存,这与第一部分对被取代文档采取的非破坏性立场相同,现在应用于单个关系。

Figure 4 - Bitemporal edges on the corpus’s own scoped-supersession case. The H3 update adds a time-valid, scope-qualified edge; the general rule’s edge stays open because it still holds outside H3. Nothing is overwritten. Image by author.图 4 - 语料库自身范围取代案例上的双时态边。H3 更新添加了一条时间有效、范围限定的边;通用规则的边保持打开状态,因为它在 H3 之外仍然成立。没有任何内容被覆盖。图片由作者提供。

Three question shapes fall out of this model almost for free, and none of them was answerable in Part 1:三种问题形式几乎免费地从该模型中得出,其中任何一种在第一部分都无法回答:

  • As-of: which inspection rule applied on 20 February 2026? Traverse only edges valid at that timestamp. Part 1 could filter documents this way, now the relationships themselves answer.截至(As-of):2026 年 2 月 20 日适用哪条检查规则?仅遍历在该时间戳有效的边。第一部分可以这样过滤文档,现在关系本身可以回答。

  • Timeline: how did the roof inspection requirement evolve? The ordered sequence of an entity’s edges by validity window, from the 2025 general rule, through the January analysis email, to the March H3 threshold.时间线:屋顶检查要求是如何演变的?实体边按有效期窗口的有序序列,从 2025 年通用规则,到 1 月分析电子邮件,再到 3 月 H3 阈值。

  • Diff: what changed in underwriting guidance this quarter? Edges whose validity opened or closed inside a window as added, superseded, expired.差异(Diff):本季度承保指南发生了什么变化?有效期在窗口内打开或关闭的边,作为已添加、已取代、已过期。

For an insurance knowledge base, these are not exotic queries. They are the questions a claims professional, an auditor, and a compliance officer respectively ask every week. Part 1 could tell you what was in force on a date. This design can tell you what changed, when, and show its work.对于保险知识库,这些不是异国情调的查询。它们是索赔专业人员、审计员和合规官每周都会问的问题。第一部分可以告诉你某日期生效的内容。此设计可以告诉你发生了什么变化、何时发生,并展示其工作过程。

6. Contradictions: from curated to discovered6. 矛盾:从人工策划到自动发现

I have to be honest about how con-001 — the trace-and-access contradiction that anchors Part 1’s strongest demo — got into the system. I put it there. The corpus was engineered so the conflict existed, and the contradiction object was curated into the knowledge store with its statements, its owner, and its status. The gate was real and automatic, the detection was me.我必须诚实地说明 con-001(锚定第一部分最强演示的追踪与访问矛盾)是如何进入系统的。是我放进去的。语料库经过精心设计,使得冲突存在,矛盾对象连同其陈述、所有者和状态一起被策划到知识存储中。拦截器是真实且自动的,但检测是我做的。

Bitemporal edges make detection automatic, and the mechanism is almost embarrassingly simple once the edges carry time. At ingest, after a new fact is extracted and its entities are resolved, check it against the currently-valid edges on the same subject:双时态边使检测自动化,一旦边带有时间,该机制就简单得令人尴尬。在摄入时,在提取新事实并解析其实体后,将其与同一主题上当前有效的边进行检查:

Figure 5 - Contradiction detection at ingest. A clear update supersedes automatically; a genuine conflict opens a contradiction object and arms the gate. The losing fact is never destroyed. Image by author.图 5 - 摄入时的矛盾检测。明确的更新会自动取代;真正的冲突会打开一个矛盾对象并激活拦截器。丢失的事实永远不会被销毁。图片由作者提供。

The policy in the middle is where the judgment lives, and it inherits Part 1’s governance stance wholesale. A clear supersession, a newer version of the same rule from the same authority expires the old edge automatically, because that is versioning, not disagreement. A genuine conflict where two current sources, different authorities, incompatible statements is surfaced, recorded as an unresolved contradiction object with an accountable owner, and never resolved by the machine. Recency is not applicability; Part 1’s rule, now enforced at write time instead of curation time.中间的政策是判断力所在的地方,它全盘继承了第一部分的治理立场。明确的取代、来自同一权威的同一规则的更新版本会自动使旧边过期,因为那是版本控制,而不是分歧。两个当前来源、不同权威、不兼容陈述的真正冲突会被浮现,记录为带有负责人的未解决矛盾对象,且永远不会由机器解决。最近性不等于适用性;第一部分的规则,现在在写入时而不是策划时执行。

What I find most satisfying about this design is that the detection costs no new infrastructure and no new concepts. It is a policy applied to machinery the system already has: typed edges, validity windows, and the contradiction object Part 1 already defined. The gate that made Part 1’s best demo work does not change at all, it just stops depending on me to load it.我发现这个设计最令人满意的是,检测不需要新的基础设施,也不需要新的概念。它只是应用于系统已有的机器的政策:类型化边、有效期窗口和第一部分已定义的矛盾对象。使第一部分最佳演示工作的拦截器根本没有改变,它只是不再依赖我来加载它。

7. Entity resolution: the subsystem the graph stands on7. 实体解析:图所依赖的子系统

Now the unglamorous part, and the reason this article exists at all: 149 concepts where the curated graph has 19.现在是不光彩的部分,也是本文存在的理由:149 个概念对比策划好的 19 个。

Every promise made so far — traversal that expands a hit into a neighborhood, edges that carry time, contradictions detected on the same subject, rests on one assumption that “the same subject” is one node. If “Actual Cash Value,” “ACV,” “cash settlement basis,” and “depreciated value” become four nodes, traversal fragments, timelines split across duplicates, and contradiction detection never fires because the conflicting facts sit on different vertices. Over-merging is worse. Collapse two genuinely different concepts and every path through the merged node is corrupted.到目前为止所做的每一个承诺——将命中结果扩展为邻域的遍历、带有时间的边、在同一主题上检测到的矛盾,都建立在一个假设之上,即“同一主题”是一个节点。如果“实际现金价值”、“ACV”、“现金结算基础”和“折旧价值”变成四个节点,遍历就会碎片化,时间线会跨重复项分裂,矛盾检测永远不会触发,因为冲突的事实位于不同的顶点上。过度合并更糟。合并两个真正不同的概念,通过合并节点的每条路径都会被破坏。

Part 1 resolved entities by alias matching — exact ID, then title, then a declared alias list. It handled the four ACV synonyms because I had declared them. The live deployment then demonstrated, at a 7x rate, what happens with synonyms nobody declared.第一部分通过别名匹配解析实体——精确 ID,然后是标题,然后是声明的别名列表。它处理了那四个 ACV 同义词,因为我已经声明了它们。实时部署随后以 7 倍的速率展示了当出现无人声明的同义词时会发生什么。

The revision treats resolution as a first-class pipeline with a shape I want to describe precisely, because the shape is what controls the cost:修订版将解析视为一流的流水线,其形状我想精确描述,因为该形状控制着成本:

Figure 6 - The resolution pipeline. Blocking keeps the expensive step bounded; two thresholds mean only the genuinely ambiguous middle pays for an LLM call; reversible same_as merges mean a wrong decision can be undone. Image by author.图 6 - 解析流水线。阻塞使昂贵的步骤有界;双阈值意味着只有真正模棱两可的中间部分才需要支付 LLM 调用费用;可逆的 same_as 合并意味着错误的决定可以撤销。图片由作者提供。

The two-threshold decision is the heart of it. High-confidence matches link automatically. Clear non-matches create a new entity. Only the gray zone in between, and the width of that zone is a tunable cost lever, goes to the model for adjudication, with the mention, its local context, and the candidate’s profile in the prompt. In Part 1’s live run, alias matching alone would have sent essentially every undeclared synonym to “create new,” which is exactly the 149 we measured. Embedding similarity in the blocking and scoring stages is what catches “cash settlement basis” without anyone having declared it.双阈值决策是核心。高置信度匹配自动链接。明确的不匹配创建新实体。只有中间的灰色区域(该区域的宽度是一个可调的成本杠杆)才会进入模型进行裁决,提示词中包含提及、其本地上下文和候选者的配置文件。在第一部分的实时运行中,仅别名匹配就会将所有未声明的同义词发送到“创建新”,这正是我们测量的 149 个。在阻塞和评分阶段嵌入相似度是无需任何人声明即可捕捉“现金结算基础”的原因。

And because resolution will sometimes be wrong, merges are modeled as reversible same_as redirect edges rather than destructive rewrites — the same supersede-don’t-overwrite stance as everywhere else in the design. A wrong merge is an edge you can expire, not a corruption you have to excavate.并且因为解析有时会出错,合并被建模为可逆的 same_as 重定向边,而不是破坏性的重写——与设计中其他地方一样,采取取代而不覆盖的立场。错误的合并是一条你可以使其过期的边,而不是你必须挖掘的腐败。

The live deployment put a first number on this pipeline. The identical corpus that fragmented into 149 concepts under Part 1’s alias, only resolution produced 120 under the new resolver, a twenty percent reduction, measured like-for-like on the same documents. Not a solved problem since 120 is still six times the canonical 19, and the residue is extraction phrasing that no similarity threshold can bridge (section 19 shows a concrete case). But the mechanism moves the number in the right direction, and now there is a number to move.实时部署为该流水线提供了第一个数字。在第一部分的别名解析下碎片化为 149 个概念的相同语料库,在新解析器下产生了 120 个,减少了 20%,在相同文档上进行了一对一测量。这不是一个已解决的问题,因为 120 仍然是规范 19 的六倍,残余的是任何相似度阈值都无法桥接的提取措辞(第 19 节展示了一个具体案例)。但该机制使数字朝着正确的方向移动,现在有了可以移动的数字。

One operational lesson from the same run, filed under things a diagram never shows: the blocking stage’s candidate embeddings must be cached. My first live seed re-embedded every canonical name for every extracted mention, where mentions times entities instead of entities, and turned a four-minute seed into a stall I had to kill. One embedding per entity, cached forever, restored the arithmetic. The kind of bug that is obvious the moment you say it out loud, and invisible until you deploy.从同一次运行中得出的一个操作经验,归类为图表从未显示的事情:阻塞阶段的候选嵌入必须缓存。我的第一次实时种子运行为每个提取的提及重新嵌入了每个规范名称(提及次数乘以实体,而不是实体次数),并将四分钟的种子运行变成了我必须杀死的停滞。每个实体一个嵌入,永久缓存,恢复了算术。这种 bug 在你大声说出来时很明显,但在部署之前是不可见的。

One recommendation I will repeat in the implementation notes: prototype this subsystem early and measure it (precision and recall against a labeled set) before committing to the extraction pipeline at scale. Part 1 accidentally produced the perfect labeled starting set: 149 machine-extracted concepts whose correct disposition against the 19 canonical ones is known. Resolution quality is the leading indicator of graph quality, and therefore of everything the graph promises. If this number is bad, nothing downstream can be good.我将在实现说明中重复一个建议:尽早对该子系统进行原型设计并测量它(针对标记集的精确度和召回率),然后再大规模投入提取流水线。第一部分意外地产生了完美的标记起始集:149 个机器提取的概念,其针对 19 个规范概念的正确处置是已知的。解析质量是图质量的领先指标,因此也是图所承诺的一切的领先指标。如果这个数字很差,下游的一切都不可能好。

8. One truth, projected8. 单一真理的投影

Retrieval is the read side. A short section on the write side, because the graph has to come from somewhere, and because Part 1’s stack contains a mechanism I provisioned and never used.检索是读取侧。关于写入侧的简短章节,因为图必须来自某个地方,而且第一部分的堆栈包含了我配置但从未使用的机制。

The design is a read/write split. One document store is the durable system of record — every source, every wiki object, verbatim, with its dates. The two retrieval stores, which are the search index and the graph, are derived projections. A change feed on the system of record drives a projection worker that updates both. One authoritative write, projections follow, accordingly.该设计是读/写分离的。一个文档存储是持久的记录系统——每个来源、每个维基对象,逐字逐句,带有日期。两个检索存储(搜索索引和图)是派生的投影。记录系统上的变更提要驱动更新两者的投影工作者。一次权威写入,投影随之而来。

Figure 7 - The write path. No dual writes, no hand-rolled consistency log: the change feed is the ordered log, and both projections are rebuildable from the record. Image by author.图 7 - 写入路径。没有双重写入,没有手写的的一致性日志:变更提要是顺序日志,两个投影都可以从记录中重建。图片由作者提供。

This one move resolves the problem that sinks most multi-store designs (keeping the stores consistent) by never asking them to be independently written. There is no dual-write to get wrong and no reconciliation job to babysit. The cost is eventual consistency measured in seconds, which for a knowledge layer over policy documents is an easy trade.这一举措通过从不要求它们独立写入,解决了困扰大多数多存储设计的问题(保持存储一致)。没有双重写入会出错,也没有协调任务需要照看。成本是以秒为单位的最终一致性,对于政策文档之上的知识层来说,这是一个容易的权衡。

I am keeping this section deliberately short. The production depth of the write path, where we have the idempotent projections, replay, poison handling, rebuild procedures, deserves its own article, and this one has a different spine. What matters here is the shape: verbatim record first, projections derived, nothing dual-written.我故意缩短了这一节。写入路径的生产深度,即我们拥有幂等投影、重放、毒消息处理、重建程序的地方,值得单独写一篇文章,而这一篇有不同的主轴。这里重要的是形状:先有逐字记录,派生投影,没有双重写入。

9. The ablation is the acceptance test9. 消融实验即验收测试

Part 1 earned whatever credibility it has by deploying and measuring instead of asserting. This design commits to the same standard, and it names the experiment that could kill its centerpiece.第一部分通过部署和测量而不是断言赢得了信誉。此设计致力于相同的标准,并命名了可能杀死其核心的实验。

The always-fused pipeline makes one strong claim that graph traversal improves retrieval, where the bundle with relational expansion beats the bundle without it. Because traversal is a fixed stage rather than a routed option, that claim is directly testable: run the same golden query set through the full pipeline and through a search-only variant with the traversal stage disabled, and compare.始终融合的流水线提出了一个强有力的主张,即图遍历改进了检索,其中带有关系扩展的包优于没有它的包。因为遍历是一个固定阶段而不是路由选项,所以该主张是直接可测试的:通过完整流水线和禁用遍历阶段的仅搜索变体运行相同的黄金查询集,然后进行比较。

  • A golden query set over the corpus, semantic questions, entity-anchored questions, relational multi-hop questions, temporal questions, and out-of-scope questions that should return nothing, each labeled with the objects the bundle must contain, and for relational queries, the paths it must surface.语料库上的黄金查询集、语义问题、实体锚定问题、关系多跳问题、时间问题以及应该返回空值的范围外问题,每个问题都标有包必须包含的对象,对于关系查询,还标有它必须浮现的路径。

  • Recall and ranking metrics on the grounded bundle, before the model ever sees it — did the needed evidence make it in, and near the top?归因包上的召回率和排名指标,在模型看到它之前——所需的证据是否进入了,并且是否在顶部附近?

  • The fused-versus-search-only ablation as the acceptance test for the graph itself. If fusion does not measurably improve recall or relational-path correctness, the graph is not earning its cost, and the fix goes into extraction and resolution before anything expands.融合与仅搜索的消融实验作为图本身的验收测试。如果融合不能显著提高召回率或关系路径的正确性,图就没有赚回它的成本,修复措施会在任何扩展之前进入提取和解析阶段。

  • Resolution precision and recall tracked as a first-class metric, because section 7 is the leading indicator for everything else.解析精确度和召回率作为一流指标进行跟踪,因为第 7 节是其他一切的领先指标。

A design that names the experiment that could falsify it is a design you can check. So here is the first run of that experiment, on the live deployment, with a small golden set of eight questions and sixteen expected evidence items:一个命名可能证伪它的实验的设计是一个你可以检查的设计。所以这是该实验的第一次运行,在实时部署上,使用由八个问题和十六个预期证据项组成的小型黄金集:

Metric

Fused

Search-only

Recall of expected items

0.75

0.75

Relationship paths per bundle (avg)

10.0

0

The honest reading: on recall, at this corpus scale, the ablation is inconclusive, where twenty-one documents are small enough that hybrid search alone already reaches what it is going to reach, and the graph’s recall contribution cannot show up. What the graph measurably adds at this scale is the second row, where an average of ten documented, time-valid relationship paths per bundle that search-only retrieval cannot produce at all. The recall half of the acceptance test needs a corpus large enough for entry-point misses to occur, and the path-correctness half needs the labeled golden set grown well beyond eight questions. Both are queued. The design said the ablation decides whether the graph earns its cost; the first measurement says the cost is currently earned by grounding quality, not recall, and says it with numbers rather than enthusiasm.诚实的阅读:在召回率方面,在此语料库规模下,消融实验是不确定的,二十一份文档太小,仅混合搜索就已经达到了它能达到的水平,图的召回贡献无法显示出来。在此规模下,图可测量地增加的是第二行,即每个包平均有十条记录在案、时间有效的关系路径,这是仅搜索检索根本无法产生的。验收测试的召回部分需要一个足够大的语料库以出现入口点丢失,路径正确性部分需要将标记的黄金集增长到八个问题以上。两者都在排队。设计说消融实验决定图是否赚回了成本;第一次测量说成本目前是由归因质量赚回的,而不是召回率,并且用数字而不是热情来说明。

10. When routing is still the right answer10. 何时路由仍然是正确的答案

Part 1 had a section titled “when you should not build this,” and I would rather keep that habit than acquire a religion.第一部分有一节标题为“你不应该构建此系统的时候”,我宁愿保持这种习惯,也不愿获得一种宗教。

Keep a router, or plain single-mode retrieval, when the corpus has no meaningful relational structure to traverse; when questions arrive in one dominant shape (“find me the paragraph”) and synthesis is rare; when latency budgets are so tight that a traversal stage plus a union rerank cannot be afforded even at one to two hops; or when there is no capacity to build and measure entity resolution, because an always-fused pipeline over a fragmented graph amplifies noise on every single query instead of only on routed ones. Fusion makes the graph’s quality everyone’s problem. That is a feature exactly when you are prepared to make the graph good.当语料库没有可遍历的有意义关系结构时;当问题以一种主导形式出现(“帮我找到段落”)且综合很少见时;当延迟预算非常紧张,以至于遍历阶段加上并集重排序即使在 1 到 2 跳时也负担不起时;或者当没有能力构建和测量实体解析时,保持路由器或简单的单模式检索。因为在碎片化图上始终融合的流水线会在每个查询上放大噪声,而不是仅在路由查询上。融合使图的质量成为每个人的问题。当你准备好让图变好时,这是一个特性。

This design targets the corpus scale of this series with tens of documents today, plausibly tens of thousands of objects for an early production system. The always-fused commitment and the two-engine topology deserve re-examination at a scale where traversal fan-out and rerank token costs dominate. At that point selective traversal stops being a router smell and becomes an engineering necessity. The interface is designed so that change would be invisible to callers which is the whole argument for the interface.此设计针对本系列目前有几十份文档的语料库规模,对于早期生产系统,可能有数万个对象。始终融合的承诺和双引擎拓扑在遍历扇出和重排序令牌成本占主导地位的规模下值得重新审视。在那一点上,选择性遍历不再是路由器的气味,而变成了工程必需品。接口的设计使得这种改变对调用者是不可见的,这也是接口的全部论点。

Part II - Growing the Azure stack第二部分 - 扩展 Azure 堆栈

11. What is already running11. 当前运行的内容

Everything above is deliberately portable. Now the part where continuity pays off, because Part 1 did not end as a diagram, but as a running resource group, and this design was shaped partly by looking at what that resource group already contains.上述所有内容均经过精心设计,具备良好的可移植性。现在到了体现持续性价值的部分,因为第 1 部分的结尾并非一个图表,而是一个正在运行的资源组。这种设计在一定程度上参考了该资源组中已包含的内容。

Already deployed (Part 1)

Role in this design

Azure AI Search (free tier)

The retriever - hybrid BM25 + vector + RRF; gains an entity-anchors field and an entities index

Cosmos DB for NoSQL

Already the knowledge store - becomes formally the system of record, and its change feed (provisioned all along, never consumed) becomes the projection backbone

Microsoft Foundry (gpt-5-mini + embeddings)

Extraction with structured outputs, resolution adjudication, and the new union reranker

Container Apps + FastAPI

The unified interface and orchestrator - the pipeline of Figure 3 replaces the router inside KnowledgePipeline

Blob Storage, Key Vault, managed identity, App Insights

Unchanged - the no-keys identity model extends to the new pieces

Figure 8 - The Part 2 resource group; the whole stack including the Gremlin account and the projection Function App, side by side with Part 1's nine services. Screenshot by author.图 8 - 第 2 部分的资源组;整个堆栈包括 Gremlin 账户和投影 Function App,与第 1 部分的九个服务并列。截图由作者提供。

One new engine joins, one worker process, and two index changes. That is the entire delta, and it is worth pausing on why it is so small: the retriever-versus-filter test kept time out of a third engine, and the read/write split reuses a change feed the stack already had. Architecture that grows by one engine at a time is architecture you can keep operating.增加了一个新引擎、一个工作进程和两项索引变更。这就是全部的增量。值得深思的是为什么增量如此之小:检索与过滤测试将时间因素排除在第三个引擎之外,而读写分离则复用了堆栈中已有的变更馈送(change feed)。这种每次只增加一个引擎的架构,才是能够持续运营的架构。

Figure 9 - The grown stack. Green is the entire Part 2 delta: one graph engine and one projection worker, fed by a change feed the Part 1 deployment already had. Image by author.图 9 - 扩展后的堆栈。绿色部分为第 2 部分的全部增量:一个图引擎和一个投影工作进程,由第 1 部分部署中已有的变更馈送提供数据。图片由作者提供。

12. The second engine: Cosmos DB for Apache Gremlin12. 第二个引擎:Cosmos DB for Apache Gremlin

The graph needs an engine because the one thing a search service cannot do is traverse. Within the Azure boundary, staying inside the identity model and the operational habits Part 1 established, the natural fit is Cosmos DB for Apache Gremlin — the graph API of the same database family already running as the system of record.图数据库需要一个引擎,因为搜索服务唯一无法做到的就是遍历。在 Azure 环境内,为了保持与第 1 部分建立的身份模型和运营习惯一致,最自然的选择是 Cosmos DB for Apache Gremlin——这是与作为记录系统运行的数据库系列属于同一家族的图数据库 API。

What the graph stores, in this design:在此设计中,图数据库存储的内容如下:

  • Entity vertices - the canonical concepts of the knowledge layer: roof-age-risk, trace-and-access, high-wind-zone-h3. One vertex per resolved entity, which is why section 7 gates everything.实体顶点(Entity vertices)- 知识层的规范概念:屋顶年龄风险(roof-age-risk)、追踪与访问(trace-and-access)、高风区 H3(high-wind-zone-h3)。每个已解析的实体对应一个顶点,这就是为什么第 7 节要对所有内容进行把关。

  • Object vertices - lightweight references to sources, decisions, and contradictions, keyed to the same IDs the search index and the record use.对象顶点(Object vertices)- 对来源、决策和冲突的轻量级引用,其键值与搜索索引和记录所使用的 ID 相同。

  • Typed edges with validity windows - narrows_rule_for, supersedes, contradicts, applies_to, each carrying valid_from, valid_to, ingested_at, and the source IDs that evidence it.带有效窗口的类型化边(Typed edges with validity windows)- 如 narrows_rule_for(缩小规则)、supersedes(取代)、contradicts(冲突)、applies_to(适用于),每一条边都携带 valid_from(生效起始)、valid_to(生效截止)、ingested_at(摄入时间)以及证明其存在的源 ID。

Traversal at query time is bounded and typed from the entry-point anchors, one to two hops, following only edge types relevant to the query intent, collecting both the connected objects and the paths themselves, because the paths are part of the evidence the reranker and the model will see.查询时的遍历从入口锚点开始,范围受限且类型明确,通常为一到两跳。遍历仅跟踪与查询意图相关的边类型,同时收集连接的对象和路径本身,因为这些路径也是重排序器(reranker)和模型所需证据的一部分。

Two operational notes from designing against Cosmos Gremlin specifically. First, traversal consumes request units in proportion to fan-out, which is the practical reason hops stay bounded at one to two and edge types are filtered, therefore the cost model punishes undisciplined traversal quickly. Second, the serverless capacity mode fits this workload the same way it fit Part 1’s store, where traversals are bursts around queries, and idle costs nothing.关于针对 Cosmos Gremlin 进行设计的两点运营说明。首先,遍历消耗的请求单元(RU)与扇出(fan-out)成正比,这就是为何将跳数限制在一到两跳并过滤边类型的实际原因,因此成本模型会迅速惩罚不规范的遍历。其次,无服务器容量模式非常适合这种工作负载,就像它适合第 1 部分的存储一样,遍历是围绕查询的突发性操作,而在空闲时则无需成本。

Figure 10 - The trace-and-access neighborhood in the live graph - typed edges radiating from one concept, including the contradicts edge the ingest-time detector created on its own. Part 1's contradiction was a document; Part 2's is a first-class edge. Screenshot by author.图 10 - 实时图中的“追踪与访问”邻域 - 从一个概念辐射出的类型化边,包括摄入时探测器自动创建的“冲突”边。第 1 部分的冲突是一个文档;而第 2 部分的冲突是一条一等公民的边。截图由作者提供。

13. Teaching the index about the graph13. 让索引了解图

The search index changes in two small ways that carry most of the fusion design.搜索索引进行了两处微小的改动,承载了大部分融合设计。

Every chunk gains an entities field — the collection of canonical entity IDs mentioned in that chunk, stamped at projection time after resolution. This is what makes step 1 of the pipeline produce anchors instead of just text: a hybrid search hit arrives already knowing where it enters the graph. Without this field, fusion would need a name-matching lookup between the stores on every query — the seam would show.每个数据块(chunk)都增加了一个实体字段(entities field)——即该块中提到的规范实体 ID 集合,在解析后的投影阶段打标。这就是为什么流水线的第 1 步产生的是锚点而非单纯的文本:混合搜索命中时已经知道它在图中的进入位置。没有这个字段,融合就需要每次查询时在存储之间进行名称匹配查找——这样接缝就会暴露出来。

A second, small index appears, the entities index. One document per canonical entity (name, type, aliases, and a name embedding) serving exactly one purpose, the blocking stage of entity resolution. When extraction produces a mention, the resolver runs a filtered hybrid lookup against this index to get its candidate set. It is a resolution tool, not a retrieval surface, because queries from users never touch it.出现了第二个小型索引,即实体索引(entities index)。每个规范实体对应一个文档(包含名称、类型、别名和名称嵌入),仅服务于一个目的:实体解析的阻塞阶段。当提取产生一个提及(mention)时,解析器会针对该索引运行过滤后的混合查找以获取候选集。这是一个解析工具,而非检索界面,因为用户的查询永远不会触及它。

Time, meanwhile, stays where the retriever-versus-filter test put it, and that is inside the main index as filterable fields (effective_date, superseded_on) and scoring adjustments — the Azure AI Search machinery Part 1 already used for as_of, now shared with edge-level validity in the graph.与此同时,时间因素保留在检索与过滤测试所放置的位置,即作为主索引中的可过滤字段(effective_date, superseded_on)和评分调整项——这是第 1 部分已使用的 Azure AI Search 机制,现在与图中的边级有效性共享。


Figure 11 - The search side of the graph - the main index gains an entity-anchors field, and a dedicated entities index holds each canonical entity with its aliases and name vector for resolution blocking. Screenshot by author.
图 11 - 图的搜索侧 - 主索引增加了实体锚点字段,专门的实体索引保存每个规范实体及其别名和名称向量,用于解析阻塞。截图由作者提供。

14. The projection worker, and the ontology Ostermere already has14. 投影工作进程与 Ostermere 已有的本体

The write path lands on Azure with one addition that an Azure Functions worker on the Cosmos change feed. Every write to the system of record, either it is a new source, an extracted concept, an edge, flows through the feed to the worker, which projects it into the search index (chunk, vector, entity anchors) and the graph (vertices, typed time-valid edges). Part 1’s demo seeded synchronously because it was easy to run locally; the change-feed worker is that same projection logic given a production shape, and the demo mode keeps a synchronous fallback so the repository still runs with no Azure credentials at all.写入路径在 Azure 上增加了一个组件:运行在 Cosmos 变更馈送上的 Azure Functions 工作进程。对记录系统的每次写入(无论是新来源、提取的概念还是边)都会通过馈送流向工作进程,由其投影到搜索索引(数据块、向量、实体锚点)和图(顶点、带时间有效性的类型化边)中。第 1 部分的演示是同步播种的,因为易于本地运行;变更馈送工作进程则是赋予了生产形态的相同投影逻辑,演示模式保留了同步回退功能,以便存储库在完全没有 Azure 凭据的情况下也能运行。

The part of this section I enjoy most is the ontology, because it turns out Part 1 already built it without calling it that. Typed extraction needs a target vocabulary, the set of relationship types the model is allowed to emit, and generic related_to edges are precisely the thing that makes knowledge graphs disappointing. Part 1’s curated corpus contains twenty-eight hand-written relationships using types like narrows_rule_for, scoped_exception_to, requires_validation_by, contradicts, supersedes, commonly_confused_with. That hand-curated vocabulary is the ontology: the extraction prompt targets it with structured outputs, a validation pass rejects out-of-schema edges, and the curated relationships become both the few-shot examples and the first regression set for extraction quality.本节中我最喜欢的部分是关于本体(ontology)的讨论,因为事实证明第 1 部分在没有明确定义的情况下就已经构建了它。类型化提取需要一个目标词汇表,即模型被允许输出的关系类型集合;而通用的 related_to(相关)边正是导致知识图谱令人失望的原因。第 1 部分的精选语料库包含二十八种手工编写的关系,使用如 narrows_rule_for、scoped_exception_to、requires_validation_by、contradicts、supersedes、commonly_confused_with 等类型。这些手工精选的词汇就是本体:提取提示词以结构化输出为目标,验证阶段会拒绝不符合模式的边,而这些精选关系既成为了少样本(few-shot)示例,也成为了提取质量的首个回归集。

That is the quiet advantage of having built the knowledge layer by hand first. You do not have to invent an ontology in a workshop, you promote the one your own curation already proved useful.这就是先手工构建知识层的隐性优势。你无需在研讨会上凭空发明本体,而是直接推广你自己的策展实践中已经证明有用的本体。

15. Reranking the union15. 联合重排序

Step 4 of the pipeline needs one honest paragraph of Azure specifics, because there is a trap here I want to flag before someone falls into it.流水线的第 4 步需要一段诚实的 Azure 具体说明,因为这里有一个我想在有人踩坑之前指出的陷阱。

Azure AI Search ships a built-in semantic ranker, and the natural assumption is that it handles the reranking stage. It cannot! Not because it is weak, but because it only reranks its own query results. The union in this pipeline is hand-assembled: search hits plus graph-pulled objects that never went through the search query at all. No search service reranks documents it did not retrieve.Azure AI Search 自带语义重排序器,自然的假设是它能处理重排序阶段。但它不能!不是因为它功能弱,而是因为它只能重排序其自身的查询结果。本流水线中的联合结果是手工组装的:搜索命中结果加上从图中提取的对象,而后者从未经过搜索查询。没有任何搜索服务会去重排序它未检索到的文档。

So the union rerank is external. A scoring call to a Foundry-deployed model that sees each candidate with its relational path rendered alongside its text, and orders the union by combined semantic and relational evidence. A reranker that can read “connected to the query’s anchor through narrows_rule_for, two hops” as evidence will make different, and for relational questions, better decisions than any similarity score.因此,联合重排序是外部进行的。通过对部署在 Foundry 上的模型进行评分调用,该模型查看每个候选对象及其与文本并排呈现的关系路径,并根据语义和关系证据的组合对联合结果进行排序。一个能够将“通过 narrows_rule_for 连接到查询锚点,两跳”解读为证据的重排序器,在处理关系型问题时,会比任何相似度评分做出不同且更好的决策。

There is a cost silver lining that carries over from Part 1: because the built-in semantic ranker goes unused, the search service’s free tier remains viable for the proof stage, exactly as it was in Part 1’s deployment. The rerank cost moves to model tokens, where it is proportional to use instead of provisioned per month.这有一个从第 1 部分延续下来的成本优势:由于未用到内置的语义重排序器,搜索服务的免费层级对于验证阶段依然可行,正如第 1 部分的部署一样。重排序成本转移到了模型 Token 上,这与使用量成正比,而非按月预付。

16. What the additions cost16. 增加功能的成本

Part 1 measured its running costs live ($0.16 for the whole validation period, roughly $0.05 a day idle), and the additions keep the same discipline: serverless where possible, provisioned nowhere.第 1 部分实时测量了其运行成本(整个验证期间为 0.16 美元,空闲时每天约 0.05 美元),而这些增加的功能保持了同样的原则:尽可能使用无服务器架构,不进行任何预配置。

Addition

Mode

Expected cost behavior

Cosmos DB for Gremlin

Serverless

RUs per traversal burst; idle near zero; bounded hops keep per-query RUs small

Azure Functions (projection)

Consumption

Free grant covers a demo corpus many times over

Entities index

Inside existing search service

No new service; counts against the free tier’s document quota

Union rerank

Foundry tokens

The one genuinely new variable cost - proportional to query volume and bundle size

Extraction + gray-zone adjudication

Foundry tokens

Ingest-time, one-off per document; the two-threshold design exists to keep the gray zone narrow

The honest summary: the fixed-cost delta is close to zero, and the two things that can actually cost money (rerank tokens and gray-zone adjudication) are both governed by knobs the design exposes on purpose (bundle size, hop bounds, threshold width). The implementation article will put measured numbers against each, the way Part 1 did.诚实的总结是:固定成本增量接近于零,而真正可能产生费用的两件事(重排序 Token 和灰色地带裁决)都由设计中特意暴露的旋钮(捆绑包大小、跳数限制、阈值宽度)所控制。实现文章将像第 1 部分那样,为每一项给出具体的测量数值。

Part III - The corpus, traversed第三部分 - 遍历语料库

The corpus does not change. The twenty-one Ostermere Mutual documents, the contradiction, the claims, the wind zones — all of it carries over from Part 1, which is precisely what makes it useful here. The questions that exposed Part 1’s limits can now be replayed against the new design. And they were literally. The design above is deployed on a fresh resource group next to Part 1’s, the corpus was reseeded through the fused ingestion path (live extraction, live edge extraction against the ontology, live resolution, projection into a real Gremlin graph — 75 vertices and 165 edges from 21 documents), and every output quoted below came back from that deployment.语料库没有改变。二十一份 Ostermere Mutual 文档、冲突、索赔、风区——所有内容都从第 1 部分延续下来,这正是它在此处有用的原因。那些暴露了第 1 部分局限性的问题现在可以针对新设计重新回放。事实也确实如此。上述设计部署在紧邻第 1 部分的新资源组上,语料库通过融合摄入路径重新播种(实时提取、针对本体的实时边提取、实时解析、投影到真实的 Gremlin 图中——从 21 份文档中提取出 75 个顶点和 165 条边),下面引用的所有输出均来自该部署。

One number before the walkthroughs, because it validates the section this article leans on hardest. Under Part 1’s alias-only resolution, live extraction produced 149 concepts against 19 canonical ones. Under the new resolution pipeline — embedding similarity in blocking and scoring, two thresholds, gray-zone adjudication, the same corpus produced 120. A twenty percent reduction in fragmentation from the same documents, measured like-for-like. Not yet 19, since the direction and the mechanism are confirmed, and the remaining gap is extraction phrasing, which the next walkthrough makes concrete.在演示之前先看一个数字,因为它验证了本文所依赖的核心部分。在第 1 部分仅基于别名的解析下,实时提取产生了 149 个概念,对应 19 个规范概念。而在新的解析流水线下——结合了阻塞和评分中的嵌入相似度、两个阈值、灰色地带裁决,同样的语料库产生了 120 个概念。在相同文档下,碎片化程度降低了 20%,这是等效测量结果。虽然还没达到 19,但方向和机制已得到确认,剩余的差距在于提取措辞,接下来的演示将具体说明。

17. The multi-hop question, answered properly17. 多跳问题的正确解答

Part 1, section 5.6, described multi-hop as a failure mode: “Why was this claim triaged Level 1?” needs the claim notes, then the triage guideline, then the water-damage concept, then the policy clause and top-k similarity ranks, it does not traverse. Part 1’s live deployment answered the question anyway, but it did so on the strength of the model reasoning over whatever chunks happened to rank, the hops lived in the prompt, not in retrieval.第 1 部分的 5.6 节将多跳描述为一种失败模式:“为什么该索赔被定为 1 级?”需要索赔记录,然后是分诊指南,然后是水损概念,然后是保单条款和 Top-k 相似度排名,它无法进行遍历。第 1 部分的实时部署虽然回答了这个问题,但那是基于模型对碰巧排名的任何数据块进行推理的结果,跳数存在于提示词中,而非检索中。

Under fusion, the same question retrieves differently. Hybrid search surfaces the claim notes as entry points, carrying anchors for CLM-1042, claim-triage-level, and sudden-accidental-water-damage. Traversal walks one hop: the triage level’s edge to the guideline that defines it, the water-damage concept’s scoped_exception_to relationship with the wear-and-tear exclusion, the claim’s contrasts_with link to CLM-1155. The bundle the model receives is the chain itself. Four objects and the typed paths connecting them rather than four chunks that happen to co-rank.在融合模式下,同样的问题检索方式不同。混合搜索将索赔记录作为入口点,携带 CLM-1042、索赔分诊级别和突发意外水损的锚点。遍历走一跳:分诊级别到定义它的指南的边、水损概念与磨损除外条款之间的 scoped_exception_to 关系、索赔到 CLM-1155 的 contrasts_with 链接。模型接收到的捆绑包是链本身。是四个对象以及连接它们的类型化路径,而不是碰巧并列排名的四个数据块。

The difference shows up in what can go wrong. In Part 1, if one chunk of the chain missed the top-k, the model had a gap to bridge by inference. Under fusion, the chain is pulled in because it is connected, and the reranker sees the connection as evidence. Retrieval does the walking; the model does the explaining.区别在于可能出错的地方。在第 1 部分中,如果链中的一个数据块未进入 Top-k,模型必须通过推理来填补缺口。在融合模式下,链因为连接而直接被拉入,重排序器将连接视为证据。检索负责“行走”;模型负责解释。

Here is the live run. The request:以下是实时运行情况。请求:

jsonjson
{ "question": "Explain why the CLM-1042 water loss was placed at triage Level 1
   under the property claim triage process.", "top_k": 6 }

The grounded bundle came back with fourteen typed paths alongside the ranked objects. A sample of the paths array, verbatim:基础捆绑包返回了十四条类型化路径以及排序后的对象。路径数组的 verbatim 样本:

markupmarkup
provisional-operational-view-level-1-triage -explained_by-> initial-repair-estimate    (valid 2026-04-18..open)required-initial-information-intake -applies_to-> first-notice-of-loss-fnol    (valid 2026-02-15..open)localized-corrosion -applies_to-> supply-pipe-connector-failure    (valid 2026-04-18..open)

And the model’s answer walked the chain with citations, exactly as designed:模型按照设计,引用了路径中的链条进行了回答:

Summary why CLM-1042 was triaged Level 1, Triage criterion: Level 1 applies where damage is minor, there is no safety concern or continuing water entry, and estimated exposure is below EUR 5,000 [INS-SYN-002]. Facts from the file that meet those criteria: damage was limited (wet kitchen ceiling board; moisture in a limited section of the upstairs bathroom floor) …总结 CLM-1042 为何被定为 1 级的原因。分诊标准:当损害较小、无安全隐患或持续进水,且估计风险敞口低于 5,000 欧元时,适用 1 级 [INS-SYN-002]。文件中符合这些标准的证据:损害有限(厨房天花板受潮;楼上浴室地板局部受潮)……

Notice the vertex names in those paths: provisional-operational-view-level-1-triage, first-notice-of-loss-fnol. These are live-extracted provisional entities, named by the model’s own phrasing — the graph a real extraction pipeline builds is messier than the one you curate, and the paths carry that texture honestly.注意这些路径中的顶点名称:provisional-operational-view-level-1-triage、first-notice-of-loss-fnol。这些是实时提取的临时实体,由模型自己的措辞命名——真正的提取流水线构建的图比你策展的图更混乱,而路径真实地承载了这种纹理。


Figure 12 - The live grounded bundle - the answer arrives with the typed relationship paths retrieval walked, visible as evidence next to the ranked objects. Screenshot by author.
图 12 - 实时基础捆绑包 - 答案伴随着检索所遍历的类型化关系路径,作为证据显示在排序对象旁边。截图由作者提供。

18. The question Part 1 could not ask18. 第 1 部分无法提出的问题

Here is a question no version of Part 1 could answer, and every insurance auditor asks: “How did the roof inspection requirement evolve, and what exactly changed on 1 March 2026?”这是一个第 1 部分的任何版本都无法回答,但每位保险审计员都会问的问题:“屋顶检查要求是如何演变的,2026 年 3 月 1 日究竟发生了什么变化?”

Part 1 could answer as-of questions — which rule applied on date X — by filtering documents. But evolution is a question about relationships over time, and Part 1’s edges had no time on them. Under the bitemporal model, the corpus’s own data answers it as a timeline traversal on the roof-age-risk vertex:第 1 部分可以通过过滤文档来回答“截至日期 X”的问题——即当时适用什么规则。但演变是一个关于随时间变化的关系的问题,而第 1 部分的边没有时间属性。在双时态模型下,语料库本身的数据通过对 roof-age-risk 顶点的时序遍历来回答该问题:

  • 2025-06-01 - edge opens: general inspection threshold above 20 years (INS-SYN-003).2025-06-01 - 边开启:一般检查阈值为 20 年以上 (INS-SYN-003)。

  • 2026-01-22 - edge opens: the rationale relationship — the analysis email explaining why a 15-year threshold was coming, with its 3.4x displacement finding (INS-SYN-018).2026-01-22 - 边开启:基本原理关系——解释为何引入 15 年阈值的分析邮件,及其 3.4 倍的置换发现 (INS-SYN-018)。

  • 2026-03-01 - edge opens: threshold above 15 years, scoped to H3 new business (INS-SYN-004). The general edge stays open — it still holds outside H3. Scoped supersession, Part 1’s first failure mode, expressed as two concurrently-valid edges with different scopes.2026-03-01 - 边开启:针对 H3 新业务的 15 年以上阈值 (INS-SYN-004)。一般性边保持开启——它在 H3 之外仍然有效。范围化的取代(Scoped supersession)是第 1 部分的首个失败模式,现在表现为两条具有不同范围且同时有效的边。

Figure 13 - The evolution of one requirement as time-valid edges on a single vertex. Two threshold edges stay concurrently open because their scopes differ - Part 1’s scoped-supersession trap, expressed in the data model instead of guarded against in prose. Image by author.图 13 - 一个需求在单个顶点上作为时间有效边的演变。两条阈值边同时保持开启,因为它们的范围不同——这是第 1 部分的“范围化取代”陷阱,现在体现在数据模型中,而不是在正文中进行防御。图片由作者提供。

And the diff form — what changed in Q1 2026, is the same traversal filtered to edges whose validity opened in the window: the H3 threshold, the zone-classification ownership, the recoverable-depreciation change from policy v1.1 to v1.2. Each with its source attached, each still carrying Part 1’s discipline that the answer cites documents, not itself.差异形式(diff form)——2026 年第一季度发生了什么变化,是相同的遍历结果,过滤掉那些在窗口期内开启有效性的边:H3 阈值、区域分类所有权、保单 v1.1 到 v1.2 的可回收折旧变化。每一项都附有来源,每一项都保留了第 1 部分的准则,即答案引用文档,而非引用自身。

The live run returned twenty-four time-valid edges for the roof-age-risk neighborhood, ordered by validity, and the model produced exactly the chronological narrative the design promised:实时运行返回了 roof-age-risk 邻域的 24 条时间有效边,按有效性排序,模型产生了设计所承诺的精确时间叙事:

Summary timeline (chronological by effective date). I cite the governing source IDs in brackets and preserve each rule’s scope. 2025-06-01 - Baseline rule: a roof inspection is normally requested when the primary roof covering is more than 20 years old (roof-age-inspection-threshold) …时间线总结(按生效日期排序)。我在括号中引用了管理来源 ID,并保留了每条规则的范围。2025-06-01 - 基准规则:当主要屋顶覆盖物超过 20 年时,通常要求进行屋顶检查 (roof-age-inspection-threshold) ……

And the diff form (temporal_mode: "diff", as_of: "2026-01-01") returned only the edges whose validity opened in the window, each tagged opened or superseded.差异形式(temporal_mode: "diff", as_of: "2026-01-01")仅返回了在窗口期内开启有效性的边,每条边都标记为开启或取代。

One honest artifact from the live data, worth recording because it is a real bitemporal modeling lesson: the curated edge high-wind-zone-h3 narrows_rule_for roof-age-risk shows valid_from: 2025-06-01 in the live graph — the date of its earliest evidencing source, not the date the narrowing took effect (2026-03-01). My reconstruction heuristic for pre-bitemporal curated edges uses the earliest source date, and for supersession-flavored relations that is semantically wrong; the later date is the truth. Reconstruction of validity windows for edges that predate bitemporal capture needs per-relation-type rules, not one heuristic. Noted, and queued for the fix.关于实时数据,有一个值得记录的真实产物,因为它是一个真正的双时态建模经验:精选的边 high-wind-zone-h3 narrows_rule_for roof-age-risk 在实时图中显示 valid_from: 2025-06-01 —— 这是其最早证据来源的日期,而非缩减生效的日期(2026-03-01)。我对预双时态精选边的重建启发式算法使用了最早的来源日期,对于具有取代性质的关系,这在语义上是错误的;较晚的日期才是事实。对于早于双时态捕获的边,其有效窗口的重建需要针对每个关系类型的规则,而非单一的启发式方法。已记录,并排队等待修复。

I want to be precise about what is new here. The facts were always in the corpus. Part 1 retrieved them individually just fine. What was missing was the shape of the question — evolution and change are traversals over time-valid edges, and no amount of better ranking produces them from a system whose relationships do not know when they were true.我想明确这里的新内容。事实一直都在语料库中。第 1 部分可以单独检索它们。缺失的是问题的形态——演变和变化是对时间有效边的遍历,而对于一个关系不知道其何时为真的系统,无论如何优化排名都无法产生这些答案。

19. Would the system have found con-001 on its own?19. 系统能自己发现 con-001 吗?

The question I most wanted to answer with this design: if the contradiction detection of section 6 had existed when the corpus was first ingested, would con-001 have been discovered instead of curated?我最想通过此设计回答的问题是:如果第 6 节的冲突检测在语料库首次摄入时就存在,con-001 会被发现而不是被策展吗?

Walk the ingest. The Claims Handling Manual (INS-SYN-012, effective 2025-11-01) arrives, and extraction produces an edge: trace-and-access covered_as_standard, limit EUR 5,000, valid from November. Later the Endorsement Catalogue (INS-SYN-008, effective 2026-01-01) arrives, and extraction produces: trace-and-access optional_paid_endorsement, HS-TA-01, EUR 24 premium.回顾摄入过程。《索赔处理手册》(INS-SYN-012,2025-11-01 生效)到达,提取产生了一条边:trace-and-access covered_as_standard,限额 5,000 欧元,11 月起生效。后来《背书目录》(INS-SYN-008,2026-01-01 生效)到达,提取产生:trace-and-access optional_paid_endorsement,HS-TA-01,24 欧元保费。

The detection check asks: does the new fact conflict with a currently-valid edge on the same subject? Both edges attach to the same vertex — but only if resolution correctly lands “trace-and-access costs” from a claims manual and “Trace and Access (HS-TA-01)” from a product catalogue on one entity. Assume it does (and note, again, everything routes through section 7). The two edges are incompatible, standard cover and paid endorsement cannot both describe the same policy, and the supersession test fails on both criteria that would allow an automatic expiry: different authorities wrote them, and the newer document does not claim to replace the older one. Genuine conflict. The system opens a contradiction object, attaches both statements with their sources and dates, assigns the owner, and the gate arms itself.检测检查会问:新事实是否与同一主题上当前有效的边冲突?两条边都连接到同一个顶点——但前提是解析正确地将来自索赔手册的“trace-and-access costs”和来自产品目录的“Trace and Access (HS-TA-01)”落在同一个实体上。假设确实如此(再次注意,所有内容都通过第 7 节)。这两条边不兼容,标准承保和付费背书不能同时描述同一保单,且取代测试在允许自动过期的两个标准上均失败:不同的权威机构编写了它们,且较新的文档并未声称取代旧文档。真正的冲突。系统打开一个冲突对象,附上带有来源和日期的两条陈述,分配所有者,并进行把关。

That was the design-time prediction. What actually happened on the live deployment was better in one way and worse in another, and both are worth reporting.这是设计时的预测。实时部署中发生的情况在某些方面更好,在某些方面更糟,两者都值得报告。

Better: I never had to run the replay. The detector fired during the corpus seed itself, unprompted, and this object appeared in the register next to the curated con-001:更好的是:我从未需要运行重放。检测器在语料库播种期间自动触发,该对象出现在寄存器中,就在精选的 con-001 旁边:

json
{
  "id": "con-auto-trace-and-access-cost-recovery-coverage_basis",
  "status": "unresolved",
  "statements": [
    { "source_id": "INS-SYN-008", "effective_date": "2026-01-01",
      "statement": "trace-and-access-cost-recovery coverage_basis
                    endorsement-hearthmere-catalogue" },
    { "source_id": "INS-SYN-012", "effective_date": "2025-11-01",
      "statement": "trace-and-access-cost-recovery coverage_basis
                    trace-and-access-costs-are-covered-as-standard-under-hearthmere-
                    up-to-eur-5-000-..." }
  ],
  "why_not_resolved": "Both sources are current and come from different authorities;
    the newer document does not supersede the older one. Detected at ingest - the
    system records the conflict and does not pick a side."
}

The live model extracted incompatible coverage_basis claims from the two documents, resolution landed them on one vertex, the supersession test failed on the different-authority criterion, and the machine opened an unresolved contradiction with both statements and their dates. Nobody curated it. The policy found it.实时模型从两份文档中提取了不兼容的 coverage_basis 声明,解析将它们落在同一个顶点上,取代测试在不同权威标准上失败,机器打开了一个带有两条陈述及其日期的未解决冲突。没有人策展它。保单自己发现了它。

Worse — and this is the caveat the design-time version of this section predicted, observed in the wild: look at the subject vertex. It is trace-and-access-cost-recovery, a provisional entity named by the model’s own phrasing, not the canonical trace-and-access the curated graph uses. Resolution correctly put both conflicting mentions on one vertex, which is why detection fired at all, but not on the canonical one. The auto-detected contradiction and the curated one describe the same real conflict from two vertices that resolution should eventually merge. Detection is a policy on top of extraction and resolution quality; sections 6 and 7 are one investment, not two features. The live run proved both halves of that sentence in a single object.更糟的是——这也是设计时版本所预测的 caveat,在野外观察到:看看主题顶点。它是 trace-and-access-cost-recovery,一个由模型自己的措辞命名的临时实体,而不是精选图所使用的规范 trace-and-access。解析正确地将两个冲突的提及放在一个顶点上,这就是检测触发的原因,但不是落在规范顶点上。自动检测到的冲突和精选的冲突描述了同一个真实冲突,但来自两个应该最终合并的顶点。检测是提取和解析质量之上的策略;第 6 节和第 7 节是一项投资,而非两个功能。实时运行在单个对象中证明了这句话的两个方面。

The auto-detected statements are also visibly rawer than the curated ones, rendered triples rather than quoted prose. That is what ingest-time detection looks like at this maturity, and I would rather show it than polish it: the mechanism works, and the finishing quality is extraction work, not architecture work.自动检测到的陈述也明显比精选的陈述更原始,是渲染的三元组而非引用的散文。这就是摄入时检测在当前成熟度下的样子,我宁愿展示它也不愿润色它:机制是有效的,而润色质量是提取工作,而非架构工作。


Figure 14 - The contradiction nobody wrote - the ingest-time detector opened this object on its own during the corpus seed, with both statements, both sources and an unresolved status. The automated counterpart of Part 1's hand-curated con-001. Screenshot by author.
图 14 - 没人编写的冲突 - 摄入时检测器在语料库播种期间自动打开了该对象,包含两条陈述、两个来源和一个未解决状态。这是第 1 部分手工精选 con-001 的自动化对应物。截图由作者提供。

20. To sum it all up20. 总结

Part 1 argued that a RAG system needs somewhere for understanding to accumulate, and built the place. This article is about what that place must become once you take its own contents seriously: the relationships deserve traversal, the traversal deserves to run on every query, the edges deserve to know when they were true, and all of it stands on entity resolution done properly.第 1 部分认为 RAG 系统需要一个积累理解的地方,并构建了这个地方。本文讨论的是当你认真对待其内容时,这个地方必须变成什么样:关系值得遍历,遍历值得在每次查询时运行,边值得知道它们何时为真,而这一切都建立在妥善完成的实体解析之上。

In practical terms, the revision is four commitments:从实际角度看,这次修订包含四个承诺:

The router asks: which store should answer this question?
Fusion answers: both, always — and the results compete on evidence.
路由器问:哪个存储应该回答这个问题?融合回答:两者,总是——结果在证据上竞争。

Part 1's edges said:  these two things are related.
Bitemporal edges say: they were related from March, until the update — and here is what changed.

Part 1's contradiction was curated.  The detector discovers it at ingest.
第 1 部分的边说:这两件事是相关的。双时态边说:它们从 3 月开始相关,直到更新——这就是变化的内容。第 1 部分的冲突是手工策展的。检测器在摄入时发现它。

Part 1's resolution matched aliases. The pipeline earns the graph its connections.第 1 部分的解析匹配别名。流水线为图赢得了连接。

And one thing deliberately does not change: the gate. When the evidence touches a conflict the organization has not resolved, the system still returns both positions, names the accountable owner, and declines to answer as though the matter were settled. Fusion changes how evidence is gathered. It does not change the honesty of what happens when the evidence disagrees with itself.有一件事是有意不改变的:把关。当证据触及组织尚未解决的冲突时,系统仍然返回两个立场,命名负责的所有者,并拒绝像问题已解决那样回答。融合改变了收集证据的方式。它没有改变当证据与自身冲突时所发生情况的诚实性。

The Azure delta is one engine, one worker, and two index changes on the stack that has been running since Part 1, the implementation lands in the same repository, and the next article will validate these designs the way Part 1’s were validated: deployed, queried live, measured, and reported with the failures left in.Azure 的增量是一个引擎、一个工作进程和两个索引更改,在自第 1 部分以来一直运行的堆栈上,实现落在同一个存储库中,下一篇文章将像验证第 1 部分一样验证这些设计:部署、实时查询、测量,并报告留下的失败。

Thank you for continuing this exploration with me. Part 1 ended by saying the application was no longer searching a pile of documents but slowly building a reviewable model of a domain. This part is about letting the system walk that model, along its relationships, backward through its history, and straight into its own contradictions, and I strongly believe that is the difference between a knowledge layer that stores understanding and one that uses it.感谢您与我继续这次探索。第 1 部分的结尾说,应用程序不再是搜索一堆文档,而是慢慢构建一个可审查的领域模型。这一部分是关于让系统沿着其关系、回溯其历史、直抵其自身的矛盾来行走该模型,我坚信这就是存储理解的知识层与使用理解的知识层之间的区别。

Disclosure: I am a Microsoft MVP. This article reflects my own independent work and opinions; Microsoft had no involvement in or review of its content. All Azure usage described is based on public documentation and my own deployment. 披露:我是微软 MVP。本文反映了我自己的独立工作和观点;微软未参与或审查其内容。所描述的所有 Azure 使用均基于公开文档和我自己的部署。

References

[1] (Part 1 link) - the first article in this series, with the accompanying repository at github.com/mcekikj/persistent-knowledge-layer[1] (第 1 部分链接) - 本系列的第一篇文章,配套存储库位于 github.com/mcekikj/persistent-knowledge-layer

[2] P. Lewis et al., Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks (2020), NeurIPS 2020[2] P. Lewis 等人,Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks (2020), NeurIPS 2020

[3] D. Edge et al., From Local to Global: A Graph RAG Approach to Query-Focused Summarization (2024), arXiv[3] D. Edge 等人,From Local to Global: A Graph RAG Approach to Query-Focused Summarization (2024), arXiv

[4] Microsoft, Hybrid Search Overview - Azure AI Search (2026), Microsoft Learn[4] 微软, Hybrid Search Overview - Azure AI Search (2026), Microsoft Learn

[5] Microsoft, Relevance Scoring in Hybrid Search Using Reciprocal Rank Fusion (2026), Microsoft Learn[5] 微软, Relevance Scoring in Hybrid Search Using Reciprocal Rank Fusion (2026), Microsoft Learn

[6] Microsoft, Introduction to Azure Cosmos DB for Apache Gremlin (2026), Microsoft Learn[6] 微软, Introduction to Azure Cosmos DB for Apache Gremlin (2026), Microsoft Learn

[7] Microsoft, Change Feed in Azure Cosmos DB (2026), Microsoft Learn[7] 微软, Change Feed in Azure Cosmos DB (2026), Microsoft Learn

[8] Microsoft, Azure Cosmos DB Trigger for Azure Functions (2026), Microsoft Learn[8] 微软, Azure Cosmos DB Trigger for Azure Functions (2026), Microsoft Learn

[9] Microsoft, Add Scoring Profiles to a Search Index (2026), Microsoft Learn[9] 微软, Add Scoring Profiles to a Search Index (2026), Microsoft Learn

[10] Microsoft, Structured Outputs with Azure OpenAI (2026), Microsoft Learn[10] 微软, Structured Outputs with Azure OpenAI (2026), Microsoft Learn

Related Articles