Semantic Search / Dynamic Cypher / Confidence
前提:工程7-1(完了)
I/O契約(JSONL)固定 + 正規化DoD
rowsは常にlist[object]confidenceは常にobjectrows>0の場合、各rowにanalysis_packが必ず付与される- 変更は追加のみ(削除/rename/型変更/required化禁止)
7-1 DoD確認 jq(あなたが実行してOKになったやつ)
jq -e '
def ok_rows: (.rows|type)=="array" and ((.rows|all(type=="object")));
def ok_conf: (.confidence|type)=="object";
def ok_ap: ((.rows|length)==0) or ((.rows[0].analysis_pack|type)=="object");
if ok_rows and ok_conf and ok_ap then
"OK DoD: rows=list[object], confidence=object, analysis_pack per-row when rows>0"
else
error("NG DoD")
end
' /tmp/responses_rank.check.jsonl
工程7-2:Semantic Search Targets(Chroma等)導入(LLMなしでOK)
目的
- 固定テンプレCypherの「前段」に semantic_search_targets を追加し、
- slots不足/曖昧なときに、候補(opp_id / rep_id / company_id等)を補完できるようにする
- 検索が弱い/空なら従来ルール(固定Cypher)へフォールバック(安全)
実装ポイント(設計)
- 追加のみで入れる(既存の pack/registry の固定Cypherを壊さない)
diagnostics.assist.semantic_searchに結果を記録(add-only)enabled,provider,query,hits,picked,elapsed_ms,reason
DoD(7-2)
- semantic_search が ON のとき:
diagnostics.assist.semantic_search.enabled=truehits>=0が入り、落ちても エラーにせず warnings に記録して続行
- OFF のとき:従来と同じ(出力契約・結果が変わらない)
実行コマンド(例:rank)
cd ~/DeciKG/decikg
python -m decikg.graph_query.run_jsonl \
--in ./requests_opportunity_pipeline.rank.jsonl \
--out /tmp/responses_rank.7_2.jsonl \
--progress --log-level INFO \
2>&1 | tee /tmp/run_rank.7_2.log
DoD確認 jq(7-1 + assist確認)
jq -e '
def ok_rows: (.rows|type)=="array" and ((.rows|all(type=="object")));
def ok_conf: (.confidence|type)=="object";
def ok_ap: ((.rows|length)==0) or ((.rows[0].analysis_pack|type)=="object");
def ok_assist:
(.diagnostics.assist.semantic_search? | type) == "object"
or true; # assistは optional(offでもOK)
if ok_rows and ok_conf and ok_ap and ok_assist then
"OK 7-2: contract ok (+assist optional)"
else
error("NG 7-2")
end
' /tmp/responses_rank.7_2.jsonl
工程7-3:Dynamic Cypher(制約付きText-to-Cypher)を “fallback専用” で導入
目的
- 既知
action_xmlid + query_keyは従来通り固定Cypher(最優先) - 以下の場合のみ “fallback” として動的生成を使う:
unknown_action_xmlid/unknown_query_key- 必須slots不足
- もしくは semantic_search しても補完できない
制約(安全ガード)
- dynamic生成は必ず:
MATCH/RETURNのみ(CREATE/DELETE/MERGE禁止)LIMIT強制timeout_ms強制- allowlist(ラベル/リレーション)以外は参照禁止
- 失敗時は
skippedorerrorで返し、契約は維持
DoD(7-3)
- dynamic生成が走ったら:
diagnostics.assist.dynamic_cypher.used=truediagnostics.assist.dynamic_cypher.reasonが入る(why fallback)
- 生成されたCypherの先頭が危険語を含まない(検査)
- 失敗しても runner は落ちず、1行=1レスポンスを維持
DoD確認 jq(fallbackが発生したときだけ)
jq -e '
if (.diagnostics.assist.dynamic_cypher.used? == true) then
"OK 7-3: dynamic_cypher used reason=" + (.diagnostics.assist.dynamic_cypher.reason|tostring)
else
"OK 7-3: dynamic_cypher not used"
end
' /tmp/responses_rank.7_3.jsonl
工程7-4:Confidence Scoring を 100/60系へ拡張(add-only)
目的
confidenceは objectのまま(7-1維持)- 0..1の
confidence.scoreを壊さずに、追加で 0..100 の評価を載せる
推奨スキーマ(add-only)
confidence.score: 0..1(既存)confidence.score100: 0..100(追加)confidence.badges: string[](追加)confidence.notes: string(追加)confidence.components: object(追加、根拠内訳)
DoD(7-4)
confidenceは常に objectconfidence.score100が存在する場合は 0..100 int に丸められているconfidence.badgesは list[string]
DoD確認 jq(score100の検査)
jq -e '
def ok_conf: (.confidence|type)=="object";
def ok_score100:
(.confidence.score100? == null)
or ((.confidence.score100|type)=="number" and (.confidence.score100>=0) and (.confidence.score100<=100));
def ok_badges:
(.confidence.badges? == null)
or ((.confidence.badges|type)=="array" and (.confidence.badges|all(type=="string")));
if ok_conf and ok_score100 and ok_badges then
"OK 7-4: confidence extended (add-only)"
else
error("NG 7-4")
end
' /tmp/responses_rank.7_4.jsonl
ontract: confidence extension (7-4) v0.1 (add-only)
Input(既存の期待)
response.confidence は無い場合がある
ある場合は object で、confidence.score(0..1 float)が入ることがある
Output(7-4で保証すること)
response.confidence は 常に object(無い場合は {})
confidence.score は 既存互換(触らない。無ければ追加もしない)
confidence.score100(追加)は以下条件でのみ付与:
confidence.score が number のとき
score100 = clamp(round(score*100), 0, 100)(int)
confidence.badges(追加)は以下条件でのみ付与:
付与するなら list[string](空配列OK)
confidence.notes(追加)は任意 string
confidence.components(追加)は任意 object
Recommended badges(最小)
fixed_spec / semantic_search_filled / dynamic_fallback / rows_nonempty / neo4j_ok
DoD jq(あなたのままでOK)
jq -e '
def ok_conf: (.confidence|type)=="object";
def ok_score100:
(.confidence.score100? == null)
or ((.confidence.score100|type)=="number" and (.confidence.score100>=0) and (.confidence.score100<=100));
def ok_badges:
(.confidence.badges? == null)
or ((.confidence.badges|type)=="array" and (.confidence.badges|all(type=="string")));
if ok_conf and ok_score100 and ok_badges then
"OK 7-4: confidence extended (add-only)"
else
error("NG 7-4")
end
' /tmp/responses_rank.7_4.jsonl
工程7-5:E2E(7-1〜7-4を一括で守る回帰DoD)
目的
- どの経路(固定Cypher / semantic補完 / dynamic fallback / postprocess)でも
- JSONL 1行=1レスポンス
- 7-1契約が崩れない
- assistやconfidence拡張が入っても破壊しない
一括DoD jq(最終)
jq -e '
def ok_rows: (.rows|type)=="array" and ((.rows|all(type=="object")));
def ok_conf: (.confidence|type)=="object";
def ok_ap: ((.rows|length)==0) or ((.rows[0].analysis_pack|type)=="object");
def ok_conf_ext:
(.confidence.score100? == null)
or ((.confidence.score100|type)=="number" and (.confidence.score100>=0) and (.confidence.score100<=100));
if ok_rows and ok_conf and ok_ap and ok_conf_ext then
"OK 7-5: contract+confidence-ext maintained"
else
error("NG 7-5")
end
' /tmp/responses_rank.final.jsonl
実行コマンド(あなたが欲しい “detail check” 版)
あなたの指定の形で、detail用のチェック実行を置いておきます(ログも保存):
cd ~/DeciKG/decikg
python -m decikg.graph_query.run_jsonl \
--in ./requests_opportunity_detail_gold3.jsonl \
--out /tmp/responses_detail.check.jsonl \
--progress --log-level INFO \
2>&1 | tee /tmp/run_detail.check.log
そして、最終DoD(7-1契約)確認:
jq -e '
def ok_rows: (.rows|type)=="array" and ((.rows|all(type=="object")));
def ok_conf: (.confidence|type)=="object";
def ok_ap: ((.rows|length)==0) or ((.rows[0].analysis_pack|type)=="object");
if ok_rows and ok_conf and ok_ap then
"OK DoD: rows=list[object], confidence=object, analysis_pack per-row when rows>0"
else
error("NG DoD")
end
' /tmp/responses_detail.check.jsonl
工程7-6:NL → Cypher生成 → 実行 → 説明(対話入力の完成)
ゴール(7-6の到達点)
1つの 自然言語質問(question) を入力すると、内部で:
- semantic search(関連ビュー/クエリ候補/制約を取得)
- 制約付き Cypher 生成(安全な範囲で text-to-cypher)
- Neo4j 実行(timeout/limit/エラー分類)
- 結果の説明(根拠+要約+confidence)
を行い、出力は GraphQueryResponse v0.1(追加のみ) を守る。
“残りの部品”一覧(7-6で実装するもの)
A. 入力の入口(質問を受ける契約)
- GraphQueryRequest に
questionを追加(add-only)- 例:
request.question: str(必須ではなく optional にして段階移行)
- 例:
- 既存の
action_xmlid/query_key/slotsが無い場合でも、questionから 候補の action_xmlid/query_key を推定できる導線を作る
ここを「別コマンド/別モジュール」にすると運用が崩れやすいので、最初は JSONL input でも question を扱える形が良いです。
B. semantic_search(候補を引く)
semantic_search_targets(例)- GraphQuery “packs” のメタ(action_xmlid, query_key, 説明文, required slots, shape)
- DomainGuide / view catalog(将来)
- 最初は軽量でOK:
- pack registry をテキスト化してベクトル化(Chroma or ローカルTF-IDFでも可)
- 出力は
diagnostics.assist.semantic_searchに集約(add-only)- 例:
used, hits_total, hits_kept, top_hits[{action_xmlid, query_key, score}]
- 例:
C. dynamic_cypher(制約付き生成)
ここが7-6の核です。
- “候補が決まった場合”:
- 既存の テンプレCypher+slots 方式を優先(安定)
- “候補が決まらない/slots不足”の場合だけ:
- LLMでCypherを生成(ただし必ず制約/検証を通す)
最低限の制約(MUST):
- READ ONLY(CREATE/MERGE/DELETE/SET/CALL dbms… を禁止)
- 単一ステートメント
- 必ず LIMIT(options.limit を上限に)
- 許可ラベル/許可リレーション/許可プロパティの allowlist
- パラメータ化優先(文字列直書きしない)
生成の結果は:
diagnostics.cypherに入る(include_cypher時)diagnostics.assist.dynamic_cypherに「使った/使わない/理由/elapsed_ms」
D. slot_filling(必要スロットを埋める)
“質問→実行”で破綻しがちなのがここ。
- packが要求する
required_slotsがあるのに埋まらない場合:- 7-6では 2段階にするのが現実的
- extract(質問から company_id / opp_id / period 等を正規表現 + 辞書で拾う)
- 足りなければ
missing_required_slotsで返す(対話UIなら追加質問につなぐ)
- 7-6では 2段階にするのが現実的
LLMに全部やらせるより、まず deterministic に拾ってから不足だけ聞く方が、回帰が安定します。
E. 実行(Neo4jClientのラップ強化)
すでに neo4j_client.py はあるので、7-6はここを “プロダクト仕様” に寄せます。
- timeout/limit は options.timeout_ms / options.limit を最優先
- Neo4j例外を reason_code に分類
neo4j_error/timeout/invalid_paramsなど
- 実行結果 row_count を
diagnostics.row_countに - 必要なら
EXPLAIN/PROFILEは debug時だけ(通常は不要)
F. 説明生成(explanation)
説明は add-only で、出す場所を決めます。
推奨:
- 各 row に
analysis_pack.explanation(string) - もしくは top-level
analysis_packを正式フィールド化(今はdynamic注入もあるので整理)
説明の最低仕様:
- 何を返したか(要約)
- 根拠(countsや代表的なevidence)
- 注意(欠損/推定/空ヒット)
- confidence と整合する(lowなのに断定しない)
G. confidence_scoring(100/60等の拡張)
現状は confidence: {level, score(0..1)} があるので、追加で:
confidence.score_100(0..100)を追加(add-only)confidence.notes(list[str])追加- ルール:
- evidence量(counts)+ 生成経路(テンプレかLLMか)+ missing補正 で決める
7-6のDoD(回帰可能な形)
入力(例)
JSONLで question を渡せる:
{"request_id":"q1","question":"直近30日でメール反応が多い案件を上位10件","params":{"now":"2026-02-10"}}
出力(必須)
- 7-1の形は絶対に崩さない(rows/confidence/analysis_pack)
diagnostics.assist.semantic_searchが出る(少なくとも used/reason)diagnostics.cypherが(include_cypher=trueのとき)出るanalysis_pack.explanationが rows>0 のとき全rowに入る
成功判定(例)
status=okで rows が返り、explanation が付く- 候補不明/不足なら
status=error or skipped+missing_required_slotsで落ち方が安定
実装タスクの切り方(7-6-1〜7-6-4)
7-6-4: explanation + confidence(100点換算) + E2E(質問入力)追加
7-6-1: requestに question 追加 + registryで経路分岐(テンプレ優先)
7-6-2: semantic search(pack registryのベクトル化)+ assist診断
7-6-3: dynamic cypher generator + guard/allowlist + reason_code分類
コメントを残す