mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-08-17 21:25:46 +02:00
Merge branch 'main' into rlm/update-nomic-embd
This commit is contained in:
@@ -175,3 +175,5 @@ docs/docs_skeleton/yarn.lock
|
||||
# Any new jupyter notebooks
|
||||
# not intended for the repo
|
||||
Untitled*.ipynb
|
||||
|
||||
Chinook.db
|
||||
|
||||
@@ -10,12 +10,18 @@
|
||||
|
||||
## Overview
|
||||
|
||||
[LangGraph](https://langchain-ai.github.io/langgraph/) is a library for building stateful, multi-actor applications with LLMs.
|
||||
Inspired by [Pregel](https://research.google/pubs/pub37252/) and [Apache Beam](https://beam.apache.org/), LangGraph lets you coordinate and checkpoint multiple chains (or actors) across cyclic computational steps using regular python functions (or [JS](https://github.com/langchain-ai/langgraphjs)). The public interface draws inspiration from [NetworkX](https://networkx.org/documentation/latest/).
|
||||
[LangGraph](https://langchain-ai.github.io/langgraph/) is a library for building stateful, multi-actor applications with LLMs, used to create agent and multi-agent workflows. Compared to other LLM frameworks, it offers these core benefits: cycles, controllability, and persistence. LangGraph allows you to define flows that involve cycles, essential for most agentic architectures, differentiating it from DAG-based solutions. As a very low-level framework, it provides fine-grained control over both the flow and state of your application, crucial for creating reliable agents. Additionally, LangGraph includes built-in persistence, enabling advanced human-in-the-loop and memory features.
|
||||
|
||||
The main use is for adding **cycles** and **persistence** to your LLM application. If you only need quick Directed Acyclic Graphs (DAGs), you can already accomplish this using [LangChain Expression Language](https://python.langchain.com/docs/expression_language/).
|
||||
LangGraph is inspired by [Pregel](https://research.google/pubs/pub37252/) and [Apache Beam](https://beam.apache.org/). The public interface draws inspiration from [NetworkX](https://networkx.org/documentation/latest/). LangGraph is built by LangChain Inc, the creators of LangChain, but can be used without LangChain.
|
||||
|
||||
### Key Features
|
||||
|
||||
- **Cycles and Branching**: Implement loops and conditionals in your apps.
|
||||
- **Persistence**: Automatically save state after each step in the graph. Pause and resume the graph execution at any point to support error recovery, human-in-the-loop workflows, time travel and more.
|
||||
- **Human-in-the-Loop**: Interrupt graph execution to approve or edit next action planned by the agent.
|
||||
- **Streaming Support**: Stream outputs as they are produced by each node (including token streaming).
|
||||
- **Integration with LangChain**: LangGraph integrates seamlessly with [LangChain](https://github.com/langchain-ai/langchain/) and [LangSmith](https://docs.smith.langchain.com/) (but does not require them).
|
||||
|
||||
Cycles are important for agentic behaviors, where you call an LLM in a loop, asking it what action to take next.
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -23,196 +29,16 @@ Cycles are important for agentic behaviors, where you call an LLM in a loop, ask
|
||||
pip install -U langgraph
|
||||
```
|
||||
|
||||
## Quick start
|
||||
## Example
|
||||
|
||||
One of the central concepts of LangGraph is state. Each graph execution creates a state that is passed between nodes in the graph as they execute, and each node updates this internal state with its return value after it executes. The way that the graph updates its internal state is defined by either the type of graph chosen or a custom function.
|
||||
|
||||
State in LangGraph can be pretty general, but to keep things simpler to start, we'll show off an example where the graph's state is limited to a list of chat messages using the built-in `MessageGraph` class. This is convenient when using LangGraph with LangChain chat models because we can directly return chat model output.
|
||||
|
||||
First, install the LangChain OpenAI integration package:
|
||||
|
||||
```python
|
||||
pip install langchain_openai
|
||||
```
|
||||
|
||||
We also need to export some environment variables:
|
||||
Let's take a look at a simple example of an agent that can search the web using [Tavily Search API](https://tavily.com/).
|
||||
|
||||
```shell
|
||||
export OPENAI_API_KEY=sk-...
|
||||
pip install langchain_openai langchain_community
|
||||
```
|
||||
|
||||
And now we're ready! The graph below contains a single node called `"oracle"` that executes a chat model, then returns the result:
|
||||
|
||||
```python
|
||||
from langchain_openai import ChatOpenAI
|
||||
from langchain_core.messages import HumanMessage
|
||||
from langgraph.graph import END, MessageGraph
|
||||
|
||||
model = ChatOpenAI(temperature=0)
|
||||
|
||||
graph = MessageGraph()
|
||||
|
||||
graph.add_node("oracle", model)
|
||||
graph.add_edge("oracle", END)
|
||||
|
||||
graph.set_entry_point("oracle")
|
||||
|
||||
runnable = graph.compile()
|
||||
```
|
||||
|
||||
Let's run it!
|
||||
|
||||
```python
|
||||
runnable.invoke(HumanMessage("What is 1 + 1?"))
|
||||
```
|
||||
|
||||
```
|
||||
[HumanMessage(content='What is 1 + 1?'), AIMessage(content='1 + 1 equals 2.')]
|
||||
```
|
||||
|
||||
So what did we do here? Let's break it down step by step:
|
||||
|
||||
1. First, we initialize our model and a `MessageGraph`.
|
||||
2. Next, we add a single node to the graph, called `"oracle"`, which simply calls the model with the given input.
|
||||
3. We add an edge from this `"oracle"` node to the special string `END` (`"__end__"`). This means that execution will end after the current node.
|
||||
4. We set `"oracle"` as the entrypoint to the graph.
|
||||
5. We compile the graph, translating it to low-level [pregel operations](https://research.google/pubs/pregel-a-system-for-large-scale-graph-processing/) ensuring that it can be run.
|
||||
|
||||
Then, when we execute the graph:
|
||||
|
||||
1. LangGraph adds the input message to the internal state, then passes the state to the entrypoint node, `"oracle"`.
|
||||
2. The `"oracle"` node executes, invoking the chat model.
|
||||
3. The chat model returns an `AIMessage`. LangGraph adds this to the state.
|
||||
4. Execution progresses to the special `END` value and outputs the final state.
|
||||
|
||||
And as a result, we get a list of two chat messages as output.
|
||||
|
||||
### Interaction with LCEL
|
||||
|
||||
As an aside for those already familiar with LangChain - `add_node` actually takes any function or [runnable](https://python.langchain.com/docs/expression_language/interface/) as input. In the above example, the model is used "as-is", but we could also have passed in a function:
|
||||
|
||||
```python
|
||||
def call_oracle(messages: list):
|
||||
return model.invoke(messages)
|
||||
|
||||
graph.add_node("oracle", call_oracle)
|
||||
```
|
||||
|
||||
Just make sure you are mindful of the fact that the input to the [runnable](https://python.langchain.com/docs/expression_language/interface/) is the **entire current state**. So this will fail:
|
||||
|
||||
```python
|
||||
# This will not work with MessageGraph!
|
||||
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
|
||||
|
||||
prompt = ChatPromptTemplate.from_messages([
|
||||
("system", "You are a helpful assistant named {name} who always speaks in pirate dialect"),
|
||||
MessagesPlaceholder(variable_name="messages"),
|
||||
])
|
||||
|
||||
chain = prompt | model
|
||||
|
||||
# State is a list of messages, but our chain expects a dict input:
|
||||
#
|
||||
# { "name": some_string, "messages": [] }
|
||||
#
|
||||
# Therefore, the graph will throw an exception when it executes here.
|
||||
graph.add_node("oracle", chain)
|
||||
```
|
||||
|
||||
## Conditional edges
|
||||
|
||||
Now, let's move onto something a little bit less trivial. LLMs struggle with math, so let's allow the LLM to conditionally call a `"multiply"` node using [tool calling](https://python.langchain.com/docs/modules/model_io/chat/function_calling/).
|
||||
|
||||
We'll recreate our graph with an additional `"multiply"` that will take the result of the most recent message, if it is a tool call, and calculate the result.
|
||||
We'll also [bind](https://api.python.langchain.com/en/latest/chat_models/langchain_openai.chat_models.base.ChatOpenAI.html#langchain_openai.chat_models.base.ChatOpenAI.bind_tools) the calculator's schema to the OpenAI model as a tool to allow the model to optionally use the tool necessary to respond to the current state:
|
||||
|
||||
```python
|
||||
from langchain_core.tools import tool
|
||||
from langgraph.prebuilt import ToolNode
|
||||
|
||||
@tool
|
||||
def multiply(first_number: int, second_number: int):
|
||||
"""Multiplies two numbers together."""
|
||||
return first_number * second_number
|
||||
|
||||
model = ChatOpenAI(temperature=0)
|
||||
model_with_tools = model.bind_tools([multiply])
|
||||
|
||||
builder = MessageGraph()
|
||||
|
||||
builder.add_node("oracle", model_with_tools)
|
||||
|
||||
tool_node = ToolNode([multiply])
|
||||
builder.add_node("multiply", tool_node)
|
||||
|
||||
builder.add_edge("multiply", END)
|
||||
|
||||
builder.set_entry_point("oracle")
|
||||
```
|
||||
|
||||
Now let's think - what do we want to have happened?
|
||||
|
||||
- If the `"oracle"` node returns a message expecting a tool call, we want to execute the `"multiply"` node
|
||||
- If not, we can just end execution
|
||||
|
||||
We can achieve this using **conditional edges**, which call a function on the current state and routes execution to a node the function's output.
|
||||
|
||||
Here's what that looks like:
|
||||
|
||||
```python
|
||||
from typing import Literal
|
||||
|
||||
def router(state: List[BaseMessage]) -> Literal["multiply", "__end__"]:
|
||||
tool_calls = state[-1].additional_kwargs.get("tool_calls", [])
|
||||
if len(tool_calls):
|
||||
return "multiply"
|
||||
else:
|
||||
return "__end__"
|
||||
|
||||
builder.add_conditional_edges("oracle", router)
|
||||
```
|
||||
|
||||
If the model output contains a tool call, we move to the `"multiply"` node. Otherwise, we end execution.
|
||||
|
||||
Great! Now all that's left is to compile the graph and try it out. Math-related questions are routed to the calculator tool:
|
||||
|
||||
```python
|
||||
runnable = builder.compile()
|
||||
|
||||
runnable.invoke(HumanMessage("What is 123 * 456?"))
|
||||
```
|
||||
|
||||
```
|
||||
|
||||
[HumanMessage(content='What is 123 * 456?'),
|
||||
AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_OPbdlm8Ih1mNOObGf3tMcNgb', 'function': {'arguments': '{"first_number":123,"second_number":456}', 'name': 'multiply'}, 'type': 'function'}]}),
|
||||
ToolMessage(content='56088', tool_call_id='call_OPbdlm8Ih1mNOObGf3tMcNgb')]
|
||||
```
|
||||
|
||||
While conversational responses are outputted directly:
|
||||
|
||||
```python
|
||||
runnable.invoke(HumanMessage("What is your name?"))
|
||||
```
|
||||
|
||||
```
|
||||
[HumanMessage(content='What is your name?'),
|
||||
AIMessage(content='My name is Assistant. How can I assist you today?')]
|
||||
```
|
||||
|
||||
## Cycles
|
||||
|
||||
Now, let's go over a more general cyclic example. We will recreate the `AgentExecutor` class from LangChain. The agent itself will use chat models and tool calling.
|
||||
This agent will represent all its state as a list of messages.
|
||||
|
||||
We will need to install some LangChain community packages, as well as [Tavily](https://app.tavily.com/sign-in) to use as an example tool.
|
||||
|
||||
```shell
|
||||
pip install -U langgraph langchain_openai tavily-python
|
||||
```
|
||||
|
||||
We also need to export some additional environment variables for OpenAI and Tavily API access.
|
||||
|
||||
```shell
|
||||
export OPENAI_API_KEY=sk-...
|
||||
export TAVILY_API_KEY=tvly-...
|
||||
@@ -225,114 +51,32 @@ export LANGCHAIN_TRACING_V2="true"
|
||||
export LANGCHAIN_API_KEY=ls__...
|
||||
```
|
||||
|
||||
### Set up the tools
|
||||
|
||||
As above, we will first define the tools we want to use.
|
||||
For this simple example, we will use a web search tool.
|
||||
However, it is really easy to create your own tools - see documentation [here](https://python.langchain.com/docs/modules/agents/tools/custom_tools) on how to do that.
|
||||
|
||||
```python
|
||||
from typing import Annotated, Literal, TypedDict
|
||||
|
||||
from langchain_core.messages import HumanMessage
|
||||
from langchain_community.tools.tavily_search import TavilySearchResults
|
||||
|
||||
tools = [TavilySearchResults(max_results=1)]
|
||||
```
|
||||
|
||||
We can now wrap these tools in a simple LangGraph [ToolNode](https://langchain-ai.github.io/langgraph/reference/prebuilt/#toolnode).
|
||||
This class receives the list of messages (containing [tool_calls](https://api.python.langchain.com/en/latest/messages/langchain_core.messages.ai.AIMessage.html#langchain_core.messages.ai.AIMessage.tool_calls), calls the tool(s) the LLM has requested to run, and returns the output as new [ToolMessage](https://api.python.langchain.com/en/latest/messages/langchain_core.messages.tool.ToolMessage.html#langchain_core.messages.tool.ToolMessage)(s).
|
||||
|
||||
|
||||
```python
|
||||
from langchain_openai import ChatOpenAI
|
||||
from langgraph.checkpoint import MemorySaver
|
||||
from langgraph.graph import END, StateGraph, MessagesState
|
||||
from langgraph.prebuilt import ToolNode
|
||||
|
||||
|
||||
# Define the tools for the agent to use
|
||||
tools = [TavilySearchResults(max_results=1)]
|
||||
tool_node = ToolNode(tools)
|
||||
```
|
||||
|
||||
### Set up the model
|
||||
|
||||
Now we need to load the chat model to use.
|
||||
|
||||
```python
|
||||
from langchain_openai import ChatOpenAI
|
||||
|
||||
# We will set streaming=True so that we can stream tokens
|
||||
# See the streaming section for more information on this.
|
||||
model = ChatOpenAI(model="gpt-3.5-turbo", temperature=0, streaming=True)
|
||||
```
|
||||
|
||||
After we've done this, we should make sure the model knows that it has these tools available to call.
|
||||
We can do this by converting the LangChain tools into the format for OpenAI tool calling using the [bind_tools()](https://api.python.langchain.com/en/latest/chat_models/langchain_openai.chat_models.base.ChatOpenAI.html#langchain_openai.chat_models.base.ChatOpenAI.bind_tools) method.
|
||||
|
||||
```python
|
||||
model = model.bind_tools(tools)
|
||||
```
|
||||
|
||||
### Define the agent state
|
||||
|
||||
This time, we'll use the more general `StateGraph`.
|
||||
This graph is parameterized by a state object that it passes around to each node.
|
||||
Remember that each node then returns operations to update that state.
|
||||
These operations can either SET specific attributes on the state (e.g. overwrite the existing values) or ADD to the existing attribute.
|
||||
Whether to set or add is denoted by annotating the state object you construct the graph with.
|
||||
|
||||
For this example, the state we will track will just be a list of messages.
|
||||
We want each node to just add messages to that list.
|
||||
Therefore, we will use a `TypedDict` with one key (`messages`) and annotate it so that we always **add** to the `messages` key when updating it using the is always added to with the second parameter (`operator.add`).
|
||||
(Note: the state can be any [type](https://docs.python.org/3/library/stdtypes.html#type-objects), including [pydantic BaseModel's](https://docs.pydantic.dev/latest/api/base_model/)).
|
||||
|
||||
```python
|
||||
from typing import TypedDict, Annotated
|
||||
|
||||
def add_messages(left: list, right: list):
|
||||
"""Add-don't-overwrite."""
|
||||
return left + right
|
||||
|
||||
class AgentState(TypedDict):
|
||||
# The `add_messages` function within the annotation defines
|
||||
# *how* updates should be merged into the state.
|
||||
messages: Annotated[list, add_messages]
|
||||
```
|
||||
|
||||
You can think of the `MessageGraph` used in the initial example as a preconfigured version of this graph, where the state is directly an array of messages,
|
||||
and the update step always appends the returned values of a node to the internal state.
|
||||
|
||||
### Define the nodes
|
||||
|
||||
We now need to define a few different nodes in our graph.
|
||||
In `langgraph`, a node can be either a regular python function or a [runnable](https://python.langchain.com/docs/expression_language/).
|
||||
|
||||
There are two main nodes we need for this:
|
||||
|
||||
1. The agent: responsible for deciding what (if any) actions to take.
|
||||
2. A function to invoke tools: if the agent decides to take an action, this node will then execute that action. We already defined this above.
|
||||
|
||||
We will also need to define some edges.
|
||||
Some of these edges may be conditional.
|
||||
The reason they are conditional is that the destination depends on the contents of the graph's `State`.
|
||||
|
||||
The path that is taken is not known until that node is run (the LLM decides). For our use case, we will need one of each type of edge:
|
||||
|
||||
1. Conditional Edge: after the agent is called, we should either:
|
||||
|
||||
a. Run tools if the agent said to take an action, OR
|
||||
|
||||
b. Finish (respond to the user) if the agent did not ask to run tools
|
||||
|
||||
2. Normal Edge: after the tools are invoked, the graph should always return to the agent to decide what to do next
|
||||
|
||||
Let's define the nodes, as well as a function to define the conditional edge to take.
|
||||
|
||||
```python
|
||||
from typing import Literal
|
||||
model = ChatOpenAI(temperature=0).bind_tools(tools)
|
||||
|
||||
# Define the function that determines whether to continue or not
|
||||
def should_continue(state: AgentState) -> Literal["tools", "__end__"]:
|
||||
def should_continue(state: AgentState) -> Literal["tools", END]:
|
||||
messages = state['messages']
|
||||
last_message = messages[-1]
|
||||
# If the LLM makes a tool call, then we route to the "tools" node
|
||||
if last_message.tool_calls:
|
||||
return "tools"
|
||||
# Otherwise, we stop (reply to the user)
|
||||
return "__end__"
|
||||
return END
|
||||
|
||||
|
||||
# Define the function that calls the model
|
||||
@@ -341,16 +85,10 @@ def call_model(state: AgentState):
|
||||
response = model.invoke(messages)
|
||||
# We return a list, because this will get added to the existing list
|
||||
return {"messages": [response]}
|
||||
```
|
||||
|
||||
### Define the graph
|
||||
|
||||
We can now put it all together and define the graph!
|
||||
|
||||
```python
|
||||
from langgraph.graph import StateGraph, END
|
||||
# Define a new graph
|
||||
workflow = StateGraph(AgentState)
|
||||
workflow = StateGraph(MessagesState)
|
||||
|
||||
# Define the two nodes we will cycle between
|
||||
workflow.add_node("agent", call_model)
|
||||
@@ -373,130 +111,97 @@ workflow.add_conditional_edges(
|
||||
# This means that after `tools` is called, `agent` node is called next.
|
||||
workflow.add_edge("tools", 'agent')
|
||||
|
||||
# Initialize memory to persist state between graph runs
|
||||
checkpointer = MemorySaver()
|
||||
|
||||
# Finally, we compile it!
|
||||
# This compiles it into a LangChain Runnable,
|
||||
# meaning you can use it as you would any other runnable
|
||||
app = workflow.compile()
|
||||
# meaning you can use it as you would any other runnable.
|
||||
# Note that we're (optionally) passing the memory when compiling the graph
|
||||
app = workflow.compile(checkpointer=checkpointer)
|
||||
|
||||
# Use the Runnable
|
||||
final_state = app.invoke(
|
||||
{"messages": [HumanMessage(content="what is the weather in sf")]},
|
||||
config={"configurable": {"thread_id": 42}}
|
||||
)
|
||||
final_state["messages"][-1].content
|
||||
```
|
||||
|
||||
### Use it!
|
||||
```
|
||||
'The current weather in San Francisco is as follows:\n- Temperature: 60.1°F (15.6°C)\n- Condition: Partly cloudy\n- Wind: 5.6 mph (9.0 kph) from SSW\n- Humidity: 83%\n- Visibility: 9.0 miles (16.0 km)\n- UV Index: 4.0\n\nFor more details, you can visit [Weather API](https://www.weatherapi.com/).'
|
||||
```
|
||||
|
||||
We can now use it!
|
||||
This now exposes the [same interface](https://python.langchain.com/docs/expression_language/) as all other LangChain runnables.
|
||||
This [runnable](https://python.langchain.com/docs/expression_language/interface/) accepts a list of messages.
|
||||
Now when we pass the same `"thread_id"`, the conversation context is retained via the saved state (i.e. stored list of messages)
|
||||
|
||||
```python
|
||||
from langchain_core.messages import HumanMessage
|
||||
|
||||
inputs = {"messages": [HumanMessage(content="what is the weather in sf")]}
|
||||
app.invoke(inputs)
|
||||
```
|
||||
|
||||
This may take a little bit - it's making a few calls behind the scenes.
|
||||
In order to start seeing some intermediate results as they happen, we can use streaming - see below for more information on that.
|
||||
|
||||
## Streaming
|
||||
|
||||
LangGraph has support for several different types of streaming.
|
||||
|
||||
### Streaming Node Output
|
||||
|
||||
One of the benefits of using LangGraph is that it is easy to stream output as it's produced by each node.
|
||||
|
||||
```python
|
||||
inputs = {"messages": [HumanMessage(content="what is the weather in sf")]}
|
||||
for output in app.stream(inputs, stream_mode="updates"):
|
||||
# stream() yields dictionaries with output keyed by node name
|
||||
for key, value in output.items():
|
||||
print(f"Output from node '{key}':")
|
||||
print("---")
|
||||
print(value)
|
||||
print("\n---\n")
|
||||
final_state = app.invoke(
|
||||
{"messages": [HumanMessage(content="what about ny")]},
|
||||
config={"configurable": {"thread_id": 42}}
|
||||
)
|
||||
final_state["messages"][-1].content
|
||||
```
|
||||
|
||||
```
|
||||
Output from node 'agent':
|
||||
---
|
||||
{'messages': [AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\n "query": "weather in San Francisco"\n}', 'name': 'tavily_search_results_json'}})]}
|
||||
|
||||
---
|
||||
|
||||
Output from node 'tools':
|
||||
---
|
||||
{'messages': [FunctionMessage(content="[{'url': 'https://weatherspark.com/h/m/557/2024/1/Historical-Weather-in-January-2024-in-San-Francisco-California-United-States', 'content': 'January 2024 Weather History in San Francisco California, United States Daily Precipitation in January 2024 in San Francisco Observed Weather in January 2024 in San Francisco San Francisco Temperature History January 2024 Hourly Temperature in January 2024 in San Francisco Hours of Daylight and Twilight in January 2024 in San FranciscoThis report shows the past weather for San Francisco, providing a weather history for January 2024. It features all historical weather data series we have available, including the San Francisco temperature history for January 2024. You can drill down from year to month and even day level reports by clicking on the graphs.'}]", name='tavily_search_results_json')]}
|
||||
|
||||
---
|
||||
|
||||
Output from node 'agent':
|
||||
---
|
||||
{'messages': [AIMessage(content="I couldn't find the current weather in San Francisco. However, you can visit [WeatherSpark](https://weatherspark.com/h/m/557/2024/1/Historical-Weather-in-January-2024-in-San-Francisco-California-United-States) to check the historical weather data for January 2024 in San Francisco.")]}
|
||||
|
||||
---
|
||||
|
||||
Output from node '__end__':
|
||||
---
|
||||
{'messages': [HumanMessage(content='what is the weather in sf'), AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\n "query": "weather in San Francisco"\n}', 'name': 'tavily_search_results_json'}}), FunctionMessage(content="[{'url': 'https://weatherspark.com/h/m/557/2024/1/Historical-Weather-in-January-2024-in-San-Francisco-California-United-States', 'content': 'January 2024 Weather History in San Francisco California, United States Daily Precipitation in January 2024 in San Francisco Observed Weather in January 2024 in San Francisco San Francisco Temperature History January 2024 Hourly Temperature in January 2024 in San Francisco Hours of Daylight and Twilight in January 2024 in San FranciscoThis report shows the past weather for San Francisco, providing a weather history for January 2024. It features all historical weather data series we have available, including the San Francisco temperature history for January 2024. You can drill down from year to month and even day level reports by clicking on the graphs.'}]", name='tavily_search_results_json'), AIMessage(content="I couldn't find the current weather in San Francisco. However, you can visit [WeatherSpark](https://weatherspark.com/h/m/557/2024/1/Historical-Weather-in-January-2024-in-San-Francisco-California-United-States) to check the historical weather data for January 2024 in San Francisco.")]}
|
||||
|
||||
---
|
||||
'The current weather in New York is as follows:\n- Temperature: 20.3°C (68.5°F)\n- Condition: Overcast\n- Wind: 2.2 mph from the north\n- Humidity: 65%\n- Cloud Cover: 100%\n- UV Index: 5.0\n\nFor more details, you can visit [Weather API](https://www.weatherapi.com/).'
|
||||
```
|
||||
|
||||
### Streaming LLM Tokens
|
||||
### Step-by-step Breakdown:
|
||||
|
||||
You can also access the LLM tokens as they are produced by each node.
|
||||
In this case only the "agent" node produces LLM tokens.
|
||||
In order for this to work properly, you must be using an LLM that supports streaming as well as have set it when constructing the LLM (e.g. `ChatOpenAI(model="gpt-3.5-turbo-1106", streaming=True)`)
|
||||
1. <details>
|
||||
<summary>Initialize the model and tools.</summary>
|
||||
|
||||
```python
|
||||
inputs = {"messages": [HumanMessage(content="what is the weather in sf")]}
|
||||
async for output in app.astream_log(inputs, include_types=["llm"]):
|
||||
# astream_log() yields the requested logs (here LLMs) in JSONPatch format
|
||||
for op in output.ops:
|
||||
if op["path"] == "/streamed_output/-":
|
||||
# this is the output from .stream()
|
||||
...
|
||||
elif op["path"].startswith("/logs/") and op["path"].endswith(
|
||||
"/streamed_output/-"
|
||||
):
|
||||
# because we chose to only include LLMs, these are LLM tokens
|
||||
print(op["value"])
|
||||
```
|
||||
- we use `ChatOpenAI` as our LLM. **NOTE:** we need make sure the model knows that it has these tools available to call. We can do this by converting the LangChain tools into the format for OpenAI tool calling using the `.bind_tools()` method.
|
||||
- we define the tools we want to use -- a web search tool in our case. It is really easy to create your own tools - see documentation here on how to do that [here](https://python.langchain.com/docs/modules/agents/tools/custom_tools).
|
||||
</details>
|
||||
2. <details>
|
||||
<summary>Initialize graph with state.</summary>
|
||||
|
||||
```
|
||||
content='' additional_kwargs={'function_call': {'arguments': '', 'name': 'tavily_search_results_json'}}
|
||||
content='' additional_kwargs={'function_call': {'arguments': '{\n', 'name': ''}}}
|
||||
content='' additional_kwargs={'function_call': {'arguments': ' ', 'name': ''}}
|
||||
content='' additional_kwargs={'function_call': {'arguments': ' "', 'name': ''}}
|
||||
content='' additional_kwargs={'function_call': {'arguments': 'query', 'name': ''}}
|
||||
...
|
||||
```
|
||||
- we initialize graph (`StateGraph`) by passing state schema (in our case `MessagesState`)
|
||||
- `MessagesState` is a prebuilt state schema that has one attribute -- a list of LangChain `Message` objects, as well as logic for merging the updates from each node into the state
|
||||
</details>
|
||||
3. <details>
|
||||
<summary>Define graph nodes.</summary>
|
||||
|
||||
## When to Use
|
||||
There are two main nodes we need:
|
||||
- The `agent` node: responsible for deciding what (if any) actions to take.
|
||||
- The `tools` node that invokes tools: if the agent decides to take an action, this node will then execute that action.
|
||||
</details>
|
||||
4. <details>
|
||||
<summary>Define entry point and graph edges.</summary>
|
||||
|
||||
When should you use this versus [LangChain Expression Language](https://python.langchain.com/docs/expression_language/)?
|
||||
First, we need to set the entry point for graph execution - `agent` node.
|
||||
|
||||
If you need cycles.
|
||||
Then we define one normal and one conditional edge. Conditional edge means that the destination depends on the contents of the graph's state (`MessageState`). In our case, the destination is not known until the agent (LLM) decides.
|
||||
|
||||
- Conditional edge: after the agent is called, we should either:
|
||||
- a. Run tools if the agent said to take an action, OR
|
||||
- b. Finish (respond to the user) if the agent did not ask to run tools
|
||||
- Normal edge: after the tools are invoked, the graph should always return to the agent to decide what to do next
|
||||
</details>
|
||||
5. <details>
|
||||
<summary>Compile the graph.</summary>
|
||||
|
||||
- When we compile the graph, we turn it into a LangChain [Runnable](https://python.langchain.com/v0.2/docs/concepts/#runnable-interface), which automatically enables calling `.invoke()`, `.stream()` and `.batch()` with your inputs
|
||||
- We can also optionally pass checkpointer object for persisting state between graph runs, and enabling memory, human-in-the-loop workflows, time travel and more. In our case we use `MemorySaver` - a simple in-memory checkpointer
|
||||
</details>
|
||||
6. <details>
|
||||
<summary>Execute the graph.</summary>
|
||||
|
||||
1. LangGraph adds the input message to the internal state, then passes the state to the entrypoint node, `"agent"`.
|
||||
2. The `"agent"` node executes, invoking the chat model.
|
||||
3. The chat model returns an `AIMessage`. LangGraph adds this to the state.
|
||||
4. Graph cycles the following steps until there are no more `tool_calls` on `AIMessage`:
|
||||
- If `AIMessage` has `tool_calls`, `"tools"` node executes
|
||||
- The `"agent"` node executes again and returns `AIMessage`
|
||||
5. Execution progresses to the special `END` value and outputs the final state.
|
||||
And as a result, we get a list of all our chat messages as output.
|
||||
</details>
|
||||
|
||||
Langchain Expression Language allows you to easily define chains (DAGs) but does not have a good mechanism for adding in cycles.
|
||||
`langgraph` adds that syntax.
|
||||
|
||||
## Documentation
|
||||
|
||||
We hope this gave you a taste of what you can build! Check out the rest of the docs to learn more.
|
||||
|
||||
### Tutorials
|
||||
|
||||
Learn to build with LangGraph through guided examples in the [LangGraph Tutorials](https://langchain-ai.github.io/langgraph/tutorials/).
|
||||
|
||||
We recommend starting with the [Introduction to LangGraph](https://langchain-ai.github.io/langgraph/tutorials/introduction/) before trying out the more advanced guides.
|
||||
|
||||
### How-to Guides
|
||||
|
||||
The [LangGraph how-to guides](https://langchain-ai.github.io/langgraph/how-tos/) show how to accomplish specific things within LangGraph, from streaming, to adding memory & persistence, to common design patterns (branching, subgraphs, etc.), these are the place to go if you want to copy and run a specific code snippet.
|
||||
|
||||
### Conceptual Guides
|
||||
|
||||
The [Conceptual Guides](https://langchain-ai.github.io/langgraph/concepts/) provide in-depth explanations of the key concepts and principles behind LangGraph, such as nodes, edges, state and more.
|
||||
|
||||
### Reference
|
||||
|
||||
LangGraph's API has a few important classes and methods that are all covered in the [Reference Documents](https://langchain-ai.github.io/langgraph/reference/graphs/). Check these out to see the specific function arguments and simple examples of how to use the graph + checkpointing APIs or to see some of the higher-level prebuilt components.
|
||||
* [Tutorials](https://langchain-ai.github.io/langgraph/tutorials/): Learn to build with LangGraph through guided examples.
|
||||
* [How-to Guides](https://langchain-ai.github.io/langgraph/how-tos/): Accomplish specific things within LangGraph, from streaming, to adding memory & persistence, to common design patterns (branching, subgraphs, etc.), these are the place to go if you want to copy and run a specific code snippet.
|
||||
* [Conceptual Guides](https://langchain-ai.github.io/langgraph/concepts/): In-depth explanations of the key concepts and principles behind LangGraph, such as nodes, edges, state and more.
|
||||
* [API Reference](https://langchain-ai.github.io/langgraph/reference/graphs/): Review important classes and methods, simple examples of how to use the graph and checkpointing APIs, higher-level prebuilt components and more.
|
||||
@@ -29,10 +29,13 @@ _MANUAL = {
|
||||
"configuration.ipynb",
|
||||
"map-reduce.ipynb",
|
||||
"extraction/retries.ipynb",
|
||||
"create-react-agent.ipynb",
|
||||
],
|
||||
"tutorials": [
|
||||
"introduction.ipynb",
|
||||
"customer-support/customer-support.ipynb",
|
||||
"tutorials/tnt-llm/tnt-llm.ipynb",
|
||||
"tutorials/sql-agent.ipynb"
|
||||
],
|
||||
}
|
||||
_MANUAL_INVERSE = {v: docs_dir / k for k, vs in _MANUAL.items() for v in vs}
|
||||
@@ -116,6 +119,10 @@ def copy_notebooks():
|
||||
print(f"Overriding: {src_path} to {dst_path}")
|
||||
break
|
||||
|
||||
# Avoid double nesting.
|
||||
dst_path = dst_path.replace("tutorials/tutorials", "tutorials").replace(
|
||||
"how-tos/how-tos", "how-tos"
|
||||
)
|
||||
os.makedirs(os.path.dirname(dst_path), exist_ok=True)
|
||||
print(f"Copying: {src_path} to {dst_path}")
|
||||
shutil.copy(src_path, dst_path)
|
||||
|
||||
@@ -6,6 +6,7 @@ Welcome to the LangGraph how-to guides! These guides provide practical, step-by-
|
||||
|
||||
The core guides show how to address common needs when building out AI workflows, with special focus placed on [ReAct](https://arxiv.org/abs/2210.03629)-style agents with [tool calling](https://python.langchain.com/docs/modules/model_io/chat/function_calling/).
|
||||
|
||||
- [ReAct agent](create-react-agent.ipynb): How to create a tool-calling agent that **Re**asons and **Act**s to accomplish tasks
|
||||
- [Persistence](persistence.ipynb): How to give your graph "memory" and resilience by saving and loading state
|
||||
- [Time travel](time-travel.ipynb): How to navigate and manipulate graph state history once it's persisted
|
||||
- [Async execution](async.ipynb): How to run nodes asynchronously for improved performance
|
||||
|
||||
@@ -55,6 +55,10 @@ Learn from example implementations of graphs designed for specific scenarios and
|
||||
- [Agent-based](chatbot-simulation-evaluation/agent-simulation-evaluation.ipynb): Evaluating chatbots via simulated user interactions
|
||||
- [Within LangSmith](chatbot-simulation-evaluation/langsmith-agent-simulation-evaluation.ipynb): Evaluating chatbots in LangSmith over a dialog dataset
|
||||
|
||||
#### Text Mining
|
||||
|
||||
- [TNT-LLM](tnt-llm/tnt-llm.ipynb): learn to build rich, interpretable taxonomies of user intentand using the classification system developed by Microsoft for their Bing Copilot application.
|
||||
|
||||
#### Competitive Programming
|
||||
|
||||
- [Can Language Models Solve Olympiad Programming?](usaco/usaco.ipynb): Build an agent with few-shot "episodic memory" and human-in-the-loop collaboration to solve problems from the USA Computing Olympiad; adapted from the [paper of the same name](https://arxiv.org/abs/2404.10952v1) by Shi, Tang, Narasimhan, and Yao.
|
||||
|
||||
+4
-1
@@ -2,7 +2,6 @@ site_name: LangGraph
|
||||
site_description: Build language agents as graphs
|
||||
site_url: https://langchain-ai.github.io/langgraph/
|
||||
repo_url: https://github.com/langchain-ai/langgraph
|
||||
edit_uri: edit/main/docs/docs/
|
||||
theme:
|
||||
name: material
|
||||
custom_dir: overrides
|
||||
@@ -129,12 +128,16 @@ nav:
|
||||
- Chatbot Eval via Sim:
|
||||
- Agent-based: tutorials/chatbot-simulation-evaluation/agent-simulation-evaluation.ipynb
|
||||
- In LangSmith: tutorials/chatbot-simulation-evaluation/langsmith-agent-simulation-evaluation.ipynb
|
||||
- Text Mining:
|
||||
- TNT-LLM: tutorials/tnt-llm/tnt-llm.ipynb
|
||||
- Web Navigation: tutorials/web-navigation/web_voyager.ipynb
|
||||
- Competitive Programming: tutorials/usaco/usaco.ipynb
|
||||
- SQL: tutorials/sql-agent.ipynb
|
||||
|
||||
- "How-to Guides":
|
||||
- 'how-tos/index.md'
|
||||
- Core:
|
||||
- "ReAct Agent": how-tos/create-react-agent.ipynb
|
||||
- "Persistence": how-tos/persistence.ipynb
|
||||
- "Time Travel": how-tos/time-travel.ipynb
|
||||
- "Async Execution": how-tos/async.ipynb
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%%capture --no-stderr\n",
|
||||
"%pip install --quiet -U langchain langchain_openai langchainhub tavily-python"
|
||||
"%pip install --quiet -U langgraph langchain langchain_openai langchainhub tavily-python"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -45,8 +45,8 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"import getpass\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"os.environ[\"OPENAI_API_KEY\"] = getpass.getpass(\"OpenAI API Key:\")\n",
|
||||
"os.environ[\"TAVILY_API_KEY\"] = getpass.getpass(\"Tavily API Key:\")"
|
||||
@@ -78,7 +78,7 @@
|
||||
"source": [
|
||||
"## Create the LangChain agent\n",
|
||||
"\n",
|
||||
"First, we will create the LangChain agent. For more information on LangChain agents, see [this documentation](https://python.langchain.com/docs/modules/agents/)"
|
||||
"First, we will create the LangChain agent. For more information on LangChain agents, see [this documentation](https://python.langchain.com/v0.2/docs/concepts/#agents)"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -90,8 +90,8 @@
|
||||
"source": [
|
||||
"from langchain import hub\n",
|
||||
"from langchain.agents import create_openai_functions_agent\n",
|
||||
"from langchain_openai.chat_models import ChatOpenAI\n",
|
||||
"from langchain_community.tools.tavily_search import TavilySearchResults\n",
|
||||
"from langchain_openai.chat_models import ChatOpenAI\n",
|
||||
"\n",
|
||||
"tools = [TavilySearchResults(max_results=1)]\n",
|
||||
"\n",
|
||||
@@ -127,10 +127,11 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing import TypedDict, Annotated, List, Union\n",
|
||||
"import operator\n",
|
||||
"from typing import Annotated, TypedDict, Union\n",
|
||||
"\n",
|
||||
"from langchain_core.agents import AgentAction, AgentFinish\n",
|
||||
"from langchain_core.messages import BaseMessage\n",
|
||||
"import operator\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class AgentState(TypedDict):\n",
|
||||
@@ -155,7 +156,7 @@
|
||||
"## Define the nodes\n",
|
||||
"\n",
|
||||
"We now need to define a few different nodes in our graph.\n",
|
||||
"In `langgraph`, a node can be either a function or a [runnable](https://python.langchain.com/docs/expression_language/).\n",
|
||||
"In `langgraph`, a node can be either a function or a [runnable](https://python.langchain.com/v0.2/docs/concepts/#langchain-expression-language-lcel).\n",
|
||||
"There are two main nodes we need for this:\n",
|
||||
"\n",
|
||||
"1. The agent: responsible for deciding what (if any) actions to take.\n",
|
||||
@@ -182,6 +183,7 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain_core.agents import AgentFinish\n",
|
||||
"\n",
|
||||
"from langgraph.prebuilt.tool_executor import ToolExecutor\n",
|
||||
"\n",
|
||||
"# This a helper class we have that is useful for running tools\n",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%%capture --no-stderr\n",
|
||||
"%pip install --quiet -U langchain langchain_openai tavily-python"
|
||||
"%pip install --quiet -U langgraph langchain langchain_openai tavily-python"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -50,8 +50,8 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"import getpass\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"os.environ[\"OPENAI_API_KEY\"] = getpass.getpass(\"OpenAI API Key:\")\n",
|
||||
"os.environ[\"TAVILY_API_KEY\"] = getpass.getpass(\"Tavily API Key:\")"
|
||||
@@ -83,7 +83,7 @@
|
||||
"source": [
|
||||
"## Create the LangChain agent\n",
|
||||
"\n",
|
||||
"First, we will create the LangChain agent. For more information on LangChain agents, see [this documentation](https://python.langchain.com/docs/modules/agents/)"
|
||||
"First, we will create the LangChain agent. For more information on LangChain agents, see [this documentation](https://python.langchain.com/v0.2/docs/concepts/#agents)"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -95,8 +95,8 @@
|
||||
"source": [
|
||||
"from langchain import hub\n",
|
||||
"from langchain.agents import create_openai_functions_agent\n",
|
||||
"from langchain_openai.chat_models import ChatOpenAI\n",
|
||||
"from langchain_community.tools.tavily_search import TavilySearchResults\n",
|
||||
"from langchain_openai.chat_models import ChatOpenAI\n",
|
||||
"\n",
|
||||
"tools = [TavilySearchResults(max_results=1)]\n",
|
||||
"\n",
|
||||
@@ -132,10 +132,11 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing import TypedDict, Annotated, List, Union\n",
|
||||
"import operator\n",
|
||||
"from typing import Annotated, TypedDict, Union\n",
|
||||
"\n",
|
||||
"from langchain_core.agents import AgentAction, AgentFinish\n",
|
||||
"from langchain_core.messages import BaseMessage\n",
|
||||
"import operator\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class AgentState(TypedDict):\n",
|
||||
@@ -160,7 +161,7 @@
|
||||
"## Define the nodes\n",
|
||||
"\n",
|
||||
"We now need to define a few different nodes in our graph.\n",
|
||||
"In `langgraph`, a node can be either a function or a [runnable](https://python.langchain.com/docs/expression_language/).\n",
|
||||
"In `langgraph`, a node can be either a function or a [runnable](https://python.langchain.com/v0.2/docs/concepts/#langchain-expression-language-lcel).\n",
|
||||
"There are two main nodes we need for this:\n",
|
||||
"\n",
|
||||
"1. The agent: responsible for deciding what (if any) actions to take.\n",
|
||||
@@ -187,6 +188,7 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain_core.agents import AgentFinish\n",
|
||||
"\n",
|
||||
"from langgraph.prebuilt.tool_executor import ToolExecutor\n",
|
||||
"\n",
|
||||
"# This a helper class we have that is useful for running tools\n",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%%capture --no-stderr\n",
|
||||
"%pip install --quiet -U langchain langchain_openai tavily-python"
|
||||
"%pip install --quiet -U langgraph langchain langchain_openai tavily-python"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -50,8 +50,8 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"import getpass\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"os.environ[\"OPENAI_API_KEY\"] = getpass.getpass(\"OpenAI API Key:\")\n",
|
||||
"os.environ[\"TAVILY_API_KEY\"] = getpass.getpass(\"Tavily API Key:\")"
|
||||
@@ -83,7 +83,7 @@
|
||||
"source": [
|
||||
"## Create the LangChain agent\n",
|
||||
"\n",
|
||||
"First, we will create the LangChain agent. For more information on LangChain agents, see [this documentation](https://python.langchain.com/docs/modules/agents/)"
|
||||
"First, we will create the LangChain agent. For more information on LangChain agents, see [this documentation](https://python.langchain.com/v0.2/docs/concepts/#agents)"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -95,8 +95,8 @@
|
||||
"source": [
|
||||
"from langchain import hub\n",
|
||||
"from langchain.agents import create_openai_functions_agent\n",
|
||||
"from langchain_openai.chat_models import ChatOpenAI\n",
|
||||
"from langchain_community.tools.tavily_search import TavilySearchResults\n",
|
||||
"from langchain_openai.chat_models import ChatOpenAI\n",
|
||||
"\n",
|
||||
"tools = [TavilySearchResults(max_results=1)]\n",
|
||||
"\n",
|
||||
@@ -132,10 +132,11 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing import TypedDict, Annotated, List, Union\n",
|
||||
"import operator\n",
|
||||
"from typing import Annotated, TypedDict, Union\n",
|
||||
"\n",
|
||||
"from langchain_core.agents import AgentAction, AgentFinish\n",
|
||||
"from langchain_core.messages import BaseMessage\n",
|
||||
"import operator\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class AgentState(TypedDict):\n",
|
||||
@@ -160,7 +161,7 @@
|
||||
"## Define the nodes\n",
|
||||
"\n",
|
||||
"We now need to define a few different nodes in our graph.\n",
|
||||
"In `langgraph`, a node can be either a function or a [runnable](https://python.langchain.com/docs/expression_language/).\n",
|
||||
"In `langgraph`, a node can be either a function or a [runnable](https://python.langchain.com/v0.2/docs/concepts/#langchain-expression-language-lcel).\n",
|
||||
"There are two main nodes we need for this:\n",
|
||||
"\n",
|
||||
"1. The agent: responsible for deciding what (if any) actions to take.\n",
|
||||
@@ -187,6 +188,7 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain_core.agents import AgentFinish\n",
|
||||
"\n",
|
||||
"from langgraph.prebuilt.tool_executor import ToolExecutor\n",
|
||||
"\n",
|
||||
"# This a helper class we have that is useful for running tools\n",
|
||||
@@ -317,7 +319,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stdin",
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"[y/n] continue with: tool='tavily_search_results_json' tool_input={'query': 'weather in San Francisco'} log=\"\\nInvoking: `tavily_search_results_json` with `{'query': 'weather in San Francisco'}`\\n\\n\\n\" message_log=[AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\"query\":\"weather in San Francisco\"}', 'name': 'tavily_search_results_json'}})]? y\n"
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%%capture --no-stderr\n",
|
||||
"%pip install --quiet -U langchain langchain_openai tavily-python"
|
||||
"%pip install --quiet -U langgraph langchain langchain_openai tavily-python"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -50,8 +50,8 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"import getpass\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"os.environ[\"OPENAI_API_KEY\"] = getpass.getpass(\"OpenAI API Key:\")\n",
|
||||
"os.environ[\"TAVILY_API_KEY\"] = getpass.getpass(\"Tavily API Key:\")"
|
||||
@@ -83,7 +83,7 @@
|
||||
"source": [
|
||||
"## Create the LangChain agent\n",
|
||||
"\n",
|
||||
"First, we will create the LangChain agent. For more information on LangChain agents, see [this documentation](https://python.langchain.com/docs/modules/agents/)"
|
||||
"First, we will create the LangChain agent. For more information on LangChain agents, see [this documentation](https://python.langchain.com/v0.2/docs/concepts/#agents)"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -95,8 +95,8 @@
|
||||
"source": [
|
||||
"from langchain import hub\n",
|
||||
"from langchain.agents import create_openai_functions_agent\n",
|
||||
"from langchain_openai.chat_models import ChatOpenAI\n",
|
||||
"from langchain_community.tools.tavily_search import TavilySearchResults\n",
|
||||
"from langchain_openai.chat_models import ChatOpenAI\n",
|
||||
"\n",
|
||||
"tools = [TavilySearchResults(max_results=1)]\n",
|
||||
"\n",
|
||||
@@ -132,10 +132,11 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing import TypedDict, Annotated, List, Union\n",
|
||||
"import operator\n",
|
||||
"from typing import Annotated, TypedDict, Union\n",
|
||||
"\n",
|
||||
"from langchain_core.agents import AgentAction, AgentFinish\n",
|
||||
"from langchain_core.messages import BaseMessage\n",
|
||||
"import operator\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class AgentState(TypedDict):\n",
|
||||
@@ -160,7 +161,7 @@
|
||||
"## Define the nodes\n",
|
||||
"\n",
|
||||
"We now need to define a few different nodes in our graph.\n",
|
||||
"In `langgraph`, a node can be either a function or a [runnable](https://python.langchain.com/docs/expression_language/).\n",
|
||||
"In `langgraph`, a node can be either a function or a [runnable](https://python.langchain.com/v0.2/docs/concepts/#langchain-expression-language-lcel).\n",
|
||||
"There are two main nodes we need for this:\n",
|
||||
"\n",
|
||||
"1. The agent: responsible for deciding what (if any) actions to take.\n",
|
||||
@@ -187,6 +188,7 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain_core.agents import AgentFinish\n",
|
||||
"\n",
|
||||
"from langgraph.prebuilt.tool_executor import ToolExecutor\n",
|
||||
"\n",
|
||||
"# This a helper class we have that is useful for running tools\n",
|
||||
|
||||
@@ -57,8 +57,8 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"import getpass\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def _set_env(var: str):\n",
|
||||
@@ -113,8 +113,10 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"from typing import Annotated\n",
|
||||
"\n",
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"\n",
|
||||
"from langgraph.graph.message import add_messages\n",
|
||||
"\n",
|
||||
"# Add messages essentially does this with more\n",
|
||||
@@ -306,7 +308,7 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.graph import StateGraph, END\n",
|
||||
"from langgraph.graph import END, StateGraph\n",
|
||||
"\n",
|
||||
"# Define a new graph\n",
|
||||
"workflow = StateGraph(State)\n",
|
||||
@@ -584,7 +586,7 @@
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.2"
|
||||
"version": "3.12.2"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
|
||||
+20
-17
@@ -40,10 +40,12 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.graph import StateGraph\n",
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"from typing import Annotated\n",
|
||||
"import operator\n",
|
||||
"from typing import Annotated, Any\n",
|
||||
"\n",
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"\n",
|
||||
"from langgraph.graph import StateGraph\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class State(TypedDict):\n",
|
||||
@@ -51,9 +53,6 @@
|
||||
" aggregate: Annotated[list, operator.add]\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"from typing import Any\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class ReturnNodeValue:\n",
|
||||
" def __init__(self, node_secret: str):\n",
|
||||
" self._value = node_secret\n",
|
||||
@@ -164,10 +163,12 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.graph import StateGraph\n",
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"from typing import Annotated\n",
|
||||
"import operator\n",
|
||||
"from typing import Annotated\n",
|
||||
"\n",
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"\n",
|
||||
"from langgraph.graph import StateGraph\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class State(TypedDict):\n",
|
||||
@@ -264,11 +265,12 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing import Sequence\n",
|
||||
"from langgraph.graph import StateGraph, END, START\n",
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"from typing import Annotated\n",
|
||||
"import operator\n",
|
||||
"from typing import Annotated, Sequence\n",
|
||||
"\n",
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"\n",
|
||||
"from langgraph.graph import END, START, StateGraph\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class State(TypedDict):\n",
|
||||
@@ -412,11 +414,12 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing import Sequence\n",
|
||||
"from langgraph.graph import StateGraph\n",
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"from typing import Annotated\n",
|
||||
"import operator\n",
|
||||
"from typing import Annotated, Sequence\n",
|
||||
"\n",
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"\n",
|
||||
"from langgraph.graph import StateGraph\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def reduce_fanouts(left, right):\n",
|
||||
|
||||
@@ -47,8 +47,8 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"import getpass\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"os.environ[\"OPENAI_API_KEY\"] = getpass.getpass(\"OpenAI API Key:\")\n",
|
||||
"os.environ[\"TAVILY_API_KEY\"] = getpass.getpass(\"Tavily API Key:\")"
|
||||
@@ -82,7 +82,7 @@
|
||||
"\n",
|
||||
"We will first define the tools we want to use.\n",
|
||||
"For this simple example, we will use create a placeholder search engine.\n",
|
||||
"However, it is really easy to create your own tools - see documentation [here](https://python.langchain.com/docs/modules/agents/tools/custom_tools) on how to do that.\n",
|
||||
"However, it is really easy to create your own tools - see documentation [here](https://python.langchain.com/v0.2/docs/how_to/custom_tools) on how to do that.\n",
|
||||
"\n",
|
||||
"**MODIFICATION**\n",
|
||||
"\n",
|
||||
@@ -165,8 +165,9 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing import TypedDict, Annotated, Sequence\n",
|
||||
"import operator\n",
|
||||
"from typing import Annotated, Sequence, TypedDict\n",
|
||||
"\n",
|
||||
"from langchain_core.messages import BaseMessage\n",
|
||||
"\n",
|
||||
"\n",
|
||||
@@ -182,7 +183,7 @@
|
||||
"## Define the nodes\n",
|
||||
"\n",
|
||||
"We now need to define a few different nodes in our graph.\n",
|
||||
"In `langgraph`, a node can be either a function or a [runnable](https://python.langchain.com/docs/expression_language/).\n",
|
||||
"In `langgraph`, a node can be either a function or a [runnable](https://python.langchain.com/v0.2/docs/concepts/#langchain-expression-language-lcel).\n",
|
||||
"There are two main nodes we need for this:\n",
|
||||
"\n",
|
||||
"1. The agent: responsible for deciding what (if any) actions to take.\n",
|
||||
@@ -252,7 +253,7 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.graph import StateGraph, END\n",
|
||||
"from langgraph.graph import END, StateGraph\n",
|
||||
"\n",
|
||||
"# Define a new graph\n",
|
||||
"workflow = StateGraph(AgentState)\n",
|
||||
@@ -304,7 +305,7 @@
|
||||
"## Use it!\n",
|
||||
"\n",
|
||||
"We can now use it!\n",
|
||||
"This now exposes the [same interface](https://python.langchain.com/docs/expression_language/) as all other LangChain runnables."
|
||||
"This now exposes the [same interface](https://python.langchain.com/v0.2/docs/concepts/#langchain-expression-language-lcel) as all other LangChain runnables."
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%%capture --no-stderr\n",
|
||||
"%pip install --quiet -U langchain langchain_openai tavily-python"
|
||||
"%pip install --quiet -U langgraph langchain langchain_openai tavily-python"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -46,8 +46,8 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"import getpass\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"os.environ[\"OPENAI_API_KEY\"] = getpass.getpass(\"OpenAI API Key:\")\n",
|
||||
"os.environ[\"TAVILY_API_KEY\"] = getpass.getpass(\"Tavily API Key:\")"
|
||||
@@ -81,7 +81,7 @@
|
||||
"\n",
|
||||
"We will first define the tools we want to use.\n",
|
||||
"For this simple example, we will use create a placeholder search engine.\n",
|
||||
"However, it is really easy to create your own tools - see documentation [here](https://python.langchain.com/docs/modules/agents/tools/custom_tools) on how to do that.\n"
|
||||
"However, it is really easy to create your own tools - see documentation [here](https://python.langchain.com/v0.2/docs/how_to/custom_tools) on how to do that.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -193,8 +193,9 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing import TypedDict, Annotated, Sequence\n",
|
||||
"import operator\n",
|
||||
"from typing import Annotated, Sequence, TypedDict\n",
|
||||
"\n",
|
||||
"from langchain_core.messages import BaseMessage\n",
|
||||
"\n",
|
||||
"\n",
|
||||
@@ -210,7 +211,7 @@
|
||||
"## Define the nodes\n",
|
||||
"\n",
|
||||
"We now need to define a few different nodes in our graph.\n",
|
||||
"In `langgraph`, a node can be either a function or a [runnable](https://python.langchain.com/docs/expression_language/).\n",
|
||||
"In `langgraph`, a node can be either a function or a [runnable](https://python.langchain.com/v0.2/docs/concepts/#langchain-expression-language-lcel).\n",
|
||||
"There are two main nodes we need for this:\n",
|
||||
"\n",
|
||||
"1. The agent: responsible for deciding what (if any) actions to take.\n",
|
||||
@@ -236,9 +237,10 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.prebuilt import ToolInvocation\n",
|
||||
"from langchain_core.messages import ToolMessage\n",
|
||||
"\n",
|
||||
"from langgraph.prebuilt import ToolInvocation\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define the function that determines whether to continue or not\n",
|
||||
"def should_continue(state):\n",
|
||||
@@ -299,7 +301,7 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.graph import StateGraph, END\n",
|
||||
"from langgraph.graph import END, StateGraph\n",
|
||||
"\n",
|
||||
"# Define a new graph\n",
|
||||
"workflow = StateGraph(AgentState)\n",
|
||||
@@ -365,7 +367,7 @@
|
||||
"\n",
|
||||
"try:\n",
|
||||
" display(Image(app.get_graph(xray=True).draw_mermaid_png()))\n",
|
||||
"except:\n",
|
||||
"except Exception:\n",
|
||||
" # This requires some extra dependencies and is optional\n",
|
||||
" pass"
|
||||
]
|
||||
@@ -378,7 +380,7 @@
|
||||
"## Use it!\n",
|
||||
"\n",
|
||||
"We can now use it!\n",
|
||||
"This now exposes the [same interface](https://python.langchain.com/docs/expression_language/) as all other LangChain runnables."
|
||||
"This now exposes the [same interface](https://python.langchain.com/v0.2/docs/concepts/#langchain-expression-language-lcel) as all other LangChain runnables."
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -643,7 +645,7 @@
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.2"
|
||||
"version": "3.12.2"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
|
||||
+14
-12
@@ -17,7 +17,7 @@
|
||||
"<div class=\"admonition tip\">\n",
|
||||
" <p class=\"admonition-title\">Note</p>\n",
|
||||
" <p>\n",
|
||||
" In this how-to, we will create our agent from scratch to be transparent (but verbose). You can accomplish similar functionality using the <code>create_react_agent(model, tools=tool, interrupt_before=[\"agent\" | \"tools\"], interrupt_after=[\"agent\" | \"tools\"], checkpointer=checkpointer)</code> (<a href=\"https://langchain-ai.github.io/langgraph/reference/prebuilt/#create_react_agent\">API doc</a>) constructor. This may be more appropriate if you are used to LangChain’s <a href=\"https://python.langchain.com/v0.1/docs/modules/agents/concepts/#agentexecutor\">AgentExecutor</a> class.\n",
|
||||
" In this how-to, we will create our agent from scratch to be transparent (but verbose). You can accomplish similar functionality using the <code>create_react_agent(model, tools=tool, interrupt_before=[\"agent\" | \"tools\"], interrupt_after=[\"agent\" | \"tools\"], checkpointer=checkpointer)</code> (<a href=\"https://langchain-ai.github.io/langgraph/reference/prebuilt/#create_react_agent\">API doc</a>) constructor. This may be more appropriate if you are used to LangChain’s <a href=\"https://python.langchain.com/v0.2/docs/how_to/agent_executor/#concepts\">AgentExecutor</a> class.\n",
|
||||
" </p>\n",
|
||||
"</div> "
|
||||
]
|
||||
@@ -40,7 +40,7 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%%capture --no-stderr\n",
|
||||
"%pip install --quiet -U langchain langchain_openai tavily-python"
|
||||
"%pip install --quiet -U langgraph langchain langchain_openai tavily-python"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -58,8 +58,8 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"import getpass\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"os.environ[\"OPENAI_API_KEY\"] = getpass.getpass(\"OpenAI API Key:\")\n",
|
||||
"os.environ[\"TAVILY_API_KEY\"] = getpass.getpass(\"Tavily API Key:\")"
|
||||
@@ -93,7 +93,7 @@
|
||||
"\n",
|
||||
"We will first define the tools we want to use.\n",
|
||||
"For this simple example, we will use a built-in search tool via Tavily.\n",
|
||||
"However, it is really easy to create your own tools - see documentation [here](https://python.langchain.com/docs/modules/agents/tools/custom_tools) on how to do that.\n",
|
||||
"However, it is really easy to create your own tools - see documentation [here](https://python.langchain.com/v0.2/docs/how_to/custom_tools) on how to do that.\n",
|
||||
"\n",
|
||||
"**MODIFICATION**\n",
|
||||
"\n",
|
||||
@@ -230,8 +230,9 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing import TypedDict, Annotated, Sequence\n",
|
||||
"import operator\n",
|
||||
"from typing import Annotated, Sequence, TypedDict\n",
|
||||
"\n",
|
||||
"from langchain_core.messages import BaseMessage\n",
|
||||
"\n",
|
||||
"\n",
|
||||
@@ -247,7 +248,7 @@
|
||||
"## Define the nodes\n",
|
||||
"\n",
|
||||
"We now need to define a few different nodes in our graph.\n",
|
||||
"In `langgraph`, a node can be either a function or a [runnable](https://python.langchain.com/docs/expression_language/).\n",
|
||||
"In `langgraph`, a node can be either a function or a [runnable](https://python.langchain.com/v0.2/docs/concepts/#langchain-expression-language-lcel).\n",
|
||||
"There are two main nodes we need for this:\n",
|
||||
"\n",
|
||||
"1. The agent: responsible for deciding what (if any) actions to take.\n",
|
||||
@@ -273,8 +274,9 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.prebuilt import ToolInvocation\n",
|
||||
"from langchain_core.messages import ToolMessage"
|
||||
"from langchain_core.messages import ToolMessage\n",
|
||||
"\n",
|
||||
"from langgraph.prebuilt import ToolInvocation"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -390,7 +392,7 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.graph import StateGraph, END\n",
|
||||
"from langgraph.graph import END, StateGraph\n",
|
||||
"\n",
|
||||
"# Define a new graph\n",
|
||||
"workflow = StateGraph(AgentState)\n",
|
||||
@@ -460,7 +462,7 @@
|
||||
"\n",
|
||||
"try:\n",
|
||||
" display(Image(app.get_graph(xray=True).draw_mermaid_png()))\n",
|
||||
"except:\n",
|
||||
"except Exception:\n",
|
||||
" # This requires some extra dependencies and is optional\n",
|
||||
" pass"
|
||||
]
|
||||
@@ -473,7 +475,7 @@
|
||||
"## Use it!\n",
|
||||
"\n",
|
||||
"We can now use it!\n",
|
||||
"This now exposes the [same interface](https://python.langchain.com/docs/expression_language/) as all other LangChain runnables."
|
||||
"This now exposes the [same interface](https://python.langchain.com/v0.2/docs/concepts/#langchain-expression-language-lcel) as all other LangChain runnables."
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -589,7 +591,7 @@
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.2"
|
||||
"version": "3.12.2"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
|
||||
+10
-8
@@ -50,8 +50,8 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"import getpass\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def _set_env(var: str):\n",
|
||||
@@ -90,7 +90,7 @@
|
||||
"\n",
|
||||
"We will first define the tools we want to use.\n",
|
||||
"For this simple example, we will use create a placeholder search engine.\n",
|
||||
"However, it is really easy to create your own tools - see documentation [here](https://python.langchain.com/docs/modules/agents/tools/custom_tools) on how to do that.\n"
|
||||
"However, it is really easy to create your own tools - see documentation [here](https://python.langchain.com/v0.2/docs/how_to/custom_tools) on how to do that.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -208,8 +208,9 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing import TypedDict, Annotated, Sequence\n",
|
||||
"import operator\n",
|
||||
"from typing import Annotated, Sequence, TypedDict\n",
|
||||
"\n",
|
||||
"from langchain_core.messages import BaseMessage\n",
|
||||
"\n",
|
||||
"\n",
|
||||
@@ -225,7 +226,7 @@
|
||||
"## Define the nodes\n",
|
||||
"\n",
|
||||
"We now need to define a few different nodes in our graph.\n",
|
||||
"In `langgraph`, a node can be either a function or a [runnable](https://python.langchain.com/docs/expression_language/).\n",
|
||||
"In `langgraph`, a node can be either a function or a [runnable](https://python.langchain.com/v0.2/docs/concepts/#langchain-expression-language-lcel).\n",
|
||||
"There are two main nodes we need for this:\n",
|
||||
"\n",
|
||||
"1. The agent: responsible for deciding what (if any) actions to take.\n",
|
||||
@@ -251,9 +252,10 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.prebuilt import ToolInvocation\n",
|
||||
"from langchain_core.messages import ToolMessage\n",
|
||||
"\n",
|
||||
"from langgraph.prebuilt import ToolInvocation\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define the function that determines whether to continue or not\n",
|
||||
"def should_continue(state: AgentState):\n",
|
||||
@@ -375,7 +377,7 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.graph import StateGraph, END\n",
|
||||
"from langgraph.graph import END, StateGraph\n",
|
||||
"\n",
|
||||
"# Define a new graph\n",
|
||||
"workflow = StateGraph(AgentState)\n",
|
||||
@@ -456,7 +458,7 @@
|
||||
"## Use it!\n",
|
||||
"\n",
|
||||
"We can now use it!\n",
|
||||
"This now exposes the [same interface](https://python.langchain.com/docs/expression_language/) as all other LangChain runnables."
|
||||
"This now exposes the [same interface](https://python.langchain.com/v0.2/docs/concepts/#langchain-expression-language-lcel) as all other LangChain runnables."
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -705,7 +707,7 @@
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.2"
|
||||
"version": "3.12.2"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
|
||||
@@ -30,10 +30,11 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain_openai import ChatOpenAI\n",
|
||||
"from langchain_community.tools.tavily_search import TavilySearchResults\n",
|
||||
"from langgraph.prebuilt import create_react_agent\n",
|
||||
"from langchain_core.messages import HumanMessage"
|
||||
"from langchain_core.messages import HumanMessage\n",
|
||||
"from langchain_openai import ChatOpenAI\n",
|
||||
"\n",
|
||||
"from langgraph.prebuilt import create_react_agent"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -59,8 +59,8 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"import getpass\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"os.environ[\"OPENAI_API_KEY\"] = getpass.getpass(\"OpenAI API Key:\")\n",
|
||||
"os.environ[\"TAVILY_API_KEY\"] = getpass.getpass(\"Tavily API Key:\")"
|
||||
@@ -102,7 +102,7 @@
|
||||
"\n",
|
||||
"We will first define the tools we want to use.\n",
|
||||
"For this simple example, we will use a built-in search tool via Tavily.\n",
|
||||
"However, it is really easy to create your own tools - see documentation [here](https://python.langchain.com/docs/modules/agents/tools/custom_tools) on how to do that.\n"
|
||||
"However, it is really easy to create your own tools - see documentation [here](https://python.langchain.com/v0.2/docs/how_to/custom_tools) on how to do that.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -214,8 +214,9 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing import TypedDict, Annotated, Sequence\n",
|
||||
"import operator\n",
|
||||
"from typing import Annotated, Sequence, TypedDict\n",
|
||||
"\n",
|
||||
"from langchain_core.messages import BaseMessage\n",
|
||||
"\n",
|
||||
"\n",
|
||||
@@ -231,7 +232,7 @@
|
||||
"## Define the nodes\n",
|
||||
"\n",
|
||||
"We now need to define a few different nodes in our graph.\n",
|
||||
"In `langgraph`, a node can be either a function or a [runnable](https://python.langchain.com/docs/expression_language/).\n",
|
||||
"In `langgraph`, a node can be either a function or a [runnable](https://python.langchain.com/v0.2/docs/concepts/#langchain-expression-language-lcel).\n",
|
||||
"There are two main nodes we need for this:\n",
|
||||
"\n",
|
||||
"1. The agent: responsible for deciding what (if any) actions to take.\n",
|
||||
@@ -257,9 +258,10 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.prebuilt import ToolInvocation\n",
|
||||
"from langchain_core.messages import ToolMessage\n",
|
||||
"\n",
|
||||
"from langgraph.prebuilt import ToolInvocation\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define the function that determines whether to continue or not\n",
|
||||
"def should_continue(state):\n",
|
||||
@@ -344,8 +346,8 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.graph import StateGraph, END\n",
|
||||
"from langgraph.checkpoint.memory import MemorySaver\n",
|
||||
"from langgraph.graph import END, StateGraph\n",
|
||||
"\n",
|
||||
"# Define a new graph\n",
|
||||
"workflow = StateGraph(AgentState)\n",
|
||||
@@ -411,7 +413,7 @@
|
||||
"\n",
|
||||
"try:\n",
|
||||
" display(Image(app.get_graph(xray=True).draw_mermaid_png()))\n",
|
||||
"except:\n",
|
||||
"except Exception:\n",
|
||||
" # This requires some extra dependencies and is optional\n",
|
||||
" pass"
|
||||
]
|
||||
@@ -424,7 +426,7 @@
|
||||
"## Use it!\n",
|
||||
"\n",
|
||||
"We can now use it!\n",
|
||||
"This now exposes the [same interface](https://python.langchain.com/docs/expression_language/) as all other LangChain runnables."
|
||||
"This now exposes the [same interface](https://python.langchain.com/v0.2/docs/concepts/#langchain-expression-language-lcel) as all other LangChain runnables."
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%%capture --no-stderr\n",
|
||||
"%pip install --quiet -U langchain langchain_openai tavily-python"
|
||||
"%pip install --quiet -U langgraph langchain langchain_openai tavily-python"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -50,8 +50,8 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"import getpass\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"os.environ[\"OPENAI_API_KEY\"] = getpass.getpass(\"OpenAI API Key:\")\n",
|
||||
"os.environ[\"TAVILY_API_KEY\"] = getpass.getpass(\"Tavily API Key:\")"
|
||||
@@ -85,7 +85,7 @@
|
||||
"\n",
|
||||
"We will first define the tools we want to use.\n",
|
||||
"For this simple example, we will use a built-in search tool via Tavily.\n",
|
||||
"However, it is really easy to create your own tools - see documentation [here](https://python.langchain.com/docs/modules/agents/tools/custom_tools) on how to do that.\n"
|
||||
"However, it is really easy to create your own tools - see documentation [here](https://python.langchain.com/v0.2/docs/how_to/custom_tools) on how to do that.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -197,8 +197,9 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing import TypedDict, Annotated, Sequence\n",
|
||||
"import operator\n",
|
||||
"from typing import Annotated, Sequence, TypedDict\n",
|
||||
"\n",
|
||||
"from langchain_core.messages import BaseMessage\n",
|
||||
"\n",
|
||||
"\n",
|
||||
@@ -214,7 +215,7 @@
|
||||
"## Define the nodes\n",
|
||||
"\n",
|
||||
"We now need to define a few different nodes in our graph.\n",
|
||||
"In `langgraph`, a node can be either a function or a [runnable](https://python.langchain.com/docs/expression_language/).\n",
|
||||
"In `langgraph`, a node can be either a function or a [runnable](https://python.langchain.com/v0.2/docs/concepts/#langchain-expression-language-lcel).\n",
|
||||
"There are two main nodes we need for this:\n",
|
||||
"\n",
|
||||
"1. The agent: responsible for deciding what (if any) actions to take.\n",
|
||||
@@ -240,9 +241,10 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.prebuilt import ToolInvocation\n",
|
||||
"from langchain_core.messages import ToolMessage\n",
|
||||
"\n",
|
||||
"from langgraph.prebuilt import ToolInvocation\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define the function that determines whether to continue or not\n",
|
||||
"def should_continue(state):\n",
|
||||
@@ -340,7 +342,7 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.graph import StateGraph, END\n",
|
||||
"from langgraph.graph import END, StateGraph\n",
|
||||
"\n",
|
||||
"# Define a new graph\n",
|
||||
"workflow = StateGraph(AgentState)\n",
|
||||
@@ -406,7 +408,7 @@
|
||||
"\n",
|
||||
"try:\n",
|
||||
" display(Image(app.get_graph(xray=True).draw_mermaid_png()))\n",
|
||||
"except:\n",
|
||||
"except Exception:\n",
|
||||
" # This requires some extra dependencies and is optional\n",
|
||||
" pass"
|
||||
]
|
||||
@@ -419,7 +421,7 @@
|
||||
"## Use it!\n",
|
||||
"\n",
|
||||
"We can now use it!\n",
|
||||
"This now exposes the [same interface](https://python.langchain.com/docs/expression_language/) as all other LangChain runnables."
|
||||
"This now exposes the [same interface](https://python.langchain.com/v0.2/docs/concepts/#langchain-expression-language-lcel) as all other LangChain runnables."
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -491,7 +493,7 @@
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.2"
|
||||
"version": "3.12.2"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%%capture --no-stderr\n",
|
||||
"%pip install --quiet -U langchain langchain_openai tavily-python"
|
||||
"%pip install --quiet -U langgraph langchain langchain_openai tavily-python"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -47,8 +47,8 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"import getpass\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"os.environ[\"OPENAI_API_KEY\"] = getpass.getpass(\"OpenAI API Key:\")\n",
|
||||
"os.environ[\"TAVILY_API_KEY\"] = getpass.getpass(\"Tavily API Key:\")"
|
||||
@@ -82,7 +82,7 @@
|
||||
"\n",
|
||||
"We will first define the tools we want to use.\n",
|
||||
"For this simple example, we will use a built-in search tool via Tavily.\n",
|
||||
"However, it is really easy to create your own tools - see documentation [here](https://python.langchain.com/docs/modules/agents/tools/custom_tools) on how to do that.\n",
|
||||
"However, it is really easy to create your own tools - see documentation [here](https://python.langchain.com/v0.2/docs/how_to/custom_tools) on how to do that.\n",
|
||||
"\n",
|
||||
"**MODIFICATION**\n",
|
||||
"\n",
|
||||
@@ -174,8 +174,9 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing import TypedDict, Annotated, Sequence\n",
|
||||
"import operator\n",
|
||||
"from typing import Annotated, Sequence, TypedDict\n",
|
||||
"\n",
|
||||
"from langchain_core.messages import BaseMessage\n",
|
||||
"\n",
|
||||
"\n",
|
||||
@@ -191,7 +192,7 @@
|
||||
"## Define the nodes\n",
|
||||
"\n",
|
||||
"We now need to define a few different nodes in our graph.\n",
|
||||
"In `langgraph`, a node can be either a function or a [runnable](https://python.langchain.com/docs/expression_language/).\n",
|
||||
"In `langgraph`, a node can be either a function or a [runnable](https://python.langchain.com/v0.2/docs/concepts/#langchain-expression-language-lcel).\n",
|
||||
"There are two main nodes we need for this:\n",
|
||||
"\n",
|
||||
"1. The agent: responsible for deciding what (if any) actions to take.\n",
|
||||
@@ -261,7 +262,7 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.graph import StateGraph, END\n",
|
||||
"from langgraph.graph import END, StateGraph\n",
|
||||
"\n",
|
||||
"# Define a new graph\n",
|
||||
"workflow = StateGraph(AgentState)\n",
|
||||
@@ -313,7 +314,7 @@
|
||||
"## Use it!\n",
|
||||
"\n",
|
||||
"We can now use it!\n",
|
||||
"This now exposes the [same interface](https://python.langchain.com/docs/expression_language/) as all other LangChain runnables."
|
||||
"This now exposes the [same interface](https://python.langchain.com/v0.2/docs/concepts/#langchain-expression-language-lcel) as all other LangChain runnables."
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -39,8 +39,10 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"from typing import Annotated\n",
|
||||
"\n",
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"\n",
|
||||
"from langgraph.graph.message import add_messages\n",
|
||||
"\n",
|
||||
"# Add messages essentially does this with more\n",
|
||||
@@ -62,7 +64,7 @@
|
||||
"\n",
|
||||
"We will first define the tools we want to use.\n",
|
||||
"For this simple example, we will use create a placeholder search engine.\n",
|
||||
"It is really easy to create your own tools - see documentation [here](https://python.langchain.com/docs/modules/agents/tools/custom_tools) on how to do that.\n"
|
||||
"It is really easy to create your own tools - see documentation [here](https://python.langchain.com/v0.2/docs/how_to/custom_tools) on how to do that.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -172,7 +174,7 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%%capture --no-stderr\n",
|
||||
"%pip install --quiet -U langchain langchain_openai tavily-python"
|
||||
"%pip install --quiet -U langgraph langchain langchain_openai tavily-python"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -190,8 +192,8 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"import getpass\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"os.environ[\"OPENAI_API_KEY\"] = getpass.getpass(\"OpenAI API Key:\")\n",
|
||||
"os.environ[\"TAVILY_API_KEY\"] = getpass.getpass(\"Tavily API Key:\")"
|
||||
@@ -225,7 +227,7 @@
|
||||
"\n",
|
||||
"We will first define the tools we want to use.\n",
|
||||
"For this simple example, we will use a built-in search tool via Tavily.\n",
|
||||
"However, it is really easy to create your own tools - see documentation [here](https://python.langchain.com/docs/modules/agents/tools/custom_tools) on how to do that.\n"
|
||||
"However, it is really easy to create your own tools - see documentation [here](https://python.langchain.com/v0.2/docs/how_to/custom_tools) on how to do that.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -352,8 +354,9 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing import TypedDict, Annotated, Sequence\n",
|
||||
"import operator\n",
|
||||
"from typing import Annotated, Sequence, TypedDict\n",
|
||||
"\n",
|
||||
"from langchain_core.messages import BaseMessage\n",
|
||||
"\n",
|
||||
"\n",
|
||||
@@ -369,7 +372,7 @@
|
||||
"## Define the nodes\n",
|
||||
"\n",
|
||||
"We now need to define a few different nodes in our graph.\n",
|
||||
"In `langgraph`, a node can be either a function or a [runnable](https://python.langchain.com/docs/expression_language/).\n",
|
||||
"In `langgraph`, a node can be either a function or a [runnable](https://python.langchain.com/v0.2/docs/concepts/#langchain-expression-language-lcel).\n",
|
||||
"There are two main nodes we need for this:\n",
|
||||
"\n",
|
||||
"1. The agent: responsible for deciding what (if any) actions to take.\n",
|
||||
@@ -399,10 +402,12 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.prebuilt import ToolInvocation\n",
|
||||
"from langchain_core.messages import ToolMessage\n",
|
||||
"from typing import Literal\n",
|
||||
"\n",
|
||||
"from langchain_core.messages import ToolMessage\n",
|
||||
"\n",
|
||||
"from langgraph.prebuilt import ToolInvocation\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define the function that determines whether to continue or not\n",
|
||||
"def should_continue(state) -> Literal[\"continue\", \"end\"]:\n",
|
||||
@@ -478,7 +483,7 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.graph import StateGraph, END\n",
|
||||
"from langgraph.graph import END, StateGraph\n",
|
||||
"\n",
|
||||
"# Define a new graph\n",
|
||||
"workflow = StateGraph(AgentState)\n",
|
||||
@@ -544,7 +549,7 @@
|
||||
"\n",
|
||||
"try:\n",
|
||||
" display(Image(app.get_graph(xray=True).draw_mermaid_png()))\n",
|
||||
"except:\n",
|
||||
"except Exception:\n",
|
||||
" # This requires some extra dependencies and is optional\n",
|
||||
" pass"
|
||||
]
|
||||
@@ -557,7 +562,7 @@
|
||||
"## Use it!\n",
|
||||
"\n",
|
||||
"We can now use it!\n",
|
||||
"This now exposes the [same interface](https://python.langchain.com/docs/expression_language/) as all other LangChain runnables."
|
||||
"This now exposes the [same interface](https://python.langchain.com/v0.2/docs/concepts/#langchain-expression-language-lcel) as all other LangChain runnables."
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -629,7 +634,7 @@
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.2"
|
||||
"version": "3.12.2"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
|
||||
@@ -40,7 +40,6 @@
|
||||
"source": [
|
||||
"import getpass\n",
|
||||
"import os\n",
|
||||
"import uuid\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def _set_if_undefined(var: str):\n",
|
||||
@@ -138,7 +137,6 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n",
|
||||
"from langchain_core.runnables import chain\n",
|
||||
"from langchain_openai import ChatOpenAI\n",
|
||||
"\n",
|
||||
"system_prompt_template = \"\"\"You are a customer of an airline company. \\\n",
|
||||
@@ -185,7 +183,6 @@
|
||||
"source": [
|
||||
"from langchain_core.messages import HumanMessage\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"messages = [HumanMessage(content=\"Hi! How can I help you?\")]\n",
|
||||
"simulated_user.invoke({\"messages\": messages})"
|
||||
]
|
||||
@@ -227,8 +224,8 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain_core.messages import AIMessage\n",
|
||||
"from langchain_community.adapters.openai import convert_message_to_dict\n",
|
||||
"from langchain_core.messages import AIMessage\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def chat_bot_node(messages):\n",
|
||||
@@ -324,7 +321,6 @@
|
||||
"source": [
|
||||
"from langgraph.graph import END, MessageGraph\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"graph_builder = MessageGraph()\n",
|
||||
"graph_builder.add_node(\"user\", simulated_user_node)\n",
|
||||
"graph_builder.add_node(\"chat_bot\", chat_bot_node)\n",
|
||||
|
||||
@@ -33,10 +33,11 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing import List\n",
|
||||
"\n",
|
||||
"from langchain_core.messages import SystemMessage\n",
|
||||
"from langchain_openai import ChatOpenAI\n",
|
||||
"from langchain_core.pydantic_v1 import BaseModel\n",
|
||||
"from typing import List"
|
||||
"from langchain_openai import ChatOpenAI"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -97,7 +98,7 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain_core.messages import HumanMessage, AIMessage, ToolMessage\n",
|
||||
"from langchain_core.messages import AIMessage, HumanMessage, ToolMessage\n",
|
||||
"\n",
|
||||
"# New system prompt\n",
|
||||
"prompt_system = \"\"\"Based on the following requirements, write a good prompt template:\n",
|
||||
@@ -145,6 +146,7 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing import Literal\n",
|
||||
"\n",
|
||||
"from langgraph.graph import END\n",
|
||||
"\n",
|
||||
"\n",
|
||||
@@ -174,8 +176,8 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.graph import MessageGraph, START\n",
|
||||
"from langgraph.checkpoint.sqlite import SqliteSaver\n",
|
||||
"from langgraph.graph import START, MessageGraph\n",
|
||||
"\n",
|
||||
"memory = SqliteSaver.from_conn_string(\":memory:\")\n",
|
||||
"workflow = MessageGraph()\n",
|
||||
@@ -215,7 +217,7 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from IPython.display import display, Image\n",
|
||||
"from IPython.display import Image, display\n",
|
||||
"\n",
|
||||
"display(Image(graph.get_graph().draw_mermaid_png()))"
|
||||
]
|
||||
@@ -296,7 +298,6 @@
|
||||
"source": [
|
||||
"import uuid\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"config = {\"configurable\": {\"thread_id\": str(uuid.uuid4())}}\n",
|
||||
"while True:\n",
|
||||
" user = input(\"User (q/Q to quit): \")\n",
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
"\n",
|
||||
"[AlphaCodium](https://github.com/Codium-ai/AlphaCodium) iteravely tests and improves an answer on public and AI-generated tests for a particular question. \n",
|
||||
"\n",
|
||||
"We will implement some of these ideas from scratch using [LangGraph](https://python.langchain.com/docs/langgraph):\n",
|
||||
"We will implement some of these ideas from scratch using [LangGraph](https://langchain-ai.github.io/langgraph/):\n",
|
||||
"\n",
|
||||
"1. We start with a set of documentation specified by a user\n",
|
||||
"2. We use a long context LLM to ingest it and perform RAG to answer a question based upon it\n",
|
||||
@@ -45,7 +45,7 @@
|
||||
"source": [
|
||||
"## Docs\n",
|
||||
"\n",
|
||||
"Load [LangChain Expression Language](https://python.langchain.com/docs/expression_language/) (LCEL) docs as an example."
|
||||
"Load [LangChain Expression Language](https://python.langchain.com/v0.2/docs/concepts/#langchain-expression-language-lcel) (LCEL) docs as an example."
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -59,7 +59,7 @@
|
||||
"from langchain_community.document_loaders.recursive_url_loader import RecursiveUrlLoader\n",
|
||||
"\n",
|
||||
"# LCEL docs\n",
|
||||
"url = \"https://python.langchain.com/docs/expression_language/\"\n",
|
||||
"url = \"https://python.langchain.com/v0.2/docs/concepts/#langchain-expression-language-lcel\"\n",
|
||||
"loader = RecursiveUrlLoader(\n",
|
||||
" url=url, max_depth=20, extractor=lambda x: Soup(x, \"html.parser\").text\n",
|
||||
")\n",
|
||||
@@ -94,9 +94,9 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain_openai import ChatOpenAI\n",
|
||||
"from langchain_core.prompts import ChatPromptTemplate\n",
|
||||
"from langchain_core.pydantic_v1 import BaseModel, Field\n",
|
||||
"from langchain_openai import ChatOpenAI\n",
|
||||
"\n",
|
||||
"### OpenAI\n",
|
||||
"\n",
|
||||
@@ -125,6 +125,7 @@
|
||||
" code: str = Field(description=\"Code block not including import statements\")\n",
|
||||
" description = \"Schema for code solutions to questions about LCEL.\"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"expt_llm = \"gpt-4-0125-preview\"\n",
|
||||
"llm = ChatOpenAI(temperature=0, model=expt_llm)\n",
|
||||
"code_gen_chain = code_gen_prompt | llm.with_structured_output(code)\n",
|
||||
@@ -181,6 +182,7 @@
|
||||
"\n",
|
||||
"structured_llm_claude = llm.with_structured_output(code, include_raw=True)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Optional: Check for errors in case tool use is flaky\n",
|
||||
"def check_claude_output(tool_output):\n",
|
||||
" \"\"\"Check for parse error or failure to call the tool\"\"\"\n",
|
||||
@@ -189,7 +191,7 @@
|
||||
" if tool_output[\"parsing_error\"]:\n",
|
||||
" # Report back output and parsing errors\n",
|
||||
" print(\"Parsing error!\")\n",
|
||||
" raw_output = str(code_output[\"raw\"].content)\n",
|
||||
" raw_output = str(tool_output[\"raw\"].content)\n",
|
||||
" error = tool_output[\"parsing_error\"]\n",
|
||||
" raise ValueError(\n",
|
||||
" f\"Error parsing your output! Be sure to invoke the tool. Output: {raw_output}. \\n Parse error: {error}\"\n",
|
||||
@@ -199,15 +201,17 @@
|
||||
" elif not tool_output[\"parsed\"]:\n",
|
||||
" print(\"Failed to invoke tool!\")\n",
|
||||
" raise ValueError(\n",
|
||||
" f\"You did not use the provided tool! Be sure to invoke the tool to structure the output.\"\n",
|
||||
" \"You did not use the provided tool! Be sure to invoke the tool to structure the output.\"\n",
|
||||
" )\n",
|
||||
" return tool_output\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Chain with output check\n",
|
||||
"code_chain_claude_raw = (\n",
|
||||
" code_gen_prompt_claude | structured_llm_claude | check_claude_output\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def insert_errors(inputs):\n",
|
||||
" \"\"\"Insert errors for tool parsing in the messages\"\"\"\n",
|
||||
"\n",
|
||||
@@ -240,6 +244,7 @@
|
||||
"\n",
|
||||
" return solution[\"parsed\"]\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Optional: With re-try to correct for failure to invoke tool\n",
|
||||
"code_gen_chain = code_gen_chain_re_try | parse_output\n",
|
||||
"\n",
|
||||
@@ -281,7 +286,7 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing import Dict, TypedDict, List\n",
|
||||
"from typing import List, TypedDict\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class GraphState(TypedDict):\n",
|
||||
@@ -318,10 +323,7 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from operator import itemgetter\n",
|
||||
"from langchain_core.pydantic_v1 import BaseModel, Field\n",
|
||||
"from langchain_core.runnables import RunnablePassthrough\n",
|
||||
"from langchain_core.prompts import PromptTemplate\n",
|
||||
"\n",
|
||||
"### Parameter\n",
|
||||
"\n",
|
||||
@@ -396,7 +398,6 @@
|
||||
" iterations = state[\"iterations\"]\n",
|
||||
"\n",
|
||||
" # Get solution components\n",
|
||||
" prefix = code_solution.prefix\n",
|
||||
" imports = code_solution.imports\n",
|
||||
" code = code_solution.code\n",
|
||||
"\n",
|
||||
@@ -457,14 +458,6 @@
|
||||
" code_solution = state[\"generation\"]\n",
|
||||
"\n",
|
||||
" # Prompt reflection\n",
|
||||
" reflection_message = [\n",
|
||||
" (\n",
|
||||
" \"user\",\n",
|
||||
" \"\"\"You tried to solve this problem and failed a unit test. Reflect on this failure\n",
|
||||
" given the provided documentation. Write a few key suggestions based on the \n",
|
||||
" documentation to avoid making this mistake again.\"\"\",\n",
|
||||
" )\n",
|
||||
" ]\n",
|
||||
"\n",
|
||||
" # Add reflection\n",
|
||||
" reflections = code_gen_chain.invoke(\n",
|
||||
@@ -613,7 +606,7 @@
|
||||
" try:\n",
|
||||
" exec(imports)\n",
|
||||
" return {\"key\": \"import_check\", \"score\": 1}\n",
|
||||
" except:\n",
|
||||
" except Exception:\n",
|
||||
" return {\"key\": \"import_check\", \"score\": 0}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
@@ -623,7 +616,7 @@
|
||||
" try:\n",
|
||||
" exec(imports + \"\\n\" + code)\n",
|
||||
" return {\"key\": \"code_execution_check\", \"score\": 1}\n",
|
||||
" except:\n",
|
||||
" except Exception:\n",
|
||||
" return {\"key\": \"code_execution_check\", \"score\": 0}"
|
||||
]
|
||||
},
|
||||
@@ -754,7 +747,7 @@
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.8"
|
||||
"version": "3.12.2"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
"\n",
|
||||
"[AlphaCodium](https://github.com/Codium-ai/AlphaCodium) iteravely tests and improves an answer on public and AI-generated tests for a particular question. \n",
|
||||
"\n",
|
||||
"We will implement some of these ideas from scratch using [LangGraph](https://python.langchain.com/docs/langgraph):\n",
|
||||
"We will implement some of these ideas from scratch using [LangGraph](https://langchain-ai.github.io/langgraph/):\n",
|
||||
"\n",
|
||||
"1. We show how to route user questions to different types of documentation\n",
|
||||
"2. We we will show how to perform inline unit tests to confirm imports and code execution work\n",
|
||||
@@ -55,8 +55,9 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"os.environ['TOKENIZERS_PARALLELISM'] = 'true'\n",
|
||||
"mistral_api_key = os.getenv(\"MISTRAL_API_KEY\") # Ensure this is set"
|
||||
"\n",
|
||||
"os.environ[\"TOKENIZERS_PARALLELISM\"] = \"true\"\n",
|
||||
"mistral_api_key = os.getenv(\"MISTRAL_API_KEY\") # Ensure this is set"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -76,19 +77,9 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"os.environ['LANGCHAIN_TRACING_V2'] = 'true'\n",
|
||||
"os.environ['LANGCHAIN_ENDPOINT'] = 'https://api.smith.langchain.com'\n",
|
||||
"os.environ['LANGCHAIN_API_KEY'] = <your-api-key>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"id": "949efd30-44c7-4a4c-a05f-eca4e2769a61",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n",
|
||||
"os.environ[\"LANGCHAIN_ENDPOINT\"] = \"https://api.smith.langchain.com\"\n",
|
||||
"os.environ[\"LANGCHAIN_API_KEY\"] = \"<your-api-key>\"\n",
|
||||
"os.environ[\"LANGCHAIN_PROJECT\"] = \"Mistral-code-gen-testing\""
|
||||
]
|
||||
},
|
||||
@@ -110,18 +101,18 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Select LLM\n",
|
||||
"from langchain_mistralai import ChatMistralAI\n",
|
||||
"from langchain_core.prompts import ChatPromptTemplate\n",
|
||||
"from langchain_core.pydantic_v1 import BaseModel, Field\n",
|
||||
"from langchain_mistralai import ChatMistralAI\n",
|
||||
"\n",
|
||||
"mistral_model = \"mistral-large-latest\"\n",
|
||||
"llm = ChatMistralAI(model=mistral_model, temperature=0)\n",
|
||||
"\n",
|
||||
"# Prompt \n",
|
||||
"# Prompt\n",
|
||||
"code_gen_prompt_claude = ChatPromptTemplate.from_messages(\n",
|
||||
" [\n",
|
||||
" (\n",
|
||||
" \"system\", \n",
|
||||
" \"system\",\n",
|
||||
" \"\"\"You are a coding assistant. Ensure any code you provide can be executed with all required imports and variables \\n\n",
|
||||
" defined. Structure your answer: 1) a prefix describing the code solution, 2) the imports, 3) the functioning code block.\n",
|
||||
" \\n Here is the user question:\"\"\",\n",
|
||||
@@ -130,6 +121,7 @@
|
||||
" ]\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Data model\n",
|
||||
"class code(BaseModel):\n",
|
||||
" \"\"\"Code output\"\"\"\n",
|
||||
@@ -139,6 +131,7 @@
|
||||
" code: str = Field(description=\"Code block not including import statements\")\n",
|
||||
" description = \"Schema for code solutions to questions about LCEL.\"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# LLM\n",
|
||||
"code_gen_chain = llm.with_structured_output(code, include_raw=False)"
|
||||
]
|
||||
@@ -192,10 +185,11 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing import Annotated\n",
|
||||
"from typing import Dict, TypedDict, List\n",
|
||||
"from typing import Annotated, TypedDict\n",
|
||||
"\n",
|
||||
"from langgraph.graph.message import AnyMessage, add_messages\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class GraphState(TypedDict):\n",
|
||||
" \"\"\"\n",
|
||||
" Represents the state of our graph.\n",
|
||||
@@ -228,14 +222,14 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from operator import itemgetter\n",
|
||||
"import uuid\n",
|
||||
"\n",
|
||||
"from langchain_core.pydantic_v1 import BaseModel, Field\n",
|
||||
"from langchain_core.runnables import RunnablePassthrough\n",
|
||||
"from langchain_core.prompts import PromptTemplate\n",
|
||||
"\n",
|
||||
"### Parameters\n",
|
||||
"max_iterations = 3\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"### Nodes\n",
|
||||
"def generate(state: GraphState):\n",
|
||||
" \"\"\"\n",
|
||||
@@ -253,7 +247,6 @@
|
||||
" # State\n",
|
||||
" messages = state[\"messages\"]\n",
|
||||
" iterations = state[\"iterations\"]\n",
|
||||
" error = state[\"error\"]\n",
|
||||
"\n",
|
||||
" # Solution\n",
|
||||
" code_solution = code_gen_chain.invoke(messages)\n",
|
||||
@@ -268,6 +261,7 @@
|
||||
" iterations = iterations + 1\n",
|
||||
" return {\"generation\": code_solution, \"messages\": messages, \"iterations\": iterations}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def code_check(state: GraphState):\n",
|
||||
" \"\"\"\n",
|
||||
" Check code\n",
|
||||
@@ -287,7 +281,6 @@
|
||||
" iterations = state[\"iterations\"]\n",
|
||||
"\n",
|
||||
" # Get solution components\n",
|
||||
" prefix = code_solution.prefix\n",
|
||||
" imports = code_solution.imports\n",
|
||||
" code = code_solution.code\n",
|
||||
"\n",
|
||||
@@ -296,7 +289,12 @@
|
||||
" exec(imports)\n",
|
||||
" except Exception as e:\n",
|
||||
" print(\"---CODE IMPORT CHECK: FAILED---\")\n",
|
||||
" error_message = [(\"user\", f\"Your solution failed the import test. Here is the error: {e}. Reflect on this error and your prior attempt to solve the problem. (1) State what you think went wrong with the prior solution and (2) try to solve this problem again. Return the FULL SOLUTION. Use the code tool to structure the output with a prefix, imports, and code block:\")]\n",
|
||||
" error_message = [\n",
|
||||
" (\n",
|
||||
" \"user\",\n",
|
||||
" f\"Your solution failed the import test. Here is the error: {e}. Reflect on this error and your prior attempt to solve the problem. (1) State what you think went wrong with the prior solution and (2) try to solve this problem again. Return the FULL SOLUTION. Use the code tool to structure the output with a prefix, imports, and code block:\",\n",
|
||||
" )\n",
|
||||
" ]\n",
|
||||
" messages += error_message\n",
|
||||
" return {\n",
|
||||
" \"generation\": code_solution,\n",
|
||||
@@ -314,7 +312,12 @@
|
||||
" exec(combined_code, global_scope)\n",
|
||||
" except Exception as e:\n",
|
||||
" print(\"---CODE BLOCK CHECK: FAILED---\")\n",
|
||||
" error_message = [(\"user\", f\"Your solution failed the code execution test: {e}) Reflect on this error and your prior attempt to solve the problem. (1) State what you think went wrong with the prior solution and (2) try to solve this problem again. Return the FULL SOLUTION. Use the code tool to structure the output with a prefix, imports, and code block:\")]\n",
|
||||
" error_message = [\n",
|
||||
" (\n",
|
||||
" \"user\",\n",
|
||||
" f\"Your solution failed the code execution test: {e}) Reflect on this error and your prior attempt to solve the problem. (1) State what you think went wrong with the prior solution and (2) try to solve this problem again. Return the FULL SOLUTION. Use the code tool to structure the output with a prefix, imports, and code block:\",\n",
|
||||
" )\n",
|
||||
" ]\n",
|
||||
" messages += error_message\n",
|
||||
" return {\n",
|
||||
" \"generation\": code_solution,\n",
|
||||
@@ -332,8 +335,10 @@
|
||||
" \"error\": \"no\",\n",
|
||||
" }\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"### Conditional edges\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def decide_to_finish(state: GraphState):\n",
|
||||
" \"\"\"\n",
|
||||
" Determines whether to finish.\n",
|
||||
@@ -354,14 +359,14 @@
|
||||
" print(\"---DECISION: RE-TRY SOLUTION---\")\n",
|
||||
" return \"generate\"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"### Utilities\n",
|
||||
"\n",
|
||||
"import uuid \n",
|
||||
"\n",
|
||||
"def _print_event(event: dict, _printed: set, max_length=1500):\n",
|
||||
" current_state = event.get(\"dialog_state\")\n",
|
||||
" if current_state:\n",
|
||||
" print(f\"Currently in: \", current_state[-1])\n",
|
||||
" print(\"Currently in: \", current_state[-1])\n",
|
||||
" message = event.get(\"messages\")\n",
|
||||
" if message:\n",
|
||||
" if isinstance(message, list):\n",
|
||||
@@ -428,7 +433,7 @@
|
||||
"\n",
|
||||
"try:\n",
|
||||
" display(Image(graph.get_graph(xray=True).draw_mermaid_png()))\n",
|
||||
"except:\n",
|
||||
"except Exception:\n",
|
||||
" # This requires some extra dependencies and is optional\n",
|
||||
" pass"
|
||||
]
|
||||
@@ -554,6 +559,7 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import uuid\n",
|
||||
"\n",
|
||||
"_printed = set()\n",
|
||||
"thread_id = str(uuid.uuid4())\n",
|
||||
"config = {\n",
|
||||
@@ -563,7 +569,7 @@
|
||||
" }\n",
|
||||
"}\n",
|
||||
"\n",
|
||||
"question = '''I want to vectorize a function\n",
|
||||
"question = \"\"\"I want to vectorize a function\n",
|
||||
"\n",
|
||||
" frame = np.zeros((out_h, out_w, 3), dtype=np.uint8)\n",
|
||||
" for i, val1 in enumerate(rows):\n",
|
||||
@@ -574,7 +580,7 @@
|
||||
"\n",
|
||||
" out.write(np.array(frame))\n",
|
||||
"\n",
|
||||
"with a simple numpy function that does something like this what is it called. Show me a test case with this working.'''\n",
|
||||
"with a simple numpy function that does something like this what is it called. Show me a test case with this working.\"\"\"\n",
|
||||
"\n",
|
||||
"events = graph.stream(\n",
|
||||
" {\"messages\": [(\"user\", question)], \"iterations\": 0}, config, stream_mode=\"values\"\n",
|
||||
|
||||
@@ -29,12 +29,13 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.graph import StateGraph, END\n",
|
||||
"from langchain_anthropic import ChatAnthropic\n",
|
||||
"from typing import TypedDict, Annotated, Sequence\n",
|
||||
"import operator\n",
|
||||
"from typing import Annotated, Sequence, TypedDict\n",
|
||||
"\n",
|
||||
"from langchain_anthropic import ChatAnthropic\n",
|
||||
"from langchain_core.messages import BaseMessage, HumanMessage\n",
|
||||
"\n",
|
||||
"from langgraph.graph import END, StateGraph\n",
|
||||
"\n",
|
||||
"model = ChatAnthropic(model_name=\"claude-2.1\")\n",
|
||||
"\n",
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -903,8 +903,8 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain_core.runnables import RunnableLambda\n",
|
||||
"from langchain_core.messages import ToolMessage\n",
|
||||
"from langchain_core.runnables import RunnableLambda\n",
|
||||
"\n",
|
||||
"from langgraph.prebuilt import ToolNode\n",
|
||||
"\n",
|
||||
@@ -932,7 +932,7 @@
|
||||
"def _print_event(event: dict, _printed: set, max_length=1500):\n",
|
||||
" current_state = event.get(\"dialog_state\")\n",
|
||||
" if current_state:\n",
|
||||
" print(f\"Currently in: \", current_state[-1])\n",
|
||||
" print(\"Currently in: \", current_state[-1])\n",
|
||||
" message = event.get(\"messages\")\n",
|
||||
" if message:\n",
|
||||
" if isinstance(message, list):\n",
|
||||
@@ -1107,7 +1107,7 @@
|
||||
"source": [
|
||||
"from langgraph.checkpoint.sqlite import SqliteSaver\n",
|
||||
"from langgraph.graph import END, StateGraph\n",
|
||||
"from langgraph.prebuilt import ToolNode, tools_condition\n",
|
||||
"from langgraph.prebuilt import tools_condition\n",
|
||||
"\n",
|
||||
"builder = StateGraph(State)\n",
|
||||
"\n",
|
||||
@@ -1151,7 +1151,7 @@
|
||||
"\n",
|
||||
"try:\n",
|
||||
" display(Image(part_1_graph.get_graph(xray=True).draw_mermaid_png()))\n",
|
||||
"except:\n",
|
||||
"except Exception:\n",
|
||||
" # This requires some extra dependencies and is optional\n",
|
||||
" pass"
|
||||
]
|
||||
@@ -1806,7 +1806,7 @@
|
||||
"\n",
|
||||
"#### State & Assistant\n",
|
||||
"\n",
|
||||
"Our graph state and LLM calling is nearly identical to Part 1 except:\n",
|
||||
"Our graph state and LLM calling is nearly identical to Part 1 except Exception:\n",
|
||||
"\n",
|
||||
"- We've added a `user_info` field that will be eagerly populated by our graph\n",
|
||||
"- We can use the state directly in the `Assistant` object rather than using the configurable params"
|
||||
@@ -1924,8 +1924,8 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.checkpoint.sqlite import SqliteSaver\n",
|
||||
"from langgraph.graph import END, StateGraph\n",
|
||||
"from langgraph.prebuilt import ToolNode, tools_condition\n",
|
||||
"from langgraph.graph import StateGraph\n",
|
||||
"from langgraph.prebuilt import tools_condition\n",
|
||||
"\n",
|
||||
"builder = StateGraph(State)\n",
|
||||
"\n",
|
||||
@@ -1979,7 +1979,7 @@
|
||||
"\n",
|
||||
"try:\n",
|
||||
" display(Image(part_2_graph.get_graph(xray=True).draw_mermaid_png()))\n",
|
||||
"except:\n",
|
||||
"except Exception:\n",
|
||||
" # This requires some extra dependencies and is optional\n",
|
||||
" pass"
|
||||
]
|
||||
@@ -2512,7 +2512,7 @@
|
||||
"from typing import Literal\n",
|
||||
"\n",
|
||||
"from langgraph.checkpoint.sqlite import SqliteSaver\n",
|
||||
"from langgraph.graph import END, StateGraph\n",
|
||||
"from langgraph.graph import StateGraph\n",
|
||||
"from langgraph.prebuilt import tools_condition\n",
|
||||
"\n",
|
||||
"builder = StateGraph(State)\n",
|
||||
@@ -2588,7 +2588,7 @@
|
||||
"\n",
|
||||
"try:\n",
|
||||
" display(Image(part_3_graph.get_graph(xray=True).draw_mermaid_png()))\n",
|
||||
"except:\n",
|
||||
"except Exception:\n",
|
||||
" # This requires some extra dependencies and is optional\n",
|
||||
" pass"
|
||||
]
|
||||
@@ -3442,7 +3442,7 @@
|
||||
"from typing import Literal\n",
|
||||
"\n",
|
||||
"from langgraph.checkpoint.sqlite import SqliteSaver\n",
|
||||
"from langgraph.graph import END, StateGraph\n",
|
||||
"from langgraph.graph import StateGraph\n",
|
||||
"from langgraph.prebuilt import tools_condition\n",
|
||||
"\n",
|
||||
"builder = StateGraph(State)\n",
|
||||
@@ -3842,7 +3842,7 @@
|
||||
"\n",
|
||||
"try:\n",
|
||||
" display(Image(part_4_graph.get_graph(xray=True).draw_mermaid_png()))\n",
|
||||
"except:\n",
|
||||
"except Exception:\n",
|
||||
" # This requires some extra dependencies and is optional\n",
|
||||
" pass"
|
||||
]
|
||||
|
||||
@@ -23,8 +23,8 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"import getpass\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"if not os.environ.get(\"OPENAI_API_KEY\"):\n",
|
||||
" os.environ[\"OPENAI_API_KEY\"] = getpass.getpass(\"OpenAI API Key:\")"
|
||||
@@ -36,8 +36,9 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain_openai import ChatOpenAI\n",
|
||||
"from langchain_core.messages import BaseMessage, HumanMessage\n",
|
||||
"from langchain_openai import ChatOpenAI\n",
|
||||
"\n",
|
||||
"from langgraph.graph import END, MessageGraph\n",
|
||||
"\n",
|
||||
"model = ChatOpenAI(temperature=0)\n",
|
||||
@@ -73,7 +74,7 @@
|
||||
"\n",
|
||||
"try:\n",
|
||||
" display(Image(runnable.get_graph(xray=True).draw_mermaid_png()))\n",
|
||||
"except:\n",
|
||||
"except Exception:\n",
|
||||
" # This requires some extra dependencies and is optional\n",
|
||||
" pass"
|
||||
]
|
||||
@@ -105,11 +106,13 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain_core.tools import tool\n",
|
||||
"from langgraph.prebuilt import ToolNode\n",
|
||||
"from langgraph.graph import END, START\n",
|
||||
"from typing import Literal\n",
|
||||
"\n",
|
||||
"from langchain_core.tools import tool\n",
|
||||
"\n",
|
||||
"from langgraph.graph import END, START\n",
|
||||
"from langgraph.prebuilt import ToolNode\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"@tool\n",
|
||||
"def multiply(first_number: int, second_number: int):\n",
|
||||
@@ -161,7 +164,7 @@
|
||||
"source": [
|
||||
"try:\n",
|
||||
" display(Image(runnable.get_graph(xray=True).draw_mermaid_png()))\n",
|
||||
"except:\n",
|
||||
"except Exception:\n",
|
||||
" # This requires some extra dependencies and is optional\n",
|
||||
" pass"
|
||||
]
|
||||
|
||||
@@ -48,8 +48,8 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"import getpass\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def _set_env(var: str):\n",
|
||||
@@ -227,8 +227,8 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing import TypedDict, Annotated\n",
|
||||
"import operator\n",
|
||||
"from typing import Annotated, TypedDict\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class AgentState(TypedDict):\n",
|
||||
@@ -269,8 +269,9 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.prebuilt import ToolInvocation\n",
|
||||
"from langchain_core.messages import ToolMessage"
|
||||
"from langchain_core.messages import ToolMessage\n",
|
||||
"\n",
|
||||
"from langgraph.prebuilt import ToolInvocation"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -386,7 +387,7 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.graph import StateGraph, END\n",
|
||||
"from langgraph.graph import END, StateGraph\n",
|
||||
"\n",
|
||||
"# Define a new graph\n",
|
||||
"workflow = StateGraph(AgentState)\n",
|
||||
@@ -604,7 +605,7 @@
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.2"
|
||||
"version": "3.12.2"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
|
||||
@@ -420,11 +420,11 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain_core.prompts import ChatPromptTemplate\n",
|
||||
"from langchain_anthropic import ChatAnthropic\n",
|
||||
"from langchain_core.prompts import ChatPromptTemplate\n",
|
||||
"\n",
|
||||
"# Or you can use ChatGroq, ChatOpenAI, ChatGoogleGemini, ChatCohere, etc.\n",
|
||||
"# See https://python.langchain.com/v0.1/docs/integrations/chat/ for more info on tool calling\n",
|
||||
"# See https://python.langchain.com/v0.2/docs/integrations/chat/ for more info on tool calling\n",
|
||||
"llm = ChatAnthropic(model=\"claude-3-haiku-20240307\")\n",
|
||||
"bound_llm = bind_validator_with_retries(llm, tools=tools)\n",
|
||||
"prompt = ChatPromptTemplate.from_messages(\n",
|
||||
@@ -953,7 +953,7 @@
|
||||
"\n",
|
||||
"try:\n",
|
||||
" display(Image(bound_llm.get_graph().draw_mermaid_png()))\n",
|
||||
"except:\n",
|
||||
"except Exception:\n",
|
||||
" pass"
|
||||
]
|
||||
},
|
||||
@@ -1036,7 +1036,7 @@
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.2"
|
||||
"version": "3.12.2"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%%capture --no-stderr\n",
|
||||
"%pip install --quiet -U langchain langchain_openai tavily-python"
|
||||
"%pip install --quiet -U langgraph langchain langchain_openai tavily-python"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -46,8 +46,8 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"import getpass\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"os.environ[\"OPENAI_API_KEY\"] = getpass.getpass(\"OpenAI API Key:\")\n",
|
||||
"os.environ[\"TAVILY_API_KEY\"] = getpass.getpass(\"Tavily API Key:\")"
|
||||
@@ -81,7 +81,7 @@
|
||||
"\n",
|
||||
"We will first define the tools we want to use.\n",
|
||||
"For this simple example, we will use a built-in search tool via Tavily.\n",
|
||||
"However, it is really easy to create your own tools - see documentation [here](https://python.langchain.com/docs/modules/agents/tools/custom_tools) on how to do that.\n"
|
||||
"However, it is really easy to create your own tools - see documentation [here](https://python.langchain.com/v0.2/docs/how_to/custom_tools) on how to do that.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -193,8 +193,9 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing import TypedDict, Annotated, Sequence\n",
|
||||
"import operator\n",
|
||||
"from typing import Annotated, Sequence, TypedDict\n",
|
||||
"\n",
|
||||
"from langchain_core.messages import BaseMessage\n",
|
||||
"\n",
|
||||
"\n",
|
||||
@@ -236,9 +237,10 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.prebuilt import ToolInvocation\n",
|
||||
"from langchain_core.messages import ToolMessage\n",
|
||||
"\n",
|
||||
"from langgraph.prebuilt import ToolInvocation\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define the function that determines whether to continue or not\n",
|
||||
"def should_continue(state):\n",
|
||||
@@ -357,7 +359,7 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.graph import StateGraph, END\n",
|
||||
"from langgraph.graph import END, StateGraph\n",
|
||||
"\n",
|
||||
"# Define a new graph\n",
|
||||
"workflow = StateGraph(AgentState)\n",
|
||||
@@ -429,7 +431,7 @@
|
||||
"\n",
|
||||
"try:\n",
|
||||
" display(Image(app.get_graph(xray=True).draw_mermaid_png()))\n",
|
||||
"except:\n",
|
||||
"except Exception:\n",
|
||||
" # This requires some extra dependencies and is optional\n",
|
||||
" pass"
|
||||
]
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
"<div class=\"admonition tip\">\n",
|
||||
" <p class=\"admonition-title\">Note</p>\n",
|
||||
" <p>\n",
|
||||
" In this how-to, we will create our agent from scratch to be transparent (but verbose). You can accomplish similar functionality using either `interrupt_before` or `interrupt_after` in the <code>create_react_agent(model, tools=tool, interrupt_before=[\"tools\" | \"agent\"], interrupt_after=[\"tools\" | \"agent\"])</code> (<a href=\"https://langchain-ai.github.io/langgraph/reference/prebuilt/#create_react_agent\">API doc</a>) constructor. This may be more appropriate if you are used to LangChain’s <a href=\"https://python.langchain.com/v0.1/docs/modules/agents/concepts/#agentexecutor\">AgentExecutor</a> class.\n",
|
||||
" In this how-to, we will create our agent from scratch to be transparent (but verbose). You can accomplish similar functionality using either `interrupt_before` or `interrupt_after` in the <code>create_react_agent(model, tools=tool, interrupt_before=[\"tools\" | \"agent\"], interrupt_after=[\"tools\" | \"agent\"])</code> (<a href=\"https://langchain-ai.github.io/langgraph/reference/prebuilt/#create_react_agent\">API doc</a>) constructor. This may be more appropriate if you are used to LangChain’s <a href=\"https://python.langchain.com/v0.2/docs/how_to/agent_executor/#concepts\">AgentExecutor</a> class.\n",
|
||||
" </p>\n",
|
||||
"</div> "
|
||||
]
|
||||
@@ -59,8 +59,8 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"import getpass\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def _set_env(var: str):\n",
|
||||
@@ -107,8 +107,10 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"from typing import Annotated\n",
|
||||
"\n",
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"\n",
|
||||
"from langgraph.graph.message import add_messages\n",
|
||||
"\n",
|
||||
"# `add_messages`` essentially does this\n",
|
||||
@@ -257,9 +259,10 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.prebuilt import ToolInvocation\n",
|
||||
"from langchain_core.messages import ToolMessage\n",
|
||||
"\n",
|
||||
"from langgraph.prebuilt import ToolInvocation\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define the function that determines whether to continue or not\n",
|
||||
"def should_continue(state):\n",
|
||||
@@ -320,7 +323,7 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.graph import StateGraph, END\n",
|
||||
"from langgraph.graph import END, StateGraph\n",
|
||||
"\n",
|
||||
"# Define a new graph\n",
|
||||
"workflow = StateGraph(State)\n",
|
||||
@@ -611,7 +614,7 @@
|
||||
" indent=2,\n",
|
||||
" )\n",
|
||||
" return AIMessage(\n",
|
||||
" content = (\n",
|
||||
" content=(\n",
|
||||
" \"I plan to invoke the following tools, do you approve?\\n\\n\"\n",
|
||||
" \"Type 'y' if you do, anything else to stop.\\n\\n\"\n",
|
||||
" f\"{serialized_tool_calls}\"\n",
|
||||
@@ -665,7 +668,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stdin",
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
" can you specify sf in CA?\n"
|
||||
@@ -696,7 +699,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stdin",
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
" y\n"
|
||||
@@ -969,7 +972,7 @@
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.10.4"
|
||||
"version": "3.12.2"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
|
||||
+20
-20
@@ -245,7 +245,7 @@
|
||||
"\n",
|
||||
"try:\n",
|
||||
" display(Image(graph.get_graph().draw_mermaid_png()))\n",
|
||||
"except:\n",
|
||||
"except Exception:\n",
|
||||
" # This requires some extra dependencies and is optional\n",
|
||||
" pass"
|
||||
]
|
||||
@@ -398,7 +398,7 @@
|
||||
"\n",
|
||||
"Before we start, make sure you have the necessary packages installed and API keys set up:\n",
|
||||
"\n",
|
||||
"First, install the requirements to use the [Tavily Search Engine](https://python.langchain.com/docs/integrations/tools/tavily_search/), and set your [TAVILY_API_KEY](https://tavily.com/)."
|
||||
"First, install the requirements to use the [Tavily Search Engine](https://python.langchain.com/v0.2/docs/integrations/tools/tavily_search/), and set your [TAVILY_API_KEY](https://tavily.com/)."
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -653,7 +653,7 @@
|
||||
"\n",
|
||||
"try:\n",
|
||||
" display(Image(graph.get_graph().draw_mermaid_png()))\n",
|
||||
"except:\n",
|
||||
"except Exception:\n",
|
||||
" # This requires some extra dependencies and is optional\n",
|
||||
" pass"
|
||||
]
|
||||
@@ -775,7 +775,7 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing import Annotated, Union\n",
|
||||
"from typing import Annotated\n",
|
||||
"\n",
|
||||
"from langchain_anthropic import ChatAnthropic\n",
|
||||
"from langchain_community.tools.tavily_search import TavilySearchResults\n",
|
||||
@@ -873,7 +873,7 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from typing import Annotated, Union\n",
|
||||
"from typing import Annotated\n",
|
||||
"\n",
|
||||
"from langchain_anthropic import ChatAnthropic\n",
|
||||
"from langchain_community.tools.tavily_search import TavilySearchResults\n",
|
||||
@@ -964,7 +964,7 @@
|
||||
"\n",
|
||||
"try:\n",
|
||||
" display(Image(graph.get_graph().draw_mermaid_png()))\n",
|
||||
"except:\n",
|
||||
"except Exception:\n",
|
||||
" # This requires some extra dependencies and is optional\n",
|
||||
" pass"
|
||||
]
|
||||
@@ -1179,7 +1179,7 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing import Annotated, Union\n",
|
||||
"from typing import Annotated\n",
|
||||
"\n",
|
||||
"from langchain_anthropic import ChatAnthropic\n",
|
||||
"from langchain_community.tools.tavily_search import TavilySearchResults\n",
|
||||
@@ -1253,7 +1253,7 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from typing import Annotated, Union\n",
|
||||
"from typing import Annotated\n",
|
||||
"\n",
|
||||
"from langchain_anthropic import ChatAnthropic\n",
|
||||
"from langchain_community.tools.tavily_search import TavilySearchResults\n",
|
||||
@@ -1443,7 +1443,7 @@
|
||||
"=================================\u001b[1m Tool Message \u001b[0m=================================\n",
|
||||
"Name: tavily_search_results_json\n",
|
||||
"\n",
|
||||
"[{\"url\": \"https://github.com/langchain-ai/langgraph\", \"content\": \"LangGraph is a Python package that extends LangChain Expression Language with the ability to coordinate multiple chains across multiple steps of computation in a cyclic manner. It is inspired by Pregel and Apache Beam and can be used for agent-like behaviors, such as chatbots, with LLMs.\"}, {\"url\": \"https://python.langchain.com/docs/langgraph/\", \"content\": \"LangGraph is a library for building stateful, multi-actor applications with LLMs, built on top of (and intended to be used with) LangChain . It extends the LangChain Expression Language with the ability to coordinate multiple chains (or actors) across multiple steps of computation in a cyclic manner. It is inspired by Pregel and Apache Beam .\"}]\n",
|
||||
"[{\"url\": \"https://github.com/langchain-ai/langgraph\", \"content\": \"LangGraph is a Python package that extends LangChain Expression Language with the ability to coordinate multiple chains across multiple steps of computation in a cyclic manner. It is inspired by Pregel and Apache Beam and can be used for agent-like behaviors, such as chatbots, with LLMs.\"}, {\"url\": \"https://langchain-ai.github.io/langgraph//\", \"content\": \"LangGraph is a library for building stateful, multi-actor applications with LLMs, built on top of (and intended to be used with) LangChain . It extends the LangChain Expression Language with the ability to coordinate multiple chains (or actors) across multiple steps of computation in a cyclic manner. It is inspired by Pregel and Apache Beam .\"}]\n",
|
||||
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
|
||||
"\n",
|
||||
"Based on the search results, LangGraph seems to be a Python library that extends the LangChain library to enable more complex, multi-step interactions with large language models (LLMs). Some key points:\n",
|
||||
@@ -1487,7 +1487,7 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing import Annotated, Union\n",
|
||||
"from typing import Annotated\n",
|
||||
"\n",
|
||||
"from langchain_anthropic import ChatAnthropic\n",
|
||||
"from langchain_community.tools.tavily_search import TavilySearchResults\n",
|
||||
@@ -1495,7 +1495,7 @@
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"\n",
|
||||
"from langgraph.checkpoint.sqlite import SqliteSaver\n",
|
||||
"from langgraph.graph import MessageGraph, StateGraph\n",
|
||||
"from langgraph.graph import StateGraph\n",
|
||||
"from langgraph.graph.message import add_messages\n",
|
||||
"from langgraph.prebuilt import ToolNode\n",
|
||||
"\n",
|
||||
@@ -1569,7 +1569,7 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from typing import Annotated, Union\n",
|
||||
"from typing import Annotated\n",
|
||||
"\n",
|
||||
"from langchain_anthropic import ChatAnthropic\n",
|
||||
"from langchain_community.tools.tavily_search import TavilySearchResults\n",
|
||||
@@ -1577,7 +1577,7 @@
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"\n",
|
||||
"from langgraph.checkpoint.sqlite import SqliteSaver\n",
|
||||
"from langgraph.graph import MessageGraph, StateGraph\n",
|
||||
"from langgraph.graph import StateGraph\n",
|
||||
"from langgraph.graph.message import add_messages\n",
|
||||
"from langgraph.prebuilt import ToolNode, tools_condition\n",
|
||||
"\n",
|
||||
@@ -1688,7 +1688,7 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from langchain_core.messages import AIMessage, ToolMessage\n",
|
||||
"from langchain_core.messages import AIMessage\n",
|
||||
"\n",
|
||||
"answer = (\n",
|
||||
" \"LangGraph is a library for building stateful, multi-actor applications with LLMs.\"\n",
|
||||
@@ -1790,7 +1790,7 @@
|
||||
"\n",
|
||||
"try:\n",
|
||||
" display(Image(graph.get_graph().draw_mermaid_png()))\n",
|
||||
"except:\n",
|
||||
"except Exception:\n",
|
||||
" # This requires some extra dependencies and is optional\n",
|
||||
" pass"
|
||||
]
|
||||
@@ -2068,7 +2068,7 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing import Annotated, Union\n",
|
||||
"from typing import Annotated\n",
|
||||
"\n",
|
||||
"from langchain_anthropic import ChatAnthropic\n",
|
||||
"from langchain_community.tools.tavily_search import TavilySearchResults\n",
|
||||
@@ -2310,7 +2310,7 @@
|
||||
"\n",
|
||||
"try:\n",
|
||||
" display(Image(graph.get_graph().draw_mermaid_png()))\n",
|
||||
"except:\n",
|
||||
"except Exception:\n",
|
||||
" # This requires some extra dependencies and is optional\n",
|
||||
" pass"
|
||||
]
|
||||
@@ -2517,7 +2517,7 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing import Annotated, Union\n",
|
||||
"from typing import Annotated\n",
|
||||
"\n",
|
||||
"from langchain_anthropic import ChatAnthropic\n",
|
||||
"from langchain_community.tools.tavily_search import TavilySearchResults\n",
|
||||
@@ -2644,7 +2644,7 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing import Annotated, Union, Literal\n",
|
||||
"from typing import Annotated, Literal\n",
|
||||
"\n",
|
||||
"from langchain_anthropic import ChatAnthropic\n",
|
||||
"from langchain_community.tools.tavily_search import TavilySearchResults\n",
|
||||
@@ -2768,7 +2768,7 @@
|
||||
"\n",
|
||||
"try:\n",
|
||||
" display(Image(graph.get_graph().draw_mermaid_png()))\n",
|
||||
"except:\n",
|
||||
"except Exception:\n",
|
||||
" # This requires some extra dependencies and is optional\n",
|
||||
" pass"
|
||||
]
|
||||
|
||||
@@ -38,8 +38,9 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# %pip install -U --quiet langchain langgraph langchain_openai\n",
|
||||
"# %pip install -U --quiet tavily-python"
|
||||
"%%capture --no-stderr\n",
|
||||
"%pip install -U --quiet langchain langgraph langchain_openai\n",
|
||||
"%pip install -U --quiet tavily-python"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -49,6 +50,8 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from __future__ import annotations # noqa: F404\n",
|
||||
"\n",
|
||||
"import getpass\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
@@ -91,13 +94,11 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from __future__ import annotations\n",
|
||||
"\n",
|
||||
"import math\n",
|
||||
"from collections import deque\n",
|
||||
"from typing import Optional\n",
|
||||
"\n",
|
||||
"from langchain_core.messages import AIMessage, BaseMessage, HumanMessage, ToolMessage\n",
|
||||
"from collections import deque\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class Node:\n",
|
||||
@@ -321,13 +322,13 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n",
|
||||
"from langchain_core.pydantic_v1 import BaseModel, Field\n",
|
||||
"from langchain_core.runnables import chain as as_runnable\n",
|
||||
"from langchain_core.output_parsers.openai_tools import (\n",
|
||||
" JsonOutputToolsParser,\n",
|
||||
" PydanticToolsParser,\n",
|
||||
")\n",
|
||||
"from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n",
|
||||
"from langchain_core.pydantic_v1 import BaseModel, Field\n",
|
||||
"from langchain_core.runnables import chain as as_runnable\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class Reflection(BaseModel):\n",
|
||||
@@ -637,6 +638,7 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing import Literal\n",
|
||||
"\n",
|
||||
"from langgraph.graph import END, StateGraph\n",
|
||||
"\n",
|
||||
"\n",
|
||||
@@ -925,7 +927,7 @@
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.2"
|
||||
"version": "3.12.2"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
|
||||
+25
-25
@@ -9,7 +9,7 @@
|
||||
"\n",
|
||||
"When running LangGraph agents, you can easily save good threads and use them in the future.\n",
|
||||
"\n",
|
||||
"**Note:** this requires passing in a checkpointer."
|
||||
"**Note:** this requires passing in a checkpointer.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -19,7 +19,7 @@
|
||||
"source": [
|
||||
"## Setup\n",
|
||||
"\n",
|
||||
"First we need to install the packages required"
|
||||
"First we need to install the packages required\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -39,7 +39,7 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"!pip install --quiet -U langchain langchain_openai tavily-python"
|
||||
"!%pip install --quiet -U langgraph langchain langchain_openai tavily-pythonvily-python"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -47,7 +47,7 @@
|
||||
"id": "0abe11f4-62ed-4dc4-8875-3db21e260d1d",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Next, we need to set API keys for OpenAI (the LLM we will use) and Tavily (the search tool we will use)"
|
||||
"Next, we need to set API keys for OpenAI (the LLM we will use) and Tavily (the search tool we will use)\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -66,8 +66,8 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"import getpass\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"os.environ[\"OPENAI_API_KEY\"] = getpass.getpass(\"OpenAI API Key:\")\n",
|
||||
"os.environ[\"TAVILY_API_KEY\"] = getpass.getpass(\"Tavily API Key:\")"
|
||||
@@ -78,7 +78,7 @@
|
||||
"id": "f0ed46a8-effe-4596-b0e1-a6a29ee16f5c",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Optionally, we can set API key for [LangSmith tracing](https://smith.langchain.com/), which will give us best-in-class observability."
|
||||
"Optionally, we can set API key for [LangSmith tracing](https://smith.langchain.com/), which will give us best-in-class observability.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -101,7 +101,7 @@
|
||||
"\n",
|
||||
"We will first define the tools we want to use.\n",
|
||||
"For this simple example, we will use a built-in search tool via Tavily.\n",
|
||||
"However, it is really easy to create your own tools - see documentation [here](https://python.langchain.com/docs/modules/agents/tools/custom_tools) on how to do that.\n"
|
||||
"However, it is really easy to create your own tools - see documentation [here](https://python.langchain.com/v0.2/docs/how_to/custom_tools) on how to do that.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -150,7 +150,7 @@
|
||||
"1. It should work with messages. We will represent all agent state in the form of messages, so it needs to be able to work well with them.\n",
|
||||
"2. It should work with OpenAI function calling. This means it should either be an OpenAI model or a model that exposes a similar interface.\n",
|
||||
"\n",
|
||||
"Note: these model requirements are not requirements for using LangGraph - they are just requirements for this one example."
|
||||
"Note: these model requirements are not requirements for using LangGraph - they are just requirements for this one example.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -170,7 +170,6 @@
|
||||
"id": "a77995c0-bae2-4cee-a036-8688a90f05b9",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"\n",
|
||||
"After we've done this, we should make sure the model knows that it has these tools available to call.\n",
|
||||
"We can do this using the `.bind_tools()` method, common to many of LangChain's chat models.\n"
|
||||
]
|
||||
@@ -193,7 +192,7 @@
|
||||
"## Define the nodes\n",
|
||||
"\n",
|
||||
"We now need to define a few different nodes in our graph.\n",
|
||||
"In `langgraph`, a node can be either a function or a [runnable](https://python.langchain.com/docs/expression_language/).\n",
|
||||
"In `langgraph`, a node can be either a function or a [runnable](https://python.langchain.com/v0.2/docs/concepts/#langchain-expression-language-lcel).\n",
|
||||
"There are two main nodes we need for this:\n",
|
||||
"\n",
|
||||
"1. The agent: responsible for deciding what (if any) actions to take.\n",
|
||||
@@ -209,7 +208,7 @@
|
||||
" b. If the agent said that it was finished, then it should finish\n",
|
||||
"2. Normal Edge: after the tools are invoked, it should always go back to the agent to decide what to do next\n",
|
||||
"\n",
|
||||
"Let's define the nodes, as well as a function to decide how what conditional edge to take."
|
||||
"Let's define the nodes, as well as a function to decide how what conditional edge to take.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -237,7 +236,7 @@
|
||||
"source": [
|
||||
"## Define the graph\n",
|
||||
"\n",
|
||||
"We can now put it all together and define the graph!"
|
||||
"We can now put it all together and define the graph!\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -247,11 +246,19 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.graph import StateGraph, END\n",
|
||||
"from typing import Annotated, TypedDict\n",
|
||||
"\n",
|
||||
"from langchain_core.messages import (\n",
|
||||
" AIMessage,\n",
|
||||
" AnyMessage,\n",
|
||||
" HumanMessage,\n",
|
||||
" SystemMessage,\n",
|
||||
" ToolMessage,\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"from langgraph.graph import END, StateGraph\n",
|
||||
"from langgraph.graph.message import add_messages\n",
|
||||
"from langgraph.managed.few_shot import FewShotExamples\n",
|
||||
"from typing import TypedDict, Annotated\n",
|
||||
"from langchain_core.messages import AnyMessage, HumanMessage, SystemMessage\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class BaseState(TypedDict):\n",
|
||||
@@ -259,9 +266,6 @@
|
||||
" examples: Annotated[list, FewShotExamples]\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"from langchain_core.messages import AIMessage, ToolMessage\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def _render_message(m):\n",
|
||||
" if isinstance(m, HumanMessage):\n",
|
||||
" return \"Human: \" + m.content\n",
|
||||
@@ -299,9 +303,7 @@
|
||||
"\n",
|
||||
"{examples}\n",
|
||||
"\n",
|
||||
"Assist the user as they require!\"\"\".format(\n",
|
||||
" examples=_examples\n",
|
||||
" )\n",
|
||||
"Assist the user as they require!\"\"\".format(examples=_examples)\n",
|
||||
"\n",
|
||||
" else:\n",
|
||||
" system_message = \"\"\"You are a helpful assistant\"\"\"\n",
|
||||
@@ -350,7 +352,7 @@
|
||||
"source": [
|
||||
"**Persistence**\n",
|
||||
"\n",
|
||||
"To add in persistence, we pass in a checkpoint when compiling the graph"
|
||||
"To add in persistence, we pass in a checkpoint when compiling the graph\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -383,7 +385,7 @@
|
||||
"id": "e8aff75b-563e-42b1-969b-742201514fc3",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Preview the graph"
|
||||
"## Preview the graph\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -435,8 +437,6 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from langchain_core.messages import HumanMessage\n",
|
||||
"\n",
|
||||
"thread = {\"configurable\": {\"thread_id\": \"1\"}}\n",
|
||||
"for event in app.stream(\n",
|
||||
" {\"messages\": [HumanMessage(content=\"whats the weather in sf?\")]}, thread\n",
|
||||
|
||||
@@ -43,8 +43,8 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"import getpass\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def _get_pass(var: str):\n",
|
||||
@@ -68,7 +68,7 @@
|
||||
"\n",
|
||||
"We'll first define the tools for the agent to use in our demo. We'll give it the class search engine + calculator combo.\n",
|
||||
"\n",
|
||||
"If you don't want to sign up for tavily, you can replace it with the free [DuckDuckGo](https://python.langchain.com/docs/integrations/tools/ddg)."
|
||||
"If you don't want to sign up for tavily, you can replace it with the free [DuckDuckGo](https://python.langchain.com/v0.2/docs/integrations/tools/ddg/)."
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -78,8 +78,8 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain_openai import ChatOpenAI\n",
|
||||
"from langchain_community.tools.tavily_search import TavilySearchResults\n",
|
||||
"from langchain_openai import ChatOpenAI\n",
|
||||
"\n",
|
||||
"# Imported from the https://github.com/langchain-ai/langgraph/tree/main/examples/plan-and-execute repo\n",
|
||||
"from math_tools import get_math_tool\n",
|
||||
@@ -191,21 +191,19 @@
|
||||
"source": [
|
||||
"from typing import Sequence\n",
|
||||
"\n",
|
||||
"from langchain import hub\n",
|
||||
"from langchain_core.language_models import BaseChatModel\n",
|
||||
"from langchain_core.prompts import ChatPromptTemplate\n",
|
||||
"from langchain_core.runnables import RunnableBranch\n",
|
||||
"from langchain_core.tools import BaseTool\n",
|
||||
"from langchain_core.messages import (\n",
|
||||
" BaseMessage,\n",
|
||||
" FunctionMessage,\n",
|
||||
" HumanMessage,\n",
|
||||
" SystemMessage,\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"from output_parser import LLMCompilerPlanParser, Task\n",
|
||||
"from langchain import hub\n",
|
||||
"from langchain_core.prompts import ChatPromptTemplate\n",
|
||||
"from langchain_core.runnables import RunnableBranch\n",
|
||||
"from langchain_core.tools import BaseTool\n",
|
||||
"from langchain_openai import ChatOpenAI\n",
|
||||
"\n",
|
||||
"from output_parser import LLMCompilerPlanParser, Task\n",
|
||||
"\n",
|
||||
"prompt = hub.pull(\"wfh/llm-compiler\")\n",
|
||||
"print(prompt.pretty_print())"
|
||||
@@ -340,16 +338,15 @@
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing import Any, Union, Iterable, List, Tuple, Dict\n",
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"import re\n",
|
||||
"import time\n",
|
||||
"from concurrent.futures import ThreadPoolExecutor, wait\n",
|
||||
"from typing import Any, Dict, Iterable, List, Union\n",
|
||||
"\n",
|
||||
"from langchain_core.runnables import (\n",
|
||||
" chain as as_runnable,\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"from concurrent.futures import ThreadPoolExecutor, wait\n",
|
||||
"import time\n",
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def _get_observations(messages: List[BaseMessage]) -> Dict[int, Any]:\n",
|
||||
@@ -472,8 +469,7 @@
|
||||
" args_for_tasks[task[\"idx\"]] = task[\"args\"]\n",
|
||||
" if (\n",
|
||||
" # Depends on other tasks\n",
|
||||
" deps\n",
|
||||
" and (any([dep not in observations for dep in deps]))\n",
|
||||
" deps and (any([dep not in observations for dep in deps]))\n",
|
||||
" ):\n",
|
||||
" futures.append(\n",
|
||||
" executor.submit(\n",
|
||||
@@ -597,9 +593,9 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain_core.pydantic_v1 import BaseModel, Field\n",
|
||||
"from langchain.chains.openai_functions import create_structured_output_runnable\n",
|
||||
"from langchain_core.messages import AIMessage\n",
|
||||
"from langchain_core.pydantic_v1 import BaseModel, Field\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class FinalResponse(BaseModel):\n",
|
||||
@@ -724,9 +720,10 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.graph import MessageGraph, END\n",
|
||||
"from typing import Dict\n",
|
||||
"\n",
|
||||
"from langgraph.graph import END, MessageGraph\n",
|
||||
"\n",
|
||||
"graph_builder = MessageGraph()\n",
|
||||
"\n",
|
||||
"# 1. Define vertices\n",
|
||||
|
||||
@@ -48,8 +48,8 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"import getpass\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def _set_env(var: str):\n",
|
||||
@@ -104,8 +104,10 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"from typing import Annotated\n",
|
||||
"\n",
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"\n",
|
||||
"from langgraph.graph.message import add_messages\n",
|
||||
"\n",
|
||||
"# Add messages essentially does this with more\n",
|
||||
@@ -127,7 +129,7 @@
|
||||
"\n",
|
||||
"We will first define the tools we want to use.\n",
|
||||
"For this simple example, we will use create a placeholder search engine.\n",
|
||||
"It is really easy to create your own tools - see documentation [here](https://python.langchain.com/docs/modules/agents/tools/custom_tools) on how to do that.\n"
|
||||
"It is really easy to create your own tools - see documentation [here](https://python.langchain.com/v0.2/docs/how_to/custom_tools) on how to do that.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -229,7 +231,7 @@
|
||||
"## Define the nodes\n",
|
||||
"\n",
|
||||
"We now need to define a few different nodes in our graph.\n",
|
||||
"In `langgraph`, a node can be either a function or a [runnable](https://python.langchain.com/docs/expression_language/).\n",
|
||||
"In `langgraph`, a node can be either a function or a [runnable](https://python.langchain.com/v0.2/docs/concepts/#langchain-expression-language-lcel).\n",
|
||||
"There are two main nodes we need for this:\n",
|
||||
"\n",
|
||||
"1. The agent: responsible for deciding what (if any) actions to take.\n",
|
||||
@@ -317,7 +319,7 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.graph import StateGraph, END\n",
|
||||
"from langgraph.graph import END, StateGraph\n",
|
||||
"\n",
|
||||
"# Define a new graph\n",
|
||||
"workflow = StateGraph(State)\n",
|
||||
@@ -392,7 +394,7 @@
|
||||
"## Use it!\n",
|
||||
"\n",
|
||||
"We can now use it!\n",
|
||||
"This now exposes the [same interface](https://python.langchain.com/docs/expression_language/) as all other LangChain runnables."
|
||||
"This now exposes the [same interface](https://python.langchain.com/v0.2/docs/concepts/#langchain-expression-language-lcel) as all other LangChain runnables."
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -675,7 +677,7 @@
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.2"
|
||||
"version": "3.12.2"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
|
||||
@@ -46,8 +46,8 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"import getpass\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def _set_env(var: str):\n",
|
||||
@@ -92,18 +92,18 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"from typing import Annotated\n",
|
||||
"from langgraph.graph import MessagesState\n",
|
||||
"from langchain_core.tools import tool\n",
|
||||
"from langgraph.prebuilt import ToolNode\n",
|
||||
"from langchain_anthropic import ChatAnthropic\n",
|
||||
"from typing import Literal\n",
|
||||
"from langgraph.graph import StateGraph, END\n",
|
||||
"\n",
|
||||
"from langchain_anthropic import ChatAnthropic\n",
|
||||
"from langchain_core.tools import tool\n",
|
||||
"\n",
|
||||
"from langgraph.checkpoint.sqlite import SqliteSaver\n",
|
||||
"from langgraph.graph import MessagesState, StateGraph\n",
|
||||
"from langgraph.prebuilt import ToolNode\n",
|
||||
"\n",
|
||||
"memory = SqliteSaver.from_conn_string(\":memory:\")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"@tool\n",
|
||||
"def search(query: str):\n",
|
||||
" \"\"\"Call to surf the web.\"\"\"\n",
|
||||
@@ -119,6 +119,7 @@
|
||||
"model = ChatAnthropic(model_name=\"claude-3-haiku-20240307\")\n",
|
||||
"bound_model = model.bind_tools(tools)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def should_continue(state: MessagesState) -> Literal[\"action\", \"__end__\"]:\n",
|
||||
" \"\"\"Return the next node to execute.\"\"\"\n",
|
||||
" last_message = state[\"messages\"][-1]\n",
|
||||
@@ -222,18 +223,18 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"from typing import Annotated\n",
|
||||
"from langgraph.graph import MessagesState\n",
|
||||
"from langchain_core.tools import tool\n",
|
||||
"from langgraph.prebuilt import ToolNode\n",
|
||||
"from langchain_anthropic import ChatAnthropic\n",
|
||||
"from typing import Literal\n",
|
||||
"from langgraph.graph import StateGraph, END\n",
|
||||
"\n",
|
||||
"from langchain_anthropic import ChatAnthropic\n",
|
||||
"from langchain_core.tools import tool\n",
|
||||
"\n",
|
||||
"from langgraph.checkpoint.sqlite import SqliteSaver\n",
|
||||
"from langgraph.graph import MessagesState, StateGraph\n",
|
||||
"from langgraph.prebuilt import ToolNode\n",
|
||||
"\n",
|
||||
"memory = SqliteSaver.from_conn_string(\":memory:\")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"@tool\n",
|
||||
"def search(query: str):\n",
|
||||
" \"\"\"Call to surf the web.\"\"\"\n",
|
||||
@@ -249,6 +250,7 @@
|
||||
"model = ChatAnthropic(model_name=\"claude-3-haiku-20240307\")\n",
|
||||
"bound_model = model.bind_tools(tools)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def should_continue(state: MessagesState) -> Literal[\"action\", \"__end__\"]:\n",
|
||||
" \"\"\"Return the next node to execute.\"\"\"\n",
|
||||
" last_message = state[\"messages\"][-1]\n",
|
||||
@@ -335,7 +337,7 @@
|
||||
"for event in app.stream({\"messages\": [input_message]}, config, stream_mode=\"values\"):\n",
|
||||
" event[\"messages\"][-1].pretty_print()\n",
|
||||
"\n",
|
||||
"# This will now not remember the previous messages \n",
|
||||
"# This will now not remember the previous messages\n",
|
||||
"# (because we set `messages[-1:]` in the filter messages argument)\n",
|
||||
"input_message = HumanMessage(content=\"whats my name?\")\n",
|
||||
"for event in app.stream({\"messages\": [input_message]}, config, stream_mode=\"values\"):\n",
|
||||
|
||||
+14
-13
@@ -7,7 +7,7 @@
|
||||
"source": [
|
||||
"# Map Reduce\n",
|
||||
"\n",
|
||||
"A common pattern in agents is to generate a list of objects, do some work on each of those objects, and then combine the results. This is very similar to the common [map-reduce](https://en.wikipedia.org/wiki/MapReduce) operation. This can be tricky for a few reasons. First, it can be tought to define in structured graph ahead of time because the length of the list of objects may be unknown. Second, in order to do this map-reduce you need multiple versions of the state to exist... but the graph shares a common shared state, so how can this be?\n",
|
||||
"A common pattern in agents is to generate a list of objects, do some work on each of those objects, and then combine the results. This is very similar to the common [map-reduce](https://en.wikipedia.org/wiki/MapReduce) operation. This can be tricky for a few reasons. First, it can be tough to define a structured graph ahead of time because the length of the list of objects may be unknown. Second, in order to do this map-reduce you need multiple versions of the state to exist... but the graph shares a common shared state, so how can this be?\n",
|
||||
"\n",
|
||||
"LangGraph supports this via the `Send` api. This can be used to allow a conditional edge to `Send` multiple different states to multiple nodes. The state it sends can be different from the state of the core graph.\n",
|
||||
"\n",
|
||||
@@ -35,12 +35,14 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"import operator\n",
|
||||
"from typing import Annotated, TypedDict\n",
|
||||
"\n",
|
||||
"from langchain_core.pydantic_v1 import BaseModel\n",
|
||||
"from langchain_openai import ChatOpenAI\n",
|
||||
"\n",
|
||||
"from langgraph.constants import Send\n",
|
||||
"from langgraph.graph import END, StateGraph\n",
|
||||
"import operator\n",
|
||||
"from typing import TypedDict, Annotated\n",
|
||||
"from langchain_openai import ChatOpenAI\n",
|
||||
"from langchain_core.pydantic_v1 import BaseModel\n",
|
||||
"\n",
|
||||
"# Model and prompts\n",
|
||||
"# Define model and prompts we will use\n",
|
||||
@@ -67,6 +69,7 @@
|
||||
"\n",
|
||||
"# Graph components: define the components that will make up the graph\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# This will be the overall state of the main graph.\n",
|
||||
"# It will contain a topic (which we expect the user to provide)\n",
|
||||
"# and then will generate a list of subjects, and then a joke for\n",
|
||||
@@ -90,14 +93,14 @@
|
||||
"\n",
|
||||
"# This is the function we will use to generate the subjects of the jokes\n",
|
||||
"def generate_topics(state: OverallState):\n",
|
||||
" prompt = subjects_prompt.format(topic=state['topic'])\n",
|
||||
" prompt = subjects_prompt.format(topic=state[\"topic\"])\n",
|
||||
" response = model.with_structured_output(Subjects).invoke(prompt)\n",
|
||||
" return {\"subjects\": response.subjects}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Here we generate a joke, given a subject\n",
|
||||
"def generate_joke(state: JokeState):\n",
|
||||
" prompt = joke_prompt.format(subject=state['subject'])\n",
|
||||
" prompt = joke_prompt.format(subject=state[\"subject\"])\n",
|
||||
" response = model.with_structured_output(Joke).invoke(prompt)\n",
|
||||
" return {\"jokes\": [response.joke]}\n",
|
||||
"\n",
|
||||
@@ -108,17 +111,15 @@
|
||||
" # We will return a list of `Send` objects\n",
|
||||
" # Each `Send` object consists of the name of a node in the graph\n",
|
||||
" # as well as the state to send to that node\n",
|
||||
" return [Send(\"generate_joke\", {\"subject\": s}) for s in state['subjects']]\n",
|
||||
" return [Send(\"generate_joke\", {\"subject\": s}) for s in state[\"subjects\"]]\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Here we will judge the best joke\n",
|
||||
"def best_joke(state: OverallState):\n",
|
||||
" jokes = \"\\n\\n\".format(\"Joke {i}: {j}\" for i, j in enumerate(state['jokes']))\n",
|
||||
" prompt = best_joke_prompt.format(topic=state['topic'], jokes=jokes)\n",
|
||||
" jokes = \"\\n\\n\".format()\n",
|
||||
" prompt = best_joke_prompt.format(topic=state[\"topic\"], jokes=jokes)\n",
|
||||
" response = model.with_structured_output(BestJoke).invoke(prompt)\n",
|
||||
" return {\"best_selected_joke\": state['jokes'][response.id]}\n",
|
||||
" \n",
|
||||
"\n",
|
||||
" return {\"best_selected_joke\": state[\"jokes\"][response.id]}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Construct the graph: here we put everything together to construct our graph\n",
|
||||
|
||||
@@ -27,8 +27,8 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# %%capture --no-stderr\n",
|
||||
"# %pip install -U langchain langchain_openai langchain_experimental langsmith pandas"
|
||||
"%%capture --no-stderr\n",
|
||||
"%pip install -U langgraph langchain langchain_openai langchain_experimental langsmith pandas"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -73,10 +73,9 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing import Annotated, List, Tuple, Union\n",
|
||||
"from typing import Annotated\n",
|
||||
"\n",
|
||||
"from langchain_community.tools.tavily_search import TavilySearchResults\n",
|
||||
"from langchain_core.tools import tool\n",
|
||||
"from langchain_experimental.tools import PythonREPLTool\n",
|
||||
"\n",
|
||||
"tavily_tool = TavilySearchResults(max_results=5)\n",
|
||||
@@ -161,8 +160,8 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n",
|
||||
"from langchain_core.output_parsers.openai_functions import JsonOutputFunctionsParser\n",
|
||||
"from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n",
|
||||
"\n",
|
||||
"members = [\"Researcher\", \"Coder\"]\n",
|
||||
"system_prompt = (\n",
|
||||
@@ -231,12 +230,13 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import operator\n",
|
||||
"from typing import Annotated, Any, Dict, List, Optional, Sequence, TypedDict\n",
|
||||
"import functools\n",
|
||||
"import operator\n",
|
||||
"from typing import Sequence, TypedDict\n",
|
||||
"\n",
|
||||
"from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n",
|
||||
"from langgraph.graph import StateGraph, END\n",
|
||||
"\n",
|
||||
"from langgraph.graph import END, StateGraph\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# The agent state is the input to each node in the graph\n",
|
||||
|
||||
@@ -59,7 +59,6 @@
|
||||
"source": [
|
||||
"import getpass\n",
|
||||
"import os\n",
|
||||
"import uuid\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def _set_if_undefined(var: str):\n",
|
||||
@@ -105,13 +104,11 @@
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing import Annotated, List, Tuple, Union\n",
|
||||
"from typing import Annotated, List\n",
|
||||
"\n",
|
||||
"import matplotlib.pyplot as plt\n",
|
||||
"from langchain_community.document_loaders import WebBaseLoader\n",
|
||||
"from langchain_community.tools.tavily_search import TavilySearchResults\n",
|
||||
"from langchain_core.tools import tool\n",
|
||||
"from langsmith import trace\n",
|
||||
"\n",
|
||||
"tavily_tool = TavilySearchResults(max_results=5)\n",
|
||||
"\n",
|
||||
@@ -236,7 +233,7 @@
|
||||
"\n",
|
||||
"@tool\n",
|
||||
"def python_repl(\n",
|
||||
" code: Annotated[str, \"The python code to execute to generate your chart.\"]\n",
|
||||
" code: Annotated[str, \"The python code to execute to generate your chart.\"],\n",
|
||||
"):\n",
|
||||
" \"\"\"Use this to execute python code. If you want to see the output of a value,\n",
|
||||
" you should print it out with `print(...)`. This is visible to the user.\"\"\"\n",
|
||||
@@ -274,13 +271,11 @@
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing import Any, Callable, List, Optional, TypedDict, Union\n",
|
||||
"from typing import List, Optional\n",
|
||||
"\n",
|
||||
"from langchain.agents import AgentExecutor, create_openai_functions_agent\n",
|
||||
"from langchain.output_parsers.openai_functions import JsonOutputFunctionsParser\n",
|
||||
"from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n",
|
||||
"from langchain_core.runnables import Runnable\n",
|
||||
"from langchain_core.tools import BaseTool\n",
|
||||
"from langchain_openai import ChatOpenAI\n",
|
||||
"\n",
|
||||
"from langgraph.graph import END, StateGraph\n",
|
||||
@@ -383,9 +378,8 @@
|
||||
"import functools\n",
|
||||
"import operator\n",
|
||||
"\n",
|
||||
"from langchain_core.messages import AIMessage, BaseMessage, HumanMessage\n",
|
||||
"from langchain_core.messages import BaseMessage, HumanMessage\n",
|
||||
"from langchain_openai.chat_models import ChatOpenAI\n",
|
||||
"import functools\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# ResearchTeam graph state\n",
|
||||
@@ -586,7 +580,7 @@
|
||||
" written_files = [\n",
|
||||
" f.relative_to(WORKING_DIRECTORY) for f in WORKING_DIRECTORY.rglob(\"*\")\n",
|
||||
" ]\n",
|
||||
" except:\n",
|
||||
" except Exception:\n",
|
||||
" pass\n",
|
||||
" if not written_files:\n",
|
||||
" return {**state, \"current_files\": \"No files written.\"}\n",
|
||||
@@ -785,10 +779,9 @@
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain_core.messages import AIMessage, BaseMessage, HumanMessage\n",
|
||||
"from langchain_core.messages import BaseMessage\n",
|
||||
"from langchain_openai.chat_models import ChatOpenAI\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"llm = ChatOpenAI(model=\"gpt-4-1106-preview\")\n",
|
||||
"\n",
|
||||
"supervisor_node = create_team_supervisor(\n",
|
||||
|
||||
@@ -77,10 +77,11 @@
|
||||
"source": [
|
||||
"from langchain_core.messages import (\n",
|
||||
" BaseMessage,\n",
|
||||
" ToolMessage,\n",
|
||||
" HumanMessage,\n",
|
||||
" ToolMessage,\n",
|
||||
")\n",
|
||||
"from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n",
|
||||
"\n",
|
||||
"from langgraph.graph import END, StateGraph\n",
|
||||
"\n",
|
||||
"\n",
|
||||
@@ -123,10 +124,11 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain_core.tools import tool\n",
|
||||
"from typing import Annotated\n",
|
||||
"from langchain_experimental.utilities import PythonREPL\n",
|
||||
"\n",
|
||||
"from langchain_community.tools.tavily_search import TavilySearchResults\n",
|
||||
"from langchain_core.tools import tool\n",
|
||||
"from langchain_experimental.utilities import PythonREPL\n",
|
||||
"\n",
|
||||
"tavily_tool = TavilySearchResults(max_results=5)\n",
|
||||
"\n",
|
||||
@@ -137,7 +139,7 @@
|
||||
"\n",
|
||||
"@tool\n",
|
||||
"def python_repl(\n",
|
||||
" code: Annotated[str, \"The python code to execute to generate your chart.\"]\n",
|
||||
" code: Annotated[str, \"The python code to execute to generate your chart.\"],\n",
|
||||
"):\n",
|
||||
" \"\"\"Use this to execute python code. If you want to see the output of a value,\n",
|
||||
" you should print it out with `print(...)`. This is visible to the user.\"\"\"\n",
|
||||
@@ -182,7 +184,6 @@
|
||||
"from typing import Annotated, Sequence, TypedDict\n",
|
||||
"\n",
|
||||
"from langchain_openai import ChatOpenAI\n",
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# This defines the object that is passed between each node\n",
|
||||
@@ -210,6 +211,7 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import functools\n",
|
||||
"\n",
|
||||
"from langchain_core.messages import AIMessage\n",
|
||||
"\n",
|
||||
"\n",
|
||||
@@ -377,7 +379,7 @@
|
||||
"\n",
|
||||
"try:\n",
|
||||
" display(Image(graph.get_graph(xray=True).draw_mermaid_png()))\n",
|
||||
"except:\n",
|
||||
"except Exception:\n",
|
||||
" # This requires some extra dependencies and is optional\n",
|
||||
" pass"
|
||||
]
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%%capture --no-stderr\n",
|
||||
"%pip install --quiet -U langchain langchain_openai"
|
||||
"%pip install --quiet -U langgraph langchain langchain_openai"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -55,10 +55,10 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"import getpass\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"if 'OPENAI_API_KEY' not in os.environ:\n",
|
||||
"if \"OPENAI_API_KEY\" not in os.environ:\n",
|
||||
" os.environ[\"OPENAI_API_KEY\"] = getpass.getpass(\"OpenAI API Key:\")"
|
||||
]
|
||||
},
|
||||
@@ -79,7 +79,7 @@
|
||||
"source": [
|
||||
"os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n",
|
||||
"\n",
|
||||
"if 'LANGCHAIN_API_KEY' not in os.environ:\n",
|
||||
"if \"LANGCHAIN_API_KEY\" not in os.environ:\n",
|
||||
" os.environ[\"LANGCHAIN_API_KEY\"] = getpass.getpass(\"LangSmith API Key:\")"
|
||||
]
|
||||
},
|
||||
@@ -90,7 +90,7 @@
|
||||
"source": [
|
||||
"## Set up the tools\n",
|
||||
"\n",
|
||||
"Here, we will make a function that dynamically creates 3 [custom-tools](https://python.langchain.com/docs/modules/agents/tools/custom_tools).\n",
|
||||
"Here, we will make a function that dynamically creates 3 [custom-tools](https://python.langchain.com/v0.2/docs/how_to/custom_tools).\n",
|
||||
"\n",
|
||||
"This function will bind to the tools the correct `user_id`, allowing the LLM to only fill in the other relevant values. Importantly,\n",
|
||||
"the LLM will be **unaware** that a user ID even exists!"
|
||||
@@ -104,25 +104,27 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing import List\n",
|
||||
"from langchain_core.tools import tool, BaseTool\n",
|
||||
"\n",
|
||||
"from langchain_core.tools import BaseTool, tool\n",
|
||||
"\n",
|
||||
"# A global dict that the tools will be updating in this example.\n",
|
||||
"user_to_pets = {}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def generate_tools_for_user(user_id: str) -> List[BaseTool]:\n",
|
||||
" \"\"\"Generate a set of tools that have a user id associated with them.\"\"\"\n",
|
||||
" \n",
|
||||
"\n",
|
||||
" @tool\n",
|
||||
" def update_favorite_pets(pets: List[str]) -> None:\n",
|
||||
" \"\"\"Add the list of favorite pets.\"\"\"\n",
|
||||
" user_to_pets[user_id] = pets\n",
|
||||
" \n",
|
||||
"\n",
|
||||
" @tool\n",
|
||||
" def delete_favorite_pets() -> None:\n",
|
||||
" \"\"\"Delete the list of favorite pets.\"\"\"\n",
|
||||
" if user_id in user_to_pets:\n",
|
||||
" del user_to_pets[user_id]\n",
|
||||
" \n",
|
||||
"\n",
|
||||
" @tool\n",
|
||||
" def list_favorite_pets() -> None:\n",
|
||||
" \"\"\"List favorite pets if any.\"\"\"\n",
|
||||
@@ -186,8 +188,9 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing import TypedDict, Annotated, Sequence\n",
|
||||
"import operator\n",
|
||||
"from typing import Annotated, Sequence, TypedDict\n",
|
||||
"\n",
|
||||
"from langchain_core.messages import BaseMessage\n",
|
||||
"\n",
|
||||
"\n",
|
||||
@@ -203,7 +206,7 @@
|
||||
"## Define the nodes\n",
|
||||
"\n",
|
||||
"We now need to define a few different nodes in our graph.\n",
|
||||
"In `langgraph`, a node can be either a function or a [runnable](https://python.langchain.com/docs/expression_language/).\n",
|
||||
"In `langgraph`, a node can be either a function or a [runnable](https://python.langchain.com/v0.2/docs/concepts/#langchain-expression-language-lcel).\n",
|
||||
"There are two main nodes we need for this:\n",
|
||||
"\n",
|
||||
"1. The agent: responsible for deciding what (if any) actions to take.\n",
|
||||
@@ -229,9 +232,9 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.prebuilt import ToolInvocation\n",
|
||||
"from langchain_core.messages import ToolMessage\n",
|
||||
"from langgraph.prebuilt import ToolExecutor\n",
|
||||
"\n",
|
||||
"from langgraph.prebuilt import ToolExecutor, ToolInvocation\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define the function that determines whether to continue or not\n",
|
||||
@@ -249,7 +252,7 @@
|
||||
"# Define the function that calls the model\n",
|
||||
"def call_model(state, config):\n",
|
||||
" messages = state[\"messages\"]\n",
|
||||
" tools = generate_tools_for_user(config['user_id'])\n",
|
||||
" tools = generate_tools_for_user(config[\"user_id\"])\n",
|
||||
" model_with_tools = model.bind_tools(tools)\n",
|
||||
" response = model_with_tools.invoke(messages)\n",
|
||||
" # We return a list, because this will get added to the existing list\n",
|
||||
@@ -275,7 +278,7 @@
|
||||
" # We can now wrap these tools in a simple ToolExecutor.\n",
|
||||
" # This is a real simple class that takes in a ToolInvocation and calls that tool, returning the output.\n",
|
||||
" # A ToolInvocation is any class with `tool` and `tool_input` attribute.\n",
|
||||
" tools = generate_tools_for_user(config['user_id'])\n",
|
||||
" tools = generate_tools_for_user(config[\"user_id\"])\n",
|
||||
" tool_executor = ToolExecutor(tools)\n",
|
||||
" responses = tool_executor.batch(tool_invocations, return_exceptions=True)\n",
|
||||
" # We use the response to create tool messages\n",
|
||||
@@ -309,7 +312,7 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.graph import StateGraph, END\n",
|
||||
"from langgraph.graph import END, StateGraph\n",
|
||||
"\n",
|
||||
"# Define a new graph\n",
|
||||
"workflow = StateGraph(AgentState)\n",
|
||||
@@ -375,7 +378,7 @@
|
||||
"\n",
|
||||
"try:\n",
|
||||
" display(Image(app.get_graph(xray=True).draw_mermaid_png()))\n",
|
||||
"except:\n",
|
||||
"except Exception:\n",
|
||||
" # This requires some extra dependencies and is optional\n",
|
||||
" pass"
|
||||
]
|
||||
@@ -388,7 +391,7 @@
|
||||
"## Use it!\n",
|
||||
"\n",
|
||||
"We can now use it!\n",
|
||||
"This now exposes the [same interface](https://python.langchain.com/docs/expression_language/) as all other LangChain runnables."
|
||||
"This now exposes the [same interface](https://python.langchain.com/v0.2/docs/concepts/#langchain-expression-language-lcel) as all other LangChain runnables."
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -427,12 +430,12 @@
|
||||
"source": [
|
||||
"from langchain_core.messages import HumanMessage\n",
|
||||
"\n",
|
||||
"user_to_pets.clear() # Clear the state\n",
|
||||
"user_to_pets.clear() # Clear the state\n",
|
||||
"\n",
|
||||
"print(f'User information prior to run: {user_to_pets}')\n",
|
||||
"print(f\"User information prior to run: {user_to_pets}\")\n",
|
||||
"\n",
|
||||
"inputs = {\"messages\": [HumanMessage(content=\"my favorite pets are cats and dogs\")]}\n",
|
||||
"for output in app.stream(inputs, {'user_id': 'eugene'}):\n",
|
||||
"for output in app.stream(inputs, {\"user_id\": \"eugene\"}):\n",
|
||||
" # stream() yields dictionaries with output keyed by node name\n",
|
||||
" for key, value in output.items():\n",
|
||||
" print(f\"Output from node '{key}':\")\n",
|
||||
@@ -440,7 +443,7 @@
|
||||
" print(value)\n",
|
||||
" print(\"\\n---\\n\")\n",
|
||||
"\n",
|
||||
"print(f'User information prior to run: {user_to_pets}')"
|
||||
"print(f\"User information prior to run: {user_to_pets}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -477,11 +480,11 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"print(f'User information prior to run: {user_to_pets}')\n",
|
||||
"print(f\"User information prior to run: {user_to_pets}\")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"inputs = {\"messages\": [HumanMessage(content=\"what are my favorite pets?\")]}\n",
|
||||
"for output in app.stream(inputs, {'user_id': 'eugene'}):\n",
|
||||
"for output in app.stream(inputs, {\"user_id\": \"eugene\"}):\n",
|
||||
" # stream() yields dictionaries with output keyed by node name\n",
|
||||
" for key, value in output.items():\n",
|
||||
" print(f\"Output from node '{key}':\")\n",
|
||||
@@ -490,7 +493,7 @@
|
||||
" print(\"\\n---\\n\")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"print(f'User information prior to run: {user_to_pets}')"
|
||||
"print(f\"User information prior to run: {user_to_pets}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -527,11 +530,15 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"print(f'User information prior to run: {user_to_pets}')\n",
|
||||
"print(f\"User information prior to run: {user_to_pets}\")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"inputs = {\"messages\": [HumanMessage(content=\"please forget what i told you about my favorite animals\")]}\n",
|
||||
"for output in app.stream(inputs, {'user_id': 'eugene'}):\n",
|
||||
"inputs = {\n",
|
||||
" \"messages\": [\n",
|
||||
" HumanMessage(content=\"please forget what i told you about my favorite animals\")\n",
|
||||
" ]\n",
|
||||
"}\n",
|
||||
"for output in app.stream(inputs, {\"user_id\": \"eugene\"}):\n",
|
||||
" # stream() yields dictionaries with output keyed by node name\n",
|
||||
" for key, value in output.items():\n",
|
||||
" print(f\"Output from node '{key}':\")\n",
|
||||
@@ -540,7 +547,7 @@
|
||||
" print(\"\\n---\\n\")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"print(f'User information prior to run: {user_to_pets}')"
|
||||
"print(f\"User information prior to run: {user_to_pets}\")"
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
"<div class=\"admonition tip\">\n",
|
||||
" <p class=\"admonition-title\">Note</p>\n",
|
||||
" <p>\n",
|
||||
" In this how-to, we will create our agent from scratch to be transparent (but verbose). You can accomplish similar functionality using the <code>create_react_agent(model, tools=tool, checkpointer=checkpointer)</code> (<a href=\"https://langchain-ai.github.io/langgraph/reference/prebuilt/#create_react_agent\">API doc</a>) constructor. This may be more appropriate if you are used to LangChain’s <a href=\"https://python.langchain.com/v0.1/docs/modules/agents/concepts/#agentexecutor\">AgentExecutor</a> class.\n",
|
||||
" In this how-to, we will create our agent from scratch to be transparent (but verbose). You can accomplish similar functionality using the <code>create_react_agent(model, tools=tool, checkpointer=checkpointer)</code> (<a href=\"https://langchain-ai.github.io/langgraph/reference/prebuilt/#create_react_agent\">API doc</a>) constructor. This may be more appropriate if you are used to LangChain’s <a href=\"https://python.langchain.com/v0.2/docs/how_to/agent_executor/#concepts\">AgentExecutor</a> class.\n",
|
||||
" </p>\n",
|
||||
"</div> "
|
||||
]
|
||||
@@ -74,8 +74,8 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"import getpass\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def _set_env(var: str):\n",
|
||||
@@ -122,8 +122,10 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"from typing import Annotated\n",
|
||||
"\n",
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"\n",
|
||||
"from langgraph.graph.message import add_messages\n",
|
||||
"\n",
|
||||
"# Add messages essentially does this with more\n",
|
||||
@@ -145,7 +147,7 @@
|
||||
"\n",
|
||||
"We will first define the tools we want to use.\n",
|
||||
"For this simple example, we will use create a placeholder search engine.\n",
|
||||
"However, it is really easy to create your own tools - see documentation [here](https://python.langchain.com/docs/modules/agents/tools/custom_tools) on how to do that.\n"
|
||||
"However, it is really easy to create your own tools - see documentation [here](https://python.langchain.com/v0.2/docs/how_to/custom_tools) on how to do that.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -200,7 +202,7 @@
|
||||
"For the design below, it must satisfy two criteria:\n",
|
||||
"\n",
|
||||
"1. It should work with **messages** (since our state contains a list of chat messages)\n",
|
||||
"2. It should work with [**tool calling**](https://python.langchain.com/v0.1/docs/modules/model_io/chat/function_calling/).\n",
|
||||
"2. It should work with [**tool calling**](https://python.langchain.com/v0.2/docs/concepts/#functiontool-calling).\n",
|
||||
"\n",
|
||||
"<div class=\"admonition tip\">\n",
|
||||
" <p class=\"admonition-title\">Note</p>\n",
|
||||
@@ -253,7 +255,7 @@
|
||||
"## Define the graph \n",
|
||||
"\n",
|
||||
"We now need to define a few different nodes in our graph.\n",
|
||||
"In `langgraph`, a node can be either a function or a [runnable](https://python.langchain.com/docs/expression_language/).\n",
|
||||
"In `langgraph`, a node can be either a function or a [runnable](https://python.langchain.com/v0.2/docs/concepts/#langchain-expression-language-lcel).\n",
|
||||
"There are two main nodes we need for this:\n",
|
||||
"\n",
|
||||
"1. The agent: responsible for deciding what (if any) actions to take.\n",
|
||||
@@ -315,7 +317,7 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.graph import StateGraph, END\n",
|
||||
"from langgraph.graph import StateGraph\n",
|
||||
"\n",
|
||||
"# Define a new graph\n",
|
||||
"workflow = StateGraph(State)\n",
|
||||
@@ -399,7 +401,7 @@
|
||||
"\n",
|
||||
"try:\n",
|
||||
" display(Image(app.get_graph().draw_mermaid_png()))\n",
|
||||
"except:\n",
|
||||
"except Exception:\n",
|
||||
" # This requires some extra dependencies and is optional\n",
|
||||
" pass"
|
||||
]
|
||||
@@ -566,7 +568,7 @@
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.2"
|
||||
"version": "3.12.2"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%%capture --no-stderr\n",
|
||||
"%pip install --quiet -U langchain-community langchain-openai tavily-python"
|
||||
"%pip install --quiet -U langgraph langchain-community langchain-openai tavily-python"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -65,8 +65,8 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"import getpass\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def _set_env(var: str):\n",
|
||||
@@ -105,7 +105,7 @@
|
||||
"source": [
|
||||
"## Define Tools\n",
|
||||
"\n",
|
||||
"We will first define the tools we want to use. For this simple example, we will use a built-in search tool via Tavily. However, it is really easy to create your own tools - see documentation [here](https://python.langchain.com/docs/modules/agents/tools/custom_tools) on how to do that."
|
||||
"We will first define the tools we want to use. For this simple example, we will use a built-in search tool via Tavily. However, it is really easy to create your own tools - see documentation [here](https://python.langchain.com/v0.2/docs/how_to/custom_tools) on how to do that."
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -154,6 +154,7 @@
|
||||
"source": [
|
||||
"from langchain import hub\n",
|
||||
"from langchain_openai import ChatOpenAI\n",
|
||||
"\n",
|
||||
"from langgraph.prebuilt import create_react_agent\n",
|
||||
"\n",
|
||||
"# Get the prompt to use - you can modify this!\n",
|
||||
@@ -212,8 +213,8 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing import List, Tuple, Annotated, TypedDict\n",
|
||||
"import operator\n",
|
||||
"from typing import Annotated, List, Tuple, TypedDict\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class PlanExecute(TypedDict):\n",
|
||||
|
||||
@@ -60,9 +60,10 @@
|
||||
"source": [
|
||||
"### LLMs\n",
|
||||
"import os\n",
|
||||
"os.environ['OPENAI_API_KEY'] = <your-api-key>\n",
|
||||
"os.environ['COHERE_API_KEY'] = <your-api-key>\n",
|
||||
"os.environ['TAVILY_API_KEY'] = <your-api-key>"
|
||||
"\n",
|
||||
"os.environ[\"OPENAI_API_KEY\"] = \"<your-api-key>\"\n",
|
||||
"os.environ[\"COHERE_API_KEY\"] = \"<your-api-key>\"\n",
|
||||
"os.environ[\"TAVILY_API_KEY\"] = \"<your-api-key>\""
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -83,9 +84,9 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"### Tracing (optional)\n",
|
||||
"os.environ['LANGCHAIN_TRACING_V2'] = 'true'\n",
|
||||
"os.environ['LANGCHAIN_ENDPOINT'] = 'https://api.smith.langchain.com'\n",
|
||||
"os.environ['LANGCHAIN_API_KEY'] = <your-api-key>"
|
||||
"os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n",
|
||||
"os.environ[\"LANGCHAIN_ENDPOINT\"] = \"https://api.smith.langchain.com\"\n",
|
||||
"os.environ[\"LANGCHAIN_API_KEY\"] = \"<your-api-key>\""
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -475,9 +476,10 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"from typing import List\n",
|
||||
"\n",
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class GraphState(TypedDict):\n",
|
||||
" \"\"\"\n",
|
||||
@@ -659,7 +661,7 @@
|
||||
" \"\"\"\n",
|
||||
"\n",
|
||||
" print(\"---ASSESS GRADED DOCUMENTS---\")\n",
|
||||
" question = state[\"question\"]\n",
|
||||
" state[\"question\"]\n",
|
||||
" filtered_documents = state[\"documents\"]\n",
|
||||
"\n",
|
||||
" if not filtered_documents:\n",
|
||||
|
||||
@@ -68,7 +68,8 @@
|
||||
"source": [
|
||||
"### LLMs\n",
|
||||
"import os\n",
|
||||
"os.environ['COHERE_API_KEY'] = <your-api-key>"
|
||||
"\n",
|
||||
"os.environ[\"COHERE_API_KEY\"] = \"<your-api-key>\""
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -83,7 +84,7 @@
|
||||
"# ### Tracing (optional)\n",
|
||||
"# os.environ['LANGCHAIN_TRACING_V2'] = 'true'\n",
|
||||
"# os.environ['LANGCHAIN_ENDPOINT'] = 'https://api.smith.langchain.com'\n",
|
||||
"# os.environ['LANGCHAIN_API_KEY'] = <your-api-key>"
|
||||
"# os.environ['LANGCHAIN_API_KEY'] ='<your-api-key>'"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -108,9 +109,9 @@
|
||||
"### Build Index\n",
|
||||
"\n",
|
||||
"from langchain.text_splitter import RecursiveCharacterTextSplitter\n",
|
||||
"from langchain_cohere import CohereEmbeddings\n",
|
||||
"from langchain_community.document_loaders import WebBaseLoader\n",
|
||||
"from langchain_community.vectorstores import Chroma\n",
|
||||
"from langchain_cohere import CohereEmbeddings\n",
|
||||
"\n",
|
||||
"# Set embeddings\n",
|
||||
"embd = CohereEmbeddings()\n",
|
||||
@@ -187,11 +188,10 @@
|
||||
],
|
||||
"source": [
|
||||
"### Router\n",
|
||||
"from typing import Literal\n",
|
||||
"\n",
|
||||
"from langchain_cohere import ChatCohere\n",
|
||||
"from langchain_core.prompts import ChatPromptTemplate\n",
|
||||
"from langchain_core.pydantic_v1 import BaseModel, Field\n",
|
||||
"from langchain_cohere import ChatCohere\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Data model\n",
|
||||
@@ -329,11 +329,8 @@
|
||||
"source": [
|
||||
"### Generate\n",
|
||||
"\n",
|
||||
"from langchain import hub\n",
|
||||
"from langchain_core.output_parsers import StrOutputParser\n",
|
||||
"import langchain\n",
|
||||
"from langchain_core.messages import HumanMessage\n",
|
||||
"\n",
|
||||
"from langchain_core.output_parsers import StrOutputParser\n",
|
||||
"\n",
|
||||
"# Preamble\n",
|
||||
"preamble = \"\"\"You are an assistant for question-answering tasks. Use the following pieces of retrieved context to answer the question. If you don't know the answer, just say that you don't know. Use three sentences maximum and keep the answer concise.\"\"\"\n",
|
||||
@@ -341,15 +338,18 @@
|
||||
"# LLM\n",
|
||||
"llm = ChatCohere(model_name=\"command-r\", temperature=0).bind(preamble=preamble)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Prompt\n",
|
||||
"prompt = lambda x: ChatPromptTemplate.from_messages(\n",
|
||||
" [\n",
|
||||
" HumanMessage(\n",
|
||||
" f\"Question: {x['question']} \\nAnswer: \",\n",
|
||||
" additional_kwargs={\"documents\": x[\"documents\"]},\n",
|
||||
" )\n",
|
||||
" ]\n",
|
||||
")\n",
|
||||
"def prompt(x):\n",
|
||||
" return ChatPromptTemplate.from_messages(\n",
|
||||
" [\n",
|
||||
" HumanMessage(\n",
|
||||
" f\"Question: {x['question']} \\nAnswer: \",\n",
|
||||
" additional_kwargs={\"documents\": x[\"documents\"]},\n",
|
||||
" )\n",
|
||||
" ]\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Chain\n",
|
||||
"rag_chain = prompt | llm | StrOutputParser()\n",
|
||||
@@ -376,11 +376,7 @@
|
||||
"source": [
|
||||
"### LLM fallback\n",
|
||||
"\n",
|
||||
"from langchain import hub\n",
|
||||
"from langchain_core.output_parsers import StrOutputParser\n",
|
||||
"import langchain\n",
|
||||
"from langchain_core.messages import HumanMessage\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Preamble\n",
|
||||
"preamble = \"\"\"You are an assistant for question-answering tasks. Answer the question based upon your knowledge. Use three sentences maximum and keep the answer concise.\"\"\"\n",
|
||||
@@ -388,10 +384,13 @@
|
||||
"# LLM\n",
|
||||
"llm = ChatCohere(model_name=\"command-r\", temperature=0).bind(preamble=preamble)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Prompt\n",
|
||||
"prompt = lambda x: ChatPromptTemplate.from_messages(\n",
|
||||
" [HumanMessage(f\"Question: {x['question']} \\nAnswer: \")]\n",
|
||||
")\n",
|
||||
"def prompt(x):\n",
|
||||
" return ChatPromptTemplate.from_messages(\n",
|
||||
" [HumanMessage(f\"Question: {x['question']} \\nAnswer: \")]\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Chain\n",
|
||||
"llm_chain = prompt | llm | StrOutputParser()\n",
|
||||
@@ -535,7 +534,7 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"### Search\n",
|
||||
"# os.environ['TAVILY_API_KEY'] = <your-api-key>\n",
|
||||
"# os.environ['TAVILY_API_KEY'] ='<your-api-key>'\n",
|
||||
"\n",
|
||||
"from langchain_community.tools.tavily_search import TavilySearchResults\n",
|
||||
"\n",
|
||||
@@ -565,9 +564,10 @@
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"from typing import List\n",
|
||||
"\n",
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class GraphState(TypedDict):\n",
|
||||
" \"\"\"|\n",
|
||||
@@ -764,7 +764,7 @@
|
||||
" \"\"\"\n",
|
||||
"\n",
|
||||
" print(\"---ASSESS GRADED DOCUMENTS---\")\n",
|
||||
" question = state[\"question\"]\n",
|
||||
" state[\"question\"]\n",
|
||||
" filtered_documents = state[\"documents\"]\n",
|
||||
"\n",
|
||||
" if not filtered_documents:\n",
|
||||
|
||||
@@ -45,7 +45,8 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"! pip install -U langchain-nomic langchain_community tiktoken langchainhub chromadb langchain langgraph tavily-python nomic[local]"
|
||||
"%capture --no-stderr\n",
|
||||
"%pip install -U langchain-nomic langchain_community tiktoken langchainhub chromadb langchain langgraph tavily-python nomic[local]"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -100,9 +101,11 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"os.environ['LANGCHAIN_TRACING_V2'] = 'true'\n",
|
||||
"os.environ['LANGCHAIN_ENDPOINT'] = 'https://api.smith.langchain.com'\n",
|
||||
"os.environ['LANGCHAIN_API_KEY'] = <your-api-key>"
|
||||
"import os\n",
|
||||
"\n",
|
||||
"os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n",
|
||||
"os.environ[\"LANGCHAIN_ENDPOINT\"] = \"https://api.smith.langchain.com\"\n",
|
||||
"os.environ[\"LANGCHAIN_API_KEY\"] = \"<your-api-key>\""
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -122,8 +125,8 @@
|
||||
"source": [
|
||||
"from langchain.text_splitter import RecursiveCharacterTextSplitter\n",
|
||||
"from langchain_community.document_loaders import WebBaseLoader\n",
|
||||
"from langchain_community.vectorstores import Chroma\n",
|
||||
"from langchain_nomic.embeddings import NomicEmbeddings\n",
|
||||
"from langchain_community.vectorstores import Chroma\n",
|
||||
"\n",
|
||||
"urls = [\n",
|
||||
" \"https://lilianweng.github.io/posts/2023-06-23-agent/\",\n",
|
||||
@@ -440,9 +443,10 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"from typing import List\n",
|
||||
"\n",
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class GraphState(TypedDict):\n",
|
||||
" \"\"\"\n",
|
||||
@@ -621,7 +625,7 @@
|
||||
" \"\"\"\n",
|
||||
"\n",
|
||||
" print(\"---ASSESS GRADED DOCUMENTS---\")\n",
|
||||
" question = state[\"question\"]\n",
|
||||
" state[\"question\"]\n",
|
||||
" filtered_documents = state[\"documents\"]\n",
|
||||
"\n",
|
||||
" if not filtered_documents:\n",
|
||||
|
||||
@@ -7,11 +7,11 @@
|
||||
"source": [
|
||||
"# LangGraph Retrieval Agent\n",
|
||||
"\n",
|
||||
"[Retrieval Agents](https://python.langchain.com/docs/use_cases/question_answering/conversational_retrieval_agents) are useful when we want to make decisions about whether to retrieve from an index.\n",
|
||||
"[Retrieval Agents](https://python.langchain.com/v0.2/docs/tutorials/qa_chat_history/#agents) are useful when we want to make decisions about whether to retrieve from an index.\n",
|
||||
"\n",
|
||||
"To implement a retrieval agent, we simple need to give an LLM access to a retriever tool.\n",
|
||||
"\n",
|
||||
"We can incorporate this into [LangGraph](https://python.langchain.com/docs/langgraph)."
|
||||
"We can incorporate this into [LangGraph](https://langchain-ai.github.io/langgraph/)."
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -32,8 +32,8 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"import getpass\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def _set_env(key: str):\n",
|
||||
@@ -116,11 +116,7 @@
|
||||
" \"Search and return information about Lilian Weng blog posts on LLM agents, prompt engineering, and adversarial attacks on LLMs.\",\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"tools = [retriever_tool]\n",
|
||||
"\n",
|
||||
"from langgraph.prebuilt import ToolExecutor\n",
|
||||
"\n",
|
||||
"tool_executor = ToolExecutor(tools)"
|
||||
"tools = [retriever_tool]"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -149,6 +145,7 @@
|
||||
"from typing import Annotated, Sequence, TypedDict\n",
|
||||
"\n",
|
||||
"from langchain_core.messages import BaseMessage\n",
|
||||
"\n",
|
||||
"from langgraph.graph.message import add_messages\n",
|
||||
"\n",
|
||||
"\n",
|
||||
@@ -204,11 +201,12 @@
|
||||
"\n",
|
||||
"from langchain import hub\n",
|
||||
"from langchain_core.messages import BaseMessage, HumanMessage\n",
|
||||
"from langchain_core.pydantic_v1 import BaseModel, Field\n",
|
||||
"from langchain_openai import ChatOpenAI\n",
|
||||
"from langgraph.prebuilt import tools_condition\n",
|
||||
"from langchain_core.output_parsers import StrOutputParser\n",
|
||||
"from langchain_core.prompts import PromptTemplate\n",
|
||||
"from langchain_core.pydantic_v1 import BaseModel, Field\n",
|
||||
"from langchain_openai import ChatOpenAI\n",
|
||||
"\n",
|
||||
"from langgraph.prebuilt import tools_condition\n",
|
||||
"\n",
|
||||
"### Edges\n",
|
||||
"\n",
|
||||
@@ -451,7 +449,7 @@
|
||||
"\n",
|
||||
"try:\n",
|
||||
" display(Image(graph.get_graph(xray=True).draw_mermaid_png()))\n",
|
||||
"except:\n",
|
||||
"except Exception:\n",
|
||||
" # This requires some extra dependencies and is optional\n",
|
||||
" pass"
|
||||
]
|
||||
@@ -529,7 +527,7 @@
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.2"
|
||||
"version": "3.12.2"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
|
||||
@@ -23,11 +23,11 @@
|
||||
"* If all documents fall below the relevance threshold or if the grader is unsure, then the framework seeks an additional datasource\n",
|
||||
"* It will use web search to supplement retrieval\n",
|
||||
" \n",
|
||||
"We will implement some of these ideas from scratch using [LangGraph](https://python.langchain.com/docs/langgraph):\n",
|
||||
"We will implement some of these ideas from scratch using [LangGraph](https://langchain-ai.github.io/langgraph/):\n",
|
||||
"\n",
|
||||
"* Let's skip the knowledge refinement phase as a first pass. This can be added back as a node, if desired. \n",
|
||||
"* If *any* documents are irrelevant, let's opt to supplement retrieval with web search. \n",
|
||||
"* We'll use [Tavily Search](https://python.langchain.com/docs/integrations/tools/tavily_search) for web search.\n",
|
||||
"* We'll use [Tavily Search](https://python.langchain.com/v0.2/docs/integrations/tools/tavily_search/) for web search.\n",
|
||||
"* Let's use query re-writing to optimize the query for web search.\n",
|
||||
"\n",
|
||||
""
|
||||
@@ -67,7 +67,8 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"os.environ['OPENAI_API_KEY'] = <your-api-key>"
|
||||
"\n",
|
||||
"os.environ[\"OPENAI_API_KEY\"] = \"<your-api-key>\""
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -87,7 +88,7 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"os.environ['TAVILY_API_KEY'] = <your-api-key>"
|
||||
"os.environ[\"TAVILY_API_KEY\"] = \"<your-api-key>\""
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -107,9 +108,9 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"os.environ['LANGCHAIN_TRACING_V2'] = 'true'\n",
|
||||
"os.environ['LANGCHAIN_ENDPOINT'] = 'https://api.smith.langchain.com'\n",
|
||||
"os.environ['LANGCHAIN_API_KEY'] = <your-api-key>"
|
||||
"os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n",
|
||||
"os.environ[\"LANGCHAIN_ENDPOINT\"] = \"https://api.smith.langchain.com\"\n",
|
||||
"os.environ[\"LANGCHAIN_API_KEY\"] = \"<your-api-key>\""
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -182,9 +183,9 @@
|
||||
"source": [
|
||||
"### Retrieval Grader\n",
|
||||
"\n",
|
||||
"from langchain_openai import ChatOpenAI\n",
|
||||
"from langchain_core.prompts import ChatPromptTemplate\n",
|
||||
"from langchain_core.pydantic_v1 import BaseModel, Field\n",
|
||||
"from langchain_openai import ChatOpenAI\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Data model\n",
|
||||
@@ -339,9 +340,10 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"from typing import List\n",
|
||||
"\n",
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class GraphState(TypedDict):\n",
|
||||
" \"\"\"\n",
|
||||
@@ -499,9 +501,9 @@
|
||||
" \"\"\"\n",
|
||||
"\n",
|
||||
" print(\"---ASSESS GRADED DOCUMENTS---\")\n",
|
||||
" question = state[\"question\"]\n",
|
||||
" state[\"question\"]\n",
|
||||
" web_search = state[\"web_search\"]\n",
|
||||
" filtered_documents = state[\"documents\"]\n",
|
||||
" state[\"documents\"]\n",
|
||||
"\n",
|
||||
" if web_search == \"Yes\":\n",
|
||||
" # All documents have been filtered check_relevance\n",
|
||||
|
||||
@@ -23,11 +23,11 @@
|
||||
"* If all documents fall below the relevance threshold or if the grader is unsure, then the framework seeks an additional datasource\n",
|
||||
"* It will use web search to supplement retrieval\n",
|
||||
" \n",
|
||||
"We will implement some of these ideas from scratch using [LangGraph](https://python.langchain.com/docs/langgraph):\n",
|
||||
"We will implement some of these ideas from scratch using [LangGraph](https://langchain-ai.github.io/langgraph/):\n",
|
||||
"\n",
|
||||
"* Let's skip the knowledge refinement phase as a first pass. This can be added back as a node, if desired. \n",
|
||||
"* If *any* documents are irrelevant, let's opt to supplement retrieval with web search. \n",
|
||||
"* We'll use [Tavily Search](https://python.langchain.com/docs/integrations/tools/tavily_search) for web search.\n",
|
||||
"* We'll use [Tavily Search](https://python.langchain.com/v0.2/docs/integrations/tools/tavily_search/) for web search.\n",
|
||||
"* Let's use query re-writing to optimize the query for web search.\n",
|
||||
"\n",
|
||||
"\n",
|
||||
@@ -99,7 +99,7 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# If using Mistral API\n",
|
||||
"mistral_api_key = <your-api-key>"
|
||||
"mistral_api_key = \"<your-api-key>\""
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -120,7 +120,8 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"os.environ['TAVILY_API_KEY'] = <your-api-key>"
|
||||
"\n",
|
||||
"os.environ[\"TAVILY_API_KEY\"] = \"<your-api-key>\""
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -140,9 +141,9 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"os.environ['LANGCHAIN_TRACING_V2'] = 'true'\n",
|
||||
"os.environ['LANGCHAIN_ENDPOINT'] = 'https://api.smith.langchain.com'\n",
|
||||
"os.environ['LANGCHAIN_API_KEY'] = <your-api-key>"
|
||||
"os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n",
|
||||
"os.environ[\"LANGCHAIN_ENDPOINT\"] = \"https://api.smith.langchain.com\"\n",
|
||||
"os.environ[\"LANGCHAIN_API_KEY\"] = \"<your-api-key>\""
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -183,10 +184,10 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain_community.document_loaders import WebBaseLoader\n",
|
||||
"from langchain_community.vectorstores import Chroma\n",
|
||||
"from langchain_nomic.embeddings import NomicEmbeddings\n",
|
||||
"from langchain.text_splitter import RecursiveCharacterTextSplitter\n",
|
||||
"from langchain_community.document_loaders import WebBaseLoader\n",
|
||||
"from langchain_nomic.embeddings import NomicEmbeddings\n",
|
||||
"from langchain_community.vectorstores import Chroma\n",
|
||||
"from langchain_mistralai import MistralAIEmbeddings\n",
|
||||
"\n",
|
||||
"# Load\n",
|
||||
@@ -244,8 +245,8 @@
|
||||
"\n",
|
||||
"from langchain.prompts import PromptTemplate\n",
|
||||
"from langchain_community.chat_models import ChatOllama\n",
|
||||
"from langchain_mistralai.chat_models import ChatMistralAI\n",
|
||||
"from langchain_core.output_parsers import JsonOutputParser\n",
|
||||
"from langchain_mistralai.chat_models import ChatMistralAI\n",
|
||||
"\n",
|
||||
"# LLM\n",
|
||||
"if run_local == \"Yes\":\n",
|
||||
@@ -399,9 +400,10 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"from typing import List\n",
|
||||
"\n",
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class GraphState(TypedDict):\n",
|
||||
" \"\"\"\n",
|
||||
@@ -559,9 +561,9 @@
|
||||
" \"\"\"\n",
|
||||
"\n",
|
||||
" print(\"---ASSESS GRADED DOCUMENTS---\")\n",
|
||||
" question = state[\"question\"]\n",
|
||||
" state[\"question\"]\n",
|
||||
" web_search = state[\"web_search\"]\n",
|
||||
" filtered_documents = state[\"documents\"]\n",
|
||||
" state[\"documents\"]\n",
|
||||
"\n",
|
||||
" if web_search == \"Yes\":\n",
|
||||
" # All documents have been filtered check_relevance\n",
|
||||
|
||||
@@ -187,7 +187,6 @@
|
||||
"source": [
|
||||
"### Generate\n",
|
||||
"\n",
|
||||
"from langchain import hub\n",
|
||||
"from langchain_core.output_parsers import StrOutputParser\n",
|
||||
"from langchain_core.prompts import PromptTemplate\n",
|
||||
"\n",
|
||||
@@ -350,7 +349,6 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"### Search\n",
|
||||
"\n",
|
||||
"from langchain_community.tools.tavily_search import TavilySearchResults\n",
|
||||
"\n",
|
||||
"web_search_tool = TavilySearchResults(k=3)"
|
||||
@@ -371,9 +369,13 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"from pprint import pprint\n",
|
||||
"from typing import List\n",
|
||||
"\n",
|
||||
"from langchain_core.documents import Document\n",
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"\n",
|
||||
"from langgraph.graph import END, StateGraph\n",
|
||||
"\n",
|
||||
"### State\n",
|
||||
"\n",
|
||||
@@ -539,9 +541,9 @@
|
||||
" \"\"\"\n",
|
||||
"\n",
|
||||
" print(\"---ASSESS GRADED DOCUMENTS---\")\n",
|
||||
" question = state[\"question\"]\n",
|
||||
" state[\"question\"]\n",
|
||||
" web_search = state[\"web_search\"]\n",
|
||||
" filtered_documents = state[\"documents\"]\n",
|
||||
" state[\"documents\"]\n",
|
||||
"\n",
|
||||
" if web_search == \"Yes\":\n",
|
||||
" # All documents have been filtered check_relevance\n",
|
||||
@@ -598,8 +600,6 @@
|
||||
" return \"not supported\"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"from langgraph.graph import END, StateGraph\n",
|
||||
"\n",
|
||||
"workflow = StateGraph(GraphState)\n",
|
||||
"\n",
|
||||
"# Define the nodes\n",
|
||||
@@ -704,7 +704,6 @@
|
||||
"app = workflow.compile()\n",
|
||||
"\n",
|
||||
"# Test\n",
|
||||
"from pprint import pprint\n",
|
||||
"\n",
|
||||
"inputs = {\"question\": \"What are the types of agent memory?\"}\n",
|
||||
"for output in app.stream(inputs):\n",
|
||||
@@ -752,12 +751,10 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# Compile\n",
|
||||
"app = workflow.compile()\n",
|
||||
"\n",
|
||||
"# Test\n",
|
||||
"from pprint import pprint\n",
|
||||
"\n",
|
||||
"# Compile\n",
|
||||
"app = workflow.compile()\n",
|
||||
"inputs = {\"question\": \"Who are the Bears expected to draft first in the NFL draft?\"}\n",
|
||||
"for output in app.stream(inputs):\n",
|
||||
" for key, value in output.items():\n",
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
"* `y (generation)` is a useful response to `x (question)`.\n",
|
||||
"* Output: `{5, 4, 3, 2, 1}`\n",
|
||||
"\n",
|
||||
"We will implement some of these ideas from scratch using [LangGraph](https://python.langchain.com/docs/langgraph).\n",
|
||||
"We will implement some of these ideas from scratch using [LangGraph](https://langchain-ai.github.io/langgraph/).\n",
|
||||
"\n",
|
||||
""
|
||||
]
|
||||
@@ -79,7 +79,8 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"os.environ['OPENAI_API_KEY'] = <your-api-key>"
|
||||
"\n",
|
||||
"os.environ[\"OPENAI_API_KEY\"] = \"<your-api-key>\""
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -99,9 +100,9 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"os.environ['LANGCHAIN_TRACING_V2'] = 'true'\n",
|
||||
"os.environ['LANGCHAIN_ENDPOINT'] = 'https://api.smith.langchain.com'\n",
|
||||
"os.environ['LANGCHAIN_API_KEY'] = <your-api-key>"
|
||||
"os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n",
|
||||
"os.environ[\"LANGCHAIN_ENDPOINT\"] = \"https://api.smith.langchain.com\"\n",
|
||||
"os.environ[\"LANGCHAIN_API_KEY\"] = \"<your-api-key>\""
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -182,7 +183,6 @@
|
||||
"source": [
|
||||
"### Retrieval Grader\n",
|
||||
"\n",
|
||||
"from typing import Literal\n",
|
||||
"\n",
|
||||
"from langchain_core.prompts import ChatPromptTemplate\n",
|
||||
"from langchain_core.pydantic_v1 import BaseModel, Field\n",
|
||||
@@ -416,9 +416,10 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"from typing import List\n",
|
||||
"\n",
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class GraphState(TypedDict):\n",
|
||||
" \"\"\"\n",
|
||||
@@ -444,8 +445,6 @@
|
||||
"source": [
|
||||
"### Nodes\n",
|
||||
"\n",
|
||||
"from langchain.schema import Document\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def retrieve(state):\n",
|
||||
" \"\"\"\n",
|
||||
@@ -550,7 +549,7 @@
|
||||
" \"\"\"\n",
|
||||
"\n",
|
||||
" print(\"---ASSESS GRADED DOCUMENTS---\")\n",
|
||||
" question = state[\"question\"]\n",
|
||||
" state[\"question\"]\n",
|
||||
" filtered_documents = state[\"documents\"]\n",
|
||||
"\n",
|
||||
" if not filtered_documents:\n",
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
"* `y (generation)` is a useful response to `x (question)`.\n",
|
||||
"* Output: `{5, 4, 3, 2, 1}`\n",
|
||||
"\n",
|
||||
"We will implement some of these ideas from scratch using [LangGraph](https://python.langchain.com/docs/langgraph).\n",
|
||||
"We will implement some of these ideas from scratch using [LangGraph](https://langchain-ai.github.io/langgraph/).\n",
|
||||
"\n",
|
||||
""
|
||||
]
|
||||
@@ -60,7 +60,8 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"! pip install -U langchain-nomic langchain_community tiktoken langchainhub chromadb langchain langgraph nomic[local]"
|
||||
"%capture --no-stderr\n",
|
||||
"%pip install -U langchain-nomic langchain_community tiktoken langchainhub chromadb langchain langgraph nomic[local]"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -115,9 +116,11 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"os.environ['LANGCHAIN_TRACING_V2'] = 'true'\n",
|
||||
"os.environ['LANGCHAIN_ENDPOINT'] = 'https://api.smith.langchain.com'\n",
|
||||
"os.environ['LANGCHAIN_API_KEY'] = <your-api-key>"
|
||||
"import os\n",
|
||||
"\n",
|
||||
"os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n",
|
||||
"os.environ[\"LANGCHAIN_ENDPOINT\"] = \"https://api.smith.langchain.com\"\n",
|
||||
"os.environ[\"LANGCHAIN_API_KEY\"] = \"<your-api-key>\""
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -139,8 +142,9 @@
|
||||
"source": [
|
||||
"from langchain.text_splitter import RecursiveCharacterTextSplitter\n",
|
||||
"from langchain_community.document_loaders import WebBaseLoader\n",
|
||||
"from langchain_community.vectorstores import Chroma\n",
|
||||
|
||||
"from langchain_nomic.embeddings import NomicEmbeddings\n",
|
||||
"from langchain_community.vectorstores import Chroma\n",
|
||||
"\n",
|
||||
"urls = [\n",
|
||||
" \"https://lilianweng.github.io/posts/2023-06-23-agent/\",\n",
|
||||
@@ -390,9 +394,10 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"from typing import List\n",
|
||||
"\n",
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class GraphState(TypedDict):\n",
|
||||
" \"\"\"\n",
|
||||
@@ -418,8 +423,6 @@
|
||||
"source": [
|
||||
"### Nodes\n",
|
||||
"\n",
|
||||
"from langchain.schema import Document\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def retrieve(state):\n",
|
||||
" \"\"\"\n",
|
||||
@@ -524,7 +527,7 @@
|
||||
" \"\"\"\n",
|
||||
"\n",
|
||||
" print(\"---ASSESS GRADED DOCUMENTS---\")\n",
|
||||
" question = state[\"question\"]\n",
|
||||
" state[\"question\"]\n",
|
||||
" filtered_documents = state[\"documents\"]\n",
|
||||
"\n",
|
||||
" if not filtered_documents:\n",
|
||||
|
||||
@@ -231,7 +231,6 @@
|
||||
"\n",
|
||||
"from langchain import hub\n",
|
||||
"from langchain_core.output_parsers import StrOutputParser\n",
|
||||
"from langchain_core.runnables import RunnablePassthrough\n",
|
||||
"\n",
|
||||
"# Prompt\n",
|
||||
"prompt = hub.pull(\"rlm/rag-prompt\")\n",
|
||||
@@ -404,9 +403,10 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"from typing import List\n",
|
||||
"\n",
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class GraphState(TypedDict):\n",
|
||||
" \"\"\"\n",
|
||||
@@ -543,7 +543,7 @@
|
||||
" \"\"\"\n",
|
||||
"\n",
|
||||
" print(\"---ASSESS GRADED DOCUMENTS---\")\n",
|
||||
" question = state[\"question\"]\n",
|
||||
" state[\"question\"]\n",
|
||||
" filtered_documents = state[\"documents\"]\n",
|
||||
"\n",
|
||||
" if not filtered_documents:\n",
|
||||
|
||||
@@ -133,10 +133,10 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n",
|
||||
"from langchain_core.pydantic_v1 import BaseModel, Field, ValidationError\n",
|
||||
"from langchain_core.messages import HumanMessage, ToolMessage\n",
|
||||
"from langchain_core.output_parsers.openai_tools import PydanticToolsParser\n",
|
||||
"from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n",
|
||||
"from langchain_core.pydantic_v1 import BaseModel, Field, ValidationError\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class Reflection(BaseModel):\n",
|
||||
@@ -346,6 +346,7 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain_core.tools import StructuredTool\n",
|
||||
"\n",
|
||||
"from langgraph.prebuilt import ToolNode\n",
|
||||
"\n",
|
||||
"\n",
|
||||
@@ -381,8 +382,8 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing import Literal\n",
|
||||
"from langgraph.graph import END, MessageGraph\n",
|
||||
"\n",
|
||||
"from langgraph.graph import END, MessageGraph\n",
|
||||
"\n",
|
||||
"MAX_ITERATIONS = 5\n",
|
||||
"builder = MessageGraph()\n",
|
||||
@@ -444,7 +445,7 @@
|
||||
"\n",
|
||||
"try:\n",
|
||||
" display(Image(graph.get_graph().draw_mermaid_png()))\n",
|
||||
"except:\n",
|
||||
"except Exception:\n",
|
||||
" # This requires some extra dependencies and is optional\n",
|
||||
" pass"
|
||||
]
|
||||
@@ -608,7 +609,7 @@
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.2"
|
||||
"version": "3.12.2"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
|
||||
@@ -50,8 +50,8 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"import getpass\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def _set_env(var: str):\n",
|
||||
@@ -106,8 +106,10 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"from typing import Annotated\n",
|
||||
"\n",
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"\n",
|
||||
"from langgraph.graph.message import add_messages\n",
|
||||
"\n",
|
||||
"# Add messages essentially does this with more\n",
|
||||
@@ -129,7 +131,7 @@
|
||||
"\n",
|
||||
"We will first define the tools we want to use.\n",
|
||||
"For this simple example, we will use create a placeholder search engine.\n",
|
||||
"It is really easy to create your own tools - see documentation [here](https://python.langchain.com/docs/modules/agents/tools/custom_tools) on how to do that.\n"
|
||||
"It is really easy to create your own tools - see documentation [here](https://python.langchain.com/v0.2/docs/how_to/custom_tools) on how to do that.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -262,8 +264,9 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing import TypedDict, Annotated, Sequence\n",
|
||||
"import operator\n",
|
||||
"from typing import Annotated, Sequence, TypedDict\n",
|
||||
"\n",
|
||||
"from langchain_core.messages import BaseMessage\n",
|
||||
"\n",
|
||||
"\n",
|
||||
@@ -279,7 +282,7 @@
|
||||
"## Define the nodes\n",
|
||||
"\n",
|
||||
"We now need to define a few different nodes in our graph.\n",
|
||||
"In `langgraph`, a node can be either a function or a [runnable](https://python.langchain.com/docs/expression_language/).\n",
|
||||
"In `langgraph`, a node can be either a function or a [runnable](https://python.langchain.com/v0.2/docs/concepts/#langchain-expression-language-lcel).\n",
|
||||
"There are two main nodes we need for this:\n",
|
||||
"\n",
|
||||
"1. The agent: responsible for deciding what (if any) actions to take.\n",
|
||||
@@ -414,7 +417,7 @@
|
||||
"## Use it!\n",
|
||||
"\n",
|
||||
"We can now use it!\n",
|
||||
"This now exposes the [same interface](https://python.langchain.com/docs/expression_language/) as all other LangChain runnables."
|
||||
"This now exposes the [same interface](https://python.langchain.com/v0.2/docs/concepts/#langchain-expression-language-lcel) as all other LangChain runnables."
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -497,7 +500,7 @@
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.1"
|
||||
"version": "3.12.2"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"\n",
|
||||
"## 0. Prerequisites\n",
|
||||
"\n",
|
||||
"For this example, we will provide the agent with a Tavily search engine tool. You can get an API key [here](https://app.tavily.com/sign-in) or replace with a free tool option (e.g., [duck duck go search](https://python.langchain.com/docs/integrations/tools/ddg)).\n",
|
||||
"For this example, we will provide the agent with a Tavily search engine tool. You can get an API key [here](https://app.tavily.com/sign-in) or replace with a free tool option (e.g., [duck duck go search](https://python.langchain.com/v0.2/docs/integrations/tools/ddg/)).\n",
|
||||
"\n",
|
||||
"To see the full langsmith trace, you can s"
|
||||
]
|
||||
@@ -58,8 +58,8 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"import getpass\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def _set_if_undefined(var: str):\n",
|
||||
@@ -91,7 +91,7 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing import TypedDict, List\n",
|
||||
"from typing import List, TypedDict\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class ReWOO(TypedDict):\n",
|
||||
@@ -233,6 +233,7 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import re\n",
|
||||
"\n",
|
||||
"from langchain_core.prompts import ChatPromptTemplate\n",
|
||||
"\n",
|
||||
"# Regex to match expressions of the form E#... = ...[...]\n",
|
||||
@@ -383,7 +384,7 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.graph import StateGraph, END\n",
|
||||
"from langgraph.graph import END, StateGraph\n",
|
||||
"\n",
|
||||
"graph = StateGraph(ReWOO)\n",
|
||||
"graph.add_node(\"plan\", get_plan)\n",
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -52,8 +52,8 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"import getpass\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def _set_env(var: str):\n",
|
||||
@@ -92,7 +92,7 @@
|
||||
"\n",
|
||||
"We will first define the tools we want to use.\n",
|
||||
"For this simple example, we will use create a placeholder search engine.\n",
|
||||
"However, it is really easy to create your own tools - see documentation [here](https://python.langchain.com/docs/modules/agents/tools/custom_tools) on how to do that.\n"
|
||||
"However, it is really easy to create your own tools - see documentation [here](https://python.langchain.com/v0.2/docs/how_to/custom_tools) on how to do that.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -212,8 +212,9 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing import Annotated, Sequence\n",
|
||||
"import operator\n",
|
||||
"from typing import Annotated, Sequence\n",
|
||||
"\n",
|
||||
"from langchain_core.messages import BaseMessage\n",
|
||||
"from langchain_core.pydantic_v1 import BaseModel\n",
|
||||
"\n",
|
||||
@@ -230,7 +231,7 @@
|
||||
"## Define the nodes\n",
|
||||
"\n",
|
||||
"We now need to define a few different nodes in our graph.\n",
|
||||
"In `langgraph`, a node can be either a function or a [runnable](https://python.langchain.com/docs/expression_language/).\n",
|
||||
"In `langgraph`, a node can be either a function or a [runnable](https://python.langchain.com/v0.2/docs/concepts/#langchain-expression-language-lcel).\n",
|
||||
"There are two main nodes we need for this:\n",
|
||||
"\n",
|
||||
"1. The agent: responsible for deciding what (if any) actions to take.\n",
|
||||
@@ -260,9 +261,10 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.prebuilt import ToolInvocation\n",
|
||||
"from langchain_core.messages import ToolMessage\n",
|
||||
"\n",
|
||||
"from langgraph.prebuilt import ToolInvocation\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define the function that determines whether to continue or not\n",
|
||||
"def should_continue(state):\n",
|
||||
@@ -323,7 +325,7 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.graph import StateGraph, END\n",
|
||||
"from langgraph.graph import END, StateGraph\n",
|
||||
"\n",
|
||||
"# Define a new graph\n",
|
||||
"workflow = StateGraph(AgentState)\n",
|
||||
@@ -398,7 +400,7 @@
|
||||
"## Use it!\n",
|
||||
"\n",
|
||||
"We can now use it!\n",
|
||||
"This now exposes the [same interface](https://python.langchain.com/docs/expression_language/) as all other LangChain runnables."
|
||||
"This now exposes the [same interface](https://python.langchain.com/v0.2/docs/concepts/#langchain-expression-language-lcel) as all other LangChain runnables."
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
+18
-16
@@ -48,9 +48,10 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# %pip install -U langchain_community langchain_openai langgraph wikipedia scikit-learn langchain_fireworks\n",
|
||||
"%%capture --no-stderr\n",
|
||||
"%pip install -U langchain_community langchain_openai langgraph wikipedia scikit-learn langchain_fireworks\n",
|
||||
"# We use one or the other search engine below\n",
|
||||
"# %pip install -U duckduckgo tavily-python"
|
||||
"%pip install -U duckduckgo tavily-python"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -71,8 +72,8 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"import getpass\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def _set_env(var: str):\n",
|
||||
@@ -104,7 +105,6 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain_openai import ChatOpenAI\n",
|
||||
"from langchain_fireworks import ChatFireworks\n",
|
||||
"\n",
|
||||
"fast_llm = ChatOpenAI(model=\"gpt-3.5-turbo\")\n",
|
||||
"# Uncomment for a Fireworks model\n",
|
||||
@@ -137,9 +137,10 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from langchain_core.pydantic_v1 import BaseModel, Field\n",
|
||||
"from typing import List, Optional\n",
|
||||
"\n",
|
||||
"from langchain_core.prompts import ChatPromptTemplate\n",
|
||||
"from langchain_core.pydantic_v1 import BaseModel, Field\n",
|
||||
"\n",
|
||||
"direct_gen_outline_prompt = ChatPromptTemplate.from_messages(\n",
|
||||
" [\n",
|
||||
@@ -360,7 +361,8 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain_community.retrievers import WikipediaRetriever\n",
|
||||
"from langchain_core.runnables import RunnableLambda, chain as as_runnable\n",
|
||||
"from langchain_core.runnables import RunnableLambda\n",
|
||||
"from langchain_core.runnables import chain as as_runnable\n",
|
||||
"\n",
|
||||
"wikipedia_retriever = WikipediaRetriever(load_all_available_meta=True, top_k_results=1)\n",
|
||||
"\n",
|
||||
@@ -455,10 +457,12 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.graph import StateGraph, END\n",
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"from typing import Annotated\n",
|
||||
"\n",
|
||||
"from langchain_core.messages import AnyMessage\n",
|
||||
"from typing import Annotated, Sequence\n",
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"\n",
|
||||
"from langgraph.graph import END, StateGraph\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def add_messages(left, right):\n",
|
||||
@@ -504,9 +508,8 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain_core.messages import AIMessage, HumanMessage, ToolMessage\n",
|
||||
"from langchain_core.prompts import MessagesPlaceholder\n",
|
||||
"from langchain_core.messages import AIMessage, BaseMessage, HumanMessage, ToolMessage\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"gen_qn_prompt = ChatPromptTemplate.from_messages(\n",
|
||||
" [\n",
|
||||
@@ -692,7 +695,6 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain_community.tools.tavily_search import TavilySearchResults\n",
|
||||
"from langchain_community.utilities.duckduckgo_search import DuckDuckGoSearchAPIWrapper\n",
|
||||
"from langchain_core.tools import tool\n",
|
||||
"\n",
|
||||
@@ -724,9 +726,10 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain_core.runnables import RunnableConfig\n",
|
||||
"import json\n",
|
||||
"\n",
|
||||
"from langchain_core.runnables import RunnableConfig\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"async def gen_answer(\n",
|
||||
" state: InterviewState,\n",
|
||||
@@ -1047,9 +1050,8 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain_core.documents import Document\n",
|
||||
"\n",
|
||||
"from langchain_community.vectorstores import SKLearnVectorStore\n",
|
||||
"from langchain_core.documents import Document\n",
|
||||
"from langchain_openai import OpenAIEmbeddings\n",
|
||||
"\n",
|
||||
"embeddings = OpenAIEmbeddings(model=\"text-embedding-3-small\")\n",
|
||||
@@ -1565,7 +1567,7 @@
|
||||
" {\n",
|
||||
" \"topic\": \"Groq, NVIDIA, Llamma.cpp and the future of LLM Inference\",\n",
|
||||
" },\n",
|
||||
" config\n",
|
||||
" config,\n",
|
||||
"):\n",
|
||||
" name = next(iter(step))\n",
|
||||
" print(name)\n",
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
"<div class=\"admonition tip\">\n",
|
||||
" <p class=\"admonition-title\">Note</p>\n",
|
||||
" <p>\n",
|
||||
" In this how-to, we will create our agent from scratch to be transparent (but verbose). You can accomplish similar functionality using the <code>create_react_agent(model, tools=tool)</code> (<a href=\"https://langchain-ai.github.io/langgraph/reference/prebuilt/#create_react_agent\">API doc</a>) constructor. This may be more appropriate if you are used to LangChain’s <a href=\"https://python.langchain.com/v0.1/docs/modules/agents/concepts/#agentexecutor\">AgentExecutor</a> class.\n",
|
||||
" In this how-to, we will create our agent from scratch to be transparent (but verbose). You can accomplish similar functionality using the <code>create_react_agent(model, tools=tool)</code> (<a href=\"https://langchain-ai.github.io/langgraph/reference/prebuilt/#create_react_agent\">API doc</a>) constructor. This may be more appropriate if you are used to LangChain’s <a href=\"https://python.langchain.com/v0.2/docs/how_to/agent_executor/#concepts\">AgentExecutor</a> class.\n",
|
||||
" </p>\n",
|
||||
"</div> \n",
|
||||
"\n",
|
||||
@@ -63,8 +63,8 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"import getpass\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def _set_env(var: str):\n",
|
||||
@@ -119,8 +119,10 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"from typing import Annotated\n",
|
||||
"\n",
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"\n",
|
||||
"from langgraph.graph.message import add_messages\n",
|
||||
"\n",
|
||||
"# Add messages essentially does this with more\n",
|
||||
@@ -142,7 +144,7 @@
|
||||
"\n",
|
||||
"We will first define the tools we want to use.\n",
|
||||
"For this simple example, we will use create a placeholder search engine.\n",
|
||||
"It is really easy to create your own tools - see documentation [here](https://python.langchain.com/docs/modules/agents/tools/custom_tools) on how to do that.\n"
|
||||
"It is really easy to create your own tools - see documentation [here](https://python.langchain.com/v0.2/docs/how_to/custom_tools) on how to do that.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -242,7 +244,7 @@
|
||||
"## Define the nodes\n",
|
||||
"\n",
|
||||
"We now need to define a few different nodes in our graph.\n",
|
||||
"In `langgraph`, a node can be either a function or a [runnable](https://python.langchain.com/docs/expression_language/).\n",
|
||||
"In `langgraph`, a node can be either a function or a [runnable](https://python.langchain.com/v0.2/docs/concepts/#langchain-expression-language-lcel).\n",
|
||||
"There are two main nodes we need for this:\n",
|
||||
"\n",
|
||||
"1. The agent: responsible for deciding what (if any) actions to take.\n",
|
||||
@@ -278,10 +280,12 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.graph import StateGraph, END, START\n",
|
||||
"from langchain_core.runnables import RunnableConfig\n",
|
||||
"from typing import Literal\n",
|
||||
"\n",
|
||||
"from langchain_core.runnables import RunnableConfig\n",
|
||||
"\n",
|
||||
"from langgraph.graph import END, START, StateGraph\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define the function that determines whether to continue or not\n",
|
||||
"def should_continue(state: State) -> Literal[\"__end__\", \"tools\"]:\n",
|
||||
@@ -370,7 +374,6 @@
|
||||
"source": [
|
||||
"from IPython.display import Image, display\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"display(Image(app.get_graph().draw_mermaid_png()))"
|
||||
]
|
||||
},
|
||||
@@ -458,18 +461,31 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.graph import StateGraph, END, START\n",
|
||||
"from langchain_core.runnables import RunnableGenerator\n",
|
||||
"from langchain_core.messages import AIMessage\n",
|
||||
"from langchain_core.runnables import RunnableGenerator\n",
|
||||
"\n",
|
||||
"from langgraph.graph import START, StateGraph\n",
|
||||
"\n",
|
||||
"# Define a new graph\n",
|
||||
"workflow = StateGraph(State)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"async def my_generator(state: State):\n",
|
||||
" messages = [\"Four\", \"score\", \"and\", \"seven\", \"years\", \"ago\", \"our\", \"fathers\", \"...\"]\n",
|
||||
" messages = [\n",
|
||||
" \"Four\",\n",
|
||||
" \"score\",\n",
|
||||
" \"and\",\n",
|
||||
" \"seven\",\n",
|
||||
" \"years\",\n",
|
||||
" \"ago\",\n",
|
||||
" \"our\",\n",
|
||||
" \"fathers\",\n",
|
||||
" \"...\",\n",
|
||||
" ]\n",
|
||||
" for message in messages:\n",
|
||||
" yield message\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"async def my_node(state: State, config: RunnableConfig):\n",
|
||||
" messages = []\n",
|
||||
" # Tagging a node makes it easy to filter out which events to include in your stream\n",
|
||||
|
||||
@@ -40,8 +40,8 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"import getpass\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def _set_env(var: str):\n",
|
||||
@@ -70,10 +70,12 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"from langgraph.graph import StateGraph\n",
|
||||
"from typing import Annotated\n",
|
||||
"\n",
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"\n",
|
||||
"from langgraph.graph import StateGraph\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def reduce_list(left: list | None, right: list | None) -> list:\n",
|
||||
" if not left:\n",
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
"<div class=\"admonition tip\">\n",
|
||||
" <p class=\"admonition-title\">Note:</p>\n",
|
||||
" <p>\n",
|
||||
" In this how-to, we will create our agent from scratch to be transparent (but verbose). You can accomplish similar functionality using the <code>create_react_agent(model, tools=tool, checkpointer=checkpointer)</code> (<a href=\"https://langchain-ai.github.io/langgraph/reference/prebuilt/#create_react_agent\">API doc</a>) constructor. This may be more appropriate if you are used to LangChain’s <a href=\"https://python.langchain.com/v0.1/docs/modules/agents/concepts/#agentexecutor\">AgentExecutor</a> class.\n",
|
||||
" In this how-to, we will create our agent from scratch to be transparent (but verbose). You can accomplish similar functionality using the <code>create_react_agent(model, tools=tool, checkpointer=checkpointer)</code> (<a href=\"https://langchain-ai.github.io/langgraph/reference/prebuilt/#create_react_agent\">API doc</a>) constructor. This may be more appropriate if you are used to LangChain’s <a href=\"https://python.langchain.com/v0.2/docs/how_to/agent_executor/#concepts\">AgentExecutor</a> class.\n",
|
||||
" </p>\n",
|
||||
"</div> "
|
||||
]
|
||||
@@ -66,8 +66,8 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"import getpass\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def _set_env(var: str):\n",
|
||||
@@ -114,8 +114,10 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"from typing import Annotated\n",
|
||||
"\n",
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"\n",
|
||||
"from langgraph.graph.message import add_messages\n",
|
||||
"\n",
|
||||
"# `add_messages`` essentially does this\n",
|
||||
@@ -137,7 +139,7 @@
|
||||
"\n",
|
||||
"We will first define the tools we want to use.\n",
|
||||
"For this simple example, we will use create a placeholder search engine.\n",
|
||||
"However, it is really easy to create your own tools - see documentation [here](https://python.langchain.com/docs/modules/agents/tools/custom_tools) on how to do that.\n"
|
||||
"However, it is really easy to create your own tools - see documentation [here](https://python.langchain.com/v0.2/docs/how_to/custom_tools) on how to do that.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -237,7 +239,7 @@
|
||||
"## Define the nodes\n",
|
||||
"\n",
|
||||
"We now need to define a few different nodes in our graph.\n",
|
||||
"In `langgraph`, a node can be either a function or a [runnable](https://python.langchain.com/docs/expression_language/).\n",
|
||||
"In `langgraph`, a node can be either a function or a [runnable](https://python.langchain.com/v0.2/docs/concepts/#langchain-expression-language-lcel).\n",
|
||||
"There are two main nodes we need for this:\n",
|
||||
"\n",
|
||||
"1. The agent: responsible for deciding what (if any) actions to take.\n",
|
||||
@@ -294,7 +296,7 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.graph import StateGraph, END\n",
|
||||
"from langgraph.graph import END, StateGraph\n",
|
||||
"\n",
|
||||
"# Define a new graph\n",
|
||||
"workflow = StateGraph(State)\n",
|
||||
@@ -403,7 +405,7 @@
|
||||
"\n",
|
||||
"try:\n",
|
||||
" display(Image(app.get_graph().draw_mermaid_png()))\n",
|
||||
"except:\n",
|
||||
"except Exception:\n",
|
||||
" # This requires some extra dependencies and is optional\n",
|
||||
" pass"
|
||||
]
|
||||
@@ -1112,7 +1114,7 @@
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.2"
|
||||
"version": "3.12.2"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 501 KiB After Width: | Height: | Size: 501 KiB |
@@ -7,7 +7,7 @@
|
||||
"source": [
|
||||
"# TNT-LLM: Text Mining at Scale\n",
|
||||
"\n",
|
||||
"[TNT-LLM](https://arxiv.org/abs/2403.12173) by Wan, et. al describes a taxonomy generation and classification system developed by Microsoft for their Bing Copilot application.\n",
|
||||
"[TNT-LLM](https://arxiv.org/abs/2403.12173) by Wan, et. al describes a taxonomy generation and classification system developed by Microsoft for their Bing Copilot application.\n",
|
||||
"\n",
|
||||
"It generates a rich, interpretable taxonomy of user intents (or other categories) from raw conversation logs. This taxonomy can then be used downstream by LLMs to label logs, which in turn can be used as training data to adapt a cheap classifier (such as logistic regression classifier on embeddings) that can be deployed in your app.\n",
|
||||
"\n",
|
||||
@@ -17,7 +17,6 @@
|
||||
"2. Label Training Data\n",
|
||||
"3. Finetune classifier + deploy\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"When applying LangGraph in this notebook, we will focus on the first phase: taxonomy generation (blue in the diagram below). We then show how to label and fit the classifier in subsequent steps below.\n",
|
||||
"\n",
|
||||
"\n",
|
||||
@@ -30,8 +29,7 @@
|
||||
"4. **Update** the taxonomy on each subsequent minibatch via a ritique and revise prompt\n",
|
||||
"5. **Review** the final taxonomy, scoring its quality and generating a final value using a final sample.\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"## Prerequisites"
|
||||
"## Prerequisites\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -77,7 +75,7 @@
|
||||
"\n",
|
||||
"Since each node of a StateGraph accepts the state (and returns an updated state), we'll define that at the outset.\n",
|
||||
"\n",
|
||||
"Our flow takes in a list of documents, batches them, and then generates and refines candidate taxonomies as interpretable \"clusters\"."
|
||||
"Our flow takes in a list of documents, batches them, and then generates and refines candidate taxonomies as interpretable \"clusters\".\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -119,7 +117,7 @@
|
||||
"source": [
|
||||
"#### 1. Summarize Docs\n",
|
||||
"\n",
|
||||
"Chat logs can get quite long. Our taxonomy generation step needs to see large, diverse minibatches to be able to adequately capture the distribution of categories. To ensure they can all fit efficiently into the context window, we first summarize each chat log. Downstream steps will use these summaries instead of the raw doc content."
|
||||
"Chat logs can get quite long. Our taxonomy generation step needs to see large, diverse minibatches to be able to adequately capture the distribution of categories. To ensure they can all fit efficiently into the context window, we first summarize each chat log. Downstream steps will use these summaries instead of the raw doc content.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -155,9 +153,7 @@
|
||||
"\n",
|
||||
"\n",
|
||||
"summary_llm_chain = (\n",
|
||||
" summary_prompt\n",
|
||||
" | ChatAnthropic(model=\"claude-3-haiku-20240307\")\n",
|
||||
" | StrOutputParser()\n",
|
||||
" summary_prompt | ChatAnthropic(model=\"claude-3-haiku-20240307\") | StrOutputParser()\n",
|
||||
" # Customize the tracing name for easier organization\n",
|
||||
").with_config(run_name=\"GenerateSummary\")\n",
|
||||
"summary_chain = summary_llm_chain | parse_summary\n",
|
||||
@@ -207,7 +203,7 @@
|
||||
"source": [
|
||||
"#### 2. Split into Minibatches\n",
|
||||
"\n",
|
||||
"Each minibatch contains a random sample of docs. This lets the flow identify inadequacies in the current taxonomy using new data."
|
||||
"Each minibatch contains a random sample of docs. This lets the flow identify inadequacies in the current taxonomy using new data.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -217,6 +213,9 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import random\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def get_minibatches(state: TaxonomyGenerationState, config: RunnableConfig):\n",
|
||||
" batch_size = config[\"configurable\"].get(\"batch_size\", 200)\n",
|
||||
" original = state[\"documents\"]\n",
|
||||
@@ -251,8 +250,7 @@
|
||||
"source": [
|
||||
"#### 3.a Taxonomy Generation Utilities\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"This section of the graph is a generate -> update 🔄 -> review cycle. Each node shares a LOT of logic, which we have factored out into the shared functions below."
|
||||
"This section of the graph is a generate -> update 🔄 -> review cycle. Each node shares a LOT of logic, which we have factored out into the shared functions below.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -262,7 +260,6 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import random\n",
|
||||
"from typing import Dict\n",
|
||||
"\n",
|
||||
"from langchain_core.runnables import Runnable\n",
|
||||
@@ -342,7 +339,7 @@
|
||||
"id": "2e2a2723-d350-4871-83e8-88f081ab4c8b",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"#### 3. Generate initial taxonomy"
|
||||
"#### 3. Generate initial taxonomy\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -387,7 +384,7 @@
|
||||
"source": [
|
||||
"#### 4. Update Taxonomy\n",
|
||||
"\n",
|
||||
"This is a \"critique -> revise\" step that is repeated N times."
|
||||
"This is a \"critique -> revise\" step that is repeated N times.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -423,7 +420,7 @@
|
||||
"source": [
|
||||
"#### 5. Review Taxonomy\n",
|
||||
"\n",
|
||||
"This runs once we've processed all the minibatches."
|
||||
"This runs once we've processed all the minibatches.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -462,7 +459,7 @@
|
||||
"source": [
|
||||
"## Define the Graph\n",
|
||||
"\n",
|
||||
"With all the functionality defined, we can define the graph!"
|
||||
"With all the functionality defined, we can define the graph!\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -537,11 +534,11 @@
|
||||
"source": [
|
||||
"## Usage\n",
|
||||
"\n",
|
||||
"The docs can contain __any__ content, but we've found it works really well on chat bot logs, such as those captured by [LangSmith](https://smith.langchain.com).\n",
|
||||
"The docs can contain **any** content, but we've found it works really well on chat bot logs, such as those captured by [LangSmith](https://smith.langchain.com).\n",
|
||||
"\n",
|
||||
"We will use that as an example below. Update the `project_name` to your own LangSmith project.\n",
|
||||
"\n",
|
||||
"You will likely have to customize the `run_to_doc` function below, since your expected keys may differ from those of this notebook's author."
|
||||
"You will likely have to customize the `run_to_doc` function below, since your expected keys may differ from those of this notebook's author.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -551,7 +548,6 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import random\n",
|
||||
"from datetime import datetime, timedelta\n",
|
||||
"\n",
|
||||
"from langsmith import Client\n",
|
||||
@@ -604,7 +600,7 @@
|
||||
"source": [
|
||||
"#### Invoke\n",
|
||||
"\n",
|
||||
"Now convert the runs to docs and kick off your graph flow. This will take some time! The summary step takes the longest. If you want to speed things up, you could try splitting the load across model providers."
|
||||
"Now convert the runs to docs and kick off your graph flow. This will take some time! The summary step takes the longest. If you want to speed things up, you could try splitting the load across model providers.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -614,11 +610,12 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Optional. If you are running into errors or rate limits and want to avoid repeated computation,\n",
|
||||
"# you can set this while debugging\n",
|
||||
"from langchain.cache import InMemoryCache\n",
|
||||
"from langchain.globals import set_llm_cache\n",
|
||||
"\n",
|
||||
"# Optional. If you are running into errors or rate limits and want to avoid repeated computation,\n",
|
||||
"# you can set this while debugging\n",
|
||||
"\n",
|
||||
"set_llm_cache(InMemoryCache())"
|
||||
]
|
||||
},
|
||||
@@ -638,31 +635,28 @@
|
||||
" \" that would benefit the user.\"\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"from langchain_core.tracers.context import tracing_v2_enabled\n",
|
||||
"\n",
|
||||
"with tracing_v2_enabled(client=Client(api_key=\"ls__eaec6db115fe4ad2af4fdf26fa553645\")):\n",
|
||||
" stream = app.stream(\n",
|
||||
" {\"documents\": docs},\n",
|
||||
" {\n",
|
||||
" \"configurable\": {\n",
|
||||
" \"use_case\": use_case,\n",
|
||||
" # Optional:\n",
|
||||
" \"batch_size\": 400,\n",
|
||||
" \"suggestion_length\": 30,\n",
|
||||
" \"cluster_name_length\": 10,\n",
|
||||
" \"cluster_description_length\": 30,\n",
|
||||
" \"explanation_length\": 20,\n",
|
||||
" \"max_num_clusters\": 25,\n",
|
||||
" },\n",
|
||||
" # We batch summarize the docs. To avoid getting errors, we will limit the\n",
|
||||
" # degree of parallelism to permit.\n",
|
||||
" \"max_concurrency\": 2,\n",
|
||||
"stream = app.stream(\n",
|
||||
" {\"documents\": docs},\n",
|
||||
" {\n",
|
||||
" \"configurable\": {\n",
|
||||
" \"use_case\": use_case,\n",
|
||||
" # Optional:\n",
|
||||
" \"batch_size\": 400,\n",
|
||||
" \"suggestion_length\": 30,\n",
|
||||
" \"cluster_name_length\": 10,\n",
|
||||
" \"cluster_description_length\": 30,\n",
|
||||
" \"explanation_length\": 20,\n",
|
||||
" \"max_num_clusters\": 25,\n",
|
||||
" },\n",
|
||||
" )\n",
|
||||
" # We batch summarize the docs. To avoid getting errors, we will limit the\n",
|
||||
" # degree of parallelism to permit.\n",
|
||||
" \"max_concurrency\": 2,\n",
|
||||
" },\n",
|
||||
")\n",
|
||||
"\n",
|
||||
" for step in stream:\n",
|
||||
" node, state = next(iter(step.items()))\n",
|
||||
" print(node, str(state)[:20] + \" ...\")"
|
||||
"for step in stream:\n",
|
||||
" node, state = next(iter(step.items()))\n",
|
||||
" print(node, str(state)[:20] + \" ...\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -672,7 +666,7 @@
|
||||
"source": [
|
||||
"## Final Result\n",
|
||||
"\n",
|
||||
"Below, render the final result as markdown:"
|
||||
"Below, render the final result as markdown:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -724,6 +718,9 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from IPython.display import Markdown\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def format_taxonomy_md(clusters):\n",
|
||||
" md = \"## Final Taxonomy\\n\\n\"\n",
|
||||
" md += \"| ID | Name | Description |\\n\"\n",
|
||||
@@ -743,8 +740,6 @@
|
||||
" return md\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"from IPython.display import Markdown\n",
|
||||
"\n",
|
||||
"Markdown(format_taxonomy_md(step[\"__end__\"][\"clusters\"][-1]))"
|
||||
]
|
||||
},
|
||||
@@ -761,13 +756,13 @@
|
||||
"\n",
|
||||
"The problem is that LLM-based tagging can be expensive.\n",
|
||||
"\n",
|
||||
"Embeddings can be ~100x cheaper to compute, and a simple logistic regression classifier on top of that would add negligible cost. \n",
|
||||
"Embeddings can be ~100x cheaper to compute, and a simple logistic regression classifier on top of that would add negligible cost.\n",
|
||||
"\n",
|
||||
"Let's tag and train a classifier!\n",
|
||||
"\n",
|
||||
"#### Label Training Data\n",
|
||||
"\n",
|
||||
"Use an LLM to label the data in a fully-automated fashion. For beter accuracy, you can sample a portion of the results to label by hand as well to verify the quality."
|
||||
"Use an LLM to label the data in a fully-automated fashion. For beter accuracy, you can sample a portion of the results to label by hand as well to verify the quality.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -860,7 +855,7 @@
|
||||
"source": [
|
||||
"#### Train Classifier\n",
|
||||
"\n",
|
||||
"Now that we've extracted the features from the text, we can generate the classifier on them."
|
||||
"Now that we've extracted the features from the text, we can generate the classifier on them.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -883,9 +878,8 @@
|
||||
"source": [
|
||||
"import numpy as np\n",
|
||||
"from sklearn.linear_model import LogisticRegression\n",
|
||||
"from sklearn.metrics import accuracy_score, f1_score, roc_auc_score\n",
|
||||
"from sklearn.metrics import accuracy_score, f1_score\n",
|
||||
"from sklearn.model_selection import train_test_split\n",
|
||||
"from sklearn.preprocessing import label_binarize\n",
|
||||
"from sklearn.utils import class_weight\n",
|
||||
"\n",
|
||||
"# Create a dictionary mapping category names to their indices in the taxonomy\n",
|
||||
@@ -934,7 +928,7 @@
|
||||
"source": [
|
||||
"## Phase 3: Deploy\n",
|
||||
"\n",
|
||||
"Now that you have your classifier, you can easily deploy it and apply to future runs! All you need is to embed the input and apply your LogisticRegression classifier. Let's try it. We will use python's [joblib](https://joblib.readthedocs.io/en/stable/) library to serialize our sklearn classifier. Below is an example:"
|
||||
"Now that you have your classifier, you can easily deploy it and apply to future runs! All you need is to embed the input and apply your LogisticRegression classifier. Let's try it. We will use python's [joblib](https://joblib.readthedocs.io/en/stable/) library to serialize our sklearn classifier. Below is an example:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -960,7 +954,7 @@
|
||||
"source": [
|
||||
"#### To deploy\n",
|
||||
"\n",
|
||||
"When deploying, you can load the classifier and initialize your embeddings encoder. They fit together easily using LCEL:"
|
||||
"When deploying, you can load the classifier and initialize your embeddings encoder. They fit together easily using LCEL:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -995,7 +989,7 @@
|
||||
"source": [
|
||||
"#### Example:\n",
|
||||
"\n",
|
||||
"Assuming you've had some more data come in, you can fetch it and apply it below"
|
||||
"Assuming you've had some more data come in, you can fetch it and apply it below\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1056,7 +1050,7 @@
|
||||
"\n",
|
||||
"Congrats on implementing TNT-LLM! While most folks use clustering-based approachs like LDA, k-means, etc. it can often be hard to really interpret what each cluster represents. TNT-LLM generates human-interpretable labels you can use downstream to monitor and improve your application.\n",
|
||||
"\n",
|
||||
"The technique also lends itself to hierarchical sub-categorizing: once you have the above taxonomy, use it to label your data, then on each sub-category, generate a new taxonomy using a similar technique to the one described above!"
|
||||
"The technique also lends itself to hierarchical sub-categorizing: once you have the above taxonomy, use it to label your data, then on each sub-category, generate a new taxonomy using a similar technique to the one described above!\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -168,7 +168,7 @@
|
||||
" except subprocess.TimeoutExpired:\n",
|
||||
" process.kill()\n",
|
||||
" q.put(\"timed out\")\n",
|
||||
" except Exception as e:\n",
|
||||
" except Exception:\n",
|
||||
" q.put(f\"failed: {traceback.format_exc()}\")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
@@ -266,7 +266,7 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing import Annotated, Optional\n",
|
||||
"from typing import Annotated\n",
|
||||
"\n",
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"\n",
|
||||
@@ -321,7 +321,7 @@
|
||||
"source": [
|
||||
"#### Node 1: Solver\n",
|
||||
"\n",
|
||||
"Create a `solver` node that prompts an LLM \"agent\" to use a [writePython tool](https://python.langchain.com/docs/integrations/chat/anthropic/#beta-tool-calling) to generate the submitted code."
|
||||
"Create a `solver` node that prompts an LLM \"agent\" to use a [writePython tool](https://python.langchain.com/v0.2/docs/integrations/chat/anthropic/#integration-details) to generate the submitted code."
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -483,7 +483,6 @@
|
||||
"\n",
|
||||
"def evaluate(state: State):\n",
|
||||
" test_cases = state[\"test_cases\"]\n",
|
||||
" runtime_limit = state[\"runtime_limit\"]\n",
|
||||
" ai_message: AIMessage = state[\"messages\"][-1]\n",
|
||||
" if not ai_message.tool_calls:\n",
|
||||
" return {\n",
|
||||
@@ -586,7 +585,7 @@
|
||||
"\n",
|
||||
"try:\n",
|
||||
" display(Image(graph.get_graph().draw_mermaid_png()))\n",
|
||||
"except:\n",
|
||||
"except Exception:\n",
|
||||
" # This requires some extra dependencies and is optional\n",
|
||||
" pass"
|
||||
]
|
||||
@@ -736,7 +735,6 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from langchain_core.messages import BaseMessage\n",
|
||||
"from langchain_core.tracers.context import tracing_v2_enabled\n",
|
||||
"from langsmith import Client\n",
|
||||
"\n",
|
||||
@@ -745,7 +743,7 @@
|
||||
"def _hide_test_cases(inputs):\n",
|
||||
" copied = inputs.copy()\n",
|
||||
" # These are tens of MB in size. No need to send them up\n",
|
||||
" copied[\"test_cases\"] = f\"...\"\n",
|
||||
" copied[\"test_cases\"] = \"...\"\n",
|
||||
" return copied\n",
|
||||
"\n",
|
||||
"\n",
|
||||
@@ -830,7 +828,7 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing import Annotated, Optional\n",
|
||||
"from typing import Annotated\n",
|
||||
"\n",
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"\n",
|
||||
@@ -1059,7 +1057,7 @@
|
||||
"\n",
|
||||
"try:\n",
|
||||
" display(Image(graph.get_graph().draw_mermaid_png()))\n",
|
||||
"except:\n",
|
||||
"except Exception:\n",
|
||||
" # This requires some extra dependencies and is optional\n",
|
||||
" pass"
|
||||
]
|
||||
@@ -1379,7 +1377,7 @@
|
||||
"\n",
|
||||
"try:\n",
|
||||
" display(Image(graph.get_graph().draw_mermaid_png()))\n",
|
||||
"except:\n",
|
||||
"except Exception:\n",
|
||||
" # This requires some extra dependencies and is optional\n",
|
||||
" pass"
|
||||
]
|
||||
@@ -1664,7 +1662,7 @@
|
||||
" \"messages\": [\n",
|
||||
" (\n",
|
||||
" \"user\",\n",
|
||||
" f\"\"\"Consider breaking down the algorithm into separate parts: reading inputs, detecting cycles using the tortoise and hare algorithm, and determining Bessie's final position by skipping ahead K steps.\n",
|
||||
" \"\"\"Consider breaking down the algorithm into separate parts: reading inputs, detecting cycles using the tortoise and hare algorithm, and determining Bessie's final position by skipping ahead K steps.\n",
|
||||
"\n",
|
||||
"Read the inputs into three arrays:\n",
|
||||
"- Two arrays L and R for the ports (adjust for 0-based indexing)\n",
|
||||
|
||||
@@ -39,10 +39,12 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import random\n",
|
||||
"from typing import Annotated, Literal\n",
|
||||
"\n",
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"\n",
|
||||
"from langgraph.graph import StateGraph\n",
|
||||
"from langgraph.graph.message import add_messages\n",
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"from typing import Annotated, Literal\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class State(TypedDict):\n",
|
||||
@@ -265,8 +267,8 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from langchain_core.runnables.graph import CurveStyle, NodeColors, MermaidDrawMethod\n",
|
||||
"from IPython.display import display, HTML, Image\n",
|
||||
"from IPython.display import Image, display\n",
|
||||
"from langchain_core.runnables.graph import CurveStyle, MermaidDrawMethod, NodeColors\n",
|
||||
"\n",
|
||||
"display(\n",
|
||||
" Image(\n",
|
||||
@@ -421,7 +423,7 @@
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.2"
|
||||
"version": "3.12.2"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
|
||||
@@ -29,24 +29,12 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"execution_count": null,
|
||||
"id": "af83b042",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"\u001b[31mERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.\n",
|
||||
"langchain-experimental 0.0.47 requires langchain<0.1,>=0.0.350, but you have langchain 0.1.4 which is incompatible.\u001b[0m\u001b[31m\n",
|
||||
"\u001b[0m\n",
|
||||
"\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip is available: \u001b[0m\u001b[31;49m23.3.1\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m24.0\u001b[0m\n",
|
||||
"\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49mpip install --upgrade pip\u001b[0m\n",
|
||||
"Note: you may need to restart the kernel to use updated packages.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%%capture --no-stderr\n",
|
||||
"%pip install -U --quiet langgraph langsmith langchain_openai"
|
||||
]
|
||||
},
|
||||
@@ -199,10 +187,10 @@
|
||||
" bbox_id = int(bbox_id)\n",
|
||||
" try:\n",
|
||||
" bbox = state[\"bboxes\"][bbox_id]\n",
|
||||
" except:\n",
|
||||
" except Exception:\n",
|
||||
" return f\"Error: no bbox for : {bbox_id}\"\n",
|
||||
" x, y = bbox[\"x\"], bbox[\"y\"]\n",
|
||||
" res = await page.mouse.click(x, y)\n",
|
||||
" await page.mouse.click(x, y)\n",
|
||||
" # TODO: In the paper, they automatically parse any downloaded PDFs\n",
|
||||
" # We could add something similar here as well and generally\n",
|
||||
" # improve response format.\n",
|
||||
@@ -308,7 +296,6 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import asyncio\n",
|
||||
"import base64\n",
|
||||
"\n",
|
||||
"from langchain_core.runnables import chain as chain_decorator\n",
|
||||
@@ -327,7 +314,7 @@
|
||||
" try:\n",
|
||||
" bboxes = await page.evaluate(\"markPage()\")\n",
|
||||
" break\n",
|
||||
" except:\n",
|
||||
" except Exception:\n",
|
||||
" # May be loading...\n",
|
||||
" asyncio.sleep(3)\n",
|
||||
" screenshot = await page.screenshot()\n",
|
||||
@@ -358,7 +345,6 @@
|
||||
"source": [
|
||||
"from langchain import hub\n",
|
||||
"from langchain_core.output_parsers import StrOutputParser\n",
|
||||
"from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n",
|
||||
"from langchain_core.runnables import RunnablePassthrough\n",
|
||||
"from langchain_openai import ChatOpenAI\n",
|
||||
"\n",
|
||||
@@ -470,6 +456,7 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain_core.runnables import RunnableLambda\n",
|
||||
"\n",
|
||||
"from langgraph.graph import END, StateGraph\n",
|
||||
"\n",
|
||||
"graph_builder = StateGraph(AgentState)\n",
|
||||
@@ -538,7 +525,6 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import playwright\n",
|
||||
"from IPython import display\n",
|
||||
"from playwright.async_api import async_playwright\n",
|
||||
"\n",
|
||||
|
||||
@@ -45,15 +45,16 @@ class AnyValue(Generic[Value], BaseChannel[Value, Value, Value]):
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
def update(self, values: Sequence[Value]) -> None:
|
||||
def update(self, values: Sequence[Value]) -> bool:
|
||||
if len(values) == 0:
|
||||
try:
|
||||
del self.value
|
||||
return True
|
||||
except AttributeError:
|
||||
pass
|
||||
return
|
||||
return False
|
||||
|
||||
self.value = values[-1]
|
||||
return True
|
||||
|
||||
def get(self) -> Value:
|
||||
try:
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from contextlib import asynccontextmanager, contextmanager
|
||||
from datetime import datetime, timezone
|
||||
from typing import (
|
||||
Any,
|
||||
AsyncGenerator,
|
||||
Generator,
|
||||
Generic,
|
||||
Mapping,
|
||||
Optional,
|
||||
Sequence,
|
||||
TypeVar,
|
||||
@@ -14,8 +12,6 @@ from typing import (
|
||||
|
||||
from typing_extensions import Self
|
||||
|
||||
from langgraph.checkpoint.base import Checkpoint
|
||||
from langgraph.checkpoint.id import uuid6
|
||||
from langgraph.errors import EmptyChannelError, InvalidUpdateError
|
||||
|
||||
Value = TypeVar("Value")
|
||||
@@ -62,12 +58,13 @@ class BaseChannel(Generic[Value, Update, C], ABC):
|
||||
# state methods
|
||||
|
||||
@abstractmethod
|
||||
def update(self, values: Sequence[Update]) -> None:
|
||||
def update(self, values: Sequence[Update]) -> bool:
|
||||
"""Update the channel's value with the given sequence of updates.
|
||||
The order of the updates in the sequence is arbitrary.
|
||||
This method is called by Pregel for all channels at the end of each step.
|
||||
If there are no updates, it is called with an empty sequence.
|
||||
Raises InvalidUpdateError if the sequence of updates is invalid."""
|
||||
Raises InvalidUpdateError if the sequence of updates is invalid.
|
||||
Returns True if the channel was updated, False otherwise."""
|
||||
|
||||
@abstractmethod
|
||||
def get(self) -> Value:
|
||||
@@ -75,76 +72,16 @@ class BaseChannel(Generic[Value, Update, C], ABC):
|
||||
|
||||
Raises EmptyChannelError if the channel is empty (never updated yet)."""
|
||||
|
||||
def consume(self) -> None:
|
||||
def consume(self) -> bool:
|
||||
"""Mark the current value of the channel as consumed. By default, no-op.
|
||||
This is called by Pregel before the start of the next step, for all
|
||||
channels that triggered a node.
|
||||
channels that triggered a node. If the channel was updated, return True.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
@contextmanager
|
||||
def ChannelsManager(
|
||||
channels: Mapping[str, BaseChannel],
|
||||
checkpoint: Checkpoint,
|
||||
) -> Generator[Mapping[str, BaseChannel], None, None]:
|
||||
"""Manage channels for the lifetime of a Pregel invocation (multiple steps)."""
|
||||
# TODO use https://docs.python.org/3/library/contextlib.html#contextlib.ExitStack
|
||||
empty = {
|
||||
k: v.from_checkpoint(checkpoint["channel_values"].get(k))
|
||||
for k, v in channels.items()
|
||||
}
|
||||
try:
|
||||
yield {k: v.__enter__() for k, v in empty.items()}
|
||||
finally:
|
||||
for v in empty.values():
|
||||
v.__exit__(None, None, None)
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def AsyncChannelsManager(
|
||||
channels: Mapping[str, BaseChannel],
|
||||
checkpoint: Checkpoint,
|
||||
) -> AsyncGenerator[Mapping[str, BaseChannel], None]:
|
||||
"""Manage channels for the lifetime of a Pregel invocation (multiple steps)."""
|
||||
empty = {
|
||||
k: v.afrom_checkpoint(checkpoint["channel_values"].get(k))
|
||||
for k, v in channels.items()
|
||||
}
|
||||
try:
|
||||
yield {k: await v.__aenter__() for k, v in empty.items()}
|
||||
finally:
|
||||
for v in empty.values():
|
||||
await v.__aexit__(None, None, None)
|
||||
|
||||
|
||||
def create_checkpoint(
|
||||
checkpoint: Checkpoint, channels: Mapping[str, BaseChannel], step: int
|
||||
) -> Checkpoint:
|
||||
"""Create a checkpoint for the given channels."""
|
||||
ts = datetime.now(timezone.utc).isoformat()
|
||||
values: dict[str, Any] = {}
|
||||
for k, v in channels.items():
|
||||
try:
|
||||
values[k] = v.checkpoint()
|
||||
except EmptyChannelError:
|
||||
pass
|
||||
return Checkpoint(
|
||||
v=1,
|
||||
ts=ts,
|
||||
id=str(uuid6(clock_seq=step)),
|
||||
channel_values=values,
|
||||
channel_versions=checkpoint["channel_versions"],
|
||||
versions_seen=checkpoint["versions_seen"],
|
||||
pending_sends=checkpoint.get("pending_sends", []),
|
||||
)
|
||||
return False
|
||||
|
||||
|
||||
__all__ = [
|
||||
"BaseChannel",
|
||||
"ChannelsManager",
|
||||
"AsyncChannelsManager",
|
||||
"create_checkpoint",
|
||||
"EmptyChannelError",
|
||||
"InvalidUpdateError",
|
||||
]
|
||||
|
||||
@@ -85,15 +85,15 @@ class BinaryOperatorAggregate(Generic[Value], BaseChannel[Value, Value, Value]):
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
def update(self, values: Sequence[Value]) -> None:
|
||||
def update(self, values: Sequence[Value]) -> bool:
|
||||
if not values:
|
||||
return
|
||||
return False
|
||||
if not hasattr(self, "value"):
|
||||
self.value = values[0]
|
||||
values = values[1:]
|
||||
|
||||
for value in values:
|
||||
self.value = self.operator(self.value, value)
|
||||
return True
|
||||
|
||||
def get(self) -> Value:
|
||||
try:
|
||||
|
||||
@@ -95,9 +95,10 @@ class Context(Generic[Value], BaseChannel[Value, None, None]):
|
||||
with self.from_checkpoint() as empty:
|
||||
yield empty
|
||||
|
||||
def update(self, values: Sequence[None]) -> None:
|
||||
def update(self, values: Sequence[None]) -> bool:
|
||||
if values:
|
||||
raise InvalidUpdateError()
|
||||
return False
|
||||
|
||||
def get(self) -> Value:
|
||||
try:
|
||||
|
||||
@@ -59,27 +59,34 @@ class DynamicBarrierValue(
|
||||
finally:
|
||||
pass
|
||||
|
||||
def update(self, values: Sequence[Union[Value, WaitForNames]]) -> None:
|
||||
def update(self, values: Sequence[Union[Value, WaitForNames]]) -> bool:
|
||||
if wait_for_names := [v for v in values if isinstance(v, WaitForNames)]:
|
||||
if len(wait_for_names) > 1:
|
||||
raise InvalidUpdateError(
|
||||
"Received multiple WaitForNames updates in the same step."
|
||||
)
|
||||
self.names = wait_for_names[0].names
|
||||
return True
|
||||
elif self.names is not None:
|
||||
updated = False
|
||||
for value in values:
|
||||
assert not isinstance(value, WaitForNames)
|
||||
if value in self.names:
|
||||
self.seen.add(value)
|
||||
if value not in self.seen:
|
||||
self.seen.add(value)
|
||||
updated = True
|
||||
else:
|
||||
raise InvalidUpdateError(f"Value {value} not in {self.names}")
|
||||
return updated
|
||||
|
||||
def get(self) -> Value:
|
||||
if self.seen != self.names:
|
||||
raise EmptyChannelError()
|
||||
return None
|
||||
|
||||
def consume(self) -> None:
|
||||
def consume(self) -> bool:
|
||||
if self.seen == self.names:
|
||||
self.seen = set()
|
||||
self.names = None
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -45,20 +45,20 @@ class EphemeralValue(Generic[Value], BaseChannel[Value, Value, Value]):
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
def update(self, values: Sequence[Value]) -> None:
|
||||
def update(self, values: Sequence[Value]) -> bool:
|
||||
if len(values) == 0:
|
||||
try:
|
||||
del self.value
|
||||
return True
|
||||
except AttributeError:
|
||||
pass
|
||||
finally:
|
||||
return
|
||||
return False
|
||||
if len(values) != 1 and self.guard:
|
||||
raise InvalidUpdateError(
|
||||
"EphemeralValue can only receive one value per step."
|
||||
)
|
||||
|
||||
self.value = values[-1]
|
||||
return True
|
||||
|
||||
def get(self) -> Value:
|
||||
try:
|
||||
|
||||
@@ -44,13 +44,14 @@ class LastValue(Generic[Value], BaseChannel[Value, Value, Value]):
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
def update(self, values: Sequence[Value]) -> None:
|
||||
def update(self, values: Sequence[Value]) -> bool:
|
||||
if len(values) == 0:
|
||||
return
|
||||
return False
|
||||
if len(values) != 1:
|
||||
raise InvalidUpdateError("LastValue can only receive one value per step.")
|
||||
|
||||
self.value = values[-1]
|
||||
return True
|
||||
|
||||
def get(self) -> Value:
|
||||
try:
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
from contextlib import asynccontextmanager, contextmanager
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any, AsyncGenerator, Generator, Mapping
|
||||
|
||||
from langgraph.channels.base import BaseChannel
|
||||
from langgraph.checkpoint.base import Checkpoint
|
||||
from langgraph.checkpoint.id import uuid6
|
||||
from langgraph.errors import EmptyChannelError
|
||||
|
||||
|
||||
@contextmanager
|
||||
def ChannelsManager(
|
||||
channels: Mapping[str, BaseChannel],
|
||||
checkpoint: Checkpoint,
|
||||
) -> Generator[Mapping[str, BaseChannel], None, None]:
|
||||
"""Manage channels for the lifetime of a Pregel invocation (multiple steps)."""
|
||||
# TODO use https://docs.python.org/3/library/contextlib.html#contextlib.ExitStack
|
||||
empty = {
|
||||
k: v.from_checkpoint(checkpoint["channel_values"].get(k))
|
||||
for k, v in channels.items()
|
||||
}
|
||||
try:
|
||||
yield {k: v.__enter__() for k, v in empty.items()}
|
||||
finally:
|
||||
for v in empty.values():
|
||||
v.__exit__(None, None, None)
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def AsyncChannelsManager(
|
||||
channels: Mapping[str, BaseChannel],
|
||||
checkpoint: Checkpoint,
|
||||
) -> AsyncGenerator[Mapping[str, BaseChannel], None]:
|
||||
"""Manage channels for the lifetime of a Pregel invocation (multiple steps)."""
|
||||
empty = {
|
||||
k: v.afrom_checkpoint(checkpoint["channel_values"].get(k))
|
||||
for k, v in channels.items()
|
||||
}
|
||||
try:
|
||||
yield {k: await v.__aenter__() for k, v in empty.items()}
|
||||
finally:
|
||||
for v in empty.values():
|
||||
await v.__aexit__(None, None, None)
|
||||
|
||||
|
||||
def create_checkpoint(
|
||||
checkpoint: Checkpoint, channels: Mapping[str, BaseChannel], step: int
|
||||
) -> Checkpoint:
|
||||
"""Create a checkpoint for the given channels."""
|
||||
ts = datetime.now(timezone.utc).isoformat()
|
||||
values: dict[str, Any] = {}
|
||||
for k, v in channels.items():
|
||||
try:
|
||||
values[k] = v.checkpoint()
|
||||
except EmptyChannelError:
|
||||
pass
|
||||
return Checkpoint(
|
||||
v=1,
|
||||
ts=ts,
|
||||
id=str(uuid6(clock_seq=step)),
|
||||
channel_values=values,
|
||||
channel_versions=checkpoint["channel_versions"],
|
||||
versions_seen=checkpoint["versions_seen"],
|
||||
pending_sends=checkpoint.get("pending_sends", []),
|
||||
)
|
||||
@@ -41,18 +41,24 @@ class NamedBarrierValue(Generic[Value], BaseChannel[Value, Value, set[Value]]):
|
||||
finally:
|
||||
pass
|
||||
|
||||
def update(self, values: Sequence[Value]) -> None:
|
||||
def update(self, values: Sequence[Value]) -> bool:
|
||||
updated = False
|
||||
for value in values:
|
||||
if value in self.names:
|
||||
self.seen.add(value)
|
||||
if value not in self.seen:
|
||||
self.seen.add(value)
|
||||
updated = True
|
||||
else:
|
||||
raise InvalidUpdateError(f"Value {value} not in {self.names}")
|
||||
return updated
|
||||
|
||||
def get(self) -> Value:
|
||||
if self.seen != self.names:
|
||||
raise EmptyChannelError()
|
||||
return None
|
||||
|
||||
def consume(self) -> None:
|
||||
def consume(self) -> bool:
|
||||
if self.seen == self.names:
|
||||
self.seen = set()
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -4,6 +4,7 @@ from typing import Any, Generator, Generic, Iterator, Optional, Sequence, Type,
|
||||
from typing_extensions import Self
|
||||
|
||||
from langgraph.channels.base import BaseChannel, Value
|
||||
from langgraph.errors import EmptyChannelError
|
||||
|
||||
|
||||
def flatten(values: Sequence[Union[Value, list[Value]]]) -> Iterator[Value]:
|
||||
@@ -66,6 +67,7 @@ class Topic(
|
||||
pass
|
||||
|
||||
def update(self, values: Sequence[Union[Value, list[Value]]]) -> None:
|
||||
current = list(self.values)
|
||||
if not self.accumulate:
|
||||
self.values = list[Value]()
|
||||
if flat_values := flatten(values):
|
||||
@@ -76,6 +78,10 @@ class Topic(
|
||||
self.values.append(value)
|
||||
else:
|
||||
self.values.extend(flat_values)
|
||||
return self.values != current
|
||||
|
||||
def get(self) -> Sequence[Value]:
|
||||
return list(self.values)
|
||||
if self.values:
|
||||
return list(self.values)
|
||||
else:
|
||||
raise EmptyChannelError
|
||||
|
||||
@@ -11,15 +11,20 @@ from typing import (
|
||||
NamedTuple,
|
||||
Optional,
|
||||
TypedDict,
|
||||
TypeVar,
|
||||
Union,
|
||||
)
|
||||
|
||||
from langchain_core.runnables import ConfigurableFieldSpec, RunnableConfig
|
||||
|
||||
from langgraph.channels.base import BaseChannel
|
||||
from langgraph.checkpoint.id import uuid6
|
||||
from langgraph.constants import Send
|
||||
from langgraph.serde.base import SerializerProtocol
|
||||
from langgraph.serde.jsonplus import JsonPlusSerializer
|
||||
|
||||
V = TypeVar("V", int, float, str)
|
||||
|
||||
|
||||
# Marked as total=False to allow for future expansion.
|
||||
class CheckpointMetadata(TypedDict, total=False):
|
||||
@@ -62,13 +67,13 @@ class Checkpoint(TypedDict):
|
||||
|
||||
Mapping from channel name to channel snapshot value.
|
||||
"""
|
||||
channel_versions: defaultdict[str, int]
|
||||
channel_versions: dict[str, Union[str, int, float]]
|
||||
"""The versions of the channels at the time of the checkpoint.
|
||||
|
||||
The keys are channel names and the values are the logical time step
|
||||
at which the channel was last updated.
|
||||
"""
|
||||
versions_seen: defaultdict[str, defaultdict[str, int]]
|
||||
versions_seen: defaultdict[str, dict[str, Union[str, int, float]]]
|
||||
"""Map from node ID to map from channel name to version seen.
|
||||
|
||||
This keeps track of the versions of the channels that each node has seen.
|
||||
@@ -80,18 +85,14 @@ class Checkpoint(TypedDict):
|
||||
Cleared by the next checkpoint."""
|
||||
|
||||
|
||||
def _seen_dict():
|
||||
return defaultdict(int)
|
||||
|
||||
|
||||
def empty_checkpoint() -> Checkpoint:
|
||||
return Checkpoint(
|
||||
v=1,
|
||||
id=str(uuid6(clock_seq=-2)),
|
||||
ts=datetime.now(timezone.utc).isoformat(),
|
||||
channel_values={},
|
||||
channel_versions=defaultdict(int),
|
||||
versions_seen=defaultdict(_seen_dict),
|
||||
channel_versions={},
|
||||
versions_seen=defaultdict(dict),
|
||||
pending_sends=[],
|
||||
)
|
||||
|
||||
@@ -102,10 +103,10 @@ def copy_checkpoint(checkpoint: Checkpoint) -> Checkpoint:
|
||||
ts=checkpoint["ts"],
|
||||
id=checkpoint["id"],
|
||||
channel_values=checkpoint["channel_values"].copy(),
|
||||
channel_versions=defaultdict(int, checkpoint["channel_versions"]),
|
||||
channel_versions=checkpoint["channel_versions"].copy(),
|
||||
versions_seen=defaultdict(
|
||||
_seen_dict,
|
||||
{k: defaultdict(int, v) for k, v in checkpoint["versions_seen"].items()},
|
||||
dict,
|
||||
{k: v.copy() for k, v in checkpoint["versions_seen"].items()},
|
||||
),
|
||||
pending_sends=checkpoint.get("pending_sends", []).copy(),
|
||||
)
|
||||
@@ -187,6 +188,7 @@ class BaseCheckpointSaver(ABC):
|
||||
self,
|
||||
config: Optional[RunnableConfig],
|
||||
*,
|
||||
filter: Optional[Dict[str, Any]] = None,
|
||||
before: Optional[RunnableConfig] = None,
|
||||
limit: Optional[int] = None,
|
||||
) -> AsyncIterator[CheckpointTuple]:
|
||||
@@ -200,3 +202,8 @@ class BaseCheckpointSaver(ABC):
|
||||
metadata: CheckpointMetadata,
|
||||
) -> RunnableConfig:
|
||||
raise NotImplementedError
|
||||
|
||||
def get_next_version(self, current: Optional[V], channel: BaseChannel) -> V:
|
||||
"""Get the next version of a channel. Default is to use int versions, incrementing by 1. If you override, you can use str/int/float versions,
|
||||
as long as they are monotonically increasing."""
|
||||
return current + 1 if current is not None else 1
|
||||
|
||||
@@ -3,12 +3,14 @@ import pickle
|
||||
import sqlite3
|
||||
import threading
|
||||
from contextlib import AbstractContextManager, contextmanager
|
||||
from hashlib import md5
|
||||
from types import TracebackType
|
||||
from typing import Any, AsyncIterator, Dict, Iterator, Optional, Sequence, Tuple
|
||||
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
from typing_extensions import Self
|
||||
|
||||
from langgraph.channels.base import BaseChannel
|
||||
from langgraph.checkpoint.base import (
|
||||
BaseCheckpointSaver,
|
||||
Checkpoint,
|
||||
@@ -16,6 +18,7 @@ from langgraph.checkpoint.base import (
|
||||
CheckpointTuple,
|
||||
SerializerProtocol,
|
||||
)
|
||||
from langgraph.errors import EmptyChannelError
|
||||
from langgraph.serde.jsonplus import JsonPlusSerializer
|
||||
|
||||
|
||||
@@ -431,6 +434,18 @@ class SqliteSaver(BaseCheckpointSaver, AbstractContextManager):
|
||||
"""
|
||||
raise NotImplementedError(_AIO_ERROR_MSG)
|
||||
|
||||
def get_next_version(self, current: Optional[str], channel: BaseChannel) -> str:
|
||||
if current is None:
|
||||
current_v = 0
|
||||
else:
|
||||
current_v = int(current.split(".")[0])
|
||||
next_v = current_v + 1
|
||||
try:
|
||||
next_h = md5(self.serde.dumps(channel.checkpoint())).hexdigest()
|
||||
except EmptyChannelError:
|
||||
next_h = ""
|
||||
return f"{next_v:032}.{next_h}"
|
||||
|
||||
|
||||
def _metadata_predicate(
|
||||
metadata_filter: Dict[str, Any],
|
||||
|
||||
@@ -155,7 +155,14 @@ class StateGraph(Graph):
|
||||
) -> None:
|
||||
if not isinstance(node, str):
|
||||
action = node
|
||||
node = getattr(action, "name", action.__name__)
|
||||
if isinstance(action, Runnable):
|
||||
node = action.name
|
||||
else:
|
||||
node = getattr(action, "__name__", action.__class__.__name__)
|
||||
if node is None:
|
||||
raise ValueError(
|
||||
"Node name must be provided if action is not a function"
|
||||
)
|
||||
if node in self.channels:
|
||||
raise ValueError(f"'{node}' is already being used as a state key")
|
||||
return super().add_node(node, action)
|
||||
|
||||
@@ -17,7 +17,7 @@ from typing import (
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
from typing_extensions import Self
|
||||
|
||||
from langgraph.channels.base import AsyncChannelsManager, ChannelsManager
|
||||
from langgraph.channels.manager import AsyncChannelsManager, ChannelsManager
|
||||
from langgraph.managed.base import ConfiguredManagedValue, ManagedValue, V
|
||||
from langgraph.pregel import Pregel
|
||||
from langgraph.pregel.io import read_channels
|
||||
|
||||
@@ -22,11 +22,28 @@ def str_output(output: Any) -> str:
|
||||
|
||||
|
||||
class ToolNode(RunnableCallable):
|
||||
"""
|
||||
A node that runs the tools requested in the last AIMessage. It can be used
|
||||
"""A node that runs the tools requested in the last AIMessage. It can be used
|
||||
either in StateGraph with a "messages" key or in MessageGraph. If multiple
|
||||
tool calls are requested, they will be run in parallel. The output will be
|
||||
a list of ToolMessages, one for each tool call.
|
||||
|
||||
The `ToolNode` is roughly analogous to:
|
||||
|
||||
```python
|
||||
tools_by_name = {tool.name: tool for tool in tools}
|
||||
def tool_node(state: dict):
|
||||
result = []
|
||||
for tool_call in state["messages"][-1].tool_calls:
|
||||
tool = tools_by_name[tool_call["name"]]
|
||||
observation = tool.invoke(tool_call["args"])
|
||||
result.append(ToolMessage(content=observation, tool_call_id=tool_call["id"]))
|
||||
return {"messages": result}
|
||||
```
|
||||
|
||||
Important:
|
||||
- The state MUST contain a list of messages.
|
||||
- The last message MUST be an `AIMessage`.
|
||||
- The `AIMessage` MUST have `tool_calls` populated.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
|
||||
+102
-23
@@ -52,10 +52,12 @@ from langchain_core.tracers._streaming import _StreamingCallbackHandler
|
||||
from typing_extensions import Self
|
||||
|
||||
from langgraph.channels.base import (
|
||||
AsyncChannelsManager,
|
||||
BaseChannel,
|
||||
ChannelsManager,
|
||||
EmptyChannelError,
|
||||
)
|
||||
from langgraph.channels.manager import (
|
||||
AsyncChannelsManager,
|
||||
ChannelsManager,
|
||||
create_checkpoint,
|
||||
)
|
||||
from langgraph.checkpoint.base import (
|
||||
@@ -564,7 +566,9 @@ class Pregel(
|
||||
),
|
||||
)
|
||||
# apply to checkpoint and save
|
||||
_apply_writes(checkpoint, channels, task.writes)
|
||||
_apply_writes(
|
||||
checkpoint, channels, task.writes, self.checkpointer.get_next_version
|
||||
)
|
||||
step = saved.metadata.get("step", -2) + 1 if saved else -1
|
||||
|
||||
# merge configurable fields with previous checkpoint config
|
||||
@@ -650,7 +654,9 @@ class Pregel(
|
||||
),
|
||||
)
|
||||
# apply to checkpoint and save
|
||||
_apply_writes(checkpoint, channels, task.writes)
|
||||
_apply_writes(
|
||||
checkpoint, channels, task.writes, self.checkpointer.get_next_version
|
||||
)
|
||||
step = saved.metadata.get("step", -2) + 1 if saved else -1
|
||||
|
||||
# merge configurable fields with previous checkpoint config
|
||||
@@ -847,9 +853,19 @@ class Pregel(
|
||||
config,
|
||||
-1,
|
||||
for_execution=True,
|
||||
get_next_version=self.checkpointer.get_next_version
|
||||
if self.checkpointer
|
||||
else _increment,
|
||||
)
|
||||
# apply input writes
|
||||
_apply_writes(checkpoint, channels, input_writes)
|
||||
_apply_writes(
|
||||
checkpoint,
|
||||
channels,
|
||||
input_writes,
|
||||
self.checkpointer.get_next_version
|
||||
if self.checkpointer
|
||||
else _increment,
|
||||
)
|
||||
# save input checkpoint
|
||||
yield from put_checkpoint(
|
||||
{
|
||||
@@ -865,8 +881,9 @@ class Pregel(
|
||||
# past previous interrupt, if any
|
||||
checkpoint = copy_checkpoint(checkpoint)
|
||||
for k in self.stream_channels_list:
|
||||
version = checkpoint["channel_versions"][k]
|
||||
checkpoint["versions_seen"][INTERRUPT][k] = version
|
||||
if k in checkpoint["channel_versions"]:
|
||||
version = checkpoint["channel_versions"][k]
|
||||
checkpoint["versions_seen"][INTERRUPT][k] = version
|
||||
|
||||
# Similarly to Bulk Synchronous Parallel / Pregel model
|
||||
# computation proceeds in steps, while there are channel updates
|
||||
@@ -884,6 +901,9 @@ class Pregel(
|
||||
step,
|
||||
for_execution=True,
|
||||
manager=run_manager,
|
||||
get_next_version=self.checkpointer.get_next_version
|
||||
if self.checkpointer
|
||||
else _increment,
|
||||
)
|
||||
|
||||
# if no more tasks, we're done
|
||||
@@ -975,7 +995,14 @@ class Pregel(
|
||||
)
|
||||
|
||||
# apply writes to channels
|
||||
_apply_writes(checkpoint, channels, pending_writes)
|
||||
_apply_writes(
|
||||
checkpoint,
|
||||
channels,
|
||||
pending_writes,
|
||||
self.checkpointer.get_next_version
|
||||
if self.checkpointer
|
||||
else _increment,
|
||||
)
|
||||
|
||||
# yield values output
|
||||
if "values" in stream_modes:
|
||||
@@ -1176,9 +1203,19 @@ class Pregel(
|
||||
config,
|
||||
-1,
|
||||
for_execution=True,
|
||||
get_next_version=self.checkpointer.get_next_version
|
||||
if self.checkpointer
|
||||
else _increment,
|
||||
)
|
||||
# apply input writes
|
||||
_apply_writes(checkpoint, channels, input_writes)
|
||||
_apply_writes(
|
||||
checkpoint,
|
||||
channels,
|
||||
input_writes,
|
||||
self.checkpointer.get_next_version
|
||||
if self.checkpointer
|
||||
else _increment,
|
||||
)
|
||||
# save input checkpoint
|
||||
for chunk in put_checkpoint(
|
||||
{"source": "input", "step": start, "writes": input}
|
||||
@@ -1191,8 +1228,9 @@ class Pregel(
|
||||
# past previous interrupt, if any
|
||||
checkpoint = copy_checkpoint(checkpoint)
|
||||
for k in self.stream_channels_list:
|
||||
version = checkpoint["channel_versions"][k]
|
||||
checkpoint["versions_seen"][INTERRUPT][k] = version
|
||||
if k in checkpoint["channel_versions"]:
|
||||
version = checkpoint["channel_versions"][k]
|
||||
checkpoint["versions_seen"][INTERRUPT][k] = version
|
||||
|
||||
# Similarly to Bulk Synchronous Parallel / Pregel model
|
||||
# computation proceeds in steps, while there are channel updates
|
||||
@@ -1210,6 +1248,9 @@ class Pregel(
|
||||
step,
|
||||
for_execution=True,
|
||||
manager=run_manager,
|
||||
get_next_version=self.checkpointer.get_next_version
|
||||
if self.checkpointer
|
||||
else _increment,
|
||||
)
|
||||
|
||||
# if no more tasks, we're done
|
||||
@@ -1304,7 +1345,14 @@ class Pregel(
|
||||
)
|
||||
|
||||
# apply writes to channels
|
||||
_apply_writes(checkpoint, channels, pending_writes)
|
||||
_apply_writes(
|
||||
checkpoint,
|
||||
channels,
|
||||
pending_writes,
|
||||
self.checkpointer.get_next_version
|
||||
if self.checkpointer
|
||||
else _increment,
|
||||
)
|
||||
|
||||
# yield current values
|
||||
if "values" in stream_modes:
|
||||
@@ -1360,7 +1408,7 @@ class Pregel(
|
||||
except NameError:
|
||||
pass
|
||||
# wait for all background tasks to finish
|
||||
await asyncio.gather(*bg)
|
||||
await asyncio.shield(asyncio.gather(*bg))
|
||||
|
||||
def invoke(
|
||||
self,
|
||||
@@ -1503,12 +1551,15 @@ def _should_interrupt(
|
||||
snapshot_channels: Sequence[str],
|
||||
tasks: list[PregelExecutableTask],
|
||||
) -> bool:
|
||||
version_type = type(next(iter(checkpoint["channel_versions"].values()), None))
|
||||
null_version = version_type()
|
||||
# defaultdicts are mutated on access :( so we need to copy
|
||||
seen = checkpoint["versions_seen"].copy()[INTERRUPT].copy()
|
||||
seen = checkpoint["versions_seen"].copy()[INTERRUPT]
|
||||
return (
|
||||
# interrupt if any of snapshopt_channels has been updated since last interrupt
|
||||
any(
|
||||
checkpoint["channel_versions"][chan] > seen[chan]
|
||||
checkpoint["channel_versions"].get(chan, null_version)
|
||||
> seen.get(chan, null_version)
|
||||
for chan in snapshot_channels
|
||||
)
|
||||
# and any triggered node is in interrupt_nodes list
|
||||
@@ -1534,7 +1585,7 @@ def _local_read(
|
||||
if fresh:
|
||||
checkpoint = create_checkpoint(checkpoint, channels, -1)
|
||||
with ChannelsManager(channels, checkpoint) as channels:
|
||||
_apply_writes(copy_checkpoint(checkpoint), channels, writes)
|
||||
_apply_writes(copy_checkpoint(checkpoint), channels, writes, None)
|
||||
return read_channels(channels, select)
|
||||
else:
|
||||
return read_channels(channels, select)
|
||||
@@ -1559,10 +1610,15 @@ def _local_write(
|
||||
commit(writes)
|
||||
|
||||
|
||||
def _increment(current: Optional[int], channel: BaseChannel) -> int:
|
||||
return current + 1 if current is not None else 1
|
||||
|
||||
|
||||
def _apply_writes(
|
||||
checkpoint: Checkpoint,
|
||||
channels: Mapping[str, BaseChannel],
|
||||
pending_writes: Sequence[tuple[str, Any]],
|
||||
get_next_version: Optional[Callable[[int, BaseChannel], int]],
|
||||
) -> None:
|
||||
if checkpoint["pending_sends"]:
|
||||
checkpoint["pending_sends"].clear()
|
||||
@@ -1579,24 +1635,30 @@ def _apply_writes(
|
||||
if checkpoint["channel_versions"]:
|
||||
max_version = max(checkpoint["channel_versions"].values())
|
||||
else:
|
||||
max_version = 0
|
||||
max_version = None
|
||||
|
||||
updated_channels: set[str] = set()
|
||||
# Apply writes to channels
|
||||
for chan, vals in pending_writes_by_channel.items():
|
||||
if chan in channels:
|
||||
try:
|
||||
channels[chan].update(vals)
|
||||
updated = channels[chan].update(vals)
|
||||
except InvalidUpdateError as e:
|
||||
raise InvalidUpdateError(
|
||||
f"Invalid update for channel {chan} with values {vals}"
|
||||
) from e
|
||||
checkpoint["channel_versions"][chan] = max_version + 1
|
||||
if updated and get_next_version is not None:
|
||||
checkpoint["channel_versions"][chan] = get_next_version(
|
||||
max_version, channels[chan]
|
||||
)
|
||||
updated_channels.add(chan)
|
||||
# Channels that weren't updated in this step are notified of a new step
|
||||
for chan in channels:
|
||||
if chan not in updated_channels:
|
||||
channels[chan].update([])
|
||||
if channels[chan].update([]) and get_next_version is not None:
|
||||
checkpoint["channel_versions"][chan] = get_next_version(
|
||||
max_version, channels[chan]
|
||||
)
|
||||
|
||||
|
||||
@overload
|
||||
@@ -1608,6 +1670,7 @@ def _prepare_next_tasks(
|
||||
config: RunnableConfig,
|
||||
step: int,
|
||||
for_execution: Literal[False],
|
||||
get_next_version: Literal[None] = None,
|
||||
manager: Literal[None] = None,
|
||||
) -> tuple[Checkpoint, list[PregelTaskDescription]]:
|
||||
...
|
||||
@@ -1622,7 +1685,8 @@ def _prepare_next_tasks(
|
||||
config: RunnableConfig,
|
||||
step: int,
|
||||
for_execution: Literal[True],
|
||||
manager: Union[ParentRunManager, AsyncParentRunManager],
|
||||
get_next_version: Callable[[int, BaseChannel], int],
|
||||
manager: Union[None, ParentRunManager, AsyncParentRunManager],
|
||||
) -> tuple[Checkpoint, list[PregelExecutableTask]]:
|
||||
...
|
||||
|
||||
@@ -1636,6 +1700,7 @@ def _prepare_next_tasks(
|
||||
step: int,
|
||||
*,
|
||||
for_execution: bool,
|
||||
get_next_version: Union[None, Callable[[int, BaseChannel], int]] = None,
|
||||
manager: Union[None, ParentRunManager, AsyncParentRunManager] = None,
|
||||
) -> tuple[Checkpoint, Union[list[PregelTaskDescription], list[PregelExecutableTask]]]:
|
||||
checkpoint = copy_checkpoint(checkpoint)
|
||||
@@ -1689,6 +1754,10 @@ def _prepare_next_tasks(
|
||||
channels_to_consume = set()
|
||||
# Check if any processes should be run in next step
|
||||
# If so, prepare the values to be passed to them
|
||||
version_type = type(next(iter(checkpoint["channel_versions"].values()), None))
|
||||
null_version = version_type()
|
||||
if null_version is None:
|
||||
return checkpoint, tasks
|
||||
for name, proc in processes.items():
|
||||
seen = checkpoint["versions_seen"][name]
|
||||
# If any of the channels read by this process were updated
|
||||
@@ -1698,7 +1767,8 @@ def _prepare_next_tasks(
|
||||
if not isinstance(
|
||||
read_channel(channels, chan, return_exception=True), EmptyChannelError
|
||||
)
|
||||
and checkpoint["channel_versions"][chan] > seen[chan]
|
||||
and checkpoint["channel_versions"].get(chan, null_version)
|
||||
> seen.get(chan, null_version)
|
||||
]:
|
||||
channels_to_consume.update(triggers)
|
||||
try:
|
||||
@@ -1712,6 +1782,7 @@ def _prepare_next_tasks(
|
||||
{
|
||||
chan: checkpoint["channel_versions"][chan]
|
||||
for chan in proc.triggers
|
||||
if chan in checkpoint["channel_versions"]
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1757,10 +1828,18 @@ def _prepare_next_tasks(
|
||||
)
|
||||
else:
|
||||
tasks.append(PregelTaskDescription(name, val))
|
||||
# Find the highest version of all channels
|
||||
if checkpoint["channel_versions"]:
|
||||
max_version = max(checkpoint["channel_versions"].values())
|
||||
else:
|
||||
max_version = None
|
||||
# Consume all channels that were read
|
||||
if for_execution:
|
||||
for chan in channels_to_consume:
|
||||
channels[chan].consume()
|
||||
if channels[chan].consume():
|
||||
checkpoint["channel_versions"][chan] = get_next_version(
|
||||
max_version, channels[chan]
|
||||
)
|
||||
return checkpoint, tasks
|
||||
|
||||
|
||||
|
||||
@@ -13,6 +13,8 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def default_retry_on(exc: Exception) -> bool:
|
||||
if isinstance(exc, ConnectionError):
|
||||
return True
|
||||
if isinstance(
|
||||
exc,
|
||||
(
|
||||
@@ -24,6 +26,10 @@ def default_retry_on(exc: Exception) -> bool:
|
||||
NameError,
|
||||
SyntaxError,
|
||||
RuntimeError,
|
||||
ReferenceError,
|
||||
StopIteration,
|
||||
StopAsyncIteration,
|
||||
OSError,
|
||||
),
|
||||
):
|
||||
return False
|
||||
@@ -90,7 +96,7 @@ def run_with_retry(
|
||||
)
|
||||
# log the retry
|
||||
logger.info(
|
||||
f"Retrying task {task.name} after {interval:.2f} seconds (attempt {attempts})"
|
||||
f"Retrying task {task.name} after {interval:.2f} seconds (attempt {attempts}) after {exc.__class__.__name__} {exc}"
|
||||
)
|
||||
|
||||
|
||||
@@ -138,5 +144,5 @@ async def arun_with_retry(
|
||||
)
|
||||
# log the retry
|
||||
logger.info(
|
||||
f"Retrying task {task.name} after {interval:.2f} seconds (attempt {attempts})"
|
||||
f"Retrying task {task.name} after {interval:.2f} seconds (attempt {attempts}) after {exc.__class__.__name__} {exc}"
|
||||
)
|
||||
|
||||
Generated
+791
-735
File diff suppressed because it is too large
Load Diff
+15
-3
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "langgraph"
|
||||
version = "0.0.66"
|
||||
version = "0.0.69"
|
||||
description = "langgraph"
|
||||
authors = []
|
||||
license = "MIT"
|
||||
@@ -30,13 +30,25 @@ jupyter = "^1.0.0"
|
||||
langchainhub = "^0.1.14"
|
||||
langchain-openai = ">=0.1.2"
|
||||
langchain-anthropic = ">=0.1.8"
|
||||
dataclasses-json = "^0.6.7"
|
||||
|
||||
[tool.poetry.group.test]
|
||||
optional = true
|
||||
|
||||
[tool.ruff]
|
||||
select = [ "E", "F", "I" ]
|
||||
ignore = [ "E501" ]
|
||||
lint.select = [ "E", "F", "I" ]
|
||||
lint.ignore = [ "E501" ]
|
||||
line-length = 88
|
||||
indent-width = 4
|
||||
extend-include = ["*.ipynb"]
|
||||
|
||||
[tool.ruff.format]
|
||||
quote-style = "double"
|
||||
indent-style = "space"
|
||||
skip-magic-trailing-comma = false
|
||||
line-ending = "auto"
|
||||
docstring-code-format = false
|
||||
docstring-code-line-length = "dynamic"
|
||||
|
||||
[tool.mypy]
|
||||
ignore_missing_imports = "True"
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,7 +1,7 @@
|
||||
import pytest
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
|
||||
from langgraph.channels.base import create_checkpoint
|
||||
from langgraph.channels.manager import create_checkpoint
|
||||
from langgraph.checkpoint.aiosqlite import AsyncSqliteSaver
|
||||
from langgraph.checkpoint.base import Checkpoint, CheckpointMetadata, empty_checkpoint
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import pytest
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
|
||||
from langgraph.channels.base import create_checkpoint
|
||||
from langgraph.channels.manager import create_checkpoint
|
||||
from langgraph.checkpoint.base import Checkpoint, CheckpointMetadata, empty_checkpoint
|
||||
from langgraph.checkpoint.memory import MemorySaver
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import pytest
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
|
||||
from langgraph.channels.base import create_checkpoint
|
||||
from langgraph.channels.manager import create_checkpoint
|
||||
from langgraph.checkpoint.base import Checkpoint, CheckpointMetadata, empty_checkpoint
|
||||
from langgraph.checkpoint.sqlite import (
|
||||
_AIO_ERROR_MSG,
|
||||
|
||||
+43
-34
@@ -56,13 +56,15 @@ def test_topic() -> None:
|
||||
assert channel.ValueType is Sequence[str]
|
||||
assert channel.UpdateType is Union[str, list[str]]
|
||||
|
||||
channel.update(["a", "b"])
|
||||
assert channel.update(["a", "b"])
|
||||
assert channel.get() == ["a", "b"]
|
||||
channel.update([["c", "d"], "d"])
|
||||
assert channel.update([["c", "d"], "d"])
|
||||
assert channel.get() == ["c", "d", "d"]
|
||||
channel.update([])
|
||||
assert channel.get() == []
|
||||
channel.update(["e"])
|
||||
assert channel.update([])
|
||||
with pytest.raises(EmptyChannelError):
|
||||
channel.get()
|
||||
assert not channel.update([]), "channel already empty"
|
||||
assert channel.update(["e"])
|
||||
assert channel.get() == ["e"]
|
||||
checkpoint = channel.checkpoint()
|
||||
with Topic(str).from_checkpoint(checkpoint) as channel:
|
||||
@@ -78,13 +80,15 @@ async def test_topic_async() -> None:
|
||||
assert channel.ValueType is Sequence[str]
|
||||
assert channel.UpdateType is Union[str, list[str]]
|
||||
|
||||
channel.update(["a", "b"])
|
||||
assert channel.update(["a", "b"])
|
||||
assert channel.get() == ["a", "b"]
|
||||
channel.update(["b", ["c", "d"], "d"])
|
||||
assert channel.update(["b", ["c", "d"], "d"])
|
||||
assert channel.get() == ["b", "c", "d", "d"]
|
||||
channel.update([])
|
||||
assert channel.get() == []
|
||||
channel.update(["e"])
|
||||
assert channel.update([])
|
||||
with pytest.raises(EmptyChannelError):
|
||||
channel.get()
|
||||
assert not channel.update([]), "channel already empty"
|
||||
assert channel.update(["e"])
|
||||
assert channel.get() == ["e"]
|
||||
checkpoint = channel.checkpoint()
|
||||
async with Topic(str).afrom_checkpoint(checkpoint) as channel:
|
||||
@@ -96,18 +100,20 @@ def test_topic_unique() -> None:
|
||||
assert channel.ValueType is Sequence[str]
|
||||
assert channel.UpdateType is Union[str, list[str]]
|
||||
|
||||
channel.update(["a", "b"])
|
||||
assert channel.update(["a", "b"])
|
||||
assert channel.get() == ["a", "b"]
|
||||
channel.update(["b", ["c", "d"], "d"])
|
||||
assert channel.update(["b", ["c", "d"], "d"])
|
||||
assert channel.get() == ["c", "d"], "de-dupes from current and previous steps"
|
||||
channel.update([])
|
||||
assert channel.get() == []
|
||||
channel.update(["e"])
|
||||
assert channel.update([])
|
||||
with pytest.raises(EmptyChannelError):
|
||||
channel.get()
|
||||
assert not channel.update([]), "channel already empty"
|
||||
assert channel.update(["e"])
|
||||
assert channel.get() == ["e"]
|
||||
checkpoint = channel.checkpoint()
|
||||
with Topic(str, unique=True).from_checkpoint(checkpoint) as channel:
|
||||
assert channel.get() == ["e"]
|
||||
channel.update(["d", "f"])
|
||||
assert channel.update(["d", "f"])
|
||||
assert channel.get() == ["f"], "de-dupes from checkpoint"
|
||||
|
||||
|
||||
@@ -116,18 +122,20 @@ async def test_topic_unique_async() -> None:
|
||||
assert channel.ValueType is Sequence[str]
|
||||
assert channel.UpdateType is Union[str, list[str]]
|
||||
|
||||
channel.update(["a", "b"])
|
||||
assert channel.update(["a", "b"])
|
||||
assert channel.get() == ["a", "b"]
|
||||
channel.update(["b", ["c", "d"], "d"])
|
||||
assert channel.update(["b", ["c", "d"], "d"])
|
||||
assert channel.get() == ["c", "d"], "de-dupes from current and previous steps"
|
||||
channel.update([])
|
||||
assert channel.get() == []
|
||||
channel.update(["e"])
|
||||
assert channel.update([])
|
||||
with pytest.raises(EmptyChannelError):
|
||||
channel.get()
|
||||
assert not channel.update([]), "channel already empty"
|
||||
assert channel.update(["e"])
|
||||
assert channel.get() == ["e"]
|
||||
checkpoint = channel.checkpoint()
|
||||
async with Topic(str, unique=True).afrom_checkpoint(checkpoint) as channel:
|
||||
assert channel.get() == ["e"]
|
||||
channel.update(["d", "f"])
|
||||
assert channel.update(["d", "f"])
|
||||
assert channel.get() == ["f"], "de-dupes from checkpoint"
|
||||
|
||||
|
||||
@@ -136,16 +144,16 @@ def test_topic_accumulate() -> None:
|
||||
assert channel.ValueType is Sequence[str]
|
||||
assert channel.UpdateType is Union[str, list[str]]
|
||||
|
||||
channel.update(["a", "b"])
|
||||
assert channel.update(["a", "b"])
|
||||
assert channel.get() == ["a", "b"]
|
||||
channel.update(["b", ["c", "d"], "d"])
|
||||
assert channel.update(["b", ["c", "d"], "d"])
|
||||
assert channel.get() == ["a", "b", "b", "c", "d", "d"]
|
||||
channel.update([])
|
||||
assert not channel.update([])
|
||||
assert channel.get() == ["a", "b", "b", "c", "d", "d"]
|
||||
checkpoint = channel.checkpoint()
|
||||
with Topic(str, accumulate=True).from_checkpoint(checkpoint) as channel:
|
||||
assert channel.get() == ["a", "b", "b", "c", "d", "d"]
|
||||
channel.update(["e"])
|
||||
assert channel.update(["e"])
|
||||
assert channel.get() == ["a", "b", "b", "c", "d", "d", "e"]
|
||||
|
||||
|
||||
@@ -154,16 +162,16 @@ async def test_topic_accumulate_async() -> None:
|
||||
assert channel.ValueType is Sequence[str]
|
||||
assert channel.UpdateType is Union[str, list[str]]
|
||||
|
||||
channel.update(["a", "b"])
|
||||
assert channel.update(["a", "b"])
|
||||
assert channel.get() == ["a", "b"]
|
||||
channel.update(["b", ["c", "d"], "d"])
|
||||
assert channel.update(["b", ["c", "d"], "d"])
|
||||
assert channel.get() == ["a", "b", "b", "c", "d", "d"]
|
||||
channel.update([])
|
||||
assert not channel.update([])
|
||||
assert channel.get() == ["a", "b", "b", "c", "d", "d"]
|
||||
checkpoint = channel.checkpoint()
|
||||
async with Topic(str, accumulate=True).afrom_checkpoint(checkpoint) as channel:
|
||||
assert channel.get() == ["a", "b", "b", "c", "d", "d"]
|
||||
channel.update(["e"])
|
||||
assert channel.update(["e"])
|
||||
assert channel.get() == ["a", "b", "b", "c", "d", "d", "e"]
|
||||
|
||||
|
||||
@@ -172,18 +180,19 @@ def test_topic_unique_accumulate() -> None:
|
||||
assert channel.ValueType is Sequence[str]
|
||||
assert channel.UpdateType is Union[str, list[str]]
|
||||
|
||||
channel.update(["a", "b"])
|
||||
assert channel.update(["a", "b"])
|
||||
assert channel.get() == ["a", "b"]
|
||||
channel.update(["b", ["c", "d"], "d"])
|
||||
assert channel.update(["b", ["c", "d"], "d"])
|
||||
assert channel.get() == ["a", "b", "c", "d"]
|
||||
channel.update([])
|
||||
assert not channel.update(["c"]), "no new values"
|
||||
assert not channel.update([])
|
||||
assert channel.get() == ["a", "b", "c", "d"]
|
||||
checkpoint = channel.checkpoint()
|
||||
with Topic(str, unique=True, accumulate=True).from_checkpoint(
|
||||
checkpoint
|
||||
) as channel:
|
||||
assert channel.get() == ["a", "b", "c", "d"]
|
||||
channel.update(["d", "e"])
|
||||
assert channel.update(["d", "e"])
|
||||
assert channel.get() == ["a", "b", "c", "d", "e"]
|
||||
|
||||
|
||||
|
||||
+49
-8
@@ -30,10 +30,17 @@ from langsmith import traceable
|
||||
from pytest_mock import MockerFixture
|
||||
from syrupy import SnapshotAssertion
|
||||
|
||||
from langgraph.channels.base import BaseChannel
|
||||
from langgraph.channels.binop import BinaryOperatorAggregate
|
||||
from langgraph.channels.context import Context
|
||||
from langgraph.channels.last_value import LastValue
|
||||
from langgraph.channels.topic import Topic
|
||||
from langgraph.checkpoint.base import (
|
||||
Checkpoint,
|
||||
CheckpointMetadata,
|
||||
CheckpointTuple,
|
||||
)
|
||||
from langgraph.checkpoint.memory import MemorySaver
|
||||
from langgraph.checkpoint.sqlite import SqliteSaver
|
||||
from langgraph.constants import Send
|
||||
from langgraph.errors import InvalidUpdateError
|
||||
@@ -171,6 +178,45 @@ def test_graph_validation() -> None:
|
||||
graph.compile()
|
||||
|
||||
|
||||
def test_checkpoint_errors() -> None:
|
||||
class FaultyGetCheckpointer(MemorySaver):
|
||||
def get_tuple(self, config: RunnableConfig) -> Optional[CheckpointTuple]:
|
||||
raise ValueError("Faulty get_tuple")
|
||||
|
||||
class FaultyPutCheckpointer(MemorySaver):
|
||||
def put(
|
||||
self,
|
||||
config: RunnableConfig,
|
||||
checkpoint: Checkpoint,
|
||||
metadata: CheckpointMetadata,
|
||||
) -> RunnableConfig:
|
||||
raise ValueError("Faulty put")
|
||||
|
||||
class FaultyVersionCheckpointer(MemorySaver):
|
||||
def get_next_version(self, current: Optional[int], channel: BaseChannel) -> int:
|
||||
raise ValueError("Faulty get_next_version")
|
||||
|
||||
def logic(inp: str) -> str:
|
||||
return ""
|
||||
|
||||
builder = Graph()
|
||||
builder.add_node("agent", logic)
|
||||
builder.set_entry_point("agent")
|
||||
builder.set_finish_point("agent")
|
||||
|
||||
graph = builder.compile(checkpointer=FaultyGetCheckpointer())
|
||||
with pytest.raises(ValueError, match="Faulty get_tuple"):
|
||||
graph.invoke("", {"configurable": {"thread_id": "thread-1"}})
|
||||
|
||||
graph = builder.compile(checkpointer=FaultyPutCheckpointer())
|
||||
with pytest.raises(ValueError, match="Faulty put"):
|
||||
graph.invoke("", {"configurable": {"thread_id": "thread-1"}})
|
||||
|
||||
graph = builder.compile(checkpointer=FaultyVersionCheckpointer())
|
||||
with pytest.raises(ValueError, match="Faulty get_next_version"):
|
||||
graph.invoke("", {"configurable": {"thread_id": "thread-1"}})
|
||||
|
||||
|
||||
def test_reducer_before_first_node() -> None:
|
||||
from langchain_core.messages import HumanMessage
|
||||
|
||||
@@ -619,7 +665,7 @@ def test_invoke_two_processes_in_dict_out(mocker: MockerFixture) -> None:
|
||||
]
|
||||
assert [*app.stream({"input": 2, "inbox": 12})] == [
|
||||
{"inbox": [3], "output": 13},
|
||||
{"inbox": [], "output": 4},
|
||||
{"output": 4},
|
||||
]
|
||||
assert [*app.stream({"input": 2, "inbox": 12}, stream_mode="debug")] == [
|
||||
{
|
||||
@@ -842,7 +888,7 @@ def test_invoke_checkpoint(mocker: MockerFixture) -> None:
|
||||
pass
|
||||
else:
|
||||
errored_once = True
|
||||
raise OSError("I will be retried")
|
||||
raise ConnectionError("I will be retried")
|
||||
if input > 10:
|
||||
raise ValueError("Input is too large")
|
||||
return input
|
||||
@@ -1216,7 +1262,7 @@ def test_channel_enter_exit_timing(mocker: MockerFixture) -> None:
|
||||
if i == 0:
|
||||
assert chunk == {"inbox": [3]}
|
||||
elif i == 1:
|
||||
assert chunk == {"inbox": [], "output": 4}
|
||||
assert chunk == {"output": 4}
|
||||
else:
|
||||
assert False, "Expected only two chunks"
|
||||
assert cleanup.call_count == 1, "Expected cleanup to be called once"
|
||||
@@ -6048,7 +6094,6 @@ def test_branch_then(snapshot: SnapshotAssertion) -> None:
|
||||
"metadata": {"thread_id": "10"},
|
||||
"callbacks": None,
|
||||
"recursion_limit": 25,
|
||||
"run_id": None,
|
||||
"configurable": {
|
||||
"thread_id": "10",
|
||||
"thread_ts": AnyStr(),
|
||||
@@ -6072,7 +6117,6 @@ def test_branch_then(snapshot: SnapshotAssertion) -> None:
|
||||
"metadata": {"thread_id": "10"},
|
||||
"callbacks": None,
|
||||
"recursion_limit": 25,
|
||||
"run_id": None,
|
||||
"configurable": {
|
||||
"thread_id": "10",
|
||||
"thread_ts": AnyStr(),
|
||||
@@ -6120,7 +6164,6 @@ def test_branch_then(snapshot: SnapshotAssertion) -> None:
|
||||
"metadata": {"thread_id": "10"},
|
||||
"callbacks": None,
|
||||
"recursion_limit": 25,
|
||||
"run_id": None,
|
||||
"configurable": {
|
||||
"thread_id": "10",
|
||||
"thread_ts": AnyStr(),
|
||||
@@ -6168,7 +6211,6 @@ def test_branch_then(snapshot: SnapshotAssertion) -> None:
|
||||
"metadata": {"thread_id": "10"},
|
||||
"callbacks": None,
|
||||
"recursion_limit": 25,
|
||||
"run_id": None,
|
||||
"configurable": {
|
||||
"thread_id": "10",
|
||||
"thread_ts": AnyStr(),
|
||||
@@ -6216,7 +6258,6 @@ def test_branch_then(snapshot: SnapshotAssertion) -> None:
|
||||
"metadata": {"thread_id": "10"},
|
||||
"callbacks": None,
|
||||
"recursion_limit": 25,
|
||||
"run_id": None,
|
||||
"configurable": {
|
||||
"thread_id": "10",
|
||||
"thread_ts": AnyStr(),
|
||||
|
||||
+224
-8
@@ -1,6 +1,7 @@
|
||||
import asyncio
|
||||
import json
|
||||
import operator
|
||||
import time
|
||||
from collections import Counter
|
||||
from contextlib import asynccontextmanager, contextmanager
|
||||
from typing import (
|
||||
@@ -24,14 +25,19 @@ from langchain_core.runnables import (
|
||||
RunnablePassthrough,
|
||||
RunnablePick,
|
||||
)
|
||||
from langchain_core.utils.aiter import aclosing
|
||||
from pytest_mock import MockerFixture
|
||||
from syrupy import SnapshotAssertion
|
||||
|
||||
from langgraph.channels.base import BaseChannel
|
||||
from langgraph.channels.binop import BinaryOperatorAggregate
|
||||
from langgraph.channels.context import Context
|
||||
from langgraph.channels.last_value import LastValue
|
||||
from langgraph.channels.topic import Topic
|
||||
from langgraph.checkpoint import BaseCheckpointSaver
|
||||
from langgraph.checkpoint.aiosqlite import AsyncSqliteSaver
|
||||
from langgraph.checkpoint.base import Checkpoint, CheckpointMetadata, CheckpointTuple
|
||||
from langgraph.checkpoint.memory import MemorySaver
|
||||
from langgraph.constants import Send
|
||||
from langgraph.errors import InvalidUpdateError
|
||||
from langgraph.graph import END, Graph, StateGraph
|
||||
@@ -53,6 +59,69 @@ from tests.memory_assert import (
|
||||
)
|
||||
|
||||
|
||||
async def test_checkpoint_errors() -> None:
|
||||
class FaultyGetCheckpointer(MemorySaver):
|
||||
async def aget_tuple(self, config: RunnableConfig) -> Optional[CheckpointTuple]:
|
||||
raise ValueError("Faulty get_tuple")
|
||||
|
||||
class FaultyPutCheckpointer(MemorySaver):
|
||||
async def aput(
|
||||
self,
|
||||
config: RunnableConfig,
|
||||
checkpoint: Checkpoint,
|
||||
metadata: CheckpointMetadata,
|
||||
) -> RunnableConfig:
|
||||
raise ValueError("Faulty put")
|
||||
|
||||
class FaultyVersionCheckpointer(MemorySaver):
|
||||
def get_next_version(self, current: Optional[int], channel: BaseChannel) -> int:
|
||||
raise ValueError("Faulty get_next_version")
|
||||
|
||||
def logic(inp: str) -> str:
|
||||
return ""
|
||||
|
||||
builder = Graph()
|
||||
builder.add_node("agent", logic)
|
||||
builder.set_entry_point("agent")
|
||||
builder.set_finish_point("agent")
|
||||
|
||||
graph = builder.compile(checkpointer=FaultyGetCheckpointer())
|
||||
with pytest.raises(ValueError, match="Faulty get_tuple"):
|
||||
await graph.ainvoke("", {"configurable": {"thread_id": "thread-1"}})
|
||||
with pytest.raises(ValueError, match="Faulty get_tuple"):
|
||||
async for _ in graph.astream("", {"configurable": {"thread_id": "thread-2"}}):
|
||||
pass
|
||||
with pytest.raises(ValueError, match="Faulty get_tuple"):
|
||||
async for _ in graph.astream_events(
|
||||
"", {"configurable": {"thread_id": "thread-3"}}, version="v2"
|
||||
):
|
||||
pass
|
||||
|
||||
graph = builder.compile(checkpointer=FaultyPutCheckpointer())
|
||||
with pytest.raises(ValueError, match="Faulty put"):
|
||||
await graph.ainvoke("", {"configurable": {"thread_id": "thread-1"}})
|
||||
with pytest.raises(ValueError, match="Faulty put"):
|
||||
async for _ in graph.astream("", {"configurable": {"thread_id": "thread-2"}}):
|
||||
pass
|
||||
with pytest.raises(ValueError, match="Faulty put"):
|
||||
async for _ in graph.astream_events(
|
||||
"", {"configurable": {"thread_id": "thread-3"}}, version="v2"
|
||||
):
|
||||
pass
|
||||
|
||||
graph = builder.compile(checkpointer=FaultyVersionCheckpointer())
|
||||
with pytest.raises(ValueError, match="Faulty get_next_version"):
|
||||
await graph.ainvoke("", {"configurable": {"thread_id": "thread-1"}})
|
||||
with pytest.raises(ValueError, match="Faulty get_next_version"):
|
||||
async for _ in graph.astream("", {"configurable": {"thread_id": "thread-2"}}):
|
||||
pass
|
||||
with pytest.raises(ValueError, match="Faulty get_next_version"):
|
||||
async for _ in graph.astream_events(
|
||||
"", {"configurable": {"thread_id": "thread-3"}}, version="v2"
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
async def test_node_cancellation_on_external_cancel() -> None:
|
||||
inner_task_cancelled = False
|
||||
|
||||
@@ -135,6 +204,158 @@ async def test_step_timeout_on_stream_hang() -> None:
|
||||
assert inner_task_cancelled
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"checkpointer",
|
||||
[
|
||||
MemorySaverAssertImmutable(),
|
||||
AsyncSqliteSaver.from_conn_string(":memory:"),
|
||||
None,
|
||||
],
|
||||
)
|
||||
async def test_cancel_graph_astream(
|
||||
checkpointer: Optional[BaseCheckpointSaver],
|
||||
) -> None:
|
||||
try:
|
||||
|
||||
class State(TypedDict):
|
||||
value: int
|
||||
|
||||
class AwhileMaker:
|
||||
def __init__(self) -> None:
|
||||
self.reset()
|
||||
|
||||
async def __call__(self, input: State) -> Any:
|
||||
self.started = True
|
||||
try:
|
||||
await asyncio.sleep(1.5)
|
||||
except asyncio.CancelledError:
|
||||
self.cancelled = True
|
||||
raise
|
||||
|
||||
def reset(self):
|
||||
self.started = False
|
||||
self.cancelled = False
|
||||
|
||||
async def alittlewhile(input: State) -> None:
|
||||
await asyncio.sleep(0.6)
|
||||
return {"value": 2}
|
||||
|
||||
awhile = AwhileMaker()
|
||||
builder = StateGraph(State)
|
||||
builder.add_node("awhile", awhile)
|
||||
builder.add_node(alittlewhile)
|
||||
builder.add_edge(START, "alittlewhile")
|
||||
builder.add_edge("alittlewhile", "awhile")
|
||||
graph = builder.compile(checkpointer=checkpointer)
|
||||
|
||||
# test interrupting astream
|
||||
thread1: RunnableConfig = {"configurable": {"thread_id": 1}}
|
||||
async with aclosing(graph.astream({"value": 1}, thread1)) as stream:
|
||||
async for chunk in stream:
|
||||
assert chunk == {"alittlewhile": {"value": 2}}
|
||||
break
|
||||
|
||||
# node "awhile" should never start
|
||||
assert awhile.started is False
|
||||
|
||||
# checkpoint with output of "alittlewhile" should not be saved
|
||||
if checkpointer is not None:
|
||||
state = await graph.aget_state(thread1)
|
||||
assert state is not None
|
||||
assert state.values == {"value": 1}
|
||||
assert state.next == ("alittlewhile",)
|
||||
assert state.metadata == {"source": "loop", "step": 0, "writes": None}
|
||||
finally:
|
||||
if getattr(checkpointer, "__aexit__", None):
|
||||
await checkpointer.__aexit__(None, None, None)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"checkpointer",
|
||||
[
|
||||
MemorySaverAssertImmutable(),
|
||||
AsyncSqliteSaver.from_conn_string(":memory:"),
|
||||
None,
|
||||
],
|
||||
)
|
||||
async def test_cancel_graph_astream_events_v2(
|
||||
checkpointer: Optional[BaseCheckpointSaver],
|
||||
) -> None:
|
||||
try:
|
||||
|
||||
class State(TypedDict):
|
||||
value: int
|
||||
|
||||
class AwhileMaker:
|
||||
def __init__(self) -> None:
|
||||
self.reset()
|
||||
|
||||
async def __call__(self, input: State) -> Any:
|
||||
self.started = True
|
||||
try:
|
||||
await asyncio.sleep(1.5)
|
||||
except asyncio.CancelledError:
|
||||
self.cancelled = True
|
||||
raise
|
||||
|
||||
def reset(self):
|
||||
self.started = False
|
||||
self.cancelled = False
|
||||
|
||||
async def alittlewhile(input: State) -> None:
|
||||
await asyncio.sleep(0.6)
|
||||
return {"value": 2}
|
||||
|
||||
awhile = AwhileMaker()
|
||||
anotherwhile = AwhileMaker()
|
||||
builder = StateGraph(State)
|
||||
builder.add_node(alittlewhile)
|
||||
builder.add_node("awhile", awhile)
|
||||
builder.add_node("anotherwhile", anotherwhile)
|
||||
builder.add_edge(START, "alittlewhile")
|
||||
builder.add_edge("alittlewhile", "awhile")
|
||||
builder.add_edge("awhile", "anotherwhile")
|
||||
graph = builder.compile(checkpointer=checkpointer)
|
||||
|
||||
# test interrupting astream_events v2
|
||||
got_event = False
|
||||
thread2: RunnableConfig = {"configurable": {"thread_id": 2}}
|
||||
async with aclosing(
|
||||
graph.astream_events({"value": 1}, thread2, version="v2")
|
||||
) as stream:
|
||||
async for chunk in stream:
|
||||
if chunk["event"] == "on_chain_stream" and not chunk["parent_ids"]:
|
||||
print(time.perf_counter(), "got event out here", chunk)
|
||||
got_event = True
|
||||
assert chunk["data"]["chunk"] == {"alittlewhile": {"value": 2}}
|
||||
break
|
||||
|
||||
# did break
|
||||
assert got_event
|
||||
|
||||
# node "awhile" starts but is cancelled
|
||||
assert awhile.started is True
|
||||
assert awhile.cancelled is True
|
||||
|
||||
# node "anotherwhile" should never start
|
||||
assert anotherwhile.started is False
|
||||
|
||||
# checkpoint with output of "alittlewhile" should not be saved
|
||||
if checkpointer is not None:
|
||||
state = await graph.aget_state(thread2)
|
||||
assert state is not None
|
||||
assert state.values == {"value": 2}
|
||||
assert state.next == ("awhile",)
|
||||
assert state.metadata == {
|
||||
"source": "loop",
|
||||
"step": 1,
|
||||
"writes": {"alittlewhile": {"value": 2}},
|
||||
}
|
||||
finally:
|
||||
if getattr(checkpointer, "__aexit__", None):
|
||||
await checkpointer.__aexit__(None, None, None)
|
||||
|
||||
|
||||
async def test_invoke_single_process_in_out(mocker: MockerFixture) -> None:
|
||||
add_one = mocker.Mock(side_effect=lambda x: x + 1)
|
||||
chain = Channel.subscribe_to("input") | add_one | Channel.write_to("output")
|
||||
@@ -529,7 +750,7 @@ async def test_invoke_two_processes_in_dict_out(mocker: MockerFixture) -> None:
|
||||
]
|
||||
assert [c async for c in app.astream({"input": 2, "inbox": 12})] == [
|
||||
{"inbox": [3], "output": 13},
|
||||
{"inbox": [], "output": 4},
|
||||
{"output": 4},
|
||||
]
|
||||
assert [
|
||||
c async for c in app.astream({"input": 2, "inbox": 12}, stream_mode="debug")
|
||||
@@ -758,7 +979,7 @@ async def test_invoke_checkpoint(mocker: MockerFixture) -> None:
|
||||
pass
|
||||
else:
|
||||
errored_once = True
|
||||
raise OSError("I will be retried")
|
||||
raise ConnectionError("I will be retried")
|
||||
if input > 10:
|
||||
raise ValueError("Input is too large")
|
||||
return input
|
||||
@@ -1146,7 +1367,7 @@ async def test_channel_enter_exit_timing(mocker: MockerFixture) -> None:
|
||||
if i == 0:
|
||||
assert chunk == {"inbox": [3]}
|
||||
elif i == 1:
|
||||
assert chunk == {"inbox": [], "output": 4}
|
||||
assert chunk == {"output": 4}
|
||||
else:
|
||||
assert False, "Expected only two chunks"
|
||||
assert setup_sync.call_count == 0
|
||||
@@ -4428,7 +4649,6 @@ async def test_branch_then() -> None:
|
||||
"metadata": {"thread_id": "10"},
|
||||
"callbacks": None,
|
||||
"recursion_limit": 25,
|
||||
"run_id": None,
|
||||
"configurable": {"thread_id": "10", "thread_ts": AnyStr()},
|
||||
},
|
||||
"values": {"my_key": ""},
|
||||
@@ -4449,7 +4669,6 @@ async def test_branch_then() -> None:
|
||||
"metadata": {"thread_id": "10"},
|
||||
"callbacks": None,
|
||||
"recursion_limit": 25,
|
||||
"run_id": None,
|
||||
"configurable": {
|
||||
"thread_id": "10",
|
||||
"thread_ts": AnyStr(),
|
||||
@@ -4493,7 +4712,6 @@ async def test_branch_then() -> None:
|
||||
"metadata": {"thread_id": "10"},
|
||||
"callbacks": None,
|
||||
"recursion_limit": 25,
|
||||
"run_id": None,
|
||||
"configurable": {
|
||||
"thread_id": "10",
|
||||
"thread_ts": AnyStr(),
|
||||
@@ -4538,7 +4756,6 @@ async def test_branch_then() -> None:
|
||||
"metadata": {"thread_id": "10"},
|
||||
"callbacks": None,
|
||||
"recursion_limit": 25,
|
||||
"run_id": None,
|
||||
"configurable": {
|
||||
"thread_id": "10",
|
||||
"thread_ts": AnyStr(),
|
||||
@@ -4586,7 +4803,6 @@ async def test_branch_then() -> None:
|
||||
"metadata": {"thread_id": "10"},
|
||||
"callbacks": None,
|
||||
"recursion_limit": 25,
|
||||
"run_id": None,
|
||||
"configurable": {
|
||||
"thread_id": "10",
|
||||
"thread_ts": AnyStr(),
|
||||
|
||||
Reference in New Issue
Block a user