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
- Ensure every decision carries the non-empty tool_call_id echoed from the unresolved action batch.
- Harden the classifier prompt with an explicit schema/example showing the required id field.
- Validate the raw JSON before model construction and drop/retry entries lacking an id.
- 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
- Echo tool_call_id verbatim from the unresolved batch into the classifier prompt and require it in the output schema.
- Parse classifier output with AutoDecisionBatch (which validates) rather than trusting raw dicts.
- Log raw malformed responses to catch prompt regressions early.
- Add a retry/fallback for individual malformed items instead of failing the whole batch.
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
- deny decisions require a reason
- Classifier result did not contain exactly one decision per r
- trusted thread, turn, and tool-call identity are required
- Auto mode requires every proposed tool call to have an ID
- Auto mode rejects action batches with duplicate tool-call ID
AI-assisted analysis of langchain-ai/deepagents@a1af029e6e (2026-08-29).
Data as JSON: /api/errors/8d3dfb0698ed2121.
Report an issue: GitHub.