akileshjayakumar commited on
Commit
252375c
1 Parent(s): 9a4dc5f

refactor dir

Browse files
.env_langgraph ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ LANGCHAIN_TRACING_V2=true
2
+ LANGCHAIN_ENDPOINT="https://api.example.langchain.com"
3
+ LANGCHAIN_API_KEY="your_langchain_api_key"
4
+ LANGCHAIN_PROJECT="your_langchain_project"
5
+
6
+ OPENAI_API_KEY="your_openai_api_key"
7
+ TAVILY_API_KEY="your_tavily_api_key"
.gitignore ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ .env
2
+ .venv/
3
+ __pycache__/
README.md CHANGED
@@ -1,13 +1,66 @@
1
- ---
2
- title: LangGraph Agentic Chatbot
3
- emoji: 🏃
4
- colorFrom: indigo
5
- colorTo: yellow
6
- sdk: gradio
7
- sdk_version: 5.4.0
8
- app_file: app.py
9
- pinned: false
10
- license: mit
11
- ---
12
-
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # LangGraph Agentic Chatbot
2
+
3
+ This repository contains a LangGraph-based chatbot that utilizes Gradio for interface rendering and integrates OpenAI's language model capabilities and custom tools for enhanced functionality. This project allowed me to learn and explore the capabilities of LangGraph and Agnetic workflows.
4
+
5
+ ## Tech Stack
6
+
7
+
8
+ - **Frontend:**
9
+
10
+ - **[Gradio](https://gradio.app/docs)**
11
+
12
+ - **Backend:**
13
+
14
+ - **[LangGraph](https://langgraph.dev/)**
15
+ - **[LangChain](https://python.langchain.com/en/latest/)**
16
+ - **[Python](https://www.python.org/)**
17
+
18
+ - **APIs:**
19
+
20
+ - **[OpenAI API](https://platform.openai.com/docs)**
21
+ - **[Tavily Search API](https://tavilyapi.com/docs)**
22
+
23
+ - **Version Control:**
24
+ - **[Git](https://git-scm.com/doc)**
25
+
26
+ ## Setup
27
+
28
+ To run this project locally, follow these steps:
29
+
30
+ 1. **Clone the repository:**
31
+ ```bash
32
+ git clone https://github.com/your-repo/langgraph-agentic-chatbot.git
33
+ ```
34
+ 2. **Navigate to the project directory:**
35
+ ```bash
36
+ cd langgraph-agentic-chatbot
37
+ ```
38
+ 3. **Install dependencies:**
39
+ ```bash
40
+ pip install -r requirements.txt
41
+ ```
42
+ 4. **Set up environment variables:**
43
+
44
+ Create a `.env` file in the root directory and add your OpenAI API key and LangChain API key to trace the chatbot's interactions.
45
+
46
+ ```
47
+ LANGCHAIN_TRACING_V2=true
48
+ LANGCHAIN_ENDPOINT="https://api.example.langchain.com"
49
+ LANGCHAIN_API_KEY="your_langchain_api_key"
50
+ LANGCHAIN_PROJECT="your_langchain_project"
51
+ ```
52
+
53
+ ```
54
+ OPENAI_API_KEY="your_openai_api_key"
55
+ TAVILY_API_KEY="your_tavily_api_key"
56
+ ```
57
+
58
+ 5. **Run the Gradio application:**
59
+
60
+ ```bash
61
+ gradio agent/main.py
62
+ ```
63
+
64
+ 6. **Access the chatbot:**
65
+
66
+ Open `http://localhost:7860` in your web browser to interact with the chatbot.
agent/README.md ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ ---
2
+ title: LangGraph Agentic Chatbot
3
+ app_file: agent/app.py
4
+ sdk: gradio
5
+ sdk_version: 5.4.0
6
+ ---
agent/app.py ADDED
@@ -0,0 +1,197 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import uuid
3
+ import logging
4
+ from dotenv import load_dotenv
5
+ import json
6
+ import gradio as gr
7
+ from langchain_community.tools.tavily_search import TavilySearchResults
8
+ from langchain_core.messages import AIMessage, HumanMessage
9
+ from typing_extensions import TypedDict
10
+ from typing import Annotated
11
+ from langchain_core.messages import ToolMessage
12
+ from langgraph.graph import StateGraph, START, END
13
+ from langgraph.graph.message import add_messages
14
+ from langchain_openai import ChatOpenAI as Chat
15
+
16
+ from uuid import uuid4
17
+
18
+ logging.basicConfig(
19
+ level=logging.INFO,
20
+ format="%(asctime)s [%(levelname)s] %(message)s",
21
+ )
22
+ logger = logging.getLogger(__name__)
23
+
24
+ # Load environment variables
25
+ load_dotenv()
26
+
27
+ # LangGraph setup
28
+ openai_api_key = os.getenv("OPENAI_API_KEY")
29
+ model = os.getenv("OPENAI_MODEL", "gpt-4")
30
+ temperature = float(os.getenv("OPENAI_TEMPERATURE", 0))
31
+
32
+ web_search = TavilySearchResults(max_results=2)
33
+ tools = [web_search]
34
+
35
+
36
+ class State(TypedDict):
37
+ messages: Annotated[list, add_messages]
38
+
39
+
40
+ graph_builder = StateGraph(State)
41
+
42
+
43
+ llm = Chat(
44
+ openai_api_key=openai_api_key,
45
+ model=model,
46
+ temperature=temperature
47
+ )
48
+ llm_with_tools = llm.bind_tools(tools)
49
+
50
+
51
+ def chatbot(state: State):
52
+ return {"messages": [llm_with_tools.invoke(state["messages"])]}
53
+
54
+
55
+ graph_builder.add_node("chatbot", chatbot)
56
+
57
+
58
+ class BasicToolNode:
59
+ """A node that runs the tools requested in the last AIMessage."""
60
+
61
+ def __init__(self, tools: list) -> None:
62
+ self.tools_by_name = {tool.name: tool for tool in tools}
63
+
64
+ def __call__(self, inputs: dict):
65
+ if messages := inputs.get("messages", []):
66
+ message = messages[-1]
67
+ else:
68
+ raise ValueError("No message found in input")
69
+ outputs = []
70
+ for tool_call in message.tool_calls:
71
+ tool_result = self.tools_by_name[tool_call["name"]].invoke(
72
+ tool_call["args"]
73
+ )
74
+ outputs.append(
75
+ ToolMessage(
76
+ content=json.dumps(tool_result),
77
+ name=tool_call["name"],
78
+ tool_call_id=tool_call["id"],
79
+ )
80
+ )
81
+ return {"messages": outputs}
82
+
83
+
84
+ def route_tools(
85
+ state: State,
86
+ ):
87
+ """
88
+ Use in the conditional_edge to route to the ToolNode if the last message
89
+ has tool calls. Otherwise, route to the end.
90
+ """
91
+ if isinstance(state, list):
92
+ ai_message = state[-1]
93
+ elif messages := state.get("messages", []):
94
+ ai_message = messages[-1]
95
+ else:
96
+ raise ValueError(
97
+ f"No messages found in input state to tool_edge: {state}")
98
+ if hasattr(ai_message, "tool_calls") and len(ai_message.tool_calls) > 0:
99
+ return "tools"
100
+ return END
101
+
102
+
103
+ tool_node = BasicToolNode(tools=[web_search])
104
+ graph_builder.add_node("tools", tool_node)
105
+ graph_builder.add_conditional_edges(
106
+ "chatbot",
107
+ route_tools,
108
+ # The following dictionary lets you tell the graph to interpret the condition's outputs as a specific node
109
+ # It defaults to the identity function, but if you
110
+ # want to use a node named something else apart from "tools",
111
+ # You can update the value of the dictionary to something else
112
+ # e.g., "tools": "my_tools"
113
+ {"tools": "tools", END: END},
114
+ )
115
+ # Any time a tool is called, we return to the chatbot to decide the next step
116
+ graph_builder.add_edge("tools", "chatbot")
117
+ graph_builder.add_edge(START, "chatbot")
118
+
119
+
120
+ def chatbot(state: State):
121
+ if not state["messages"]:
122
+ logger.info(
123
+ "Received an empty message list. Returning default response.")
124
+ return {"messages": [AIMessage(content="Hello! How can I assist you today?")]}
125
+
126
+ # Check for tool call in the last message
127
+ last_message = state["messages"][-1]
128
+ if not getattr(last_message, "tool_calls", None):
129
+ logger.info(
130
+ "No tool call in the last message. Proceeding without tool invocation.")
131
+ response = llm.invoke(state["messages"])
132
+ else:
133
+ logger.info(
134
+ "Tool call detected in the last message. Invoking tool response.")
135
+ response = llm_with_tools.invoke(state["messages"])
136
+
137
+ # Ensure the response is wrapped as AIMessage if it's not already
138
+ if not isinstance(response, AIMessage):
139
+ response = AIMessage(content=response.content)
140
+
141
+ return {"messages": [response]}
142
+
143
+
144
+ graph = graph_builder.compile()
145
+
146
+
147
+ def gradio_chat(message, history):
148
+ try:
149
+ if not isinstance(message, str):
150
+ message = str(message)
151
+
152
+ config = {
153
+ "configurable": {"thread_id": "1"},
154
+ "checkpoint_id": str(uuid4()),
155
+ "recursion_limit": 300
156
+ }
157
+
158
+ # Format the user message correctly as a HumanMessage
159
+ formatted_message = [HumanMessage(content=message)]
160
+ response = graph.invoke(
161
+ {
162
+ "messages": formatted_message
163
+ },
164
+ config=config,
165
+ stream_mode="values"
166
+ )
167
+
168
+ # Extract assistant messages and ensure they are AIMessage type
169
+ assistant_messages = [
170
+ msg for msg in response["messages"] if isinstance(msg, AIMessage)
171
+ ]
172
+ last_message = assistant_messages[-1] if assistant_messages else AIMessage(
173
+ content="No response generated.")
174
+
175
+ logger.info("Sending response back to Gradio interface.")
176
+ return last_message.content
177
+ except Exception as e:
178
+ logger.error(f"Error encountered in gradio_chat: {e}")
179
+ return "Sorry, I encountered an error. Please try again."
180
+
181
+
182
+ with gr.Blocks(theme=gr.themes.Default()) as demo:
183
+ chatbot = gr.ChatInterface(
184
+ chatbot=gr.Chatbot(height=800, render=False),
185
+ fn=gradio_chat,
186
+ multimodal=False,
187
+ title="LangGraph Agentic Chatbot",
188
+ examples=[
189
+ "What's the weather like today?",
190
+ "Show me the Movie Trailer for Doctor Strange.",
191
+ "Give me the latest news on the COVID-19 pandemic.",
192
+ "What are the latest updtaes on NVIDIA's new GPU?",
193
+ ],
194
+ )
195
+
196
+ if __name__ == "__main__":
197
+ demo.launch()
requirements.txt ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ aiofiles==23.2.1
2
+ aiohappyeyeballs==2.4.3
3
+ aiohttp==3.10.10
4
+ aiosignal==1.3.1
5
+ annotated-types==0.7.0
6
+ anthropic==0.37.1
7
+ anyio==4.6.2.post1
8
+ appnope==0.1.4
9
+ asttokens==2.4.1
10
+ attrs==24.2.0
11
+ certifi==2024.8.30
12
+ charset-normalizer==3.4.0
13
+ click==8.1.7
14
+ comm==0.2.2
15
+ dataclasses-json==0.6.7
16
+ debugpy==1.8.7
17
+ decorator==5.1.1
18
+ defusedxml==0.7.1
19
+ distro==1.9.0
20
+ executing==2.1.0
21
+ fastapi==0.115.4
22
+ ffmpy==0.4.0
23
+ filelock==3.16.1
24
+ frozenlist==1.5.0
25
+ fsspec==2024.10.0
26
+ gradio==5.4.0
27
+ gradio_client==1.4.2
28
+ h11==0.14.0
29
+ httpcore==1.0.6
30
+ httpx==0.27.2
31
+ httpx-sse==0.4.0
32
+ huggingface-hub==0.26.2
33
+ idna==3.10
34
+ ipykernel==6.29.5
35
+ ipython==8.29.0
36
+ jedi==0.19.1
37
+ Jinja2==3.1.4
38
+ jiter==0.6.1
39
+ jsonpatch==1.33
40
+ jsonpointer==3.0.0
41
+ jupyter_client==8.6.3
42
+ jupyter_core==5.7.2
43
+ langchain==0.3.6
44
+ langchain-anthropic==0.2.3
45
+ langchain-community==0.3.4
46
+ langchain-core==0.3.14
47
+ langchain-openai==0.2.4
48
+ langchain-text-splitters==0.3.1
49
+ langgraph==0.2.41
50
+ langgraph-checkpoint==2.0.2
51
+ langgraph-sdk==0.1.35
52
+ langsmith==0.1.138
53
+ markdown-it-py==3.0.0
54
+ MarkupSafe==2.1.5
55
+ marshmallow==3.23.0
56
+ matplotlib-inline==0.1.7
57
+ mdurl==0.1.2
58
+ msgpack==1.1.0
59
+ multidict==6.1.0
60
+ mypy-extensions==1.0.0
61
+ nest-asyncio==1.6.0
62
+ numpy==1.26.4
63
+ openai==1.53.0
64
+ orjson==3.10.10
65
+ packaging==24.1
66
+ pandas==2.2.3
67
+ parso==0.8.4
68
+ pexpect==4.9.0
69
+ pillow==11.0.0
70
+ platformdirs==4.3.6
71
+ prompt_toolkit==3.0.48
72
+ propcache==0.2.0
73
+ psutil==6.1.0
74
+ ptyprocess==0.7.0
75
+ pure_eval==0.2.3
76
+ pydantic==2.9.2
77
+ pydantic-settings==2.6.0
78
+ pydantic_core==2.23.4
79
+ pydub==0.25.1
80
+ Pygments==2.18.0
81
+ python-dateutil==2.9.0.post0
82
+ python-dotenv==1.0.1
83
+ python-multipart==0.0.12
84
+ pytz==2024.2
85
+ PyYAML==6.0.2
86
+ pyzmq==26.2.0
87
+ regex==2024.9.11
88
+ requests==2.32.3
89
+ requests-toolbelt==1.0.0
90
+ rich==13.9.3
91
+ ruff==0.7.1
92
+ safehttpx==0.1.1
93
+ semantic-version==2.10.0
94
+ shellingham==1.5.4
95
+ six==1.16.0
96
+ sniffio==1.3.1
97
+ SQLAlchemy==2.0.36
98
+ stack-data==0.6.3
99
+ starlette==0.41.2
100
+ tavily-python==0.5.0
101
+ tenacity==9.0.0
102
+ tiktoken==0.8.0
103
+ tokenizers==0.20.1
104
+ tomlkit==0.12.0
105
+ tornado==6.4.1
106
+ tqdm==4.66.6
107
+ traitlets==5.14.3
108
+ typer==0.12.5
109
+ typing-inspect==0.9.0
110
+ typing_extensions==4.12.2
111
+ tzdata==2024.2
112
+ urllib3==2.2.3
113
+ uvicorn==0.32.0
114
+ wcwidth==0.2.13
115
+ websockets==12.0
116
+ yarl==1.17.1
research/01_build_a_basic_chatbot.ipynb ADDED
@@ -0,0 +1,252 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": 1,
6
+ "metadata": {},
7
+ "outputs": [],
8
+ "source": [
9
+ "%%capture --no-stderr\n",
10
+ "%pip install -U langgraph langsmith langchain_anthropic"
11
+ ]
12
+ },
13
+ {
14
+ "cell_type": "code",
15
+ "execution_count": 8,
16
+ "metadata": {},
17
+ "outputs": [
18
+ {
19
+ "data": {
20
+ "text/plain": [
21
+ "True"
22
+ ]
23
+ },
24
+ "execution_count": 8,
25
+ "metadata": {},
26
+ "output_type": "execute_result"
27
+ }
28
+ ],
29
+ "source": [
30
+ "import os\n",
31
+ "from dotenv import load_dotenv\n",
32
+ "\n",
33
+ "load_dotenv()"
34
+ ]
35
+ },
36
+ {
37
+ "cell_type": "code",
38
+ "execution_count": 9,
39
+ "metadata": {},
40
+ "outputs": [],
41
+ "source": [
42
+ "openai_api_key = os.getenv(\"OPENAI_API_KEY\")\n",
43
+ "model = os.getenv(\"OPENAI_MODEL\", \"gpt-4o\")\n",
44
+ "temperature = float(os.getenv(\"OPENAI_TEMPERATURE\", 0))"
45
+ ]
46
+ },
47
+ {
48
+ "cell_type": "code",
49
+ "execution_count": 10,
50
+ "metadata": {},
51
+ "outputs": [],
52
+ "source": [
53
+ "from typing import Annotated\n",
54
+ "\n",
55
+ "from typing_extensions import TypedDict\n",
56
+ "\n",
57
+ "from langgraph.graph import StateGraph, START, END\n",
58
+ "from langgraph.graph.message import add_messages\n",
59
+ "\n",
60
+ "\n",
61
+ "class State(TypedDict):\n",
62
+ " # Messages have the type \"list\". The `add_messages` function\n",
63
+ " # in the annotation defines how this state key should be updated\n",
64
+ " # (in this case, it appends messages to the list, rather than overwriting them)\n",
65
+ " messages: Annotated[list, add_messages]\n",
66
+ "\n",
67
+ "\n",
68
+ "graph_builder = StateGraph(State)"
69
+ ]
70
+ },
71
+ {
72
+ "cell_type": "code",
73
+ "execution_count": 11,
74
+ "metadata": {},
75
+ "outputs": [
76
+ {
77
+ "data": {
78
+ "text/plain": [
79
+ "<langgraph.graph.state.StateGraph at 0x1107f6300>"
80
+ ]
81
+ },
82
+ "execution_count": 11,
83
+ "metadata": {},
84
+ "output_type": "execute_result"
85
+ }
86
+ ],
87
+ "source": [
88
+ "from langchain_openai import ChatOpenAI as Chat\n",
89
+ "\n",
90
+ "llm = Chat(\n",
91
+ " openai_api_key=openai_api_key,\n",
92
+ " model=model,\n",
93
+ " temperature=temperature\n",
94
+ ")\n",
95
+ "\n",
96
+ "\n",
97
+ "def chatbot(state: State):\n",
98
+ " return {\"messages\": [llm.invoke(state[\"messages\"])]}\n",
99
+ "\n",
100
+ "\n",
101
+ "# The first argument is the unique node name\n",
102
+ "# The second argument is the function or object that will be called whenever\n",
103
+ "# the node is used.\n",
104
+ "graph_builder.add_node(\"chatbot\", chatbot)"
105
+ ]
106
+ },
107
+ {
108
+ "cell_type": "code",
109
+ "execution_count": 12,
110
+ "metadata": {},
111
+ "outputs": [
112
+ {
113
+ "data": {
114
+ "text/plain": [
115
+ "<langgraph.graph.state.StateGraph at 0x1107f6300>"
116
+ ]
117
+ },
118
+ "execution_count": 12,
119
+ "metadata": {},
120
+ "output_type": "execute_result"
121
+ }
122
+ ],
123
+ "source": [
124
+ "graph_builder.add_edge(START, \"chatbot\")"
125
+ ]
126
+ },
127
+ {
128
+ "cell_type": "code",
129
+ "execution_count": 13,
130
+ "metadata": {},
131
+ "outputs": [
132
+ {
133
+ "data": {
134
+ "text/plain": [
135
+ "<langgraph.graph.state.StateGraph at 0x1107f6300>"
136
+ ]
137
+ },
138
+ "execution_count": 13,
139
+ "metadata": {},
140
+ "output_type": "execute_result"
141
+ }
142
+ ],
143
+ "source": [
144
+ "graph_builder.add_edge(\"chatbot\", END)"
145
+ ]
146
+ },
147
+ {
148
+ "cell_type": "code",
149
+ "execution_count": 14,
150
+ "metadata": {},
151
+ "outputs": [],
152
+ "source": [
153
+ "graph = graph_builder.compile()"
154
+ ]
155
+ },
156
+ {
157
+ "cell_type": "code",
158
+ "execution_count": 15,
159
+ "metadata": {},
160
+ "outputs": [
161
+ {
162
+ "data": {
163
+ "image/jpeg": "/9j/4AAQSkZJRgABAQAAAQABAAD/4gHYSUNDX1BST0ZJTEUAAQEAAAHIAAAAAAQwAABtbnRyUkdCIFhZWiAH4AABAAEAAAAAAABhY3NwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAA9tYAAQAAAADTLQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlkZXNjAAAA8AAAACRyWFlaAAABFAAAABRnWFlaAAABKAAAABRiWFlaAAABPAAAABR3dHB0AAABUAAAABRyVFJDAAABZAAAAChnVFJDAAABZAAAAChiVFJDAAABZAAAAChjcHJ0AAABjAAAADxtbHVjAAAAAAAAAAEAAAAMZW5VUwAAAAgAAAAcAHMAUgBHAEJYWVogAAAAAAAAb6IAADj1AAADkFhZWiAAAAAAAABimQAAt4UAABjaWFlaIAAAAAAAACSgAAAPhAAAts9YWVogAAAAAAAA9tYAAQAAAADTLXBhcmEAAAAAAAQAAAACZmYAAPKnAAANWQAAE9AAAApbAAAAAAAAAABtbHVjAAAAAAAAAAEAAAAMZW5VUwAAACAAAAAcAEcAbwBvAGcAbABlACAASQBuAGMALgAgADIAMAAxADb/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBT/wAARCADqAGsDASIAAhEBAxEB/8QAHQABAAMBAAMBAQAAAAAAAAAAAAUGBwQCAwgBCf/EAE0QAAEDAwEDBQkKDAQHAAAAAAECAwQABREGBxIhExUxQZQIFiJRVmGB0dMUFyMyNlRVcXSVJTVCUlNzkZKTsrO0YnKD0iRDREaxwfD/xAAaAQEBAAMBAQAAAAAAAAAAAAAAAQIDBAUH/8QAMxEAAgECAgcFCAIDAAAAAAAAAAECAxEEMRIUIVFxkaFBUmHB0RMjMjNTYoGSIkLh8PH/2gAMAwEAAhEDEQA/AP6p0pUFdrtLk3AWi0hIlhIXJmODebiIPRw/KcV+SnoABUrhupXnGLm7IuZMvyGozZcecQ0gdKlqCQPSajzqmyg4N3gA/aUeuuBnZ/ZSsPXCKL3MxhUq6gPrPHPAEbqPqQlI81dw0rZQMczwMfZUeqttqKzbY2H731WX6YgdpR66d9Vl+mIHaUeunerZfoeB2ZHqp3q2X6HgdmR6qe58ehdg76rL9MQO0o9dO+qy/TEDtKPXTvVsv0PA7Mj1U71bL9DwOzI9VPc+PQbB31WX6YgdpR66d9Vl+mIHaUeunerZfoeB2ZHqp3q2X6HgdmR6qe58eg2HTDu0G4EiLMjySOpl1K//AAa66gpmhNOTx8NY7epXU4mMhK0+dKgAQfODXG6iZosF9L8m6WMH4Zp9XKPw0/noV8ZxA6SlRUoDJBOAmmhCeyD27n6/8JZPItNK8W3EPNpcbUlaFAKSpJyCD0EGvKuch65D6IzDjzhwhtJWo+IAZNQGz9lR0xFuDwHuy6jnGQoZ4rcAIHH81O4geZAqauUT3fbpUXOOXaW3nxZBH/uorQUr3XouyrIKXERG2nEqGClxA3FpI8ykkeiuhbKLtvXmXsJ6lKVzkK7rraDp/ZrYxd9SXAW6Cp5EZtQaW6466s4Q2222lS1qODhKQTwPirN9Zd1NpnTE7Z+qMzPudp1VIlNmZHtkxbkdDLbpUQyhhS1L5RsIKMBQG8ojCSam+6FtNou2iIgu9q1LcBHuTEmJJ0lHU9cLdIQFFEptKcnweIOEq+PgpIJrIzO2gu6e2P631bp69XiTp7UM8zWods/Ca4LseTHjyXYjeSlZC2ytCRkb2cDiABs+s+6C0Fs9uceBqG+Ltkh6O3K+EgSVNstLJCFvLS2UsgkEZcKeg+KvfqfbnorR+pkaduV3d58ciNTm4EOBJluuMOLWhLiUstr3k5bVkj4uAVYBBOC7cxqvaBcda22XaNev2q56caRpS12Jl6NFdeejr5bnBaSkJWlwpSWn1BO4DhKiTVw2KafuidrsC9TbJcYTHvb2aB7pnQnGdyQl98usEqSMOJ8AqR0jwT1igLhst7oK1bTNbav001BnwplkujsFlbkCUGn222mlKcU6plLbat5xQDZVvEJChkKBrV6w/ZPIuGi9r+0jT1z09eko1BqBV6t94agrcty2FQmEkKkAbqFhTCk7qsEkpxnNbhQClKUBWNDYgtXWyJwGrRMMaOlOcJYU2h1pIz1JS4EDzIqz1WdJJ90XrVM9OeSeuAZbJGMhplttR8/hhweirNXRX+Y3wvxtt6leYqrvBWjblKlhtS7FNcL0jk0lSobxxvOED/lKxlRHxFZUcpUpSLRStcJ6N09qYKrqjZ7ozagxAk6g0/ZtUMsJUqI7OityUoSvG8UFQOArdTnHTgVAjubdlASU+9vpbdJBI5pYwT1fk+c1ZZOgrW4+4/DVLs7zhJWq2SVsJUScklsHcJJ45Kc9PHia9XeTI6tU34f6zPsq2aFJ5StxXpcbDw0hso0Xs/mPy9M6Us9glPt8k69bYTbC1ozndJSBkZAOKtdVfvJkeVV+/jM+yp3kyPKq/fxmfZU9nT7/AEYst5aKVlmsbddbHqbQsCLqm8GPebu7Cl8q6zvcmmBLfG58GPC32G/Hw3uHWLX3kyPKq/fxmfZU9nT7/Riy3kvqDTtr1XZ5NpvVujXW2SQA9DmNJdacAIUApKgQcEA/WBVJR3N2ylsko2caXSSCMi0sDgRgj4viNT/eTI8qr9/GZ9lTvJkeVV+/jM+yp7On3+jFlvIm0bAdmlgukW5W3QOnIFwiuJeYlRrYyhxpYOQpKgnIIPWKnrtf3JMly02Rbci653XXfjNQUnpW7/ix8VvpUcdCd5Sec6CZkcJt5vU9s8C05OU0lX18luZHm6D11PW62RLRERFhRmokdOSG2UBIyek8Os9Z66e7htT0n0GxHhZrTHsVqi2+KFBiOgISVneUrxqUetROST1kk120pWhtyd3mQUpSoBSlKAUpSgM/2kFI1zsp3iQTqKRu4HSeaLh5x1Z8f1dY0Cs/2kZ7+NlOCnHfDIzvAZ/FFw6M8c/VxxnqzWgUApSlAKUpQClKUApSlAKUpQClKUBnu0oA662T5UlONRyMBQ4q/BFx4Dh09fV0H6q0Ks92l47+tk2SQe+ORjwc5/A9x/Z/9460KgFKUoBSlKAUpSgFKVXL9qiRFn822mG3PuCUJdeL7xaZYQokJ3lBKiVHBwkDoGSU5GdkISqO0S5ljpVI591h8wsfa3vZ0591h8wsfa3vZ10arPeuaFi70qkc+6w+YWPtb3s6c+6w+YWPtb3s6arPeuaFj5R7pru3JmybbVaNPXTZ2685pq5KuMaQ3dRu3Bl2HIYQpILB3D/xGTgnBQpOTxNfZ2kL1I1JpOyXaZb12mXPgsSnoDi99UZa20qU0VYGSkkpzgZx0CsA2x9z+9tr11ovVF7t9mTM03I5QtokOKTNaB30suZa+KFje4fnKHXka/z7rD5hY+1vezpqs965oWLvSqRz7rD5hY+1vezpz7rD5hY+1vezpqs965oWLvSqRz7rD5hY+1vezr9Gr75aQZF5tkHm1HF5+3yXHHGU/nltTY3kjpODkAcAropqtTss/wAoWLtSvFC0uIStCgpKhkKByCK8q4yCqHAOda6sz1Pxx6Pc6PWavlUKB8tdW/r4/wDbt124X+/DzRV2k1SlK3EFKh4+rrTK1XN001L3r1DiNTn4vJrG4y4paW1b2N05LaxgHIxxAyKmKgFK4Z18t9sm2+HLmsRpdwdUzEYdcCVyFpQpakoHSohKVKOOgA1y23V1pu+orzYokvlbrZwwZ0fk1p5EPJKmvCICVZCSfBJxjjigJilK4Zl8t9vuNvgSZrDE64KWiJGccAcfKEFa9xPSrdSCTjoFUHdXBqAA2G5AgEGM7wP+Q131wX/8RXL7M5/Kazh8SKsyb0goq0nZSTkmCwSf9NNS9Q+jvkjZPsLH9NNTFedV+ZLiw8xVCgfLXVv6+P8A27dX2qFA+Wurf18f+3browv9+Hmgu0mqwq5RbhtX276t0xO1Pe9PWXTVtgOxINinqguS3JAdUt9biMLUlHJpQE53c5yOPHdapWudjGjtpFyi3G/2f3TcYzRYbmxpT0V/kiclsuMrQpSM5O6okcTw41sauQyCfs2Vqvuh9TWvvq1HaxC0da0CZbLgY8h9wPS0pcdcQAVkYJxwSoqOQeGK7btaX7bLojZlbosnUcrWcvTfO85Vov5skVLe8GhIfdQ2tS1laTutpSU8VlQxivpOxbO9PaZupuVstqYkw26Pad9DqyBFYKiy2ElRSAnfVxAyc8ScCq3I7nbZ7JtVitytPlMSyRVQYSWpshtSY5OVMrWlwKdbJGShwqB8VY6LBgMQStsFn7me7aku11Tcp782NKl225PQ1rUiFJ+ECmlJ3VqLYypOCQpSegkVal7OhqzbVtj5PV2oNLuW6FaCzMtdyWwEqERwhx79KE7vELyCCrrOa12XsI0LM0bC0quwpRYYMtU6HFZkvNGI8VKUVMuJWFtcVrwEKAAUQBjhXBeu5r2c6hlKk3DT65Dy2WYzq+cZSeXaabS2227h0cqkJSBuryDxJySSZosGQbNNU6k7oa8aUt2or9eNORhoqLfHGrDMVAdnynn3GlPKW3hW4kNJIQPBy7xyMCq7ZWZG1q/bDntQX28vyhP1FaedLdc3oS5bcVLyG30qZUnC1pbG8pOCrBB4cK+mNYbF9Ga7atqLvZEK5tZMeGuE+7DWyyQAWkrYWhXJkJHgZ3eA4UvmxbRWodL2fTsuwsotFnUlduZhuuRVRFJSUgtuNKStPAkHB45Oc00WC6pTupCck4GMk5NcN/8AxFcvszn8prqiRW4MRmMyClllCW0AqKiEgYHE8TwHSa5b/wDiK5fZnP5TXRD4kVZk1o75I2T7Cx/TTUxUPo75I2T7Cx/TTUxXnVfmS4sPMVQoHy11b+vj/wBu3V9qo3yzXG3XqRdrXFFxRLShMmHyobcCkDCXEFR3Tw4FJI6AQeo78NJJyTeat1T8gjrpUJztfvIy69qhe3pztfvIy69qhe3rr0PuX7L1LYm6VCc7X7yMuvaoXt6c7X7yMuvaoXt6aH3L9l6ixN0qp3TW8+zT7RCmaUurUm7SVQ4SOXiK5V1LLj5TkPEJ+DZcVk4Hg46SAZHna/eRl17VC9vTQ+5fsvUWJulQnO1+8jLr2qF7enO1+8jLr2qF7emh9y/ZeosTdcF//EVy+zOfymuPna/eRl17VC9vXi9H1BqSO7bzZHrIxIQpp6ZMkMrU2gjBKEtLXlWDwyQB08cYOUYqLTclbivUWLRo75I2T7Cx/TTUxXqixm4UVmOyndaaQG0J8SQMAV7a8mb0pOW8xFKUrAClKUApSlAUHaKnOttlhxnGoJBzu5x+CZ/mOP2j6+ODfqz/AGkI3tc7KTuqO7qKQchOQPwRcBk8eHT08ekePNaBQClKUApSlAKUpQClKUApSlAKUpQGe7Sika62TZOCdRyMeCDk8z3H9n1+jrrQqoG0cLOuNlW6XABqGRvbgyCOabh8bxDOPTir/QClKUApSlAKUpQClKUApX4pQQkqUQlIGSScACq5J2laSiOqbe1PZ23EnCkGc1lP1je4VshTnU+BN8C2byLJSqr76ujfKqz9tb9dPfV0b5VWftrfrrZq1fuPky6L3FA2obVNERdoOzliRq+wMyLbqKT7racubCVRSLXPbPKArBR4Sgnwh0qAxk8Nigzo10hR5kOQ1LhyG0vMyGFhbbqFDKVJUOBBBBBHAg1/ODuztgVj2lbfNL3/AEpe7WYGpnkRr4+xJbKIS0YBkrwcBKmx6VIPWoZ+69N612f6T07a7HbdS2di3WyK1CjNe7mzuNNoCEDp6kpFNWr9x8mNF7i90qq++ro3yqs/bW/XX6NqmjSflVZh5zObA/mpq1fuPkyaL3FppXHbLxAvUfl7dNjT2P0sZ1Lif2pJFdlaGnF2ZBSlKgFRuo9QQ9LWeRcpylJYZA8FAytaicJQkdaiSAPrqSrGdud0XIv9ltIVhhhlyc4j85ZPJtn0Dlf3h4q7sFh9arxpPLt4IqKfqjUdx1tKW7dXD7kKiWrahZ5BtPVvDocV/iUOnOAkcKjkNpaSEoSEJHQEjAFftK+jwhGlFQgrJGDbYpSqDets9pssu4g2y8TbZbHCzPvEOIHIkVacb4UreCjuZ8IoSoJ454g1J1I01eTsQv1Kzy97bbVZp99jJtF5uTdjDblwlQYyFsstLZS6Hd4rG8ndVxCQVeCTu4wT3X7avbLRc4duhQLnqKdIiidyFmjh1TUc8EurKlJACuOBkqODgVh7ent25AutKpOxXUlw1dst09eLrIMq4S2Ct54tpRvHfUPipAA4AdAq7VshNVIqaye0HhHbMGYmZDccgzUkESYquTc+okdI8xyD1its2Z7RFaoQq2XLcRemG+U3kDdTJbBA5RI6iCUhQ6iQRwOBi1eyDdF2G9Wq6tq3FRJbSlHxtqUEOJ9KFK9OPFXDjsHDF0mmv5LJ+XAzTvsZ9RUpSvnAFYptxgLjars88hRZlRHIu91JWhW+kfWQtZH+Q1tdQesdKRtZWJ23SFFpWQ4w+lOVMup+KsDr8RHWCR116GAxCwuIjUll2/kqPnRa0tIUtaghCRlSlHAA8Zqqe+7oU/8AemnvvVj/AH1crxbpenLkbbdmRFlkkI4/BvpH5Tavyh5ukZwQK4/cMY/9O1+4K+h3c0pU2rP8+ZhaxWffd0L5a6d+9WP99ZZA2SqsuoL0xM2bWjWcW43R2dGvrzsdJbZeXvqQ6HAVkoJVgpCgoY6K3n3FH/QNfuCvdWqdD2tnUeXh63Blb2hLshe1xDEBKGL3EbZtaUuIAe3YAZ3QM+BhY3fCx4+jjUbp3TerdnmoGblC06L8xdLJbocxpE1pl2FIjNqTxKzhSCFnJSScjoPXs1Kjw0bqSbTV+rb3eLBlmy++WnZfs609p3Vt6tGn75FjEvQZtyYStGVqIPx+IPjFWf33dC+WunfvVj/fVocjMuq3ltIWrxqSCa8fcMb5u1+4KzjCcIqEWrLw/wAg47FqW0aojOSLNdYV2jtr5NbsGQh5KVYB3SUkgHBBx56km4C7vcLdbWgVOTZbLACekJ3wVn0IC1fUDXpKmIe4gBLZcUEobQnwlqPQEpHEnzCtg2V7PH7U+L9d2uSnqbLcaIrBMdCulSv8agB/lGR1qrRi8VHCUXOb/l2eL/3MyjvNMpSlfNgKUpQHJdLTBvcNcS4Q2J0VfxmZDYcQfQeFVB7Ylo91RULfJYz+SxcZLafQlLgA9Aq9UrfTxFajspza4Not2ig+8bpH5rP+9pftae8bpH5rP+9pftav1K369ivqy5sXZQfeN0j81n/e0v2tPeN0j81n/e0v2tX6lNexX1Zc2LsoPvG6R+az/vaX7Wv0bDtIA8Yk8jxG7S/a1faU17FfVlzYuyB09oPT+lXC7a7UxGfI3TIIK3iPEXFEqI9NT1KVyTnKo9Kbu/EmYpSlYA//2Q==",
164
+ "text/plain": [
165
+ "<IPython.core.display.Image object>"
166
+ ]
167
+ },
168
+ "metadata": {},
169
+ "output_type": "display_data"
170
+ }
171
+ ],
172
+ "source": [
173
+ "from IPython.display import Image, display\n",
174
+ "\n",
175
+ "try:\n",
176
+ " display(Image(graph.get_graph().draw_mermaid_png()))\n",
177
+ "except Exception:\n",
178
+ " # This requires some extra dependencies and is optional\n",
179
+ " pass"
180
+ ]
181
+ },
182
+ {
183
+ "cell_type": "code",
184
+ "execution_count": 16,
185
+ "metadata": {},
186
+ "outputs": [
187
+ {
188
+ "name": "stdout",
189
+ "output_type": "stream",
190
+ "text": [
191
+ "Assistant: Sentry is a fictional superhero appearing in American comic books published by Marvel Comics. Created by writer Paul Jenkins and artist Jae Lee, the character first appeared in \"The Sentry\" #1 in 2000. The Sentry, whose real name is Robert Reynolds, is often depicted as one of the most powerful beings in the Marvel Universe.\n",
192
+ "\n",
193
+ "The character has a complex and intriguing backstory. Robert Reynolds was a regular man who gained his powers after ingesting a special serum, which granted him the power of \"a million exploding suns.\" This made him incredibly strong, fast, and nearly invulnerable, with abilities such as flight, superhuman strength, and energy projection. However, his immense power comes with a significant drawback: he has a dark alter ego known as the Void, which represents his fears and insecurities and poses a threat to the world.\n",
194
+ "\n",
195
+ "The Sentry's stories often explore themes of mental health, identity, and the duality of human nature. Despite his power, he struggles with psychological issues, including agoraphobia and schizophrenia, which add depth to his character and create tension in his interactions with other superheroes in the Marvel Universe.\n",
196
+ "Goodbye!\n"
197
+ ]
198
+ }
199
+ ],
200
+ "source": [
201
+ "def stream_graph_updates(user_input: str):\n",
202
+ " for event in graph.stream({\"messages\": [(\"user\", user_input)]}):\n",
203
+ " for value in event.values():\n",
204
+ " print(\"Assistant:\", value[\"messages\"][-1].content)\n",
205
+ "\n",
206
+ "\n",
207
+ "while True:\n",
208
+ " try:\n",
209
+ " user_input = input(\"User: \")\n",
210
+ " if user_input.lower() in [\"quit\", \"exit\", \"q\"]:\n",
211
+ " print(\"Goodbye!\")\n",
212
+ " break\n",
213
+ "\n",
214
+ " stream_graph_updates(user_input)\n",
215
+ " except:\n",
216
+ " # fallback if input() is not available\n",
217
+ " user_input = \"What do you know about LangGraph?\"\n",
218
+ " print(\"User: \" + user_input)\n",
219
+ " stream_graph_updates(user_input)\n",
220
+ " break"
221
+ ]
222
+ },
223
+ {
224
+ "cell_type": "code",
225
+ "execution_count": null,
226
+ "metadata": {},
227
+ "outputs": [],
228
+ "source": []
229
+ }
230
+ ],
231
+ "metadata": {
232
+ "kernelspec": {
233
+ "display_name": ".venv",
234
+ "language": "python",
235
+ "name": "python3"
236
+ },
237
+ "language_info": {
238
+ "codemirror_mode": {
239
+ "name": "ipython",
240
+ "version": 3
241
+ },
242
+ "file_extension": ".py",
243
+ "mimetype": "text/x-python",
244
+ "name": "python",
245
+ "nbconvert_exporter": "python",
246
+ "pygments_lexer": "ipython3",
247
+ "version": "3.12.7"
248
+ }
249
+ },
250
+ "nbformat": 4,
251
+ "nbformat_minor": 2
252
+ }
research/02_enhancing_chatbot_with_tools.ipynb ADDED
@@ -0,0 +1,304 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": 1,
6
+ "metadata": {},
7
+ "outputs": [],
8
+ "source": [
9
+ "%%capture --no-stderr\n",
10
+ "%pip install -U tavily-python langchain_community"
11
+ ]
12
+ },
13
+ {
14
+ "cell_type": "code",
15
+ "execution_count": 2,
16
+ "metadata": {},
17
+ "outputs": [
18
+ {
19
+ "data": {
20
+ "text/plain": [
21
+ "True"
22
+ ]
23
+ },
24
+ "execution_count": 2,
25
+ "metadata": {},
26
+ "output_type": "execute_result"
27
+ }
28
+ ],
29
+ "source": [
30
+ "import os\n",
31
+ "from dotenv import load_dotenv\n",
32
+ "\n",
33
+ "load_dotenv()"
34
+ ]
35
+ },
36
+ {
37
+ "cell_type": "code",
38
+ "execution_count": 3,
39
+ "metadata": {},
40
+ "outputs": [],
41
+ "source": [
42
+ "openai_api_key = os.getenv(\"OPENAI_API_KEY\")\n",
43
+ "model = os.getenv(\"OPENAI_MODEL\", \"gpt-4o\")\n",
44
+ "temperature = float(os.getenv(\"OPENAI_TEMPERATURE\", 0))"
45
+ ]
46
+ },
47
+ {
48
+ "cell_type": "code",
49
+ "execution_count": 4,
50
+ "metadata": {},
51
+ "outputs": [
52
+ {
53
+ "data": {
54
+ "text/plain": [
55
+ "[{'url': 'https://medium.com/@cplog/introduction-to-langgraph-a-beginners-guide-14f9be027141',\n",
56
+ " 'content': 'Nodes: Nodes are the building blocks of your LangGraph. Each node represents a function or a computation step. You define nodes to perform specific tasks, such as processing input, making'},\n",
57
+ " {'url': 'https://www.datacamp.com/tutorial/langgraph-tutorial',\n",
58
+ " 'content': \"In LangGraph, each node represents an LLM agent, and the edges are the communication channels between these agents. This structure allows for clear and manageable workflows, where each agent performs specific tasks and passes information to other agents as needed. State management. One of LangGraph's standout features is its automatic state\"}]"
59
+ ]
60
+ },
61
+ "execution_count": 4,
62
+ "metadata": {},
63
+ "output_type": "execute_result"
64
+ }
65
+ ],
66
+ "source": [
67
+ "from langchain_community.tools.tavily_search import TavilySearchResults\n",
68
+ "\n",
69
+ "tool = TavilySearchResults(max_results=2)\n",
70
+ "tools = [tool]\n",
71
+ "tool.invoke(\"What's a 'node' in LangGraph?\")"
72
+ ]
73
+ },
74
+ {
75
+ "cell_type": "code",
76
+ "execution_count": 5,
77
+ "metadata": {},
78
+ "outputs": [
79
+ {
80
+ "data": {
81
+ "text/plain": [
82
+ "<langgraph.graph.state.StateGraph at 0x10400f4d0>"
83
+ ]
84
+ },
85
+ "execution_count": 5,
86
+ "metadata": {},
87
+ "output_type": "execute_result"
88
+ }
89
+ ],
90
+ "source": [
91
+ "from langchain_openai import ChatOpenAI as Chat\n",
92
+ "from typing import Annotated\n",
93
+ "from typing_extensions import TypedDict\n",
94
+ "\n",
95
+ "from langgraph.graph import StateGraph, START, END\n",
96
+ "from langgraph.graph.message import add_messages\n",
97
+ "\n",
98
+ "\n",
99
+ "class State(TypedDict):\n",
100
+ " messages: Annotated[list, add_messages]\n",
101
+ "\n",
102
+ "\n",
103
+ "graph_builder = StateGraph(State)\n",
104
+ "\n",
105
+ "\n",
106
+ "llm = Chat(\n",
107
+ " openai_api_key=openai_api_key,\n",
108
+ " model=model,\n",
109
+ " temperature=temperature\n",
110
+ ")\n",
111
+ "# Modification: tell the LLM which tools it can call\n",
112
+ "llm_with_tools = llm.bind_tools(tools)\n",
113
+ "\n",
114
+ "\n",
115
+ "def chatbot(state: State):\n",
116
+ " return {\"messages\": [llm_with_tools.invoke(state[\"messages\"])]}\n",
117
+ "\n",
118
+ "\n",
119
+ "graph_builder.add_node(\"chatbot\", chatbot)"
120
+ ]
121
+ },
122
+ {
123
+ "cell_type": "code",
124
+ "execution_count": 6,
125
+ "metadata": {},
126
+ "outputs": [
127
+ {
128
+ "data": {
129
+ "text/plain": [
130
+ "<langgraph.graph.state.StateGraph at 0x10400f4d0>"
131
+ ]
132
+ },
133
+ "execution_count": 6,
134
+ "metadata": {},
135
+ "output_type": "execute_result"
136
+ }
137
+ ],
138
+ "source": [
139
+ "import json\n",
140
+ "\n",
141
+ "from langchain_core.messages import ToolMessage\n",
142
+ "\n",
143
+ "\n",
144
+ "class BasicToolNode:\n",
145
+ " \"\"\"A node that runs the tools requested in the last AIMessage.\"\"\"\n",
146
+ "\n",
147
+ " def __init__(self, tools: list) -> None:\n",
148
+ " self.tools_by_name = {tool.name: tool for tool in tools}\n",
149
+ "\n",
150
+ " def __call__(self, inputs: dict):\n",
151
+ " if messages := inputs.get(\"messages\", []):\n",
152
+ " message = messages[-1]\n",
153
+ " else:\n",
154
+ " raise ValueError(\"No message found in input\")\n",
155
+ " outputs = []\n",
156
+ " for tool_call in message.tool_calls:\n",
157
+ " tool_result = self.tools_by_name[tool_call[\"name\"]].invoke(\n",
158
+ " tool_call[\"args\"]\n",
159
+ " )\n",
160
+ " outputs.append(\n",
161
+ " ToolMessage(\n",
162
+ " content=json.dumps(tool_result),\n",
163
+ " name=tool_call[\"name\"],\n",
164
+ " tool_call_id=tool_call[\"id\"],\n",
165
+ " )\n",
166
+ " )\n",
167
+ " return {\"messages\": outputs}\n",
168
+ "\n",
169
+ "\n",
170
+ "tool_node = BasicToolNode(tools=[tool])\n",
171
+ "graph_builder.add_node(\"tools\", tool_node)"
172
+ ]
173
+ },
174
+ {
175
+ "cell_type": "code",
176
+ "execution_count": 7,
177
+ "metadata": {},
178
+ "outputs": [],
179
+ "source": [
180
+ "from typing import Literal\n",
181
+ "\n",
182
+ "\n",
183
+ "def route_tools(\n",
184
+ " state: State,\n",
185
+ "):\n",
186
+ " \"\"\"\n",
187
+ " Use in the conditional_edge to route to the ToolNode if the last message\n",
188
+ " has tool calls. Otherwise, route to the end.\n",
189
+ " \"\"\"\n",
190
+ " if isinstance(state, list):\n",
191
+ " ai_message = state[-1]\n",
192
+ " elif messages := state.get(\"messages\", []):\n",
193
+ " ai_message = messages[-1]\n",
194
+ " else:\n",
195
+ " raise ValueError(\n",
196
+ " f\"No messages found in input state to tool_edge: {state}\")\n",
197
+ " if hasattr(ai_message, \"tool_calls\") and len(ai_message.tool_calls) > 0:\n",
198
+ " return \"tools\"\n",
199
+ " return END\n",
200
+ "\n",
201
+ "\n",
202
+ "# The `tools_condition` function returns \"tools\" if the chatbot asks to use a tool, and \"END\" if\n",
203
+ "# it is fine directly responding. This conditional routing defines the main agent loop.\n",
204
+ "graph_builder.add_conditional_edges(\n",
205
+ " \"chatbot\",\n",
206
+ " route_tools,\n",
207
+ " # The following dictionary lets you tell the graph to interpret the condition's outputs as a specific node\n",
208
+ " # It defaults to the identity function, but if you\n",
209
+ " # want to use a node named something else apart from \"tools\",\n",
210
+ " # You can update the value of the dictionary to something else\n",
211
+ " # e.g., \"tools\": \"my_tools\"\n",
212
+ " {\"tools\": \"tools\", END: END},\n",
213
+ ")\n",
214
+ "# Any time a tool is called, we return to the chatbot to decide the next step\n",
215
+ "graph_builder.add_edge(\"tools\", \"chatbot\")\n",
216
+ "graph_builder.add_edge(START, \"chatbot\")\n",
217
+ "graph = graph_builder.compile()"
218
+ ]
219
+ },
220
+ {
221
+ "cell_type": "code",
222
+ "execution_count": 8,
223
+ "metadata": {},
224
+ "outputs": [
225
+ {
226
+ "data": {
227
+ "image/jpeg": "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",
228
+ "text/plain": [
229
+ "<IPython.core.display.Image object>"
230
+ ]
231
+ },
232
+ "metadata": {},
233
+ "output_type": "display_data"
234
+ }
235
+ ],
236
+ "source": [
237
+ "from IPython.display import Image, display\n",
238
+ "\n",
239
+ "try:\n",
240
+ " display(Image(graph.get_graph().draw_mermaid_png()))\n",
241
+ "except Exception:\n",
242
+ " # This requires some extra dependencies and is optional\n",
243
+ " pass"
244
+ ]
245
+ },
246
+ {
247
+ "cell_type": "code",
248
+ "execution_count": 12,
249
+ "metadata": {},
250
+ "outputs": [
251
+ {
252
+ "name": "stdout",
253
+ "output_type": "stream",
254
+ "text": [
255
+ "Assistant: \n",
256
+ "Assistant: [{\"url\": \"https://langchain-ai.github.io/langgraph/\", \"content\": \"LangGraph is a framework for creating stateful, multi-actor applications with LLMs, using cycles, controllability, and persistence. Learn how to use LangGraph with LangChain, LangSmith, and Anthropic tools to build agent and multi-agent workflows.\"}, {\"url\": \"https://langchain-ai.github.io/langgraph/tutorials/\", \"content\": \"LangGraph is a framework for building language agents as graphs. Learn how to use LangGraph to create chatbots, code assistants, planning agents, reflection agents, and more with these notebooks.\"}]\n",
257
+ "Assistant: LangGraph is a framework designed for creating stateful, multi-actor applications using large language models (LLMs). It emphasizes the use of cycles, controllability, and persistence to build complex workflows. LangGraph can be integrated with tools like LangChain, LangSmith, and Anthropic to develop agent and multi-agent workflows. It is particularly useful for building language agents as graphs, which can include applications such as chatbots, code assistants, planning agents, and reflection agents. You can find more information and tutorials on how to use LangGraph on their [official website](https://langchain-ai.github.io/langgraph/).\n"
258
+ ]
259
+ }
260
+ ],
261
+ "source": [
262
+ "def stream_graph_updates(user_input: str):\n",
263
+ " for event in graph.stream({\"messages\": [(\"user\", user_input)]}):\n",
264
+ " for value in event.values():\n",
265
+ " print(\"Assistant:\", value[\"messages\"][-1].content)\n",
266
+ "\n",
267
+ "\n",
268
+ "user_input = \"What do you know about LangGraph?\"\n",
269
+ "try:\n",
270
+ " stream_graph_updates(user_input)\n",
271
+ "except Exception as e:\n",
272
+ " print(f\"An error occurred: {e}\")\n"
273
+ ]
274
+ },
275
+ {
276
+ "cell_type": "code",
277
+ "execution_count": null,
278
+ "metadata": {},
279
+ "outputs": [],
280
+ "source": []
281
+ }
282
+ ],
283
+ "metadata": {
284
+ "kernelspec": {
285
+ "display_name": ".venv",
286
+ "language": "python",
287
+ "name": "python3"
288
+ },
289
+ "language_info": {
290
+ "codemirror_mode": {
291
+ "name": "ipython",
292
+ "version": 3
293
+ },
294
+ "file_extension": ".py",
295
+ "mimetype": "text/x-python",
296
+ "name": "python",
297
+ "nbconvert_exporter": "python",
298
+ "pygments_lexer": "ipython3",
299
+ "version": "3.12.7"
300
+ }
301
+ },
302
+ "nbformat": 4,
303
+ "nbformat_minor": 2
304
+ }
research/03_adding_memory_to_the_chatbot.ipynb ADDED
@@ -0,0 +1,415 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": 5,
6
+ "metadata": {},
7
+ "outputs": [],
8
+ "source": [
9
+ "%%capture --no-stderr\n",
10
+ "%pip install -U tavily-python langchain_community"
11
+ ]
12
+ },
13
+ {
14
+ "cell_type": "code",
15
+ "execution_count": 6,
16
+ "metadata": {},
17
+ "outputs": [
18
+ {
19
+ "data": {
20
+ "text/plain": [
21
+ "True"
22
+ ]
23
+ },
24
+ "execution_count": 6,
25
+ "metadata": {},
26
+ "output_type": "execute_result"
27
+ }
28
+ ],
29
+ "source": [
30
+ "import os\n",
31
+ "from dotenv import load_dotenv\n",
32
+ "\n",
33
+ "load_dotenv()"
34
+ ]
35
+ },
36
+ {
37
+ "cell_type": "code",
38
+ "execution_count": 7,
39
+ "metadata": {},
40
+ "outputs": [],
41
+ "source": [
42
+ "openai_api_key = os.getenv(\"OPENAI_API_KEY\")\n",
43
+ "model = os.getenv(\"OPENAI_MODEL\", \"gpt-4o\")\n",
44
+ "temperature = float(os.getenv(\"OPENAI_TEMPERATURE\", 0))"
45
+ ]
46
+ },
47
+ {
48
+ "cell_type": "code",
49
+ "execution_count": 8,
50
+ "metadata": {},
51
+ "outputs": [],
52
+ "source": [
53
+ "from langchain_community.tools.tavily_search import TavilySearchResults\n",
54
+ "\n",
55
+ "tool = TavilySearchResults(max_results=2)\n",
56
+ "tools = [tool]"
57
+ ]
58
+ },
59
+ {
60
+ "cell_type": "code",
61
+ "execution_count": 9,
62
+ "metadata": {},
63
+ "outputs": [
64
+ {
65
+ "data": {
66
+ "text/plain": [
67
+ "<langgraph.graph.state.StateGraph at 0x10cdb52b0>"
68
+ ]
69
+ },
70
+ "execution_count": 9,
71
+ "metadata": {},
72
+ "output_type": "execute_result"
73
+ }
74
+ ],
75
+ "source": [
76
+ "from langchain_openai import ChatOpenAI as Chat\n",
77
+ "from typing import Annotated\n",
78
+ "from typing_extensions import TypedDict\n",
79
+ "\n",
80
+ "from langgraph.graph import StateGraph, START, END\n",
81
+ "from langgraph.graph.message import add_messages\n",
82
+ "\n",
83
+ "\n",
84
+ "class State(TypedDict):\n",
85
+ " messages: Annotated[list, add_messages]\n",
86
+ "\n",
87
+ "\n",
88
+ "graph_builder = StateGraph(State)\n",
89
+ "\n",
90
+ "\n",
91
+ "llm = Chat(\n",
92
+ " openai_api_key=openai_api_key,\n",
93
+ " model=model,\n",
94
+ " temperature=temperature\n",
95
+ ")\n",
96
+ "# Modification: tell the LLM which tools it can call\n",
97
+ "llm_with_tools = llm.bind_tools(tools)\n",
98
+ "\n",
99
+ "\n",
100
+ "def chatbot(state: State):\n",
101
+ " return {\"messages\": [llm_with_tools.invoke(state[\"messages\"])]}\n",
102
+ "\n",
103
+ "\n",
104
+ "graph_builder.add_node(\"chatbot\", chatbot)"
105
+ ]
106
+ },
107
+ {
108
+ "cell_type": "code",
109
+ "execution_count": 10,
110
+ "metadata": {},
111
+ "outputs": [
112
+ {
113
+ "data": {
114
+ "text/plain": [
115
+ "<langgraph.graph.state.StateGraph at 0x10cdb52b0>"
116
+ ]
117
+ },
118
+ "execution_count": 10,
119
+ "metadata": {},
120
+ "output_type": "execute_result"
121
+ }
122
+ ],
123
+ "source": [
124
+ "import json\n",
125
+ "\n",
126
+ "from langchain_core.messages import ToolMessage\n",
127
+ "\n",
128
+ "\n",
129
+ "class BasicToolNode:\n",
130
+ " \"\"\"A node that runs the tools requested in the last AIMessage.\"\"\"\n",
131
+ "\n",
132
+ " def __init__(self, tools: list) -> None:\n",
133
+ " self.tools_by_name = {tool.name: tool for tool in tools}\n",
134
+ "\n",
135
+ " def __call__(self, inputs: dict):\n",
136
+ " if messages := inputs.get(\"messages\", []):\n",
137
+ " message = messages[-1]\n",
138
+ " else:\n",
139
+ " raise ValueError(\"No message found in input\")\n",
140
+ " outputs = []\n",
141
+ " for tool_call in message.tool_calls:\n",
142
+ " tool_result = self.tools_by_name[tool_call[\"name\"]].invoke(\n",
143
+ " tool_call[\"args\"]\n",
144
+ " )\n",
145
+ " outputs.append(\n",
146
+ " ToolMessage(\n",
147
+ " content=json.dumps(tool_result),\n",
148
+ " name=tool_call[\"name\"],\n",
149
+ " tool_call_id=tool_call[\"id\"],\n",
150
+ " )\n",
151
+ " )\n",
152
+ " return {\"messages\": outputs}\n",
153
+ "\n",
154
+ "\n",
155
+ "tool_node = BasicToolNode(tools=[tool])\n",
156
+ "graph_builder.add_node(\"tools\", tool_node)"
157
+ ]
158
+ },
159
+ {
160
+ "cell_type": "code",
161
+ "execution_count": 11,
162
+ "metadata": {},
163
+ "outputs": [
164
+ {
165
+ "data": {
166
+ "text/plain": [
167
+ "<langgraph.graph.state.StateGraph at 0x10cdb52b0>"
168
+ ]
169
+ },
170
+ "execution_count": 11,
171
+ "metadata": {},
172
+ "output_type": "execute_result"
173
+ }
174
+ ],
175
+ "source": [
176
+ "from typing import Literal\n",
177
+ "\n",
178
+ "\n",
179
+ "def route_tools(\n",
180
+ " state: State,\n",
181
+ "):\n",
182
+ " \"\"\"\n",
183
+ " Use in the conditional_edge to route to the ToolNode if the last message\n",
184
+ " has tool calls. Otherwise, route to the end.\n",
185
+ " \"\"\"\n",
186
+ " if isinstance(state, list):\n",
187
+ " ai_message = state[-1]\n",
188
+ " elif messages := state.get(\"messages\", []):\n",
189
+ " ai_message = messages[-1]\n",
190
+ " else:\n",
191
+ " raise ValueError(\n",
192
+ " f\"No messages found in input state to tool_edge: {state}\")\n",
193
+ " if hasattr(ai_message, \"tool_calls\") and len(ai_message.tool_calls) > 0:\n",
194
+ " return \"tools\"\n",
195
+ " return END\n",
196
+ "\n",
197
+ "\n",
198
+ "# The `tools_condition` function returns \"tools\" if the chatbot asks to use a tool, and \"END\" if\n",
199
+ "# it is fine directly responding. This conditional routing defines the main agent loop.\n",
200
+ "graph_builder.add_conditional_edges(\n",
201
+ " \"chatbot\",\n",
202
+ " route_tools,\n",
203
+ " # The following dictionary lets you tell the graph to interpret the condition's outputs as a specific node\n",
204
+ " # It defaults to the identity function, but if you\n",
205
+ " # want to use a node named something else apart from \"tools\",\n",
206
+ " # You can update the value of the dictionary to something else\n",
207
+ " # e.g., \"tools\": \"my_tools\"\n",
208
+ " {\"tools\": \"tools\", END: END},\n",
209
+ ")\n",
210
+ "# Any time a tool is called, we return to the chatbot to decide the next step\n",
211
+ "graph_builder.add_edge(\"tools\", \"chatbot\")\n",
212
+ "graph_builder.add_edge(START, \"chatbot\")"
213
+ ]
214
+ },
215
+ {
216
+ "cell_type": "code",
217
+ "execution_count": 12,
218
+ "metadata": {},
219
+ "outputs": [],
220
+ "source": [
221
+ "from IPython.display import Image, display\n",
222
+ "\n",
223
+ "try:\n",
224
+ " display(Image(graph.get_graph().draw_mermaid_png()))\n",
225
+ "except Exception:\n",
226
+ " # This requires some extra dependencies and is optional\n",
227
+ " pass"
228
+ ]
229
+ },
230
+ {
231
+ "cell_type": "code",
232
+ "execution_count": 13,
233
+ "metadata": {},
234
+ "outputs": [],
235
+ "source": [
236
+ "from langgraph.checkpoint.memory import MemorySaver\n",
237
+ "\n",
238
+ "memory = MemorySaver()"
239
+ ]
240
+ },
241
+ {
242
+ "cell_type": "code",
243
+ "execution_count": 14,
244
+ "metadata": {},
245
+ "outputs": [],
246
+ "source": [
247
+ "graph = graph_builder.compile(checkpointer=memory)"
248
+ ]
249
+ },
250
+ {
251
+ "cell_type": "code",
252
+ "execution_count": 15,
253
+ "metadata": {},
254
+ "outputs": [
255
+ {
256
+ "data": {
257
+ "image/jpeg": "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",
258
+ "text/plain": [
259
+ "<IPython.core.display.Image object>"
260
+ ]
261
+ },
262
+ "metadata": {},
263
+ "output_type": "display_data"
264
+ }
265
+ ],
266
+ "source": [
267
+ "from IPython.display import Image, display\n",
268
+ "\n",
269
+ "try:\n",
270
+ " display(Image(graph.get_graph().draw_mermaid_png()))\n",
271
+ "except Exception:\n",
272
+ " # This requires some extra dependencies and is optional\n",
273
+ " pass"
274
+ ]
275
+ },
276
+ {
277
+ "cell_type": "code",
278
+ "execution_count": 16,
279
+ "metadata": {},
280
+ "outputs": [],
281
+ "source": [
282
+ "config = {\"configurable\": {\"thread_id\": \"1\"}}"
283
+ ]
284
+ },
285
+ {
286
+ "cell_type": "code",
287
+ "execution_count": 18,
288
+ "metadata": {},
289
+ "outputs": [
290
+ {
291
+ "name": "stdout",
292
+ "output_type": "stream",
293
+ "text": [
294
+ "================================\u001b[1m Human Message \u001b[0m=================================\n",
295
+ "\n",
296
+ "Hi there! My name is Akilesh.\n",
297
+ "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
298
+ "\n",
299
+ "Hello Akilesh! How can I assist you today?\n"
300
+ ]
301
+ }
302
+ ],
303
+ "source": [
304
+ "user_input = \"Hi there! My name is Akilesh.\"\n",
305
+ "\n",
306
+ "# The config is the **second positional argument** to stream() or invoke()!\n",
307
+ "events = graph.stream(\n",
308
+ " {\"messages\": [(\"user\", user_input)]}, config, stream_mode=\"values\"\n",
309
+ ")\n",
310
+ "for event in events:\n",
311
+ " event[\"messages\"][-1].pretty_print()"
312
+ ]
313
+ },
314
+ {
315
+ "cell_type": "code",
316
+ "execution_count": 19,
317
+ "metadata": {},
318
+ "outputs": [
319
+ {
320
+ "name": "stdout",
321
+ "output_type": "stream",
322
+ "text": [
323
+ "================================\u001b[1m Human Message \u001b[0m=================================\n",
324
+ "\n",
325
+ "Remember my name?\n",
326
+ "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
327
+ "\n",
328
+ "Yes, your name is Akilesh. How can I assist you today?\n"
329
+ ]
330
+ }
331
+ ],
332
+ "source": [
333
+ "user_input = \"Remember my name?\"\n",
334
+ "\n",
335
+ "# The config is the **second positional argument** to stream() or invoke()!\n",
336
+ "events = graph.stream(\n",
337
+ " {\"messages\": [(\"user\", user_input)]}, config, stream_mode=\"values\"\n",
338
+ ")\n",
339
+ "for event in events:\n",
340
+ " event[\"messages\"][-1].pretty_print()"
341
+ ]
342
+ },
343
+ {
344
+ "cell_type": "code",
345
+ "execution_count": 20,
346
+ "metadata": {},
347
+ "outputs": [
348
+ {
349
+ "name": "stdout",
350
+ "output_type": "stream",
351
+ "text": [
352
+ "================================\u001b[1m Human Message \u001b[0m=================================\n",
353
+ "\n",
354
+ "Remember my name?\n",
355
+ "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
356
+ "\n",
357
+ "I don't have access to personal data about individuals unless it has been shared with me in the course of our conversation. Therefore, I don't know your name. If you'd like, you can tell me your name!\n"
358
+ ]
359
+ }
360
+ ],
361
+ "source": [
362
+ "# The only difference is we change the `thread_id` here to \"2\" instead of \"1\"\n",
363
+ "events = graph.stream(\n",
364
+ " {\"messages\": [(\"user\", user_input)]},\n",
365
+ " {\"configurable\": {\"thread_id\": \"2\"}},\n",
366
+ " stream_mode=\"values\",\n",
367
+ ")\n",
368
+ "for event in events:\n",
369
+ " event[\"messages\"][-1].pretty_print()"
370
+ ]
371
+ },
372
+ {
373
+ "cell_type": "code",
374
+ "execution_count": 21,
375
+ "metadata": {},
376
+ "outputs": [
377
+ {
378
+ "data": {
379
+ "text/plain": [
380
+ "StateSnapshot(values={'messages': [HumanMessage(content='Hi there! My name is Will.', additional_kwargs={}, response_metadata={}, id='7d987ada-9d45-40c7-bed5-87954770d924'), AIMessage(content='Hello Will! How can I assist you today?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 11, 'prompt_tokens': 87, 'total_tokens': 98, 'completion_tokens_details': {'audio_tokens': None, 'reasoning_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-2024-08-06', 'system_fingerprint': 'fp_159d8341cc', 'finish_reason': 'stop', 'logprobs': None}, id='run-4be6cc28-59ae-47fa-a6f2-db0fc95f8ebb-0', usage_metadata={'input_tokens': 87, 'output_tokens': 11, 'total_tokens': 98, 'input_token_details': {'cache_read': 0}, 'output_token_details': {'reasoning': 0}}), HumanMessage(content='Hi there! My name is Akilesh.', additional_kwargs={}, response_metadata={}, id='4892fff9-4a37-4124-8f00-e3da884b9f76'), AIMessage(content='Hello Akilesh! How can I assist you today?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 13, 'prompt_tokens': 115, 'total_tokens': 128, 'completion_tokens_details': {'audio_tokens': None, 'reasoning_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-2024-08-06', 'system_fingerprint': 'fp_159d8341cc', 'finish_reason': 'stop', 'logprobs': None}, id='run-5a2cca31-601a-4c7d-85ac-69c2431d89d8-0', usage_metadata={'input_tokens': 115, 'output_tokens': 13, 'total_tokens': 128, 'input_token_details': {'cache_read': 0}, 'output_token_details': {'reasoning': 0}}), HumanMessage(content='Remember my name?', additional_kwargs={}, response_metadata={}, id='90b50933-01b5-4432-8d43-a30a88c3e076'), AIMessage(content='Yes, your name is Akilesh. How can I assist you today?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 17, 'prompt_tokens': 139, 'total_tokens': 156, 'completion_tokens_details': {'audio_tokens': None, 'reasoning_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-2024-08-06', 'system_fingerprint': 'fp_90354628f2', 'finish_reason': 'stop', 'logprobs': None}, id='run-20f062ea-5018-4654-b42e-3ae33d695af6-0', usage_metadata={'input_tokens': 139, 'output_tokens': 17, 'total_tokens': 156, 'input_token_details': {'cache_read': 0}, 'output_token_details': {'reasoning': 0}})]}, next=(), config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1ef976e5-63e2-6734-8007-34e57952e482'}}, metadata={'source': 'loop', 'writes': {'chatbot': {'messages': [AIMessage(content='Yes, your name is Akilesh. How can I assist you today?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 17, 'prompt_tokens': 139, 'total_tokens': 156, 'completion_tokens_details': {'audio_tokens': None, 'reasoning_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-2024-08-06', 'system_fingerprint': 'fp_90354628f2', 'finish_reason': 'stop', 'logprobs': None}, id='run-20f062ea-5018-4654-b42e-3ae33d695af6-0', usage_metadata={'input_tokens': 139, 'output_tokens': 17, 'total_tokens': 156, 'input_token_details': {'cache_read': 0}, 'output_token_details': {'reasoning': 0}})]}}, 'step': 7, 'parents': {}}, created_at='2024-10-31T09:56:02.088510+00:00', parent_config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1ef976e5-5c40-6a76-8006-154293b894d0'}}, tasks=())"
381
+ ]
382
+ },
383
+ "execution_count": 21,
384
+ "metadata": {},
385
+ "output_type": "execute_result"
386
+ }
387
+ ],
388
+ "source": [
389
+ "snapshot = graph.get_state(config)\n",
390
+ "snapshot"
391
+ ]
392
+ }
393
+ ],
394
+ "metadata": {
395
+ "kernelspec": {
396
+ "display_name": ".venv",
397
+ "language": "python",
398
+ "name": "python3"
399
+ },
400
+ "language_info": {
401
+ "codemirror_mode": {
402
+ "name": "ipython",
403
+ "version": 3
404
+ },
405
+ "file_extension": ".py",
406
+ "mimetype": "text/x-python",
407
+ "name": "python",
408
+ "nbconvert_exporter": "python",
409
+ "pygments_lexer": "ipython3",
410
+ "version": "3.12.7"
411
+ }
412
+ },
413
+ "nbformat": 4,
414
+ "nbformat_minor": 2
415
+ }
research/04_human_in_the-loop.ipynb ADDED
@@ -0,0 +1,281 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": 1,
6
+ "metadata": {},
7
+ "outputs": [],
8
+ "source": [
9
+ "%%capture --no-stderr\n",
10
+ "%pip install -U tavily-python langchain_community"
11
+ ]
12
+ },
13
+ {
14
+ "cell_type": "code",
15
+ "execution_count": 2,
16
+ "metadata": {},
17
+ "outputs": [
18
+ {
19
+ "data": {
20
+ "text/plain": [
21
+ "True"
22
+ ]
23
+ },
24
+ "execution_count": 2,
25
+ "metadata": {},
26
+ "output_type": "execute_result"
27
+ }
28
+ ],
29
+ "source": [
30
+ "import os\n",
31
+ "from dotenv import load_dotenv\n",
32
+ "\n",
33
+ "load_dotenv()"
34
+ ]
35
+ },
36
+ {
37
+ "cell_type": "code",
38
+ "execution_count": 3,
39
+ "metadata": {},
40
+ "outputs": [],
41
+ "source": [
42
+ "openai_api_key = os.getenv(\"OPENAI_API_KEY\")\n",
43
+ "model = os.getenv(\"OPENAI_MODEL\", \"gpt-4o\")\n",
44
+ "temperature = float(os.getenv(\"OPENAI_TEMPERATURE\", 0))"
45
+ ]
46
+ },
47
+ {
48
+ "cell_type": "code",
49
+ "execution_count": 4,
50
+ "metadata": {},
51
+ "outputs": [],
52
+ "source": [
53
+ "from langchain_community.tools.tavily_search import TavilySearchResults\n",
54
+ "\n",
55
+ "tool = TavilySearchResults(max_results=2)\n",
56
+ "tools = [tool]"
57
+ ]
58
+ },
59
+ {
60
+ "cell_type": "code",
61
+ "execution_count": 7,
62
+ "metadata": {},
63
+ "outputs": [
64
+ {
65
+ "data": {
66
+ "text/plain": [
67
+ "<langgraph.graph.state.StateGraph at 0x10b077740>"
68
+ ]
69
+ },
70
+ "execution_count": 7,
71
+ "metadata": {},
72
+ "output_type": "execute_result"
73
+ }
74
+ ],
75
+ "source": [
76
+ "from typing import Annotated\n",
77
+ "from langchain_openai import ChatOpenAI as Chat\n",
78
+ "\n",
79
+ "from langchain_community.tools.tavily_search import TavilySearchResults\n",
80
+ "from typing_extensions import TypedDict\n",
81
+ "\n",
82
+ "from langgraph.checkpoint.memory import MemorySaver\n",
83
+ "from langgraph.graph import StateGraph, START\n",
84
+ "from langgraph.graph.message import add_messages\n",
85
+ "from langgraph.prebuilt import ToolNode, tools_condition\n",
86
+ "\n",
87
+ "memory = MemorySaver()\n",
88
+ "\n",
89
+ "\n",
90
+ "class State(TypedDict):\n",
91
+ " messages: Annotated[list, add_messages]\n",
92
+ "\n",
93
+ "\n",
94
+ "graph_builder = StateGraph(State)\n",
95
+ "\n",
96
+ "\n",
97
+ "tool = TavilySearchResults(max_results=2)\n",
98
+ "tools = [tool]\n",
99
+ "llm = Chat(\n",
100
+ " openai_api_key=openai_api_key,\n",
101
+ " model=model,\n",
102
+ " temperature=temperature\n",
103
+ ")\n",
104
+ "llm_with_tools = llm.bind_tools(tools)\n",
105
+ "\n",
106
+ "\n",
107
+ "def chatbot(state: State):\n",
108
+ " return {\"messages\": [llm_with_tools.invoke(state[\"messages\"])]}\n",
109
+ "\n",
110
+ "\n",
111
+ "graph_builder.add_node(\"chatbot\", chatbot)\n",
112
+ "\n",
113
+ "tool_node = ToolNode(tools=[tool])\n",
114
+ "graph_builder.add_node(\"tools\", tool_node)\n",
115
+ "\n",
116
+ "graph_builder.add_conditional_edges(\n",
117
+ " \"chatbot\",\n",
118
+ " tools_condition,\n",
119
+ ")\n",
120
+ "graph_builder.add_edge(\"tools\", \"chatbot\")\n",
121
+ "graph_builder.add_edge(START, \"chatbot\")"
122
+ ]
123
+ },
124
+ {
125
+ "cell_type": "code",
126
+ "execution_count": 9,
127
+ "metadata": {},
128
+ "outputs": [],
129
+ "source": [
130
+ "graph = graph_builder.compile(\n",
131
+ " checkpointer=memory,\n",
132
+ " # This is new!\n",
133
+ " interrupt_before=[\"tools\"],\n",
134
+ " # Note: can also interrupt __after__ tools, if desired.\n",
135
+ " # interrupt_after=[\"tools\"]\n",
136
+ ")"
137
+ ]
138
+ },
139
+ {
140
+ "cell_type": "code",
141
+ "execution_count": 10,
142
+ "metadata": {},
143
+ "outputs": [
144
+ {
145
+ "name": "stdout",
146
+ "output_type": "stream",
147
+ "text": [
148
+ "================================\u001b[1m Human Message \u001b[0m=================================\n",
149
+ "\n",
150
+ "I'm learning LangGraph. Could you do some research on it for me?\n",
151
+ "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
152
+ "Tool Calls:\n",
153
+ " tavily_search_results_json (call_rrzd6xIpsEpb8KbDwRtjJGSm)\n",
154
+ " Call ID: call_rrzd6xIpsEpb8KbDwRtjJGSm\n",
155
+ " Args:\n",
156
+ " query: LangGraph programming language\n"
157
+ ]
158
+ }
159
+ ],
160
+ "source": [
161
+ "user_input = \"I'm learning LangGraph. Could you do some research on it for me?\"\n",
162
+ "config = {\"configurable\": {\"thread_id\": \"1\"}}\n",
163
+ "# The config is the **second positional argument** to stream() or invoke()!\n",
164
+ "events = graph.stream(\n",
165
+ " {\"messages\": [(\"user\", user_input)]}, config, stream_mode=\"values\"\n",
166
+ ")\n",
167
+ "for event in events:\n",
168
+ " if \"messages\" in event:\n",
169
+ " event[\"messages\"][-1].pretty_print()"
170
+ ]
171
+ },
172
+ {
173
+ "cell_type": "code",
174
+ "execution_count": 11,
175
+ "metadata": {},
176
+ "outputs": [
177
+ {
178
+ "data": {
179
+ "text/plain": [
180
+ "('tools',)"
181
+ ]
182
+ },
183
+ "execution_count": 11,
184
+ "metadata": {},
185
+ "output_type": "execute_result"
186
+ }
187
+ ],
188
+ "source": [
189
+ "snapshot = graph.get_state(config)\n",
190
+ "snapshot.next"
191
+ ]
192
+ },
193
+ {
194
+ "cell_type": "code",
195
+ "execution_count": 12,
196
+ "metadata": {},
197
+ "outputs": [
198
+ {
199
+ "data": {
200
+ "text/plain": [
201
+ "[{'name': 'tavily_search_results_json',\n",
202
+ " 'args': {'query': 'LangGraph programming language'},\n",
203
+ " 'id': 'call_rrzd6xIpsEpb8KbDwRtjJGSm',\n",
204
+ " 'type': 'tool_call'}]"
205
+ ]
206
+ },
207
+ "execution_count": 12,
208
+ "metadata": {},
209
+ "output_type": "execute_result"
210
+ }
211
+ ],
212
+ "source": [
213
+ "existing_message = snapshot.values[\"messages\"][-1]\n",
214
+ "existing_message.tool_calls"
215
+ ]
216
+ },
217
+ {
218
+ "cell_type": "code",
219
+ "execution_count": 13,
220
+ "metadata": {},
221
+ "outputs": [
222
+ {
223
+ "name": "stdout",
224
+ "output_type": "stream",
225
+ "text": [
226
+ "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
227
+ "Tool Calls:\n",
228
+ " tavily_search_results_json (call_rrzd6xIpsEpb8KbDwRtjJGSm)\n",
229
+ " Call ID: call_rrzd6xIpsEpb8KbDwRtjJGSm\n",
230
+ " Args:\n",
231
+ " query: LangGraph programming language\n",
232
+ "=================================\u001b[1m Tool Message \u001b[0m=================================\n",
233
+ "Name: tavily_search_results_json\n",
234
+ "\n",
235
+ "[{\"url\": \"https://www.datacamp.com/tutorial/langgraph-tutorial\", \"content\": \"LangGraph can be used to build a wide range of applications. Chatbots. LangGraph is ideal for developing sophisticated chatbots that can handle a wide array of user requests. By leveraging multiple LLM agents, these chatbots can process natural language queries, provide accurate responses, and seamlessly switch between different conversation\"}, {\"url\": \"https://github.com/langchain-ai/langgraph\", \"content\": \"Overview. 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\"}]\n",
236
+ "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
237
+ "\n",
238
+ "LangGraph is a library designed for building stateful, multi-actor applications using large language models (LLMs). It is particularly useful for creating agent and multi-agent workflows. Here are some key features and applications of LangGraph:\n",
239
+ "\n",
240
+ "1. **Applications**: LangGraph can be used to develop a wide range of applications, including sophisticated chatbots. These chatbots can handle various user requests, process natural language queries, provide accurate responses, and switch seamlessly between different conversation topics.\n",
241
+ "\n",
242
+ "2. **Core Benefits**:\n",
243
+ " - **Cycles**: LangGraph supports the creation of workflows that involve cycles, which are essential for most agentic architectures.\n",
244
+ " - **Controllability**: It offers a high degree of control over the workflows, allowing developers to fine-tune the behavior of the agents.\n",
245
+ " - **Persistence**: LangGraph provides mechanisms to maintain the state of applications over time, which is crucial for building long-running applications.\n",
246
+ "\n",
247
+ "For more detailed information, you can explore resources like the [LangGraph GitHub repository](https://github.com/langchain-ai/langgraph) or tutorials available on platforms like [DataCamp](https://www.datacamp.com/tutorial/langgraph-tutorial).\n"
248
+ ]
249
+ }
250
+ ],
251
+ "source": [
252
+ "# `None` will append nothing new to the current state, letting it resume as if it had never been interrupted\n",
253
+ "events = graph.stream(None, config, stream_mode=\"values\")\n",
254
+ "for event in events:\n",
255
+ " if \"messages\" in event:\n",
256
+ " event[\"messages\"][-1].pretty_print()"
257
+ ]
258
+ }
259
+ ],
260
+ "metadata": {
261
+ "kernelspec": {
262
+ "display_name": ".venv",
263
+ "language": "python",
264
+ "name": "python3"
265
+ },
266
+ "language_info": {
267
+ "codemirror_mode": {
268
+ "name": "ipython",
269
+ "version": 3
270
+ },
271
+ "file_extension": ".py",
272
+ "mimetype": "text/x-python",
273
+ "name": "python",
274
+ "nbconvert_exporter": "python",
275
+ "pygments_lexer": "ipython3",
276
+ "version": "3.12.7"
277
+ }
278
+ },
279
+ "nbformat": 4,
280
+ "nbformat_minor": 2
281
+ }
research/05_manually_updating_the_state.ipynb ADDED
@@ -0,0 +1,476 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": 1,
6
+ "metadata": {},
7
+ "outputs": [],
8
+ "source": [
9
+ "%%capture --no-stderr\n",
10
+ "%pip install -U tavily-python langchain_community"
11
+ ]
12
+ },
13
+ {
14
+ "cell_type": "code",
15
+ "execution_count": 2,
16
+ "metadata": {},
17
+ "outputs": [
18
+ {
19
+ "data": {
20
+ "text/plain": [
21
+ "True"
22
+ ]
23
+ },
24
+ "execution_count": 2,
25
+ "metadata": {},
26
+ "output_type": "execute_result"
27
+ }
28
+ ],
29
+ "source": [
30
+ "import os\n",
31
+ "from dotenv import load_dotenv\n",
32
+ "\n",
33
+ "load_dotenv()"
34
+ ]
35
+ },
36
+ {
37
+ "cell_type": "code",
38
+ "execution_count": 3,
39
+ "metadata": {},
40
+ "outputs": [],
41
+ "source": [
42
+ "openai_api_key = os.getenv(\"OPENAI_API_KEY\")\n",
43
+ "model = os.getenv(\"OPENAI_MODEL\", \"gpt-4o\")\n",
44
+ "temperature = float(os.getenv(\"OPENAI_TEMPERATURE\", 0))"
45
+ ]
46
+ },
47
+ {
48
+ "cell_type": "code",
49
+ "execution_count": 4,
50
+ "metadata": {},
51
+ "outputs": [],
52
+ "source": [
53
+ "from langchain_community.tools.tavily_search import TavilySearchResults\n",
54
+ "\n",
55
+ "tool = TavilySearchResults(max_results=2)\n",
56
+ "tools = [tool]"
57
+ ]
58
+ },
59
+ {
60
+ "cell_type": "code",
61
+ "execution_count": 6,
62
+ "metadata": {},
63
+ "outputs": [],
64
+ "source": [
65
+ "from typing import Annotated\n",
66
+ "from langchain_openai import ChatOpenAI as Chat\n",
67
+ "\n",
68
+ "from langchain_community.tools.tavily_search import TavilySearchResults\n",
69
+ "from typing_extensions import TypedDict\n",
70
+ "\n",
71
+ "from langgraph.checkpoint.memory import MemorySaver\n",
72
+ "from langgraph.graph import StateGraph, START\n",
73
+ "from langgraph.graph.message import add_messages\n",
74
+ "from langgraph.prebuilt import ToolNode, tools_condition\n",
75
+ "\n",
76
+ "memory = MemorySaver()\n",
77
+ "\n",
78
+ "\n",
79
+ "class State(TypedDict):\n",
80
+ " messages: Annotated[list, add_messages]\n",
81
+ "\n",
82
+ "\n",
83
+ "graph_builder = StateGraph(State)\n",
84
+ "\n",
85
+ "\n",
86
+ "tool = TavilySearchResults(max_results=2)\n",
87
+ "tools = [tool]\n",
88
+ "llm = Chat(\n",
89
+ " openai_api_key=openai_api_key,\n",
90
+ " model=model,\n",
91
+ " temperature=temperature\n",
92
+ ")\n",
93
+ "llm_with_tools = llm.bind_tools(tools)\n",
94
+ "\n",
95
+ "\n",
96
+ "def chatbot(state: State):\n",
97
+ " return {\"messages\": [llm_with_tools.invoke(state[\"messages\"])]}\n",
98
+ "\n",
99
+ "\n",
100
+ "graph_builder.add_node(\"chatbot\", chatbot)\n",
101
+ "\n",
102
+ "tool_node = ToolNode(tools=[tool])\n",
103
+ "graph_builder.add_node(\"tools\", tool_node)\n",
104
+ "\n",
105
+ "graph_builder.add_conditional_edges(\n",
106
+ " \"chatbot\",\n",
107
+ " tools_condition,\n",
108
+ ")\n",
109
+ "graph_builder.add_edge(\"tools\", \"chatbot\")\n",
110
+ "graph_builder.add_edge(START, \"chatbot\")\n",
111
+ "memory = MemorySaver()\n",
112
+ "graph = graph_builder.compile(\n",
113
+ " checkpointer=memory,\n",
114
+ " # This is new!\n",
115
+ " interrupt_before=[\"tools\"],\n",
116
+ " # Note: can also interrupt **after** actions, if desired.\n",
117
+ " # interrupt_after=[\"tools\"]\n",
118
+ ")\n",
119
+ "\n",
120
+ "user_input = \"I'm learning LangGraph. Could you do some research on it for me?\"\n",
121
+ "config = {\"configurable\": {\"thread_id\": \"1\"}}\n",
122
+ "# The config is the **second positional argument** to stream() or invoke()!\n",
123
+ "events = graph.stream({\"messages\": [(\"user\", user_input)]}, config)\n",
124
+ "for event in events:\n",
125
+ " if \"messages\" in event:\n",
126
+ " event[\"messages\"][-1].pretty_print()"
127
+ ]
128
+ },
129
+ {
130
+ "cell_type": "code",
131
+ "execution_count": 7,
132
+ "metadata": {},
133
+ "outputs": [
134
+ {
135
+ "name": "stdout",
136
+ "output_type": "stream",
137
+ "text": [
138
+ "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
139
+ "Tool Calls:\n",
140
+ " tavily_search_results_json (call_LGHiKqqnTR0xCTMQj4iKZ2Uy)\n",
141
+ " Call ID: call_LGHiKqqnTR0xCTMQj4iKZ2Uy\n",
142
+ " Args:\n",
143
+ " query: LangGraph programming language\n"
144
+ ]
145
+ }
146
+ ],
147
+ "source": [
148
+ "snapshot = graph.get_state(config)\n",
149
+ "existing_message = snapshot.values[\"messages\"][-1]\n",
150
+ "existing_message.pretty_print()"
151
+ ]
152
+ },
153
+ {
154
+ "cell_type": "code",
155
+ "execution_count": 8,
156
+ "metadata": {},
157
+ "outputs": [
158
+ {
159
+ "name": "stdout",
160
+ "output_type": "stream",
161
+ "text": [
162
+ "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
163
+ "\n",
164
+ "LangGraph is a library for building stateful, multi-actor applications with LLMs.\n",
165
+ "\n",
166
+ "\n",
167
+ "Last 2 messages;\n",
168
+ "[ToolMessage(content='LangGraph is a library for building stateful, multi-actor applications with LLMs.', id='c7ee8f20-19bc-430f-a7ad-4219c25fc6ec', tool_call_id='call_LGHiKqqnTR0xCTMQj4iKZ2Uy'), AIMessage(content='LangGraph is a library for building stateful, multi-actor applications with LLMs.', additional_kwargs={}, response_metadata={}, id='a7973d5c-e60b-4c97-8876-d32b9e280acf')]\n"
169
+ ]
170
+ }
171
+ ],
172
+ "source": [
173
+ "from langchain_core.messages import AIMessage, ToolMessage\n",
174
+ "\n",
175
+ "answer = (\n",
176
+ " \"LangGraph is a library for building stateful, multi-actor applications with LLMs.\"\n",
177
+ ")\n",
178
+ "new_messages = [\n",
179
+ " # The LLM API expects some ToolMessage to match its tool call. We'll satisfy that here.\n",
180
+ " ToolMessage(content=answer,\n",
181
+ " tool_call_id=existing_message.tool_calls[0][\"id\"]),\n",
182
+ " # And then directly \"put words in the LLM's mouth\" by populating its response.\n",
183
+ " AIMessage(content=answer),\n",
184
+ "]\n",
185
+ "\n",
186
+ "new_messages[-1].pretty_print()\n",
187
+ "graph.update_state(\n",
188
+ " # Which state to update\n",
189
+ " config,\n",
190
+ " # The updated values to provide. The messages in our `State` are \"append-only\", meaning this will be appended\n",
191
+ " # to the existing state. We will review how to update existing messages in the next section!\n",
192
+ " {\"messages\": new_messages},\n",
193
+ ")\n",
194
+ "\n",
195
+ "print(\"\\n\\nLast 2 messages;\")\n",
196
+ "print(graph.get_state(config).values[\"messages\"][-2:])"
197
+ ]
198
+ },
199
+ {
200
+ "cell_type": "code",
201
+ "execution_count": 9,
202
+ "metadata": {},
203
+ "outputs": [],
204
+ "source": [
205
+ "class State(TypedDict):\n",
206
+ " messages: Annotated[list, add_messages]"
207
+ ]
208
+ },
209
+ {
210
+ "cell_type": "code",
211
+ "execution_count": 10,
212
+ "metadata": {},
213
+ "outputs": [
214
+ {
215
+ "data": {
216
+ "text/plain": [
217
+ "{'configurable': {'thread_id': '1',\n",
218
+ " 'checkpoint_ns': '',\n",
219
+ " 'checkpoint_id': '1ef97736-c6dd-6e14-8003-b885bde8bfbe'}}"
220
+ ]
221
+ },
222
+ "execution_count": 10,
223
+ "metadata": {},
224
+ "output_type": "execute_result"
225
+ }
226
+ ],
227
+ "source": [
228
+ "graph.update_state(\n",
229
+ " config,\n",
230
+ " {\"messages\": [AIMessage(content=\"I'm an AI expert!\")]},\n",
231
+ " # Which node for this function to act as. It will automatically continue\n",
232
+ " # processing as if this node just ran.\n",
233
+ " as_node=\"chatbot\",\n",
234
+ ")"
235
+ ]
236
+ },
237
+ {
238
+ "cell_type": "code",
239
+ "execution_count": 11,
240
+ "metadata": {},
241
+ "outputs": [
242
+ {
243
+ "data": {
244
+ "image/jpeg": "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",
245
+ "text/plain": [
246
+ "<IPython.core.display.Image object>"
247
+ ]
248
+ },
249
+ "metadata": {},
250
+ "output_type": "display_data"
251
+ }
252
+ ],
253
+ "source": [
254
+ "from IPython.display import Image, display\n",
255
+ "\n",
256
+ "try:\n",
257
+ " display(Image(graph.get_graph().draw_mermaid_png()))\n",
258
+ "except Exception:\n",
259
+ " # This requires some extra dependencies and is optional\n",
260
+ " pass"
261
+ ]
262
+ },
263
+ {
264
+ "cell_type": "code",
265
+ "execution_count": 12,
266
+ "metadata": {},
267
+ "outputs": [
268
+ {
269
+ "name": "stdout",
270
+ "output_type": "stream",
271
+ "text": [
272
+ "[ToolMessage(content='LangGraph is a library for building stateful, multi-actor applications with LLMs.', id='c7ee8f20-19bc-430f-a7ad-4219c25fc6ec', tool_call_id='call_LGHiKqqnTR0xCTMQj4iKZ2Uy'), AIMessage(content='LangGraph is a library for building stateful, multi-actor applications with LLMs.', additional_kwargs={}, response_metadata={}, id='a7973d5c-e60b-4c97-8876-d32b9e280acf'), AIMessage(content=\"I'm an AI expert!\", additional_kwargs={}, response_metadata={}, id='e70e7694-b6f1-44bb-bb03-01e6140d8452')]\n",
273
+ "()\n"
274
+ ]
275
+ }
276
+ ],
277
+ "source": [
278
+ "snapshot = graph.get_state(config)\n",
279
+ "print(snapshot.values[\"messages\"][-3:])\n",
280
+ "print(snapshot.next)"
281
+ ]
282
+ },
283
+ {
284
+ "cell_type": "code",
285
+ "execution_count": 13,
286
+ "metadata": {},
287
+ "outputs": [
288
+ {
289
+ "name": "stdout",
290
+ "output_type": "stream",
291
+ "text": [
292
+ "================================\u001b[1m Human Message \u001b[0m=================================\n",
293
+ "\n",
294
+ "I'm learning LangGraph. Could you do some research on it for me?\n",
295
+ "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
296
+ "Tool Calls:\n",
297
+ " tavily_search_results_json (call_a5QcJ8OTfJ81NCohydwHBe43)\n",
298
+ " Call ID: call_a5QcJ8OTfJ81NCohydwHBe43\n",
299
+ " Args:\n",
300
+ " query: LangGraph programming language\n"
301
+ ]
302
+ }
303
+ ],
304
+ "source": [
305
+ "user_input = \"I'm learning LangGraph. Could you do some research on it for me?\"\n",
306
+ "config = {\"configurable\": {\"thread_id\": \"2\"}} # we'll use thread_id = 2 here\n",
307
+ "events = graph.stream(\n",
308
+ " {\"messages\": [(\"user\", user_input)]}, config, stream_mode=\"values\"\n",
309
+ ")\n",
310
+ "for event in events:\n",
311
+ " if \"messages\" in event:\n",
312
+ " event[\"messages\"][-1].pretty_print()"
313
+ ]
314
+ },
315
+ {
316
+ "cell_type": "code",
317
+ "execution_count": 14,
318
+ "metadata": {},
319
+ "outputs": [
320
+ {
321
+ "name": "stdout",
322
+ "output_type": "stream",
323
+ "text": [
324
+ "Original\n",
325
+ "Message ID run-2f27ab36-7466-40c5-adcf-18972d1bc9fa-0\n",
326
+ "{'name': 'tavily_search_results_json', 'args': {'query': 'LangGraph programming language'}, 'id': 'call_a5QcJ8OTfJ81NCohydwHBe43', 'type': 'tool_call'}\n",
327
+ "Updated\n",
328
+ "{'name': 'tavily_search_results_json', 'args': {'query': 'LangGraph human-in-the-loop workflow'}, 'id': 'call_a5QcJ8OTfJ81NCohydwHBe43', 'type': 'tool_call'}\n",
329
+ "Message ID run-2f27ab36-7466-40c5-adcf-18972d1bc9fa-0\n",
330
+ "\n",
331
+ "\n",
332
+ "Tool calls\n"
333
+ ]
334
+ },
335
+ {
336
+ "data": {
337
+ "text/plain": [
338
+ "[{'name': 'tavily_search_results_json',\n",
339
+ " 'args': {'query': 'LangGraph human-in-the-loop workflow'},\n",
340
+ " 'id': 'call_a5QcJ8OTfJ81NCohydwHBe43',\n",
341
+ " 'type': 'tool_call'}]"
342
+ ]
343
+ },
344
+ "execution_count": 14,
345
+ "metadata": {},
346
+ "output_type": "execute_result"
347
+ }
348
+ ],
349
+ "source": [
350
+ "from langchain_core.messages import AIMessage\n",
351
+ "\n",
352
+ "snapshot = graph.get_state(config)\n",
353
+ "existing_message = snapshot.values[\"messages\"][-1]\n",
354
+ "print(\"Original\")\n",
355
+ "print(\"Message ID\", existing_message.id)\n",
356
+ "print(existing_message.tool_calls[0])\n",
357
+ "new_tool_call = existing_message.tool_calls[0].copy()\n",
358
+ "new_tool_call[\"args\"][\"query\"] = \"LangGraph human-in-the-loop workflow\"\n",
359
+ "new_message = AIMessage(\n",
360
+ " content=existing_message.content,\n",
361
+ " tool_calls=[new_tool_call],\n",
362
+ " # Important! The ID is how LangGraph knows to REPLACE the message in the state rather than APPEND this messages\n",
363
+ " id=existing_message.id,\n",
364
+ ")\n",
365
+ "\n",
366
+ "print(\"Updated\")\n",
367
+ "print(new_message.tool_calls[0])\n",
368
+ "print(\"Message ID\", new_message.id)\n",
369
+ "graph.update_state(config, {\"messages\": [new_message]})\n",
370
+ "\n",
371
+ "print(\"\\n\\nTool calls\")\n",
372
+ "graph.get_state(config).values[\"messages\"][-1].tool_calls"
373
+ ]
374
+ },
375
+ {
376
+ "cell_type": "code",
377
+ "execution_count": 15,
378
+ "metadata": {},
379
+ "outputs": [
380
+ {
381
+ "name": "stdout",
382
+ "output_type": "stream",
383
+ "text": [
384
+ "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
385
+ "Tool Calls:\n",
386
+ " tavily_search_results_json (call_a5QcJ8OTfJ81NCohydwHBe43)\n",
387
+ " Call ID: call_a5QcJ8OTfJ81NCohydwHBe43\n",
388
+ " Args:\n",
389
+ " query: LangGraph human-in-the-loop workflow\n",
390
+ "=================================\u001b[1m Tool Message \u001b[0m=================================\n",
391
+ "Name: tavily_search_results_json\n",
392
+ "\n",
393
+ "[{\"url\": \"https://www.youtube.com/watch?v=9BPCV5TYPmg\", \"content\": \"In this video, I'll show you how to handle persistence with LangGraph, enabling a unique Human-in-the-Loop workflow. This approach allows a human to grant an\"}, {\"url\": \"https://medium.com/@kbdhunga/implementing-human-in-the-loop-with-langgraph-ccfde023385c\", \"content\": \"Implementing a Human-in-the-Loop (HIL) framework in LangGraph with the Streamlit app provides a robust mechanism for user engagement and decision-making. By incorporating breakpoints and\"}]\n",
394
+ "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
395
+ "\n",
396
+ "LangGraph is a tool that supports a Human-in-the-Loop (HIL) workflow, which is a process that allows human intervention in automated systems to improve decision-making and outcomes. Here are some resources that might help you understand LangGraph better:\n",
397
+ "\n",
398
+ "1. **YouTube Video**: [Handling Persistence with LangGraph](https://www.youtube.com/watch?v=9BPCV5TYPmg) - This video demonstrates how to manage persistence in LangGraph, enabling a unique Human-in-the-Loop workflow. It shows how a human can intervene in the process to grant permissions or make decisions.\n",
399
+ "\n",
400
+ "2. **Medium Article**: [Implementing Human-in-the-Loop with LangGraph](https://medium.com/@kbdhunga/implementing-human-in-the-loop-with-langgraph-ccfde023385c) - This article discusses implementing a Human-in-the-Loop framework in LangGraph using a Streamlit app. It provides insights into creating a robust mechanism for user engagement and decision-making by incorporating breakpoints and other interactive elements.\n",
401
+ "\n",
402
+ "These resources should give you a good starting point to understand how LangGraph can be used to integrate human decision-making into automated workflows.\n"
403
+ ]
404
+ }
405
+ ],
406
+ "source": [
407
+ "events = graph.stream(None, config, stream_mode=\"values\")\n",
408
+ "for event in events:\n",
409
+ " if \"messages\" in event:\n",
410
+ " event[\"messages\"][-1].pretty_print()"
411
+ ]
412
+ },
413
+ {
414
+ "cell_type": "code",
415
+ "execution_count": 16,
416
+ "metadata": {},
417
+ "outputs": [
418
+ {
419
+ "name": "stdout",
420
+ "output_type": "stream",
421
+ "text": [
422
+ "================================\u001b[1m Human Message \u001b[0m=================================\n",
423
+ "\n",
424
+ "Remember what I'm learning about?\n",
425
+ "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
426
+ "\n",
427
+ "You're learning about LangGraph, specifically focusing on its Human-in-the-Loop workflow capabilities.\n"
428
+ ]
429
+ }
430
+ ],
431
+ "source": [
432
+ "events = graph.stream(\n",
433
+ " {\n",
434
+ " \"messages\": (\n",
435
+ " \"user\",\n",
436
+ " \"Remember what I'm learning about?\",\n",
437
+ " )\n",
438
+ " },\n",
439
+ " config,\n",
440
+ " stream_mode=\"values\",\n",
441
+ ")\n",
442
+ "for event in events:\n",
443
+ " if \"messages\" in event:\n",
444
+ " event[\"messages\"][-1].pretty_print()"
445
+ ]
446
+ },
447
+ {
448
+ "cell_type": "code",
449
+ "execution_count": null,
450
+ "metadata": {},
451
+ "outputs": [],
452
+ "source": []
453
+ }
454
+ ],
455
+ "metadata": {
456
+ "kernelspec": {
457
+ "display_name": ".venv",
458
+ "language": "python",
459
+ "name": "python3"
460
+ },
461
+ "language_info": {
462
+ "codemirror_mode": {
463
+ "name": "ipython",
464
+ "version": 3
465
+ },
466
+ "file_extension": ".py",
467
+ "mimetype": "text/x-python",
468
+ "name": "python",
469
+ "nbconvert_exporter": "python",
470
+ "pygments_lexer": "ipython3",
471
+ "version": "3.12.7"
472
+ }
473
+ },
474
+ "nbformat": 4,
475
+ "nbformat_minor": 2
476
+ }
research/06_customising_the_state.ipynb ADDED
@@ -0,0 +1,376 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": 1,
6
+ "metadata": {},
7
+ "outputs": [],
8
+ "source": [
9
+ "%%capture --no-stderr\n",
10
+ "%pip install -U tavily-python langchain_community"
11
+ ]
12
+ },
13
+ {
14
+ "cell_type": "code",
15
+ "execution_count": 12,
16
+ "metadata": {},
17
+ "outputs": [
18
+ {
19
+ "data": {
20
+ "text/plain": [
21
+ "True"
22
+ ]
23
+ },
24
+ "execution_count": 12,
25
+ "metadata": {},
26
+ "output_type": "execute_result"
27
+ }
28
+ ],
29
+ "source": [
30
+ "from typing import Annotated\n",
31
+ "\n",
32
+ "from langchain_openai import ChatOpenAI as Chat\n",
33
+ "from langchain_community.tools.tavily_search import TavilySearchResults\n",
34
+ "from langchain_core.messages import BaseMessage\n",
35
+ "# NOTE: you must use langchain-core >= 0.3 with Pydantic v2\n",
36
+ "from pydantic import BaseModel\n",
37
+ "from typing_extensions import TypedDict\n",
38
+ "\n",
39
+ "from langgraph.checkpoint.memory import MemorySaver\n",
40
+ "from langgraph.graph import StateGraph\n",
41
+ "from langgraph.graph.message import add_messages\n",
42
+ "from langgraph.prebuilt import ToolNode, tools_condition\n",
43
+ "\n",
44
+ "import os\n",
45
+ "from dotenv import load_dotenv\n",
46
+ "\n",
47
+ "load_dotenv()"
48
+ ]
49
+ },
50
+ {
51
+ "cell_type": "code",
52
+ "execution_count": 13,
53
+ "metadata": {},
54
+ "outputs": [],
55
+ "source": [
56
+ "openai_api_key = os.getenv(\"OPENAI_API_KEY\")\n",
57
+ "model = os.getenv(\"OPENAI_MODEL\", \"gpt-4o\")\n",
58
+ "temperature = float(os.getenv(\"OPENAI_TEMPERATURE\", 0))"
59
+ ]
60
+ },
61
+ {
62
+ "cell_type": "code",
63
+ "execution_count": 14,
64
+ "metadata": {},
65
+ "outputs": [],
66
+ "source": [
67
+ "class State(TypedDict):\n",
68
+ " messages: Annotated[list, add_messages]\n",
69
+ " # This flag is new\n",
70
+ " ask_human: bool"
71
+ ]
72
+ },
73
+ {
74
+ "cell_type": "code",
75
+ "execution_count": 15,
76
+ "metadata": {},
77
+ "outputs": [],
78
+ "source": [
79
+ "class RequestAssistance(BaseModel):\n",
80
+ " \"\"\"Escalate the conversation to an expert. Use this if you are unable to assist directly or if the user requires support beyond your permissions.\n",
81
+ "\n",
82
+ " To use this function, relay the user's 'request' so the expert can provide the right guidance.\n",
83
+ " \"\"\"\n",
84
+ "\n",
85
+ " request: str\n",
86
+ "\n",
87
+ "\n",
88
+ "tool = TavilySearchResults(max_results=2)\n",
89
+ "tools = [tool]\n",
90
+ "llm = Chat(\n",
91
+ " openai_api_key=openai_api_key,\n",
92
+ " model=model,\n",
93
+ " temperature=temperature\n",
94
+ ")\n",
95
+ "# We can bind the llm to a tool definition, a pydantic model, or a json schema\n",
96
+ "llm_with_tools = llm.bind_tools(tools + [RequestAssistance])\n",
97
+ "\n",
98
+ "\n",
99
+ "def chatbot(state: State):\n",
100
+ " response = llm_with_tools.invoke(state[\"messages\"])\n",
101
+ " ask_human = False\n",
102
+ " if (\n",
103
+ " response.tool_calls\n",
104
+ " and response.tool_calls[0][\"name\"] == RequestAssistance.__name__\n",
105
+ " ):\n",
106
+ " ask_human = True\n",
107
+ " return {\"messages\": [response], \"ask_human\": ask_human}"
108
+ ]
109
+ },
110
+ {
111
+ "cell_type": "code",
112
+ "execution_count": 16,
113
+ "metadata": {},
114
+ "outputs": [
115
+ {
116
+ "data": {
117
+ "text/plain": [
118
+ "<langgraph.graph.state.StateGraph at 0x1128bc8f0>"
119
+ ]
120
+ },
121
+ "execution_count": 16,
122
+ "metadata": {},
123
+ "output_type": "execute_result"
124
+ }
125
+ ],
126
+ "source": [
127
+ "graph_builder = StateGraph(State)\n",
128
+ "\n",
129
+ "graph_builder.add_node(\"chatbot\", chatbot)\n",
130
+ "graph_builder.add_node(\"tools\", ToolNode(tools=[tool]))"
131
+ ]
132
+ },
133
+ {
134
+ "cell_type": "code",
135
+ "execution_count": 17,
136
+ "metadata": {},
137
+ "outputs": [],
138
+ "source": [
139
+ "from langchain_core.messages import AIMessage, ToolMessage\n",
140
+ "\n",
141
+ "def create_response(response: str, ai_message: AIMessage):\n",
142
+ " return ToolMessage(\n",
143
+ " content=response,\n",
144
+ " tool_call_id=ai_message.tool_calls[0][\"id\"],\n",
145
+ " )\n",
146
+ "\n",
147
+ "\n",
148
+ "def human_node(state: State):\n",
149
+ " new_messages = []\n",
150
+ " if not isinstance(state[\"messages\"][-1], ToolMessage):\n",
151
+ " # Typically, the user will have updated the state during the interrupt.\n",
152
+ " # If they choose not to, we will include a placeholder ToolMessage to\n",
153
+ " # let the LLM continue.\n",
154
+ " new_messages.append(\n",
155
+ " create_response(\"No response from human.\", state[\"messages\"][-1])\n",
156
+ " )\n",
157
+ " return {\n",
158
+ " # Append the new messages\n",
159
+ " \"messages\": new_messages,\n",
160
+ " # Unset the flag\n",
161
+ " \"ask_human\": False,\n",
162
+ " }"
163
+ ]
164
+ },
165
+ {
166
+ "cell_type": "code",
167
+ "execution_count": 18,
168
+ "metadata": {},
169
+ "outputs": [],
170
+ "source": [
171
+ "graph_builder.add_node(\"human\", human_node)\n",
172
+ "\n",
173
+ "\n",
174
+ "def select_next_node(state: State):\n",
175
+ " if state[\"ask_human\"]:\n",
176
+ " return \"human\"\n",
177
+ " # Otherwise, we can route as before\n",
178
+ " return tools_condition(state)\n",
179
+ "\n",
180
+ "\n",
181
+ "graph_builder.add_conditional_edges(\n",
182
+ " \"chatbot\",\n",
183
+ " select_next_node,\n",
184
+ " {\"human\": \"human\", \"tools\": \"tools\", \"__end__\": \"__end__\"},\n",
185
+ ")\n",
186
+ "graph_builder.add_edge(\"tools\", \"chatbot\")\n",
187
+ "graph_builder.add_edge(\"human\", \"chatbot\")\n",
188
+ "graph_builder.set_entry_point(\"chatbot\")\n",
189
+ "memory = MemorySaver()\n",
190
+ "graph = graph_builder.compile(\n",
191
+ " checkpointer=memory,\n",
192
+ " interrupt_before=[\"human\"],\n",
193
+ ")"
194
+ ]
195
+ },
196
+ {
197
+ "cell_type": "code",
198
+ "execution_count": 19,
199
+ "metadata": {},
200
+ "outputs": [
201
+ {
202
+ "data": {
203
+ "image/jpeg": "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fp6km0/mspg8vpeLCsuYii+22vZZdfIRMGAmNpZKDzu83zT16INwREQFV8ftiNeZGizZtfJVW5BjB6JmOEcx/sIdB0HpDj3lWhVgjx3iUxzNy3HYlzJDt03sTNLRv69qxJHo3HrC6ML+6J0W/wDOdlhZ0RFzoIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiCv5mhYx2VbncfD4xL2QguVWnzp4WkuaWejtGFztgehDnDpuCPDJ4jS/FPT3iuToY/UmIdIHOrXYGzRtkb6HMePNe3fqCAQe8BWNQmV0bistdN18MlXIEAG7SmfXmcB3BzmEF4HqduOp6LfFVNcRFerX1XTpVNvg3cKWBwbw40u0PGzgMTB1G4Ox831gf4Lv0/wM4d6TzFbLYXQ+n8Tk6xJhuU8bFFLGSC08rmtBG4JH9hKkDoiYABmp88xo9HbxO/5mMlPImx7VZ766H+Er+Hh7/KS0bVoRVfyJse1We+uh/hKp8VcfldG6ByuYx2qcwblYRmMTywlnnSsadx2Y9Dj6U/Dw9/lJaNrVF+EBwII3B7wqx5E2ParPfXQ/wk8ibHtVnvrof4Sfh4e/yktG1Xv/Vr4T/9W2lf+EQfur9Pg2cJ3Ek8N9LEnvJxMBJ/8KsHkTY9qs99dD/CX6NDueOWfUWdsM67t8bEW4P542tP+BTIw9/lJaNrvy2eq4PsKMDG2MjK3arjoSA9wHTcj+jGPS89B/aQD5adwz8TWnksvZNkbkvjFuZgPK6QgDZu/Xla1rWj8zRv1JXswunMbp6OVmPqMrmUh0su5dJKR0Be9xLnnb0uJKkljVVTEZNGj9zyERFpQREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQFnvH8gcIs/uSBtB3f9vH+cLQlnvH/f8EWf2232g+Ntt/p4/Wg0JERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQFnnhAjfhDqDqG9IOpHT/TxrQ1nnhA7fgh1Bv0G0Ho3/18aDQ0REBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQERE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D4czPsZm/raX3lPhzM+xmb+tpfeVf0TvUbkc+pfgqGExGQymbrZXJU3YuKm17a1SSRr5XPeNi95Y4tADdwACT5xJI7lb0Rc2JiTiTeSZuIiLUgiIgIiICIiAiIgIiICIiAiIgIiII7UcL7GnspFG0ukfVla1o9JLCAq1pd7ZNNYlzTu11SEg+scgV2VTtcPm9vI/GZvJYOF7i81aYgfCHHqS1ssT+Xc9dmkDck7dV24OJTFM0VTbWuqzpRcHkBkPbPN/UUvu6eQGQ9s839RS+7rffD3459C3F3ouDyAyHtnm/qKX3dPIDIe2eb+opfd0vh78c+hbi70XB5AZD2zzf1FL7unkBkPbPN/UUvu6Xw9+OfQtxd6Lg8gMh7Z5v6il93TyAyHtnm/qKX3dL4e/HPoW4u9FweQGQ9s839RS+7p5AZD2zzf1FL7ul8Pfjn0LcXei4PIDIe2eb+opfd08gMh7Z5v6il93S+Hvxz6FuLvRcHkBkPbPN/UUvu6eQGQ9s839RS+7pfD3459C3F3ouDyAyHtnm/qKX3dPIDIe2eb+opfd0vh78c+hbi70XB5AZD2zzf1FL7uvJmgbm5Eurs1Mw97ezqM36+tsAI/uPpUvh78c+hbi/eH7CL2rZQd2S5cFp2PoqVmH/wATXD+5W9cmKxVXCY+GlShEFaIENbuXEkkkuJO5c4kklxJJJJJJJXWuLGrjErmqNHTMTnERFpQREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERB//9k=",
204
+ "text/plain": [
205
+ "<IPython.core.display.Image object>"
206
+ ]
207
+ },
208
+ "metadata": {},
209
+ "output_type": "display_data"
210
+ }
211
+ ],
212
+ "source": [
213
+ "from IPython.display import Image, display\n",
214
+ "\n",
215
+ "try:\n",
216
+ " display(Image(graph.get_graph().draw_mermaid_png()))\n",
217
+ "except Exception:\n",
218
+ " # This requires some extra dependencies and is optional\n",
219
+ " pass"
220
+ ]
221
+ },
222
+ {
223
+ "cell_type": "code",
224
+ "execution_count": 20,
225
+ "metadata": {},
226
+ "outputs": [
227
+ {
228
+ "name": "stdout",
229
+ "output_type": "stream",
230
+ "text": [
231
+ "================================\u001b[1m Human Message \u001b[0m=================================\n",
232
+ "\n",
233
+ "I need some expert guidance for building this AI agent. Could you request assistance for me?\n",
234
+ "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
235
+ "Tool Calls:\n",
236
+ " RequestAssistance (call_I7J538pxIJ61RlE8CTndDi5i)\n",
237
+ " Call ID: call_I7J538pxIJ61RlE8CTndDi5i\n",
238
+ " Args:\n",
239
+ " request: I need expert guidance for building an AI agent.\n"
240
+ ]
241
+ }
242
+ ],
243
+ "source": [
244
+ "user_input = \"I need some expert guidance for building this AI agent. Could you request assistance for me?\"\n",
245
+ "config = {\"configurable\": {\"thread_id\": \"1\"}}\n",
246
+ "# The config is the **second positional argument** to stream() or invoke()!\n",
247
+ "events = graph.stream(\n",
248
+ " {\"messages\": [(\"user\", user_input)]}, config, stream_mode=\"values\"\n",
249
+ ")\n",
250
+ "for event in events:\n",
251
+ " if \"messages\" in event:\n",
252
+ " event[\"messages\"][-1].pretty_print()"
253
+ ]
254
+ },
255
+ {
256
+ "cell_type": "code",
257
+ "execution_count": 21,
258
+ "metadata": {},
259
+ "outputs": [
260
+ {
261
+ "data": {
262
+ "text/plain": [
263
+ "('human',)"
264
+ ]
265
+ },
266
+ "execution_count": 21,
267
+ "metadata": {},
268
+ "output_type": "execute_result"
269
+ }
270
+ ],
271
+ "source": [
272
+ "snapshot = graph.get_state(config)\n",
273
+ "snapshot.next"
274
+ ]
275
+ },
276
+ {
277
+ "cell_type": "code",
278
+ "execution_count": 22,
279
+ "metadata": {},
280
+ "outputs": [
281
+ {
282
+ "data": {
283
+ "text/plain": [
284
+ "{'configurable': {'thread_id': '1',\n",
285
+ " 'checkpoint_ns': '',\n",
286
+ " 'checkpoint_id': '1ef97775-7f9c-69e4-8002-c2bb25439766'}}"
287
+ ]
288
+ },
289
+ "execution_count": 22,
290
+ "metadata": {},
291
+ "output_type": "execute_result"
292
+ }
293
+ ],
294
+ "source": [
295
+ "ai_message = snapshot.values[\"messages\"][-1]\n",
296
+ "human_response = (\n",
297
+ " \"We, the experts are here to help! We'd recommend you check out LangGraph to build your agent.\"\n",
298
+ " \" It's much more reliable and extensible than simple autonomous agents.\"\n",
299
+ ")\n",
300
+ "tool_message = create_response(human_response, ai_message)\n",
301
+ "graph.update_state(config, {\"messages\": [tool_message]})"
302
+ ]
303
+ },
304
+ {
305
+ "cell_type": "code",
306
+ "execution_count": 23,
307
+ "metadata": {},
308
+ "outputs": [
309
+ {
310
+ "data": {
311
+ "text/plain": [
312
+ "[HumanMessage(content='I need some expert guidance for building this AI agent. Could you request assistance for me?', additional_kwargs={}, response_metadata={}, id='72a2beaf-917b-42e4-9e31-caa4d4617fc2'),\n",
313
+ " AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_I7J538pxIJ61RlE8CTndDi5i', 'function': {'arguments': '{\"request\":\"I need expert guidance for building an AI agent.\"}', 'name': 'RequestAssistance'}, 'type': 'function'}], 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 24, 'prompt_tokens': 160, 'total_tokens': 184, 'completion_tokens_details': {'audio_tokens': None, 'reasoning_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-2024-08-06', 'system_fingerprint': 'fp_a7d06e42a7', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-81f37266-03c6-48b9-b7c1-8c5de886bec1-0', tool_calls=[{'name': 'RequestAssistance', 'args': {'request': 'I need expert guidance for building an AI agent.'}, 'id': 'call_I7J538pxIJ61RlE8CTndDi5i', 'type': 'tool_call'}], usage_metadata={'input_tokens': 160, 'output_tokens': 24, 'total_tokens': 184, 'input_token_details': {'cache_read': 0}, 'output_token_details': {'reasoning': 0}}),\n",
314
+ " ToolMessage(content=\"We, the experts are here to help! We'd recommend you check out LangGraph to build your agent. It's much more reliable and extensible than simple autonomous agents.\", id='7fa1dfff-e757-4941-82b9-73bdfc81cf33', tool_call_id='call_I7J538pxIJ61RlE8CTndDi5i')]"
315
+ ]
316
+ },
317
+ "execution_count": 23,
318
+ "metadata": {},
319
+ "output_type": "execute_result"
320
+ }
321
+ ],
322
+ "source": [
323
+ "graph.get_state(config).values[\"messages\"]"
324
+ ]
325
+ },
326
+ {
327
+ "cell_type": "code",
328
+ "execution_count": 24,
329
+ "metadata": {},
330
+ "outputs": [
331
+ {
332
+ "name": "stdout",
333
+ "output_type": "stream",
334
+ "text": [
335
+ "=================================\u001b[1m Tool Message \u001b[0m=================================\n",
336
+ "\n",
337
+ "We, the experts are here to help! We'd recommend you check out LangGraph to build your agent. It's much more reliable and extensible than simple autonomous agents.\n",
338
+ "=================================\u001b[1m Tool Message \u001b[0m=================================\n",
339
+ "\n",
340
+ "We, the experts are here to help! We'd recommend you check out LangGraph to build your agent. It's much more reliable and extensible than simple autonomous agents.\n",
341
+ "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
342
+ "\n",
343
+ "I've requested expert assistance for you, and the experts recommend checking out LangGraph for building your AI agent. It's noted to be more reliable and extensible than simple autonomous agents. If you need further assistance, feel free to ask!\n"
344
+ ]
345
+ }
346
+ ],
347
+ "source": [
348
+ "events = graph.stream(None, config, stream_mode=\"values\")\n",
349
+ "for event in events:\n",
350
+ " if \"messages\" in event:\n",
351
+ " event[\"messages\"][-1].pretty_print()"
352
+ ]
353
+ }
354
+ ],
355
+ "metadata": {
356
+ "kernelspec": {
357
+ "display_name": ".venv",
358
+ "language": "python",
359
+ "name": "python3"
360
+ },
361
+ "language_info": {
362
+ "codemirror_mode": {
363
+ "name": "ipython",
364
+ "version": 3
365
+ },
366
+ "file_extension": ".py",
367
+ "mimetype": "text/x-python",
368
+ "name": "python",
369
+ "nbconvert_exporter": "python",
370
+ "pygments_lexer": "ipython3",
371
+ "version": "3.12.7"
372
+ }
373
+ },
374
+ "nbformat": 4,
375
+ "nbformat_minor": 2
376
+ }
research/07_time_travel.ipynb ADDED
@@ -0,0 +1,419 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": 1,
6
+ "metadata": {},
7
+ "outputs": [],
8
+ "source": [
9
+ "%%capture --no-stderr\n",
10
+ "%pip install -U tavily-python langchain_community"
11
+ ]
12
+ },
13
+ {
14
+ "cell_type": "code",
15
+ "execution_count": 2,
16
+ "metadata": {},
17
+ "outputs": [
18
+ {
19
+ "data": {
20
+ "text/plain": [
21
+ "True"
22
+ ]
23
+ },
24
+ "execution_count": 2,
25
+ "metadata": {},
26
+ "output_type": "execute_result"
27
+ }
28
+ ],
29
+ "source": [
30
+ "from typing import Annotated\n",
31
+ "\n",
32
+ "from langchain_openai import ChatOpenAI as Chat\n",
33
+ "from langchain_community.tools.tavily_search import TavilySearchResults\n",
34
+ "from langchain_core.messages import BaseMessage\n",
35
+ "# NOTE: you must use langchain-core >= 0.3 with Pydantic v2\n",
36
+ "from pydantic import BaseModel\n",
37
+ "from typing_extensions import TypedDict\n",
38
+ "\n",
39
+ "from langgraph.checkpoint.memory import MemorySaver\n",
40
+ "from langgraph.graph import StateGraph\n",
41
+ "from langgraph.graph.message import add_messages\n",
42
+ "from langgraph.prebuilt import ToolNode, tools_condition\n",
43
+ "\n",
44
+ "import os\n",
45
+ "from dotenv import load_dotenv\n",
46
+ "\n",
47
+ "load_dotenv()"
48
+ ]
49
+ },
50
+ {
51
+ "cell_type": "code",
52
+ "execution_count": 3,
53
+ "metadata": {},
54
+ "outputs": [],
55
+ "source": [
56
+ "openai_api_key = os.getenv(\"OPENAI_API_KEY\")\n",
57
+ "model = os.getenv(\"OPENAI_MODEL\", \"gpt-4o\")\n",
58
+ "temperature = float(os.getenv(\"OPENAI_TEMPERATURE\", 0))"
59
+ ]
60
+ },
61
+ {
62
+ "cell_type": "code",
63
+ "execution_count": 4,
64
+ "metadata": {},
65
+ "outputs": [],
66
+ "source": [
67
+ "class State(TypedDict):\n",
68
+ " messages: Annotated[list, add_messages]\n",
69
+ " # This flag is new\n",
70
+ " ask_human: bool"
71
+ ]
72
+ },
73
+ {
74
+ "cell_type": "code",
75
+ "execution_count": 5,
76
+ "metadata": {},
77
+ "outputs": [],
78
+ "source": [
79
+ "class RequestAssistance(BaseModel):\n",
80
+ " \"\"\"Escalate the conversation to an expert. Use this if you are unable to assist directly or if the user requires support beyond your permissions.\n",
81
+ "\n",
82
+ " To use this function, relay the user's 'request' so the expert can provide the right guidance.\n",
83
+ " \"\"\"\n",
84
+ "\n",
85
+ " request: str\n",
86
+ "\n",
87
+ "\n",
88
+ "tool = TavilySearchResults(max_results=2)\n",
89
+ "tools = [tool]\n",
90
+ "llm = Chat(\n",
91
+ " openai_api_key=openai_api_key,\n",
92
+ " model=model,\n",
93
+ " temperature=temperature\n",
94
+ ")\n",
95
+ "# We can bind the llm to a tool definition, a pydantic model, or a json schema\n",
96
+ "llm_with_tools = llm.bind_tools(tools + [RequestAssistance])\n",
97
+ "\n",
98
+ "\n",
99
+ "def chatbot(state: State):\n",
100
+ " response = llm_with_tools.invoke(state[\"messages\"])\n",
101
+ " ask_human = False\n",
102
+ " if (\n",
103
+ " response.tool_calls\n",
104
+ " and response.tool_calls[0][\"name\"] == RequestAssistance.__name__\n",
105
+ " ):\n",
106
+ " ask_human = True\n",
107
+ " return {\"messages\": [response], \"ask_human\": ask_human}"
108
+ ]
109
+ },
110
+ {
111
+ "cell_type": "code",
112
+ "execution_count": 6,
113
+ "metadata": {},
114
+ "outputs": [
115
+ {
116
+ "data": {
117
+ "text/plain": [
118
+ "<langgraph.graph.state.StateGraph at 0x10da3b290>"
119
+ ]
120
+ },
121
+ "execution_count": 6,
122
+ "metadata": {},
123
+ "output_type": "execute_result"
124
+ }
125
+ ],
126
+ "source": [
127
+ "graph_builder = StateGraph(State)\n",
128
+ "\n",
129
+ "graph_builder.add_node(\"chatbot\", chatbot)\n",
130
+ "graph_builder.add_node(\"tools\", ToolNode(tools=[tool]))"
131
+ ]
132
+ },
133
+ {
134
+ "cell_type": "code",
135
+ "execution_count": 8,
136
+ "metadata": {},
137
+ "outputs": [],
138
+ "source": [
139
+ "from langchain_core.messages import AIMessage, ToolMessage\n",
140
+ "\n",
141
+ "def create_response(response: str, ai_message: AIMessage):\n",
142
+ " return ToolMessage(\n",
143
+ " content=response,\n",
144
+ " tool_call_id=ai_message.tool_calls[0][\"id\"],\n",
145
+ " )\n",
146
+ "\n",
147
+ "\n",
148
+ "def human_node(state: State):\n",
149
+ " new_messages = []\n",
150
+ " if not isinstance(state[\"messages\"][-1], ToolMessage):\n",
151
+ " # Typically, the user will have updated the state during the interrupt.\n",
152
+ " # If they choose not to, we will include a placeholder ToolMessage to\n",
153
+ " # let the LLM continue.\n",
154
+ " new_messages.append(\n",
155
+ " create_response(\"No response from human.\", state[\"messages\"][-1])\n",
156
+ " )\n",
157
+ " return {\n",
158
+ " # Append the new messages\n",
159
+ " \"messages\": new_messages,\n",
160
+ " # Unset the flag\n",
161
+ " \"ask_human\": False,\n",
162
+ " }"
163
+ ]
164
+ },
165
+ {
166
+ "cell_type": "code",
167
+ "execution_count": 9,
168
+ "metadata": {},
169
+ "outputs": [],
170
+ "source": [
171
+ "graph_builder.add_node(\"human\", human_node)\n",
172
+ "\n",
173
+ "\n",
174
+ "def select_next_node(state: State):\n",
175
+ " if state[\"ask_human\"]:\n",
176
+ " return \"human\"\n",
177
+ " # Otherwise, we can route as before\n",
178
+ " return tools_condition(state)\n",
179
+ "\n",
180
+ "\n",
181
+ "graph_builder.add_conditional_edges(\n",
182
+ " \"chatbot\",\n",
183
+ " select_next_node,\n",
184
+ " {\"human\": \"human\", \"tools\": \"tools\", \"__end__\": \"__end__\"},\n",
185
+ ")\n",
186
+ "graph_builder.add_edge(\"tools\", \"chatbot\")\n",
187
+ "graph_builder.add_edge(\"human\", \"chatbot\")\n",
188
+ "graph_builder.set_entry_point(\"chatbot\")\n",
189
+ "memory = MemorySaver()\n",
190
+ "graph = graph_builder.compile(\n",
191
+ " checkpointer=memory,\n",
192
+ " interrupt_before=[\"human\"],\n",
193
+ ")"
194
+ ]
195
+ },
196
+ {
197
+ "cell_type": "code",
198
+ "execution_count": 10,
199
+ "metadata": {},
200
+ "outputs": [
201
+ {
202
+ "data": {
203
+ "image/jpeg": "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fp6km0/mspg8vpeLCsuYii+22vZZdfIRMGAmNpZKDzu83zT16INwREQFV8ftiNeZGizZtfJVW5BjB6JmOEcx/sIdB0HpDj3lWhVgjx3iUxzNy3HYlzJDt03sTNLRv69qxJHo3HrC6ML+6J0W/wDOdlhZ0RFzoIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiCv5mhYx2VbncfD4xL2QguVWnzp4WkuaWejtGFztgehDnDpuCPDJ4jS/FPT3iuToY/UmIdIHOrXYGzRtkb6HMePNe3fqCAQe8BWNQmV0bistdN18MlXIEAG7SmfXmcB3BzmEF4HqduOp6LfFVNcRFerX1XTpVNvg3cKWBwbw40u0PGzgMTB1G4Ox831gf4Lv0/wM4d6TzFbLYXQ+n8Tk6xJhuU8bFFLGSC08rmtBG4JH9hKkDoiYABmp88xo9HbxO/5mMlPImx7VZ766H+Er+Hh7/KS0bVoRVfyJse1We+uh/hKp8VcfldG6ByuYx2qcwblYRmMTywlnnSsadx2Y9Dj6U/Dw9/lJaNrVF+EBwII3B7wqx5E2ParPfXQ/wk8ibHtVnvrof4Sfh4e/yktG1Xv/Vr4T/9W2lf+EQfur9Pg2cJ3Ek8N9LEnvJxMBJ/8KsHkTY9qs99dD/CX6NDueOWfUWdsM67t8bEW4P542tP+BTIw9/lJaNrvy2eq4PsKMDG2MjK3arjoSA9wHTcj+jGPS89B/aQD5adwz8TWnksvZNkbkvjFuZgPK6QgDZu/Xla1rWj8zRv1JXswunMbp6OVmPqMrmUh0su5dJKR0Be9xLnnb0uJKkljVVTEZNGj9zyERFpQREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQFnvH8gcIs/uSBtB3f9vH+cLQlnvH/f8EWf2232g+Ntt/p4/Wg0JERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQFnnhAjfhDqDqG9IOpHT/TxrQ1nnhA7fgh1Bv0G0Ho3/18aDQ0REBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQERE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D4czPsZm/raX3lPhzM+xmb+tpfeVf0TvUbkc+pfgqGExGQymbrZXJU3YuKm17a1SSRr5XPeNi95Y4tADdwACT5xJI7lb0Rc2JiTiTeSZuIiLUgiIgIiICIiAiIgIiICIiAiIgIiII7UcL7GnspFG0ukfVla1o9JLCAq1pd7ZNNYlzTu11SEg+scgV2VTtcPm9vI/GZvJYOF7i81aYgfCHHqS1ssT+Xc9dmkDck7dV24OJTFM0VTbWuqzpRcHkBkPbPN/UUvu6eQGQ9s839RS+7rffD3459C3F3ouDyAyHtnm/qKX3dPIDIe2eb+opfd0vh78c+hbi70XB5AZD2zzf1FL7unkBkPbPN/UUvu6Xw9+OfQtxd6Lg8gMh7Z5v6il93TyAyHtnm/qKX3dL4e/HPoW4u9FweQGQ9s839RS+7p5AZD2zzf1FL7ul8Pfjn0LcXei4PIDIe2eb+opfd08gMh7Z5v6il93S+Hvxz6FuLvRcHkBkPbPN/UUvu6eQGQ9s839RS+7pfD3459C3F3ouDyAyHtnm/qKX3dPIDIe2eb+opfd0vh78c+hbi70XB5AZD2zzf1FL7uvJmgbm5Eurs1Mw97ezqM36+tsAI/uPpUvh78c+hbi/eH7CL2rZQd2S5cFp2PoqVmH/wATXD+5W9cmKxVXCY+GlShEFaIENbuXEkkkuJO5c4kklxJJJJJJJXWuLGrjErmqNHTMTnERFpQREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERB//9k=",
204
+ "text/plain": [
205
+ "<IPython.core.display.Image object>"
206
+ ]
207
+ },
208
+ "metadata": {},
209
+ "output_type": "display_data"
210
+ }
211
+ ],
212
+ "source": [
213
+ "from IPython.display import Image, display\n",
214
+ "\n",
215
+ "try:\n",
216
+ " display(Image(graph.get_graph().draw_mermaid_png()))\n",
217
+ "except Exception:\n",
218
+ " # This requires some extra dependencies and is optional\n",
219
+ " pass"
220
+ ]
221
+ },
222
+ {
223
+ "cell_type": "code",
224
+ "execution_count": 11,
225
+ "metadata": {},
226
+ "outputs": [
227
+ {
228
+ "name": "stdout",
229
+ "output_type": "stream",
230
+ "text": [
231
+ "================================\u001b[1m Human Message \u001b[0m=================================\n",
232
+ "\n",
233
+ "I'm learning LangGraph. Could you do some research on it for me?\n",
234
+ "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
235
+ "Tool Calls:\n",
236
+ " tavily_search_results_json (call_VKkBzQsN4sGkuRDrg0qhqJLi)\n",
237
+ " Call ID: call_VKkBzQsN4sGkuRDrg0qhqJLi\n",
238
+ " Args:\n",
239
+ " query: LangGraph programming language\n",
240
+ "=================================\u001b[1m Tool Message \u001b[0m=================================\n",
241
+ "Name: tavily_search_results_json\n",
242
+ "\n",
243
+ "[{\"url\": \"https://www.gettingstarted.ai/langgraph-tutorial-with-example/\", \"content\": \"LangGraph is a library built by the LangChain team that aims to help developers create graph-based single or multi-agent AI applications. As a low-level framework, LangGraph lets you control how agents interact with each other, which tools to use, and how information flows within the application. LangGraph uses this graph concept to organize AI agents and their interactions. We can then build our Graph by passing our State to the StateGraph class so that all graph nodes communicate by reading and writing to the shared state. A LangGraph node takes the state of the graph as a parameter and returns an updated state after it is executed. Great, now we'll wrap these mock methods and expose them as tools which will become part of the Tools node within our LangGraph graph.\"}, {\"url\": \"https://www.datacamp.com/tutorial/langgraph-tutorial\", \"content\": \"LangGraph can be used to build a wide range of applications. Chatbots. LangGraph is ideal for developing sophisticated chatbots that can handle a wide array of user requests. By leveraging multiple LLM agents, these chatbots can process natural language queries, provide accurate responses, and seamlessly switch between different conversation\"}]\n",
244
+ "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
245
+ "\n",
246
+ "LangGraph is a library developed by the LangChain team, designed to assist developers in creating graph-based single or multi-agent AI applications. It serves as a low-level framework that allows you to control how agents interact with each other, which tools to use, and how information flows within the application. The concept of a graph is used to organize AI agents and their interactions.\n",
247
+ "\n",
248
+ "Key features of LangGraph include:\n",
249
+ "\n",
250
+ "1. **Graph-Based Structure**: LangGraph uses a graph concept to manage AI agents and their interactions. You can build a graph by passing your state to the `StateGraph` class, allowing all graph nodes to communicate by reading and writing to a shared state.\n",
251
+ "\n",
252
+ "2. **Node Functionality**: A LangGraph node takes the state of the graph as a parameter and returns an updated state after execution. This allows for dynamic and flexible interactions within the graph.\n",
253
+ "\n",
254
+ "3. **Tool Integration**: You can wrap methods and expose them as tools, which become part of the Tools node within your LangGraph graph.\n",
255
+ "\n",
256
+ "4. **Application Versatility**: LangGraph is suitable for building a wide range of applications, such as sophisticated chatbots that can handle various user requests. By leveraging multiple LLM (Large Language Model) agents, these chatbots can process natural language queries, provide accurate responses, and switch seamlessly between different conversation topics.\n",
257
+ "\n",
258
+ "For more detailed tutorials and examples, you can visit resources like [Getting Started with LangGraph](https://www.gettingstarted.ai/langgraph-tutorial-with-example/) and [DataCamp's LangGraph Tutorial](https://www.datacamp.com/tutorial/langgraph-tutorial).\n"
259
+ ]
260
+ }
261
+ ],
262
+ "source": [
263
+ "config = {\"configurable\": {\"thread_id\": \"1\"}}\n",
264
+ "events = graph.stream(\n",
265
+ " {\n",
266
+ " \"messages\": [\n",
267
+ " (\"user\", \"I'm learning LangGraph. Could you do some research on it for me?\")\n",
268
+ " ]\n",
269
+ " },\n",
270
+ " config,\n",
271
+ " stream_mode=\"values\",\n",
272
+ ")\n",
273
+ "for event in events:\n",
274
+ " if \"messages\" in event:\n",
275
+ " event[\"messages\"][-1].pretty_print()"
276
+ ]
277
+ },
278
+ {
279
+ "cell_type": "code",
280
+ "execution_count": 15,
281
+ "metadata": {},
282
+ "outputs": [
283
+ {
284
+ "name": "stdout",
285
+ "output_type": "stream",
286
+ "text": [
287
+ "================================\u001b[1m Human Message \u001b[0m=================================\n",
288
+ "\n",
289
+ "Ya that's helpful. Maybe I'll build an autonomous agent with it!\n",
290
+ "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
291
+ "\n",
292
+ "That sounds like an exciting project! Building an autonomous agent with LangGraph can be a great way to explore the capabilities of graph-based AI applications. If you have any questions or need further assistance as you work on your project, feel free to ask. Good luck with your development!\n"
293
+ ]
294
+ }
295
+ ],
296
+ "source": [
297
+ "events = graph.stream(\n",
298
+ " {\n",
299
+ " \"messages\": [\n",
300
+ " (\"user\", \"Ya that's helpful. Maybe I'll build an autonomous agent with it!\")\n",
301
+ " ]\n",
302
+ " },\n",
303
+ " config,\n",
304
+ " stream_mode=\"values\",\n",
305
+ ")\n",
306
+ "for event in events:\n",
307
+ " if \"messages\" in event:\n",
308
+ " event[\"messages\"][-1].pretty_print()"
309
+ ]
310
+ },
311
+ {
312
+ "cell_type": "code",
313
+ "execution_count": 16,
314
+ "metadata": {},
315
+ "outputs": [
316
+ {
317
+ "name": "stdout",
318
+ "output_type": "stream",
319
+ "text": [
320
+ "Num Messages: 8 Next: ()\n",
321
+ "--------------------------------------------------------------------------------\n",
322
+ "Num Messages: 7 Next: ('chatbot',)\n",
323
+ "--------------------------------------------------------------------------------\n",
324
+ "Num Messages: 6 Next: ('__start__',)\n",
325
+ "--------------------------------------------------------------------------------\n",
326
+ "Num Messages: 6 Next: ()\n",
327
+ "--------------------------------------------------------------------------------\n",
328
+ "Num Messages: 5 Next: ('chatbot',)\n",
329
+ "--------------------------------------------------------------------------------\n",
330
+ "Num Messages: 4 Next: ('__start__',)\n",
331
+ "--------------------------------------------------------------------------------\n",
332
+ "Num Messages: 4 Next: ()\n",
333
+ "--------------------------------------------------------------------------------\n",
334
+ "Num Messages: 3 Next: ('chatbot',)\n",
335
+ "--------------------------------------------------------------------------------\n",
336
+ "Num Messages: 2 Next: ('tools',)\n",
337
+ "--------------------------------------------------------------------------------\n",
338
+ "Num Messages: 1 Next: ('chatbot',)\n",
339
+ "--------------------------------------------------------------------------------\n",
340
+ "Num Messages: 0 Next: ('__start__',)\n",
341
+ "--------------------------------------------------------------------------------\n"
342
+ ]
343
+ }
344
+ ],
345
+ "source": [
346
+ "to_replay = None\n",
347
+ "for state in graph.get_state_history(config):\n",
348
+ " print(\"Num Messages: \", len(\n",
349
+ " state.values[\"messages\"]), \"Next: \", state.next)\n",
350
+ " print(\"-\" * 80)\n",
351
+ " if len(state.values[\"messages\"]) == 6:\n",
352
+ " # We are somewhat arbitrarily selecting a specific state based on the number of chat messages in the state.\n",
353
+ " to_replay = state"
354
+ ]
355
+ },
356
+ {
357
+ "cell_type": "code",
358
+ "execution_count": 17,
359
+ "metadata": {},
360
+ "outputs": [
361
+ {
362
+ "name": "stdout",
363
+ "output_type": "stream",
364
+ "text": [
365
+ "()\n",
366
+ "{'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1ef9777b-23cb-64a2-8006-ecf6bb76b8ce'}}\n"
367
+ ]
368
+ }
369
+ ],
370
+ "source": [
371
+ "print(to_replay.next)\n",
372
+ "print(to_replay.config)"
373
+ ]
374
+ },
375
+ {
376
+ "cell_type": "code",
377
+ "execution_count": 18,
378
+ "metadata": {},
379
+ "outputs": [
380
+ {
381
+ "name": "stdout",
382
+ "output_type": "stream",
383
+ "text": [
384
+ "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
385
+ "\n",
386
+ "That sounds like an exciting project! Building an autonomous agent with LangGraph can be a great way to explore the capabilities of graph-based AI applications. If you have any questions or need further assistance as you work on your project, feel free to ask. Good luck with your development!\n"
387
+ ]
388
+ }
389
+ ],
390
+ "source": [
391
+ "# The `checkpoint_id` in the `to_replay.config` corresponds to a state we've persisted to our checkpointer.\n",
392
+ "for event in graph.stream(None, to_replay.config, stream_mode=\"values\"):\n",
393
+ " if \"messages\" in event:\n",
394
+ " event[\"messages\"][-1].pretty_print()"
395
+ ]
396
+ }
397
+ ],
398
+ "metadata": {
399
+ "kernelspec": {
400
+ "display_name": ".venv",
401
+ "language": "python",
402
+ "name": "python3"
403
+ },
404
+ "language_info": {
405
+ "codemirror_mode": {
406
+ "name": "ipython",
407
+ "version": 3
408
+ },
409
+ "file_extension": ".py",
410
+ "mimetype": "text/x-python",
411
+ "name": "python",
412
+ "nbconvert_exporter": "python",
413
+ "pygments_lexer": "ipython3",
414
+ "version": "3.12.7"
415
+ }
416
+ },
417
+ "nbformat": 4,
418
+ "nbformat_minor": 2
419
+ }