MiroFish · 专题 01
← MiroFish 项目分析 专题 01 / 03
Technical deep-dive · 01

材料驱动图谱构建机制

材料到图谱的主链路是真实且完整的:上传文件被解析和预处理,LLM 依据材料与推演目标生成本体,文本被分块后写入 Zep 批次,服务等待批次条目和图谱事件处理结束。最值得参照的是“确定性操作标识 + 内容摘要 + 模糊响应对账 + 完成屏障”;最不应直接照搬的是强绑定舆情场景的本体提示、只保证“最多十类”却对外要求“恰好十类”的约束落差,以及没有本地图数据库替换层的 Zep Cloud 绑定。

版本 0.1.0 证据基线 117ed3775 分析日期 2026-08-24
Key finding
最值得参照的是“确定性操作标识 + 内容摘要 + 模糊响应对账 + 完成屏障”;最不应直接照搬的是强绑定舆情场景的本体提示、只保证“最多十类”却对外要求“恰好十类”的约束落差,以及没有本地图数据库替换层的 Zep Cloud 绑定。

专题结论

材料到图谱的主链路是真实且完整的:上传文件被解析和预处理,LLM 依据材料与推演目标生成本体,文本被分块后写入 Zep 批次,服务等待批次条目和图谱事件处理结束。最值得参照的是“确定性操作标识 + 内容摘要 + 模糊响应对账 + 完成屏障”;最不应直接照搬的是强绑定舆情场景的本体提示、只保证“最多十类”却对外要求“恰好十类”的约束落差,以及没有本地图数据库替换层的 Zep Cloud 绑定。

原始类与方法

原始符号 真实职责 源码定义 固定提交行级证据
generate_ontology()(接收文件与模拟需求并创建项目的图谱接口函数,约第 266 行) 保存文件、抽取文本、调用本体生成服务并迁移项目状态 图谱接口文件 固定版本第 266-447 行
OntologyGenerator(封装提示、长文抽样、本体生成和校验的服务类,约第 194 行) 把非结构化材料和模拟要求转换为实体/关系 schema 本体生成文件 固定版本第 194-202 行
OntologyGenerator.generate()(调用 LLM 并把结果交给校验器的入口方法,约第 203 行) 形成系统/用户消息,最多尝试两次结构化响应 本体生成文件 固定版本第 203-248 行
OntologyGenerator._validate_and_process()(清洗实体、关系、属性和兜底类型的方法,约第 432 行) 归一化模型输出,去重、截断并修复引用 本体生成文件 固定版本第 432-626 行
build_graph()(恢复或发起图谱构建异步任务的接口函数,约第 449 行) 校验项目状态、分块、恢复批次并启动云端处理 图谱接口文件 固定版本第 449-766 行
GraphBuilderService(集中封装 Zep 图谱操作和批次生命周期的服务类,约第 61 行) 创建图谱、设置本体、提交批次、等待处理和读取图数据 图谱构建文件 固定版本第 61-120 行
GraphBuilderService.set_ontology()(把 LLM 本体转换为 Zep 数据模型的方法,约第 313 行) 动态创建实体与关系模型并注册到云端图谱 图谱构建文件 固定版本第 313-406 行
GraphBuilderService.add_text_batches()(以可对账方式批量提交文本分块的方法,约第 407 行) 建立操作标识和分块摘要,提交、核对并启动批次处理 图谱构建文件 固定版本第 407-564 行
GraphBuilderService._wait_for_batch()(轮询批次条目终态的方法,约第 631 行) 等待全部条目成功,失败或超时则阻断后续完成状态 图谱构建文件 固定版本第 631-719 行
GraphBuilderService._wait_for_episodes()(轮询图谱事件处理完成的方法,约第 720 行) 防止 API 已接收但事实抽取尚未完成时提前发布完成态 图谱构建文件 固定版本第 720-776 行

原始调用链

  1. generate_ontology()(接收上传材料并创建项目的入口,约第 266 行)调用文件解析、文本预处理和 OntologyGenerator.generate()(生成本体的主方法,约第 203 行)。
  2. OntologyGenerator.generate()(构造提示并调用结构化 LLM 的方法,约第 203 行)把返回值交给 OntologyGenerator._validate_and_process()(规范化并限制本体的后处理方法,约第 432 行)。
  3. build_graph()(校验项目、分块并启动图谱构建的接口函数,约第 449 行)调用 GraphBuilderService.create_graph()(创建或对账图谱的方法,约第 218 行)和 GraphBuilderService.set_ontology()(注册动态实体/关系模型的方法,约第 313 行)。
  4. GraphBuilderService.add_text_batches()(提交分块并对账模糊结果的方法,约第 407 行)完成提交后,由 GraphBuilderService._wait_for_batch()(等待批次条目终态的方法,约第 631 行)和 GraphBuilderService._wait_for_episodes()(等待图谱事件处理完成的方法,约第 720 行)形成双重完成屏障。

