在知识图谱问答(KGQA)领域,越往后做越会遇到这类问题:单条事实很好查,但用户问的是需要跨多个关系跳转才能回答的复杂问题,而支撑答案的知识又分散在不同机构手里。FedV-KGQA 这个方向,目标就是在纵向分割(Vertically Partitioned)的知识图谱上完成多跳问答(Multi-Hop Question Answering),让参与方在不暴露本地完整知识图谱的前提下,协作推理出跨机构的问题答案。
这篇笔记围绕 FedV-KGQA 展开,会先解释纵向分割知识图谱为什么比横向分割更麻烦,然后给出一个可落地的框架拆解、一套最小可运行的模拟联邦实现、评估方式,以及排查路径。适合正在做知识图谱问答、联邦学习,或者要在多个业务方之间做跨库推理的算法工程师和研究生。学完后,你可以自己构造一个模拟数据,把 FedV-KGQA 的主链路跑通,并且知道下一步如果要接入联邦训练或隐私保护,应该改哪些地方。
1. 先理解纵向分割知识图谱给多跳问答带来哪些冲突
1.1 从单跳问答到多跳问答,问题难度主要来自路径组合
KGQA 的基础形式可以理解为三元组匹配。给定一个实体和关系,去知识图谱里找出对应的尾实体。例如“《泰坦尼克号》的导演是谁”,可以拆成movie -> directed_by -> director,属于单跳问题。
多跳问答则要求沿多条边进行组合推理。例如“《泰坦尼克号》的导演获得过什么奖项”,路径是:
titanic -> directed_by -> james_cameron -> award_received -> award这条路径横跨了两条关系,第一跳找到导演,第二跳再从导演实体出发找到奖项。路径越长,候选空间膨胀得越厉害,同时只要中间任意一跳断裂,最终答案就无法到达。
如果把知识图谱看成一张全局图,多跳问答等价于在图上做受限路径搜索。难点不在于“搜索”本身,而在于:
- 中间实体可能不是用户问题里直接提到的实体。
- 关系路径存在多种写法,比如“导演”可以是
directed_by,也可以是director_of。 - 多跳组合数量指数级增长,需要在精度和开销之间做剪枝取舍。
1.2 纵向分割知识图谱的定义和现实场景
知识图谱的联邦场景,按照数据切分方式可以分成两类。
横向分割指不同机构持有不同实体子集,但关系类型是重合的。例如两家银行各有自己的客户图谱,实体不重叠,但都使用“账户、交易、风险等级”这些关系。
纵向分割指不同机构持有同一批实体空间,但各自掌握的关系类型不同。每个参与方相当于拥有全局实体主键的一个“关系视图”。举一个现实例子:
- 机构 A 持有电影数据,包括
movie -> directed_by -> director、movie -> genre -> genre。 - 机构 B 持有艺人数据,包括
director -> award_received -> award、director -> born_in -> place。 - 机构 C 持有票房数据,包括
movie -> box_office -> amount。
当用户问“《泰坦尼克号》导演获得过哪个奖”,机构 A 能回答第一跳,机构 B 能回答第二跳,但没有任何一家机构能独立完成整条路径。FedV-KGQA 要解决的问题就在这里:在不把 A、B 的本地图谱直接合并给出任何一方的前提下,完成跨机构的多跳推理。
1.3 纵向分割给多跳问答带来的三个难点
第一个难点是路径不可见。全局图不存在,每个参与方只知道自己本地的关系子图。机构 A 不知道导演在机构 B 里还有“获奖”这条边,因此无法本地规划完整路径。
第二个难点是中间结果传递带来的隐私泄露风险。多跳推理必须把中间实体从一个参与方传给另一个参与方。如果一条路径上返回了过多候选实体和关系细节,参与方完全可以根据响应内容反推另一方的图谱结构。例如本地频繁向对方询问“某实体有哪些关系”,对方返回的邻居集合本身就是敏感信息。
第三个难点是实体对齐和谓词对齐。纵向分割理想情况下假设实体 ID 共享,但实际业务里会对同一实体使用不同 ID 或不同命名。不同机构对同一种关系的命名也可能不一致。directed_by和film_director在语义上可能是同一条边,但字符串不相等,路径匹配就失败。
注意:FedV-KGQA 这类方案的前提一定是“实体主键能被对齐,关系谓词能被归一化”。如果这个前提不成立,后续所有联邦推理方案都很难落地。
2. FedV-KGQA 的整体思路与核心模块
2.1 框架目标:在全局图不可见时完成路径推理
FedV-KGQA 的输入是一个自然语言问题,输出是答案实体及置信度。系统中间不依赖任何一方持有完整图谱,而是通过“查询解析 + 多跳扩展 + 联邦消息交换 + 本地安全打分 + 全局聚合”来完成推理。
可以把系统拆成两类角色:
- Coordinator:负责接收问题、解析关系路径、广播中间候选、收集打分、输出最终答案。协调者本身不保存任何机构图谱,只维护联邦会话状态。
- Client:每个参与方持有一份纵向切分的知识图谱子集。Client 只响应协调者发来的查询,返回候选实体和得分,不暴露本地全量图。
2.2 模块拆解和职责边界
实际实现时可以按下面几个模块组织代码:
- Query Parser:把自然语言转换成起点实体和关系序列。生产环境往往由实体链接模型和关系预测模型组成。
- Path Expander:在单个 Client 内部,根据当前实体集合和目标关系,从本地图谱找回边尾实体。
- Message Protocol:规定 Coordinator 和 Client 之间传什么消息、字段有哪些、候选数量上限是多少。协议设计直接决定隐私泄露程度。
- Local Scorer:Client 端对本地候选给出的置信度,可以是简单计数,也可以是基于图嵌入模型的得分。
- Global Aggregator:汇总多个 Client 返回的候选路径得分,按策略排序得到答案。
为了最小可运行,第一阶段可以用简单规则代替 Query Parser,例如预先定义问题模板和对应的关系路径。这样能把精力集中在联邦推理链路上。
2.3 端到端推理流程:联邦版的多跳 Beam Search
FedV-KGQA 的核心推理可以理解成“分布式的受限 Beam Search”。每一跳都做四件事:
- Coordinator 把当前候选实体集合广播给所有 Client。
- 每个 Client 在本地查找以候选实体为头实体、且满足当前跳关系约束的边。
- Client 只返回得分最高的 top-k 尾实体及其得分。
- Coordinator 合并各方返回结果,剪枝保留全局 top-k,进入下一跳。
重复这个过程,直到走完关系路径,最终对候选实体路径得分排序。
用伪代码描述如下:
输入: q = "《泰坦尼克号》导演获得过什么奖项?" start_entities = {titanic} rel_path = [directed_by, award_received] 过程: current = start_entities for t in range(len(rel_path)): responses = [] for client in clients: responses.append(client.expand(current, rel_path[t], top_k=5)) current = merge_and_prune(responses, beam_size=5) final_scores = aggregate_path_scores(current) 输出: 候选答案实体列表,按得分降序这个流程的优点是简单、可控、可审计。每一跳都只交换候选实体和置信度,不交换本地图结构。缺点是 top-k 剪枝太狠会影响长路径召回,因此 beam_size 和 top_k 是两个最关键的参数。
3. 环境准备、实验数据与指标设计
3.1 运行环境与依赖
模拟联邦不需要一开始就上真实分布式框架。用一台机器、多个 Client 对象,就能验证 FedV-KGQA 的推理逻辑。建议环境如下:
Python 3.8+ numpy pandas networkx scikit-learn如果要进一步引入图神经网络或联邦训练,再补充:
torch torch-geometric flwr 或自研联邦通信层学习阶段建议先用 pandas 读取三元组文件,用 dict 建图,避免过早引入分布式组件带来的排障成本。
3.2 构造纵向分割知识图谱模拟数据
实验数据可以从公开 KG 数据集中抽取一个子集,然后按“关系”切分到不同 Client。假设原始三元组文件格式为head, relation, tail,切分代码如下:
import pandas as pd from collections import defaultdict triples = pd.read_csv("kg_triples.csv") # 字段: head, relation, tail # 按关系划分到三个参与方 client_spec = { "client_a": ["directed_by", "starred_actors"], "client_b": ["award_received", "born_in"], "client_c": ["genre", "release_year"], } kg_clients = {} for client_name, rels in client_spec.items(): subset = triples[triples["relation"].isin(rels)].copy() kg_clients[client_name] = subset for name, sub in kg_clients.items(): print(name, sub.shape)切分后要检查一个关键条件:同一个实体是否出现在不同 Client 中。这决定了路径能否跨机构衔接。
entities = {} for name, sub in kg_clients.items(): head_set = set(sub["head"]) tail_set = set(sub["tail"]) entities[name] = head_set | tail_set overlap = entities["client_a"] & entities["client_b"] print("client_a 与 client_b 的共享实体数:", len(overlap))如果共享实体数为 0,路径一定无法跨机构衔接。这个检查看起来很基础,但实际是纵向联邦实验里最常见的失败原因。
3.3 评估指标和基线设计
FedV-KGQA 的评估不能只看答案是否正确,还要看推理路径是否存在、通信开销多大、隐私泄露风险是否可控。
| 指标 | 含义 | 用途 |
|---|---|---|
| Hits@1 | 第一个候选正确的比例 | 衡量最终答案质量 |
| Hits@10 | 前 10 个候选包含正确答案的比例 | 衡量召回能力 |
| MRR | 正确答案在候选列表中的平均倒数排名 | 综合衡量排序质量 |
| Path Acc | 最终答案是否在完整图上存在正确路径 | 区分推理失败和检索失败 |
| 通信量 | 联邦过程中交换的候选实体数量总和 | 衡量系统开销 |
| 隐私预算 | 差分隐私噪声强度或消息剪枝阈值 | 评估隐私保护强度 |
常用的基线包括:
- Centralized:直接把所有 Client 图谱合并成一张图,做集中式多跳查询。作为精度上界。
- Local Only:只用本机构图谱回答,能查到第一跳就返回,查不到就失败。
- 无隐私约束的联邦:各 Client 返回所有匹配邻居,精度高但泄露严重。
- FedV-KGQA 默认配置:top-k 剪枝,可选差分隐私噪声。
通过对比这四个设置,可以看清“联邦推理损失多少精度”和“隐私保护又损失多少精度”这两个问题分别来自哪里。
4. 核心实现:一个可运行的 FedV-KGQA 最小示例
4.1 消息协议定义
Coordinator 和 Client 之间可以复用简单的 dict 消息结构。建议把协议单独定义,方便后续替换成真正的 RPC 通信层。
# message_protocol.py def build_request(qid, step, entity_scores, required_rel): return { "qid": qid, "step": step, "entity_scores": entity_scores, # {entity: score} "required_rel": required_rel, } def build_response(qid, step, candidate_scores): return { "qid": qid, "step": step, "candidate_scores": candidate_scores, # {entity: score} }注意:entity_scores 里的实体是全局共享的 ID,不是各家内部 ID。协议层只关心实体 ID、关系和分数,不关心家内部如何存储。
4.2 Client 端本地图查询与打分
每个 Client 需要实现expand方法。输入当前候选实体集合和关系,输出候选尾实体及得分。
from collections import defaultdict class KGClient: def __init__(self, client_id, triples_df): self.client_id = client_id # (head, relation) -> tail list self.rel_tail = defaultdict(list) # head -> [(relation, tail)] self.head_edges = defaultdict(list) for _, row in triples_df.iterrows(): h, r, t = row["head"], row["relation"], row["tail"] self.rel_tail[(h, r)].append(t) self.head_edges[h].append((r, t)) def expand(self, entity_scores, required_rel, top_k=5): candidate_scores = {} for entity, score in entity_scores.items(): tails = self.rel_tail.get((entity, required_rel), []) for tail in tails: candidate_scores[tail] = max( candidate_scores.get(tail, 0.0), score, ) # 只返回 top-k 个候选,避免暴露全量邻居 sorted_candidates = sorted( candidate_scores.items(), key=lambda x: -x[1], )[:top_k] return dict(sorted_candidates)这段代码有三点需要注意:
- 使用
score作为路径得分累计,简单起见直接用上游得分,实际项目可换成带衰减的路径概率。 top_k参数既控制通信量,也控制隐私边界。如果返回空,说明该 Client 本地没有可衔接的边。- 这里没有加入差分隐私噪声。带噪声版本可以在返回前给分数加随机扰动,再做 top-k 排序。
4.3 Coordinator 端 Beam Search 主循环
Coordinator 不持有任何图谱,只维护候选实体集合和路径得分。
class Coordinator: def __init__(self, clients): self.clients = clients def answer(self, start_entities, rel_path, beam_size=5, top_k=5): # candidates: {entity: path_score} candidates = {e: 1.0 for e in start_entities} path_log = {} for step, rel in enumerate(rel_path): merged = defaultdict(float) for client in self.clients: local_result = client.expand(candidates, rel, top_k=top_k) for entity, score in local_result.items(): merged[entity] = max(merged[entity], score) # 剪枝:只保留全局 top-k 候选 pruned = sorted( merged.items(), key=lambda x: -x[1], )[:beam_size] candidates = dict(pruned) path_log[step] = { "relation": rel, "candidates": candidates, } return candidates, path_loganswer方法返回两个值:
- 最终候选实体字典,按路径得分排序。
- 每一步的候选列表,用于排查路径断裂在哪个环节。
业务方可以在此基础上从最终候选里取第一个作为答案,也可以把候选 ID 还原成实体名称进行展示。
4.4 主流程示例与预期输出
把模拟数据和一个简单的查询拼起来:
if __name__ == "__main__": client_a = KGClient("client_a", kg_clients["client_a"]) client_b = KGClient("client_b", kg_clients["client_b"]) coordinator = Coordinator(clients=[client_a, client_b]) # 问题: Titanic 的导演获得过什么奖项 start_entities = ["titanic"] rel_path = ["directed_by", "award_received"] candidates, path_log = coordinator.answer( start_entities, rel_path, beam_size=5, top_k=5, ) print("最终候选:") for entity, score in sorted(candidates.items(), key=lambda x: -x[1]): print(f" {entity}: {score:.4f}") print("路径日志:") for step, log in path_log.items(): print(f" step {step} ({log['relation']}): {log['candidates']}")预期输出类似:
最终候选: academy_award: 1.0000 golden_globe_award: 1.0000 路径日志: step 0 (directed_by): {'james_cameron': 1.0} step 1 (award_received): {'academy_award': 1.0, 'golden_globe_award': 1.0}注意:这个示例只验证主链路是否跑通,不能代表真实模型的精度。真实项目里,start_entities 需要实体链接模型从问题里解析,rel_path 需要关系预测模型生成,而不应该是手工写好的固定序列。
5. 关键参数、训练策略与联邦聚合设计
5.1 参数速查表与调整建议
| 参数 | 默认建议 | 作用 | 调大影响 | 调小影响 |
|---|---|---|---|---|
| beam_size | 5 | 每跳保留的全局候选数 | 召回提高但通信量增大 | 候选太少,长路径容易断裂 |
| top_k | 5 | 每个 Client 返回的最大候选数 | 减少漏召回但泄露更多信息 | 更隐私但跨机构路劲容易丢 |
| max_hops | 3 | 最大关系跳数 | 支持更长路径 | 无法回答深度推理问题 |
| noise_scale | 0.0 | 差分隐私噪声强度 | 隐私更好但排序变差 | 精度更高但可能泄露局部结构 |
| score_decay | 0.9 | 路径得分随跳数衰减 | 惩罚长路径 | 长路径和短路径得分差异变小 |
实际调整时,先固定 beam_size 和 top_k,再观察 Path Acc。如果 Path Acc 很低但 Hits@1 很高,说明失败案例主要来自查询解析而不是联邦推理。如果 Path Acc 高但 Hits@1 低,问题往往出现在剪枝和排序策略上。
5.2 纯推理模式与联邦训练模式的差异
FedV-KGQA 并不一定需要训练模型。如果关系路径由规则或外部模型解析,且本地图谱是静态的,那么联邦推理阶段可以直接用“计数打分”完成。这是最快的落地方式。
但如果希望每个 Client 端能预测“哪些本地边更有可能衔接下一跳”,而不是只靠静态匹配,就需要引入图嵌入模型。此时每个 Client 可以独立训练一个本地边的评分器,Coordinator 只聚合最终概率分数。这种方式扩展性更强,但需要考虑不同 Client 的模型输出分布差异,不能简单把分数直接相加。
5.3 答案聚合策略选型
| 聚合方式 | 计算方式 | 适合场景 | 缺点 |
|---|---|---|---|
| Max | 取每个候选在 Client 端的最高分 | 路径必须在某一路径上完整存在 | 分数不可比时容易失真 |
| Average | 多端分数平均 | 多方机制对同一实体都有证据 | 平均会稀释强证据 |
| Weighted | 按准确率或数据量加权 | Client 质量不均衡 | 权重需要额外标定 |
| Rank Aggregation | 按排名而不是分数融合 | 各端打分尺度不一致 | 实现稍复杂 |
最小实现阶段用 Max 最直观:同一答案只要在某一跳路径上被连续找回,就能拿到完整的路径得分。生产阶段建议同时输出 Max 和 Weighted 两个版本做对比,观察波动。
6. 运行、评估与结果分析
6.1 在模拟环境里批量评估
批量评估需要准备测试集。每条测试样本包含:
{ "question": "titanic director awards", "start_entity": "titanic", "rel_path": ["directed_by", "award_received"], "answer": "academy_award" }评估代码可以循环调用 Coordinator:
import json from collections import defaultdict test_set = json.load(open("test_questions.json")) hits1 = 0 mrr_total = 0.0 path_ok = 0 for sample in test_set: candidates, path_log = coordinator.answer( start_entities=[sample["start_entity"]], rel_path=sample["rel_path"], ) ranked = [e for e, _ in sorted( candidates.items(), key=lambda x: -x[1], )] if ranked and ranked[0] == sample["answer"]: hits1 += 1 if sample["answer"] in ranked: rank = ranked.index(sample["answer"]) + 1 mrr_total += 1.0 / rank path_ok += 1 n = len(test_set) print("Hits@1:", round(hits1 / n, 4)) print("MRR:", round(mrr_total / n, 4)) print("Path Recall:", round(path_ok / n, 4))Path Recall 在这里表示“答案出现在最终候选里的比例”,可以从侧面反映跨机构路劲找回能力。
6.2 一个模拟对比结果示例
下面是一组用于说明维度的示例数据,不代表任何真实论文结果。它的意义在于展示不同配置下,精度和隐私之间的权衡关系。
| 方法 | Hits@1 | MRR | 通信量 | 隐私泄露风险 |
|---|---|---|---|---|
| 中心化全图 | 0.72 | 0.80 | 无联邦开销 | 高:全图可见 |
| 仅本地单跳 | 0.18 | 0.25 | 无联邦开销 | 低 |
| FedV-KGQA,top_k=100 | 0.66 | 0.74 | 高 | 中 |
| FedV-KGQA,top_k=5 | 0.58 | 0.66 | 中 | 低 |
| FedV-KGQA + DP噪声 | 0.51 | 0.58 | 中 | 低 |
从趋势上可以看出两个结论:
- top_k 从 100 降到 5,会让一部分正确答案在剪枝阶段被丢弃,精度下降,但消息体变小,局部分数还原难度也降低。
- 加噪声后准确率进一步下降,说明隐私保护和精度之间存在强对抗关系。
6.3 如何分析失败案例
评估之后要区分三种失败类型:
- 查询解析失败:问题没有被解析成正确的起点实体或关系路径。此时无论联邦推理多完美,答案都不可能正确。
- 路径断裂:按正确 rel_path 执行,但某一跳没有任何 Client 返回候选。这是联邦图覆盖问题。
- 排序失败:正确候选虽然出现在最终候选里,但排名不够靠前。这是打分和聚合策略的问题。
用路径日志来定位。如果path_log[step]只有空 dict,说明该跳没有找到边。如果后一跳候选为空,而前一跳 candidate 数量很少,优先检查beam_size和top_k。
注意:不要只验证程序能启动并输出候选,还要把每跳候选变化、空结果场景和聚合分数打印出来,否则很难判断是“没有答案”还是“答案被剪掉了”。
7. 常见问题与排查清单
7.1 问题排查总表
| 问题现象 | 常见原因 | 检查方式 | 处理建议 |
|---|---|---|---|
| 第一跳就无候选 | 起点实体不在任何 Client 本地图谱中,或关系名不一致 | 检查起点实体是否存在于各 Client 的 head 集合 | 统一实体 ID 和谓词命名 |
| 第二跳以后候选骤减 | 中间实体没能被下一跳 Client 识别,或 top_k 太小 | 打印每跳候选数量,检查共享实体数 | 提高 beam_size 和 top_k |
| 答案在候选中但排名低 | 打分只看跳数,没有区分不同关系的置信度 | 检查聚合函数和 score_decay | 引入关系权重或模型打分 |
| 最终候选为空 | 关系路径长度超过图谱实际连接深度 | 检查测试样本的 rel_path 是否在完整图中存在 | 修正路径模板或增加数据覆盖 |
| 不同 Client 分数不可比 | 各 Client 打分尺度差异大 | 打印各 Client 候选分数分布 | 改用 Rank Aggregation 或 z-score 归一化 |
7.2 三个最常见的坑
第一个坑是关系谓词没有归一化。两个机构分别使用directed_by和film_director表示同一条关系,但代码里用字符串精确匹配,路径必然断掉。推荐做法是在系统入口维护一张谓词映射表,或者离线把所有 Client 的谓词统一转换后再切分。
第二个坑是候选剪枝过狠导致长路径召回丢失。top_k=2看起来通信量小,但真实多跳图中间环节稍有分支,正确路径就会被剪掉。推荐先用 top_k=20 跑一遍,观察 Path Recall,再逐步降低到可接受的通信水平。
第三个坑是忽略隐私边界,让 Client 返回了全部匹配邻居。虽然精度高,但参与方完全可以记录对方在每一跳询问的实体集合,推断出对方的图结构。生产环境必须对返回候选做 top-k 限制,并考虑加差分隐私噪声。
7.3 从输入到输出的排查顺序
当系统返回错误答案时,按下面的顺序定位:
- 检查查询解析结果:起点实体是否正确,关系路径是否完整。
- 检查第一跳候选:每个 Client 是否返回了非空结果。
- 检查中间实体衔接:前一跳的候选实体是否存在于下一跳 Client 的 head 集合。
- 检查剪枝参数:被剪掉的是低分噪声,还是正确中间实体。
- 检查聚合函数:不同 Client 的分数是否被合理比较。
- 检查答案排序:正确实体是否在最终候选中,被谁挤掉了。
这个顺序从“输入是否正确”开始,一直排到“排序策略是否合理”,可以避免一上来就改模型或调参。
8. 最佳实践与扩展方向
8.1 从模拟实验到生产环境需要补齐的能力
最小模拟只验证了联邦推理主线。进入真实生产环境,还需要考虑以下机制:
- 实体链接和关系规范化:自然语言问题必须先链接到全局实体 ID,再映射到各家的关系谓词。
- 联邦通信安全:Coordinator 与 Client 之间需要 TLS、身份认证、请求审计。
- 隐私预算管理:如果使用差分隐私,要为每个 Client 设置噪声预算上限,避免长期运行后隐私耗尽。
- 日志与监控:记录每跳候选数量、通信量、失败率,方便定位问题。
- 回滚安排:当某家图谱版本更新导致路径断裂时,系统要能快速切换旧版本。
8.2 可执行的最佳实践清单
- 动手前先确认实体 ID 在所有 Client 间可对齐,不要假设中间实体天然一致。
- 第一版用规则解析问题模板,先跑通联邦推理,再替换成神经网络解析模块。
- 在评估报告里同时输出 Hits@1、MRR、Path Recall、通信量四类指标,缺一不可。
- 每次修改 top_k 或 beam_size 后,重新观察失败案例,而不是只关注总体准确率。
- 记录最终可行的实体路径,用户可据此判断答案是否可解释。
- 不要在高频路径查询里启动大规模模型推理,先缓存可以复用的短路径结果。
8.3 扩展方向:隐私增强、模型化推理与可解释性
FedV-KGQA 可以沿三个方向继续深入。
隐私增强方向,在消息返回前加入稀疏化、top-k 截断、差分隐私噪声,或者使用秘密共享技术对候选分数做安全聚合。这样即使 Coordinator 被攻击,也无法轻易还原出某一个 Client 的完整图谱。
模型化推理方向,每个 Client 用本地图谱训练一个图编码器,把实体映射成向量。协调者不再直接广播实体 ID,而是广播向量和候选 IDs,通过向量相似度完成跨机构衔接。这个方向能够处理谓词命名不一致,但对联邦训练和通信效率要求更高。
可解释性方向,把最终答案对应的多跳路径片段返回给用户。例如“titanic --directed_by--> james_cameron --award_received--> academy_award”。这条路径既是答案证据,也是排查失败案例的关键信息。
FedV-KGQA 的核心价值不在于把某一方模型变得更强,而在于设计一个跨机构协作的协议,让每一方在最小披露的前提下完成全局推理。建议你先从最简单的 5 个 Client 模拟器开始,自己构造一批能跨 2 到 3 跳的问题,把每跳候选变化打印出来观察一遍。这个调试过程比直接引入图神经网络更有助于理解纵向联邦 KGQA 的本质约束。