langchain-ai/langchain · error · ValueError

When tool includes an InjectedToolCallId argument, tool must

Error message

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': '...'}

What it means

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.

Source

Thrown at libs/core/langchain_core/tools/base.py:834

            return tool_input

        if input_args is not None:
            if isinstance(input_args, dict):
                return tool_input
            result: BaseModel | BaseModelV1
            if issubclass(input_args, BaseModel):
                # Check args_schema for InjectedToolCallId
                for k, v in get_all_basemodel_annotations(input_args).items():
                    if _is_injected_arg_type(v, injected_type=InjectedToolCallId):
                        if tool_call_id is None:
                            msg = (
                                "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': '...'}"
                            )
                            raise ValueError(msg)
                        tool_input[k] = tool_call_id
                result_v2 = input_args.model_validate(tool_input)
                result_dict = result_v2.model_dump()
                provided_fields = result_v2.model_fields_set
                result = result_v2
            elif issubclass(input_args, BaseModelV1):
                # Check args_schema for InjectedToolCallId
                for k, v in get_all_basemodel_annotations(input_args).items():
                    if _is_injected_arg_type(v, injected_type=InjectedToolCallId):
                        if tool_call_id is None:
                            msg = (
                                "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': '...'}"
                            )
                            raise ValueError(msg)

View on GitHub (pinned to e32fa9a52e)

Solutions

  1. 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).
  2. In tests, fabricate the tool call with a dummy id: `ToolCall(name='t', args={...}, id='test-id', type='tool_call')`.
  3. If you truly don't need the call id, remove the `InjectedToolCallId` annotation from the schema.

Example fix

# before
tool.invoke({'query': 'hello'})  # schema has InjectedToolCallId field
# after
tool.invoke(
    {'name': 'search', 'args': {'query': 'hello'}, 'type': 'tool_call', 'tool_call_id': 'call_123'}
)
Defensive patterns

Strategy: validation

Validate before calling

from typing import get_type_hints
from langchain_core.tools import InjectedToolCallId

def requires_tool_call_id(tool) -> bool:
    schema = tool.args_schema
    if schema is None or isinstance(schema, dict):
        return False
    hints = get_type_hints(schema)
    return any(
        getattr(h, '__metadata__', ()) and any(
            isinstance(m, type) and issubclass(m, InjectedToolCallId)
            for m in h.__metadata__
        )
        for h in hints.values() if hasattr(h, '__metadata__')
    )

# before direct invocation
if requires_tool_call_id(tool):
    assert tool_call_id is not None, 'this tool needs a full ToolCall'

Type guard

def is_full_tool_call(obj) -> bool:
    if isinstance(obj, dict):
        return obj.get('type') == 'tool_call' and bool(obj.get('tool_call_id')) and 'args' in obj
    return getattr(obj, 'type', None) == 'tool_call' and bool(getattr(obj, 'id', None))

Try / catch

try:
    out = tool.invoke(tool_input)
except ValueError as e:
    if 'InjectedToolCallId' in str(e):
        out = tool.invoke({
            'name': tool.name, 'args': tool_input, 'type': 'tool_call',
            'tool_call_id': f'call_{uuid4().hex[:8]}',
        })
    else:
        raise

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of langchain-ai/langchain@e32fa9a52e (2026-08-14). Data as JSON: /api/errors/eaa8607f090da9a1. Report an issue: GitHub.