Repository navigation
fix(providers/openai): skip reasoning that can't go inline #81
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
e1788a2
2f7f243
8a6fe75
13e36ee
a4b7d52
7752a17
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -222,14 +222,13 @@ func (o responsesLanguageModel) prepareParams(call fantasy.Call) (*responses.Res | |
| allowPreviousResponseToolOutputs = true | ||
| } | ||
|
|
||
| storeEnabled := openaiOptions != nil && openaiOptions.Store != nil && *openaiOptions.Store | ||
| var input responses.ResponseInputParam | ||
| var inputWarnings []fantasy.CallWarning | ||
| var err error | ||
| if allowPreviousResponseToolOutputs { | ||
| input, inputWarnings, err = toResponsesPromptWithValidation(call.Prompt, modelConfig.systemMessageMode, storeEnabled, true) | ||
| input, inputWarnings, err = toResponsesPromptWithValidation(call.Prompt, modelConfig.systemMessageMode, true) | ||
| } else { | ||
| input, inputWarnings, err = toResponsesPrompt(call.Prompt, modelConfig.systemMessageMode, storeEnabled) | ||
| input, inputWarnings, err = toResponsesPrompt(call.Prompt, modelConfig.systemMessageMode) | ||
| } | ||
| warnings = append(warnings, inputWarnings...) | ||
| if err != nil { | ||
|
|
@@ -462,11 +461,11 @@ func responsesUsage(resp responses.Response) fantasy.Usage { | |
| return usage | ||
| } | ||
|
|
||
| func toResponsesPrompt(prompt fantasy.Prompt, systemMessageMode string, store bool) (responses.ResponseInputParam, []fantasy.CallWarning, error) { | ||
| return toResponsesPromptWithValidation(prompt, systemMessageMode, store, false) | ||
| func toResponsesPrompt(prompt fantasy.Prompt, systemMessageMode string) (responses.ResponseInputParam, []fantasy.CallWarning, error) { | ||
| return toResponsesPromptWithValidation(prompt, systemMessageMode, false) | ||
| } | ||
|
|
||
| func toResponsesPromptWithValidation(prompt fantasy.Prompt, systemMessageMode string, store bool, allowUnpairedFunctionCallOutputs bool) (responses.ResponseInputParam, []fantasy.CallWarning, error) { | ||
| func toResponsesPromptWithValidation(prompt fantasy.Prompt, systemMessageMode string, allowUnpairedFunctionCallOutputs bool) (responses.ResponseInputParam, []fantasy.CallWarning, error) { | ||
| var input responses.ResponseInputParam | ||
| var warnings []fantasy.CallWarning | ||
|
|
||
|
|
@@ -682,13 +681,11 @@ func toResponsesPromptWithValidation(prompt fantasy.Prompt, systemMessageMode st | |
| } | ||
| if item, ok := responsesReasoningInputItem(meta); ok { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P3 [CRF-1] No test covers an assistant message whose only content is unfinalized reasoning, which this PR changes from kept (as an A scratch test with
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in 7752a17: added a reasoning-only subtest to TestResponsesToPrompt_DropsEmptyMessages.
|
||
| input = append(input, item) | ||
| } else if store { | ||
| input = append(input, responses.ResponseInputItemParamOfItemReference(meta.ItemID)) | ||
| } else { | ||
| // Unstored reasoning without finalized encrypted | ||
| // content cannot be resolved by the API. | ||
| continue | ||
| } | ||
| // Reasoning that cannot be sent inline is skipped, not | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P3 [CRF-3] The comment says a reference fails for "an item OpenAI did not store", which implies a reference is safe when The PR's evidence shows references to stored items also return 404: a stored response whose stream was cut, zero data retention, and expired items. That is the reason the
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in a4b7d52, reworded to an item OpenAI does not hold.
|
||
| // referenced: a reference to an item OpenAI does not hold, | ||
| // such as one from an unstored response, fails the whole | ||
| // request. | ||
| continue | ||
| case fantasy.ContentTypeToolResult: | ||
| toolResultPart, ok := fantasy.AsContentType[fantasy.ToolResultPart](c) | ||
|
|
@@ -1120,7 +1117,7 @@ func hasVisibleResponsesUserContent(content responses.ResponseInputMessageConten | |
| func hasVisibleResponsesAssistantContent(items []responses.ResponseInputItemUnionParam, startIdx int) bool { | ||
| // Check if we added any assistant content parts from this message | ||
| for i := startIdx; i < len(items); i++ { | ||
| if items[i].OfMessage != nil || items[i].OfFunctionCall != nil || items[i].OfItemReference != nil || items[i].OfReasoning != nil || items[i].OfComputerCall != nil || items[i].OfWebSearchCall != nil { | ||
| if items[i].OfMessage != nil || items[i].OfFunctionCall != nil || items[i].OfReasoning != nil || items[i].OfComputerCall != nil || items[i].OfWebSearchCall != nil { | ||
| return true | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -109,8 +109,6 @@ func TestResponsesGenerate_ReasoningMetadataIsFinalized(t *testing.T) { | |
| func TestResponsesReplay_FinalizedReasoningInput(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| // The SDK marshals item references without a type field. | ||
| reference := map[string]any{"id": "rs_1"} | ||
| fullItem := func(summary ...any) map[string]any { | ||
| return map[string]any{ | ||
| "type": "reasoning", | ||
|
|
@@ -126,6 +124,9 @@ func TestResponsesReplay_FinalizedReasoningInput(t *testing.T) { | |
| encrypted *string | ||
| summary []string | ||
| webSearch bool | ||
| // unfinalized clears Finalized, as in streaming placeholders and | ||
| // metadata saved before Finalized existed. | ||
| unfinalized bool | ||
| // want is the reasoning input item; nil means none is sent. | ||
| want map[string]any | ||
| }{ | ||
|
|
@@ -154,11 +155,16 @@ func TestResponsesReplay_FinalizedReasoningInput(t *testing.T) { | |
| { | ||
| name: "no blob store true", | ||
| store: true, | ||
| want: reference, | ||
| }, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P4 [CRF-6] The new
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in 7752a17: dropped the unfinalized store false row. The no blob store false row predates this PR, so I left it.
|
||
| { | ||
| name: "no blob store false", | ||
| }, | ||
| { | ||
| name: "unfinalized store true", | ||
| store: true, | ||
| encrypted: new("enc-final"), | ||
| unfinalized: true, | ||
| }, | ||
| { | ||
| name: "store true before web search", | ||
| store: true, | ||
|
|
@@ -185,7 +191,7 @@ func TestResponsesReplay_FinalizedReasoningInput(t *testing.T) { | |
| ItemID: "rs_1", | ||
| EncryptedContent: tc.encrypted, | ||
| Summary: tc.summary, | ||
| Finalized: true, | ||
| Finalized: !tc.unfinalized, | ||
| }}}, | ||
| next, | ||
| fantasy.TextPart{Text: "a"}, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P2 [CRF-2] With
Store: trueand noIncludeReasoningEncryptedContentinInclude, all reasoning, including reasoning from completed responses, is now dropped from every later request with no warning. (Pariston, Mafuuu, Melody, Razor, Leorio)Fantasy never adds
reasoning.encrypted_contentitself (prepareParamsonly copiesopenaiOptions.Include, lines 312-313), sofinalResponsesReasoningMetadatagivesFinalized: true, EncryptedContent: nil; the description's "always has encrypted content" holds for coder/coder only becausechatopenai.EnsureResponseIncludesforces the include. Before this PR that item went back as anitem_referenceto a stored, completed response, which your probe shows returns 200, and theno blob store truecase now asserts nothing is sent, so these callers lose reasoning between tool-call steps of a single agent run. Fix one of: addIncludeReasoningEncryptedContentinprepareParamsfor reasoning models so replay always has a blob; keep the reference forFinalizeditems whenstoreis true and skip only unfinalized ones; or emit aCallWarningper skipped reasoning item with anItemID(asdropping web search ...does) and document onStoreandIncludethat reasoning is replayed only with that include. The warning also gives a signal for the pre-coder/coder#30298 rows this PR targets.