2626from llama_stack_api .openai_responses import (
2727 OpenAIResponseInputToolChoice as ToolChoice ,
2828)
29+ from llama_stack_api .openai_responses import (
30+ OpenAIResponseInputToolChoiceAllowedTools as AllowedTools ,
31+ )
2932from llama_stack_api .openai_responses import (
3033 OpenAIResponseInputToolChoiceMode as ToolChoiceMode ,
3134)
@@ -417,6 +420,55 @@ def extract_vector_store_ids_from_tools(
417420 return vector_store_ids
418421
419422
423+ def _tool_matches_allowed_entry (tool : InputTool , entry : dict [str , str ]) -> bool :
424+ """Return True if the tool satisfies every key in the allowlist entry.
425+
426+ ``OpenAIResponseInputToolChoiceAllowedTools.tools`` entries use string keys
427+ and values (e.g. ``type``, ``server_label``, ``name``); each must match the
428+ corresponding attribute on the tool.
429+
430+ Parameters:
431+ tool: A configured input tool.
432+ entry: One allowlist entry from ``allowed_tools.tools``.
433+
434+ Returns:
435+ True if all entry keys match the tool.
436+ """
437+ for key , value in entry .items ():
438+ if not hasattr (tool , key ):
439+ return False
440+ attr = getattr (tool , key )
441+ if attr is None :
442+ return False
443+ if attr != value and str (attr ) != value :
444+ return False
445+ return True
446+
447+
448+ def filter_tools_by_allowed_entries (
449+ tools : list [InputTool ],
450+ allowed_entries : list [dict [str , str ]],
451+ ) -> list [InputTool ]:
452+ """Keep tools that match at least one allowlist entry.
453+
454+ If ``allowed_entries`` is empty, no tools are kept (strict allowlist).
455+
456+ Parameters:
457+ tools: Tools to filter (typically after translation / preparation).
458+ allowed_entries: Entries from ``OpenAIResponseInputToolChoiceAllowedTools.tools``.
459+
460+ Returns:
461+ A sublist of ``tools`` matching the allowlist.
462+ """
463+ if not allowed_entries :
464+ return []
465+ return [
466+ t
467+ for t in tools
468+ if any (_tool_matches_allowed_entry (t , e ) for e in allowed_entries )
469+ ]
470+
471+
420472def resolve_vector_store_ids (
421473 vector_store_ids : list [str ], byok_rags : list [ByokRag ]
422474) -> list [str ]:
@@ -1332,10 +1384,19 @@ async def resolve_tool_choice(
13321384) -> tuple [Optional [list [InputTool ]], Optional [ToolChoice ], Optional [list [str ]]]:
13331385 """Resolve tools and tool_choice for the Responses API.
13341386
1335- If the request includes tools, uses them as-is and derives vector_store_ids
1336- from tool configs; otherwise loads tools via prepare_tools (using all
1337- configured vector stores) and honors tool_choice "none" via the no_tools
1338- flag. When no tools end up configured, tool_choice is cleared to None.
1387+ If ``tool_choice`` is ``none``, always returns ``(None, None, None)`` — no
1388+ tools are sent to Llama Stack, even when the request included explicit
1389+ ``tools`` (e.g. file_search).
1390+
1391+ If ``tool_choice`` is ``allowed_tools``, it is rewritten for downstream
1392+ services: tools are filtered to those matching the allowlist entries, and
1393+ ``tool_choice`` becomes ``auto`` or ``required`` per the allowlist ``mode``.
1394+
1395+ If the request includes tools and tool_choice is not ``none``, uses them
1396+ (after allowlist filtering) and derives vector_store_ids from the prepared
1397+ tools; otherwise loads tools via prepare_tools (using all configured vector
1398+ stores), then applies allowlist filtering when present. When no tools end
1399+ up configured, tool_choice is cleared to None.
13391400
13401401 Args:
13411402 tools: Tools from the request, or None to use LCORE-configured tools.
@@ -1349,35 +1410,46 @@ async def resolve_tool_choice(
13491410 prepared_tools is the list of tools to use, or None if none configured;
13501411 prepared_tool_choice is the resolved tool choice, or None when there
13511412 are no tools; vector_store_ids is extracted from tools (in user-facing format)
1352- when provided, otherwise None.
1413+ when provided, otherwise None (also None when tool_choice is ``none``) .
13531414 """
1415+ if isinstance (tool_choice , ToolChoiceMode ) and tool_choice == ToolChoiceMode .none :
1416+ return None , None , None
1417+
1418+ allowed_filters : Optional [list [dict [str , str ]]] = None
1419+ if isinstance (tool_choice , AllowedTools ):
1420+ allowed_filters = tool_choice .tools
1421+ tool_choice = ToolChoiceMode (tool_choice .mode )
1422+
13541423 prepared_tools : Optional [list [InputTool ]] = None
1355- client = AsyncLlamaStackClientHolder ().get_client ()
13561424 if tools : # explicitly specified in request
1357- # Per-request override of vector stores (user-facing rag_ids)
1358- vector_store_ids = extract_vector_store_ids_from_tools (tools )
1359- # Translate user-facing rag_ids to llama-stack vector_store_ids in each file_search tool
13601425 byok_rags = configuration .configuration .byok_rag
13611426 prepared_tools = translate_tools_vector_store_ids (tools , byok_rags )
1427+ if allowed_filters is not None :
1428+ prepared_tools = filter_tools_by_allowed_entries (
1429+ prepared_tools , allowed_filters
1430+ )
1431+ if not prepared_tools :
1432+ return None , None , None
1433+ vector_store_ids_list = extract_vector_store_ids_from_tools (prepared_tools )
1434+ vector_store_ids = vector_store_ids_list if vector_store_ids_list else None
13621435 prepared_tool_choice = tool_choice or ToolChoiceMode .auto
13631436 else :
1364- # Vector stores were not overwritten in request, use all configured vector stores
13651437 vector_store_ids = None
1366- # Get all tools configured in LCORE (returns None or non-empty list)
1367- no_tools = (
1368- isinstance (tool_choice , ToolChoiceMode )
1369- and tool_choice == ToolChoiceMode .none
1370- )
1371- # Vector stores are prepared in llama-stack format
1438+ client = AsyncLlamaStackClientHolder ().get_client ()
13721439 prepared_tools = await prepare_tools (
13731440 client = client ,
1374- vector_store_ids = vector_store_ids , # allow all configured vector stores
1375- no_tools = no_tools ,
1441+ vector_store_ids = vector_store_ids ,
1442+ no_tools = False ,
13761443 token = token ,
13771444 mcp_headers = mcp_headers ,
13781445 request_headers = request_headers ,
13791446 )
1380- # If there are no tools, tool_choice cannot be set at all - LLS implicit behavior
1447+ if allowed_filters is not None and prepared_tools :
1448+ prepared_tools = filter_tools_by_allowed_entries (
1449+ prepared_tools , allowed_filters
1450+ )
1451+ if not prepared_tools :
1452+ prepared_tools = None
13811453 prepared_tool_choice = tool_choice if prepared_tools else None
13821454
13831455 return prepared_tools , prepared_tool_choice , vector_store_ids
0 commit comments