1
+ from litellm .utils import ChatCompletionDeltaToolCall , Function
2
+ import json
3
+
4
+ from .toolcall_types import ResolvedToolCall , ResolvedFunction
5
+
6
+ class ToolCallList ():
7
+ """
8
+ A helper object that defines a custom `__iadd__()` method which accepts a
9
+ `tool_call_deltas: list[ChatCompletionDeltaToolCall]` argument. This class
10
+ is used to aggregate the tool call deltas yielded from a LiteLLM response
11
+ stream and produce a list of tool calls.
12
+
13
+ After all tool call deltas are added, the `process()` method may be called
14
+ to return a list of resolved tool calls.
15
+
16
+ Example usage:
17
+
18
+ ```py
19
+ tool_call_list = ToolCallList()
20
+ reply_stream = await litellm.acompletion(..., stream=True)
21
+
22
+ async for chunk in reply_stream:
23
+ tool_call_delta = chunk.choices[0].delta.tool_calls
24
+ tool_call_list += tool_call_delta
25
+
26
+ tool_call_list.resolve()
27
+ ```
28
+ """
29
+
30
+ _aggregate : list [ChatCompletionDeltaToolCall ]
31
+
32
+ def __init__ (self ):
33
+ self .size = None
34
+
35
+ # Initialize `_aggregate`
36
+ self ._aggregate = []
37
+
38
+
39
+ def __iadd__ (self , other : list [ChatCompletionDeltaToolCall ] | None ) -> 'ToolCallList' :
40
+ """
41
+ Adds a list of tool call deltas to this instance.
42
+
43
+ NOTE: This assumes the 'index' attribute on each entry in this list to
44
+ be accurate. If this assumption doesn't hold, we will need to rework the
45
+ logic here.
46
+ """
47
+ if other is None :
48
+ return self
49
+
50
+ # Iterate through each delta
51
+ for delta in other :
52
+ # Ensure `self._aggregate` is at least of size `delta.index + 1`
53
+ for i in range (len (self ._aggregate ), delta .index + 1 ):
54
+ self ._aggregate .append (ChatCompletionDeltaToolCall (
55
+ function = Function (arguments = "" ),
56
+ index = i ,
57
+ ))
58
+
59
+ # Find the corresponding target in the `self._aggregate` and add the
60
+ # delta on top of it. In most cases, the value of aggregate
61
+ # attribute is set as soon as any delta sets it to a non-`None`
62
+ # value. However, `delta.function.arguments` is a string that should
63
+ # be appended to the aggregate value of that attribute.
64
+ target = self ._aggregate [delta .index ]
65
+ if delta .type :
66
+ target .type = delta .type
67
+ if delta .id :
68
+ target .id = delta .id
69
+ if delta .function .name :
70
+ target .function .name = delta .function .name
71
+ if delta .function .arguments :
72
+ target .function .arguments += delta .function .arguments
73
+
74
+ return self
75
+
76
+
77
+ def __add__ (self , other : list [ChatCompletionDeltaToolCall ] | None ) -> 'ToolCallList' :
78
+ """
79
+ Alias for `__iadd__()`.
80
+ """
81
+ return self .__iadd__ (other )
82
+
83
+
84
+ def resolve (self ) -> list [ResolvedToolCall ]:
85
+ """
86
+ Resolve the aggregated tool call delta lists into a list of tool calls.
87
+ """
88
+ resolved_toolcalls : list [ResolvedToolCall ] = []
89
+ for i , raw_toolcall in enumerate (self ._aggregate ):
90
+ # Verify entries are at the correct index in the aggregated list
91
+ assert raw_toolcall .index == i
92
+
93
+ # Verify each tool call specifies the name of the tool to run.
94
+ #
95
+ # TODO: Check if this may cause a runtime error. The docstring on
96
+ # `litellm.utils.Function` implies that `name` may be `None`.
97
+ assert raw_toolcall .function .name
98
+
99
+ # Verify each tool call defines the type of tool it is calling.
100
+ assert raw_toolcall .type is not None
101
+
102
+ # Parse the function argument string into a dictionary
103
+ resolved_fn_args = json .loads (raw_toolcall .function .arguments )
104
+
105
+ # Add to the returned list
106
+ resolved_fn = ResolvedFunction (
107
+ name = raw_toolcall .function .name ,
108
+ arguments = resolved_fn_args
109
+ )
110
+ resolved_toolcall = ResolvedToolCall (
111
+ id = raw_toolcall .id ,
112
+ type = raw_toolcall .type ,
113
+ index = i ,
114
+ function = resolved_fn
115
+ )
116
+ resolved_toolcalls .append (resolved_toolcall )
117
+
118
+ return resolved_toolcalls
119
+
120
+
121
+
0 commit comments