feat: Support structured output parsed from OpenAI
Added support for structured output (JSON mode) from the OpenAI API in `openai.py` and `azure_openai.py`. When `response_format` is used to request structured data, the new logic checks for the `message.parsed` attribute. If it exists, it's serialized into a JSON string as the final content. If not, the code falls back to the existing `message.content` handling, ensuring backward compatibility.
This commit is contained in:
parent
c9e1c86e81
commit
9f69c5bf85
2 changed files with 57 additions and 37 deletions
|
|
@ -113,9 +113,20 @@ async def azure_openai_complete_if_cache(
|
||||||
|
|
||||||
return inner()
|
return inner()
|
||||||
else:
|
else:
|
||||||
content = response.choices[0].message.content
|
message = response.choices[0].message
|
||||||
if r"\u" in content:
|
|
||||||
content = safe_unicode_decode(content.encode("utf-8"))
|
# Handle parsed responses (structured output via response_format)
|
||||||
|
# When using beta.chat.completions.parse(), the response is in message.parsed
|
||||||
|
if hasattr(message, "parsed") and message.parsed is not None:
|
||||||
|
# Serialize the parsed structured response to JSON
|
||||||
|
content = message.parsed.model_dump_json()
|
||||||
|
logger.debug("Using parsed structured response from API")
|
||||||
|
else:
|
||||||
|
# Handle regular content responses
|
||||||
|
content = message.content
|
||||||
|
if content and r"\u" in content:
|
||||||
|
content = safe_unicode_decode(content.encode("utf-8"))
|
||||||
|
|
||||||
return content
|
return content
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -453,46 +453,55 @@ async def openai_complete_if_cache(
|
||||||
raise InvalidResponseError("Invalid response from OpenAI API")
|
raise InvalidResponseError("Invalid response from OpenAI API")
|
||||||
|
|
||||||
message = response.choices[0].message
|
message = response.choices[0].message
|
||||||
content = getattr(message, "content", None)
|
|
||||||
reasoning_content = getattr(message, "reasoning_content", "")
|
# Handle parsed responses (structured output via response_format)
|
||||||
|
# When using beta.chat.completions.parse(), the response is in message.parsed
|
||||||
|
if hasattr(message, "parsed") and message.parsed is not None:
|
||||||
|
# Serialize the parsed structured response to JSON
|
||||||
|
final_content = message.parsed.model_dump_json()
|
||||||
|
logger.debug("Using parsed structured response from API")
|
||||||
|
else:
|
||||||
|
# Handle regular content responses
|
||||||
|
content = getattr(message, "content", None)
|
||||||
|
reasoning_content = getattr(message, "reasoning_content", "")
|
||||||
|
|
||||||
# Handle COT logic for non-streaming responses (only if enabled)
|
# Handle COT logic for non-streaming responses (only if enabled)
|
||||||
final_content = ""
|
final_content = ""
|
||||||
|
|
||||||
if enable_cot:
|
if enable_cot:
|
||||||
# Check if we should include reasoning content
|
# Check if we should include reasoning content
|
||||||
should_include_reasoning = False
|
should_include_reasoning = False
|
||||||
if reasoning_content and reasoning_content.strip():
|
if reasoning_content and reasoning_content.strip():
|
||||||
if not content or content.strip() == "":
|
if not content or content.strip() == "":
|
||||||
# Case 1: Only reasoning content, should include COT
|
# Case 1: Only reasoning content, should include COT
|
||||||
should_include_reasoning = True
|
should_include_reasoning = True
|
||||||
final_content = (
|
final_content = (
|
||||||
content or ""
|
content or ""
|
||||||
) # Use empty string if content is None
|
) # Use empty string if content is None
|
||||||
|
else:
|
||||||
|
# Case 3: Both content and reasoning_content present, ignore reasoning
|
||||||
|
should_include_reasoning = False
|
||||||
|
final_content = content
|
||||||
else:
|
else:
|
||||||
# Case 3: Both content and reasoning_content present, ignore reasoning
|
# No reasoning content, use regular content
|
||||||
should_include_reasoning = False
|
final_content = content or ""
|
||||||
final_content = content
|
|
||||||
|
# Apply COT wrapping if needed
|
||||||
|
if should_include_reasoning:
|
||||||
|
if r"\u" in reasoning_content:
|
||||||
|
reasoning_content = safe_unicode_decode(
|
||||||
|
reasoning_content.encode("utf-8")
|
||||||
|
)
|
||||||
|
final_content = f"<think>{reasoning_content}</think>{final_content}"
|
||||||
else:
|
else:
|
||||||
# No reasoning content, use regular content
|
# COT disabled, only use regular content
|
||||||
final_content = content or ""
|
final_content = content or ""
|
||||||
|
|
||||||
# Apply COT wrapping if needed
|
# Validate final content
|
||||||
if should_include_reasoning:
|
if not final_content or final_content.strip() == "":
|
||||||
if r"\u" in reasoning_content:
|
logger.error("Received empty content from OpenAI API")
|
||||||
reasoning_content = safe_unicode_decode(
|
await openai_async_client.close() # Ensure client is closed
|
||||||
reasoning_content.encode("utf-8")
|
raise InvalidResponseError("Received empty content from OpenAI API")
|
||||||
)
|
|
||||||
final_content = f"<think>{reasoning_content}</think>{final_content}"
|
|
||||||
else:
|
|
||||||
# COT disabled, only use regular content
|
|
||||||
final_content = content or ""
|
|
||||||
|
|
||||||
# Validate final content
|
|
||||||
if not final_content or final_content.strip() == "":
|
|
||||||
logger.error("Received empty content from OpenAI API")
|
|
||||||
await openai_async_client.close() # Ensure client is closed
|
|
||||||
raise InvalidResponseError("Received empty content from OpenAI API")
|
|
||||||
|
|
||||||
# Apply Unicode decoding to final content if needed
|
# Apply Unicode decoding to final content if needed
|
||||||
if r"\u" in final_content:
|
if r"\u" in final_content:
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue