コンテンツにスキップ

AI Code Import

code-import SQS キューからコード取り込みジョブを処理する。コンポーネントのソースコード(およびオプションのスクリーンショット)を解析し、セマンティックワード、視覚的特徴、汎用 source context を抽出、512 次元埋め込みベクトル(visual + semantic + context)を生成し、DocumentDB の code コレクションにアップサートした後、Webhook でバックエンドにステータスを通知する。

置き換え対象: guinness-ai-develop の V1 code import predecessor キュー: code-import 最大実行時間: 約 1〜3 分 ソース: guinness-ai-v2(新規リポジトリ)


技術スタック

  • ランタイム: Python 3.12 on AWS Lambda
  • AI フレームワーク: PydanticAI(エージェントオーケストレーション、プロバイダー非依存)
  • LLM: DESC_MODEL 環境変数で設定(例: openai:gpt-5.4-nano)— コード解析 + オプションの Vision
  • 埋め込み: EMBEDDING_MODEL 環境変数で設定(例: text-embedding-3-small、512 次元: visual + semantic + context)— openai SDK 直呼び出し
  • データベース: Amazon DocumentDB(MongoDB 互換) — code コレクション、AI 専用
  • ストレージ: Amazon S3(コンポーネントのスクリーンショット、オプション)
  • キュー: AWS SQS(code-import キュー)
  • ステータス通知: Webhook POST /v1/webhooks/ai-status → guinness-backend
  • MySQL/PostgreSQL 非アクセス: データベース分離を徹底

V1 との主な変更点

項目 V1 V2(code-import)
AI フレームワーク LangGraph StateGraph PydanticAI — 2 エージェント(run_sync)
スクリーンショット img_url 必須 img_url 任意 — なしの場合は visual をテキストのみで推論
ソースコードの LLM 利用 保存のみ、解析なし Semantic エージェントが source_code + css_code を読む
埋め込み 1 本(vision キーワードの vector_embedding) 3 本 — visual + semantics + context
Visual LLM 入力 画像のみ img_url あり: 画像 + CSS / なし: コード + CSS のみ
DB 書き込み MySQL + DocumentDB DocumentDB のみ + Webhook(RDB 非アクセス)
DocDB コレクション legacy flat code-like collection code
CSS 未保存 css_code(任意)
バッチ処理 最初の失敗で全体リトライ batchItemFailures(レコード単位)
organization_id DocDB になし 全ドキュメントに保存

フィールド単位の公式契約: io-definition.ja.md。


処理フロー

flowchart TD
    A["SQS: code-import\n(code_id, source_code, img_url?)"] --> B["パース + 検証 CodeImportMessage"]
    B --> C["Semantic エージェント — テキストのみ\nsource_code + css_code → semantic_words"]
    B --> X["決定的 source parser\nimports, exports, tags, tokens,\nfamily, variant, level"]
    B --> D{img_url あり?}
    D -->|Yes| E["S3 GET スクリーンショット\n(最大 10 MB)"]
    E --> F["Visual エージェント — マルチモーダル\n画像 + CSS → visual_features"]
    D -->|No| G["Visual エージェント — テキストのみ\nsource_code + CSS → visual_features"]
    F --> H
    G --> H
    C --> H
    X --> H
    H["OpenAI Embeddings — 1 回バッチ\n[visual_features, semantic_words, context_text] → 512 次元"]
    H --> I["CodeDocument 構築\nDocumentDB upsert _id=code_id"]
    I --> J["POST webhook success"]

    style A fill:#f9f,stroke:#333
    style C fill:#bbf,stroke:#333
    style F fill:#bbf,stroke:#333
    style G fill:#bbf,stroke:#333
    style I fill:#bfb,stroke:#333
    style J fill:#fdb,stroke:#333

実装: apps/code-import/src/code_import/service.py(process_record)。エージェントは handler.py で packages/agentic から構築。


埋め込み戦略

