mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
fix(coderd/x/chatd): show correct provider and clean detail for Bedrock errors (#26338)
## Problem
Bedrock errors (routed through aibridge) showed the wrong provider
("Anthropic ...")
and a doubly-wrapped detail string instead of the clean message.
## Fix (chatd only)
- **Provider label:** thread the configured provider to error
classification via
`GenerateAssistantOptions.ErrorProvider`. Transport provider still
drives prompt
prep, sanitization, and metric labels (unchanged).
- **Detail:** unwrap the SDK transport wrapper (`METHOD "URL": NNN
{body}`) to surface
the inner message; handles top-level `{"message":...}` and nested
`{"error":{"message":...}}`.
## Notes
- Surfacing a top-level `message` now applies to all providers
(intentional; nested wins when both present).
- The advisor path keeps the transport label; accepted as-is (it returns
`err.Error()`, not the classification).
- Fixes display in chatd only; the wrapper originates in aibridge (out
of scope here).
🤖 Generated by Coder Agents.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package chatloop
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/base64"
|
||||
@@ -210,7 +211,13 @@ type RunOptions struct {
|
||||
|
||||
// GenerateAssistantOptions configures one assistant model call.
|
||||
type GenerateAssistantOptions struct {
|
||||
Model fantasy.LanguageModel
|
||||
Model fantasy.LanguageModel
|
||||
// ErrorProvider labels user-facing errors with the configured provider
|
||||
// identity (e.g. "bedrock"). It differs from Model.Provider(), which
|
||||
// reflects the fantasy transport client and is "anthropic" for Bedrock
|
||||
// routed through aibridge. Metrics and prompt preparation keep using
|
||||
// Model.Provider(). When empty, Model.Provider() is used.
|
||||
ErrorProvider string
|
||||
Messages []fantasy.Message
|
||||
Tools []fantasy.AgentTool
|
||||
ActiveTools []string
|
||||
@@ -344,6 +351,11 @@ func GenerateAssistant(ctx context.Context, opts GenerateAssistantOptions) (Assi
|
||||
|
||||
provider := opts.Model.Provider()
|
||||
modelName := opts.Model.Model()
|
||||
// errorProvider labels user-facing errors with the configured provider;
|
||||
// see GenerateAssistantOptions.ErrorProvider. The transport provider is
|
||||
// kept for prompt preparation, Anthropic history sanitization, and the
|
||||
// metric labels below.
|
||||
errorProvider := cmp.Or(opts.ErrorProvider, provider)
|
||||
runOpts := RunOptions{
|
||||
Model: opts.Model,
|
||||
Logger: opts.Logger,
|
||||
@@ -382,8 +394,8 @@ func GenerateAssistant(ctx context.Context, opts GenerateAssistantOptions) (Assi
|
||||
opts.Metrics,
|
||||
)
|
||||
if streamErr != nil {
|
||||
wrappedErr := wrapProviderStreamError(provider, streamErr)
|
||||
classified := chaterror.Classify(wrappedErr).WithProvider(provider)
|
||||
wrappedErr := wrapProviderStreamError(errorProvider, streamErr)
|
||||
classified := chaterror.Classify(wrappedErr).WithProvider(errorProvider)
|
||||
if classified.Retryable {
|
||||
opts.Metrics.RecordStreamRetry(provider, modelName, classified)
|
||||
}
|
||||
@@ -396,8 +408,8 @@ func GenerateAssistant(ctx context.Context, opts GenerateAssistantOptions) (Assi
|
||||
if errors.Is(err, ErrInterrupted) {
|
||||
return AssistantOutcome{}, ErrInterrupted
|
||||
}
|
||||
wrappedErr := wrapProviderStreamError(provider, err)
|
||||
classified := chaterror.Classify(wrappedErr).WithProvider(provider)
|
||||
wrappedErr := wrapProviderStreamError(errorProvider, err)
|
||||
classified := chaterror.Classify(wrappedErr).WithProvider(errorProvider)
|
||||
if classified.Retryable {
|
||||
opts.Metrics.RecordStreamRetry(provider, modelName, classified)
|
||||
}
|
||||
|
||||
@@ -171,6 +171,33 @@ func TestGenerateAssistant_ProviderContextSurvivesStreamError(t *testing.T) {
|
||||
require.Equal(t, "OpenAI returned an unexpected error.", classified.Message)
|
||||
}
|
||||
|
||||
func TestGenerateAssistant_ErrorProviderOverridesTransportLabel(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Bedrock routed through aibridge uses the Anthropic transport, so
|
||||
// Model.Provider() reports "anthropic". The user-facing error must use
|
||||
// the configured provider from ErrorProvider instead.
|
||||
model := &chattest.FakeModel{
|
||||
ProviderName: "anthropic",
|
||||
ModelName: "qwen3-coder-next",
|
||||
StreamFn: func(_ context.Context, _ fantasy.Call) (fantasy.StreamResponse, error) {
|
||||
return nil, xerrors.New("upstream returned status 400")
|
||||
},
|
||||
}
|
||||
|
||||
_, err := GenerateAssistant(context.Background(), GenerateAssistantOptions{
|
||||
Model: model,
|
||||
ErrorProvider: "bedrock",
|
||||
Messages: []fantasy.Message{
|
||||
textMessage(fantasy.MessageRoleUser, "hello"),
|
||||
},
|
||||
})
|
||||
require.Error(t, err)
|
||||
classified := chaterror.Classify(err)
|
||||
require.Equal(t, "bedrock", classified.Provider)
|
||||
require.Equal(t, "AWS Bedrock returned an unexpected error.", classified.Message)
|
||||
}
|
||||
|
||||
func TestGenerateAssistant_HTTP2TransportErrorClassifiedAsRetryableTimeout(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@@ -441,11 +441,17 @@ func TestGenerateAssistant_StreamRetryRecordsMetric(t *testing.T) {
|
||||
}
|
||||
|
||||
_, err := chatloop.GenerateAssistant(context.Background(), chatloop.GenerateAssistantOptions{
|
||||
Model: model,
|
||||
Metrics: metrics,
|
||||
Model: model,
|
||||
// ErrorProvider diverges from the transport provider. Error copy must
|
||||
// use it while the retry metric stays on the transport provider.
|
||||
ErrorProvider: "bedrock",
|
||||
Metrics: metrics,
|
||||
})
|
||||
require.Error(t, err)
|
||||
require.Equal(t, 1, calls)
|
||||
// Error classification uses the configured provider.
|
||||
require.Equal(t, "bedrock", chaterror.Classify(err).Provider)
|
||||
// Retry metric keeps the transport provider label, not "bedrock".
|
||||
requireCounter(t, reg, "coderd_chatd_stream_retries_total", 1, map[string]string{
|
||||
"provider": "test-provider",
|
||||
"model": "test-model",
|
||||
|
||||
Reference in New Issue
Block a user