{"record":{"id":"8d3dfb0698ed2121","repo":"langchain-ai/deepagents","slug":"tool-call-id-must-not-be-empty","errorCode":null,"errorMessage":"tool_call_id must not be empty","messagePattern":"tool_call_id must not be empty","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"libs/code/deepagents_code/auto_mode.py","lineNumber":278,"sourceCode":"    OTHER_POLICY = \"other_policy\"\n\n\nclass AutoDecision(BaseModel):\n    \"\"\"One structured classifier decision for a proposed tool call.\"\"\"\n\n    model_config = ConfigDict(extra=\"forbid\")\n\n    tool_call_id: str\n    decision: Literal[\"allow\", \"deny\"]\n    category: AutoDecisionCategory\n    reason: str\n\n    @field_validator(\"tool_call_id\")\n    @classmethod\n    def _nonempty_id(cls, value: str) -> str:\n        if not value:\n            msg = \"tool_call_id must not be empty\"\n            raise ValueError(msg)\n        return value\n\n    @model_validator(mode=\"after\")\n    def _denial_has_reason(self) -> AutoDecision:\n        if self.decision == \"deny\" and not self.reason.strip():\n            msg = \"deny decisions require a reason\"\n            raise ValueError(msg)\n        return self\n\n\nclass AutoDecisionBatch(BaseModel):\n    \"\"\"Validated classifier response for one unresolved action batch.\"\"\"\n\n    model_config = ConfigDict(extra=\"forbid\")\n\n    decisions: list[AutoDecision]\n\n","sourceCodeStart":260,"sourceCodeEnd":296,"githubUrl":"https://github.com/langchain-ai/deepagents/blob/a1af029e6e73cb17c36bff823d227747b28e91e1/libs/code/deepagents_code/auto_mode.py#L260-L296","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: model omits id\nclassifier_output = {\"decision\": \"allow\", \"reason\": \"safe\"}\nAutoDecision(**classifier_output)  # ValueError\n// after: validate before constructing\nraw = classifier_output.get(\"tool_call_id\", \"\")\nif not raw:\n    raise ClassifierProtocolError(\"missing tool_call_id in classifier output\")\nAutoDecision(**classifier_output)","handlingStrategy":"validation","validationCode":"def _safe_decision(raw: dict) -> AutoDecision | None:\n    if not raw.get(\"tool_call_id\"):\n        logger.warning(\"classifier dropped tool_call_id: %r\", raw)\n        return None  # skip / retry this item\n    return AutoDecision(**raw)","typeGuard":"def _has_id(raw: object) -> TypeGuard[dict[str, str]]:\n    return isinstance(raw, dict) and bool(str(raw.get(\"tool_call_id\", \"\")).strip())","tryCatchPattern":"try:\n    decision = AutoDecision(**raw)\nexcept pydantic.ValidationError as exc:\n    if any(e[\"msg\"] == \"Value error, tool_call_id must not be empty\" for e in exc.errors()):\n        decision = reclassify(raw[\"action\"])  # retry the item\n    else:\n        raise","preventionTips":["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."],"tags":["pydantic","validation","llm-output","auto-mode"],"backgroundTag":"schema-validation-failed","analyzedSha":"a1af029e6e73cb17c36bff823d227747b28e91e1","analyzedAt":"2026-08-29T11:43:24.718Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}