design-import とは異なり、visual と semantic は 1 回の vision で取るわけではない。Semantic と visual は別エージェントで抽出し、context は source と任意の汎用 metadata から決定的に導出する。

埋め込み エージェント 入力 エンコード内容 des2code での用途
semantics Code Semantics source_code, css_code 目的・機能キーワード(5〜10) デザインが求める機能に近いコンポーネント
visual Code Visual スクショ + CSS、またはコード/CSS のみ visual_features テキスト → 埋め込み デザインに見た目が近いコンポーネント
context 決定的 parser source、CSS、name、matching hints imports、exports、JSX/HTML tags、referenced components、source tokens、family、variant、level 構造、階層、コンポーネント構成で関連する code を探す

全ベクトルは 1 回の embeddings.create で生成(モデル・次元のずれを防止)。

Code Import は最小限の matching contract にする。SQS payload から受け取る任意 matching hints は component level、family、variant のみで、それ以外の context は source 自体から導出する。


1. SQS メッセージスキーマ(入力)

エンベロープ

バックエンド(apps/app/src/services/code.ts)が code-import キューにエンキューする。Lambda は標準の SQSEvent ラッパーで受信する:

{
  "Records": [{
    "messageId": "0192a3b4-c5d6-7e8f-9a0b-1c2d3e4f5a6b",
    "body": "{\"code_id\":\"660e8400-e29b-41d4-a716-446655440001\",\"organization_id\":1,\"project_id\":42,\"name\":\"button-primary\",\"type\":1,\"based_on\":0,\"source_code\":\"<button className='btn btn-primary'>Click me</button>\",\"css_code\":\".btn-primary { background:#007bff; }\",\"img_url\":\"s3://bucket/code/42_button.png\"}",
    "attributes": { "ApproximateReceiveCount": "1" },
    "eventSource": "aws:sqs"
  }]
}

Body フィールド

Records[0].body は JSON 文字列。パース後:

フィールド 型 必須 説明
code_id string (UUID) Yes 主キー。PG code.id および DocumentDB _id と同一値。
organization_id int Yes 所属組織。DocumentDB のテナント分離に使用。
project_id int Yes 所属プロジェクト。ベクトル検索の $match スコープに使用。
name string Yes 人間可読な名前(例: "button-primary")。
type int Yes 0 = page、1 = code。
based_on int Yes 0 = imported、1 = design 由来、2 = wireframe 由来。
source_code string Yes コンポーネントのソース(JSX/TSX/HTML)。
css_code string No オプション CSS。存在すればそのまま保存。
img_url string No スクリーンショットの S3 URL(オプション)。省略時は視覚的特徴をコードから推論。
component_level string No atom、molecule、organism、template、page などの任意 level。
family string No 任意 component family。省略時は name/source tokens から導出。
variant string No 任意 variant。省略時は name/source tokens から導出。

バリデーションルール

  • code_id は RFC 4122 UUID v4 に準拠。
  • type ∈ {0, 1}、based_on ∈ {0, 1, 2} — それ以外は拒否。
  • source_code は非空(長さ ≥ 1、推奨 ≤ 200 KB)。
  • img_url(存在する場合)は次のいずれかの形式: s3://bucket/key、https://bucket.s3.<region>.amazonaws.com/key、https://s3.<region>.amazonaws.com/bucket/key。URL エンコードキーも受け付ける。

Pydantic モデル

from pydantic import BaseModel, Field

class CodeImportMessage(BaseModel):
    code_id: str = Field(..., description="UUID v4; PG code.id および DocDB _id と一致")
    organization_id: int
    project_id: int
    name: str
    type: int = Field(..., ge=0, le=1, description="0=page, 1=code")
    based_on: int = Field(..., ge=0, le=2, description="0=imported, 1=design, 2=wireframe")
    source_code: str = Field(..., min_length=1)
    css_code: str | None = None
    img_url: str | None = None
    component_level: str | None = None
    family: str | None = None
    variant: str | None = None

