/** * WPML compatibility functions * * @global array $duplicated_posts Array to store the posts being duplicated. * * @package Yoast\WP\Duplicate_Post * @since 3.2 */ add_action( 'admin_init', 'duplicate_post_wpml_init' ); /** * Add handlers for WPML compatibility. */ function duplicate_post_wpml_init() { if ( defined( 'ICL_SITEPRESS_VERSION' ) ) { add_action( 'dp_duplicate_page', 'duplicate_post_wpml_copy_translations', 10, 3 ); add_action( 'dp_duplicate_post', 'duplicate_post_wpml_copy_translations', 10, 3 ); add_action( 'shutdown', 'duplicate_wpml_string_packages', 11 ); } } global $duplicated_posts; // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals -- Reason: Renaming a global variable is a BC break. $duplicated_posts = []; /** * Copy post translations. * * @global SitePress $sitepress Instance of the Main WPML class. * @global array $duplicated_posts Array of duplicated posts. * * @param int $post_id ID of the copy. * @param WP_Post $post Original post object. * @param string $status Status of the new post. */ function duplicate_post_wpml_copy_translations( $post_id, $post, $status = '' ) { global $sitepress; global $duplicated_posts; remove_action( 'dp_duplicate_page', 'duplicate_post_wpml_copy_translations', 10 ); remove_action( 'dp_duplicate_post', 'duplicate_post_wpml_copy_translations', 10 ); $current_language = $sitepress->get_current_language(); $trid = $sitepress->get_element_trid( $post->ID ); if ( ! empty( $trid ) ) { $translations = $sitepress->get_element_translations( $trid ); $new_trid = $sitepress->get_element_trid( $post_id ); foreach ( $translations as $code => $details ) { if ( $code !== $current_language ) { if ( $details->element_id ) { $translation = get_post( $details->element_id ); if ( ! $translation ) { continue; } $new_post_id = duplicate_post_create_duplicate( $translation, $status ); if ( ! is_wp_error( $new_post_id ) ) { $sitepress->set_element_language_details( $new_post_id, 'post_' . $translation->post_type, $new_trid, $code, $current_language ); } } } } // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals -- Reason: see above. $duplicated_posts[ $post->ID ] = $post_id; } } /** * Duplicate string packages. * * @global array() $duplicated_posts Array of duplicated posts. */ function duplicate_wpml_string_packages() { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals -- Reason: renaming the function would be a BC-break. global $duplicated_posts; foreach ( $duplicated_posts as $original_post_id => $duplicate_post_id ) { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals -- Reason: using WPML native filter. $original_string_packages = apply_filters( 'wpml_st_get_post_string_packages', false, $original_post_id ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals -- Reason: using WPML native filter. $new_string_packages = apply_filters( 'wpml_st_get_post_string_packages', false, $duplicate_post_id ); if ( is_array( $original_string_packages ) ) { foreach ( $original_string_packages as $original_string_package ) { $translated_original_strings = $original_string_package->get_translated_strings( [] ); foreach ( $new_string_packages as $new_string_package ) { $cache = new WPML_WP_Cache( 'WPML_Package' ); $cache->flush_group_cache(); $new_strings = $new_string_package->get_package_strings(); foreach ( $new_strings as $new_string ) { if ( isset( $translated_original_strings[ $new_string->name ] ) ) { foreach ( $translated_original_strings[ $new_string->name ] as $language => $translated_string ) { do_action( // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals -- Reason: using WPML native filter. 'wpml_add_string_translation', $new_string->id, $language, $translated_string['value'], $translated_string['status'] ); } } } } } } } } 追加開発:全体のゴール(今回の追加開発で“固定する振る舞い”) – Raqqa

追加開発:全体のゴール(今回の追加開発で“固定する振る舞い”)

  1. /nlq/plan
  • LLM①で QueryPlan(JSON) を生成
  • route(SQL/Cypher/Hybrid/Parallel)と params(sql_params/decikg_params)を候補に埋める
  • ルーティング判断は Mermaid/概念モデルに載っている対象をDeciKG優先を反映
  1. /nlq/execute
  • 候補の route に応じて Route Executor が外部呼び出し
    • SqlExecutor:Dev-Portal に params 渡し(SQL文字列はnlq-devで作らない)
    • CypherExecutor:DeciKG に params 渡し(Cypher文字列はnlq-devで作らない)
    • Hybrid/Parallel:連携・統合(中間結果を constraints/candidates に差し込む)
  1. 履歴
  • 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段階:
    1. Fast heuristic(非LLM):キーワード・既知概念ヒットで初期routeを当てる
    2. 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.py
    • decikg_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) -> ExecuteResult
    • SqlExecutor(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__.py
  • services/executors/route_executor.py
  • services/executors/sql_executor.py
  • services/executors/cypher_executor.py
  • services/executors/hybrid_executor.py
  • services/executors/parallel_executor.py
  • services/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.py
    • health()
    • 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_result
    • concept_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 は保持(互換)
  • 追加で graph merged execution_kind route を載せる

想定変更ファイル

  • schemas/execute.py(ExecuteResponse拡張)
  • routers/nlq_execute.py(レスポンス構築)
  • services/analysis_deep.py(分析入力が SQLだけ前提なら拡張)

新規ファイル候補

  • schemas/graph_result.py
  • schemas/merge_result.py

DoD

  • SQLだけでも従来通り返る
  • DeciKGだけでも graph が返る(columns/rows は空でもOK)
  • Hybrid/Parallelでも merged に統合要約が入る

工程 6:履歴と成果物(history/artifacts)の拡張

目的:route/params/外部結果要約/失敗を追跡できるようにし、後で改善に使えるログを残す。

想定変更ファイル

  • services/history_service.py
  • schemas/history.py
  • sql/040_nlq_history.sql
  • sql/060_nlq_history_artifacts.sql(必要なら)
  • routers/history.py

設計メモ

  • historyに保存したい最低限:
    • route, candidate_kind
    • sql_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.py
  • routers/nlq_execute.py
  • services/nlq_plan.py
  • services/llm_client.py
  • services/graph_client.py(DeciKG client 化)
  • services/sql_guard.py, services/sql_normalize.py
  • services/history_service.py
  • schemas/nlq.py, schemas/execute.py, schemas/history.py
  • sql/040_nlq_history.sql, sql/060_nlq_history_artifacts.sql
  • docs/*, README.md

新規になりやすい

  • services/executors/*(route executor一式)
  • services/dev_portal_client.py
  • services/decikg_client.py(or graph_client.pyを置換)
  • services/routing_policy.py
  • schemas/query_plan.py / schemas/graph_result.py / schemas/merge_result.py
  • config/mermaid_context.md / services/prompt_templates/*

次の一手としては、工程0の「現行の nlq_plan / nlq_execute の入出力(Pydantic定義)」が分かると、工程1〜3のスキーマと実装差分を一気に確定できます。
もし schemas/nlq.pyschemas/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”: […] }


Comments

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です