下のほうが具体性あり
1) 固有名詞(案件名など)を何とかする:DeciKG側で「Entity Resolver」を持つ
「自然文→パラメータ化」をDeciKGでやるなら、LLMに いきなりCypher全文を書かせる より、2段に分けるのが安定です。
1-A. まず “固有名詞候補” を抽出(LLM)
例:「PoCという名前が付く案件」「担当者が佐藤の案件」から
- entity_type: opportunity / partner / rep
- mention: “PoC” / “佐藤”
- match: exact / contains / startswith
- company_id(あれば)・期間(now/30日)・limit
1-B. 次に “候補解決”(非LLM)
LLMが出した mention をもとに、DeciKG内部で 候補検索してIDに落とします。
- OpportunityResolver:
Opportunity.nameを contains で引いてopp_id候補リスト - RepResolver:
User/Rep.nameを contains で引いてrep_id候補 - PartnerResolver:
Partner.nameを contains で引いてpartner_id候補
ここは ベクトル検索でも良いし、単純containsでもOK(まずは軽い実装で十分)。
**重要なのは「候補が複数なら、その事実を返す」**ことです(勝手に決めない)。
1-C. 最後に Cypher を組み立てる(テンプレ+最小LLM)
IDが決まれば、Cypherはテンプレで安定します。
- 候補が1件に絞れた →
WHERE o.opp_id=$opp_idのように確定 - 候補が複数 →
WHERE o.opp_id IN $opp_idsで実行し、結果と一緒に「曖昧だった」情報も返す
2) nlq-dev が分析できるように:DeciKGが「列定義メタ」を返す
おっしゃる通り、nlq-dev は 列の意味を知らないと分析が崩れます。
でも、nlq-dev のプロンプトに “全DB定義” を入れる必要はありません。
結論:DeciKGが返すレスポンスに「最小限のデータ定義」を同梱する
nlq-dev はそれをそのままプロンプトに入れて分析します。
おすすめはこういう形です(add-onlyで今の契約にも載せやすい):
diagnostics.cypher/diagnostics.params(これはもうOKになった)diagnostics.schema_hint(追加)primary_entity: “Opportunity”columns:{name: "...", type: "...", meaning: "...", unit: "...", grain: "..."}joins_used: もしJOINしたなら “何と何” だけ(on句は不要)
analysis_pack(各rowの explanation は既に入れてるので、ここに “集計の意味” も入れる)
例(イメージ):
email_event_count: 「直近30日(params.now基準)で関連EmailEventの件数」score: 「signal3 + email2 + phrase1 + outcome1 の加重スコア(仕様v0.1)」
これだけで nlq-dev は
- 「上位5件の理由」
- 「スコアの内訳」
- 「次に確認すべき案件」
を自然に語れます。
専門用語が多い問題
→ 定義メタに 日本語の意味(meaning_ja) を入れて解決します。
nlq-dev の分析は“その meaning_ja を引用しながら”やればいい。
3) “DeciKGにLLMを入れる”ときの境界線(安全でブレない設計)
あなたの方針だと、LLMはDeciKGの中でこう使うのが一番安定します。
LLMがやること
- 質問文からの 意図分類(rank/detail/search…)
- 固有名詞の抽出(案件名・会社名・担当者名)
- 期間・limit・フィルタの推定(なければデフォルト)
- 「必要なresolver」を選ぶ(OpportunityResolver/RepResolver…)
LLMにやらせないこと(やらせると壊れやすい)
- 生Cypherを自由作文させる(いまのdynamic fallbackは便利だが、まずは “最小範囲” に留める)
- 勝手に定義を作る(列の意味は“仕様として固定”し、返却メタはテンプレから生成)
4) いまの実装(dynamic fallback)をどう位置づけるか
現状の dynamic fallback は「contains検索」みたいな便利レイヤとしては良いです。
ただ、nlq-dev分析に繋げるなら、次のどちらかに寄せるのが良いです:
- A: dynamicは“検索系だけ”に限定(name contains、担当者 contains、など)
- B: dynamicでも必ず
schema_hintを返す(列の意味を付ける)
いま起きていた「cypher/paramsがassist側にだけある問題」は直ったので、次は schema_hintの整備がボトルネックになります。
具体的に「どんな定義データを返すべきか」最小セット
nlq-devが分析できる最低限はこれです:
- result_grain(粒度)
- “1行=案件(Opportunity)” なのか
- “1行=メール” なのか
- “1行=担当者×期間” なのか
- columnsの意味(日本語)
- meaning_ja(短文でOK)
- type(int/string/date)
- filters(この結果に適用済の制約:company_id / 直近30日 / limit)
- score/ランキングロジック(ある場合だけ)
- スコア式、重み、並び順
これを diagnostics.schema_hint か analysis_pack.meta に入れて返すと、nlq-devの分析が一気に楽になります。
意味はありました。ただ、**「どこで何をやるか」**の切り方が今の方針と少しズレていただけです。
Cypher_then_sql が“効く”場面
Cypher_then_sql は本質的にこれです:
- (1) Cypherで対象集合を確定する(IDリスト・サブグラフ・関係の抽出)
- (2) その結果(IDやキー)を使ってSQLで集計/精算する(数値集計、期間集計、重いJOIN、整形)
つまり 「グラフで絞り込み」「RDBで集計」 の二段階が必要なときに強い。
例:
- 「メール反応が多い案件」
→ Cypherでopp_idを“メールイベントとの関係”で拾う(ただの名前検索じゃない)
→ SQLでfact_email_eventを期間条件で集計してランキング - 「担当者Aの案件で、直近30日の反応率が高い順」
→ Cypherで担当者→案件の関係からopp_idを確定
→ SQLでメール/アウトカム/売上などを集計
今回 “意味が薄く見えた” 理由
あなたが今やっている DeciKG の動的フォールバックは多くが
Opportunity.name contains $qみたいな 単体ノード検索
になっていて、ここだと SQLに渡すべき “グラフ由来の価値” がほぼ出ません。
(SQLだけでも同じ絞り込みができるから)
さらに、あなたの新方針は
- DeciKGはCypherと結果だけ返す
- 分析はnlq-dev
なので、Cypher_then_sql をやるなら DeciKG側がSQLまで叩く責務を持つ必要が出てきます。
(そこが方針と衝突して “意味がない” 体感になった)
結論:Cypher_then_sql は「捨て」ではなく「出番の再定義」
今の設計だと、Cypher_then_sql はこう整理すると生きます:
A) 現方針(DeciKGはCypher実行まで)を優先するなら
- route: CYPHER_ONLY / CYPHER_THEN_CYPHER(=追加Cypher) に寄せる
- SQL集計は nlq-dev(or Dev-Portal)でやる
- その代わり DeciKGが返すものに
opp_ids(確定集合)scope_hint(粒度/期間/意味)
を入れて、nlq-devがSQLを作りやすくする
B) 「集計はSQLの方が正しい/速い」領域を残したいなら
- Cypher_then_sql は “グラフで集合確定 → SQLで集計” の専用ルートとして残す
- ただしその場合は SQL実行主体をどこに置くかを決める必要がある
- DeciKGがSQLも叩く(責務が増える)
- nlq-devがSQLを叩く(DeciKGは集合とヒントだけ返す)
- Dev-PortalがSQLを叩く(nlq-devがオーケストレーション)
あなたの今の方針だと、自然なのは 「nlq-devがSQLを叩く」 です。
どういうときに“本当に必要”か(判断基準)
Cypher_then_sql を使う価値があるのは、Cypherが 単なるcontains検索を超えて 次をやるときです:
- 関係を辿って集合を作る(担当者→案件→メール、顧客→案件→アウトカム等)
- パス/近傍/グルーピングで集合を作る(イベント系列、類似案件の近傍)
- “RDBにはない関係” を使って対象を絞る(ここが一番大きい)
逆に、Opportunity 単体の属性検索(name contains等)だけなら Cypher_then_sql は薄いです。
あなたの現状だと、
- 固有名詞解決(案件名/担当者名)→ opp_id確定 は DeciKGが強い
- 集計(直近30日で多い、率、ランキング) は SQLが強い
なので、Cypher_then_sql を「無意味」と結論づけるより、
Cypher_then_sql は “分析のための集合確定ルート” としては有用。
ただし SQL実行主体を DeciKGに置かないなら、実体は「CypherでIDs返してnlq-devがSQL」になる。
…という整理が一番しっくり来ます。
もし「Cypher_then_sql を残すならどんなI/Fにすべきか」を次に決めたいなら、
あなたの route enum(SQL_ONLY / CYPHER_ONLY / CYPHER_THEN_SQL / …)の現行定義を貼ってください。そこに合わせて、DeciKG→nlq-dev で返すべき ids + schema_hint + sql_seed の最小契約を提案します。
その流れでいけます。ポイントは「1回の質問→1回で終わらない」前提のパイプを、最初から“会話ではなくワークフロー”として固定することです。
以下の形にすると、いまの方針(DeciKGはCypher実行まで/分析はnlq-dev)と矛盾しません。
目標のパイプ(LLMが自分で次の質問を作って回す)
ステージ0: User Question(自然文)
- 例「直近30日でメール反応が多い案件を上位5件。理由も」
ステージ1: DeciKG(NL→Cypher→実行→“材料”返却)
DeciKGの責務:
- Cypherを作る(LLMでもルールでもOK)
- Neo4jで実行する
- 結果行 + “説明に必要な材料” を返す
- 次のデータ要求(follow-ups) を提案する(ここが重要)
返すべきもの(追加のみでOK):
diagnostics.cypher/diagnostics.params(あなたが今直したやつ)rows[]analysis_pack(トップレベルでも各rowでも)narrative_draft(まずは箇条書きでもいい)followup_questions[](nlq-devに投げる次の質問の候補)followup_requests[](構造化:目的・必要データ・想定SQL側など)
ここで「こんなデータもあったら良いな」を 文章ではなく“要求”として構造化して返す のが肝です。
ステージ2: nlq-dev(分析→追加質問生成→Dev-Portalへ)
nlq-devの責務:
- DeciKGの結果を読んで「分析文」を仕上げる(LLM)
followup_questionsを優先度付けし、必要なら整形して Dev-Portal に投げる- Dev-Portal のSQL結果を受けて、最終文章をアップデートする
nlq-devが持つべき“最低限のプロンプト材料”は2種類だけ:
- Dev-Portal側のデータ辞書(domain guide / view catalog / KPI定義)
- DeciKGが返した“今回の文脈”(cypher、rows、analysis_pack)
「専門用語ばかりになる」問題はここで潰せます。
nlq-devは “専門用語→一般語の言い換え辞書” を持つ(固定の辞書でOK、LLMに任せすぎない)。
ステージ3: Dev-Portal(SQL生成→実行→根拠データ返却)
Dev-Portalの責務:
- 質問(nlq-devが作ったfollow-up)を受け、SQL生成・検証・実行
- 結果(rows)+ diagnostics(sql、guards、citations)返却
ステージ4: nlq-dev(統合・最終出力)
nlq-devで最終的に返す:
final_narrative(文章)evidence(DeciKG/Dev-Portalの根拠リンク・行数・クエリ)next_questions(次のアクション)
これを成立させる “最小の契約” 追加(おすすめ)
今すぐ作るなら、DeciKGレスポンスにこれを追加するとパイプが回ります。
A) DeciKG → nlq-dev 用の follow-up 形式(超小)
analysis_pack.followups を追加:
{
"analysis_pack": {
"followups": [
{
"target": "dev_portal",
"question": "上位5件の案件について、直近30日のメール返信率(返信/送信)を出して",
"priority": 80,
"why": "“反応が多い”が送信数の多さ由来か、返信率が高いのかを判別するため",
"inputs": {
"opp_ids": ["op0124", "op0173", "op0066", "op0095", "op0250"],
"window_days": 30,
"now": "2026-02-12"
}
}
]
}
}
ここで question は nlq-devがそのままDev-Portalに投げられる文章にしておくのがコツです。
(nlq-dev側で頑張りすぎない)
B) “専門用語問題” の対策は nlq-dev の固定プロンプトで吸収
opp_id/signal_count/phrase_statなどを、人間向けの語にマップする辞書を固定化- 文章生成時は「表示名(やさしい語)」「内部名(括弧で)」の併記を基本にする
実装順(迷わない最短)
- DeciKG: まず
analysis_pack.followups[]だけ追加(ルール生成でOK。LLM不要)- 例:rank系なら「返信率」「ステージ別」「担当者別」「顧客規模別」などテンプレ化
- nlq-dev: followups をそのまま Dev-Portal に投げる“ループ”を作る(LLM無しでOK)
- nlq-dev: 最後に文章生成(LLM)を入れる
- 入力は「DeciKG結果+Dev-Portal結果+辞書」
- DeciKG: 余力が出たら、固有名詞解決やCypher生成にLLMを入れる
重要な割り切り(成功しやすい)
- 「LLMでCypher作って分析して文章…」を 最初から全部LLMに任せない
- まずは
- DeciKG: 結果+followupテンプレ
- nlq-dev: followup実行ループ
だけで“パイプ”を完成させる
- その後に「followupの自動生成の質」や「文章の質」を上げる
次、パイプ用に nlq-dev側の“followup実行ループ”のI/Fを固めたいです。
あなたの nlq-dev のコントラクト(/nlq/plan /nlq/execute /nlq/analysis)に合わせて、
AnalysisResponseにfollowups[]をどう乗せるか- Dev-Portal呼び出し結果をどこに入れて最終文章に反映するか
を、追加のみで設計します。
今の nlq-dev の ExecuteResponse v0.2 のフィールド名(route/stub_used/warnings/errors/timings/providers/sql_result/…)に合わせたいので、そのレスポンスの雛形JSON(空でもOK)を1つ貼ってください。こちらで“追加だけ”で流れる形に落とします。
役割分担(あなたの定義のまま固定)
DeciKG(ここで止める)
- 入力:question-only(自然文だけでも可)
- 出力:
- cypher(実行したもの)
- params
- rows(結果データ)
- result_schema(データ定義) ←これが重要
- provenance(根拠):graphのどのノード/リレーション/期間で取ったか、など(機械向け)
DeciKGは文章を書かない。ランキング理由も書かない。「データとその意味(定義)」だけ返す。
nlq-dev(分析だけ)
- DeciKGの
rows + result_schema + provenance + cypherを入力に - 分析文・要約・示唆・注意点 を生成
- 追加データが必要になったら、人が質問を作る(ここもあなたの定義)
Dev-Portal(追加データ取得だけ)
- 人が作った質問を受けてSQL結果を返す
- nlq-devがそれを再分析(必要なら)
「専門用語ばかりで解析できない」問題の解き方
nlq-devに“専門用語の意味”を全部プロンプトで覚えさせるのは重いので、DeciKGが返す result_schema をソース・オブ・トゥルースにします。
DeciKGが返すべき最小メタ(result_schema)
rowsの列について、nlq-devが文章化できる最低限:
field: 列名(opp_id, name, score…)label_ja: 表示名(案件ID、案件名、スコア)description_ja: 意味(何を表すか)unit: 件/回/%/円…calc: 算出定義(例:score=signal3+email2+phrase1+outcome1)grain: 行の粒度(例:1行=1案件)dimensions/measures: 次元/指標
これがあると nlq-dev は「内部名」を知らなくても分析できます。
具体例(DeciKGレスポンスに足すだけ:add-only)
今のレスポンスに、analysis_pack を“文章”ではなく schema_pack として使うのが一番自然です(既存互換も壊さない)。
例(イメージ):
{
"status":"ok",
"diagnostics": {
"cypher":"MATCH ...",
"params": {"company_id":"c001","limit":5}
},
"rows":[
{"opp_id":"op0124","name":"...", "score": 12, "email_event_count": 5}
],
"analysis_pack": {
"schema_pack": {
"entity": "Opportunity",
"grain": "one_row_per_opportunity",
"fields":[
{"field":"opp_id","label_ja":"案件ID","description_ja":"案件の一意ID","type":"string","role":"dimension"},
{"field":"score","label_ja":"反応スコア","description_ja":"反応の多さを示す合成スコア","type":"number","role":"measure",
"calc":"signal_count*3 + email_event_count*2 + phrase_stat_count*1 + outcome_count*1"}
]
}
}
}
nlq-devはこれだけで「スコアとは何か」を説明できます。
いまの実装状況との整合
あなたが直した通り、dynamic fallback の場合でも
diagnostics.cypherdiagnostics.params
が埋まるようになったので、nlq-dev側は “Cypherと結果” を確実に受け取れる状態になっています。
あとは result_schema を返すだけで、役割分担が完成します。
次にやるべき最短タスク
- DeciKG:
analysis_pack.schema_packを追加(固定テンプレでOK、LLM不要)- action_xmlidごとに schema_pack を用意(opportunity_pipeline rank/detail 等)
- dynamic fallback(部分一致検索)時は、最低限
grainとfields(opp_id/nameなど)だけでも返す
- nlq-dev:schema_pack をプロンプトに差し込む
- 「このfields定義に基づいて、日本語で分析文を書け」
- 専門用語はschema_packのlabel_ja/description_jaを優先して使う
必要なら、次の返信でこちらから DeciKG側の schema_pack テンプレ(opportunity_pipeline rank / dynamic search) を“追加のみ”で書ける形で提示します。今のDeciKGの action_xmlid(opportunity_pipeline / opportunity_detail など)だけ教えてくれれば、それに合わせて出します。
デバックしやすいようにDeciKGにLLM仕込んでパラメータ化してそこからCypher作りたいんですよね。
了解。**DeciKGの中で「自然文→パラメータ化→(テンプレ or 生成)Cypher」**に寄せるのは、デバッグ性かなり上がります。やるなら “LLMはCypherを直接書かない” にして、まず Params(構造化)だけを吐かせるのが一番安定します(ログも差分も取りやすい)。
目標の形(デバッグ最優先)
DeciKG内部でこう分けます:
- LLM: question → intent + params(JSON)
- deterministic: params → cypher(テンプレ組み立て)
- execute → rows
- 返す:cypher / params / rows / schema_pack
LLMが壊れても「paramsが壊れた」のか「テンプレが壊れた」のかが一発で分かります。
1) LLMが出す “paramsスキーマ” を固定する
まずは最小で十分(今のユースケースに直結):
共通(全クエリ)
spec_key: どのクエリ種類か(例:opportunity.search_by_name_contains/opportunity.rank_by_email_reaction_last30d)company_id(必須に寄せたいならここで)limit(default 5)now(YYYY-MM-DD。無ければ今日を埋める or require)filters(配列)
filters(例)
{ "field": "name", "op": "contains", "value": "PoC" }{ "field": "rep_name", "op": "contains", "value": "佐藤" }{ "field": "days", "op": "lte", "value": 30 }
ここまでを LLMが確実に JSON で返すようにする。
2) Cypherは “テンプレのみ” にする(LLMは書かない)
たとえば search_by_name_contains は固定:
MATCH (n:Opportunity {company_id: $company_id})
WHERE toLower(n.name) CONTAINS toLower($q)
RETURN n.opp_id AS opp_id, n.name AS name, n.partner_id AS partner_id, n.rep_id AS rep_id
LIMIT $limit
LLMが返した params から:
q = filters[name contains].valuelimit = params.limitcompany_id = slots.company_id or params.company_id
これでデバッグは “JSONだけ” 見ればOKになります。
3) デバッグ用の診断出力を固定(add-only)
あなたが今整備した diagnostics.cypher/params に加えて、LLMを入れるならここを追加するとめちゃ見やすいです:
diagnostics.assist.param_llm = { used, model, prompt_hash, parsed_json, errors[] }
そして 必ず
diagnostics.params← “最終的にテンプレに渡したparams”diagnostics.assist.param_llm.parsed_json← “LLMの生出力(正規化前)”
を両方残す。
「LLMが言ったこと」と「最終採用」を分離して保存、がデバッグの肝です。
4) “固有名詞が難しい” 問題の解き方(DeciKG内で完結)
固有名詞は LLMに当てさせず、手堅くやるのが良いです:
- LLMは「検索したい対象は 案件名」「キーは PoC」みたいな 意図だけ出す
- 候補の正規化は graph側でやる
toLower CONTAINS- 可能なら
name_norm(ひらがな/カナ/英数正規化)を事前生成して一致率UP
「佐藤」みたいに人名が絡むなら、LLMは rep_name contains "佐藤" までで止める。
Cypher側で (:Opportunity)-[:OWNED_BY]->(:SalesRep) とか、あなたのデータモデルに合わせてテンプレ化。
5) nlq-dev連携は “Cypher + params + schema_pack” だけで足りる
nlq-devは分析担当なので、DeciKGが返すべきは:
diagnostics.cypher(必須)diagnostics.params(必須)rowsanalysis_pack.schema_pack(rowsの意味定義)
これで nlq-dev は専門用語を知らなくても文章化できます。
実装順(最短で壊れにくい)
- DeciKGに param_llm を1本入れる(spec_key + q + limit だけ)
- Cypherは今の dynamic fallback テンプレをそのまま使う(LLMは書かない)
- diagnosticsに
assist.param_llm.parsed_jsonparams(最終採用)
を出す
- その後、spec_keyを増やす(rank系/担当者系/期間系)
もし次に進めるなら、こちらから
param_llmの 固定JSONスキーマ- プロンプト(短く・失敗しにくい)
dynamic_cypherを “params駆動テンプレ” に置き換えるための関数の追加案(最小差分)
を、修正部分だけで出します。
いまDeciKGで「rank/detail/search」みたいに 最初に対応したいspec_key候補を3つだけ挙げてくれれば、それに合わせて作ります。
コメントを残す