mirror of
https://github.com/mulgadc/spinifex.git
synced 2026-10-15 09:01:31 +00:00
fix(bedrock): GetFoundationModel answers an unknown model with ValidationException (#1104)
This commit is contained in:
@@ -463,8 +463,7 @@ func LookupServingSpec(modelID string) (spec ServingSpec, found, selfHost bool)
|
||||
// An unknown model and an ungranted one are deliberately distinguishable here
|
||||
// (ResourceNotFoundException vs AccessDeniedException) because the caller has
|
||||
// already been told the model exists by its own catalog listing; the describe
|
||||
// path collapses both to ResourceNotFoundException instead, where that is not
|
||||
// true.
|
||||
// path collapses both to ValidationException instead, where that is not true.
|
||||
func grantedCatalogEntry(ctx context.Context, accountID, modelID string, access AccessResolver) (catalogEntry, error) {
|
||||
entry, ok := lookupCatalogEntry(modelID)
|
||||
if !ok {
|
||||
@@ -496,18 +495,13 @@ func ListFoundationModels(ctx context.Context, accountID string, resolver Creden
|
||||
return &bedrock.ListFoundationModelsOutput{ModelSummaries: summaries}, nil
|
||||
}
|
||||
|
||||
// GetFoundationModel looks up a single model by exact modelId, gated by the
|
||||
// caller's grant. An ungranted model, and a self-host model with no resolvable
|
||||
// weights snapshot, are both reported as ResourceNotFoundException rather than
|
||||
// AccessDeniedException so describe agrees with list: a model the account
|
||||
// cannot see does not exist as far as this API is concerned, and the error does
|
||||
// not confirm the model's existence to an account probing for it. A weights
|
||||
// resolve error is an internal fault, not a not-found verdict, and is surfaced
|
||||
// (and logged) as such.
|
||||
// GetFoundationModel describes one model. Unknown, ungranted and unservable
|
||||
// models all fail with AWS's ValidationException so describe agrees with list
|
||||
// and never confirms a hidden model exists; a weights resolve error is a fault.
|
||||
func GetFoundationModel(ctx context.Context, accountID string, modelID string, access AccessResolver) (*bedrock.GetFoundationModelOutput, error) {
|
||||
entry, ok := lookupCatalogEntry(modelID)
|
||||
if !ok {
|
||||
return nil, errors.New(awserrors.ErrorResourceNotFoundException)
|
||||
return nil, awserrors.Errorf(awserrors.ErrorValidationException, "The provided model identifier is invalid.")
|
||||
}
|
||||
// Grant first: an ungranted account must not learn whether the model is
|
||||
// servable, only that it is not there.
|
||||
@@ -516,7 +510,7 @@ func GetFoundationModel(ctx context.Context, accountID string, modelID string, a
|
||||
return nil, err
|
||||
}
|
||||
if !granted {
|
||||
return nil, errors.New(awserrors.ErrorResourceNotFoundException)
|
||||
return nil, awserrors.Errorf(awserrors.ErrorValidationException, "The provided model identifier is invalid.")
|
||||
}
|
||||
if entry.Provider == tierSelfHost {
|
||||
_, resolvable, err := currentWeightsResolver().Resolve(ctx, entry.ModelID)
|
||||
@@ -525,7 +519,7 @@ func GetFoundationModel(ctx context.Context, accountID string, modelID string, a
|
||||
return nil, fmt.Errorf("resolve weights for %s: %w", entry.ModelID, err)
|
||||
}
|
||||
if !resolvable {
|
||||
return nil, errors.New(awserrors.ErrorResourceNotFoundException)
|
||||
return nil, awserrors.Errorf(awserrors.ErrorValidationException, "The provided model identifier is invalid.")
|
||||
}
|
||||
}
|
||||
return &bedrock.GetFoundationModelOutput{ModelDetails: entry.toDetails()}, nil
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/aws/aws-sdk-go/service/bedrock"
|
||||
"github.com/mulgadc/spinifex/spinifex/awserrors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -125,11 +126,11 @@ func TestGetFoundationModel_KnownModelWithResolvableWeights(t *testing.T) {
|
||||
assert.Equal(t, selfHostTestModel, *out.ModelDetails.ModelId)
|
||||
}
|
||||
|
||||
func TestGetFoundationModel_SelfHostWithUnresolvableWeightsReturnsNotFound(t *testing.T) {
|
||||
func TestGetFoundationModel_SelfHostWithUnresolvableWeightsMatchesUnknown(t *testing.T) {
|
||||
withWeightsResolver(t, stubWeightsResolver{ok: map[string]bool{}})
|
||||
_, err := GetFoundationModel(context.Background(), "000000000001", selfHostTestModel, grantAll{})
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, "ResourceNotFoundException", err.Error())
|
||||
assert.True(t, awserrors.IsErrorCode(err, awserrors.ErrorValidationException))
|
||||
}
|
||||
|
||||
// TestListFoundationModels_GrantDoesNotOverrideCredentialTier keeps the two
|
||||
@@ -163,26 +164,29 @@ func TestGetFoundationModel_KnownGrantedModel(t *testing.T) {
|
||||
assert.Equal(t, selfHostTestModel, *out.ModelDetails.ModelId)
|
||||
}
|
||||
|
||||
func TestGetFoundationModel_UnknownModelReturnsNotFound(t *testing.T) {
|
||||
func TestGetFoundationModel_UnknownModelReturnsValidation(t *testing.T) {
|
||||
_, err := GetFoundationModel(context.Background(), "000000000001", "does-not-exist", grantAll{})
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, "ResourceNotFoundException", err.Error())
|
||||
code, message, ok := awserrors.ResolveErrorDetail(err)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, awserrors.ErrorValidationException, code)
|
||||
assert.Equal(t, "The provided model identifier is invalid.", message)
|
||||
}
|
||||
|
||||
// TestGetFoundationModel_UngrantedModelReturnsNotFound pins describe to the
|
||||
// same answer as list: an ungranted model is reported as absent rather than
|
||||
// forbidden, so the error cannot be used to confirm the model exists.
|
||||
func TestGetFoundationModel_UngrantedModelReturnsNotFound(t *testing.T) {
|
||||
// TestGetFoundationModel_UngrantedModelMatchesUnknown pins describe to the
|
||||
// same answer as list: an ungranted model gets the unknown-model error rather
|
||||
// than a denial, so the error cannot be used to confirm the model exists.
|
||||
func TestGetFoundationModel_UngrantedModelMatchesUnknown(t *testing.T) {
|
||||
_, err := GetFoundationModel(context.Background(), "000000000001", selfHostTestModel, grantSet{})
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, "ResourceNotFoundException", err.Error())
|
||||
assert.True(t, awserrors.IsErrorCode(err, awserrors.ErrorValidationException))
|
||||
}
|
||||
|
||||
func TestGetFoundationModel_WeightsResolveErrorIsNotResourceNotFound(t *testing.T) {
|
||||
func TestGetFoundationModel_WeightsResolveErrorIsNotValidation(t *testing.T) {
|
||||
withWeightsResolver(t, errWeightsResolver{})
|
||||
_, err := GetFoundationModel(context.Background(), "000000000001", selfHostTestModel, grantAll{})
|
||||
require.Error(t, err)
|
||||
assert.NotEqual(t, "ResourceNotFoundException", err.Error())
|
||||
assert.False(t, awserrors.IsErrorCode(err, awserrors.ErrorValidationException))
|
||||
}
|
||||
|
||||
func TestListFoundationModels_WeightsResolveErrorPropagates(t *testing.T) {
|
||||
|
||||
@@ -126,12 +126,12 @@ func TestBedrockRequest_DenyByDefault(t *testing.T) {
|
||||
assert.Empty(t, out.ModelSummaries)
|
||||
})
|
||||
|
||||
t.Run("describe reports not found", func(t *testing.T) {
|
||||
t.Run("describe reports it as unknown", func(t *testing.T) {
|
||||
req := bedrockRequestWithAccount(http.MethodGet, "/foundation-models/"+bedrockTestLlamaModelID, "")
|
||||
w := httptest.NewRecorder()
|
||||
err := gw.Bedrock_Request(w, req)
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, awserrors.ErrorResourceNotFoundException, err.Error())
|
||||
assert.True(t, awserrors.IsErrorCode(err, awserrors.ErrorValidationException))
|
||||
})
|
||||
|
||||
// Every runtime verb must refuse, not just the one that happens to be
|
||||
@@ -187,7 +187,7 @@ func TestBedrockRequest_GetFoundationModel(t *testing.T) {
|
||||
assert.Equal(t, bedrockTestLlamaModelID, *out.ModelDetails.ModelId)
|
||||
}
|
||||
|
||||
func TestBedrockRequest_GetFoundationModel_NotFound(t *testing.T) {
|
||||
func TestBedrockRequest_GetFoundationModel_UnknownModel(t *testing.T) {
|
||||
ts := newVLLMStub(t)
|
||||
gw := newBedrockRequestGateway(t, ts.URL)
|
||||
|
||||
@@ -195,7 +195,7 @@ func TestBedrockRequest_GetFoundationModel_NotFound(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
err := gw.Bedrock_Request(w, req)
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, awserrors.ErrorResourceNotFoundException, err.Error())
|
||||
assert.True(t, awserrors.IsErrorCode(err, awserrors.ErrorValidationException))
|
||||
}
|
||||
|
||||
func TestBedrockRequest_UnknownRouteReturnsInvalidAction(t *testing.T) {
|
||||
@@ -249,7 +249,7 @@ func TestBedrockRuntimeRequest_MalformedBodyReturnsValidationException(t *testin
|
||||
w := httptest.NewRecorder()
|
||||
err := gw.BedrockRuntime_Request(w, req)
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, awserrors.ErrorValidationException, err.Error())
|
||||
assert.True(t, awserrors.IsErrorCode(err, awserrors.ErrorValidationException))
|
||||
}
|
||||
|
||||
func TestBedrockRuntimeRequest_InvokeModel(t *testing.T) {
|
||||
|
||||
@@ -22,17 +22,17 @@ const (
|
||||
modelBogus = "does.not-exist-v1:0"
|
||||
)
|
||||
|
||||
// TestGetFoundationModel asserts ResourceNotFoundException for an unknown
|
||||
// model ID. v1 ships no provider-tier entry that resolves unconditionally
|
||||
// TestGetFoundationModel asserts ValidationException for an unknown model ID,
|
||||
// as AWS returns. v1 ships no provider-tier entry that resolves unconditionally
|
||||
// (modelAnthropic is not in the catalog at all — it returns the same
|
||||
// ResourceNotFoundException as a genuinely unknown ID), so the positive,
|
||||
// ValidationException as a genuinely unknown ID), so the positive,
|
||||
// known-model path is covered separately by TestGetFoundationModelSelfHost,
|
||||
// which is gated on a real weights snapshot being staged.
|
||||
func TestGetFoundationModel(t *testing.T) {
|
||||
f := requireBedrockFixture(t)
|
||||
|
||||
t.Run("unknown model", func(t *testing.T) {
|
||||
harness.ExpectError(t, "ResourceNotFoundException", func() error {
|
||||
harness.ExpectError(t, "ValidationException", func() error {
|
||||
_, e := f.AWS.Bedrock.GetFoundationModel(&bedrock.GetFoundationModelInput{
|
||||
ModelIdentifier: aws.String(modelBogus),
|
||||
})
|
||||
@@ -41,7 +41,7 @@ func TestGetFoundationModel(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("provider-tier model ID is absent from v1", func(t *testing.T) {
|
||||
harness.ExpectError(t, "ResourceNotFoundException", func() error {
|
||||
harness.ExpectError(t, "ValidationException", func() error {
|
||||
_, e := f.AWS.Bedrock.GetFoundationModel(&bedrock.GetFoundationModelInput{
|
||||
ModelIdentifier: aws.String(modelAnthropic),
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user