注意(V1 → V2 リネーム): V2 では legacy import identity fields を統合された code コレクションに合わせて code_id / name に正規化。


2. Webhook ペイロードスキーマ(HTTP 出力)

処理完了後、ワーカーは WEBHOOK_BASE_URL(POST /v1/webhooks/ai-status)に 1 回 POST する。認証は X-API-Key(タイミングセーフ比較)。受信側コントラクトは ai-status webhook を参照。

成功時

{
  "type": "code-import",
  "recordId": "660e8400-e29b-41d4-a716-446655440001",
  "projectId": 42,
  "organizationId": 1,
  "status": "success",
  "result": {
    "keywords": ["submit", "checkout", "transaction", "action", "primary"],
    "embeddingDims": 512
  }
}

失敗時

{
  "type": "code-import",
  "recordId": "660e8400-e29b-41d4-a716-446655440001",
  "projectId": 42,
  "organizationId": 1,
  "status": "failed"
}

フィールド一覧

フィールド 型 説明
type string 常に "code-import"(ディスクリミネータ)。
recordId string SQS body の code_id をエコー。バックエンドが PostgreSQL の code 行を更新する際に使用。
projectId int project_id をエコー。
organizationId int organization_id をエコー。backend scope validation に使用。
status string "success" または "failed"。
result object status="success" の場合のみ存在。
result.keywords string[] ソースコードから抽出されたセマンティックワード(5–10 件)。
result.embeddingDims int 512(HNSW インデックス次元と一致)。

型整合性に関する注意: このフローでは backend code.id が UUID 文字列のため、recordId は string。backend webhook contract は code_id と同じ値を受け付ける必要がある。

送信タイミング

イベント 送信タイミング Body
全工程が例外なく完了 DocumentDB アップサート成功後 成功ペイロード
process_record() で例外発生時 例外を再 raise する直前 失敗ペイロード

HTTP 呼び出しは httpx を使用、timeout=10s, retries=2(指数バックオフ)。Webhook 自体の失敗は retryable processing failure として扱い、worker は SQS が再配送できるように再 raise する。


3. PydanticAI — LLM ロール定義

ワーカーは 2 つのロールに対応した 2 つの専用 LLM 呼び出し を行う。

ロール アーキテクチャノード 入力 出力
Code Analysis LLM C — ソースコード解析 → semantic words ソースコード + CSS(テキスト) semantic_words リスト
Vision LLM F — 視覚特徴抽出 / G — コード構造から推論 スクリーンショット(ある場合)またはソースコード + CSS visual_features 文字列

両呼び出しとも同じ DESC_MODEL 環境変数(provider:model 形式、例: openai:gpt-5.4-nano)を使用。独立した呼び出しであり、各出力は別々に埋め込まれる。

3.1 Code Analysis LLM(テキスト専用)

アーキテクチャノード C に対応。ソースコード(およびオプションの CSS)を読み、コンポーネントが 何をするか を表す 5–10 個のセマンティックキーワードを出力。

from pydantic_ai import Agent
from pydantic import BaseModel, Field

class CodeSemantics(BaseModel):
    semantic_words: list[str] = Field(
        min_length=5, max_length=10,
        description="コンポーネントの目的・機能・動作を表す 5–10 個のキーワード",
    )

code_analysis_llm = Agent(
    DESC_MODEL,
    output_type=CodeSemantics,
    instructions=(
        "あなたはフロントエンドコードアナリストです。コンポーネントのソースコードと "
        "オプションの CSS が与えられたら、目的・機能・動作を表す 5–10 個の "
        "簡潔なキーワードを抽出してください。例: 'submit', 'checkout', 'navigation', "
        "'modal', 'form', 'pagination'。視覚的スタイルはここでは記述しない — "
        "別途扱う。"
    ),
)

入力(ワーカーが構築): 以下の形式の単一ユーザーメッセージ

