langchain 1.0.1__py3-none-any.whl → 1.0.2__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.

Potentially problematic release.


This version of langchain might be problematic. Click here for more details.

@@ -89,6 +89,7 @@ if TYPE_CHECKING:
89
89
  from collections.abc import Sequence
90
90
 
91
91
  from langgraph.runtime import Runtime
92
+ from pydantic_core import ErrorDetails
92
93
 
93
94
  # right now we use a dict as the default, can change this to AgentState, but depends
94
95
  # on if this lives in LangChain or LangGraph... ideally would have some typed
@@ -303,7 +304,11 @@ class ToolInvocationError(ToolException):
303
304
  """
304
305
 
305
306
  def __init__(
306
- self, tool_name: str, source: ValidationError, tool_kwargs: dict[str, Any]
307
+ self,
308
+ tool_name: str,
309
+ source: ValidationError,
310
+ tool_kwargs: dict[str, Any],
311
+ filtered_errors: list[ErrorDetails] | None = None,
307
312
  ) -> None:
308
313
  """Initialize the ToolInvocationError.
309
314
 
@@ -311,13 +316,28 @@ class ToolInvocationError(ToolException):
311
316
  tool_name: The name of the tool that failed.
312
317
  source: The exception that occurred.
313
318
  tool_kwargs: The keyword arguments that were passed to the tool.
319
+ filtered_errors: Optional list of filtered validation errors excluding
320
+ injected arguments.
314
321
  """
322
+ # Format error display based on filtered errors if provided
323
+ if filtered_errors is not None:
324
+ # Manually format the filtered errors without URLs or fancy formatting
325
+ error_str_parts = []
326
+ for error in filtered_errors:
327
+ loc_str = ".".join(str(loc) for loc in error.get("loc", ()))
328
+ msg = error.get("msg", "Unknown error")
329
+ error_str_parts.append(f"{loc_str}: {msg}")
330
+ error_display_str = "\n".join(error_str_parts)
331
+ else:
332
+ error_display_str = str(source)
333
+
315
334
  self.message = TOOL_INVOCATION_ERROR_TEMPLATE.format(
316
- tool_name=tool_name, tool_kwargs=tool_kwargs, error=source
335
+ tool_name=tool_name, tool_kwargs=tool_kwargs, error=error_display_str
317
336
  )
318
337
  self.tool_name = tool_name
319
338
  self.tool_kwargs = tool_kwargs
320
339
  self.source = source
340
+ self.filtered_errors = filtered_errors
321
341
  super().__init__(self.message)
322
342
 
323
343
 
@@ -442,6 +462,59 @@ def _infer_handled_types(handler: Callable[..., str]) -> tuple[type[Exception],
442
462
  return (Exception,)
443
463
 
444
464
 
465
+ def _filter_validation_errors(
466
+ validation_error: ValidationError,
467
+ tool_to_state_args: dict[str, str | None],
468
+ tool_to_store_arg: str | None,
469
+ tool_to_runtime_arg: str | None,
470
+ ) -> list[ErrorDetails]:
471
+ """Filter validation errors to only include LLM-controlled arguments.
472
+
473
+ When a tool invocation fails validation, only errors for arguments that the LLM
474
+ controls should be included in error messages. This ensures the LLM receives
475
+ focused, actionable feedback about parameters it can actually fix. System-injected
476
+ arguments (state, store, runtime) are filtered out since the LLM has no control
477
+ over them.
478
+
479
+ This function also removes injected argument values from the `input` field in error
480
+ details, ensuring that only LLM-provided arguments appear in error messages.
481
+
482
+ Args:
483
+ validation_error: The Pydantic ValidationError raised during tool invocation.
484
+ tool_to_state_args: Mapping of state argument names to state field names.
485
+ tool_to_store_arg: Name of the store argument, if any.
486
+ tool_to_runtime_arg: Name of the runtime argument, if any.
487
+
488
+ Returns:
489
+ List of ErrorDetails containing only errors for LLM-controlled arguments,
490
+ with system-injected argument values removed from the input field.
491
+ """
492
+ injected_args = set(tool_to_state_args.keys())
493
+ if tool_to_store_arg:
494
+ injected_args.add(tool_to_store_arg)
495
+ if tool_to_runtime_arg:
496
+ injected_args.add(tool_to_runtime_arg)
497
+
498
+ filtered_errors: list[ErrorDetails] = []
499
+ for error in validation_error.errors():
500
+ # Check if error location contains any injected argument
501
+ # error['loc'] is a tuple like ('field_name',) or ('field_name', 'nested_field')
502
+ if error["loc"] and error["loc"][0] not in injected_args:
503
+ # Create a copy of the error dict to avoid mutating the original
504
+ error_copy: dict[str, Any] = {**error}
505
+
506
+ # Remove injected arguments from input_value if it's a dict
507
+ if isinstance(error_copy.get("input"), dict):
508
+ input_dict = error_copy["input"]
509
+ input_copy = {k: v for k, v in input_dict.items() if k not in injected_args}
510
+ error_copy["input"] = input_copy
511
+
512
+ # Cast is safe because ErrorDetails is a TypedDict compatible with this structure
513
+ filtered_errors.append(error_copy) # type: ignore[arg-type]
514
+
515
+ return filtered_errors
516
+
517
+
445
518
  class _ToolNode(RunnableCallable):
446
519
  """A node for executing tools in LangGraph workflows.
