# [DEF:backend.src.services.llm_prompt_templates:Module] # @TIER: STANDARD # @SEMANTICS: llm, prompts, templates, settings # @PURPOSE: Provide default LLM prompt templates and normalization helpers for runtime usage. # @LAYER: Domain # @RELATION: DEPENDS_ON -> backend.src.core.config_manager # @INVARIANT: All required prompt template keys are always present after normalization. from __future__ import annotations from copy import deepcopy from typing import Dict, Any, Optional # [DEF:DEFAULT_LLM_PROMPTS:Constant] # @TIER: STANDARD # @PURPOSE: Default prompt templates used by documentation, dashboard validation, and git commit generation. DEFAULT_LLM_PROMPTS: Dict[str, str] = { "dashboard_validation_prompt": ( "Analyze the attached dashboard screenshot and the following execution logs for health and visual issues.\n\n" "Logs:\n" "{logs}\n\n" "Provide the analysis in JSON format with the following structure:\n" "{\n" ' "status": "PASS" | "WARN" | "FAIL",\n' ' "summary": "Short summary of findings",\n' ' "issues": [\n' " {\n" ' "severity": "WARN" | "FAIL",\n' ' "message": "Description of the issue",\n' ' "location": "Optional location info (e.g. chart name)"\n' " }\n" " ]\n" "}" ), "documentation_prompt": ( "Generate professional documentation for the following dataset and its columns.\n" "Dataset: {dataset_name}\n" "Columns: {columns_json}\n\n" "Provide the documentation in JSON format:\n" "{\n" ' "dataset_description": "General description of the dataset",\n' ' "column_descriptions": [\n' " {\n" ' "name": "column_name",\n' ' "description": "Generated description"\n' " }\n" " ]\n" "}" ), "git_commit_prompt": ( "Generate a concise and professional git commit message based on the following diff and recent history.\n" "Use Conventional Commits format (e.g., feat: ..., fix: ..., docs: ...).\n\n" "Recent History:\n" "{history}\n\n" "Diff:\n" "{diff}\n\n" "Commit Message:" ), } # [/DEF:DEFAULT_LLM_PROMPTS:Constant] # [DEF:DEFAULT_LLM_PROVIDER_BINDINGS:Constant] # @TIER: STANDARD # @PURPOSE: Default provider binding per task domain. DEFAULT_LLM_PROVIDER_BINDINGS: Dict[str, str] = { "dashboard_validation": "", "documentation": "", "git_commit": "", } # [/DEF:DEFAULT_LLM_PROVIDER_BINDINGS:Constant] # [DEF:DEFAULT_LLM_ASSISTANT_SETTINGS:Constant] # @TIER: STANDARD # @PURPOSE: Default planner settings for assistant chat intent model/provider resolution. DEFAULT_LLM_ASSISTANT_SETTINGS: Dict[str, str] = { "assistant_planner_provider": "", "assistant_planner_model": "", } # [/DEF:DEFAULT_LLM_ASSISTANT_SETTINGS:Constant] # [DEF:normalize_llm_settings:Function] # @TIER: STANDARD # @PURPOSE: Ensure llm settings contain stable schema with prompts section and default templates. # @PRE: llm_settings is dictionary-like value or None. # @POST: Returned dict contains prompts with all required template keys. def normalize_llm_settings(llm_settings: Any) -> Dict[str, Any]: normalized: Dict[str, Any] = { "providers": [], "default_provider": "", "prompts": {}, "provider_bindings": {}, **DEFAULT_LLM_ASSISTANT_SETTINGS, } if isinstance(llm_settings, dict): normalized.update( { k: v for k, v in llm_settings.items() if k in ( "providers", "default_provider", "prompts", "provider_bindings", "assistant_planner_provider", "assistant_planner_model", ) } ) prompts = normalized.get("prompts") if isinstance(normalized.get("prompts"), dict) else {} merged_prompts = deepcopy(DEFAULT_LLM_PROMPTS) merged_prompts.update({k: v for k, v in prompts.items() if isinstance(v, str) and v.strip()}) normalized["prompts"] = merged_prompts bindings = normalized.get("provider_bindings") if isinstance(normalized.get("provider_bindings"), dict) else {} merged_bindings = deepcopy(DEFAULT_LLM_PROVIDER_BINDINGS) merged_bindings.update({k: v for k, v in bindings.items() if isinstance(v, str)}) normalized["provider_bindings"] = merged_bindings for key, default_value in DEFAULT_LLM_ASSISTANT_SETTINGS.items(): value = normalized.get(key, default_value) normalized[key] = value.strip() if isinstance(value, str) else default_value return normalized # [/DEF:normalize_llm_settings:Function] # [DEF:is_multimodal_model:Function] # @TIER: STANDARD # @PURPOSE: Heuristically determine whether model supports image input required for dashboard validation. # @PRE: model_name may be empty or mixed-case. # @POST: Returns True when model likely supports multimodal input. def is_multimodal_model(model_name: str, provider_type: Optional[str] = None) -> bool: token = (model_name or "").strip().lower() if not token: return False provider = (provider_type or "").strip().lower() text_only_markers = ( "text-only", "embedding", "rerank", "whisper", "tts", "transcribe", ) if any(marker in token for marker in text_only_markers): return False multimodal_markers = ( "gpt-4o", "gpt-4.1", "vision", "vl", "gemini", "claude-3", "claude-sonnet-4", "omni", "multimodal", "pixtral", "llava", "internvl", "qwen-vl", "qwen2-vl", ) if any(marker in token for marker in multimodal_markers): return True return False # [/DEF:is_multimodal_model:Function] # [DEF:resolve_bound_provider_id:Function] # @TIER: STANDARD # @PURPOSE: Resolve provider id configured for a task binding with fallback to default provider. # @PRE: llm_settings is normalized or raw dict from config. # @POST: Returns configured provider id or fallback id/empty string when not defined. def resolve_bound_provider_id(llm_settings: Any, task_key: str) -> str: normalized = normalize_llm_settings(llm_settings) bindings = normalized.get("provider_bindings", {}) bound = bindings.get(task_key) if isinstance(bound, str) and bound.strip(): return bound.strip() default_provider = normalized.get("default_provider", "") return default_provider.strip() if isinstance(default_provider, str) else "" # [/DEF:resolve_bound_provider_id:Function] # [DEF:render_prompt:Function] # @TIER: STANDARD # @PURPOSE: Render prompt template using deterministic placeholder replacement with graceful fallback. # @PRE: template is a string and variables values are already stringifiable. # @POST: Returns rendered prompt text with known placeholders substituted. def render_prompt(template: str, variables: Dict[str, Any]) -> str: rendered = template for key, value in variables.items(): rendered = rendered.replace("{" + key + "}", str(value)) return rendered # [/DEF:render_prompt:Function] # [/DEF:backend.src.services.llm_prompt_templates:Module]