/** * 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) api/app/config.py(既存:追加のみ)

目的:環境変数の受け口を追加。起動ログに Embedding 仕様を出す。
変更点

  • CHROMA_URL(必須)、CHROMA_API_KEY(任意)を追加。
  • Embedding 既定を OpenAI固定に:
    • EMBED_PROVIDER=openai(既定)、OPENAI_API_KEY(既存を流用)、
    • EMBED_MODEL=text-embedding-3-small(既定)、EMBED_DIMENSIONS=1536(既定)、EMBED_BATCH_SIZE=64(既定)、CHROMA_TIMEOUT_S=10(既定)。
  • 起動時ログ:EMBED_PROVIDER/MODEL/DIMENSIONS を INFO 出力(次元不一致の早期発見)。

2) api/app/schemas/chroma_upsert.py(既存:整合チェック)

目的:OpenAPI の ChromaUpsertRequest/Result と厳密一致。
変更点

  • Request: collections?: string[], limit: int=1000 (1..5000), dry_run: bool=false、余剰プロパティ拒否。
  • Response: processed/upserted/skipped/failederrors[{doc_id, reason}]
  • 既に一致していれば変更なし。差異があれば既定値/範囲を調整。

3) api/app/repos/portal_chroma_doc_repo.py(既存:メソッド追加・参照列限定)

目的queued 取得状態更新だけを担う薄い Repo。
変更点

  • list_queued(collections: list[str]|None, limit: int) -> list[Row]
    取得列は 固定id, doc_id, lang, collection, doc_text, meta(+必要なら entity/natural_key)。
    model/status/payload は一切 SELECT しない
  • mark_upserted(ids: list[int])mark_failed(id: int, reason: str) を追加。
  • 既存の upsert ロジックは 触らない(それはフェーズGで使用済み)。

4) api/app/services/chroma_client.py(既存:簡素 upsert API を追加)

目的:Chroma へ投げる最小クライアント。
変更点

  • ensure_collection(name) -> CollectionHandle:存在しなければ作成。
  • embed_and_upsert(collection, items, *, batch_size, timeout_s) -> (upserted, failed, errors[])
    • Embeddingはこの層で実施(OpenAI固定)。入力:[(id, text, metadata)]
    • dry_run 時は埋め込みも Chroma 呼び出しも行わないで件数だけ返す。
  • 例外を要約して返す(errors: [{doc_id, reason}])。再送で安定化する前提の軽いリトライ(1回)を入れてもOK。

5) api/app/services/chroma_upsert.py新規

目的:H のオーケストレーション本体。
変更点(実装方針)

  • 入力:collections?, limit, dry_run
  • 取得:repo.list_queued(..., limit)
  • Chroma用IDの決定規則(idempotent)
    • doc_id:: を含む(= entity::natural_key 系)→ doc_id + '::' + langChroma の document.id に採用(言語でユニーク化)。
    • それ以外(例:sha256 64桁など既に言語込み)→ doc_id をそのまま採用。
  • コレクション単位にまとめて chroma_client.embed_and_upsert(...) を実行。
    dry_run の場合は実行せず件数集計のみ。
  • 成功分のみ mark_upserted、失敗分は mark_failed
  • 戻り値:processed/upserted/skipped(=0固定)/failed/errors[] を構築。

6) api/app/routers/chroma.py(既存:/chroma/upsert ルータを実装/簡素化)

目的:入力検証 → サービス呼び出し → 結果返却(Problem変換は既存ヘルパ準拠)。
変更点

  • POST /chroma/upsert にて schemas.ChromaUpsertRequest を受け、services.chroma_upsert.run() を呼ぶだけに簡素化。
  • 例外マッピングは既存 routers/_helpers.py の共通を利用。

変更対象(任意/後回し

A) api/app/services/package.py(Gフェーズ最終確認のみ)

  • 確認ポイント(修正が要る場合のみ最小修正)
    • view_common の NK を必ず view_common::<action_xmlid>::<target> で生成して portal_chroma_doc に保存。
    • repo.upsert()doc_id を渡さない未知カラム(model/status/payload)を渡さない
    • meta は dict のまま(JSONB バインド)。

H の upsert で出る natural_key NOT NULL や「Unconsumed column names」はGの入力不備が原因のため、ここだけは見落としがないか最終確認します(修正行は最小)。

B) DB(optional cleanup)

  • いまテーブルにある 余剰列(model, status, payload)はアプリから無視します。
  • 収束後にクリーンアップするなら以下のいずれか:
    • DROP列(本番影響が無いことを確認のうえ)
    • もしくは CREATE VIEW portal_chroma_doc_min AS SELECT id, entity, natural_key, lang, doc_text, meta, source_hash, collection, state, last_error, created_at, updated_at, doc_id FROM portal_chroma_doc; に差し替え参照(アプリは view を見る)。
      ※ H-slim では 実施不要

C) K8s(dev だけで可)

  • k8s/overlays/dev/patch-api-env.yamlCHROMA_URL / EMBED_* を追加(OpenAIキーは既存 Secret 流用)。
  • 変更が困る場合は kubectl set env 等の都度設定でも可。

実装の肝(合意しておきたいポイント)

  • idempotency:DB doc_id が「entity::nk型」でも「sha256(entity:nk:lang)型」でも、
    上記の :: を含むなら + '::' + lang、含まなければそのまま」Chroma の document.id を決定。
    → これで どちらの DDL 系でも 再実行上書きが安定します。
  • dry_run埋め込みも Chroma も呼ばない(速度&安全重視)。
  • 失敗の扱い:1件ずつ部分成功を許容。last_error は 300字程度に要約保存。
  • 余剰列回避:Repoレイヤで SELECT/UPDATE の列を固定し、未知列に触れない。
    → 「Unconsumed column names」系の事故を確実に封じます。

最小テスト(通し)

  1. 既存ランブックで .../chroma/package (dry_run=false)GET /chroma/docs?status=queued(nk が NULL でない)。
  2. POST /chroma/upsert { collections:["portal_view_common_ja"], limit:100, dry_run:true }processed>0, failed=0
  3. dry_run=falseupserted 増、DB の state='upserted' が増える。
  4. 同じリクエスト再実行でエラーなく上書き(idempotent確認)。

① Deployment が今参照している正確なタグを取得

NS=portal-dev
APP=portal-api

IMG_EXPECTED=$(kubectl -n $NS get deploy $APP \
  -o jsonpath='{.spec.template.spec.containers[?(@.name=="api")].image}')
echo "Deployment expects image: $IMG_EXPECTED"

② そのタグ名でローカルにビルド(タグを IMG_EXPECTED に合わせる

Minikube の Docker デーモンを直接使う方法が一番確実です:

# Minikube の Docker を使う(以降の docker build/push は minikube ノード内に反映)
eval $(minikube -p minikube docker-env)

# 取得したタグ(IMG_EXPECTED)でビルド
docker build -t "$IMG_EXPECTED" -f api/Dockerfile api

# 念のため存在確認
docker images | grep portal-api

すでにローカルで別タグを作っている場合は、そのタグを IMG_EXPECTED に付け替えてもOKです:

docker tag your-local-tag "$IMG_EXPECTED"

③(containerd でも確実に)ノードへ登録されているか確認

# ノード側のコンテナランタイムに登録されているか
minikube ssh -- 'sudo crictl images | grep portal-api || sudo nerdctl images | grep portal-api || docker images | grep portal-api'

eval $(minikube docker-env) でビルドしていれば、ここに IMG_EXPECTED が見えるはずです。
もし eval を使わない流儀で行くなら:

# ローカルでビルドしたタグをそのままノードへ配送
minikube image load "$IMG_EXPECTED"

④ (開発中は推奨)pull policy を IfNotPresent に固定

kubectl -n $NS patch deploy/$APP -p \
'{"spec":{"template":{"spec":{"containers":[{"name":"api","imagePullPolicy":"IfNotPresent"}]}}}}'

⑤ そのままロールアウト

kubectl -n $NS rollout restart deploy/$APP
kubectl -n $NS rollout status deploy/$APP

Comments

コメントを残す

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