447
520
 
@@ -623,17 +696,10 @@ class _ToolNode(RunnableCallable):
623
696
  )
624
697
  tool_runtimes.append(tool_runtime)
625
698
 
626
- # Inject tool arguments (including runtime)
627
-
628
- injected_tool_calls = []
699
+ # Pass original tool calls without injection
629
700
  input_types = [input_type] * len(tool_calls)
630
- for call, tool_runtime in zip(tool_calls, tool_runtimes, strict=False):
631
- injected_call = self._inject_tool_args(call, tool_runtime) # type: ignore[arg-type]
632
- injected_tool_calls.append(injected_call)
633
701
  with get_executor_for_config(config) as executor:
634
- outputs = list(
635
- executor.map(self._run_one, injected_tool_calls, input_types, tool_runtimes)
636
- )
702
+ outputs = list(executor.map(self._run_one, tool_calls, input_types, tool_runtimes))
637
703
 
638
704
  return self._combine_tool_outputs(outputs, input_type)
639
705
 
@@ -660,12 +726,10 @@ class _ToolNode(RunnableCallable):
660
726
  )
661
727
  tool_runtimes.append(tool_runtime)
662
728
 
663
- injected_tool_calls = []
729
+ # Pass original tool calls without injection
664
730
  coros = []
665
731
  for call, tool_runtime in zip(tool_calls, tool_runtimes, strict=False):
666
- injected_call = self._inject_tool_args(call, tool_runtime) # type: ignore[arg-type]
667
- injected_tool_calls.append(injected_call)
668
- coros.append(self._arun_one(injected_call, input_type, tool_runtime)) # type: ignore[arg-type]
732
+ coros.append(self._arun_one(call, input_type, tool_runtime)) # type: ignore[arg-type]
669
733
  outputs = await asyncio.gather(*coros)
670
734
 
671
735
  return self._combine_tool_outputs(outputs, input_type)
@@ -742,13 +806,23 @@ class _ToolNode(RunnableCallable):
742
806
  msg = f"Tool {call['name']} is not registered with ToolNode"
743
807
  raise TypeError(msg)
744
808
 
745
- call_args = {**call, "type": "tool_call"}
809
+ # Inject state, store, and runtime right before invocation
810
+ injected_call = self._inject_tool_args(call, request.runtime)
811
+ call_args = {**injected_call, "type": "tool_call"}
746
812
 
747
813
  try:
748
814
  try:
749
815
  response = tool.invoke(call_args, config)
750
816
  except ValidationError as exc:
