mirror of
https://github.com/memohai/Memoh.git
synced 2026-04-27 07:16:19 +09:00
feat: add context compaction to automatically summarize old messages (#compaction) (#276)
When input tokens exceed a configurable threshold after a conversation round, the system asynchronously compacts older messages into a summary. Cascading compactions reference prior summaries via <prior_context> tags to maintain conversational continuity without duplicating content. - Add bot_history_message_compacts table and compact_id on messages - Add compaction_enabled, compaction_threshold, compaction_model_id to bots - Implement compaction service (internal/compaction) with LLM summarization - Integrate into conversation flow: replace compacted messages with summaries wrapped in <summary> tags during context loading - Add REST API endpoints (GET/DELETE /bots/:bot_id/compaction/logs) - Add frontend Compaction tab with settings and log viewer - Wire compaction service into both dev (cmd/agent) and prod (cmd/memoh) entry points - Update test mocks to include new GetBotByID columns
This commit is contained in:
@@ -147,6 +147,7 @@ SELECT
|
||||
m.content,
|
||||
m.metadata,
|
||||
m.usage,
|
||||
m.compact_id,
|
||||
m.created_at,
|
||||
ci.display_name AS sender_display_name,
|
||||
ci.avatar_url AS sender_avatar_url,
|
||||
@@ -177,6 +178,7 @@ type ListActiveMessagesSinceRow struct {
|
||||
Content []byte `json:"content"`
|
||||
Metadata []byte `json:"metadata"`
|
||||
Usage []byte `json:"usage"`
|
||||
CompactID pgtype.UUID `json:"compact_id"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
SenderDisplayName pgtype.Text `json:"sender_display_name"`
|
||||
SenderAvatarUrl pgtype.Text `json:"sender_avatar_url"`
|
||||
@@ -204,6 +206,7 @@ func (q *Queries) ListActiveMessagesSince(ctx context.Context, arg ListActiveMes
|
||||
&i.Content,
|
||||
&i.Metadata,
|
||||
&i.Usage,
|
||||
&i.CompactID,
|
||||
&i.CreatedAt,
|
||||
&i.SenderDisplayName,
|
||||
&i.SenderAvatarUrl,
|
||||
@@ -232,6 +235,7 @@ SELECT
|
||||
m.content,
|
||||
m.metadata,
|
||||
m.usage,
|
||||
m.compact_id,
|
||||
m.created_at,
|
||||
ci.display_name AS sender_display_name,
|
||||
ci.avatar_url AS sender_avatar_url,
|
||||
@@ -262,6 +266,7 @@ type ListActiveMessagesSinceBySessionRow struct {
|
||||
Content []byte `json:"content"`
|
||||
Metadata []byte `json:"metadata"`
|
||||
Usage []byte `json:"usage"`
|
||||
CompactID pgtype.UUID `json:"compact_id"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
SenderDisplayName pgtype.Text `json:"sender_display_name"`
|
||||
SenderAvatarUrl pgtype.Text `json:"sender_avatar_url"`
|
||||
@@ -289,6 +294,7 @@ func (q *Queries) ListActiveMessagesSinceBySession(ctx context.Context, arg List
|
||||
&i.Content,
|
||||
&i.Metadata,
|
||||
&i.Usage,
|
||||
&i.CompactID,
|
||||
&i.CreatedAt,
|
||||
&i.SenderDisplayName,
|
||||
&i.SenderAvatarUrl,
|
||||
@@ -1050,6 +1056,64 @@ func (q *Queries) ListObservedConversationsByChannelIdentity(ctx context.Context
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const listUncompactedMessagesBySession = `-- name: ListUncompactedMessagesBySession :many
|
||||
SELECT id, role, content, usage, created_at
|
||||
FROM bot_history_messages
|
||||
WHERE session_id = $1
|
||||
AND compact_id IS NULL
|
||||
ORDER BY created_at ASC
|
||||
`
|
||||
|
||||
type ListUncompactedMessagesBySessionRow struct {
|
||||
ID pgtype.UUID `json:"id"`
|
||||
Role string `json:"role"`
|
||||
Content []byte `json:"content"`
|
||||
Usage []byte `json:"usage"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
}
|
||||
|
||||
func (q *Queries) ListUncompactedMessagesBySession(ctx context.Context, sessionID pgtype.UUID) ([]ListUncompactedMessagesBySessionRow, error) {
|
||||
rows, err := q.db.Query(ctx, listUncompactedMessagesBySession, sessionID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []ListUncompactedMessagesBySessionRow
|
||||
for rows.Next() {
|
||||
var i ListUncompactedMessagesBySessionRow
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.Role,
|
||||
&i.Content,
|
||||
&i.Usage,
|
||||
&i.CreatedAt,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const markMessagesCompacted = `-- name: MarkMessagesCompacted :exec
|
||||
UPDATE bot_history_messages
|
||||
SET compact_id = $1
|
||||
WHERE id = ANY($2::uuid[])
|
||||
`
|
||||
|
||||
type MarkMessagesCompactedParams struct {
|
||||
CompactID pgtype.UUID `json:"compact_id"`
|
||||
MessageIds []pgtype.UUID `json:"message_ids"`
|
||||
}
|
||||
|
||||
func (q *Queries) MarkMessagesCompacted(ctx context.Context, arg MarkMessagesCompactedParams) error {
|
||||
_, err := q.db.Exec(ctx, markMessagesCompacted, arg.CompactID, arg.MessageIds)
|
||||
return err
|
||||
}
|
||||
|
||||
const searchMessages = `-- name: SearchMessages :many
|
||||
SELECT
|
||||
m.id,
|
||||
|
||||
Reference in New Issue
Block a user