Skip to main content

langchain-empty-response-retry

A small, model-agnostic LangChain agent middleware that retries with a short corrective nudge when the model returns a successful completion with no text and no tool calls.

Why

Models occasionally return an empty completion, most often at the end of long tool-calling chains. The agent loop treats it as a finished turn and the user receives nothing. Because no exception is raised, ModelRetryMiddleware never fires.

EmptyResponseRetryMiddleware detects this by content, not by provider-specific finish reasons, and retries the request with a short nudge.

Install

pip install langchain-empty-response-retry

Usage

from langchain.agents import create_agent
from langchain_core.tools import tool

from langchain_empty_response_retry import EmptyResponseRetryMiddleware


@tool
def search(query: str) -> str:
    """Search the knowledge base."""
    return "Refunds take 5 days."


agent = create_agent(
    model="openai:gpt-4o",  # any chat model
    tools=[search],
    middleware=[EmptyResponseRetryMiddleware(max_retries=2)],
)

result = agent.invoke({"messages": [{"role": "user", "content": "How long do refunds take?"}]})

What a retry looks like

On an empty completion the request is retried with one nudge appended. Only the accepted response is kept in the agent state.

model call 1: [Human: "How long do refunds take?"]                     -> AIMessage("")
model call 2: [Human: "How long do refunds take?",
               Human: "Your previous response was empty. Respond to ..."] -> AIMessage("About 5 days.")

final state:  [Human: "How long do refunds take?", AI: "About 5 days."]

Failing loudly or using a fallback reply

# Raise EmptyModelResponseError after the last retry:
EmptyResponseRetryMiddleware(max_retries=1, on_exhausted="error")

# Or reply with fixed text instead of an empty message:
EmptyResponseRetryMiddleware(on_exhausted="Sorry, I couldn't generate a reply. Please try again.")

How it works

On every model call the middleware:

  1. Calls the handler. A response is not empty if it has text, any tool_calls or invalid_tool_calls, or a structured_response. Reasoning-only responses count as empty.
  2. If empty, retries with original messages + [nudge]. The blank attempt is not included (some providers reject empty assistant turns) and nudges do not accumulate across attempts.
  3. When retries are exhausted, on_exhausted decides the outcome.

The predicate is exported for reuse:

from langchain_core.messages import AIMessage
from langchain_empty_response_retry import is_empty_ai_message

is_empty_ai_message(AIMessage(content="  "))  # True

Configuration

Constructor arg Default Description
max_retries 2 Retries after the first attempt. 0 disables retries but still applies on_exhausted.
nudge see DEFAULT_NUDGE Text of the corrective message.
nudge_role "human" "human" or "system". Mid-conversation system messages are rejected by some providers.
on_exhausted "return" "return" returns the last response unchanged, "error" raises EmptyModelResponseError, any other string is returned to the user as the reply.
is_empty None Custom predicate (AIMessage) -> bool replacing the default check.

Ordering

Middleware order is outermost first. Recommended placement:

  • Place inside any middleware that trims or rewrites message history, so the nudge is not trimmed away.
  • Put a ModelCallLimitMiddleware outermost as a hard ceiling; retries across several guards multiply model calls.

Limitations

  • Streaming: the retry happens after the first attempt has already streamed. Final state is correct, but a live stream_mode="messages" consumer may have seen the rejected attempt.
  • Does not fix blank responses caused by context overflow or hard safety blocks; retries will also be blank.
  • Each retry is a full model call. Keep max_retries small.

Development

pip install -e ".[dev]"
pytest

License

MIT

Metadata

Release files for langchain-empty-response-retry 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for langchain-empty-response-retry 0.1.0
File Size Uploaded Provenance Actions
langchain_empty_response_retry-0.1.0.tar.gz 7.7 kB Without provenance Details for langchain_empty_response_retry-0.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for langchain-empty-response-retry 0.1.0
File Interpreter ABI Platform Actions
langchain_empty_response_retry-0.1.0-py3-none-any.whl Python 3 none any Details for langchain_empty_response_retry-0.1.0-py3-none-any.whl

Total release size: 14.7 kB

Release files / langchain_empty_response_retry-0.1.0.tar.gz

Download URL langchain_empty_response_retry-0.1.0.tar.gz
Size 7.7 kB
Tags Source
SHA-256 checksum
How to use checksums
f467cd93e4ca24ffd7fc91238daf779df108e230936ee033d030a8bf3fb10738
BLAKE2b-256 checksum
How to use checksums
555823c42f097fc1826f66e64d8bdf10c12c51a6565f35d4f396a998df42188c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.10

Release files / langchain_empty_response_retry-0.1.0-py3-none-any.whl

Download URL langchain_empty_response_retry-0.1.0-py3-none-any.whl
Size 7.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
21bece0ac060f607706624007c576bd7029e323de695f5505c5de16362026a69
BLAKE2b-256 checksum
How to use checksums
8dec32f1d4a64740ac7c3d693acc7fa6ea645abcdcf9b07e95048eca8285ab13
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.10

Security for langchain-empty-response-retry 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page