@@ -279,10 +279,103 @@ func TestExtractUsageFromLangchainGenerationInfo(t *testing.T) {
279279 }
280280}
281281
282+ // resolveCallOptions applies the built options so assertions can inspect the resolved values.
283+ func resolveCallOptions (opts []llms.CallOption ) llms.CallOptions {
284+ var resolved llms.CallOptions
285+ for _ , opt := range opts {
286+ opt (& resolved )
287+ }
288+ return resolved
289+ }
290+
291+ func TestTranslateToolChoice (t * testing.T ) {
292+ tests := map [string ]struct {
293+ toolChoice string
294+ provider Provider
295+ hasTools bool
296+ expected any
297+ }{
298+ "non-anthropic provider passes the bare string through" : {
299+ toolChoice : "required" ,
300+ hasTools : true ,
301+ expected : "required" ,
302+ },
303+ "non-anthropic provider passes a named tool through" : {
304+ toolChoice : "get_weather" ,
305+ hasTools : true ,
306+ expected : "get_weather" ,
307+ },
308+ "non-anthropic provider passes through without tools" : {
309+ toolChoice : "required" ,
310+ expected : "required" ,
311+ },
312+ "anthropic auto is omitted" : {
313+ toolChoice : "auto" ,
314+ provider : ProviderAnthropic ,
315+ hasTools : true ,
316+ expected : nil ,
317+ },
318+ "anthropic required maps to any" : {
319+ toolChoice : "required" ,
320+ provider : ProviderAnthropic ,
321+ hasTools : true ,
322+ expected : map [string ]any {"type" : "any" },
323+ },
324+ "anthropic any maps to any" : {
325+ toolChoice : "any" ,
326+ provider : ProviderAnthropic ,
327+ hasTools : true ,
328+ expected : map [string ]any {"type" : "any" },
329+ },
330+ "anthropic none maps to none" : {
331+ toolChoice : "none" ,
332+ provider : ProviderAnthropic ,
333+ hasTools : true ,
334+ expected : map [string ]any {"type" : "none" },
335+ },
336+ "anthropic named tool maps to tool" : {
337+ toolChoice : "get_weather" ,
338+ provider : ProviderAnthropic ,
339+ hasTools : true ,
340+ expected : map [string ]any {"type" : "tool" , "name" : "get_weather" },
341+ },
342+ "anthropic omits required without tools" : {
343+ toolChoice : "required" ,
344+ provider : ProviderAnthropic ,
345+ expected : nil ,
346+ },
347+ "anthropic omits a named tool without tools" : {
348+ toolChoice : "get_weather" ,
349+ provider : ProviderAnthropic ,
350+ expected : nil ,
351+ },
352+ }
353+
354+ for name , tt := range tests {
355+ t .Run (name , func (t * testing.T ) {
356+ assert .Equal (t , tt .expected , translateToolChoice (tt .toolChoice , tt .provider , tt .hasTools ))
357+ })
358+ }
359+ }
360+
361+ // TestTranslateToolChoiceIsDeterministic guards the caching contract: Anthropic invalidates
362+ // cached message blocks when tool_choice changes, so repeated calls must not vary.
363+ func TestTranslateToolChoiceIsDeterministic (t * testing.T ) {
364+ for _ , toolChoice := range []string {"auto" , "required" , "any" , "none" , "get_weather" } {
365+ t .Run (toolChoice , func (t * testing.T ) {
366+ first := translateToolChoice (toolChoice , ProviderAnthropic , true )
367+ for range 3 {
368+ assert .Equal (t , first , translateToolChoice (toolChoice , ProviderAnthropic , true ))
369+ }
370+ })
371+ }
372+ }
373+
282374func TestGetOptionsFromRequest (t * testing.T ) {
283375 log := logger .NewLogger ("test" )
284376
285377 toolChoice := "auto"
378+ requiredToolChoice := "required"
286379 tools := []llms.Tool {
287380 {
288381 Type : "function" ,
@@ -296,6 +389,7 @@ func TestGetOptionsFromRequest(t *testing.T) {
296389
297390 tests := map [string ]struct {
298391 request * conversation.Request
392+ provider Provider
299393 existingOpts []llms.CallOption
300394 validate func (t * testing.T , r * conversation.Request , opts []llms.CallOption )
301395 }{
@@ -346,6 +440,38 @@ func TestGetOptionsFromRequest(t *testing.T) {
346440 },
347441 validate : func (t * testing.T , r * conversation.Request , opts []llms.CallOption ) {
348442 assert .Len (t , opts , 2 )
443+ assert .Equal (t , "auto" , resolveCallOptions (opts ).ToolChoice )
444+ },
445+ },
446+ "anthropic auto tool choice omits the option" : {
447+ request : & conversation.Request {
448+ Tools : & tools ,
449+ ToolChoice : & toolChoice ,
450+ },
451+ provider : ProviderAnthropic ,
452+ validate : func (t * testing.T , r * conversation.Request , opts []llms.CallOption ) {
453+ assert .Len (t , opts , 1 )
454+ assert .Nil (t , resolveCallOptions (opts ).ToolChoice )
455+ },
456+ },
457+ "anthropic required tool choice sets the object form" : {
458+ request : & conversation.Request {
459+ Tools : & tools ,
460+ ToolChoice : & requiredToolChoice ,
461+ },
462+ provider : ProviderAnthropic ,
463+ validate : func (t * testing.T , r * conversation.Request , opts []llms.CallOption ) {
464+ assert .Len (t , opts , 2 )
465+ assert .Equal (t , map [string ]any {"type" : "any" }, resolveCallOptions (opts ).ToolChoice )
466+ },
467+ },
468+ "anthropic tool choice without tools omits the option" : {
469+ request : & conversation.Request {
470+ ToolChoice : & requiredToolChoice ,
471+ },
472+ provider : ProviderAnthropic ,
473+ validate : func (t * testing.T , r * conversation.Request , opts []llms.CallOption ) {
474+ assert .Empty (t , opts )
349475 },
350476 },
351477 "metadata sets option" : {
@@ -382,7 +508,7 @@ func TestGetOptionsFromRequest(t *testing.T) {
382508 for name , tt := range tests {
383509 t .Run (name , func (t * testing.T ) {
384510 assert .NotPanics (t , func () {
385- opts := getOptionsFromRequest (tt .request , log , tt .existingOpts ... )
511+ opts := getOptionsFromRequest (tt .request , tt . provider , log , tt .existingOpts ... )
386512 tt .validate (t , tt .request , opts )
387513 })
388514 })
0 commit comments