mirror of
https://hubproxy.babadafafafafa.cn/https://github.com/yunionio/cloudpods.git
synced 2026-09-20 08:03:53 +08:00
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.
242 lines
7.2 KiB
Go
242 lines
7.2 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 models
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"yunion.io/x/jsonutils"
|
|
"yunion.io/x/log"
|
|
"yunion.io/x/pkg/errors"
|
|
|
|
"yunion.io/x/onecloud/pkg/apis"
|
|
api "yunion.io/x/onecloud/pkg/apis/aiproxy"
|
|
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
|
"yunion.io/x/onecloud/pkg/mcclient"
|
|
)
|
|
|
|
// standardCatalogProviderKeys lists built-in provider_key values seeded at InitDB.
|
|
var standardCatalogProviderKeys = api.StandardCatalogProviderKeys
|
|
|
|
func standardProviderConfig(providerKey string) *api.SAiProviderConfig {
|
|
if u := api.DefaultPublicBaseURL(providerKey); u != "" {
|
|
return &api.SAiProviderConfig{BaseURL: u}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
const placeholderCatalogModelKey = "default"
|
|
|
|
func catalogProviderId(providerKey string) string {
|
|
return providerKey
|
|
}
|
|
|
|
func catalogProviderExists(providerId string) (bool, error) {
|
|
cnt, err := AiProviderManager.RawQuery().Equals("id", providerId).CountWithError()
|
|
if err != nil {
|
|
return false, errors.Wrap(err, "count catalog ai_provider")
|
|
}
|
|
return cnt > 0, nil
|
|
}
|
|
|
|
func catalogModelExists(modelId string) (bool, error) {
|
|
cnt, err := AiModelManager.RawQuery().Equals("id", modelId).CountWithError()
|
|
if err != nil {
|
|
return false, errors.Wrap(err, "count catalog ai_model")
|
|
}
|
|
return cnt > 0, nil
|
|
}
|
|
|
|
func insertCatalogProvider(ctx context.Context, providerKey, description string, cfg *api.SAiProviderConfig) error {
|
|
providerId := catalogProviderId(providerKey)
|
|
exists, err := catalogProviderExists(providerId)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if exists {
|
|
return nil
|
|
}
|
|
|
|
prov := SAiProvider{}
|
|
prov.SetModelManager(AiProviderManager, &prov)
|
|
prov.Id = providerId
|
|
prov.Name = providerKey
|
|
prov.ProviderKey = providerKey
|
|
prov.Description = description
|
|
prov.Config = cfg
|
|
prov.SetEnabled(true)
|
|
prov.Status = apis.STATUS_AVAILABLE
|
|
prov.Progress = 100
|
|
if err := AiProviderManager.TableSpec().Insert(ctx, &prov); err != nil {
|
|
return errors.Wrapf(err, "insert ai_provider %s", providerKey)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func insertCatalogModel(ctx context.Context, providerId, providerKey, modelKey, description string) error {
|
|
modelId := catalogModelId(providerKey, modelKey)
|
|
exists, err := catalogModelExists(modelId)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if exists {
|
|
return nil
|
|
}
|
|
|
|
m := SAiModel{}
|
|
m.SetModelManager(AiModelManager, &m)
|
|
m.Id = modelId
|
|
m.Name = modelId
|
|
m.AiProviderId = providerId
|
|
m.ModelKey = modelKey
|
|
m.Description = description
|
|
m.SetEnabled(true)
|
|
m.Status = apis.STATUS_AVAILABLE
|
|
m.Progress = 100
|
|
if err := AiModelManager.TableSpec().Insert(ctx, &m); err != nil {
|
|
return errors.Wrapf(err, "insert ai_model %s/%s", providerKey, modelKey)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func ensureSeedModelsEntries(ctx context.Context, providerId, providerKey string, entries []catalogSeedModel) error {
|
|
if len(entries) == 0 {
|
|
return insertCatalogModel(ctx, providerId, providerKey, placeholderCatalogModelKey,
|
|
"Catalog seed placeholder; replace with concrete model_key values or use a provider with a built-in catalog.")
|
|
}
|
|
for i := range entries {
|
|
if err := insertCatalogModel(ctx, providerId, providerKey, entries[i].ModelKey, entries[i].Description); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func ensureSeedProvider(ctx context.Context, providerKey string) error {
|
|
providerKey = strings.TrimSpace(providerKey)
|
|
providerId := catalogProviderId(providerKey)
|
|
if err := insertCatalogProvider(ctx, providerKey,
|
|
fmt.Sprintf("Standard provider catalog entry: %s", providerKey),
|
|
standardProviderConfig(providerKey)); err != nil {
|
|
return err
|
|
}
|
|
return ensureSeedModelsEntries(ctx, providerId, providerKey, catalogSeedModelsForProvider(providerKey))
|
|
}
|
|
|
|
func providerModelExists(providerId, modelKey string) (bool, error) {
|
|
cnt, err := AiModelManager.Query().
|
|
Equals("ai_provider_id", providerId).
|
|
Equals("model_key", modelKey).
|
|
CountWithError()
|
|
if err != nil {
|
|
return false, errors.Wrap(err, "count ai_model for provider")
|
|
}
|
|
return cnt > 0, nil
|
|
}
|
|
|
|
func createUserProviderModel(
|
|
ctx context.Context,
|
|
userCred mcclient.TokenCredential,
|
|
ownerId mcclient.IIdentityProvider,
|
|
prov *SAiProvider,
|
|
modelKey, description string,
|
|
) error {
|
|
if prov == nil || strings.TrimSpace(prov.Id) == "" {
|
|
return errors.Error("ai_provider is nil or has no id")
|
|
}
|
|
modelKey = strings.TrimSpace(modelKey)
|
|
if modelKey == "" {
|
|
return errors.Error("model_key is empty")
|
|
}
|
|
exists, err := providerModelExists(prov.Id, modelKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if exists {
|
|
return nil
|
|
}
|
|
dataDict := jsonutils.NewDict()
|
|
dataDict.Set("ai_provider_id", jsonutils.NewString(prov.Id))
|
|
dataDict.Set("model_key", jsonutils.NewString(modelKey))
|
|
dataDict.Set("enabled", jsonutils.JSONTrue)
|
|
dataDict.Set("generate_name", jsonutils.NewString(defaultAiModelName(prov.Name, modelKey)))
|
|
if desc := strings.TrimSpace(description); desc != "" {
|
|
dataDict.Set("description", jsonutils.NewString(desc))
|
|
}
|
|
if _, err := db.DoCreate(AiModelManager, ctx, userCred, nil, dataDict, ownerId); err != nil {
|
|
return errors.Wrapf(err, "create ai_model %q for provider %s", modelKey, prov.Id)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func createSelectedProviderModels(
|
|
ctx context.Context,
|
|
userCred mcclient.TokenCredential,
|
|
ownerId mcclient.IIdentityProvider,
|
|
prov *SAiProvider,
|
|
modelKeys []string,
|
|
) error {
|
|
if prov == nil {
|
|
return nil
|
|
}
|
|
for _, modelKey := range modelKeys {
|
|
if err := createUserProviderModel(ctx, userCred, ownerId, prov, modelKey, ""); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// createCatalogModelsForUserProvider inserts built-in catalog model rows for a newly created public SaaS provider.
|
|
func createCatalogModelsForUserProvider(
|
|
ctx context.Context,
|
|
userCred mcclient.TokenCredential,
|
|
ownerId mcclient.IIdentityProvider,
|
|
prov *SAiProvider,
|
|
) error {
|
|
if prov == nil {
|
|
return nil
|
|
}
|
|
pk := strings.TrimSpace(prov.ProviderKey)
|
|
if !api.HasDefaultPublicBaseURL(pk) {
|
|
return nil
|
|
}
|
|
entries := catalogSeedModelsForProvider(pk)
|
|
if len(entries) == 0 {
|
|
return createUserProviderModel(ctx, userCred, ownerId, prov, placeholderCatalogModelKey,
|
|
"Catalog seed placeholder; replace with concrete model_key values or use a provider with a built-in catalog.")
|
|
}
|
|
for i := range entries {
|
|
if err := createUserProviderModel(ctx, userCred, ownerId, prov, entries[i].ModelKey, entries[i].Description); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// SeedStandardCatalog inserts built-in ai_provider / ai_model catalog rows on first init only.
|
|
// Existing rows are left unchanged so user config survives service restarts.
|
|
func SeedStandardCatalog(ctx context.Context) error {
|
|
for _, pk := range standardCatalogProviderKeys {
|
|
if err := ensureSeedProvider(ctx, pk); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
log.Infof("aiproxy: standard catalog seed completed (%d providers)", len(standardCatalogProviderKeys))
|
|
return nil
|
|
}
|