langchain 1.0.5__py3-none-any.whl → 1.2.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. langchain/__init__.py +1 -1
  2. langchain/agents/__init__.py +1 -7
  3. langchain/agents/factory.py +153 -79
  4. langchain/agents/middleware/__init__.py +18 -23
  5. langchain/agents/middleware/_execution.py +29 -32
  6. langchain/agents/middleware/_redaction.py +108 -22
  7. langchain/agents/middleware/_retry.py +123 -0
  8. langchain/agents/middleware/context_editing.py +47 -25
  9. langchain/agents/middleware/file_search.py +19 -14
  10. langchain/agents/middleware/human_in_the_loop.py +87 -57
  11. langchain/agents/middleware/model_call_limit.py +64 -18
  12. langchain/agents/middleware/model_fallback.py +7 -9
  13. langchain/agents/middleware/model_retry.py +307 -0
  14. langchain/agents/middleware/pii.py +82 -29
  15. langchain/agents/middleware/shell_tool.py +254 -107
  16. langchain/agents/middleware/summarization.py +469 -95
  17. langchain/agents/middleware/todo.py +129 -31
  18. langchain/agents/middleware/tool_call_limit.py +105 -71
  19. langchain/agents/middleware/tool_emulator.py +47 -38
  20. langchain/agents/middleware/tool_retry.py +183 -164
  21. langchain/agents/middleware/tool_selection.py +81 -37
  22. langchain/agents/middleware/types.py +856 -427
  23. langchain/agents/structured_output.py +65 -42
  24. langchain/chat_models/__init__.py +1 -7
  25. langchain/chat_models/base.py +253 -196
  26. langchain/embeddings/__init__.py +0 -5
  27. langchain/embeddings/base.py +79 -65
  28. langchain/messages/__init__.py +0 -5
  29. langchain/tools/__init__.py +1 -7
  30. {langchain-1.0.5.dist-info → langchain-1.2.4.dist-info}/METADATA +5 -7
  31. langchain-1.2.4.dist-info/RECORD +36 -0
  32. {langchain-1.0.5.dist-info → langchain-1.2.4.dist-info}/WHEEL +1 -1
  33. langchain-1.0.5.dist-info/RECORD +0 -34
  34. {langchain-1.0.5.dist-info → langchain-1.2.4.dist-info}/licenses/LICENSE +0 -0
@@ -4,12 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  import logging
6
6
  from dataclasses import dataclass
7
- from typing import TYPE_CHECKING, Annotated, Literal, Union
8
-
9
- if TYPE_CHECKING:
10
- from collections.abc import Awaitable, Callable
11
-
12
- from langchain.tools import BaseTool
7
+ from typing import TYPE_CHECKING, Annotated, Any, Literal, Union
13
8
 
14
9
  from langchain_core.language_models.chat_models import BaseChatModel
15
10
  from langchain_core.messages import HumanMessage
@@ -24,6 +19,11 @@ from langchain.agents.middleware.types import (
24
19
  )
25
20
  from langchain.chat_models.base import init_chat_model
26
21
 
22
+ if TYPE_CHECKING:
23
+ from collections.abc import Awaitable, Callable
24
+
25
+ from langchain.tools import BaseTool
26
+
27
27
  logger = logging.getLogger(__name__)
28
28
 
