mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-09-24 08:00:57 +00:00
llmman (https://github.com/llmmanorg/llmman) is a local model runner serving Ollama- and OpenAI-compatible routes on 127.0.0.1:17434. Register it in the agent provider catalog next to Ollama and extend every Ollama special case (no API key, verification skipped, OpenClaw placeholder key, manual initial model) to cover it as well.
238 lines
6.6 KiB
Go
238 lines
6.6 KiB
Go
package ai
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"github.com/1Panel-dev/1Panel/agent/app/model"
|
|
providercatalog "github.com/1Panel-dev/1Panel/agent/app/provider"
|
|
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
|
"github.com/1Panel-dev/1Panel/agent/global"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
var agentAccountRepo = repo.NewIAgentAccountRepo()
|
|
var agentAccountModelRepo = repo.NewIAgentAccountModelRepo()
|
|
var terminalRuntimeVersion atomic.Uint64
|
|
var fileAIRuntimeVersion atomic.Uint64
|
|
|
|
type TerminalRuntimeSettings struct {
|
|
AccountID uint
|
|
Prefix string
|
|
RiskCommands []string
|
|
}
|
|
|
|
func CurrentTerminalRuntimeVersion() uint64 {
|
|
return terminalRuntimeVersion.Load()
|
|
}
|
|
|
|
func InvalidateTerminalRuntimeCache() {
|
|
terminalRuntimeVersion.Add(1)
|
|
}
|
|
|
|
func InvalidateFileAIRuntimeCache() {
|
|
fileAIRuntimeVersion.Add(1)
|
|
}
|
|
|
|
func ResolveGeneratorConfig(accountID uint) (GeneratorConfig, time.Duration, error) {
|
|
account, err := loadAgentAccount(accountID)
|
|
if err != nil {
|
|
return GeneratorConfig{}, 0, err
|
|
}
|
|
|
|
provider := strings.ToLower(strings.TrimSpace(account.Provider))
|
|
if provider == "" {
|
|
return GeneratorConfig{}, 0, fmt.Errorf("agent account provider is required")
|
|
}
|
|
apiType := strings.TrimSpace(account.APIType)
|
|
if providercatalog.IsImageAPIType(apiType) {
|
|
return GeneratorConfig{}, 0, fmt.Errorf("api type %s does not support text generation", apiType)
|
|
}
|
|
model, err := resolveAccountModelConfig(account.ID, provider)
|
|
if err != nil {
|
|
return GeneratorConfig{}, 0, err
|
|
}
|
|
baseURL := strings.TrimSpace(account.BaseURL)
|
|
if baseURL == "" {
|
|
if defaultURL, ok := providercatalog.DefaultBaseURL(provider); ok {
|
|
baseURL = defaultURL
|
|
}
|
|
}
|
|
apiKey := strings.TrimSpace(account.APIKey)
|
|
if apiKey == "" {
|
|
apiKey = lookupProviderAPIKey(provider)
|
|
}
|
|
if apiKey == "" && provider != "ollama" && provider != "llmman" {
|
|
return GeneratorConfig{}, 0, fmt.Errorf("agent account api key is required")
|
|
}
|
|
return GeneratorConfig{
|
|
Provider: provider,
|
|
BaseURL: baseURL,
|
|
APIKey: strings.TrimSpace(apiKey),
|
|
Model: model,
|
|
APIType: apiType,
|
|
AuthMode: strings.TrimSpace(account.AuthMode),
|
|
MaxTokens: defaultTerminalTokens,
|
|
}, 30 * time.Second, nil
|
|
}
|
|
|
|
func lookupProviderAPIKey(provider string) string {
|
|
envKey := providercatalog.EnvKey(provider)
|
|
if envKey == "" {
|
|
return ""
|
|
}
|
|
return strings.TrimSpace(os.Getenv(envKey))
|
|
}
|
|
|
|
func defaultModelForProvider(provider string) string {
|
|
meta, ok := providercatalog.Get(provider)
|
|
if !ok || len(meta.Models) == 0 {
|
|
return ""
|
|
}
|
|
return meta.Models[0].ID
|
|
}
|
|
|
|
func resolveAccountModelConfig(accountID uint, provider string) (string, error) {
|
|
if accountID > 0 {
|
|
rows, err := agentAccountModelRepo.List(repo.WithByAccountID(accountID), repo.WithOrderAsc("sort_order"), repo.WithOrderAsc("id"))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if len(rows) > 0 {
|
|
return strings.TrimSpace(rows[0].Model), nil
|
|
}
|
|
}
|
|
return defaultModelForProvider(provider), nil
|
|
}
|
|
|
|
func ResolveGeneratorConfigFromAgentSettings() (GeneratorConfig, uint, time.Duration, error) {
|
|
status, err := loadAgentSettingValue("AIStatus")
|
|
if err != nil && !os.IsNotExist(err) {
|
|
return GeneratorConfig{}, 0, 0, err
|
|
}
|
|
if !strings.EqualFold(strings.TrimSpace(status), "Enable") {
|
|
return GeneratorConfig{}, 0, 0, os.ErrNotExist
|
|
}
|
|
accountValue, err := loadAgentSettingValue("AIAccountID")
|
|
if err != nil {
|
|
return GeneratorConfig{}, 0, 0, err
|
|
}
|
|
accountID, err := strconv.ParseUint(strings.TrimSpace(accountValue), 10, 64)
|
|
if err != nil || accountID == 0 {
|
|
return GeneratorConfig{}, 0, 0, os.ErrNotExist
|
|
}
|
|
config, timeout, err := ResolveGeneratorConfig(uint(accountID))
|
|
return config, uint(accountID), timeout, err
|
|
}
|
|
|
|
func ResolveGeneratorConfigFromFileSettings() (GeneratorConfig, uint, time.Duration, error) {
|
|
status, err := loadAgentSettingValue("FileAIStatus")
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return GeneratorConfig{}, 0, 0, os.ErrNotExist
|
|
}
|
|
return GeneratorConfig{}, 0, 0, err
|
|
}
|
|
if !strings.EqualFold(strings.TrimSpace(status), "Enable") {
|
|
return GeneratorConfig{}, 0, 0, os.ErrNotExist
|
|
}
|
|
accountValue, err := loadAgentSettingValue("FileAIAccountID")
|
|
if err != nil {
|
|
return GeneratorConfig{}, 0, 0, err
|
|
}
|
|
accountID, err := strconv.ParseUint(strings.TrimSpace(accountValue), 10, 64)
|
|
if err != nil || accountID == 0 {
|
|
return GeneratorConfig{}, 0, 0, os.ErrNotExist
|
|
}
|
|
config, timeout, err := ResolveGeneratorConfig(uint(accountID))
|
|
return config, uint(accountID), timeout, err
|
|
}
|
|
|
|
func LoadFileAIRuntimeConfig() (GeneratorConfig, time.Duration, error) {
|
|
cfg, _, timeout, err := ResolveGeneratorConfigFromFileSettings()
|
|
return cfg, timeout, err
|
|
}
|
|
|
|
func LoadTerminalRuntimeSettings() (TerminalRuntimeSettings, GeneratorConfig, time.Duration, error) {
|
|
config, accountID, timeout, err := ResolveGeneratorConfigFromAgentSettings()
|
|
if err != nil {
|
|
return TerminalRuntimeSettings{}, GeneratorConfig{}, 0, err
|
|
}
|
|
prefix, err := loadAgentSettingValue("AIPrefix")
|
|
if err != nil && !os.IsNotExist(err) {
|
|
return TerminalRuntimeSettings{}, GeneratorConfig{}, 0, err
|
|
}
|
|
riskCommands, err := loadRiskCommands()
|
|
if err != nil {
|
|
return TerminalRuntimeSettings{}, GeneratorConfig{}, 0, err
|
|
}
|
|
return TerminalRuntimeSettings{
|
|
AccountID: accountID,
|
|
Prefix: strings.TrimSpace(prefix),
|
|
RiskCommands: riskCommands,
|
|
}, config, timeout, nil
|
|
}
|
|
|
|
func loadAgentAccount(accountID uint) (*model.AgentAccount, error) {
|
|
if accountID == 0 {
|
|
return nil, os.ErrNotExist
|
|
}
|
|
account, err := agentAccountRepo.GetFirst(repo.WithByID(accountID))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return account, nil
|
|
}
|
|
|
|
func loadAgentSettingValue(key string) (string, error) {
|
|
var setting model.Setting
|
|
if err := global.DB.Where("key = ?", key).First(&setting).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return "", os.ErrNotExist
|
|
}
|
|
return "", err
|
|
}
|
|
return setting.Value, nil
|
|
}
|
|
|
|
func loadRiskCommands() ([]string, error) {
|
|
value, err := loadAgentSettingValue("AIRiskCommands")
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return []string{}, nil
|
|
}
|
|
return nil, err
|
|
}
|
|
if strings.TrimSpace(value) == "" {
|
|
return []string{}, nil
|
|
}
|
|
var commands []string
|
|
if err := json.Unmarshal([]byte(value), &commands); err != nil {
|
|
return nil, err
|
|
}
|
|
return normalizeRiskCommands(commands), nil
|
|
}
|
|
|
|
func normalizeRiskCommands(commands []string) []string {
|
|
seen := make(map[string]struct{}, len(commands))
|
|
result := make([]string, 0, len(commands))
|
|
for _, command := range commands {
|
|
command = strings.TrimSpace(command)
|
|
if command == "" {
|
|
continue
|
|
}
|
|
if _, ok := seen[command]; ok {
|
|
continue
|
|
}
|
|
seen[command] = struct{}{}
|
|
result = append(result, command)
|
|
}
|
|
return result
|
|
}
|