Files
cloudpods/pkg/aiproxy/ft/catalog.go
Zexi Li f538dcf2e6 feat(aiproxy): add Anthropic Messages API and migrate functional tests to Go (#25097)
Add /v1/messages handler with Anthropic-to-OpenAI translation, upstream
failover, and probe endpoints. Replace shell-based functional test scripts
with pkg/aiproxy/ft and climc test commands; consolidate documentation.
2026-07-07 20:13:03 +08:00

170 lines
5.3 KiB
Go

// Copyright 2019 Yunion
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package ft
import (
"fmt"
"sort"
"strings"
"yunion.io/x/jsonutils"
"yunion.io/x/pkg/errors"
api "yunion.io/x/onecloud/pkg/apis/aiproxy"
"yunion.io/x/onecloud/pkg/mcclient"
apmodules "yunion.io/x/onecloud/pkg/mcclient/modules/aiproxy"
)
func CatalogModelID(providerKey, modelKey string) string {
return fmt.Sprintf("%s-%s", providerKey, modelKey)
}
func DefaultModelForProvider(providerKey string) string {
switch providerKey {
case api.ProviderKeyAliyun:
return "qwen-turbo"
case api.ProviderKeyXiaomi:
return "mimo-v2-flash"
case api.ProviderKeyDeepseek:
return "deepseek-v4-flash"
case api.ProviderKeyOpenAI:
return "gpt-4o-mini"
case api.ProviderKeyAnthropic:
return "claude-sonnet-4-5"
default:
return ""
}
}
func DefaultPromptForProvider(providerKey string) string {
switch providerKey {
case api.ProviderKeyAliyun:
return "用一句话介绍通义千问"
case api.ProviderKeyXiaomi:
return "用一句话介绍小米 MiMo"
default:
return "用一句话介绍这个模型"
}
}
func ListCatalogProviderKeys(session *mcclient.ClientSession) ([]string, error) {
query := jsonutils.NewDict()
query.Set("limit", jsonutils.NewInt(500))
result, err := apmodules.AiProviders.List(session, query)
if err != nil {
return nil, err
}
keys := make([]string, 0, len(result.Data))
seen := map[string]struct{}{}
for _, item := range result.Data {
pk, _ := item.GetString("provider_key")
pk = strings.TrimSpace(pk)
if pk == "" {
continue
}
if _, ok := seen[pk]; ok {
continue
}
seen[pk] = struct{}{}
keys = append(keys, pk)
}
sort.Strings(keys)
return keys, nil
}
func ListCatalogModelKeys(session *mcclient.ClientSession, providerKey string) ([]string, error) {
query := jsonutils.NewDict()
query.Set("limit", jsonutils.NewInt(500))
query.Set("ai_provider_id", jsonutils.NewString(providerKey))
result, err := apmodules.AiModels.List(session, query)
if err != nil {
return nil, err
}
models := make([]string, 0, len(result.Data))
seen := map[string]struct{}{}
for _, item := range result.Data {
mk, _ := item.GetString("model_key")
mk = strings.TrimSpace(mk)
if mk == "" || mk == "default" {
continue
}
if _, ok := seen[mk]; ok {
continue
}
seen[mk] = struct{}{}
models = append(models, mk)
}
sort.Strings(models)
return models, nil
}
func VerifyCatalog(session *mcclient.ClientSession, providerKey, modelKey string, warnMissingModel bool) error {
if _, err := apmodules.AiProviders.Get(session, providerKey, nil); err != nil {
return errors.Wrapf(err, "ai_provider %s missing; run aiproxy master InitDB first", providerKey)
}
if _, err := findAiModelByKey(session, providerKey, modelKey); err == nil {
return nil
}
catalogID := CatalogModelID(providerKey, modelKey)
if _, err := apmodules.AiModels.Get(session, catalogID, nil); err == nil {
return nil
}
if warnMissingModel {
fmt.Printf("WARN: ai_model %s not in catalog; will create for test if needed\n", catalogID)
return nil
}
return errors.Errorf("ai_model %s not in catalog (re-run aiproxy master InitDB)", catalogID)
}
func findAiModelByKey(session *mcclient.ClientSession, providerKey, modelKey string) (jsonutils.JSONObject, error) {
query := jsonutils.NewDict()
query.Set("limit", jsonutils.NewInt(10))
query.Set("ai_provider_id", jsonutils.NewString(providerKey))
query.Set("model_key", jsonutils.NewString(modelKey))
result, err := apmodules.AiModels.List(session, query)
if err != nil {
return nil, err
}
if len(result.Data) == 0 {
return nil, errors.Errorf("model_key %s not found under provider %s", modelKey, providerKey)
}
return result.Data[0], nil
}
// EnsureAiModel guarantees an ai_model row exists for providerKey/modelKey.
// Returns the model reference for ai_routing and the created resource name (if any).
func ensureAiModel(session *mcclient.ClientSession, tracker *ResourceTracker, providerKey, modelKey string) (routingModelRef string, createdName string, err error) {
catalogID := CatalogModelID(providerKey, modelKey)
if _, err := apmodules.AiModels.Get(session, catalogID, nil); err == nil {
return modelKey, "", nil
}
if _, err := findAiModelByKey(session, providerKey, modelKey); err == nil {
return modelKey, "", nil
}
fmt.Printf("ai_model %s not in catalog, creating for test\n", catalogID)
params := jsonutils.NewDict()
params.Set("name", jsonutils.NewString(catalogID))
params.Set("ai_provider_id", jsonutils.NewString(providerKey))
params.Set("model_key", jsonutils.NewString(modelKey))
params.Set("enabled", jsonutils.JSONTrue)
if _, err := apmodules.AiModels.Create(session, params); err != nil {
return "", "", errors.Wrapf(err, "ai-model-create %s/%s", providerKey, modelKey)
}
if tracker != nil {
tracker.createdAiModel = catalogID
}
return modelKey, catalogID, nil
}