SOURCE CODE:
<source_code>

CSS (optional):
<css_code>

出力: CodeSemantics.semantic_words → Webhook の result.keywords および semantic 埋め込み に使用。

3.2 Vision LLM(vision 対応、条件分岐)

アーキテクチャノード F(スクリーンショットあり)および G(スクリーンショットなし — コード構造から推論)に対応。img_url の有無で分岐。

class VisualFeatures(BaseModel):
    visual_features: str = Field(
        min_length=20,
        description="視覚的記述: 色、形、間隔、レイアウト、タイポグラフィ、全体的な美学",
    )

vision_llm = Agent(
    DESC_MODEL,
    output_type=VisualFeatures,
    instructions=(
        "あなたは UI デザインアナリストです。コンポーネントの視覚スタイル "
        "(色、形、間隔、レイアウト、タイポグラフィ、全体的な美学)を記述してください。"
        "スクリーンショットがある場合は画像を優先。"
        "ソースコード/CSS のみの場合は、クラス名・インラインスタイル・構造から推論してください。"
    ),
)
分岐 LLM 入力 アーキテクチャノード
img_url あり スクリーンショットの base64 + (オプションの)CSS テキスト F — Vision LLM がピクセルレベルの特徴を抽出
img_url なし source_code + css_code(テキストのみ) G — コード構造から視覚特徴を推論

出力: VisualFeatures.visual_features → visual 埋め込み に使用。


4. 埋め込み生成

PydanticAI は埋め込みを扱わない。ワーカーは openai SDK を直接呼び出す。コストとレイテンシ削減のため、全埋め込みを 1 回のバッチ API 呼び出しで生成。

from openai import AsyncOpenAI

client = AsyncOpenAI()  # OPENAI_API_KEY を環境変数から読み取り

async def generate_embeddings(
    visual_text: str,
    semantic_words: list[str],
    context_text: str,
) -> tuple[list[float], list[float], list[float]]:
    response = await client.embeddings.create(
        model=EMBEDDING_MODEL,        # 例: "text-embedding-3-large"
        input=[visual_text, " ".join(semantic_words), context_text],
        dimensions=512,
    )
    return response.data[0].embedding, response.data[1].embedding, response.data[2].embedding
埋め込み 入力テキスト ソースエージェント インデックスフィールド
visual visual_features Visual Agent visual.vector_embedding
semantics " ".join(semantic_words) Semantic Agent semantics.vector_embedding
context source parser 由来の context_text 決定的 parser context.vector_embedding

全ベクトルは DocumentDB の HNSW インデックスに合わせて 512 次元。


5. DocumentDB 操作

5.1 アップサート

collection.update_one(
    {"_id": payload.code_id},
    {"$set": document},
    upsert=True,
)

アップサートは 冪等: 同じ code_id で再処理しても最終状態は同じ。

5.2 ドキュメント構造(code コレクション)

