|
25 | 25 | ) |
26 | 26 |
|
27 | 27 |
|
28 | | -class TestToolCallBuffer: # pylint: disable=no-self-use |
29 | | - def test_append_arguments_with_string(self): |
30 | | - buf = ToolCallBuffer(0, "call_1", "get_weather") |
31 | | - buf.append_arguments('{"city":') |
32 | | - buf.append_arguments(' "NYC"}') |
33 | | - assert "".join(buf.arguments) == '{"city": "NYC"}' |
34 | | - |
35 | | - def test_append_arguments_with_none_is_skipped(self): |
36 | | - """Regression test for issue #4344. |
37 | | -
|
38 | | - Some OpenAI-compatible providers (vLLM, TGI, etc.) send |
39 | | - arguments=None on tool-call delta chunks instead of arguments="". |
40 | | - This must not crash when joining the arguments list. |
41 | | - """ |
42 | | - buf = ToolCallBuffer(0, "call_1", "get_weather") |
43 | | - buf.append_arguments(None) |
44 | | - buf.append_arguments('{"city": "NYC"}') |
45 | | - buf.append_arguments(None) |
46 | | - assert "".join(buf.arguments) == '{"city": "NYC"}' |
47 | | - |
48 | | - def test_append_arguments_all_none(self): |
49 | | - buf = ToolCallBuffer(0, "call_1", "get_weather") |
50 | | - buf.append_arguments(None) |
51 | | - buf.append_arguments(None) |
52 | | - assert "".join(buf.arguments) == "" |
53 | | - |
54 | | - def test_append_arguments_empty_string(self): |
55 | | - buf = ToolCallBuffer(0, "call_1", "get_weather") |
56 | | - buf.append_arguments("") |
57 | | - buf.append_arguments('{"city": "NYC"}') |
58 | | - assert "".join(buf.arguments) == '{"city": "NYC"}' |
59 | | - |
60 | | - |
61 | | -class TestChoiceBuffer: # pylint: disable=no-self-use |
62 | | - def test_append_tool_call_with_none_arguments(self): |
63 | | - """End-to-end regression test for issue #4344. |
64 | | -
|
65 | | - Simulates the exact scenario from the bug report where a provider |
66 | | - sends arguments=None on the first tool-call delta chunk. |
67 | | - """ |
68 | | - buf = ChoiceBuffer(0) |
69 | | - buf.append_tool_call( |
70 | | - ChoiceDeltaToolCall( |
71 | | - index=0, |
72 | | - id="call_1", |
73 | | - type="function", |
74 | | - function=ChoiceDeltaToolCallFunction( |
75 | | - name="get_weather", arguments=None |
76 | | - ), |
77 | | - ) |
| 28 | +def test_toolcallbuffer_append_arguments_with_string(): |
| 29 | + buf = ToolCallBuffer(0, "call_1", "get_weather") |
| 30 | + buf.append_arguments('{"city":') |
| 31 | + buf.append_arguments(' "NYC"}') |
| 32 | + assert "".join(buf.arguments) == '{"city": "NYC"}' |
| 33 | + |
| 34 | + |
| 35 | +def test_toolcallbuffer_append_arguments_with_none_is_skipped(): |
| 36 | + """Regression test for issue #4344. |
| 37 | +
|
| 38 | + Some OpenAI-compatible providers (vLLM, TGI, etc.) send |
| 39 | + arguments=None on tool-call delta chunks instead of arguments="". |
| 40 | + This must not crash when joining the arguments list. |
| 41 | + """ |
| 42 | + buf = ToolCallBuffer(0, "call_1", "get_weather") |
| 43 | + buf.append_arguments(None) |
| 44 | + buf.append_arguments('{"city": "NYC"}') |
| 45 | + buf.append_arguments(None) |
| 46 | + assert "".join(buf.arguments) == '{"city": "NYC"}' |
| 47 | + |
| 48 | + |
| 49 | +def test_toolcallbuffer_append_arguments_all_none(): |
| 50 | + buf = ToolCallBuffer(0, "call_1", "get_weather") |
| 51 | + buf.append_arguments(None) |
| 52 | + buf.append_arguments(None) |
| 53 | + assert "".join(buf.arguments) == "" |
| 54 | + |
| 55 | + |
| 56 | +def test_toolcallbuffer_append_arguments_empty_string(): |
| 57 | + buf = ToolCallBuffer(0, "call_1", "get_weather") |
| 58 | + buf.append_arguments("") |
| 59 | + buf.append_arguments('{"city": "NYC"}') |
| 60 | + assert "".join(buf.arguments) == '{"city": "NYC"}' |
| 61 | + |
| 62 | + |
| 63 | +def test_choicebuffer_append_tool_call_with_none_arguments(): |
| 64 | + """End-to-end regression test for issue #4344. |
| 65 | +
|
| 66 | + Simulates the exact scenario from the bug report where a provider |
| 67 | + sends arguments=None on the first tool-call delta chunk. |
| 68 | + """ |
| 69 | + buf = ChoiceBuffer(0) |
| 70 | + buf.append_tool_call( |
| 71 | + ChoiceDeltaToolCall( |
| 72 | + index=0, |
| 73 | + id="call_1", |
| 74 | + type="function", |
| 75 | + function=ChoiceDeltaToolCallFunction( |
| 76 | + name="get_weather", arguments=None |
| 77 | + ), |
78 | 78 | ) |
79 | | - buf.append_tool_call( |
80 | | - ChoiceDeltaToolCall( |
81 | | - index=0, |
82 | | - function=ChoiceDeltaToolCallFunction( |
83 | | - arguments='{"city": "NYC"}' |
84 | | - ), |
85 | | - ) |
| 79 | + ) |
| 80 | + buf.append_tool_call( |
| 81 | + ChoiceDeltaToolCall( |
| 82 | + index=0, |
| 83 | + function=ChoiceDeltaToolCallFunction( |
| 84 | + arguments='{"city": "NYC"}' |
| 85 | + ), |
86 | 86 | ) |
87 | | - |
88 | | - # This must not raise TypeError |
89 | | - result = "".join(buf.tool_calls_buffers[0].arguments) |
90 | | - assert result == '{"city": "NYC"}' |
91 | | - |
92 | | - def test_append_tool_call_normal_flow(self): |
93 | | - """Standard OpenAI flow where arguments="" on first delta.""" |
94 | | - buf = ChoiceBuffer(0) |
95 | | - buf.append_tool_call( |
96 | | - ChoiceDeltaToolCall( |
97 | | - index=0, |
98 | | - id="call_1", |
99 | | - type="function", |
100 | | - function=ChoiceDeltaToolCallFunction( |
101 | | - name="get_weather", arguments="" |
102 | | - ), |
103 | | - ) |
| 87 | + ) |
| 88 | + |
| 89 | + # This must not raise TypeError |
| 90 | + result = "".join(buf.tool_calls_buffers[0].arguments) |
| 91 | + assert result == '{"city": "NYC"}' |
| 92 | + |
| 93 | + |
| 94 | +def test_choicebuffer_append_tool_call_normal_flow(): |
| 95 | + """Standard OpenAI flow where arguments="" on first delta.""" |
| 96 | + buf = ChoiceBuffer(0) |
| 97 | + buf.append_tool_call( |
| 98 | + ChoiceDeltaToolCall( |
| 99 | + index=0, |
| 100 | + id="call_1", |
| 101 | + type="function", |
| 102 | + function=ChoiceDeltaToolCallFunction( |
| 103 | + name="get_weather", arguments="" |
| 104 | + ), |
104 | 105 | ) |
105 | | - buf.append_tool_call( |
106 | | - ChoiceDeltaToolCall( |
107 | | - index=0, |
108 | | - function=ChoiceDeltaToolCallFunction( |
109 | | - arguments='{"city": "NYC"}' |
110 | | - ), |
111 | | - ) |
| 106 | + ) |
| 107 | + buf.append_tool_call( |
| 108 | + ChoiceDeltaToolCall( |
| 109 | + index=0, |
| 110 | + function=ChoiceDeltaToolCallFunction( |
| 111 | + arguments='{"city": "NYC"}' |
| 112 | + ), |
112 | 113 | ) |
| 114 | + ) |
| 115 | + |
| 116 | + result = "".join(buf.tool_calls_buffers[0].arguments) |
| 117 | + assert result == '{"city": "NYC"}' |
113 | 118 |
|
114 | | - result = "".join(buf.tool_calls_buffers[0].arguments) |
115 | | - assert result == '{"city": "NYC"}' |
116 | | - |
117 | | - def test_append_multiple_tool_calls_with_none_arguments(self): |
118 | | - """Multiple tool calls where some have arguments=None.""" |
119 | | - buf = ChoiceBuffer(0) |
120 | | - |
121 | | - # First tool call |
122 | | - buf.append_tool_call( |
123 | | - ChoiceDeltaToolCall( |
124 | | - index=0, |
125 | | - id="call_1", |
126 | | - type="function", |
127 | | - function=ChoiceDeltaToolCallFunction( |
128 | | - name="get_weather", arguments=None |
129 | | - ), |
130 | | - ) |
131 | | - ) |
132 | | - buf.append_tool_call( |
133 | | - ChoiceDeltaToolCall( |
134 | | - index=0, |
135 | | - function=ChoiceDeltaToolCallFunction( |
136 | | - arguments='{"city": "NYC"}' |
137 | | - ), |
138 | | - ) |
139 | | - ) |
140 | 119 |
|
141 | | - # Second tool call |
142 | | - buf.append_tool_call( |
143 | | - ChoiceDeltaToolCall( |
144 | | - index=1, |
145 | | - id="call_2", |
146 | | - type="function", |
147 | | - function=ChoiceDeltaToolCallFunction( |
148 | | - name="get_time", arguments=None |
149 | | - ), |
150 | | - ) |
| 120 | +def test_choicebuffer_append_multiple_tool_calls_with_none_arguments(): |
| 121 | + """Multiple tool calls where some have arguments=None.""" |
| 122 | + buf = ChoiceBuffer(0) |
| 123 | + |
| 124 | + # First tool call |
| 125 | + buf.append_tool_call( |
| 126 | + ChoiceDeltaToolCall( |
| 127 | + index=0, |
| 128 | + id="call_1", |
| 129 | + type="function", |
| 130 | + function=ChoiceDeltaToolCallFunction( |
| 131 | + name="get_weather", arguments=None |
| 132 | + ), |
151 | 133 | ) |
152 | | - buf.append_tool_call( |
153 | | - ChoiceDeltaToolCall( |
154 | | - index=1, |
155 | | - function=ChoiceDeltaToolCallFunction( |
156 | | - arguments='{"tz": "EST"}' |
157 | | - ), |
158 | | - ) |
| 134 | + ) |
| 135 | + buf.append_tool_call( |
| 136 | + ChoiceDeltaToolCall( |
| 137 | + index=0, |
| 138 | + function=ChoiceDeltaToolCallFunction( |
| 139 | + arguments='{"city": "NYC"}' |
| 140 | + ), |
159 | 141 | ) |
160 | | - |
161 | | - assert ( |
162 | | - "".join(buf.tool_calls_buffers[0].arguments) == '{"city": "NYC"}' |
| 142 | + ) |
| 143 | + |
| 144 | + # Second tool call |
| 145 | + buf.append_tool_call( |
| 146 | + ChoiceDeltaToolCall( |
| 147 | + index=1, |
| 148 | + id="call_2", |
| 149 | + type="function", |
| 150 | + function=ChoiceDeltaToolCallFunction( |
| 151 | + name="get_time", arguments=None |
| 152 | + ), |
| 153 | + ) |
| 154 | + ) |
| 155 | + buf.append_tool_call( |
| 156 | + ChoiceDeltaToolCall( |
| 157 | + index=1, |
| 158 | + function=ChoiceDeltaToolCallFunction( |
| 159 | + arguments='{"tz": "EST"}' |
| 160 | + ), |
163 | 161 | ) |
164 | | - assert "".join(buf.tool_calls_buffers[1].arguments) == '{"tz": "EST"}' |
165 | | - |
166 | | - def test_append_tool_call_with_none_function(self): |
167 | | - """Handle delta chunks where function is None.""" |
168 | | - buf = ChoiceBuffer(0) |
169 | | - buf.append_tool_call( |
170 | | - ChoiceDeltaToolCall( |
171 | | - index=0, |
172 | | - id="call_1", |
173 | | - type="function", |
174 | | - function=ChoiceDeltaToolCallFunction( |
175 | | - name="get_weather", arguments='{"city": "NYC"}' |
176 | | - ), |
177 | | - ) |
| 162 | + ) |
| 163 | + |
| 164 | + assert ( |
| 165 | + "".join(buf.tool_calls_buffers[0].arguments) == '{"city": "NYC"}' |
| 166 | + ) |
| 167 | + assert "".join(buf.tool_calls_buffers[1].arguments) == '{"tz": "EST"}' |
| 168 | + |
| 169 | + |
| 170 | +def test_choicebuffer_append_tool_call_with_none_function(): |
| 171 | + """Handle delta chunks where function is None.""" |
| 172 | + buf = ChoiceBuffer(0) |
| 173 | + buf.append_tool_call( |
| 174 | + ChoiceDeltaToolCall( |
| 175 | + index=0, |
| 176 | + id="call_1", |
| 177 | + type="function", |
| 178 | + function=ChoiceDeltaToolCallFunction( |
| 179 | + name="get_weather", arguments='{"city": "NYC"}' |
| 180 | + ), |
178 | 181 | ) |
179 | | - # Subsequent delta with function=None should not crash |
180 | | - buf.append_tool_call( |
181 | | - ChoiceDeltaToolCall( |
182 | | - index=0, |
183 | | - function=None, |
184 | | - ) |
| 182 | + ) |
| 183 | + # Subsequent delta with function=None should not crash |
| 184 | + buf.append_tool_call( |
| 185 | + ChoiceDeltaToolCall( |
| 186 | + index=0, |
| 187 | + function=None, |
185 | 188 | ) |
| 189 | + ) |
186 | 190 |
|
187 | | - result = "".join(buf.tool_calls_buffers[0].arguments) |
188 | | - assert result == '{"city": "NYC"}' |
| 191 | + result = "".join(buf.tool_calls_buffers[0].arguments) |
| 192 | + assert result == '{"city": "NYC"}' |
0 commit comments