751
- raise ToolInvocationError(call["name"], exc, call["args"]) from exc
817
+ # Filter out errors for injected arguments
818
+ filtered_errors = _filter_validation_errors(
819
+ exc,
820
+ self._tool_to_state_args.get(call["name"], {}),
821
+ self._tool_to_store_arg.get(call["name"]),
822
+ self._tool_to_runtime_arg.get(call["name"]),
823
+ )
824
+ # Use original call["args"] without injected values for error reporting
825
+ raise ToolInvocationError(call["name"], exc, call["args"], filtered_errors) from exc
752
826
 
753
827
  # GraphInterrupt is a special exception that will always be raised.
754
828
  # It can be triggered in the following scenarios,
@@ -887,13 +961,23 @@ class _ToolNode(RunnableCallable):
887
961
  msg = f"Tool {call['name']} is not registered with ToolNode"
888
962
  raise TypeError(msg)
889
963
 
890
- call_args = {**call, "type": "tool_call"}
964
+ # Inject state, store, and runtime right before invocation
965
+ injected_call = self._inject_tool_args(call, request.runtime)
966
+ call_args = {**injected_call, "type": "tool_call"}
891
967
 
892
968
  try:
893
969
  try:
894
970
  response = await tool.ainvoke(call_args, config)
895
971
  except ValidationError as exc:
896
- raise ToolInvocationError(call["name"], exc, call["args"]) from exc
972
+ # Filter out errors for injected arguments
973
+ filtered_errors = _filter_validation_errors(
974
+ exc,
975
+ self._tool_to_state_args.get(call["name"], {}),
976
+ self._tool_to_store_arg.get(call["name"]),
977
+ self._tool_to_runtime_arg.get(call["name"]),
978
+ )
979
+ # Use original call["args"] without injected values for error reporting
980
+ raise ToolInvocationError(call["name"], exc, call["args"], filtered_errors) from exc
897
981
 
898
982
  # GraphInterrupt is a special exception that will always be raised.
899
983
  # It can be triggered in the following scenarios,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: langchain
3
- Version: 1.0.1
3
+ Version: 1.0.2
4
4
  Summary: Building applications with LLMs through composability
5
5
  Project-URL: Homepage, https://docs.langchain.com/
6
6
  Project-URL: Documentation, https://reference.langchain.com/python/langchain/langchain/
@@ -27,8 +27,8 @@ langchain/embeddings/base.py,sha256=V9YgYiRAJs5U5o8BQUsmfV8XtRQrRWV3rENXHLKiECg,
27
27
  langchain/messages/__init__.py,sha256=p7NlF1yf8MkMgJzJ2wggXGkkA_okz1f-g63KoflL6PA,1710
28
28
  langchain/rate_limiters/__init__.py,sha256=5490xUNhet37N2nX6kbJlDgf8u1DX-C1Cs_r7etXn8A,351
29
29
  langchain/tools/__init__.py,sha256=hMzbaGcfHhNYfJx20uV57uMd9a-gNLbmopG4gDReeEc,628
30
- langchain/tools/tool_node.py,sha256=JVrF5W8JER7Y--f1zagMJz8efOaT6gsYfuh98HAZQ8g,65378
31
- langchain-1.0.1.dist-info/METADATA,sha256=eX-uYQv1NzmNVWzJWwd9SibmfmWUEsWMVUkmgeYCn70,4709
32
- langchain-1.0.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
33
- langchain-1.0.1.dist-info/licenses/LICENSE,sha256=TsZ-TKbmch26hJssqCJhWXyGph7iFLvyFBYAa3stBHg,1067
34
- langchain-1.0.1.dist-info/RECORD,,
30
+ langchain/tools/tool_node.py,sha256=3wHSg6TQk43Zv3xrkuLwYZn5Hv5w3oZOv0vkmI_40sQ,69489
31
+ langchain-1.0.2.dist-info/METADATA,sha256=XNAflqRgEm9FqOvFckJKFFhjmP4ouNOJ8HdPXJrKgbc,4709
32
+ langchain-1.0.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
33
+ langchain-1.0.2.dist-info/licenses/LICENSE,sha256=TsZ-TKbmch26hJssqCJhWXyGph7iFLvyFBYAa3stBHg,1067
34
+ langchain-1.0.2.dist-info/RECORD,,