Raise LLMOutputUnparseable on empty LLM responses instead of silently returning ""

When Azure OpenAI or Anthropic returns null/empty content (content
filter, truncation, transient failure), call_openai/call_anthropic now
raise LLMOutputUnparseable instead of returning an empty string that
silently flows through the pipeline and produces 422 "Could not
generate any optimizations." All optimizer callers catch
LLMOutputUnparseable to preserve cost tracking while returning None.
This commit is contained in:
Kevin Turcios 2026-04-21 05:59:07 -05:00
parent 7355b05663
commit 9b3cd48048
8 changed files with 53 additions and 8 deletions

View file

@ -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,

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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