chat compress and some features

This commit is contained in:
2026-01-26 14:28:21 +03:00
parent 39deb0ea19
commit 04a65701d1
6 changed files with 111 additions and 47 deletions

View File

@@ -11,7 +11,7 @@ COPY ./utils ./utils
COPY ./main.go ./ COPY ./main.go ./
RUN --mount=type=cache,target=/root/.cache/go-build \ RUN --mount=type=cache,target=/root/.cache/go-build \
--mount=type=cache,target=/go/pkg/mod \ --mount=type=cache,target=/go/pkg/mod \
CGO_ENABLED=0 go build -trimpath \ CGO_ENABLED=0 GOEXPERIMENT=greenteagc go build -trimpath \
-ldflags="-s -w -X 'kurumibot/utils.BuildTime=$BUILD_TIME' -X 'kurumibot/utils.GitCommit=$GIT_COMMIT'" \ -ldflags="-s -w -X 'kurumibot/utils.BuildTime=$BUILD_TIME' -X 'kurumibot/utils.GitCommit=$GIT_COMMIT'" \
-v -o /usr/local/bin/kurumi ./ -v -o /usr/local/bin/kurumi ./

View File

@@ -5,5 +5,7 @@ build:
@echo "Building commit $(GIT_COMMIT)" @echo "Building commit $(GIT_COMMIT)"
@echo "Build time $(BUILD_TIME)" @echo "Build time $(BUILD_TIME)"
go mod tidy go mod tidy
docker build --build-arg GIT_COMMIT --build-arg BUILD_TIME -t git.nix13.pw/scuroneko/kurumibotgo:latest -t git.nix13.pw/scuroneko/kurumibotgo:0.2.0 -f ./Dockerfile . docker build --build-arg GIT_COMMIT --build-arg BUILD_TIME \
-t git.nix13.pw/scuroneko/kurumibotgo:latest \
-t git.nix13.pw/scuroneko/kurumibotgo:0.2.0 -f ./Dockerfile .
docker push git.nix13.pw/scuroneko/kurumibotgo --all-tags docker push git.nix13.pw/scuroneko/kurumibotgo --all-tags

View File

