mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-08-21 15:12:26 +02:00
* first draft * vadym comments * Update docs/mkdocs.yml Co-authored-by: Vadym Barda <vadym@langchain.dev> * vadym comments * spellign * more comments * spelling * final nits --------- Co-authored-by: Vadym Barda <vadym@langchain.dev>
239 lines
7.9 KiB
Plaintext
239 lines
7.9 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"# How to force function calling agent to structure output\n",
|
|
"\n",
|
|
"You might want your agent to return its output in a structured format. For example, if the output of the agent is used by some other downstream software, you may want the output to be in the same structured format every time the agent is invoked to ensure consistency.\n",
|
|
"\n",
|
|
"This guide shows how you can do this. We will be using a basic [ReAct agent](https://langchain-ai.github.io/langgraph/how-tos/create-react-agent/) (a model node and a tool-calling node) together with a third node at the end that will format response for the user.\n",
|
|
"\n",
|
|
"\n",
|
|
"## Setup\n",
|
|
"\n",
|
|
"### Structured Output\n",
|
|
"\n",
|
|
"First we need to define how we want to structure our output. To do this, we will use the `with_structured_output` method from LangChain, which you can read more about [here](https://python.langchain.com/v0.2/docs/how_to/structured_output/)."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 1,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"from pydantic import BaseModel, Field\n",
|
|
"\n",
|
|
"class WeatherResponse(BaseModel):\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_speed: float = Field(description=\"The speed of the wind in km/h\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"### Graph State\n",
|
|
"\n",
|
|
"We can now define our graph state:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 2,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"from typing import Annotated, Any\n",
|
|
"from typing_extensions import TypedDict\n",
|
|
"from langgraph.graph.message import add_messages\n",
|
|
"\n",
|
|
"class AgentState(TypedDict):\n",
|
|
" # list of chat messages from user, LLM, and tools\n",
|
|
" messages: Annotated[list, add_messages]\n",
|
|
" # Final structured response from the agent\n",
|
|
" final_response: WeatherResponse"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"### Tools + Models\n",
|
|
"\n",
|
|
"We can now instantiate the tools and models we are going to use in our graph. We are going to use a single tool in this example for finding the weather, and we are going to have two models in our graph, one that does the function calling and one that does the responding."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 3,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"from typing import Literal\n",
|
|
"from langchain_core.tools import tool\n",
|
|
"from langchain_anthropic import ChatAnthropic\n",
|
|
"\n",
|
|
"@tool\n",
|
|
"def get_weather(city: Literal[\"nyc\", \"sf\"]):\n",
|
|
" \"\"\"Use this to get weather information.\"\"\"\n",
|
|
" if city == \"nyc\":\n",
|
|
" return \"It is cloudy in NYC, with 5 mph winds in the North-East direction and a temperature of 70 degrees\"\n",
|
|
" elif city == \"sf\":\n",
|
|
" 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",
|
|
"tools = [get_weather]\n",
|
|
" \n",
|
|
"model = ChatAnthropic(model=\"claude-3-opus-20240229\")\n",
|
|
" \n",
|
|
"model_with_tools = model.bind_tools(tools)\n",
|
|
"model_with_structured_output = model.with_structured_output(WeatherResponse)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"### Define Graph\n",
|
|
"\n",
|
|
"Now that we have defined our tools and models, we can define our graph."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 18,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"from langgraph.graph import StateGraph, END\n",
|
|
"from langgraph.prebuilt import ToolNode\n",
|
|
"from langchain_core.messages import HumanMessage\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 no function call, then we respond to the user\n",
|
|
" if not last_message.tool_calls:\n",
|
|
" return \"respond\"\n",
|
|
" # Otherwise if there is, we continue\n",
|
|
" else:\n",
|
|
" return \"continue\"\n",
|
|
"\n",
|
|
"# Define the function that calls the model\n",
|
|
"def call_model(state: AgentState):\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",
|
|
"# 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",
|
|
" # We return the final answer\n",
|
|
" return {\"final_response\": response}\n",
|
|
"\n",
|
|
"\n",
|
|
"# Define a new graph\n",
|
|
"workflow = StateGraph(AgentState)\n",
|
|
"\n",
|
|
"# Define the two nodes we will cycle between\n",
|
|
"workflow.add_node(\"agent\", call_model)\n",
|
|
"workflow.add_node(\"respond\", respond)\n",
|
|
"workflow.add_node(\"tools\", ToolNode(tools))\n",
|
|
"\n",
|
|
"# Set the entrypoint as `agent`\n",
|
|
"# This means that this node is the first one called\n",
|
|
"workflow.set_entry_point(\"agent\")\n",
|
|
"\n",
|
|
"# We now add a conditional edge\n",
|
|
"workflow.add_conditional_edges(\n",
|
|
" \"agent\",\n",
|
|
" should_continue,\n",
|
|
" {\n",
|
|
" \"continue\": \"tools\",\n",
|
|
" \"respond\": \"respond\",\n",
|
|
" },\n",
|
|
")\n",
|
|
"\n",
|
|
"workflow.add_edge(\"tools\", \"agent\")\n",
|
|
"workflow.add_edge(\"respond\", END)\n",
|
|
"graph = workflow.compile()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Usage\n",
|
|
"\n",
|
|
"We can now invoke our graph to verify that the output is being structured as desired:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 19,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"answer = graph.invoke(input={\"messages\": [(\"human\", \"what's the weather in SF?\")]})['final_response']"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"WeatherResponse(temperature=75.0, wind_directon='SE', wind_speed=3.0)"
|
|
]
|
|
},
|
|
"execution_count": 6,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"answer"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"As we can see, the agent returned a `WeatherResponse` object as we expected. If would now be easy to use this agent in a more complex software stack without having to worry about the output of the agent not matching the format expected from the next step in the stack."
|
|
]
|
|
}
|
|
],
|
|
"metadata": {
|
|
"kernelspec": {
|
|
"display_name": "Python 3",
|
|
"language": "python",
|
|
"name": "python3"
|
|
},
|
|
"language_info": {
|
|
"codemirror_mode": {
|
|
"name": "ipython",
|
|
"version": 3
|
|
},
|
|
"file_extension": ".py",
|
|
"mimetype": "text/x-python",
|
|
"name": "python",
|
|
"nbconvert_exporter": "python",
|
|
"pygments_lexer": "ipython3",
|
|
"version": "3.11.9"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 2
|
|
}
|