LLMにトリプル抽出させたら壊れたKG ─ 構築自動化3パターンと落とし穴
5,000ドキュメントを人手で組む地獄
社内のRFP・契約書・議事録を全部ナレッジグラフ(KG)に載せたい、という相談を受けたことがあります。
ファイル数を数えたら5,200本ありました。1本あたり平均15分で読み、エンティティと関係を3組ずつ抽出する。電卓を叩くと1,300時間でした。私一人だと半年仕事です。レビューを足したら8ヶ月、年度内に間に合いません。
「人手抽出は無理。LLMに任せます」と即答したものの、3週間後に出来上がったKGはノードだけで12万、エッジは40万。重複と矛盾だらけで、Cypherクエリを投げるたびに違う答えが返ってきました。「Microsoft」のノードを数えたら7つあって、それぞれ違うエッジを持っていたときは天井を見上げました。
その失敗を踏まえて整理した「構築自動化の3パターン」と、どのパターンでも必ず踏む落とし穴をまとめます。私と同じ轍を踏まないでください。

2026年の前提: どこを使えば外さないか
3パターンに入る前に、2026年5月時点の主要ツールの現在地を押さえます。古い情報のまま「OntoGPTってまだあるんですか?」と聞かれる場面が増えたので。
Property Graph vs RDFはどちらを狙うか
抽出ツール選定の前に、ターゲットがプロパティグラフかRDFかで分岐します。私の結論はこうです。
業務アプリのバックエンドに組み込むならプロパティグラフ(Neo4jなど)。Cypher が書きやすく、エッジに属性を持てるので「出典URL」「信頼度スコア」を後付けできます。一方、医療・化学・公共データのように既存オントロジー(SNOMED、Gene Ontology等)とつなぐ前提ならRDF。OntoGPTはRDF寄りです。
迷ったらプロパティグラフでスタートし、外部公開フェーズでRDFエクスポートを足す、という順序を私は取ります。最初からRDFで組むと、SPARQLの学習コストでチームメンバーが脱落しやすいというのが、3案件回した上での実感です。
パターン1: Few-shot Prompt + 後段Validator
最もシンプルな構成です。プロンプトに3〜5例のトリプル抽出例を見せて、JSON配列で返してもらう。後段でJSON Schemaバリデータに通す、それだけ。
PROMPT = """以下のテキストからトリプルを抽出してください。
例1: テキスト「Microsoftは2024年にGraphRAGを公開した」
出力: [{"subject":"Microsoft","predicate":"published","object":"GraphRAG","year":2024}]
テキスト: {document}
出力:"""
抽出後はPydanticで型検証し、
predicateが定義済みの12種類のいずれかでなければ捨てる。これだけで「LLMが勝手に発明した動詞」を弾けます。
from pydantic import BaseModel, field_validator
ALLOWED_PREDICATES = {"published", "acquired", "develops", "works_at", ...}
class Triple(BaseModel):
subject: str
predicate: str
object: str
@field_validator("predicate")
def check_predicate(cls, v):
if v not in ALLOWED_PREDICATES:
raise ValueError(f"unknown predicate: {v}")
return v
最初の案件では、validator なしで動かしたら
acquires
purchased
boughtが全部別の述語として登録されてしまい、後でCypherクエリが書けなくなりました。バリデータは初日から入れるべきです。
エラーを raise するか、ログに書いて捨てるかは好みです。私は最初の3日間は raise して目で確認し、述語の揺れが落ち着いたら warning ログに変えて捨てる、という運用にしています。raise のままだと夜間バッチが止まって朝の対応に追われます。
強み: 半日で組める。GPT-5-miniなら1,000ドキュメントで$3程度。
弱み: スキーマ進化に弱い。新しい述語が必要になるたびにプロンプトを書き直す。
PoCや「とりあえずKGを見てみたい」というフェーズなら、迷わずこれです。私の5,200ドキュメント案件もパターン1から始めるべきでした。最初から本気でパターン3を組んで、スキーマが固まる前にコストだけ垂れ流したのが当時の反省点です。
パターン2: スキーマ駆動抽出(OntoGPT / LangChain型)
スキーマを先に定義し、LLMにはそれを埋めるタスクだけ与える方式です。
OntoGPTならLinkML形式のスキーマYAML、LangChain LLMGraphTransformerなら
allowed_nodesと
allowed_relationshipsを渡します。
from langchain_experimental.graph_transformers import LLMGraphTransformer
transformer = LLMGraphTransformer(
llm=llm,
allowed_nodes=["Company", "Product", "Person"],
allowed_relationships=["ACQUIRED", "DEVELOPS", "WORKS_AT"],
node_properties=["name", "founded_year"],
relationship_properties=["date", "amount"],
)
graph_documents = transformer.convert_to_graph_documents(docs)
LLMがFunction callingで構造化出力するため、JSONパースの失敗がほぼなくなります。OntoGPTのSPIRESはさらに洗練されていて、ネストしたオブジェクトを根からたどって再帰抽出します(SPIRES論文)。
OntoGPT 側のスキーマはLinkMLのYAMLで書きます。クラス・スロット・enumを階層的に定義するだけで、LLMへのプロンプトも自動生成されます。
classes:
Acquisition:
attributes:
acquirer:
range: Company
acquired:
range: Company
date:
range: date
amount_usd:
range: integer
このスキーマだけで「主語と目的語の取り違え」がほぼ起きなくなります(後述の落とし穴2)。なぜなら
acquirerと
acquiredという名前で意味が制約されるからです。
強み: スキーマで型が固定されるため、表記揺れ以外のノイズはかなり減る。
弱み: スキーマ設計が前提作業として重い。第4章で書いたNeo4j 7ステップのStep 3そのものです。
5,200ドキュメント案件は、結局ここに行き着きました。最初の2週間でオントロジーを固め、残り1週間で抽出を回す、という配分でした。スキーマ確定までは「とにかくPMと業務担当者と会議室にこもる」が一番効きます。エンジニアだけで設計すると、現場で実際に使う関係が漏れます。
パターン3: マルチパス + Self-correction
抽出 → 検証 → 修正の3回LLMを走らせる方式です。
- Pass 1: パターン2と同じ方式でトリプル抽出
- Pass 2: 抽出結果を原文と並べてLLMに「事実関係が一致しているか」を問う
- Pass 3: Pass 2で不一致だったトリプルだけ、修正案をLLMに出させる
Microsoft GraphRAGの内部処理に近い構造です(GraphRAG GitHub)。最近はLazyGraphRAGがこのうちPass 2/3を検索時に遅延実行する方式を提案していて、構築コストが下がっています。
# Pass 2: 検証
verify_prompt = f"""
原文: {source_text}
抽出されたトリプル: {triples_json}
各トリプルについて、原文と矛盾しないか judge してください。
出力: [{{"triple_id":1,"verdict":"valid|invalid|partial"}}]
"""
Pass 3 はこんな具合です。
# Pass 3: 修正
fix_prompt = f"""
原文: {source_text}
不正確と判定されたトリプル: {invalid_triples}
原文に基づいて、各トリプルを修正してください。
修正不能(=原文に該当する事実がない)なら null で返してください。
"""
Pass 2 で
invalid判定が出たトリプルだけPass 3に回せば、コストを抑えられます。私の案件ではPass 2の
invalid率は8%程度でした。
強み: 精度が体感で2割以上上がる。本番グレードのKGを組むならこれ一択。
弱み: トークンコストが3倍。GPT-5系で本気で回すと、1,000ドキュメントで$30〜$50。月次バッチで5万ドキュメント回すと、ざっくり$2,000のラインに乗ります。
3パターンの選び方
私の使い分けはこうです。
| フェーズ | パターン | 理由 |
|---|---|---|
| PoC・1週間で見せたい | パターン1 | 半日で組める |
| 本番投入・スキーマが固まった | パターン2 | コストと精度のバランス |
| 規制業界・監査対象 | パターン3 | Self-correctionで後から説明できる |
順番に昇格していくのが安全です。最初からパターン3を組むと、スキーマがブレた瞬間に全部のパスを書き直すことになります。
チーム規模で見ると、1人なら迷わずパターン1。2〜3人ならパターン2。5人以上で本番運用するチームならパターン3。それぞれ「人がスキーマレビューに割ける時間」「コストの説明責任の重さ」が判断材料です。私が見てきた失敗の多くは、人数とパターンのミスマッチでした。
評価と更新の現実については、過去記事『KG×LLMを本番に入れて気づいた評価・更新の現実』も参照してください(検索すれば出てきます)。本番運用ではトリプル抽出の精度より、更新パイプラインの設計のほうが事故率を左右します。新しい文書が来たときに「再構築するのか」「差分マージするのか」を決めずに作ると、半年後に誰もKGを信用しなくなります。
どのパターンでも踏む3つの落とし穴