29
29
  DEFAULT_SYSTEM_PROMPT = (
@@ -42,21 +42,25 @@ class _SelectionRequest:
42
42
  valid_tool_names: list[str]
43
43
 
44
44
 
45
- def _create_tool_selection_response(tools: list[BaseTool]) -> TypeAdapter:
45
+ def _create_tool_selection_response(tools: list[BaseTool]) -> TypeAdapter[Any]:
46
46
  """Create a structured output schema for tool selection.
47
47
 
48
48
  Args:
49
49
  tools: Available tools to include in the schema.
50
50
 
51
51
  Returns:
52
- TypeAdapter for a schema where each tool name is a Literal with its description.
52
+ `TypeAdapter` for a schema where each tool name is a `Literal` with its
53
+ description.
54
+
55
+ Raises:
56
+ AssertionError: If `tools` is empty.
53
57
  """
54
58
  if not tools:
55
59
  msg = "Invalid usage: tools must be non-empty"
56
60
  raise AssertionError(msg)
57
61
 
58
62
  # Create a Union of Annotated Literal types for each tool name with description
59
- # Example: Union[Annotated[Literal["tool1"], Field(description="...")], ...] noqa: ERA001
63
+ # For instance: Union[Annotated[Literal["tool1"], Field(description="...")], ...]
60
64
  literals = [
61
65
  Annotated[Literal[tool.name], Field(description=tool.description)] for tool in tools
62
66
  ]
@@ -92,23 +96,25 @@ class LLMToolSelectorMiddleware(AgentMiddleware):
92
96
  and helps the main model focus on the right tools.
93
97
 
94
98
  Examples:
95
- Limit to 3 tools:
96
- ```python
97
- from langchain.agents.middleware import LLMToolSelectorMiddleware
99
+ !!! example "Limit to 3 tools"
98
100
 
99
- middleware = LLMToolSelectorMiddleware(max_tools=3)
101
+ ```python
102
+ from langchain.agents.middleware import LLMToolSelectorMiddleware
100
103
 
101
- agent = create_agent(
102
- model="openai:gpt-4o",
103
- tools=[tool1, tool2, tool3, tool4, tool5],
104
- middleware=[middleware],
105
- )
106
- ```
104
+ middleware = LLMToolSelectorMiddleware(max_tools=3)
105
+
106
+ agent = create_agent(
107
+ model="openai:gpt-4o",
108
+ tools=[tool1, tool2, tool3, tool4, tool5],
109
+ middleware=[middleware],
110
+ )
111
+ ```
112
+
113
+ !!! example "Use a smaller model for selection"
107
114
 
108
- Use a smaller model for selection:
109
- ```python
110
- middleware = LLMToolSelectorMiddleware(model="openai:gpt-4o-mini", max_tools=2)
111
- ```
115
+ ```python
116
+ middleware = LLMToolSelectorMiddleware(model="openai:gpt-4o-mini", max_tools=2)
117
+ ```
112
118
  """
113
119
 
114
120
  def __init__(
@@ -122,13 +128,20 @@ class LLMToolSelectorMiddleware(AgentMiddleware):
122
128
  """Initialize the tool selector.
123
129
 
124
130
  Args:
125
- model: Model to use for selection. If not provided, uses the agent's main model.
126
- Can be a model identifier string or BaseChatModel instance.
131
+ model: Model to use for selection.
132
+
133
+ If not provided, uses the agent's main model.
134
+
135
+ Can be a model identifier string or `BaseChatModel` instance.
127
136
  system_prompt: Instructions for the selection model.
128
- max_tools: Maximum number of tools to select. If the model selects more,
129
- only the first max_tools will be used. No limit if not specified.
137
+ max_tools: Maximum number of tools to select.
138
+
139
+ If the model selects more, only the first `max_tools` will be used.
140
+
141
+ If not specified, there is no limit.
130
142
  always_include: Tool names to always include regardless of selection.
131
- These do not count against the max_tools limit.
143
+
144
+ These do not count against the `max_tools` limit.
132
145
  """
133
146
  super().__init__()
134
147
  self.system_prompt = system_prompt
@@ -143,8 +156,16 @@ class LLMToolSelectorMiddleware(AgentMiddleware):
143
156
  def _prepare_selection_request(self, request: ModelRequest) -> _SelectionRequest | None:
144
157
  """Prepare inputs for tool selection.
145
158
 
159
+ Args:
160
+ request: the model request.
161
+
146
162
  Returns:
147
- SelectionRequest with prepared inputs, or None if no selection is needed.
163
+ `SelectionRequest` with prepared inputs, or `None` if no selection is
164
+ needed.
165
+
166
+ Raises:
167
+ ValueError: If tools in `always_include` are not found in the request.
168
+ AssertionError: If no user message is found in the request messages.
148
169
  """
149
170
  # If no tools available, return None
150
171
  if not request.tools or len(request.tools) == 0:
@@ -206,12 +227,12 @@ class LLMToolSelectorMiddleware(AgentMiddleware):
206
227
 
207
228
  def _process_selection_response(
208
229
  self,
209
- response: dict,
230
+ response: dict[str, Any],
210
231
  available_tools: list[BaseTool],
211
232
  valid_tool_names: list[str],
212
233
  request: ModelRequest,
213
234
  ) -> ModelRequest:
214
- """Process the selection response and return filtered ModelRequest."""
235
+ """Process the selection response and return filtered `ModelRequest`."""
215
236
  selected_tool_names: list[str] = []
216
237
  invalid_tool_selections = []
217
238
 
@@ -244,15 +265,26 @@ class LLMToolSelectorMiddleware(AgentMiddleware):
244
265
  # Also preserve any provider-specific tool dicts from the original request
245
266
  provider_tools = [tool for tool in request.tools if isinstance(tool, dict)]
246
267
 
247
- request.tools = [*selected_tools, *provider_tools]
248
- return request
268
+ return request.override(tools=[*selected_tools, *provider_tools])
249
269
 
250
270
  def wrap_model_call(
251
271
  self,
252
272
  request: ModelRequest,
253
273
  handler: Callable[[ModelRequest], ModelResponse],
254
274
  ) -> ModelCallResult:
255
- """Filter tools based on LLM selection before invoking the model via handler."""
275
+ """Filter tools based on LLM selection before invoking the model via handler.
276
+
277
+ Args:
278
+ request: Model request to execute (includes state and runtime).
279
+ handler: Async callback that executes the model request and returns
280
+ `ModelResponse`.
281
+
282
+ Returns:
283
+ The model call result.
284
+
285
+ Raises:
286
+ AssertionError: If the selection model response is not a dict.
287
+ """
256
288
  selection_request = self._prepare_selection_request(request)
257
289
  if selection_request is None:
258
290
  return handler(request)
@@ -272,7 +304,7 @@ class LLMToolSelectorMiddleware(AgentMiddleware):
272
304
  # Response should be a dict since we're passing a schema (not a Pydantic model class)
273
305
  if not isinstance(response, dict):
274
306
  msg = f"Expected dict response, got {type(response)}"
275
- raise AssertionError(msg)
307
+ raise AssertionError(msg) # noqa: TRY004
276
308
  modified_request = self._process_selection_response(
277
309
  response, selection_request.available_tools, selection_request.valid_tool_names, request
278
310
  )
@@ -283,7 +315,19 @@ class LLMToolSelectorMiddleware(AgentMiddleware):
283
315
  request: ModelRequest,
284
316
  handler: Callable[[ModelRequest], Awaitable[ModelResponse]],
285
317
  ) -> ModelCallResult:
286
- """Filter tools based on LLM selection before invoking the model via handler."""
318
+ """Filter tools based on LLM selection before invoking the model via handler.
319
+
320
+ Args:
321
+ request: Model request to execute (includes state and runtime).
322
+ handler: Async callback that executes the model request and returns
323
+ `ModelResponse`.
324
+
325
+ Returns:
326
+ The model call result.
327
+
328
+ Raises:
329
+ AssertionError: If the selection model response is not a dict.
330
+ """
287
331
  selection_request = self._prepare_selection_request(request)
288
332
  if selection_request is None:
289
333
  return await handler(request)
@@ -303,7 +347,7 @@ class LLMToolSelectorMiddleware(AgentMiddleware):
303
347
  # Response should be a dict since we're passing a schema (not a Pydantic model class)
304
348
  if not isinstance(response, dict):
305
349
  msg = f"Expected dict response, got {type(response)}"
306
- raise AssertionError(msg)
350
+ raise AssertionError(msg) # noqa: TRY004
307
351
  modified_request = self._process_selection_response(
308
352
  response, selection_request.available_tools, selection_request.valid_tool_names, request
309
353
  )