mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-09-14 04:01:35 +00:00
refactor(session): represent the system prompt as surface node zero
Consolidate the representation-change PR and its rebase reconciliations into one baseline. Preserve the exact tree and keep the in-history feature in the dependent PR. Follow-up fixes remain separate.
This commit is contained in:
@@ -13,7 +13,7 @@ import {
|
||||
resolveTargetPolicy,
|
||||
} from '@deepseek-ai/dsh-compaction-basic/src/config.ts'
|
||||
import type { CompactionResult } from '@deepseek-ai/dsh-compaction'
|
||||
import LlmRuntime, { createUserMessage, ToolCallId, CONTEXT_WINDOW_EXCEEDED_CODE, createToolResultMessage, LlmAdapter , createMessage } from '@deepseek-ai/dsh-llm'
|
||||
import LlmRuntime, { createUserMessage, ToolCallId, CONTEXT_WINDOW_EXCEEDED_CODE, createSystemMessage, createToolResultMessage, LlmAdapter , createMessage } from '@deepseek-ai/dsh-llm'
|
||||
import type {
|
||||
ContentBlock,
|
||||
GenerateOptions,
|
||||
@@ -107,11 +107,23 @@ function promptInput(text: string): SummarizationInput {
|
||||
})] }
|
||||
}
|
||||
|
||||
/** Closed two-message turns followed by one open turn for durable compaction events. */
|
||||
function conversation(turns = 4, text = 'fixture '.repeat(40).trim()): Session {
|
||||
const SYSTEM_PROMPT_PLUGIN = '@deepseek-ai/dsh-system-prompt'
|
||||
|
||||
/**
|
||||
* Closed two-message turns followed by one open turn for durable compaction events.
|
||||
* A `system` text puts a `system/message` at surface node 0 before turn 1's user message.
|
||||
*/
|
||||
function conversation(turns = 4, text = 'fixture '.repeat(40).trim(), system?: string): Session {
|
||||
const session = Session.create(SessionId(`conversation-${turns}`))
|
||||
for (let turn = 1; turn <= turns; turn += 1) {
|
||||
session.append('turn/start', { turn })
|
||||
if (turn === 1 && system !== undefined) {
|
||||
session.append('system/message', {
|
||||
turn,
|
||||
step: 1,
|
||||
message: createSystemMessage(system, SYSTEM_PROMPT_PLUGIN),
|
||||
}, { surfaceOp: 'append' })
|
||||
}
|
||||
session.append('user/message', createUserMessage({
|
||||
content: [{ type: 'text', text: `${text} user ${turn}` }],
|
||||
source: { kind: 'user' },
|
||||
@@ -621,12 +633,46 @@ describe('pressure measurement and retention', () => {
|
||||
expect(await compactIfNeeded(compact, conversation(2))).toBeNull()
|
||||
|
||||
const session = conversation(4)
|
||||
const head = session.surface.nodes[0]!
|
||||
const result = await compactIfNeeded(compact, session)
|
||||
expect(result).not.toBeNull()
|
||||
expect(result?.shadowedSeqs[0]).toBe(head)
|
||||
expect(result?.shadowedSeqs.length).toBeGreaterThan(2)
|
||||
expect(session.surface.nodes.length).toBeLessThan(8)
|
||||
})
|
||||
|
||||
it('starts the range after a system head and keeps that node at surface position 0', async () => {
|
||||
const ctx = createContext()
|
||||
const compact = service(compactConfig, ctx)
|
||||
const session = conversation(4, undefined, 'SYSTEM HEAD')
|
||||
const [head, firstUser] = session.surface.nodes
|
||||
expect(session.eventAt(head!)?.type).toBe('system/message')
|
||||
|
||||
const range = selectCompactableRange(session, ctx.tokenMeter.measure(session), compactConfig.retainTokens!)
|
||||
expect(range?.start).toBe(firstUser)
|
||||
|
||||
const result = await compactIfNeeded(compact, session)
|
||||
expect(result).not.toBeNull()
|
||||
expect(result?.shadowedSeqs).not.toContain(head)
|
||||
expect(result?.shadowedSeqs[0]).toBe(firstUser)
|
||||
expect(session.surface.nodes[0]).toBe(head)
|
||||
expect(session.deriveMessages()[0]).toMatchObject({
|
||||
role: 'system',
|
||||
content: [{ type: 'text', text: 'SYSTEM HEAD' }],
|
||||
})
|
||||
expect(compact.calls[0]!.input.system).toBe('SYSTEM HEAD')
|
||||
expect(summarizedText(compact.calls[0]!.input)).not.toContain('SYSTEM HEAD')
|
||||
})
|
||||
|
||||
it('declines when only the system head precedes the retained tail', () => {
|
||||
const ctx = createContext()
|
||||
const session = conversation(1, undefined, 'SYSTEM HEAD')
|
||||
const priced = ctx.tokenMeter.measure(session)
|
||||
const [, user, assistant] = priced.nodes
|
||||
expect(priced.nodes).toHaveLength(3)
|
||||
expect(selectCompactableRange(session, priced, user!.tokens + assistant!.tokens)).toBeNull()
|
||||
})
|
||||
|
||||
it('counts the durable routed request envelope without putting it on the surface', async () => {
|
||||
const compact = service({
|
||||
auto: false,
|
||||
@@ -639,7 +685,7 @@ describe('pressure measurement and retention', () => {
|
||||
session.append('request/header', {
|
||||
header: {
|
||||
config: { provider: MODEL, model: MODEL },
|
||||
system: 's'.repeat(2_000),
|
||||
tools: [{ name: 'bulk', description: 's'.repeat(2_000), parameters: { type: 'object' } }],
|
||||
},
|
||||
reason: 'resume',
|
||||
})
|
||||
@@ -669,17 +715,18 @@ describe('pressure measurement and retention', () => {
|
||||
|
||||
it('declines when envelope pressure is high but the surface has no compactable range', async () => {
|
||||
const compact = service(compactConfig)
|
||||
const bulkTools = [{ name: 'bulk', description: 'x'.repeat(100_000), parameters: { type: 'object' } }]
|
||||
const empty = Session.create(SessionId('empty'))
|
||||
empty.append('turn/start', { turn: 1 })
|
||||
empty.append('request/header', {
|
||||
header: { config: { provider: MODEL, model: MODEL }, system: 'x'.repeat(100_000) },
|
||||
header: { config: { provider: MODEL, model: MODEL }, tools: bulkTools },
|
||||
reason: 'initial',
|
||||
})
|
||||
expect(await compactIfNeeded(compact, empty)).toBeNull()
|
||||
|
||||
const retained = conversation(1)
|
||||
retained.append('request/header', {
|
||||
header: { config: { provider: MODEL, model: MODEL }, system: 'x'.repeat(100_000) },
|
||||
header: { config: { provider: MODEL, model: MODEL }, tools: bulkTools },
|
||||
reason: 'resume',
|
||||
})
|
||||
expect(await compactIfNeeded(compact, retained)).toBeNull()
|
||||
@@ -891,21 +938,49 @@ describe('compaction region transaction', () => {
|
||||
expect(replay.deriveMessages()).toEqual(session.deriveMessages())
|
||||
})
|
||||
|
||||
it('replays the latest routed header so the summarizer reuses the cache', async () => {
|
||||
it('replays the system head and latest routed tools so the summarizer reuses the cache', async () => {
|
||||
const compact = service()
|
||||
const session = conversation(3)
|
||||
const session = conversation(3, undefined, 'CONVERSATION SYSTEM')
|
||||
const tools = [{ name: 'do_thing', description: 'd', parameters: { type: 'object' } }]
|
||||
session.append('request/header', {
|
||||
header: { config: { provider: MODEL, model: MODEL }, system: 'CONVERSATION SYSTEM', tools },
|
||||
header: { config: { provider: MODEL, model: MODEL }, tools },
|
||||
reason: 'resume',
|
||||
})
|
||||
const nodes = session.surface.nodes
|
||||
await compact.compactRegion(nodes[0]!, nodes[1]!, agent(session, MODEL), SIGNAL)
|
||||
await compact.compactRegion(nodes[1]!, nodes[2]!, agent(session, MODEL), SIGNAL)
|
||||
|
||||
const { input } = compact.calls[0]!
|
||||
expect(input.system).toBe('CONVERSATION SYSTEM')
|
||||
expect(input.tools).toEqual(tools)
|
||||
expect(summarizedText(input)).toContain('fixture user 1')
|
||||
expect(summarizedText(input)).not.toContain('CONVERSATION SYSTEM')
|
||||
})
|
||||
|
||||
it('omits the summarizer system prompt for an empty system head or a system-less surface', async () => {
|
||||
const compact = service()
|
||||
const emptyHead = conversation(3, undefined, '')
|
||||
const emptyNodes = emptyHead.surface.nodes
|
||||
expect(emptyHead.eventAt(emptyNodes[0]!)?.type).toBe('system/message')
|
||||
await compact.compactRegion(emptyNodes[1]!, emptyNodes[2]!, agent(emptyHead, MODEL), SIGNAL)
|
||||
expect(compact.calls[0]!.input).not.toHaveProperty('system')
|
||||
|
||||
const headless = conversation(3)
|
||||
const nodes = headless.surface.nodes
|
||||
await compact.compactRegion(nodes[0]!, nodes[1]!, agent(headless, MODEL), SIGNAL)
|
||||
expect(compact.calls[1]!.input).not.toHaveProperty('system')
|
||||
expect(compact.calls[1]!.input.messages[0]).toMatchObject({ role: 'user' })
|
||||
})
|
||||
|
||||
it('surfaces the session rejection of a range that covers the system head', async () => {
|
||||
const compact = service()
|
||||
const session = conversation(3, undefined, 'PROTECTED SYSTEM')
|
||||
const nodes = [...session.surface.nodes]
|
||||
await expect(compact.compactRegion(nodes[0]!, nodes[3]!, agent(session, MODEL), SIGNAL))
|
||||
.rejects.toThrow(/node 0 holds the system prompt/)
|
||||
|
||||
expect(session.surface.nodes).toEqual(nodes)
|
||||
const end = session.snapshotEvents().findLast(event => event.type === 'compaction/end')
|
||||
expect(end?.type === 'compaction/end' && end.data.error).toBeDefined()
|
||||
})
|
||||
|
||||
it.each([
|
||||
|
||||
Reference in New Issue
Block a user