- /nlq/plan
- LLM①で QueryPlan(JSON) を生成
route(SQL/Cypher/Hybrid/Parallel)とparams(sql_params/decikg_params)を候補に埋める- ルーティング判断は Mermaid/概念モデルに載っている対象をDeciKG優先を反映
- /nlq/execute
- 候補の
routeに応じて Route Executor が外部呼び出し- SqlExecutor:Dev-Portal に params 渡し(SQL文字列はnlq-devで作らない)
- CypherExecutor:DeciKG に params 渡し(Cypher文字列はnlq-devで作らない)
- Hybrid/Parallel:連携・統合(中間結果を constraints/candidates に差し込む)
- 履歴
- SQLだけでなく route / params / 外部呼び出し結果要約 / 失敗情報 を保存できるようにする
工程 0:現状把握と“壊さない”土台作り(最初にやる)
目的:追加開発で既存I/F・既存UIが壊れないように、現状の動線を固定する。
作業
- 現行の
/nlq/plan/nlq/executeの入出力をコードから確認(Pydantic / Router) - stubモード(
services/nlq_stub.py)の使い所を整理 - “互換維持ルール”を README/Docs に明記
想定変更ファイル
docs/NLQ_API_CONTRACT.md(または既存の契約doc)※今後の単一ソースservices/config.py(ENV整理が必要なら)
DoD
docker compose ... upで API 起動/nlq/planが既存レスポンスを返せる(スタブでもOK)
工程 1:Planスキーマ拡張(QueryPlan v1.1 を“追加フィールド”で入れる)
目的:既存の SqlCandidate.sql を残しつつ、route/params を載せられるようにする。
設計方針
- 既存互換:
SqlCandidate.sql: strは残す(DeciKG候補でもダミー可) - 追加:
candidate_kind,route,sql_params,decikg_params,mergeなどを追加
想定変更ファイル
schemas/nlq.py(SqlCandidate / PlanResponse 拡張)- (必要なら)
schemas/execute.py(ExecuteResponse拡張の下準備)
新規ファイル候補
schemas/query_plan.py(QueryPlan関連を分離したい場合)Route,CandidateKind,SqlParams,DeciKGParams,MergeStrategyなど
DoD
- OpenAPI/Swagger 上で plan のレスポンスに追加フィールドが出る
- 既存フロントが
sqlだけ読んでも落ちない
工程 2:Routing 判定(SQL/Cypher/両方)を“仕様化して実装”
目的:あなたの方針(Mermaid/概念モデルに載る対象はDeciKG優先)を、nlq-dev内部の決定ロジックとして固定する。
実装方針(おすすめ)
- ルーティングは2段階:
- Fast heuristic(非LLM):キーワード・既知概念ヒットで初期routeを当てる
- LLM①:QueryPlan生成時に route を最終確定(ただし policy に従う)
- Mermaid/概念モデルは「プロンプトテキストボックスで更新」方式を採用するなら、nlq-dev側は
config/mermaid_context.md(or DB)を読み込み- その内容を LLM① の system/context に差し込む
想定変更ファイル
services/nlq_plan.py(route判定+LLM①出力をQueryPlanに固定)services/llm_client.py(JSON schema出力・バリデーション失敗時のリトライ方針)services/schema_service.py(ここが“どのスキーマ参照か”を整理:Dev-Portalメタ/業務DB/DeciKGのどれ?)
新規ファイル候補
services/routing_policy.pydecikg_first_concepts()(Mermaid由来)decide_route(question, hints, mermaid_context) -> Route
config/mermaid_context.md(運用ファイル)services/prompt_templates/plan_system.md(プロンプト分離)
DoD
- “構造質問”で
route=CYPHER_ONLY(またはCYPHER_THEN_SQL)を返せる - “純粋な集計”で
route=SQL_ONLYを返せる - どちらも失敗時は stub にフォールバックできる
工程 3:Route Executor の骨格(実行を分岐・薄いRouterにする)
目的:実行処理を routers/nlq_execute.py から分離し、route別に外部呼び出しできるようにする。
route=SQL_ONLY:SQLをDBに投げて結果を返せる
route=CYPHER_ONLY:DeciKGに投げて「構造回答」を返せる(DB不要でもOK)
route=CYPHER_THEN_SQL:DeciKGでエンティティ解決 → SQL生成 → DB実行 → 結果返却
失敗時:stub有効なら stub 結果で返せる(落ちない)
想定アーキ
RouteExecutor.execute(plan_candidate, options) -> ExecuteResultSqlExecutor(Dev-Portalへ params)CypherExecutor(DeciKGへ params)HybridExecutor(CYPHER_THEN_SQL / SQL_THEN_CYPHER)ParallelExecutor(同時実行→merge)
execute のレスポンススキーマ v1.1(planと同じ思想:追加フィールドで非破壊)
services/nlq_execute.py を作って、SQL_ONLY を確実に動かす
DeciKG は最初は ダミー実装(cypher文字列 or intent結果を返すだけ)でCYPHER_ONLYを通す
最後に CYPHER_THEN_SQL(DeciKG→SQL)をつなぐ
想定変更ファイル
routers/nlq_execute.py(薄く:RouteExecutor呼ぶだけ)services/sql_guard.py/services/sql_normalize.py- “SQL_ONLY と HybridのSQLフェーズだけ適用”に変更(常時適用をやめる)
新規ファイル候補
services/executors/__init__.pyservices/executors/route_executor.pyservices/executors/sql_executor.pyservices/executors/cypher_executor.pyservices/executors/hybrid_executor.pyservices/executors/parallel_executor.pyservices/merge_service.py(結果統合ロジックを分離したい場合)
DoD
route=SQL_ONLY:SQLをDBに投げて結果を返せるroute=CYPHER_ONLY:DeciKGに投げて「構造回答」を返せる(DB不要でもOK)route=CYPHER_THEN_SQL:DeciKGでエンティティ解決 → SQL生成 → DB実行 → 結果返却- 失敗時:stub有効なら stub 結果で返せる(落ちない)
工程 4:外部連携クライアントの確定(Dev-Portal / DeciKG)
目的:nlq-dev が「文(SQL/Cypher)を作らずに params を渡す」前提で、外部I/Fを固定する。
4-A) Dev-Portal 側(SqlExecutor)
- nlq-dev → Dev-Portal へ渡すのは “SQL” ではなく “sql_params”
- Dev-Portal 側で SQL生成+安全実行(あるいは既存の /ask/sql を利用)
想定変更ファイル
services/...(Dev-Portal呼び出しが既にあるならそこを整理)
新規ファイル候補
services/dev_portal_client.pyhealth()execute_sql_params(sql_params, max_rows, dry_run) -> tabular_result
schemas/dev_portal.py(Dev-Portalレスポンスの型を最低限定義)
4-B) DeciKG 側(CypherExecutor)
- nlq-dev → DeciKG へ渡すのは “cypher” ではなく “decikg_params”
- 戻りは graph_result(nodes/edges/evidence/summary など)
想定変更ファイル
services/graph_client.py(Neo4j直結ではなく “DeciKG API client” に寄せる)
新規ファイル候補
services/decikg_client.py(graph_client を置換するなら)health()query(decikg_params) -> graph_resultconcept_lookup(text) -> candidates(必要なら)
DoD
- ENV(例)
DEVPORTAL_URL,DEVPORTAL_API_KEY(必要なら)DECIGK_URL,DECIGK_API_KEY(必要なら)
- 疎通確認用のヘルスチェックが通る(HTTP 200 相当)
工程 5:Executeレスポンスの拡張(sql_result / graph_result / merged / narration)
目的:SQLしか返せない前提をやめ、DeciKG結果や統合結果を返せるようにする。
互換の落とし所
- 既存の
ExecuteResponse.columns/rows/sqlは保持(互換) - 追加で
graphmergedexecution_kindrouteを載せる
想定変更ファイル
schemas/execute.py(ExecuteResponse拡張)routers/nlq_execute.py(レスポンス構築)services/analysis_deep.py(分析入力が SQLだけ前提なら拡張)
新規ファイル候補
schemas/graph_result.pyschemas/merge_result.py
DoD
- SQLだけでも従来通り返る
- DeciKGだけでも
graphが返る(columns/rowsは空でもOK) - Hybrid/Parallelでも
mergedに統合要約が入る
工程 6:履歴と成果物(history/artifacts)の拡張
目的:route/params/外部結果要約/失敗を追跡できるようにし、後で改善に使えるログを残す。
想定変更ファイル
services/history_service.pyschemas/history.pysql/040_nlq_history.sqlsql/060_nlq_history_artifacts.sql(必要なら)routers/history.py
設計メモ
- historyに保存したい最低限:
route,candidate_kindsql_params,decikg_params(JSON)execution_kind- 外部呼び出しの
request_id/timings_ms/error_summary
- 失敗時も記録(片系失敗の partial success を残す)
DoD
- /nlq/history で route と実行種別が追える
- “なぜDeciKGになったか”が後から説明できる(mermaidヒット等をメモとして残す)
工程 7:ドキュメント更新(契約・運用・環境)
目的:オフショア/フロントが迷わない “固定文書” を揃える。
想定変更ファイル
docs/nlq-dev-portal-integration.md(既に更新済み方針に合わせる)docs/NLQ_API_CONTRACT.md(今回の v0.2 拡張内容を反映)README.md(起動方法・env・composeの正)
新規ファイル候補
docs/decikg-integration.md(DeciKG params/レスポンス例)config/mermaid_context.md(運用)
DoD
- “どこを直せば会社差分が入るか(Mermaid更新箇所)”が明確
- “nlq-devはSQL/Cypher文字列を作らない”が明文化されている
修正提案(あなたの箇条書きへの“微修正”)
- ✅ A) Planはその通り。ただし API互換のため
sqlは残す(ダミー可) - ✅ B) Executor再編はその通り。ただし **graph_client は “Neo4j直結”ではなく “DeciKG API client”**に寄せる
- ✅ C) 履歴はその通り。特に params(JSON)とrouteの永続化が重要
- ✅ sql_guard/sql_normalize は SQLフェーズ限定適用(常時適用しない)でOK
変更ファイル一覧(候補まとめ)
変更になりやすい
routers/nlq_plan.pyrouters/nlq_execute.pyservices/nlq_plan.pyservices/llm_client.pyservices/graph_client.py(DeciKG client 化)services/sql_guard.py,services/sql_normalize.pyservices/history_service.pyschemas/nlq.py,schemas/execute.py,schemas/history.pysql/040_nlq_history.sql,sql/060_nlq_history_artifacts.sqldocs/*,README.md
新規になりやすい
services/executors/*(route executor一式)services/dev_portal_client.pyservices/decikg_client.py(orgraph_client.pyを置換)services/routing_policy.pyschemas/query_plan.py/schemas/graph_result.py/schemas/merge_result.pyconfig/mermaid_context.md/services/prompt_templates/*
次の一手としては、工程0の「現行の nlq_plan / nlq_execute の入出力(Pydantic定義)」が分かると、工程1〜3のスキーマと実装差分を一気に確定できます。
もし schemas/nlq.py と schemas/execute.py の現状(中身)を貼れるなら、**“破壊せずに足す差分”**で具体的なフィールド名まで揃えたタスクリストに落とし込みます。
DeciKGへの仮プロンプト
Router Prompt v0(仮プロンプト案:そのまま使える形)
使い方の意図(このプロンプトで“出し分け”が起きるポイント)
- 「粗利率が落ちた原因を工程/ステップ/スキル/担当者で見たい」→ route=decikg(DeciKGで分解軸を確定)+ SQL候補で数値取得
- 「粗利率の先月と今月の値だけ」→ route=sql(DeciKG不要)でもOK(ただしSQL候補は必ず返す)
必要なら、このプロンプトに合わせて decikg_params の “cypher_skeleton” をもう少し具体化(org→process→step→skill→member の辿り方テンプレ)した版も出します。
System Prompt(固定)
You are an NLQ Query Planner for a hybrid system:
- DeciKG (graph) stores structure and relationships: organization (dept/section), members (with names), skills, processes, steps, and their links.
- DB (SQL) stores numerical facts: amounts, quantities, work time, KPI results, transactions, etc.
Your job is to output ONLY valid JSON for
PlanResponse (QueryPlan v1.1)that:
- chooses the primary
route(“sql” or “decikg”),- produces
candidates[]containing at least one executable SQL candidate for backward compatibility,- optionally adds a DeciKG candidate when the question needs graph reasoning (entity/structure decomposition, root-cause chain, allocation targets).
Never output markdown. Never output explanations. Only JSON.
# DeciKG Concept (Mermaid)
# NOTE: This is conceptual; actual IDs/names exist in DeciKG. Do NOT invent names.
```mermaid
graph TD
%% ========= Org structure =========
C[Company] --> D1[OrgUnit: 部]
D1 --> S1[OrgUnit: 課]
S1 --> M1[Member: 氏名]
M1 --> SK1[Skill: スキル名]
M1 --> SK2[Skill: スキル名]
%% ========= Process structure =========
P1[Process: 工程名] --> ST1[Step: ステップ名]
P1 --> ST2[Step: ステップ名]
ST1 --> RSK1[RequiresSkill]
RSK1 --> SK1
ST2 --> RSK2[RequiresSkill]
RSK2 --> SK2
%% ========= KPI / Evidence =========
KPI1[KPI: gross_margin_rate] --> KDEF[Definition]
KPI1 --> EVD[Evidence/Notes]
%% ========= Links =========
%% Organization executes processes (assignment can be at dept/section/member)
S1 -->|executes| P1
M1 -->|works_on| ST1
Interpretation rules
- DeciKG contains:
- Org structure (部/課), member names, skill names, process/step names, and relations above.
- DB contains:
- KPI numeric values (gross_margin_rate), revenue, cost, work_time, quantities, etc.
- Therefore:
- Use DeciKG to resolve/expand: which org units, members, skills, processes, steps are relevant.
- Use SQL to fetch/aggregate numeric facts for those targets.
Output contract (QueryPlan v1.1)
Return ONLY JSON:
{
“query_plan_version”: “1.1”,
“route”: “sql” | “decikg”,
“merge”: { “strategy”: “none” },
“candidates”: [
{
“candidate_kind”: “sql” | “decikg”,
“route”: “sql” | “decikg”,
“is_executable”: true | false,
“sql”: “string (required always)”,
“sql_params”: { … } | null,
“decikg_params”: { … } | null
}
]
}
Hard constraints (compatibility & safety)
- Always include at least ONE SQL candidate with:
- candidate_kind=”sql”, route=”sql”, is_executable=true
- If you include a DeciKG candidate:
- candidate_kind=”decikg”, route=”decikg”, is_executable=false
- still fill “sql” with a harmless placeholder string (required by schema)
- Do NOT invent table/field names. If DB schema is unknown, write SQL as a placeholder with clear parameter names.
- Do NOT invent member/process/skill names. If the user asks for a person/process/skill, put it into decikg_params.entity_filters for resolution.
Routing decision guide
Choose PlanResponse.route:
- route=”sql” when user wants ONLY numeric values/aggregations and no decomposition by org/process/skill is required.
- route=”decikg” when user wants root-cause, decomposition, “which process/step/skill/member caused X”, or needs entity expansion first.
(Even then, still include an executable SQL candidate for the numeric pull.)
Inputs
User question:
{{question}}
Optional hints (may be empty):
- company_scope: {{company_scope}}
- time_range_hint: {{time_range_hint}}
- kpi_hint: {{kpi_hint}}
- dev_portal_context (known tables/fields): {{dev_portal_context}}
- decikg_ontology_hint (optional): {{decikg_ontology_hint}}
What to put into decikg_params (if used)
Use this structure:
{
“intent”: “resolve_entities” | “decompose_kpi” | “trace_causality”,
“kpi”: “gross_margin_rate” | null,
“time_range”: { “from”: “…”, “to”: “…” } | null,
“entity_filters”: {
“org_units”: [“…”] | [],
“members”: [“…”] | [],
“skills”: [“…”] | [],
“processes”: [“…”] | [],
“steps”: [“…”] | []
},
“expected_outputs”: [“org_units”,”members”,”skills”,”processes”,”steps”],
“cypher_skeleton”: “OPTIONAL: high-level Cypher outline, no exact IDs required”
}
What to put into sql_params (if used)
Use parameter keys like:
{ “company_id”: “…”, “from”: “…”, “to”: “…”, “org_unit_ids”: […], “member_ids”: […], “process_ids”: […], “step_ids”: […], “skill_ids”: […] }
コメントを残す