Skip to content

Commit 2eeb557

Browse files
authored
feat(adk): tool reduction middleware (#746)
* feat(adk): tool reduction middleware * refactor(adk): move ancient reduction middleware to internal package * chore(adk): reduction mw add i18n, rename TokenCounter * chore(adk): add reduction mw comments * chore(adk): refactor reduction mw config field, tool stream copy
1 parent 6eab11a commit 2eeb557

File tree

9 files changed

+1045
-18
lines changed

9 files changed

+1045
-18
lines changed
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* Copyright 2026 CloudWeGo Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
// Package reduction provides middlewares to trim context and clear tool results.
18+
package reduction
19+
20+
import "github.com/cloudwego/eino/adk/internal"
21+
22+
func getLineTruncFmt() string {
23+
s, _ := internal.SelectPrompt(internal.I18nPrompts{
24+
English: lineTruncFmt,
25+
Chinese: lineTruncFmtZh,
26+
})
27+
if s == "" {
28+
return lineTruncFmt
29+
}
30+
return s
31+
}
32+
33+
func getContentTruncFmt() string {
34+
s, _ := internal.SelectPrompt(internal.I18nPrompts{
35+
English: contentTruncFmt,
36+
Chinese: contentTruncFmtZh,
37+
})
38+
if s == "" {
39+
return contentTruncFmt
40+
}
41+
return s
42+
}
43+
44+
func getToolOffloadResultFmt() string {
45+
s, _ := internal.SelectPrompt(internal.I18nPrompts{
46+
English: toolOffloadResultFmt,
47+
Chinese: toolOffloadResultFmtZh,
48+
})
49+
if s == "" {
50+
return toolOffloadResultFmt
51+
}
52+
return s
53+
}
54+
55+
const (
56+
lineTruncFmt = `... (line truncated due to length limitation, %d chars total)`
57+
lineTruncFmtZh = `...(由于长度限制截断本行, 总计 %d 字符)`
58+
59+
contentTruncFmt = `... (content truncated due to length limitation, %d chars total)`
60+
contentTruncFmtZh = `...(由于长度限制截断末尾, 总计 %d 字符)`
61+
)
62+
63+
const (
64+
toolOffloadResultFmt = `Tool result is too large, retrieve from %s if needed`
65+
toolOffloadResultFmtZh = `工具输出结果过长, 需要时从 %s 中导入`
66+
)
67+
68+
const (
69+
msgReducedFlag = "_reduction_mw_processed"
70+
msgReducedTokens = "_reduction_mw_tokens"
71+
)

adk/middlewares/reduction/clear_tool_result.go renamed to adk/middlewares/reduction/internal/clear_tool_result.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@
1414
* limitations under the License.
1515
*/
1616

17-
// Package reduction provides middlewares to trim context and clear tool results.
18-
package reduction
17+
// Package internal provides middlewares to trim context and clear tool results.
18+
package internal
1919

2020
import (
2121
"context"

adk/middlewares/reduction/clear_tool_result_test.go renamed to adk/middlewares/reduction/internal/clear_tool_result_test.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17-
package reduction
17+
package internal
1818

1919
import (
2020
"context"
@@ -308,7 +308,7 @@ func TestNewChatModelAgentMiddleware(t *testing.T) {
308308

309309
t.Run("valid config with default settings", func(t *testing.T) {
310310
backend := &clearToolResultMockBackend{}
311-
m, err := NewChatModelAgentMiddleware(ctx, &ToolResultConfig{
311+
m, err := NewToolResultHandler(ctx, &ToolResultConfig{
312312
Backend: backend,
313313
})
314314
assert.NoError(t, err)
@@ -327,7 +327,7 @@ func TestNewChatModelAgentMiddleware(t *testing.T) {
327327

328328
t.Run("custom config values", func(t *testing.T) {
329329
backend := &clearToolResultMockBackend{}
330-
m, err := NewChatModelAgentMiddleware(ctx, &ToolResultConfig{
330+
m, err := NewToolResultHandler(ctx, &ToolResultConfig{
331331
Backend: backend,
332332
ClearingTokenThreshold: 5000,
333333
KeepRecentTokens: 10000,
@@ -352,7 +352,7 @@ func TestToolResultMiddleware_BeforeModelRewriteState(t *testing.T) {
352352
backend := &clearToolResultMockBackend{}
353353

354354
t.Run("clears old tool results when threshold exceeded", func(t *testing.T) {
355-
m, err := NewChatModelAgentMiddleware(ctx, &ToolResultConfig{
355+
m, err := NewToolResultHandler(ctx, &ToolResultConfig{
356356
Backend: backend,
357357
ClearingTokenThreshold: 20,
358358
KeepRecentTokens: 10,
@@ -380,7 +380,7 @@ func TestToolResultMiddleware_BeforeModelRewriteState(t *testing.T) {
380380
})
381381

382382
t.Run("no clearing when under threshold", func(t *testing.T) {
383-
m, err := NewChatModelAgentMiddleware(ctx, &ToolResultConfig{
383+
m, err := NewToolResultHandler(ctx, &ToolResultConfig{
384384
Backend: backend,
385385
ClearingTokenThreshold: 100000,
386386
KeepRecentTokens: 100000,
@@ -406,7 +406,7 @@ func TestToolResultMiddleware_WrapInvokableToolCall(t *testing.T) {
406406
backend := &clearToolResultMockBackend{}
407407

408408
t.Run("small result passes through unchanged", func(t *testing.T) {
409-
m, err := NewChatModelAgentMiddleware(ctx, &ToolResultConfig{
409+
m, err := NewToolResultHandler(ctx, &ToolResultConfig{
410410
Backend: backend,
411411
})
412412
assert.NoError(t, err)
@@ -425,7 +425,7 @@ func TestToolResultMiddleware_WrapInvokableToolCall(t *testing.T) {
425425
})
426426

427427
t.Run("large result is offloaded", func(t *testing.T) {
428-
m, err := NewChatModelAgentMiddleware(ctx, &ToolResultConfig{
428+
m, err := NewToolResultHandler(ctx, &ToolResultConfig{
429429
Backend: backend,
430430
OffloadingTokenLimit: 5,
431431
})
@@ -452,7 +452,7 @@ func TestToolResultMiddleware_WrapStreamableToolCall(t *testing.T) {
452452
backend := &clearToolResultMockBackend{}
453453

454454
t.Run("small result passes through unchanged", func(t *testing.T) {
455-
m, err := NewChatModelAgentMiddleware(ctx, &ToolResultConfig{
455+
m, err := NewToolResultHandler(ctx, &ToolResultConfig{
456456
Backend: backend,
457457
})
458458
assert.NoError(t, err)
@@ -480,7 +480,7 @@ func TestToolResultMiddleware_WrapStreamableToolCall(t *testing.T) {
480480
})
481481

482482
t.Run("large result is offloaded", func(t *testing.T) {
483-
m, err := NewChatModelAgentMiddleware(ctx, &ToolResultConfig{
483+
m, err := NewToolResultHandler(ctx, &ToolResultConfig{
484484
Backend: backend,
485485
OffloadingTokenLimit: 5,
486486
})

adk/middlewares/reduction/large_tool_result.go renamed to adk/middlewares/reduction/internal/large_tool_result.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17-
package reduction
17+
package internal
1818

1919
import (
2020
"bufio"

adk/middlewares/reduction/large_tool_result_test.go renamed to adk/middlewares/reduction/internal/large_tool_result_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17-
package reduction
17+
package internal
1818

1919
import (
2020
"context"

adk/middlewares/reduction/tool_result.go renamed to adk/middlewares/reduction/internal/tool_result.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17-
package reduction
17+
package internal
1818

1919
import (
2020
"context"
@@ -103,7 +103,7 @@ type ToolResultConfig struct {
103103
// which provides the read_file tool automatically, OR
104104
// - Implement your own read_file tool that reads from the same Backend
105105
//
106-
// Deprecated: Use NewChatModelAgentMiddleware instead. NewChatModelAgentMiddleware returns
106+
// Deprecated: Use NewToolResultHandler instead. NewToolResultHandler returns
107107
// a ChatModelAgentMiddleware which provides better context propagation through wrapper methods
108108
// and is the recommended approach for new code. See ChatModelAgentMiddleware documentation
109109
// for details on the benefits over AgentMiddleware.
@@ -127,7 +127,7 @@ func NewToolResultMiddleware(ctx context.Context, cfg *ToolResultConfig) (adk.Ag
127127
}, nil
128128
}
129129

130-
// NewChatModelAgentMiddleware creates a tool result reduction middleware as a ChatModelAgentMiddleware.
130+
// NewToolResultHandler creates a tool result reduction middleware as a ChatModelAgentMiddleware.
131131
//
132132
// This is the recommended constructor for new code. It returns a ChatModelAgentMiddleware which provides:
133133
// - Better context propagation through WrapInvokableToolCall and WrapStreamableToolCall methods
@@ -155,14 +155,14 @@ func NewToolResultMiddleware(ctx context.Context, cfg *ToolResultConfig) (adk.Ag
155155
//
156156
// Example usage:
157157
//
158-
// middleware, err := reduction.NewChatModelAgentMiddleware(ctx, &reduction.ToolResultConfig{
158+
// middleware, err := NewToolResultHandler(ctx, &ToolResultConfig{
159159
// Backend: myBackend,
160160
// })
161161
// agent, err := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
162162
// // ...
163163
// Handlers: []adk.ChatModelAgentMiddleware{middleware},
164164
// })
165-
func NewChatModelAgentMiddleware(ctx context.Context, cfg *ToolResultConfig) (adk.ChatModelAgentMiddleware, error) {
165+
func NewToolResultHandler(ctx context.Context, cfg *ToolResultConfig) (adk.ChatModelAgentMiddleware, error) {
166166
m := &toolResultMiddleware{
167167
clearConfig: &ClearToolResultConfig{
168168
ToolResultTokenThreshold: cfg.ClearingTokenThreshold,
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright 2026 CloudWeGo Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package reduction
18+
19+
import "github.com/cloudwego/eino/adk/middlewares/reduction/internal"
20+
21+
// Package reduction provides historical compatibility exports for reduction middleware APIs.
22+
//
23+
// DEPRECATED: All top-level exports in this file are maintained exclusively for backward compatibility.
24+
// New reduction middleware implementations are now developed and maintained in this package.
25+
// It is STRONGLY RECOMMENDED that new code directly use the NewToolReductionMiddleware instead.
26+
//
27+
// Existing code relying on these exports will continue to work indefinitely,
28+
// but no new features or bug fixes will be backported to this compatibility layer.
29+
30+
type (
31+
ClearToolResultConfig = internal.ClearToolResultConfig
32+
ToolResultConfig = internal.ToolResultConfig
33+
Backend = internal.Backend
34+
)
35+
36+
var (
37+
NewClearToolResult = internal.NewClearToolResult
38+
NewToolResultMiddleware = internal.NewToolResultMiddleware
39+
NewToolResultHandler = internal.NewToolResultHandler
40+
)

0 commit comments

Comments
 (0)