Skip to content

Conversation

eyurtsev
Copy link
Collaborator

@eyurtsev eyurtsev commented Oct 6, 2025

Still WIP:

  • Need to update the API in the middleware

Changes:

  • Goal is to remove ToolNode from accepted types in create_agent, but re-introduce support for tool handling. (Removing ToolNode as it's not obvious how it should interact with tools brought by middleware).
  • Adds an interceptor ToolNode
  • Adds an middleware on_tool_call
  • Allows composing middlewares for handling tool calls.
from langchain.agents.middleware import RetryMiddleware

# Retry network errors up to 3 times with 1 second delay
retry = RetryMiddleware(
    max_retries=3,
    delay=1.0,
    retry_on=(TimeoutError, ConnectionError),
)

agent = create_agent(
    model="openai:gpt-4o",
    tools=[my_tool],
    middleware=[retry],
)
class SimpleRetryMiddleware(AgentMiddleware):
    def __init__(self, max_retries=3, delay=1.0):
        super().__init__()
        self.max_retries = max_retries
        self.delay = delay

    def on_tool_call(self, request):
        for attempt in range(self.max_retries + 1):
            response = yield request

            if response.action == "return":
                return response

            if response.action == "raise":
                if attempt < self.max_retries:
                    time.sleep(self.delay)
                    continue
                return response

@github-actions github-actions bot added langchain Related to the package `langchain` feature v1 Issue specific to LangChain 1.0 labels Oct 6, 2025
Copy link
Collaborator

@sydney-runkle sydney-runkle left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initial pass, just looking at types

)


def _infer_retriable_types(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't actually need this right?

"""Response returned from on_tool_call handler after tool execution.

The action field determines control flow:
- "return": Handler completed successfully, use result
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

continue?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah i've been debatinc continue vs. return

Comment on lines 138 to 145
def __post_init__(self) -> None:
"""Validate that required fields are present based on action."""
if self.action == "return" and self.result is None:
msg = "action='return' requires a result"
raise ValueError(msg)
if self.action == "raise" and self.exception is None:
msg = "action='raise' requires an exception"
raise ValueError(msg)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like

from typing import Literal
from typing_extensions TypedDict, NotRequired

class _ContinueToolCallResponse(TypedDict):
    """Response when tool execution completed successfully.
    
    Attributes:
        action: Always "continue" to indicate normal flow.
        result: ToolMessage or Command to be processed.
        exception: Optional exception for logging purposes.
    """
    action: Literal["continue"]
    result: ToolMessage | Command
    exception: NotRequired[Exception]

class _RaiseToolCallResponse(TypedDict):
    """Response when tool execution wants to propagate an exception.
    
    Attributes:
        action: Always "raise" to indicate exception propagation.
        exception: The exception to be raised.
    """
    action: Literal["raise"]
    exception: Exception

ToolCallResponse = _ContinueToolCallResponse | _RaiseToolCallResponse

@@ -100,6 +98,61 @@ def my_tool(x: int) -> str:
)


@dataclass()
class ToolRequest:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ToolCallRequest

Copy link
Collaborator

@sydney-runkle sydney-runkle left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

more fun thoughts :)

return (Exception,)


class RetryMiddleware(AgentMiddleware):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
class RetryMiddleware(AgentMiddleware):
class ToolRetryMiddleware(AgentMiddleware):

@langchain-ai langchain-ai deleted a comment from sydney-runkle Oct 6, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature langchain Related to the package `langchain` v1 Issue specific to LangChain 1.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants