fix(llm): skip unsupported json_object mode for openrouter stepfun models

This commit is contained in:
2026-02-24 14:22:08 +03:00
parent 7e6bd56488
commit 4fd9d6b6d5

View File

@@ -437,6 +437,26 @@ class LLMClient:
self.client = AsyncOpenAI(api_key=api_key, base_url=base_url) self.client = AsyncOpenAI(api_key=api_key, base_url=base_url)
# [/DEF:LLMClient.__init__:Function] # [/DEF:LLMClient.__init__:Function]
# [DEF:LLMClient._supports_json_response_format:Function]
# @PURPOSE: Detect whether provider/model is likely compatible with response_format=json_object.
# @PRE: Client initialized with base_url and default_model.
# @POST: Returns False for known-incompatible combinations to avoid avoidable 400 errors.
def _supports_json_response_format(self) -> bool:
base = (self.base_url or "").lower()
model = (self.default_model or "").lower()
# OpenRouter routes to many upstream providers; some models reject json_object mode.
if "openrouter.ai" in base:
incompatible_tokens = (
"stepfun/",
"step-",
":free",
)
if any(token in model for token in incompatible_tokens):
return False
return True
# [/DEF:LLMClient._supports_json_response_format:Function]
# [DEF:LLMClient.get_json_completion:Function] # [DEF:LLMClient.get_json_completion:Function]
# @PURPOSE: Helper to handle LLM calls with JSON mode and fallback parsing. # @PURPOSE: Helper to handle LLM calls with JSON mode and fallback parsing.
# @PRE: messages is a list of valid message dictionaries. # @PRE: messages is a list of valid message dictionaries.
@@ -460,19 +480,34 @@ class LLMClient:
with belief_scope("get_json_completion"): with belief_scope("get_json_completion"):
response = None response = None
try: try:
use_json_mode = self._supports_json_response_format()
try: try:
logger.info(f"[get_json_completion] Attempting LLM call with JSON mode for model: {self.default_model}") logger.info(
f"[get_json_completion] Attempting LLM call for model: {self.default_model} "
f"(json_mode={'on' if use_json_mode else 'off'})"
)
logger.info(f"[get_json_completion] Base URL being used: {self.base_url}") logger.info(f"[get_json_completion] Base URL being used: {self.base_url}")
logger.info(f"[get_json_completion] Number of messages: {len(messages)}") logger.info(f"[get_json_completion] Number of messages: {len(messages)}")
logger.info(f"[get_json_completion] API Key present: {bool(self.api_key and len(self.api_key) > 0)}") logger.info(f"[get_json_completion] API Key present: {bool(self.api_key and len(self.api_key) > 0)}")
if use_json_mode:
response = await self.client.chat.completions.create( response = await self.client.chat.completions.create(
model=self.default_model, model=self.default_model,
messages=messages, messages=messages,
response_format={"type": "json_object"} response_format={"type": "json_object"}
) )
else:
response = await self.client.chat.completions.create(
model=self.default_model,
messages=messages
)
except Exception as e: except Exception as e:
if "JSON mode is not enabled" in str(e) or "400" in str(e): if use_json_mode and (
"JSON mode is not enabled" in str(e)
or "json_object is not supported" in str(e).lower()
or "response_format" in str(e).lower()
or "400" in str(e)
):
logger.warning(f"[get_json_completion] JSON mode failed or not supported: {str(e)}. Falling back to plain text response.") logger.warning(f"[get_json_completion] JSON mode failed or not supported: {str(e)}. Falling back to plain text response.")
response = await self.client.chat.completions.create( response = await self.client.chat.completions.create(
model=self.default_model, model=self.default_model,