ここから先は、パターン選定とは別レイヤの話です。3つとも私が踏み抜きました。
落とし穴1: エンティティ表記揺れ
「Microsoft」「マイクロソフト」「MS」「Microsoft Corporation」が4つの別ノードになって、Cypherクエリで「Microsoftの製品一覧」を出すと半分しか取れない、というやつです。
対策は2段構えです。
# 段1: 正規化辞書(手で20社ほど作る)
ALIASES = {
"マイクロソフト": "Microsoft",
"MS": "Microsoft",
"Microsoft Corporation": "Microsoft",
}
# 段2: 埋め込み類似度で残りをマージ
from sentence_transformers import SentenceTransformer
model = SentenceTransformer("intfloat/multilingual-e5-large")
# コサイン類似度0.92以上を同一エンティティ候補にする
Neo4j LLM Knowledge Graph Builderには post-extraction clean-up が組み込まれていて、この処理を自動化してくれます。自前実装が面倒ならこちらを使うのも手です。
ただ、自動マージは「気付かないうちに別物が同一視される事故」も起こします。例えば「Apple(会社)」と「apple(果物)」がコサイン類似度0.93で1ノードに統合されたことがありました。マージ閾値を上げるか、ドメイン語(
Companyvs
Food)で先にクラスタを切るのが防御策です。
最終的に有効だったのは、辞書を「育てる」という発想でした。最初の20社で立ち上げて、運用しながら週次でレビュー会を回し、半年で200社まで増やす。一気に作ろうとするとレビューが追いつきません。辞書は機械学習モデルと同じで、データが集まるほど精度が上がります。
落とし穴2: 関係の方向性
「AがBを買収した」が「BがAを買収した」になる事故です。日本語は語順が緩いため、LLMが主語と目的語をひっくり返すことが体感で10〜15%発生します。
対策はプロンプトで方向を明示すること。
述語ACQUIREDは「買収企業 -[ACQUIRED]-> 被買収企業」と読んでください。 例: Microsoft -[ACQUIRED]-> GitHub (Microsoftが買収企業)
それでも10%ぐらいは間違えます。パターン3のSelf-correctionで「主語と目的語を入れ替えたほうが原文と合うか」を聞き直すと、ほぼ救えます。
スキーマ駆動(パターン2)を使うと、述語名そのものが方向を担うので事故率が下がります。
ACQUIREDではなく
acquirer / acquiredのような attribute 名にした OntoGPT スキーマが強いのは、この点でも理にかなっています。
落とし穴3: 矛盾の取り扱い
文書Aには「CEOは山田」、文書Bには「CEOは佐藤」と書いてある。LLMは両方とも素直にトリプル化します。
私が最初にやった失敗は「新しい日付の文書を優先」というルールを足したことでした。結果、古い契約書を参照したい場面で答えが出なくなりました。
正解はこうです。
- トリプルを 削除しない
- エッジ属性に
source_doc_id
とextracted_at
を必ず付ける - 矛盾は「矛盾として可視化」する。クエリ時にユーザーに見せる
矛盾の存在自体が、ビジネス上の有用なシグナルだったりします。隠してはいけません。
実際の案件では、契約書のKGに「契約金額」が3パターン同居していたことがありました。覚書で増額、別紙で減額、本文で初期金額。これを1つに正規化するとビジネス側が「そんなはずはない」と怒り出します。3つを並べて見せると「あ、それは別紙が間違ってます」と即答が出ました。矛盾は意思決定の入り口です。
まとめ
- KGの自動構築は3パターンに整理できる: Few-shot / スキーマ駆動 / マルチパス
- フェーズに応じて1→2→3と昇格させるのが安全
- 2026年はOntoGPT・LangChain LLMGraphTransformer・Neo4j LLM Graph Builder・Microsoft LazyGraphRAGが主要選択肢
- パターン選定とは別レイヤで、表記揺れ・方向性・矛盾の3つは必ず踏む
- 矛盾は隠さず可視化する。削除すると後で痛い目を見る
5,200ドキュメント案件はパターン2 + 落とし穴対策3点セットで、最終的に精度78%まで持ち上がりました。100点には届きませんが、人手レビューと組み合わせれば実用域です。半年運用したあとに測り直すと、辞書とスキーマが太っていって85%まで上がりました。KGは生き物です。
最後にコスト感の目安を置いておきます。1,000ドキュメント規模で、パターン1=$3、パターン2=$10前後、パターン3=$30〜$50。年間の運用ではこれに加えてレビュー工数とインフラ費が乗りますが、人手で組むよりはるかに安いのは間違いありません。1,300時間の人手作業と比べると、たとえパターン3で月次$2,000かかったとしても安いものです。
KGは作って終わるものではありません。運用しながら磨いていきます。最初から100点を狙わず、まずパターン1で動くものを見せましょう。動くものさえあれば、社内の協力者は集まります。