@@ -113,6 +113,23 @@ func TestFromJSONFilter(t *testing.T) {
113113 }
114114}
115115
116+ func TestUndefinedCallableErrorNamesExpression (t * testing.T ) {
117+ tmpl , err := jinja .Compile (`{{ args.items() }}` )
118+ if err != nil {
119+ t .Fatalf ("compile: %v" , err )
120+ }
121+
122+ _ , err = tmpl .Render (nil )
123+ if err == nil {
124+ t .Fatal ("render: expected error" )
125+ }
126+
127+ const want = "args.items is not callable: Undefined"
128+ if err .Error () != want {
129+ t .Errorf ("error: got %q, want %q" , err , want )
130+ }
131+ }
132+
116133func TestNamespace (t * testing.T ) {
117134 source := `{%- set ns = namespace(found=false) -%}
118135{%- for item in items -%}
@@ -192,6 +209,106 @@ func TestDictMethods(t *testing.T) {
192209 }
193210}
194211
212+ func TestFromJSONWithDictItems (t * testing.T ) {
213+ source := `{%- set func = tool['function'] -%}
214+ {%- set args = func['arguments'] -%}
215+ {%- if args is string -%}
216+ {%- set args = args | from_json -%}
217+ {%- endif -%}
218+ {%- for key, val in args.items() -%}{{ key }}={{ val }}{%- endfor -%}`
219+
220+ tests := []struct {
221+ name string
222+ arguments any
223+ want string
224+ }{
225+ {
226+ name : "json string" ,
227+ arguments : `{"location":"New York City, NY"}` ,
228+ want : "location=New York City, NY" ,
229+ },
230+ {
231+ name : "map" ,
232+ arguments : map [string ]any {"location" : "New York City, NY" },
233+ want : "location=New York City, NY" ,
234+ },
235+ }
236+
237+ for _ , tt := range tests {
238+ t .Run (tt .name , func (t * testing.T ) {
239+ tmpl , err := jinja .Compile (source )
240+ if err != nil {
241+ t .Fatalf ("compile: %v" , err )
242+ }
243+
244+ result , err := tmpl .Render (map [string ]any {
245+ "tool" : map [string ]any {
246+ "function" : map [string ]any {
247+ "name" : "get_weather" ,
248+ "arguments" : tt .arguments ,
249+ },
250+ },
251+ })
252+ if err != nil {
253+ t .Fatalf ("render: %v" , err )
254+ }
255+
256+ if result != tt .want {
257+ t .Errorf ("result: got %q, want %q" , result , tt .want )
258+ }
259+ })
260+ }
261+ }
262+
263+ func TestNestedRangePreservesOuterLoop (t * testing.T ) {
264+ source := `{%- for message in messages -%}
265+ {%- if message['role'] == 'assistant' -%}
266+ {%- set ep = namespace(idx=(loop.index0 - 1), done=false) -%}
267+ {%- for _i in range(loop.index0) -%}
268+ {%- if not ep.done and ep.idx >= 0 -%}
269+ {%- set ep.done = true -%}
270+ {%- endif -%}
271+ {%- endfor -%}
272+ {%- for tool in message['tool_calls'] -%}
273+ {%- set args = tool['function']['arguments'] -%}
274+ {%- if args is string -%}{%- set args = args | from_json -%}{%- endif -%}
275+ {%- for key, val in args.items() -%}{{ key }}={{ val }}{%- endfor -%}
276+ {%- endfor -%}
277+ {%- endif -%}
278+ {%- endfor -%}`
279+
280+ tmpl , err := jinja .Compile (source )
281+ if err != nil {
282+ t .Fatalf ("compile: %v" , err )
283+ }
284+
285+ result , err := tmpl .Render (map [string ]any {
286+ "messages" : []map [string ]any {
287+ {"role" : "user" , "content" : "weather" },
288+ {
289+ "role" : "assistant" ,
290+ "tool_calls" : []map [string ]any {
291+ {
292+ "function" : map [string ]any {
293+ "name" : "get_weather" ,
294+ "arguments" : map [string ]any {"location" : "New York City, NY" },
295+ },
296+ },
297+ },
298+ },
299+ {"role" : "tool" , "content" : `{"temperature":"72°F"}` },
300+ },
301+ })
302+ if err != nil {
303+ t .Fatalf ("render: %v" , err )
304+ }
305+
306+ const want = "location=New York City, NY"
307+ if result != want {
308+ t .Errorf ("result: got %q, want %q" , result , want )
309+ }
310+ }
311+
195312func TestInlineIf (t * testing.T ) {
196313 source := `{{ "yes" if x else "no" }}`
197314 tmpl , err := jinja .Compile (source )
0 commit comments