fix(compaction): adjust instructions and structure to be more clear to smaller models like dsv4 flash (#42045)
Co-authored-by: akenra <37288280+akenra@users.noreply.github.com>
This commit is contained in:
@@ -4,12 +4,32 @@ import { SessionCompaction } from "@opencode-ai/core/session/compaction"
|
||||
test("compaction prompt preserves detailed work state and relevant files", () => {
|
||||
const prompt = SessionCompaction.buildPrompt({ context: ["conversation history"] })
|
||||
|
||||
expect(prompt).toStartWith(
|
||||
"Here is the conversation so far:\n\n<conversation>\nconversation history\n</conversation>",
|
||||
)
|
||||
expect(prompt.indexOf("</conversation>")).toBeLessThan(prompt.indexOf("Create a new anchored summary"))
|
||||
expect(prompt).toContain("conversation history in the <conversation> tags above")
|
||||
expect(prompt).toContain("## Work State\n### Completed")
|
||||
expect(prompt).toContain("### Active")
|
||||
expect(prompt).toContain("### Blocked")
|
||||
expect(prompt).toContain("## Relevant Files")
|
||||
})
|
||||
|
||||
test("compaction prompt gives update instructions for a prior summary", () => {
|
||||
const prompt = SessionCompaction.buildPrompt({
|
||||
context: ["new conversation"],
|
||||
previousSummary: "existing summary",
|
||||
})
|
||||
|
||||
expect(prompt.indexOf("<conversation>")).toBeLessThan(prompt.indexOf("<prior-summary>"))
|
||||
expect(prompt.indexOf("</prior-summary>")).toBeLessThan(prompt.indexOf("The <prior-summary> summarizes"))
|
||||
expect(prompt).toContain(
|
||||
"Carry forward objectives, constraints, user directives, decisions, and parallel workstreams from the <prior-summary>",
|
||||
)
|
||||
expect(prompt).toContain('Move completed work from "Active" to "Completed".')
|
||||
expect(prompt).toContain('Update "Objective" and "Next Move" to reflect the current work state.')
|
||||
})
|
||||
|
||||
test("compaction describes tool media without embedding base64", () => {
|
||||
const base64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAAB"
|
||||
const serialized = SessionCompaction.serializeToolContent([
|
||||
|
||||
@@ -1135,7 +1135,7 @@ describe("SessionRunnerLLM", () => {
|
||||
|
||||
expect(requests).toHaveLength(2)
|
||||
expect(userTexts(requests[0])[0]).toContain(
|
||||
"<previous-summary>\n## Objective\n- Preserve the task\n</previous-summary>",
|
||||
"<prior-summary>\n## Objective\n- Preserve the task\n</prior-summary>",
|
||||
)
|
||||
expect(userTexts(requests[0])[0]).toContain("Recent exact request")
|
||||
expect((yield* (yield* SessionStore.Service).context(sessionID))[0]).toMatchObject({
|
||||
@@ -1145,6 +1145,67 @@ describe("SessionRunnerLLM", () => {
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("retains only complete serialized messages during compaction", () =>
|
||||
Effect.gen(function* () {
|
||||
yield* setup
|
||||
const session = yield* SessionV2.Service
|
||||
const earlier = `EARLIER_BOUNDARY ${"a".repeat(3_000)} EARLIER_END`
|
||||
const recent = `RECENT_BOUNDARY ${"b".repeat(3_000)} RECENT_END`
|
||||
response = fragmentFixture("text", "text-earlier", ["Earlier answer"]).completeEvents
|
||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: earlier }), resume: false })
|
||||
yield* session.resume(sessionID)
|
||||
|
||||
currentModel = compactModel
|
||||
requests.length = 0
|
||||
responses = [
|
||||
fragmentFixture("text", "text-summary", ["## Objective\n- Preserve the task"]).completeEvents,
|
||||
fragmentFixture("text", "text-final", ["Continued"]).completeEvents,
|
||||
]
|
||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: recent }), resume: false })
|
||||
yield* session.resume(sessionID)
|
||||
|
||||
expect(requests).toHaveLength(2)
|
||||
const summary = userTexts(requests[0])[0]
|
||||
const continuation = userTexts(requests[1])[0]
|
||||
expect(summary.match(/EARLIER_BOUNDARY/g)).toHaveLength(1)
|
||||
expect(summary).toContain(`EARLIER_BOUNDARY ${"a".repeat(3_000)} EARLIER_END`)
|
||||
expect(summary).not.toContain("RECENT_BOUNDARY")
|
||||
expect(continuation).not.toContain("EARLIER_BOUNDARY")
|
||||
expect(continuation).not.toContain("EARLIER_END")
|
||||
expect(continuation).toContain("<recent-context>\n[Assistant]: Earlier answer")
|
||||
expect(continuation).toContain(`RECENT_BOUNDARY ${"b".repeat(3_000)} RECENT_END`)
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("summarizes an oversized newest message without retaining a fragment", () =>
|
||||
Effect.gen(function* () {
|
||||
yield* setup
|
||||
const session = yield* SessionV2.Service
|
||||
response = fragmentFixture("text", "text-earlier", ["Earlier answer"]).completeEvents
|
||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Earlier question" }), resume: false })
|
||||
yield* session.resume(sessionID)
|
||||
|
||||
const oversized = `OVERSIZED_BOUNDARY ${"x".repeat(4_500)} OVERSIZED_END`
|
||||
currentModel = compactModel
|
||||
requests.length = 0
|
||||
responses = [
|
||||
fragmentFixture("text", "text-summary", ["## Objective\n- Preserve the task"]).completeEvents,
|
||||
fragmentFixture("text", "text-final", ["Continued"]).completeEvents,
|
||||
]
|
||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: oversized }), resume: false })
|
||||
yield* session.resume(sessionID)
|
||||
|
||||
expect(requests).toHaveLength(2)
|
||||
const summary = userTexts(requests[0])[0]
|
||||
const continuation = userTexts(requests[1])[0]
|
||||
expect(summary.match(/OVERSIZED_BOUNDARY/g)).toHaveLength(1)
|
||||
expect(summary).toContain(oversized)
|
||||
expect(continuation).not.toContain("OVERSIZED_BOUNDARY")
|
||||
expect(continuation).not.toContain("OVERSIZED_END")
|
||||
expect(continuation).toContain("<recent-context>\n\n</recent-context>")
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("forces one compaction and retries after provider context overflow", () =>
|
||||
Effect.gen(function* () {
|
||||
const session = yield* setupOverflowRecovery
|
||||
|
||||
Reference in New Issue
Block a user