fix: handle wierd empty finish content as error

This commit is contained in:
Alex committed 2026-07-21 12:03:44 +01:00
1 parent b1ded11432
commit 196e4e86d2
2 files changed
+44

No files matched your search

@@ -33,6 +33,25 @@ TERMINATED_RESPONSE_PLACEHOLDER = (
)
def _final_finish_reason(llm: Any) -> Optional[str]:
"""Best-effort finish_reason of the round that actually produced the answer.
Read from the responding provider — the fallback instance when a fallback
served the stream, else the primary. Set by the Responses path, the
non-streaming path, and (now) the chat-completions stream. ``None`` when
unavailable (e.g. the stream was cut before any finish frame).
"""
if llm is None:
return None
fallback = getattr(llm, "_fallback_llm", None)
responding = getattr(llm, "_responding_provider", None)
if fallback is not None and responding == getattr(fallback, "provider_name", None):
fr = getattr(fallback, "_last_finish_reason", None)
if fr is not None:
return fr
return getattr(llm, "_last_finish_reason", None)
class ConversationService:
def get_conversation(
self, conversation_id: str, user_id: str
@@ -112,6 +131,25 @@ class ConversationService:
if metadata:
message_payload["metadata"] = metadata
# Classify empty answers at the point of persistence. A tool-request
# turn (finish_reason=tool_calls — the /v1 client-tool loop) and a
# genuine dead-end (finish_reason=stop with no visible content) both
# persist an empty ``response`` and are otherwise indistinguishable in
# the stored row; log the discriminators so they can be told apart.
if not (response or "").strip():
logger.info(
"empty_answer_persisted",
extra={
"model": model_id,
"finish_reason": _final_finish_reason(llm),
"has_tool_calls": bool(tool_calls),
"tool_call_count": len(tool_calls or []),
"thought_len": len(thought or ""),
"responding_provider": getattr(llm, "_responding_provider", None),
"conversation_id": conversation_id,
},
)
if conversation_id is not None and index is not None:
with db_session() as conn:
repo = ConversationsRepository(conn)
+6
View File
@@ -536,6 +536,7 @@ class OpenAILLM(BaseLLM):
request_params["stream_options"] = stream_options
self._last_usage = None
self._stream_reached_finish = False
self._last_finish_reason = None
response = self.client.chat.completions.create(**request_params)
try:
@@ -564,6 +565,11 @@ class OpenAILLM(BaseLLM):
# this to refuse restreaming a delivered answer when a
# trailing frame fails.
self._stream_reached_finish = True
# Capture the reason itself (not just the bool) so the save
# path can classify an empty answer: a ``stop`` with no
# content is a genuine dead-end, ``tool_calls`` is a tool
# request. Previously the chat path discarded this.
self._last_finish_reason = finish_reason
# Yield non-content chunks only when needed for tool-call handling.
if has_tool_calls or finish_reason == "tool_calls":