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.
- langchain/__init__.py +1 -1
- langchain/agents/__init__.py +1 -7
- langchain/agents/factory.py +153 -79
- langchain/agents/middleware/__init__.py +18 -23
- langchain/agents/middleware/_execution.py +29 -32
- langchain/agents/middleware/_redaction.py +108 -22
- langchain/agents/middleware/_retry.py +123 -0
- langchain/agents/middleware/context_editing.py +47 -25
- langchain/agents/middleware/file_search.py +19 -14
- langchain/agents/middleware/human_in_the_loop.py +87 -57
- langchain/agents/middleware/model_call_limit.py +64 -18
- langchain/agents/middleware/model_fallback.py +7 -9
- langchain/agents/middleware/model_retry.py +307 -0
- langchain/agents/middleware/pii.py +82 -29
- langchain/agents/middleware/shell_tool.py +254 -107
- langchain/agents/middleware/summarization.py +469 -95
- langchain/agents/middleware/todo.py +129 -31
- langchain/agents/middleware/tool_call_limit.py +105 -71
- langchain/agents/middleware/tool_emulator.py +47 -38
- langchain/agents/middleware/tool_retry.py +183 -164
- langchain/agents/middleware/tool_selection.py +81 -37
- langchain/agents/middleware/types.py +856 -427
- langchain/agents/structured_output.py +65 -42
- langchain/chat_models/__init__.py +1 -7
- langchain/chat_models/base.py +253 -196
- langchain/embeddings/__init__.py +0 -5
- langchain/embeddings/base.py +79 -65
- langchain/messages/__init__.py +0 -5
- langchain/tools/__init__.py +1 -7
- {langchain-1.0.5.dist-info → langchain-1.2.4.dist-info}/METADATA +5 -7
- langchain-1.2.4.dist-info/RECORD +36 -0
- {langchain-1.0.5.dist-info → langchain-1.2.4.dist-info}/WHEEL +1 -1
- langchain-1.0.5.dist-info/RECORD +0 -34
- {langchain-1.0.5.dist-info → langchain-1.2.4.dist-info}/licenses/LICENSE +0 -0
langchain/__init__.py
CHANGED
langchain/agents/__init__.py
CHANGED
|
@@ -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
|
langchain/agents/factory.py
CHANGED
|
@@ -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
|
|
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(
|
|
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 =
|
|
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
|
-
|
|
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)
|
|
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
|
-
|
|
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(
|
|
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.
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
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
|
-
|
|
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
|
-
|
|
554
|
-
|
|
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.
|
|
559
|
-
|
|
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.
|
|
566
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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(
|
|
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
|
|
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.
|
|
1044
|
-
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
|
-
|
|
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.
|
|
1097
|
-
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
|
-
|
|
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(
|
|
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 [
|
|
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
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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 .
|
|
20
|
-
from .
|
|
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",
|