summary |
shortlog |
log |
commit | commitdiff |
tree
raw |
patch |
inline | side by side (from parent 1:
56129a5)
The first try of a compaction sends the opening tag and each whole block
of four view lines as parts of their own, and then one last part with
the rest of the lines, the closing tag, and the task. The note of a
retry is the part after that. Joined, the parts are the same bytes as
before.
A pair check showed that a compaction whose view shares whole blocks
with an earlier one, but has its own rest part, reads nothing from the
cache, and that an exact repeat does. A rule that fits all results so
far: the ChatGPT backend stores a cache entry for a request without its
last part, and a later request reads it only if it begins with that
whole entry. With the end of the view in the last part, the entry of a
compaction ends at its last whole block, and a later compaction with a
longer view begins with it.
Issue:
8c5fd2a
// Compactor builds the nodes of one agent with a model. A compaction is a call
// like a turn, with the same system prompt and tools, so it reads them from the
// prompt cache of the turns. The tools are never called. After them comes one
// Compactor builds the nodes of one agent with a model. A compaction is a call
// like a turn, with the same system prompt and tools, so it reads them from the
// prompt cache of the turns. The tools are never called. After them comes one
-// user message with the compaction view up to the node and the task. The view
-// goes in parts of viewBlock lines, as viewParts gives them, so compactions
-// whose views end at different lines still send the same first parts.
+// user message with the compaction view up to the node and the task, in the
+// parts that compactionParts gives. Only the last part holds the end of the
+// view, so compactions whose views end at different lines begin with the same
+// parts up to their last shared block.
//
// When the line is over nodeBytes, the next try sends the same view and task,
// plus a note with the size of the newest line and its cut at the limit. A try
//
// When the line is over nodeBytes, the next try sends the same view and task,
// plus a note with the size of the newest line and its cut at the limit. A try
// Summarize returns the line of the node of task.
func (c *Compactor) Summarize(ctx context.Context, task Task) (string, error) {
// Summarize returns the line of the node of task.
func (c *Compactor) Summarize(ctx context.Context, task Task) (string, error) {
- view := viewParts(task.Context)
text, err := compactionTask(task)
if err != nil {
return "", err
}
text, err := compactionTask(task)
if err != nil {
return "", err
}
+ first := compactionParts(task.Context, text)
var lines []string
note := ""
for len(lines) < compactionTries {
var lines []string
note := ""
for len(lines) < compactionTries {
- parts := slices.Concat(view, []string{text})
- parts = append(parts, note)
+ parts = append(slices.Clip(first), note)
return shortest(lines), nil
}
return shortest(lines), nil
}
-// viewParts returns the parts of the compaction view context: the opening tag,
-// one part for each whole block of viewBlock lines, and the rest of the lines
-// with the closing tag and a newline. Joined, the parts are context and a
-// newline.
-func viewParts(context string) []string {
+// compactionParts returns the input parts of a first try for the compaction
+// view context and the task text: the opening tag, one part for each whole
+// block of viewBlock lines, and then one part with the rest of the lines, the
+// closing tag, a newline, and the task. Joined, the parts are the view, a
+// newline, and the task.
+func compactionParts(context, task string) []string {
lines := strings.SplitAfter(strings.TrimPrefix(context, "<chat>\n"), "\n")
whole := len(lines) - 1 - (len(lines)-1)%viewBlock
parts := []string{"<chat>\n"}
lines := strings.SplitAfter(strings.TrimPrefix(context, "<chat>\n"), "\n")
whole := len(lines) - 1 - (len(lines)-1)%viewBlock
parts := []string{"<chat>\n"}
parts = append(parts, strings.Join(lines[n:n+viewBlock], ""))
}
parts = append(parts, strings.Join(lines[n:n+viewBlock], ""))
}
- return append(parts, strings.Join(lines[whole:], "")+"\n")
+ return append(parts, strings.Join(lines[whole:], "")+"\n"+task)
}
// compactionTask returns the task of a build, with its input.
}
// compactionTask returns the task of a build, with its input.
require.Equal(t, []string{
"<chat>\n",
"0+4|a\n4+1|b\n5+1|c\n6+1|d\n",
require.Equal(t, []string{
"<chat>\n",
"0+4|a\n4+1|b\n5+1|c\n6+1|d\n",
- "7+1|e\n</chat>\n",
- "Compaction: compress message 7 into one line of at most 512 bytes\n" +
+ "7+1|e\n</chat>\n" +
+ "Compaction: compress message 7 into one line of at most 512 bytes\n" +
"(about 70 words), the length of this ruler:\n" +
strings.Repeat("-", 512) + "\n" +
"<input>\nuser: " + strings.Repeat("x", 600) + "\n</input>",
}, parts)
}
"(about 70 words), the length of this ruler:\n" +
strings.Repeat("-", 512) + "\n" +
"<input>\nuser: " + strings.Repeat("x", 600) + "\n</input>",
}, parts)
}
-func TestViewPartsSendWholeBlocksOfLines(t *testing.T) {
+func TestCompactionPartsEndWithTheRestAndTheTask(t *testing.T) {
testCases := []struct {
name string
context string
want []string
}{
{
testCases := []struct {
name string
context string
want []string
}{
{
- name: "empty view has the tags only",
+ name: "empty view has the opening tag and the last part",
context: "<chat>\n</chat>",
context: "<chat>\n</chat>",
- want: []string{"<chat>\n", "</chat>\n"},
+ want: []string{"<chat>\n", "</chat>\nTASK"},
- name: "lines short of a block go with the closing tag",
+ name: "lines short of a block go into the last part",
context: "<chat>\n0+1|a\n1+1|b\n2+1|c\n</chat>",
context: "<chat>\n0+1|a\n1+1|b\n2+1|c\n</chat>",
- want: []string{"<chat>\n", "0+1|a\n1+1|b\n2+1|c\n</chat>\n"},
+ want: []string{"<chat>\n", "0+1|a\n1+1|b\n2+1|c\n</chat>\nTASK"},
},
{
name: "one whole block",
context: "<chat>\n0+1|a\n1+1|b\n2+1|c\n3+1|d\n</chat>",
},
{
name: "one whole block",
context: "<chat>\n0+1|a\n1+1|b\n2+1|c\n3+1|d\n</chat>",
- want: []string{"<chat>\n", "0+1|a\n1+1|b\n2+1|c\n3+1|d\n", "</chat>\n"},
+ want: []string{"<chat>\n", "0+1|a\n1+1|b\n2+1|c\n3+1|d\n", "</chat>\nTASK"},
},
{
name: "two whole blocks and a rest",
},
{
name: "two whole blocks and a rest",
"<chat>\n",
"0+2|a\n2+1|b\n3+1|c\n4+1|d\n",
"5+1|e\n6+1|f\n7+1|g\n8+1|h\n",
"<chat>\n",
"0+2|a\n2+1|b\n3+1|c\n4+1|d\n",
"5+1|e\n6+1|f\n7+1|g\n8+1|h\n",
+ "9+1|i\n</chat>\nTASK",
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
- parts := viewParts(tc.context)
+ parts := compactionParts(tc.context, "TASK")
require.Equal(t, tc.want, parts)
require.Equal(t, tc.want, parts)
- require.Equal(t, tc.context+"\n", strings.Join(parts, ""))
+ require.Equal(t, tc.context+"\nTASK", strings.Join(parts, ""))
-func TestViewPartsKeepTheBlocksOfAShorterView(t *testing.T) {
- shorter := viewParts("<chat>\n0+1|a\n1+1|b\n2+1|c\n3+1|d\n4+1|e\n</chat>")
- longer := viewParts("<chat>\n0+1|a\n1+1|b\n2+1|c\n3+1|d\n4+1|e\n5+1|f\n6+1|g\n7+1|h\n</chat>")
+func TestCompactionPartsOfALongerViewBeginWithTheBlocksOfAShorterOne(t *testing.T) {
+ shorter := compactionParts("<chat>\n0+1|a\n1+1|b\n2+1|c\n3+1|d\n4+1|e\n</chat>", "task 5")
+ longer := compactionParts("<chat>\n0+1|a\n1+1|b\n2+1|c\n3+1|d\n4+1|e\n5+1|f\n6+1|g\n7+1|h\n</chat>", "task 8")
+ require.Len(t, shorter, 3)
require.Equal(t, shorter[:2], longer[:2])
require.NotEqual(t, shorter[2], longer[2])
}
require.Equal(t, shorter[:2], longer[:2])
require.NotEqual(t, shorter[2], longer[2])
}
}
first := inputParts(t, server.Call(0))
}
first := inputParts(t, server.Call(0))
- require.Len(t, first, 3)
+ require.Len(t, first, 2)
for n, want := range []string{long[0], long[1]} {
call := server.Call(n + 1)
parts := inputParts(t, call)
for n, want := range []string{long[0], long[1]} {
call := server.Call(n + 1)
parts := inputParts(t, call)
- require.Equal(t, first, parts[:3])
- require.Equal(t, []string{tooLong(want)}, parts[3:])
- require.Contains(t, parts[3], "was "+strconv.Itoa(len(want))+" bytes,")
- require.Contains(t, parts[3], "\n"+want[:512]+"| <- LIMIT")
+ require.Equal(t, first, parts[:2])
+ require.Equal(t, []string{tooLong(want)}, parts[2:])
+ require.Contains(t, parts[2], "was "+strconv.Itoa(len(want))+" bytes,")
+ require.Contains(t, parts[2], "\n"+want[:512]+"| <- LIMIT")
note, err := json.Marshal(map[string]string{
"type": "input_text",
note, err := json.Marshal(map[string]string{
"type": "input_text",
})
require.NoError(t, err)
require.LessOrEqual(t, len(call.raw), len(server.Call(0).raw)+len(",")+len(note))
})
require.NoError(t, err)
require.LessOrEqual(t, len(call.raw), len(server.Call(0).raw)+len(",")+len(note))