Contain provider errors at the LLM adapter
This commit is contained in:
@@ -142,7 +142,7 @@ func (c *PromptKitClient) CompleteStructured(ctx context.Context, req contracts.
|
||||
if ctxErr := ctx.Err(); ctxErr != nil {
|
||||
return contracts.StructuredCompletionResponse{}, ctxErr
|
||||
}
|
||||
return contracts.StructuredCompletionResponse{}, fmt.Errorf("prepare PromptKit prompt %q: %w", promptID, redactPromptKitError(err))
|
||||
return contracts.StructuredCompletionResponse{}, fmt.Errorf("prepare PromptKit prompt %q: %v", promptID, redactPromptKitError(err))
|
||||
}
|
||||
defer prepared.Discard()
|
||||
preparedDetails := prepared.Details()
|
||||
@@ -169,7 +169,7 @@ func (c *PromptKitClient) CompleteStructured(ctx context.Context, req contracts.
|
||||
redactPromptKitError(err),
|
||||
)
|
||||
}
|
||||
return contracts.StructuredCompletionResponse{}, fmt.Errorf("run PromptKit prompt %q: %w", promptID, redactPromptKitError(err))
|
||||
return contracts.StructuredCompletionResponse{}, fmt.Errorf("run PromptKit prompt %q: %v", promptID, redactPromptKitError(err))
|
||||
}
|
||||
if result == nil {
|
||||
return contracts.StructuredCompletionResponse{}, fmt.Errorf("run PromptKit prompt %q: %w: empty result", promptID, contracts.ErrInvalidStructuredOutput)
|
||||
@@ -427,17 +427,13 @@ func redactPromptKitError(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
return redactedProviderError{err: err}
|
||||
return sanitizedProviderDiagnostic{message: bearerTokenPattern.ReplaceAllString(err.Error(), "Bearer "+secretReplacement)}
|
||||
}
|
||||
|
||||
type redactedProviderError struct {
|
||||
err error
|
||||
type sanitizedProviderDiagnostic struct {
|
||||
message string
|
||||
}
|
||||
|
||||
func (e redactedProviderError) Error() string {
|
||||
return bearerTokenPattern.ReplaceAllString(e.err.Error(), "Bearer "+secretReplacement)
|
||||
}
|
||||
|
||||
func (e redactedProviderError) Unwrap() error {
|
||||
return e.err
|
||||
func (e sanitizedProviderDiagnostic) Error() string {
|
||||
return e.message
|
||||
}
|
||||
|
||||
@@ -741,6 +741,11 @@ model: local-model
|
||||
!strings.Contains(err.Error(), promptkit.BackendLocal) {
|
||||
t.Fatalf("CompleteStructured() without registration error = %v, want preparation failure with local backend context", err)
|
||||
}
|
||||
for _, sentinel := range []error{promptkit.ErrProfileLoad, promptkit.ErrInvalidRequest, promptkit.ErrPromptNotFound} {
|
||||
if errors.Is(err, sentinel) {
|
||||
t.Fatalf("preparation failure exposes PromptKit sentinel %v: %v", sentinel, err)
|
||||
}
|
||||
}
|
||||
if providerCalls.Load() != 1 {
|
||||
t.Fatalf("provider calls after missing-registration failure = %d, want 1", providerCalls.Load())
|
||||
}
|
||||
@@ -814,7 +819,8 @@ func TestPromptKitClientDecodeFailureReturnsRawResponse(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPromptKitClientProviderFailureIncludesContextAndRedactsBearerToken(t *testing.T) {
|
||||
client := newTestPromptKitClient(t, &fakePromptKitLLM{err: errors.New("provider failed with Bearer secret-token")})
|
||||
providerErr := &credentialBearingProviderError{}
|
||||
client := newTestPromptKitClient(t, &fakePromptKitLLM{err: providerErr})
|
||||
|
||||
var out map[string]any
|
||||
resp, err := client.CompleteStructured(context.Background(), contracts.StructuredCompletionRequest{
|
||||
@@ -836,6 +842,21 @@ func TestPromptKitClientProviderFailureIncludesContextAndRedactsBearerToken(t *t
|
||||
if strings.Contains(err.Error(), "secret-token") || !strings.Contains(err.Error(), "Bearer [REDACTED]") {
|
||||
t.Fatalf("error = %q, want redacted bearer token", err.Error())
|
||||
}
|
||||
for current := err; current != nil; current = errors.Unwrap(current) {
|
||||
if strings.Contains(current.Error(), "secret-token") {
|
||||
t.Fatalf("error chain exposes credential: %v", err)
|
||||
}
|
||||
}
|
||||
if errors.Unwrap(err) != nil {
|
||||
t.Fatalf("provider failure must not expose a wrapped diagnostic: %v", err)
|
||||
}
|
||||
var recoveredProviderErr *credentialBearingProviderError
|
||||
if errors.As(err, &recoveredProviderErr) {
|
||||
t.Fatalf("provider error escaped the adapter: %v", err)
|
||||
}
|
||||
if errors.Is(err, promptkit.ErrLLMGenerate) {
|
||||
t.Fatalf("provider failure exposes PromptKit generation sentinel: %v", err)
|
||||
}
|
||||
if resp.Debug != nil {
|
||||
t.Fatalf("debug material = %#v, want none for provider failure without result", resp.Debug)
|
||||
}
|
||||
@@ -1214,6 +1235,12 @@ type fakePromptKitLLM struct {
|
||||
maxInFlight int32
|
||||
}
|
||||
|
||||
type credentialBearingProviderError struct{}
|
||||
|
||||
func (*credentialBearingProviderError) Error() string {
|
||||
return "provider failed with Bearer secret-token"
|
||||
}
|
||||
|
||||
func (f *fakePromptKitLLM) Generate(ctx context.Context, req promptkit.GenerateRequest) (*promptkit.GenerateResponse, error) {
|
||||
f.mu.Lock()
|
||||
f.last = req
|
||||
|
||||
Reference in New Issue
Block a user