WeKnora deployment source with the reviewed wiki identity, summary and recovery fixes
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
package compaction
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/Tencent/WeKnora/internal/models/chat"
|
||||
)
|
||||
|
||||
const (
|
||||
// toolResultMaxChars caps a single tool result inside the summarization
|
||||
// request. Tool output is the largest contributor to context size and the
|
||||
// summary needs its gist, not its bytes.
|
||||
toolResultMaxChars = 2000
|
||||
// textMaxChars caps user and assistant prose, which is rarely the problem
|
||||
// but should not be unbounded either.
|
||||
textMaxChars = 4000
|
||||
// toolArgsMaxChars caps rendered tool-call arguments. A write_sandbox_file
|
||||
// call carries an entire file body in its arguments; the summary needs the
|
||||
// path and the fact of the write, never the content.
|
||||
toolArgsMaxChars = 400
|
||||
)
|
||||
|
||||
// serializeConversation renders messages as a transcript rather than passing
|
||||
// them as a conversation. A model handed real messages tries to continue them;
|
||||
// handed a transcript, it summarizes them.
|
||||
func serializeConversation(messages []chat.Message) string {
|
||||
var parts []string
|
||||
for i := range messages {
|
||||
msg := &messages[i]
|
||||
switch msg.Role {
|
||||
case "system":
|
||||
continue
|
||||
case "user":
|
||||
if content := truncate(msg.Content, textMaxChars); content != "" {
|
||||
parts = append(parts, "[User]: "+content)
|
||||
}
|
||||
case "assistant":
|
||||
if msg.ReasoningContent != "" {
|
||||
parts = append(parts,
|
||||
"[Assistant thinking]: "+truncate(msg.ReasoningContent, textMaxChars))
|
||||
}
|
||||
if msg.Content != "" {
|
||||
parts = append(parts, "[Assistant]: "+truncate(msg.Content, textMaxChars))
|
||||
}
|
||||
if calls := serializeToolCalls(msg.ToolCalls); calls != "" {
|
||||
parts = append(parts, "[Assistant tool calls]: "+calls)
|
||||
}
|
||||
case "tool":
|
||||
if content := truncate(msg.Content, toolResultMaxChars); content != "" {
|
||||
parts = append(parts, fmt.Sprintf("[Tool result %s]: %s", msg.Name, content))
|
||||
}
|
||||
}
|
||||
}
|
||||
return strings.Join(parts, "\n\n")
|
||||
}
|
||||
|
||||
func serializeToolCalls(calls []chat.ToolCall) string {
|
||||
if len(calls) == 0 {
|
||||
return ""
|
||||
}
|
||||
rendered := make([]string, 0, len(calls))
|
||||
for _, tc := range calls {
|
||||
rendered = append(rendered,
|
||||
fmt.Sprintf("%s(%s)", tc.Function.Name, renderToolArgs(tc.Function.Arguments)))
|
||||
}
|
||||
return strings.Join(rendered, "; ")
|
||||
}
|
||||
|
||||
// renderToolArgs turns an arguments JSON blob into `key=value` pairs, dropping
|
||||
// oversized values. Keys are sorted so the same call always renders the same
|
||||
// way, which matters when the transcript is compared across compactions.
|
||||
func renderToolArgs(arguments string) string {
|
||||
var parsed map[string]any
|
||||
if err := json.Unmarshal([]byte(arguments), &parsed); err != nil {
|
||||
return truncate(arguments, toolArgsMaxChars)
|
||||
}
|
||||
keys := make([]string, 0, len(parsed))
|
||||
for k := range parsed {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
pairs := make([]string, 0, len(keys))
|
||||
for _, k := range keys {
|
||||
encoded, err := json.Marshal(parsed[k])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
pairs = append(pairs, fmt.Sprintf("%s=%s", k, truncate(string(encoded), toolArgsMaxChars)))
|
||||
}
|
||||
return strings.Join(pairs, ", ")
|
||||
}
|
||||
|
||||
func truncate(s string, maxChars int) string {
|
||||
s = strings.TrimSpace(s)
|
||||
runes := []rune(s)
|
||||
if len(runes) <= maxChars {
|
||||
return s
|
||||
}
|
||||
return fmt.Sprintf("%s\n\n[... %d more characters truncated]",
|
||||
string(runes[:maxChars]), len(runes)-maxChars)
|
||||
}
|
||||
|
||||
// rawArchive is the fallback when the summarizer is unavailable. It is lossy
|
||||
// and unstructured, but it keeps the tool names and paths that the next round
|
||||
// needs in order not to redo finished work.
|
||||
func rawArchive(messages []chat.Message) string {
|
||||
var sb strings.Builder
|
||||
sb.WriteString("Raw conversation archive (LLM summarization unavailable):\n\n")
|
||||
for i := range messages {
|
||||
msg := &messages[i]
|
||||
switch msg.Role {
|
||||
case "user":
|
||||
fmt.Fprintf(&sb, "- User: %s\n", truncate(msg.Content, 500))
|
||||
case "assistant":
|
||||
if calls := serializeToolCalls(msg.ToolCalls); calls != "" {
|
||||
fmt.Fprintf(&sb, "- Assistant [%s]: %s\n", calls, truncate(msg.Content, 500))
|
||||
continue
|
||||
}
|
||||
fmt.Fprintf(&sb, "- Assistant: %s\n", truncate(msg.Content, 500))
|
||||
case "tool":
|
||||
fmt.Fprintf(&sb, "- Tool[%s]: %s\n", msg.Name, truncate(msg.Content, 500))
|
||||
}
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
Reference in New Issue
Block a user