F1 · Denies that a LangChain tool has any place of its own for provider-specific fields, then rebuilds the tool definition by hand — the verdict, the artefact and the attribution all wrong in the same direction
langchain 1.2.0 shipped 2025-12-15 (the artifact, langchain-core 1.2.0, on 2025-12-12); this draw states a May 2026 cutoff, five months later and not the same month, so the fairness rule and the same-month bar both clear. The surface is above this subject's measured boundary on this library (1.0.0, 2025-10-17). This is the pre-registered charging arm and tasks 1, 4 and 6 are pre-registered probes on this surface. One finding, three artefacts (JOURNAL/062): the pre-registration graded the verdict (task 1) and the artefact (task 4) independently, and the attribution question (task 6) asked the same belief a third way; all three came back wrong in the same direction, which is one belief measured three times. Scored S3, not LF26's S2, because the workaround this draw shipped was executed against the installed packages and it works — see impact.
Task 1, single word on its own line: "no", glossed "To my knowledge there is no per-tool, first-class, documented slot for provider-specific fields.
BaseToolhasmetadata, but that is callback/tracing metadata and is never serialized into the tool definition sent to the provider. The route people actually use is to handbind_toolsa raw dict instead of the tool object." Task 6, on which release first gave a tool such a place: "To my knowledge, no release has done so. Through everything I can describe, provider-specific fields reach the wire only by bypassing the tool object — a raw dict handed tobind_tools/.bind." The MECHANISM line the battery required at the end of task 4: "bind_tools(raw Anthropic tool dict passed through;.bind(tools=...)as the strict-passthrough fallback)". The draw hedged its own answer accurately in both places — "this is exactly the kind of thing that could have been added and that I would not reliably know about", and "if something like aprovider_extras/extra_bodyslot onBaseToolor@toollanded after my reliable recall window, I would not know it, and given how much churn this area has seen I would not bet heavily against it" — which is a calibrated hedge attached to a wrong verdict, not a retraction of it.
# The artefact from task 4: the capability is denied, so the tool definition is
# rebuilt by hand and the provider fields are spliced into the copy.
from langchain_core.utils.function_calling import convert_to_openai_tool
@tool
def search_docs(query: str) -> str:
"""Search the internal documentation and return matching passages."""
return f"...results for {query}..."
fn = convert_to_openai_tool(search_docs)["function"]
anthropic_tool = {
"name": fn["name"],
"description": fn["description"],
"input_schema": fn["parameters"],
"cache_control": {"type": "ephemeral"},
"defer_loading": True,
}
llm = ChatAnthropic(model="claude-sonnet-4-5-20250929").bind_tools([anthropic_tool])
ai = llm.invoke("What do the docs say about retry backoff?")
# The bound dict is only the schema; execution still goes through the @tool object.
tools_by_name = {search_docs.name: search_docs}
for call in ai.tool_calls:
tool_message = tools_by_name[call["name"]].invoke(call)@tool(extras={"cache_control": {"type": "ephemeral"}, "defer_loading": True})
def search_docs(query: str) -> str:
"""Search the internal documentation and return matching passages."""
...
llm = ChatAnthropic(model="claude-sonnet-4-5").bind_tools([search_docs])Executed against the installed packages on 2026-09-06 (langchain-core 1.6.2, langchain-anthropic 1.7.1), and the result is why this is S3 rather than LF26's S2. convert_to_anthropic_tool on this draw's hand-built dict returns {'name': 'search_docs', 'description': ..., 'input_schema': {...}, 'cache_control': {'type': 'ephemeral'}, 'defer_loading': True} — both provider fields do reach the wire, because AnthropicTool is a TypedDict and an already-Anthropic-shaped dict is copied whole. So the denial does not silently drop the provider instruction the way LF26's three recorded wrong forms do (@tool(extras={'anthropic': {...}}) was executed in the same session and both fields were dropped by the _ANTHROPIC_EXTRA_FIELDS filter). What the denial costs is the thing the draw itself spelled out: the tool is now defined twice, once as a callable and once as a schema, the name has to be kept in sync by hand or dispatch breaks, and a separate tools_by_name lookup is needed to execute the call the model returns — all of it replaced by ten characters of extras= since December 2025. The draw also predicted its own uncertainty about the passthrough ("whether ChatAnthropic.bind_tools passes unknown keys on an already-Anthropic-shaped dict through verbatim... if it strips them, the guaranteed-passthrough fallback is model.bind(tools=[...])"): it does pass them through.
- LangChain changelog — langchain v1.2.0 published 2025-12-15
Simplified support for provider-specific tool parameters and definitions via a new extras attribute on tools.
- langchain-core 1.2.0 published wheel — langchain_core/tools/base.py:499, the release that adds the field published 2025-12-12
extras: dict[str, Any] | None = None
- langchain-core 1.1.3 published wheel — the release below the window, where the field is absent from tools/base.py and from every tool() overload published 2025-12-09
response_format: Literal["content", "content_and_artifact"] = "content",
- langchain-anthropic 1.7.1 published wheel — chat_models.py, convert_to_anthropic_tool reading tool.extras onto the wire published 2026-09-03
for key, value in tool.extras.items(): if key in _ANTHROPIC_EXTRA_FIELDS: # all are populated top-level anthropic_formatted[key] = value