12
12
# ' Eg, the identity is simply `function(self, ...) list(...)`.
13
13
# ' The default callback prunes the previous tool calls from the chat history and
14
14
# ' inserts a tool call request, so that the LLM always sees retrieval results.
15
- # '
15
+ # ' @param .on_retrieval A function that is called when the tool retrieves results.
16
+ # ' It's called with `self` (the instance of `RagnarChat`), `results` the results
17
+ # ' retrieved from the store. It's useful for applying deoverlapping, and for
18
+ # ' pruning repeated chunks in the context.
16
19
# ' @export
17
20
chat_ragnar <- function (
18
21
chat_fun ,
@@ -23,10 +26,13 @@ chat_ragnar <- function(
23
26
self $ turns_prune_tool_calls(keep_last_n = 0 )
24
27
# inserts a new tool call request with the user's input
25
28
self $ turns_insert_tool_call_request(... , query = paste(... , collapse = " " ))
29
+ },
30
+ .on_retrieval = function (self , results ) {
31
+ results
26
32
}
27
33
) {
28
34
chat <- chat_fun(... )
29
- RagnarChat $ new(chat , .store , .on_user_turn )
35
+ RagnarChat $ new(chat , .store , .on_user_turn , .on_retrieval )
30
36
}
31
37
32
38
# ' Adds extra capabilities to a `ellmer::Chat` object.
@@ -46,7 +52,13 @@ RagnarChat <- R6::R6Class(
46
52
# ' Eg, the identity is simply `function(self, ...) list(...)`.
47
53
on_user_turn = NULL ,
48
54
49
- initialize = function (chat , store , on_user_turn ) {
55
+ # ' @field on_retrieval A function that is called when the tool retrieves results.
56
+ # ' It's called with `self` (the instance of `RagnarChat`), `results` the results
57
+ # ' retrieved from the store. It's useful for applying deoverlapping, and for
58
+ # ' pruning repeated chunks in the context.
59
+ on_retrieval = NULL ,
60
+
61
+ initialize = function (chat , store , on_user_turn , on_retrieval ) {
50
62
self $ ragnar_store <- store
51
63
super $ initialize(
52
64
chat $ get_provider(),
@@ -63,6 +75,7 @@ RagnarChat <- R6::R6Class(
63
75
)
64
76
self $ register_tool(self $ ragnar_tool_def )
65
77
self $ on_user_turn <- on_user_turn
78
+ self $ on_retrieval <- on_retrieval
66
79
},
67
80
68
81
chat = function (... , echo = NULL ) {
@@ -106,6 +119,7 @@ RagnarChat <- R6::R6Class(
106
119
}
107
120
108
121
results | >
122
+ private $ callback_retrieval() | >
109
123
dplyr :: select(- hash ) | >
110
124
jsonlite :: toJSON()
111
125
},
@@ -271,6 +285,76 @@ RagnarChat <- R6::R6Class(
271
285
self $ set_turns(turns )
272
286
},
273
287
288
+ # ' @description
289
+ # ' Removes chunks from the history by id.
290
+ # ' Rewrites the LLm context remving the chunks with the given ids. It will also
291
+ # ' enitrely remove the tool call request and results if all chunks are removed.
292
+ # '
293
+ # ' @param chunk_ids A vector of chunk ids to remove from the chat history.
294
+ turns_remove_chunks = function (chunk_ids ) {
295
+ turns <- self $ get_turns()
296
+ drop_turn_idx <- integer(0 )
297
+
298
+ for (ti in seq_along(turns )) {
299
+ turn <- turns [[ti ]]
300
+ if (turn @ role != " user" ) {
301
+ next
302
+ }
303
+
304
+ contents <- turn @ contents
305
+ drop_content_idx <- integer(0 )
306
+
307
+ for (ci in seq_along(contents )) {
308
+ content <- contents [[ci ]]
309
+
310
+ if (! S7 :: S7_inherits(content , ellmer :: ContentToolResult )) {
311
+ next
312
+ }
313
+ if (content @ request @ name != self $ ragnar_tool_def @ name ) {
314
+ next
315
+ }
316
+ if (! is.character(content @ value )) {
317
+ next
318
+ }
319
+
320
+ chunks <- jsonlite :: fromJSON(content @ value , simplifyVector = FALSE )
321
+
322
+ # Remove the chunks with the given ids.
323
+ chunks <- chunks [! sapply(chunks , function (x ) x $ id %in% chunk_ids )]
324
+
325
+ # If we have no chunks left, we remove the entire content from the list.
326
+ if (length(chunks ) == 0 ) {
327
+ drop_content_idx [[length(drop_content_idx ) + 1 ]] <- ci
328
+ next
329
+ }
330
+
331
+ # Restore the content if some chunks remained.
332
+ contents [[ci ]]@ value <- jsonlite :: toJSON(chunks , pretty = TRUE )
333
+ }
334
+
335
+ turn @ contents <- contents
336
+ if (length(drop_content_idx ) > 0 ) {
337
+ turn @ contents <- contents [- drop_content_idx ]
338
+ }
339
+
340
+ # If we removed all contents from the turn, we remove the entire turn.
341
+ # and the assistant turn that came before it.
342
+ if (length(turn @ contents ) == 0 ) {
343
+ drop_turn_idx <- c(drop_turn_idx , ti , ti - 1L )
344
+ next
345
+ }
346
+
347
+ turns [[ti ]] <- turn
348
+ }
349
+
350
+ # Remove the turns that we marked for removal.
351
+ if (length(drop_turn_idx ) > 0 ) {
352
+ turns <- turns [- drop_turn_idx ]
353
+ }
354
+
355
+ self $ set_turns(turns )
356
+ },
357
+
274
358
# ' @description
275
359
# ' Some LLM's are lazy at tool calling, and for applications to be
276
360
# ' robust, it's great to append context for the LLM, even if
@@ -312,6 +396,10 @@ RagnarChat <- R6::R6Class(
312
396
result <- list (result )
313
397
}
314
398
result
399
+ },
400
+ callback_retrieval = function (results ) {
401
+ result <- self $ on_retrieval(self , results )
402
+ result
315
403
}
316
404
)
317
405
)
0 commit comments