Skip to content

fix(copilot_auth): tolerate missing object/index in chat completions - #23

Open
snair20-1984 wants to merge 5 commits into
mpfaffenberger:mainfrom
snair20-1984:bug/copilot_openai
Open

fix(copilot_auth): tolerate missing object/index in chat completions#23
snair20-1984 wants to merge 5 commits into
mpfaffenberger:mainfrom
snair20-1984:bug/copilot_openai

Conversation

@snair20-1984

Copy link
Copy Markdown

GitHub Copilot's /chat/completions returns a body that is almost OpenAI-shaped but omits two purely cosmetic fields: the top-level "object": "chat.completion" discriminator and the per-choice "index".

The OpenAI SDK builds ChatCompletion via model_construct and performs no validation, so both fields silently arrive as None. pydantic-ai then runs a strict _ChatCompletion.model_validate() in _process_response and raises UnexpectedModelBehavior, discarding a response whose message content was perfectly valid.

This only affects non-streaming calls -- the streaming path parses lenient ChatCompletionChunks -- so normal chat was unaffected while /compact failed every time on Copilot models. Compaction reported success with 0% token reduction because the summariser is the only bare agent.run() call in the codebase.

Subclass OpenAIChatModel and backfill the two fields via _validate_completion, which pydantic-ai documents as the hook for custom completion validation. Existing non-None values are never overwritten, so a spec-compliant response passes through untouched, and any future non-streaming caller is covered by the same seam.

Affects all Copilot models routed through Chat Completions, not just the Claude family.

Fixes #22

GitHub Copilot's /chat/completions returns a body that is almost
OpenAI-shaped but omits two purely cosmetic fields: the top-level
"object": "chat.completion" discriminator and the per-choice "index".

The OpenAI SDK builds ChatCompletion via model_construct and performs no
validation, so both fields silently arrive as None. pydantic-ai then runs
a strict _ChatCompletion.model_validate() in _process_response and raises
UnexpectedModelBehavior, discarding a response whose message content was
perfectly valid.

This only affects non-streaming calls -- the streaming path parses lenient
ChatCompletionChunks -- so normal chat was unaffected while /compact failed
every time on Copilot models. Compaction reported success with 0% token
reduction because the summariser is the only bare agent.run() call in the
codebase.

Subclass OpenAIChatModel and backfill the two fields via
_validate_completion, which pydantic-ai documents as the hook for custom
completion validation. Existing non-None values are never overwritten, so
a spec-compliant response passes through untouched, and any future
non-streaming caller is covered by the same seam.

Affects all Copilot models routed through Chat Completions, not just the
Claude family.
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.

/compact fails with UnexpectedModelBehavior on GitHub Copilot models (non-streaming summarization call)

1 participant