Hello
I noticed that the Injected arguments do not get stripped before trustcall verification causing verification issues.
For example using an InjectedState, InjectedToolCallId or InjectedToolArgs in the tool arguments will trigger a verification of these fields even if parsed as Optional.
Here is the example of the handoff_tool from langgraph documentation:
def create_handoff_tool(*, agent_name: str, description: str | None = None):
name = f"transfer_to_{agent_name}"
description = description or f"Transfer to {agent_name}"
@tool(name, description=description)
def handoff_tool(
state: Optional[Annotated[AgentState, InjectedState]] = None,
tool_call_id: Optional[Annotated[str, InjectedToolCallId]] = None,
) -> Command:
tool_message = {
"role": "tool",
"content": f"Successfully transferred to {agent_name}",
"name": name,
"tool_call_id": tool_call_id,
}
return Command(
goto=agent_name,
update={"messages": state["messages"] + [tool_message]},
graph=Command.PARENT,
)
return handoff_tool
The (truncated) error it produces:
"Error:\n\n\nExpected either a dictionary with a 'type' key or an object with a 'type' attribute. Instead got type <class 'dict'>.\n\nExpected Parameter Schema:\n\n```json\n{'$defs': {'AIMessage': {'additionalProperties': True, 'description': 'Message from an AI.....
In other tools, injecting the state is also problematic but this time it can be avoided by marking it as Optional:
@tool("SomeTool")
def some_tool(
some_argument: Type,
state: Optional[
Annotated[AgentState, InjectedState]
] = None, # Optional or else trustcall will try to generate it itself
) -> Dict[str, Any]:
I have noticed that in _base.py this snippet of code that seems responsible for stripping the injected tool arguments is not called for both my tools mentioned above:
def csff_(function: Callable) -> Type[BaseModel]:
fn = _strip_injected(function)
schema = create_schema_from_function(function.__name__, fn)
schema.__name__ = function.__name__
return schema
used there:
def ensure_tools(
tools: Sequence[TOOL_T],
) -> List[Union[BaseTool, Type[BaseModel], Callable]]:
results: list = []
for t in tools:
if isinstance(t, dict):
print(f"DEBUG: dict: {t}")
if all(k in t for k in ("name", "description", "parameters")):
schema = create_model_from_schema(
{"title": t["name"], **t["parameters"]}
)
schema.__doc__ = (getattr(schema, __doc__, "") or "") + (
t.get("description") or ""
)
schema.__name__ = t["name"]
results.append(schema)
elif all(k in t for k in ("type", "function")):
# Already in openai format
resolved = ensure_tools([t["function"]])
results.extend(resolved)
else:
model = create_model_from_schema(t)
if not model.__doc__:
model.__doc__ = t.get("description") or model.__name__
results.append(model)
elif is_typeddict(t):
results.append(_convert_any_typed_dicts_to_pydantic(cast(type, t)))
elif isinstance(t, (BaseTool, type)):
# All my tools end up here and are not stripped of their injected args <-----------------------
results.append(t)
elif callable(t):
# This section calls the stripping function <-----------------------
results.append(csff_(t))
else:
raise ValueError(f"Invalid tool type: {type(t)}")
return list(results)
I use trustcall using the create_extractor method:
bound_decision_llm = create_extractor(
llm,
tools=[some_tool, transfer_to_call_model],
tool_choice="any",
)
Do you have any insights on this behavior? Is it a bug or am I missing something in my implementation?
Best regards,
Dimitri
Hello
I noticed that the Injected arguments do not get stripped before trustcall verification causing verification issues.
For example using an
InjectedState,InjectedToolCallIdorInjectedToolArgsin the tool arguments will trigger a verification of these fields even if parsed asOptional.Here is the example of the
handoff_toolfrom langgraph documentation:The (truncated) error it produces:
In other tools, injecting the state is also problematic but this time it can be avoided by marking it as
Optional:I have noticed that in
_base.pythis snippet of code that seems responsible for stripping the injected tool arguments is not called for both my tools mentioned above:used there:
I use trustcall using the
create_extractormethod:Do you have any insights on this behavior? Is it a bug or am I missing something in my implementation?
Best regards,
Dimitri