langchain-ai/deepagents · error · ValueError

call_id and failed_attempt must be provided together

Error message

call_id and failed_attempt must be provided together

What it means

build_retry_event constructs a model_retry event dict and supports optional retry-correlation fields: call_id and failed_attempt. These two fields are only meaningful as a pair, so providing exactly one raises ValueError('call_id and failed_attempt must be provided together'). This guarantees every retry event either carries the full legacy shape or complete correlation data for downstream matching.

Source

Thrown at libs/code/deepagents_code/model_retry.py:919

        attempt: The 1-indexed retry number about to be attempted.
        max_retries: The configured maximum retry count.
        call_id: Opaque ID correlating every attempt of one model call. Omit
            for producers that predate attempt lifecycle events.
        failed_attempt: The 0-indexed attempt being superseded. Required to
            carry `call_id`.
        output_may_have_started: Whether the superseded attempt may have put
            message output beyond server control. Conservative by design: the
            tracker flags before forwarding a chunk.

    Returns:
        A stream-writer payload consumed by the client renderers.

    Raises:
        ValueError: If only one of `call_id` and `failed_attempt` is given.
    """
    if (call_id is None) != (failed_attempt is None):
        msg = "call_id and failed_attempt must be provided together"
        raise ValueError(msg)
    event: dict[str, object] = {
        "type": "model_retry",
        "attempt": attempt,
        "max_retries": max_retries,
        "message": format_retry_status(attempt, max_retries),
    }
    if call_id is not None:
        event["call_id"] = call_id
        event["failed_attempt"] = failed_attempt
        event["output_may_have_started"] = output_may_have_started
    return event


def build_attempt_event(call_id: str, attempt: int, *, phase: str) -> dict[str, object]:
    """Build the custom-stream payload marking one model attempt boundary.

    Args:
        call_id: Opaque ID shared by every attempt of one model call.

View on GitHub (pinned to a1af029e6e)

Solutions

  1. Pass both call_id and failed_attempt together (or neither) when building the event.
  2. If the call_id is unknown, omit both fields to emit the legacy-shape event instead of a partial one.
  3. Fix call sites/test fixtures to source both values from the same retry attempt record.

Example fix

// before
build_retry_event(attempt=2, max_retries=3, call_id="call-1")  # ValueError
// after
build_retry_event(attempt=2, max_retries=3, call_id="call-1", failed_attempt=2)
Defensive patterns

Strategy: validation

Validate before calling

if (call_id is None) != (failed_attempt is None):
    raise SystemExit("Pass call_id and failed_attempt together, or neither")

Try / catch

try:
    event = build_retry_event(attempt, max_retries, call_id=call_id, failed_attempt=failed_attempt)
except ValueError:
    event = build_retry_event(attempt, max_retries)  # legacy shape

Prevention

When it happens

Trigger: Calling build_retry_event(attempt, max_retries, call_id="abc") without failed_attempt, or failed_attempt=2 without call_id — the XOR check `(call_id is None) != (failed_attempt is None)` fires.

Common situations: Refactoring code that started passing call_id but not the new failed_attempt parameter (or vice versa); constructing the event from partially available data when a call hasn't been assigned an ID yet; test fixtures updated incompletely.

Related errors


AI-assisted analysis of langchain-ai/deepagents@a1af029e6e (2026-08-29). Data as JSON: /api/errors/d35ba72c5b1cbd04. Report an issue: GitHub.