Skip to content

Fix OpenAI API schema compatibility by flattening discriminated unions#53

Merged
JPrier merged 3 commits into
mainfrom
copilot/fix-2f075487-d726-4ae8-bad4-ea8aeaad55bd
Sep 28, 2025
Merged

Fix OpenAI API schema compatibility by flattening discriminated unions#53
JPrier merged 3 commits into
mainfrom
copilot/fix-2f075487-d726-4ae8-bad4-ea8aeaad55bd

Conversation

Copilot AI commented Sep 28, 2025

Copy link
Copy Markdown
Contributor

Fixes the OpenAI API error that occurred when using structured outputs with Pydantic discriminated unions. The CLI command treeagent --model-type openai "hello" was failing with:

Error code: 400 - {'error': {'message': "Invalid schema for response_format 'response': In context=('properties', 'response'), 'oneOf' is not permitted.", 'type': 'invalid_request_error', 'param': 'response_format', 'code': None}}

Problem

OpenAI's structured outputs API doesn't support oneOf or anyOf anywhere in JSON schemas, but Pydantic discriminated unions (like ModelResponse) generate schemas with oneOf at the root level:

{
  "oneOf": [
    {"$ref": "#/$defs/DecomposedResponse"},
    {"$ref": "#/$defs/ImplementedResponse"},
    {"$ref": "#/$defs/FollowUpResponse"},
    {"$ref": "#/$defs/FailedResponse"}
  ],
  "discriminator": {"propertyName": "type"}
}

The existing wrapping approach still placed oneOf inside properties.response, which OpenAI rejects.

Solution

Implemented a comprehensive schema flattening system that converts discriminated unions into OpenAI-compatible flat object schemas:

Key Changes

  1. Schema Flattening: The new _flatten_discriminated_union() method converts oneOf schemas by:

    • Merging all possible properties from union alternatives into a single object schema
    • Creating an enum constraint for the discriminator field (type)
    • Preserving complex $defs references for nested types
  2. Nullable Field Handling: Added _clean_oneof_anyof_recursive() to convert nullable fields from {"anyOf": [{"type": "string"}, {"type": "null"}]} to simple {"type": "string"} (optional)

  3. Comprehensive Cleaning: Recursively removes all oneOf/anyOf structures throughout the entire schema tree

  4. Response Processing: Updated _extract_response_from_openai_format() to work with flattened schemas (no unwrapping needed)

Before/After Example

Before (incompatible):

{
  "type": "object",
  "properties": {
    "response": {
      "oneOf": [...]  // ❌ OpenAI rejects this
    }
  }
}

After (compatible):

{
  "type": "object",
  "properties": {
    "type": {"type": "string", "enum": ["decomposed", "implemented", "follow_up_required", "failed"]},
    "content": {"type": "string"},
    "artifacts": {"type": "array", "items": {"type": "string"}},
    "subtasks": {"type": "array", "items": {"$ref": "#/$defs/Task"}},
    "follow_up_ask": {"$ref": "#/$defs/Task"},
    "error_message": {"type": "string"},
    "retryable": {"type": "boolean"}
  },
  "required": ["type"]
}

Testing

  • All existing OpenAI accessor tests updated and passing
  • Added comprehensive test case for full ModelResponse flattening
  • Verified compatibility with all response types (ClarifierResponse, DesignerResponse, TesterResponse)
  • End-to-end integration tests confirm the CLI scenario now works
  • Non-union schemas continue to work unchanged (no regressions)

The fix maintains full backward compatibility while enabling TreeAgent to work seamlessly with OpenAI's structured outputs API.

Original prompt

$ treeagent --model-type openai "hello"
2025-09-28 11:43:35,938 [INFO] Executing root-task (REQUIREMENTS) using openai
2025-09-28 11:43:37,604 [ERROR] Task root-task failed: Error code: 400 - {'error': {'message': "Invalid schema for response_format 'response': In context=('properties', 'response'), 'oneOf' is not permitted.", 'type': 'invalid_request_error', 'param': 'response_format', 'code': None}}
Project Summary:
Completed Tasks: 0
In Progress Tasks: 0
Failed Tasks: 1
Queued Tasks: 0


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Copilot AI and others added 2 commits September 28, 2025 15:54
Co-authored-by: JPrier <24302717+JPrier@users.noreply.github.com>
Co-authored-by: JPrier <24302717+JPrier@users.noreply.github.com>
Copilot AI changed the title $ treeagent --model-type openai "hello" 2025-09-28 11:43:35,938 [INFO] Executing root-task (REQUIREMENTS) using openai 2025-09-28 11:43:37,604 [ERROR] Task root-task failed: Error code: 400 - {'error': {'message': "Invalid schema for response_format 'r... Fix OpenAI API schema compatibility by flattening discriminated unions Sep 28, 2025
Copilot AI requested a review from JPrier September 28, 2025 15:59
@JPrier JPrier marked this pull request as ready for review September 28, 2025 16:06
@JPrier JPrier merged commit c8d765d into main Sep 28, 2025
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants