create employee table
This commit is contained in:
@@ -345,15 +345,33 @@ func translate(err error) error {
|
||||
return &Error{Code: CodeUpstream, Message: "the model call failed", Cause: err}
|
||||
}
|
||||
|
||||
// The upstream reason, carried through when there is one — the same rule the
|
||||
// OpenAI path already follows, and for the same reason it gives: a 400 that
|
||||
// says which model id or tool schema was rejected is worth more than "the
|
||||
// model rejected the request", and the trajectory records only the message.
|
||||
//
|
||||
// This was not a hypothetical. Every run on a deployment failed with a bare
|
||||
// `gateway.invalid_request`, and neither the run detail nor anything a
|
||||
// client could read said why — the one fact needed to fix it was discarded
|
||||
// here, three lines from where it arrived. `Cause` keeps the full error for
|
||||
// a Go caller; nothing reads it by the time a run is persisted.
|
||||
detail := anthropicErrorMessage(apierr.RawJSON())
|
||||
withDetail := func(base string) string {
|
||||
if detail == "" {
|
||||
return base
|
||||
}
|
||||
return base + ": " + detail
|
||||
}
|
||||
|
||||
switch apierr.StatusCode {
|
||||
case 400:
|
||||
return &Error{Code: CodeInvalidRequest, Message: "the model rejected the request", Status: 400, Cause: err}
|
||||
return &Error{Code: CodeInvalidRequest, Message: withDetail("the model rejected the request"), Status: 400, Cause: err}
|
||||
case 401, 403:
|
||||
return &Error{Code: CodeUnauthorized, Message: "the model credentials were refused", Status: apierr.StatusCode, Cause: err}
|
||||
return &Error{Code: CodeUnauthorized, Message: withDetail("the model credentials were refused"), Status: apierr.StatusCode, Cause: err}
|
||||
case 408:
|
||||
return &Error{Code: CodeTimeout, Message: "the model call timed out", Status: 408, Cause: err}
|
||||
return &Error{Code: CodeTimeout, Message: withDetail("the model call timed out"), Status: 408, Cause: err}
|
||||
case 429:
|
||||
return &Error{Code: CodeRateLimited, Message: "the model is rate limiting this deployment", Status: 429, Cause: err}
|
||||
return &Error{Code: CodeRateLimited, Message: withDetail("the model is rate limiting this deployment"), Status: 429, Cause: err}
|
||||
case 529:
|
||||
// Anthropic's "overloaded" — the service is up and temporarily out of
|
||||
// capacity. Named separately from the 500s because it is the one that
|
||||
@@ -368,12 +386,36 @@ func translate(err error) error {
|
||||
// message.
|
||||
return &Error{
|
||||
Code: CodeUpstream,
|
||||
Message: fmt.Sprintf("the model call failed (http %d)", apierr.StatusCode),
|
||||
Message: withDetail(fmt.Sprintf("the model call failed (http %d)", apierr.StatusCode)),
|
||||
Status: apierr.StatusCode, Cause: err,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// anthropicErrorMessage digs the human-readable reason out of an error body.
|
||||
//
|
||||
// Best-effort, exactly like its OpenAI counterpart: the envelope is documented
|
||||
// and stable enough to be worth reading, and an unparseable body yields nothing
|
||||
// rather than failing a failure.
|
||||
func anthropicErrorMessage(raw string) string {
|
||||
if raw == "" {
|
||||
return ""
|
||||
}
|
||||
var envelope struct {
|
||||
Error struct {
|
||||
Message string `json:"message"`
|
||||
Type string `json:"type"`
|
||||
} `json:"error"`
|
||||
}
|
||||
if err := json.Unmarshal([]byte(raw), &envelope); err != nil {
|
||||
return ""
|
||||
}
|
||||
if envelope.Error.Message != "" {
|
||||
return envelope.Error.Message
|
||||
}
|
||||
return envelope.Error.Type
|
||||
}
|
||||
|
||||
/* ── Streaming ──────────────────────────────────────────────────────────── */
|
||||
|
||||
// Stream is Complete, with the assistant's text delivered as it arrives.
|
||||
|
||||
46
go-api/internal/gateway/anthropic_error_test.go
Normal file
46
go-api/internal/gateway/anthropic_error_test.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package gateway
|
||||
|
||||
import "testing"
|
||||
|
||||
// The reason a 400 gives is the whole diagnosis, and it used to be thrown away.
|
||||
//
|
||||
// A deployment answered every single run with "The agent could not finish —
|
||||
// something it needed did not answer." Retrieval worked, the run was created,
|
||||
// the provider was reached, and the trajectory recorded `gateway.invalid_request`
|
||||
// and nothing else. The actual cause was one sentence long and Anthropic had
|
||||
// sent it: the account was out of credit. Nothing a client, a log or a run
|
||||
// detail could show said so, because `translate` dropped the body.
|
||||
//
|
||||
// The envelope below is the real one, copied from that response.
|
||||
func TestAnthropicErrorMessage(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
raw string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "the outage this test exists for",
|
||||
raw: `{"type":"error","error":{"type":"invalid_request_error",` +
|
||||
`"message":"Your credit balance is too low to access the Anthropic API. ` +
|
||||
`Please go to Plans & Billing to upgrade or purchase credits."},` +
|
||||
`"request_id":"req_011CeiJqr8rTdpbZYhcL6VHG"}`,
|
||||
want: "Your credit balance is too low to access the Anthropic API. " +
|
||||
"Please go to Plans & Billing to upgrade or purchase credits.",
|
||||
},
|
||||
{
|
||||
name: "a message-less envelope falls back to the type",
|
||||
raw: `{"type":"error","error":{"type":"overloaded_error"}}`,
|
||||
want: "overloaded_error",
|
||||
},
|
||||
// Best-effort by design: a failure to read a failure must not become a
|
||||
// second failure.
|
||||
{name: "unparseable body yields nothing", raw: `not json at all`, want: ""},
|
||||
{name: "empty body yields nothing", raw: ``, want: ""},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
if got := anthropicErrorMessage(c.raw); got != c.want {
|
||||
t.Errorf("%s: anthropicErrorMessage() = %q, want %q", c.name, got, c.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user