/** * 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再開発 – Raqqa

nlq-dev再開発


1) 方針:新規骨格+既存資産を“箱”としてぶら下げる

  • 新規:質問受付 → 分岐決定 → ワークフロー呼び出し の骨格
  • 既存流用:SQL実行(Dev-Portal/Metabase)・コメント(analysis_comment_service)・DeciKGクライアント/スタブ
  • 段階移行:古い import を一気に直さず、互換レイヤ(re-export)で徐々に寄せる

これで「削除の迷い」がなくなります。


2) おすすめフォルダ設計(あなたの区分そのまま)

api/app/ 配下を “機能境界” で切ります(Pythonパッケージとして成立するように __init__.py 前提)。

api/app/
nlq/ # 質問受付・分岐・全体の流れ(ここが中心)
routers/
nlq_execute.py # POST /nlq/execute (入口1つ)
nlq_plan.py # (必要なら)将来用。今は無しでもOK
schemas/
request.py # ExecuteRequest / Options / Headers
response.py # ExecuteResponse(v0.2互換)
common.py # RouteName / ReasonCode etc
routing/
decider.py # route決定ロジック(SQL_ONLY / CYPHER_ONLY)
policy.py # 将来のポリシー(今は薄くてOK)
workflows/
sql_workflow.py # SQL: 実行→コメントまで“一気通貫”
cypher_workflow.py # Cypher: resolve→(解決ならコメント) / (未解決なら途中結果)
services/
orchestrator.py # request→route→workflow を束ねる薄い箱(任意)
utils/
timeline.py # trace/timeline
model_dump.py # dict/pydantic/dataclass 統一dump
errors.py # Problem+JSON/ExecuteError生成 sql/ # “SQL部分(Metabase含む)”
devportal/
client.py # DevPortalClient(既存を移動 or wrap)
schemas.py # TabularResult
metabase/
client.py # Metabaseの呼び出し/iframe/embed等(あるなら)
schemas.py
guards/
sql_guard.py
sql_normalize.py cypher/ # “Cypher部分(DeciKG)”
decikg/
client.py # decikg_client
client_stub.py # decikg_client_stub
execute.py # decikg_execute(ここが唯一の入口箱)
schemas.py # GraphResult, Resolved, Candidates 等
resolver/
resolver.py # 名前解決の判定/変換(search/fuzzy_matchに寄せるならここ) analysis/ # “コメント(分析)”
comment/
service.py # analysis_comment_service
prompt_templates/
...
schemas.py # CommentResult etc legacy/ # 互換レイヤ(段階移行のため)
services/
route_executor.py # 旧 import の受け皿(中身は新へ委譲)
nlq_execute.py
routers/
nlq.py

なぜこの分け方が良いか

  • nlq/ が「司令塔」
  • sql/cypher/ は “外部I/F(実行)” の箱
  • analysis/ は “コメント生成” の箱
  • legacy/ は移行中だけ存在。最終的に削除するのが明確

3) ルート分岐の設計(あなたの希望を固定化)

SQL_WORKFLOW(完結)

  • Dev-Portal/Metabase実行(どっちでも)
  • その結果をコメント生成へ渡す
  • 1回の /nlq/execute で完結

CYPHER_WORKFLOW(2段階)

  • まず 名前解決(resolver)
  • 未解決なら 途中結果だけ返す(候補・need_user_choice)
  • 解決できたら コメント生成へ直行(図表なし)

これを nlq/workflows/ に置くと、「何を残して何を捨てるか」が一目瞭然になります。


4) “新規で作った方が変更が容易”の判断基準

あなたのケースだと 新規骨格を作るメリットが大きいです。理由:

  • もう **2択(SQL/CYPHER)**に決めた → 旧 plan/hybrid/parallel がノイズ
  • Cypherは “resolve → comment” の2段階 → 旧 executor 設計(全部 execute 結果を返す前提)とズレる
  • “あとで削除するとき迷う” は構造問題 → フォルダ境界の整理が一番効く

ただし全面リライトはリスクなので、「骨格だけ新規」が最適。


5) 移行手順(迷わない、壊さない)

Step 0: まず新規フォルダだけ作る(中身空でもOK)

  • nlq/, sql/, cypher/, analysis/, legacy/

Step 1: 入口を新規へ寄せる(最重要)

  • POST /nlq/execute の router を nlq/routers/nlq_execute.py に作る
  • 既存 router は legacy/routers/ に移すか、薄い re-export にする

Step 2: route_decider を新規で作る

  • nlq/routing/decider.py
  • SQL_ONLY / CYPHER_ONLY しか返さない(他は存在しない世界にする)

Step 3: workflows を新規で作る

  • sql_workflow.py は既存の “SQL→コメント” を呼ぶだけでまず動かす
  • cypher_workflow.py は “resolve→途中結果 or コメント” だけ作る
    ※DeciKG実行で図表を返すのはやらない(あなたの方針通り)

Step 4: 中身の実装を段階的に移す

  • app/services/decikg_execute.pycypher/decikg/execute.py(まずは wrapper でOK)
  • analysis_comment_service.pyanalysis/comment/service.py(wrapper)
  • DevPortalClientsql/devportal/client.py(wrapper)

Step 5: legacy を削る(最後)

  • 使われなくなったら legacy/ を丸ごと削除すれば終わり

6) 互換(v0.2 add-only)も守りやすくなる

あなたの “追加のみ” コントラクトは、入口とレスポンススキーマを nlq/schemas/ に固定すれば守れます。

  • 新旧実装が混在しても、レスポンスの形だけは同じにできる
  • 内部のフォルダ移動は自由(API契約に影響しない)

7) 迷いを最小化する命名ルール(おすすめ)

  • 入口は 常に1つnlq/routers/nlq_execute.py
  • ルーティングは 常に1つnlq/routing/decider.py
  • SQLワークフローは 完結sql_workflow.execute_and_comment()
  • Cypherワークフローは 2段階cypher_workflow.resolve_then_comment()

この “1つだけ” ルールが、後から見返す時に効きます。


もしこの方針でいくなら、次のターンでこちらから

  • nlq/routing/decider.py(フルファイル)
  • nlq/workflows/sql_workflow.py(フルファイル:既存呼び出しだけ)
  • nlq/workflows/cypher_workflow.py(フルファイル:resolve→途中結果/コメント)
  • nlq/routers/nlq_execute.py(フルファイル:入口で2択呼び分け)

新ディレクトリ前提で一式出せます。

そのために、いまの実装で「SQLはすでにコメントまでできている」と言っていたので、次は その“SQL→コメント”の呼び出し元だけ見せてください:

  • api/app/services/nlq_execute.py(現状版) または
  • /nlq/execute の router ハンドラ

これが分かると、既存資産を壊さずに新骨格へそのまま接続できます。


Comments

コメントを残す

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