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
langchain/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  """Main entrypoint into LangChain."""
2
2
 
3
- __version__ = "1.0.5"
3
+ __version__ = "1.2.4"
@@ -1,10 +1,4 @@
1
- """Entrypoint to building [Agents](https://docs.langchain.com/oss/python/langchain/agents) with LangChain.
2
-
3
- !!! warning "Reference docs"
4
- This page contains **reference documentation** for Agents. See
5
- [the docs](https://docs.langchain.com/oss/python/langchain/agents) for conceptual
6
- guides, tutorials, and examples on using Agents.
7
- """ # noqa: E501
1
+ """Entrypoint to building [Agents](https://docs.langchain.com/oss/python/langchain/agents) with LangChain.""" # noqa: E501
8
2
 
9
3
  from langchain.agents.factory import create_agent
10
4
  from langchain.agents.middleware.types import AgentState
@@ -20,9 +20,7 @@ from langgraph._internal._runnable import RunnableCallable
20
20
  from langgraph.constants import END, START
21
21
  from langgraph.graph.state import StateGraph
22
22
  from langgraph.prebuilt.tool_node import ToolCallWithContext, ToolNode
23
- from langgraph.runtime import Runtime # noqa: TC002
24
23
  from langgraph.types import Command, Send
25
- from langgraph.typing import ContextT # noqa: TC002
26
24
  from typing_extensions import NotRequired, Required, TypedDict
27
25
 
