"""Claude prompt step — call LLM via LiteLLM proxy.""" import logging logger = logging.getLogger(__name__) async def execute_claude_prompt( config: dict, context: dict | None = None, llm=None, default_model: str = "claude-haiku", escalation_model: str = "claude-sonnet", **_kwargs, ) -> str: """Send a prompt to Claude and return the response. Supports vision: if config contains 'image_b64' or trigger context has 'image_b64', the image is included as a vision content block. """ if not llm: raise RuntimeError("LLM client not configured") prompt = config.get("prompt", "") if not prompt: raise ValueError("claude_prompt step requires 'prompt' field") model_name = config.get("model", "default") model = escalation_model if model_name == "escalation" else default_model # Check for image data (from config or trigger context) image_b64 = config.get("image_b64", "") image_mime = config.get("image_mime", "image/png") if not image_b64 and context: trigger = context.get("trigger", {}) image_b64 = trigger.get("image_b64", "") image_mime = trigger.get("mime_type", "image/png") # Build message content if image_b64: content = [ { "type": "image_url", "image_url": { "url": f"data:{image_mime};base64,{image_b64}", }, }, { "type": "text", "text": prompt, }, ] else: content = prompt response = await llm.chat.completions.create( model=model, messages=[{"role": "user", "content": content}], max_tokens=4096, ) return response.choices[0].message.content or ""