{
  "_id": "660e8400-e29b-41d4-a716-446655440001",
  "organization_id": 1,
  "project_id": 42,
  "name": "button-primary",
  "type": 1,
  "based_on": 0,
  "index_schema_version": "2026.05.1",
  "source_code": "<button className='btn btn-primary'>Click me</button>",
  "css_code": ".btn-primary { background:#007bff; border-radius:8px; }",
  "code_context": {
    "component_level": "atom",
    "family": "button",
    "variant": "primary",
    "imports": ["react"],
    "exports": ["Button"],
    "referenced_components": ["button"],
    "source_tokens": ["button", "primary", "click", "btn-primary"]
  },
  "visual": {
    "metadata": {
      "name": "button-primary",
      "image_url": "s3://bucket/code/42_button.png",
      "encoder": "text-embedding-3-small@512"
    },
    "vector_embedding": [0.12, -0.04, 0.88, "..."]
  },
  "semantics": {
    "metadata": {
      "words": ["submit", "checkout", "transaction", "action", "primary"]
    },
    "vector_embedding": [0.45, 0.22, -0.10, "..."]
  },
  "context": {
    "metadata": {
      "context_text": "level=atom family=button variant=primary exports=Button tags=button tokens=button primary click btn-primary"
    },
    "vector_embedding": [0.18, 0.31, -0.22, "..."]
  }
}
フィールド ソース 備考
_id SQS body の code_id UUID; PG code.id と一致
organization_id SQS body テナント分離
project_id SQS body ベクトル検索の $match スコープ
name, type, based_on SQS body そのまま
source_code, css_code SQS body そのまま; css_code は省略可
index_schema_version schemas.py の INDEX_SCHEMA_VERSION 形状/エンコーダ変更時にバンプ
code_context 決定的 parser + 任意 matching hints context retrieval と reranking に使う matching evidence
visual.metadata.image_url SQS img_url または null(テキストのみ visual) —
visual.metadata.encoder インデックス時の {model}@{dims} —
visual.vector_embedding visual_features 文字列の埋め込み 512 次元
semantics.metadata.words Code Semantics エージェント出力 5–10 個の文字列
semantics.vector_embedding semantic_words 結合文字列の埋め込み 512 次元
context.metadata.context_text 決定的 context text context retrieval に使用
context.vector_embedding context_text 文字列の埋め込み 512 次元

5.3 ベクトルインデックス

packages/models/documentdb/code.py で起動時に 1 回だけ作成(冪等):

インデックス フィールド 型 次元 類似度
visualVectorIndex visual.vector_embedding HNSW 512 cosine
semanticVectorIndex semantics.vector_embedding HNSW 512 cosine
contextVectorIndex context.vector_embedding HNSW 512 cosine

HNSW パラメータ: m=16、efConstruction=64。

5.4 design-import / des2code とのクロスモーダルブリッジ

des2code は design ドキュメントのベクトルと evidence を使い、visual、semantic、context signal で code コレクションを検索する。

des2code 検索 design 側クエリ code 側パス
Visual design.visual.vector_embedding code.visual.vector_embedding
Semantic design.semantics.vector_embedding code.semantics.vector_embedding
Context / structural design.structural.vector_embedding と design.query_terms code.context.vector_embedding と code_context

semantic の生成元はワーカーごとに異なります:

ワーカー semantics.metadata.words の出所
design-import 1 回の vision → デザイン画像から semantic_words
code-import テキスト専用エージェント → ソースコード + CSS から

埋め込みモデル・512 次元は共通のため検索は可能ですが、semantic 軸は「同じ画像を二重に渡す」ものではなく、意図のブリッジです。生成時のコンポーネント画像は code.visual.metadata.image_url(コンポーネントごとに任意)。


6. エラーハンドリング

アプリケーションレベルのリトライなし — SQS のリドライブに依存。

def handler(event, context):
    if "Records" not in event:
        raise ValueError("Invalid SQS event")

    for record in event["Records"]:
        try:
            process_record(record)
        except Exception as e:
            logger.error(f"Failed: {record.get('messageId')} — {e}")
            send_webhook(status="failed", ...)  # ベストエフォート
            raise  # SQS がバッチをリトライ

失敗マトリクス

ステージ 例外クラス 動作 Webhook 送信?
SQS エンベロープに Records なし ValueError 再 raise → SQS リトライ ❌(レコードコンテキストなし)
Body JSON パース失敗 json.JSONDecodeError 再 raise → SQS リトライ ❌(code_id なし)
Pydantic バリデーション失敗 ValidationError 再 raise → SQS リトライ ⚠️ 部分的(code_id がパース可能ならベストエフォート)
S3 ダウンロード失敗 botocore.exceptions.ClientError 再 raise → SQS リトライ ✅ failed
LLM 呼び出し失敗(semantic / visual) pydantic_ai.exceptions.AgentRunError 再 raise → SQS リトライ ✅ failed
埋め込み API 失敗 openai.APIError 再 raise → SQS リトライ ✅ failed
DocumentDB 書き込み失敗 pymongo.errors.PyMongoError 再 raise → SQS リトライ ✅ failed
Webhook POST 失敗 httpx.HTTPError log して SQS retry のため再 raise n/a
最大 SQS リトライ超過 — DLQ へ配送(Terraform で設定) n/a

