/** * 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'] ); } } } } } } } } nlq-dev 追加開発フロー(DeciKG優先ルーティング) – Raqqa

nlq-dev 追加開発フロー(DeciKG優先ルーティング)

0) nlq-devの役割(今回の追加範囲)

nlq-dev は 「実行計画(QueryPlan)を作り、外部を叩き、結果を統合して返す」
ここでの追加開発は主に以下:

  • Concept Lookup(外部参照して「概念がKG側にあるか」判定)
  • Router(LLM①):QueryPlan生成(SQL/Cypher文は作らない)
  • PlanValidator:JSON Schema検証+制約(limit/depth/権限)
  • Route Executor:routeごとの呼び分け(Cypherのみ/SQLのみ/両方)
  • ResultMerger:SQL結果とGraph結果の正規化・紐付け
  • Narrator(LLM②):自然文回答+次アクション生成

1) nlq-dev E2Eフロー(追加後)

sequenceDiagram
  autonumber
  participant U as User
  participant N as nlq-dev
  participant X as External(Concept Lookup)
  participant G as DeciKG
  participant D as dev-portal(SQL)

  U->>N: NLQ
  N->>X: concept_lookup(query_terms)
  X-->>N: resolved_concepts + preferred_source

  N->>N: LLM① Router -> QueryPlan(JSON)
  N->>N: PlanValidator(schema + guardrails)

  alt route == CYPHER_ONLY
    N->>G: execute_cypher(params)
    G-->>N: graph_result
  else route == SQL_ONLY
    N->>D: execute_sql(params)
    D-->>N: sql_result
  else route == CYPHER_THEN_SQL
    N->>G: execute_cypher(params)
    G-->>N: graph_result(candidates/definition)
    N->>D: execute_sql(params + candidates)
    D-->>N: sql_result
  else route == SQL_THEN_CYPHER
    N->>D: execute_sql(params)
    D-->>N: sql_result(top drivers)
    N->>G: execute_cypher(params + top drivers)
    G-->>N: graph_result
  else route == PARALLEL_AND_MERGE
    par
      N->>D: execute_sql(params)
      D-->>N: sql_result
    and
      N->>G: execute_cypher(params)
      G-->>N: graph_result
    end
  end

  N->>N: ResultMerger(normalize + bind evidence)
  N->>N: LLM② Narrator(answer + next actions)
  N-->>U: response

2) nlq-devが持つ「概念照会(Concept Lookup)」インターフェース

※ “どこに保存されているか” は nlq-dev外。nlq-devは 問い合わせるだけ

2.1 nlq-dev内部API(実装する)

  • POST /router/concept-lookup
    • 入力:query_terms[], company_scope, user_scope
    • 出力:resolved_concepts[](concept_id / canonical / preferred_source / synonyms等)

実体のデータは外部(DeciKG or dev-portal側のChroma等)でOK。nlq-devは「解決結果」を使うだけ。


3) Router(LLM①)の責務(nlq-devで実装)

3.1 Routerの入力

  • user NLQ
  • session context(会社・権限・期間推定)
  • concept_lookup結果(ここが最重要
  • 管理者プロンプト(Mermaid+ルール+JSONスキーマ)

3.2 Routerの出力(QueryPlanのみ)

  • route(SQL_ONLY / CYPHER_ONLY / CYPHER_THEN_SQL / SQL_THEN_CYPHER / PARALLEL_AND_MERGE)
  • preferred_source(kg/sql/hybrid)
  • sql params(intent/metrics/dims/filters/time_range/compare/limit)
  • cypher params(intent/focus/depth/constraints/limit)
  • merge.strategy

※ SQL/Cypher文は禁止。


4) PlanValidator(nlq-devで実装)

QueryPlanを 機械的に安全化する層。

  • JSON Schema validation(必須項目・enum制約)
  • limit 最大値(例200)、depth 最大値(例3)
  • company_id/org_unit 等のスコープ未確定なら notes_for_execution に明記し、実行前にデフォルト確定
  • routeとintentの整合性チェック
    • 例:route=CYPHER_ONLY なのに sql.intent が入っていたら拒否/補正

5) Route Executor(nlq-devで実装)

Route別に外部呼び出しを実行する。

  • CypherExecutor:DeciKG API呼び出し(params渡し)
  • SqlExecutor:dev-portal API呼び出し(params渡し)
  • HybridExecutor:CYPHER_THEN_SQL / SQL_THEN_CYPHER の連携(中間結果を次のconstraints/candidatesに差し込む)
  • ParallelExecutor:同時実行→後でmerge

6) ResultMerger(nlq-devで実装)

LLM②に渡すため、結果を正規化する。

  • sql_result:数値/表/集計(共通フォーマットへ)
  • graph_result:構造/依存/根拠(パス/ノード/エッジ/説明キー)
  • binding
    • SQLで出たキー(product_category=A等)を、KG側の概念/ノードと紐付け(可能なら)
  • 失敗時の扱い:
    • 片方が失敗しても「片方だけで回答」できるように degradation を設計

7) Narrator(LLM②)の責務(nlq-devで実装)

入力:merged_result
出力:

  • 結論(数値があれば数値を明示)
  • 根拠(KG由来の構造を要点化)
  • 不確実性(データ不足・推定)を明示
  • 次アクション(追加の切り口、次の質問案)

8) nlq-devの追加成果物(実装対象リスト)

A. API

  • POST /router/plan(NLQ→QueryPlan)
  • POST /router/execute(QueryPlan→結果)
  • POST /router/concept-lookup(外部照会ラッパ)
  • POST /router/narrate(結果→自然文)

B. 内部モジュール

  • concept_lookup_service
  • router_llm
  • plan_validator
  • executors/*
  • result_merger
  • narrator_llm

C. 設定(ENV)

  • DECIKG_URL / DECIKG_API_KEY(任意)
  • DEVPORTAL_URL / DEVPORTAL_API_KEY(任意)
  • CONCEPT_LOOKUP_PROVIDER(decikg|devportal|hybrid)
  • ROUTER_PROMPT_VERSION(管理者プロンプトのバージョン)
  • LLMモデル/温度/タイムアウト

9) DoD(nlq-devだけで判定できる)

  • QueryPlanが常にスキーマ準拠
  • concept_lookup結果により DeciKG優先ルーティングが再現性を持つ
  • 5つのrouteすべてで execute できる
  • 片系障害でも degradation できる(SQLだけ/Graphだけで回答)
  • 監査ログ:NLQ・QueryPlan・外部呼び出し結果要約・回答 を session に紐付けて保存

Comments

コメントを残す

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