{"record":{"id":"eaa8607f090da9a1","repo":"langchain-ai/langchain","slug":"when-tool-includes-an-injectedtoolcallid-argument","errorCode":null,"errorMessage":"When tool includes an InjectedToolCallId argument, tool must always be invoked with a full model ToolCall of the form: {'args': {...}, 'name': '...', 'type': 'tool_call', 'tool_call_id': '...'}","messagePattern":"When tool includes an InjectedToolCallId argument, tool must always be invoked with a full model ToolCall of the form: (.+?), 'name': '\\.\\.\\.', 'type': 'tool_call', 'tool_call_id': '\\.\\.\\.'\\}","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"libs/core/langchain_core/tools/base.py","lineNumber":834,"sourceCode":"            return tool_input\n\n        if input_args is not None:\n            if isinstance(input_args, dict):\n                return tool_input\n            result: BaseModel | BaseModelV1\n            if issubclass(input_args, BaseModel):\n                # Check args_schema for InjectedToolCallId\n                for k, v in get_all_basemodel_annotations(input_args).items():\n                    if _is_injected_arg_type(v, injected_type=InjectedToolCallId):\n                        if tool_call_id is None:\n                            msg = (\n                                \"When tool includes an InjectedToolCallId \"\n                                \"argument, tool must always be invoked with a full \"\n                                \"model ToolCall of the form: {'args': {...}, \"\n                                \"'name': '...', 'type': 'tool_call', \"\n                                \"'tool_call_id': '...'}\"\n                            )\n                            raise ValueError(msg)\n                        tool_input[k] = tool_call_id\n                result_v2 = input_args.model_validate(tool_input)\n                result_dict = result_v2.model_dump()\n                provided_fields = result_v2.model_fields_set\n                result = result_v2\n            elif issubclass(input_args, BaseModelV1):\n                # Check args_schema for InjectedToolCallId\n                for k, v in get_all_basemodel_annotations(input_args).items():\n                    if _is_injected_arg_type(v, injected_type=InjectedToolCallId):\n                        if tool_call_id is None:\n                            msg = (\n                                \"When tool includes an InjectedToolCallId \"\n                                \"argument, tool must always be invoked with a full \"\n                                \"model ToolCall of the form: {'args': {...}, \"\n                                \"'name': '...', 'type': 'tool_call', \"\n                                \"'tool_call_id': '...'}\"\n                            )\n                            raise ValueError(msg)","sourceCodeStart":816,"sourceCodeEnd":852,"githubUrl":"https://github.com/langchain-ai/langchain/blob/e32fa9a52eab3b61ad7a45399bfde59b3e580fc4/libs/core/langchain_core/tools/base.py#L816-L852","documentation":"If a tool's `args_schema` declares a field annotated `InjectedToolCallId`, langchain treats the tool as tool-calling-aware and requires invocation to carry the originating `ToolCall` (specifically a non-None `tool_call_id`), which it injects into that field. Invoking such a tool without `tool_call_id` (e.g. plain dict input, or `tool.invoke({'x': 1})`) raises this ValueError before validation completes.","triggerScenarios":"Defining `class Args(BaseModel): tool_call_id: Annotated[str, InjectedToolCallId]` and calling `tool.invoke({'query': 'hi'})` directly; passing a `ToolCall` dict that lacks `'tool_call_id'`; manually replaying tool calls without preserving the id.","commonSituations":"Testing InjectedToolCallId tools outside a tool-calling agent loop; copying tool-call examples that build partial `ToolCall` dicts; calling artifact-style tools (which need the call id to store artifacts) via `.invoke()` with only the args dict.","solutions":["Invoke with a full tool call object or dict: `tool.invoke({'args': {...}, 'name': 't', 'type': 'tool_call', 'tool_call_id': 'abc123'})` (or a `ToolCall` pydantic object).","In tests, fabricate the tool call with a dummy id: `ToolCall(name='t', args={...}, id='test-id', type='tool_call')`.","If you truly don't need the call id, remove the `InjectedToolCallId` annotation from the schema."],"exampleFix":"# before\ntool.invoke({'query': 'hello'})  # schema has InjectedToolCallId field\n# after\ntool.invoke(\n    {'name': 'search', 'args': {'query': 'hello'}, 'type': 'tool_call', 'tool_call_id': 'call_123'}\n)","handlingStrategy":"validation","validationCode":"from typing import get_type_hints\nfrom langchain_core.tools import InjectedToolCallId\n\ndef requires_tool_call_id(tool) -> bool:\n    schema = tool.args_schema\n    if schema is None or isinstance(schema, dict):\n        return False\n    hints = get_type_hints(schema)\n    return any(\n        getattr(h, '__metadata__', ()) and any(\n            isinstance(m, type) and issubclass(m, InjectedToolCallId)\n            for m in h.__metadata__\n        )\n        for h in hints.values() if hasattr(h, '__metadata__')\n    )\n\n# before direct invocation\nif requires_tool_call_id(tool):\n    assert tool_call_id is not None, 'this tool needs a full ToolCall'","typeGuard":"def is_full_tool_call(obj) -> bool:\n    if isinstance(obj, dict):\n        return obj.get('type') == 'tool_call' and bool(obj.get('tool_call_id')) and 'args' in obj\n    return getattr(obj, 'type', None) == 'tool_call' and bool(getattr(obj, 'id', None))","tryCatchPattern":"try:\n    out = tool.invoke(tool_input)\nexcept ValueError as e:\n    if 'InjectedToolCallId' in str(e):\n        out = tool.invoke({\n            'name': tool.name, 'args': tool_input, 'type': 'tool_call',\n            'tool_call_id': f'call_{uuid4().hex[:8]}',\n        })\n    else:\n        raise","preventionTips":["Invoke InjectedToolCallId tools with full ToolCall objects carrying an id.","In tests, fabricate dummy tool_call_ids rather than calling with bare args.","Only annotate a field InjectedToolCallId if the tool is used inside a tool-calling agent loop."],"tags":["tool","tool-calling","injected-tool-call-id","agents"],"backgroundTag":null,"analyzedSha":"e32fa9a52eab3b61ad7a45399bfde59b3e580fc4","analyzedAt":"2026-08-14T18:42:09.092Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}