feat: max context tokens

This commit is contained in:
Acbox
2026-02-18 17:20:22 +08:00
parent 46e8a48ab8
commit 77e9f585a1
26 changed files with 277 additions and 51 deletions
BIN
View File
Binary file not shown.
+2
View File
@@ -117,6 +117,7 @@ CREATE TABLE IF NOT EXISTS bots (
is_active BOOLEAN NOT NULL DEFAULT true, is_active BOOLEAN NOT NULL DEFAULT true,
status TEXT NOT NULL DEFAULT 'ready', status TEXT NOT NULL DEFAULT 'ready',
max_context_load_time INTEGER NOT NULL DEFAULT 1440, max_context_load_time INTEGER NOT NULL DEFAULT 1440,
max_context_tokens INTEGER NOT NULL DEFAULT 0,
language TEXT NOT NULL DEFAULT 'auto', language TEXT NOT NULL DEFAULT 'auto',
allow_guest BOOLEAN NOT NULL DEFAULT false, allow_guest BOOLEAN NOT NULL DEFAULT false,
chat_model_id UUID REFERENCES models(id) ON DELETE SET NULL, chat_model_id UUID REFERENCES models(id) ON DELETE SET NULL,
@@ -242,6 +243,7 @@ CREATE TABLE IF NOT EXISTS bot_history_messages (
role TEXT NOT NULL CHECK (role IN ('user', 'assistant', 'system', 'tool')), role TEXT NOT NULL CHECK (role IN ('user', 'assistant', 'system', 'tool')),
content JSONB NOT NULL, content JSONB NOT NULL,
metadata JSONB NOT NULL DEFAULT '{}'::jsonb, metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
usage JSONB,
created_at TIMESTAMPTZ NOT NULL DEFAULT now() created_at TIMESTAMPTZ NOT NULL DEFAULT now()
); );
@@ -0,0 +1,4 @@
-- 0008_max_context_tokens
-- Remove max_context_tokens column from bots table
ALTER TABLE bots DROP COLUMN IF EXISTS max_context_tokens;
@@ -0,0 +1,4 @@
-- 0008_max_context_tokens
-- Add max_context_tokens column to bots table for token-based context trimming
ALTER TABLE bots ADD COLUMN IF NOT EXISTS max_context_tokens INTEGER NOT NULL DEFAULT 0;
@@ -0,0 +1,4 @@
-- 0009_message_usage
-- Remove usage JSONB column from bot_history_messages
ALTER TABLE bot_history_messages DROP COLUMN IF EXISTS usage;
+4
View File
@@ -0,0 +1,4 @@
-- 0009_message_usage
-- Add usage JSONB column to bot_history_messages for storing LLM token usage
ALTER TABLE bot_history_messages ADD COLUMN IF NOT EXISTS usage JSONB;
+6 -6
View File
@@ -1,21 +1,21 @@
-- name: CreateBot :one -- name: CreateBot :one
INSERT INTO bots (owner_user_id, type, display_name, avatar_url, is_active, metadata, status) INSERT INTO bots (owner_user_id, type, display_name, avatar_url, is_active, metadata, status)
VALUES ($1, $2, $3, $4, $5, $6, $7) VALUES ($1, $2, $3, $4, $5, $6, $7)
RETURNING id, owner_user_id, type, display_name, avatar_url, is_active, status, max_context_load_time, language, allow_guest, chat_model_id, memory_model_id, embedding_model_id, search_provider_id, metadata, created_at, updated_at; RETURNING id, owner_user_id, type, display_name, avatar_url, is_active, status, max_context_load_time, max_context_tokens, language, allow_guest, chat_model_id, memory_model_id, embedding_model_id, search_provider_id, metadata, created_at, updated_at;
-- name: GetBotByID :one -- name: GetBotByID :one
SELECT id, owner_user_id, type, display_name, avatar_url, is_active, status, max_context_load_time, language, allow_guest, chat_model_id, memory_model_id, embedding_model_id, search_provider_id, metadata, created_at, updated_at SELECT id, owner_user_id, type, display_name, avatar_url, is_active, status, max_context_load_time, max_context_tokens, language, allow_guest, chat_model_id, memory_model_id, embedding_model_id, search_provider_id, metadata, created_at, updated_at
FROM bots FROM bots
WHERE id = $1; WHERE id = $1;
-- name: ListBotsByOwner :many -- name: ListBotsByOwner :many
SELECT id, owner_user_id, type, display_name, avatar_url, is_active, status, max_context_load_time, language, allow_guest, chat_model_id, memory_model_id, embedding_model_id, search_provider_id, metadata, created_at, updated_at SELECT id, owner_user_id, type, display_name, avatar_url, is_active, status, max_context_load_time, max_context_tokens, language, allow_guest, chat_model_id, memory_model_id, embedding_model_id, search_provider_id, metadata, created_at, updated_at
FROM bots FROM bots
WHERE owner_user_id = $1 WHERE owner_user_id = $1
ORDER BY created_at DESC; ORDER BY created_at DESC;
-- name: ListBotsByMember :many -- name: ListBotsByMember :many
SELECT b.id, b.owner_user_id, b.type, b.display_name, b.avatar_url, b.is_active, b.status, b.max_context_load_time, b.language, b.allow_guest, b.chat_model_id, b.memory_model_id, b.embedding_model_id, b.search_provider_id, b.metadata, b.created_at, b.updated_at SELECT b.id, b.owner_user_id, b.type, b.display_name, b.avatar_url, b.is_active, b.status, b.max_context_load_time, b.max_context_tokens, b.language, b.allow_guest, b.chat_model_id, b.memory_model_id, b.embedding_model_id, b.search_provider_id, b.metadata, b.created_at, b.updated_at
FROM bots b FROM bots b
JOIN bot_members m ON m.bot_id = b.id JOIN bot_members m ON m.bot_id = b.id
WHERE m.user_id = $1 WHERE m.user_id = $1
@@ -29,14 +29,14 @@ SET display_name = $2,
metadata = $5, metadata = $5,
updated_at = now() updated_at = now()
WHERE id = $1 WHERE id = $1
RETURNING id, owner_user_id, type, display_name, avatar_url, is_active, status, max_context_load_time, language, allow_guest, chat_model_id, memory_model_id, embedding_model_id, search_provider_id, metadata, created_at, updated_at; RETURNING id, owner_user_id, type, display_name, avatar_url, is_active, status, max_context_load_time, max_context_tokens, language, allow_guest, chat_model_id, memory_model_id, embedding_model_id, search_provider_id, metadata, created_at, updated_at;
-- name: UpdateBotOwner :one -- name: UpdateBotOwner :one
UPDATE bots UPDATE bots
SET owner_user_id = $2, SET owner_user_id = $2,
updated_at = now() updated_at = now()
WHERE id = $1 WHERE id = $1
RETURNING id, owner_user_id, type, display_name, avatar_url, is_active, status, max_context_load_time, language, allow_guest, chat_model_id, memory_model_id, embedding_model_id, search_provider_id, metadata, created_at, updated_at; RETURNING id, owner_user_id, type, display_name, avatar_url, is_active, status, max_context_load_time, max_context_tokens, language, allow_guest, chat_model_id, memory_model_id, embedding_model_id, search_provider_id, metadata, created_at, updated_at;
-- name: UpdateBotStatus :exec -- name: UpdateBotStatus :exec
UPDATE bots UPDATE bots
+9 -2
View File
@@ -9,7 +9,8 @@ INSERT INTO bot_history_messages (
source_reply_to_message_id, source_reply_to_message_id,
role, role,
content, content,
metadata metadata,
usage
) )
VALUES ( VALUES (
sqlc.arg(bot_id), sqlc.arg(bot_id),
@@ -21,7 +22,8 @@ VALUES (
sqlc.narg(source_reply_to_message_id)::text, sqlc.narg(source_reply_to_message_id)::text,
sqlc.arg(role), sqlc.arg(role),
sqlc.arg(content), sqlc.arg(content),
sqlc.arg(metadata) sqlc.arg(metadata),
sqlc.arg(usage)
) )
RETURNING RETURNING
id, id,
@@ -35,6 +37,7 @@ RETURNING
role, role,
content, content,
metadata, metadata,
usage,
created_at; created_at;
-- name: ListMessages :many -- name: ListMessages :many
@@ -50,6 +53,7 @@ SELECT
m.role, m.role,
m.content, m.content,
m.metadata, m.metadata,
m.usage,
m.created_at, m.created_at,
ci.display_name AS sender_display_name, ci.display_name AS sender_display_name,
ci.avatar_url AS sender_avatar_url ci.avatar_url AS sender_avatar_url
@@ -72,6 +76,7 @@ SELECT
m.role, m.role,
m.content, m.content,
m.metadata, m.metadata,
m.usage,
m.created_at, m.created_at,
ci.display_name AS sender_display_name, ci.display_name AS sender_display_name,
ci.avatar_url AS sender_avatar_url ci.avatar_url AS sender_avatar_url
@@ -94,6 +99,7 @@ SELECT
m.role, m.role,
m.content, m.content,
m.metadata, m.metadata,
m.usage,
m.created_at, m.created_at,
ci.display_name AS sender_display_name, ci.display_name AS sender_display_name,
ci.avatar_url AS sender_avatar_url ci.avatar_url AS sender_avatar_url
@@ -117,6 +123,7 @@ SELECT
m.role, m.role,
m.content, m.content,
m.metadata, m.metadata,
m.usage,
m.created_at, m.created_at,
ci.display_name AS sender_display_name, ci.display_name AS sender_display_name,
ci.avatar_url AS sender_avatar_url ci.avatar_url AS sender_avatar_url
+5 -1
View File
@@ -2,6 +2,7 @@
SELECT SELECT
bots.id AS bot_id, bots.id AS bot_id,
bots.max_context_load_time, bots.max_context_load_time,
bots.max_context_tokens,
bots.language, bots.language,
bots.allow_guest, bots.allow_guest,
chat_models.model_id AS chat_model_id, chat_models.model_id AS chat_model_id,
@@ -19,6 +20,7 @@ WHERE bots.id = $1;
WITH updated AS ( WITH updated AS (
UPDATE bots UPDATE bots
SET max_context_load_time = sqlc.arg(max_context_load_time), SET max_context_load_time = sqlc.arg(max_context_load_time),
max_context_tokens = sqlc.arg(max_context_tokens),
language = sqlc.arg(language), language = sqlc.arg(language),
allow_guest = sqlc.arg(allow_guest), allow_guest = sqlc.arg(allow_guest),
chat_model_id = COALESCE(sqlc.narg(chat_model_id)::uuid, bots.chat_model_id), chat_model_id = COALESCE(sqlc.narg(chat_model_id)::uuid, bots.chat_model_id),
@@ -27,11 +29,12 @@ WITH updated AS (
search_provider_id = COALESCE(sqlc.narg(search_provider_id)::uuid, bots.search_provider_id), search_provider_id = COALESCE(sqlc.narg(search_provider_id)::uuid, bots.search_provider_id),
updated_at = now() updated_at = now()
WHERE bots.id = sqlc.arg(id) WHERE bots.id = sqlc.arg(id)
RETURNING bots.id, bots.max_context_load_time, bots.language, bots.allow_guest, bots.chat_model_id, bots.memory_model_id, bots.embedding_model_id, bots.search_provider_id RETURNING bots.id, bots.max_context_load_time, bots.max_context_tokens, bots.language, bots.allow_guest, bots.chat_model_id, bots.memory_model_id, bots.embedding_model_id, bots.search_provider_id
) )
SELECT SELECT
updated.id AS bot_id, updated.id AS bot_id,
updated.max_context_load_time, updated.max_context_load_time,
updated.max_context_tokens,
updated.language, updated.language,
updated.allow_guest, updated.allow_guest,
chat_models.model_id AS chat_model_id, chat_models.model_id AS chat_model_id,
@@ -47,6 +50,7 @@ LEFT JOIN search_providers ON search_providers.id = updated.search_provider_id;
-- name: DeleteSettingsByBotID :exec -- name: DeleteSettingsByBotID :exec
UPDATE bots UPDATE bots
SET max_context_load_time = 1440, SET max_context_load_time = 1440,
max_context_tokens = 0,
language = 'auto', language = 'auto',
allow_guest = false, allow_guest = false,
chat_model_id = NULL, chat_model_id = NULL,
+108 -22
View File
@@ -150,6 +150,12 @@ type gatewayRequest struct {
type gatewayResponse struct { type gatewayResponse struct {
Messages []conversation.ModelMessage `json:"messages"` Messages []conversation.ModelMessage `json:"messages"`
Skills []string `json:"skills"` Skills []string `json:"skills"`
Usage json.RawMessage `json:"usage,omitempty"`
}
type gatewayUsage struct {
InputTokens *int `json:"inputTokens"`
OutputTokens *int `json:"outputTokens"`
} }
// gatewaySchedule matches the agent gateway ScheduleModel for /chat/trigger-schedule. // gatewaySchedule matches the agent gateway ScheduleModel for /chat/trigger-schedule.
@@ -212,13 +218,15 @@ func (r *Resolver) resolve(ctx context.Context, req conversation.ChatRequest) (r
return resolvedContext{}, err return resolvedContext{}, err
} }
maxCtx := coalescePositiveInt(req.MaxContextLoadTime, botSettings.MaxContextLoadTime, defaultMaxContextMinutes) maxCtx := coalescePositiveInt(req.MaxContextLoadTime, botSettings.MaxContextLoadTime, defaultMaxContextMinutes)
maxTokens := botSettings.MaxContextTokens
var messages []conversation.ModelMessage var messages []conversation.ModelMessage
if !skipHistory && r.conversationSvc != nil { if !skipHistory && r.conversationSvc != nil {
messages, err = r.loadMessages(ctx, req.ChatID, maxCtx) loaded, loadErr := r.loadMessages(ctx, req.ChatID, maxCtx)
if err != nil { if loadErr != nil {
return resolvedContext{}, err return resolvedContext{}, loadErr
} }
messages = trimMessagesByTokens(loaded, maxTokens)
} }
if memoryMsg := r.loadMemoryContextMessage(ctx, req); memoryMsg != nil { if memoryMsg := r.loadMemoryContextMessage(ctx, req); memoryMsg != nil {
messages = append(messages, *memoryMsg) messages = append(messages, *memoryMsg)
@@ -291,7 +299,7 @@ func (r *Resolver) Chat(ctx context.Context, req conversation.ChatRequest) (conv
if err != nil { if err != nil {
return conversation.ChatResponse{}, err return conversation.ChatResponse{}, err
} }
if err := r.storeRound(ctx, req, resp.Messages); err != nil { if err := r.storeRound(ctx, req, resp.Messages, resp.Usage); err != nil {
return conversation.ChatResponse{}, err return conversation.ChatResponse{}, err
} }
return conversation.ChatResponse{ return conversation.ChatResponse{
@@ -345,7 +353,7 @@ func (r *Resolver) TriggerSchedule(ctx context.Context, botID string, payload sc
if err != nil { if err != nil {
return err return err
} }
return r.storeRound(ctx, req, resp.Messages) return r.storeRound(ctx, req, resp.Messages, resp.Usage)
} }
// --- StreamChat --- // --- StreamChat ---
@@ -552,7 +560,7 @@ func (r *Resolver) tryStoreStream(ctx context.Context, req conversation.ChatRequ
if eventType == "done" { if eventType == "done" {
var resp gatewayResponse var resp gatewayResponse
if err := json.Unmarshal([]byte(data), &resp); err == nil && len(resp.Messages) > 0 { if err := json.Unmarshal([]byte(data), &resp); err == nil && len(resp.Messages) > 0 {
return true, r.storeRound(ctx, req, resp.Messages) return true, r.storeRound(ctx, req, resp.Messages, resp.Usage)
} }
} }
@@ -562,15 +570,16 @@ func (r *Resolver) tryStoreStream(ctx context.Context, req conversation.ChatRequ
Data json.RawMessage `json:"data"` Data json.RawMessage `json:"data"`
Messages []conversation.ModelMessage `json:"messages"` Messages []conversation.ModelMessage `json:"messages"`
Skills []string `json:"skills"` Skills []string `json:"skills"`
Usage json.RawMessage `json:"usage,omitempty"`
} }
if err := json.Unmarshal([]byte(data), &envelope); err == nil { if err := json.Unmarshal([]byte(data), &envelope); err == nil {
if (envelope.Type == "agent_end" || envelope.Type == "done") && len(envelope.Messages) > 0 { if (envelope.Type == "agent_end" || envelope.Type == "done") && len(envelope.Messages) > 0 {
return true, r.storeRound(ctx, req, envelope.Messages) return true, r.storeRound(ctx, req, envelope.Messages, envelope.Usage)
} }
if envelope.Type == "done" && len(envelope.Data) > 0 { if envelope.Type == "done" && len(envelope.Data) > 0 {
var resp gatewayResponse var resp gatewayResponse
if err := json.Unmarshal(envelope.Data, &resp); err == nil && len(resp.Messages) > 0 { if err := json.Unmarshal(envelope.Data, &resp); err == nil && len(resp.Messages) > 0 {
return true, r.storeRound(ctx, req, resp.Messages) return true, r.storeRound(ctx, req, resp.Messages, resp.Usage)
} }
} }
} }
@@ -578,7 +587,7 @@ func (r *Resolver) tryStoreStream(ctx context.Context, req conversation.ChatRequ
// fallback: data: {messages: [...]} // fallback: data: {messages: [...]}
var resp gatewayResponse var resp gatewayResponse
if err := json.Unmarshal([]byte(data), &resp); err == nil && len(resp.Messages) > 0 { if err := json.Unmarshal([]byte(data), &resp); err == nil && len(resp.Messages) > 0 {
return true, r.storeRound(ctx, req, resp.Messages) return true, r.storeRound(ctx, req, resp.Messages, resp.Usage)
} }
return false, nil return false, nil
} }
@@ -648,7 +657,12 @@ func (r *Resolver) resolveContainerID(ctx context.Context, botID, explicit strin
// --- message loading --- // --- message loading ---
func (r *Resolver) loadMessages(ctx context.Context, chatID string, maxContextMinutes int) ([]conversation.ModelMessage, error) { type messageWithUsage struct {
Message conversation.ModelMessage
UsageInputTokens *int
}
func (r *Resolver) loadMessages(ctx context.Context, chatID string, maxContextMinutes int) ([]messageWithUsage, error) {
if r.messageService == nil { if r.messageService == nil {
return nil, nil return nil, nil
} }
@@ -657,7 +671,7 @@ func (r *Resolver) loadMessages(ctx context.Context, chatID string, maxContextMi
if err != nil { if err != nil {
return nil, err return nil, err
} }
var result []conversation.ModelMessage var result []messageWithUsage
for _, m := range msgs { for _, m := range msgs {
var mm conversation.ModelMessage var mm conversation.ModelMessage
if err := json.Unmarshal(m.Content, &mm); err != nil { if err := json.Unmarshal(m.Content, &mm); err != nil {
@@ -667,11 +681,84 @@ func (r *Resolver) loadMessages(ctx context.Context, chatID string, maxContextMi
} else { } else {
mm.Role = m.Role mm.Role = m.Role
} }
result = append(result, mm) var inputTokens *int
if len(m.Usage) > 0 {
var u gatewayUsage
if json.Unmarshal(m.Usage, &u) == nil {
inputTokens = u.InputTokens
}
}
result = append(result, messageWithUsage{Message: mm, UsageInputTokens: inputTokens})
} }
return result, nil return result, nil
} }
func estimateMessageTokens(msg conversation.ModelMessage) int {
text := msg.TextContent()
if len(text) == 0 {
data, _ := json.Marshal(msg.Content)
return len(data) / 4
}
return len(text) / 4
}
func trimMessagesByTokens(messages []messageWithUsage, maxTokens int) []conversation.ModelMessage {
if maxTokens <= 0 || len(messages) == 0 {
result := make([]conversation.ModelMessage, len(messages))
for i, m := range messages {
result[i] = m.Message
}
return result
}
// Scan backwards. When a message with UsageInputTokens is found, that value
// represents the cumulative input tokens for all messages up to and including
// that message. Messages after it are estimated with chars/4.
totalTokens := 0
anchorFound := false
cutoff := 0
tailEstimate := 0
for i := len(messages) - 1; i >= 0; i-- {
if !anchorFound && messages[i].UsageInputTokens != nil {
anchorFound = true
totalTokens = *messages[i].UsageInputTokens + tailEstimate
if totalTokens > maxTokens {
cutoff = i + 1
break
}
continue
}
est := estimateMessageTokens(messages[i].Message)
if anchorFound {
totalTokens += est
if totalTokens > maxTokens {
cutoff = i + 1
break
}
} else {
tailEstimate += est
}
}
if !anchorFound {
totalTokens = 0
for i := len(messages) - 1; i >= 0; i-- {
totalTokens += estimateMessageTokens(messages[i].Message)
if totalTokens > maxTokens {
cutoff = i + 1
break
}
}
}
result := make([]conversation.ModelMessage, 0, len(messages)-cutoff)
for _, m := range messages[cutoff:] {
result = append(result, m.Message)
}
return result
}
type memoryContextItem struct { type memoryContextItem struct {
Namespace string Namespace string
Item memory.MemoryItem Item memory.MemoryItem
@@ -792,9 +879,7 @@ func (r *Resolver) persistUserMessage(ctx context.Context, req conversation.Chat
return err return err
} }
func (r *Resolver) storeRound(ctx context.Context, req conversation.ChatRequest, messages []conversation.ModelMessage) error { func (r *Resolver) storeRound(ctx context.Context, req conversation.ChatRequest, messages []conversation.ModelMessage, usage json.RawMessage) error {
// Add user query as the first message if not already present in the round.
// This ensures the user's prompt is persisted alongside the assistant's response.
fullRound := make([]conversation.ModelMessage, 0, len(messages)+1) fullRound := make([]conversation.ModelMessage, 0, len(messages)+1)
hasUserQuery := false hasUserQuery := false
for _, m := range messages { for _, m := range messages {
@@ -813,7 +898,6 @@ func (r *Resolver) storeRound(ctx context.Context, req conversation.ChatRequest,
} }
for _, m := range messages { for _, m := range messages {
if req.UserMessagePersisted && m.Role == "user" && strings.TrimSpace(m.TextContent()) == strings.TrimSpace(req.Query) { if req.UserMessagePersisted && m.Role == "user" && strings.TrimSpace(m.TextContent()) == strings.TrimSpace(req.Query) {
// User message was already persisted before streaming; skip duplicate copy in round payload.
continue continue
} }
fullRound = append(fullRound, m) fullRound = append(fullRound, m)
@@ -822,14 +906,12 @@ func (r *Resolver) storeRound(ctx context.Context, req conversation.ChatRequest,
return nil return nil
} }
r.storeMessages(ctx, req, fullRound) r.storeMessages(ctx, req, fullRound, usage)
// Run memory extraction in the background so that the SSE stream can
// finish immediately after messages are persisted.
go r.storeMemory(context.WithoutCancel(ctx), req.BotID, fullRound) go r.storeMemory(context.WithoutCancel(ctx), req.BotID, fullRound)
return nil return nil
} }
func (r *Resolver) storeMessages(ctx context.Context, req conversation.ChatRequest, messages []conversation.ModelMessage) { func (r *Resolver) storeMessages(ctx context.Context, req conversation.ChatRequest, messages []conversation.ModelMessage, usage json.RawMessage) {
if r.messageService == nil { if r.messageService == nil {
return return
} }
@@ -838,7 +920,7 @@ func (r *Resolver) storeMessages(ctx context.Context, req conversation.ChatReque
} }
meta := buildRouteMetadata(req) meta := buildRouteMetadata(req)
senderChannelIdentityID, senderUserID := r.resolvePersistSenderIDs(ctx, req) senderChannelIdentityID, senderUserID := r.resolvePersistSenderIDs(ctx, req)
for _, msg := range messages { for i, msg := range messages {
content, err := json.Marshal(msg) content, err := json.Marshal(msg)
if err != nil { if err != nil {
r.logger.Warn("storeMessages: marshal failed", slog.Any("error", err)) r.logger.Warn("storeMessages: marshal failed", slog.Any("error", err))
@@ -857,9 +939,12 @@ func (r *Resolver) storeMessages(ctx context.Context, req conversation.ChatReque
assets = chatAttachmentsToAssetRefs(req.Attachments) assets = chatAttachmentsToAssetRefs(req.Attachments)
} }
} else if strings.TrimSpace(req.ExternalMessageID) != "" { } else if strings.TrimSpace(req.ExternalMessageID) != "" {
// Assistant/tool/system outputs are linked to the inbound source message for cross-channel reply threading.
sourceReplyToMessageID = req.ExternalMessageID sourceReplyToMessageID = req.ExternalMessageID
} }
var msgUsage json.RawMessage
if i == len(messages)-1 && len(usage) > 0 {
msgUsage = usage
}
if _, err := r.messageService.Persist(ctx, messagepkg.PersistInput{ if _, err := r.messageService.Persist(ctx, messagepkg.PersistInput{
BotID: req.BotID, BotID: req.BotID,
RouteID: req.RouteID, RouteID: req.RouteID,
@@ -871,6 +956,7 @@ func (r *Resolver) storeMessages(ctx context.Context, req conversation.ChatReque
Role: msg.Role, Role: msg.Role,
Content: content, Content: content,
Metadata: meta, Metadata: meta,
Usage: msgUsage,
Assets: assets, Assets: assets,
}); err != nil { }); err != nil {
r.logger.Warn("persist message failed", slog.Any("error", err)) r.logger.Warn("persist message failed", slog.Any("error", err))
+12 -6
View File
@@ -14,7 +14,7 @@ import (
const createBot = `-- name: CreateBot :one const createBot = `-- name: CreateBot :one
INSERT INTO bots (owner_user_id, type, display_name, avatar_url, is_active, metadata, status) INSERT INTO bots (owner_user_id, type, display_name, avatar_url, is_active, metadata, status)
VALUES ($1, $2, $3, $4, $5, $6, $7) VALUES ($1, $2, $3, $4, $5, $6, $7)
RETURNING id, owner_user_id, type, display_name, avatar_url, is_active, status, max_context_load_time, language, allow_guest, chat_model_id, memory_model_id, embedding_model_id, search_provider_id, metadata, created_at, updated_at RETURNING id, owner_user_id, type, display_name, avatar_url, is_active, status, max_context_load_time, max_context_tokens, language, allow_guest, chat_model_id, memory_model_id, embedding_model_id, search_provider_id, metadata, created_at, updated_at
` `
type CreateBotParams struct { type CreateBotParams struct {
@@ -47,6 +47,7 @@ func (q *Queries) CreateBot(ctx context.Context, arg CreateBotParams) (Bot, erro
&i.IsActive, &i.IsActive,
&i.Status, &i.Status,
&i.MaxContextLoadTime, &i.MaxContextLoadTime,
&i.MaxContextTokens,
&i.Language, &i.Language,
&i.AllowGuest, &i.AllowGuest,
&i.ChatModelID, &i.ChatModelID,
@@ -84,7 +85,7 @@ func (q *Queries) DeleteBotMember(ctx context.Context, arg DeleteBotMemberParams
} }
const getBotByID = `-- name: GetBotByID :one const getBotByID = `-- name: GetBotByID :one
SELECT id, owner_user_id, type, display_name, avatar_url, is_active, status, max_context_load_time, language, allow_guest, chat_model_id, memory_model_id, embedding_model_id, search_provider_id, metadata, created_at, updated_at SELECT id, owner_user_id, type, display_name, avatar_url, is_active, status, max_context_load_time, max_context_tokens, language, allow_guest, chat_model_id, memory_model_id, embedding_model_id, search_provider_id, metadata, created_at, updated_at
FROM bots FROM bots
WHERE id = $1 WHERE id = $1
` `
@@ -101,6 +102,7 @@ func (q *Queries) GetBotByID(ctx context.Context, id pgtype.UUID) (Bot, error) {
&i.IsActive, &i.IsActive,
&i.Status, &i.Status,
&i.MaxContextLoadTime, &i.MaxContextLoadTime,
&i.MaxContextTokens,
&i.Language, &i.Language,
&i.AllowGuest, &i.AllowGuest,
&i.ChatModelID, &i.ChatModelID,
@@ -171,7 +173,7 @@ func (q *Queries) ListBotMembers(ctx context.Context, botID pgtype.UUID) ([]BotM
} }
const listBotsByMember = `-- name: ListBotsByMember :many const listBotsByMember = `-- name: ListBotsByMember :many
SELECT b.id, b.owner_user_id, b.type, b.display_name, b.avatar_url, b.is_active, b.status, b.max_context_load_time, b.language, b.allow_guest, b.chat_model_id, b.memory_model_id, b.embedding_model_id, b.search_provider_id, b.metadata, b.created_at, b.updated_at SELECT b.id, b.owner_user_id, b.type, b.display_name, b.avatar_url, b.is_active, b.status, b.max_context_load_time, b.max_context_tokens, b.language, b.allow_guest, b.chat_model_id, b.memory_model_id, b.embedding_model_id, b.search_provider_id, b.metadata, b.created_at, b.updated_at
FROM bots b FROM bots b
JOIN bot_members m ON m.bot_id = b.id JOIN bot_members m ON m.bot_id = b.id
WHERE m.user_id = $1 WHERE m.user_id = $1
@@ -196,6 +198,7 @@ func (q *Queries) ListBotsByMember(ctx context.Context, userID pgtype.UUID) ([]B
&i.IsActive, &i.IsActive,
&i.Status, &i.Status,
&i.MaxContextLoadTime, &i.MaxContextLoadTime,
&i.MaxContextTokens,
&i.Language, &i.Language,
&i.AllowGuest, &i.AllowGuest,
&i.ChatModelID, &i.ChatModelID,
@@ -217,7 +220,7 @@ func (q *Queries) ListBotsByMember(ctx context.Context, userID pgtype.UUID) ([]B
} }
const listBotsByOwner = `-- name: ListBotsByOwner :many const listBotsByOwner = `-- name: ListBotsByOwner :many
SELECT id, owner_user_id, type, display_name, avatar_url, is_active, status, max_context_load_time, language, allow_guest, chat_model_id, memory_model_id, embedding_model_id, search_provider_id, metadata, created_at, updated_at SELECT id, owner_user_id, type, display_name, avatar_url, is_active, status, max_context_load_time, max_context_tokens, language, allow_guest, chat_model_id, memory_model_id, embedding_model_id, search_provider_id, metadata, created_at, updated_at
FROM bots FROM bots
WHERE owner_user_id = $1 WHERE owner_user_id = $1
ORDER BY created_at DESC ORDER BY created_at DESC
@@ -241,6 +244,7 @@ func (q *Queries) ListBotsByOwner(ctx context.Context, ownerUserID pgtype.UUID)
&i.IsActive, &i.IsActive,
&i.Status, &i.Status,
&i.MaxContextLoadTime, &i.MaxContextLoadTime,
&i.MaxContextTokens,
&i.Language, &i.Language,
&i.AllowGuest, &i.AllowGuest,
&i.ChatModelID, &i.ChatModelID,
@@ -266,7 +270,7 @@ UPDATE bots
SET owner_user_id = $2, SET owner_user_id = $2,
updated_at = now() updated_at = now()
WHERE id = $1 WHERE id = $1
RETURNING id, owner_user_id, type, display_name, avatar_url, is_active, status, max_context_load_time, language, allow_guest, chat_model_id, memory_model_id, embedding_model_id, search_provider_id, metadata, created_at, updated_at RETURNING id, owner_user_id, type, display_name, avatar_url, is_active, status, max_context_load_time, max_context_tokens, language, allow_guest, chat_model_id, memory_model_id, embedding_model_id, search_provider_id, metadata, created_at, updated_at
` `
type UpdateBotOwnerParams struct { type UpdateBotOwnerParams struct {
@@ -286,6 +290,7 @@ func (q *Queries) UpdateBotOwner(ctx context.Context, arg UpdateBotOwnerParams)
&i.IsActive, &i.IsActive,
&i.Status, &i.Status,
&i.MaxContextLoadTime, &i.MaxContextLoadTime,
&i.MaxContextTokens,
&i.Language, &i.Language,
&i.AllowGuest, &i.AllowGuest,
&i.ChatModelID, &i.ChatModelID,
@@ -307,7 +312,7 @@ SET display_name = $2,
metadata = $5, metadata = $5,
updated_at = now() updated_at = now()
WHERE id = $1 WHERE id = $1
RETURNING id, owner_user_id, type, display_name, avatar_url, is_active, status, max_context_load_time, language, allow_guest, chat_model_id, memory_model_id, embedding_model_id, search_provider_id, metadata, created_at, updated_at RETURNING id, owner_user_id, type, display_name, avatar_url, is_active, status, max_context_load_time, max_context_tokens, language, allow_guest, chat_model_id, memory_model_id, embedding_model_id, search_provider_id, metadata, created_at, updated_at
` `
type UpdateBotProfileParams struct { type UpdateBotProfileParams struct {
@@ -336,6 +341,7 @@ func (q *Queries) UpdateBotProfile(ctx context.Context, arg UpdateBotProfilePara
&i.IsActive, &i.IsActive,
&i.Status, &i.Status,
&i.MaxContextLoadTime, &i.MaxContextLoadTime,
&i.MaxContextTokens,
&i.Language, &i.Language,
&i.AllowGuest, &i.AllowGuest,
&i.ChatModelID, &i.ChatModelID,
+1 -1
View File
@@ -590,7 +590,7 @@ WITH updated AS (
SET display_name = $1, SET display_name = $1,
updated_at = now() updated_at = now()
WHERE bots.id = $2 WHERE bots.id = $2
RETURNING id, owner_user_id, type, display_name, avatar_url, is_active, status, max_context_load_time, language, allow_guest, chat_model_id, memory_model_id, embedding_model_id, search_provider_id, metadata, created_at, updated_at RETURNING id, owner_user_id, type, display_name, avatar_url, is_active, status, max_context_load_time, max_context_tokens, language, allow_guest, chat_model_id, memory_model_id, embedding_model_id, search_provider_id, metadata, created_at, updated_at
) )
SELECT SELECT
updated.id AS id, updated.id AS id,
+21 -2
View File
@@ -22,7 +22,8 @@ INSERT INTO bot_history_messages (
source_reply_to_message_id, source_reply_to_message_id,
role, role,
content, content,
metadata metadata,
usage
) )
VALUES ( VALUES (
$1, $1,
@@ -34,7 +35,8 @@ VALUES (
$7::text, $7::text,
$8, $8,
$9, $9,
$10 $10,
$11
) )
RETURNING RETURNING
id, id,
@@ -48,6 +50,7 @@ RETURNING
role, role,
content, content,
metadata, metadata,
usage,
created_at created_at
` `
@@ -62,6 +65,7 @@ type CreateMessageParams struct {
Role string `json:"role"` Role string `json:"role"`
Content []byte `json:"content"` Content []byte `json:"content"`
Metadata []byte `json:"metadata"` Metadata []byte `json:"metadata"`
Usage []byte `json:"usage"`
} }
type CreateMessageRow struct { type CreateMessageRow struct {
@@ -76,6 +80,7 @@ type CreateMessageRow struct {
Role string `json:"role"` Role string `json:"role"`
Content []byte `json:"content"` Content []byte `json:"content"`
Metadata []byte `json:"metadata"` Metadata []byte `json:"metadata"`
Usage []byte `json:"usage"`
CreatedAt pgtype.Timestamptz `json:"created_at"` CreatedAt pgtype.Timestamptz `json:"created_at"`
} }
@@ -91,6 +96,7 @@ func (q *Queries) CreateMessage(ctx context.Context, arg CreateMessageParams) (C
arg.Role, arg.Role,
arg.Content, arg.Content,
arg.Metadata, arg.Metadata,
arg.Usage,
) )
var i CreateMessageRow var i CreateMessageRow
err := row.Scan( err := row.Scan(
@@ -105,6 +111,7 @@ func (q *Queries) CreateMessage(ctx context.Context, arg CreateMessageParams) (C
&i.Role, &i.Role,
&i.Content, &i.Content,
&i.Metadata, &i.Metadata,
&i.Usage,
&i.CreatedAt, &i.CreatedAt,
) )
return i, err return i, err
@@ -133,6 +140,7 @@ SELECT
m.role, m.role,
m.content, m.content,
m.metadata, m.metadata,
m.usage,
m.created_at, m.created_at,
ci.display_name AS sender_display_name, ci.display_name AS sender_display_name,
ci.avatar_url AS sender_avatar_url ci.avatar_url AS sender_avatar_url
@@ -155,6 +163,7 @@ type ListMessagesRow struct {
Role string `json:"role"` Role string `json:"role"`
Content []byte `json:"content"` Content []byte `json:"content"`
Metadata []byte `json:"metadata"` Metadata []byte `json:"metadata"`
Usage []byte `json:"usage"`
CreatedAt pgtype.Timestamptz `json:"created_at"` CreatedAt pgtype.Timestamptz `json:"created_at"`
SenderDisplayName pgtype.Text `json:"sender_display_name"` SenderDisplayName pgtype.Text `json:"sender_display_name"`
SenderAvatarUrl pgtype.Text `json:"sender_avatar_url"` SenderAvatarUrl pgtype.Text `json:"sender_avatar_url"`
@@ -181,6 +190,7 @@ func (q *Queries) ListMessages(ctx context.Context, botID pgtype.UUID) ([]ListMe
&i.Role, &i.Role,
&i.Content, &i.Content,
&i.Metadata, &i.Metadata,
&i.Usage,
&i.CreatedAt, &i.CreatedAt,
&i.SenderDisplayName, &i.SenderDisplayName,
&i.SenderAvatarUrl, &i.SenderAvatarUrl,
@@ -208,6 +218,7 @@ SELECT
m.role, m.role,
m.content, m.content,
m.metadata, m.metadata,
m.usage,
m.created_at, m.created_at,
ci.display_name AS sender_display_name, ci.display_name AS sender_display_name,
ci.avatar_url AS sender_avatar_url ci.avatar_url AS sender_avatar_url
@@ -237,6 +248,7 @@ type ListMessagesBeforeRow struct {
Role string `json:"role"` Role string `json:"role"`
Content []byte `json:"content"` Content []byte `json:"content"`
Metadata []byte `json:"metadata"` Metadata []byte `json:"metadata"`
Usage []byte `json:"usage"`
CreatedAt pgtype.Timestamptz `json:"created_at"` CreatedAt pgtype.Timestamptz `json:"created_at"`
SenderDisplayName pgtype.Text `json:"sender_display_name"` SenderDisplayName pgtype.Text `json:"sender_display_name"`
SenderAvatarUrl pgtype.Text `json:"sender_avatar_url"` SenderAvatarUrl pgtype.Text `json:"sender_avatar_url"`
@@ -263,6 +275,7 @@ func (q *Queries) ListMessagesBefore(ctx context.Context, arg ListMessagesBefore
&i.Role, &i.Role,
&i.Content, &i.Content,
&i.Metadata, &i.Metadata,
&i.Usage,
&i.CreatedAt, &i.CreatedAt,
&i.SenderDisplayName, &i.SenderDisplayName,
&i.SenderAvatarUrl, &i.SenderAvatarUrl,
@@ -290,6 +303,7 @@ SELECT
m.role, m.role,
m.content, m.content,
m.metadata, m.metadata,
m.usage,
m.created_at, m.created_at,
ci.display_name AS sender_display_name, ci.display_name AS sender_display_name,
ci.avatar_url AS sender_avatar_url ci.avatar_url AS sender_avatar_url
@@ -317,6 +331,7 @@ type ListMessagesLatestRow struct {
Role string `json:"role"` Role string `json:"role"`
Content []byte `json:"content"` Content []byte `json:"content"`
Metadata []byte `json:"metadata"` Metadata []byte `json:"metadata"`
Usage []byte `json:"usage"`
CreatedAt pgtype.Timestamptz `json:"created_at"` CreatedAt pgtype.Timestamptz `json:"created_at"`
SenderDisplayName pgtype.Text `json:"sender_display_name"` SenderDisplayName pgtype.Text `json:"sender_display_name"`
SenderAvatarUrl pgtype.Text `json:"sender_avatar_url"` SenderAvatarUrl pgtype.Text `json:"sender_avatar_url"`
@@ -343,6 +358,7 @@ func (q *Queries) ListMessagesLatest(ctx context.Context, arg ListMessagesLatest
&i.Role, &i.Role,
&i.Content, &i.Content,
&i.Metadata, &i.Metadata,
&i.Usage,
&i.CreatedAt, &i.CreatedAt,
&i.SenderDisplayName, &i.SenderDisplayName,
&i.SenderAvatarUrl, &i.SenderAvatarUrl,
@@ -370,6 +386,7 @@ SELECT
m.role, m.role,
m.content, m.content,
m.metadata, m.metadata,
m.usage,
m.created_at, m.created_at,
ci.display_name AS sender_display_name, ci.display_name AS sender_display_name,
ci.avatar_url AS sender_avatar_url ci.avatar_url AS sender_avatar_url
@@ -397,6 +414,7 @@ type ListMessagesSinceRow struct {
Role string `json:"role"` Role string `json:"role"`
Content []byte `json:"content"` Content []byte `json:"content"`
Metadata []byte `json:"metadata"` Metadata []byte `json:"metadata"`
Usage []byte `json:"usage"`
CreatedAt pgtype.Timestamptz `json:"created_at"` CreatedAt pgtype.Timestamptz `json:"created_at"`
SenderDisplayName pgtype.Text `json:"sender_display_name"` SenderDisplayName pgtype.Text `json:"sender_display_name"`
SenderAvatarUrl pgtype.Text `json:"sender_avatar_url"` SenderAvatarUrl pgtype.Text `json:"sender_avatar_url"`
@@ -423,6 +441,7 @@ func (q *Queries) ListMessagesSince(ctx context.Context, arg ListMessagesSincePa
&i.Role, &i.Role,
&i.Content, &i.Content,
&i.Metadata, &i.Metadata,
&i.Usage,
&i.CreatedAt, &i.CreatedAt,
&i.SenderDisplayName, &i.SenderDisplayName,
&i.SenderAvatarUrl, &i.SenderAvatarUrl,
+2
View File
@@ -17,6 +17,7 @@ type Bot struct {
IsActive bool `json:"is_active"` IsActive bool `json:"is_active"`
Status string `json:"status"` Status string `json:"status"`
MaxContextLoadTime int32 `json:"max_context_load_time"` MaxContextLoadTime int32 `json:"max_context_load_time"`
MaxContextTokens int32 `json:"max_context_tokens"`
Language string `json:"language"` Language string `json:"language"`
AllowGuest bool `json:"allow_guest"` AllowGuest bool `json:"allow_guest"`
ChatModelID pgtype.UUID `json:"chat_model_id"` ChatModelID pgtype.UUID `json:"chat_model_id"`
@@ -69,6 +70,7 @@ type BotHistoryMessage struct {
Role string `json:"role"` Role string `json:"role"`
Content []byte `json:"content"` Content []byte `json:"content"`
Metadata []byte `json:"metadata"` Metadata []byte `json:"metadata"`
Usage []byte `json:"usage"`
CreatedAt pgtype.Timestamptz `json:"created_at"` CreatedAt pgtype.Timestamptz `json:"created_at"`
} }
+18 -8
View File
@@ -14,6 +14,7 @@ import (
const deleteSettingsByBotID = `-- name: DeleteSettingsByBotID :exec const deleteSettingsByBotID = `-- name: DeleteSettingsByBotID :exec
UPDATE bots UPDATE bots
SET max_context_load_time = 1440, SET max_context_load_time = 1440,
max_context_tokens = 0,
language = 'auto', language = 'auto',
allow_guest = false, allow_guest = false,
chat_model_id = NULL, chat_model_id = NULL,
@@ -33,6 +34,7 @@ const getSettingsByBotID = `-- name: GetSettingsByBotID :one
SELECT SELECT
bots.id AS bot_id, bots.id AS bot_id,
bots.max_context_load_time, bots.max_context_load_time,
bots.max_context_tokens,
bots.language, bots.language,
bots.allow_guest, bots.allow_guest,
chat_models.model_id AS chat_model_id, chat_models.model_id AS chat_model_id,
@@ -50,6 +52,7 @@ WHERE bots.id = $1
type GetSettingsByBotIDRow struct { type GetSettingsByBotIDRow struct {
BotID pgtype.UUID `json:"bot_id"` BotID pgtype.UUID `json:"bot_id"`
MaxContextLoadTime int32 `json:"max_context_load_time"` MaxContextLoadTime int32 `json:"max_context_load_time"`
MaxContextTokens int32 `json:"max_context_tokens"`
Language string `json:"language"` Language string `json:"language"`
AllowGuest bool `json:"allow_guest"` AllowGuest bool `json:"allow_guest"`
ChatModelID pgtype.Text `json:"chat_model_id"` ChatModelID pgtype.Text `json:"chat_model_id"`
@@ -64,6 +67,7 @@ func (q *Queries) GetSettingsByBotID(ctx context.Context, id pgtype.UUID) (GetSe
err := row.Scan( err := row.Scan(
&i.BotID, &i.BotID,
&i.MaxContextLoadTime, &i.MaxContextLoadTime,
&i.MaxContextTokens,
&i.Language, &i.Language,
&i.AllowGuest, &i.AllowGuest,
&i.ChatModelID, &i.ChatModelID,
@@ -78,19 +82,21 @@ const upsertBotSettings = `-- name: UpsertBotSettings :one
WITH updated AS ( WITH updated AS (
UPDATE bots UPDATE bots
SET max_context_load_time = $1, SET max_context_load_time = $1,
language = $2, max_context_tokens = $2,
allow_guest = $3, language = $3,
chat_model_id = COALESCE($4::uuid, bots.chat_model_id), allow_guest = $4,
memory_model_id = COALESCE($5::uuid, bots.memory_model_id), chat_model_id = COALESCE($5::uuid, bots.chat_model_id),
embedding_model_id = COALESCE($6::uuid, bots.embedding_model_id), memory_model_id = COALESCE($6::uuid, bots.memory_model_id),
search_provider_id = COALESCE($7::uuid, bots.search_provider_id), embedding_model_id = COALESCE($7::uuid, bots.embedding_model_id),
search_provider_id = COALESCE($8::uuid, bots.search_provider_id),
updated_at = now() updated_at = now()
WHERE bots.id = $8 WHERE bots.id = $9
RETURNING bots.id, bots.max_context_load_time, bots.language, bots.allow_guest, bots.chat_model_id, bots.memory_model_id, bots.embedding_model_id, bots.search_provider_id RETURNING bots.id, bots.max_context_load_time, bots.max_context_tokens, bots.language, bots.allow_guest, bots.chat_model_id, bots.memory_model_id, bots.embedding_model_id, bots.search_provider_id
) )
SELECT SELECT
updated.id AS bot_id, updated.id AS bot_id,
updated.max_context_load_time, updated.max_context_load_time,
updated.max_context_tokens,
updated.language, updated.language,
updated.allow_guest, updated.allow_guest,
chat_models.model_id AS chat_model_id, chat_models.model_id AS chat_model_id,
@@ -106,6 +112,7 @@ LEFT JOIN search_providers ON search_providers.id = updated.search_provider_id
type UpsertBotSettingsParams struct { type UpsertBotSettingsParams struct {
MaxContextLoadTime int32 `json:"max_context_load_time"` MaxContextLoadTime int32 `json:"max_context_load_time"`
MaxContextTokens int32 `json:"max_context_tokens"`
Language string `json:"language"` Language string `json:"language"`
AllowGuest bool `json:"allow_guest"` AllowGuest bool `json:"allow_guest"`
ChatModelID pgtype.UUID `json:"chat_model_id"` ChatModelID pgtype.UUID `json:"chat_model_id"`
@@ -118,6 +125,7 @@ type UpsertBotSettingsParams struct {
type UpsertBotSettingsRow struct { type UpsertBotSettingsRow struct {
BotID pgtype.UUID `json:"bot_id"` BotID pgtype.UUID `json:"bot_id"`
MaxContextLoadTime int32 `json:"max_context_load_time"` MaxContextLoadTime int32 `json:"max_context_load_time"`
MaxContextTokens int32 `json:"max_context_tokens"`
Language string `json:"language"` Language string `json:"language"`
AllowGuest bool `json:"allow_guest"` AllowGuest bool `json:"allow_guest"`
ChatModelID pgtype.Text `json:"chat_model_id"` ChatModelID pgtype.Text `json:"chat_model_id"`
@@ -129,6 +137,7 @@ type UpsertBotSettingsRow struct {
func (q *Queries) UpsertBotSettings(ctx context.Context, arg UpsertBotSettingsParams) (UpsertBotSettingsRow, error) { func (q *Queries) UpsertBotSettings(ctx context.Context, arg UpsertBotSettingsParams) (UpsertBotSettingsRow, error) {
row := q.db.QueryRow(ctx, upsertBotSettings, row := q.db.QueryRow(ctx, upsertBotSettings,
arg.MaxContextLoadTime, arg.MaxContextLoadTime,
arg.MaxContextTokens,
arg.Language, arg.Language,
arg.AllowGuest, arg.AllowGuest,
arg.ChatModelID, arg.ChatModelID,
@@ -141,6 +150,7 @@ func (q *Queries) UpsertBotSettings(ctx context.Context, arg UpsertBotSettingsPa
err := row.Scan( err := row.Scan(
&i.BotID, &i.BotID,
&i.MaxContextLoadTime, &i.MaxContextLoadTime,
&i.MaxContextTokens,
&i.Language, &i.Language,
&i.AllowGuest, &i.AllowGuest,
&i.ChatModelID, &i.ChatModelID,
+8
View File
@@ -79,6 +79,7 @@ func (s *DBService) Persist(ctx context.Context, input PersistInput) (Message, e
Role: input.Role, Role: input.Role,
Content: content, Content: content,
Metadata: metaBytes, Metadata: metaBytes,
Usage: input.Usage,
}) })
if err != nil { if err != nil {
return Message{}, err return Message{}, err
@@ -213,6 +214,7 @@ func toMessageFromCreate(row sqlc.CreateMessageRow) Message {
row.Role, row.Role,
row.Content, row.Content,
row.Metadata, row.Metadata,
row.Usage,
row.CreatedAt, row.CreatedAt,
) )
} }
@@ -232,6 +234,7 @@ func toMessageFromListRow(row sqlc.ListMessagesRow) Message {
row.Role, row.Role,
row.Content, row.Content,
row.Metadata, row.Metadata,
row.Usage,
row.CreatedAt, row.CreatedAt,
) )
} }
@@ -251,6 +254,7 @@ func toMessageFromSinceRow(row sqlc.ListMessagesSinceRow) Message {
row.Role, row.Role,
row.Content, row.Content,
row.Metadata, row.Metadata,
row.Usage,
row.CreatedAt, row.CreatedAt,
) )
} }
@@ -270,6 +274,7 @@ func toMessageFromLatestRow(row sqlc.ListMessagesLatestRow) Message {
row.Role, row.Role,
row.Content, row.Content,
row.Metadata, row.Metadata,
row.Usage,
row.CreatedAt, row.CreatedAt,
) )
} }
@@ -288,6 +293,7 @@ func toMessageFields(
role string, role string,
content []byte, content []byte,
metadata []byte, metadata []byte,
usage []byte,
createdAt pgtype.Timestamptz, createdAt pgtype.Timestamptz,
) Message { ) Message {
return Message{ return Message{
@@ -304,6 +310,7 @@ func toMessageFields(
Role: role, Role: role,
Content: json.RawMessage(content), Content: json.RawMessage(content),
Metadata: parseJSONMap(metadata), Metadata: parseJSONMap(metadata),
Usage: json.RawMessage(usage),
CreatedAt: createdAt.Time, CreatedAt: createdAt.Time,
} }
} }
@@ -347,6 +354,7 @@ func toMessageFromBeforeRow(row sqlc.ListMessagesBeforeRow) Message {
row.Role, row.Role,
row.Content, row.Content,
row.Metadata, row.Metadata,
row.Usage,
row.CreatedAt, row.CreatedAt,
) )
} }
+2
View File
@@ -36,6 +36,7 @@ type Message struct {
Role string `json:"role"` Role string `json:"role"`
Content json.RawMessage `json:"content"` Content json.RawMessage `json:"content"`
Metadata map[string]any `json:"metadata,omitempty"` Metadata map[string]any `json:"metadata,omitempty"`
Usage json.RawMessage `json:"usage,omitempty"`
Assets []MessageAsset `json:"assets,omitempty"` Assets []MessageAsset `json:"assets,omitempty"`
CreatedAt time.Time `json:"created_at"` CreatedAt time.Time `json:"created_at"`
} }
@@ -59,6 +60,7 @@ type PersistInput struct {
Role string Role string
Content json.RawMessage Content json.RawMessage
Metadata map[string]any Metadata map[string]any
Usage json.RawMessage
Assets []AssetRef Assets []AssetRef
} }
+14 -3
View File
@@ -54,10 +54,13 @@ func (s *Service) UpsertBot(ctx context.Context, botID string, req UpsertRequest
} }
isPersonalBot := strings.EqualFold(strings.TrimSpace(botRow.Type), "personal") isPersonalBot := strings.EqualFold(strings.TrimSpace(botRow.Type), "personal")
current := normalizeBotSetting(botRow.MaxContextLoadTime, botRow.Language, botRow.AllowGuest) current := normalizeBotSetting(botRow.MaxContextLoadTime, botRow.MaxContextTokens, botRow.Language, botRow.AllowGuest)
if req.MaxContextLoadTime != nil && *req.MaxContextLoadTime > 0 { if req.MaxContextLoadTime != nil && *req.MaxContextLoadTime > 0 {
current.MaxContextLoadTime = *req.MaxContextLoadTime current.MaxContextLoadTime = *req.MaxContextLoadTime
} }
if req.MaxContextTokens != nil && *req.MaxContextTokens >= 0 {
current.MaxContextTokens = *req.MaxContextTokens
}
if strings.TrimSpace(req.Language) != "" { if strings.TrimSpace(req.Language) != "" {
current.Language = strings.TrimSpace(req.Language) current.Language = strings.TrimSpace(req.Language)
} }
@@ -106,6 +109,7 @@ func (s *Service) UpsertBot(ctx context.Context, botID string, req UpsertRequest
updated, err := s.queries.UpsertBotSettings(ctx, sqlc.UpsertBotSettingsParams{ updated, err := s.queries.UpsertBotSettings(ctx, sqlc.UpsertBotSettingsParams{
ID: pgID, ID: pgID,
MaxContextLoadTime: int32(current.MaxContextLoadTime), MaxContextLoadTime: int32(current.MaxContextLoadTime),
MaxContextTokens: int32(current.MaxContextTokens),
Language: current.Language, Language: current.Language,
AllowGuest: current.AllowGuest, AllowGuest: current.AllowGuest,
ChatModelID: chatModelUUID, ChatModelID: chatModelUUID,
@@ -130,15 +134,19 @@ func (s *Service) Delete(ctx context.Context, botID string) error {
return s.queries.DeleteSettingsByBotID(ctx, pgID) return s.queries.DeleteSettingsByBotID(ctx, pgID)
} }
func normalizeBotSetting(maxContextLoadTime int32, language string, allowGuest bool) Settings { func normalizeBotSetting(maxContextLoadTime int32, maxContextTokens int32, language string, allowGuest bool) Settings {
settings := Settings{ settings := Settings{
MaxContextLoadTime: int(maxContextLoadTime), MaxContextLoadTime: int(maxContextLoadTime),
MaxContextTokens: int(maxContextTokens),
Language: strings.TrimSpace(language), Language: strings.TrimSpace(language),
AllowGuest: allowGuest, AllowGuest: allowGuest,
} }
if settings.MaxContextLoadTime <= 0 { if settings.MaxContextLoadTime <= 0 {
settings.MaxContextLoadTime = DefaultMaxContextLoadTime settings.MaxContextLoadTime = DefaultMaxContextLoadTime
} }
if settings.MaxContextTokens < 0 {
settings.MaxContextTokens = 0
}
if settings.Language == "" { if settings.Language == "" {
settings.Language = DefaultLanguage settings.Language = DefaultLanguage
} }
@@ -148,6 +156,7 @@ func normalizeBotSetting(maxContextLoadTime int32, language string, allowGuest b
func normalizeBotSettingsReadRow(row sqlc.GetSettingsByBotIDRow) Settings { func normalizeBotSettingsReadRow(row sqlc.GetSettingsByBotIDRow) Settings {
return normalizeBotSettingsFields( return normalizeBotSettingsFields(
row.MaxContextLoadTime, row.MaxContextLoadTime,
row.MaxContextTokens,
row.Language, row.Language,
row.AllowGuest, row.AllowGuest,
row.ChatModelID, row.ChatModelID,
@@ -160,6 +169,7 @@ func normalizeBotSettingsReadRow(row sqlc.GetSettingsByBotIDRow) Settings {
func normalizeBotSettingsWriteRow(row sqlc.UpsertBotSettingsRow) Settings { func normalizeBotSettingsWriteRow(row sqlc.UpsertBotSettingsRow) Settings {
return normalizeBotSettingsFields( return normalizeBotSettingsFields(
row.MaxContextLoadTime, row.MaxContextLoadTime,
row.MaxContextTokens,
row.Language, row.Language,
row.AllowGuest, row.AllowGuest,
row.ChatModelID, row.ChatModelID,
@@ -171,6 +181,7 @@ func normalizeBotSettingsWriteRow(row sqlc.UpsertBotSettingsRow) Settings {
func normalizeBotSettingsFields( func normalizeBotSettingsFields(
maxContextLoadTime int32, maxContextLoadTime int32,
maxContextTokens int32,
language string, language string,
allowGuest bool, allowGuest bool,
chatModelID pgtype.Text, chatModelID pgtype.Text,
@@ -178,7 +189,7 @@ func normalizeBotSettingsFields(
embeddingModelID pgtype.Text, embeddingModelID pgtype.Text,
searchProviderID pgtype.UUID, searchProviderID pgtype.UUID,
) Settings { ) Settings {
settings := normalizeBotSetting(maxContextLoadTime, language, allowGuest) settings := normalizeBotSetting(maxContextLoadTime, maxContextTokens, language, allowGuest)
settings.ChatModelID = strings.TrimSpace(chatModelID.String) settings.ChatModelID = strings.TrimSpace(chatModelID.String)
settings.MemoryModelID = strings.TrimSpace(memoryModelID.String) settings.MemoryModelID = strings.TrimSpace(memoryModelID.String)
settings.EmbeddingModelID = strings.TrimSpace(embeddingModelID.String) settings.EmbeddingModelID = strings.TrimSpace(embeddingModelID.String)
+2
View File
@@ -11,6 +11,7 @@ type Settings struct {
EmbeddingModelID string `json:"embedding_model_id"` EmbeddingModelID string `json:"embedding_model_id"`
SearchProviderID string `json:"search_provider_id"` SearchProviderID string `json:"search_provider_id"`
MaxContextLoadTime int `json:"max_context_load_time"` MaxContextLoadTime int `json:"max_context_load_time"`
MaxContextTokens int `json:"max_context_tokens"`
Language string `json:"language"` Language string `json:"language"`
AllowGuest bool `json:"allow_guest"` AllowGuest bool `json:"allow_guest"`
} }
@@ -21,6 +22,7 @@ type UpsertRequest struct {
EmbeddingModelID string `json:"embedding_model_id,omitempty"` EmbeddingModelID string `json:"embedding_model_id,omitempty"`
SearchProviderID string `json:"search_provider_id,omitempty"` SearchProviderID string `json:"search_provider_id,omitempty"`
MaxContextLoadTime *int `json:"max_context_load_time,omitempty"` MaxContextLoadTime *int `json:"max_context_load_time,omitempty"`
MaxContextTokens *int `json:"max_context_tokens,omitempty"`
Language string `json:"language,omitempty"` Language string `json:"language,omitempty"`
AllowGuest *bool `json:"allow_guest,omitempty"` AllowGuest *bool `json:"allow_guest,omitempty"`
} }
+3
View File
@@ -673,6 +673,7 @@ export type MessageMessage = {
sender_display_name?: string; sender_display_name?: string;
sender_user_id?: string; sender_user_id?: string;
source_reply_to_message_id?: string; source_reply_to_message_id?: string;
usage?: Array<number>;
}; };
export type MessageMessageAsset = { export type MessageMessageAsset = {
@@ -866,6 +867,7 @@ export type SettingsSettings = {
embedding_model_id?: string; embedding_model_id?: string;
language?: string; language?: string;
max_context_load_time?: number; max_context_load_time?: number;
max_context_tokens?: number;
memory_model_id?: string; memory_model_id?: string;
search_provider_id?: string; search_provider_id?: string;
}; };
@@ -876,6 +878,7 @@ export type SettingsUpsertRequest = {
embedding_model_id?: string; embedding_model_id?: string;
language?: string; language?: string;
max_context_load_time?: number; max_context_load_time?: number;
max_context_tokens?: number;
memory_model_id?: string; memory_model_id?: string;
search_provider_id?: string; search_provider_id?: string;
}; };
+1
View File
@@ -354,6 +354,7 @@
"searchProvider": "Search Provider", "searchProvider": "Search Provider",
"searchProviderPlaceholder": "Select search provider", "searchProviderPlaceholder": "Select search provider",
"maxContextLoadTime": "Max Context Load Time", "maxContextLoadTime": "Max Context Load Time",
"maxContextTokens": "Max Context Tokens",
"language": "Language", "language": "Language",
"allowGuest": "Allow Guest Access", "allowGuest": "Allow Guest Access",
"allowGuestPersonalHint": "Personal bots do not support guest access. Use a public bot instead.", "allowGuestPersonalHint": "Personal bots do not support guest access. Use a public bot instead.",
+1
View File
@@ -350,6 +350,7 @@
"searchProvider": "搜索提供方", "searchProvider": "搜索提供方",
"searchProviderPlaceholder": "选择搜索提供方", "searchProviderPlaceholder": "选择搜索提供方",
"maxContextLoadTime": "最大上下文加载时间", "maxContextLoadTime": "最大上下文加载时间",
"maxContextTokens": "最大上下文Token数",
"language": "语言", "language": "语言",
"allowGuest": "允许游客访问", "allowGuest": "允许游客访问",
"allowGuestPersonalHint": "个人 Bot 不支持游客访问,请使用公开 Bot。", "allowGuestPersonalHint": "个人 Bot 不支持游客访问,请使用公开 Bot。",
@@ -58,6 +58,17 @@
/> />
</div> </div>
<!-- Max Context Tokens -->
<div class="space-y-2">
<Label>{{ $t('bots.settings.maxContextTokens') }}</Label>
<Input
v-model.number="form.max_context_tokens"
type="number"
:min="0"
placeholder="0"
/>
</div>
<!-- Language --> <!-- Language -->
<div class="space-y-2"> <div class="space-y-2">
<Label>{{ $t('bots.settings.language') }}</Label> <Label>{{ $t('bots.settings.language') }}</Label>
@@ -226,6 +237,7 @@ const form = reactive<SettingsSettings>({
embedding_model_id: '', embedding_model_id: '',
search_provider_id: '', search_provider_id: '',
max_context_load_time: 0, max_context_load_time: 0,
max_context_tokens: 0,
language: '', language: '',
allow_guest: false, allow_guest: false,
}) })
@@ -238,6 +250,7 @@ watch(settings, (val) => {
form.embedding_model_id = val.embedding_model_id ?? '' form.embedding_model_id = val.embedding_model_id ?? ''
form.search_provider_id = val.search_provider_id ?? '' form.search_provider_id = val.search_provider_id ?? ''
form.max_context_load_time = val.max_context_load_time ?? 0 form.max_context_load_time = val.max_context_load_time ?? 0
form.max_context_tokens = val.max_context_tokens ?? 0
form.language = val.language ?? '' form.language = val.language ?? ''
form.allow_guest = val.allow_guest ?? false form.allow_guest = val.allow_guest ?? false
} }
@@ -252,6 +265,7 @@ const hasChanges = computed(() => {
|| form.embedding_model_id !== (s.embedding_model_id ?? '') || form.embedding_model_id !== (s.embedding_model_id ?? '')
|| form.search_provider_id !== (s.search_provider_id ?? '') || form.search_provider_id !== (s.search_provider_id ?? '')
|| form.max_context_load_time !== (s.max_context_load_time ?? 0) || form.max_context_load_time !== (s.max_context_load_time ?? 0)
|| form.max_context_tokens !== (s.max_context_tokens ?? 0)
|| form.language !== (s.language ?? '') || form.language !== (s.language ?? '')
if (isPublicBot.value) { if (isPublicBot.value) {
changed = changed || form.allow_guest !== (s.allow_guest ?? false) changed = changed || form.allow_guest !== (s.allow_guest ?? false)
+12
View File
@@ -6451,6 +6451,12 @@ const docTemplate = `{
}, },
"source_reply_to_message_id": { "source_reply_to_message_id": {
"type": "string" "type": "string"
},
"usage": {
"type": "array",
"items": {
"type": "integer"
}
} }
} }
}, },
@@ -6949,6 +6955,9 @@ const docTemplate = `{
"max_context_load_time": { "max_context_load_time": {
"type": "integer" "type": "integer"
}, },
"max_context_tokens": {
"type": "integer"
},
"memory_model_id": { "memory_model_id": {
"type": "string" "type": "string"
}, },
@@ -6975,6 +6984,9 @@ const docTemplate = `{
"max_context_load_time": { "max_context_load_time": {
"type": "integer" "type": "integer"
}, },
"max_context_tokens": {
"type": "integer"
},
"memory_model_id": { "memory_model_id": {
"type": "string" "type": "string"
}, },
+12
View File
@@ -6442,6 +6442,12 @@
}, },
"source_reply_to_message_id": { "source_reply_to_message_id": {
"type": "string" "type": "string"
},
"usage": {
"type": "array",
"items": {
"type": "integer"
}
} }
} }
}, },
@@ -6940,6 +6946,9 @@
"max_context_load_time": { "max_context_load_time": {
"type": "integer" "type": "integer"
}, },
"max_context_tokens": {
"type": "integer"
},
"memory_model_id": { "memory_model_id": {
"type": "string" "type": "string"
}, },
@@ -6966,6 +6975,9 @@
"max_context_load_time": { "max_context_load_time": {
"type": "integer" "type": "integer"
}, },
"max_context_tokens": {
"type": "integer"
},
"memory_model_id": { "memory_model_id": {
"type": "string" "type": "string"
}, },
+8
View File
@@ -1101,6 +1101,10 @@ definitions:
type: string type: string
source_reply_to_message_id: source_reply_to_message_id:
type: string type: string
usage:
items:
type: integer
type: array
type: object type: object
message.MessageAsset: message.MessageAsset:
properties: properties:
@@ -1435,6 +1439,8 @@ definitions:
type: string type: string
max_context_load_time: max_context_load_time:
type: integer type: integer
max_context_tokens:
type: integer
memory_model_id: memory_model_id:
type: string type: string
search_provider_id: search_provider_id:
@@ -1452,6 +1458,8 @@ definitions:
type: string type: string
max_context_load_time: max_context_load_time:
type: integer type: integer
max_context_tokens:
type: integer
memory_model_id: memory_model_id:
type: string type: string
search_provider_id: search_provider_id: