langchain-ai/deepagents · error · ValueError

tool_call_id must not be empty

Error message

tool_call_id must not be empty

What it means

A pydantic `field_validator` on `AutoDecision.tool_call_id` rejects empty string IDs. `AutoDecision` is the validated model for LLM-produced classifier decisions in auto mode; an empty `tool_call_id` would make the decision unmatchable to a pending tool call, so it is rejected at parse time.

Source

Thrown at libs/code/deepagents_code/auto_mode.py:278

    OTHER_POLICY = "other_policy"


class AutoDecision(BaseModel):
    """One structured classifier decision for a proposed tool call."""

    model_config = ConfigDict(extra="forbid")

    tool_call_id: str
    decision: Literal["allow", "deny"]
    category: AutoDecisionCategory
    reason: str

    @field_validator("tool_call_id")
    @classmethod
    def _nonempty_id(cls, value: str) -> str:
        if not value:
            msg = "tool_call_id must not be empty"
            raise ValueError(msg)
        return value

    @model_validator(mode="after")
    def _denial_has_reason(self) -> AutoDecision:
        if self.decision == "deny" and not self.reason.strip():
            msg = "deny decisions require a reason"
            raise ValueError(msg)
        return self


class AutoDecisionBatch(BaseModel):
    """Validated classifier response for one unresolved action batch."""

    model_config = ConfigDict(extra="forbid")

    decisions: list[AutoDecision]

View on GitHub (pinned to a1af029e6e)

Solutions

  1. Ensure every decision carries the non-empty tool_call_id echoed from the unresolved action batch.
  2. Harden the classifier prompt with an explicit schema/example showing the required id field.
  3. Validate the raw JSON before model construction and drop/retry entries lacking an id.
  4. Log the offending raw response to identify which batch item was malformed and retry that item.

Example fix

// before: model omits id
classifier_output = {"decision": "allow", "reason": "safe"}
AutoDecision(**classifier_output)  # ValueError
// after: validate before constructing
raw = classifier_output.get("tool_call_id", "")
if not raw:
    raise ClassifierProtocolError("missing tool_call_id in classifier output")
AutoDecision(**classifier_output)
Defensive patterns

Strategy: validation

Validate before calling

def _safe_decision(raw: dict) -> AutoDecision | None:
    if not raw.get("tool_call_id"):
        logger.warning("classifier dropped tool_call_id: %r", raw)
        return None  # skip / retry this item
    return AutoDecision(**raw)

Type guard

def _has_id(raw: object) -> TypeGuard[dict[str, str]]:
    return isinstance(raw, dict) and bool(str(raw.get("tool_call_id", "")).strip())

Try / catch

try:
    decision = AutoDecision(**raw)
except pydantic.ValidationError as exc:
    if any(e["msg"] == "Value error, tool_call_id must not be empty" for e in exc.errors()):
        decision = reclassify(raw["action"])  # retry the item
    else:
        raise

Prevention

When it happens

Trigger: Constructing AutoDecision(tool_call_id="", ...) directly, or parsing a classifier/LLM response batch into AutoDecision (AutoDecisionBatch) where a decision omits or blanks its tool_call_id.

Common situations: The model omitted the id field or emitted `""` in JSON output; prompt-template drift caused the classifier to drop the field; a mapping step overwrote the id with an empty default.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


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