28
26
  from langchain.agents.middleware.types import (
@@ -53,16 +51,30 @@ from langchain.chat_models import init_chat_model
53
51
  if TYPE_CHECKING:
54
52
  from collections.abc import Awaitable, Callable, Sequence
55
53
 
56
- from langchain_core.runnables import Runnable
54
+ from langchain_core.runnables import Runnable, RunnableConfig
57
55
  from langgraph.cache.base import BaseCache
58
56
  from langgraph.graph.state import CompiledStateGraph
57
+ from langgraph.runtime import Runtime
59
58
  from langgraph.store.base import BaseStore
60
59
  from langgraph.types import Checkpointer
60
+ from langgraph.typing import ContextT
61
61
 
62
62
  from langchain.agents.middleware.types import ToolCallRequest, ToolCallWrapper
63
63
 
64
64
  STRUCTURED_OUTPUT_ERROR_TEMPLATE = "Error: {error}\n Please fix your mistakes."
65
65
 
66
+ FALLBACK_MODELS_WITH_STRUCTURED_OUTPUT = [
67
+ # if model profile data are not available, these models are assumed to support
68
+ # structured output
69
+ "grok",
70
+ "gpt-5",
71
+ "gpt-4.1",
72
+ "gpt-4o",
73
+ "gpt-oss",
74
+ "o3-pro",
75
+ "o3-mini",
76
+ ]
77
+
66
78
 
67
79
  def _normalize_to_model_response(result: ModelResponse | AIMessage) -> ModelResponse:
68
80
  """Normalize middleware return value to ModelResponse."""
@@ -276,6 +288,9 @@ def _resolve_schema(schemas: set[type], schema_name: str, omit_flag: str | None
276
288
  schema_name: Name for the generated `TypedDict`
277
289
  omit_flag: If specified, omit fields with this flag set (`'input'` or
278
290
  `'output'`)
291
+
292
+ Returns:
293
+ Merged schema as `TypedDict`
279
294
  """
280
295
  all_annotations = {}
281
296
 
@@ -299,10 +314,10 @@ def _resolve_schema(schemas: set[type], schema_name: str, omit_flag: str | None
299
314
  return TypedDict(schema_name, all_annotations) # type: ignore[operator]
300
315
 
301
316
 
302
- def _extract_metadata(type_: type) -> list:
317
+ def _extract_metadata(type_: type) -> list[Any]:
303
318
  """Extract metadata from a field type, handling Required/NotRequired and Annotated wrappers."""
304
319
  # Handle Required[Annotated[...]] or NotRequired[Annotated[...]]
305
- if get_origin(type_) in (Required, NotRequired):
320
+ if get_origin(type_) in {Required, NotRequired}:
306
321
  inner_type = get_args(type_)[0]
307
322
  if get_origin(inner_type) is Annotated:
308
323
  return list(get_args(inner_type)[1:])
@@ -349,11 +364,15 @@ def _get_can_jump_to(middleware: AgentMiddleware[Any, Any], hook_name: str) -> l
349
364
  return []
350
365
 
351
366
 
352
- def _supports_provider_strategy(model: str | BaseChatModel) -> bool:
367
+ def _supports_provider_strategy(
368
+ model: str | BaseChatModel, tools: list[BaseTool | dict[str, Any]] | None = None
369
+ ) -> bool:
353
370
  """Check if a model supports provider-specific structured output.
354
371
 
355
372
  Args:
356
373
  model: Model name string or `BaseChatModel` instance.
374
+ tools: Optional list of tools provided to the agent. Needed because some models
375
+ don't support structured output together with tool calling.
357
376
 
358
377
  Returns:
359
378
  `True` if the model supports provider-specific structured output, `False` otherwise.
@@ -362,11 +381,23 @@ def _supports_provider_strategy(model: str | BaseChatModel) -> bool:
362
381
  if isinstance(model, str):
363
382
  model_name = model
364
383
  elif isinstance(model, BaseChatModel):
365
- model_name = getattr(model, "model_name", None)
384
+ model_name = (
385
+ getattr(model, "model_name", None)
386
+ or getattr(model, "model", None)
387
+ or getattr(model, "model_id", "")
388
+ )
389
+ model_profile = model.profile
390
+ if (
391
+ model_profile is not None
392
+ and model_profile.get("structured_output")
393
+ # We make an exception for Gemini models, which currently do not support
394
+ # simultaneous tool use with structured output
395
+ and not (tools and isinstance(model_name, str) and "gemini" in model_name.lower())
396
+ ):
397
+ return True
366
398
 
367
399
  return (
368
- "grok" in model_name.lower()
369
- or any(part in model_name for part in ["gpt-5", "gpt-4.1", "gpt-oss", "o3-pro", "o3-mini"])
400
+ any(part in model_name.lower() for part in FALLBACK_MODELS_WITH_STRUCTURED_OUTPUT)
370
401
  if model_name
371
402
  else False
372
403
  )
@@ -374,7 +405,7 @@ def _supports_provider_strategy(model: str | BaseChatModel) -> bool:
374
405
 
375
406
  def _handle_structured_output_error(
376
407
  exception: Exception,
377
- response_format: ResponseFormat,
408
+ response_format: ResponseFormat[Any],
378
409
  ) -> tuple[bool, str]:
379
410
  """Handle structured output error. Returns `(should_retry, retry_tool_message)`."""
380
411
  if not isinstance(response_format, ToolStrategy):
@@ -388,18 +419,15 @@ def _handle_structured_output_error(
388
419
  return True, STRUCTURED_OUTPUT_ERROR_TEMPLATE.format(error=str(exception))
389
420
  if isinstance(handle_errors, str):
390
421
  return True, handle_errors
391
- if isinstance(handle_errors, type) and issubclass(handle_errors, Exception):
392
- if isinstance(exception, handle_errors):
422
+ if isinstance(handle_errors, type):
423
+ if issubclass(handle_errors, Exception) and isinstance(exception, handle_errors):
393
424
  return True, STRUCTURED_OUTPUT_ERROR_TEMPLATE.format(error=str(exception))
394
425
  return False, ""
395
426
  if isinstance(handle_errors, tuple):
396
427
  if any(isinstance(exception, exc_type) for exc_type in handle_errors):
397
428
  return True, STRUCTURED_OUTPUT_ERROR_TEMPLATE.format(error=str(exception))
398
429
  return False, ""
399
- if callable(handle_errors):
400
- # type narrowing not working appropriately w/ callable check, can fix later
401
- return True, handle_errors(exception) # type: ignore[return-value,call-arg]
402
- return False, ""
430
+ return True, handle_errors(exception)
403
431
 
404
432
 
405
433
  def _chain_tool_call_wrappers(
@@ -429,10 +457,10 @@ def _chain_tool_call_wrappers(
429
457
 
430
458
  def composed(
431
459
  request: ToolCallRequest,
432
- execute: Callable[[ToolCallRequest], ToolMessage | Command],
433
- ) -> ToolMessage | Command:
460
+ execute: Callable[[ToolCallRequest], ToolMessage | Command[Any]],
461
+ ) -> ToolMessage | Command[Any]:
434
462
  # Create a callable that invokes inner with the original execute
435
- def call_inner(req: ToolCallRequest) -> ToolMessage | Command:
463
+ def call_inner(req: ToolCallRequest) -> ToolMessage | Command[Any]:
436
464
  return inner(req, execute)
437
465
 
438
466
  # Outer can call call_inner multiple times
@@ -451,14 +479,14 @@ def _chain_tool_call_wrappers(
451
479
  def _chain_async_tool_call_wrappers(
452
480
  wrappers: Sequence[
453
481
  Callable[
454
- [ToolCallRequest, Callable[[ToolCallRequest], Awaitable[ToolMessage | Command]]],
455
- Awaitable[ToolMessage | Command],
482
+ [ToolCallRequest, Callable[[ToolCallRequest], Awaitable[ToolMessage | Command[Any]]]],
483
+ Awaitable[ToolMessage | Command[Any]],
456
484
  ]
457
485
  ],
458
486
  ) -> (
459
487
  Callable[
460
- [ToolCallRequest, Callable[[ToolCallRequest], Awaitable[ToolMessage | Command]]],
461
- Awaitable[ToolMessage | Command],
488
+ [ToolCallRequest, Callable[[ToolCallRequest], Awaitable[ToolMessage | Command[Any]]]],
489
+ Awaitable[ToolMessage | Command[Any]],
462
490
  ]
463
491
  | None
464
492
  ):
@@ -478,25 +506,25 @@ def _chain_async_tool_call_wrappers(
478
506
 
479
507
  def compose_two(
480
508
  outer: Callable[
481
- [ToolCallRequest, Callable[[ToolCallRequest], Awaitable[ToolMessage | Command]]],
482
- Awaitable[ToolMessage | Command],
509
+ [ToolCallRequest, Callable[[ToolCallRequest], Awaitable[ToolMessage | Command[Any]]]],
510
+ Awaitable[ToolMessage | Command[Any]],
483
511
  ],
484
512
  inner: Callable[
485
- [ToolCallRequest, Callable[[ToolCallRequest], Awaitable[ToolMessage | Command]]],
486
- Awaitable[ToolMessage | Command],
513
+ [ToolCallRequest, Callable[[ToolCallRequest], Awaitable[ToolMessage | Command[Any]]]],
514
+ Awaitable[ToolMessage | Command[Any]],
487
515
  ],
488
516
  ) -> Callable[
489
- [ToolCallRequest, Callable[[ToolCallRequest], Awaitable[ToolMessage | Command]]],
490
- Awaitable[ToolMessage | Command],
517
+ [ToolCallRequest, Callable[[ToolCallRequest], Awaitable[ToolMessage | Command[Any]]]],
518
+ Awaitable[ToolMessage | Command[Any]],
491
519
  ]:
492
520
  """Compose two async wrappers where outer wraps inner."""
493
521
 
494
522
  async def composed(
495
523
  request: ToolCallRequest,
496
- execute: Callable[[ToolCallRequest], Awaitable[ToolMessage | Command]],
497
- ) -> ToolMessage | Command:
524
+ execute: Callable[[ToolCallRequest], Awaitable[ToolMessage | Command[Any]]],
525
+ ) -> ToolMessage | Command[Any]:
498
526
  # Create an async callable that invokes inner with the original execute
499
- async def call_inner(req: ToolCallRequest) -> ToolMessage | Command:
527
+ async def call_inner(req: ToolCallRequest) -> ToolMessage | Command[Any]:
500
528
  return await inner(req, execute)
501
529
 
502
530
  # Outer can call call_inner multiple times
@@ -512,13 +540,13 @@ def _chain_async_tool_call_wrappers(
512
540
  return result
513
541
 
514
542
 
515
- def create_agent( # noqa: PLR0915
543
+ def create_agent(
516
544
  model: str | BaseChatModel,
517
- tools: Sequence[BaseTool | Callable | dict[str, Any]] | None = None,
545
+ tools: Sequence[BaseTool | Callable[..., Any] | dict[str, Any]] | None = None,
518
546
  *,
519
- system_prompt: str | None = None,
547
+ system_prompt: str | SystemMessage | None = None,
520
548
  middleware: Sequence[AgentMiddleware[StateT_co, ContextT]] = (),
521
- response_format: ResponseFormat[ResponseT] | type[ResponseT] | None = None,
549
+ response_format: ResponseFormat[ResponseT] | type[ResponseT] | dict[str, Any] | None = None,
522
550
  state_schema: type[AgentState[ResponseT]] | None = None,
523
551
  context_schema: type[ContextT] | None = None,
524
552
  checkpointer: Checkpointer | None = None,
@@ -527,7 +555,7 @@ def create_agent( # noqa: PLR0915
527
555
  interrupt_after: list[str] | None = None,
528
556
  debug: bool = False,
529
557
  name: str | None = None,
530
- cache: BaseCache | None = None,
558
+ cache: BaseCache[Any] | None = None,
531
559
  ) -> CompiledStateGraph[
532
560
  AgentState[ResponseT], ContextT, _InputAgentState, _OutputAgentState[ResponseT]
533
561
  ]:
@@ -537,42 +565,64 @@ def create_agent( # noqa: PLR0915
537
565
  visit the [Agents](https://docs.langchain.com/oss/python/langchain/agents) docs.
538
566
 
539
567
  Args:
540
- model: The language model for the agent. Can be a string identifier
541
- (e.g., `"openai:gpt-4"`) or a direct chat model instance (e.g.,
542
- [`ChatOpenAI`][langchain_openai.ChatOpenAI] or other another
543
- [chat model](https://docs.langchain.com/oss/python/integrations/chat)).
568
+ model: The language model for the agent.
569
+
570
+ Can be a string identifier (e.g., `"openai:gpt-4"`) or a direct chat model
571
+ instance (e.g., [`ChatOpenAI`][langchain_openai.ChatOpenAI] or other another
572
+ [LangChain chat model](https://docs.langchain.com/oss/python/integrations/chat)).
544
573
 
545
574
  For a full list of supported model strings, see
546
575
  [`init_chat_model`][langchain.chat_models.init_chat_model(model_provider)].
547
- tools: A list of tools, `dicts`, or `Callable`.
576
+
577
+ !!! tip ""
578
+
579
+ See the [Models](https://docs.langchain.com/oss/python/langchain/models)
580
+ docs for more information.
581
+ tools: A list of tools, `dict`, or `Callable`.
548
582
 
549
583
  If `None` or an empty list, the agent will consist of a model node without a
550
584
  tool calling loop.
585
+
586
+
587
+ !!! tip ""
588
+
589
+ See the [Tools](https://docs.langchain.com/oss/python/langchain/tools)
590
+ docs for more information.
551
591
  system_prompt: An optional system prompt for the LLM.
552
592
 
553
- Prompts are converted to a
554
- [`SystemMessage`][langchain.messages.SystemMessage] and added to the
555
- beginning of the message list.
593
+ Can be a `str` (which will be converted to a `SystemMessage`) or a
594
+ `SystemMessage` instance directly. The system message is added to the
595
+ beginning of the message list when calling the model.
556
596
  middleware: A sequence of middleware instances to apply to the agent.
557
597
 
558
- Middleware can intercept and modify agent behavior at various stages. See
559
- the [full guide](https://docs.langchain.com/oss/python/langchain/middleware).
598
+ Middleware can intercept and modify agent behavior at various stages.
599
+
600
+ !!! tip ""
601
+
602
+ See the [Middleware](https://docs.langchain.com/oss/python/langchain/middleware)
603
+ docs for more information.
560
604
  response_format: An optional configuration for structured responses.
561
605
 
562
606
  Can be a `ToolStrategy`, `ProviderStrategy`, or a Pydantic model class.
563
607
 
564
608
  If provided, the agent will handle structured output during the
565
- conversation flow. Raw schemas will be wrapped in an appropriate strategy
566
- based on model capabilities.
609
+ conversation flow.
610
+
611
+ Raw schemas will be wrapped in an appropriate strategy based on model
612
+ capabilities.
613
+
614
+ !!! tip ""
615
+
616
+ See the [Structured output](https://docs.langchain.com/oss/python/langchain/structured-output)
617
+ docs for more information.
567
618
  state_schema: An optional `TypedDict` schema that extends `AgentState`.
568
619
 
569
620
  When provided, this schema is used instead of `AgentState` as the base
570
621
  schema for merging with middleware state schemas. This allows users to
571
622
  add custom state fields without needing to create custom middleware.
623
+
572
624
  Generally, it's recommended to use `state_schema` extensions via middleware
573
625
  to keep relevant extensions scoped to corresponding hooks / tools.
574
-
575
- The schema must be a subclass of `AgentState[ResponseT]`.
576
626
  context_schema: An optional schema for runtime context.
577
627
  checkpointer: An optional checkpoint saver object.
578
628
 
@@ -605,6 +655,9 @@ def create_agent( # noqa: PLR0915
605
655
  Returns:
606
656
  A compiled `StateGraph` that can be used for chat interactions.
607
657
 
658
+ Raises:
659
+ AssertionError: If duplicate middleware instances are provided.
660
+
608
661
  The agent node calls the language model with the messages list (after applying
609
662
  the system prompt). If the resulting [`AIMessage`][langchain.messages.AIMessage]
610
663
  contains `tool_calls`, the graph will then call the tools. The tools node executes
@@ -637,6 +690,14 @@ def create_agent( # noqa: PLR0915
637
690
  if isinstance(model, str):
638
691
  model = init_chat_model(model)
639
692
 
693
+ # Convert system_prompt to SystemMessage if needed
694
+ system_message: SystemMessage | None = None
695
+ if system_prompt is not None:
696
+ if isinstance(system_prompt, SystemMessage):
697
+ system_message = system_prompt
698
+ else:
699
+ system_message = SystemMessage(content=system_prompt)
700
+
640
701
  # Handle tools being None or empty
641
702
  if tools is None:
642
703
  tools = []
@@ -645,7 +706,7 @@ def create_agent( # noqa: PLR0915
645
706
  # Raw schemas are wrapped in AutoStrategy to preserve auto-detection intent.
646
707
  # AutoStrategy is converted to ToolStrategy upfront to calculate tools during agent creation,
647
708
  # but may be replaced with ProviderStrategy later based on model capabilities.
648
- initial_response_format: ToolStrategy | ProviderStrategy | AutoStrategy | None
709
+ initial_response_format: ToolStrategy[Any] | ProviderStrategy[Any] | AutoStrategy[Any] | None
649
710
  if response_format is None:
650
711
  initial_response_format = None
651
712
  elif isinstance(response_format, (ToolStrategy, ProviderStrategy)):
@@ -660,13 +721,13 @@ def create_agent( # noqa: PLR0915
660
721
 
661
722
  # For AutoStrategy, convert to ToolStrategy to setup tools upfront
662
723
  # (may be replaced with ProviderStrategy later based on model)
663
- tool_strategy_for_setup: ToolStrategy | None = None
724
+ tool_strategy_for_setup: ToolStrategy[Any] | None = None
664
725
  if isinstance(initial_response_format, AutoStrategy):
665
726
  tool_strategy_for_setup = ToolStrategy(schema=initial_response_format.schema)
666
727
  elif isinstance(initial_response_format, ToolStrategy):
667
728
  tool_strategy_for_setup = initial_response_format
668
729
 
669
- structured_output_tools: dict[str, OutputToolBinding] = {}
730
+ structured_output_tools: dict[str, OutputToolBinding[Any]] = {}
670
731
  if tool_strategy_for_setup:
671
732
  for response_schema in tool_strategy_for_setup.schema_specs:
672
733
  structured_tool_info = OutputToolBinding.from_schema_spec(response_schema)
@@ -735,9 +796,9 @@ def create_agent( # noqa: PLR0915
735
796
  default_tools = list(built_in_tools)
736
797
 
737
798
  # validate middleware
738
- assert len({m.name for m in middleware}) == len(middleware), ( # noqa: S101
739
- "Please remove duplicate middleware instances."
740
- )
799
+ if len({m.name for m in middleware}) != len(middleware):
800
+ msg = "Please remove duplicate middleware instances."
801
+ raise AssertionError(msg)
741
802
  middleware_w_before_agent = [
742
803
  m
743
804
  for m in middleware
@@ -813,7 +874,7 @@ def create_agent( # noqa: PLR0915
813
874
  )
814
875
 
815
876
  def _handle_model_output(
816
- output: AIMessage, effective_response_format: ResponseFormat | None
877
+ output: AIMessage, effective_response_format: ResponseFormat[Any] | None
817
878
  ) -> dict[str, Any]:
818
879
  """Handle model output including structured responses.
819
880
 
@@ -830,12 +891,12 @@ def create_agent( # noqa: PLR0915
830
891
  )
831
892
  try:
832
893
  structured_response = provider_strategy_binding.parse(output)
833
- except Exception as exc: # noqa: BLE001
894
+ except Exception as exc:
834
895
  schema_name = getattr(
835
896
  effective_response_format.schema_spec.schema, "__name__", "response_format"
836
897
  )
837
898
  validation_error = StructuredOutputValidationError(schema_name, exc, output)
838
- raise validation_error
899
+ raise validation_error from exc
839
900
  else:
840
901
  return {"messages": [output], "structured_response": structured_response}
841
902
  return {"messages": [output]}
@@ -881,8 +942,7 @@ def create_agent( # noqa: PLR0915
881
942
 
882
943
  tool_message_content = (
883
944
  effective_response_format.tool_message_content
884
- if effective_response_format.tool_message_content
885
- else f"Returning structured response: {structured_response}"
945
+ or f"Returning structured response: {structured_response}"
886
946
  )
887
947
 
888
948
  return {
@@ -896,13 +956,13 @@ def create_agent( # noqa: PLR0915
896
956
  ],
897
957
  "structured_response": structured_response,
898
958
  }
899
- except Exception as exc: # noqa: BLE001
959
+ except Exception as exc:
900
960
  exception = StructuredOutputValidationError(tool_call["name"], exc, output)
901
961
  should_retry, error_message = _handle_structured_output_error(
902
962
  exception, effective_response_format
903
963
  )
904
964
  if not should_retry:
905
- raise exception
965
+ raise exception from exc
906
966
 
907
967
  return {
908
968
  "messages": [
@@ -917,7 +977,9 @@ def create_agent( # noqa: PLR0915
917
977
 
918
978
  return {"messages": [output]}
919
979
 
920
- def _get_bound_model(request: ModelRequest) -> tuple[Runnable, ResponseFormat | None]:
980
+ def _get_bound_model(
981
+ request: ModelRequest,
982
+ ) -> tuple[Runnable[Any, Any], ResponseFormat[Any] | None]:
921
983
  """Get the model with appropriate tool bindings.
922
984
 
923
985
  Performs auto-detection of strategy if needed based on model capabilities.
@@ -929,6 +991,10 @@ def create_agent( # noqa: PLR0915
929
991
  Tuple of `(bound_model, effective_response_format)` where
930
992
  `effective_response_format` is the actual strategy used (may differ from
931
993
  initial if auto-detected).
994
+
995
+ Raises:
996
+ ValueError: If middleware returned unknown client-side tool names.
997
+ ValueError: If `ToolStrategy` specifies tools not declared upfront.
932
998
  """
933
999
  # Validate ONLY client-side tools that need to exist in tool_node
934
1000
  # Build map of available client-side tools from the ToolNode
@@ -963,10 +1029,10 @@ def create_agent( # noqa: PLR0915
963
1029
  raise ValueError(msg)
964
1030
 
965
1031
  # Determine effective response format (auto-detect if needed)
966
- effective_response_format: ResponseFormat | None
1032
+ effective_response_format: ResponseFormat[Any] | None
967
1033
  if isinstance(request.response_format, AutoStrategy):
968
1034
  # User provided raw schema via AutoStrategy - auto-detect best strategy based on model
969
- if _supports_provider_strategy(request.model):
1035
+ if _supports_provider_strategy(request.model, tools=request.tools):
970
1036
  # Model supports provider strategy - use it
971
1037
  effective_response_format = ProviderStrategy(schema=request.response_format.schema)
972
1038
  else:
@@ -987,7 +1053,7 @@ def create_agent( # noqa: PLR0915
987
1053
 
988
1054
  # Bind model based on effective response format
989
1055
  if isinstance(effective_response_format, ProviderStrategy):
990
- # Use provider-specific structured output
1056
+ # (Backward compatibility) Use OpenAI format structured output
991
1057
  kwargs = effective_response_format.to_model_kwargs()
992
1058
  return (
993
1059
  request.model.bind_tools(
@@ -1040,10 +1106,12 @@ def create_agent( # noqa: PLR0915
1040
1106
  # Get the bound model (with auto-detection if needed)
1041
1107
  model_, effective_response_format = _get_bound_model(request)
1042
1108
  messages = request.messages
1043
- if request.system_prompt:
1044
- messages = [SystemMessage(request.system_prompt), *messages]
1109
+ if request.system_message:
1110
+ messages = [request.system_message, *messages]
1045
1111
 
1046
1112
  output = model_.invoke(messages)
1113
+ if name:
1114
+ output.name = name
1047
1115
 
1048
1116
  # Handle model output to get messages and structured_response
1049
1117
  handled_output = _handle_model_output(output, effective_response_format)
@@ -1055,12 +1123,12 @@ def create_agent( # noqa: PLR0915
1055
1123
  structured_response=structured_response,
1056
1124
  )
1057
1125
 
1058
- def model_node(state: AgentState, runtime: Runtime[ContextT]) -> dict[str, Any]:
1126
+ def model_node(state: AgentState[Any], runtime: Runtime[ContextT]) -> dict[str, Any]:
1059
1127
  """Sync model request handler with sequential middleware processing."""
1060
1128
  request = ModelRequest(
1061
1129
  model=model,
1062
1130
  tools=default_tools,
1063
- system_prompt=system_prompt,
1131
+ system_message=system_message,
1064
1132
  response_format=initial_response_format,
1065
1133
  messages=state["messages"],
1066
1134
  tool_choice=None,
@@ -1093,10 +1161,12 @@ def create_agent( # noqa: PLR0915
1093
1161
  # Get the bound model (with auto-detection if needed)
1094
1162
  model_, effective_response_format = _get_bound_model(request)
1095
1163
  messages = request.messages
1096
- if request.system_prompt:
1097
- messages = [SystemMessage(request.system_prompt), *messages]
1164
+ if request.system_message:
1165
+ messages = [request.system_message, *messages]
1098
1166
 
1099
1167
  output = await model_.ainvoke(messages)
1168
+ if name:
1169
+ output.name = name
1100
1170
 
1101
1171
  # Handle model output to get messages and structured_response
1102
1172
  handled_output = _handle_model_output(output, effective_response_format)
@@ -1108,12 +1178,12 @@ def create_agent( # noqa: PLR0915
1108
1178
  structured_response=structured_response,
1109
1179
  )
1110
1180
 
1111
- async def amodel_node(state: AgentState, runtime: Runtime[ContextT]) -> dict[str, Any]:
1181
+ async def amodel_node(state: AgentState[Any], runtime: Runtime[ContextT]) -> dict[str, Any]:
1112
1182
  """Async model request handler with sequential middleware processing."""
1113
1183
  request = ModelRequest(
1114
1184
  model=model,
1115
1185
  tools=default_tools,
1116
- system_prompt=system_prompt,
1186
+ system_message=system_message,
1117
1187
  response_format=initial_response_format,
1118
1188
  messages=state["messages"],
1119
1189
  tool_choice=None,
@@ -1412,6 +1482,10 @@ def create_agent( # noqa: PLR0915
1412
1482
  can_jump_to=_get_can_jump_to(middleware_w_after_agent[0], "after_agent"),
1413
1483
  )
1414
1484
 
1485
+ config: RunnableConfig = {"recursion_limit": 10_000}
1486
+ if name:
1487
+ config["metadata"] = {"lc_agent_name": name}
1488
+
1415
1489
  return graph.compile(
1416
1490
  checkpointer=checkpointer,
1417
1491
  store=store,
@@ -1420,7 +1494,7 @@ def create_agent( # noqa: PLR0915
1420
1494
  debug=debug,
1421
1495
  name=name,
1422
1496
  cache=cache,
1423
- ).with_config({"recursion_limit": 10_000})
1497
+ ).with_config(config)
1424
1498
 
1425
1499
 
1426
1500
  def _resolve_jump(
@@ -1457,7 +1531,7 @@ def _fetch_last_ai_and_tool_messages(
1457
1531
  def _make_model_to_tools_edge(
1458
1532
  *,
1459
1533
  model_destination: str,
1460
- structured_output_tools: dict[str, OutputToolBinding],
1534
+ structured_output_tools: dict[str, OutputToolBinding[Any]],
1461
1535
  end_destination: str,
1462
1536
  ) -> Callable[[dict[str, Any]], str | list[Send] | None]:
1463
1537
  def model_to_tools(
@@ -1541,7 +1615,7 @@ def _make_tools_to_model_edge(
1541
1615
  *,
1542
1616
  tool_node: ToolNode,
1543
1617
  model_destination: str,
1544
- structured_output_tools: dict[str, OutputToolBinding],
1618
+ structured_output_tools: dict[str, OutputToolBinding[Any]],
1545
1619
  end_destination: str,
1546
1620
  ) -> Callable[[dict[str, Any]], str | None]:
1547
1621
  def tools_to_model(state: dict[str, Any]) -> str | None:
@@ -1,36 +1,29 @@
1
- """Entrypoint to using [Middleware](https://docs.langchain.com/oss/python/langchain/middleware) plugins with [Agents](https://docs.langchain.com/oss/python/langchain/agents).
1
+ """Entrypoint to using [middleware](https://docs.langchain.com/oss/python/langchain/middleware) plugins with [Agents](https://docs.langchain.com/oss/python/langchain/agents).""" # noqa: E501
2
2
 
3
- !!! warning "Reference docs"
4
- This page contains **reference documentation** for Middleware. See
5
- [the docs](https://docs.langchain.com/oss/python/langchain/middleware) for conceptual
6
- guides, tutorials, and examples on using Middleware.
7
- """ # noqa: E501
8
-
9
- from .context_editing import (
10
- ClearToolUsesEdit,
11
- ContextEditingMiddleware,
12
- )
13
- from .human_in_the_loop import (
3
+ from langchain.agents.middleware.context_editing import ClearToolUsesEdit, ContextEditingMiddleware
4
+ from langchain.agents.middleware.file_search import FilesystemFileSearchMiddleware
5
+ from langchain.agents.middleware.human_in_the_loop import (
14
6
  HumanInTheLoopMiddleware,
15
7
  InterruptOnConfig,
16
8
  )
17
- from .model_call_limit import ModelCallLimitMiddleware
18
- from .model_fallback import ModelFallbackMiddleware
19
- from .pii import PIIDetectionError, PIIMiddleware
20
- from .shell_tool import (
9
+ from langchain.agents.middleware.model_call_limit import ModelCallLimitMiddleware
10
+ from langchain.agents.middleware.model_fallback import ModelFallbackMiddleware
11
+ from langchain.agents.middleware.model_retry import ModelRetryMiddleware
12
+ from langchain.agents.middleware.pii import PIIDetectionError, PIIMiddleware
13
+ from langchain.agents.middleware.shell_tool import (
21
14
  CodexSandboxExecutionPolicy,
22
15
  DockerExecutionPolicy,
23
16
  HostExecutionPolicy,
24
17
  RedactionRule,
25
18
  ShellToolMiddleware,
26
19
  )
27
- from .summarization import SummarizationMiddleware
28
- from .todo import TodoListMiddleware
29
- from .tool_call_limit import ToolCallLimitMiddleware
30
- from .tool_emulator import LLMToolEmulator
31
- from .tool_retry import ToolRetryMiddleware
32
- from .tool_selection import LLMToolSelectorMiddleware
33
- from .types import (
20
+ from langchain.agents.middleware.summarization import SummarizationMiddleware
21
+ from langchain.agents.middleware.todo import TodoListMiddleware
22
+ from langchain.agents.middleware.tool_call_limit import ToolCallLimitMiddleware
23
+ from langchain.agents.middleware.tool_emulator import LLMToolEmulator
24
+ from langchain.agents.middleware.tool_retry import ToolRetryMiddleware
25
+ from langchain.agents.middleware.tool_selection import LLMToolSelectorMiddleware
26
+ from langchain.agents.middleware.types import (
34
27
  AgentMiddleware,
35
28
  AgentState,
36
29
  ModelRequest,
@@ -52,6 +45,7 @@ __all__ = [
52
45
  "CodexSandboxExecutionPolicy",
53
46
  "ContextEditingMiddleware",
54
47
  "DockerExecutionPolicy",
48
+ "FilesystemFileSearchMiddleware",
55
49
  "HostExecutionPolicy",
56
50
  "HumanInTheLoopMiddleware",
57
51
  "InterruptOnConfig",
@@ -61,6 +55,7 @@ __all__ = [
61
55
  "ModelFallbackMiddleware",
62
56
  "ModelRequest",
63
57
  "ModelResponse",
58
+ "ModelRetryMiddleware",
64
59
  "PIIDetectionError",
65
60
  "PIIMiddleware",
66
61
  "RedactionRule",