源码片段一:本体生成不是规则抽取,而是 LLM 生成后校验

原项目关键片段。来源:OntologyGenerator.generate()(调用结构化 LLM 并进入校验的方法,约第 220-248 行);固定提交第 220-248 行

原项目关键片段:

python
# 构建用户消息
user_message = self._build_user_message(
    document_texts,
    simulation_requirement,
    additional_context
)

messages = [
    {"role": "system", "content": system_prompt},
    {"role": "user", "content": user_message}
]

result = self.llm_client.chat_json(
    messages=messages,
    temperature=0.3,
    max_tokens=None,
    max_attempts=2,
)

result = self._validate_and_process(result)
return result

这段代码证明本体来源是模型结构化输出,而不是确定性的实体识别规则。可借鉴的是“生成后进入专门校验阶段”;不能照搬的是把模型输出当作已验证领域本体。生产引入需要加入字段级错误、人工确认、版本化 schema 和材料覆盖率指标。

源码片段二:“恰好十类”实际只实现了上限和兜底

原项目关键片段。来源:OntologyGenerator._validate_and_process()(添加人物/组织兜底并执行数量上限的方法,约第 521-548 行);固定提交第 521-548 行

原项目关键片段:

python
entity_names = {e["name"] for e in result["entity_types"]}
has_person = "Person" in entity_names
has_organization = "Organization" in entity_names

fallbacks_to_add = []
if not has_person:
    fallbacks_to_add.append(person_fallback)
if not has_organization:
    fallbacks_to_add.append(organization_fallback)

if fallbacks_to_add:
    current_count = len(result["entity_types"])
    needed_slots = len(fallbacks_to_add)
    if current_count + needed_slots > MAX_ONTOLOGY_TYPES:
        to_remove = current_count + needed_slots - MAX_ONTOLOGY_TYPES
        result["entity_types"] = result["entity_types"][:-to_remove]
    result["entity_types"].extend(fallbacks_to_add)

result["entity_types"] = result["entity_types"][:MAX_ONTOLOGY_TYPES]

提示要求与实现的差异是:代码能截断超量类型并加入兜底,但不会在模型只生成少量类型时补齐到十类。这里应把“恰好”改成可执行校验,或把产品文案改成“最多”。

源码片段三:云端批次不盲目重放,而是先对账

原项目关键片段。来源:GraphBuilderService.add_text_batches()(创建业务操作标识并对账批次创建结果的方法,约第 423-455 行);固定提交第 423-455 行

原项目关键片段:

python
if not graph_id:
    raise ValueError("graph_id is required")
self.validate_batch_chunks(chunks, batch_size=batch_size)

total_chunks = len(chunks)
operation_id = self.build_operation_id(graph_id, chunks)
if batch_created_callback:
    batch_created_callback(None, operation_id)

try:
    batch = self.client.batch.create(
        metadata={
            "mirofish_operation_id": operation_id,
            "graph_id": graph_id,
            "chunk_count": total_chunks,
        }
    )
except Exception as error:
    if not is_retryable_zep_error(error):
        raise
    batch = self._find_batch_by_operation_id(graph_id, operation_id)
    if batch is None:
        raise RuntimeError(
            "Zep batch creation is unconfirmed and no matching operation was found"
        ) from error

这段是整个项目最值得参照的后端模式之一:写请求超时不等于服务端未执行,直接重试会重复写入;先以确定性业务标识查询并恢复,才能区分“已成功但响应丢失”和“确实未创建”。引入时可复用思想,但操作标识、重试边界和服务端查询能力需要按目标基础设施重新设计。

数据与约束边界

边界 实现事实 影响
输入格式 文件解析器支持 PDF、Markdown 与文本;配置允许单文件 50MB 不支持扫描件 OCR;大文件上限不等于模型实际完整读取
本体上下文 本体生成器最多向 LLM 提供 50,000 字符,并对长文等距抽样 覆盖首中尾但可能漏掉稀疏关键证据
图谱批次 GraphBuilderService.validate_batch_chunks()(在首次云端写入前验证批次总量和单块长度的方法,约第 567 行)限制最多 50,000 个条目、单块 10,000 字符 能提前失败,避免部分提交;仍需估算 Zep 配额和费用
本体模型 GraphBuilderService.set_ontology()(动态创建并注册实体/关系数据模型的方法,约第 313 行)把生成本体直接下发云端 schema 变化会影响历史图谱兼容和报告检索
状态持久化 项目模型使用本地 JSON 保存项目、本体、批次和错误 适合单机恢复,不具备关系数据库事务和多实例协调

对当前需求的取舍

建议局部引入批次对账、完成屏障、分阶段状态和可视化确认思路;暂不建议直接引入生成式本体与 Zep 专属数据层。PoC 至少应验证:十组真实材料的本体覆盖率、人工修订成本、重复/断网恢复、图谱事件来源、云配额与删除一致性,并把原材料事实和模拟写回放入不同命名空间。

返回 MiroFish 项目分析主报告