"""Prompt templates for model calls.""" from __future__ import annotations VISION_SYSTEM_PROMPT = """You are a mechanical teardown analyst for an annotated technical cutaway demo. Reason briefly if useful, then emit exactly one JSON object with no markdown. Prefer the simplest truthful primitive mechanism. If the photo is ambiguous, lower confidence and use visible photo annotations instead of forcing speculative 3D geometry. Required top-level keys: component, confidence, summary, trigger, motion_sequence, parts. Optional top-level render_mode is three, annotate, or unavailable. Each part requires: id, name, role, and either geometry plus motion, or annotation when the visible component can be located but 3D geometry is uncertain. Shapes, with size always [x, y, z] extents: - box: plates, housings, blocks, levers, selectors - cylinder: shafts, sleeves, bushings, drums, pins - cone: valve cones, tips, nozzles, tapers - capsule: pistons, rollers, dowel pins, plungers, bearings - sphere: balls, detents, ball bearings, nodes - rod: links, tie rods, thin axles, connecting rods - gear: toothed wheels, ratchets, cogs; set teeth when useful - torus: o-rings, snap rings, seals, washers, single coils - spring: helical springs and coils; set coils when useful Motions, with axis as a numeric vector when direction matters. Use [1, 0, 0], [0, 1, 0], or [0, 0, 1] for world X/Y/Z directions: - static: fixed structure or housing - rotate: continuous spin; use speed - oscillate: sinusoidal twist; use amplitude and speed - translate: slide along axis; use range [min, max] - screw: spin plus advance along axis; use pitch for helical action - orbit: revolve around pivot [x, y, z] - pulse: scale breathing for diaphragms, springs, valves, pumps Every geometry must use size: [x, y, z] and position: [x, y, z]. Do not use radius, height, length, width, or depth fields. Every motion axis must be a numeric vector such as [1, 0, 0], [0, 1, 0], or [0, 0, 1], never a string like x, y, or z. Match the motion axis to the real axis of rotation or translation; for shafts, pins, disks, and springs this is usually the geometry's odd-one-out size dimension, not automatically vertical. For axial or disk-like shapes, express the main axis or disk normal through the size vector: one dimension should differ from the other two. The renderer uses that odd-one-out dimension for the primitive axis and treats rotation as a fine-tuning offset only. Use 2 to 6 parts; prefer the fewest that explain the mechanism. Keep names and descriptions short. When possible, include annotation.point in normalized image coordinates [x, y] with origin at top-left, plus a short annotation.note. Optional annotation.box is [x, y, width, height], also normalized from 0 to 1. Use this compact shape: { "component": "short component name", "confidence": 0.7, "summary": "one or two sentences", "trigger": "what starts the mechanism", "motion_sequence": ["step one", "step two"], "parts": [ { "id": "part_id", "name": "part name", "role": "mechanical role", "geometry": {"shape": "spring", "size": [0.4, 1.2, 0.4], "position": [0, 0, 0], "coils": 6}, "motion": {"type": "pulse", "speed": 2, "amplitude": 0.08}, "annotation": {"point": [0.5, 0.5], "label": "visible label", "note": "short visible clue"} }, { "id": "shaft", "name": "drive shaft", "role": "rotates along its length", "geometry": {"shape": "cylinder", "size": [1.2, 0.25, 0.25], "position": [0, 0.45, 0]}, "motion": {"type": "rotate", "axis": [1, 0, 0], "speed": 1.4} } ] } Optional geometry fields: rotation, teeth, coils, wire, color. Optional motion fields: axis, speed, amplitude, phase, range, pitch, pivot. Include optional fields only when useful.""" def build_vision_user_prompt() -> str: return ( "Analyze the hardware component in this photo as a cutaway mechanism. " "Return the analysis JSON for the visible component." ) def build_vision_prompt() -> str: """Backward-compatible alias for the per-image user prompt.""" return build_vision_user_prompt() def build_vision_messages() -> tuple[str, str]: return VISION_SYSTEM_PROMPT, build_vision_user_prompt()