Repository: meta-llama/llama-stack
File: src/llama_stack/providers/inline/agents/meta_reference/responses/streaming.py (Python)

Code before:
                # This maintains the constraint across all iterations while letting model
                # decide freely whether to call a tool or respond
                allowed_tool_names = {
                    tool["function"]["name"]
                    for tool in processed_tool_choice.allowed_tools.tools
                    if tool.get("type") == "function" and "function" in tool
                }
                # Use the mode (e.g., "required") for first iteration, then "auto"
                chat_tool_choice = (
                    processed_tool_choice.allowed_tools.mode if processed_tool_choice.allowed_tools.mode else "auto"
                )
            else:
                chat_tool_choice = processed_tool_choice.model_dump()

        n_iter = 0
        messages = self.ctx.messages.copy()
        final_status = "completed"
        last_completion_result: ChatCompletionResult | None = None

        try:
            while True:
                if (
                    self.max_output_tokens is not None
                    and self.accumulated_builtin_output_tokens >= self.max_output_tokens
                ):
                    logger.info(
                        "Skipping chat completion since max_output_tokens reached: "
                        f"{self.accumulated_builtin_output_tokens}/{self.max_output_tokens}"
                    )
                    final_status = "incomplete"
                    break

                remaining_output_tokens = (
                    self.max_output_tokens - self.accumulated_builtin_output_tokens
                    if self.max_output_tokens is not None
                    else None
                )
                # Text is the default response format for chat completion so don't need to pass it
                # (some providers don't support non-empty response_format when tools are present)
                response_format = (
                    None if getattr(self.ctx.response_format, "type", None) == "text" else self.ctx.response_format
                )
                # Filter tools to only allowed ones if tool_choice specified an allowed list
                effective_tools = self.ctx.chat_tools
                if allowed_tool_names is not None:
                    effective_tools = [
                        tool
                        for tool in self.ctx.chat_tools
                        if tool.get("function", {}).get("name") in allowed_tool_names
                    ]
                logger.debug(f"calling openai_chat_completion with tools: {effective_tools}")

Review comment (suggestion):
nit: should we validate max_output_tokens value against a different lower bound before reaching here? A good place could be in openai_responses.py (e.g: https://github.com/llamastack/llama-stack/blob/main/src/llama_stack/providers/inline/agents/meta_reference/responses/openai_responses.py#L383)

OpenAI requires max_output_tokens to be >=16 otherwise an error is returned. I am not sure if we want to follow the same lower bound or not. Would love to hear others opinions on this.
```
openai.BadRequestError: Error code: 400 - {'error': {'message': "Invalid 'max_output_tokens': integer below minimum value. Expected a value >= 16, but got 10 instead.", 'type': 'invalid_request_error', 'param': 'max_output_tokens', 'code': 'integer_below_min_value'}}
```

Code after:
            elif isinstance(processed_tool_choice, OpenAIChatCompletionToolChoiceAllowedTools):
                # For allowed_tools: filter the tools list instead of using tool_choice
                # This maintains the constraint across all iterations while letting model
                # decide freely whether to call a tool or respond
                allowed_tool_names = {
                    tool["function"]["name"]
                    for tool in processed_tool_choice.allowed_tools.tools
                    if tool.get("type") == "function" and "function" in tool
                }
                # Use the mode (e.g., "required") for first iteration, then "auto"
                chat_tool_choice = (
                    processed_tool_choice.allowed_tools.mode if processed_tool_choice.allowed_tools.mode else "auto"
                )
            else:
                chat_tool_choice = processed_tool_choice.model_dump()

        n_iter = 0
        messages = self.ctx.messages.copy()
        final_status = "completed"
        last_completion_result: ChatCompletionResult | None = None

        try:
            while True:
                if (
                    self.max_output_tokens is not None
                    and self.accumulated_builtin_output_tokens >= self.max_output_tokens
                ):
                    logger.info(
                        "Skipping inference call since max_output_tokens reached: "
                        f"{self.accumulated_builtin_output_tokens}/{self.max_output_tokens}"
                    )
                    final_status = "incomplete"
                    break

                remaining_output_tokens = (
                    self.max_output_tokens - self.accumulated_builtin_output_tokens
                    if self.max_output_tokens is not None
                    else None
                )
                # Text is the default response format for chat completion so don't need to pass it
                # (some providers don't support non-empty response_format when tools are present)
                response_format = (
                    None if getattr(self.ctx.response_format, "type", None) == "text" else self.ctx.response_format
                )
                # Filter tools to only allowed ones if tool_choice specified an allowed list
                effective_tools = self.ctx.chat_tools
                if allowed_tool_names is not None:
                    effective_tools = [
                        tool
                        for tool in self.ctx.chat_tools
                        if tool.get("function", {}).get("name") in allowed_tool_names