リトライ可能 vs 終端

種類 例 SQS の扱い
終端(リトライ無意味) 不正な JSON、バリデーションエラー 初回失敗で DLQ
リトライ可能(一時的) S3 5xx、OpenAI 429、DocDB 接続リセット 指数リトライ後 DLQ

区別は SQS パーシャルバッチレスポンス(batchItemFailures)で実装。


7. フォルダ構成

guinness-ai-v2/
  packages/
    agentic/
      __init__.py
      orchestrator.py            # build_manager_agent(), build_tool_agent() — PydanticAI
      agents/
        __init__.py
        vision.py                # Vision LLM + DesignDescription             (design-import 用)
        code_semantics.py        # ★ Code Analysis LLM + CodeSemantics      (code-import 用, arch node C)
        code_visual.py           # ★ Vision LLM + VisualFeatures            (code-import 用, arch nodes F/G)
        code_gen.py              # コード生成 LLM (des2code 用)
    models/
      documentdb/
        __init__.py              # initialize_collections()
        design.py                # DesignCollection
        code.py                  # ★ CodeCollection + visual / semantic インデックス
    helpers/
      s3.py                      # parse_s3_url(), download_image_from_s3()
      embedding.py               # generate_embeddings() — 共有 openai SDK ユーティリティ
      webhook.py                 # send_webhook() — 共有 httpx POST
    utils/
      settings.py                # 共有 Pydantic Settings 基底
      documentdb.py              # 共有 DocumentDB 接続シングルトン

  apps/
    code-import/                 # リポジトリ上のディレクトリ(ハイフン); uv ワークスペースのパス
      pyproject.toml             # Hatch プロジェクト名 code_import と import パスが一致
      Dockerfile
      README.md
      __tests__/                 # 未配置 — test-cases.ja.md に従って追加
      src/
        code_import/             # Python パッケージ(アンダースコア)
          handler.py             # Lambda エントリ lambda_handler(Dockerfile CMD)
          config.py              # Pydantic Settings のたたき台
          schemas.py             # CodeImportMessage / CodeDocument / Webhook(拡張予定)
          prompts.py             # プロンプト文字列・バージョン
          db.py                  # (予定)DocumentDB アップサートヘルパー

ファイル責務(アプリレベル)

パス 責務
src/code_import/handler.py Lambda lambda_handler(event, context) — SQS バッチ処理とレコード単位ワークフロー
src/code_import/config.py Pydantic Settings のたたき台 — 機能追加に合わせて環境変数を増やす
src/code_import/schemas.py 入力 / 出力モデル(CodeImportMessage, DocDB / Webhook ペイロード)
src/code_import/prompts.py プロンプト資産 / バージョン
(予定)src/code_import/db.py upsert_code(document) — DocDB 書き込み

ファイル責務(共有パッケージ)

ファイル 責務
packages/agentic/agents/code_semantics.py Code Analysis LLM(arch ノード C) + CodeSemantics 出力モデル
packages/agentic/agents/code_visual.py Vision LLM(arch ノード F/G) + VisualFeatures 出力モデル
packages/models/documentdb/code.py CodeCollection クラス、ベクトルインデックス作成
packages/helpers/embedding.py generate_embeddings() — 共有 openai SDK 呼び出し
packages/helpers/webhook.py send_webhook() — 共有 httpx POST

V1 から削除: config/mysql.py、すべての save_*_to_mysql ヘルパー(V2 では MySQL アクセスなし)。


8. 環境変数

