@@ -50,9 +50,15 @@ import (
5050// important information. After all, ClearPostProcess will be called, which you could save or notify current state.
5151type Config struct {
5252 // Backend is the storage backend where truncated content will be saved.
53- // Optional. When Backend not set, truncated / cleared content will be discarded .
53+ // Required .
5454 Backend Backend
5555
56+ // SkipTruncation skip truncating.
57+ SkipTruncation bool
58+
59+ // SkipClear skip clearing.
60+ SkipClear bool
61+
5662 // ReadFileToolName is tool name used to retrieve from file.
5763 // After offloading content to file, you should give agent the same tool to retrieve content.
5864 // Required. Default is "read_file".
@@ -94,7 +100,7 @@ type Config struct {
94100
95101type ToolReductionConfig struct {
96102 // Backend is the storage backend where truncated content will be saved.
97- // Optional. When Backend not set, truncated / cleared content will be discarded .
103+ // Required .
98104 Backend Backend
99105
100106 // SkipTruncation skip truncating this tool.
@@ -104,7 +110,7 @@ type ToolReductionConfig struct {
104110 SkipClear bool
105111
106112 // ReadFileToolName is tool name used to retrieve from file.
107- // Optional . Default is "read_file".
113+ // Required . Default is "read_file".
108114 ReadFileToolName string
109115
110116 // RootDir root dir to save truncated content, file name is {tool_call_id}.
@@ -134,6 +140,9 @@ type ToolDetail struct {
134140
135141// OffloadInfo contains the result of the Handler's decision.
136142type OffloadInfo struct {
143+ // NeedClear indicates whether the tool output should be clear.
144+ NeedClear bool
145+
137146 // NeedOffload indicates whether the tool output should be offloaded.
138147 NeedOffload bool
139148
@@ -145,28 +154,32 @@ type OffloadInfo struct {
145154 OffloadContent string
146155}
147156
148- func (t * ToolReductionConfig ) fillDefaults () * ToolReductionConfig {
157+ func (t * ToolReductionConfig ) fillDefaults () (* ToolReductionConfig , error ) {
158+ if t .Backend == nil && ! t .SkipTruncation {
159+ return nil , fmt .Errorf ("backend must be set when not skipping truncation" )
160+ }
149161 if t .ReadFileToolName == "" {
150162 t .ReadFileToolName = "read_file"
151163 }
152164 if t .RootDir == "" {
153165 t .RootDir = "/tmp"
154166 if ! t .SkipClear && t .ClearHandler == nil {
155- t .ClearHandler = defaultClearHandler ("/tmp/clear" )
167+ t .ClearHandler = defaultClearHandler ("/tmp/clear" , t . Backend != nil , t . ReadFileToolName )
156168 }
157169 } else {
158170 if ! t .SkipClear && t .ClearHandler == nil {
159- t .ClearHandler = defaultClearHandler (filepath .Join (t .RootDir , "clear" ))
171+ t .ClearHandler = defaultClearHandler (filepath .Join (t .RootDir , "clear" ), t . Backend != nil , t . ReadFileToolName )
160172 }
161173 }
162174 if t .MaxLengthForTrunc == 0 {
163175 t .MaxLengthForTrunc = 50000
164176 }
165- return t
177+ return t , nil
166178}
167179
168180// New creates tool reduction middleware from config
169181func New (_ context.Context , config * Config ) (adk.ChatModelAgentMiddleware , error ) {
182+ var err error
170183 if config == nil {
171184 return nil , fmt .Errorf ("config must not be nil" )
172185 }
@@ -178,19 +191,25 @@ func New(_ context.Context, config *Config) (adk.ChatModelAgentMiddleware, error
178191 }
179192 defaultReductionConfig := & ToolReductionConfig {
180193 Backend : config .Backend ,
181- SkipTruncation : false ,
182- SkipClear : false ,
194+ SkipTruncation : config . SkipTruncation ,
195+ SkipClear : config . SkipClear ,
183196 ReadFileToolName : config .ReadFileToolName ,
184197 RootDir : config .RootDir ,
185198 MaxLengthForTrunc : config .MaxLengthForTrunc ,
186199 }
187- defaultReductionConfig = defaultReductionConfig .fillDefaults ()
200+ defaultReductionConfig , err = defaultReductionConfig .fillDefaults ()
201+ if err != nil {
202+ return nil , err
203+ }
188204
189205 for _ , reductionConfig := range config .ToolConfig {
190206 if reductionConfig == nil {
191207 continue
192208 }
193- reductionConfig = reductionConfig .fillDefaults ()
209+ reductionConfig , err = reductionConfig .fillDefaults ()
210+ if err != nil {
211+ return nil , err
212+ }
194213 }
195214
196215 return & toolReductionMiddleware {
@@ -303,33 +322,25 @@ func (t *toolReductionMiddleware) toolTruncationHandler(ctx context.Context, con
303322 }
304323
305324 filePath := filepath .Join (config .RootDir , "trunc" , detail .ToolContext .CallID )
306- var truncatedMsg string
307- if config .Backend != nil {
308- truncatedMsg , err = pyfmt .Fmt (getTruncWithOffloadingFmt (), map [string ]any {
309- "removed_count" : len (resultText ) - config .MaxLengthForTrunc ,
310- "file_path" : filePath ,
311- "read_file_tool_name" : config .ReadFileToolName ,
312- })
313-
314- } else {
315- truncatedMsg , err = pyfmt .Fmt (getTruncWithoutOffloadingFmt (), map [string ]any {
316- "removed_count" : len (resultText ) - config .MaxLengthForTrunc ,
317- })
318- }
325+ previewSize := config .MaxLengthForTrunc / 2
326+ truncatedMsg , err := pyfmt .Fmt (getTruncFmt (), map [string ]any {
327+ "original_size" : len (resultText ),
328+ "file_path" : filePath ,
329+ "preview_size" : previewSize ,
330+ "preview_first" : resultText [:previewSize ],
331+ "preview_last" : resultText [len (resultText )- previewSize :],
332+ })
319333 if err != nil {
320334 return "" , err
321335 }
322336
323337 truncResult = resultText [:config .MaxLengthForTrunc ] + truncatedMsg
324-
325- if config .Backend != nil {
326- err = config .Backend .Write (ctx , & filesystem.WriteRequest {
327- FilePath : filePath ,
328- Content : resultText ,
329- })
330- if err != nil {
331- return "" , err
332- }
338+ err = config .Backend .Write (ctx , & filesystem.WriteRequest {
339+ FilePath : filePath ,
340+ Content : resultText ,
341+ })
342+ if err != nil {
343+ return "" , err
333344 }
334345
335346 return truncResult , nil
@@ -426,10 +437,10 @@ func (t *toolReductionMiddleware) BeforeModelRewriteState(ctx context.Context, s
426437 if offloadErr != nil {
427438 return ctx , state , offloadErr
428439 }
429- if ! offloadInfo .NeedOffload {
440+ if ! offloadInfo .NeedClear {
430441 continue
431442 }
432- if cfg .Backend != nil {
443+ if offloadInfo . NeedOffload && cfg .Backend != nil {
433444 writeErr := cfg .Backend .Write (ctx , & filesystem.WriteRequest {
434445 FilePath : offloadInfo .FilePath ,
435446 Content : offloadInfo .OffloadContent ,
@@ -513,23 +524,42 @@ func defaultTokenCounter(_ context.Context, msgs []*schema.Message, tools []*sch
513524 return tokens , nil
514525}
515526
516- func defaultClearHandler (rootDir string ) func (ctx context.Context , detail * ToolDetail ) (* OffloadInfo , error ) {
517- return func (ctx context.Context , detail * ToolDetail ) (* OffloadInfo , error ) {
518- fileName := detail .ToolContext .CallID
519- if fileName == "" {
520- fileName = uuid .NewString ()
521- }
522- filePath := filepath .Join (rootDir , fileName )
523- nResult := fmt .Sprintf (getToolOffloadResultFmt (), filePath )
527+ func defaultClearHandler (rootDir string , needOffload bool , readFileToolName string ) func (ctx context.Context , detail * ToolDetail ) (* OffloadInfo , error ) {
528+ return func (ctx context.Context , detail * ToolDetail ) (offloadInfo * OffloadInfo , err error ) {
524529 if len (detail .ToolResult .Parts ) == 0 || detail .ToolResult .Parts [0 ].Type != schema .ToolPartTypeText {
525530 // brutal judge
526531 return nil , fmt .Errorf ("default offload currently not support multimodal content" )
527532 }
528- offloadInfo := & OffloadInfo {
529- NeedOffload : true ,
530- FilePath : filePath ,
531- OffloadContent : detail .ToolResult .Parts [0 ].Text ,
533+
534+ fileName := detail .ToolContext .CallID
535+ if fileName == "" {
536+ fileName = uuid .NewString ()
537+ }
538+
539+ var nResult string
540+ if needOffload {
541+ filePath := filepath .Join (rootDir , fileName )
542+ nResult , err = pyfmt .Fmt (getClearWithOffloadingFmt (), map [string ]any {
543+ "file_path" : filePath ,
544+ "read_tool_name" : readFileToolName ,
545+ })
546+ if err != nil {
547+ return nil , err
548+ }
549+ offloadInfo = & OffloadInfo {
550+ NeedClear : true ,
551+ NeedOffload : true ,
552+ FilePath : filePath ,
553+ OffloadContent : detail .ToolResult .Parts [0 ].Text ,
554+ }
555+ } else {
556+ nResult = getClearWithoutOffloadingFmt ()
557+ offloadInfo = & OffloadInfo {
558+ NeedClear : true ,
559+ NeedOffload : false ,
560+ }
532561 }
562+
533563 detail .ToolResult .Parts [0 ].Text = nResult
534564
535565 return offloadInfo , nil
0 commit comments