Reduction
adk/middlewares/reduction
π‘ This middleware was introduced in v0.8.0.
Overview
The reduction middleware manages the token count occupied by tool outputs in Agent conversations, operating in two phases:
- Truncation: Triggered immediately when a tool call returns. When a single output exceeds
MaxLengthForTrunc, the full content is stored in the Backend and the message is replaced with a truncated summary. - Clear: Triggered before model calls (
BeforeModelRewriteState). When total tokens exceedMaxTokensForClear, it iterates through historical messages and offloads old tool arguments and results to the Backend.
Architecture
Tool call returns result
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β WrapInvokableToolCall / WrapStreamableToolCall β
β WrapEnhancedInvokableToolCall / WrapEnhancedStreamable β
β β
β Truncation (can be skipped via SkipTruncation) β
β Result length > MaxLengthForTrunc? β
β Yes β Truncate content, save full content to Backend β
β No β Return as-is β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
Result added to Messages
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β BeforeModelRewriteState β
β β
β Clear (can be skipped via SkipClear) β
β Total tokens > MaxTokensForClear? β
β Yes β ClearMessageRewriter preprocessing β
β β Old tool results stored to Backend, replaced β
β with file paths β
β β ClearAtLeastTokens minimum release check β
β β ClearPostProcess callback β
β No β Do nothing β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
Call Model
Generic System
This middleware follows the ADK standard generic pattern, supporting both *schema.Message and *schema.AgenticMessage:
// Generic config, M is constrained to adk.MessageType
type TypedConfig[M adk.MessageType] struct { ... }
// Backward-compatible alias
type Config = TypedConfig[*schema.Message]
Constructors are also available in both generic and non-generic forms:
func NewTyped[M adk.MessageType](ctx context.Context, config *TypedConfig[M]) (adk.TypedChatModelAgentMiddleware[M], error)
func New(ctx context.Context, config *Config) (adk.ChatModelAgentMiddleware, error)
Configuration
TypedConfig[M] Main Configuration
| Field | Type | Description |
| Backend | Backend | Storage backend. Required when SkipTruncationis false; can be nil when only doing Clear without offload. |
| SkipTruncation | bool | Skip the truncation phase. |
| SkipClear | bool | Skip the clear phase. |
| ReadFileToolName | string | Tool name for reading offloaded content. Default "read_file". |
| RootDir | string | Root directory for saving content. Default "/tmp". Truncated content is saved to {RootDir}/trunc/{tool_call_id}, cleared content to {RootDir}/clear/{tool_call_id}. |
| GenTruncOffloadFilePath | func(ctx, *ToolDetail) (string, error) | Custom truncation file path generator. When set, RootDir does not apply to truncation. Useful for scenarios where tool_call_id is not unique. |
| GenClearOffloadFilePath | func(ctx, *ToolDetail) (string, error) | Custom clear file path generator. When set, RootDir does not apply to clear. |
| MaxLengthForTrunc | int | Maximum character length to trigger truncation. Default 50000. |
| TruncExcludeTools | []string | List of tool names to exclude from truncation. |
| TokenCounter | func(ctx, []M, []*schema.ToolInfo) (int64, error) | Token counting function. Defaults to character_count/4 estimation. Recommend replacing with tiktoken-go/tokenizer. |
| MaxTokensForClear | int64 | Token threshold to trigger clear. Default 160000. |
| ClearRetentionSuffixLimit | int | Keep the most recent N assistant message rounds without clearing. Default 1. |
| ClearAtLeastTokens | int64 | Minimum token amount that must be released by clearing. If not met, clearing is not executed (avoids needlessly breaking prompt cache). Default 0. |
| ClearExcludeTools | []string | List of tool names to exclude from clearing. |
| ClearMessageRewriter | func(ctx, M, []M) ([]M, error) | Message rewrite callback before clearing. Parameters are toolCallMsg and the corresponding toolResponseMsgs. Can be used to rewrite write_file/edit_file calls into system-reminders. Returning nil removes that message group. |
| ClearPostProcess | func(ctx, *adk.TypedChatModelAgentState[M]) context.Context | Callback after clearing completes, can save state or send notifications. Returns a potentially updated context. |
| ToolConfig | map[string]*ToolReductionConfig | Per-tool configuration, takes precedence over global settings. |
ToolReductionConfig Tool-level Configuration
type ToolReductionConfig struct {
Backend Backend
SkipTruncation bool
TruncHandler func(ctx context.Context, detail *ToolDetail) (*TruncResult, error)
SkipClear bool
ClearHandler func(ctx context.Context, detail *ToolDetail) (*ClearResult, error)
}
TruncHandler/ClearHandler: when nil and not skipped, the global default handler is used.Backend: independent storage backend for this tool, overrides the global Backend.
ToolDetail Tool Details
type ToolDetail struct {
ToolContext *adk.ToolContext
ToolArgument *schema.ToolArgument
ToolResult *schema.ToolResult // non-streaming
StreamToolResult *schema.StreamReader[*schema.ToolResult] // streaming
}
TruncResult Truncation Result
type TruncResult struct {
NeedTrunc bool
ToolResult *schema.ToolResult // Required when NeedTrunc && non-streaming
StreamToolResult *schema.StreamReader[*schema.ToolResult] // Required when NeedTrunc && streaming
NeedOffload bool
OffloadFilePath string // Required when NeedOffload
OffloadContent string // Required when NeedOffload
}
ClearResult Clear Result
type ClearResult struct {
NeedClear bool
ToolArgument *schema.ToolArgument // Required when NeedClear
ToolResult *schema.ToolResult // Required when NeedClear
NeedOffload bool
OffloadFilePath string // Required when NeedOffload
OffloadContent string // Required when NeedOffload
}
Backend Interface
// Defined in reduction/internal, exported via type alias
type Backend interface {
Write(context.Context, *filesystem.WriteRequest) error
}
filesystem.WriteRequest contains two fields: FilePath string and Content string.
Creating the Middleware
Basic Usage
import "github.com/cloudwego/eino/adk/middlewares/reduction"
middleware, err := reduction.New(ctx, &reduction.Config{
Backend: myBackend,
})
agent, err := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
Model: chatModel,
Middlewares: []adk.ChatModelAgentMiddleware{middleware},
})
Generic Usage (AgenticMessage)
middleware, err := reduction.NewTyped[*schema.AgenticMessage](ctx, &reduction.TypedConfig[*schema.AgenticMessage]{
Backend: myBackend,
TokenCounter: myAgenticTokenCounter,
})
agent, err := adk.NewTypedChatModelAgent(ctx, &adk.TypedChatModelAgentConfig[*schema.AgenticMessage]{
Model: chatModel,
Middlewares: []adk.TypedChatModelAgentMiddleware[*schema.AgenticMessage]{middleware},
})
Custom Configuration
middleware, err := reduction.New(ctx, &reduction.Config{
Backend: myBackend,
RootDir: "/data/agent",
MaxLengthForTrunc: 30000,
MaxTokensForClear: 100000,
ClearRetentionSuffixLimit: 2,
ClearAtLeastTokens: 10000,
TruncExcludeTools: []string{"search_tool"},
ClearExcludeTools: []string{"read_file"},
ClearMessageRewriter: func(ctx context.Context, toolCallMsg *schema.Message, toolResponseMsgs []*schema.Message) ([]*schema.Message, error) {
// Rewrite write_file calls into system-reminder
return []*schema.Message{schema.UserMessage("<system-reminder>file written</system-reminder>")}, nil
},
ClearPostProcess: func(ctx context.Context, state *adk.ChatModelAgentState) context.Context {
log.Printf("Clear completed, messages: %d", len(state.Messages))
return ctx
},
ToolConfig: map[string]*reduction.ToolReductionConfig{
"grep": {Backend: grepBackend},
"read_file": {SkipClear: true},
},
})
Truncation Only
middleware, err := reduction.New(ctx, &reduction.Config{
Backend: myBackend,
SkipClear: true,
})
Clear Only
middleware, err := reduction.New(ctx, &reduction.Config{
SkipTruncation: true,
MaxTokensForClear: 100000,
// When Backend is nil, clearing still replaces content with placeholders but does not perform offload
})
How It Works
Truncation
Handled in WrapInvokableToolCall / WrapStreamableToolCall / WrapEnhancedInvokableToolCall / WrapEnhancedStreamableToolCall:
- Tool returns result
- Check
TruncExcludeTools; skip if matched - Look up ToolConfig β global defaultConfig to obtain TruncHandler
- TruncHandler determines: reads the full output, checks if the total length of all text parts exceeds
MaxLengthForTrunc - If exceeded: retains the first and last
MaxLengthForTrunc/(textParts*2)characters as a preview, stores the full content in the Backend - Returns a truncation notice informing the agent of the file path for the full content
π‘ For streaming tools, the default TruncHandler waits for the complete stream to be read before deciding whether to truncate. If you need strict incremental streaming behavior, provide a custom TruncHandler for that tool.
Clear
Handled in BeforeModelRewriteState:
- Use
TokenCounterto calculate total tokens - Skip if not exceeding
MaxTokensForClear - Determine clear range: from the first unprocessed assistant message to
len(messages) - ClearRetentionSuffixLimitrounds - If
ClearMessageRewriteris configured, execute rewrite preprocessing on messages within the range first - Iterate through tool call messages in range, skipping
ClearExcludeTools - Call ClearHandler for each tool call, replacing arguments and results
- If
ClearAtLeastTokensis set: operate on a copy first, compare token difference before and after clearing; abandon this clearing attempt if threshold not met - Once threshold is met, execute actual offload writes and update state.Messages
- Call
ClearPostProcess
Multi-language Support
Truncation and clear prompt text supports automatic Chinese/English switching:
adk.SetLanguage(adk.LanguageChinese) // Chinese
adk.SetLanguage(adk.LanguageEnglish) // English (default)
Notes
- When
SkipTruncationis false,Backendmust be set - The default TokenCounter uses character_count/4 estimation; recommend replacing with
github.com/tiktoken-go/tokenizer - Already processed messages are marked via the Extra field
_reduction_mw_processedand will not be processed again - Configuration in
ToolConfigtakes precedence over global settings; if a ToolConfig only setsSkipTruncation: falsewithout providing aTruncHandler, it falls back to the default handler GenTruncOffloadFilePath/GenClearOffloadFilePathare useful for scenarios where tool_call_id is not unique (e.g., retries), preventing file overwritesClearMessageRewriterexecutes after the clear range is determined but before per-tool clearing, suitable for compressing write/edit-type calls into brief promptsClearAtLeastTokensset to 0 means clearing executes whenever the threshold is exceeded; values greater than 0 can avoid minimal clearing that would break prompt cache- Legacy API (
NewClearToolResult,NewToolResultMiddleware) is deprecated; recommend migrating toNew/NewTyped