mirror of
https://hubproxy.babadafafafafa.cn/https://github.com/1Panel-dev/1Panel.git
synced 2026-09-20 08:03:55 +08:00
feat: OpenClaw supports configuring model Max Tokens (#13818)
This commit is contained in:
@@ -162,16 +162,25 @@ type AgentWebsiteBindReq struct {
|
||||
}
|
||||
|
||||
type AgentModelConfigUpdateReq struct {
|
||||
AgentID uint `json:"agentId" validate:"required"`
|
||||
AccountID uint `json:"accountId" validate:"required"`
|
||||
Model string `json:"model" validate:"required"`
|
||||
Fallbacks []string `json:"fallbacks"`
|
||||
AgentID uint `json:"agentId" validate:"required"`
|
||||
AccountID uint `json:"accountId" validate:"required"`
|
||||
Model string `json:"model" validate:"required"`
|
||||
Fallbacks []string `json:"fallbacks"`
|
||||
Metadata []AgentModelMetadata `json:"metadata" validate:"dive"`
|
||||
}
|
||||
|
||||
type AgentModelConfig struct {
|
||||
AccountID uint `json:"accountId"`
|
||||
Model string `json:"model"`
|
||||
Fallbacks []string `json:"fallbacks"`
|
||||
AccountID uint `json:"accountId"`
|
||||
Model string `json:"model"`
|
||||
Fallbacks []string `json:"fallbacks"`
|
||||
Metadata []AgentModelMetadata `json:"metadata"`
|
||||
}
|
||||
|
||||
type AgentModelMetadata struct {
|
||||
Model string `json:"model" validate:"required"`
|
||||
InputMode string `json:"inputMode" validate:"required,oneof=auto text image"`
|
||||
ContextWindow int `json:"contextWindow" validate:"min=0"`
|
||||
MaxTokens int `json:"maxTokens" validate:"min=0"`
|
||||
}
|
||||
|
||||
type AgentHermesChatSessionItem struct {
|
||||
|
||||
@@ -936,6 +936,7 @@ func (a AgentService) GetModelConfig(req dto.AgentIDReq) (*dto.AgentModelConfig,
|
||||
AccountID: agent.AccountID,
|
||||
Model: model,
|
||||
Fallbacks: extractOpenclawFallbackModelIDs(conf, account, models, model),
|
||||
Metadata: extractOpenclawModelMetadata(conf, account, models),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -967,7 +968,7 @@ func (a AgentService) UpdateModelConfig(req dto.AgentModelConfigUpdateReq) error
|
||||
if agent.AgentType != constant.AppOpenclaw {
|
||||
return fmt.Errorf("%s does not support", agent.AgentType)
|
||||
}
|
||||
if err := writeOpenclawConfig(confDir, account, modelName, agent.Token, nil, req.Fallbacks); err != nil {
|
||||
if err := writeOpenclawConfig(confDir, account, modelName, agent.Token, nil, req.Fallbacks, req.Metadata); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -1684,7 +1685,7 @@ func (a AgentService) syncAgentsByAccount(account *model.AgentAccount) error {
|
||||
return err
|
||||
}
|
||||
fallbacks := extractOpenclawFallbackModelIDs(conf, account, accountModels, selectedAccountModel.ID)
|
||||
if err := writeOpenclawConfig(confDir, account, modelName, agent.Token, nil, fallbacks); err != nil {
|
||||
if err := writeOpenclawConfig(confDir, account, modelName, agent.Token, nil, fallbacks, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
case constant.AppHermesAgent:
|
||||
|
||||
@@ -1354,6 +1354,10 @@ func installOpenclawPlugin(mgr *cmd.CommandHelper, containerName, spec, pluginID
|
||||
} else if slices.Contains(options, "--dangerously-force-unsafe-install") {
|
||||
args = append(args, "--dangerously-force-unsafe-install")
|
||||
}
|
||||
// Source confirmation does not grant the selected channel plugin's capabilities.
|
||||
if slices.Contains(options, "--accept-capabilities") {
|
||||
args = append(args, "--accept-capabilities")
|
||||
}
|
||||
return mgr.Run("docker", args...)
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"net/url"
|
||||
"path"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -737,9 +738,11 @@ type modelProvider struct {
|
||||
}
|
||||
|
||||
type modelEntry struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Input []string `json:"input,omitempty"`
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Input []string `json:"input,omitempty"`
|
||||
ContextWindow int `json:"contextWindow,omitempty"`
|
||||
MaxTokens int `json:"maxTokens,omitempty"`
|
||||
}
|
||||
|
||||
func requiresOpenclawProviderModels(provider string) bool {
|
||||
@@ -767,7 +770,7 @@ type browserConfig struct {
|
||||
DefaultProfile string `json:"defaultProfile"`
|
||||
}
|
||||
|
||||
func writeOpenclawConfig(confDir string, account *model.AgentAccount, modelName, token string, allowedOrigins []string, fallbacks []string) error {
|
||||
func writeOpenclawConfig(confDir string, account *model.AgentAccount, modelName, token string, allowedOrigins []string, fallbacks []string, metadata []dto.AgentModelMetadata) error {
|
||||
if strings.TrimSpace(confDir) == "" {
|
||||
return fmt.Errorf("config dir is required")
|
||||
}
|
||||
@@ -852,6 +855,7 @@ func writeOpenclawConfig(confDir string, account *model.AgentAccount, modelName,
|
||||
}
|
||||
conf = initial
|
||||
} else {
|
||||
preserveOpenclawModelMetadata(conf, cfg.Models)
|
||||
if err := applyOpenclawModelsConfig(conf, cfg.Models); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -906,6 +910,9 @@ func writeOpenclawConfig(confDir string, account *model.AgentAccount, modelName,
|
||||
if allowedOrigins != nil {
|
||||
setSecurityConfig(conf, dto.AgentSecurityConfig{AllowedOrigins: allowedOrigins})
|
||||
}
|
||||
if err := applyOpenclawModelMetadata(conf, account, metadata); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := writeOpenclawConfigRaw(configPath, conf); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -920,6 +927,144 @@ func writeOpenclawConfig(confDir string, account *model.AgentAccount, modelName,
|
||||
return writeAgentEnvMap(path.Join(confDir, ".env"), envMap, order)
|
||||
}
|
||||
|
||||
func readOpenclawModelsConfig(conf map[string]interface{}) *modelsConfig {
|
||||
raw, ok := conf["models"]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
payload, err := json.Marshal(raw)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
var models modelsConfig
|
||||
if err := json.Unmarshal(payload, &models); err != nil {
|
||||
return nil
|
||||
}
|
||||
return &models
|
||||
}
|
||||
|
||||
func preserveOpenclawModelMetadata(conf map[string]interface{}, next *modelsConfig) {
|
||||
current := readOpenclawModelsConfig(conf)
|
||||
if current == nil || next == nil {
|
||||
return
|
||||
}
|
||||
for providerID, nextProvider := range next.Providers {
|
||||
currentProvider, ok := current.Providers[providerID]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
byID := make(map[string]modelEntry, len(currentProvider.Models))
|
||||
for _, entry := range currentProvider.Models {
|
||||
byID[entry.ID] = entry
|
||||
}
|
||||
for index := range nextProvider.Models {
|
||||
currentEntry, ok := byID[nextProvider.Models[index].ID]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
nextProvider.Models[index].Input = currentEntry.Input
|
||||
nextProvider.Models[index].ContextWindow = currentEntry.ContextWindow
|
||||
nextProvider.Models[index].MaxTokens = currentEntry.MaxTokens
|
||||
}
|
||||
next.Providers[providerID] = nextProvider
|
||||
}
|
||||
}
|
||||
|
||||
func extractOpenclawModelMetadata(conf map[string]interface{}, account *model.AgentAccount, accountModels []dto.AgentAccountModel) []dto.AgentModelMetadata {
|
||||
result := make([]dto.AgentModelMetadata, 0, len(accountModels))
|
||||
configured := readOpenclawModelsConfig(conf)
|
||||
for _, item := range accountModels {
|
||||
_, inferred, providerID, _, err := buildOpenclawAccountModelConfig(account, item)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
metadata := dto.AgentModelMetadata{Model: item.ID, InputMode: "auto"}
|
||||
if configured != nil {
|
||||
for _, entry := range configured.Providers[providerID].Models {
|
||||
if entry.ID != inferred.ID {
|
||||
continue
|
||||
}
|
||||
metadata.ContextWindow = entry.ContextWindow
|
||||
metadata.MaxTokens = entry.MaxTokens
|
||||
if len(entry.Input) > 0 && !slices.Equal(entry.Input, inferred.Input) {
|
||||
if slices.Contains(entry.Input, "image") {
|
||||
metadata.InputMode = "image"
|
||||
} else {
|
||||
metadata.InputMode = "text"
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
result = append(result, metadata)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func applyOpenclawModelMetadata(conf map[string]interface{}, account *model.AgentAccount, requested []dto.AgentModelMetadata) error {
|
||||
if len(requested) == 0 {
|
||||
return nil
|
||||
}
|
||||
configured := readOpenclawModelsConfig(conf)
|
||||
if configured == nil {
|
||||
return fmt.Errorf("model metadata is not supported for provider %s", account.Provider)
|
||||
}
|
||||
accountModels, err := loadAgentAccountModels(account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
available := make(map[string]dto.AgentAccountModel, len(accountModels))
|
||||
for _, item := range accountModels {
|
||||
available[item.ID] = item
|
||||
}
|
||||
seen := make(map[string]struct{}, len(requested))
|
||||
for _, metadata := range requested {
|
||||
item, ok := available[metadata.Model]
|
||||
if !ok {
|
||||
return buserr.New("ErrAgentModelNotInAccount")
|
||||
}
|
||||
if _, ok := seen[metadata.Model]; ok {
|
||||
return fmt.Errorf("duplicate model metadata: %s", metadata.Model)
|
||||
}
|
||||
seen[metadata.Model] = struct{}{}
|
||||
_, inferred, providerID, _, err := buildOpenclawAccountModelConfig(account, item)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
provider := configured.Providers[providerID]
|
||||
found := false
|
||||
for index := range provider.Models {
|
||||
if provider.Models[index].ID != inferred.ID {
|
||||
continue
|
||||
}
|
||||
found = true
|
||||
provider.Models[index].ContextWindow = metadata.ContextWindow
|
||||
provider.Models[index].MaxTokens = metadata.MaxTokens
|
||||
switch metadata.InputMode {
|
||||
case "auto":
|
||||
provider.Models[index].Input = inferred.Input
|
||||
case "text":
|
||||
provider.Models[index].Input = []string{"text"}
|
||||
case "image":
|
||||
provider.Models[index].Input = []string{"text", "image"}
|
||||
default:
|
||||
return fmt.Errorf("unsupported model input mode: %s", metadata.InputMode)
|
||||
}
|
||||
break
|
||||
}
|
||||
if !found {
|
||||
return buserr.New("ErrAgentModelNotInAccount")
|
||||
}
|
||||
configured.Providers[providerID] = provider
|
||||
}
|
||||
modelsMap, err := structToMap(configured)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conf["models"] = modelsMap
|
||||
return nil
|
||||
}
|
||||
|
||||
func resolveOpenclawFallbackModels(account *model.AgentAccount, primaryModel string, fallbackIDs []string) ([]string, error) {
|
||||
accountModels, err := loadAgentAccountModels(account)
|
||||
if err != nil {
|
||||
@@ -1041,7 +1186,7 @@ func prepareOpenclawInstallFiles(appInstall *model.AppInstall, account *model.Ag
|
||||
return fmt.Errorf("app install is required")
|
||||
}
|
||||
confDir := path.Join(appInstall.GetPath(), "data", "conf")
|
||||
if err := writeOpenclawConfig(confDir, account, modelName, token, allowedOrigins, nil); err != nil {
|
||||
if err := writeOpenclawConfig(confDir, account, modelName, token, allowedOrigins, nil, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
dataDir := path.Join(appInstall.GetPath(), "data")
|
||||
|
||||
@@ -426,12 +426,21 @@ export namespace AI {
|
||||
accountId: number;
|
||||
model: string;
|
||||
fallbacks: string[];
|
||||
metadata: AgentModelMetadata[];
|
||||
}
|
||||
|
||||
export interface AgentModelConfig {
|
||||
accountId: number;
|
||||
model: string;
|
||||
fallbacks: string[];
|
||||
metadata: AgentModelMetadata[];
|
||||
}
|
||||
|
||||
export interface AgentModelMetadata {
|
||||
model: string;
|
||||
inputMode: 'auto' | 'text' | 'image';
|
||||
contextWindow: number;
|
||||
maxTokens: number;
|
||||
}
|
||||
|
||||
export interface AgentHermesChatSessionItem {
|
||||
|
||||
@@ -758,6 +758,14 @@ const message = {
|
||||
addFallbackModel: 'Add Fallback Model',
|
||||
fallbackModelsEmpty: 'No fallback models configured',
|
||||
modelInputTypes: 'Input Types',
|
||||
modelMetadata: 'Model Capabilities',
|
||||
modelMetadataHelper:
|
||||
'These settings apply only to this OpenClaw instance. Leave values empty to use OpenClaw or provider defaults.',
|
||||
inputModeAuto: 'Auto detect',
|
||||
inputModeText: 'Text only',
|
||||
inputModeImage: 'Text and images',
|
||||
contextWindow: 'Context Window',
|
||||
useModelDefault: 'Use model default',
|
||||
reasoning: 'Reasoning Model',
|
||||
verified: 'Verified',
|
||||
verifySkipped: 'No verification',
|
||||
|
||||
@@ -764,6 +764,14 @@ const message = {
|
||||
addFallbackModel: 'Agregar modelo de respaldo',
|
||||
fallbackModelsEmpty: 'No hay modelos de respaldo configurados',
|
||||
modelInputTypes: 'Tipos de entrada',
|
||||
modelMetadata: 'Capacidades del modelo',
|
||||
modelMetadataHelper:
|
||||
'Estos ajustes solo se aplican a esta instancia de OpenClaw. Déjelos vacíos para usar los valores predeterminados de OpenClaw o del proveedor.',
|
||||
inputModeAuto: 'Detectar automáticamente',
|
||||
inputModeText: 'Solo texto',
|
||||
inputModeImage: 'Texto e imágenes',
|
||||
contextWindow: 'Ventana de contexto',
|
||||
useModelDefault: 'Usar valor predeterminado del modelo',
|
||||
reasoning: 'Modelo de razonamiento',
|
||||
verified: 'Verificado',
|
||||
verifySkipped: 'Sin verificación',
|
||||
|
||||
@@ -755,6 +755,14 @@ const message = {
|
||||
addFallbackModel: 'افزودن مدل جایگزین',
|
||||
fallbackModelsEmpty: 'هیچ مدل جایگزینی پیکربندی نشده است',
|
||||
modelInputTypes: 'انواع ورودی',
|
||||
modelMetadata: 'قابلیتهای مدل',
|
||||
modelMetadataHelper:
|
||||
'این تنظیمات فقط برای نمونه فعلی OpenClaw اعمال میشوند. برای استفاده از پیشفرض OpenClaw یا ارائهدهنده، فیلدها را خالی بگذارید.',
|
||||
inputModeAuto: 'تشخیص خودکار',
|
||||
inputModeText: 'فقط متن',
|
||||
inputModeImage: 'متن و تصویر',
|
||||
contextWindow: 'پنجره زمینه',
|
||||
useModelDefault: 'استفاده از پیشفرض مدل',
|
||||
reasoning: 'مدل استدلالی',
|
||||
verified: 'تأیید شده',
|
||||
verifySkipped: 'بدون تأیید',
|
||||
|
||||
@@ -759,6 +759,14 @@ const message = {
|
||||
addFallbackModel: 'フォールバックモデルを追加',
|
||||
fallbackModelsEmpty: 'フォールバックモデルは未設定です',
|
||||
modelInputTypes: '入力タイプ',
|
||||
modelMetadata: 'モデル機能設定',
|
||||
modelMetadataHelper:
|
||||
'この設定は現在の OpenClaw にのみ適用されます。空欄の場合は OpenClaw またはプロバイダーの既定値を使用します。',
|
||||
inputModeAuto: '自動判定',
|
||||
inputModeText: 'テキストのみ',
|
||||
inputModeImage: 'テキストと画像',
|
||||
contextWindow: 'コンテキストウィンドウ',
|
||||
useModelDefault: 'モデルの既定値を使用',
|
||||
reasoning: '推論モデル',
|
||||
verified: '検証済み',
|
||||
verifySkipped: '検証なし',
|
||||
|
||||
@@ -749,6 +749,14 @@ const message = {
|
||||
addFallbackModel: '폴백 모델 추가',
|
||||
fallbackModelsEmpty: '구성된 폴백 모델이 없습니다',
|
||||
modelInputTypes: '입력 유형',
|
||||
modelMetadata: '모델 기능 설정',
|
||||
modelMetadataHelper:
|
||||
'이 설정은 현재 OpenClaw에만 적용됩니다. 비워 두면 OpenClaw 또는 공급자의 기본값을 사용합니다.',
|
||||
inputModeAuto: '자동 감지',
|
||||
inputModeText: '텍스트만',
|
||||
inputModeImage: '텍스트 및 이미지',
|
||||
contextWindow: '컨텍스트 창',
|
||||
useModelDefault: '모델 기본값 사용',
|
||||
reasoning: '추론 모델',
|
||||
verified: '검증됨',
|
||||
verifySkipped: '검증 안 함',
|
||||
|
||||
@@ -750,6 +750,14 @@ const message = {
|
||||
addFallbackModel: 'ເພີ່ມໂມເດວສຳຮອງ',
|
||||
fallbackModelsEmpty: 'ຍັງບໍ່ໄດ້ຕັ້ງຄ່າໂມເດວສຳຮອງ',
|
||||
modelInputTypes: 'ປະເພດຂໍ້ມູນຂາເຂົ້າ',
|
||||
modelMetadata: 'ຄວາມສາມາດຂອງໂມເດວ',
|
||||
modelMetadataHelper:
|
||||
'ການຕັ້ງຄ່ານີ້ໃຊ້ກັບ OpenClaw ປັດຈຸບັນເທົ່ານັ້ນ. ປ່ອຍຫວ່າງເພື່ອໃຊ້ຄ່າເລີ່ມຕົ້ນຂອງ OpenClaw ຫຼືຜູ້ໃຫ້ບໍລິການ.',
|
||||
inputModeAuto: 'ກວດຫາອັດຕະໂນມັດ',
|
||||
inputModeText: 'ຂໍ້ຄວາມເທົ່ານັ້ນ',
|
||||
inputModeImage: 'ຂໍ້ຄວາມແລະຮູບພາບ',
|
||||
contextWindow: 'ຂອບເຂດບໍລິບົດ',
|
||||
useModelDefault: 'ໃຊ້ຄ່າເລີ່ມຕົ້ນຂອງໂມເດວ',
|
||||
reasoning: 'ໂມເດວການໃຊ້ເຫດຜົນ',
|
||||
verified: 'ກວດສອບແລ້ວ',
|
||||
verifySkipped: 'ບໍ່ມີການກວດສອບ',
|
||||
|
||||
@@ -767,6 +767,14 @@ const message = {
|
||||
addFallbackModel: 'Tambah model fallback',
|
||||
fallbackModelsEmpty: 'Tiada model fallback dikonfigurasikan',
|
||||
modelInputTypes: 'Jenis input',
|
||||
modelMetadata: 'Keupayaan model',
|
||||
modelMetadataHelper:
|
||||
'Tetapan ini hanya digunakan pada tika OpenClaw semasa. Biarkan kosong untuk menggunakan nilai lalai OpenClaw atau penyedia.',
|
||||
inputModeAuto: 'Kesan secara automatik',
|
||||
inputModeText: 'Teks sahaja',
|
||||
inputModeImage: 'Teks dan imej',
|
||||
contextWindow: 'Tetingkap konteks',
|
||||
useModelDefault: 'Gunakan lalai model',
|
||||
reasoning: 'Model penaakulan',
|
||||
verified: 'Disahkan',
|
||||
verifySkipped: 'Tanpa pengesahan',
|
||||
|
||||
@@ -761,6 +761,14 @@ const message = {
|
||||
addFallbackModel: 'Adicionar modelo de fallback',
|
||||
fallbackModelsEmpty: 'Nenhum modelo de fallback configurado',
|
||||
modelInputTypes: 'Tipos de entrada',
|
||||
modelMetadata: 'Recursos do modelo',
|
||||
modelMetadataHelper:
|
||||
'Estas configurações se aplicam somente a esta instância do OpenClaw. Deixe em branco para usar os padrões do OpenClaw ou do provedor.',
|
||||
inputModeAuto: 'Detectar automaticamente',
|
||||
inputModeText: 'Somente texto',
|
||||
inputModeImage: 'Texto e imagens',
|
||||
contextWindow: 'Janela de contexto',
|
||||
useModelDefault: 'Usar padrão do modelo',
|
||||
reasoning: 'Modelo de raciocínio',
|
||||
verified: 'Verificado',
|
||||
verifySkipped: 'Sem verificação',
|
||||
|
||||
@@ -756,6 +756,14 @@ const message = {
|
||||
addFallbackModel: 'Добавить резервную модель',
|
||||
fallbackModelsEmpty: 'Резервные модели не настроены',
|
||||
modelInputTypes: 'Типы ввода',
|
||||
modelMetadata: 'Возможности модели',
|
||||
modelMetadataHelper:
|
||||
'Эти настройки применяются только к текущему экземпляру OpenClaw. Оставьте поля пустыми, чтобы использовать значения OpenClaw или поставщика.',
|
||||
inputModeAuto: 'Определять автоматически',
|
||||
inputModeText: 'Только текст',
|
||||
inputModeImage: 'Текст и изображения',
|
||||
contextWindow: 'Контекстное окно',
|
||||
useModelDefault: 'Использовать значение модели',
|
||||
reasoning: 'Модель рассуждений',
|
||||
verified: 'Проверено',
|
||||
verifySkipped: 'Без проверки',
|
||||
|
||||
@@ -762,6 +762,14 @@ const message = {
|
||||
addFallbackModel: 'Yedek model ekle',
|
||||
fallbackModelsEmpty: 'Yapılandırılmış yedek model yok',
|
||||
modelInputTypes: 'Girdi türleri',
|
||||
modelMetadata: 'Model yetenekleri',
|
||||
modelMetadataHelper:
|
||||
'Bu ayarlar yalnızca mevcut OpenClaw örneğine uygulanır. OpenClaw veya sağlayıcı varsayılanlarını kullanmak için boş bırakın.',
|
||||
inputModeAuto: 'Otomatik algıla',
|
||||
inputModeText: 'Yalnızca metin',
|
||||
inputModeImage: 'Metin ve görseller',
|
||||
contextWindow: 'Bağlam penceresi',
|
||||
useModelDefault: 'Model varsayılanını kullan',
|
||||
reasoning: 'Akıl yürütme modeli',
|
||||
verified: 'Doğrulandı',
|
||||
verifySkipped: 'Doğrulama yok',
|
||||
|
||||
@@ -729,6 +729,13 @@ const message = {
|
||||
addFallbackModel: '新增備用模型',
|
||||
fallbackModelsEmpty: '尚未設定備用模型',
|
||||
modelInputTypes: '輸入類型',
|
||||
modelMetadata: '模型能力設定',
|
||||
modelMetadataHelper: '設定僅套用於目前的 OpenClaw。留空時使用 OpenClaw 或模型供應商的預設值。',
|
||||
inputModeAuto: '自動識別',
|
||||
inputModeText: '僅文字',
|
||||
inputModeImage: '文字和圖片',
|
||||
contextWindow: '上下文視窗',
|
||||
useModelDefault: '使用模型預設值',
|
||||
reasoning: '推理模型',
|
||||
verified: '驗證狀態',
|
||||
verifySkipped: '略過驗證',
|
||||
|
||||
@@ -746,6 +746,13 @@ const message = {
|
||||
addFallbackModel: '新增备用模型',
|
||||
fallbackModelsEmpty: '尚未配置备用模型',
|
||||
modelInputTypes: '输入类型',
|
||||
modelMetadata: '模型能力配置',
|
||||
modelMetadataHelper: '配置仅作用于当前 OpenClaw。留空时使用 OpenClaw 或模型供应商的默认值。',
|
||||
inputModeAuto: '自动识别',
|
||||
inputModeText: '仅文本',
|
||||
inputModeImage: '文本和图片',
|
||||
contextWindow: '上下文窗口',
|
||||
useModelDefault: '使用模型默认值',
|
||||
reasoning: '推理模型',
|
||||
verified: '验证状态',
|
||||
verifySkipped: '不验证',
|
||||
|
||||
@@ -55,6 +55,46 @@
|
||||
</el-table>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<template v-if="showMetadata">
|
||||
<el-divider content-position="left">{{ t('aiTools.agents.modelMetadata') }}</el-divider>
|
||||
<span class="input-help metadata-help">{{ t('aiTools.agents.modelMetadataHelper') }}</span>
|
||||
<el-table :data="metadataRows" size="small">
|
||||
<el-table-column :label="t('aiTools.model.model')" min-width="180">
|
||||
<template #default="{ row }">
|
||||
{{ modelOptions.find((item) => item.id === row.model)?.name || row.model }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('aiTools.agents.modelInputTypes')" min-width="160">
|
||||
<template #default="{ row }">
|
||||
<el-select v-model="row.inputMode">
|
||||
<el-option :label="t('aiTools.agents.inputModeAuto')" value="auto" />
|
||||
<el-option :label="t('aiTools.agents.inputModeText')" value="text" />
|
||||
<el-option :label="t('aiTools.agents.inputModeImage')" value="image" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('aiTools.agents.contextWindow')" min-width="150">
|
||||
<template #default="{ row }">
|
||||
<el-input-number
|
||||
v-model="row.contextWindow"
|
||||
:min="1"
|
||||
:controls="false"
|
||||
:placeholder="t('aiTools.agents.useModelDefault')"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="Max Tokens" min-width="150">
|
||||
<template #default="{ row }">
|
||||
<el-input-number
|
||||
v-model="row.maxTokens"
|
||||
:min="1"
|
||||
:controls="false"
|
||||
:placeholder="t('aiTools.agents.useModelDefault')"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</template>
|
||||
<el-form-item>
|
||||
<el-button v-permission type="primary" :loading="saving" @click="saveModel">
|
||||
{{ t('commons.button.save') }}
|
||||
@@ -88,6 +128,16 @@ const agentType = ref<AI.AgentType>('openclaw');
|
||||
const accountOptions = ref<AI.AgentAccountItem[]>([]);
|
||||
const modelOptions = ref<AI.AgentAccountModel[]>([]);
|
||||
const showFallbacks = computed(() => agentType.value === 'openclaw');
|
||||
const metadataRows = ref<
|
||||
Array<
|
||||
Omit<AI.AgentModelMetadata, 'contextWindow' | 'maxTokens'> & {
|
||||
contextWindow?: number;
|
||||
maxTokens?: number;
|
||||
}
|
||||
>
|
||||
>([]);
|
||||
const selectedAccount = computed(() => accountOptions.value.find((item) => item.id === form.accountId));
|
||||
const showMetadata = computed(() => agentType.value === 'openclaw' && selectedAccount.value?.provider !== 'gemini');
|
||||
|
||||
interface AgentModelLoadParams {
|
||||
agentId: number;
|
||||
@@ -145,11 +195,25 @@ const syncFallbacks = (fallbacks?: string[]) => {
|
||||
}
|
||||
};
|
||||
|
||||
const syncMetadata = (metadata: AI.AgentModelMetadata[] = []) => {
|
||||
const configured = new Map(metadata.map((item) => [item.model, item]));
|
||||
metadataRows.value = modelOptions.value.map((model) => {
|
||||
const item = configured.get(model.id);
|
||||
return {
|
||||
model: model.id,
|
||||
inputMode: item?.inputMode || 'auto',
|
||||
contextWindow: item?.contextWindow || undefined,
|
||||
maxTokens: item?.maxTokens || undefined,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const handleAccountChange = () => {
|
||||
setModelOptionsByAccount(form.accountId);
|
||||
ensureSelectedModel();
|
||||
form.fallbacks = [];
|
||||
fallbackCandidate.value = '';
|
||||
syncMetadata();
|
||||
};
|
||||
|
||||
const handleModelChange = () => {
|
||||
@@ -199,6 +263,7 @@ const load = async (params: AgentModelLoadParams) => {
|
||||
form.model = '';
|
||||
form.fallbacks = [];
|
||||
modelOptions.value = [];
|
||||
metadataRows.value = [];
|
||||
return;
|
||||
}
|
||||
const config = configRes.data;
|
||||
@@ -208,6 +273,7 @@ const load = async (params: AgentModelLoadParams) => {
|
||||
setModelOptionsByAccount(currentAccount.id);
|
||||
ensureSelectedModel(config.model);
|
||||
syncFallbacks(showFallbacks.value ? config.fallbacks || [] : []);
|
||||
syncMetadata(showMetadata.value ? config.metadata || [] : []);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
@@ -225,6 +291,14 @@ const saveModel = async () => {
|
||||
accountId: form.accountId,
|
||||
model: form.model,
|
||||
fallbacks: showFallbacks.value ? form.fallbacks : [],
|
||||
metadata: showMetadata.value
|
||||
? metadataRows.value.map((item) => ({
|
||||
model: item.model,
|
||||
inputMode: item.inputMode,
|
||||
contextWindow: item.contextWindow || 0,
|
||||
maxTokens: item.maxTokens || 0,
|
||||
}))
|
||||
: [],
|
||||
});
|
||||
MsgSuccess(t('aiTools.agents.switchModelSuccess'));
|
||||
emit('updated');
|
||||
@@ -257,4 +331,13 @@ defineExpose({
|
||||
padding: 12px 0;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
|
||||
.metadata-help {
|
||||
display: block;
|
||||
margin: -8px 0 12px;
|
||||
}
|
||||
|
||||
:deep(.el-input-number) {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user