変数 説明 例
OPENAI_API_KEY OpenAI キー(PydanticAI と openai SDK の両方で使用) sk-...
DESC_MODEL コード解析 + vision モデル(プロバイダー前置詞付き) openai:gpt-5.4-nano
EMBEDDING_MODEL 埋め込みモデル text-embedding-3-large
EMBEDDING_DIMENSIONS ベクトル次元 512
DOCUMENTDB_CONNECTION_STRING DocumentDB 接続文字列 mongodb://...
DOCUMENTDB_NAME DocumentDB データベース名 guinness_ai
CODE_TABLE_NAME DocumentDB コレクション名 code
DOCUMENTDB_CA_PATH TLS CA(Lambda) /var/task/global-bundle.pem
WEBHOOK_BASE_URL バックエンド Webhook URL(プライベート VPC) https://api.internal/v1/webhooks/ai-status
WEBHOOK_API_KEY X-API-Key として送信する API キー(Secrets Manager から) (secret)

V2 で不要(Terraform が管理): SQS_*_QUEUE_URL、S3_BUCKET。

V1 から削除: DATABASE_RDS_PROXY_ENDPOINT、DATABASE_NAME、DATABASE_USER、DATABASE_PASSWORD、DATABASE_PORT(V2 では MySQL なし)。


9. 依存パッケージ

パッケージ 用途
openai OpenAI SDK(埋め込み)
pydantic-ai PydanticAI(エージェントオーケストレーション、プロバイダー非依存)
pydantic データモデル、構造化出力
pydantic-settings 環境変数設定
pymongo DocumentDB ドライバー
boto3 AWS SDK(S3 ダウンロード)
loguru 構造化ログ
httpx HTTP クライアント(Webhook POST)

V1 から削除: langchain-openai、langgraph、PyMySQL。


10. エンドツーエンドシーケンス

sequenceDiagram
    participant SQS as code-import SQS
    participant L as Lambda code-import
    participant S3 as S3
    participant SA as Semantic Agent (PydanticAI)
    participant VA as Visual Agent (PydanticAI)
    participant E as OpenAI Embeddings
    participant D as DocumentDB (code)
    participant WH as POST /v1/webhooks/ai-status

    SQS->>L: SQSEvent (1 record)
    L->>L: parse + validate (CodeImportMessage)
    L->>L: deterministic source parser -> code_context + context_text
    L->>SA: source_code + css_code
    SA-->>L: semantic_words[5..10]
    alt img_url あり
        L->>S3: GetObject(img_url)
        S3-->>L: bytes
        L->>VA: image (base64) + css_code
    else img_url なし
        L->>VA: source_code + css_code (テキストのみ)
    end
    VA-->>L: visual_features (str)
    L->>E: embeddings.create([visual_features, " ".join(semantic_words), context_text], dim=512)
    E-->>L: [visual_emb, semantic_emb, context_emb]
    L->>D: upsert {_id: code_id, ...visual, ...semantics, ...context, code_context}
    D-->>L: ack
    L->>WH: POST {type:"code-import", recordId, status:"success", result:{...}}
    WH-->>L: 200

例外発生時:

sequenceDiagram
    participant L as Lambda
    participant WH as Webhook
    participant SQS as SQS
    L->>L: 例外をキャッチ
    L->>WH: POST {type:"code-import", recordId, status:"failed"}
    WH-->>L: 200(ベストエフォート)
    L--xSQS: 再 raise → メッセージがキューに戻る
    Note over SQS: SQS が RedrivePolicy に従ってリドライブ

11. 未確定事項 / 将来作業

トピック 備考
ソースコードの長さ上限 暫定 200 KB。バックエンドと擦り合わせ後に強制適用。
画像フォーマット対応 ワーカーは PNG/JPEG/GIF/WebP を受信。OpenAI vision は現状 PNG/JPEG が最良。自動変換は将来の改善項目。
クロス組織検索 vector search は organization_id + project_id で scope する。Des2Code は検索前に両方を検証する。