langchain - ✅(Solved) Fix Agents silently fail to deal with structured responses when models forget to make a tool call. [3 pull requests, 6 comments, 6 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
langchain-ai/langchain#36349Fetched 2026-04-08 01:45:23
View on GitHub
Comments
6
Participants
6
Timeline
19
Reactions
0
Timeline (top)
commented ×6cross-referenced ×3labeled ×3mentioned ×2

Error Message

from collections.abc import Callable from typing import Any, Sequence

from pydantic import BaseModel, Field

from langchain_core.runnables import Runnable from langchain_core.tools import BaseTool from langchain_core.messages import BaseMessage from langchain_core.prompt_values import PromptValue from langchain_core.language_models.fake_chat_models import GenericFakeChatModel

from langchain.tools import tool from langchain.messages import AIMessage, ToolCall from langchain.agents.structured_output import( StructuredOutputValidationError, ToolStrategy ) from langchain.agents import create_agent

To bind structured output tool to fake model

class FakeWithSO(GenericFakeChatModel): def bind_tools( self, tools: Sequence[dict[str, Any] | type | Callable[..., Any] | BaseTool], *, tool_choice: str | None = None, **kwargs: Any ) -> Runnable[PromptValue | str | Sequence[BaseMessage | list[str] | tuple[str, str] | str | dict[str, Any]], AIMessage]: return self.bind(tools=tools, tool_choice=tool_choice, **kwargs)

Expected response format

class Parrot(BaseModel): name: str = Field(description="Parrot name.") species: str = Field(description="Parrot species.") response: str = Field(description="Parrot's response.")

A fake tool

@tool def fake_tool(x:str)-> str: """A fake tool.""" return "fake!"

Test for correct tool calls

correct_call_model = FakeWithSO( messages=iter( [ AIMessage( content="A tool call", tool_calls=[ ToolCall( name="Parrot", args={ "name": "Polly", "species": "Norwegian Blue", "response": "...", }, id="call_1", ) ], ), ] ) )

correct_call_agent = create_agent( model=correct_call_model, tools=[fake_tool], response_format=ToolStrategy(Parrot, handle_errors=False) ) try: response = correct_call_agent.invoke( {"messages": [{"role": "user", "content": "Hello, Polly!!"}]} ) print("No exception raised for correct tool call!") assert("structured_response" in response) except StructuredOutputValidationError: print("Exception raised for correct tool call!")

Test for bad tool calls (name -> NOTname)

bad_call_model = FakeWithSO( messages=iter( [ AIMessage( content="A tool call", tool_calls=[ ToolCall( name="Parrot", args={ "NOTname": "Polly", "species": "Norwegian Blue", "response": "...", }, id="call_1", ) ], ), ] ) )

bad_call_agent = create_agent( model=bad_call_model, tools=[fake_tool], response_format=ToolStrategy(Parrot, handle_errors=False) ) try: response = bad_call_agent.invoke( {"messages": [{"role": "user", "content": "Hello, Polly!!"}]} ) print("No exception raised for bad tool call!") assert("structured_response" in response) except StructuredOutputValidationError: print("Exception raised for bad tool call!")

Test for no tool calls

no_call_model = FakeWithSO( messages=iter( [ AIMessage( content="NOT a tool call", ), ] ) )

no_call_agent = create_agent( model=no_call_model, tools=[fake_tool], response_format=ToolStrategy(Parrot, handle_errors=False) ) try: response = no_call_agent.invoke( {"messages": [{"role": "user", "content": "Hello, Polly!!"}]} ) print("No exception raised for no tool call!") assert("structured_response" in response) except StructuredOutputValidationError: print("Exception raised for no tool call!")

No exception raised for correct tool call!

Exception raised for bad tool call!

No exception raised for no tool call!

Root Cause

In the example posted I created three agents with different fake chat models (I had to adapt them so they would work with structured outputs, the issue I raised in #36277 ); one always makes correct tool calls, one makes bad tool calls and one makes no calls at all. With handle_errors=False, the second and third should raise an exception, but only the second does. The result is a response without a structured_response key, which can silently disrupt an entire workflow because of a single LLM mistake.

Fix Action

Fix / Workaround

  • This is a bug, not a usage question.
  • I added a clear and descriptive title that summarizes this issue.
  • I used the GitHub search to find a similar question and didn't find it.
  • I am sure that this is a bug in LangChain rather than my code.
  • The bug is not resolved by updating to the latest stable version of LangChain (or the specific integration package).
  • This is not related to the langchain-community package.
  • I posted a self-contained, minimal, reproducible example. A maintainer can copy it and run it AS IS.

Other Dependencies

httpx: 0.28.1 jsonpatch: 1.33 langgraph: 1.1.3 orjson: 3.11.7 packaging: 26.0 pydantic: 2.12.5 pyyaml: 6.0.3 requests: 2.33.0 requests-toolbelt: 1.0.0 tenacity: 9.1.4 typing-extensions: 4.15.0 uuid-utils: 0.14.1 xxhash: 3.6.0 zstandard: 0.25.0

PR fix notes

PR #36350: fix(agents): raise NoStructuredOutputError when model forgets to make tool calls

Description (problem / solution / changelog)

When using ToolStrategy for structured output, if the model fails to make any tool calls at all, the agent now raises NoStructuredOutputError instead of silently failing without a structured_response key.

Changes

  • Added NoStructuredOutputError exception class in structured_output.py
  • Modified _handle_model_output in factory.py to detect missing tool calls
  • Updated _make_model_to_tools_edge to loop back to model for retry when handle_errors=True and error feedback was added
  • Added comprehensive tests for the new functionality

Testing

All existing tests pass, plus new tests verify:

  1. NoStructuredOutputError is raised when no tool calls are made (handle_errors=False)
  2. Error message contains the AI message
  3. Retry works when handle_errors=True
  4. NoStructuredOutputError is a subclass of StructuredOutputError
  5. Retry middleware can catch and handle NoStructuredOutputError

Fixes #36349

Changed files

  • libs/langchain_v1/langchain/agents/factory.py (modified, +92/-68)
  • libs/langchain_v1/langchain/agents/structured_output.py (modified, +13/-0)
  • libs/langchain_v1/tests/unit_tests/agents/middleware/implementations/test_no_structured_output.py (added, +256/-0)

PR #36353: fix(langchain): raise NoToolCallError when ToolStrategy model returns no tool calls

Description (problem / solution / changelog)

Why

When ToolStrategy is set as the response_format for create_agent, the agent silently returns a response without a structured_response key if the model forgets to make a tool call altogether. The existing if block in factory.py only handles cases where output.tool_calls is non-empty, so a plain AIMessage with no tool calls falls through to return {"messages": [output]} with no error raised and no structured_response populated.

This is a silent failure that can disrupt entire agentic workflows without any indication of what went wrong.

Fixes #36349

Changes

  • structured_output.py: add NoToolCallError exception (subclass of StructuredOutputError) raised when ToolStrategy is active but the model returns no tool calls.
  • factory.py: add elif branch after the existing ToolStrategy handler to catch the no-tool-call case, route it through _handle_structured_output_error, and either raise the exception or return a SystemMessage prompt for retry — consistent with how MultipleStructuredOutputsError and StructuredOutputValidationError are handled.
  • test_response_format.py: add TestNoToolCallError with two tests — raise on handle_errors=False, retry and succeed on handle_errors=True.

Review notes

The change adds one new elif branch and mirrors the existing error-handling pattern exactly. No public API surfaces are changed beyond the addition of NoToolCallError to structured_output.py.

Changed files

  • libs/langchain_v1/langchain/agents/factory.py (modified, +20/-0)
  • libs/langchain_v1/langchain/agents/structured_output.py (modified, +13/-0)
  • libs/langchain_v1/tests/unit_tests/agents/test_response_format.py (modified, +52/-0)

PR #36355: fix: raise when structured output tool call is missing

Description (problem / solution / changelog)

Summary

Raise a structured-output error when a ToolStrategy response forgets to call the structured output tool, instead of silently returning an unstructured AI message.

Changes

  • add NoStructuredOutputError for missing structured output tool calls
  • raise that error when ToolStrategy(..., handle_errors=False) receives an AI message with no tool calls
  • add regression tests for both direct failure and middleware-driven retry recovery

Testing

  • uv run --project libs/langchain_v1 --group test pytest libs/langchain_v1/tests/unit_tests/agents/middleware/implementations/test_structured_output_retry.py -q

Fixes langchain-ai/langchain#36349

Changed files

  • libs/langchain_v1/langchain/agents/factory.py (modified, +11/-5)
  • libs/langchain_v1/langchain/agents/structured_output.py (modified, +15/-0)
  • libs/langchain_v1/tests/unit_tests/agents/middleware/implementations/test_structured_output_retry.py (modified, +76/-3)

Code Example

from collections.abc import Callable
from typing import Any, Sequence

from pydantic import BaseModel, Field

from langchain_core.runnables import Runnable
from langchain_core.tools import BaseTool
from langchain_core.messages import BaseMessage
from langchain_core.prompt_values import PromptValue
from langchain_core.language_models.fake_chat_models import GenericFakeChatModel

from langchain.tools import tool
from langchain.messages import AIMessage, ToolCall
from langchain.agents.structured_output import(
    StructuredOutputValidationError,
    ToolStrategy
)
from langchain.agents import create_agent


# To bind structured output tool to fake model
class FakeWithSO(GenericFakeChatModel):
    def bind_tools(
        self,
        tools: Sequence[dict[str, Any] | type | Callable[..., Any] | BaseTool],
        *,
        tool_choice: str | None = None, **kwargs: Any
        ) -> Runnable[PromptValue | str | Sequence[BaseMessage | list[str] | tuple[str, str] | str | dict[str, Any]], AIMessage]:
        return self.bind(tools=tools, tool_choice=tool_choice, **kwargs)


# Expected response format
class Parrot(BaseModel):
    name: str = Field(description="Parrot name.")
    species: str = Field(description="Parrot species.")
    response: str = Field(description="Parrot's response.")

# A fake tool
@tool
def fake_tool(x:str)-> str:
    """A fake tool."""
    return "fake!"



# Test for correct tool calls
correct_call_model = FakeWithSO(
    messages=iter(
        [
            AIMessage(
                content="A tool call",
                tool_calls=[
                    ToolCall(
                        name="Parrot",
                        args={
                            "name": "Polly",
                            "species": "Norwegian Blue",
                            "response": "...",
                        },
                        id="call_1",
                    )
                ],
            ),
        ]
    )
)

correct_call_agent = create_agent(
    model=correct_call_model,
    tools=[fake_tool],
    response_format=ToolStrategy(Parrot, handle_errors=False)
)
try:
    response = correct_call_agent.invoke(
        {"messages": [{"role": "user", "content": "Hello, Polly!!"}]}
    )
    print("No exception raised for correct tool call!")
    assert("structured_response" in response)
except StructuredOutputValidationError:
    print("Exception raised for correct tool call!")


# Test for bad tool calls (name -> NOTname)
bad_call_model = FakeWithSO(
    messages=iter(
        [
            AIMessage(
                content="A tool call",
                tool_calls=[
                    ToolCall(
                        name="Parrot",
                        args={
                            "NOTname": "Polly",
                            "species": "Norwegian Blue",
                            "response": "...",
                        },
                        id="call_1",
                    )
                ],
            ),
        ]
    )
)

bad_call_agent = create_agent(
    model=bad_call_model,
    tools=[fake_tool],
    response_format=ToolStrategy(Parrot, handle_errors=False)
)
try:
    response = bad_call_agent.invoke(
        {"messages": [{"role": "user", "content": "Hello, Polly!!"}]}
    )
    print("No exception raised for bad tool call!")
    assert("structured_response" in response)
except StructuredOutputValidationError:
    print("Exception raised for bad tool call!")


# Test for no tool calls
no_call_model = FakeWithSO(
    messages=iter(
        [
            AIMessage(
                content="NOT a tool call",
            ),
        ]
    )
)

no_call_agent = create_agent(
    model=no_call_model,
    tools=[fake_tool],
    response_format=ToolStrategy(Parrot, handle_errors=False)
)
try:
    response = no_call_agent.invoke(
        {"messages": [{"role": "user", "content": "Hello, Polly!!"}]}
    )
    print("No exception raised for no tool call!")
    assert("structured_response" in response)
except StructuredOutputValidationError:
    print("Exception raised for no tool call!")


# No exception raised for correct tool call!
# Exception raised for bad tool call!
# No exception raised for no tool call!

---

---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
Cell In[1], line 133
    129     response = no_call_agent.invoke(
    130         {"messages": [{"role": "user", "content": "Hello, Polly!!"}]}
    131     )
    132     print("No exception raised for no tool call!")
--> 133     assert("structured_response" in response)
    134 except StructuredOutputValidationError:
    135     print("Exception raised for no tool call!")

AssertionError:

---

# at langchain_v1/langchain/agents/structured_outputs.py
class NoStructuredOutputError(StructuredOutputError):
    """Raised when no structured output tool is called."""

    def __init__(self, ai_message: AIMessage) -> None:
        """Initialize `NoStructuredOutputError`.

        Args:
            ai_message: The AI message that contained the invalid structured output.
        """

        self.ai_message = ai_message
        super().__init__(f"No structured output tool called.")

---

elif (isinstance(effective_response_format, ToolStrategy)
            and isinstance(output, AIMessage)
        ):
            exception = NoStructuredOutputError(output)
            should_retry, error_message = _handle_structured_output_error(
                exception, effective_response_format
            )
            if not should_retry:
                raise exception
            
            return {
                "messages": [
                    output,
                    ToolMessage(
                    content=error_message,
                    ),
                ],
            }
RAW_BUFFERClick to expand / collapse

Checked other resources

  • This is a bug, not a usage question.
  • I added a clear and descriptive title that summarizes this issue.
  • I used the GitHub search to find a similar question and didn't find it.
  • I am sure that this is a bug in LangChain rather than my code.
  • The bug is not resolved by updating to the latest stable version of LangChain (or the specific integration package).
  • This is not related to the langchain-community package.
  • I posted a self-contained, minimal, reproducible example. A maintainer can copy it and run it AS IS.

Package (Required)

  • langchain
  • langchain-openai
  • langchain-anthropic
  • langchain-classic
  • langchain-core
  • langchain-model-profiles
  • langchain-tests
  • langchain-text-splitters
  • langchain-chroma
  • langchain-deepseek
  • langchain-exa
  • langchain-fireworks
  • langchain-groq
  • langchain-huggingface
  • langchain-mistralai
  • langchain-nomic
  • langchain-ollama
  • langchain-openrouter
  • langchain-perplexity
  • langchain-qdrant
  • langchain-xai
  • Other / not sure / general

Related Issues / PRs

#36277 #32951

Reproduction Steps / Example Code (Python)

from collections.abc import Callable
from typing import Any, Sequence

from pydantic import BaseModel, Field

from langchain_core.runnables import Runnable
from langchain_core.tools import BaseTool
from langchain_core.messages import BaseMessage
from langchain_core.prompt_values import PromptValue
from langchain_core.language_models.fake_chat_models import GenericFakeChatModel

from langchain.tools import tool
from langchain.messages import AIMessage, ToolCall
from langchain.agents.structured_output import(
    StructuredOutputValidationError,
    ToolStrategy
)
from langchain.agents import create_agent


# To bind structured output tool to fake model
class FakeWithSO(GenericFakeChatModel):
    def bind_tools(
        self,
        tools: Sequence[dict[str, Any] | type | Callable[..., Any] | BaseTool],
        *,
        tool_choice: str | None = None, **kwargs: Any
        ) -> Runnable[PromptValue | str | Sequence[BaseMessage | list[str] | tuple[str, str] | str | dict[str, Any]], AIMessage]:
        return self.bind(tools=tools, tool_choice=tool_choice, **kwargs)


# Expected response format
class Parrot(BaseModel):
    name: str = Field(description="Parrot name.")
    species: str = Field(description="Parrot species.")
    response: str = Field(description="Parrot's response.")

# A fake tool
@tool
def fake_tool(x:str)-> str:
    """A fake tool."""
    return "fake!"



# Test for correct tool calls
correct_call_model = FakeWithSO(
    messages=iter(
        [
            AIMessage(
                content="A tool call",
                tool_calls=[
                    ToolCall(
                        name="Parrot",
                        args={
                            "name": "Polly",
                            "species": "Norwegian Blue",
                            "response": "...",
                        },
                        id="call_1",
                    )
                ],
            ),
        ]
    )
)

correct_call_agent = create_agent(
    model=correct_call_model,
    tools=[fake_tool],
    response_format=ToolStrategy(Parrot, handle_errors=False)
)
try:
    response = correct_call_agent.invoke(
        {"messages": [{"role": "user", "content": "Hello, Polly!!"}]}
    )
    print("No exception raised for correct tool call!")
    assert("structured_response" in response)
except StructuredOutputValidationError:
    print("Exception raised for correct tool call!")


# Test for bad tool calls (name -> NOTname)
bad_call_model = FakeWithSO(
    messages=iter(
        [
            AIMessage(
                content="A tool call",
                tool_calls=[
                    ToolCall(
                        name="Parrot",
                        args={
                            "NOTname": "Polly",
                            "species": "Norwegian Blue",
                            "response": "...",
                        },
                        id="call_1",
                    )
                ],
            ),
        ]
    )
)

bad_call_agent = create_agent(
    model=bad_call_model,
    tools=[fake_tool],
    response_format=ToolStrategy(Parrot, handle_errors=False)
)
try:
    response = bad_call_agent.invoke(
        {"messages": [{"role": "user", "content": "Hello, Polly!!"}]}
    )
    print("No exception raised for bad tool call!")
    assert("structured_response" in response)
except StructuredOutputValidationError:
    print("Exception raised for bad tool call!")


# Test for no tool calls
no_call_model = FakeWithSO(
    messages=iter(
        [
            AIMessage(
                content="NOT a tool call",
            ),
        ]
    )
)

no_call_agent = create_agent(
    model=no_call_model,
    tools=[fake_tool],
    response_format=ToolStrategy(Parrot, handle_errors=False)
)
try:
    response = no_call_agent.invoke(
        {"messages": [{"role": "user", "content": "Hello, Polly!!"}]}
    )
    print("No exception raised for no tool call!")
    assert("structured_response" in response)
except StructuredOutputValidationError:
    print("Exception raised for no tool call!")


# No exception raised for correct tool call!
# Exception raised for bad tool call!
# No exception raised for no tool call!

Error Message and Stack Trace (if applicable)

---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
Cell In[1], line 133
    129     response = no_call_agent.invoke(
    130         {"messages": [{"role": "user", "content": "Hello, Polly!!"}]}
    131     )
    132     print("No exception raised for no tool call!")
--> 133     assert("structured_response" in response)
    134 except StructuredOutputValidationError:
    135     print("Exception raised for no tool call!")

AssertionError:

Description

The issue

I've created a few agentic pipelines, using the create_agent function from langchain.agents, and noticed that these agents will sometimes silently fail to generate a structured_response key in their response dictionary after a response_format is set (just like in the quickstart example from the docs). The messages show a response from the agent, but no structured_response key is generated. It is a problem very similar to the one raised in issue #352951 .

Replication and why it fails

The key word in the paragraph above is sometimes. The problem didn't always happened, and that is what made it hard to pin down and to replicate.

After some testing, I found what is most likely happening: the problem arises only when models fail to make an output tool call altogether. The system correctly addresses outputs with bad tool calls, but not outputs without any tool calls.

In the example posted I created three agents with different fake chat models (I had to adapt them so they would work with structured outputs, the issue I raised in #36277 ); one always makes correct tool calls, one makes bad tool calls and one makes no calls at all. With handle_errors=False, the second and third should raise an exception, but only the second does. The result is a response without a structured_response key, which can silently disrupt an entire workflow because of a single LLM mistake.

Possible cause

Looking at the code, I've found the possible cause at this if statement:

https://github.com/langchain-ai/langchain/blob/4d9842da67cbb4f6a8482dd7069fc6c0093748e6/libs/langchain_v1/langchain/agents/factory.py#L1056-L1129

It only checks and handles outputs with response formats and tool calls, ignoring cases when there should be a tool call, but there is none.

Proposed Solution

I am not completely familiar with the inner workings of the framework, but I believe simply appending a new case to the aforementioned if statement (and a new exception class) could solve the issue. Something like:

# at langchain_v1/langchain/agents/structured_outputs.py
class NoStructuredOutputError(StructuredOutputError):
    """Raised when no structured output tool is called."""

    def __init__(self, ai_message: AIMessage) -> None:
        """Initialize `NoStructuredOutputError`.

        Args:
            ai_message: The AI message that contained the invalid structured output.
        """

        self.ai_message = ai_message
        super().__init__(f"No structured output tool called.")

and

        elif (isinstance(effective_response_format, ToolStrategy)
            and isinstance(output, AIMessage)
        ):
            exception = NoStructuredOutputError(output)
            should_retry, error_message = _handle_structured_output_error(
                exception, effective_response_format
            )
            if not should_retry:
                raise exception
            
            return {
                "messages": [
                    output,
                    ToolMessage(
                    content=error_message,
                    ),
                ],
            }

I’d be happy to work on this and open a PR if this approach looks good.

System Info

System Information

OS: Linux OS Version: #106-Ubuntu SMP PREEMPT_DYNAMIC Fri Mar 6 07:58:08 UTC 2026 Python Version: 3.12.3 (main, Mar 3 2026, 12:15:18) [GCC 13.3.0]

Package Information

langchain_core: 1.2.22 langchain: 1.2.13 langsmith: 0.7.22 langgraph_sdk: 0.3.12

Optional packages not installed

deepagents deepagents-cli

Other Dependencies

httpx: 0.28.1 jsonpatch: 1.33 langgraph: 1.1.3 orjson: 3.11.7 packaging: 26.0 pydantic: 2.12.5 pyyaml: 6.0.3 requests: 2.33.0 requests-toolbelt: 1.0.0 tenacity: 9.1.4 typing-extensions: 4.15.0 uuid-utils: 0.14.1 xxhash: 3.6.0 zstandard: 0.25.0

extent analysis

Fix Plan

To fix the issue, we need to add a new case to handle the situation when no tool call is made. We will introduce a new exception class NoStructuredOutputError and modify the create_agent function to handle this new exception.

Step 1: Add a new exception class

Add the following code to langchain_v1/langchain/agents/structured_outputs.py:

class NoStructuredOutputError(StructuredOutputError):
    """Raised when no structured output tool is called."""

    def __init__(self, ai_message: AIMessage) -> None:
        """Initialize `NoStructuredOutputError`.

        Args:
            ai_message: The AI message that contained the invalid structured output.
        """

        self.ai_message = ai_message
        super().__init__(f"No structured output tool called.")

Step 2: Modify the create_agent function

Modify the create_agent function in langchain_v1/langchain/agents/factory.py to handle the new exception:

elif (isinstance(effective_response_format, ToolStrategy)
    and isinstance(output, AIMessage)
    and not output.tool_calls  # Check if there are no tool calls
):
    exception = NoStructuredOutputError(output)
    should_retry, error_message = _handle_structured_output_error(
        exception, effective_response_format
    )
    if not should_retry:
        raise exception

    return {
        "messages": [
            output,
            ToolMessage(
                content=error_message,
            ),
        ],
    }

Step 3: Update the handle_errors parameter

When creating an agent, set handle_errors=True to enable error handling for no tool calls:

agent = create_agent(
    model=model,
    tools=[fake_tool],
    response_format=ToolStrategy(Parrot, handle_errors=True)
)

Verification

To verify that the fix worked, run the test cases again and check that the NoStructuredOutputError exception is raised when no tool call is made.

Extra Tips

  • Make sure to update the langchain package to the latest version to ensure that the fix is included.
  • If you encounter any issues with the new exception class or the modified create_agent function, check the LangChain documentation and GitHub issues for troubleshooting guides.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

langchain - ✅(Solved) Fix Agents silently fail to deal with structured responses when models forget to make a tool call. [3 pull requests, 6 comments, 6 participants]