mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-09-23 10:05:08 +02:00
Merge pull request #1932 from langchain-ai/eugene/format_docs
docs: format with ruff
This commit is contained in:
@@ -533,12 +533,12 @@
|
||||
" # because we chose to only include LLMs, these are LLM tokens\n",
|
||||
" try:\n",
|
||||
" content = op[\"value\"].content[0]\n",
|
||||
" if 'partial_json' in content:\n",
|
||||
" print(content['partial_json'], end=\"|\")\n",
|
||||
" elif 'text' in content:\n",
|
||||
" print(content['text'], end='|')\n",
|
||||
" if \"partial_json\" in content:\n",
|
||||
" print(content[\"partial_json\"], end=\"|\")\n",
|
||||
" elif \"text\" in content:\n",
|
||||
" print(content[\"text\"], end=\"|\")\n",
|
||||
" else:\n",
|
||||
" print(content,end=\"|\")\n",
|
||||
" print(content, end=\"|\")\n",
|
||||
" except:\n",
|
||||
" pass"
|
||||
]
|
||||
|
||||
@@ -158,6 +158,7 @@
|
||||
" \"openai\": openai_model,\n",
|
||||
"}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def _call_model(state: AgentState, config: RunnableConfig):\n",
|
||||
" # Access the config through the configurable key\n",
|
||||
" model_name = config[\"configurable\"].get(\"model\", \"anthropic\")\n",
|
||||
@@ -253,12 +254,14 @@
|
||||
"source": [
|
||||
"from langchain_core.messages import SystemMessage\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# We can define a config schema to specify the configuration options for the graph\n",
|
||||
"# A config schema is useful for indicating which fields are available in the configurable dict inside the config\n",
|
||||
"class ConfigSchema(TypedDict):\n",
|
||||
" model: Optional[str]\n",
|
||||
" system_message: Optional[str]\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def _call_model(state: AgentState, config: RunnableConfig):\n",
|
||||
" # Access the config through the configurable key\n",
|
||||
" model_name = config[\"configurable\"].get(\"model\", \"anthropic\")\n",
|
||||
|
||||
@@ -176,6 +176,7 @@
|
||||
],
|
||||
"source": [
|
||||
"from langchain_core.messages import HumanMessage\n",
|
||||
"\n",
|
||||
"config = {\"configurable\": {\"thread_id\": \"42\"}}\n",
|
||||
"inputs = {\"messages\": [(\"user\", \"what is the weather in SF, CA?\")]}\n",
|
||||
"\n",
|
||||
@@ -285,10 +286,10 @@
|
||||
"source": [
|
||||
"state = graph.get_state(config)\n",
|
||||
"\n",
|
||||
"last_message = state.values['messages'][-1]\n",
|
||||
"last_message.tool_calls[0]['args'] = {\"location\": \"San Francisco\"}\n",
|
||||
"last_message = state.values[\"messages\"][-1]\n",
|
||||
"last_message.tool_calls[0][\"args\"] = {\"location\": \"San Francisco\"}\n",
|
||||
"\n",
|
||||
"graph.update_state(config, {\"messages\": [ last_message]})"
|
||||
"graph.update_state(config, {\"messages\": [last_message]})"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -21,13 +21,15 @@
|
||||
"from langgraph.graph import MessagesState\n",
|
||||
"from langgraph.graph import StateGraph, START, END\n",
|
||||
"\n",
|
||||
"llm = ChatOpenAI(model=\"o1-preview\",temperature=1)\n",
|
||||
"llm = ChatOpenAI(model=\"o1-preview\", temperature=1)\n",
|
||||
"\n",
|
||||
"graph_builder = StateGraph(MessagesState)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def chatbot(state: MessagesState):\n",
|
||||
" return {\"messages\": [llm.invoke(state[\"messages\"])]}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"graph_builder.add_node(\"chatbot\", chatbot)\n",
|
||||
"graph_builder.add_edge(START, \"chatbot\")\n",
|
||||
"graph_builder.add_edge(\"chatbot\", END)\n",
|
||||
@@ -112,7 +114,7 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"input = {\"messages\": {\"role\":\"user\", \"content\":\"how many r's are in strawberry?\"}}\n",
|
||||
"input = {\"messages\": {\"role\": \"user\", \"content\": \"how many r's are in strawberry?\"}}\n",
|
||||
"try:\n",
|
||||
" async for event in graph.astream_events(input, version=\"v2\"):\n",
|
||||
" if event[\"event\"] == \"on_chat_model_end\":\n",
|
||||
@@ -138,13 +140,15 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"llm = ChatOpenAI(model=\"o1-preview\",temperature=1,disable_streaming=True)\n",
|
||||
"llm = ChatOpenAI(model=\"o1-preview\", temperature=1, disable_streaming=True)\n",
|
||||
"\n",
|
||||
"graph_builder = StateGraph(MessagesState)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def chatbot(state: MessagesState):\n",
|
||||
" return {\"messages\": [llm.invoke(state[\"messages\"])]}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"graph_builder.add_node(\"chatbot\", chatbot)\n",
|
||||
"graph_builder.add_edge(START, \"chatbot\")\n",
|
||||
"graph_builder.add_edge(\"chatbot\", END)\n",
|
||||
@@ -187,7 +191,7 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"input = {\"messages\": {\"role\":\"user\", \"content\":\"how many r's are in strawberry?\"}}\n",
|
||||
"input = {\"messages\": {\"role\": \"user\", \"content\": \"how many r's are in strawberry?\"}}\n",
|
||||
"async for event in graph.astream_events(input, version=\"v2\"):\n",
|
||||
" if event[\"event\"] == \"on_chat_model_end\":\n",
|
||||
" print(event[\"data\"][\"output\"].content, end=\"\", flush=True)"
|
||||
|
||||
@@ -94,12 +94,15 @@
|
||||
"def step_2(state: State) -> State:\n",
|
||||
" # Let's optionally raise a NodeInterrupt\n",
|
||||
" # if the length of the input is longer than 5 characters\n",
|
||||
" if len(state['input']) > 5:\n",
|
||||
" raise NodeInterrupt(f\"Received input that is longer than 5 characters: {state['input']}\")\n",
|
||||
" \n",
|
||||
" if len(state[\"input\"]) > 5:\n",
|
||||
" raise NodeInterrupt(\n",
|
||||
" f\"Received input that is longer than 5 characters: {state['input']}\"\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
" print(\"---Step 2---\")\n",
|
||||
" return state\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def step_3(state: State) -> State:\n",
|
||||
" print(\"---Step 3---\")\n",
|
||||
" return state\n",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -382,6 +382,7 @@
|
||||
"\n",
|
||||
"from pydantic import BaseModel\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",
|
||||
@@ -396,6 +397,7 @@
|
||||
"\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",
|
||||
|
||||
@@ -79,9 +79,11 @@
|
||||
"class OutputState(TypedDict):\n",
|
||||
" answer: str\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class OverallState(InputState, OutputState):\n",
|
||||
" pass\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def answer_node(state: InputState):\n",
|
||||
" return {\"answer\": \"bye\"}\n",
|
||||
"\n",
|
||||
|
||||
@@ -163,7 +163,7 @@
|
||||
" # Next, we pass in the function that will determine which node is called next.\n",
|
||||
" should_continue,\n",
|
||||
" # Next, we pass in the path map - all the possible nodes this edge could go to\n",
|
||||
" ['action', END]\n",
|
||||
" [\"action\", END],\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"# We now add a normal edge from `tools` to `agent`.\n",
|
||||
|
||||
@@ -154,7 +154,7 @@
|
||||
" # Next, we pass in the function that will determine which node is called next.\n",
|
||||
" should_continue,\n",
|
||||
" # Next, we pass in the path map - all the possible nodes this edge could go to\n",
|
||||
" ['action',END]\n",
|
||||
" [\"action\", END],\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"# We now add a normal edge from `tools` to `agent`.\n",
|
||||
@@ -291,7 +291,7 @@
|
||||
" # Next, we pass in the function that will determine which node is called next.\n",
|
||||
" should_continue,\n",
|
||||
" # Next, we pass in the pathmap - all the possible nodes this edge could go to\n",
|
||||
" ['action', END]\n",
|
||||
" [\"action\", END],\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"# We now add a normal edge from `tools` to `agent`.\n",
|
||||
|
||||
@@ -204,11 +204,7 @@
|
||||
"workflow.add_node(\"tools\", tool_node)\n",
|
||||
"\n",
|
||||
"workflow.add_edge(START, \"agent\")\n",
|
||||
"workflow.add_conditional_edges(\n",
|
||||
" \"agent\",\n",
|
||||
" should_continue,\n",
|
||||
" [\"tools\",END]\n",
|
||||
")\n",
|
||||
"workflow.add_conditional_edges(\"agent\", should_continue, [\"tools\", END])\n",
|
||||
"workflow.add_edge(\"tools\", \"agent\")\n",
|
||||
"\n",
|
||||
"app = workflow.compile()"
|
||||
|
||||
@@ -102,7 +102,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"execution_count": 1,
|
||||
"id": "1d36e782-80f4-4334-b7d7-ee4c79864480",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -112,6 +112,7 @@
|
||||
"\n",
|
||||
"from langchain_core.documents import Document\n",
|
||||
"from langchain_core.tools import tool\n",
|
||||
"from langchain_core.messages import ToolMessage\n",
|
||||
"from langgraph.prebuilt import InjectedState\n",
|
||||
"\n",
|
||||
"from pydantic import BaseModel\n",
|
||||
@@ -553,7 +554,7 @@
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.9"
|
||||
"version": "3.11.4"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
|
||||
+582
-581
File diff suppressed because one or more lines are too long
@@ -256,9 +256,7 @@
|
||||
" }\n",
|
||||
" }\n",
|
||||
"\n",
|
||||
" checkpoint = serde.loads_typed(\n",
|
||||
" (data[b\"type\"].decode(), data[b\"checkpoint\"])\n",
|
||||
" )\n",
|
||||
" checkpoint = serde.loads_typed((data[b\"type\"].decode(), data[b\"checkpoint\"]))\n",
|
||||
" metadata = serde.loads(data[b\"metadata\"].decode())\n",
|
||||
" parent_checkpoint_id = data.get(b\"parent_checkpoint_id\", b\"\").decode()\n",
|
||||
" parent_config = (\n",
|
||||
|
||||
@@ -82,6 +82,7 @@
|
||||
"from langchain_core.messages import BaseMessage\n",
|
||||
"from langgraph.graph.message import add_messages\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class AgentState(TypedDict):\n",
|
||||
" \"\"\"The state of the agent.\"\"\"\n",
|
||||
"\n",
|
||||
@@ -108,16 +109,18 @@
|
||||
"\n",
|
||||
"model = ChatOpenAI(model=\"gpt-4o-mini\")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"@tool\n",
|
||||
"def get_weather(location: str):\n",
|
||||
" \"\"\"Call to get the weather from a specific location.\"\"\"\n",
|
||||
" # This is a placeholder for the actual implementation\n",
|
||||
" # Don't let the LLM know this though 😊\n",
|
||||
" if any([city in location.lower() for city in ['sf','san francisco']]):\n",
|
||||
" if any([city in location.lower() for city in [\"sf\", \"san francisco\"]]):\n",
|
||||
" return \"It's sunny in San Francisco, but you better look out if you're a Gemini 😈.\"\n",
|
||||
" else:\n",
|
||||
" return f\"I am not sure what the weather is in {location}\"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"tools = [get_weather]\n",
|
||||
"\n",
|
||||
"model = model.bind_tools(tools)"
|
||||
@@ -145,13 +148,13 @@
|
||||
"from langchain_core.runnables import RunnableConfig\n",
|
||||
"\n",
|
||||
"tools_by_name = {tool.name: tool for tool in tools}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define our tool node\n",
|
||||
"def tool_node(state: AgentState):\n",
|
||||
" outputs = []\n",
|
||||
" for tool_call in state['messages'][-1].tool_calls:\n",
|
||||
" tool_result = tools_by_name[tool_call[\"name\"]].invoke(\n",
|
||||
" tool_call[\"args\"]\n",
|
||||
" )\n",
|
||||
" for tool_call in state[\"messages\"][-1].tool_calls:\n",
|
||||
" tool_result = tools_by_name[tool_call[\"name\"]].invoke(tool_call[\"args\"])\n",
|
||||
" outputs.append(\n",
|
||||
" ToolMessage(\n",
|
||||
" content=json.dumps(tool_result),\n",
|
||||
@@ -161,17 +164,21 @@
|
||||
" )\n",
|
||||
" return {\"messages\": outputs}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define the node that calls the model\n",
|
||||
"def call_model(\n",
|
||||
" state: AgentState,\n",
|
||||
" config: RunnableConfig,\n",
|
||||
"):\n",
|
||||
" # this is similar to customizing the create_react_agent with state_modifier, but is a lot more flexible\n",
|
||||
" system_prompt = SystemMessage(\"You are a helpful AI assistant, please respond to the users query to the best of your ability!\")\n",
|
||||
" response = model.invoke([system_prompt] + state['messages'], config)\n",
|
||||
" system_prompt = SystemMessage(\n",
|
||||
" \"You are a helpful AI assistant, please respond to the users query to the best of your ability!\"\n",
|
||||
" )\n",
|
||||
" response = model.invoke([system_prompt] + state[\"messages\"], config)\n",
|
||||
" # We return a list, because this will get added to the existing list\n",
|
||||
" return {\"messages\": [response]}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define the conditional edge that determines whether to continue or not\n",
|
||||
"def should_continue(state: AgentState):\n",
|
||||
" messages = state[\"messages\"]\n",
|
||||
@@ -308,6 +315,7 @@
|
||||
" else:\n",
|
||||
" message.pretty_print()\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"inputs = {\"messages\": [(\"user\", \"what is the weather in sf\")]}\n",
|
||||
"print_stream(graph.stream(inputs, stream_mode=\"values\"))"
|
||||
]
|
||||
|
||||
@@ -112,22 +112,28 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from pydantic import BaseModel, Field\n",
|
||||
"from typing import Literal\n",
|
||||
"from typing import Literal\n",
|
||||
"from langchain_core.tools import tool\n",
|
||||
"from langchain_anthropic import ChatAnthropic\n",
|
||||
"from langgraph.graph import MessagesState\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class WeatherResponse(BaseModel):\n",
|
||||
" \"\"\"Respond to the user with this\"\"\"\n",
|
||||
"\n",
|
||||
" temperature: float = Field(description=\"The temperature in fahrenheit\")\n",
|
||||
" wind_directon: str = Field(description=\"The direction of the wind in abbreviated form\")\n",
|
||||
" wind_directon: str = Field(\n",
|
||||
" description=\"The direction of the wind in abbreviated form\"\n",
|
||||
" )\n",
|
||||
" wind_speed: float = Field(description=\"The speed of the wind in km/h\")\n",
|
||||
"\n",
|
||||
"# Inherit 'messages' key from MessagesState, which is a list of chat messages \n",
|
||||
"\n",
|
||||
"# Inherit 'messages' key from MessagesState, which is a list of chat messages\n",
|
||||
"class AgentState(MessagesState):\n",
|
||||
" # Final structured response from the agent\n",
|
||||
" final_response: WeatherResponse\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"@tool\n",
|
||||
"def get_weather(city: Literal[\"nyc\", \"sf\"]):\n",
|
||||
" \"\"\"Use this to get weather information.\"\"\"\n",
|
||||
@@ -137,11 +143,12 @@
|
||||
" return \"It is 75 degrees and sunny in SF, with 3 mph winds in the South-East direction\"\n",
|
||||
" else:\n",
|
||||
" raise AssertionError(\"Unknown city\")\n",
|
||||
" \n",
|
||||
"\n",
|
||||
"\n",
|
||||
"tools = [get_weather]\n",
|
||||
" \n",
|
||||
"\n",
|
||||
"model = ChatAnthropic(model=\"claude-3-opus-20240229\")\n",
|
||||
" \n",
|
||||
"\n",
|
||||
"model_with_tools = model.bind_tools(tools)\n",
|
||||
"model_with_structured_output = model.with_structured_output(WeatherResponse)"
|
||||
]
|
||||
@@ -170,33 +177,40 @@
|
||||
"\n",
|
||||
"tools = [get_weather, WeatherResponse]\n",
|
||||
"\n",
|
||||
"# Force the model to use tools by passing tool_choice=\"any\" \n",
|
||||
"model_with_response_tool = model.bind_tools(tools,tool_choice=\"any\")\n",
|
||||
"# Force the model to use tools by passing tool_choice=\"any\"\n",
|
||||
"model_with_response_tool = model.bind_tools(tools, tool_choice=\"any\")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define the function that calls the model\n",
|
||||
"def call_model(state: AgentState):\n",
|
||||
" response = model_with_response_tool.invoke(state['messages'])\n",
|
||||
" response = model_with_response_tool.invoke(state[\"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 that responds to the user\n",
|
||||
"def respond(state: AgentState):\n",
|
||||
" # Construct the final answer from the arguments of the last tool call\n",
|
||||
" response = WeatherResponse(**state['messages'][-1].tool_calls[0]['args'])\n",
|
||||
" response = WeatherResponse(**state[\"messages\"][-1].tool_calls[0][\"args\"])\n",
|
||||
" # We return the final answer\n",
|
||||
" return {\"final_response\": response}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define the function that determines whether to continue or not\n",
|
||||
"def should_continue(state: AgentState):\n",
|
||||
" messages = state[\"messages\"]\n",
|
||||
" last_message = messages[-1]\n",
|
||||
" # If there is only one tool call and it is the response tool call we respond to the user\n",
|
||||
" if len(last_message.tool_calls) == 1 and last_message.tool_calls[0]['name'] == \"WeatherResponse\":\n",
|
||||
" if (\n",
|
||||
" len(last_message.tool_calls) == 1\n",
|
||||
" and last_message.tool_calls[0][\"name\"] == \"WeatherResponse\"\n",
|
||||
" ):\n",
|
||||
" return \"respond\"\n",
|
||||
" # Otherwise we will use the tool node again\n",
|
||||
" else:\n",
|
||||
" return \"continue\"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define a new graph\n",
|
||||
"workflow = StateGraph(AgentState)\n",
|
||||
"\n",
|
||||
@@ -239,7 +253,9 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"answer = graph.invoke(input={\"messages\": [(\"human\", \"what's the weather in SF?\")]})['final_response']"
|
||||
"answer = graph.invoke(input={\"messages\": [(\"human\", \"what's the weather in SF?\")]})[\n",
|
||||
" \"final_response\"\n",
|
||||
"]"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -292,21 +308,26 @@
|
||||
"from langgraph.prebuilt import ToolNode\n",
|
||||
"from langchain_core.messages import HumanMessage\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define the function that calls the model\n",
|
||||
"def call_model(state: AgentState):\n",
|
||||
" response = model_with_tools.invoke(state['messages'])\n",
|
||||
" response = model_with_tools.invoke(state[\"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 that responds to the user\n",
|
||||
"def respond(state: AgentState):\n",
|
||||
" # We call the model with structured output in order to return the same format to the user every time\n",
|
||||
" # state['messages'][-2] is the last ToolMessage in the convo, which we convert to a HumanMessage for the model to use\n",
|
||||
" # We could also pass the entire chat history, but this saves tokens since all we care to structure is the output of the tool\n",
|
||||
" response = model_with_structured_output.invoke([HumanMessage(content=state['messages'][-2].content)])\n",
|
||||
" response = model_with_structured_output.invoke(\n",
|
||||
" [HumanMessage(content=state[\"messages\"][-2].content)]\n",
|
||||
" )\n",
|
||||
" # We return the final answer\n",
|
||||
" return {\"final_response\": response}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define the function that determines whether to continue or not\n",
|
||||
"def should_continue(state: AgentState):\n",
|
||||
" messages = state[\"messages\"]\n",
|
||||
@@ -318,6 +339,7 @@
|
||||
" else:\n",
|
||||
" return \"continue\"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define a new graph\n",
|
||||
"workflow = StateGraph(AgentState)\n",
|
||||
"\n",
|
||||
@@ -361,7 +383,9 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"answer = graph.invoke(input={\"messages\": [(\"human\", \"what's the weather in SF?\")]})['final_response']"
|
||||
"answer = graph.invoke(input={\"messages\": [(\"human\", \"what's the weather in SF?\")]})[\n",
|
||||
" \"final_response\"\n",
|
||||
"]"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -139,7 +139,7 @@
|
||||
"from langgraph.errors import GraphRecursionError\n",
|
||||
"\n",
|
||||
"try:\n",
|
||||
" graph.invoke({\"aggregate\": []},{\"recursion_limit\":3})\n",
|
||||
" graph.invoke({\"aggregate\": []}, {\"recursion_limit\": 3})\n",
|
||||
"except GraphRecursionError:\n",
|
||||
" print(\"Recursion Error\")"
|
||||
]
|
||||
@@ -169,7 +169,7 @@
|
||||
],
|
||||
"source": [
|
||||
"try:\n",
|
||||
" graph.invoke({\"aggregate\": []},{\"recursion_limit\":4})\n",
|
||||
" graph.invoke({\"aggregate\": []}, {\"recursion_limit\": 4})\n",
|
||||
"except GraphRecursionError:\n",
|
||||
" print(\"Recursion Error\")"
|
||||
]
|
||||
|
||||
@@ -59,29 +59,34 @@
|
||||
"from langgraph.graph import StateGraph\n",
|
||||
"from langgraph.graph import START, END\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class State(TypedDict):\n",
|
||||
" value: str\n",
|
||||
" action_result: str\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def router(state: State):\n",
|
||||
" if state['value'] == \"end\":\n",
|
||||
" if state[\"value\"] == \"end\":\n",
|
||||
" return END\n",
|
||||
" else:\n",
|
||||
" return \"action\"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def decision_node(state):\n",
|
||||
" return {'value':'keep going!'}\n",
|
||||
" return {\"value\": \"keep going!\"}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def action_node(state: State):\n",
|
||||
" # Do your action here ...\n",
|
||||
" return {'action_result':'what a great result!'}\n",
|
||||
" return {\"action_result\": \"what a great result!\"}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"workflow = StateGraph(State)\n",
|
||||
"workflow.add_node('decision',decision_node)\n",
|
||||
"workflow.add_node('action',action_node)\n",
|
||||
"workflow.add_edge(START,'decision')\n",
|
||||
"workflow.add_conditional_edges('decision',router,['action',END])\n",
|
||||
"workflow.add_edge('action','decision')\n",
|
||||
"workflow.add_node(\"decision\", decision_node)\n",
|
||||
"workflow.add_node(\"action\", action_node)\n",
|
||||
"workflow.add_edge(START, \"decision\")\n",
|
||||
"workflow.add_conditional_edges(\"decision\", router, [\"action\", END])\n",
|
||||
"workflow.add_edge(\"action\", \"decision\")\n",
|
||||
"app = workflow.compile()"
|
||||
]
|
||||
},
|
||||
@@ -131,7 +136,7 @@
|
||||
"from langgraph.errors import GraphRecursionError\n",
|
||||
"\n",
|
||||
"try:\n",
|
||||
" app.invoke({\"value\":\"hi!\"})\n",
|
||||
" app.invoke({\"value\": \"hi!\"})\n",
|
||||
"except GraphRecursionError:\n",
|
||||
" print(\"Recursion Error\")"
|
||||
]
|
||||
@@ -168,34 +173,39 @@
|
||||
" def __call__(self, step: int) -> bool:\n",
|
||||
" limit = self.config.get(\"recursion_limit\", 0)\n",
|
||||
" return step >= limit - 2\n",
|
||||
" \n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class State(TypedDict):\n",
|
||||
" value: str\n",
|
||||
" action_result: str\n",
|
||||
" is_last_step: Annotated[bool, IsLastOrSecondToLastStepManager]\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def router(state: State):\n",
|
||||
" # Force the agent to end if it is on the last step\n",
|
||||
" if state['is_last_step']:\n",
|
||||
" if state[\"is_last_step\"]:\n",
|
||||
" return END\n",
|
||||
" if state['value'] == \"end\":\n",
|
||||
" if state[\"value\"] == \"end\":\n",
|
||||
" return END\n",
|
||||
" else:\n",
|
||||
" return \"action\"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def decision_node(state):\n",
|
||||
" return {'value':'keep going!'}\n",
|
||||
" return {\"value\": \"keep going!\"}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def action_node(state: State):\n",
|
||||
" # Do your action here ...\n",
|
||||
" return {'action_result':'what a great result!'}\n",
|
||||
" return {\"action_result\": \"what a great result!\"}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"workflow = StateGraph(State)\n",
|
||||
"workflow.add_node('decision',decision_node)\n",
|
||||
"workflow.add_node('action',action_node)\n",
|
||||
"workflow.add_edge(START,'decision')\n",
|
||||
"workflow.add_conditional_edges('decision',router,['action',END])\n",
|
||||
"workflow.add_edge('action','decision')\n",
|
||||
"workflow.add_node(\"decision\", decision_node)\n",
|
||||
"workflow.add_node(\"action\", action_node)\n",
|
||||
"workflow.add_edge(START, \"decision\")\n",
|
||||
"workflow.add_conditional_edges(\"decision\", router, [\"action\", END])\n",
|
||||
"workflow.add_edge(\"action\", \"decision\")\n",
|
||||
"app = workflow.compile()"
|
||||
]
|
||||
},
|
||||
@@ -216,7 +226,7 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"app.invoke({\"value\":\"hi!\"})"
|
||||
"app.invoke({\"value\": \"hi!\"})"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -136,8 +136,10 @@
|
||||
" print(message)\n",
|
||||
" else:\n",
|
||||
" message.pretty_print()\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"inputs = {\"messages\": [(\"user\", \"what is the weather in sf\")]}\n",
|
||||
"config = {\"configurable\": {\"run_id\":\"12345\"}}\n",
|
||||
"config = {\"configurable\": {\"run_id\": \"12345\"}}\n",
|
||||
"\n",
|
||||
"print_stream(graph.stream(inputs, config, stream_mode=\"values\"))"
|
||||
]
|
||||
|
||||
@@ -231,6 +231,7 @@
|
||||
"\n",
|
||||
"from pydantic import BaseModel\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class AgentState(BaseModel):\n",
|
||||
" messages: Annotated[Sequence[BaseMessage], operator.add]"
|
||||
]
|
||||
|
||||
@@ -71,9 +71,10 @@
|
||||
"from langgraph.graph import START, StateGraph, MessagesState, END\n",
|
||||
"from langgraph.types import StreamWriter\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"async def my_node(\n",
|
||||
" state: MessagesState, \n",
|
||||
" writer: StreamWriter # <-- provide StreamWriter to write chunks to be streamed\n",
|
||||
" state: MessagesState,\n",
|
||||
" writer: StreamWriter, # <-- provide StreamWriter to write chunks to be streamed\n",
|
||||
"):\n",
|
||||
" chunks = [\n",
|
||||
" \"Four\",\n",
|
||||
@@ -87,11 +88,12 @@
|
||||
" \"...\",\n",
|
||||
" ]\n",
|
||||
" for chunk in chunks:\n",
|
||||
" # write the chunk to be streamed using stream_mode=custom \n",
|
||||
" # write the chunk to be streamed using stream_mode=custom\n",
|
||||
" writer(chunk)\n",
|
||||
"\n",
|
||||
" return {\"messages\": [AIMessage(content=\" \".join(chunks))]}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define a new graph\n",
|
||||
"workflow = StateGraph(MessagesState)\n",
|
||||
"\n",
|
||||
@@ -184,6 +186,7 @@
|
||||
"from langchain_core.runnables import RunnableConfig, RunnableLambda\n",
|
||||
"from langchain_core.callbacks.manager import adispatch_custom_event\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"async def my_node(state: MessagesState, config: RunnableConfig):\n",
|
||||
" chunks = [\n",
|
||||
" \"Four\",\n",
|
||||
@@ -200,11 +203,12 @@
|
||||
" await adispatch_custom_event(\n",
|
||||
" \"my_custom_event\",\n",
|
||||
" {\"chunk\": chunk},\n",
|
||||
" config=config # <-- propagate config\n",
|
||||
" config=config, # <-- propagate config\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
" return {\"messages\": [AIMessage(content=\" \".join(chunks))]}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define a new graph\n",
|
||||
"workflow = StateGraph(MessagesState)\n",
|
||||
"\n",
|
||||
|
||||
@@ -196,12 +196,19 @@
|
||||
"\n",
|
||||
"inputs = [HumanMessage(content=\"what is the weather in sf\")]\n",
|
||||
"final_message = \"\"\n",
|
||||
"async for msg, metadata in agent.astream({\"messages\": [(\"human\", \"what items are on the shelf?\")]}, stream_mode=\"messages\"):\n",
|
||||
"async for msg, metadata in agent.astream(\n",
|
||||
" {\"messages\": [(\"human\", \"what items are on the shelf?\")]}, stream_mode=\"messages\"\n",
|
||||
"):\n",
|
||||
" # Stream all messages from the tool node\n",
|
||||
" if msg.content and not isinstance(msg,HumanMessage) and metadata['langgraph_node'] == 'tools' and not msg.name:\n",
|
||||
" if (\n",
|
||||
" msg.content\n",
|
||||
" and not isinstance(msg, HumanMessage)\n",
|
||||
" and metadata[\"langgraph_node\"] == \"tools\"\n",
|
||||
" and not msg.name\n",
|
||||
" ):\n",
|
||||
" print(msg.content, end=\"|\", flush=True)\n",
|
||||
" # Final message should come from our agent\n",
|
||||
" if msg.content and metadata['langgraph_node'] == \"agent\":\n",
|
||||
" if msg.content and metadata[\"langgraph_node\"] == \"agent\":\n",
|
||||
" final_message += msg.content"
|
||||
]
|
||||
},
|
||||
|
||||
@@ -163,6 +163,7 @@
|
||||
" response.id = last_ai_message.id\n",
|
||||
" return {\"messages\": [response]}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"workflow = StateGraph(MessagesState)\n",
|
||||
"\n",
|
||||
"workflow.add_node(\"agent\", call_model)\n",
|
||||
@@ -222,7 +223,8 @@
|
||||
"source": [
|
||||
"import warnings\n",
|
||||
"from langchain_core._api import LangChainBetaWarning\n",
|
||||
"warnings.filterwarnings('ignore', category=LangChainBetaWarning)"
|
||||
"\n",
|
||||
"warnings.filterwarnings(\"ignore\", category=LangChainBetaWarning)"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -260,7 +262,11 @@
|
||||
"\n",
|
||||
"inputs = [HumanMessage(content=\"what is the weather in sf\")]\n",
|
||||
"async for msg, metadata in app.astream({\"messages\": inputs}, stream_mode=\"messages\"):\n",
|
||||
" if msg.content and not isinstance(msg,HumanMessage) and metadata['langgraph_node'] == 'final':\n",
|
||||
" if (\n",
|
||||
" msg.content\n",
|
||||
" and not isinstance(msg, HumanMessage)\n",
|
||||
" and metadata[\"langgraph_node\"] == \"final\"\n",
|
||||
" ):\n",
|
||||
" print(msg.content, end=\"|\", flush=True)"
|
||||
]
|
||||
},
|
||||
|
||||
@@ -87,7 +87,7 @@
|
||||
"def add_logs(left: list[Logs], right: list[Logs]) -> list[Logs]:\n",
|
||||
" if not left:\n",
|
||||
" left = []\n",
|
||||
" \n",
|
||||
"\n",
|
||||
" if not right:\n",
|
||||
" right = []\n",
|
||||
"\n",
|
||||
@@ -141,6 +141,7 @@
|
||||
" # subgraph keys\n",
|
||||
" summary: str\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def generate_summary(state: QuestionSummarizationState):\n",
|
||||
" docs = state[\"logs\"]\n",
|
||||
" # NOTE: you can implement custom summarization logic here\n",
|
||||
@@ -255,7 +256,7 @@
|
||||
" id=\"3\",\n",
|
||||
" question=\"How do I create react agent in langgraph?\",\n",
|
||||
" answer=\"from langgraph.prebuilt import create_react_agent\",\n",
|
||||
" )\n",
|
||||
" ),\n",
|
||||
"]\n",
|
||||
"\n",
|
||||
"input = {\"raw_logs\": dummy_logs}"
|
||||
@@ -335,11 +336,18 @@
|
||||
"source": [
|
||||
"# Format the namespace slightly nicer\n",
|
||||
"def format_namespace(namespace):\n",
|
||||
" return namespace[-1].split(':')[0]+' subgraph' if len(namespace) > 0 else 'parent graph'\n",
|
||||
" return (\n",
|
||||
" namespace[-1].split(\":\")[0] + \" subgraph\"\n",
|
||||
" if len(namespace) > 0\n",
|
||||
" else \"parent graph\"\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"for namespace, chunk in graph.stream(input, stream_mode=\"updates\", subgraphs=True):\n",
|
||||
" node_name = list(chunk.keys())[0]\n",
|
||||
" print(f\"---------- Update from node {node_name} in {format_namespace(namespace)} ---------\")\n",
|
||||
" print(\n",
|
||||
" f\"---------- Update from node {node_name} in {format_namespace(namespace)} ---------\"\n",
|
||||
" )\n",
|
||||
" print(chunk[node_name])"
|
||||
]
|
||||
},
|
||||
|
||||
@@ -99,6 +99,7 @@
|
||||
" ensure_config,\n",
|
||||
" get_callback_manager_for_config,\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"openai_client = AsyncOpenAI()\n",
|
||||
"# define tool schema for openai tool calling\n",
|
||||
"\n",
|
||||
@@ -311,7 +312,10 @@
|
||||
"from langchain_core.messages import AIMessageChunk\n",
|
||||
"\n",
|
||||
"first = True\n",
|
||||
"async for msg, metadata in graph.astream({\"messages\": [{\"role\": \"user\", \"content\": \"what's in the bedroom\"}]}, stream_mode=\"messages\"):\n",
|
||||
"async for msg, metadata in graph.astream(\n",
|
||||
" {\"messages\": [{\"role\": \"user\", \"content\": \"what's in the bedroom\"}]},\n",
|
||||
" stream_mode=\"messages\",\n",
|
||||
"):\n",
|
||||
" if msg.content:\n",
|
||||
" print(msg.content, end=\"|\", flush=True)\n",
|
||||
"\n",
|
||||
|
||||
@@ -339,7 +339,7 @@
|
||||
" # Next, we pass in the function that will determine which node is called next.\n",
|
||||
" should_continue,\n",
|
||||
" # Next we pass in the path map - all the nodes this edge could go to\n",
|
||||
" [\"tools\",END]\n",
|
||||
" [\"tools\", END],\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"workflow.add_edge(\"tools\", \"agent\")\n",
|
||||
@@ -414,7 +414,7 @@
|
||||
"inputs = [HumanMessage(content=\"what is the weather in sf\")]\n",
|
||||
"first = True\n",
|
||||
"async for msg, metadata in app.astream({\"messages\": inputs}, stream_mode=\"messages\"):\n",
|
||||
" if msg.content and not isinstance(msg,HumanMessage):\n",
|
||||
" if msg.content and not isinstance(msg, HumanMessage):\n",
|
||||
" print(msg.content, end=\"|\", flush=True)\n",
|
||||
"\n",
|
||||
" if isinstance(msg, AIMessageChunk):\n",
|
||||
|
||||
@@ -71,10 +71,12 @@
|
||||
"class GrandChildState(TypedDict):\n",
|
||||
" my_grandchild_key: str\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def grandchild_1(state: GrandChildState) -> GrandChildState:\n",
|
||||
" # NOTE: child or parent keys will not be accessible here\n",
|
||||
" return {\"my_grandchild_key\": state[\"my_grandchild_key\"] + \", how are you\"}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"grandchild = StateGraph(GrandChildState)\n",
|
||||
"grandchild.add_node(\"grandchild_1\", grandchild_1)\n",
|
||||
"\n",
|
||||
@@ -194,11 +196,13 @@
|
||||
"source": [
|
||||
"class ParentState(TypedDict):\n",
|
||||
" my_key: str\n",
|
||||
" \n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def parent_1(state: ParentState) -> ParentState:\n",
|
||||
" # NOTE: child or grandchild keys won't be accessible here\n",
|
||||
" return {\"my_key\": \"hi \" + state[\"my_key\"]}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def parent_2(state: ParentState) -> ParentState:\n",
|
||||
" return {\"my_key\": state[\"my_key\"] + \" bye!\"}\n",
|
||||
"\n",
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
"def add_logs(left: list[Logs], right: list[Logs]) -> list[Logs]:\n",
|
||||
" if not left:\n",
|
||||
" left = []\n",
|
||||
" \n",
|
||||
"\n",
|
||||
" if not right:\n",
|
||||
" right = []\n",
|
||||
"\n",
|
||||
@@ -225,7 +225,7 @@
|
||||
" id=\"3\",\n",
|
||||
" question=\"How do I create react agent in langgraph?\",\n",
|
||||
" answer=\"from langgraph.prebuilt import create_react_agent\",\n",
|
||||
" )\n",
|
||||
" ),\n",
|
||||
"]\n",
|
||||
"\n",
|
||||
"\n",
|
||||
@@ -323,6 +323,7 @@
|
||||
"\n",
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# define a simple reducer\n",
|
||||
"def reduce_list(left: list, right: list) -> list:\n",
|
||||
" if not left:\n",
|
||||
@@ -331,6 +332,7 @@
|
||||
" right = []\n",
|
||||
" return left + right\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# define parent and child state\n",
|
||||
"class ChildState(TypedDict):\n",
|
||||
" name: str\n",
|
||||
@@ -345,7 +347,7 @@
|
||||
"# define a helper to build the graph\n",
|
||||
"def make_graph(parent_schema, child_schema):\n",
|
||||
" child_builder = StateGraph(child_schema)\n",
|
||||
" \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",
|
||||
@@ -353,16 +355,16 @@
|
||||
" 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",
|
||||
"\n",
|
||||
" builder = StateGraph(parent_schema)\n",
|
||||
" \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",
|
||||
"\n",
|
||||
" # Add connections\n",
|
||||
" builder.add_edge(\"grandparent\", \"parent\")\n",
|
||||
" builder.add_edge(\"parent\", \"child\")\n",
|
||||
@@ -508,6 +510,7 @@
|
||||
"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",
|
||||
|
||||
@@ -89,6 +89,7 @@
|
||||
" \"\"\"Get the weather for a specific city\"\"\"\n",
|
||||
" return f\"It's sunny in {city}!\"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"raw_model = ChatOpenAI()\n",
|
||||
"model = raw_model.with_structured_output(get_weather)\n",
|
||||
"\n",
|
||||
@@ -98,11 +99,12 @@
|
||||
"\n",
|
||||
"\n",
|
||||
"def model_node(state: SubGraphState):\n",
|
||||
" result = model.invoke(state['messages'])\n",
|
||||
" result = model.invoke(state[\"messages\"])\n",
|
||||
" return {\"city\": result[\"city\"]}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def weather_node(state: SubGraphState):\n",
|
||||
" result = get_weather.invoke({\"city\": state['city']})\n",
|
||||
" result = get_weather.invoke({\"city\": state[\"city\"]})\n",
|
||||
" return {\"messages\": [{\"role\": \"assistant\", \"content\": result}]}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
@@ -145,22 +147,26 @@
|
||||
"class Router(TypedDict):\n",
|
||||
" route: Literal[\"weather\", \"other\"]\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"router_model = raw_model.with_structured_output(Router)\n",
|
||||
" \n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def router_node(state: RouterState):\n",
|
||||
" system_message = \"Classify the incoming query as either about weather or not.\"\n",
|
||||
" messages = [{\"role\": \"system\", \"content\": system_message}] + state['messages']\n",
|
||||
" messages = [{\"role\": \"system\", \"content\": system_message}] + state[\"messages\"]\n",
|
||||
" route = router_model.invoke(messages)\n",
|
||||
" return {\"route\": route['route']}\n",
|
||||
" return {\"route\": route[\"route\"]}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def normal_llm_node(state: RouterState):\n",
|
||||
" response = raw_model.invoke(state['messages'])\n",
|
||||
" response = raw_model.invoke(state[\"messages\"])\n",
|
||||
" return {\"messages\": [response]}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def route_after_prediction(state: RouterState) -> Literal[\"weather_graph\", \"normal_llm_node\"]:\n",
|
||||
" if state['route'] == \"weather\":\n",
|
||||
"def route_after_prediction(\n",
|
||||
" state: RouterState,\n",
|
||||
") -> Literal[\"weather_graph\", \"normal_llm_node\"]:\n",
|
||||
" if state[\"route\"] == \"weather\":\n",
|
||||
" return \"weather_graph\"\n",
|
||||
" else:\n",
|
||||
" return \"normal_llm_node\"\n",
|
||||
@@ -421,7 +427,9 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"parent_graph_state_before_subgraph = next(h for h in graph.get_state_history(config) if h.next == ('weather_graph',))"
|
||||
"parent_graph_state_before_subgraph = next(\n",
|
||||
" h for h in graph.get_state_history(config) if h.next == (\"weather_graph\",)\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -430,7 +438,11 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"subgraph_state_before_model_node = next(h for h in graph.get_state_history(parent_graph_state_before_subgraph.tasks[0].state) if h.next == ('model_node',))\n",
|
||||
"subgraph_state_before_model_node = next(\n",
|
||||
" h\n",
|
||||
" for h in graph.get_state_history(parent_graph_state_before_subgraph.tasks[0].state)\n",
|
||||
" if h.next == (\"model_node\",)\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"# This pattern can be extended no matter how many levels deep - image model node was another subgraph in this case\n",
|
||||
"# subsubgraph_stat_history = next(h for h in graph.get_state_history(subgraph_state_before_model_node.tasks[0].state) if h.next == ('my_subsubgraph_node',))"
|
||||
@@ -486,7 +498,12 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"for value in graph.stream(None, config=subgraph_state_before_model_node.config, stream_mode=\"values\", subgraphs=True):\n",
|
||||
"for value in graph.stream(\n",
|
||||
" None,\n",
|
||||
" config=subgraph_state_before_model_node.config,\n",
|
||||
" stream_mode=\"values\",\n",
|
||||
" subgraphs=True,\n",
|
||||
"):\n",
|
||||
" print(value)"
|
||||
]
|
||||
},
|
||||
@@ -546,7 +563,7 @@
|
||||
],
|
||||
"source": [
|
||||
"state = graph.get_state(config, subgraphs=True)\n",
|
||||
"state.values['messages']"
|
||||
"state.values[\"messages\"]"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -637,16 +654,22 @@
|
||||
"source": [
|
||||
"config = {\"configurable\": {\"thread_id\": \"14\"}}\n",
|
||||
"inputs = {\"messages\": [{\"role\": \"user\", \"content\": \"what's the weather in sf\"}]}\n",
|
||||
"for update in graph.stream(inputs, config=config, stream_mode=\"updates\", subgraphs=True):\n",
|
||||
"for update in graph.stream(\n",
|
||||
" inputs, config=config, stream_mode=\"updates\", subgraphs=True\n",
|
||||
"):\n",
|
||||
" print(update)\n",
|
||||
"# Graph execution should stop before the weather node\n",
|
||||
"print(\"interrupted!\")\n",
|
||||
"state = graph.get_state(config, subgraphs=True)\n",
|
||||
"# We update the state by passing in the message we want returned from the weather node, and make sure to use as_node\n",
|
||||
"graph.update_state(state.tasks[0].state.config, {\"messages\": [{\"role\": \"assistant\", \"content\": \"rainy\"}]}, as_node=\"weather_node\")\n",
|
||||
"graph.update_state(\n",
|
||||
" state.tasks[0].state.config,\n",
|
||||
" {\"messages\": [{\"role\": \"assistant\", \"content\": \"rainy\"}]},\n",
|
||||
" as_node=\"weather_node\",\n",
|
||||
")\n",
|
||||
"for update in graph.stream(None, config=config, stream_mode=\"updates\", subgraphs=True):\n",
|
||||
" print(update)\n",
|
||||
"print(graph.get_state(config).values['messages'])"
|
||||
"print(graph.get_state(config).values[\"messages\"])"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -679,16 +702,22 @@
|
||||
"source": [
|
||||
"config = {\"configurable\": {\"thread_id\": \"8\"}}\n",
|
||||
"inputs = {\"messages\": [{\"role\": \"user\", \"content\": \"what's the weather in sf\"}]}\n",
|
||||
"for update in graph.stream(inputs, config=config, stream_mode=\"updates\", subgraphs=True):\n",
|
||||
"for update in graph.stream(\n",
|
||||
" inputs, config=config, stream_mode=\"updates\", subgraphs=True\n",
|
||||
"):\n",
|
||||
" print(update)\n",
|
||||
"# Graph execution should stop before the weather node\n",
|
||||
"print(\"interrupted!\")\n",
|
||||
"# We update the state by passing in the message we want returned from the weather graph, making sure to use as_node\n",
|
||||
"# Note that we don't need to pass in the subgraph config, since we aren't updating the state inside the subgraph\n",
|
||||
"graph.update_state(config, {\"messages\": [{\"role\": \"assistant\", \"content\": \"rainy\"}]}, as_node=\"weather_graph\")\n",
|
||||
"graph.update_state(\n",
|
||||
" config,\n",
|
||||
" {\"messages\": [{\"role\": \"assistant\", \"content\": \"rainy\"}]},\n",
|
||||
" as_node=\"weather_graph\",\n",
|
||||
")\n",
|
||||
"for update in graph.stream(None, config=config, stream_mode=\"updates\"):\n",
|
||||
" print(update)\n",
|
||||
"print(graph.get_state(config).values['messages'])"
|
||||
"print(graph.get_state(config).values[\"messages\"])"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -723,22 +752,26 @@
|
||||
"class Router(TypedDict):\n",
|
||||
" route: Literal[\"weather\", \"other\"]\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"router_model = raw_model.with_structured_output(Router)\n",
|
||||
" \n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def router_node(state: RouterState):\n",
|
||||
" system_message = \"Classify the incoming query as either about weather or not.\"\n",
|
||||
" messages = [{\"role\": \"system\", \"content\": system_message}] + state['messages']\n",
|
||||
" messages = [{\"role\": \"system\", \"content\": system_message}] + state[\"messages\"]\n",
|
||||
" route = router_model.invoke(messages)\n",
|
||||
" return {\"route\": route['route']}\n",
|
||||
" return {\"route\": route[\"route\"]}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def normal_llm_node(state: RouterState):\n",
|
||||
" response = raw_model.invoke(state['messages'])\n",
|
||||
" response = raw_model.invoke(state[\"messages\"])\n",
|
||||
" return {\"messages\": [response]}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def route_after_prediction(state: RouterState) -> Literal[\"weather_graph\", \"normal_llm_node\"]:\n",
|
||||
" if state['route'] == \"weather\":\n",
|
||||
"def route_after_prediction(\n",
|
||||
" state: RouterState,\n",
|
||||
") -> Literal[\"weather_graph\", \"normal_llm_node\"]:\n",
|
||||
" if state[\"route\"] == \"weather\":\n",
|
||||
" return \"weather_graph\"\n",
|
||||
" else:\n",
|
||||
" return \"normal_llm_node\"\n",
|
||||
@@ -765,24 +798,30 @@
|
||||
"\n",
|
||||
"memory = MemorySaver()\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class GrandfatherState(MessagesState):\n",
|
||||
" to_continue: bool\n",
|
||||
" \n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def router_node(state: GrandfatherState):\n",
|
||||
" # Dummy logic that will always continue\n",
|
||||
" return {\"to_continue\": True}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def route_after_prediction(state: GrandfatherState):\n",
|
||||
" if state['to_continue']:\n",
|
||||
" if state[\"to_continue\"]:\n",
|
||||
" return \"graph\"\n",
|
||||
" else:\n",
|
||||
" return END\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"grandparent_graph = StateGraph(GrandfatherState)\n",
|
||||
"grandparent_graph.add_node(router_node)\n",
|
||||
"grandparent_graph.add_node(\"graph\", graph)\n",
|
||||
"grandparent_graph.add_edge(START, \"router_node\")\n",
|
||||
"grandparent_graph.add_conditional_edges(\"router_node\", route_after_prediction, ['graph',END])\n",
|
||||
"grandparent_graph.add_conditional_edges(\n",
|
||||
" \"router_node\", route_after_prediction, [\"graph\", END]\n",
|
||||
")\n",
|
||||
"grandparent_graph.add_edge(\"graph\", END)\n",
|
||||
"grandparent_graph = grandparent_graph.compile(checkpointer=MemorySaver())"
|
||||
]
|
||||
@@ -835,7 +874,9 @@
|
||||
"source": [
|
||||
"config = {\"configurable\": {\"thread_id\": \"2\"}}\n",
|
||||
"inputs = {\"messages\": [{\"role\": \"user\", \"content\": \"what's the weather in sf\"}]}\n",
|
||||
"for update in grandparent_graph.stream(inputs, config=config, stream_mode=\"updates\", subgraphs=True):\n",
|
||||
"for update in grandparent_graph.stream(\n",
|
||||
" inputs, config=config, stream_mode=\"updates\", subgraphs=True\n",
|
||||
"):\n",
|
||||
" print(update)"
|
||||
]
|
||||
},
|
||||
@@ -897,10 +938,16 @@
|
||||
"grandparent_graph_state = state\n",
|
||||
"parent_graph_state = grandparent_graph_state.tasks[0].state\n",
|
||||
"subgraph_state = parent_graph_state.tasks[0].state\n",
|
||||
"grandparent_graph.update_state(subgraph_state.config, {\"messages\": [{\"role\": \"assistant\", \"content\": \"rainy\"}]}, as_node=\"weather_node\")\n",
|
||||
"for update in grandparent_graph.stream(None, config=config, stream_mode=\"updates\", subgraphs=True):\n",
|
||||
"grandparent_graph.update_state(\n",
|
||||
" subgraph_state.config,\n",
|
||||
" {\"messages\": [{\"role\": \"assistant\", \"content\": \"rainy\"}]},\n",
|
||||
" as_node=\"weather_node\",\n",
|
||||
")\n",
|
||||
"for update in grandparent_graph.stream(\n",
|
||||
" None, config=config, stream_mode=\"updates\", subgraphs=True\n",
|
||||
"):\n",
|
||||
" print(update)\n",
|
||||
"print(grandparent_graph.get_state(config).values['messages'])"
|
||||
"print(grandparent_graph.get_state(config).values[\"messages\"])"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -155,11 +155,7 @@
|
||||
"workflow.add_node(\"tools\", tool_node)\n",
|
||||
"\n",
|
||||
"workflow.add_edge(START, \"agent\")\n",
|
||||
"workflow.add_conditional_edges(\n",
|
||||
" \"agent\",\n",
|
||||
" should_continue,\n",
|
||||
" ['tools',END]\n",
|
||||
")\n",
|
||||
"workflow.add_conditional_edges(\"agent\", should_continue, [\"tools\", END])\n",
|
||||
"workflow.add_edge(\"tools\", \"agent\")\n",
|
||||
"\n",
|
||||
"app = workflow.compile()"
|
||||
@@ -296,6 +292,7 @@
|
||||
"from langchain_core.output_parsers import StrOutputParser\n",
|
||||
"from pydantic import BaseModel, Field\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class HaikuRequest(BaseModel):\n",
|
||||
" topic: list[str] = Field(\n",
|
||||
" max_length=3,\n",
|
||||
@@ -303,7 +300,6 @@
|
||||
" )\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"@tool\n",
|
||||
"def master_haiku_generator(request: HaikuRequest):\n",
|
||||
" \"\"\"Generates a haiku based on the provided topics.\"\"\"\n",
|
||||
@@ -341,11 +337,7 @@
|
||||
"workflow.add_node(\"tools\", tool_node)\n",
|
||||
"\n",
|
||||
"workflow.add_edge(START, \"agent\")\n",
|
||||
"workflow.add_conditional_edges(\n",
|
||||
" \"agent\",\n",
|
||||
" should_continue,\n",
|
||||
" ['tools',END]\n",
|
||||
")\n",
|
||||
"workflow.add_conditional_edges(\"agent\", should_continue, [\"tools\", END])\n",
|
||||
"workflow.add_edge(\"tools\", \"agent\")\n",
|
||||
"\n",
|
||||
"app = workflow.compile()\n",
|
||||
@@ -483,11 +475,7 @@
|
||||
"workflow.add_node(\"fallback_agent\", call_fallback_model)\n",
|
||||
"\n",
|
||||
"workflow.add_edge(START, \"agent\")\n",
|
||||
"workflow.add_conditional_edges(\n",
|
||||
" \"agent\",\n",
|
||||
" should_continue,\n",
|
||||
" ['tools',END]\n",
|
||||
")\n",
|
||||
"workflow.add_conditional_edges(\"agent\", should_continue, [\"tools\", END])\n",
|
||||
"workflow.add_conditional_edges(\"tools\", should_fallback)\n",
|
||||
"workflow.add_edge(\"remove_failed_tool_call_attempt\", \"fallback_agent\")\n",
|
||||
"workflow.add_edge(\"fallback_agent\", \"tools\")\n",
|
||||
|
||||
@@ -335,11 +335,7 @@
|
||||
"workflow.add_node(\"tools\", tool_node)\n",
|
||||
"\n",
|
||||
"workflow.add_edge(START, \"agent\")\n",
|
||||
"workflow.add_conditional_edges(\n",
|
||||
" \"agent\",\n",
|
||||
" should_continue,\n",
|
||||
" ['tools',END]\n",
|
||||
")\n",
|
||||
"workflow.add_conditional_edges(\"agent\", should_continue, [\"tools\", END])\n",
|
||||
"workflow.add_edge(\"tools\", \"agent\")\n",
|
||||
"\n",
|
||||
"app = workflow.compile()"
|
||||
|
||||
@@ -425,7 +425,9 @@
|
||||
"try:\n",
|
||||
" display(Image(app.get_graph().draw_png()))\n",
|
||||
"except ImportError:\n",
|
||||
" print(\"You likely need to install dependencies for pygraphviz, see more here https://github.com/pygraphviz/pygraphviz/blob/main/INSTALL.txt\")"
|
||||
" print(\n",
|
||||
" \"You likely need to install dependencies for pygraphviz, see more here https://github.com/pygraphviz/pygraphviz/blob/main/INSTALL.txt\"\n",
|
||||
" )"
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
@@ -407,7 +407,7 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"for chunk in simulation.stream({\"messages\":[]}):\n",
|
||||
"for chunk in simulation.stream({\"messages\": []}):\n",
|
||||
" # Print out all events aside from the final end chunk\n",
|
||||
" if END not in chunk:\n",
|
||||
" print(chunk)\n",
|
||||
|
||||
@@ -378,8 +378,8 @@
|
||||
],
|
||||
"source": [
|
||||
"import uuid\n",
|
||||
" \n",
|
||||
"cached_human_responses = ['hi!','rag prompt','1 rag, 2 none, 3 no, 4 no','red','q']\n",
|
||||
"\n",
|
||||
"cached_human_responses = [\"hi!\", \"rag prompt\", \"1 rag, 2 none, 3 no, 4 no\", \"red\", \"q\"]\n",
|
||||
"cached_response_index = 0\n",
|
||||
"config = {\"configurable\": {\"thread_id\": str(uuid.uuid4())}}\n",
|
||||
"while True:\n",
|
||||
|
||||
@@ -196,7 +196,9 @@
|
||||
"llm = ChatOpenAI(temperature=0, model=expt_llm)\n",
|
||||
"code_gen_chain_oai = code_gen_prompt | llm.with_structured_output(code)\n",
|
||||
"question = \"How do I build a RAG chain in LCEL?\"\n",
|
||||
"solution = code_gen_chain_oai.invoke({\"context\":concatenated_content,\"messages\":[(\"user\",question)]})\n",
|
||||
"solution = code_gen_chain_oai.invoke(\n",
|
||||
" {\"context\": concatenated_content, \"messages\": [(\"user\", question)]}\n",
|
||||
")\n",
|
||||
"solution"
|
||||
]
|
||||
},
|
||||
@@ -618,7 +620,7 @@
|
||||
],
|
||||
"source": [
|
||||
"question = \"How can I directly pass a string to a runnable and use it to construct the input needed for my prompt?\"\n",
|
||||
"solution = app.invoke({\"messages\": [(\"user\", question)], \"iterations\": 0, \"error\":\"\"})"
|
||||
"solution = app.invoke({\"messages\": [(\"user\", question)], \"iterations\": 0, \"error\": \"\"})"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -639,7 +641,7 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"solution['generation']"
|
||||
"solution[\"generation\"]"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -764,7 +766,9 @@
|
||||
"\n",
|
||||
"def predict_langgraph(example: dict):\n",
|
||||
" \"\"\"LangGraph\"\"\"\n",
|
||||
" graph = app.invoke({\"messages\": [(\"user\", example[\"question\"])], \"iterations\": 0, \"error\": \"\"})\n",
|
||||
" graph = app.invoke(\n",
|
||||
" {\"messages\": [(\"user\", example[\"question\"])], \"iterations\": 0, \"error\": \"\"}\n",
|
||||
" )\n",
|
||||
" solution = graph[\"generation\"]\n",
|
||||
" return {\"imports\": solution.imports, \"code\": solution.code}"
|
||||
]
|
||||
|
||||
@@ -107,6 +107,8 @@
|
||||
" f.write(response.content)\n",
|
||||
" # Backup - we will use this to \"reset\" our DB in each section\n",
|
||||
" shutil.copy(local_file, backup_file)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Convert the flights to present time for our tutorial\n",
|
||||
"def update_dates(file):\n",
|
||||
" shutil.copy(backup_file, file)\n",
|
||||
@@ -151,6 +153,7 @@
|
||||
"\n",
|
||||
" return file\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"db = update_dates(local_file)"
|
||||
]
|
||||
},
|
||||
@@ -2560,9 +2563,7 @@
|
||||
"\n",
|
||||
"\n",
|
||||
"builder.add_conditional_edges(\n",
|
||||
" \"assistant\",\n",
|
||||
" route_tools,\n",
|
||||
" [\"safe_tools\", \"sensitive_tools\", END]\n",
|
||||
" \"assistant\", route_tools, [\"safe_tools\", \"sensitive_tools\", END]\n",
|
||||
")\n",
|
||||
"builder.add_edge(\"safe_tools\", \"assistant\")\n",
|
||||
"builder.add_edge(\"sensitive_tools\", \"assistant\")\n",
|
||||
@@ -3540,7 +3541,11 @@
|
||||
"\n",
|
||||
"builder.add_edge(\"update_flight_sensitive_tools\", \"update_flight\")\n",
|
||||
"builder.add_edge(\"update_flight_safe_tools\", \"update_flight\")\n",
|
||||
"builder.add_conditional_edges(\"update_flight\", route_update_flight, [\"update_flight_sensitive_tools\",\"update_flight_safe_tools\",\"leave_skill\",END])\n",
|
||||
"builder.add_conditional_edges(\n",
|
||||
" \"update_flight\",\n",
|
||||
" route_update_flight,\n",
|
||||
" [\"update_flight_sensitive_tools\", \"update_flight_safe_tools\", \"leave_skill\", END],\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# This node will be shared for exiting all specialized assistants\n",
|
||||
@@ -3620,7 +3625,16 @@
|
||||
"\n",
|
||||
"builder.add_edge(\"book_car_rental_sensitive_tools\", \"book_car_rental\")\n",
|
||||
"builder.add_edge(\"book_car_rental_safe_tools\", \"book_car_rental\")\n",
|
||||
"builder.add_conditional_edges(\"book_car_rental\", route_book_car_rental, [\"book_car_rental_safe_tools\",\"book_car_rental_sensitive_tools\",\"leave_skill\",END])"
|
||||
"builder.add_conditional_edges(\n",
|
||||
" \"book_car_rental\",\n",
|
||||
" route_book_car_rental,\n",
|
||||
" [\n",
|
||||
" \"book_car_rental_safe_tools\",\n",
|
||||
" \"book_car_rental_sensitive_tools\",\n",
|
||||
" \"leave_skill\",\n",
|
||||
" END,\n",
|
||||
" ],\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -3672,7 +3686,11 @@
|
||||
"\n",
|
||||
"builder.add_edge(\"book_hotel_sensitive_tools\", \"book_hotel\")\n",
|
||||
"builder.add_edge(\"book_hotel_safe_tools\", \"book_hotel\")\n",
|
||||
"builder.add_conditional_edges(\"book_hotel\", route_book_hotel, [\"leave_skill\", \"book_hotel_safe_tools\", \"book_hotel_sensitive_tools\", END])"
|
||||
"builder.add_conditional_edges(\n",
|
||||
" \"book_hotel\",\n",
|
||||
" route_book_hotel,\n",
|
||||
" [\"leave_skill\", \"book_hotel_safe_tools\", \"book_hotel_sensitive_tools\", END],\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -3725,7 +3743,11 @@
|
||||
"\n",
|
||||
"builder.add_edge(\"book_excursion_sensitive_tools\", \"book_excursion\")\n",
|
||||
"builder.add_edge(\"book_excursion_safe_tools\", \"book_excursion\")\n",
|
||||
"builder.add_conditional_edges(\"book_excursion\", route_book_excursion, [\"book_excursion_safe_tools\",\"book_excursion_sensitive_tools\",\"leave_skill\",END])"
|
||||
"builder.add_conditional_edges(\n",
|
||||
" \"book_excursion\",\n",
|
||||
" route_book_excursion,\n",
|
||||
" [\"book_excursion_safe_tools\", \"book_excursion_sensitive_tools\", \"leave_skill\", END],\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -291,7 +291,7 @@
|
||||
" return \"validator\"\n",
|
||||
" return END\n",
|
||||
"\n",
|
||||
" builder.add_conditional_edges(\"llm\", route_validator, ['validator',END])\n",
|
||||
" builder.add_conditional_edges(\"llm\", route_validator, [\"validator\", END])\n",
|
||||
" builder.add_edge(\"fallback\", \"validator\")\n",
|
||||
" max_attempts = retry_strategy.get(\"max_attempts\", 3)\n",
|
||||
"\n",
|
||||
@@ -307,7 +307,9 @@
|
||||
" return \"fallback\"\n",
|
||||
" return \"finalizer\"\n",
|
||||
"\n",
|
||||
" builder.add_conditional_edges(\"validator\", route_validation, [\"finalizer\", \"fallback\"])\n",
|
||||
" builder.add_conditional_edges(\n",
|
||||
" \"validator\", route_validation, [\"finalizer\", \"fallback\"]\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
" builder.add_edge(\"finalizer\", END)\n",
|
||||
"\n",
|
||||
|
||||
@@ -2108,6 +2108,7 @@
|
||||
"source": [
|
||||
"from pydantic import BaseModel\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class RequestAssistance(BaseModel):\n",
|
||||
" \"\"\"Escalate the conversation to an expert. Use this if you are unable to assist directly or if the user requires support beyond your permissions.\n",
|
||||
"\n",
|
||||
@@ -2668,6 +2669,7 @@
|
||||
"from langchain_anthropic import ChatAnthropic\n",
|
||||
"from langchain_community.tools.tavily_search import TavilySearchResults\n",
|
||||
"from langchain_core.messages import AIMessage, ToolMessage\n",
|
||||
"\n",
|
||||
"# NOTE: you must use langchain-core >= 0.3 with Pydantic v2\n",
|
||||
"from pydantic import BaseModel\n",
|
||||
"from typing_extensions import TypedDict\n",
|
||||
|
||||
@@ -142,6 +142,7 @@
|
||||
" def normalized_score(self) -> float:\n",
|
||||
" return self.score / 10.0\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class Node:\n",
|
||||
" def __init__(\n",
|
||||
" self,\n",
|
||||
@@ -476,12 +477,22 @@
|
||||
" \"\"\"Generate the initial candidate response.\"\"\"\n",
|
||||
" res = initial_answer_chain.invoke({\"input\": state[\"input\"]})\n",
|
||||
" parsed = parser.invoke(res)\n",
|
||||
" tool_responses = [tool_node.invoke(\n",
|
||||
" {\"messages\": [\n",
|
||||
" AIMessage(content=\"\",tool_calls=[{\"name\":r[\"type\"], \"args\":r[\"args\"], 'id':r['id']}]) \n",
|
||||
" ]}\n",
|
||||
" ) for r in parsed]\n",
|
||||
" output_messages = [res] + [tr['messages'][0] for tr in tool_responses]\n",
|
||||
" tool_responses = [\n",
|
||||
" tool_node.invoke(\n",
|
||||
" {\n",
|
||||
" \"messages\": [\n",
|
||||
" AIMessage(\n",
|
||||
" content=\"\",\n",
|
||||
" tool_calls=[\n",
|
||||
" {\"name\": r[\"type\"], \"args\": r[\"args\"], \"id\": r[\"id\"]}\n",
|
||||
" ],\n",
|
||||
" )\n",
|
||||
" ]\n",
|
||||
" }\n",
|
||||
" )\n",
|
||||
" for r in parsed\n",
|
||||
" ]\n",
|
||||
" output_messages = [res] + [tr[\"messages\"][0] for tr in tool_responses]\n",
|
||||
" reflection = reflection_chain.invoke(\n",
|
||||
" {\"input\": state[\"input\"], \"candidate\": output_messages}\n",
|
||||
" )\n",
|
||||
@@ -575,12 +586,13 @@
|
||||
"source": [
|
||||
"from collections import defaultdict\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def select(root: Node) -> dict:\n",
|
||||
" \"\"\"Starting from the root node a child node is selected at each tree level until a leaf node is reached.\"\"\"\n",
|
||||
"\n",
|
||||
" if not root.children:\n",
|
||||
" return root\n",
|
||||
" \n",
|
||||
"\n",
|
||||
" node = root\n",
|
||||
" while node.children:\n",
|
||||
" max_child = max(node.children, key=lambda child: child.upper_confidence_bound())\n",
|
||||
@@ -588,6 +600,7 @@
|
||||
"\n",
|
||||
" return node\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def expand(state: TreeState, config: RunnableConfig) -> dict:\n",
|
||||
" \"\"\"Starting from the \"best\" node in the tree, generate N candidates for the next step.\"\"\"\n",
|
||||
" root = state[\"root\"]\n",
|
||||
@@ -603,16 +616,31 @@
|
||||
" for i, tool_calls in enumerate(parsed)\n",
|
||||
" for tool_call in tool_calls\n",
|
||||
" ]\n",
|
||||
" tool_responses = [(i,tool_node.invoke(\n",
|
||||
" {\"messages\":\n",
|
||||
" [AIMessage(content=\"\",tool_calls=[{\"name\":tool_call[\"type\"], \"args\":tool_call[\"args\"], 'id':tool_call['id']}])]\n",
|
||||
" }\n",
|
||||
" )) for i, tool_call in flattened]\n",
|
||||
" tool_responses = [\n",
|
||||
" (\n",
|
||||
" i,\n",
|
||||
" tool_node.invoke(\n",
|
||||
" {\n",
|
||||
" \"messages\": [\n",
|
||||
" AIMessage(\n",
|
||||
" content=\"\",\n",
|
||||
" tool_calls=[\n",
|
||||
" {\n",
|
||||
" \"name\": tool_call[\"type\"],\n",
|
||||
" \"args\": tool_call[\"args\"],\n",
|
||||
" \"id\": tool_call[\"id\"],\n",
|
||||
" }\n",
|
||||
" ],\n",
|
||||
" )\n",
|
||||
" ]\n",
|
||||
" }\n",
|
||||
" ),\n",
|
||||
" )\n",
|
||||
" for i, tool_call in flattened\n",
|
||||
" ]\n",
|
||||
" collected_responses = defaultdict(list)\n",
|
||||
" for i, resp in tool_responses:\n",
|
||||
" collected_responses[i].append(\n",
|
||||
" resp['messages'][0]\n",
|
||||
" )\n",
|
||||
" collected_responses[i].append(resp[\"messages\"][0])\n",
|
||||
" output_messages = []\n",
|
||||
" for i, candidate in enumerate(new_candidates):\n",
|
||||
" output_messages.append([candidate] + collected_responses[i])\n",
|
||||
@@ -675,13 +703,13 @@
|
||||
" \"start\",\n",
|
||||
" # Either expand/rollout or finish\n",
|
||||
" should_loop,\n",
|
||||
" ['expand',END]\n",
|
||||
" [\"expand\", END],\n",
|
||||
")\n",
|
||||
"builder.add_conditional_edges(\n",
|
||||
" \"expand\",\n",
|
||||
" # Either continue to rollout or finish\n",
|
||||
" should_loop,\n",
|
||||
" ['expand',END]\n",
|
||||
" [\"expand\", END],\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"graph = builder.compile()"
|
||||
|
||||
@@ -859,8 +859,7 @@
|
||||
" args_for_tasks[task[\"idx\"]] = task[\"args\"]\n",
|
||||
" if (\n",
|
||||
" # Depends on other tasks\n",
|
||||
" deps\n",
|
||||
" and (any([dep not in observations for dep in deps]))\n",
|
||||
" deps and (any([dep not in observations for dep in deps]))\n",
|
||||
" ):\n",
|
||||
" futures.append(\n",
|
||||
" executor.submit(\n",
|
||||
@@ -883,7 +882,10 @@
|
||||
" }\n",
|
||||
" tool_messages = [\n",
|
||||
" FunctionMessage(\n",
|
||||
" name=name, content=str(obs), additional_kwargs={\"idx\": k, \"args\": task_args}, tool_call_id = k\n",
|
||||
" name=name,\n",
|
||||
" content=str(obs),\n",
|
||||
" additional_kwargs={\"idx\": k, \"args\": task_args},\n",
|
||||
" tool_call_id=k,\n",
|
||||
" )\n",
|
||||
" for k, (name, task_args, obs) in new_observations.items()\n",
|
||||
" ]\n",
|
||||
@@ -936,7 +938,9 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"tool_messages = plan_and_schedule.invoke({\"messages\":[HumanMessage(content=example_question)]})['messages']"
|
||||
"tool_messages = plan_and_schedule.invoke(\n",
|
||||
" {\"messages\": [HumanMessage(content=example_question)]}\n",
|
||||
")[\"messages\"]"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1050,11 +1054,13 @@
|
||||
"def _parse_joiner_output(decision: JoinOutputs) -> List[BaseMessage]:\n",
|
||||
" response = [AIMessage(content=f\"Thought: {decision.thought}\")]\n",
|
||||
" if isinstance(decision.action, Replan):\n",
|
||||
" return {\"messages\": response + [\n",
|
||||
" SystemMessage(\n",
|
||||
" content=f\"Context from last attempt: {decision.action.feedback}\"\n",
|
||||
" )\n",
|
||||
" ]\n",
|
||||
" return {\n",
|
||||
" \"messages\": response\n",
|
||||
" + [\n",
|
||||
" SystemMessage(\n",
|
||||
" content=f\"Context from last attempt: {decision.action.feedback}\"\n",
|
||||
" )\n",
|
||||
" ]\n",
|
||||
" }\n",
|
||||
" else:\n",
|
||||
" return {\"messages\": response + [AIMessage(content=decision.action.response)]}\n",
|
||||
@@ -1102,7 +1108,7 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"joiner.invoke({\"messages\":input_messages})"
|
||||
"joiner.invoke({\"messages\": input_messages})"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1217,7 +1223,7 @@
|
||||
],
|
||||
"source": [
|
||||
"# Final answer\n",
|
||||
"print(step['join']['messages'][-1].content)"
|
||||
"print(step[\"join\"][\"messages\"][-1].content)"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1248,12 +1254,13 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"steps = chain.stream({\"messages\":\n",
|
||||
" [\n",
|
||||
" HumanMessage(\n",
|
||||
" content=\"What's the oldest parrot alive, and how much longer is that than the average?\"\n",
|
||||
" )\n",
|
||||
" ]\n",
|
||||
"steps = chain.stream(\n",
|
||||
" {\n",
|
||||
" \"messages\": [\n",
|
||||
" HumanMessage(\n",
|
||||
" content=\"What's the oldest parrot alive, and how much longer is that than the average?\"\n",
|
||||
" )\n",
|
||||
" ]\n",
|
||||
" },\n",
|
||||
" {\n",
|
||||
" \"recursion_limit\": 100,\n",
|
||||
@@ -1280,7 +1287,7 @@
|
||||
],
|
||||
"source": [
|
||||
"# Final answer\n",
|
||||
"print(step['join']['messages'][-1].content)"
|
||||
"print(step[\"join\"][\"messages\"][-1].content)"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1307,12 +1314,14 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"for step in chain.stream({\"messages\":\n",
|
||||
" [\n",
|
||||
" HumanMessage(\n",
|
||||
" content=\"What's ((3*(4+5)/0.5)+3245) + 8? What's 32/4.23? What's the sum of those two values?\"\n",
|
||||
" )\n",
|
||||
" ]}\n",
|
||||
"for step in chain.stream(\n",
|
||||
" {\n",
|
||||
" \"messages\": [\n",
|
||||
" HumanMessage(\n",
|
||||
" content=\"What's ((3*(4+5)/0.5)+3245) + 8? What's 32/4.23? What's the sum of those two values?\"\n",
|
||||
" )\n",
|
||||
" ]\n",
|
||||
" }\n",
|
||||
"):\n",
|
||||
" print(step)"
|
||||
]
|
||||
@@ -1335,7 +1344,7 @@
|
||||
],
|
||||
"source": [
|
||||
"# Final answer\n",
|
||||
"print(step['join']['messages'][-1].content)"
|
||||
"print(step[\"join\"][\"messages\"][-1].content)"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1364,12 +1373,14 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"for step in chain.stream({\"messages\":\n",
|
||||
" [\n",
|
||||
" HumanMessage(\n",
|
||||
" content=\"Find the current temperature in Tokyo, then, respond with a flashcard summarizing this information\"\n",
|
||||
" )\n",
|
||||
" ]}\n",
|
||||
"for step in chain.stream(\n",
|
||||
" {\n",
|
||||
" \"messages\": [\n",
|
||||
" HumanMessage(\n",
|
||||
" content=\"Find the current temperature in Tokyo, then, respond with a flashcard summarizing this information\"\n",
|
||||
" )\n",
|
||||
" ]\n",
|
||||
" }\n",
|
||||
"):\n",
|
||||
" print(step)"
|
||||
]
|
||||
|
||||
@@ -158,7 +158,7 @@
|
||||
" user_id = config[\"configurable\"].get(\"user_id\")\n",
|
||||
" if user_id is None:\n",
|
||||
" raise ValueError(\"User ID needs to be provided to save a memory.\")\n",
|
||||
" \n",
|
||||
"\n",
|
||||
" return user_id\n",
|
||||
"\n",
|
||||
"\n",
|
||||
@@ -166,7 +166,9 @@
|
||||
"def save_recall_memory(memory: str, config: RunnableConfig) -> str:\n",
|
||||
" \"\"\"Save memory to vectorstore for later semantic retrieval.\"\"\"\n",
|
||||
" user_id = get_user_id(config)\n",
|
||||
" document = Document(page_content=memory, id=str(uuid.uuid4()), metadata={\"user_id\": user_id})\n",
|
||||
" document = Document(\n",
|
||||
" page_content=memory, id=str(uuid.uuid4()), metadata={\"user_id\": user_id}\n",
|
||||
" )\n",
|
||||
" recall_vector_store.add_documents([document])\n",
|
||||
" return memory\n",
|
||||
"\n",
|
||||
@@ -175,10 +177,13 @@
|
||||
"def search_recall_memories(query: str, config: RunnableConfig) -> List[str]:\n",
|
||||
" \"\"\"Search for relevant memories.\"\"\"\n",
|
||||
" user_id = get_user_id(config)\n",
|
||||
"\n",
|
||||
" def _filter_function(doc: Document) -> bool:\n",
|
||||
" return doc.metadata.get(\"user_id\") == user_id\n",
|
||||
"\n",
|
||||
" documents = recall_vector_store.similarity_search(query, k=3, filter=_filter_function)\n",
|
||||
" documents = recall_vector_store.similarity_search(\n",
|
||||
" query, k=3, filter=_filter_function\n",
|
||||
" )\n",
|
||||
" return [document.page_content for document in documents]"
|
||||
]
|
||||
},
|
||||
@@ -281,7 +286,7 @@
|
||||
" \" information you want to retain in the next conversation. If you\"\n",
|
||||
" \" do call tools, all text preceding the tool call is an internal\"\n",
|
||||
" \" message. Respond AFTER calling the tool, once you have\"\n",
|
||||
" \" confirmation that the tool completed successfully.\\n\\n\"\n",
|
||||
" \" confirmation that the tool completed successfully.\\n\\n\",\n",
|
||||
" ),\n",
|
||||
" (\"placeholder\", \"{messages}\"),\n",
|
||||
" ]\n",
|
||||
@@ -300,6 +305,7 @@
|
||||
"\n",
|
||||
"tokenizer = tiktoken.encoding_for_model(\"gpt-4o\")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def agent(state: State) -> State:\n",
|
||||
" \"\"\"Process the current state and generate a response using the LLM.\n",
|
||||
"\n",
|
||||
@@ -354,7 +360,7 @@
|
||||
" msg = state[\"messages\"][-1]\n",
|
||||
" if msg.tool_calls:\n",
|
||||
" return \"tools\"\n",
|
||||
" \n",
|
||||
"\n",
|
||||
" return END"
|
||||
]
|
||||
},
|
||||
@@ -594,7 +600,10 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"for chunk in graph.stream({\"messages\": [(\"user\", \"yes -- pepperoni!\")]}, config={\"configurable\": {\"user_id\": \"1\", \"thread_id\": \"1\"}}):\n",
|
||||
"for chunk in graph.stream(\n",
|
||||
" {\"messages\": [(\"user\", \"yes -- pepperoni!\")]},\n",
|
||||
" config={\"configurable\": {\"user_id\": \"1\", \"thread_id\": \"1\"}},\n",
|
||||
"):\n",
|
||||
" pretty_print_stream_chunk(chunk)"
|
||||
]
|
||||
},
|
||||
@@ -638,7 +647,10 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"for chunk in graph.stream({\"messages\": [(\"user\", \"i also just moved to new york\")]}, config={\"configurable\": {\"user_id\": \"1\", \"thread_id\": \"1\"}}):\n",
|
||||
"for chunk in graph.stream(\n",
|
||||
" {\"messages\": [(\"user\", \"i also just moved to new york\")]},\n",
|
||||
" config={\"configurable\": {\"user_id\": \"1\", \"thread_id\": \"1\"}},\n",
|
||||
"):\n",
|
||||
" pretty_print_stream_chunk(chunk)"
|
||||
]
|
||||
},
|
||||
@@ -682,7 +694,9 @@
|
||||
"source": [
|
||||
"config = {\"configurable\": {\"user_id\": \"1\", \"thread_id\": \"2\"}}\n",
|
||||
"\n",
|
||||
"for chunk in graph.stream({\"messages\": [(\"user\", \"where should i go for dinner?\")]}, config=config):\n",
|
||||
"for chunk in graph.stream(\n",
|
||||
" {\"messages\": [(\"user\", \"where should i go for dinner?\")]}, config=config\n",
|
||||
"):\n",
|
||||
" pretty_print_stream_chunk(chunk)"
|
||||
]
|
||||
},
|
||||
@@ -740,7 +754,10 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"for chunk in graph.stream({\"messages\": [(\"user\", \"what's the address for joe's in greenwich village?\")]}, config=config):\n",
|
||||
"for chunk in graph.stream(\n",
|
||||
" {\"messages\": [(\"user\", \"what's the address for joe's in greenwich village?\")]},\n",
|
||||
" config=config,\n",
|
||||
"):\n",
|
||||
" pretty_print_stream_chunk(chunk)"
|
||||
]
|
||||
},
|
||||
@@ -924,7 +941,9 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"for chunk in graph.stream({\"messages\": [(\"user\", \"My friend John likes Pizza.\")]}, config=config):\n",
|
||||
"for chunk in graph.stream(\n",
|
||||
" {\"messages\": [(\"user\", \"My friend John likes Pizza.\")]}, config=config\n",
|
||||
"):\n",
|
||||
" pretty_print_stream_chunk(chunk)"
|
||||
]
|
||||
},
|
||||
@@ -962,7 +981,9 @@
|
||||
"source": [
|
||||
"config = {\"configurable\": {\"user_id\": \"3\", \"thread_id\": \"2\"}}\n",
|
||||
"\n",
|
||||
"for chunk in graph.stream({\"messages\": [(\"user\", \"What food should I bring to John's party?\")]}, config=config):\n",
|
||||
"for chunk in graph.stream(\n",
|
||||
" {\"messages\": [(\"user\", \"What food should I bring to John's party?\")]}, config=config\n",
|
||||
"):\n",
|
||||
" pretty_print_stream_chunk(chunk)"
|
||||
]
|
||||
},
|
||||
@@ -1007,7 +1028,9 @@
|
||||
"\n",
|
||||
"\n",
|
||||
"# Fetch records\n",
|
||||
"records = recall_vector_store.similarity_search(\"Alice\", k=2, filter=lambda doc: doc.metadata[\"user_id\"] == \"3\")\n",
|
||||
"records = recall_vector_store.similarity_search(\n",
|
||||
" \"Alice\", k=2, filter=lambda doc: doc.metadata[\"user_id\"] == \"3\"\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Plot graph\n",
|
||||
|
||||
@@ -124,9 +124,12 @@
|
||||
"source": [
|
||||
"from langchain_core.messages import HumanMessage\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def agent_node(state, agent, name):\n",
|
||||
" result = agent.invoke(state)\n",
|
||||
" return {\"messages\": [HumanMessage(content=result[\"messages\"][-1].content, name=name)]}"
|
||||
" return {\n",
|
||||
" \"messages\": [HumanMessage(content=result[\"messages\"][-1].content, name=name)]\n",
|
||||
" }"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -163,9 +166,11 @@
|
||||
"# and decides when the work is completed\n",
|
||||
"options = [\"FINISH\"] + members\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class routeResponse(BaseModel):\n",
|
||||
" next: Literal[*options]\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"prompt = ChatPromptTemplate.from_messages(\n",
|
||||
" [\n",
|
||||
" (\"system\", system_prompt),\n",
|
||||
@@ -181,11 +186,9 @@
|
||||
"\n",
|
||||
"llm = ChatOpenAI(model=\"gpt-4o\")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def supervisor_agent(state):\n",
|
||||
" supervisor_chain = (\n",
|
||||
" prompt\n",
|
||||
" | llm.with_structured_output(routeResponse)\n",
|
||||
" )\n",
|
||||
" supervisor_chain = prompt | llm.with_structured_output(routeResponse)\n",
|
||||
" return supervisor_chain.invoke(state)"
|
||||
]
|
||||
},
|
||||
@@ -216,6 +219,7 @@
|
||||
"from langgraph.graph import END, StateGraph, START\n",
|
||||
"from langgraph.prebuilt import create_react_agent\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# The agent state is the input to each node in the graph\n",
|
||||
"class AgentState(TypedDict):\n",
|
||||
" # The annotation tells the graph that new messages will always\n",
|
||||
|
||||
@@ -305,7 +305,9 @@
|
||||
"\n",
|
||||
"def agent_node(state, agent, name):\n",
|
||||
" result = agent.invoke(state)\n",
|
||||
" return {\"messages\": [HumanMessage(content=result[\"messages\"][-1].content, name=name)]}\n",
|
||||
" return {\n",
|
||||
" \"messages\": [HumanMessage(content=result[\"messages\"][-1].content, name=name)]\n",
|
||||
" }\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def create_team_supervisor(llm: ChatOpenAI, system_prompt, members) -> str:\n",
|
||||
@@ -340,7 +342,8 @@
|
||||
" ]\n",
|
||||
" ).partial(options=str(options), team_members=\", \".join(members))\n",
|
||||
" return (\n",
|
||||
" prompt | trimmer\n",
|
||||
" prompt\n",
|
||||
" | trimmer\n",
|
||||
" | llm.bind_functions(functions=[function_def], function_call=\"route\")\n",
|
||||
" | JsonOutputFunctionsParser()\n",
|
||||
" )"
|
||||
@@ -379,6 +382,7 @@
|
||||
"from langchain_openai.chat_models import ChatOpenAI\n",
|
||||
"from langgraph.prebuilt import create_react_agent\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# ResearchTeam graph state\n",
|
||||
"class ResearchTeamState(TypedDict):\n",
|
||||
" # A message is added after each team member finishes\n",
|
||||
@@ -595,14 +599,16 @@
|
||||
"\n",
|
||||
"llm = ChatOpenAI(model=\"gpt-4o\")\n",
|
||||
"\n",
|
||||
"doc_writer_agent = create_react_agent(llm, tools=[write_document, edit_document, read_document])\n",
|
||||
"doc_writer_agent = create_react_agent(\n",
|
||||
" llm, tools=[write_document, edit_document, read_document]\n",
|
||||
")\n",
|
||||
"# Injects current directory working state before each call\n",
|
||||
"context_aware_doc_writer_agent = prelude | doc_writer_agent\n",
|
||||
"doc_writing_node = functools.partial(\n",
|
||||
" agent_node, agent=context_aware_doc_writer_agent, name=\"DocWriter\"\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"note_taking_agent = create_react_agent(llm,tools=[create_outline, read_document])\n",
|
||||
"note_taking_agent = create_react_agent(llm, tools=[create_outline, read_document])\n",
|
||||
"context_aware_note_taking_agent = prelude | note_taking_agent\n",
|
||||
"note_taking_node = functools.partial(\n",
|
||||
" agent_node, agent=context_aware_note_taking_agent, name=\"NoteTaker\"\n",
|
||||
|
||||
@@ -393,6 +393,7 @@
|
||||
"from typing import Literal\n",
|
||||
"from langgraph.graph import END\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"async def execute_step(state: PlanExecute):\n",
|
||||
" plan = state[\"plan\"]\n",
|
||||
" plan_str = \"\\n\".join(f\"{i+1}. {step}\" for i, step in enumerate(plan))\n",
|
||||
@@ -459,7 +460,7 @@
|
||||
" \"replan\",\n",
|
||||
" # Next, we pass in the function that will determine which node is called next.\n",
|
||||
" should_end,\n",
|
||||
" [\"agent\",END]\n",
|
||||
" [\"agent\", END],\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"# Finally, we compile it!\n",
|
||||
|
||||
@@ -185,7 +185,6 @@
|
||||
"from pydantic import BaseModel, Field\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Data model\n",
|
||||
"class RouteQuery(BaseModel):\n",
|
||||
" \"\"\"Route a user query to the most relevant datasource.\"\"\"\n",
|
||||
|
||||
@@ -59,9 +59,10 @@
|
||||
"source": [
|
||||
"### LLM\n",
|
||||
"from langchain_ollama import ChatOllama\n",
|
||||
"local_llm = 'llama3.2:3b-instruct-fp16'\n",
|
||||
"\n",
|
||||
"local_llm = \"llama3.2:3b-instruct-fp16\"\n",
|
||||
"llm = ChatOllama(model=local_llm, temperature=0)\n",
|
||||
"llm_json_mode = ChatOllama(model=local_llm, temperature=0, format='json')"
|
||||
"llm_json_mode = ChatOllama(model=local_llm, temperature=0, format=\"json\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -76,19 +77,22 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"execution_count": null,
|
||||
"id": "8a8792f5",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os, getpass\n",
|
||||
"import os\n",
|
||||
"import getpass\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(\"TAVILY_API_KEY\")\n",
|
||||
"os.environ['TOKENIZERS_PARALLELISM'] = 'true'"
|
||||
"os.environ[\"TOKENIZERS_PARALLELISM\"] = \"true\""
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -194,7 +198,7 @@
|
||||
"import json\n",
|
||||
"from langchain_core.messages import HumanMessage, SystemMessage\n",
|
||||
"\n",
|
||||
"# Prompt \n",
|
||||
"# Prompt\n",
|
||||
"router_instructions = \"\"\"You are an expert at routing a user question to a vectorstore or web search.\n",
|
||||
"\n",
|
||||
"The vectorstore contains documents related to agents, prompt engineering, and adversarial attacks.\n",
|
||||
@@ -204,10 +208,27 @@
|
||||
"Return JSON with single key, datasource, that is 'websearch' or 'vectorstore' depending on the question.\"\"\"\n",
|
||||
"\n",
|
||||
"# Test router\n",
|
||||
"test_web_search = llm_json_mode.invoke([SystemMessage(content=router_instructions)] + [HumanMessage(content=\"Who is favored to win the NFC Championship game in the 2024 season?\")])\n",
|
||||
"test_web_search_2 = llm_json_mode.invoke([SystemMessage(content=router_instructions)] + [HumanMessage(content=\"What are the models released today for llama3.2?\")])\n",
|
||||
"test_vector_store = llm_json_mode.invoke([SystemMessage(content=router_instructions)] + [HumanMessage(content=\"What are the types of agent memory?\")])\n",
|
||||
"print(json.loads(test_web_search.content), json.loads(test_web_search_2.content), json.loads(test_vector_store.content))"
|
||||
"test_web_search = llm_json_mode.invoke(\n",
|
||||
" [SystemMessage(content=router_instructions)]\n",
|
||||
" + [\n",
|
||||
" HumanMessage(\n",
|
||||
" content=\"Who is favored to win the NFC Championship game in the 2024 season?\"\n",
|
||||
" )\n",
|
||||
" ]\n",
|
||||
")\n",
|
||||
"test_web_search_2 = llm_json_mode.invoke(\n",
|
||||
" [SystemMessage(content=router_instructions)]\n",
|
||||
" + [HumanMessage(content=\"What are the models released today for llama3.2?\")]\n",
|
||||
")\n",
|
||||
"test_vector_store = llm_json_mode.invoke(\n",
|
||||
" [SystemMessage(content=router_instructions)]\n",
|
||||
" + [HumanMessage(content=\"What are the types of agent memory?\")]\n",
|
||||
")\n",
|
||||
"print(\n",
|
||||
" json.loads(test_web_search.content),\n",
|
||||
" json.loads(test_web_search_2.content),\n",
|
||||
" json.loads(test_vector_store.content),\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -228,9 +249,9 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"### Retrieval Grader \n",
|
||||
"### Retrieval Grader\n",
|
||||
"\n",
|
||||
"# Doc grader instructions \n",
|
||||
"# Doc grader instructions\n",
|
||||
"doc_grader_instructions = \"\"\"You are a grader assessing relevance of a retrieved document to a user question.\n",
|
||||
"\n",
|
||||
"If the document contains keyword(s) or semantic meaning related to the question, grade it as relevant.\"\"\"\n",
|
||||
@@ -246,8 +267,13 @@
|
||||
"question = \"What is Chain of thought prompting?\"\n",
|
||||
"docs = retriever.invoke(question)\n",
|
||||
"doc_txt = docs[1].page_content\n",
|
||||
"doc_grader_prompt_formatted = doc_grader_prompt.format(document=doc_txt, question=question)\n",
|
||||
"result = llm_json_mode.invoke([SystemMessage(content=doc_grader_instructions)] + [HumanMessage(content=doc_grader_prompt_formatted)])\n",
|
||||
"doc_grader_prompt_formatted = doc_grader_prompt.format(\n",
|
||||
" document=doc_txt, question=question\n",
|
||||
")\n",
|
||||
"result = llm_json_mode.invoke(\n",
|
||||
" [SystemMessage(content=doc_grader_instructions)]\n",
|
||||
" + [HumanMessage(content=doc_grader_prompt_formatted)]\n",
|
||||
")\n",
|
||||
"json.loads(result.content)"
|
||||
]
|
||||
},
|
||||
@@ -287,10 +313,12 @@
|
||||
"\n",
|
||||
"Answer:\"\"\"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Post-processing\n",
|
||||
"def format_docs(docs):\n",
|
||||
" return \"\\n\\n\".join(doc.page_content for doc in docs)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Test\n",
|
||||
"docs = retriever.invoke(question)\n",
|
||||
"docs_txt = format_docs(docs)\n",
|
||||
@@ -318,9 +346,9 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"### Hallucination Grader \n",
|
||||
"### Hallucination Grader\n",
|
||||
"\n",
|
||||
"# Hallucination grader instructions \n",
|
||||
"# Hallucination grader instructions\n",
|
||||
"hallucination_grader_instructions = \"\"\"\n",
|
||||
"\n",
|
||||
"You are a teacher grading a quiz. \n",
|
||||
@@ -348,9 +376,14 @@
|
||||
"\n",
|
||||
"Return JSON with two two keys, binary_score is 'yes' or 'no' score to indicate whether the STUDENT ANSWER is grounded in the FACTS. And a key, explanation, that contains an explanation of the score.\"\"\"\n",
|
||||
"\n",
|
||||
"# Test using documents and generation from above \n",
|
||||
"hallucination_grader_prompt_formatted = hallucination_grader_prompt.format(documents=docs_txt, generation=generation.content)\n",
|
||||
"result = llm_json_mode.invoke([SystemMessage(content=hallucination_grader_instructions)] + [HumanMessage(content=hallucination_grader_prompt_formatted)])\n",
|
||||
"# Test using documents and generation from above\n",
|
||||
"hallucination_grader_prompt_formatted = hallucination_grader_prompt.format(\n",
|
||||
" documents=docs_txt, generation=generation.content\n",
|
||||
")\n",
|
||||
"result = llm_json_mode.invoke(\n",
|
||||
" [SystemMessage(content=hallucination_grader_instructions)]\n",
|
||||
" + [HumanMessage(content=hallucination_grader_prompt_formatted)]\n",
|
||||
")\n",
|
||||
"json.loads(result.content)"
|
||||
]
|
||||
},
|
||||
@@ -373,9 +406,9 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"### Answer Grader \n",
|
||||
"### Answer Grader\n",
|
||||
"\n",
|
||||
"# Answer grader instructions \n",
|
||||
"# Answer grader instructions\n",
|
||||
"answer_grader_instructions = \"\"\"You are a teacher grading a quiz. \n",
|
||||
"\n",
|
||||
"You will be given a QUESTION and a STUDENT ANSWER. \n",
|
||||
@@ -401,13 +434,18 @@
|
||||
"\n",
|
||||
"Return JSON with two two keys, binary_score is 'yes' or 'no' score to indicate whether the STUDENT ANSWER meets the criteria. And a key, explanation, that contains an explanation of the score.\"\"\"\n",
|
||||
"\n",
|
||||
"# Test \n",
|
||||
"# Test\n",
|
||||
"question = \"What are the vision models released today as part of Llama 3.2?\"\n",
|
||||
"answer = \"The Llama 3.2 models released today include two vision models: Llama 3.2 11B Vision Instruct and Llama 3.2 90B Vision Instruct, which are available on Azure AI Model Catalog via managed compute. These models are part of Meta's first foray into multimodal AI and rival closed models like Anthropic's Claude 3 Haiku and OpenAI's GPT-4o mini in visual reasoning. They replace the older text-only Llama 3.1 models.\"\n",
|
||||
"\n",
|
||||
"# Test using question and generation from above \n",
|
||||
"answer_grader_prompt_formatted = answer_grader_prompt.format(question=question, generation=answer)\n",
|
||||
"result = llm_json_mode.invoke([SystemMessage(content=answer_grader_instructions)] + [HumanMessage(content=answer_grader_prompt_formatted)])\n",
|
||||
"# Test using question and generation from above\n",
|
||||
"answer_grader_prompt_formatted = answer_grader_prompt.format(\n",
|
||||
" question=question, generation=answer\n",
|
||||
")\n",
|
||||
"result = llm_json_mode.invoke(\n",
|
||||
" [SystemMessage(content=answer_grader_instructions)]\n",
|
||||
" + [HumanMessage(content=answer_grader_prompt_formatted)]\n",
|
||||
")\n",
|
||||
"json.loads(result.content)"
|
||||
]
|
||||
},
|
||||
@@ -428,6 +466,7 @@
|
||||
"source": [
|
||||
"### Search\n",
|
||||
"from langchain_community.tools.tavily_search import TavilySearchResults\n",
|
||||
"\n",
|
||||
"web_search_tool = TavilySearchResults(k=3)"
|
||||
]
|
||||
},
|
||||
@@ -461,17 +500,19 @@
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"from typing import List, Annotated\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class GraphState(TypedDict):\n",
|
||||
" \"\"\"\n",
|
||||
" Graph state is a dictionary that contains information we want to propagate to, and modify in, each graph node.\n",
|
||||
" \"\"\"\n",
|
||||
" question : str # User question\n",
|
||||
" generation : str # LLM generation\n",
|
||||
" web_search : str # Binary decision to run web search\n",
|
||||
" max_retries : int # Max number of retries for answer generation \n",
|
||||
" answers : int # Number of answers generated\n",
|
||||
" loop_step: Annotated[int, operator.add] \n",
|
||||
" documents : List[str] # List of retrieved documents"
|
||||
"\n",
|
||||
" question: str # User question\n",
|
||||
" generation: str # LLM generation\n",
|
||||
" web_search: str # Binary decision to run web search\n",
|
||||
" max_retries: int # Max number of retries for answer generation\n",
|
||||
" answers: int # Number of answers generated\n",
|
||||
" loop_step: Annotated[int, operator.add]\n",
|
||||
" documents: List[str] # List of retrieved documents"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -504,6 +545,7 @@
|
||||
"from langchain.schema import Document\n",
|
||||
"from langgraph.graph import END\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"### Nodes\n",
|
||||
"def retrieve(state):\n",
|
||||
" \"\"\"\n",
|
||||
@@ -522,6 +564,7 @@
|
||||
" documents = retriever.invoke(question)\n",
|
||||
" return {\"documents\": documents}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def generate(state):\n",
|
||||
" \"\"\"\n",
|
||||
" Generate answer using RAG on retrieved documents\n",
|
||||
@@ -536,12 +579,13 @@
|
||||
" question = state[\"question\"]\n",
|
||||
" documents = state[\"documents\"]\n",
|
||||
" loop_step = state.get(\"loop_step\", 0)\n",
|
||||
" \n",
|
||||
"\n",
|
||||
" # RAG generation\n",
|
||||
" docs_txt = format_docs(documents)\n",
|
||||
" rag_prompt_formatted = rag_prompt.format(context=docs_txt, question=question)\n",
|
||||
" generation = llm.invoke([HumanMessage(content=rag_prompt_formatted)])\n",
|
||||
" return {\"generation\": generation, \"loop_step\": loop_step+1}\n",
|
||||
" return {\"generation\": generation, \"loop_step\": loop_step + 1}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def grade_documents(state):\n",
|
||||
" \"\"\"\n",
|
||||
@@ -558,14 +602,19 @@
|
||||
" print(\"---CHECK DOCUMENT RELEVANCE TO QUESTION---\")\n",
|
||||
" question = state[\"question\"]\n",
|
||||
" documents = state[\"documents\"]\n",
|
||||
" \n",
|
||||
"\n",
|
||||
" # Score each doc\n",
|
||||
" filtered_docs = []\n",
|
||||
" web_search = \"No\" \n",
|
||||
" web_search = \"No\"\n",
|
||||
" for d in documents:\n",
|
||||
" doc_grader_prompt_formatted = doc_grader_prompt.format(document=d.page_content, question=question)\n",
|
||||
" result = llm_json_mode.invoke([SystemMessage(content=doc_grader_instructions)] + [HumanMessage(content=doc_grader_prompt_formatted)])\n",
|
||||
" grade = json.loads(result.content)['binary_score']\n",
|
||||
" doc_grader_prompt_formatted = doc_grader_prompt.format(\n",
|
||||
" document=d.page_content, question=question\n",
|
||||
" )\n",
|
||||
" result = llm_json_mode.invoke(\n",
|
||||
" [SystemMessage(content=doc_grader_instructions)]\n",
|
||||
" + [HumanMessage(content=doc_grader_prompt_formatted)]\n",
|
||||
" )\n",
|
||||
" grade = json.loads(result.content)[\"binary_score\"]\n",
|
||||
" # Document relevant\n",
|
||||
" if grade.lower() == \"yes\":\n",
|
||||
" print(\"---GRADE: DOCUMENT RELEVANT---\")\n",
|
||||
@@ -578,7 +627,8 @@
|
||||
" web_search = \"Yes\"\n",
|
||||
" continue\n",
|
||||
" return {\"documents\": filtered_docs, \"web_search\": web_search}\n",
|
||||
" \n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def web_search(state):\n",
|
||||
" \"\"\"\n",
|
||||
" Web search based based on the question\n",
|
||||
@@ -601,11 +651,13 @@
|
||||
" documents.append(web_results)\n",
|
||||
" return {\"documents\": documents}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"### Edges\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def route_question(state):\n",
|
||||
" \"\"\"\n",
|
||||
" Route question to web search or RAG \n",
|
||||
" Route question to web search or RAG\n",
|
||||
"\n",
|
||||
" Args:\n",
|
||||
" state (dict): The current graph state\n",
|
||||
@@ -615,15 +667,19 @@
|
||||
" \"\"\"\n",
|
||||
"\n",
|
||||
" print(\"---ROUTE QUESTION---\")\n",
|
||||
" route_question = llm_json_mode.invoke([SystemMessage(content=router_instructions)] + [HumanMessage(content=state[\"question\"])])\n",
|
||||
" source = json.loads(route_question.content)['datasource']\n",
|
||||
" if source == 'websearch':\n",
|
||||
" route_question = llm_json_mode.invoke(\n",
|
||||
" [SystemMessage(content=router_instructions)]\n",
|
||||
" + [HumanMessage(content=state[\"question\"])]\n",
|
||||
" )\n",
|
||||
" source = json.loads(route_question.content)[\"datasource\"]\n",
|
||||
" if source == \"websearch\":\n",
|
||||
" print(\"---ROUTE QUESTION TO WEB SEARCH---\")\n",
|
||||
" return \"websearch\"\n",
|
||||
" elif source == 'vectorstore':\n",
|
||||
" elif source == \"vectorstore\":\n",
|
||||
" print(\"---ROUTE QUESTION TO RAG---\")\n",
|
||||
" return \"vectorstore\"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def decide_to_generate(state):\n",
|
||||
" \"\"\"\n",
|
||||
" Determines whether to generate an answer, or add web search\n",
|
||||
@@ -643,13 +699,16 @@
|
||||
" if web_search == \"Yes\":\n",
|
||||
" # All documents have been filtered check_relevance\n",
|
||||
" # We will re-generate a new query\n",
|
||||
" print(\"---DECISION: NOT ALL DOCUMENTS ARE RELEVANT TO QUESTION, INCLUDE WEB SEARCH---\")\n",
|
||||
" print(\n",
|
||||
" \"---DECISION: NOT ALL DOCUMENTS ARE RELEVANT TO QUESTION, INCLUDE WEB SEARCH---\"\n",
|
||||
" )\n",
|
||||
" return \"websearch\"\n",
|
||||
" else:\n",
|
||||
" # We have relevant documents, so generate answer\n",
|
||||
" print(\"---DECISION: GENERATE---\")\n",
|
||||
" return \"generate\"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def grade_generation_v_documents_and_question(state):\n",
|
||||
" \"\"\"\n",
|
||||
" Determines whether the generation is grounded in the document and answers question\n",
|
||||
@@ -665,21 +724,31 @@
|
||||
" question = state[\"question\"]\n",
|
||||
" documents = state[\"documents\"]\n",
|
||||
" generation = state[\"generation\"]\n",
|
||||
" max_retries = state.get(\"max_retries\", 3) # Default to 3 if not provided\n",
|
||||
" max_retries = state.get(\"max_retries\", 3) # Default to 3 if not provided\n",
|
||||
"\n",
|
||||
" hallucination_grader_prompt_formatted = hallucination_grader_prompt.format(documents=format_docs(documents), generation=generation.content)\n",
|
||||
" result = llm_json_mode.invoke([SystemMessage(content=hallucination_grader_instructions)] + [HumanMessage(content=hallucination_grader_prompt_formatted)])\n",
|
||||
" grade = json.loads(result.content)['binary_score']\n",
|
||||
" hallucination_grader_prompt_formatted = hallucination_grader_prompt.format(\n",
|
||||
" documents=format_docs(documents), generation=generation.content\n",
|
||||
" )\n",
|
||||
" result = llm_json_mode.invoke(\n",
|
||||
" [SystemMessage(content=hallucination_grader_instructions)]\n",
|
||||
" + [HumanMessage(content=hallucination_grader_prompt_formatted)]\n",
|
||||
" )\n",
|
||||
" grade = json.loads(result.content)[\"binary_score\"]\n",
|
||||
"\n",
|
||||
" # Check hallucination\n",
|
||||
" if grade == \"yes\":\n",
|
||||
" print(\"---DECISION: GENERATION IS GROUNDED IN DOCUMENTS---\")\n",
|
||||
" # Check question-answering\n",
|
||||
" print(\"---GRADE GENERATION vs QUESTION---\")\n",
|
||||
" # Test using question and generation from above \n",
|
||||
" answer_grader_prompt_formatted = answer_grader_prompt.format(question=question, generation=generation.content)\n",
|
||||
" result = llm_json_mode.invoke([SystemMessage(content=answer_grader_instructions)] + [HumanMessage(content=answer_grader_prompt_formatted)])\n",
|
||||
" grade = json.loads(result.content)['binary_score']\n",
|
||||
" # Test using question and generation from above\n",
|
||||
" answer_grader_prompt_formatted = answer_grader_prompt.format(\n",
|
||||
" question=question, generation=generation.content\n",
|
||||
" )\n",
|
||||
" result = llm_json_mode.invoke(\n",
|
||||
" [SystemMessage(content=answer_grader_instructions)]\n",
|
||||
" + [HumanMessage(content=answer_grader_prompt_formatted)]\n",
|
||||
" )\n",
|
||||
" grade = json.loads(result.content)[\"binary_score\"]\n",
|
||||
" if grade == \"yes\":\n",
|
||||
" print(\"---DECISION: GENERATION ADDRESSES QUESTION---\")\n",
|
||||
" return \"useful\"\n",
|
||||
@@ -688,7 +757,7 @@
|
||||
" return \"not useful\"\n",
|
||||
" else:\n",
|
||||
" print(\"---DECISION: MAX RETRIES REACHED---\")\n",
|
||||
" return \"max retries\" \n",
|
||||
" return \"max retries\"\n",
|
||||
" elif state[\"loop_step\"] <= max_retries:\n",
|
||||
" print(\"---DECISION: GENERATION IS NOT GROUNDED IN DOCUMENTS, RE-TRY---\")\n",
|
||||
" return \"not supported\"\n",
|
||||
@@ -729,10 +798,10 @@
|
||||
"workflow = StateGraph(GraphState)\n",
|
||||
"\n",
|
||||
"# Define the nodes\n",
|
||||
"workflow.add_node(\"websearch\", web_search) # web search\n",
|
||||
"workflow.add_node(\"retrieve\", retrieve) # retrieve\n",
|
||||
"workflow.add_node(\"grade_documents\", grade_documents) # grade documents\n",
|
||||
"workflow.add_node(\"generate\", generate) # generate\n",
|
||||
"workflow.add_node(\"websearch\", web_search) # web search\n",
|
||||
"workflow.add_node(\"retrieve\", retrieve) # retrieve\n",
|
||||
"workflow.add_node(\"grade_documents\", grade_documents) # grade documents\n",
|
||||
"workflow.add_node(\"generate\", generate) # generate\n",
|
||||
"\n",
|
||||
"# Build graph\n",
|
||||
"workflow.set_conditional_entry_point(\n",
|
||||
@@ -798,7 +867,10 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Test on current events\n",
|
||||
"inputs = {\"question\": \"What are the models released today for llama3.2?\", \"max_retries\": 3}\n",
|
||||
"inputs = {\n",
|
||||
" \"question\": \"What are the models released today for llama3.2?\",\n",
|
||||
" \"max_retries\": 3,\n",
|
||||
"}\n",
|
||||
"for event in graph.stream(inputs, stream_mode=\"values\"):\n",
|
||||
" print(event)"
|
||||
]
|
||||
@@ -836,7 +908,7 @@
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.6"
|
||||
"version": "3.11.4"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
|
||||
@@ -699,7 +699,9 @@
|
||||
" \"\"\"\n",
|
||||
" Find all tool calls in the messages returned\n",
|
||||
" \"\"\"\n",
|
||||
" tool_calls = [tc['name'] for m in messages['messages'] for tc in getattr(m, 'tool_calls', [])]\n",
|
||||
" tool_calls = [\n",
|
||||
" tc[\"name\"] for m in messages[\"messages\"] for tc in getattr(m, \"tool_calls\", [])\n",
|
||||
" ]\n",
|
||||
" return tool_calls\n",
|
||||
"\n",
|
||||
"\n",
|
||||
|
||||
@@ -58,6 +58,7 @@
|
||||
" return\n",
|
||||
" os.environ[var] = getpass.getpass(var)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"_set_if_undefined(\"TAVILY_API_KEY\")\n",
|
||||
"_set_if_undefined(\"FIREWORKS_API_KEY\")"
|
||||
]
|
||||
@@ -108,8 +109,7 @@
|
||||
" ]\n",
|
||||
")\n",
|
||||
"llm = ChatFireworks(\n",
|
||||
" model=\"accounts/fireworks/models/mixtral-8x7b-instruct\",\n",
|
||||
" max_tokens=32768\n",
|
||||
" model=\"accounts/fireworks/models/mixtral-8x7b-instruct\", max_tokens=32768\n",
|
||||
")\n",
|
||||
"generate = prompt | llm"
|
||||
]
|
||||
@@ -331,15 +331,15 @@
|
||||
"\n",
|
||||
"\n",
|
||||
"async def generation_node(state: State) -> State:\n",
|
||||
" return {\"messages\": [await generate.ainvoke(state['messages'])]}\n",
|
||||
" return {\"messages\": [await generate.ainvoke(state[\"messages\"])]}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"async def reflection_node(state: State) -> State:\n",
|
||||
" # Other messages we need to adjust\n",
|
||||
" cls_map = {\"ai\": HumanMessage, \"human\": AIMessage}\n",
|
||||
" # First message is the original user request. We hold it the same for all nodes\n",
|
||||
" translated = [state['messages'][0]] + [\n",
|
||||
" cls_map[msg.type](content=msg.content) for msg in state['messages'][1:]\n",
|
||||
" translated = [state[\"messages\"][0]] + [\n",
|
||||
" cls_map[msg.type](content=msg.content) for msg in state[\"messages\"][1:]\n",
|
||||
" ]\n",
|
||||
" res = await reflect.ainvoke(translated)\n",
|
||||
" # We treat the output of this as human feedback for the generator\n",
|
||||
@@ -359,7 +359,6 @@
|
||||
" return \"reflect\"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"builder.add_conditional_edges(\"generate\", should_continue)\n",
|
||||
"builder.add_edge(\"reflect\", \"generate\")\n",
|
||||
"memory = MemorySaver()\n",
|
||||
@@ -406,13 +405,16 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"async for event in graph.astream({\n",
|
||||
" \"messages\": [\n",
|
||||
" HumanMessage(\n",
|
||||
" content=\"Generate an essay on the topicality of The Little Prince and its message in modern life\"\n",
|
||||
" )\n",
|
||||
" ],\n",
|
||||
"}, config):\n",
|
||||
"async for event in graph.astream(\n",
|
||||
" {\n",
|
||||
" \"messages\": [\n",
|
||||
" HumanMessage(\n",
|
||||
" content=\"Generate an essay on the topicality of The Little Prince and its message in modern life\"\n",
|
||||
" )\n",
|
||||
" ],\n",
|
||||
" },\n",
|
||||
" config,\n",
|
||||
"):\n",
|
||||
" print(event)\n",
|
||||
" print(\"---\")"
|
||||
]
|
||||
|
||||
@@ -66,6 +66,7 @@
|
||||
" return\n",
|
||||
" os.environ[var] = getpass.getpass(var)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"_set_if_undefined(\"ANTHROPIC_API_KEY\")\n",
|
||||
"_set_if_undefined(\"TAVILY_API_KEY\")"
|
||||
]
|
||||
@@ -192,7 +193,7 @@
|
||||
" response = []\n",
|
||||
" for attempt in range(3):\n",
|
||||
" response = self.runnable.invoke(\n",
|
||||
" {\"messages\": state['messages']}, {\"tags\": [f\"attempt:{attempt}\"]}\n",
|
||||
" {\"messages\": state[\"messages\"]}, {\"tags\": [f\"attempt:{attempt}\"]}\n",
|
||||
" )\n",
|
||||
" try:\n",
|
||||
" self.validator.invoke(response)\n",
|
||||
@@ -259,7 +260,9 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"example_question = \"Why is reflection useful in AI?\"\n",
|
||||
"initial = first_responder.respond({\"messages\":[HumanMessage(content=example_question)]})"
|
||||
"initial = first_responder.respond(\n",
|
||||
" {\"messages\": [HumanMessage(content=example_question)]}\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -332,20 +335,26 @@
|
||||
"import json\n",
|
||||
"\n",
|
||||
"revised = revisor.respond(\n",
|
||||
" {\"messages\": [\n",
|
||||
" HumanMessage(content=example_question),\n",
|
||||
" initial['messages'],\n",
|
||||
" ToolMessage(\n",
|
||||
" tool_call_id=initial['messages'].tool_calls[0][\"id\"],\n",
|
||||
" content=json.dumps(\n",
|
||||
" tavily_tool.invoke(\n",
|
||||
" {\"query\": initial['messages'].tool_calls[0][\"args\"][\"search_queries\"][0]}\n",
|
||||
" )\n",
|
||||
" {\n",
|
||||
" \"messages\": [\n",
|
||||
" HumanMessage(content=example_question),\n",
|
||||
" initial[\"messages\"],\n",
|
||||
" ToolMessage(\n",
|
||||
" tool_call_id=initial[\"messages\"].tool_calls[0][\"id\"],\n",
|
||||
" content=json.dumps(\n",
|
||||
" tavily_tool.invoke(\n",
|
||||
" {\n",
|
||||
" \"query\": initial[\"messages\"].tool_calls[0][\"args\"][\n",
|
||||
" \"search_queries\"\n",
|
||||
" ][0]\n",
|
||||
" }\n",
|
||||
" )\n",
|
||||
" ),\n",
|
||||
" ),\n",
|
||||
" ),\n",
|
||||
" ]}\n",
|
||||
" ]\n",
|
||||
" }\n",
|
||||
")\n",
|
||||
"revised['messages']"
|
||||
"revised[\"messages\"]"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -439,7 +448,7 @@
|
||||
"\n",
|
||||
"def event_loop(state: list):\n",
|
||||
" # in our case, we'll just stop after N plans\n",
|
||||
" num_iterations = _get_num_iterations(state['messages'])\n",
|
||||
" num_iterations = _get_num_iterations(state[\"messages\"])\n",
|
||||
" if num_iterations > MAX_ITERATIONS:\n",
|
||||
" return END\n",
|
||||
" return \"execute_tools\"\n",
|
||||
@@ -598,7 +607,7 @@
|
||||
")\n",
|
||||
"for i, step in enumerate(events):\n",
|
||||
" print(f\"Step {i}\")\n",
|
||||
" step['messages'][-1].pretty_print()"
|
||||
" step[\"messages\"][-1].pretty_print()"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -317,7 +317,7 @@
|
||||
" \"\"\"Worker node that executes the tools of a given plan.\"\"\"\n",
|
||||
" _step = _get_current_task(state)\n",
|
||||
" _, step_name, tool, tool_input = state[\"steps\"][_step - 1]\n",
|
||||
" _results = (state[\"results\"] or {}) if \"results\" in state else {}\n",
|
||||
" _results = (state[\"results\"] or {}) if \"results\" in state else {}\n",
|
||||
" for k, v in _results.items():\n",
|
||||
" tool_input = tool_input.replace(k, v)\n",
|
||||
" if tool == \"Google\":\n",
|
||||
@@ -363,7 +363,7 @@
|
||||
"def solve(state: ReWOO):\n",
|
||||
" plan = \"\"\n",
|
||||
" for _plan, step_name, tool, tool_input in state[\"steps\"]:\n",
|
||||
" _results = (state[\"results\"] or {}) if \"results\" in state else {}\n",
|
||||
" _results = (state[\"results\"] or {}) if \"results\" in state else {}\n",
|
||||
" for k, v in _results.items():\n",
|
||||
" tool_input = tool_input.replace(k, v)\n",
|
||||
" step_name = step_name.replace(k, v)\n",
|
||||
@@ -464,7 +464,7 @@
|
||||
],
|
||||
"source": [
|
||||
"# Print out the final result\n",
|
||||
"print(s['solve']['result'])"
|
||||
"print(s[\"solve\"][\"result\"])"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -847,7 +847,9 @@
|
||||
"builder.add_edge(\"ask_question\", \"answer_question\")\n",
|
||||
"\n",
|
||||
"builder.add_edge(START, \"ask_question\")\n",
|
||||
"interview_graph = builder.compile(checkpointer=False).with_config(run_name=\"Conduct Interviews\")"
|
||||
"interview_graph = builder.compile(checkpointer=False).with_config(\n",
|
||||
" run_name=\"Conduct Interviews\"\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -172,9 +172,7 @@
|
||||
"\n",
|
||||
"\n",
|
||||
"summary_llm_chain = (\n",
|
||||
" summary_prompt\n",
|
||||
" | ChatAnthropic(model=\"claude-3-haiku-20240307\")\n",
|
||||
" | StrOutputParser()\n",
|
||||
" summary_prompt | ChatAnthropic(model=\"claude-3-haiku-20240307\") | StrOutputParser()\n",
|
||||
" # Customize the tracing name for easier organization\n",
|
||||
").with_config(run_name=\"GenerateSummary\")\n",
|
||||
"summary_chain = summary_llm_chain | parse_summary\n",
|
||||
|
||||
Reference in New Issue
Block a user