mirror of
https://github.com/Direct-Dev-Ru/go-lcg.git
synced 2026-11-09 08:56:39 +00:00
273 lines
7.3 KiB
Go
273 lines
7.3 KiB
Go
package gpt
|
||
|
||
import (
|
||
"fmt"
|
||
"os"
|
||
"path/filepath"
|
||
"strings"
|
||
"time"
|
||
|
||
"github.com/direct-dev-ru/linux-command-gpt/config"
|
||
)
|
||
|
||
// ProxySimpleChatRequest структура для простого запроса
|
||
type ProxySimpleChatRequest struct {
|
||
Message string `json:"message"`
|
||
Model string `json:"model,omitempty"`
|
||
}
|
||
|
||
// ProxySimpleChatResponse структура ответа для простого запроса
|
||
type ProxySimpleChatResponse 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"`
|
||
Model string `json:"model,omitempty"`
|
||
Timeout int `json:"timeout_seconds,omitempty"`
|
||
}
|
||
|
||
// Gpt3 обновленная структура с поддержкой разных провайдеров
|
||
type Gpt3 struct {
|
||
Provider Provider
|
||
Prompt string
|
||
Model string
|
||
HomeDir string
|
||
ApiKeyFile string
|
||
ApiKey string
|
||
Temperature float64
|
||
Context string // дополнительные данные к запросу, отправляются отдельным сообщением
|
||
ProviderType string // "ollama", "proxy", "apimira"
|
||
}
|
||
|
||
type Chat struct {
|
||
Role string `json:"role"`
|
||
Content string `json:"content"`
|
||
}
|
||
|
||
type Gpt3Request struct {
|
||
Model string `json:"model"`
|
||
Stream bool `json:"stream"`
|
||
Messages []Chat `json:"messages"`
|
||
Options Gpt3Options `json:"options"`
|
||
}
|
||
|
||
type Gpt3ThinkRequest struct {
|
||
Model string `json:"model"`
|
||
Stream bool `json:"stream"`
|
||
Think bool `json:"think"`
|
||
Messages []Chat `json:"messages"`
|
||
Options Gpt3Options `json:"options"`
|
||
}
|
||
|
||
type Gpt3Options struct {
|
||
Temperature float64 `json:"temperature"`
|
||
}
|
||
|
||
type Gpt3Response struct {
|
||
Choices []struct {
|
||
Message Chat `json:"message"`
|
||
} `json:"choices"`
|
||
}
|
||
|
||
// LlamaResponse represents the response structure.
|
||
type OllamaResponse struct {
|
||
Model string `json:"model"`
|
||
CreatedAt string `json:"created_at"`
|
||
Message Chat `json:"message"`
|
||
Done bool `json:"done"`
|
||
TotalDuration int64 `json:"total_duration"`
|
||
LoadDuration int64 `json:"load_duration"`
|
||
PromptEvalCount int64 `json:"prompt_eval_count"`
|
||
PromptEvalDuration int64 `json:"prompt_eval_duration"`
|
||
EvalCount int64 `json:"eval_count"`
|
||
EvalDuration int64 `json:"eval_duration"`
|
||
}
|
||
|
||
func (gpt3 *Gpt3) deleteApiKey() {
|
||
filePath := gpt3.HomeDir + string(filepath.Separator) + gpt3.ApiKeyFile
|
||
if _, err := os.Stat(filePath); os.IsNotExist(err) {
|
||
return
|
||
}
|
||
err := os.Remove(filePath)
|
||
if err != nil {
|
||
panic(err)
|
||
}
|
||
}
|
||
|
||
func (gpt3 *Gpt3) updateApiKey(apiKey string) {
|
||
filePath := gpt3.HomeDir + string(filepath.Separator) + gpt3.ApiKeyFile
|
||
file, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
||
if err != nil {
|
||
panic(err)
|
||
}
|
||
defer file.Close()
|
||
|
||
apiKey = strings.TrimSpace(apiKey)
|
||
_, err = file.WriteString(apiKey)
|
||
if err != nil {
|
||
panic(err)
|
||
}
|
||
if err := os.Chmod(filePath, 0600); err != nil {
|
||
panic(err)
|
||
}
|
||
gpt3.ApiKey = apiKey
|
||
}
|
||
|
||
func (gpt3 *Gpt3) storeApiKey(apiKey string) {
|
||
if apiKey == "" {
|
||
return
|
||
}
|
||
filePath := gpt3.HomeDir + string(filepath.Separator) + gpt3.ApiKeyFile
|
||
file, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
||
if err != nil {
|
||
panic(err)
|
||
}
|
||
defer file.Close()
|
||
|
||
apiKey = strings.TrimSpace(apiKey)
|
||
_, err = file.WriteString(apiKey)
|
||
if err != nil {
|
||
panic(err)
|
||
}
|
||
if err := os.Chmod(filePath, 0600); err != nil {
|
||
panic(err)
|
||
}
|
||
gpt3.ApiKey = apiKey
|
||
}
|
||
|
||
func (gpt3 *Gpt3) loadApiKey() bool {
|
||
dirSeparator := string(filepath.Separator)
|
||
apiKeyFile := gpt3.HomeDir + dirSeparator + gpt3.ApiKeyFile
|
||
if _, err := os.Stat(apiKeyFile); os.IsNotExist(err) {
|
||
return false
|
||
}
|
||
apiKey, err := os.ReadFile(apiKeyFile)
|
||
if err != nil {
|
||
return false
|
||
}
|
||
gpt3.ApiKey = string(apiKey)
|
||
|
||
return true
|
||
}
|
||
|
||
func (gpt3 *Gpt3) UpdateKey() {
|
||
var apiKey string
|
||
if gpt3.ProviderType == "apimira" {
|
||
fmt.Print("apimira API Key: ")
|
||
} else {
|
||
fmt.Print("OpenAI API Key: ")
|
||
}
|
||
fmt.Scanln(&apiKey)
|
||
gpt3.updateApiKey(apiKey)
|
||
}
|
||
|
||
func (gpt3 *Gpt3) DeleteKey() {
|
||
var c string
|
||
fmt.Print("Are you sure you want to delete the API key? (y/N): ")
|
||
fmt.Scanln(&c)
|
||
if c == "Y" || c == "y" {
|
||
gpt3.deleteApiKey()
|
||
}
|
||
}
|
||
|
||
func (gpt3 *Gpt3) InitKey() {
|
||
// Для ollama, proxy и apimira провайдеров ключ не запрашивается интерактивно:
|
||
// ollama и proxy не требуют ключа, apimira читает его из LCG_JWT_TOKEN или
|
||
// ключевого файла (задать через команду update-key)
|
||
if gpt3.ProviderType == "ollama" || gpt3.ProviderType == "proxy" || gpt3.ProviderType == "apimira" {
|
||
return
|
||
}
|
||
|
||
load := gpt3.loadApiKey()
|
||
if load {
|
||
return
|
||
}
|
||
var apiKey string
|
||
fmt.Print("OpenAI API Key: ")
|
||
fmt.Scanln(&apiKey)
|
||
gpt3.storeApiKey(apiKey)
|
||
}
|
||
|
||
// NewGpt3 создает новый экземпляр GPT с выбранным провайдером
|
||
func NewGpt3(providerType, host, apiKey, model, prompt string, temperature float64, timeout int) *Gpt3 {
|
||
var provider Provider
|
||
|
||
switch providerType {
|
||
case "proxy":
|
||
provider = NewProxyAPIProvider(host, apiKey, model, timeout) // apiKey используется как JWT токен
|
||
case "apimira":
|
||
provider = NewApimiraProvider(host, apiKey, model, config.AppConfig.Completions, temperature, timeout)
|
||
case "ollama":
|
||
provider = NewOllamaProvider(host, model, temperature, timeout)
|
||
default:
|
||
provider = NewOllamaProvider(host, model, temperature, timeout)
|
||
}
|
||
|
||
homeDir, _ := os.UserHomeDir()
|
||
|
||
return &Gpt3{
|
||
Provider: provider,
|
||
Prompt: prompt,
|
||
Model: model,
|
||
HomeDir: homeDir,
|
||
ApiKeyFile: config.AppConfig.ApiKeyFile,
|
||
ApiKey: apiKey,
|
||
Temperature: temperature,
|
||
ProviderType: providerType,
|
||
}
|
||
}
|
||
|
||
// Completions обновленный метод с поддержкой разных провайдеров
|
||
func (gpt3 *Gpt3) Completions(ask string) string {
|
||
messages := []Chat{
|
||
{"system", gpt3.Prompt},
|
||
}
|
||
if gpt3.Context != "" {
|
||
messages = append(messages, Chat{"user", gpt3.Context})
|
||
}
|
||
messages = append(messages, Chat{"user", ask + ". " + gpt3.Prompt})
|
||
|
||
retries := config.AppConfig.Retries
|
||
if retries < 0 {
|
||
retries = 0
|
||
}
|
||
|
||
var response string
|
||
var err error
|
||
for attempt := 0; ; attempt++ {
|
||
response, err = gpt3.Provider.Chat(messages)
|
||
if err == nil {
|
||
break
|
||
}
|
||
if attempt >= retries || !IsRetryable(err) {
|
||
break
|
||
}
|
||
delay := time.Duration(5<<attempt) * time.Second // 5с, 10с, 20с, 40с, ...
|
||
msg := fmt.Sprintf("⚠️ Попытка %d из %d не удалась: %v — повтор через %s...", attempt+1, retries+1, err, delay)
|
||
fmt.Printf("\r%-60s\n", msg)
|
||
config.DebugLogf("[RETRY] %s", msg)
|
||
time.Sleep(delay)
|
||
}
|
||
|
||
if err != nil {
|
||
config.DebugLogf("[ERROR] completions: %v", err)
|
||
fmt.Printf("Ошибка при выполнении запроса: %v\n", err)
|
||
return ""
|
||
}
|
||
|
||
config.DebugLogf("[RESULT] %s", response)
|
||
return response
|
||
}
|
||
|
||
// Health проверяет состояние провайдера
|
||
func (gpt3 *Gpt3) Health() error {
|
||
return gpt3.Provider.Health()
|
||
}
|
||
|
||
// GetAvailableModels возвращает список доступных моделей
|
||
func (gpt3 *Gpt3) GetAvailableModels() ([]string, error) {
|
||
return gpt3.Provider.GetAvailableModels()
|
||
}
|