847 Commits
Author SHA1 Message Date
ce9066138d fix(anthropic): support Claude Sonnet 5.5 compatibility (#40882)
Co-authored-by: Hunter Lovell <hntrl@users.noreply.github.com>
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
Co-authored-by: ccurme <ccurme@users.noreply.github.com>
Co-authored-by: Chester Curme <chester.curme@gmail.com>
2026-09-29 09:05:37 -04:00
19cadaa1a1 fix(core): abbreviate long tool IDs in XML buffer strings (#40792)
Co-authored-by: ccurme <ccurme@users.noreply.github.com>
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-09-23 15:36:26 -04:00
ccurmeandopen-swe[bot] 3290757339 chore(core): deprecate chat message history (#40711)
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-09-21 10:28:24 -04:00
Jacob Lee e0e1557bd6 feat(core): Allow model name and provider tracing metadata override based on gateway response (#40406)
With custom configs, the actual model name and provider the gateway
actually runs are different from what are requested. This reconciles
clientside tracing.
2026-09-11 10:13:02 -07:00
Bob Ok 16a58955e4 test(core): cover the deprecated .text() access path (#40243) 2026-09-06 09:32:58 -04:00
ccurme 3f212e7a88 feat(openai): support async tools (#40208) 2026-09-04 15:49:14 -04:00
ccurmeandHotragn Pettugani e92c8a08bf fix(core): avoid mutation in google-genai standard content (#40023)
Co-authored-by: Hotragn Pettugani <103170876+Hotragn@users.noreply.github.com>
2026-08-29 22:44:25 -04:00
ccurmeandZhewen Tan 36f0d10348 fix(core): avoid mutation in bedrock converse standard content (#40022)
Co-authored-by: Zhewen Tan <127607634+tandede@users.noreply.github.com>
2026-08-29 22:16:32 -04:00
ccurme 38e211359f fix(core): shore up indexing in genai v1 streaming content (#39964) 2026-08-27 12:37:49 -04:00
Emil F 13b1b2feae fix(core): make StructuredTool JSON-serializable (#39631)
## Problem

`StructuredTool` cannot be dumped to JSON:

```python
@tool
def write_file(file_path: str, content: str) -> str:
    """Write content to the given path."""
    return "ok"

write_file.model_dump(mode="json")
# PydanticSerializationError: Unable to serialize unknown type:
#   <class 'pydantic._internal._model_construction.ModelMetaclass'>
```

`args_schema` holds a Pydantic model class, and `func` / `coroutine`
hold callables. None of them have a JSON form. Python-mode
`model_dump()` works; only the JSON modes raise.

This also costs tracing performance. The [LangSmith
SDK](https://github.com/langchain-ai/langsmith-sdk/blob/main/python/langsmith/_internal/_serde.py)
catches the error, dumps again in Python mode, and then sends every
class and function left in the result through its own `default` hook.

## Change

A `PlainSerializer(..., when_used="json-unless-none")` on the three
fields.

- `args_schema` dumps as its own JSON schema: `model_json_schema()` for
a Pydantic v2 class, `schema()` for a v1 one. A dict schema passes
through unchanged. A schema holding an arbitrary type has no JSON schema
at all, so it falls back to its repr instead of raising.
- `func` and `coroutine` dump as strings.
- Python-mode dumps are unchanged — still the live schema class and
callables.
- `exclude` / `include` / `exclude_none` keep working.
- Attached with `Annotated`, not `@field_serializer`. A field has only
one serializer slot, so `@field_serializer` would break any subclass
that declares its own serializer for the same field.
- Schema generation is cached per class. Pydantic does not memoize it,
and tracing would pay for it on every run.

## Result

The dump is JSON-native, and the schema it carries has the same shape a
dict `args_schema` already has, so it validates back into a working
tool.

Measured on the 8 filesystem tools of a deepagents agent, dumped through
the LangSmith serializer:

| | master | this PR |
|---|---|---|
| time per dump | 0.119 ms | **0.025 ms** |
| payload | 7,706 B | 12,213 B |
| objects reaching the SDK's `default` hook | 32 | 8 |

The payload grows because `args_schema` now carries the real schema
instead of `"<class ...>"`. Only the tool itself still enters the
`default` hook; the class and functions inside it no longer do. Without
the per-class cache the same dump takes 1.10 ms, so the cache is what
makes this a win rather than a regression.

## Why not on `BaseTool`

`args_schema` is declared there as well, so `Tool` and custom subclasses
hit the same error. But an `Annotated` serializer only applies where the
field is declared, and `StructuredTool` redeclares `args_schema` — it
would not inherit one from `BaseTool`.

## Tests

In `libs/core/tests/unit_tests/test_tools.py`: JSON round trip, Python
mode unchanged, dump options respected, dict `args_schema` preserved,
Pydantic v1 schema class, arbitrary-type fallback, and a subclass
declaring its own serializers for the same fields.
2026-08-27 17:50:53 +02:00
Eugene Yurtsev ed0ad742e9 feat(core): propagate gateway information on error path (#39829)
This PR plumbs through gateway metadata information for exceptions

<img width="2380" height="956" alt="image"
src="https://github.com/user-attachments/assets/c3b3644e-3022-421c-b447-6ab6603021e4"
/>
2026-08-21 14:33:30 -04:00
Mason Daugherty 5c3538e83a fix(core): resolve postponed annotations in StructuredTool._injected_args_keys (#39602)
Closes #39568

Related: #33999

> [!WARNING]
> This PR expands the surface area for arbitrary code execution during
tool setup. Detecting injected arguments now requires calling
`typing.get_type_hints`, which *evaluates* string annotations (e.g.
those created by `from __future__ import annotations` or quoted forward
references) as Python expressions. As with existing type-hint resolution
paths, wrapped tool callables must be trusted application code — never
point `StructuredTool` at a callable whose module or annotations come
from an untrusted source.

Tools with a custom `args_schema` could drop injected arguments such as
`ToolRuntime` when the wrapped function's module uses postponed
annotations. The injected value was removed during input validation, so
an otherwise valid tool call failed at invocation.

---

`StructuredTool` now resolves annotations with
`typing.get_type_hints(..., include_extras=True)` before identifying
injected parameters. If an unrelated forward reference prevents
resolving the complete signature, each string annotation is resolved
independently so resolvable injected arguments are still preserved.
Callable wrappers resolve annotations from the source of their effective
signature, honoring `__wrapped__` and `__signature__`, while other
callable objects use their `__call__` method. `functools.partial`
callables retain their effective signature so already-bound injected
arguments remain excluded.

<details>
<summary><b>Before/after:</b> injected arg dropped under <code>from
__future__ import annotations</code></summary>

With postponed annotations, every annotation is stored as a plain
string. Previously `_injected_args_keys` read the raw `signature()`
annotations, so `runtime` was never recognized as injected and was
stripped during `args_schema` validation:

```python
from __future__ import annotations  # all annotations become strings

from pydantic import BaseModel
from langchain_core.tools import tool, ToolRuntime

class InputSchema(BaseModel):
    query: str

@tool(args_schema=InputSchema)
def my_tool(query: str, runtime: ToolRuntime) -> str:
    """Echo the query."""
    return query
```

| | Behavior |
|---|---|
| **Before** | `runtime` not detected as injected → removed during
validation → tool call fails at invocation |
| **After** | `runtime` detected via `get_type_hints` → survives
validation and is injected at invocation; hidden from the model-facing
schema |

</details>

<details>
<summary><b>Before/after:</b> one unresolvable annotation disabling
injection for the whole signature</summary>

`get_type_hints` resolves *all* annotations at once and raises on the
first failure. A single unresolvable forward reference — even on an
unrelated parameter — previously meant *no* hints were available, so the
resolvable injected arg was dropped too:

```python
@tool(args_schema=InputSchema)
def my_tool(
    query: "SomeTypeThatDoesNotExist",  # unresolvable forward reference
    runtime: "ToolRuntime",             # resolvable injected arg
) -> str:
    """Echo the query."""
    return query
```

| | Behavior |
|---|---|
| **Before** | `get_type_hints` raises on `query` → all hints discarded
→ `runtime` not detected as injected |
| **After** | each annotation is retried independently → `query` falls
back to its raw string (not injected), `runtime` still resolves and is
injected |

</details>

<details>
<summary><b>Before/after:</b> callable objects and wrappers</summary>

For non-function callables, the annotations now come from the source of
the *effective* signature: `__call__` for callable objects, and the
wrapped function for wrappers (`__wrapped__` / `__signature__`):

```python
class MyCallableTool:
    def __call__(self, query: str, runtime: ToolRuntime) -> str:
        return query

tool = StructuredTool.from_function(
    func=MyCallableTool(),
    name="my_tool",
    description="Echo the query.",
    args_schema=InputSchema,
)
```

| | Behavior |
|---|---|
| **Before** | annotations read from the wrong callable (or left as
unresolved strings) → `runtime` dropped |
| **After** | annotations resolved from `__call__` / the unwrapped
function → `runtime` injected correctly |

</details>

<details>
<summary><b>Unchanged:</b> <code>functools.partial</code> with an
already-bound injected arg</summary>

A `partial` that already binds an injected argument keeps its effective
signature — the bound parameter is absent, so nothing is re-injected
over it:

```python
from functools import partial

def fn(x: int, runtime: ToolRuntime, y: int) -> int:
    return x + y

tool = StructuredTool.from_function(
    func=partial(fn, 1, bound_runtime),
    name="fn",
    description="Add two numbers.",
    args_schema=InputSchema,
)
```

**Before & after:** `runtime` is already bound by the `partial` →
excluded from the signature → the bound value is used as-is

</details>

Co-authored-by: Soban Shankar
<165470467+Soban-2004@users.noreply.github.com>
2026-08-19 11:38:30 -04:00
gaoanze888 ded2a1fb3c fix(core): allow deserializing RunnablePick (#39753) 2026-08-19 10:45:58 -04:00
04ae7447d7 fix(core): make convert_to_openai_function handle callables and non-dict mappings (#39750)
Co-authored-by: gaoanze <gaoanze@meituan.com>
Co-authored-by: Chester Curme <chester.curme@gmail.com>
2026-08-19 10:39:18 -04:00
c8b2d767bf fix(core): make subprocess and temporary file tests portable on Windows (#39664)
Co-authored-by: Pu Jingnan <149932541+Puuuuup@users.noreply.github.com>
Co-authored-by: Chester Curme <chester.curme@gmail.com>
2026-08-18 17:34:30 -04:00
Hunter Lovell b5e8e2e85e fix(core): fail fast when tool schemas can't resolve forward refs during serialization (#39570)
fixes #39099

We currently allow forward refs in pydantic v2 schemas upon creation:

```python
class Container(BaseModel):
    rows: list["Row"] = [] # "Row" is declared below, after the tool is decorated

@tool
def my_tool(container: Container):
    """A tool whose schema depends on a forward reference that is not resolvable yet."""
    return "ok"

class Row(BaseModel):
    name: str
```

When it comes time to introspect the tool schema (notably in
`count_tokens_approximately` and `convert_to_openai_tool`), we rely on
[signature
introspection](https://github.com/langchain-ai/langchain/blob/943dd700ef7c33e3f1f21d3e280c9c249b88259c/libs/core/langchain_core/tools/base.py#L1654-L1661)
to extract the tool's input schema. If that contains invalid forward
references, there's no schema fields to extract which results in an
empty dict:

<details>

<summary>Invalid forward reference MRE</summary>

```python
from __future__ import annotations

import inspect

from pydantic import BaseModel, Field
from pydantic.errors import PydanticUndefinedAnnotation

from langchain_core.tools.base import get_all_basemodel_annotations
from langchain_core.utils.pydantic import _create_subset_model, model_json_schema


class Container(BaseModel):
    """A model with a nested forward reference that can never resolve."""

    rows: list["UndefinedRow"] = Field(default_factory=list)


def main() -> None:
    """Print the field-selection inputs and their zero-field subset result."""
    selected_annotations = get_all_basemodel_annotations(Container)
    subset_schema = _create_subset_model(
        "ContainerSubset",
        Container,
        list(selected_annotations),
        fn_description=Container.__doc__,
    )

    print(f"Pydantic complete: {Container.__pydantic_complete__}")
    print(f"Pydantic fields: {list(Container.model_fields)}")
    print(f"inspect.signature: {inspect.signature(Container)}")
    print(f"Fields selected by get_all_basemodel_annotations: {selected_annotations}")
    print(f"Subset properties: {model_json_schema(subset_schema)['properties']}")


if __name__ == "__main__":
    main()
```

```output
Pydantic complete: False
Pydantic fields: ['rows']
inspect.signature: (**data: 'Any') -> 'None'
Fields selected by get_all_basemodel_annotations: {}
Subset properties: {}
```

</details>

<details>

<summary>Valid forward reference MRE</summary>

```python
from __future__ import annotations

import inspect

from pydantic import BaseModel, Field
from pydantic.errors import PydanticUndefinedAnnotation

from langchain_core.tools.base import get_all_basemodel_annotations
from langchain_core.utils.pydantic import _create_subset_model, model_json_schema


class Container(BaseModel):
    """A model with a nested forward reference that can never resolve."""

    rows: list["UndefinedRow"] = Field(default_factory=list)

class UndefinedRow(BaseModel):
    name: str = Field()


def main() -> None:
    """Print the field-selection inputs and their zero-field subset result."""
    Container.model_rebuild()
    selected_annotations = get_all_basemodel_annotations(Container)
    subset_schema = _create_subset_model(
        "ContainerSubset",
        Container,
        list(selected_annotations),
        fn_description=Container.__doc__,
    )

    print(f"Pydantic complete: {Container.__pydantic_complete__}")
    print(f"Pydantic fields: {list(Container.model_fields)}")
    print(f"inspect.signature: {inspect.signature(Container)}")
    print(f"Fields selected by get_all_basemodel_annotations: {selected_annotations}")
    print(f"Subset properties: {model_json_schema(subset_schema)['properties']}")


if __name__ == "__main__":
    main()

```

```output
Pydantic complete: True
Pydantic fields: ['rows']
inspect.signature: (*, rows: list[__main__.UndefinedRow] = <factory>) -> None
Fields selected by get_all_basemodel_annotations: {'rows': list[__main__.UndefinedRow]}
Subset properties: {'rows': {'items': {'$ref': '#/$defs/UndefinedRow'}, 'title': 'Rows', 'type': 'array'}}
```
</details>

---

The fix is to
* at introspection time, resolve forward references using
`.model_rebuild()` that raises a pydantic exception if forward
references cant be resolved
* i'm also widening a pydantic utility to use a type guard instead of
having to use bool + cast

I'm intentionally not rebuilding pydantic v1 schemas in the same way
since
* forward references are specified by explicitly passing names into
`update_forward_refs`
* pydantic v1 is old news
2026-08-18 14:08:52 -07:00
Mason Daughertyandopen-swe[bot] 72fb0090bd test(core): avoid version-dependent runnable snapshots (#39705)
Runnable snapshots currently embed the exact `langchain-core` version,
forcing unrelated snapshot rewrites during every release. Normalize only
the current `VERSION` to a stable placeholder before comparison, so
missing or stale version metadata still fails.

Made by [Open
SWE](https://openswe.vercel.app/agents/bfd72574-359e-544e-dbf5-78f8bae3636a)

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-08-18 16:16:21 -04:00
65e5e3cfa3 fix(core): require all nested properties for strict tool schemas (#39306)
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
Co-authored-by: Chester Curme <chester.curme@gmail.com>
Co-authored-by: dkundu56 <188023074+dkundu56@users.noreply.github.com>
Co-authored-by: Andrea Rossi <6909990+AndRossi@users.noreply.github.com>
2026-08-18 17:08:57 +00:00
94509faaed fix(core): remove stale sync-stream xfail [closes #39720] (#39723)
Co-authored-by: PAVAN KUMAR S <239303217+pufuki@users.noreply.github.com>
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-08-18 12:57:41 -04:00
Lingjin 9c21d84bcb fix(core): accept non-dict Mapping values in mustache templates (#39680) 2026-08-17 20:29:33 -04:00
James Yang 300eb71549 fix(core): finalize chain-group runs on BaseException (#39699) 2026-08-17 19:52:46 -04:00
Eugene Yurtsev 4033a4eb7f chore(core): release 1.5.6 (#39704)
Release 1.5.6
2026-08-17 21:00:09 +00:00
5650448a03 feat(core): incorporate gateway metadata to traces (#39703)
This PR sends gateway metadata information (if present in the client
response) as metadata for the llm invocation.

Requires changes corresponding changes in the ChatModel implementations
(e.g., ChatOpenAI) so gateway metadata is picked up from the gateway
response headers.

---------

Signed-off-by: Eugene Yurtsev <eugene@langchain.dev>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: eyurtsev <3205522+eyurtsev@users.noreply.github.com>
2026-08-17 20:18:26 +00:00
ccurme 555702e1c6 release(core): 1.5.5 (#39655) 2026-08-14 14:13:15 -04:00
pxmps f9ee55d94c fix(core): make abatch_iterate consistent with batch_iterate for None and zero size (#39367) 2026-08-13 17:27:44 -04:00
Hunter Lovell cde529347a fix(core): respect pydantic aliases when validating tool inputs (#39572) 2026-08-13 09:19:50 -04:00
Nishitha Mandopen-swe[bot] a648460cfd fix(core): issues in merging chunks (#39535)
Closes #38064, #35259, #38850

`merge_dicts`, `merge_lists`, and `AddableDict` all guessed at merge
semantics for streaming chunks in ways that silently corrupted data
instead of failing loudly:

- `merge_dicts` fell into the `int` branch for differing `bool` values
(since `bool` subclasses `int`) and summed them, turning `True + False`
into `1`. It now raises `TypeError`, consistent with other unmergeable
types.
- `merge_lists` used `"index" in e_left` on untyped list elements; when
an element was a plain `str` containing the literal substring `"index"`,
it then subscripted the string and raised an unrelated `TypeError`. It
now checks `isinstance(e_left, dict)` first.
- `AddableDict.__add__`/`__radd__` silently discarded the left-hand
value on any `TypeError` from `chunk[key] + other[key]`. They now
re-raise a `TypeError` naming the key and both types.

### Release note

`merge_dicts` now raises `TypeError` for differing boolean values at the
same key instead of silently summing them to an `int`; `AddableDict`
addition now raises `TypeError` on type-incompatible keys instead of
silently dropping data; `merge_lists` no longer misidentifies non-dict
elements as index-keyed.

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-08-12 17:40:35 -04:00
Hunter Lovell 13b2f8d727 fix(core): handle v1 base model validation in async path (#39576) 2026-08-12 15:49:24 -04:00
Hunter Lovelland王俊锋 624fd031c2 fix(core): handle tool descriptions for infer_schema=False (#39573)
Co-authored-by: 王俊锋 <37211900+kinch-tech@users.noreply.github.com>
2026-08-12 15:22:04 -04:00
ccurmeandSolaris-star 0a86934ab5 fix(core): clear usage metadata callback on exceptions in context manager (#39616)
Co-authored-by: Solaris-star <820622658@qq.com>
2026-08-12 13:32:51 -04:00
Hunter Lovell b6eccc5f97 fix(core): handle falsy LLM and chat model caches (#39283) 2026-08-12 12:39:16 -04:00
zerafachris 7cdf9658f0 fix(core): preserve non-str/non-dict items in DictPromptTemplate list values (#39588) 2026-08-11 21:32:33 -04:00
Rin 61c5678835 fix(core): raise ValueError when explicit tool_outputs length mismatches tool_calls in tool_example_to_messages (#39142) 2026-08-11 18:54:59 -04:00
Willow Lopez e34cd76346 fix(core): guard malformed Anthropic content blocks (#38670) 2026-08-11 18:50:39 -04:00
ccurme 5ff19c613a release(core): 1.5.4 (#39592) 2026-08-11 13:43:45 -04:00
ccurmeandPRINCE KUMAR MAURYA 9f738f5297 fix(core): compat with pydantic 2.14 (#39328)
Co-authored-by: PRINCE KUMAR MAURYA <218506877+alwaysprince05@users.noreply.github.com>
2026-08-09 11:46:45 -04:00
幻andJony 1499aa9900 fix(core): stop StructuredPrompt from mutating caller kwargs (#39174)
Co-authored-by: Jony <13896935+zyz619963502zyz@users.noreply.github.com>
2026-08-07 22:20:14 -04:00
Nishitha Mandshusnapx 0424e03dc7 fix(core): preserve flat tool args schema for RootModel runnables (#39307)
Closes #38713

Fixes `Runnable.as_tool()` advertising `TypedDict` inputs under a
synthetic `root` key.

When a runnable's `input_schema` is a `RootModel` (e.g. compiled
`StateGraph`s), the tool schema is now built from the runnable's type
hints instead, keeping the advertised schema consistent with the flat
input the runnable actually expects.

---------

Co-authored-by: shusnapx <ashu.kumarexam@gmail.com>
2026-08-07 14:22:15 -04:00
Mason Daugherty a6b904fd5b fix(core): close internally created event loops in streaming tracers (#39222)
Fixed a resource leak in the `astream_events` and `astream_log`
streaming tracers: when constructed from synchronous code on Python
3.14+, an internally created event loop was never closed, causing a
`ResourceWarning: unclosed event loop` to be emitted at
garbage-collection time (and intermittent failures of warning-sensitive
tests such as `test_chat_prompt_template_variable_names`). The loop is
now closed when the handler is garbage collected.
2026-08-03 12:08:44 -04:00
ccurmeandMiguel Ingram ffef4e7d05 fix(core): preserve OpenAI file blocks (#39205)
Co-authored-by: Miguel Ingram <miguel.ingram.research@gmail.com>
2026-08-02 16:53:35 -04:00
ccurmeandonyx679 d6b0c82fde fix(core): handle injected args for subclasses of BaseTool (#39202)
Co-authored-by: onyx679 <126763931+onyx679@users.noreply.github.com>
2026-08-02 16:38:55 -04:00
ccurmeandindexedakki 576b6fa060 fix(core): respect include_injected=False with filter_args (#39200)
Co-authored-by: indexedakki <74480055+indexedakki@users.noreply.github.com>
2026-08-02 15:49:05 -04:00
John Kennedyandjkennedyvz 4cc6231785 fix(core): redact streaming callback options (#39179)
Credential-bearing model kwargs can be sanitized by an integration's
`_get_invocation_params`, but streaming callbacks also received the
original kwargs through `options`. This could expose remote MCP
credentials to callback handlers and persisted LangSmith traces even
when `invocation_params` was redacted.

Streaming and v3 streaming-event callbacks now construct `options` from
the integration-sanitized invocation parameters while the model
invocation continues to receive the original values. Sync and async
regression coverage verifies both callback redaction and provider
propagation.

## Release note

Streaming chat-model callbacks no longer receive unsanitized invocation
options when an integration redacts sensitive parameters.

This contribution was prepared with AI-agent assistance.

Co-authored-by: jkennedyvz <jkennedyvz@users.noreply.github.com>
2026-07-31 15:18:58 -07:00
Nick Hollon a78daf0233 fix(core): type text stream projections (#39170)
Types sync and async text stream projections as strings.
2026-07-31 14:49:02 -04:00
ccurme 01d8481ae3 release(core): 1.5.3 (#39145) 2026-07-30 10:40:19 -04:00
ccurme cdca311de2 fix(core): fall back to LANGSMITH_API_KEY for gateway (#39115) 2026-07-28 20:59:08 -04:00
ccurme c1ab807b1f release(core): 1.5.2 (#39108) 2026-07-28 12:30:42 -04:00
ccurme 55af8a4b88 fix(core): handle empty string in gateway env vars (#39107) 2026-07-28 09:14:22 -04:00
ccurme eb705ca4e8 release(core): 1.5.1 (#39042) 2026-07-23 15:57:23 -04:00
ccurme d35a4ef183 feat(anthropic,fireworks,openai): support langsmith gateway through env var (#38742) 2026-07-23 15:49:06 -04:00