""" AI Summarization Engine for Code Storage Service (Phase 4.6.12 Hardening) Handles LLM-powered summary generation and action-oriented naming for extracted code blocks. """ import asyncio import json import os from typing import Any, cast from src.server.config.logfire_config import search_logger def _get_model_choice_logic() -> str: """Get MODEL_CHOICE with direct fallback from credentials or environment.""" try: from src.server.services.credential_service import credential_service if credential_service._cache_initialized and "MODEL_CHOICE" in credential_service._cache: model = credential_service._cache["MODEL_CHOICE"] else: model = os.getenv("MODEL_CHOICE") if not model: raise ValueError("MODEL_CHOICE is not configured in environment or credentials") return cast(str, model) except Exception as e: search_logger.error(f"Error getting model choice logic: {e}") raise def generate_code_example_summary_logic( code: str, context_before: str, context_after: str, language: str = "", provider: str | None = None, ) -> dict[str, str]: """ Generate a summary and name for a code example using its surrounding context. """ model_choice = _get_model_choice_logic() prompt = f""" {context_before[-500:] if len(context_before) > 500 else context_before} {code[:1500] if len(code) > 1500 else code} {context_after[:500] if len(context_after) > 500 else context_after} Based on the code example and its surrounding context, provide: 1. A concise, action-oriented name (1-4 words) that describes what this code DOES. 2. A summary (2-3 sentences) that describes what this code example demonstrates. Format your response as JSON: {{ "example_name": "Action-oriented name", "summary": "Description" }} """ try: import openai api_key = os.getenv("OPENAI_API_KEY") if not api_key: from src.server.services.credential_service import credential_service if credential_service._cache_initialized and "OPENAI_API_KEY" in credential_service._cache: cached_key = credential_service._cache["OPENAI_API_KEY"] if isinstance(cached_key, dict) and cached_key.get("is_encrypted"): from src.server.services.credentials.crypto_utils import CryptoUtils api_key = CryptoUtils.decrypt_value(cached_key["encrypted_value"]) else: api_key = cached_key else: api_key = os.getenv("OPENAI_API_KEY", "") if not api_key: raise ValueError("No OpenAI API key available for code summarization") client = openai.OpenAI(api_key=api_key) from src.server.services.prompt_service import prompt_service default_instruction = "You are a helpful assistant that analyzes code examples." system_prompt = prompt_service.get_prompt("CODE_EXAMPES_AUDITOR", default=default_instruction) response = client.chat.completions.create( model=model_choice, messages=[ { "role": "system", "content": system_prompt, }, {"role": "user", "content": prompt}, ], response_format={"type": "json_object"}, ) raw_content = response.choices[0].message.content response_content = raw_content.strip() if raw_content is not None else "" if not response_content: raise ValueError("LLM returned empty summary") result = json.loads(response_content) return { "example_name": result.get( "example_name", f"Code Example ({language})" if language else "Code Example", ), "summary": result.get("summary", "Code example for demonstration purposes."), } except Exception as e: search_logger.error(f"Summarization AI Error: {e}") return { "example_name": (f"Code Example ({language})" if language else "Code Example"), "summary": "Code example for demonstration purposes.", } async def generate_code_summaries_batch_logic( service_instance, code_blocks: list[dict[str, Any]], max_workers: int | None = None, progress_callback: Any = None, provider: str | None = None, ) -> list[dict[str, str]]: """ Generate summaries for a batch of code blocks concurrently. """ if not code_blocks: return [] # Get max_workers from settings if not provided if max_workers is None: try: from src.server.services.credential_service import credential_service if credential_service._cache_initialized and "CODE_SUMMARY_MAX_WORKERS" in credential_service._cache: max_workers = int(credential_service._cache["CODE_SUMMARY_MAX_WORKERS"]) else: max_workers = int(os.getenv("CODE_SUMMARY_MAX_WORKERS", "3")) except Exception: max_workers = 3 search_logger.info(f"Generating summaries for {len(code_blocks)} code blocks with max_workers={max_workers}") # Semaphore to limit concurrent requests semaphore = asyncio.Semaphore(max_workers) completed_count = 0 lock = asyncio.Lock() async def _sum_single(block: dict[str, Any]) -> dict[str, str]: nonlocal completed_count async with semaphore: # CPU/IO intensive LLM call to thread result = await asyncio.to_thread( generate_code_example_summary_logic, code=block["code"], context_before=block.get("context_before", ""), context_after=block.get("context_after", ""), language=block.get("language", ""), provider=provider, ) async with lock: completed_count += 1 if progress_callback: await progress_callback( { "status": "code_summarization", "log": f"Generated {completed_count}/{len(code_blocks)} code summaries", } ) return result tasks = [_sum_single(block) for block in code_blocks] results = await asyncio.gather(*tasks) return list(results)