Surface web-search url_citation annotations on the OpenAI Chat Completions path like the Responses provider#700
Conversation
The Chat Completions non-streaming conversion built TextContent only from choice.Message.Content and ignored choice.Message.Annotations, so the url_citation entries emitted by the web-search tool were dropped. The Responses provider already maps these via populateAnnotations, so citations were surfaced there but not on the Chat Completions path. Populate the TextContent's Annotations with message.CitationAnnotation for each url_citation, mirroring the Responses path and the .NET/Python SDKs which expose web-search citations on both surfaces.
e601174 to
21c552a
Compare
There was a problem hiding this comment.
Pull request overview
Restores parity between the OpenAI Chat Completions and Responses provider surfaces by propagating web-search url_citation annotations into emitted message.TextContent.Annotations on the non-streaming Chat Completions path.
Changes:
- Populate
TextContent.Annotationsfromchoice.Message.Annotationsfor non-streaming Chat Completions responses. - Add a targeted non-streaming test that verifies
url_citationannotations are surfaced as*message.CitationAnnotation.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| provider/openaiprovider/chat.go | Adds populateChatAnnotations and wires it into the non-streaming Chat Completions message-to-content conversion. |
| provider/openaiprovider/chat_test.go | Adds a regression test covering non-streaming url_citation annotation propagation. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| for _, ann := range anns { | ||
| content.Annotations = append(content.Annotations, &message.CitationAnnotation{ | ||
| URL: ann.URLCitation.URL, | ||
| Title: ann.URLCitation.Title, | ||
| RawRepresentation: ann, |
There was a problem hiding this comment.
Good catch — fixed in 62a8607: populateChatAnnotations now skips any annotation that isn't a url_citation or has an empty URLCitation.URL, so no bogus empty citations are emitted. This mirrors the type-filtering the Responses path does in populateAnnotations. Added a chat_test.go case locking the empty-URL skip.
This comment has been minimized.
This comment has been minimized.
Parity Review — ✅ ApprovedThis PR is a parity fix, not a divergence. The change adds an unexported helper Cross-SDK parity
API surfaceNo new exported Go identifiers were added. The Warning Firewall blocked 1 domainThe following domain was blocked by the firewall during workflow execution:
network:
allowed:
- defaults
- "awmgmcpg"See Network Configuration for more information.
|
What
The OpenAI Chat Completions non-streaming conversion built
TextContentonly fromchoice.Message.Contentand never readchoice.Message.Annotations. As a result, theurl_citationannotations that the web-search tool emits (the SDK'sChatCompletionMessageAnnotationwithURLCitation{URL, Title, StartIndex, EndIndex}) were silently dropped, even thoughbuildCompletionParamsenables the web-search tool for*hostedtool.WebSearch.This adds a small
populateChatAnnotationshelper that maps eachurl_citationonto theTextContent'sAnnotationsas a*message.CitationAnnotation{URL, Title, RawRepresentation}, mirroringpopulateAnnotationson the Responses path.Why
The Responses provider already surfaces web-search citations via
populateAnnotations(ResponseOutputTextAnnotationURLCitation->*message.CitationAnnotation) on both its streaming and non-streaming paths. The Chat Completions path was the only surface that discarded them, an inconsistency across the two OpenAI surfaces. The .NET and Python SDKs expose web-search citations on both the Chat Completions and Responses surfaces, so this restores cross-SDK and cross-surface parity.Scope
Scoped to
provider/openaiprovider/chat.goand its canonical test file.responses.gois intentionally untouched.Testing
Added
TestChatURLCitationAnnotations_NonStreaminginchat_test.go, a black-box test that drives a Chat Completion whose message carries aurl_citationannotation through the existing httptest harness and asserts the emittedTextContent.Annotationscontains a*message.CitationAnnotationwithURL == https://example.comandTitle == Example. The test fails before the change and passes after.go build ./...,go vet ./provider/openaiprovider/..., andgo test ./provider/openaiprovider/...are green.