fix context compressor summary prompt (#1041)

This commit is contained in:
ekko
2026-05-26 17:29:19 +08:00
committed by GitHub
parent ad1cab277a
commit b0000b4c38
3 changed files with 57 additions and 7 deletions
@@ -15,6 +15,8 @@
import { encodingForModel, getEncoding } from 'js-tiktoken'
import { randomUUID } from 'crypto'
import { mkdir, writeFile } from 'fs/promises'
import { resolve } from 'path'
import { logger } from '../../services/logger'
import { AgentBridgeClient, type AgentBridgeRunResult } from '../../services/hermes/agent-bridge'
import {
@@ -82,6 +84,25 @@ export interface SummarizerOptions {
workerKey?: string
}
const SUMMARIZER_TRIGGER_MESSAGE = 'Generate the context checkpoint summary now.'
const SUMMARIZER_DEBUG_DIR = 'logs/context-compressor'
const SUMMARIZER_DEBUG_FILE = 'summarizer-debug.json'
async function writeSummarizerDebugDump(payload: Record<string, unknown>): Promise<void> {
if (process.env.NODE_ENV !== 'development') return
try {
const debugDir = resolve(process.cwd(), SUMMARIZER_DEBUG_DIR)
await mkdir(debugDir, { recursive: true })
await writeFile(
resolve(debugDir, SUMMARIZER_DEBUG_FILE),
`${JSON.stringify(payload, null, 2)}\n`,
'utf8',
)
} catch (err) {
logger.warn(err, '[context-compressor] failed to write summarizer debug dump')
}
}
// ─── Token counting ─────────────────────────────────────
let _encoder: ReturnType<typeof getEncoding> | null = null
@@ -444,24 +465,40 @@ export async function callSummarizer(
? { profile: summarizer }
: summarizer || {}
const profile = options.profile || 'default'
const convHistory: Array<{ role: string; content: string }> = [...history]
void history
const convHistory: Array<{ role: string; content: string }> = []
if (previousSummary) {
convHistory.unshift(
{ role: 'user', content: `[Previous summary]\n${previousSummary}` },
{ role: 'assistant', content: 'Understood, I will update the summary.' },
{ role: 'user', content: prompt },
)
} else {
convHistory.unshift({ role: 'user', content: prompt })
}
const bridge = new AgentBridgeClient({ timeoutMs: timeoutMs + 15_000 })
const sessionId = `compress_${Date.now().toString(36)}_${randomUUID().replace(/-/g, '').slice(0, 12)}`
const workerKey = options.workerKey || `${profile}:compression:${sessionId}`
const message = SUMMARIZER_TRIGGER_MESSAGE
await writeSummarizerDebugDump({
writtenAt: new Date().toISOString(),
sessionId,
workerKey,
profile,
model: options.model || null,
provider: options.provider || null,
message,
convHistory,
})
try {
const result = await bridge.request<AgentBridgeRunResult>({
action: 'chat',
session_id: sessionId,
message: prompt,
message,
conversation_history: convHistory,
profile,
worker_key: workerKey,
@@ -622,10 +659,9 @@ export class ChatContextCompressor {
try {
const contentToSummarize = serializeForSummary(toCompress)
const prompt = buildIncrementalPrompt(previousSummary, contentToSummarize, this.config.summaryBudget)
const history = buildConversationHistory(toCompress)
const t0 = Date.now()
summary = await callSummarizer(upstream, apiKey, prompt, history, this.config.summarizationTimeoutMs, previousSummary, summarizer)
summary = await callSummarizer(upstream, apiKey, prompt, [], this.config.summarizationTimeoutMs, previousSummary, summarizer)
logger.info('[context-compressor] incremental-llm done in %dms, %d chars', Date.now() - t0, summary.length)
} catch (err: any) {
logger.warn('[context-compressor] incremental-llm failed: %s — keeping new messages verbatim', err.message)
@@ -701,12 +737,11 @@ export class ChatContextCompressor {
const contentToSummarize = serializeForSummary(toCompress)
const prompt = buildFullPrompt(contentToSummarize, this.config.summaryBudget)
const history = buildConversationHistory(toCompress)
let summary: string | null = null
try {
const t0 = Date.now()
summary = await callSummarizer(upstream, apiKey, prompt, history, this.config.summarizationTimeoutMs, undefined, summarizer)
summary = await callSummarizer(upstream, apiKey, prompt, [], this.config.summarizationTimeoutMs, undefined, summarizer)
logger.info('[context-compressor] full-llm done in %dms, %d chars', Date.now() - t0, summary.length)
} catch (err: any) {
logger.warn('[context-compressor] full-llm failed: %s', err.message)
@@ -333,7 +333,6 @@ export async function buildCompressedHistory(
history = await compressHistory(history, null, sessionId, upstream, apiKey, cState, totalTokens, emit, sessionMap, modelContext, compressionConfig.compressor, currentRunInputTokens)
}
}
return history
} catch (err) {
if (isContextWindowTooSmallError(err)) throw err
+16
View File
@@ -178,8 +178,14 @@ describe('ChatContextCompressor', () => {
action: 'chat',
profile: 'default',
worker_key: 'default:compression:s1',
message: 'Generate the context checkpoint summary now.',
wait: true,
}), expect.any(Object))
const request = bridgeRequestMock.mock.calls[0][0]
expect(request.conversation_history[0]).toEqual(expect.objectContaining({
role: 'user',
content: expect.stringContaining('TURNS TO SUMMARIZE:'),
}))
const compressSessionId = bridgeRequestMock.mock.calls[0][0].session_id
expect(String(compressSessionId)).toMatch(/^compress_/)
expect(bridgeDestroyMock).toHaveBeenCalledWith(
@@ -471,6 +477,16 @@ describe('ChatContextCompressor', () => {
const result = await compressor.compress(messages, 'http://upstream', undefined, 's1')
expect(bridgeRequestMock).toHaveBeenCalledTimes(1)
const request = bridgeRequestMock.mock.calls[0][0]
expect(request.message).toBe('Generate the context checkpoint summary now.')
expect(request.conversation_history.slice(0, 3)).toEqual([
{ role: 'user', content: '[Previous summary]\nprevious summary' },
{ role: 'assistant', content: 'Understood, I will update the summary.' },
expect.objectContaining({
role: 'user',
content: expect.stringContaining('NEW TURNS TO INCORPORATE:'),
}),
])
expect(result.messages.map(m => m.content)).toEqual([
'head 0',
'head 1',