@@ -27,7 +27,7 @@ func RegisterRP(bot *laniakea.Bot) {
rp.Payload(rpModelSet, "rp.model_set") rp.Payload(rpModelSet, "rp.model_set")
rp.Payload(rpScenarioList, "rp.scenario_list") rp.Payload(rpScenarioList, "rp.scenario_list")
rp.Payload(rpSettingList, "rp.setting_list") rp.Payload(rpSettingList, "rp.setting_list")
rp.Payload(chatStat, "rp.tokens") rp.Payload(chatStat, "rp.chat_stat")
rp.Payload(newChatStage1, "rp.new_chat_s1") rp.Payload(newChatStage1, "rp.new_chat_s1")
rp.Payload(newChatStage2, "rp.new_chat_s2") rp.Payload(newChatStage2, "rp.new_chat_s2")
@@ -98,7 +98,7 @@ func rpInfo(ctx *laniakea.MsgContext, db *laniakea.DatabaseContext) {
} }
kb := laniakea.NewInlineKeyboard(2) kb := laniakea.NewInlineKeyboard(2)
kb.AddCallbackButton("Статистика чата", "rp.tokens") kb.AddCallbackButton("Статистика чата", "rp.chat_stat")
kb.AddCallbackButton("Сменить вайфу", "rp.waifu_list") kb.AddCallbackButton("Сменить вайфу", "rp.waifu_list")
kb.AddCallbackButton("Сменить пресет", "rp.preset_list") kb.AddCallbackButton("Сменить пресет", "rp.preset_list")
kb.AddCallbackButton("Сменить модель", "rp.model_list") kb.AddCallbackButton("Сменить модель", "rp.model_list")
@@ -167,7 +167,7 @@ func rpPresetsList(ctx *laniakea.MsgContext, db *laniakea.DatabaseContext) {
kb := laniakea.NewInlineKeyboard(2) kb := laniakea.NewInlineKeyboard(2)
for i, preset := range presets { for i, preset := range presets {
out[i] = fmt.Sprintf( out[i] = fmt.Sprintf(
"*%s*\n%s", "*%s* - %s",
laniakea.EscapeMarkdown(preset.Name), preset.Description, laniakea.EscapeMarkdown(preset.Name), preset.Description,
) )
kb.AddCallbackButton(preset.Name, "rp.preset_set", preset.ID) kb.AddCallbackButton(preset.Name, "rp.preset_set", preset.ID)
@@ -212,7 +212,7 @@ func rpModelList(ctx *laniakea.MsgContext, db *laniakea.DatabaseContext) {
out := make([]string, len(models)) out := make([]string, len(models))
kb := laniakea.NewInlineKeyboard(2) kb := laniakea.NewInlineKeyboard(2)
for i, model := range models { for i, model := range models {
out[i] = fmt.Sprintf("*%s* - размер контекста %dK", model.Name, model.ContextSize) out[i] = fmt.Sprintf("*%s* - размер контекста _%dK_", model.Name, model.ContextSize)
kb.AddCallbackButton(model.Name, "rp.model_set", model.ID) kb.AddCallbackButton(model.Name, "rp.model_set", model.ID)
} }
kb.AddLine() kb.AddLine()
@@ -266,7 +266,7 @@ func rpSettingList(ctx *laniakea.MsgContext, db *laniakea.DatabaseContext) {
out := make([]string, len(settings)) out := make([]string, len(settings))
for i, setting := range settings { for i, setting := range settings {
out[i] = fmt.Sprintf("*%s* - _%s_\n", setting.Name, setting.Description) out[i] = fmt.Sprintf("*%s* - %s\n", setting.Name, setting.Description)
} }
kb := laniakea.NewInlineKeyboard(1) kb := laniakea.NewInlineKeyboard(1)
kb.AddCallbackButton("На главную", "rp.info") kb.AddCallbackButton("На главную", "rp.info")
@@ -296,12 +296,14 @@ func chatStat(ctx *laniakea.MsgContext, db *laniakea.DatabaseContext) {
} }
tokens := redisRpRep.GetChatTokens(ctx.FromID, waifuId) tokens := redisRpRep.GetChatTokens(ctx.FromID, waifuId)
kb := laniakea.NewInlineKeyboard(1).AddCallbackButton("На главную", "rp.info") kb := laniakea.NewInlineKeyboard(1)
kb = kb.AddCallbackButton("Сжать чат", "rp.compress_chat")
kb = kb.AddCallbackButton("На главную", "rp.info")
out := []string{ out := []string{
"Статистика чата", "Статистика чата",
fmt.Sprintf("ID: `%s`", chatId), fmt.Sprintf("*ID*: `%s`", chatId),
fmt.Sprintf("Кол-во сообщений: %d", messageCount), fmt.Sprintf("*Кол-во сообщений*: %d", messageCount),
fmt.Sprintf("Кол-во токенов: %d", tokens), fmt.Sprintf("*Кол-во токенов*: %d", tokens),
} }
ctx.EditCallback(strings.Join(out, "\n"), kb) ctx.EditCallback(strings.Join(out, "\n"), kb)
} }
@@ -352,7 +354,7 @@ func newChatStage2(ctx *laniakea.MsgContext, db *laniakea.DatabaseContext) {
} }
kb := laniakea.NewInlineKeyboard(2) kb := laniakea.NewInlineKeyboard(2)
for _, scenario := range scenarios { for _, scenario := range scenarios {
out = append(out, fmt.Sprintf("*%s* - _%s_", scenario.Name, scenario.Description)) out = append(out, fmt.Sprintf("*%s* - %s", scenario.Name, scenario.Description))
kb.AddCallbackButton(scenario.Name, "rp.new_chat", settingId, scenario.ID) kb.AddCallbackButton(scenario.Name, "rp.new_chat", settingId, scenario.ID)
} }
kb.AddCallbackButton("Без сценария", "rp.new_chat", 0) kb.AddCallbackButton("Без сценария", "rp.new_chat", 0)
@@ -535,18 +537,12 @@ func generate(ctx *laniakea.MsgContext, db *laniakea.DatabaseContext) {
} }
userMessage := strings.TrimSpace(strings.Join(ctx.Args, " ")) userMessage := strings.TrimSpace(strings.Join(ctx.Args, " "))
messages = append(messages, afterHistory, ai.Message{ messages = append(messages, afterHistory)
Role: "user",
Content: userMessage,
})
kb := laniakea.NewInlineKeyboard(1).AddCallbackButton("Отменить", "rp.cancel") kb := laniakea.NewInlineKeyboard(1).AddCallbackButton("Отменить", "rp.cancel")
m := ctx.Keyboard("Генерация запущена...", kb) m := ctx.Keyboard("Генерация запущена...", kb)
api := ai.NewOpenAIAPI(ai.GPTBaseUrl, "", rpUser.Model.Key) api := ai.NewOpenAIAPI(ai.GPTBaseUrl, "", rpUser.Model.Key)
res, err := api.CreateCompletion(ai.CreateCompletionReq{ res, err := api.CreateCompletion(messages, userMessage, 1.0)
Messages: messages,
Temperature: 1.0,
})
if err != nil { if err != nil {
ctx.Error(err) ctx.Error(err)
return return
@@ -568,9 +564,9 @@ func generate(ctx *laniakea.MsgContext, db *laniakea.DatabaseContext) {
ctx.Error(err) ctx.Error(err)
} }
rpUser.UsedTokens = rpUser.UsedTokens + res.Usage.TotalTokens rpUser.UsedTokens = rpUser.UsedTokens + int64(len(userMessage)) + int64(len(answerContent))
tokens := redisRpRep.GetChatTokens(ctx.FromID, waifuId) tokens := redisRpRep.GetChatTokens(ctx.FromID, waifuId)
tokens += int(res.Usage.CompletionTokens) tokens += len(userMessage) + len(answerContent)
err = redisRpRep.SetChatTokens(ctx.FromID, waifuId, tokens) err = redisRpRep.SetChatTokens(ctx.FromID, waifuId, tokens)
if err != nil { if err != nil {
ctx.Error(err) ctx.Error(err)
@@ -581,12 +577,11 @@ func generate(ctx *laniakea.MsgContext, db *laniakea.DatabaseContext) {
} }
m.Delete() m.Delete()
kb = laniakea.NewInlineKeyboard(1).AddCallbackButton("Сжать чат", "rp.compress_chat") ctx.Answer(laniakea.EscapeMarkdown(answerContent))
ctx.Keyboard(laniakea.EscapeMarkdown(answerContent), kb)
} }
func compress(ctx *laniakea.MsgContext, db *laniakea.DatabaseContext) { func compress(ctx *laniakea.MsgContext, db *laniakea.DatabaseContext) {
m := ctx.Answer("Запущено сжатие чата.") m := ctx.EditCallback("Запущено сжатие чата…", nil)
redisRpRep := red.NewRPRepository(db) redisRpRep := red.NewRPRepository(db)
waifuId := redisRpRep.GetSelectedWaifu(ctx.FromID) waifuId := redisRpRep.GetSelectedWaifu(ctx.FromID)
if waifuId == 0 { if waifuId == 0 {
@@ -615,17 +610,21 @@ func compress(ctx *laniakea.MsgContext, db *laniakea.DatabaseContext) {
Content: m.Message, Content: m.Message,
}) })
} }
api := ai.NewOpenAIAPI(ai.GPTBaseUrl, "", "deepseek-ai/deepseek-v3.1-terminus")
//compressModel := "anthropic/claude-sonnet-4"
compressModel := "gpt-5.1"
//compressModel := "deepseek-ai/deepseek-v3.2"
api := ai.NewOpenAIAPI(ai.GPTBaseUrl, "", compressModel)
res, err := api.CompressChat(messages) res, err := api.CompressChat(messages)
if err != nil { if err != nil {
ctx.Error(err) ctx.Error(err)
} }
if len(res.Choices) == 0 { if len(res.Choices) == 0 {
m.Edit("Не удалось сжать диалог") m.Edit("Не удалось сжать чат")
return return
} }
compressedHistory := strings.TrimSpace(res.Choices[0].Message.Content)
compressedHistory := res.Choices[0].Message.Content compressedHistory = strings.ReplaceAll(compressedHistory, "*", "")
chatId = uuid.New().String() chatId = uuid.New().String()
err = redisRpRep.SetChatId(ctx.FromID, waifuId, chatId) err = redisRpRep.SetChatId(ctx.FromID, waifuId, chatId)
@@ -637,9 +636,21 @@ func compress(ctx *laniakea.MsgContext, db *laniakea.DatabaseContext) {
err = mdb.UpdateChatHistory(db, chatId, "assistant", compressedHistory) err = mdb.UpdateChatHistory(db, chatId, "assistant", compressedHistory)
if err != nil { if err != nil {
ctx.Error(err) ctx.Error(err)
return
} }
m.Edit("Сжатие завершено") offset := 20
if len(history) < 20 {
offset = len(history)
}
for _, m := range history[len(history)-offset:] {
err = mdb.UpdateChatHistory(db, chatId, m.Role, m.Message)
if err != nil {
ctx.Error(err)
}
}
kb := laniakea.NewInlineKeyboard(1)
kb = kb.AddCallbackButton("Назад", "rp.chat_stat")
kb = kb.AddCallbackButton("На главную", "rp.info")
ctx.EditCallback("Сжатие завершено", kb)
} }
func generalClose(ctx *laniakea.MsgContext, _ *laniakea.DatabaseContext) { func generalClose(ctx *laniakea.MsgContext, _ *laniakea.DatabaseContext) {

View File

@@ -1,11 +1,7 @@
package ai package ai
import (
"fmt"
)
const PawanBaseURL = "https://api.pawan.krd"
const GPTBaseUrl = "https://chat.gpt-chatbot.ru/api/openai" const GPTBaseUrl = "https://chat.gpt-chatbot.ru/api/openai"
const CompressPrompt = "Сделай выжимку нашего диалога. Сохрани все ключевые моменты(например одежду, расположение, основные действия и фразы и тд.) из каждого сообщения. Если в истории уже есть выжимка, проанализируй размер и информацию. Если выжимка короткая, скопируй её с минимальными изменениями, а если длинная, то сократи её, не упуская деталей. Всегда копируй основные моменты из выжимки, если таковая имеется. Для моих сообщений используй обращение во втором лице(ты, вы), для своих в первом лице(я)." const CompressPrompt = "Сделай выжимку нашего диалога. Сохрани все ключевые моменты(например сколько одежды, расположение, основные действия и фразы и тд.) из каждого сообщения, но никогда не копируй описание персонажа(цвет волос и глаз, характер и т.д.). Если в истории уже есть выжимка, проанализируй размер и информацию. Если выжимка короткая, скопируй её с минимальными изменениями, а если длинная, то сократи, не упуская деталей. Всегда копируй основные моменты из выжимки, если таковая имеется. Для моих сообщений используй обращение во втором лице(ты, вы), для своих в первом(я). Всегда называй вещи своими именами и не используй Markdown форматирование. После твоего сообщения будет добавлено 20 сообщений из этого диалога, по 10 твоих и моих."
var CosmoRPUrl = fmt.Sprintf("%s/cosmosrp-2.5", PawanBaseURL) //const PawanBaseURL = "https://api.pawan.krd"
//var CosmoRPUrl = fmt.Sprintf("%s/cosmosrp-2.5", PawanBaseURL)

View File

@@ -22,21 +22,18 @@ type OpenAIResponse struct {
Usage Usage `json:"usage"` Usage Usage `json:"usage"`
ServiceTier string `json:"service_tier"` ServiceTier string `json:"service_tier"`
} }
type Choice struct { type Choice struct {
Index int64 `json:"index"` Index int64 `json:"index"`
Message Message `json:"message"` Message Message `json:"message"`
Logprobs interface{} `json:"logprobs"` Logprobs interface{} `json:"logprobs"`
FinishReason string `json:"finish_reason"` FinishReason string `json:"finish_reason"`
} }
type Message struct { type Message struct {
Role string `json:"role"` Role string `json:"role"`
Content string `json:"content"` Content string `json:"content"`
Refusal interface{} `json:"refusal"` Refusal interface{} `json:"refusal"`
Annotations []interface{} `json:"annotations"` Annotations []interface{} `json:"annotations"`
} }
type Usage struct { type Usage struct {
PromptTokens int64 `json:"prompt_tokens"` PromptTokens int64 `json:"prompt_tokens"`
CompletionTokens int64 `json:"completion_tokens"` CompletionTokens int64 `json:"completion_tokens"`
@@ -44,19 +41,16 @@ type Usage struct {
PromptTokensDetails PromptTokensDetails `json:"prompt_tokens_details"` PromptTokensDetails PromptTokensDetails `json:"prompt_tokens_details"`
CompletionTokensDetails CompletionTokensDetails `json:"completion_tokens_details"` CompletionTokensDetails CompletionTokensDetails `json:"completion_tokens_details"`
} }
type CompletionTokensDetails struct { type CompletionTokensDetails struct {
ReasoningTokens int64 `json:"reasoning_tokens"` ReasoningTokens int64 `json:"reasoning_tokens"`
AudioTokens int64 `json:"audio_tokens"` AudioTokens int64 `json:"audio_tokens"`
AcceptedPredictionTokens int64 `json:"accepted_prediction_tokens"` AcceptedPredictionTokens int64 `json:"accepted_prediction_tokens"`
RejectedPredictionTokens int64 `json:"rejected_prediction_tokens"` RejectedPredictionTokens int64 `json:"rejected_prediction_tokens"`
} }
type PromptTokensDetails struct { type PromptTokensDetails struct {
CachedTokens int64 `json:"cached_tokens"` CachedTokens int64 `json:"cached_tokens"`
AudioTokens int64 `json:"audio_tokens"` AudioTokens int64 `json:"audio_tokens"`
} }
type OpenAIAPI struct { type OpenAIAPI struct {
Token string Token string
Model string Model string
@@ -67,7 +61,11 @@ type OpenAIAPI struct {
func NewOpenAIAPI(baseURL, token, model string) *OpenAIAPI { func NewOpenAIAPI(baseURL, token, model string) *OpenAIAPI {
logger := laniakea.CreateLogger() logger := laniakea.CreateLogger()
logger = logger.Prefix("AI").Level(laniakea.DEBUG) level := laniakea.FATAL
if os.Getenv("DEBUG") == "true" {
level = laniakea.DEBUG
}
logger = logger.Prefix("AI").Level(level)
proxy, err := url.Parse(os.Getenv("HTTPS_PROXY")) proxy, err := url.Parse(os.Getenv("HTTPS_PROXY"))
if err != nil { if err != nil {
logger.Error(err) logger.Error(err)
@@ -160,9 +158,16 @@ func (o *OpenAIAPI) DoRequest(url string, params any, retries int) ([]byte, erro
return responseBody, err return responseBody, err
} }
func (o *OpenAIAPI) CreateCompletion(request CreateCompletionReq) (*OpenAIResponse, error) { func (o *OpenAIAPI) CreateCompletion(history []Message, message string, temp float32) (*OpenAIResponse, error) {
u := fmt.Sprintf("%s/v1/chat/completions", o.BaseURL) u := fmt.Sprintf("%s/v1/chat/completions", o.BaseURL)
request.Model = o.Model request := CreateCompletionReq{
Model: o.Model,
Messages: append(history, Message{
Role: "user",
Content: message,
}),
Temperature: temp,
}
data, err := json.Marshal(request) data, err := json.Marshal(request)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@@ -6,3 +6,53 @@ func FormatPrompt(prompt, char, user string) string {
s := strings.ReplaceAll(prompt, "{{user}}", user) s := strings.ReplaceAll(prompt, "{{user}}", user)
return strings.ReplaceAll(s, "{{char}}", char) return strings.ReplaceAll(s, "{{char}}", char)
} }
type ProviderConfig struct {
NeedCode bool `json:"needCode"`
HideUserAPIKey bool `json:"hideUserApiKey"`
DisableGPT4 bool `json:"disableGPT4"`
HideBalanceQuery bool `json:"hideBalanceQuery"`
DisableFastLink bool `json:"disableFastLink"`
CustomModels string `json:"customModels"`
DefaultModel string `json:"defaultModel"`
VisionModels string `json:"visionModels"`
}
/* POST https://chat.gpt-chatbot.ru/api/config
+gpt-5.1@OpenAI
+gpt-5@OpenAI
+anthropic/claude-sonnet-4@OpenAI
+mercury-coder@OpenAI
+Olmo-3.1-32B-Instruct@OpenAI
+gpt-4.1-mini@OpenAI
+chatgpt-4o-latest@OpenAI
+google/gemini-2.5-pro-preview-05-06@OpenAI
+x-ai/grok-4@OpenAI
+deepseek-ai/DeepSeek-V3.2@OpenAI
+deepseek-ai/DeepSeek-V3.1-Terminus@OpenAI
+deepseek-ai/deepseek-r1-0528@OpenAI
+o1-preview@OpenAI
+o3-mini@OpenAI
+qwen/qwen3-coder-480b-a35b-instruct@OpenAI
+moonshotai/kimi-k2-thinking@OpenAI
+moonshotai/kimi-k2-instruct-0905@OpenAI
+openai/gpt-oss-120b@OpenAI
+openai/gpt-oss-20b@OpenAI
+meta/llama-3.1-405b-instruct@OpenAI
+meta/llama-4-maverick-17b-128e-instruct@OpenAI
+meta/llama-4-scout-17b-16e-instruct@OpenAI
+meta-llama-3.3-70b-instruct@OpenAI
+meta-llama-3.1-8b-instruct@OpenAI
+google/gemma-3-27b-it@OpenAI
+nvidia/nemotron-3-nano-30b-a3b@OpenAI
+qwen/qwq-32b@OpenAI
+deepseek-ai/deepseek-r1-distill-qwen-32b@OpenAI
+qwen/qwen3-235b-a22b@OpenAI
+minimaxai/minimax-m2@OpenAI
+zai-org/GLM-4.6@OpenAI
+meta-llama/Llama-3.1-8B-Instruct@OpenAI
+mistralai/mistral-large-3-675b-instruct-2512@OpenAI
+mistralai/magistral-small-2506@OpenAI
+mistralai/mistral-small-3.1-24b-instruct-2503@OpenAI
+mistralai/ministral-14b-instruct-2512@OpenAI
*/