AI Design Import — Overview
A Lambda worker that processes design ingestion jobs from the design-import SQS queue. It downloads the Figma node image and Figma node JSON from S3, extracts visual features, semantic keywords, component functions, and structural node evidence, generates 3 512-dimensional embedding vectors (visual + semantic + structural), upserts them into the design collection in DocumentDB, and then notifies the backend of the status via Webhook.
Replaces: V1 design app (guinness-ai-develop)
Queue: design-import
Max execution time: approx. 2–5 minutes
Source: guinness-ai-v2 (new repository)
1. Tech Stack
| Layer | Technology |
|---|---|
| Runtime | Python 3.12 on AWS Lambda |
| AI Framework | PydanticAI (agent orchestration, provider-agnostic) |
| LLM | DESC_MODEL environment variable (e.g., openai:gpt-5.4-nano) — visual feature extraction + semantic keywords |
| Embeddings | EMBEDDING_MODEL environment variable (e.g., text-embedding-3-small, 512 dimensions) — direct openai SDK call |
| Database | Amazon DocumentDB (MongoDB-compatible) — design collection, AI-dedicated |
| Storage | Amazon S3 (design images, Figma JSON) |
| Queue | AWS SQS (design-import queue) |
| Status notification | Webhook POST /v1/webhooks/ai-status → guinness-backend |
| RDB | No access — does not touch PostgreSQL / MySQL (strict database isolation) |
2. Key Changes from V1
| Item | V1 | V2 |
|---|---|---|
| AI Framework | LangGraph StateGraph |
PydanticAI agent (run_sync) |
| Embeddings per design | 1 (visual style keywords) | 3 (visual + semantic + structural) |
| Database writes | Direct MySQL + DocumentDB | DocumentDB only + Webhook (no RDB access) |
| Figma JSON | Stored as-is in document | Parsed into structural embedding + component IDs + viewport |
| Batch processing | Retries entire batch on first failure | batchItemFailures response (retry per record) |
design_id validation |
Descriptive only | Enforced by Pydantic model_validator |
| LLM client | ChatOpenAI (langchain-openai) |
PydanticAI (provider-agnostic) |
| Embedding client | OpenAIEmbeddings (langchain) — single |
openai SDK direct call — visual + semantic + structural |
3. Processing Flow
flowchart TD
A["SQS: design-import message"] --> B["Parse + SqsBody validation\n(design_id composite key enforced)"]
B --> C["Download design image from S3\n(max 10 MB)"]
C --> E["Download Figma JSON from S3\nfigma.extract_structural(schema, node_id)"]
E --> F["→ structural_text\n→ component_ids\n→ viewport"]
F --> H
H["Vision Agent — PydanticAI run_sync\nDesignDescription:\n layout, component_types, color_palette\n typography_style, semantic_words"]
H --> I["Build embedding input texts\nvisual_text, semantic_text\n+ structural_text\n+ query_terms"]
I --> J["OpenAI Embeddings — single batch call\n3 inputs → 512-dim vectors"]
J --> K["Build DesignDocument\nupsert into DocumentDB with _id=design_id"]
K --> L["POST webhook success"]
style A fill:#f9f,stroke:#333
style H fill:#bbf,stroke:#333
style K fill:#bfb,stroke:#333
style L fill:#fdb,stroke:#333
The worker executes 8 steps sequentially for each record. Any message that fails along the way goes through the error handling path, which sends a failure Webhook before adding the message to batchItemFailures.
4. Embedding Strategy
Up to 3 embeddings per design cover orthogonal search signals. All are generated in a single batch call to prevent drift:
| Embedding | Input Text | Purpose |
|---|---|---|
visual |
"layout: {layout} \| components: {types} \| palette: {colors} \| typography: {style}" |
Find designs that look the same |
semantics |
" ".join(semantic_words) |
Find designs about the same domain |
structural |
Deterministic Figma tree rendering (see figma.py) |
Find designs with the same component structure |
structural is generated for every successful import because json_schema_url is required. The document also persists query_terms and Figma node evidence for Des2Code reranking.
5. Figma JSON Parsing (figma.extract_structural)
Figma JSON is parsed deterministically without an LLM. The parser (apps/design-import/src/design_import/figma.py) traverses the node tree and produces three outputs:
structural_text — a compact single-line string encoding layout, components, and tree shape:
tree=FRAME[V,gap_md]>[FRAME[H,gap_none]>[TEXT_HEADING],INSTANCE[comp_input_email],INSTANCE[comp_input_password],INSTANCE[comp_button_primary]] | components=FRAME[H]x1 FRAME[V]x1 INSTANCE[comp_button_primary]x1 TEXT_HEADINGx1 | depth=2
Design choices:
- Layout direction and spacing are bucketed (gap_none, gap_xs, gap_sm, gap_md, gap_lg, gap_xl)
- INSTANCE nodes use Figma's componentId (canonical ID) as the key; designer-assigned names are ignored
- TEXT nodes are bucketed by font size (TEXT_HEADING ≥28px, TEXT_SUBHEADING ≥18px, TEXT_BODY)
- Tree depth is capped at 12 levels (max_depth)
component_ids — a sorted list of all INSTANCE.componentId values in the tree. Allows des2code Stage 2 to verify component mapping coverage without re-parsing the JSON.
viewport — {width, height, aspect_ratio, device_class} derived from the root node's absoluteBoundingBox. Device classes: mobile (<600px), tablet (<1024px), desktop (<1600px), wide (≥1600px).
6. SQS Message Schema
Message enqueued to the design-import queue by the backend (apps/app/src/services/design.ts):
{
"Records": [{
"messageId": "0192a3b4-c5d6-7e8f-9a0b-1c2d3e4f5a6b",
"body": "{\"organization_id\":1,\"project_id\":42,\"img_url\":\"s3://gnss-prod-inputs/designs/42_hDDA9B..._40002029:37033.png\",\"node_id\":\"40002029:37033\",\"file_id\":\"hDDA9BNori9OTXSClduXqR\",\"design_id\":\"42_hDDA9BNori9OTXSClduXqR_40002029:37033\",\"design_name\":\"Login Screen\",\"json_schema_url\":\"s3://gnss-prod-inputs/figma_json_schema/42_hDDA9BNori9OTXSClduXqR_40002029:37033.json\"}",
"attributes": { "ApproximateReceiveCount": "1" },
"eventSource": "aws:sqs"
}]
}
Body fields (validated by the SqsBody Pydantic model):
| Field | Type | Required | Notes |
|---|---|---|---|
organization_id |
integer | ✓ | Tenant scope. Also copied into the DocumentDB document and webhook |
project_id |
integer | ✓ | Project scope. Also copied into the DocumentDB document and webhook |
img_url |
string | ✓ | S3 URL of the design image |
node_id |
string | ✓ | Figma node id (typically <int>:<int> format) |
file_id |
string | ✓ | Figma file id |
design_id |
string | ✓ | Must match "{project_id}_{file_id}_{node_id}" — enforced by Pydantic model_validator. Becomes the DocumentDB _id |
design_name |
string | ✓ | Figma frame name |
json_schema_url |
string | ✓ | S3 URL of the Figma node JSON export. Required; missing, download failure, or parse failure makes the import fail |
7. Webhook Payload Schema
After processing completes, a POST is sent to WEBHOOK_BASE_URL:
On success
{
"type": "design-import",
"recordId": "42_hDDA9BNori9OTXSClduXqR_40002029:37033",
"projectId": 42,
"organizationId": 1,
"status": "success",
"result": {
"keywords": ["login", "authentication", "form", "submit", "credentials"],
"embeddingDims": 512,
"vectorsProduced": ["visual", "semantic", "structural"],
"indexSchemaVersion": "2026.05.2"
}
}
On failure
{
"type": "design-import",
"recordId": "42_hDDA9BNori9OTXSClduXqR_40002029:37033",
"projectId": 42,
"organizationId": 1,
"status": "failed"
}
On failure, there is no result block. If the failure occurs before parsing, recordId, projectId, and organizationId may be null.
8. PydanticAI — Vision Agent
Replaces V1's LangGraph StateGraph with a single PydanticAI agent. The framework is provider-agnostic — the model name is specified in provider:model format:
from pydantic_ai import Agent
from pydantic import BaseModel
class DesignDescription(BaseModel):
layout: str # one of 11 archetypes
component_types: list[str] # 3–15 normalized snake_case names
color_palette: list[str] # 2–5 dominant hex colors (#RRGGBB)
typography_style: str # one of 8 archetypes
semantic_words: list[str] # 5–10 domain keywords
vision_agent = Agent(
DESC_MODEL, # e.g., "openai:gpt-5.4-nano"
output_type=DesignDescription,
)
Input: design image downloaded from S3 (binary).
Output: DesignDescription — structured visual features and semantic keywords.
Execution mode: run_sync (synchronous) — once per record.
9. Embedding Generation
PydanticAI does not handle embeddings. The openai Python SDK is used directly to generate all vectors in a single batch call (to prevent model drift):
client.embeddings.create(
model=config.embedding_model,
input=[visual_text, semantic_text, structural_text],
dimensions=config.embedding_dimensions, # default 512
)
# data[0] → visual, data[1] → semantic, data[2] → structural (if present)
| Embedding | Input Text |
|---|---|
| Visual | "layout: {layout} \| components: {component_types} \| palette: {color_palette} \| typography: {typography_style}" |
| Semantic | " ".join(semantic_words) |
| Structural | structural_text from required Figma JSON |
10. DocumentDB Document Structure
{
"_id": "42_hDDA9BNori9OTXSClduXqR_40002029:37033",
"organization_id": 1,
"project_id": 42,
"name": "Login Screen",
"type": "screen",
"based_on": "figma_import",
"index_schema_version": "2026.05.2",
"generation_context": "Login Screen. Viewport: 375x812 (mobile). Layout: stacked_form. ...",
"viewport": {
"width": 375,
"height": 812,
"aspect_ratio": 0.4619,
"device_class": "mobile"
},
"visual": {
"metadata": {
"name": "Login Screen",
"image_url": "s3://gnss-prod-inputs/designs/...",
"encoder": "text-embedding-3-small@512",
"layout": "stacked_form",
"component_types": ["input_email", "input_password", "button_primary"],
"color_palette": ["#1A1A2E", "#FFFFFF", "#4A90E2"],
"typography_style": "modern_sans"
},
"vector_embedding": [/* 512 floats */]
},
"semantics": {
"metadata": { "words": ["login", "authentication", "form", "submit", "credentials"] },
"vector_embedding": [/* 512 floats */]
},
"structural": {
"metadata": {
"structural_text": "tree=FRAME[V,gap_md]>[...] | components=... | depth=2"
},
"vector_embedding": [/* 512 floats */]
}
}
viewport and structural are required on new successful imports because json_schema_url is required.
HNSW Vector Indexes
packages/models/documentdb/design.py creates these idempotently at cold start:
| Index Name | Path | Dimensions | Similarity | m | efConstruction |
|---|---|---|---|---|---|
visualVectorIndex |
visual.vector_embedding |
512 | cosine | 16 | 64 |
semanticVectorIndex |
semantics.vector_embedding |
512 | cosine | 16 | 64 |
structuralVectorIndex |
structural.vector_embedding |
512 | cosine | 16 | 64 |
structuralVectorIndex operates sparsely — documents without a structural block are excluded from structural search results.
11. Error Handling
Partial batch failure support — the handler returns {"batchItemFailures": [...]}, so a single failure does not contaminate the rest of the batch. ReportBatchItemFailures must be enabled on the Lambda event source mapping.
def lambda_handler(event, context):
failures = []
for record in event["Records"]:
try:
process_record(record, ...)
except Exception:
failures.append({"itemIdentifier": record["messageId"]})
return {"batchItemFailures": failures}
| Scenario | Behavior |
|---|---|
Invalid SQS event envelope (no Records) |
ValueError. No Webhook. Entire invocation fails |
design_id does not match composite key |
Pydantic validation error. Failure Webhook. Added to batchItemFailures |
| S3 GET failure (image) | Failure Webhook. Added to batchItemFailures |
| Image exceeds 10 MB | ValueError. Failure Webhook. Added to batchItemFailures |
| Missing or invalid Figma JSON | Failure Webhook. Added to batchItemFailures |
| Vision LLM exception | Failure Webhook. Added to batchItemFailures |
| Embedding API exception | Failure Webhook. Added to batchItemFailures |
| DocumentDB write exception | Failure Webhook. Added to batchItemFailures (up to 2 retries) |
| Webhook POST itself fails | WARN log. Added to batchItemFailures so SQS retries |
| SQS retry count exceeded | Message is moved to DLQ |
12. Module Structure
apps/design-import/src/design_import/
handler.py # Lambda entry — SQS parsing, batchItemFailures, warm-start clients
service.py # process_record() — 8-step pipeline
schemas.py # SqsBody, DesignDocument, VisualBlock, SemanticsBlock, StructuralBlock,
# Viewport, WebhookSuccessPayload, WebhookFailurePayload
repo.py # DesignRepo — S3 download, DocumentDB upsert, Webhook POST
figma.py # extract_structural() — Figma JSON → structural_text + component_ids + viewport
prompts.py # VISION_INSTRUCTIONS, VISION_USER_MESSAGE, PROMPT_VERSION
config.py # Config (pydantic-settings)
packages/agentic/src/agentic/
vision.py # build_vision_agent(), DesignDescription schema
packages/models/src/models/documentdb/
design.py # collection name, HNSW index creation, VECTOR_DIMENSIONS constant
| File | Responsibility | V1 Equivalent |
|---|---|---|
handler.py |
Lambda handler, batchItemFailures | apps/design/src/main.py |
service.py |
process_record() — 8-step pipeline |
Inline in main.py (V1) |
schemas.py |
Pydantic models (SQS / DocumentDB / Webhook) | apps/design/src/schema.py |
repo.py |
DocumentDB upsert, S3 download | Inline in main.py (V1) |
figma.py |
Figma JSON parser | — (new in V2) |
config.py |
Environment variables (pydantic-settings) | apps/design/src/config/env.py |
Removed from V1: config/mysql.py (no MySQL access in V2).
13. Environment Variables
| Variable | Description | Example / Default |
|---|---|---|
DESC_MODEL |
Vision LLM model (with provider prefix) | openai:gpt-5.4-nano |
EMBEDDING_MODEL |
OpenAI embedding model | text-embedding-3-small |
EMBEDDING_DIMENSIONS |
Vector dimensions (must match HNSW index) | 512 |
OPENAI_API_KEY |
OpenAI API key | sk-... |
OPENAI_MAX_RETRIES |
SDK 429/5xx retry count | 3 |
DOCUMENTDB_CONNECTION_STRING |
DocumentDB connection string | mongodb://user:pass@host:27017/?tls=true |
DOCUMENTDB_NAME |
DocumentDB database name | guinness_ai |
DESIGN_TABLE_NAME |
DocumentDB collection name | design |
DOCUMENTDB_CA_PATH |
TLS CA bundle path | /var/task/global-bundle.pem |
WEBHOOK_BASE_URL |
Backend Webhook endpoint (private VPC) | https://api.internal/v1/webhooks/ai-status |
WEBHOOK_API_KEY |
Shared service key sent as X-API-Key |
Secrets Manager / SOPS value |
All environment variables are documented in AI Infrastructure — Environment Variables.
14. Package Dependencies
For the full list of package dependencies, see AI Infrastructure — Shared Package Dependencies.