mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-08-17 21:25:46 +02:00
[Docs] use END instead of set_finish_point (#903)
This commit is contained in:
+235
-15
@@ -20,7 +20,10 @@
|
||||
"id": "bb54e2d0",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": ["%%capture --no-stderr\n%pip install -U langgraph"]
|
||||
"source": [
|
||||
"%%capture --no-stderr\n",
|
||||
"%pip install -U langgraph"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -36,7 +39,42 @@
|
||||
"id": "09372b8b-edea-4b9d-9ec3-3d93ce1ba819",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": ["import operator\nfrom typing import Annotated, Any\n\nfrom typing_extensions import TypedDict\n\nfrom langgraph.graph import StateGraph\n\n\nclass State(TypedDict):\n # The operator.add reducer fn makes this append-only\n aggregate: Annotated[list, operator.add]\n\n\nclass ReturnNodeValue:\n def __init__(self, node_secret: str):\n self._value = node_secret\n\n def __call__(self, state: State) -> Any:\n print(f\"Adding {self._value} to {state['aggregate']}\")\n return {\"aggregate\": [self._value]}\n\n\nbuilder = StateGraph(State)\nbuilder.add_node(\"a\", ReturnNodeValue(\"I'm A\"))\nbuilder.add_edge(START, \"a\")\nbuilder.add_node(\"b\", ReturnNodeValue(\"I'm B\"))\nbuilder.add_node(\"c\", ReturnNodeValue(\"I'm C\"))\nbuilder.add_node(\"d\", ReturnNodeValue(\"I'm D\"))\nbuilder.add_edge(\"a\", \"b\")\nbuilder.add_edge(\"a\", \"c\")\nbuilder.add_edge(\"b\", \"d\")\nbuilder.add_edge(\"c\", \"d\")\nbuilder.set_finish_point(\"d\")\ngraph = builder.compile()"]
|
||||
"source": [
|
||||
"import operator\n",
|
||||
"from typing import Annotated, Any\n",
|
||||
"\n",
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"\n",
|
||||
"from langgraph.graph import StateGraph, START, END\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class State(TypedDict):\n",
|
||||
" # The operator.add reducer fn makes this append-only\n",
|
||||
" aggregate: Annotated[list, operator.add]\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class ReturnNodeValue:\n",
|
||||
" def __init__(self, node_secret: str):\n",
|
||||
" self._value = node_secret\n",
|
||||
"\n",
|
||||
" def __call__(self, state: State) -> Any:\n",
|
||||
" print(f\"Adding {self._value} to {state['aggregate']}\")\n",
|
||||
" return {\"aggregate\": [self._value]}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"builder = StateGraph(State)\n",
|
||||
"builder.add_node(\"a\", ReturnNodeValue(\"I'm A\"))\n",
|
||||
"builder.add_edge(START, \"a\")\n",
|
||||
"builder.add_node(\"b\", ReturnNodeValue(\"I'm B\"))\n",
|
||||
"builder.add_node(\"c\", ReturnNodeValue(\"I'm C\"))\n",
|
||||
"builder.add_node(\"d\", ReturnNodeValue(\"I'm D\"))\n",
|
||||
"builder.add_edge(\"a\", \"b\")\n",
|
||||
"builder.add_edge(\"a\", \"c\")\n",
|
||||
"builder.add_edge(\"b\", \"d\")\n",
|
||||
"builder.add_edge(\"c\", \"d\")\n",
|
||||
"builder.add_edge(\"d\", END)\n",
|
||||
"graph = builder.compile()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
@@ -55,7 +93,11 @@
|
||||
"output_type": "display_data"
|
||||
}
|
||||
],
|
||||
"source": ["from IPython.display import Image, display\n\ndisplay(Image(graph.get_graph().draw_mermaid_png()))"]
|
||||
"source": [
|
||||
"from IPython.display import Image, display\n",
|
||||
"\n",
|
||||
"display(Image(graph.get_graph().draw_mermaid_png()))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
@@ -84,7 +126,9 @@
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": ["graph.invoke({\"aggregate\": []}, {\"configurable\": {\"thread_id\": \"foo\"}})"]
|
||||
"source": [
|
||||
"graph.invoke({\"aggregate\": []}, {\"configurable\": {\"thread_id\": \"foo\"}})"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -118,7 +162,34 @@
|
||||
"id": "259a7704-5aa0-4e4c-aeef-cca04e8be0ff",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": ["import operator\nfrom typing import Annotated\n\nfrom typing_extensions import TypedDict\n\nfrom langgraph.graph import StateGraph\n\n\nclass State(TypedDict):\n # The operator.add reducer fn makes this append-only\n aggregate: Annotated[list, operator.add]\n\n\nbuilder = StateGraph(State)\nbuilder.add_node(\"a\", ReturnNodeValue(\"I'm A\"))\nbuilder.add_edge(START, \"a\")\nbuilder.add_node(\"b\", ReturnNodeValue(\"I'm B\"))\nbuilder.add_node(\"b2\", ReturnNodeValue(\"I'm B2\"))\nbuilder.add_node(\"c\", ReturnNodeValue(\"I'm C\"))\nbuilder.add_node(\"d\", ReturnNodeValue(\"I'm D\"))\nbuilder.add_edge(\"a\", \"b\")\nbuilder.add_edge(\"a\", \"c\")\nbuilder.add_edge(\"b\", \"b2\")\nbuilder.add_edge([\"b2\", \"c\"], \"d\")\nbuilder.set_finish_point(\"d\")\ngraph = builder.compile()"]
|
||||
"source": [
|
||||
"import operator\n",
|
||||
"from typing import Annotated\n",
|
||||
"\n",
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"\n",
|
||||
"from langgraph.graph import StateGraph\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class State(TypedDict):\n",
|
||||
" # The operator.add reducer fn makes this append-only\n",
|
||||
" aggregate: Annotated[list, operator.add]\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"builder = StateGraph(State)\n",
|
||||
"builder.add_node(\"a\", ReturnNodeValue(\"I'm A\"))\n",
|
||||
"builder.add_edge(START, \"a\")\n",
|
||||
"builder.add_node(\"b\", ReturnNodeValue(\"I'm B\"))\n",
|
||||
"builder.add_node(\"b2\", ReturnNodeValue(\"I'm B2\"))\n",
|
||||
"builder.add_node(\"c\", ReturnNodeValue(\"I'm C\"))\n",
|
||||
"builder.add_node(\"d\", ReturnNodeValue(\"I'm D\"))\n",
|
||||
"builder.add_edge(\"a\", \"b\")\n",
|
||||
"builder.add_edge(\"a\", \"c\")\n",
|
||||
"builder.add_edge(\"b\", \"b2\")\n",
|
||||
"builder.add_edge([\"b2\", \"c\"], \"d\")\n",
|
||||
"builder.add_edge(\"d\", END)\n",
|
||||
"graph = builder.compile()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
@@ -137,7 +208,11 @@
|
||||
"output_type": "display_data"
|
||||
}
|
||||
],
|
||||
"source": ["from IPython.display import Image, display\n\ndisplay(Image(graph.get_graph().draw_mermaid_png()))"]
|
||||
"source": [
|
||||
"from IPython.display import Image, display\n",
|
||||
"\n",
|
||||
"display(Image(graph.get_graph().draw_mermaid_png()))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
@@ -167,7 +242,9 @@
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": ["graph.invoke({\"aggregate\": []})"]
|
||||
"source": [
|
||||
"graph.invoke({\"aggregate\": []})"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -187,7 +264,49 @@
|
||||
"id": "95f5e026",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": ["import operator\nfrom typing import Annotated, Sequence\n\nfrom typing_extensions import TypedDict\n\nfrom langgraph.graph import END, START, StateGraph\n\n\nclass State(TypedDict):\n # The operator.add reducer fn makes this append-only\n aggregate: Annotated[list, operator.add]\n which: str\n\n\nbuilder = StateGraph(State)\nbuilder.add_node(\"a\", ReturnNodeValue(\"I'm A\"))\nbuilder.add_edge(START, \"a\")\nbuilder.add_node(\"b\", ReturnNodeValue(\"I'm B\"))\nbuilder.add_node(\"c\", ReturnNodeValue(\"I'm C\"))\nbuilder.add_node(\"d\", ReturnNodeValue(\"I'm D\"))\nbuilder.add_node(\"e\", ReturnNodeValue(\"I'm E\"))\n\n\ndef route_bc_or_cd(state: State) -> Sequence[str]:\n if state[\"which\"] == \"cd\":\n return [\"c\", \"d\"]\n return [\"b\", \"c\"]\n\n\nintermediates = [\"b\", \"c\", \"d\"]\nbuilder.add_conditional_edges(\n \"a\",\n route_bc_or_cd,\n intermediates,\n)\nfor node in intermediates:\n builder.add_edge(node, \"e\")\n\n\nbuilder.add_edge(\"e\", END)\ngraph = builder.compile()"]
|
||||
"source": [
|
||||
"import operator\n",
|
||||
"from typing import Annotated, Sequence\n",
|
||||
"\n",
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"\n",
|
||||
"from langgraph.graph import END, START, StateGraph\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class State(TypedDict):\n",
|
||||
" # The operator.add reducer fn makes this append-only\n",
|
||||
" aggregate: Annotated[list, operator.add]\n",
|
||||
" which: str\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"builder = StateGraph(State)\n",
|
||||
"builder.add_node(\"a\", ReturnNodeValue(\"I'm A\"))\n",
|
||||
"builder.add_edge(START, \"a\")\n",
|
||||
"builder.add_node(\"b\", ReturnNodeValue(\"I'm B\"))\n",
|
||||
"builder.add_node(\"c\", ReturnNodeValue(\"I'm C\"))\n",
|
||||
"builder.add_node(\"d\", ReturnNodeValue(\"I'm D\"))\n",
|
||||
"builder.add_node(\"e\", ReturnNodeValue(\"I'm E\"))\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def route_bc_or_cd(state: State) -> Sequence[str]:\n",
|
||||
" if state[\"which\"] == \"cd\":\n",
|
||||
" return [\"c\", \"d\"]\n",
|
||||
" return [\"b\", \"c\"]\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"intermediates = [\"b\", \"c\", \"d\"]\n",
|
||||
"builder.add_conditional_edges(\n",
|
||||
" \"a\",\n",
|
||||
" route_bc_or_cd,\n",
|
||||
" intermediates,\n",
|
||||
")\n",
|
||||
"for node in intermediates:\n",
|
||||
" builder.add_edge(node, \"e\")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"builder.add_edge(\"e\", END)\n",
|
||||
"graph = builder.compile()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
@@ -206,7 +325,11 @@
|
||||
"output_type": "display_data"
|
||||
}
|
||||
],
|
||||
"source": ["from IPython.display import Image, display\n\ndisplay(Image(graph.get_graph().draw_mermaid_png()))"]
|
||||
"source": [
|
||||
"from IPython.display import Image, display\n",
|
||||
"\n",
|
||||
"display(Image(graph.get_graph().draw_mermaid_png()))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
@@ -235,7 +358,9 @@
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": ["graph.invoke({\"aggregate\": [], \"which\": \"bc\"})"]
|
||||
"source": [
|
||||
"graph.invoke({\"aggregate\": [], \"which\": \"bc\"})"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
@@ -264,7 +389,9 @@
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": ["graph.invoke({\"aggregate\": [], \"which\": \"cd\"})"]
|
||||
"source": [
|
||||
"graph.invoke({\"aggregate\": [], \"which\": \"cd\"})"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -286,7 +413,92 @@
|
||||
"id": "836bc12d",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": ["import operator\nfrom typing import Annotated, Sequence\n\nfrom typing_extensions import TypedDict\n\nfrom langgraph.graph import StateGraph\n\n\ndef reduce_fanouts(left, right):\n if left is None:\n left = []\n if not right:\n # Overwrite\n return []\n return left + right\n\n\nclass State(TypedDict):\n # The operator.add reducer fn makes this append-only\n aggregate: Annotated[list, operator.add]\n fanout_values: Annotated[list, reduce_fanouts]\n which: str\n\n\nbuilder = StateGraph(State)\nbuilder.add_node(\"a\", ReturnNodeValue(\"I'm A\"))\nbuilder.add_edge(START, \"a\")\n\n\nclass ParallelReturnNodeValue:\n def __init__(\n self,\n node_secret: str,\n reliability: float,\n ):\n self._value = node_secret\n self._reliability = reliability\n\n def __call__(self, state: State) -> Any:\n print(f\"Adding {self._value} to {state['aggregate']} in parallel.\")\n return {\n \"fanout_values\": [\n {\n \"value\": [self._value],\n \"reliability\": self._reliability,\n }\n ]\n }\n\n\nbuilder.add_node(\"b\", ParallelReturnNodeValue(\"I'm B\", reliability=0.9))\n\nbuilder.add_node(\"c\", ParallelReturnNodeValue(\"I'm C\", reliability=0.1))\nbuilder.add_node(\"d\", ParallelReturnNodeValue(\"I'm D\", reliability=0.3))\n\n\ndef aggregate_fanout_values(state: State) -> Any:\n # Sort by reliability\n ranked_values = sorted(\n state[\"fanout_values\"], key=lambda x: x[\"reliability\"], reverse=True\n )\n return {\n \"aggregate\": [x[\"value\"] for x in ranked_values] + [\"I'm E\"],\n \"fanout_values\": [],\n }\n\n\nbuilder.add_node(\"e\", aggregate_fanout_values)\n\n\ndef route_bc_or_cd(state: State) -> Sequence[str]:\n if state[\"which\"] == \"cd\":\n return [\"c\", \"d\"]\n return [\"b\", \"c\"]\n\n\nintermediates = [\"b\", \"c\", \"d\"]\nbuilder.add_conditional_edges(\"a\", route_bc_or_cd, intermediates)\n\nfor node in intermediates:\n builder.add_edge(node, \"e\")\n\nbuilder.set_finish_point(\"e\")\ngraph = builder.compile()"]
|
||||
"source": [
|
||||
"import operator\n",
|
||||
"from typing import Annotated, Sequence\n",
|
||||
"\n",
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"\n",
|
||||
"from langgraph.graph import StateGraph\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def reduce_fanouts(left, right):\n",
|
||||
" if left is None:\n",
|
||||
" left = []\n",
|
||||
" if not right:\n",
|
||||
" # Overwrite\n",
|
||||
" return []\n",
|
||||
" return left + right\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class State(TypedDict):\n",
|
||||
" # The operator.add reducer fn makes this append-only\n",
|
||||
" aggregate: Annotated[list, operator.add]\n",
|
||||
" fanout_values: Annotated[list, reduce_fanouts]\n",
|
||||
" which: str\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"builder = StateGraph(State)\n",
|
||||
"builder.add_node(\"a\", ReturnNodeValue(\"I'm A\"))\n",
|
||||
"builder.add_edge(START, \"a\")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class ParallelReturnNodeValue:\n",
|
||||
" def __init__(\n",
|
||||
" self,\n",
|
||||
" node_secret: str,\n",
|
||||
" reliability: float,\n",
|
||||
" ):\n",
|
||||
" self._value = node_secret\n",
|
||||
" self._reliability = reliability\n",
|
||||
"\n",
|
||||
" def __call__(self, state: State) -> Any:\n",
|
||||
" print(f\"Adding {self._value} to {state['aggregate']} in parallel.\")\n",
|
||||
" return {\n",
|
||||
" \"fanout_values\": [\n",
|
||||
" {\n",
|
||||
" \"value\": [self._value],\n",
|
||||
" \"reliability\": self._reliability,\n",
|
||||
" }\n",
|
||||
" ]\n",
|
||||
" }\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"builder.add_node(\"b\", ParallelReturnNodeValue(\"I'm B\", reliability=0.9))\n",
|
||||
"\n",
|
||||
"builder.add_node(\"c\", ParallelReturnNodeValue(\"I'm C\", reliability=0.1))\n",
|
||||
"builder.add_node(\"d\", ParallelReturnNodeValue(\"I'm D\", reliability=0.3))\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def aggregate_fanout_values(state: State) -> Any:\n",
|
||||
" # Sort by reliability\n",
|
||||
" ranked_values = sorted(\n",
|
||||
" state[\"fanout_values\"], key=lambda x: x[\"reliability\"], reverse=True\n",
|
||||
" )\n",
|
||||
" return {\n",
|
||||
" \"aggregate\": [x[\"value\"] for x in ranked_values] + [\"I'm E\"],\n",
|
||||
" \"fanout_values\": [],\n",
|
||||
" }\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"builder.add_node(\"e\", aggregate_fanout_values)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def route_bc_or_cd(state: State) -> Sequence[str]:\n",
|
||||
" if state[\"which\"] == \"cd\":\n",
|
||||
" return [\"c\", \"d\"]\n",
|
||||
" return [\"b\", \"c\"]\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"intermediates = [\"b\", \"c\", \"d\"]\n",
|
||||
"builder.add_conditional_edges(\"a\", route_bc_or_cd, intermediates)\n",
|
||||
"\n",
|
||||
"for node in intermediates:\n",
|
||||
" builder.add_edge(node, \"e\")\n",
|
||||
"\n",
|
||||
"builder.add_edge(\"e\", END)\n",
|
||||
"graph = builder.compile()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
@@ -305,7 +517,11 @@
|
||||
"output_type": "display_data"
|
||||
}
|
||||
],
|
||||
"source": ["from IPython.display import Image, display\n\ndisplay(Image(graph.get_graph().draw_mermaid_png()))"]
|
||||
"source": [
|
||||
"from IPython.display import Image, display\n",
|
||||
"\n",
|
||||
"display(Image(graph.get_graph().draw_mermaid_png()))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
@@ -335,7 +551,9 @@
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": ["graph.invoke({\"aggregate\": [], \"which\": \"bc\", \"fanout_values\": []})"]
|
||||
"source": [
|
||||
"graph.invoke({\"aggregate\": [], \"which\": \"bc\", \"fanout_values\": []})"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
@@ -365,7 +583,9 @@
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": ["graph.invoke({\"aggregate\": [], \"which\": \"cd\"})"]
|
||||
"source": [
|
||||
"graph.invoke({\"aggregate\": [], \"which\": \"cd\"})"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
|
||||
@@ -20,9 +20,10 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph_sdk import get_client\n",
|
||||
"\n",
|
||||
"client = get_client()\n",
|
||||
"assistants = await client.assistants.search()\n",
|
||||
"assistants = [a for a in assistants if not a['config']]\n",
|
||||
"assistants = [a for a in assistants if not a[\"config\"]]\n",
|
||||
"assistant = assistants[0]\n",
|
||||
"thread = await client.threads.create()"
|
||||
]
|
||||
@@ -43,7 +44,12 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# This schedules a job to run at 15:27 (3:27PM) every day\n",
|
||||
"cron_1 = await client.crons.create_for_thread(thread['thread_id'],assistant['assistant_id'],schedule=\"27 15 * * *\",input={'messages':[{\"role\":\"user\",\"content\":\"What time is it?\"}]})"
|
||||
"cron_1 = await client.crons.create_for_thread(\n",
|
||||
" thread[\"thread_id\"],\n",
|
||||
" assistant[\"assistant_id\"],\n",
|
||||
" schedule=\"27 15 * * *\",\n",
|
||||
" input={\"messages\": [{\"role\": \"user\", \"content\": \"What time is it?\"}]},\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -59,7 +65,7 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"await client.crons.delete(cron_1['cron_id'])"
|
||||
"await client.crons.delete(cron_1[\"cron_id\"])"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -78,7 +84,11 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# This schedules a job to run at 15:27 (3:27PM) every day\n",
|
||||
"cron_2 = await client.crons.create(assistant['assistant_id'],schedule=\"27 15 * * *\",input={'messages':[{\"role\":\"user\",\"content\":\"What time is it?\"}]})"
|
||||
"cron_2 = await client.crons.create(\n",
|
||||
" assistant[\"assistant_id\"],\n",
|
||||
" schedule=\"27 15 * * *\",\n",
|
||||
" input={\"messages\": [{\"role\": \"user\", \"content\": \"What time is it?\"}]},\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -94,7 +104,7 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"await client.crons.delete(cron_2['cron_id'])"
|
||||
"await client.crons.delete(cron_2[\"cron_id\"])"
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
@@ -35,11 +35,12 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph_sdk import get_client\n",
|
||||
"\n",
|
||||
"client = get_client()\n",
|
||||
"assistants = await client.assistants.search()\n",
|
||||
"assistants = [a for a in assistants if not a['config']]\n",
|
||||
"assistants = [a for a in assistants if not a[\"config\"]]\n",
|
||||
"assistant = assistants[0]\n",
|
||||
"assistant_id = assistant['assistant_id']\n",
|
||||
"assistant_id = assistant[\"assistant_id\"]\n",
|
||||
"thread = await client.threads.create()"
|
||||
]
|
||||
},
|
||||
|
||||
@@ -26,9 +26,10 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph_sdk import get_client\n",
|
||||
"\n",
|
||||
"client = get_client()\n",
|
||||
"assistants = await client.assistants.search()\n",
|
||||
"assistants = [a for a in assistants if not a['config']]\n",
|
||||
"assistants = [a for a in assistants if not a[\"config\"]]\n",
|
||||
"assistant = assistants[0]\n",
|
||||
"thread = await client.threads.create()"
|
||||
]
|
||||
@@ -58,11 +59,11 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"input = {'messages':[{\"role\":\"user\",\"content\":\"search for weather in SF\"}]}\n",
|
||||
"input = {\"messages\": [{\"role\": \"user\", \"content\": \"search for weather in SF\"}]}\n",
|
||||
"\n",
|
||||
"async for chunk in client.runs.stream(\n",
|
||||
" thread[\"thread_id\"],\n",
|
||||
" assistant[\"assistant_id\"], # graph_id\n",
|
||||
" assistant[\"assistant_id\"], # graph_id\n",
|
||||
" input=input,\n",
|
||||
" stream_mode=\"updates\",\n",
|
||||
" interrupt_before=[\"action\"],\n",
|
||||
@@ -99,14 +100,14 @@
|
||||
],
|
||||
"source": [
|
||||
"# First, lets get the current state\n",
|
||||
"current_state = await client.threads.get_state(thread['thread_id'])\n",
|
||||
"current_state = await client.threads.get_state(thread[\"thread_id\"])\n",
|
||||
"\n",
|
||||
"# Let's now get the last message in the state\n",
|
||||
"# This is the one with the tool calls that we want to update\n",
|
||||
"last_message = current_state['values']['messages'][-1]\n",
|
||||
"last_message = current_state[\"values\"][\"messages\"][-1]\n",
|
||||
"\n",
|
||||
"# Let's now update the args for that tool call\n",
|
||||
"last_message['tool_calls'][0]['args'] = {'query': 'current weather in Sidi Frej'}\n",
|
||||
"last_message[\"tool_calls\"][0][\"args\"] = {\"query\": \"current weather in Sidi Frej\"}\n",
|
||||
"\n",
|
||||
"# Let's now call `update_state` to pass in this message in the `messages` key\n",
|
||||
"# This will get treated as any other update to the state\n",
|
||||
@@ -114,7 +115,7 @@
|
||||
"# That reducer function will use the ID of the message to update it\n",
|
||||
"# It's important that it has the right ID! Otherwise it would get appended\n",
|
||||
"# as a new message\n",
|
||||
"await client.threads.update_state(thread['thread_id'], {\"messages\": last_message})"
|
||||
"await client.threads.update_state(thread[\"thread_id\"], {\"messages\": last_message})"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -145,7 +146,7 @@
|
||||
"source": [
|
||||
"async for chunk in client.runs.stream(\n",
|
||||
" thread[\"thread_id\"],\n",
|
||||
" assistant[\"assistant_id\"], # graph_id\n",
|
||||
" assistant[\"assistant_id\"], # graph_id\n",
|
||||
" input=None,\n",
|
||||
" stream_mode=\"updates\",\n",
|
||||
"):\n",
|
||||
|
||||
@@ -24,9 +24,10 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph_sdk import get_client\n",
|
||||
"\n",
|
||||
"client = get_client()\n",
|
||||
"assistants = await client.assistants.search()\n",
|
||||
"assistants = [a for a in assistants if not a['config']]\n",
|
||||
"assistants = [a for a in assistants if not a[\"config\"]]\n",
|
||||
"assistant = assistants[0]\n",
|
||||
"thread = await client.threads.create()"
|
||||
]
|
||||
@@ -58,11 +59,11 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"input = {'messages':[{\"role\":\"user\",\"content\":\"Please search the weather in SF\"}]}\n",
|
||||
"input = {\"messages\": [{\"role\": \"user\", \"content\": \"Please search the weather in SF\"}]}\n",
|
||||
"\n",
|
||||
"async for chunk in client.runs.stream(\n",
|
||||
" thread[\"thread_id\"],\n",
|
||||
" assistant[\"assistant_id\"], # graph_id\n",
|
||||
" assistant[\"assistant_id\"], # graph_id\n",
|
||||
" input=input,\n",
|
||||
" stream_mode=\"updates\",\n",
|
||||
"):\n",
|
||||
@@ -83,7 +84,7 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"states = await client.threads.get_history(thread['thread_id'])"
|
||||
"states = await client.threads.get_history(thread[\"thread_id\"])"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -105,7 +106,7 @@
|
||||
"source": [
|
||||
"# We can confirm that this state is correct by checking the 'next' attribute and seeing that it is the tool call node\n",
|
||||
"state_to_replay = states[2]\n",
|
||||
"state_to_replay['next']"
|
||||
"state_to_replay[\"next\"]"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -132,10 +133,10 @@
|
||||
"source": [
|
||||
"async for chunk in client.runs.stream(\n",
|
||||
" thread[\"thread_id\"],\n",
|
||||
" assistant[\"assistant_id\"], # graph_id\n",
|
||||
" assistant[\"assistant_id\"], # graph_id\n",
|
||||
" input=None,\n",
|
||||
" stream_mode=\"updates\",\n",
|
||||
" config={\"configurable\":{\"thread_ts\":state_to_replay['checkpoint_id']}}\n",
|
||||
" config={\"configurable\": {\"thread_ts\": state_to_replay[\"checkpoint_id\"]}},\n",
|
||||
"):\n",
|
||||
" if chunk.data and \"run_id\" not in chunk.data:\n",
|
||||
" print(chunk.data)"
|
||||
@@ -162,12 +163,16 @@
|
||||
"source": [
|
||||
"# Let's now get the last message in the state\n",
|
||||
"# This is the one with the tool calls that we want to update\n",
|
||||
"last_message = state_to_replay['values']['messages'][-1]\n",
|
||||
"last_message = state_to_replay[\"values\"][\"messages\"][-1]\n",
|
||||
"\n",
|
||||
"# Let's now update the args for that tool call\n",
|
||||
"last_message['tool_calls'][0]['args'] = {'query': 'current weather in SF'}\n",
|
||||
"last_message[\"tool_calls\"][0][\"args\"] = {\"query\": \"current weather in SF\"}\n",
|
||||
"\n",
|
||||
"new_state = await client.threads.update_state(thread['thread_id'],{\"messages\":[last_message]},checkpoint_id=state_to_replay['checkpoint_id'])"
|
||||
"new_state = await client.threads.update_state(\n",
|
||||
" thread[\"thread_id\"],\n",
|
||||
" {\"messages\": [last_message]},\n",
|
||||
" checkpoint_id=state_to_replay[\"checkpoint_id\"],\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -194,10 +199,10 @@
|
||||
"source": [
|
||||
"async for chunk in client.runs.stream(\n",
|
||||
" thread[\"thread_id\"],\n",
|
||||
" assistant[\"assistant_id\"], # graph_id\n",
|
||||
" assistant[\"assistant_id\"], # graph_id\n",
|
||||
" input=None,\n",
|
||||
" stream_mode=\"updates\",\n",
|
||||
" config={\"configurable\":{\"thread_ts\":new_state['configurable']['thread_ts']}}\n",
|
||||
" config={\"configurable\": {\"thread_ts\": new_state[\"configurable\"][\"thread_ts\"]}},\n",
|
||||
"):\n",
|
||||
" if chunk.data and \"run_id\" not in chunk.data:\n",
|
||||
" print(chunk.data)"
|
||||
|
||||
@@ -44,9 +44,10 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph_sdk import get_client\n",
|
||||
"\n",
|
||||
"client = get_client()\n",
|
||||
"assistants = await client.assistants.search()\n",
|
||||
"assistants = [a for a in assistants if not a['config']]\n",
|
||||
"assistants = [a for a in assistants if not a[\"config\"]]\n",
|
||||
"assistant = assistants[0]\n",
|
||||
"thread = await client.threads.create()"
|
||||
]
|
||||
@@ -76,11 +77,18 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"input = {'messages':[{\"role\":\"user\",\"content\":\"Use the search tool to ask the user where they are, then look up the weather there\"}]}\n",
|
||||
"input = {\n",
|
||||
" \"messages\": [\n",
|
||||
" {\n",
|
||||
" \"role\": \"user\",\n",
|
||||
" \"content\": \"Use the search tool to ask the user where they are, then look up the weather there\",\n",
|
||||
" }\n",
|
||||
" ]\n",
|
||||
"}\n",
|
||||
"\n",
|
||||
"async for chunk in client.runs.stream(\n",
|
||||
" thread[\"thread_id\"],\n",
|
||||
" assistant[\"assistant_id\"], # graph_id\n",
|
||||
" assistant[\"assistant_id\"], # graph_id\n",
|
||||
" input=input,\n",
|
||||
" stream_mode=\"updates\",\n",
|
||||
" interrupt_before=[\"ask_human\"],\n",
|
||||
@@ -118,13 +126,17 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"state = await client.threads.get_state(thread['thread_id'])\n",
|
||||
"tool_call_id = state['values']['messages'][-1]['tool_calls'][0]['id']\n",
|
||||
"state = await client.threads.get_state(thread[\"thread_id\"])\n",
|
||||
"tool_call_id = state[\"values\"][\"messages\"][-1][\"tool_calls\"][0][\"id\"]\n",
|
||||
"\n",
|
||||
"# We now create the tool call with the id and the response we want\n",
|
||||
"tool_message = [{\"tool_call_id\": tool_call_id, \"type\": \"tool\", \"content\": \"san francisco\"}]\n",
|
||||
"tool_message = [\n",
|
||||
" {\"tool_call_id\": tool_call_id, \"type\": \"tool\", \"content\": \"san francisco\"}\n",
|
||||
"]\n",
|
||||
"\n",
|
||||
"await client.threads.update_state(thread['thread_id'], {\"messages\": tool_message}, as_node=\"ask_human\")\n"
|
||||
"await client.threads.update_state(\n",
|
||||
" thread[\"thread_id\"], {\"messages\": tool_message}, as_node=\"ask_human\"\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -154,7 +166,7 @@
|
||||
"source": [
|
||||
"async for chunk in client.runs.stream(\n",
|
||||
" thread[\"thread_id\"],\n",
|
||||
" assistant[\"assistant_id\"], # graph_id\n",
|
||||
" assistant[\"assistant_id\"], # graph_id\n",
|
||||
" input=None,\n",
|
||||
" stream_mode=\"updates\",\n",
|
||||
"):\n",
|
||||
|
||||
@@ -119,6 +119,7 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph_sdk import get_client\n",
|
||||
"\n",
|
||||
"client = get_client()"
|
||||
]
|
||||
},
|
||||
@@ -354,7 +355,9 @@
|
||||
],
|
||||
"source": [
|
||||
"inputs = {\"messages\": [(\"human\", \"what's the weather in sf\")]}\n",
|
||||
"async for chunk in client.runs.stream(None, \"agent\", input=inputs, stream_mode=\"values\"):\n",
|
||||
"async for chunk in client.runs.stream(\n",
|
||||
" None, \"agent\", input=inputs, stream_mode=\"values\"\n",
|
||||
"):\n",
|
||||
" if chunk.event == \"values\":\n",
|
||||
" messages = convert_to_messages(chunk.data[\"messages\"])\n",
|
||||
" messages[-1].pretty_print()"
|
||||
@@ -423,7 +426,9 @@
|
||||
],
|
||||
"source": [
|
||||
"inputs = {\"messages\": [(\"human\", \"what's the weather in nyc\")]}\n",
|
||||
"invoke_output = await graph_with_memory.ainvoke(inputs, config={\"configurable\": {\"thread_id\": \"1\"}})\n",
|
||||
"invoke_output = await graph_with_memory.ainvoke(\n",
|
||||
" inputs, config={\"configurable\": {\"thread_id\": \"1\"}}\n",
|
||||
")\n",
|
||||
"invoke_output[\"messages\"][-1].pretty_print()"
|
||||
]
|
||||
},
|
||||
@@ -458,7 +463,9 @@
|
||||
],
|
||||
"source": [
|
||||
"inputs = {\"messages\": [(\"human\", \"what's it known for?\")]}\n",
|
||||
"invoke_output = await graph_with_memory.ainvoke(inputs, config={\"configurable\": {\"thread_id\": \"1\"}})\n",
|
||||
"invoke_output = await graph_with_memory.ainvoke(\n",
|
||||
" inputs, config={\"configurable\": {\"thread_id\": \"1\"}}\n",
|
||||
")\n",
|
||||
"invoke_output[\"messages\"][-1].pretty_print()"
|
||||
]
|
||||
},
|
||||
@@ -480,7 +487,9 @@
|
||||
],
|
||||
"source": [
|
||||
"inputs = {\"messages\": [(\"human\", \"what's it known for?\")]}\n",
|
||||
"invoke_output = await graph_with_memory.ainvoke(inputs, config={\"configurable\": {\"thread_id\": \"2\"}})\n",
|
||||
"invoke_output = await graph_with_memory.ainvoke(\n",
|
||||
" inputs, config={\"configurable\": {\"thread_id\": \"2\"}}\n",
|
||||
")\n",
|
||||
"invoke_output[\"messages\"][-1].pretty_print()"
|
||||
]
|
||||
},
|
||||
@@ -724,7 +733,12 @@
|
||||
],
|
||||
"source": [
|
||||
"inputs = {\"messages\": [(\"human\", \"what's the weather in sf\")]}\n",
|
||||
"async for chunk in graph_with_memory.astream(inputs, stream_mode=\"values\", interrupt_before=[\"tools\"], config={\"configurable\": {\"thread_id\": \"3\"}}):\n",
|
||||
"async for chunk in graph_with_memory.astream(\n",
|
||||
" inputs,\n",
|
||||
" stream_mode=\"values\",\n",
|
||||
" interrupt_before=[\"tools\"],\n",
|
||||
" config={\"configurable\": {\"thread_id\": \"3\"}},\n",
|
||||
"):\n",
|
||||
" chunk[\"messages\"][-1].pretty_print()"
|
||||
]
|
||||
},
|
||||
@@ -749,7 +763,12 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"async for chunk in graph_with_memory.astream(None, stream_mode=\"values\", interrupt_before=[\"tools\"], config={\"configurable\": {\"thread_id\": \"3\"}}):\n",
|
||||
"async for chunk in graph_with_memory.astream(\n",
|
||||
" None,\n",
|
||||
" stream_mode=\"values\",\n",
|
||||
" interrupt_before=[\"tools\"],\n",
|
||||
" config={\"configurable\": {\"thread_id\": \"3\"}},\n",
|
||||
"):\n",
|
||||
" chunk[\"messages\"][-1].pretty_print()"
|
||||
]
|
||||
},
|
||||
@@ -794,7 +813,13 @@
|
||||
"source": [
|
||||
"thread = await client.threads.create()\n",
|
||||
"\n",
|
||||
"async for chunk in client.runs.stream(thread[\"thread_id\"], \"agent\", input=inputs, stream_mode=\"values\", interrupt_before=[\"tools\"]):\n",
|
||||
"async for chunk in client.runs.stream(\n",
|
||||
" thread[\"thread_id\"],\n",
|
||||
" \"agent\",\n",
|
||||
" input=inputs,\n",
|
||||
" stream_mode=\"values\",\n",
|
||||
" interrupt_before=[\"tools\"],\n",
|
||||
"):\n",
|
||||
" if chunk.event == \"values\":\n",
|
||||
" messages = convert_to_messages(chunk.data[\"messages\"])\n",
|
||||
" messages[-1].pretty_print()"
|
||||
@@ -821,7 +846,13 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"async for chunk in client.runs.stream(thread[\"thread_id\"], \"agent\", input=None, stream_mode=\"values\", interrupt_before=[\"tools\"]):\n",
|
||||
"async for chunk in client.runs.stream(\n",
|
||||
" thread[\"thread_id\"],\n",
|
||||
" \"agent\",\n",
|
||||
" input=None,\n",
|
||||
" stream_mode=\"values\",\n",
|
||||
" interrupt_before=[\"tools\"],\n",
|
||||
"):\n",
|
||||
" if chunk.event == \"values\":\n",
|
||||
" messages = convert_to_messages(chunk.data[\"messages\"])\n",
|
||||
" messages[-1].pretty_print()"
|
||||
@@ -1000,7 +1031,9 @@
|
||||
],
|
||||
"source": [
|
||||
"inputs = {\"messages\": [(\"human\", \"what's the weather in sf\")]}\n",
|
||||
"async for chunk in client.runs.stream(None, \"agent\", input=inputs, stream_mode=\"events\"):\n",
|
||||
"async for chunk in client.runs.stream(\n",
|
||||
" None, \"agent\", input=inputs, stream_mode=\"events\"\n",
|
||||
"):\n",
|
||||
" if chunk.event == \"events\" and chunk.data[\"event\"] == \"on_chat_model_stream\":\n",
|
||||
" print(chunk.data[\"data\"][\"chunk\"])"
|
||||
]
|
||||
|
||||
@@ -20,9 +20,10 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph_sdk import get_client\n",
|
||||
"\n",
|
||||
"client = get_client()\n",
|
||||
"assistants = await client.assistants.search()\n",
|
||||
"assistants = [a for a in assistants if not a['config']]\n",
|
||||
"assistants = [a for a in assistants if not a[\"config\"]]\n",
|
||||
"assistant = assistants[0]\n",
|
||||
"thread = await client.threads.create()"
|
||||
]
|
||||
@@ -50,13 +51,17 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"input = {\"messages\":[{\"role\": \"user\", \"content\": \"Hello! My name is Bagatur and I am 26 years old.\"}]}\n",
|
||||
"input = {\n",
|
||||
" \"messages\": [\n",
|
||||
" {\"role\": \"user\", \"content\": \"Hello! My name is Bagatur and I am 26 years old.\"}\n",
|
||||
" ]\n",
|
||||
"}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"async for chunk in client.runs.stream(\n",
|
||||
" # Don't pass in a thread_id and the stream will be stateless\n",
|
||||
" None,\n",
|
||||
" assistant[\"assistant_id\"], # graph_id\n",
|
||||
" assistant[\"assistant_id\"], # graph_id\n",
|
||||
" input=input,\n",
|
||||
" stream_mode=\"updates\",\n",
|
||||
"):\n",
|
||||
@@ -81,7 +86,7 @@
|
||||
"source": [
|
||||
"stateless_run_result = await client.runs.wait(\n",
|
||||
" None,\n",
|
||||
" assistant[\"assistant_id\"], # graph_id\n",
|
||||
" assistant[\"assistant_id\"], # graph_id\n",
|
||||
" input=input,\n",
|
||||
")"
|
||||
]
|
||||
|
||||
@@ -113,7 +113,9 @@
|
||||
"\n",
|
||||
"from langgraph.prebuilt import create_react_agent\n",
|
||||
"\n",
|
||||
"graph = create_react_agent(model, tools=tools, interrupt_before=[\"tools\"], checkpointer=memory)"
|
||||
"graph = create_react_agent(\n",
|
||||
" model, tools=tools, interrupt_before=[\"tools\"], checkpointer=memory\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -32,7 +32,9 @@
|
||||
"id": "af4ce0ba-7596-4e5f-8bf8-0b0bd6e62833",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": ["%%capture --no-stderr\n%pip install --quiet -U langgraph langchain_anthropic"]
|
||||
"source": [
|
||||
"%%capture --no-stderr\n%pip install --quiet -U langgraph langchain_anthropic"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -56,7 +58,9 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": ["import getpass\nimport os\n\n\ndef _set_env(var: str):\n if not os.environ.get(var):\n os.environ[var] = getpass.getpass(f\"{var}: \")\n\n\n_set_env(\"ANTHROPIC_API_KEY\")"]
|
||||
"source": [
|
||||
"import getpass\nimport os\n\n\ndef _set_env(var: str):\n if not os.environ.get(var):\n os.environ[var] = getpass.getpass(f\"{var}: \")\n\n\n_set_env(\"ANTHROPIC_API_KEY\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -72,7 +76,9 @@
|
||||
"id": "95e25aec-7c9f-4a63-b143-225d0e9a79c3",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": ["os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n_set_env(\"LANGCHAIN_API_KEY\")"]
|
||||
"source": [
|
||||
"os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n_set_env(\"LANGCHAIN_API_KEY\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -90,7 +96,105 @@
|
||||
"id": "6098e5cb",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": ["# Set up the tool\nfrom langchain_anthropic import ChatAnthropic\nfrom langchain_core.tools import tool\nfrom langgraph.graph import MessagesState, START\nfrom langgraph.prebuilt import ToolNode\nfrom langgraph.graph import END, StateGraph\nfrom langgraph.checkpoint.memory import MemorySaver\n\n\n@tool\ndef search(query: str):\n \"\"\"Call to surf the web.\"\"\"\n # This is a placeholder for the actual implementation\n # Don't let the LLM know this though 😊\n return [\n \"It's sunny in San Francisco, but you better look out if you're a Gemini 😈.\"\n ]\n\ntools = [search]\ntool_node = ToolNode(tools)\n\n# Set up the model\n\nmodel = ChatAnthropic(model=\"claude-3-5-sonnet-20240620\")\nmodel = model.bind_tools(tools)\n\n\n# Define nodes and conditional edges\n\n\n# Define the function that determines whether to continue or not\ndef should_continue(state):\n messages = state[\"messages\"]\n last_message = messages[-1]\n # If there is no function call, then we finish\n if not last_message.tool_calls:\n return \"end\"\n # Otherwise if there is, we continue\n else:\n return \"continue\"\n\n\n# Define the function that calls the model\ndef call_model(state):\n messages = state[\"messages\"]\n response = model.invoke(messages)\n # We return a list, because this will get added to the existing list\n return {\"messages\": [response]}\n\n# Define a new graph\nworkflow = StateGraph(MessagesState)\n\n# Define the two nodes we will cycle between\nworkflow.add_node(\"agent\", call_model)\nworkflow.add_node(\"action\", tool_node)\n\n# Set the entrypoint as `agent`\n# This means that this node is the first one called\nworkflow.add_edge(START, \"agent\")\n\n# We now add a conditional edge\nworkflow.add_conditional_edges(\n # First, we define the start node. We use `agent`.\n # This means these are the edges taken after the `agent` node is called.\n \"agent\",\n # Next, we pass in the function that will determine which node is called next.\n should_continue,\n # Finally we pass in a mapping.\n # The keys are strings, and the values are other nodes.\n # END is a special node marking that the graph should finish.\n # What will happen is we will call `should_continue`, and then the output of that\n # will be matched against the keys in this mapping.\n # Based on which one it matches, that node will then be called.\n {\n # If `tools`, then we call the tool node.\n \"continue\": \"action\",\n # Otherwise we finish.\n \"end\": END,\n },\n)\n\n# We now add a normal edge from `tools` to `agent`.\n# This means that after `tools` is called, `agent` node is called next.\nworkflow.add_edge(\"action\", \"agent\")\n\n# Set up memory\nmemory = MemorySaver()\n\n# Finally, we compile it!\n# This compiles it into a LangChain Runnable,\n# meaning you can use it as you would any other runnable\n\n# We add in `interrupt_before=[\"action\"]`\n# This will add a breakpoint before the `action` node is called\napp = workflow.compile(checkpointer=memory, interrupt_before=[\"action\"])"]
|
||||
"source": [
|
||||
"# Set up the tool\n",
|
||||
"from langchain_anthropic import ChatAnthropic\n",
|
||||
"from langchain_core.tools import tool\n",
|
||||
"from langgraph.graph import MessagesState, START\n",
|
||||
"from langgraph.prebuilt import ToolNode\n",
|
||||
"from langgraph.graph import END, StateGraph\n",
|
||||
"from langgraph.checkpoint.memory import MemorySaver\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"@tool\n",
|
||||
"def search(query: str):\n",
|
||||
" \"\"\"Call to surf the web.\"\"\"\n",
|
||||
" # This is a placeholder for the actual implementation\n",
|
||||
" # Don't let the LLM know this though 😊\n",
|
||||
" return [\n",
|
||||
" \"It's sunny in San Francisco, but you better look out if you're a Gemini 😈.\"\n",
|
||||
" ]\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"tools = [search]\n",
|
||||
"tool_node = ToolNode(tools)\n",
|
||||
"\n",
|
||||
"# Set up the model\n",
|
||||
"\n",
|
||||
"model = ChatAnthropic(model=\"claude-3-5-sonnet-20240620\")\n",
|
||||
"model = model.bind_tools(tools)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define nodes and conditional edges\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define the function that determines whether to continue or not\n",
|
||||
"def should_continue(state):\n",
|
||||
" messages = state[\"messages\"]\n",
|
||||
" last_message = messages[-1]\n",
|
||||
" # If there is no function call, then we finish\n",
|
||||
" if not last_message.tool_calls:\n",
|
||||
" return \"end\"\n",
|
||||
" # Otherwise if there is, we continue\n",
|
||||
" else:\n",
|
||||
" return \"continue\"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define the function that calls the model\n",
|
||||
"def call_model(state):\n",
|
||||
" messages = state[\"messages\"]\n",
|
||||
" response = model.invoke(messages)\n",
|
||||
" # We return a list, because this will get added to the existing list\n",
|
||||
" return {\"messages\": [response]}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define a new graph\n",
|
||||
"workflow = StateGraph(MessagesState)\n",
|
||||
"\n",
|
||||
"# Define the two nodes we will cycle between\n",
|
||||
"workflow.add_node(\"agent\", call_model)\n",
|
||||
"workflow.add_node(\"action\", tool_node)\n",
|
||||
"\n",
|
||||
"# Set the entrypoint as `agent`\n",
|
||||
"# This means that this node is the first one called\n",
|
||||
"workflow.add_edge(START, \"agent\")\n",
|
||||
"\n",
|
||||
"# We now add a conditional edge\n",
|
||||
"workflow.add_conditional_edges(\n",
|
||||
" # First, we define the start node. We use `agent`.\n",
|
||||
" # This means these are the edges taken after the `agent` node is called.\n",
|
||||
" \"agent\",\n",
|
||||
" # Next, we pass in the function that will determine which node is called next.\n",
|
||||
" should_continue,\n",
|
||||
" # Finally we pass in a mapping.\n",
|
||||
" # The keys are strings, and the values are other nodes.\n",
|
||||
" # END is a special node marking that the graph should finish.\n",
|
||||
" # What will happen is we will call `should_continue`, and then the output of that\n",
|
||||
" # will be matched against the keys in this mapping.\n",
|
||||
" # Based on which one it matches, that node will then be called.\n",
|
||||
" {\n",
|
||||
" # If `tools`, then we call the tool node.\n",
|
||||
" \"continue\": \"action\",\n",
|
||||
" # Otherwise we finish.\n",
|
||||
" \"end\": END,\n",
|
||||
" },\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"# We now add a normal edge from `tools` to `agent`.\n",
|
||||
"# This means that after `tools` is called, `agent` node is called next.\n",
|
||||
"workflow.add_edge(\"action\", \"agent\")\n",
|
||||
"\n",
|
||||
"# Set up memory\n",
|
||||
"memory = MemorySaver()\n",
|
||||
"\n",
|
||||
"# Finally, we compile it!\n",
|
||||
"# This compiles it into a LangChain Runnable,\n",
|
||||
"# meaning you can use it as you would any other runnable\n",
|
||||
"\n",
|
||||
"# We add in `interrupt_before=[\"action\"]`\n",
|
||||
"# This will add a breakpoint before the `action` node is called\n",
|
||||
"app = workflow.compile(checkpointer=memory, interrupt_before=[\"action\"])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -126,7 +230,9 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": ["from langchain_core.messages import HumanMessage\n\nthread = {\"configurable\": {\"thread_id\": \"3\"}}\ninputs = [HumanMessage(content=\"search for the weather in sf now\")]\nfor event in app.stream({\"messages\": inputs}, thread, stream_mode=\"values\"):\n event[\"messages\"][-1].pretty_print()"]
|
||||
"source": [
|
||||
"from langchain_core.messages import HumanMessage\n\nthread = {\"configurable\": {\"thread_id\": \"3\"}}\ninputs = [HumanMessage(content=\"search for the weather in sf now\")]\nfor event in app.stream({\"messages\": inputs}, thread, stream_mode=\"values\"):\n event[\"messages\"][-1].pretty_print()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -166,7 +272,9 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": ["for event in app.stream(None, thread, stream_mode=\"values\"):\n event[\"messages\"][-1].pretty_print()"]
|
||||
"source": [
|
||||
"for event in app.stream(None, thread, stream_mode=\"values\"):\n event[\"messages\"][-1].pretty_print()"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
|
||||
@@ -32,7 +32,10 @@
|
||||
"id": "af4ce0ba-7596-4e5f-8bf8-0b0bd6e62833",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": ["%%capture --no-stderr\n%pip install --quiet -U langgraph langchain_anthropic"]
|
||||
"source": [
|
||||
"%%capture --no-stderr\n",
|
||||
"%pip install --quiet -U langgraph langchain_anthropic"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -49,14 +52,25 @@
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdin",
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"ANTHROPIC_API_KEY: ········\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": ["import getpass\nimport os\n\n\ndef _set_env(var: str):\n if not os.environ.get(var):\n os.environ[var] = getpass.getpass(f\"{var}: \")\n\n\n_set_env(\"ANTHROPIC_API_KEY\")"]
|
||||
"source": [
|
||||
"import getpass\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def _set_env(var: str):\n",
|
||||
" if not os.environ.get(var):\n",
|
||||
" os.environ[var] = getpass.getpass(f\"{var}: \")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"_set_env(\"ANTHROPIC_API_KEY\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -72,7 +86,10 @@
|
||||
"id": "95e25aec-7c9f-4a63-b143-225d0e9a79c3",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": ["os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n_set_env(\"LANGCHAIN_API_KEY\")"]
|
||||
"source": [
|
||||
"os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n",
|
||||
"_set_env(\"LANGCHAIN_API_KEY\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -90,7 +107,104 @@
|
||||
"id": "6098e5cb",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": ["# Set up the tool\nfrom langchain_anthropic import ChatAnthropic\nfrom langchain_core.tools import tool\nfrom langgraph.graph import MessagesState, START\nfrom langgraph.prebuilt import ToolNode\nfrom langgraph.graph import END, StateGraph\nfrom langgraph.checkpoint.memory import MemorySaver\n\n\n@tool\ndef search(query: str):\n \"\"\"Call to surf the web.\"\"\"\n # This is a placeholder for the actual implementation\n # Don't let the LLM know this though 😊\n return [\n \"It's sunny in San Francisco, but you better look out if you're a Gemini 😈.\"\n ]\n\ntools = [search]\ntool_node = ToolNode(tools)\n\n# Set up the model\n\nmodel = ChatAnthropic(model=\"claude-3-5-sonnet-20240620\")\nmodel = model.bind_tools(tools)\n\n\n# Define nodes and conditional edges\n\n\n# Define the function that determines whether to continue or not\ndef should_continue(state):\n messages = state[\"messages\"]\n last_message = messages[-1]\n # If there is no function call, then we finish\n if not last_message.tool_calls:\n return \"end\"\n # Otherwise if there is, we continue\n else:\n return \"continue\"\n\n\n# Define the function that calls the model\ndef call_model(state):\n messages = state[\"messages\"]\n response = model.invoke(messages)\n # We return a list, because this will get added to the existing list\n return {\"messages\": [response]}\n\n# Define a new graph\nworkflow = StateGraph(MessagesState)\n\n# Define the two nodes we will cycle between\nworkflow.add_node(\"agent\", call_model)\nworkflow.add_node(\"action\", tool_node)\n\n# Set the entrypoint as `agent`\n# This means that this node is the first one called\nworkflow.add_edge(START, \"agent\")\n\n# We now add a conditional edge\nworkflow.add_conditional_edges(\n # First, we define the start node. We use `agent`.\n # This means these are the edges taken after the `agent` node is called.\n \"agent\",\n # Next, we pass in the function that will determine which node is called next.\n should_continue,\n # Finally we pass in a mapping.\n # The keys are strings, and the values are other nodes.\n # END is a special node marking that the graph should finish.\n # What will happen is we will call `should_continue`, and then the output of that\n # will be matched against the keys in this mapping.\n # Based on which one it matches, that node will then be called.\n {\n # If `tools`, then we call the tool node.\n \"continue\": \"action\",\n # Otherwise we finish.\n \"end\": END,\n },\n)\n\n# We now add a normal edge from `tools` to `agent`.\n# This means that after `tools` is called, `agent` node is called next.\nworkflow.add_edge(\"action\", \"agent\")\n\n# Set up memory\nmemory = MemorySaver()\n\n# Finally, we compile it!\n# This compiles it into a LangChain Runnable,\n# meaning you can use it as you would any other runnable\n\n# We add in `interrupt_before=[\"action\"]`\n# This will add a breakpoint before the `action` node is called\napp = workflow.compile(checkpointer=memory, interrupt_before=[\"action\"])"]
|
||||
"source": [
|
||||
"# Set up the tool\n",
|
||||
"from langchain_anthropic import ChatAnthropic\n",
|
||||
"from langchain_core.tools import tool\n",
|
||||
"from langgraph.graph import MessagesState, START, END, StateGraph\n",
|
||||
"from langgraph.prebuilt import ToolNode\n",
|
||||
"from langgraph.checkpoint.memory import MemorySaver\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"@tool\n",
|
||||
"def search(query: str):\n",
|
||||
" \"\"\"Call to surf the web.\"\"\"\n",
|
||||
" # This is a placeholder for the actual implementation\n",
|
||||
" # Don't let the LLM know this though 😊\n",
|
||||
" return [\n",
|
||||
" \"It's sunny in San Francisco, but you better look out if you're a Gemini 😈.\"\n",
|
||||
" ]\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"tools = [search]\n",
|
||||
"tool_node = ToolNode(tools)\n",
|
||||
"\n",
|
||||
"# Set up the model\n",
|
||||
"\n",
|
||||
"model = ChatAnthropic(model=\"claude-3-5-sonnet-20240620\")\n",
|
||||
"model = model.bind_tools(tools)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define nodes and conditional edges\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define the function that determines whether to continue or not\n",
|
||||
"def should_continue(state):\n",
|
||||
" messages = state[\"messages\"]\n",
|
||||
" last_message = messages[-1]\n",
|
||||
" # If there is no function call, then we finish\n",
|
||||
" if not last_message.tool_calls:\n",
|
||||
" return \"end\"\n",
|
||||
" # Otherwise if there is, we continue\n",
|
||||
" else:\n",
|
||||
" return \"continue\"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define the function that calls the model\n",
|
||||
"def call_model(state):\n",
|
||||
" messages = state[\"messages\"]\n",
|
||||
" response = model.invoke(messages)\n",
|
||||
" # We return a list, because this will get added to the existing list\n",
|
||||
" return {\"messages\": [response]}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define a new graph\n",
|
||||
"workflow = StateGraph(MessagesState)\n",
|
||||
"\n",
|
||||
"# Define the two nodes we will cycle between\n",
|
||||
"workflow.add_node(\"agent\", call_model)\n",
|
||||
"workflow.add_node(\"action\", tool_node)\n",
|
||||
"\n",
|
||||
"# Set the entrypoint as `agent`\n",
|
||||
"# This means that this node is the first one called\n",
|
||||
"workflow.add_edge(START, \"agent\")\n",
|
||||
"\n",
|
||||
"# We now add a conditional edge\n",
|
||||
"workflow.add_conditional_edges(\n",
|
||||
" # First, we define the start node. We use `agent`.\n",
|
||||
" # This means these are the edges taken after the `agent` node is called.\n",
|
||||
" \"agent\",\n",
|
||||
" # Next, we pass in the function that will determine which node is called next.\n",
|
||||
" should_continue,\n",
|
||||
" # Finally we pass in a mapping.\n",
|
||||
" # The keys are strings, and the values are other nodes.\n",
|
||||
" # END is a special node marking that the graph should finish.\n",
|
||||
" # What will happen is we will call `should_continue`, and then the output of that\n",
|
||||
" # will be matched against the keys in this mapping.\n",
|
||||
" # Based on which one it matches, that node will then be called.\n",
|
||||
" {\n",
|
||||
" # If `tools`, then we call the tool node.\n",
|
||||
" \"continue\": \"action\",\n",
|
||||
" # Otherwise we finish.\n",
|
||||
" \"end\": END,\n",
|
||||
" },\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"# We now add a normal edge from `tools` to `agent`.\n",
|
||||
"# This means that after `tools` is called, `agent` node is called next.\n",
|
||||
"workflow.add_edge(\"action\", \"agent\")\n",
|
||||
"\n",
|
||||
"# Set up memory\n",
|
||||
"memory = MemorySaver()\n",
|
||||
"\n",
|
||||
"# Finally, we compile it!\n",
|
||||
"# This compiles it into a LangChain Runnable,\n",
|
||||
"# meaning you can use it as you would any other runnable\n",
|
||||
"\n",
|
||||
"# We add in `interrupt_before=[\"action\"]`\n",
|
||||
"# This will add a breakpoint before the `action` node is called\n",
|
||||
"app = workflow.compile(checkpointer=memory, interrupt_before=[\"action\"])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -126,7 +240,14 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": ["from langchain_core.messages import HumanMessage\n\nthread = {\"configurable\": {\"thread_id\": \"3\"}}\ninputs = [HumanMessage(content=\"search for the weather in sf now\")]\nfor event in app.stream({\"messages\": inputs}, thread, stream_mode=\"values\"):\n event[\"messages\"][-1].pretty_print()"]
|
||||
"source": [
|
||||
"from langchain_core.messages import HumanMessage\n",
|
||||
"\n",
|
||||
"thread = {\"configurable\": {\"thread_id\": \"3\"}}\n",
|
||||
"inputs = [HumanMessage(content=\"search for the weather in sf now\")]\n",
|
||||
"for event in app.stream({\"messages\": inputs}, thread, stream_mode=\"values\"):\n",
|
||||
" event[\"messages\"][-1].pretty_print()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -156,7 +277,25 @@
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": ["# First, lets get the current state\ncurrent_state = app.get_state(thread)\n\n# Let's now get the last message in the state\n# This is the one with the tool calls that we want to update\nlast_message = current_state.values['messages'][-1]\n\n# Let's now update the args for that tool call\nlast_message.tool_calls[0]['args'] = {'query': 'current weather in SF'}\n\n# Let's now call `update_state` to pass in this message in the `messages` key\n# This will get treated as any other update to the state\n# It will get passed to the reducer function for the `messages` key\n# That reducer function will use the ID of the message to update it\n# It's important that it has the right ID! Otherwise it would get appended\n# as a new message\napp.update_state(thread, {\"messages\": last_message})"]
|
||||
"source": [
|
||||
"# First, lets get the current state\n",
|
||||
"current_state = app.get_state(thread)\n",
|
||||
"\n",
|
||||
"# Let's now get the last message in the state\n",
|
||||
"# This is the one with the tool calls that we want to update\n",
|
||||
"last_message = current_state.values[\"messages\"][-1]\n",
|
||||
"\n",
|
||||
"# Let's now update the args for that tool call\n",
|
||||
"last_message.tool_calls[0][\"args\"] = {\"query\": \"current weather in SF\"}\n",
|
||||
"\n",
|
||||
"# Let's now call `update_state` to pass in this message in the `messages` key\n",
|
||||
"# This will get treated as any other update to the state\n",
|
||||
"# It will get passed to the reducer function for the `messages` key\n",
|
||||
"# That reducer function will use the ID of the message to update it\n",
|
||||
"# It's important that it has the right ID! Otherwise it would get appended\n",
|
||||
"# as a new message\n",
|
||||
"app.update_state(thread, {\"messages\": last_message})"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -185,7 +324,10 @@
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": ["current_state = app.get_state(thread).values['messages'][-1].tool_calls\ncurrent_state"]
|
||||
"source": [
|
||||
"current_state = app.get_state(thread).values[\"messages\"][-1].tool_calls\n",
|
||||
"current_state"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -223,7 +365,10 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": ["for event in app.stream(None, thread, stream_mode=\"values\"):\n event[\"messages\"][-1].pretty_print()"]
|
||||
"source": [
|
||||
"for event in app.stream(None, thread, stream_mode=\"values\"):\n",
|
||||
" event[\"messages\"][-1].pretty_print()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
@@ -231,7 +376,7 @@
|
||||
"id": "78780afe-409d-46cd-a734-e82538cdd8de",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [""]
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
|
||||
@@ -39,7 +39,9 @@
|
||||
"id": "af4ce0ba-7596-4e5f-8bf8-0b0bd6e62833",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": ["%%capture --no-stderr\n%pip install --quiet -U langgraph langchain_anthropic"]
|
||||
"source": [
|
||||
"%%capture --no-stderr\n%pip install --quiet -U langgraph langchain_anthropic"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -63,7 +65,9 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": ["import getpass\nimport os\n\n\ndef _set_env(var: str):\n if not os.environ.get(var):\n os.environ[var] = getpass.getpass(f\"{var}: \")\n\n\n_set_env(\"ANTHROPIC_API_KEY\")"]
|
||||
"source": [
|
||||
"import getpass\nimport os\n\n\ndef _set_env(var: str):\n if not os.environ.get(var):\n os.environ[var] = getpass.getpass(f\"{var}: \")\n\n\n_set_env(\"ANTHROPIC_API_KEY\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -79,7 +83,9 @@
|
||||
"id": "95e25aec-7c9f-4a63-b143-225d0e9a79c3",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": ["os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n_set_env(\"LANGCHAIN_API_KEY\")"]
|
||||
"source": [
|
||||
"os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n_set_env(\"LANGCHAIN_API_KEY\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -97,7 +103,105 @@
|
||||
"id": "f5319e01",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": ["# Set up the tool\nfrom langchain_anthropic import ChatAnthropic\nfrom langchain_core.tools import tool\nfrom langgraph.graph import MessagesState, START\nfrom langgraph.prebuilt import ToolNode\nfrom langgraph.graph import END, StateGraph\nfrom langgraph.checkpoint.memory import MemorySaver\n\n\n@tool\ndef search(query: str):\n \"\"\"Call to surf the web.\"\"\"\n # This is a placeholder for the actual implementation\n # Don't let the LLM know this though 😊\n return [\n \"It's sunny in San Francisco, but you better look out if you're a Gemini 😈.\"\n ]\n\ntools = [search]\ntool_node = ToolNode(tools)\n\n# Set up the model\n\nmodel = ChatAnthropic(model=\"claude-3-5-sonnet-20240620\")\nmodel = model.bind_tools(tools)\n\n\n# Define nodes and conditional edges\n\n\n# Define the function that determines whether to continue or not\ndef should_continue(state):\n messages = state[\"messages\"]\n last_message = messages[-1]\n # If there is no function call, then we finish\n if not last_message.tool_calls:\n return \"end\"\n # Otherwise if there is, we continue\n else:\n return \"continue\"\n\n\n# Define the function that calls the model\ndef call_model(state):\n messages = state[\"messages\"]\n response = model.invoke(messages)\n # We return a list, because this will get added to the existing list\n return {\"messages\": [response]}\n\n# Define a new graph\nworkflow = StateGraph(MessagesState)\n\n# Define the two nodes we will cycle between\nworkflow.add_node(\"agent\", call_model)\nworkflow.add_node(\"action\", tool_node)\n\n# Set the entrypoint as `agent`\n# This means that this node is the first one called\nworkflow.add_edge(START, \"agent\")\n\n# We now add a conditional edge\nworkflow.add_conditional_edges(\n # First, we define the start node. We use `agent`.\n # This means these are the edges taken after the `agent` node is called.\n \"agent\",\n # Next, we pass in the function that will determine which node is called next.\n should_continue,\n # Finally we pass in a mapping.\n # The keys are strings, and the values are other nodes.\n # END is a special node marking that the graph should finish.\n # What will happen is we will call `should_continue`, and then the output of that\n # will be matched against the keys in this mapping.\n # Based on which one it matches, that node will then be called.\n {\n # If `tools`, then we call the tool node.\n \"continue\": \"action\",\n # Otherwise we finish.\n \"end\": END,\n },\n)\n\n# We now add a normal edge from `tools` to `agent`.\n# This means that after `tools` is called, `agent` node is called next.\nworkflow.add_edge(\"action\", \"agent\")\n\n# Set up memory\nmemory = MemorySaver()\n\n# Finally, we compile it!\n# This compiles it into a LangChain Runnable,\n# meaning you can use it as you would any other runnable\n\n# We add in `interrupt_before=[\"action\"]`\n# This will add a breakpoint before the `action` node is called\napp = workflow.compile(checkpointer=memory)"]
|
||||
"source": [
|
||||
"# Set up the tool\n",
|
||||
"from langchain_anthropic import ChatAnthropic\n",
|
||||
"from langchain_core.tools import tool\n",
|
||||
"from langgraph.graph import MessagesState, START\n",
|
||||
"from langgraph.prebuilt import ToolNode\n",
|
||||
"from langgraph.graph import END, StateGraph\n",
|
||||
"from langgraph.checkpoint.memory import MemorySaver\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"@tool\n",
|
||||
"def search(query: str):\n",
|
||||
" \"\"\"Call to surf the web.\"\"\"\n",
|
||||
" # This is a placeholder for the actual implementation\n",
|
||||
" # Don't let the LLM know this though 😊\n",
|
||||
" return [\n",
|
||||
" \"It's sunny in San Francisco, but you better look out if you're a Gemini 😈.\"\n",
|
||||
" ]\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"tools = [search]\n",
|
||||
"tool_node = ToolNode(tools)\n",
|
||||
"\n",
|
||||
"# Set up the model\n",
|
||||
"\n",
|
||||
"model = ChatAnthropic(model=\"claude-3-5-sonnet-20240620\")\n",
|
||||
"model = model.bind_tools(tools)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define nodes and conditional edges\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define the function that determines whether to continue or not\n",
|
||||
"def should_continue(state):\n",
|
||||
" messages = state[\"messages\"]\n",
|
||||
" last_message = messages[-1]\n",
|
||||
" # If there is no function call, then we finish\n",
|
||||
" if not last_message.tool_calls:\n",
|
||||
" return \"end\"\n",
|
||||
" # Otherwise if there is, we continue\n",
|
||||
" else:\n",
|
||||
" return \"continue\"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define the function that calls the model\n",
|
||||
"def call_model(state):\n",
|
||||
" messages = state[\"messages\"]\n",
|
||||
" response = model.invoke(messages)\n",
|
||||
" # We return a list, because this will get added to the existing list\n",
|
||||
" return {\"messages\": [response]}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define a new graph\n",
|
||||
"workflow = StateGraph(MessagesState)\n",
|
||||
"\n",
|
||||
"# Define the two nodes we will cycle between\n",
|
||||
"workflow.add_node(\"agent\", call_model)\n",
|
||||
"workflow.add_node(\"action\", tool_node)\n",
|
||||
"\n",
|
||||
"# Set the entrypoint as `agent`\n",
|
||||
"# This means that this node is the first one called\n",
|
||||
"workflow.add_edge(START, \"agent\")\n",
|
||||
"\n",
|
||||
"# We now add a conditional edge\n",
|
||||
"workflow.add_conditional_edges(\n",
|
||||
" # First, we define the start node. We use `agent`.\n",
|
||||
" # This means these are the edges taken after the `agent` node is called.\n",
|
||||
" \"agent\",\n",
|
||||
" # Next, we pass in the function that will determine which node is called next.\n",
|
||||
" should_continue,\n",
|
||||
" # Finally we pass in a mapping.\n",
|
||||
" # The keys are strings, and the values are other nodes.\n",
|
||||
" # END is a special node marking that the graph should finish.\n",
|
||||
" # What will happen is we will call `should_continue`, and then the output of that\n",
|
||||
" # will be matched against the keys in this mapping.\n",
|
||||
" # Based on which one it matches, that node will then be called.\n",
|
||||
" {\n",
|
||||
" # If `tools`, then we call the tool node.\n",
|
||||
" \"continue\": \"action\",\n",
|
||||
" # Otherwise we finish.\n",
|
||||
" \"end\": END,\n",
|
||||
" },\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"# We now add a normal edge from `tools` to `agent`.\n",
|
||||
"# This means that after `tools` is called, `agent` node is called next.\n",
|
||||
"workflow.add_edge(\"action\", \"agent\")\n",
|
||||
"\n",
|
||||
"# Set up memory\n",
|
||||
"memory = MemorySaver()\n",
|
||||
"\n",
|
||||
"# Finally, we compile it!\n",
|
||||
"# This compiles it into a LangChain Runnable,\n",
|
||||
"# meaning you can use it as you would any other runnable\n",
|
||||
"\n",
|
||||
"# We add in `interrupt_before=[\"action\"]`\n",
|
||||
"# This will add a breakpoint before the `action` node is called\n",
|
||||
"app = workflow.compile(checkpointer=memory)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -151,7 +255,9 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": ["from langchain_core.messages import HumanMessage\n\nconfig = {\"configurable\": {\"thread_id\": \"1\"}}\ninput_message = HumanMessage(content=\"Use the search tool to look up the weather in SF\")\nfor event in app.stream({\"messages\": [input_message]}, config, stream_mode=\"values\"):\n event[\"messages\"][-1].pretty_print()"]
|
||||
"source": [
|
||||
"from langchain_core.messages import HumanMessage\n\nconfig = {\"configurable\": {\"thread_id\": \"1\"}}\ninput_message = HumanMessage(content=\"Use the search tool to look up the weather in SF\")\nfor event in app.stream({\"messages\": [input_message]}, config, stream_mode=\"values\"):\n event[\"messages\"][-1].pretty_print()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -186,7 +292,9 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": ["all_states = []\nfor state in app.get_state_history(config):\n print(state)\n all_states.append(state)\n print(\"--\")"]
|
||||
"source": [
|
||||
"all_states = []\nfor state in app.get_state_history(config):\n print(state)\n all_states.append(state)\n print(\"--\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -204,7 +312,9 @@
|
||||
"id": "02250602-8c4a-4fb5-bd6c-d0b9046e8699",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": ["to_replay = all_states[2]"]
|
||||
"source": [
|
||||
"to_replay = all_states[2]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
@@ -224,7 +334,9 @@
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": ["to_replay.values"]
|
||||
"source": [
|
||||
"to_replay.values"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
@@ -243,7 +355,9 @@
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": ["to_replay.next"]
|
||||
"source": [
|
||||
"to_replay.next"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -268,7 +382,9 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": ["for event in app.stream(None, to_replay.config):\n for v in event.values():\n print(v)"]
|
||||
"source": [
|
||||
"for event in app.stream(None, to_replay.config):\n for v in event.values():\n print(v)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -288,7 +404,19 @@
|
||||
"id": "fbd5ad3b-5363-4ab7-ac63-b04668bc998f",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": ["# Let's now get the last message in the state\n# This is the one with the tool calls that we want to update\nlast_message = to_replay.values['messages'][-1]\n\n# Let's now update the args for that tool call\nlast_message.tool_calls[0]['args'] = {'query': 'current weather in SF'}\n\nbranch_config = app.update_state(\n to_replay.config, {\"messages\": [last_message]},\n)"]
|
||||
"source": [
|
||||
"# Let's now get the last message in the state\n",
|
||||
"# This is the one with the tool calls that we want to update\n",
|
||||
"last_message = to_replay.values[\"messages\"][-1]\n",
|
||||
"\n",
|
||||
"# Let's now update the args for that tool call\n",
|
||||
"last_message.tool_calls[0][\"args\"] = {\"query\": \"current weather in SF\"}\n",
|
||||
"\n",
|
||||
"branch_config = app.update_state(\n",
|
||||
" to_replay.config,\n",
|
||||
" {\"messages\": [last_message]},\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -313,7 +441,9 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": ["for event in app.stream(None, branch_config):\n for v in event.values():\n print(v)"]
|
||||
"source": [
|
||||
"for event in app.stream(None, branch_config):\n for v in event.values():\n print(v)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -329,7 +459,21 @@
|
||||
"id": "01abb480-df55-4eba-a2be-cf9372b60b54",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": ["from langchain_core.messages import AIMessage\n\n# Let's now get the last message in the state\n# This is the one with the tool calls that we want to update\nlast_message = to_replay.values['messages'][-1]\n\n# Let's now get the ID for the last message, and create a new message with that ID.\nnew_message = AIMessage(content=\"its warm!\", id=last_message.id)\n\nbranch_config = app.update_state(\n to_replay.config, {\"messages\": [new_message]},\n)"]
|
||||
"source": [
|
||||
"from langchain_core.messages import AIMessage\n",
|
||||
"\n",
|
||||
"# Let's now get the last message in the state\n",
|
||||
"# This is the one with the tool calls that we want to update\n",
|
||||
"last_message = to_replay.values[\"messages\"][-1]\n",
|
||||
"\n",
|
||||
"# Let's now get the ID for the last message, and create a new message with that ID.\n",
|
||||
"new_message = AIMessage(content=\"its warm!\", id=last_message.id)\n",
|
||||
"\n",
|
||||
"branch_config = app.update_state(\n",
|
||||
" to_replay.config,\n",
|
||||
" {\"messages\": [new_message]},\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
@@ -337,7 +481,9 @@
|
||||
"id": "1a7cfcd4-289e-419e-8b49-dfaef4f88641",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": ["branch_state = app.get_state(branch_config)"]
|
||||
"source": [
|
||||
"branch_state = app.get_state(branch_config)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
@@ -357,7 +503,9 @@
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": ["branch_state.values"]
|
||||
"source": [
|
||||
"branch_state.values"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
@@ -376,7 +524,9 @@
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": ["branch_state.next"]
|
||||
"source": [
|
||||
"branch_state.next"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -392,7 +542,9 @@
|
||||
"id": "74a7a5ed-0c14-4883-a16b-d70aaf40f7ea",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [""]
|
||||
"source": [
|
||||
""
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
|
||||
@@ -40,7 +40,9 @@
|
||||
"id": "af4ce0ba-7596-4e5f-8bf8-0b0bd6e62833",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": ["%%capture --no-stderr\n%pip install --quiet -U langgraph langchain_anthropic"]
|
||||
"source": [
|
||||
"%%capture --no-stderr\n%pip install --quiet -U langgraph langchain_anthropic"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -64,7 +66,9 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": ["import getpass\nimport os\n\n\ndef _set_env(var: str):\n if not os.environ.get(var):\n os.environ[var] = getpass.getpass(f\"{var}: \")\n\n\n_set_env(\"ANTHROPIC_API_KEY\")"]
|
||||
"source": [
|
||||
"import getpass\nimport os\n\n\ndef _set_env(var: str):\n if not os.environ.get(var):\n os.environ[var] = getpass.getpass(f\"{var}: \")\n\n\n_set_env(\"ANTHROPIC_API_KEY\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -80,7 +84,9 @@
|
||||
"id": "95e25aec-7c9f-4a63-b143-225d0e9a79c3",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": ["os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n_set_env(\"LANGCHAIN_API_KEY\")"]
|
||||
"source": [
|
||||
"os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n_set_env(\"LANGCHAIN_API_KEY\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -98,7 +104,147 @@
|
||||
"id": "f5319e01",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": ["# Set up the state\nfrom langgraph.graph import MessagesState, START\n\n# Set up the tool\n# We will have one real tool - a search tool\n# We'll also have one \"fake\" tool - a \"ask_human\" tool\n# Here we define any ACTUAL tools\nfrom langchain_core.tools import tool\nfrom langgraph.prebuilt import ToolNode\n\n@tool\ndef search(query: str):\n \"\"\"Call to surf the web.\"\"\"\n # This is a placeholder for the actual implementation\n # Don't let the LLM know this though 😊\n return [\n f\"I looked up: {query}. Result: It's sunny in San Francisco, but you better look out if you're a Gemini 😈.\"\n ]\n\n\ntools = [search]\ntool_node = ToolNode(tools)\n\n# Set up the model\nfrom langchain_anthropic import ChatAnthropic\n\nmodel = ChatAnthropic(model=\"claude-3-5-sonnet-20240620\")\n\n\n# We are going \"bind\" all tools to the model\n# We have the ACTUAL tools from above, but we also need a mock tool to ask a human\n# Since `bind_tools` takes in tools but also just tool definitions,\n# We can define a tool definition for `ask_human`\n\nfrom langchain_core.pydantic_v1 import BaseModel\n\nclass AskHuman(BaseModel):\n \"\"\"Ask the human a question\"\"\"\n question: str\n\n\nmodel = model.bind_tools(tools + [AskHuman])\n\n# Define nodes and conditional edges\n\nfrom langchain_core.messages import ToolMessage\n\nfrom langgraph.prebuilt import ToolInvocation\n\n\n# Define the function that determines whether to continue or not\ndef should_continue(state):\n messages = state[\"messages\"]\n last_message = messages[-1]\n # If there is no function call, then we finish\n if not last_message.tool_calls:\n return \"end\"\n # If tool call is asking Human, we return that node\n # You could also add logic here to let some system know that there's something that requires Human input\n # For example, send a slack message, etc\n elif last_message.tool_calls[0]['name'] == \"AskHuman\":\n return \"ask_human\"\n # Otherwise if there is, we continue\n else:\n return \"continue\"\n\n\n# Define the function that calls the model\ndef call_model(state):\n messages = state[\"messages\"]\n response = model.invoke(messages)\n # We return a list, because this will get added to the existing list\n return {\"messages\": [response]}\n\n\n\n# We define a fake node to ask the human\ndef ask_human(state):\n pass\n\n# Build the graph\n\nfrom langgraph.graph import END, StateGraph\n\n# Define a new graph\nworkflow = StateGraph(MessagesState)\n\n# Define the three nodes we will cycle between\nworkflow.add_node(\"agent\", call_model)\nworkflow.add_node(\"action\", tool_node)\nworkflow.add_node(\"ask_human\", ask_human)\n\n# Set the entrypoint as `agent`\n# This means that this node is the first one called\nworkflow.add_edge(START, \"agent\")\n\n# We now add a conditional edge\nworkflow.add_conditional_edges(\n # First, we define the start node. We use `agent`.\n # This means these are the edges taken after the `agent` node is called.\n \"agent\",\n # Next, we pass in the function that will determine which node is called next.\n should_continue,\n # Finally we pass in a mapping.\n # The keys are strings, and the values are other nodes.\n # END is a special node marking that the graph should finish.\n # What will happen is we will call `should_continue`, and then the output of that\n # will be matched against the keys in this mapping.\n # Based on which one it matches, that node will then be called.\n {\n # If `tools`, then we call the tool node.\n \"continue\": \"action\",\n # We may ask the human\n \"ask_human\": \"ask_human\",\n # Otherwise we finish.\n \"end\": END,\n },\n)\n\n# We now add a normal edge from `tools` to `agent`.\n# This means that after `tools` is called, `agent` node is called next.\nworkflow.add_edge(\"action\", \"agent\")\n\n# After we get back the human response, we go back to the agent\nworkflow.add_edge(\"ask_human\", \"agent\")\n\n# Set up memory\nfrom langgraph.checkpoint.memory import MemorySaver\n\nmemory = MemorySaver()\n\n# Finally, we compile it!\n# This compiles it into a LangChain Runnable,\n# meaning you can use it as you would any other runnable\n# We add a breakpoint BEFORE the `ask_human` node so it never executes\napp = workflow.compile(checkpointer=memory, interrupt_before=['ask_human'])"]
|
||||
"source": [
|
||||
"# Set up the state\n",
|
||||
"from langgraph.graph import MessagesState, START\n",
|
||||
"\n",
|
||||
"# Set up the tool\n",
|
||||
"# We will have one real tool - a search tool\n",
|
||||
"# We'll also have one \"fake\" tool - a \"ask_human\" tool\n",
|
||||
"# Here we define any ACTUAL tools\n",
|
||||
"from langchain_core.tools import tool\n",
|
||||
"from langgraph.prebuilt import ToolNode\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"@tool\n",
|
||||
"def search(query: str):\n",
|
||||
" \"\"\"Call to surf the web.\"\"\"\n",
|
||||
" # This is a placeholder for the actual implementation\n",
|
||||
" # Don't let the LLM know this though 😊\n",
|
||||
" return [\n",
|
||||
" f\"I looked up: {query}. Result: It's sunny in San Francisco, but you better look out if you're a Gemini 😈.\"\n",
|
||||
" ]\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"tools = [search]\n",
|
||||
"tool_node = ToolNode(tools)\n",
|
||||
"\n",
|
||||
"# Set up the model\n",
|
||||
"from langchain_anthropic import ChatAnthropic\n",
|
||||
"\n",
|
||||
"model = ChatAnthropic(model=\"claude-3-5-sonnet-20240620\")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# We are going \"bind\" all tools to the model\n",
|
||||
"# We have the ACTUAL tools from above, but we also need a mock tool to ask a human\n",
|
||||
"# Since `bind_tools` takes in tools but also just tool definitions,\n",
|
||||
"# We can define a tool definition for `ask_human`\n",
|
||||
"\n",
|
||||
"from langchain_core.pydantic_v1 import BaseModel\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class AskHuman(BaseModel):\n",
|
||||
" \"\"\"Ask the human a question\"\"\"\n",
|
||||
"\n",
|
||||
" question: str\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"model = model.bind_tools(tools + [AskHuman])\n",
|
||||
"\n",
|
||||
"# Define nodes and conditional edges\n",
|
||||
"\n",
|
||||
"from langchain_core.messages import ToolMessage\n",
|
||||
"\n",
|
||||
"from langgraph.prebuilt import ToolInvocation\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define the function that determines whether to continue or not\n",
|
||||
"def should_continue(state):\n",
|
||||
" messages = state[\"messages\"]\n",
|
||||
" last_message = messages[-1]\n",
|
||||
" # If there is no function call, then we finish\n",
|
||||
" if not last_message.tool_calls:\n",
|
||||
" return \"end\"\n",
|
||||
" # If tool call is asking Human, we return that node\n",
|
||||
" # You could also add logic here to let some system know that there's something that requires Human input\n",
|
||||
" # For example, send a slack message, etc\n",
|
||||
" elif last_message.tool_calls[0][\"name\"] == \"AskHuman\":\n",
|
||||
" return \"ask_human\"\n",
|
||||
" # Otherwise if there is, we continue\n",
|
||||
" else:\n",
|
||||
" return \"continue\"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define the function that calls the model\n",
|
||||
"def call_model(state):\n",
|
||||
" messages = state[\"messages\"]\n",
|
||||
" response = model.invoke(messages)\n",
|
||||
" # We return a list, because this will get added to the existing list\n",
|
||||
" return {\"messages\": [response]}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# We define a fake node to ask the human\n",
|
||||
"def ask_human(state):\n",
|
||||
" pass\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Build the graph\n",
|
||||
"\n",
|
||||
"from langgraph.graph import END, StateGraph\n",
|
||||
"\n",
|
||||
"# Define a new graph\n",
|
||||
"workflow = StateGraph(MessagesState)\n",
|
||||
"\n",
|
||||
"# Define the three nodes we will cycle between\n",
|
||||
"workflow.add_node(\"agent\", call_model)\n",
|
||||
"workflow.add_node(\"action\", tool_node)\n",
|
||||
"workflow.add_node(\"ask_human\", ask_human)\n",
|
||||
"\n",
|
||||
"# Set the entrypoint as `agent`\n",
|
||||
"# This means that this node is the first one called\n",
|
||||
"workflow.add_edge(START, \"agent\")\n",
|
||||
"\n",
|
||||
"# We now add a conditional edge\n",
|
||||
"workflow.add_conditional_edges(\n",
|
||||
" # First, we define the start node. We use `agent`.\n",
|
||||
" # This means these are the edges taken after the `agent` node is called.\n",
|
||||
" \"agent\",\n",
|
||||
" # Next, we pass in the function that will determine which node is called next.\n",
|
||||
" should_continue,\n",
|
||||
" # Finally we pass in a mapping.\n",
|
||||
" # The keys are strings, and the values are other nodes.\n",
|
||||
" # END is a special node marking that the graph should finish.\n",
|
||||
" # What will happen is we will call `should_continue`, and then the output of that\n",
|
||||
" # will be matched against the keys in this mapping.\n",
|
||||
" # Based on which one it matches, that node will then be called.\n",
|
||||
" {\n",
|
||||
" # If `tools`, then we call the tool node.\n",
|
||||
" \"continue\": \"action\",\n",
|
||||
" # We may ask the human\n",
|
||||
" \"ask_human\": \"ask_human\",\n",
|
||||
" # Otherwise we finish.\n",
|
||||
" \"end\": END,\n",
|
||||
" },\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"# We now add a normal edge from `tools` to `agent`.\n",
|
||||
"# This means that after `tools` is called, `agent` node is called next.\n",
|
||||
"workflow.add_edge(\"action\", \"agent\")\n",
|
||||
"\n",
|
||||
"# After we get back the human response, we go back to the agent\n",
|
||||
"workflow.add_edge(\"ask_human\", \"agent\")\n",
|
||||
"\n",
|
||||
"# Set up memory\n",
|
||||
"from langgraph.checkpoint.memory import MemorySaver\n",
|
||||
"\n",
|
||||
"memory = MemorySaver()\n",
|
||||
"\n",
|
||||
"# Finally, we compile it!\n",
|
||||
"# This compiles it into a LangChain Runnable,\n",
|
||||
"# meaning you can use it as you would any other runnable\n",
|
||||
"# We add a breakpoint BEFORE the `ask_human` node so it never executes\n",
|
||||
"app = workflow.compile(checkpointer=memory, interrupt_before=[\"ask_human\"])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -134,7 +280,16 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": ["from langchain_core.messages import HumanMessage\n\nconfig = {\"configurable\": {\"thread_id\": \"2\"}}\ninput_message = HumanMessage(content=\"Use the search tool to ask the user where they are, then look up the weather there\")\nfor event in app.stream({\"messages\": [input_message]}, config, stream_mode=\"values\"):\n event[\"messages\"][-1].pretty_print()"]
|
||||
"source": [
|
||||
"from langchain_core.messages import HumanMessage\n",
|
||||
"\n",
|
||||
"config = {\"configurable\": {\"thread_id\": \"2\"}}\n",
|
||||
"input_message = HumanMessage(\n",
|
||||
" content=\"Use the search tool to ask the user where they are, then look up the weather there\"\n",
|
||||
")\n",
|
||||
"for event in app.stream({\"messages\": [input_message]}, config, stream_mode=\"values\"):\n",
|
||||
" event[\"messages\"][-1].pretty_print()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -163,7 +318,31 @@
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": ["tool_call_id = app.get_state(config).values['messages'][-1].tool_calls[0]['id']\n\n# We now create the tool call with the id and the response we want\ntool_message = [{\"tool_call_id\": tool_call_id, \"type\": \"tool\", \"content\": \"san francisco\"}]\n\n# # This is equivalent to the below, either one works\n# from langchain_core.messages import ToolMessage\n# tool_message = [ToolMessage(tool_call_id=tool_call_id, content=\"san francisco\")]\n\n# We now update the state\n# Notice that we are also specifying `as_node=\"ask_human\"`\n# This will apply this update as this node,\n# which will make it so that afterwards it continues as normal\napp.update_state(config, {\"messages\": tool_message}, as_node=\"ask_human\")\n\n# We can check the state\n# We can see that the state currently has the `agent` node next\n# This is based on how we define our graph, \n# where after the `ask_human` node goes (which we just triggered)\n# there is an edge to the `agent` node\napp.get_state(config).next"]
|
||||
"source": [
|
||||
"tool_call_id = app.get_state(config).values[\"messages\"][-1].tool_calls[0][\"id\"]\n",
|
||||
"\n",
|
||||
"# We now create the tool call with the id and the response we want\n",
|
||||
"tool_message = [\n",
|
||||
" {\"tool_call_id\": tool_call_id, \"type\": \"tool\", \"content\": \"san francisco\"}\n",
|
||||
"]\n",
|
||||
"\n",
|
||||
"# # This is equivalent to the below, either one works\n",
|
||||
"# from langchain_core.messages import ToolMessage\n",
|
||||
"# tool_message = [ToolMessage(tool_call_id=tool_call_id, content=\"san francisco\")]\n",
|
||||
"\n",
|
||||
"# We now update the state\n",
|
||||
"# Notice that we are also specifying `as_node=\"ask_human\"`\n",
|
||||
"# This will apply this update as this node,\n",
|
||||
"# which will make it so that afterwards it continues as normal\n",
|
||||
"app.update_state(config, {\"messages\": tool_message}, as_node=\"ask_human\")\n",
|
||||
"\n",
|
||||
"# We can check the state\n",
|
||||
"# We can see that the state currently has the `agent` node next\n",
|
||||
"# This is based on how we define our graph,\n",
|
||||
"# where after the `ask_human` node goes (which we just triggered)\n",
|
||||
"# there is an edge to the `agent` node\n",
|
||||
"app.get_state(config).next"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -205,7 +384,9 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": ["for event in app.stream(None, config, stream_mode=\"values\"):\n event[\"messages\"][-1].pretty_print()"]
|
||||
"source": [
|
||||
"for event in app.stream(None, config, stream_mode=\"values\"):\n event[\"messages\"][-1].pretty_print()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
@@ -213,7 +394,9 @@
|
||||
"id": "f6f972d1-3d99-4fc1-8b33-92b71e74835d",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [""]
|
||||
"source": [
|
||||
""
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
|
||||
+884
-64
File diff suppressed because it is too large
Load Diff
@@ -42,7 +42,16 @@
|
||||
"source": [
|
||||
"\"\"\"Implementation of a langgraph checkpoint saver using Postgres.\"\"\"\n",
|
||||
"from contextlib import asynccontextmanager, contextmanager\n",
|
||||
"from typing import Any, AsyncGenerator, AsyncIterator, Generator, Optional, Union, Tuple, List\n",
|
||||
"from typing import (\n",
|
||||
" Any,\n",
|
||||
" AsyncGenerator,\n",
|
||||
" AsyncIterator,\n",
|
||||
" Generator,\n",
|
||||
" Optional,\n",
|
||||
" Union,\n",
|
||||
" Tuple,\n",
|
||||
" List,\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"import psycopg\n",
|
||||
"from langchain_core.runnables import RunnableConfig\n",
|
||||
@@ -136,8 +145,7 @@
|
||||
" sync_connection: Optional[Union[psycopg.Connection, ConnectionPool]] = None,\n",
|
||||
" async_connection: Optional[\n",
|
||||
" Union[psycopg.AsyncConnection, AsyncConnectionPool]\n",
|
||||
" ] = None\n",
|
||||
" \n",
|
||||
" ] = None,\n",
|
||||
" ):\n",
|
||||
" super().__init__(serde=JsonPlusSerializer())\n",
|
||||
" self.sync_connection = sync_connection\n",
|
||||
@@ -168,15 +176,12 @@
|
||||
" );\n",
|
||||
" \"\"\"\n",
|
||||
"\n",
|
||||
" \n",
|
||||
"\n",
|
||||
" @staticmethod\n",
|
||||
" def create_tables(connection: Union[psycopg.Connection, ConnectionPool], /) -> None:\n",
|
||||
" \"\"\"Create the schema for the checkpoint saver.\"\"\"\n",
|
||||
" with _get_sync_connection(connection) as conn:\n",
|
||||
" with conn.cursor() as cur:\n",
|
||||
" cur.execute(PostgresSaver.CREATE_TABLES_QUERY)\n",
|
||||
" \n",
|
||||
"\n",
|
||||
" @staticmethod\n",
|
||||
" async def acreate_tables(\n",
|
||||
@@ -209,7 +214,12 @@
|
||||
" metadata = EXCLUDED.metadata;\n",
|
||||
" \"\"\"\n",
|
||||
"\n",
|
||||
" def put(self, config: RunnableConfig, checkpoint: Checkpoint, metadata: CheckpointMetadata) -> RunnableConfig:\n",
|
||||
" def put(\n",
|
||||
" self,\n",
|
||||
" config: RunnableConfig,\n",
|
||||
" checkpoint: Checkpoint,\n",
|
||||
" metadata: CheckpointMetadata,\n",
|
||||
" ) -> RunnableConfig:\n",
|
||||
" \"\"\"Put the checkpoint for the given configuration.\n",
|
||||
" Args:\n",
|
||||
" config: The configuration for the checkpoint.\n",
|
||||
@@ -244,7 +254,10 @@
|
||||
" }\n",
|
||||
"\n",
|
||||
" async def aput(\n",
|
||||
" self, config: RunnableConfig, checkpoint: Checkpoint, metadata: CheckpointMetadata\n",
|
||||
" self,\n",
|
||||
" config: RunnableConfig,\n",
|
||||
" checkpoint: Checkpoint,\n",
|
||||
" metadata: CheckpointMetadata,\n",
|
||||
" ) -> RunnableConfig:\n",
|
||||
" \"\"\"Put the checkpoint for the given configuration.\n",
|
||||
" Args:\n",
|
||||
@@ -403,18 +416,18 @@
|
||||
" if value:\n",
|
||||
" checkpoint, metadata, thread_ts, parent_ts = value\n",
|
||||
" return CheckpointTuple(\n",
|
||||
" config=config,\n",
|
||||
" checkpoint=self.serde.loads(checkpoint),\n",
|
||||
" metadata=self.serde.loads(metadata),\n",
|
||||
" parent_config={\n",
|
||||
" \"configurable\": {\n",
|
||||
" \"thread_id\": thread_id,\n",
|
||||
" \"thread_ts\": thread_ts,\n",
|
||||
" }\n",
|
||||
" config=config,\n",
|
||||
" checkpoint=self.serde.loads(checkpoint),\n",
|
||||
" metadata=self.serde.loads(metadata),\n",
|
||||
" parent_config={\n",
|
||||
" \"configurable\": {\n",
|
||||
" \"thread_id\": thread_id,\n",
|
||||
" \"thread_ts\": thread_ts,\n",
|
||||
" }\n",
|
||||
" if thread_ts\n",
|
||||
" else None,\n",
|
||||
" )\n",
|
||||
" }\n",
|
||||
" if thread_ts\n",
|
||||
" else None,\n",
|
||||
" )\n",
|
||||
" else:\n",
|
||||
" cur.execute(\n",
|
||||
" self.GET_CHECKPOINT_QUERY,\n",
|
||||
@@ -473,18 +486,18 @@
|
||||
" if value:\n",
|
||||
" checkpoint, metadata, thread_ts, parent_ts = value\n",
|
||||
" return CheckpointTuple(\n",
|
||||
" config=config,\n",
|
||||
" checkpoint=self.serde.loads(checkpoint),\n",
|
||||
" metadata=self.serde.loads(metadata),\n",
|
||||
" parent_config={\n",
|
||||
" \"configurable\": {\n",
|
||||
" \"thread_id\": thread_id,\n",
|
||||
" \"thread_ts\": thread_ts,\n",
|
||||
" }\n",
|
||||
" config=config,\n",
|
||||
" checkpoint=self.serde.loads(checkpoint),\n",
|
||||
" metadata=self.serde.loads(metadata),\n",
|
||||
" parent_config={\n",
|
||||
" \"configurable\": {\n",
|
||||
" \"thread_id\": thread_id,\n",
|
||||
" \"thread_ts\": thread_ts,\n",
|
||||
" }\n",
|
||||
" if thread_ts\n",
|
||||
" else None,\n",
|
||||
" )\n",
|
||||
" }\n",
|
||||
" if thread_ts\n",
|
||||
" else None,\n",
|
||||
" )\n",
|
||||
" else:\n",
|
||||
" await cur.execute(\n",
|
||||
" self.GET_CHECKPOINT_QUERY,\n",
|
||||
@@ -663,9 +676,7 @@
|
||||
" max_size=20,\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"checkpointer = PostgresSaver(\n",
|
||||
" sync_connection=pool\n",
|
||||
")\n",
|
||||
"checkpointer = PostgresSaver(sync_connection=pool)\n",
|
||||
"checkpointer.create_tables(pool)"
|
||||
]
|
||||
},
|
||||
@@ -761,9 +772,7 @@
|
||||
"from psycopg import Connection\n",
|
||||
"\n",
|
||||
"with Connection.connect(DB_URI) as conn:\n",
|
||||
" checkpointer = PostgresSaver(\n",
|
||||
" sync_connection=conn\n",
|
||||
" )\n",
|
||||
" checkpointer = PostgresSaver(sync_connection=conn)\n",
|
||||
"\n",
|
||||
" graph = create_react_agent(model, tools=tools, checkpointer=checkpointer)\n",
|
||||
" config = {\"configurable\": {\"thread_id\": \"2\"}}\n",
|
||||
@@ -833,9 +842,7 @@
|
||||
" max_size=20,\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"checkpointer = PostgresSaver(\n",
|
||||
" async_connection=pool\n",
|
||||
")\n",
|
||||
"checkpointer = PostgresSaver(async_connection=pool)\n",
|
||||
"await checkpointer.acreate_tables(pool)"
|
||||
]
|
||||
},
|
||||
@@ -848,7 +855,9 @@
|
||||
"source": [
|
||||
"graph = create_react_agent(model, tools=tools, checkpointer=checkpointer)\n",
|
||||
"config = {\"configurable\": {\"thread_id\": \"3\"}}\n",
|
||||
"res = await graph.ainvoke({\"messages\": [(\"human\", \"what's the weather in nyc\")]}, config)"
|
||||
"res = await graph.ainvoke(\n",
|
||||
" {\"messages\": [(\"human\", \"what's the weather in nyc\")]}, config\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -900,12 +909,12 @@
|
||||
"from psycopg import AsyncConnection\n",
|
||||
"\n",
|
||||
"async with await AsyncConnection.connect(DB_URI) as conn:\n",
|
||||
" checkpointer = PostgresSaver(\n",
|
||||
" async_connection=conn\n",
|
||||
" )\n",
|
||||
" checkpointer = PostgresSaver(async_connection=conn)\n",
|
||||
" graph = create_react_agent(model, tools=tools, checkpointer=checkpointer)\n",
|
||||
" config = {\"configurable\": {\"thread_id\": \"4\"}}\n",
|
||||
" res = await graph.ainvoke({\"messages\": [(\"human\", \"what's the weather in nyc\")]}, config)\n",
|
||||
" res = await graph.ainvoke(\n",
|
||||
" {\"messages\": [(\"human\", \"what's the weather in nyc\")]}, config\n",
|
||||
" )\n",
|
||||
" checkpoint_tuples = [c async for c in checkpointer.alist(config)]"
|
||||
]
|
||||
},
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -56,7 +56,9 @@
|
||||
"id": "4a660963-bd3d-4c87-b2e4-b6e432055211",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": ["%%capture --no-stderr\n%pip install -U langchain_community tiktoken langchainhub scikit-learn langchain langgraph tavily-python nomic[local] langchain-nomic langchain_openai"]
|
||||
"source": [
|
||||
"%%capture --no-stderr\n%pip install -U langchain_community tiktoken langchainhub scikit-learn langchain langgraph tavily-python nomic[local] langchain-nomic langchain_openai"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
@@ -64,7 +66,12 @@
|
||||
"id": "68316ba0-854b-41e1-9af5-1f9e965946e3",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": ["# Search\nimport os\nos.environ[\"TAVILY_API_KEY\"] = \"xxx\""]
|
||||
"source": [
|
||||
"# Search\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"os.environ[\"TAVILY_API_KEY\"] = \"xxx\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
@@ -72,7 +79,9 @@
|
||||
"id": "0be68860-dded-481e-9fc7-a5042bf92c04",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": ["# Embedding (optional)\nos.environ[\"OPENAI_API_KEY\"] = \"xxx\""]
|
||||
"source": [
|
||||
"# Embedding (optional)\nos.environ[\"OPENAI_API_KEY\"] = \"xxx\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
@@ -80,7 +89,9 @@
|
||||
"id": "7248ab88-2b97-41eb-8dbb-4ea65525ed9a",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": ["# Tracing and testing (optional)\nos.environ[\"LANGCHAIN_API_KEY\"] = \"xxx\"\nos.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\nos.environ[\"LANGCHAIN_ENDPOINT\"] = \"https://api.smith.langchain.com\"\nos.environ[\"LANGCHAIN_PROJECT\"] = \"corrective-rag-agent-testing\""]
|
||||
"source": [
|
||||
"# Tracing and testing (optional)\nos.environ[\"LANGCHAIN_API_KEY\"] = \"xxx\"\nos.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\nos.environ[\"LANGCHAIN_ENDPOINT\"] = \"https://api.smith.langchain.com\"\nos.environ[\"LANGCHAIN_PROJECT\"] = \"corrective-rag-agent-testing\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -98,7 +109,9 @@
|
||||
"id": "2f4db331-c4d0-4c7c-a9a5-0bebc8a89c6c",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": ["local_llm = \"llama3\"\nmodel_tested = \"llama3-8b\"\nmetadata = f\"CRAG, {model_tested}\""]
|
||||
"source": [
|
||||
"local_llm = \"llama3\"\nmodel_tested = \"llama3-8b\"\nmetadata = f\"CRAG, {model_tested}\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -124,7 +137,48 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": ["from langchain.text_splitter import RecursiveCharacterTextSplitter\nfrom langchain_community.document_loaders import WebBaseLoader\nfrom langchain_community.vectorstores import SKLearnVectorStore\nfrom langchain_nomic.embeddings import NomicEmbeddings # local\nfrom langchain_openai import OpenAIEmbeddings # api\n\n# List of URLs to load documents from\nurls = [\n \"https://lilianweng.github.io/posts/2023-06-23-agent/\",\n \"https://lilianweng.github.io/posts/2023-03-15-prompt-engineering/\",\n \"https://lilianweng.github.io/posts/2023-10-25-adv-attack-llm/\",\n]\n\n# Load documents from the URLs\ndocs = [WebBaseLoader(url).load() for url in urls]\ndocs_list = [item for sublist in docs for item in sublist]\n\n# Initialize a text splitter with specified chunk size and overlap\ntext_splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder(\n chunk_size=250, chunk_overlap=0\n)\n\n# Split the documents into chunks\ndoc_splits = text_splitter.split_documents(docs_list)\n\n# Embedding\n'''\nembedding=NomicEmbeddings(\n model=\"nomic-embed-text-v1.5\",\n inference_mode=\"local\",\n)\n'''\nembedding = OpenAIEmbeddings()\n\n# Add the document chunks to the \"vector store\"\nvectorstore = SKLearnVectorStore.from_documents(\n documents=doc_splits,\n embedding=embedding,\n)\nretriever = vectorstore.as_retriever(k=4)"]
|
||||
"source": [
|
||||
"from langchain.text_splitter import RecursiveCharacterTextSplitter\n",
|
||||
"from langchain_community.document_loaders import WebBaseLoader\n",
|
||||
"from langchain_community.vectorstores import SKLearnVectorStore\n",
|
||||
"from langchain_nomic.embeddings import NomicEmbeddings # local\n",
|
||||
"from langchain_openai import OpenAIEmbeddings # api\n",
|
||||
"\n",
|
||||
"# List of URLs to load documents from\n",
|
||||
"urls = [\n",
|
||||
" \"https://lilianweng.github.io/posts/2023-06-23-agent/\",\n",
|
||||
" \"https://lilianweng.github.io/posts/2023-03-15-prompt-engineering/\",\n",
|
||||
" \"https://lilianweng.github.io/posts/2023-10-25-adv-attack-llm/\",\n",
|
||||
"]\n",
|
||||
"\n",
|
||||
"# Load documents from the URLs\n",
|
||||
"docs = [WebBaseLoader(url).load() for url in urls]\n",
|
||||
"docs_list = [item for sublist in docs for item in sublist]\n",
|
||||
"\n",
|
||||
"# Initialize a text splitter with specified chunk size and overlap\n",
|
||||
"text_splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder(\n",
|
||||
" chunk_size=250, chunk_overlap=0\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"# Split the documents into chunks\n",
|
||||
"doc_splits = text_splitter.split_documents(docs_list)\n",
|
||||
"\n",
|
||||
"# Embedding\n",
|
||||
"\"\"\"\n",
|
||||
"embedding=NomicEmbeddings(\n",
|
||||
" model=\"nomic-embed-text-v1.5\",\n",
|
||||
" inference_mode=\"local\",\n",
|
||||
")\n",
|
||||
"\"\"\"\n",
|
||||
"embedding = OpenAIEmbeddings()\n",
|
||||
"\n",
|
||||
"# Add the document chunks to the \"vector store\"\n",
|
||||
"vectorstore = SKLearnVectorStore.from_documents(\n",
|
||||
" documents=doc_splits,\n",
|
||||
" embedding=embedding,\n",
|
||||
")\n",
|
||||
"retriever = vectorstore.as_retriever(k=4)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
@@ -149,7 +203,9 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": ["### Retrieval Grader\n\nfrom langchain.prompts import PromptTemplate\nfrom langchain_community.chat_models import ChatOllama\nfrom langchain_core.output_parsers import JsonOutputParser\nfrom langchain_mistralai.chat_models import ChatMistralAI\n\n# LLM\nllm = ChatOllama(model=local_llm, format=\"json\", temperature=0)\n\n# Prompt\nprompt = PromptTemplate(\n template=\"\"\"You are a teacher grading a quiz. You will be given: \n 1/ a QUESTION\n 2/ A FACT provided by the student\n \n You are grading RELEVANCE RECALL:\n A score of 1 means that ANY of the statements in the FACT are relevant to the QUESTION. \n A score of 0 means that NONE of the statements in the FACT are relevant to the QUESTION. \n 1 is the highest (best) score. 0 is the lowest score you can give. \n \n Explain your reasoning in a step-by-step manner. Ensure your reasoning and conclusion are correct. \n \n Avoid simply stating the correct answer at the outset.\n \n Question: {question} \\n\n Fact: \\n\\n {documents} \\n\\n\n \n Give a binary score 'yes' or 'no' score to indicate whether the document is relevant to the question. \\n\n Provide the binary score as a JSON with a single key 'score' and no premable or explanation.\n \"\"\",\n input_variables=[\"question\", \"documents\"],\n)\n\nretrieval_grader = prompt | llm | JsonOutputParser()\nquestion = \"agent memory\"\ndocs = retriever.invoke(question)\ndoc_txt = docs[1].page_content\nprint(retrieval_grader.invoke({\"question\": question, \"documents\": doc_txt}))"]
|
||||
"source": [
|
||||
"### Retrieval Grader\n\nfrom langchain.prompts import PromptTemplate\nfrom langchain_community.chat_models import ChatOllama\nfrom langchain_core.output_parsers import JsonOutputParser\nfrom langchain_mistralai.chat_models import ChatMistralAI\n\n# LLM\nllm = ChatOllama(model=local_llm, format=\"json\", temperature=0)\n\n# Prompt\nprompt = PromptTemplate(\n template=\"\"\"You are a teacher grading a quiz. You will be given: \n 1/ a QUESTION\n 2/ A FACT provided by the student\n \n You are grading RELEVANCE RECALL:\n A score of 1 means that ANY of the statements in the FACT are relevant to the QUESTION. \n A score of 0 means that NONE of the statements in the FACT are relevant to the QUESTION. \n 1 is the highest (best) score. 0 is the lowest score you can give. \n \n Explain your reasoning in a step-by-step manner. Ensure your reasoning and conclusion are correct. \n \n Avoid simply stating the correct answer at the outset.\n \n Question: {question} \\n\n Fact: \\n\\n {documents} \\n\\n\n \n Give a binary score 'yes' or 'no' score to indicate whether the document is relevant to the question. \\n\n Provide the binary score as a JSON with a single key 'score' and no premable or explanation.\n \"\"\",\n input_variables=[\"question\", \"documents\"],\n)\n\nretrieval_grader = prompt | llm | JsonOutputParser()\nquestion = \"agent memory\"\ndocs = retriever.invoke(question)\ndoc_txt = docs[1].page_content\nprint(retrieval_grader.invoke({\"question\": question, \"documents\": doc_txt}))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
@@ -165,7 +221,9 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": ["### Generate\n\nfrom langchain_core.output_parsers import StrOutputParser\n\n# Prompt\nprompt = PromptTemplate(\n template=\"\"\"You are an assistant for question-answering tasks. \n \n Use the following documents to answer the question. \n \n If you don't know the answer, just say that you don't know. \n \n Use three sentences maximum and keep the answer concise:\n Question: {question} \n Documents: {documents} \n Answer: \n \"\"\",\n input_variables=[\"question\", \"documents\"],\n)\n\n# LLM\nllm = ChatOllama(model=local_llm, temperature=0)\n\n# Chain\nrag_chain = prompt | llm | StrOutputParser()\n\n# Run\ngeneration = rag_chain.invoke({\"documents\": docs, \"question\": question})\nprint(generation)"]
|
||||
"source": [
|
||||
"### Generate\n\nfrom langchain_core.output_parsers import StrOutputParser\n\n# Prompt\nprompt = PromptTemplate(\n template=\"\"\"You are an assistant for question-answering tasks. \n \n Use the following documents to answer the question. \n \n If you don't know the answer, just say that you don't know. \n \n Use three sentences maximum and keep the answer concise:\n Question: {question} \n Documents: {documents} \n Answer: \n \"\"\",\n input_variables=[\"question\", \"documents\"],\n)\n\n# LLM\nllm = ChatOllama(model=local_llm, temperature=0)\n\n# Chain\nrag_chain = prompt | llm | StrOutputParser()\n\n# Run\ngeneration = rag_chain.invoke({\"documents\": docs, \"question\": question})\nprint(generation)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
@@ -173,7 +231,9 @@
|
||||
"id": "b36a2f36-bc5f-408d-a5e8-3fa203c233f6",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": ["### Search\n\nfrom langchain_community.tools.tavily_search import TavilySearchResults\n\nweb_search_tool = TavilySearchResults(k=3)"]
|
||||
"source": [
|
||||
"### Search\n\nfrom langchain_community.tools.tavily_search import TavilySearchResults\n\nweb_search_tool = TavilySearchResults(k=3)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -202,7 +262,9 @@
|
||||
"output_type": "display_data"
|
||||
}
|
||||
],
|
||||
"source": ["from typing import List\nfrom typing_extensions import TypedDict\nfrom IPython.display import Image, display\nfrom langchain.schema import Document\nfrom langgraph.graph import START, END, StateGraph\n\n\nclass GraphState(TypedDict):\n \"\"\"\n Represents the state of our graph.\n\n Attributes:\n question: question\n generation: LLM generation\n search: whether to add search\n documents: list of documents\n \"\"\"\n\n question: str\n generation: str\n search: str\n documents: List[str]\n steps: List[str]\n\n\ndef retrieve(state):\n \"\"\"\n Retrieve documents\n\n Args:\n state (dict): The current graph state\n\n Returns:\n state (dict): New key added to state, documents, that contains retrieved documents\n \"\"\"\n question = state[\"question\"]\n documents = retriever.invoke(question)\n steps = state[\"steps\"]\n steps.append(\"retrieve_documents\")\n return {\"documents\": documents, \"question\": question, \"steps\": steps}\n\n\ndef generate(state):\n \"\"\"\n Generate answer\n\n Args:\n state (dict): The current graph state\n\n Returns:\n state (dict): New key added to state, generation, that contains LLM generation\n \"\"\"\n\n question = state[\"question\"]\n documents = state[\"documents\"]\n generation = rag_chain.invoke({\"documents\": documents, \"question\": question})\n steps = state[\"steps\"]\n steps.append(\"generate_answer\")\n return {\n \"documents\": documents,\n \"question\": question,\n \"generation\": generation,\n \"steps\": steps,\n }\n\n\ndef grade_documents(state):\n \"\"\"\n Determines whether the retrieved documents are relevant to the question.\n\n Args:\n state (dict): The current graph state\n\n Returns:\n state (dict): Updates documents key with only filtered relevant documents\n \"\"\"\n\n question = state[\"question\"]\n documents = state[\"documents\"]\n steps = state[\"steps\"]\n steps.append(\"grade_document_retrieval\")\n filtered_docs = []\n search = \"No\"\n for d in documents:\n score = retrieval_grader.invoke(\n {\"question\": question, \"documents\": d.page_content}\n )\n grade = score[\"score\"]\n if grade == \"yes\":\n filtered_docs.append(d)\n else:\n search = \"Yes\"\n continue\n return {\n \"documents\": filtered_docs,\n \"question\": question,\n \"search\": search,\n \"steps\": steps,\n }\n\n\ndef web_search(state):\n \"\"\"\n Web search based on the re-phrased question.\n\n Args:\n state (dict): The current graph state\n\n Returns:\n state (dict): Updates documents key with appended web results\n \"\"\"\n\n question = state[\"question\"]\n documents = state.get(\"documents\", [])\n steps = state[\"steps\"]\n steps.append(\"web_search\")\n web_results = web_search_tool.invoke({\"query\": question})\n documents.extend(\n [\n Document(page_content=d[\"content\"], metadata={\"url\": d[\"url\"]})\n for d in web_results\n ]\n )\n return {\"documents\": documents, \"question\": question, \"steps\": steps}\n\n\ndef decide_to_generate(state):\n \"\"\"\n Determines whether to generate an answer, or re-generate a question.\n\n Args:\n state (dict): The current graph state\n\n Returns:\n str: Binary decision for next node to call\n \"\"\"\n search = state[\"search\"]\n if search == \"Yes\":\n return \"search\"\n else:\n return \"generate\"\n\n\n# Graph\nworkflow = StateGraph(GraphState)\n\n# Define the nodes\nworkflow.add_node(\"retrieve\", retrieve) # retrieve\nworkflow.add_node(\"grade_documents\", grade_documents) # grade documents\nworkflow.add_node(\"generate\", generate) # generatae\nworkflow.add_node(\"web_search\", web_search) # web search\n\n# Build graph\nworkflow.add_edge(START, \"retrieve\")\nworkflow.add_edge(\"retrieve\", \"grade_documents\")\nworkflow.add_conditional_edges(\n \"grade_documents\",\n decide_to_generate,\n {\n \"search\": \"web_search\",\n \"generate\": \"generate\",\n },\n)\nworkflow.add_edge(\"web_search\", \"generate\")\nworkflow.add_edge(\"generate\", END)\n\ncustom_graph = workflow.compile()\n\ndisplay(Image(custom_graph.get_graph(xray=True).draw_mermaid_png()))"]
|
||||
"source": [
|
||||
"from typing import List\nfrom typing_extensions import TypedDict\nfrom IPython.display import Image, display\nfrom langchain.schema import Document\nfrom langgraph.graph import START, END, StateGraph\n\n\nclass GraphState(TypedDict):\n \"\"\"\n Represents the state of our graph.\n\n Attributes:\n question: question\n generation: LLM generation\n search: whether to add search\n documents: list of documents\n \"\"\"\n\n question: str\n generation: str\n search: str\n documents: List[str]\n steps: List[str]\n\n\ndef retrieve(state):\n \"\"\"\n Retrieve documents\n\n Args:\n state (dict): The current graph state\n\n Returns:\n state (dict): New key added to state, documents, that contains retrieved documents\n \"\"\"\n question = state[\"question\"]\n documents = retriever.invoke(question)\n steps = state[\"steps\"]\n steps.append(\"retrieve_documents\")\n return {\"documents\": documents, \"question\": question, \"steps\": steps}\n\n\ndef generate(state):\n \"\"\"\n Generate answer\n\n Args:\n state (dict): The current graph state\n\n Returns:\n state (dict): New key added to state, generation, that contains LLM generation\n \"\"\"\n\n question = state[\"question\"]\n documents = state[\"documents\"]\n generation = rag_chain.invoke({\"documents\": documents, \"question\": question})\n steps = state[\"steps\"]\n steps.append(\"generate_answer\")\n return {\n \"documents\": documents,\n \"question\": question,\n \"generation\": generation,\n \"steps\": steps,\n }\n\n\ndef grade_documents(state):\n \"\"\"\n Determines whether the retrieved documents are relevant to the question.\n\n Args:\n state (dict): The current graph state\n\n Returns:\n state (dict): Updates documents key with only filtered relevant documents\n \"\"\"\n\n question = state[\"question\"]\n documents = state[\"documents\"]\n steps = state[\"steps\"]\n steps.append(\"grade_document_retrieval\")\n filtered_docs = []\n search = \"No\"\n for d in documents:\n score = retrieval_grader.invoke(\n {\"question\": question, \"documents\": d.page_content}\n )\n grade = score[\"score\"]\n if grade == \"yes\":\n filtered_docs.append(d)\n else:\n search = \"Yes\"\n continue\n return {\n \"documents\": filtered_docs,\n \"question\": question,\n \"search\": search,\n \"steps\": steps,\n }\n\n\ndef web_search(state):\n \"\"\"\n Web search based on the re-phrased question.\n\n Args:\n state (dict): The current graph state\n\n Returns:\n state (dict): Updates documents key with appended web results\n \"\"\"\n\n question = state[\"question\"]\n documents = state.get(\"documents\", [])\n steps = state[\"steps\"]\n steps.append(\"web_search\")\n web_results = web_search_tool.invoke({\"query\": question})\n documents.extend(\n [\n Document(page_content=d[\"content\"], metadata={\"url\": d[\"url\"]})\n for d in web_results\n ]\n )\n return {\"documents\": documents, \"question\": question, \"steps\": steps}\n\n\ndef decide_to_generate(state):\n \"\"\"\n Determines whether to generate an answer, or re-generate a question.\n\n Args:\n state (dict): The current graph state\n\n Returns:\n str: Binary decision for next node to call\n \"\"\"\n search = state[\"search\"]\n if search == \"Yes\":\n return \"search\"\n else:\n return \"generate\"\n\n\n# Graph\nworkflow = StateGraph(GraphState)\n\n# Define the nodes\nworkflow.add_node(\"retrieve\", retrieve) # retrieve\nworkflow.add_node(\"grade_documents\", grade_documents) # grade documents\nworkflow.add_node(\"generate\", generate) # generatae\nworkflow.add_node(\"web_search\", web_search) # web search\n\n# Build graph\nworkflow.add_edge(START, \"retrieve\")\nworkflow.add_edge(\"retrieve\", \"grade_documents\")\nworkflow.add_conditional_edges(\n \"grade_documents\",\n decide_to_generate,\n {\n \"search\": \"web_search\",\n \"generate\": \"generate\",\n },\n)\nworkflow.add_edge(\"web_search\", \"generate\")\nworkflow.add_edge(\"generate\", END)\n\ncustom_graph = workflow.compile()\n\ndisplay(Image(custom_graph.get_graph(xray=True).draw_mermaid_png()))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
@@ -225,7 +287,22 @@
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": ["import uuid\n\ndef predict_custom_agent_local_answer(example: dict):\n config = {\"configurable\": {\"thread_id\": str(uuid.uuid4())}}\n state_dict = custom_graph.invoke(\n {\"question\": example[\"input\"], \"steps\": []}, config\n )\n return {\"response\": state_dict[\"generation\"], \"steps\": state_dict[\"steps\"]}\n\n\nexample = {\"input\": \"What are the types of agent memory?\"}\nresponse = predict_custom_agent_local_answer(example)\nresponse"]
|
||||
"source": [
|
||||
"import uuid\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def predict_custom_agent_local_answer(example: dict):\n",
|
||||
" config = {\"configurable\": {\"thread_id\": str(uuid.uuid4())}}\n",
|
||||
" state_dict = custom_graph.invoke(\n",
|
||||
" {\"question\": example[\"input\"], \"steps\": []}, config\n",
|
||||
" )\n",
|
||||
" return {\"response\": state_dict[\"generation\"], \"steps\": state_dict[\"steps\"]}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"example = {\"input\": \"What are the types of agent memory?\"}\n",
|
||||
"response = predict_custom_agent_local_answer(example)\n",
|
||||
"response"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -261,7 +338,9 @@
|
||||
"id": "b83706ac-724b-46b1-9f08-66e6c4fac742",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": ["from langsmith import Client\n\nclient = Client()\n\n# Create a dataset\nexamples = [\n (\n \"How does the ReAct agent use self-reflection? \",\n \"ReAct integrates reasoning and acting, performing actions - such tools like Wikipedia search API - and then observing / reasoning about the tool outputs.\",\n ),\n (\n \"What are the types of biases that can arise with few-shot prompting?\",\n \"The biases that can arise with few-shot prompting include (1) Majority label bias, (2) Recency bias, and (3) Common token bias.\",\n ),\n (\n \"What are five types of adversarial attacks?\",\n \"Five types of adversarial attacks are (1) Token manipulation, (2) Gradient based attack, (3) Jailbreak prompting, (4) Human red-teaming, (5) Model red-teaming.\",\n ),\n (\n \"Who did the Chicago Bears draft first in the 2024 NFL draft”?\",\n \"The Chicago Bears drafted Caleb Williams first in the 2024 NFL draft.\",\n ),\n (\"Who won the 2024 NBA finals?\", \"The Boston Celtics on the 2024 NBA finals\"),\n]\n\n# Save it\ndataset_name = \"Corrective RAG Agent Testing\"\nif not client.has_dataset(dataset_name=dataset_name):\n dataset = client.create_dataset(dataset_name=dataset_name)\n inputs, outputs = zip(\n *[({\"input\": text}, {\"output\": label}) for text, label in examples]\n )\n client.create_examples(inputs=inputs, outputs=outputs, dataset_id=dataset.id)"]
|
||||
"source": [
|
||||
"from langsmith import Client\n\nclient = Client()\n\n# Create a dataset\nexamples = [\n (\n \"How does the ReAct agent use self-reflection? \",\n \"ReAct integrates reasoning and acting, performing actions - such tools like Wikipedia search API - and then observing / reasoning about the tool outputs.\",\n ),\n (\n \"What are the types of biases that can arise with few-shot prompting?\",\n \"The biases that can arise with few-shot prompting include (1) Majority label bias, (2) Recency bias, and (3) Common token bias.\",\n ),\n (\n \"What are five types of adversarial attacks?\",\n \"Five types of adversarial attacks are (1) Token manipulation, (2) Gradient based attack, (3) Jailbreak prompting, (4) Human red-teaming, (5) Model red-teaming.\",\n ),\n (\n \"Who did the Chicago Bears draft first in the 2024 NFL draft”?\",\n \"The Chicago Bears drafted Caleb Williams first in the 2024 NFL draft.\",\n ),\n (\"Who won the 2024 NBA finals?\", \"The Boston Celtics on the 2024 NBA finals\"),\n]\n\n# Save it\ndataset_name = \"Corrective RAG Agent Testing\"\nif not client.has_dataset(dataset_name=dataset_name):\n dataset = client.create_dataset(dataset_name=dataset_name)\n inputs, outputs = zip(\n *[({\"input\": text}, {\"output\": label}) for text, label in examples]\n )\n client.create_examples(inputs=inputs, outputs=outputs, dataset_id=dataset.id)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -281,7 +360,39 @@
|
||||
"id": "0a63776c-f9cd-46ce-b8cf-95c066dc5b06",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": ["from langchain import hub\nfrom langchain_openai import ChatOpenAI\n\n# Grade prompt\ngrade_prompt_answer_accuracy = hub.pull(\"langchain-ai/rag-answer-vs-reference\")\n\ndef answer_evaluator(run, example) -> dict:\n \"\"\"\n A simple evaluator for RAG answer accuracy\n \"\"\"\n\n # Get the question, the ground truth reference answer, RAG chain answer prediction\n input_question = example.inputs[\"input\"]\n reference = example.outputs[\"output\"]\n prediction = run.outputs[\"response\"]\n\n # Define an LLM grader\n llm = ChatOpenAI(model=\"gpt-4o\", temperature=0)\n answer_grader = grade_prompt_answer_accuracy | llm\n\n # Run evaluator\n score = answer_grader.invoke(\n {\n \"question\": input_question,\n \"correct_answer\": reference,\n \"student_answer\": prediction,\n }\n )\n score = score[\"Score\"]\n return {\"key\": \"answer_v_reference_score\", \"score\": score}"]
|
||||
"source": [
|
||||
"from langchain import hub\n",
|
||||
"from langchain_openai import ChatOpenAI\n",
|
||||
"\n",
|
||||
"# Grade prompt\n",
|
||||
"grade_prompt_answer_accuracy = hub.pull(\"langchain-ai/rag-answer-vs-reference\")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def answer_evaluator(run, example) -> dict:\n",
|
||||
" \"\"\"\n",
|
||||
" A simple evaluator for RAG answer accuracy\n",
|
||||
" \"\"\"\n",
|
||||
"\n",
|
||||
" # Get the question, the ground truth reference answer, RAG chain answer prediction\n",
|
||||
" input_question = example.inputs[\"input\"]\n",
|
||||
" reference = example.outputs[\"output\"]\n",
|
||||
" prediction = run.outputs[\"response\"]\n",
|
||||
"\n",
|
||||
" # Define an LLM grader\n",
|
||||
" llm = ChatOpenAI(model=\"gpt-4o\", temperature=0)\n",
|
||||
" answer_grader = grade_prompt_answer_accuracy | llm\n",
|
||||
"\n",
|
||||
" # Run evaluator\n",
|
||||
" score = answer_grader.invoke(\n",
|
||||
" {\n",
|
||||
" \"question\": input_question,\n",
|
||||
" \"correct_answer\": reference,\n",
|
||||
" \"student_answer\": prediction,\n",
|
||||
" }\n",
|
||||
" )\n",
|
||||
" score = score[\"Score\"]\n",
|
||||
" return {\"key\": \"answer_v_reference_score\", \"score\": score}"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -301,7 +412,51 @@
|
||||
"id": "deb28175-27a1-4afc-9747-2983e87fc881",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": ["from langsmith.schemas import Example, Run\n\n# Reasoning traces that we expect the agents to take\nexpected_trajectory_1 = [\n \"retrieve_documents\",\n \"grade_document_retrieval\",\n \"web_search\",\n \"generate_answer\",\n]\nexpected_trajectory_2 = [\n \"retrieve_documents\",\n \"grade_document_retrieval\",\n \"generate_answer\",\n]\n\ndef check_trajectory_react(root_run: Run, example: Example) -> dict:\n \"\"\"\n Check if all expected tools are called in exact order and without any additional tool calls.\n \"\"\"\n messages = root_run.outputs[\"messages\"]\n tool_calls = find_tool_calls_react(messages)\n print(f\"Tool calls ReAct agent: {tool_calls}\")\n if tool_calls == expected_trajectory_1 or tool_calls == expected_trajectory_2:\n score = 1\n else:\n score = 0\n\n return {\"score\": int(score), \"key\": \"tool_calls_in_exact_order\"}\n\n\ndef check_trajectory_custom(root_run: Run, example: Example) -> dict:\n \"\"\"\n Check if all expected tools are called in exact order and without any additional tool calls.\n \"\"\"\n tool_calls = root_run.outputs[\"steps\"]\n print(f\"Tool calls custom agent: {tool_calls}\")\n if tool_calls == expected_trajectory_1 or tool_calls == expected_trajectory_2:\n score = 1\n else:\n score = 0\n\n return {\"score\": int(score), \"key\": \"tool_calls_in_exact_order\"}"]
|
||||
"source": [
|
||||
"from langsmith.schemas import Example, Run\n",
|
||||
"\n",
|
||||
"# Reasoning traces that we expect the agents to take\n",
|
||||
"expected_trajectory_1 = [\n",
|
||||
" \"retrieve_documents\",\n",
|
||||
" \"grade_document_retrieval\",\n",
|
||||
" \"web_search\",\n",
|
||||
" \"generate_answer\",\n",
|
||||
"]\n",
|
||||
"expected_trajectory_2 = [\n",
|
||||
" \"retrieve_documents\",\n",
|
||||
" \"grade_document_retrieval\",\n",
|
||||
" \"generate_answer\",\n",
|
||||
"]\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def check_trajectory_react(root_run: Run, example: Example) -> dict:\n",
|
||||
" \"\"\"\n",
|
||||
" Check if all expected tools are called in exact order and without any additional tool calls.\n",
|
||||
" \"\"\"\n",
|
||||
" messages = root_run.outputs[\"messages\"]\n",
|
||||
" tool_calls = find_tool_calls_react(messages)\n",
|
||||
" print(f\"Tool calls ReAct agent: {tool_calls}\")\n",
|
||||
" if tool_calls == expected_trajectory_1 or tool_calls == expected_trajectory_2:\n",
|
||||
" score = 1\n",
|
||||
" else:\n",
|
||||
" score = 0\n",
|
||||
"\n",
|
||||
" return {\"score\": int(score), \"key\": \"tool_calls_in_exact_order\"}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def check_trajectory_custom(root_run: Run, example: Example) -> dict:\n",
|
||||
" \"\"\"\n",
|
||||
" Check if all expected tools are called in exact order and without any additional tool calls.\n",
|
||||
" \"\"\"\n",
|
||||
" tool_calls = root_run.outputs[\"steps\"]\n",
|
||||
" print(f\"Tool calls custom agent: {tool_calls}\")\n",
|
||||
" if tool_calls == expected_trajectory_1 or tool_calls == expected_trajectory_2:\n",
|
||||
" score = 1\n",
|
||||
" else:\n",
|
||||
" score = 0\n",
|
||||
"\n",
|
||||
" return {\"score\": int(score), \"key\": \"tool_calls_in_exact_order\"}"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
@@ -355,7 +510,20 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": ["from langsmith.evaluation import evaluate\n\nexperiment_prefix = f\"custom-agent-{model_tested}\"\nexperiment_results = evaluate(\n predict_custom_agent_local_answer,\n data=dataset_name,\n evaluators=[answer_evaluator, check_trajectory_custom],\n experiment_prefix=experiment_prefix + \"-answer-and-tool-use\",\n num_repetitions=3,\n max_concurrency=1, # Use when running locally\n metadata={\"version\": metadata},\n)"]
|
||||
"source": [
|
||||
"from langsmith.evaluation import evaluate\n",
|
||||
"\n",
|
||||
"experiment_prefix = f\"custom-agent-{model_tested}\"\n",
|
||||
"experiment_results = evaluate(\n",
|
||||
" predict_custom_agent_local_answer,\n",
|
||||
" data=dataset_name,\n",
|
||||
" evaluators=[answer_evaluator, check_trajectory_custom],\n",
|
||||
" experiment_prefix=experiment_prefix + \"-answer-and-tool-use\",\n",
|
||||
" num_repetitions=3,\n",
|
||||
" max_concurrency=1, # Use when running locally\n",
|
||||
" metadata={\"version\": metadata},\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {
|
||||
@@ -382,7 +550,9 @@
|
||||
"id": "79295798-0181-417e-abad-11dddb6ff05e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [""]
|
||||
"source": [
|
||||
""
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -210,12 +210,14 @@
|
||||
"from langchain_core.pydantic_v1 import BaseModel\n",
|
||||
"from langchain_core.runnables import RunnableConfig\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class AgentContext(BaseModel):\n",
|
||||
" class Config:\n",
|
||||
" arbitrary_types_allowed = True\n",
|
||||
"\n",
|
||||
" httpx_session: httpx.Client\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"@contextmanager\n",
|
||||
"def make_agent_context(config: RunnableConfig):\n",
|
||||
" # here you could read the config values passed invoke/stream to customize the context object\n",
|
||||
@@ -225,7 +227,7 @@
|
||||
" try:\n",
|
||||
" yield AgentContext(httpx_session=session)\n",
|
||||
" finally:\n",
|
||||
" session.close()\n"
|
||||
" session.close()"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -324,9 +326,9 @@
|
||||
"# Define the function that calls the model\n",
|
||||
"def call_model(state):\n",
|
||||
" # using context value\n",
|
||||
" req = state.context.httpx_session.get('https://www.langchain.com/')\n",
|
||||
" req = state.context.httpx_session.get(\"https://www.langchain.com/\")\n",
|
||||
" assert req.status_code == 200, req\n",
|
||||
" \n",
|
||||
"\n",
|
||||
" messages = state.messages\n",
|
||||
" response = model.invoke(messages)\n",
|
||||
" # We return a list, because this will get added to the existing list\n",
|
||||
|
||||
+163
-14
@@ -32,7 +32,10 @@
|
||||
"id": "af4ce0ba-7596-4e5f-8bf8-0b0bd6e62833",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": ["%%capture --no-stderr\n%pip install --quiet -U langgraph langchain_openai"]
|
||||
"source": [
|
||||
"%%capture --no-stderr\n",
|
||||
"%pip install --quiet -U langgraph langchain_openai"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -48,7 +51,18 @@
|
||||
"id": "c903a1cf-2977-4e2d-ad7d-8b3946821d89",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": ["import getpass\nimport os\n\n\ndef _set_env(var: str):\n if not os.environ.get(var):\n os.environ[var] = getpass.getpass(f\"{var}: \")\n\n\n_set_env(\"OPENAI_API_KEY\")"]
|
||||
"source": [
|
||||
"import getpass\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def _set_env(var: str):\n",
|
||||
" if not os.environ.get(var):\n",
|
||||
" os.environ[var] = getpass.getpass(f\"{var}: \")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"_set_env(\"OPENAI_API_KEY\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -64,7 +78,10 @@
|
||||
"id": "95e25aec-7c9f-4a63-b143-225d0e9a79c3",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": ["os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n_set_env(\"LANGCHAIN_API_KEY\")"]
|
||||
"source": [
|
||||
"os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n",
|
||||
"_set_env(\"LANGCHAIN_API_KEY\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -84,7 +101,20 @@
|
||||
"id": "d7ef57dd-5d6e-4ad3-9377-a92201c1310e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": ["from langchain_core.tools import tool\n\n\n@tool\ndef search(query: str):\n \"\"\"Call to surf the web.\"\"\"\n # This is a placeholder for the actual implementation\n # Don't let the LLM know this though 😊\n return [\"The answer to your question lies within.\"]\n\n\ntools = [search]"]
|
||||
"source": [
|
||||
"from langchain_core.tools import tool\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"@tool\n",
|
||||
"def search(query: str):\n",
|
||||
" \"\"\"Call to surf the web.\"\"\"\n",
|
||||
" # This is a placeholder for the actual implementation\n",
|
||||
" # Don't let the LLM know this though 😊\n",
|
||||
" return [\"The answer to your question lies within.\"]\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"tools = [search]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -103,7 +133,11 @@
|
||||
"id": "5cf3331e-ccb3-41c8-aeb9-a840a94d41e7",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": ["from langgraph.prebuilt import ToolExecutor\n\ntool_executor = ToolExecutor(tools)"]
|
||||
"source": [
|
||||
"from langgraph.prebuilt import ToolExecutor\n",
|
||||
"\n",
|
||||
"tool_executor = ToolExecutor(tools)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -127,7 +161,11 @@
|
||||
"id": "892b54b9-75f0-4804-9ed0-88b5e5532989",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": ["from langchain_openai import ChatOpenAI\n\nmodel = ChatOpenAI(temperature=0)"]
|
||||
"source": [
|
||||
"from langchain_openai import ChatOpenAI\n",
|
||||
"\n",
|
||||
"model = ChatOpenAI(temperature=0)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -145,7 +183,9 @@
|
||||
"id": "cd3cbae5-d92c-4559-a4aa-44721b80d107",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": ["model = model.bind_tools(tools)"]
|
||||
"source": [
|
||||
"model = model.bind_tools(tools)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -171,7 +211,17 @@
|
||||
"id": "ea793afa-2eab-4901-910d-6eed90cd6564",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": ["import operator\nfrom typing import Annotated, Sequence\n\nfrom langchain_core.messages import BaseMessage\nfrom langchain_core.pydantic_v1 import BaseModel\n\n\nclass AgentState(BaseModel):\n messages: Annotated[Sequence[BaseMessage], operator.add]"]
|
||||
"source": [
|
||||
"import operator\n",
|
||||
"from typing import Annotated, Sequence\n",
|
||||
"\n",
|
||||
"from langchain_core.messages import BaseMessage\n",
|
||||
"from langchain_core.pydantic_v1 import BaseModel\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class AgentState(BaseModel):\n",
|
||||
" messages: Annotated[Sequence[BaseMessage], operator.add]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -210,7 +260,53 @@
|
||||
"id": "3b541bb9-900c-40d0-964d-7b5dfee30667",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": ["from langchain_core.messages import ToolMessage\n\nfrom langgraph.prebuilt import ToolInvocation\n\n\n# Define the function that determines whether to continue or not\ndef should_continue(state):\n messages = state.messages\n last_message = messages[-1]\n # If there is no function call, then we finish\n if not last_message.tool_calls:\n return \"end\"\n # Otherwise if there is, we continue\n else:\n return \"continue\"\n\n\n# Define the function that calls the model\ndef call_model(state):\n messages = state.messages\n response = model.invoke(messages)\n # We return a list, because this will get added to the existing list\n return {\"messages\": [response]}\n\n\n# Define the function to execute tools\ndef call_tool(state):\n messages = state.messages\n # Based on the continue condition\n # we know the last message involves a function call\n last_message = messages[-1]\n # We construct an ToolInvocation from the function_call\n tool_call = last_message.tool_calls[0]\n action = ToolInvocation(\n tool=tool_call[\"name\"],\n tool_input=tool_call[\"args\"],\n )\n # We call the tool_executor and get back a response\n response = tool_executor.invoke(action)\n # We use the response to create a ToolMessage\n tool_message = ToolMessage(\n content=str(response), name=action.tool, tool_call_id=tool_call[\"id\"]\n )\n # We return a list, because this will get added to the existing list\n return {\"messages\": [tool_message]}"]
|
||||
"source": [
|
||||
"from langchain_core.messages import ToolMessage\n",
|
||||
"\n",
|
||||
"from langgraph.prebuilt import ToolInvocation\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define the function that determines whether to continue or not\n",
|
||||
"def should_continue(state):\n",
|
||||
" messages = state.messages\n",
|
||||
" last_message = messages[-1]\n",
|
||||
" # If there is no function call, then we finish\n",
|
||||
" if not last_message.tool_calls:\n",
|
||||
" return \"end\"\n",
|
||||
" # Otherwise if there is, we continue\n",
|
||||
" else:\n",
|
||||
" return \"continue\"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define the function that calls the model\n",
|
||||
"def call_model(state):\n",
|
||||
" messages = state.messages\n",
|
||||
" response = model.invoke(messages)\n",
|
||||
" # We return a list, because this will get added to the existing list\n",
|
||||
" return {\"messages\": [response]}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define the function to execute tools\n",
|
||||
"def call_tool(state):\n",
|
||||
" messages = state.messages\n",
|
||||
" # Based on the continue condition\n",
|
||||
" # we know the last message involves a function call\n",
|
||||
" last_message = messages[-1]\n",
|
||||
" # We construct an ToolInvocation from the function_call\n",
|
||||
" tool_call = last_message.tool_calls[0]\n",
|
||||
" action = ToolInvocation(\n",
|
||||
" tool=tool_call[\"name\"],\n",
|
||||
" tool_input=tool_call[\"args\"],\n",
|
||||
" )\n",
|
||||
" # We call the tool_executor and get back a response\n",
|
||||
" response = tool_executor.invoke(action)\n",
|
||||
" # We use the response to create a ToolMessage\n",
|
||||
" tool_message = ToolMessage(\n",
|
||||
" content=str(response), name=action.tool, tool_call_id=tool_call[\"id\"]\n",
|
||||
" )\n",
|
||||
" # We return a list, because this will get added to the existing list\n",
|
||||
" return {\"messages\": [tool_message]}"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -228,7 +324,50 @@
|
||||
"id": "813ae66c-3b58-4283-a02a-36da72a2ab90",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": ["from langgraph.graph import END, StateGraph, START\n\n# Define a new graph\nworkflow = StateGraph(AgentState)\n\n# Define the two nodes we will cycle between\nworkflow.add_node(\"agent\", call_model)\nworkflow.add_node(\"action\", call_tool)\n\n# Set the entrypoint as `agent`\n# This means that this node is the first one called\nworkflow.add_edge(START, \"agent\")\n\n# We now add a conditional edge\nworkflow.add_conditional_edges(\n # First, we define the start node. We use `agent`.\n # This means these are the edges taken after the `agent` node is called.\n \"agent\",\n # Next, we pass in the function that will determine which node is called next.\n should_continue,\n # Finally we pass in a mapping.\n # The keys are strings, and the values are other nodes.\n # END is a special node marking that the graph should finish.\n # What will happen is we will call `should_continue`, and then the output of that\n # will be matched against the keys in this mapping.\n # Based on which one it matches, that node will then be called.\n {\n # If `tools`, then we call the tool node.\n \"continue\": \"action\",\n # Otherwise we finish.\n \"end\": END,\n },\n)\n\n# We now add a normal edge from `tools` to `agent`.\n# This means that after `tools` is called, `agent` node is called next.\nworkflow.add_edge(\"action\", \"agent\")\n\n# Finally, we compile it!\n# This compiles it into a LangChain Runnable,\n# meaning you can use it as you would any other runnable\napp = workflow.compile()"]
|
||||
"source": [
|
||||
"from langgraph.graph import END, StateGraph, START\n",
|
||||
"\n",
|
||||
"# Define a new graph\n",
|
||||
"workflow = StateGraph(AgentState)\n",
|
||||
"\n",
|
||||
"# Define the two nodes we will cycle between\n",
|
||||
"workflow.add_node(\"agent\", call_model)\n",
|
||||
"workflow.add_node(\"action\", call_tool)\n",
|
||||
"\n",
|
||||
"# Set the entrypoint as `agent`\n",
|
||||
"# This means that this node is the first one called\n",
|
||||
"workflow.add_edge(START, \"agent\")\n",
|
||||
"\n",
|
||||
"# We now add a conditional edge\n",
|
||||
"workflow.add_conditional_edges(\n",
|
||||
" # First, we define the start node. We use `agent`.\n",
|
||||
" # This means these are the edges taken after the `agent` node is called.\n",
|
||||
" \"agent\",\n",
|
||||
" # Next, we pass in the function that will determine which node is called next.\n",
|
||||
" should_continue,\n",
|
||||
" # Finally we pass in a mapping.\n",
|
||||
" # The keys are strings, and the values are other nodes.\n",
|
||||
" # END is a special node marking that the graph should finish.\n",
|
||||
" # What will happen is we will call `should_continue`, and then the output of that\n",
|
||||
" # will be matched against the keys in this mapping.\n",
|
||||
" # Based on which one it matches, that node will then be called.\n",
|
||||
" {\n",
|
||||
" # If `tools`, then we call the tool node.\n",
|
||||
" \"continue\": \"action\",\n",
|
||||
" # Otherwise we finish.\n",
|
||||
" \"end\": END,\n",
|
||||
" },\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"# We now add a normal edge from `tools` to `agent`.\n",
|
||||
"# This means that after `tools` is called, `agent` node is called next.\n",
|
||||
"workflow.add_edge(\"action\", \"agent\")\n",
|
||||
"\n",
|
||||
"# Finally, we compile it!\n",
|
||||
"# This compiles it into a LangChain Runnable,\n",
|
||||
"# meaning you can use it as you would any other runnable\n",
|
||||
"app = workflow.compile()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
@@ -247,7 +386,11 @@
|
||||
"output_type": "display_data"
|
||||
}
|
||||
],
|
||||
"source": ["from IPython.display import Image, display\n\ndisplay(Image(app.get_graph().draw_mermaid_png()))"]
|
||||
"source": [
|
||||
"from IPython.display import Image, display\n",
|
||||
"\n",
|
||||
"display(Image(app.get_graph().draw_mermaid_png()))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -289,7 +432,13 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": ["from langchain_core.messages import HumanMessage\n\ninputs = {\"messages\": [HumanMessage(content=\"what is the weather in sf\")]}\nfor chunk in app.stream(inputs, stream_mode=\"values\"):\n chunk[\"messages\"][-1].pretty_print()"]
|
||||
"source": [
|
||||
"from langchain_core.messages import HumanMessage\n",
|
||||
"\n",
|
||||
"inputs = {\"messages\": [HumanMessage(content=\"what is the weather in sf\")]}\n",
|
||||
"for chunk in app.stream(inputs, stream_mode=\"values\"):\n",
|
||||
" chunk[\"messages\"][-1].pretty_print()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
@@ -297,7 +446,7 @@
|
||||
"id": "296c7456-da05-4326-95dc-47d6b312da9d",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [""]
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
@@ -316,7 +465,7 @@
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.2"
|
||||
"version": "3.12.2"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
|
||||
@@ -753,7 +753,7 @@
|
||||
"execution_count": 73,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": ["from langgraph.checkpoint.memory import MemorySaver\n\nbuilder_of_storm = StateGraph(ResearchState)\n\nnodes = [\n (\"init_research\", initialize_research),\n (\"conduct_interviews\", conduct_interviews),\n (\"refine_outline\", refine_outline),\n (\"index_references\", index_references),\n (\"write_sections\", write_sections),\n (\"write_article\", write_article),\n]\nfor i in range(len(nodes)):\n name, node = nodes[i]\n builder_of_storm.add_node(name, node)\n if i > 0:\n builder_of_storm.add_edge(nodes[i - 1][0], name)\n\nbuilder_of_storm.add_edge(START, nodes[0][0])\nbuilder_of_storm.set_finish_point(nodes[-1][0])\nstorm = builder_of_storm.compile(checkpointer=MemorySaver())"]
|
||||
"source": ["from langgraph.checkpoint.memory import MemorySaver\n\nbuilder_of_storm = StateGraph(ResearchState)\n\nnodes = [\n (\"init_research\", initialize_research),\n (\"conduct_interviews\", conduct_interviews),\n (\"refine_outline\", refine_outline),\n (\"index_references\", index_references),\n (\"write_sections\", write_sections),\n (\"write_article\", write_article),\n]\nfor i in range(len(nodes)):\n name, node = nodes[i]\n builder_of_storm.add_node(name, node)\n if i > 0:\n builder_of_storm.add_edge(nodes[i - 1][0], name)\n\nbuilder_of_storm.add_edge(START, nodes[0][0])\nbuilder_of_storm.add_edge(nodes[-1][0], END)\nstorm = builder_of_storm.compile(checkpointer=MemorySaver())"]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
|
||||
@@ -116,7 +116,9 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"@tool\n",
|
||||
"async def get_items(place: str, callbacks: Callbacks) -> str: # <--- Accept callbacks (Python <= 3.10)\n",
|
||||
"async def get_items(\n",
|
||||
" place: str, callbacks: Callbacks\n",
|
||||
") -> str: # <--- Accept callbacks (Python <= 3.10)\n",
|
||||
" \"\"\"Use this tool to look up which items are in the given place.\"\"\"\n",
|
||||
" template = ChatPromptTemplate.from_messages(\n",
|
||||
" [\n",
|
||||
@@ -193,7 +195,9 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"async for event in agent.astream_events({\"messages\": [(\"human\", \"what items are on the shelf?\")]}, version=\"v2\"):\n",
|
||||
"async for event in agent.astream_events(\n",
|
||||
" {\"messages\": [(\"human\", \"what items are on the shelf?\")]}, version=\"v2\"\n",
|
||||
"):\n",
|
||||
" tags = event.get(\"tags\", [])\n",
|
||||
" if event[\"event\"] == \"on_chat_model_stream\" and \"tool_llm\" in tags:\n",
|
||||
" print(event[\"data\"][\"chunk\"].content, end=\"\", flush=True)"
|
||||
|
||||
@@ -22,7 +22,9 @@
|
||||
"id": "c04a3f8e-0bc9-430b-85db-3edfa026d2cd",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": ["%%capture --no-stderr\n%pip install -U langgraph langchain-openai"]
|
||||
"source": [
|
||||
"%%capture --no-stderr\n%pip install -U langgraph langchain-openai"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
@@ -38,7 +40,9 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": ["import getpass\nimport os\n\n\ndef _set_env(var: str):\n if not os.environ.get(var):\n os.environ[var] = getpass.getpass(f\"{var}: \")\n\n\n_set_env(\"OPENAI_API_KEY\")"]
|
||||
"source": [
|
||||
"import getpass\nimport os\n\n\ndef _set_env(var: str):\n if not os.environ.get(var):\n os.environ[var] = getpass.getpass(f\"{var}: \")\n\n\n_set_env(\"OPENAI_API_KEY\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -54,7 +58,9 @@
|
||||
"id": "1d51c35c-dbf2-4c01-932d-c5d308ea37d2",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": ["from typing import Literal\nfrom langchain_community.tools.tavily_search import TavilySearchResults\nfrom langchain_core.runnables import ConfigurableField\nfrom langchain_core.tools import tool\nfrom langchain_openai import ChatOpenAI\nfrom langgraph.prebuilt import create_react_agent\nfrom langgraph.prebuilt import ToolNode\n\n\n@tool\ndef get_weather(city: Literal[\"nyc\", \"sf\"]):\n \"\"\"Use this to get weather information.\"\"\"\n if city == \"nyc\":\n return \"It might be cloudy in nyc\"\n elif city == \"sf\":\n return \"It's always sunny in sf\"\n else:\n raise AssertionError(\"Unknown city\")\n\n\ntools = [get_weather]\nmodel = ChatOpenAI(model_name=\"gpt-3.5-turbo\", temperature=0)\nfinal_model = ChatOpenAI(model_name=\"gpt-3.5-turbo\", temperature=0)\n\nmodel = model.bind_tools(tools)\n# NOTE: this is where we're adding a tag that we'll be using later to filter the outputs of the final node\nfinal_model = final_model.with_config(tags=[\"final_node\"])"]
|
||||
"source": [
|
||||
"from typing import Literal\nfrom langchain_community.tools.tavily_search import TavilySearchResults\nfrom langchain_core.runnables import ConfigurableField\nfrom langchain_core.tools import tool\nfrom langchain_openai import ChatOpenAI\nfrom langgraph.prebuilt import create_react_agent\nfrom langgraph.prebuilt import ToolNode\n\n\n@tool\ndef get_weather(city: Literal[\"nyc\", \"sf\"]):\n \"\"\"Use this to get weather information.\"\"\"\n if city == \"nyc\":\n return \"It might be cloudy in nyc\"\n elif city == \"sf\":\n return \"It's always sunny in sf\"\n else:\n raise AssertionError(\"Unknown city\")\n\n\ntools = [get_weather]\nmodel = ChatOpenAI(model_name=\"gpt-3.5-turbo\", temperature=0)\nfinal_model = ChatOpenAI(model_name=\"gpt-3.5-turbo\", temperature=0)\n\nmodel = model.bind_tools(tools)\n# NOTE: this is where we're adding a tag that we'll be using later to filter the outputs of the final node\nfinal_model = final_model.with_config(tags=[\"final_node\"])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
@@ -62,7 +68,9 @@
|
||||
"id": "0af37212-e592-484d-9194-35d53fa79678",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": ["tool_node = ToolNode(tools=tools)"]
|
||||
"source": [
|
||||
"tool_node = ToolNode(tools=tools)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
@@ -70,7 +78,9 @@
|
||||
"id": "ac9d4f5b-655a-48f3-b514-a4a0815714a6",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": ["from typing import TypedDict, Annotated\n\nfrom langgraph.graph import END, StateGraph, START\nfrom langgraph.graph.message import MessagesState\nfrom langchain_core.messages import BaseMessage"]
|
||||
"source": [
|
||||
"from typing import TypedDict, Annotated\n\nfrom langgraph.graph import END, StateGraph, START\nfrom langgraph.graph.message import MessagesState\nfrom langchain_core.messages import BaseMessage"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -86,7 +96,9 @@
|
||||
"id": "3948c6b8-0317-4001-b699-32b25306a023",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": ["from langchain_core.messages import SystemMessage, HumanMessage"]
|
||||
"source": [
|
||||
"from langchain_core.messages import SystemMessage, HumanMessage"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
@@ -94,7 +106,37 @@
|
||||
"id": "2efe9fb4-c6c2-4171-becd-d45bbf899209",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": ["def should_continue(state: MessagesState) -> Literal[\"tools\", \"final\"]:\n messages = state['messages']\n last_message = messages[-1]\n # If the LLM makes a tool call, then we route to the \"tools\" node\n if last_message.tool_calls:\n return \"tools\"\n # Otherwise, we stop (reply to the user)\n return \"final\"\n\n\ndef call_model(state: MessagesState):\n messages = state['messages']\n response = model.invoke(messages)\n # We return a list, because this will get added to the existing list\n return {\"messages\": [response]}\n\n\ndef call_final_model(state: MessagesState):\n messages = state['messages']\n last_ai_message = messages[-1]\n response = final_model.invoke([\n SystemMessage(\"Rewrite this in the voice of Al Roker\"),\n HumanMessage(last_ai_message.content)\n ])\n # overwrite the last AI message from the agent\n response.id = last_ai_message.id\n return {\"messages\": [response]}"]
|
||||
"source": [
|
||||
"def should_continue(state: MessagesState) -> Literal[\"tools\", \"final\"]:\n",
|
||||
" messages = state[\"messages\"]\n",
|
||||
" last_message = messages[-1]\n",
|
||||
" # If the LLM makes a tool call, then we route to the \"tools\" node\n",
|
||||
" if last_message.tool_calls:\n",
|
||||
" return \"tools\"\n",
|
||||
" # Otherwise, we stop (reply to the user)\n",
|
||||
" return \"final\"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def call_model(state: MessagesState):\n",
|
||||
" messages = state[\"messages\"]\n",
|
||||
" response = model.invoke(messages)\n",
|
||||
" # We return a list, because this will get added to the existing list\n",
|
||||
" return {\"messages\": [response]}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def call_final_model(state: MessagesState):\n",
|
||||
" messages = state[\"messages\"]\n",
|
||||
" last_ai_message = messages[-1]\n",
|
||||
" response = final_model.invoke(\n",
|
||||
" [\n",
|
||||
" SystemMessage(\"Rewrite this in the voice of Al Roker\"),\n",
|
||||
" HumanMessage(last_ai_message.content),\n",
|
||||
" ]\n",
|
||||
" )\n",
|
||||
" # overwrite the last AI message from the agent\n",
|
||||
" response.id = last_ai_message.id\n",
|
||||
" return {\"messages\": [response]}"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
@@ -102,7 +144,23 @@
|
||||
"id": "b1a9a981-8629-4d25-a0e1-d666c3968b30",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": ["workflow = StateGraph(MessagesState)\n\nworkflow.add_node(\"agent\", call_model)\nworkflow.add_node(\"tools\", tool_node)\n# add a separate final node\nworkflow.add_node(\"final\", call_final_model)\n\nworkflow.add_edge(START, \"agent\")\nworkflow.add_conditional_edges(\n \"agent\",\n should_continue,\n)\n\nworkflow.add_edge(\"tools\", 'agent')\nworkflow.add_edge(\"final\", END)"]
|
||||
"source": [
|
||||
"workflow = StateGraph(MessagesState)\n",
|
||||
"\n",
|
||||
"workflow.add_node(\"agent\", call_model)\n",
|
||||
"workflow.add_node(\"tools\", tool_node)\n",
|
||||
"# add a separate final node\n",
|
||||
"workflow.add_node(\"final\", call_final_model)\n",
|
||||
"\n",
|
||||
"workflow.add_edge(START, \"agent\")\n",
|
||||
"workflow.add_conditional_edges(\n",
|
||||
" \"agent\",\n",
|
||||
" should_continue,\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"workflow.add_edge(\"tools\", \"agent\")\n",
|
||||
"workflow.add_edge(\"final\", END)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
@@ -110,7 +168,9 @@
|
||||
"id": "a7b0251f-dcee-49d6-8133-af50d4a55e22",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": ["app = workflow.compile()"]
|
||||
"source": [
|
||||
"app = workflow.compile()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
@@ -118,7 +178,9 @@
|
||||
"id": "f8b77e74-17e9-4fee-a164-4637013b55ff",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": ["from IPython.display import display, Image"]
|
||||
"source": [
|
||||
"from IPython.display import display, Image"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
@@ -137,7 +199,9 @@
|
||||
"output_type": "display_data"
|
||||
}
|
||||
],
|
||||
"source": ["display(Image(app.get_graph().draw_mermaid_png()))"]
|
||||
"source": [
|
||||
"display(Image(app.get_graph().draw_mermaid_png()))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -169,7 +233,9 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": ["inputs = {\"messages\": [(\"human\", \"what's the weather in nyc?\")]}\nasync for event in app.astream_events(inputs, version=\"v2\"):\n kind = event[\"event\"]\n tags = event.get(\"tags\", [])\n if kind == \"on_chat_model_stream\" and \"final_node\" in tags:\n data = event[\"data\"]\n if data[\"chunk\"].content:\n # Empty content in the context of OpenAI or Anthropic usually means\n # that the model is asking for a tool to be invoked.\n # So we only print non-empty content\n print(data[\"chunk\"].content, end=\"|\")"]
|
||||
"source": [
|
||||
"inputs = {\"messages\": [(\"human\", \"what's the weather in nyc?\")]}\nasync for event in app.astream_events(inputs, version=\"v2\"):\n kind = event[\"event\"]\n tags = event.get(\"tags\", [])\n if kind == \"on_chat_model_stream\" and \"final_node\" in tags:\n data = event[\"data\"]\n if data[\"chunk\"].content:\n # Empty content in the context of OpenAI or Anthropic usually means\n # that the model is asking for a tool to be invoked.\n # So we only print non-empty content\n print(data[\"chunk\"].content, end=\"|\")"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
|
||||
+157
-10
@@ -22,7 +22,10 @@
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": ["%%capture --no-stderr\n%pip install -U langgraph"]
|
||||
"source": [
|
||||
"%%capture --no-stderr\n",
|
||||
"%pip install -U langgraph"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -36,7 +39,19 @@
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": ["import getpass\nimport os\n\n\ndef _set_env(var: str):\n if not os.environ.get(var):\n os.environ[var] = getpass.getpass(f\"{var}: \")\n\n\nos.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n_set_env(\"LANGCHAIN_API_KEY\")"]
|
||||
"source": [
|
||||
"import getpass\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def _set_env(var: str):\n",
|
||||
" if not os.environ.get(var):\n",
|
||||
" os.environ[var] = getpass.getpass(f\"{var}: \")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n",
|
||||
"_set_env(\"LANGCHAIN_API_KEY\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -54,7 +69,60 @@
|
||||
"execution_count": 1,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": ["from typing import Annotated\n\nfrom typing_extensions import TypedDict\n\nfrom langgraph.graph import StateGraph, START\n\n\ndef reduce_list(left: list | None, right: list | None) -> list:\n if not left:\n left = []\n if not right:\n right = []\n return left + right\n\n\nclass ChildState(TypedDict):\n name: str\n path: Annotated[list[str], reduce_list]\n\n\nclass ParentState(TypedDict):\n name: str\n path: Annotated[list[str], reduce_list]\n\n\nchild_builder = StateGraph(ChildState)\n\nchild_builder.add_node(\"child_start\", lambda state: {\"path\": [\"child_start\"]})\nchild_builder.add_edge(START, \"child_start\")\nchild_builder.add_node(\"child_middle\", lambda state: {\"path\": [\"child_middle\"]})\nchild_builder.add_node(\"child_end\", lambda state: {\"path\": [\"child_end\"]})\nchild_builder.add_edge(\"child_start\", \"child_middle\")\nchild_builder.add_edge(\"child_middle\", \"child_end\")\nchild_builder.set_finish_point(\"child_end\")\n\nbuilder = StateGraph(ParentState)\n\nbuilder.add_node(\"grandparent\", lambda state: {\"path\": [\"grandparent\"]})\nbuilder.add_edge(START, \"grandparent\")\nbuilder.add_node(\"parent\", lambda state: {\"path\": [\"parent\"]})\nbuilder.add_node(\"child\", child_builder.compile())\nbuilder.add_node(\"sibling\", lambda state: {\"path\": [\"sibling\"]})\nbuilder.add_node(\"fin\", lambda state: {\"path\": [\"fin\"]})\n\n# Add connections\nbuilder.add_edge(\"grandparent\", \"parent\")\nbuilder.add_edge(\"parent\", \"child\")\nbuilder.add_edge(\"parent\", \"sibling\")\nbuilder.add_edge(\"child\", \"fin\")\nbuilder.add_edge(\"sibling\", \"fin\")\nbuilder.set_finish_point(\"fin\")\ngraph = builder.compile()"]
|
||||
"source": [
|
||||
"from typing import Annotated\n",
|
||||
"\n",
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"\n",
|
||||
"from langgraph.graph import StateGraph, START, END\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def reduce_list(left: list | None, right: list | None) -> list:\n",
|
||||
" if not left:\n",
|
||||
" left = []\n",
|
||||
" if not right:\n",
|
||||
" right = []\n",
|
||||
" return left + right\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class ChildState(TypedDict):\n",
|
||||
" name: str\n",
|
||||
" path: Annotated[list[str], reduce_list]\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class ParentState(TypedDict):\n",
|
||||
" name: str\n",
|
||||
" path: Annotated[list[str], reduce_list]\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"child_builder = StateGraph(ChildState)\n",
|
||||
"\n",
|
||||
"child_builder.add_node(\"child_start\", lambda state: {\"path\": [\"child_start\"]})\n",
|
||||
"child_builder.add_edge(START, \"child_start\")\n",
|
||||
"child_builder.add_node(\"child_middle\", lambda state: {\"path\": [\"child_middle\"]})\n",
|
||||
"child_builder.add_node(\"child_end\", lambda state: {\"path\": [\"child_end\"]})\n",
|
||||
"child_builder.add_edge(\"child_start\", \"child_middle\")\n",
|
||||
"child_builder.add_edge(\"child_middle\", \"child_end\")\n",
|
||||
"child_builder.add_edge(\"child_end\", END)\n",
|
||||
"\n",
|
||||
"builder = StateGraph(ParentState)\n",
|
||||
"\n",
|
||||
"builder.add_node(\"grandparent\", lambda state: {\"path\": [\"grandparent\"]})\n",
|
||||
"builder.add_edge(START, \"grandparent\")\n",
|
||||
"builder.add_node(\"parent\", lambda state: {\"path\": [\"parent\"]})\n",
|
||||
"builder.add_node(\"child\", child_builder.compile())\n",
|
||||
"builder.add_node(\"sibling\", lambda state: {\"path\": [\"sibling\"]})\n",
|
||||
"builder.add_node(\"fin\", lambda state: {\"path\": [\"fin\"]})\n",
|
||||
"\n",
|
||||
"# Add connections\n",
|
||||
"builder.add_edge(\"grandparent\", \"parent\")\n",
|
||||
"builder.add_edge(\"parent\", \"child\")\n",
|
||||
"builder.add_edge(\"parent\", \"sibling\")\n",
|
||||
"builder.add_edge(\"child\", \"fin\")\n",
|
||||
"builder.add_edge(\"sibling\", \"fin\")\n",
|
||||
"builder.add_edge(\"fin\", END)\n",
|
||||
"graph = builder.compile()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
@@ -72,7 +140,12 @@
|
||||
"output_type": "display_data"
|
||||
}
|
||||
],
|
||||
"source": ["from IPython.display import Image, display\n\n# Setting xray to 1 will show the internal structure of the nested graph\ndisplay(Image(graph.get_graph(xray=1).draw_mermaid_png()))"]
|
||||
"source": [
|
||||
"from IPython.display import Image, display\n",
|
||||
"\n",
|
||||
"# Setting xray to 1 will show the internal structure of the nested graph\n",
|
||||
"display(Image(graph.get_graph(xray=1).draw_mermaid_png()))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
@@ -162,7 +235,9 @@
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": ["graph.invoke({\"name\": \"test\"}, debug=True)"]
|
||||
"source": [
|
||||
"graph.invoke({\"name\": \"test\"}, debug=True)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -182,14 +257,79 @@
|
||||
"execution_count": 23,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": ["import uuid\n\n\ndef reduce_list(left: list | None, right: list | None) -> list:\n \"\"\"Append the right-hand list, replacing any elements with the same id in the left-hand list.\"\"\"\n if not left:\n left = []\n if not right:\n right = []\n left_, right_ = [], []\n for orig, new in [(left, left_), (right, right_)]:\n for val in orig:\n if not isinstance(val, dict):\n val = {\"val\": val}\n if \"id\" not in val:\n val[\"id\"] = str(uuid.uuid4())\n new.append(val)\n # Merge the two lists\n left_idx_by_id = {val[\"id\"]: i for i, val in enumerate(left_)}\n merged = left_.copy()\n for val in right_:\n if (existing_idx := left_idx_by_id.get(val[\"id\"])) is not None:\n merged[existing_idx] = val\n else:\n merged.append(val)\n return merged\n\n\nclass ChildState(TypedDict):\n name: str\n path: Annotated[list[str], reduce_list]\n\n\nclass ParentState(TypedDict):\n name: str\n path: Annotated[list[str], reduce_list]"]
|
||||
"source": [
|
||||
"import uuid\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def reduce_list(left: list | None, right: list | None) -> list:\n",
|
||||
" \"\"\"Append the right-hand list, replacing any elements with the same id in the left-hand list.\"\"\"\n",
|
||||
" if not left:\n",
|
||||
" left = []\n",
|
||||
" if not right:\n",
|
||||
" right = []\n",
|
||||
" left_, right_ = [], []\n",
|
||||
" for orig, new in [(left, left_), (right, right_)]:\n",
|
||||
" for val in orig:\n",
|
||||
" if not isinstance(val, dict):\n",
|
||||
" val = {\"val\": val}\n",
|
||||
" if \"id\" not in val:\n",
|
||||
" val[\"id\"] = str(uuid.uuid4())\n",
|
||||
" new.append(val)\n",
|
||||
" # Merge the two lists\n",
|
||||
" left_idx_by_id = {val[\"id\"]: i for i, val in enumerate(left_)}\n",
|
||||
" merged = left_.copy()\n",
|
||||
" for val in right_:\n",
|
||||
" if (existing_idx := left_idx_by_id.get(val[\"id\"])) is not None:\n",
|
||||
" merged[existing_idx] = val\n",
|
||||
" else:\n",
|
||||
" merged.append(val)\n",
|
||||
" return merged\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class ChildState(TypedDict):\n",
|
||||
" name: str\n",
|
||||
" path: Annotated[list[str], reduce_list]\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class ParentState(TypedDict):\n",
|
||||
" name: str\n",
|
||||
" path: Annotated[list[str], reduce_list]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 24,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": ["child_builder = StateGraph(ChildState)\n\nchild_builder.add_node(\"child_start\", lambda state: {\"path\": [\"child_start\"]})\nchild_builder.add_edge(START, \"child_start\")\nchild_builder.add_node(\"child_middle\", lambda state: {\"path\": [\"child_middle\"]})\nchild_builder.add_node(\"child_end\", lambda state: {\"path\": [\"child_end\"]})\nchild_builder.add_edge(\"child_start\", \"child_middle\")\nchild_builder.add_edge(\"child_middle\", \"child_end\")\nchild_builder.set_finish_point(\"child_end\")\n\nbuilder = StateGraph(ParentState)\n\nbuilder.add_node(\"grandparent\", lambda state: {\"path\": [\"grandparent\"]})\nbuilder.add_edge(START, \"grandparent\")\nbuilder.add_node(\"parent\", lambda state: {\"path\": [\"parent\"]})\nbuilder.add_node(\"child\", child_builder.compile())\nbuilder.add_node(\"sibling\", lambda state: {\"path\": [\"sibling\"]})\nbuilder.add_node(\"fin\", lambda state: {\"path\": [\"fin\"]})\n\n# Add connections\nbuilder.add_edge(\"grandparent\", \"parent\")\nbuilder.add_edge(\"parent\", \"child\")\nbuilder.add_edge(\"parent\", \"sibling\")\nbuilder.add_edge(\"child\", \"fin\")\nbuilder.add_edge(\"sibling\", \"fin\")\nbuilder.set_finish_point(\"fin\")\ngraph = builder.compile()"]
|
||||
"source": [
|
||||
"child_builder = StateGraph(ChildState)\n",
|
||||
"\n",
|
||||
"child_builder.add_node(\"child_start\", lambda state: {\"path\": [\"child_start\"]})\n",
|
||||
"child_builder.add_edge(START, \"child_start\")\n",
|
||||
"child_builder.add_node(\"child_middle\", lambda state: {\"path\": [\"child_middle\"]})\n",
|
||||
"child_builder.add_node(\"child_end\", lambda state: {\"path\": [\"child_end\"]})\n",
|
||||
"child_builder.add_edge(\"child_start\", \"child_middle\")\n",
|
||||
"child_builder.add_edge(\"child_middle\", \"child_end\")\n",
|
||||
"child_builder.add_edge(\"child_end\", END)\n",
|
||||
"\n",
|
||||
"builder = StateGraph(ParentState)\n",
|
||||
"\n",
|
||||
"builder.add_node(\"grandparent\", lambda state: {\"path\": [\"grandparent\"]})\n",
|
||||
"builder.add_edge(START, \"grandparent\")\n",
|
||||
"builder.add_node(\"parent\", lambda state: {\"path\": [\"parent\"]})\n",
|
||||
"builder.add_node(\"child\", child_builder.compile())\n",
|
||||
"builder.add_node(\"sibling\", lambda state: {\"path\": [\"sibling\"]})\n",
|
||||
"builder.add_node(\"fin\", lambda state: {\"path\": [\"fin\"]})\n",
|
||||
"\n",
|
||||
"# Add connections\n",
|
||||
"builder.add_edge(\"grandparent\", \"parent\")\n",
|
||||
"builder.add_edge(\"parent\", \"child\")\n",
|
||||
"builder.add_edge(\"parent\", \"sibling\")\n",
|
||||
"builder.add_edge(\"child\", \"fin\")\n",
|
||||
"builder.add_edge(\"sibling\", \"fin\")\n",
|
||||
"builder.add_edge(\"fin\", END)\n",
|
||||
"graph = builder.compile()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
@@ -207,7 +347,12 @@
|
||||
"output_type": "display_data"
|
||||
}
|
||||
],
|
||||
"source": ["from IPython.display import Image, display\n\n# Setting xray to 1 will show the internal structure of the nested graph\ndisplay(Image(graph.get_graph(xray=1).draw_mermaid_png()))"]
|
||||
"source": [
|
||||
"from IPython.display import Image, display\n",
|
||||
"\n",
|
||||
"# Setting xray to 1 will show the internal structure of the nested graph\n",
|
||||
"display(Image(graph.get_graph(xray=1).draw_mermaid_png()))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
@@ -301,7 +446,9 @@
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": ["graph.invoke({\"name\": \"test\"}, debug=True)"]
|
||||
"source": [
|
||||
"graph.invoke({\"name\": \"test\"}, debug=True)"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
@@ -320,7 +467,7 @@
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.2"
|
||||
"version": "3.12.2"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
|
||||
@@ -129,6 +129,7 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain_openai import ChatOpenAI\n",
|
||||
"\n",
|
||||
"model_tested = \"gpt-4o\"\n",
|
||||
"metadata = \"CRAG, gpt-4o\"\n",
|
||||
"llm = ChatOpenAI(model_name=model_tested, temperature=0)"
|
||||
@@ -150,6 +151,7 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain_fireworks import ChatFireworks\n",
|
||||
"\n",
|
||||
"model_tested = \"firefunction-v2\"\n",
|
||||
"metadata = \"CRAG, firefunction-v\"\n",
|
||||
"llm = ChatFireworks(model=\"accounts/fireworks/models/firefunction-v2\", temperature=0)"
|
||||
@@ -344,9 +346,9 @@
|
||||
"source": [
|
||||
"@tool\n",
|
||||
"def generate_answer(answer: str) -> str:\n",
|
||||
" \"\"\"You are an assistant for question-answering tasks. \n",
|
||||
" Use the retrieved documents to answer the user question. \n",
|
||||
" If you don't know the answer, just say that you don't know. \n",
|
||||
" \"\"\"You are an assistant for question-answering tasks.\n",
|
||||
" Use the retrieved documents to answer the user question.\n",
|
||||
" If you don't know the answer, just say that you don't know.\n",
|
||||
" Use three sentences maximum and keep the answer concise\"\"\"\n",
|
||||
" return f\"Here is the answer to the user question: {answer}\""
|
||||
]
|
||||
@@ -439,7 +441,7 @@
|
||||
" (\n",
|
||||
" \"system\",\n",
|
||||
" \" You are a helpful assistant tasked with answering user questions using the provided vector store. \"\n",
|
||||
" \" Use the provided vector store to retrieve documents. Then grade them to ensure they are relevant before answering the question. \"\n",
|
||||
" \" Use the provided vector store to retrieve documents. Then grade them to ensure they are relevant before answering the question. \",\n",
|
||||
" ),\n",
|
||||
" (\"placeholder\", \"{messages}\"),\n",
|
||||
" ]\n",
|
||||
@@ -629,7 +631,8 @@
|
||||
" ]\n",
|
||||
" return tool_calls\n",
|
||||
"\n",
|
||||
"find_tool_calls_react(response['messages'])"
|
||||
"\n",
|
||||
"find_tool_calls_react(response[\"messages\"])"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1080,6 +1083,7 @@
|
||||
"# Grade prompt\n",
|
||||
"grade_prompt_answer_accuracy = hub.pull(\"langchain-ai/rag-answer-vs-reference\")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def answer_evaluator(run, example) -> dict:\n",
|
||||
" \"\"\"\n",
|
||||
" A simple evaluator for RAG answer accuracy\n",
|
||||
@@ -1140,6 +1144,7 @@
|
||||
" \"generate_answer\",\n",
|
||||
"]\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def check_trajectory_react(root_run: Run, example: Example) -> dict:\n",
|
||||
" \"\"\"\n",
|
||||
" Check if all expected tools are called in exact order and without any additional tool calls.\n",
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -190,7 +190,7 @@
|
||||
"id": "f1f97ea4-53e5-4f55-8d73-b5b2234a47d9",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": ["from langgraph.graph import StateGraph, START\n\ngraph = StateGraph(TaxonomyGenerationState)\ngraph.add_node(\"summarize\", map_reduce_chain)\ngraph.add_node(\"get_minibatches\", get_minibatches)\ngraph.add_node(\"generate_taxonomy\", generate_taxonomy)\ngraph.add_node(\"update_taxonomy\", update_taxonomy)\ngraph.add_node(\"review_taxonomy\", review_taxonomy)\n\ngraph.add_edge(\"summarize\", \"get_minibatches\")\ngraph.add_edge(\"get_minibatches\", \"generate_taxonomy\")\ngraph.add_edge(\"generate_taxonomy\", \"update_taxonomy\")\n\n\ndef should_review(state: TaxonomyGenerationState) -> str:\n num_minibatches = len(state[\"minibatches\"])\n num_revisions = len(state[\"clusters\"])\n if num_revisions < num_minibatches:\n return \"update_taxonomy\"\n return \"review_taxonomy\"\n\n\ngraph.add_conditional_edges(\n \"update_taxonomy\",\n should_review,\n # Optional (but required for the diagram to be drawn correctly below)\n {\"update_taxonomy\": \"update_taxonomy\", \"review_taxonomy\": \"review_taxonomy\"},\n)\ngraph.set_finish_point(\"review_taxonomy\")\n\ngraph.add_edge(START, \"summarize\")\napp = graph.compile()"]
|
||||
"source": ["from langgraph.graph import StateGraph, START, END\n\ngraph = StateGraph(TaxonomyGenerationState)\ngraph.add_node(\"summarize\", map_reduce_chain)\ngraph.add_node(\"get_minibatches\", get_minibatches)\ngraph.add_node(\"generate_taxonomy\", generate_taxonomy)\ngraph.add_node(\"update_taxonomy\", update_taxonomy)\ngraph.add_node(\"review_taxonomy\", review_taxonomy)\n\ngraph.add_edge(\"summarize\", \"get_minibatches\")\ngraph.add_edge(\"get_minibatches\", \"generate_taxonomy\")\ngraph.add_edge(\"generate_taxonomy\", \"update_taxonomy\")\n\n\ndef should_review(state: TaxonomyGenerationState) -> str:\n num_minibatches = len(state[\"minibatches\"])\n num_revisions = len(state[\"clusters\"])\n if num_revisions < num_minibatches:\n return \"update_taxonomy\"\n return \"review_taxonomy\"\n\n\ngraph.add_conditional_edges(\n \"update_taxonomy\",\n should_review,\n # Optional (but required for the diagram to be drawn correctly below)\n {\"update_taxonomy\": \"update_taxonomy\", \"review_taxonomy\": \"review_taxonomy\"},\n)\ngraph.add_edge(\"review_taxonomy\", END)\n\ngraph.add_edge(START, \"summarize\")\napp = graph.compile()"]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
|
||||
@@ -16,7 +16,10 @@
|
||||
"id": "32a0e7f4",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": ["%%capture --no-stderr\n%pip install -U langgraph"]
|
||||
"source": [
|
||||
"%%capture --no-stderr\n",
|
||||
"%pip install -U langgraph"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -34,7 +37,73 @@
|
||||
"id": "6d604311",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": ["import random\nfrom typing import Annotated, Literal\n\nfrom typing_extensions import TypedDict\n\nfrom langgraph.graph import StateGraph, START\nfrom langgraph.graph.message import add_messages\n\n\nclass State(TypedDict):\n messages: Annotated[list, add_messages]\n\n\nclass MyNode:\n def __init__(self, name: str):\n self.name = name\n\n def __call__(self, state: State):\n return {\"messages\": [(\"assistant\", f\"Called node {self.name}\")]}\n\n\ndef route(state) -> Literal[\"entry_node\", \"__end__\"]:\n if len(state[\"messages\"]) > 10:\n return \"__end__\"\n return \"entry_node\"\n\n\ndef add_fractal_nodes(builder, current_node, level, max_level):\n if level > max_level:\n return\n\n # Number of nodes to create at this level\n num_nodes = random.randint(1, 3) # Adjust randomness as needed\n for i in range(num_nodes):\n nm = [\"A\", \"B\", \"C\"][i]\n node_name = f\"node_{current_node}_{nm}\"\n builder.add_node(node_name, MyNode(node_name))\n builder.add_edge(current_node, node_name)\n\n # Recursively add more nodes\n r = random.random()\n if r > 0.2 and level + 1 < max_level:\n add_fractal_nodes(builder, node_name, level + 1, max_level)\n elif r > 0.05:\n builder.add_conditional_edges(node_name, route, node_name)\n else:\n # End\n builder.add_edge(node_name, \"__end__\")\n\n\ndef build_fractal_graph(max_level: int):\n builder = StateGraph(State)\n entry_point = \"entry_node\"\n builder.add_node(entry_point, MyNode(entry_point))\n builder.add_edge(START, entry_point)\n\n add_fractal_nodes(builder, entry_point, 1, max_level)\n\n # Optional: set a finish point if required\n builder.set_finish_point(entry_point) # or any specific node\n\n return builder.compile()\n\n\napp = build_fractal_graph(3)"]
|
||||
"source": [
|
||||
"import random\n",
|
||||
"from typing import Annotated, Literal\n",
|
||||
"\n",
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"\n",
|
||||
"from langgraph.graph import StateGraph, START, END\n",
|
||||
"from langgraph.graph.message import add_messages\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class State(TypedDict):\n",
|
||||
" messages: Annotated[list, add_messages]\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class MyNode:\n",
|
||||
" def __init__(self, name: str):\n",
|
||||
" self.name = name\n",
|
||||
"\n",
|
||||
" def __call__(self, state: State):\n",
|
||||
" return {\"messages\": [(\"assistant\", f\"Called node {self.name}\")]}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def route(state) -> Literal[\"entry_node\", \"__end__\"]:\n",
|
||||
" if len(state[\"messages\"]) > 10:\n",
|
||||
" return \"__end__\"\n",
|
||||
" return \"entry_node\"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def add_fractal_nodes(builder, current_node, level, max_level):\n",
|
||||
" if level > max_level:\n",
|
||||
" return\n",
|
||||
"\n",
|
||||
" # Number of nodes to create at this level\n",
|
||||
" num_nodes = random.randint(1, 3) # Adjust randomness as needed\n",
|
||||
" for i in range(num_nodes):\n",
|
||||
" nm = [\"A\", \"B\", \"C\"][i]\n",
|
||||
" node_name = f\"node_{current_node}_{nm}\"\n",
|
||||
" builder.add_node(node_name, MyNode(node_name))\n",
|
||||
" builder.add_edge(current_node, node_name)\n",
|
||||
"\n",
|
||||
" # Recursively add more nodes\n",
|
||||
" r = random.random()\n",
|
||||
" if r > 0.2 and level + 1 < max_level:\n",
|
||||
" add_fractal_nodes(builder, node_name, level + 1, max_level)\n",
|
||||
" elif r > 0.05:\n",
|
||||
" builder.add_conditional_edges(node_name, route, node_name)\n",
|
||||
" else:\n",
|
||||
" # End\n",
|
||||
" builder.add_edge(node_name, \"__end__\")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def build_fractal_graph(max_level: int):\n",
|
||||
" builder = StateGraph(State)\n",
|
||||
" entry_point = \"entry_node\"\n",
|
||||
" builder.add_node(entry_point, MyNode(entry_point))\n",
|
||||
" builder.add_edge(START, entry_point)\n",
|
||||
"\n",
|
||||
" add_fractal_nodes(builder, entry_point, 1, max_level)\n",
|
||||
"\n",
|
||||
" # Optional: set a finish point if required\n",
|
||||
" builder.add_edge(entry_point, END) # or any specific node\n",
|
||||
"\n",
|
||||
" return builder.compile()\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"app = build_fractal_graph(3)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -96,7 +165,9 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": ["app.get_graph().print_ascii()"]
|
||||
"source": [
|
||||
"app.get_graph().print_ascii()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -155,7 +226,9 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": ["print(app.get_graph().draw_mermaid())"]
|
||||
"source": [
|
||||
"print(app.get_graph().draw_mermaid())"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -193,7 +266,18 @@
|
||||
"output_type": "display_data"
|
||||
}
|
||||
],
|
||||
"source": ["from IPython.display import Image, display\nfrom langchain_core.runnables.graph import CurveStyle, MermaidDrawMethod, NodeColors\n\ndisplay(\n Image(\n app.get_graph().draw_mermaid_png(\n draw_method=MermaidDrawMethod.API,\n )\n )\n)"]
|
||||
"source": [
|
||||
"from IPython.display import Image, display\n",
|
||||
"from langchain_core.runnables.graph import CurveStyle, MermaidDrawMethod, NodeColors\n",
|
||||
"\n",
|
||||
"display(\n",
|
||||
" Image(\n",
|
||||
" app.get_graph().draw_mermaid_png(\n",
|
||||
" draw_method=MermaidDrawMethod.API,\n",
|
||||
" )\n",
|
||||
" )\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -219,7 +303,11 @@
|
||||
}
|
||||
},
|
||||
"outputs": [],
|
||||
"source": ["%%capture --no-stderr\n%pip install --quiet pyppeteer\n%pip install --quiet nest_asyncio"]
|
||||
"source": [
|
||||
"%%capture --no-stderr\n",
|
||||
"%pip install --quiet pyppeteer\n",
|
||||
"%pip install --quiet nest_asyncio"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
@@ -243,7 +331,25 @@
|
||||
"output_type": "display_data"
|
||||
}
|
||||
],
|
||||
"source": ["import nest_asyncio\n\nnest_asyncio.apply() # Required for Jupyter Notebook to run async functions\n\ndisplay(\n Image(\n app.get_graph().draw_mermaid_png(\n curve_style=CurveStyle.LINEAR,\n node_colors=NodeColors(start=\"#ffdfba\", end=\"#baffc9\", other=\"#fad7de\"),\n wrap_label_n_words=9,\n output_file_path=None,\n draw_method=MermaidDrawMethod.PYPPETEER,\n background_color=\"white\",\n padding=10,\n )\n )\n)"]
|
||||
"source": [
|
||||
"import nest_asyncio\n",
|
||||
"\n",
|
||||
"nest_asyncio.apply() # Required for Jupyter Notebook to run async functions\n",
|
||||
"\n",
|
||||
"display(\n",
|
||||
" Image(\n",
|
||||
" app.get_graph().draw_mermaid_png(\n",
|
||||
" curve_style=CurveStyle.LINEAR,\n",
|
||||
" node_colors=NodeColors(start=\"#ffdfba\", end=\"#baffc9\", other=\"#fad7de\"),\n",
|
||||
" wrap_label_n_words=9,\n",
|
||||
" output_file_path=None,\n",
|
||||
" draw_method=MermaidDrawMethod.PYPPETEER,\n",
|
||||
" background_color=\"white\",\n",
|
||||
" padding=10,\n",
|
||||
" )\n",
|
||||
" )\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -269,7 +375,10 @@
|
||||
}
|
||||
},
|
||||
"outputs": [],
|
||||
"source": ["%%capture --no-stderr\n%pip install pygraphviz"]
|
||||
"source": [
|
||||
"%%capture --no-stderr\n",
|
||||
"%pip install pygraphviz"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
@@ -293,7 +402,9 @@
|
||||
"output_type": "display_data"
|
||||
}
|
||||
],
|
||||
"source": ["display(Image(app.get_graph().draw_png()))"]
|
||||
"source": [
|
||||
"display(Image(app.get_graph().draw_png()))"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
|
||||
Reference in New Issue
Block a user