package gpt import ( "bytes" "encoding/json" "fmt" "io" "net/http" "strings" "time" "github.com/direct-dev-ru/linux-command-gpt/config" ) // Provider интерфейс для работы с разными LLM провайдерами type Provider interface { Chat(messages []Chat) (string, error) Health() error GetAvailableModels() ([]string, error) } // ProxyAPIProvider реализация для прокси API (gin-restapi) type ProxyAPIProvider struct { BaseURL string JWTToken string Model string HTTPClient *http.Client } // ProxyChatRequest структура запроса к прокси API type ProxyChatRequest struct { Messages []Chat `json:"messages"` Model string `json:"model,omitempty"` Temperature float64 `json:"temperature,omitempty"` TopP float64 `json:"top_p,omitempty"` Stream bool `json:"stream,omitempty"` SystemContent string `json:"system_content,omitempty"` UserContent string `json:"user_content,omitempty"` RandomWords []string `json:"random_words,omitempty"` FallbackString string `json:"fallback_string,omitempty"` } // ProxyChatResponse структура ответа от прокси API type ProxyChatResponse struct { Response string `json:"response"` Usage struct { PromptTokens int `json:"prompt_tokens"` CompletionTokens int `json:"completion_tokens"` TotalTokens int `json:"total_tokens"` } `json:"usage,omitempty"` Error string `json:"error,omitempty"` Model string `json:"model,omitempty"` Timeout int `json:"timeout_seconds,omitempty"` } // ProxyHealthResponse структура ответа health check type ProxyHealthResponse struct { Status string `json:"status"` Message string `json:"message"` Model string `json:"default_model,omitempty"` Timeout int `json:"default_timeout_seconds,omitempty"` } // OllamaProvider реализация для Ollama API type OllamaProvider struct { BaseURL string Model string Temperature float64 HTTPClient *http.Client } // OllamaTagsResponse структура ответа для получения списка моделей type OllamaTagsResponse struct { Models []struct { Name string `json:"name"` ModifiedAt string `json:"modified_at"` Size int64 `json:"size"` } `json:"models"` } func NewProxyAPIProvider(baseURL, jwtToken, model string, timeout int) *ProxyAPIProvider { return &ProxyAPIProvider{ BaseURL: strings.TrimSuffix(baseURL, "/"), JWTToken: jwtToken, Model: model, HTTPClient: &http.Client{Timeout: time.Duration(timeout) * time.Second}, } } func NewOllamaProvider(baseURL, model string, temperature float64, timeout int) *OllamaProvider { return &OllamaProvider{ BaseURL: strings.TrimSuffix(baseURL, "/"), Model: model, Temperature: temperature, HTTPClient: &http.Client{Timeout: time.Duration(timeout) * time.Second}, } } // Chat для ProxyAPIProvider func (p *ProxyAPIProvider) Chat(messages []Chat) (string, error) { // Используем основной endpoint /api/v1/protected/sberchat/chat payload := ProxyChatRequest{ Messages: messages, Model: p.Model, Temperature: 0.5, TopP: 0.5, Stream: false, RandomWords: []string{"linux", "command", "gpt"}, FallbackString: "I'm sorry, I can't help with that. Please try again.", } jsonData, err := json.Marshal(payload) if err != nil { return "", fmt.Errorf("ошибка маршалинга запроса: %w", err) } req, err := http.NewRequest("POST", p.BaseURL+config.AppConfig.Server.ProxyUrl, bytes.NewBuffer(jsonData)) if err != nil { return "", fmt.Errorf("ошибка создания запроса: %w", err) } req.Header.Set("Content-Type", "application/json") if p.JWTToken != "" { req.Header.Set("Authorization", "Bearer "+p.JWTToken) } if config.AppConfig.MainFlags.Debug { fmt.Println("Chat URL: ", p.BaseURL+config.AppConfig.Server.ProxyUrl) fmt.Println("ProxyChatRequest: ", req) } resp, err := p.HTTPClient.Do(req) if err != nil { return "", fmt.Errorf("ошибка выполнения запроса: %w", err) } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return "", fmt.Errorf("ошибка чтения ответа: %w", err) } if resp.StatusCode != http.StatusOK { return "", fmt.Errorf("ошибка API: %d - %s", resp.StatusCode, string(body)) } var response ProxyChatResponse if err := json.Unmarshal(body, &response); err != nil { return "", fmt.Errorf("ошибка парсинга ответа: %w", err) } if response.Error != "" { return "", fmt.Errorf("ошибка прокси API: %s", response.Error) } if response.Response == "" { return "", fmt.Errorf("пустой ответ от API") } return strings.TrimSpace(response.Response), nil } // Health для ProxyAPIProvider func (p *ProxyAPIProvider) Health() error { req, err := http.NewRequest("GET", p.BaseURL+config.AppConfig.Server.HealthUrl, nil) if err != nil { return fmt.Errorf("ошибка создания health check запроса: %w", err) } if p.JWTToken != "" { req.Header.Set("Authorization", "Bearer "+p.JWTToken) } resp, err := p.HTTPClient.Do(req) if err != nil { return fmt.Errorf("ошибка health check: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return fmt.Errorf("health check failed: %d", resp.StatusCode) } var healthResponse ProxyHealthResponse body, err := io.ReadAll(resp.Body) if err != nil { return fmt.Errorf("ошибка чтения health check ответа: %w", err) } if err := json.Unmarshal(body, &healthResponse); err != nil { return fmt.Errorf("ошибка парсинга health check ответа: %w", err) } if healthResponse.Status != "ok" { return fmt.Errorf("health check status: %s - %s", healthResponse.Status, healthResponse.Message) } return nil } // Chat для OllamaProvider func (o *OllamaProvider) Chat(messages []Chat) (string, error) { think := config.AppConfig.Think var payload interface{} if think { payload = Gpt3Request{ Model: o.Model, Messages: messages, Stream: false, Options: Gpt3Options{o.Temperature}, } } else { payload = Gpt3ThinkRequest{ Model: o.Model, Messages: messages, Stream: false, Think: false, Options: Gpt3Options{o.Temperature}, } } jsonData, err := json.Marshal(payload) if err != nil { return "", fmt.Errorf("ошибка маршалинга запроса: %w", err) } req, err := http.NewRequest("POST", o.BaseURL+"/api/chat", bytes.NewBuffer(jsonData)) if err != nil { return "", fmt.Errorf("ошибка создания запроса: %w", err) } req.Header.Set("Content-Type", "application/json") resp, err := o.HTTPClient.Do(req) if err != nil { return "", fmt.Errorf("ошибка выполнения запроса: %w", err) } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return "", fmt.Errorf("ошибка чтения ответа: %w", err) } if resp.StatusCode != http.StatusOK { return "", fmt.Errorf("ошибка API: %d - %s", resp.StatusCode, string(body)) } var response OllamaResponse if err := json.Unmarshal(body, &response); err != nil { return "", fmt.Errorf("ошибка парсинга ответа: %w", err) } return strings.TrimSpace(response.Message.Content), nil } // Health для OllamaProvider func (o *OllamaProvider) Health() error { req, err := http.NewRequest("GET", o.BaseURL+"/api/tags", nil) if err != nil { return fmt.Errorf("ошибка создания health check запроса: %w", err) } resp, err := o.HTTPClient.Do(req) if err != nil { return fmt.Errorf("ошибка health check: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return fmt.Errorf("health check failed: %d", resp.StatusCode) } return nil } // GetAvailableModels для ProxyAPIProvider возвращает фиксированный список func (p *ProxyAPIProvider) GetAvailableModels() ([]string, error) { return []string{"GigaChat-2", "GigaChat-2-Pro", "GigaChat-2-Max"}, nil } // GetAvailableModels возвращает список доступных моделей для провайдера func (o *OllamaProvider) GetAvailableModels() ([]string, error) { req, err := http.NewRequest("GET", o.BaseURL+"/api/tags", nil) if err != nil { return nil, fmt.Errorf("ошибка создания запроса: %w", err) } resp, err := o.HTTPClient.Do(req) if err != nil { return nil, fmt.Errorf("ошибка получения моделей: %w", err) } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("ошибка чтения ответа: %w", err) } if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("ошибка API: %d - %s", resp.StatusCode, string(body)) } var response OllamaTagsResponse if err := json.Unmarshal(body, &response); err != nil { return nil, fmt.Errorf("ошибка парсинга ответа: %w", err) } var models []string for _, model := range response.Models { models = append(models, model.Name) } return models, nil }