mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
> Worked on by Mux on Mike's behalf. ## Summary - Disable OpenAI Responses `previous_response_id` chain mode when the prior assistant response has unresolved local tool calls, so the next request can include paired tool outputs instead of sending an incomplete continuation. - Update the fantasy pin to a Responses replay fix that preserves stored reasoning references, only replays web search references when paired with reasoning, and validates local function-call output pairing before send. - Add fake OpenAI Responses input validation for the two production 400 shapes and integration coverage for full-history reasoning plus web search replay. - Add sanitized diagnostics for the OpenAI Responses continuity errors. ## Tests - `go test ./providers/openai -run 'TestResponsesToPrompt_(ReasoningWithStore|ReasoningWithWebSearchCombined|WebSearchRequiresReasoningReference|ReasoningWithFunctionCallCombined|WebSearchProviderExecutedToolResults)|TestPrepareParams_(SkipsProviderExecutedToolReferences|ValidatesFunctionCallOutputPairing)|TestValidateResponsesInput_WebSearchReferenceRequiresReasoning' -count=1` - `go test ./providers/openai -count=1` - `GOWORK=off go test ./coderd/x/chatd/chattest -run TestValidateResponsesAPIInput -count=1` - `GOWORK=off go test ./coderd/x/chatd -run 'TestOpenAIResponses(NoStaleWebSearchReplay|FullReplayPairsReasoningAndWebSearch|ChainModeSkipsWhenLocalCallPending|ChainModeStillFiresForProviderExecutedOnly)$|TestResolveChainMode_' -count=1` - `GOWORK=off go test ./coderd/x/chatd/chatprompt -run 'TestInjectMissingToolResults_' -count=1` - `GOWORK=off go test ./coderd/x/chatd/chaterror -run TestClassify_OpenAIResponsesAPIDiagnostics -count=1` - `GOWORK=off go test ./coderd/x/chatd/... -count=1` - `git diff --check` - `git commit` pre-commit hook
101 lines
3.4 KiB
Go
101 lines
3.4 KiB
Go
package chattest_test
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/coder/coder/v2/coderd/x/chatd/chattest"
|
|
)
|
|
|
|
func TestValidateResponsesAPIInput(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
t.Run("valid reasoning and web search references", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
errResp := chattest.ValidateResponsesAPIInput([]interface{}{
|
|
map[string]interface{}{"type": "item_reference", "id": "rs_valid"},
|
|
map[string]interface{}{"type": "item_reference", "id": "ws_valid"},
|
|
})
|
|
require.Nil(t, errResp)
|
|
})
|
|
|
|
t.Run("rejects web search without reasoning", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
errResp := chattest.ValidateResponsesAPIInput([]interface{}{
|
|
map[string]interface{}{"type": "item_reference", "id": "ws_orphan"},
|
|
})
|
|
require.NotNil(t, errResp)
|
|
require.Equal(t, 400, errResp.StatusCode)
|
|
require.Contains(t, errResp.Message, "web_search_call")
|
|
require.Contains(t, errResp.Message, "reasoning")
|
|
})
|
|
|
|
t.Run("valid function call and output", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
errResp := chattest.ValidateResponsesAPIInput([]interface{}{
|
|
map[string]interface{}{"type": "function_call", "call_id": "call_valid"},
|
|
map[string]interface{}{"type": "function_call_output", "call_id": "call_valid"},
|
|
})
|
|
require.Nil(t, errResp)
|
|
})
|
|
|
|
t.Run("rejects function call without output", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
errResp := chattest.ValidateResponsesAPIInput([]interface{}{
|
|
map[string]interface{}{"type": "function_call", "call_id": "call_orphan"},
|
|
})
|
|
require.NotNil(t, errResp)
|
|
require.Contains(t, errResp.Message, "No tool output found for function call call_orphan")
|
|
})
|
|
|
|
t.Run("rejects output before function call", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
errResp := chattest.ValidateResponsesAPIInput([]interface{}{
|
|
map[string]interface{}{"type": "function_call_output", "call_id": "call_late"},
|
|
map[string]interface{}{"type": "function_call", "call_id": "call_late"},
|
|
})
|
|
require.NotNil(t, errResp)
|
|
require.Contains(t, errResp.Message, "Tool output found without preceding function call call_late")
|
|
})
|
|
|
|
t.Run("rejects duplicate function call", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
errResp := chattest.ValidateResponsesAPIInput([]interface{}{
|
|
map[string]interface{}{"type": "function_call", "call_id": "call_duplicate"},
|
|
map[string]interface{}{"type": "function_call", "call_id": "call_duplicate"},
|
|
map[string]interface{}{"type": "function_call_output", "call_id": "call_duplicate"},
|
|
})
|
|
require.NotNil(t, errResp)
|
|
require.Contains(t, errResp.Message, "Duplicate function call found for call_id call_duplicate")
|
|
})
|
|
|
|
t.Run("rejects duplicate function call output", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
errResp := chattest.ValidateResponsesAPIInput([]interface{}{
|
|
map[string]interface{}{"type": "function_call", "call_id": "call_duplicate_output"},
|
|
map[string]interface{}{"type": "function_call_output", "call_id": "call_duplicate_output"},
|
|
map[string]interface{}{"type": "function_call_output", "call_id": "call_duplicate_output"},
|
|
})
|
|
require.NotNil(t, errResp)
|
|
require.Contains(t, errResp.Message, "Duplicate tool output found for function call call_duplicate_output")
|
|
})
|
|
|
|
t.Run("classifies item reference by prefix without type field", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
errResp := chattest.ValidateResponsesAPIInput([]interface{}{
|
|
map[string]interface{}{"id": "rs_prefix_only"},
|
|
map[string]interface{}{"id": "ws_prefix_only"},
|
|
})
|
|
require.Nil(t, errResp)
|
|
})
|
|
}
|