diff --git a/django/aiservice/aiservice/llm.py b/django/aiservice/aiservice/llm.py index e41831830..ceeb6ea71 100644 --- a/django/aiservice/aiservice/llm.py +++ b/django/aiservice/aiservice/llm.py @@ -198,6 +198,17 @@ class LLMClient: response = await self.anthropic_client.messages.create(**kwargs) # type: ignore[union-attr] content = "".join(block.text for block in response.content if hasattr(block, "text")) + if not content: + logger.warning( + "Anthropic returned empty content: model=%s, stop_reason=%s", + llm.name, + response.stop_reason, + ) + raise LLMOutputUnparseable( + f"Empty response from {llm.name} (stop_reason={response.stop_reason})", + cost=calculate_llm_cost(response, llm), + ) + return LLMResponse( content=content, usage=LLMUsage(input_tokens=response.usage.input_tokens, output_tokens=response.usage.output_tokens), @@ -216,8 +227,21 @@ class LLMClient: model=llm.name, messages=messages, max_tokens=max_tokens ) + content = response.choices[0].message.content if response.choices else None + if not content: + finish = response.choices[0].finish_reason if response.choices else "unknown" + logger.warning( + "OpenAI returned empty content: model=%s, finish_reason=%s", + llm.name, + finish, + ) + raise LLMOutputUnparseable( + f"Empty response from {llm.name} (finish_reason={finish})", + cost=calculate_llm_cost(response, llm), + ) + return LLMResponse( - content=response.choices[0].message.content or "", + content=content, usage=LLMUsage( input_tokens=response.usage.prompt_tokens if response.usage else 0, output_tokens=response.usage.completion_tokens if response.usage else 0, diff --git a/django/aiservice/core/languages/java/optimizer.py b/django/aiservice/core/languages/java/optimizer.py index 583703f38..24970e6c0 100644 --- a/django/aiservice/core/languages/java/optimizer.py +++ b/django/aiservice/core/languages/java/optimizer.py @@ -17,7 +17,7 @@ from openai.types.chat import ChatCompletionSystemMessageParam, ChatCompletionUs from aiservice.analytics.posthog import ph from aiservice.common_utils import validate_trace_id from aiservice.env_specific import debug_log_sensitive_data, debug_log_sensitive_data_from_callable -from aiservice.llm import llm_client +from aiservice.llm import LLMOutputUnparseable, llm_client from aiservice.llm_models import LLM, OPTIMIZE_MODEL from authapp.auth import AuthenticatedRequest from authapp.user import get_user_by_id @@ -119,6 +119,9 @@ async def optimize_java_code_single( python_version="N/A", # Not applicable for Java context=obs_context, ) + except LLMOutputUnparseable as e: + debug_log_sensitive_data(f"Empty LLM response for Java source:\n{source_code}") + return None, e.cost, optimize_model.name except Exception: debug_log_sensitive_data(f"Failed to generate code for Java source:\n{source_code}") return None, None, optimize_model.name diff --git a/django/aiservice/core/languages/java/optimizer_lp.py b/django/aiservice/core/languages/java/optimizer_lp.py index 0e37856b6..259eb15dc 100644 --- a/django/aiservice/core/languages/java/optimizer_lp.py +++ b/django/aiservice/core/languages/java/optimizer_lp.py @@ -17,7 +17,7 @@ from openai.types.chat import ChatCompletionSystemMessageParam, ChatCompletionUs from aiservice.analytics.posthog import ph from aiservice.env_specific import debug_log_sensitive_data, debug_log_sensitive_data_from_callable -from aiservice.llm import llm_client +from aiservice.llm import LLMOutputUnparseable, llm_client from aiservice.llm_models import OPTIMIZE_MODEL from aiservice.validators.java_validator import validate_java_syntax from core.languages.java.optimizer import is_multi_context_java @@ -166,6 +166,9 @@ Here is the code to optimize: python_version=f"Java {language_version}", context=obs_context, ) + except LLMOutputUnparseable as e: + debug_log_sensitive_data(f"Empty LLM response for source:\n{source_code}") + return None, e.cost, optimize_model.name except Exception: debug_log_sensitive_data(f"Failed to generate code for source:\n{source_code}") return None, None, optimize_model.name diff --git a/django/aiservice/core/languages/js_ts/optimizer.py b/django/aiservice/core/languages/js_ts/optimizer.py index d0e6bcee7..27edcdfe4 100644 --- a/django/aiservice/core/languages/js_ts/optimizer.py +++ b/django/aiservice/core/languages/js_ts/optimizer.py @@ -19,7 +19,7 @@ from aiservice.analytics.posthog import ph from aiservice.common.markdown_utils import split_markdown_code from aiservice.common_utils import validate_trace_id from aiservice.env_specific import debug_log_sensitive_data, debug_log_sensitive_data_from_callable -from aiservice.llm import llm_client +from aiservice.llm import LLMOutputUnparseable, llm_client from aiservice.llm_models import LLM, OPTIMIZE_MODEL from aiservice.validators.javascript_validator import validate_javascript_syntax, validate_typescript_syntax from authapp.auth import AuthenticatedRequest @@ -155,6 +155,9 @@ You MUST output the target file. You may also output helper files if you optimiz python_version=language_version, # Reusing python_version field for language version context=obs_context, ) + except LLMOutputUnparseable as e: + debug_log_sensitive_data(f"Empty LLM response for source:\n{source_code}") + return None, e.cost, optimize_model.name except Exception: debug_log_sensitive_data(f"Failed to generate code for source:\n{source_code}") return None, None, optimize_model.name diff --git a/django/aiservice/core/languages/js_ts/optimizer_lp.py b/django/aiservice/core/languages/js_ts/optimizer_lp.py index b4a73382e..2c6ca6a9b 100644 --- a/django/aiservice/core/languages/js_ts/optimizer_lp.py +++ b/django/aiservice/core/languages/js_ts/optimizer_lp.py @@ -18,7 +18,7 @@ from openai.types.chat import ChatCompletionSystemMessageParam, ChatCompletionUs from aiservice.analytics.posthog import ph from aiservice.common.markdown_utils import split_markdown_code from aiservice.env_specific import debug_log_sensitive_data, debug_log_sensitive_data_from_callable -from aiservice.llm import llm_client +from aiservice.llm import LLMOutputUnparseable, llm_client from aiservice.llm_models import OPTIMIZE_MODEL from aiservice.validators.javascript_validator import validate_javascript_syntax, validate_typescript_syntax from core.languages.js_ts.context_helpers import is_multi_context_js, is_multi_context_ts @@ -167,6 +167,9 @@ Here is the code to optimize: python_version=language_version, # Reusing python_version field for language version context=obs_context, ) + except LLMOutputUnparseable as e: + debug_log_sensitive_data(f"Empty LLM response for source:\n{source_code}") + return None, e.cost, optimize_model.name except Exception: debug_log_sensitive_data(f"Failed to generate code for source:\n{source_code}") return None, None, optimize_model.name diff --git a/django/aiservice/core/languages/python/jit_rewrite/jit_rewrite.py b/django/aiservice/core/languages/python/jit_rewrite/jit_rewrite.py index 163209785..0a7835bc6 100644 --- a/django/aiservice/core/languages/python/jit_rewrite/jit_rewrite.py +++ b/django/aiservice/core/languages/python/jit_rewrite/jit_rewrite.py @@ -16,7 +16,7 @@ from aiservice.analytics.posthog import ph from aiservice.background import fire_and_forget from aiservice.common_utils import parse_python_version, validate_trace_id from aiservice.env_specific import debug_log_sensitive_data, debug_log_sensitive_data_from_callable -from aiservice.llm import llm_client +from aiservice.llm import LLMOutputUnparseable, llm_client from aiservice.llm_models import LLM, OPTIMIZE_MODEL from authapp.auth import AuthenticatedRequest from authapp.user import get_user_by_id @@ -74,6 +74,9 @@ async def jit_rewrite_python_code_single( python_version=python_version_str, context=obs_context, ) + except LLMOutputUnparseable as e: + debug_log_sensitive_data(f"Empty LLM response for source:\n{ctx.source_code}") + return None, e.cost, jit_rewrite_model.name except Exception: debug_log_sensitive_data(f"Failed to generate code for source:\n{ctx.source_code}") return None, None, jit_rewrite_model.name diff --git a/django/aiservice/core/languages/python/optimizer/optimizer.py b/django/aiservice/core/languages/python/optimizer/optimizer.py index 70227feeb..ee2688dfa 100644 --- a/django/aiservice/core/languages/python/optimizer/optimizer.py +++ b/django/aiservice/core/languages/python/optimizer/optimizer.py @@ -15,7 +15,7 @@ from aiservice.analytics.posthog import ph from aiservice.background import fire_and_forget from aiservice.common_utils import parse_python_version, validate_trace_id from aiservice.env_specific import debug_log_sensitive_data, debug_log_sensitive_data_from_callable -from aiservice.llm import llm_client +from aiservice.llm import LLMOutputUnparseable, llm_client from aiservice.llm_models import LLM, OPTIMIZE_MODEL from authapp.user import get_user_by_id from core.languages.python.optimizer.context_utils.optimizer_context import BaseOptimizerContext @@ -82,6 +82,9 @@ async def generate_optimization_candidate( python_version=python_version_str, context=obs_context, ) + except LLMOutputUnparseable as e: + debug_log_sensitive_data(f"Empty LLM response for source:\n{ctx.source_code}") + return None, e.cost, optimize_model.name except Exception: debug_log_sensitive_data(f"Failed to generate code for source:\n{ctx.source_code}") return None, None, optimize_model.name diff --git a/django/aiservice/core/languages/python/optimizer/optimizer_line_profiler.py b/django/aiservice/core/languages/python/optimizer/optimizer_line_profiler.py index d0c86bd65..1d059c41f 100644 --- a/django/aiservice/core/languages/python/optimizer/optimizer_line_profiler.py +++ b/django/aiservice/core/languages/python/optimizer/optimizer_line_profiler.py @@ -13,7 +13,7 @@ from aiservice.background import fire_and_forget from aiservice.common.markdown_utils import split_markdown_code from aiservice.common_utils import parse_python_version, validate_trace_id from aiservice.env_specific import debug_log_sensitive_data, debug_log_sensitive_data_from_callable -from aiservice.llm import llm_client +from aiservice.llm import LLMOutputUnparseable, llm_client from aiservice.llm_models import OPTIMIZE_MODEL from aiservice.validators.javascript_validator import validate_javascript_syntax, validate_typescript_syntax from core.languages.java.optimizer_lp import optimize_java_code_line_profiler @@ -90,6 +90,9 @@ async def optimize_python_code_line_profiler_single( python_version=python_version_str, context=obs_context, ) + except LLMOutputUnparseable as e: + debug_log_sensitive_data(f"Empty LLM response for source:\n{ctx.source_code}") + return None, e.cost, optimize_model.name except Exception: debug_log_sensitive_data(f"Failed to generate code for source:\n{ctx.source_code}") return None, None, optimize_model.name