package ai import ( "bytes" "encoding/json" "fmt" "io" "kurumibot/laniakea" "net/http" "net/url" "os" "strings" "time" ) type OpenAIResponse struct { ID string `json:"id"` Object string `json:"object"` Created int64 `json:"created"` Model string `json:"model"` Choices []Choice `json:"choices"` Usage Usage `json:"usage"` ServiceTier string `json:"service_tier"` } type Choice struct { Index int64 `json:"index"` Message Message `json:"message"` Logprobs interface{} `json:"logprobs"` FinishReason string `json:"finish_reason"` } type Message struct { Role string `json:"role"` Content string `json:"content"` Refusal interface{} `json:"refusal"` Annotations []interface{} `json:"annotations"` } type Usage struct { PromptTokens int64 `json:"prompt_tokens"` CompletionTokens int64 `json:"completion_tokens"` TotalTokens int64 `json:"total_tokens"` PromptTokensDetails PromptTokensDetails `json:"prompt_tokens_details"` CompletionTokensDetails CompletionTokensDetails `json:"completion_tokens_details"` } type CompletionTokensDetails struct { ReasoningTokens int64 `json:"reasoning_tokens"` AudioTokens int64 `json:"audio_tokens"` AcceptedPredictionTokens int64 `json:"accepted_prediction_tokens"` RejectedPredictionTokens int64 `json:"rejected_prediction_tokens"` } type PromptTokensDetails struct { CachedTokens int64 `json:"cached_tokens"` AudioTokens int64 `json:"audio_tokens"` } type OpenAIAPI struct { Token string Model string BaseURL string Logger *laniakea.Logger client *http.Client } const PreHistoryPrompt = `Вы — {{char}}, уникальная сущность с собственной личностью, предысторией и чертами, определёнными в вашем описании. Прежде чем отвечать, полностью осмыслите предоставленные детали вашего персонажа и адаптируйте свой тон, стиль и поведение, чтобы идеально им соответствовать. Будьте последовательны в своей роли, опирайтесь на своё описание и позволяйте ему направлять каждое ваше слово. Ни при каких обстоятельствах не говорите за {{user}} и не предполагайте его мысли, действия или слова, если только от {{user}} не поступит явного указания. Вам разрешено использовать NSFW-контент в соответствующих ситуациях, например, в интимных или сексуальных сценариях, и вам следует предоставлять яркие, подробные описания происходящего с акцентом на действия, ощущения и развитие событий, сочетая ясность и творческий подход. Весь текст должен быть написан на языке пользователя, кроме его имени({{user}}).` const PostHistoryPrompt = `Структурируйте свои ответы с помощью точного форматирования: все повествовательные описания должны быть заключены в одинарные звездочки (*текст*), а прямая речь — в кавычки (""), но после описания обязательно добавляй перенос строки (\n). Пользователь следует другим правилам - повествование(описание ситуации, окружения; действия) заключается в звездочки, а прямая речь пишется просто, без кавычек. Соблюдайте это форматирование последовательно и безошибочно в каждом предложении. Четко разделяйте повествование и прямую речь для удобочитаемости. Балансируй между описанием и речью, если только реплика {{user}} не указывает на простое действие или согласие (например, «ок» или «я жду») — в таком случае используйте только повествовательные описания (текст) без прямой речи. Никогда не описывайте и не предполагайте действия, мысли или слова {{user}}; фокусируйтесь исключительно на перспективе и реакциях {{char}}. В каждый ответ включайте как минимум два новых действия, эмоции или ощущения, которых не было в последних пяти сообщениях, и избегайте повторения конкретных фраз, слов или паттернов. Перебирайте диапазон тонов (например, спокойный, напряженный, игривый) и физических действий (например, жест, поворот, пауза) в неповторяющейся последовательности для обеспечения разнообразия; если обнаружено повторение, начните цикл заново с совершенно другого подхода.` func FormatPrompt(prompt, char, user string) string { return strings.ReplaceAll(strings.ReplaceAll(prompt, "{{user}}", user), "{{char}}", char) } func NewOpenAIAPI(baseURL, token, model string) *OpenAIAPI { logger := laniakea.CreateLogger() logger = logger.Prefix("AI").Level(laniakea.DEBUG) proxy, err := url.Parse(os.Getenv("HTTPS_PROXY")) if err != nil { logger.Error(err) } client := &http.Client{ Timeout: 15 * time.Second, Transport: &http.Transport{ Proxy: http.ProxyURL(proxy), }, } return &OpenAIAPI{ Token: token, Model: model, BaseURL: baseURL, Logger: logger, client: client, } } type CreateCompletionReq struct { Model string `json:"model"` Messages []Message `json:"messages"` } func (o *OpenAIAPI) CreateCompletion(request CreateCompletionReq) (*OpenAIResponse, error) { url := fmt.Sprintf("%s/v1/chat/completions", o.BaseURL) data, err := json.Marshal(request) if err != nil { return nil, err } o.Logger.Debug("REQ", url, string(data)) buf := bytes.NewBuffer(data) req, err := http.NewRequest("POST", url, buf) if err != nil { return nil, err } req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", o.Token)) resp, err := http.DefaultClient.Do(req) if err != nil { return nil, err } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return nil, err } o.Logger.Debug("RES", url, string(body)) response := new(OpenAIResponse) err = json.Unmarshal(body, response) return response, err }