Skip to content

[BUG] : structured_output hard fails when LiteLLM returns JSON without tool_calls finish_reason #1005

@Arindam200

Description

@Arindam200

Checks

  • I have updated to the lastest minor and patch version of Strands
  • I have checked the documentation and this is not expected behavior
  • I have searched ./issues and there are no duplicates of my issue

Strands Version

1.10.0 (strands-agents)

Python Version

3.11.11

Operating System

macOS 14.5 (Apple Silicon)

Installation Method

other

Steps to Reproduce

  1. Create a project using Strands Agents and install dependencies with uv add strands-agents 'strands-agents[litellm]' strands-agents-tools.

  2. Configure the agent as shown below:

    from dotenv import load_dotenv
    from pydantic import BaseModel
    from strands import Agent
    from strands.models.litellm import LiteLLMModel
    import os
    
    load_dotenv()
    
    model = LiteLLMModel(
       client_args={"api_key": os.getenv("NEBIUS_API_KEY")},
       model_id="nebius/zai-org/GLM-4.5",
    )
    
    class PersonInfo(BaseModel):
       name: str
       age: int
       occupation: str
    
    agent = Agent(
       model=model,
       system_prompt="You are a helpful assistant that extracts structured information about people from text.",
    )
    
    agent.structured_output(PersonInfo, "John Smith is a 30-year-old software engineer")
  3. Run the script via uv run main.py.

Expected Behavior

structured_output should recognize the JSON payload returned in message.content (even when finish_reason is "stop") and return a populated PersonInfo instance.

Actual Behavior

The call raises ValueError: No tool_calls found in response from strands/models/litellm.py because the LiteLLM adapter only checks for responses with finish_reason == "tool_calls".

Additional Context

  • Provider: Nebius zai-org/GLM-4.5 via LiteLLM
  • The provider returns the structured response in choice.message.content with finish_reason: "stop".
  • Local environment: macOS, Apple Silicon, Strands Agents 1.10.0, Python 3.11.11.

Possible Solution

Add a fallback in LiteLLMModel.structured_output to attempt parsing the first choice's message.content as JSON when no tool calls are present. A simple local patch that adds this fallback resolves the issue and produces the expected structured output.

I tried this approach and it worked:

        # Fallback: sometimes providers return the structured JSON payload in
        # the message content but use a different finish_reason (e.g. "stop").
        # Attempt to parse the first choice's message content as JSON and
        # return it if successful.
        if response.choices:
            first_choice = response.choices[0]
            content = getattr(getattr(first_choice, "message", None), "content", None)
            if content:
                try:
                    tool_call_data = json.loads(content)
                    yield {"output": output_model(**tool_call_data)}
                    return
                except (json.JSONDecodeError, TypeError, ValueError):
                    # not JSON or doesn't match expected shape; fall through to error
                    pass

        # If we reach here, no tool_calls or usable JSON content was found
        finish_reasons = [getattr(c, "finish_reason", None) for c in response.choices]
        raise ValueError(f"No tool_calls found in response (finish_reasons={finish_reasons})")

Related Issues

N/A

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions