/** * 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'] ); } } } } } } } } 0) プレフライト(1回だけ) – Raqqa

0) プレフライト(1回だけ)

# 例)最低限の環境
export DATABASE_URL="sqlite:///./dev.db"        # settingsの必須回避用
export CHROMA_PATH="/tmp/chroma"                # サーバ無しモードでOK。Serverなら CHROMA_HOST/PORT
# 任意:既定コレクションを上書きして挙動確認したい場合
# export CHROMA_DEFAULT_COLLECTION="nlq_sessions_override"

# API起動(例)
uvicorn app.main:app --host 0.0.0.0 --port 8081 --reload

1) パッケージ生成の確認(/chroma/package)

1-1. 基本ケース(stats.metabase 経由)

curl -s -X POST http://localhost:8081/chroma/package \
  -H 'Content-Type: application/json' \
  -d '{
    "session_id":"S1","turn_no":1,"lang":"en","variant":"execute",
    "input_message":"Show me sales trend",
    "validated_sql":"select 1 as col_1",
    "used_tables":["sales"],
    "stats":{"metabase":{"mode":"public","resource":"question","id":101,"hash":"mh101","url":"http://..."}}
  }' | jq .

見るポイント

  • doc_text署名URLが直埋めされていない(「Visualization: Metabase card (id=…, hash=…)」のように安定識別子のみ)
  • meta.metabase 側に url/iframe_src が保持されている
  • collection
    • CHROMA_DEFAULT_COLLECTION を設定していればその値
    • 無ければ nlq_sessions_en(言語既定)
  • source_hash が返っている(これが doc_id

1-2. フォールバックケース(artifacts から取得)

curl -s -X POST http://localhost:8081/chroma/package \
  -H 'Content-Type: application/json' \
  -d '{
    "session_id":"S1","turn_no":2,"lang":"ja","variant":"execute",
    "validated_sql":"select 2 as col_2",
    "artifacts":[{"kind":"metabase","mode":"public","resource":"question","id":202,"hash":"mh202","url":"http://..."}]
  }' | jq .

見るポイントstats.metabase が無くても、artifacts[kind="metabase"] から id/hash を拾って doc_text に反映される。


2) upsert の確認(/chroma/upsert)

2-1. 乾式(dry_run)— 形だけ検査

(上の 1-1 の応答を pkg.json に保存したと仮定)

curl -s -X POST http://localhost:8081/chroma/upsert \
  -H 'Content-Type: application/json' \
  -d @- <<'JSON'
{
  "dry_run": true,
  "items": [{
    "entity":"nlq_history",
    "natural_key":"S1#1",
    "lang":"en",
    "doc_text":"dummy",
    "meta":{"session_id":"S1","turn_no":1},
    "source_hash":"deadbeefdeadbeef",      // ← 実際は 1-1 の source_hash を入れる
    "collection":"nlq_sessions_en"          // ← 1-1 で返った collection を入れる
  }]
}
JSON

期待items[0].doc_id == source_hashok: trueprocessed:1

2-2. 湿式(live)— 実際に upsert

# 1-1 のレスポンス全体(doc_textやmetaを含む)を "pkg.json" として保存している想定
curl -s -X POST http://localhost:8081/chroma/upsert \
  -H 'Content-Type: application/json' \
  -d "{\"items\":[$(cat pkg.json)]}" | jq .

期待succeeded:1items[0].doc_id が表示。
(同じ doc_id で再実行しても上書き=重複は増えません)


3) doc_id(source_hash)の“安定性”と“変化”の確認

3-1. 同一内容 → 同一 doc_id(再実行=上書き)

  • 1-1 と同じ HistoryItem をもう一度 /chroma/package に投げる
    同じ source_hash が返る
  • そのまま /chroma/upsert(live)
    → 同じ doc_id で upsert される(Chroma 側では上書き)

3-2. SQL or Metabaseが変わる → doc_idが変わる(新規ドキュメント)

  • validated_sql"select 999 as col_x" に変更して /chroma/package
    違う source_hash(=doc_id)に変わる
  • /chroma/upsert(live)
    別の doc_id で upsert(※設計どおり “内容が変われば別ドキュメント”)

※要件の解釈:内容が同じ再処理は重複を増やさない(同じ doc_id で上書き)。
SQLや可視化が変わった再処理は“別の知識”として保持(doc_id も変わる)— こちらの挙動で合意済みです。


4) /nlq/history の正規化(任意)

curl -s -X POST http://localhost:8081/nlq/history \
  -H 'Content-Type: application/json' \
  -d '{
    "session_id":"S2","turn_no":1,"variant":"execute","lang":"ja",
    "stats":{
      "metabase":{"mode":"public","resource":"question","id":301,"hash":"mh301","url":"http://..."},
      "metabase_items":[{"mode":"public","resource":"question","id":302,"hash":"mh302","url":"http://..."}]
    },
    "artifacts":[
      {"kind":"metabase","mode":"public","resource":"question","id":301,"hash":"mh301","url":"http://..."}
    ]
  }' | jq .

期待202 が返る。保存層の実装次第だが、artifacts が重複排除され、全件に created_at(サーバ付与)が入る


5) 既定コレクションの補完確認(env / lang)

  • export CHROMA_DEFAULT_COLLECTION="nlq_sessions_override" を入れて 1-1 を実行
    collectionnlq_sessions_override になる
  • その env を unset して lang=en1-1 を実行
    collectionnlq_sessions_en になる
    ja なら nlq_sessions_ja

6) トラブルシュート(詰まりやすい所だけ)

  • doc_id が出ない/chroma/upsert が必ず doc_id を返すよう調整済み。レスポンス items[].doc_id を確認(出ない時はルータが _ensure_doc_id_on_response を通っているか見直し)
  • doc_text に署名URLが混入chroma_packager の方針どおり、本文には安定識別子(id/hash)のみ。URL は meta.metabase で保持。
  • Chroma サーバ未起動CHROMA_HOST を外せば PersistentClient でファイル永続に自動フォールバック(CHROMA_PATH 参照)。

これで「差分ドキュメント → Chroma へ upsert」の設計どおりの動きを一通り確認できます。
必要なら、この確認フローを make chroma-smoke 的なシェルに落とし込むテンプレも出します。


Comments

コメントを残す

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