Request file_search_call.results and surface the FileSearch tool-call output in the Responses provider - #731
Conversation
There was a problem hiding this comment.
Pull request overview
This PR updates the OpenAI Responses provider to properly request and surface file_search_call.results, ensuring FileSearch tool-call retrieved chunks are no longer silently discarded and can be consumed as message content (with citations), in line with other SDKs’ behavior.
Changes:
- Adds
file_search_call.resultstoparams.Includewhen the hosted FileSearch tool is configured, avoiding duplicate include entries. - Handles
responses.ResponseFileSearchToolCalloutput items in both non-streaming and streaming processing paths. - Adds unit tests covering non-streaming, streaming, and “does not duplicate include” scenarios for FileSearch.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| provider/openaiprovider/responses.go | Requests FileSearch results in include and surfaces file_search_call output items as TextContent with CitationAnnotation. |
| provider/openaiprovider/responses_test.go | Adds coverage to ensure file_search_call.results is requested and FileSearch results appear in collected/streamed message output without duplicate includes. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // fileSearchToolCallContents surfaces a file_search_call output item as message | ||
| // content. Each retrieved chunk becomes a TextContent carrying a CitationAnnotation | ||
| // (file ID, filename and snippet), so the queries/results are no longer dropped and | ||
| // only the file_citation annotations survive. The raw item (queries + status) is kept | ||
| // on RawRepresentation, matching the .NET/Python surfacing of FileSearch results. |
There was a problem hiding this comment.
Good catch — fixed in ee9c5c4: reworded the doc comment so the dropped queries/results are described as the previous behavior this helper fixes, not the current one.
| func fileSearchToolCallContents(item responses.ResponseFileSearchToolCall) []message.Content { | ||
| contents := make([]message.Content, 0, len(item.Results)) | ||
| for _, res := range item.Results { | ||
| textContent := &message.TextContent{ | ||
| ContentHeader: message.ContentHeader{RawRepresentation: item}, | ||
| Text: res.Text, | ||
| } | ||
| textContent.Annotations = append(textContent.Annotations, &message.CitationAnnotation{ | ||
| FileID: res.FileID, | ||
| Title: res.Filename, | ||
| Snippet: res.Text, | ||
| ToolName: "file_search", | ||
| RawRepresentation: res, | ||
| }) | ||
| contents = append(contents, textContent) | ||
| } | ||
| return contents | ||
| } |
There was a problem hiding this comment.
Good catch — fixed in ee9c5c4: a zero-result file_search_call now emits a single empty annotated TextContent (ToolName file_search) carrying the raw item on RawRepresentation, so the call is surfaced and, being annotated, is not coalesced away. Added TestResponsesFileSearchTool_NoResultsStillSurfaced to lock this.
Attaching a *hostedtool.FileSearch tool never requested file_search_call.results, and responsesProcessResponse had no case for the file_search_call output item, so the queries and retrieved chunks were silently dropped and only file_citation annotations survived. Request responses.ResponseIncludableFileSearchCallResults (guarded like the reasoning-encrypted-content include so it is not duplicated) and map the ResponseFileSearchToolCall output item to TextContent carrying a CitationAnnotation (file ID, filename and snippet) in both the non-streaming and streaming paths, matching how the .NET/Python SDKs surface FileSearch results.
ee9c5c4 to
4219007
Compare
Parity Review — Cross-SDK Consistency ✅SummaryThis PR fixes a silent data-loss bug in Upstream parityPython ( Behavior alignment: Python surfaces No .NET equivalent found: The .NET Public API surfaceAll changes are to unexported functions ( VerdictThe PR aligns the Go Responses provider with upstream Python behavior and contains no inconsistencies or divergences that warrant a parity concern. Labelled 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 Responses provider maps
*hostedtool.FileSearchto aresponses.FileSearchToolParam, but it never asked the API to include the retrieved chunks and never handled the resulting output item:responsesBuildCompletionParamsonly ever appendedResponseIncludableReasoningEncryptedContenttoparams.Include— neverfile_search_call.results.responsesProcessResponse(and the streamingResponseOutputItemDoneEventswitch) had no case forresponses.ResponseFileSearchToolCall.Net effect: the
file_search_callitem (queries + retrieved chunks) was dropped; onlyfile_citationannotations on the assistant message surfaced viapopulateAnnotations.This change:
*hostedtool.FileSearchbranch, guard-appendsresponses.ResponseIncludableFileSearchCallResultstoparams.Include(same not-duplicated guard used for the reasoning-encrypted-content include), so the API returns the retrieved chunks.responses.ResponseFileSearchToolCallcase in both the non-streaming and streaming output switches that surfaces each result as aTextContentcarrying aCitationAnnotation(file ID, filename, snippet), with the raw call (queries + status) kept onRawRepresentation.Why
This aligns the Go Responses provider with how the .NET and Python Agent Framework SDKs surface FileSearch tool output: the retrieved file chunks and their citations become message content rather than being discarded. Without requesting
file_search_call.resultsthe API omits the chunk text entirely, so both parts are needed to close the gap.Testing
go build ./...,go vet ./provider/openaiprovider/...andgo test ./provider/openaiprovider/...all pass. Added toresponses_test.go:TestResponsesFileSearchTool_NonStreaming— asserts the requestincludecontainsfile_search_call.resultsand that the retrieved file ID / filename / text are surfaced throughRunText().Collect().TestResponsesFileSearchTool_Streaming— same, through the streaming path.TestResponsesFileSearchTool_DoesNotDuplicateInclude— a caller that already set the include viaResponsesNewParamsdoes not get a duplicated entry.All three fail before the change (missing include in the request body; results dropped) and pass after.