langgraph-prebuilt 0.1.2__tar.gz → 0.1.4__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: langgraph-prebuilt
3
- Version: 0.1.2
3
+ Version: 0.1.4
4
4
  Summary: Library with high-level APIs for creating and executing LangGraph agents and tools.
5
5
  Home-page: https://www.github.com/langchain-ai/langgraph
6
6
  License: MIT
@@ -10,6 +10,7 @@ from typing import (
10
10
  TypeVar,
11
11
  Union,
12
12
  cast,
13
+ get_type_hints,
13
14
  )
14
15
 
15
16
  from langchain_core.language_models import (
@@ -57,13 +58,27 @@ class AgentState(TypedDict):
57
58
  remaining_steps: RemainingSteps
58
59
 
59
60
 
61
+ class AgentStatePydantic(BaseModel):
62
+ """The state of the agent."""
63
+
64
+ messages: Annotated[Sequence[BaseMessage], add_messages]
65
+
66
+ remaining_steps: RemainingSteps = 25
67
+
68
+
60
69
  class AgentStateWithStructuredResponse(AgentState):
61
70
  """The state of the agent with a structured response."""
62
71
 
63
72
  structured_response: StructuredResponse
64
73
 
65
74
 
66
- StateSchema = TypeVar("StateSchema", bound=AgentState)
75
+ class AgentStateWithStructuredResponsePydantic(AgentStatePydantic):
76
+ """The state of the agent with a structured response."""
77
+
78
+ structured_response: StructuredResponse
79
+
80
+
81
+ StateSchema = TypeVar("StateSchema", bound=Union[AgentState, AgentStatePydantic])
67
82
  StateSchemaType = Type[StateSchema]
68
83
 
69
84
  PROMPT_RUNNABLE_NAME = "Prompt"
@@ -76,21 +91,29 @@ Prompt = Union[
76
91
  ]
77
92
 
78
93
 
94
+ def _get_state_value(state: StateSchema, key: str, default: Any = None) -> Any:
95
+ return (
96
+ state.get(key, default)
97
+ if isinstance(state, dict)
98
+ else getattr(state, key, default)
99
+ )
100
+
101
+
79
102
  def _get_prompt_runnable(prompt: Optional[Prompt]) -> Runnable:
80
103
  prompt_runnable: Runnable
81
104
  if prompt is None:
82
105
  prompt_runnable = RunnableCallable(
83
- lambda state: state["messages"], name=PROMPT_RUNNABLE_NAME
106
+ lambda state: _get_state_value(state, "messages"), name=PROMPT_RUNNABLE_NAME
84
107
  )
85
108
  elif isinstance(prompt, str):
86
109
  _system_message: BaseMessage = SystemMessage(content=prompt)
87
110
  prompt_runnable = RunnableCallable(
88
- lambda state: [_system_message] + state["messages"],
111
+ lambda state: [_system_message] + _get_state_value(state, "messages"),
89
112
  name=PROMPT_RUNNABLE_NAME,
90
113
  )
91
114
  elif isinstance(prompt, SystemMessage):
92
115
  prompt_runnable = RunnableCallable(
93
- lambda state: [prompt] + state["messages"],
116
+ lambda state: [prompt] + _get_state_value(state, "messages"),
94
117
  name=PROMPT_RUNNABLE_NAME,
95
118
  )
96
119
  elif inspect.iscoroutinefunction(prompt):
@@ -234,7 +257,7 @@ def _validate_chat_history(
234
257
  @_convert_modifier_to_prompt
235
258
  def create_react_agent(
236
259
  model: Union[str, LanguageModelLike],
237
- tools: Union[Sequence[BaseTool], ToolNode],
260
+ tools: Union[Sequence[Union[BaseTool, Callable]], ToolNode],
238
261
  *,
239
262
  prompt: Optional[Prompt] = None,
240
263
  response_format: Optional[
@@ -283,7 +306,7 @@ def create_react_agent(
283
306
  The graph will make a separate call to the LLM to generate the structured response after the agent loop is finished.
284
307
  This is not the only strategy to get structured responses, see more options in [this guide](https://langchain-ai.github.io/langgraph/how-tos/react-agent-structured-output/).
285
308
  state_schema: An optional state schema that defines graph state.
286
- Must have `messages` and `is_last_step` keys.
309
+ Must have `messages` and `remaining_steps` keys.
287
310
  Defaults to `AgentState` that defines those two keys.
288
311
  config_schema: An optional schema for configuration.
289
312
  Use this to expose configurable parameters via agent.config_specs.
@@ -359,12 +382,11 @@ def create_react_agent(
359
382
  Use with a simple tool:
360
383
 
361
384
  ```pycon
362
- >>> from datetime import datetime
363
385
  >>> from langchain_openai import ChatOpenAI
364
386
  >>> from langgraph.prebuilt import create_react_agent
365
387
 
366
388
 
367
- ... def check_weather(location: str, at_time: datetime | None = None) -> str:
389
+ ... def check_weather(location: str) -> str:
368
390
  ... '''Return the weather forecast for the specified location.'''
369
391
  ... return f"It's always sunny in {location}"
370
392
  >>>
@@ -572,7 +594,7 @@ def create_react_agent(
572
594
 
573
595
  ```pycon
574
596
  >>> import time
575
- ... def check_weather(location: str, at_time: datetime | None = None) -> float:
597
+ ... def check_weather(location: str) -> str:
576
598
  ... '''Return the weather forecast for the specified location.'''
577
599
  ... time.sleep(2)
578
600
  ... return f"It's always sunny in {location}"
@@ -595,7 +617,8 @@ def create_react_agent(
595
617
  if response_format is not None:
596
618
  required_keys.add("structured_response")
597
619
 
598
- if missing_keys := required_keys - set(state_schema.__annotations__):
620
+ schema_keys = set(get_type_hints(state_schema))
621
+ if missing_keys := required_keys - set(schema_keys):
599
622
  raise ValueError(f"Missing required key(s) {missing_keys} in state_schema")
600
623
 
601
624
  if state_schema is None:
@@ -636,35 +659,34 @@ def create_react_agent(
636
659
  # our graph needs to check if these were called
637
660
  should_return_direct = {t.name for t in tool_classes if t.return_direct}
638
661
 
639
- # Define the function that calls the model
640
- def call_model(state: AgentState, config: RunnableConfig) -> AgentState:
641
- _validate_chat_history(state["messages"])
642
- response = cast(AIMessage, model_runnable.invoke(state, config))
643
- # add agent name to the AIMessage
644
- response.name = name
662
+ def _are_more_steps_needed(state: StateSchema, response: BaseMessage) -> bool:
645
663
  has_tool_calls = isinstance(response, AIMessage) and response.tool_calls
646
664
  all_tools_return_direct = (
647
665
  all(call["name"] in should_return_direct for call in response.tool_calls)
648
666
  if isinstance(response, AIMessage)
649
667
  else False
650
668
  )
651
- if (
652
- (
653
- "remaining_steps" not in state
654
- and state.get("is_last_step", False)
655
- and has_tool_calls
656
- )
669
+ remaining_steps = _get_state_value(state, "remaining_steps", None)
670
+ is_last_step = _get_state_value(state, "is_last_step", False)
671
+ return (
672
+ (remaining_steps is None and is_last_step and has_tool_calls)
657
673
  or (
658
- "remaining_steps" in state
659
- and state["remaining_steps"] < 1
674
+ remaining_steps is not None
675
+ and remaining_steps < 1
660
676
  and all_tools_return_direct
661
677
  )
662
- or (
663
- "remaining_steps" in state
664
- and state["remaining_steps"] < 2
665
- and has_tool_calls
666
- )
667
- ):
678
+ or (remaining_steps is not None and remaining_steps < 2 and has_tool_calls)
679
+ )
680
+
681
+ # Define the function that calls the model
682
+ def call_model(state: StateSchema, config: RunnableConfig) -> StateSchema:
683
+ messages = _get_state_value(state, "messages")
684
+ _validate_chat_history(messages)
685
+ response = cast(AIMessage, model_runnable.invoke(state, config))
686
+ # add agent name to the AIMessage
687
+ response.name = name
688
+
689
+ if _are_more_steps_needed(state, response):
668
690
  return {
669
691
  "messages": [
670
692
  AIMessage(
@@ -676,34 +698,13 @@ def create_react_agent(
676
698
  # We return a list, because this will get added to the existing list
677
699
  return {"messages": [response]}
678
700
 
679
- async def acall_model(state: AgentState, config: RunnableConfig) -> AgentState:
680
- _validate_chat_history(state["messages"])
701
+ async def acall_model(state: StateSchema, config: RunnableConfig) -> StateSchema:
702
+ messages = _get_state_value(state, "messages")
703
+ _validate_chat_history(messages)
681
704
  response = cast(AIMessage, await model_runnable.ainvoke(state, config))
682
705
  # add agent name to the AIMessage
683
706
  response.name = name
684
- has_tool_calls = isinstance(response, AIMessage) and response.tool_calls
685
- all_tools_return_direct = (
686
- all(call["name"] in should_return_direct for call in response.tool_calls)
687
- if isinstance(response, AIMessage)
688
- else False
689
- )
690
- if (
691
- (
692
- "remaining_steps" not in state
693
- and state.get("is_last_step", False)
694
- and has_tool_calls
695
- )
696
- or (
697
- "remaining_steps" in state
698
- and state["remaining_steps"] < 1
699
- and all_tools_return_direct
700
- )
701
- or (
702
- "remaining_steps" in state
703
- and state["remaining_steps"] < 2
704
- and has_tool_calls
705
- )
706
- ):
707
+ if _are_more_steps_needed(state, response):
707
708
  return {
708
709
  "messages": [
709
710
  AIMessage(
@@ -716,11 +717,9 @@ def create_react_agent(
716
717
  return {"messages": [response]}
717
718
 
718
719
  def generate_structured_response(
719
- state: AgentState, config: RunnableConfig
720
- ) -> AgentState:
721
- # NOTE: we exclude the last message because there is enough information
722
- # for the LLM to generate the structured response
723
- messages = state["messages"][:-1]
720
+ state: StateSchema, config: RunnableConfig
721
+ ) -> StateSchema:
722
+ messages = _get_state_value(state, "messages")
724
723
  structured_response_schema = response_format
725
724
  if isinstance(response_format, tuple):
726
725
  system_prompt, structured_response_schema = response_format
@@ -733,11 +732,9 @@ def create_react_agent(
733
732
  return {"structured_response": response}
734
733
 
735
734
  async def agenerate_structured_response(
736
- state: AgentState, config: RunnableConfig
737
- ) -> AgentState:
738
- # NOTE: we exclude the last message because there is enough information
739
- # for the LLM to generate the structured response
740
- messages = state["messages"][:-1]
735
+ state: StateSchema, config: RunnableConfig
736
+ ) -> StateSchema:
737
+ messages = _get_state_value(state, "messages")
741
738
  structured_response_schema = response_format
742
739
  if isinstance(response_format, tuple):
743
740
  system_prompt, structured_response_schema = response_format
@@ -773,8 +770,8 @@ def create_react_agent(
773
770
  )
774
771
 
775
772
  # Define the function that determines whether to continue or not
776
- def should_continue(state: AgentState) -> Union[str, list]:
777
- messages = state["messages"]
773
+ def should_continue(state: StateSchema) -> Union[str, list]:
774
+ messages = _get_state_value(state, "messages")
778
775
  last_message = messages[-1]
779
776
  # If there is no function call, then we finish
780
777
  if not isinstance(last_message, AIMessage) or not last_message.tool_calls:
@@ -824,8 +821,8 @@ def create_react_agent(
824
821
  path_map=should_continue_destinations,
825
822
  )
826
823
 
827
- def route_tool_responses(state: AgentState) -> Literal["agent", "__end__"]:
828
- for m in reversed(state["messages"]):
824
+ def route_tool_responses(state: StateSchema) -> Literal["agent", "__end__"]:
825
+ for m in reversed(_get_state_value(state, "messages")):
829
826
  if not isinstance(m, ToolMessage):
830
827
  break
831
828
  if m.name in should_return_direct:
@@ -857,4 +854,7 @@ __all__ = [
857
854
  "create_react_agent",
858
855
  "create_tool_calling_executor",
859
856
  "AgentState",
857
+ "AgentStatePydantic",
858
+ "AgentStateWithStructuredResponse",
859
+ "AgentStateWithStructuredResponsePydantic",
860
860
  ]
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "langgraph-prebuilt"
3
- version = "0.1.2"
3
+ version = "0.1.4"
4
4
  description = "Library with high-level APIs for creating and executing LangGraph agents and tools."
5
5
  authors = []
6
6
  license = "MIT"