Skip to content

Commit 754528d

Browse files
authored
feat(langchain): add stuff and map reduce chains (#32333)
* Add stuff and map reduce chains * We'll need to rename and add unit tests to the chains prior to official release
1 parent ac706c7 commit 754528d

File tree

13 files changed

+1416
-12
lines changed

13 files changed

+1416
-12
lines changed

libs/langchain_v1/langchain/_internal/__init__.py

Whitespace-only changes.
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
"""Internal document utilities."""
2+
3+
from __future__ import annotations
4+
5+
from typing import TYPE_CHECKING
6+
7+
if TYPE_CHECKING:
8+
from langchain_core.documents import Document
9+
10+
11+
def format_document_xml(doc: Document) -> str:
12+
"""Format a document as XML-like structure for LLM consumption.
13+
14+
Args:
15+
doc: Document to format
16+
17+
Returns:
18+
Document wrapped in XML tags:
19+
<document>
20+
<id>...</id>
21+
<content>...</content>
22+
<metadata>...</metadata>
23+
</document>
24+
25+
Note:
26+
Does not generate valid XML or escape special characters.
27+
Intended for semi-structured LLM input only.
28+
"""
29+
id_str = f"<id>{doc.id}</id>" if doc.id is not None else "<id></id>"
30+
metadata_str = ""
31+
if doc.metadata:
32+
metadata_items = [f"{k}: {v!s}" for k, v in doc.metadata.items()]
33+
metadata_str = f"<metadata>{', '.join(metadata_items)}</metadata>"
34+
return (
35+
f"<document>{id_str}"
36+
f"<content>{doc.page_content}</content>"
37+
f"{metadata_str}"
38+
f"</document>"
39+
)
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
"""Lazy import utilities."""
2+
3+
from importlib import import_module
4+
from typing import Union
5+
6+
7+
def import_attr(
8+
attr_name: str,
9+
module_name: Union[str, None],
10+
package: Union[str, None],
11+
) -> object:
12+
"""Import an attribute from a module located in a package.
13+
14+
This utility function is used in custom __getattr__ methods within __init__.py
15+
files to dynamically import attributes.
16+
17+
Args:
18+
attr_name: The name of the attribute to import.
19+
module_name: The name of the module to import from. If None, the attribute
20+
is imported from the package itself.
21+
package: The name of the package where the module is located.
22+
"""
23+
if module_name == "__module__" or module_name is None:
24+
try:
25+
result = import_module(f".{attr_name}", package=package)
26+
except ModuleNotFoundError:
27+
msg = f"module '{package!r}' has no attribute {attr_name!r}"
28+
raise AttributeError(msg) from None
29+
else:
30+
try:
31+
module = import_module(f".{module_name}", package=package)
32+
except ModuleNotFoundError as err:
33+
msg = f"module '{package!r}.{module_name!r}' not found ({err})"
34+
raise ImportError(msg) from None
35+
result = getattr(module, attr_name)
36+
return result
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
"""Internal prompt resolution utilities.
2+
3+
This module provides utilities for resolving different types of prompt specifications
4+
into standardized message formats for language models. It supports both synchronous
5+
and asynchronous prompt resolution with automatic detection of callable types.
6+
7+
The module is designed to handle common prompt patterns across LangChain components,
8+
particularly for summarization chains and other document processing workflows.
9+
"""
10+
11+
from __future__ import annotations
12+
13+
import inspect
14+
from typing import TYPE_CHECKING, Callable, Union
15+
16+
if TYPE_CHECKING:
17+
from collections.abc import Awaitable
18+
19+
from langchain_core.messages import MessageLikeRepresentation
20+
from langgraph.runtime import Runtime
21+
22+
from langchain._internal._typing import ContextT, StateT
23+
24+
25+
def resolve_prompt(
26+
prompt: Union[
27+
str,
28+
None,
29+
Callable[[StateT, Runtime[ContextT]], list[MessageLikeRepresentation]],
30+
],
31+
state: StateT,
32+
runtime: Runtime[ContextT],
33+
default_user_content: str,
34+
default_system_content: str,
35+
) -> list[MessageLikeRepresentation]:
36+
"""Resolve a prompt specification into a list of messages.
37+
38+
Handles prompt resolution across different strategies. Supports callable functions,
39+
string system messages, and None for default behavior.
40+
41+
Args:
42+
prompt: The prompt specification to resolve. Can be:
43+
- Callable: Function taking (state, runtime) returning message list.
44+
- str: A system message string.
45+
- None: Use the provided default system message.
46+
state: Current state, passed to callable prompts.
47+
runtime: LangGraph runtime instance, passed to callable prompts.
48+
default_user_content: User content to include (e.g., document text).
49+
default_system_content: Default system message when prompt is None.
50+
51+
Returns:
52+
List of message dictionaries for language models, typically containing
53+
a system message and user message with content.
54+
55+
Raises:
56+
TypeError: If prompt type is not str, None, or callable.
57+
58+
Example:
59+
```python
60+
def custom_prompt(state, runtime):
61+
return [{"role": "system", "content": "Custom"}]
62+
63+
messages = resolve_prompt(custom_prompt, state, runtime, "content", "default")
64+
messages = resolve_prompt("Custom system", state, runtime, "content", "default")
65+
messages = resolve_prompt(None, state, runtime, "content", "Default")
66+
```
67+
68+
Note:
69+
Callable prompts have full control over message structure and content
70+
parameter is ignored. String/None prompts create standard system + user
71+
structure.
72+
"""
73+
if callable(prompt):
74+
return prompt(state, runtime)
75+
if isinstance(prompt, str):
76+
system_msg = prompt
77+
elif prompt is None:
78+
system_msg = default_system_content
79+
else:
80+
msg = f"Invalid prompt type: {type(prompt)}. Expected str, None, or callable."
81+
raise TypeError(msg)
82+
83+
return [
84+
{"role": "system", "content": system_msg},
85+
{"role": "user", "content": default_user_content},
86+
]
87+
88+
89+
async def aresolve_prompt(
90+
prompt: Union[
91+
str,
92+
None,
93+
Callable[[StateT, Runtime[ContextT]], list[MessageLikeRepresentation]],
94+
Callable[
95+
[StateT, Runtime[ContextT]], Awaitable[list[MessageLikeRepresentation]]
96+
],
97+
],
98+
state: StateT,
99+
runtime: Runtime[ContextT],
100+
default_user_content: str,
101+
default_system_content: str,
102+
) -> list[MessageLikeRepresentation]:
103+
"""Async version of resolve_prompt supporting both sync and async callables.
104+
105+
Handles prompt resolution across different strategies. Supports sync/async callable
106+
functions, string system messages, and None for default behavior.
107+
108+
Args:
109+
prompt: The prompt specification to resolve. Can be:
110+
- Callable (sync): Function taking (state, runtime) returning message list.
111+
- Callable (async): Async function taking (state, runtime) returning
112+
awaitable message list.
113+
- str: A system message string.
114+
- None: Use the provided default system message.
115+
state: Current state, passed to callable prompts.
116+
runtime: LangGraph runtime instance, passed to callable prompts.
117+
default_user_content: User content to include (e.g., document text).
118+
default_system_content: Default system message when prompt is None.
119+
120+
Returns:
121+
List of message dictionaries for language models, typically containing
122+
a system message and user message with content.
123+
124+
Raises:
125+
TypeError: If prompt type is not str, None, or callable.
126+
127+
Example:
128+
```python
129+
async def async_prompt(state, runtime):
130+
return [{"role": "system", "content": "Async"}]
131+
132+
def sync_prompt(state, runtime):
133+
return [{"role": "system", "content": "Sync"}]
134+
135+
messages = await aresolve_prompt(
136+
async_prompt, state, runtime, "content", "default"
137+
)
138+
messages = await aresolve_prompt(
139+
sync_prompt, state, runtime, "content", "default"
140+
)
141+
messages = await aresolve_prompt("Custom", state, runtime, "content", "default")
142+
```
143+
144+
Note:
145+
Callable prompts have full control over message structure and content
146+
parameter is ignored. Automatically detects and handles async
147+
callables.
148+
"""
149+
if callable(prompt):
150+
result = prompt(state, runtime)
151+
# Check if the result is awaitable (async function)
152+
if inspect.isawaitable(result):
153+
return await result
154+
return result
155+
if isinstance(prompt, str):
156+
system_msg = prompt
157+
elif prompt is None:
158+
system_msg = default_system_content
159+
else:
160+
msg = f"Invalid prompt type: {type(prompt)}. Expected str, None, or callable."
161+
raise TypeError(msg)
162+
163+
return [
164+
{"role": "system", "content": system_msg},
165+
{"role": "user", "content": default_user_content},
166+
]
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
"""Private typing utilities for langchain."""
2+
3+
from __future__ import annotations
4+
5+
from typing import TYPE_CHECKING, Any, ClassVar, Protocol, TypeVar, Union
6+
7+
from langgraph.graph._node import StateNode
8+
from pydantic import BaseModel
9+
from typing_extensions import TypeAlias
10+
11+
if TYPE_CHECKING:
12+
from dataclasses import Field
13+
14+
15+
class TypedDictLikeV1(Protocol):
16+
"""Protocol to represent types that behave like TypedDicts.
17+
18+
Version 1: using `ClassVar` for keys.
19+
"""
20+
21+
__required_keys__: ClassVar[frozenset[str]]
22+
__optional_keys__: ClassVar[frozenset[str]]
23+
24+
25+
class TypedDictLikeV2(Protocol):
26+
"""Protocol to represent types that behave like TypedDicts.
27+
28+
Version 2: not using `ClassVar` for keys.
29+
"""
30+
31+
__required_keys__: frozenset[str]
32+
__optional_keys__: frozenset[str]
33+
34+
35+
class DataclassLike(Protocol):
36+
"""Protocol to represent types that behave like dataclasses.
37+
38+
Inspired by the private _DataclassT from dataclasses that uses a similar
39+
protocol as a bound.
40+
"""
41+
42+
__dataclass_fields__: ClassVar[dict[str, Field[Any]]]
43+
44+
45+
StateLike: TypeAlias = Union[TypedDictLikeV1, TypedDictLikeV2, DataclassLike, BaseModel]
46+
"""Type alias for state-like types.
47+
48+
It can either be a `TypedDict`, `dataclass`, or Pydantic `BaseModel`.
49+
Note: we cannot use either `TypedDict` or `dataclass` directly due to limitations in
50+
type checking.
51+
"""
52+
53+
StateT = TypeVar("StateT", bound=StateLike)
54+
"""Type variable used to represent the state in a graph."""
55+
56+
ContextT = TypeVar("ContextT", bound=Union[StateLike, None])
57+
"""Type variable for context types."""
58+
59+
60+
__all__ = [
61+
"ContextT",
62+
"StateLike",
63+
"StateNode",
64+
"StateT",
65+
]
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Re-exporting internal utilities from LangGraph for internal use in LangChain.
2+
# A different wrapper needs to be created for this purpose in LangChain.
3+
from langgraph._internal._runnable import RunnableCallable
4+
5+
__all__ = [
6+
"RunnableCallable",
7+
]
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from langchain.chains.documents import (
2+
create_map_reduce_chain,
3+
create_stuff_documents_chain,
4+
)
5+
6+
__all__ = [
7+
"create_map_reduce_chain",
8+
"create_stuff_documents_chain",
9+
]
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
"""Document extraction chains.
2+
3+
This module provides different strategies for extracting information from collections
4+
of documents using LangGraph and modern language models.
5+
6+
Available Strategies:
7+
- Stuff: Processes all documents together in a single context window
8+
- Map-Reduce: Processes documents in parallel (map), then combines results (reduce)
9+
"""
10+
11+
from langchain.chains.documents.map_reduce import create_map_reduce_chain
12+
from langchain.chains.documents.stuff import create_stuff_documents_chain
13+
14+
__all__ = [
15+
"create_map_reduce_chain",
16+
"create_stuff_documents_chain",
17+
]

0 commit comments

Comments
 (0)