Files
langgraph/examples/chat_executor_with_function_calling/respond-in-format.ipynb
T
2024-01-15 13:16:05 -08:00

11 KiB

In [1]:
from langchain import hub
from langchain_openai import ChatOpenAI
from langchain_community.tools.tavily_search import TavilySearchResults
from langgraph.prebuilt.messages_executor import create_messages_executor
from langchain_core.messages import HumanMessage
In [2]:
tools = [TavilySearchResults(max_results=1)]
model = ChatOpenAI()
In [3]:
from langchain.tools.render import format_tool_to_openai_function
In [5]:
# Here we bind an additional function beside just tools - the response format
functions = [format_tool_to_openai_function(t) for t in tools]

from langchain_core.pydantic_v1 import BaseModel, Field

class Response(BaseModel):
    """Final response to the user"""
    temperature: float = Field(description="the temperature")
    other_notes: str = Field(description="any other notes about the weather")

from langchain_core.utils.function_calling import convert_pydantic_to_openai_function
In [6]:
model = model.bind_functions(functions + [convert_pydantic_to_openai_function(Response)])
In [7]:
from langgraph.prebuilt.tool_executor import ToolExecutor
tool_executor = ToolExecutor(tools)
In [8]:
from typing import TypedDict, Annotated, Sequence
import operator
from langchain_core.messages import BaseMessage
# We create the AgentState that we will pass around
# This simply involves a list of messages
# We want steps to return messages to append to the list
# So we annotate the messages attribute with operator.add
class AgentState(TypedDict):
    messages: Annotated[Sequence[BaseMessage], operator.add]
In [14]:
from langchain_core.agents import AgentAction
import json
from langchain_core.messages import FunctionMessage

# Define the function that determines whether to continue or not
# This needs to get updated
def should_continue(state):
    messages = state['messages']
    last_message = messages[-1]
    # If there is no function call, then we finish
    if "function_call" not in last_message.additional_kwargs:
        return "end"
    # Otherwise if there is, we need to check what type of function call it is
    elif last_message.additional_kwargs["function_call"]["name"] == "Response":
        return "end"
    else:
        return "continue"
In [15]:
# Define the function that calls the model
def call_model(state):
    messages = state['messages']
    response = model.invoke(messages)
    # We return a list, because this will get added to the existing list
    return {"messages": [response]}

# Define the function to execute tools
def call_tool(state):
    messages = state['messages']
    # Based on the continue condition
    # we know the last message involves a function call
    last_message = messages[-1]
    # We construct an AgentAction from the function_call
    action = AgentAction(
        tool=last_message.additional_kwargs["function_call"]["name"],
        tool_input=json.loads(last_message.additional_kwargs["function_call"]["arguments"]),
        log="",
    )
    # We call the tool_executor and get back a response
    response = tool_executor.invoke(action)
    # We use the response to create a FunctionMessage
    function_message = FunctionMessage(content=str(response), name=action.tool)
    # We return a list, because this will get added to the existing list
    return {"messages": [function_message]}
In [16]:
from langgraph.graph import StateGraph, END
# Define a new graph
workflow = StateGraph(AgentState)

# Define the two nodes we will cycle between
workflow.add_node("agent", call_model)
workflow.add_node("action", call_tool)

# Set the entrypoint as `agent`
# This means that this node is the first one called
workflow.set_entry_point("agent")

# We now add a conditional edge
workflow.add_conditional_edges(
    # First, we define the start node. We use `agent`.
    # This means these are the edges taken after the `agent` node is called.
    "agent",
    # Next, we pass in the function that will determine which node is called next.
    should_continue,
    # Finally we pass in a mapping.
    # The keys are strings, and the values are other nodes.
    # END is a special node marking that the graph should finish.
    # What will happen is we will call `should_continue`, and then the output of that
    # will be matched against the keys in this mapping.
    # Based on which one it matches, that node will then be called.
    {
        # If `tools`, then we call the tool node.
        "continue": "action",
        # Otherwise we finish.
        "end": END
    }
)

# We now add a normal edge from `tools` to `agent`.
# This means that after `tools` is called, `agent` node is called next.
workflow.add_edge('action', 'agent')

# Finally, we compile it!
# This compiles it into a LangChain Runnable,
# meaning you can use it as you would any other runnable
app = workflow.compile()
In [17]:
inputs = {"messages": [HumanMessage(content="what is the weather in sf")]}
for s in app.stream(inputs):
    print(list(s.values())[0])
    print("----")
{'messages': [AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\n  "query": "weather in San Francisco"\n}', 'name': 'tavily_search_results_json'}})]}
----
{'messages': [FunctionMessage(content="[{'url': 'https://weatherspark.com/h/m/557/2024/1/Historical-Weather-in-January-2024-in-San-Francisco-California-United-States', 'content': 'January 2024 Weather History in San Francisco California, United States  Daily Precipitation in January 2024 in San Francisco Observed Weather in January 2024 in San Francisco  San Francisco Temperature History January 2024 Hourly Temperature in January 2024 in San Francisco  Hours of Daylight and Twilight in January 2024 in San Francisco80 deg, E Cloud Cover Mostly Cloudy 18,000 ft Mostly Clear 4,000 ft Partly Cloudy 15,000 ft Raw: KSFO 121956Z 08006KT 10SM FEW040 SCT150 BKN180 11/07 A3028 RMK AO2 SLP254 T01110067 This report shows the past weather for San Francisco, providing a weather history for January 2024.'}]", name='tavily_search_results_json')]}
----
{'messages': [AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\n  "temperature": 80,\n  "other_notes": "Mostly cloudy"\n}', 'name': 'Response'}})]}
----
{'messages': [HumanMessage(content='what is the weather in sf'), AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\n  "query": "weather in San Francisco"\n}', 'name': 'tavily_search_results_json'}}), FunctionMessage(content="[{'url': 'https://weatherspark.com/h/m/557/2024/1/Historical-Weather-in-January-2024-in-San-Francisco-California-United-States', 'content': 'January 2024 Weather History in San Francisco California, United States  Daily Precipitation in January 2024 in San Francisco Observed Weather in January 2024 in San Francisco  San Francisco Temperature History January 2024 Hourly Temperature in January 2024 in San Francisco  Hours of Daylight and Twilight in January 2024 in San Francisco80 deg, E Cloud Cover Mostly Cloudy 18,000 ft Mostly Clear 4,000 ft Partly Cloudy 15,000 ft Raw: KSFO 121956Z 08006KT 10SM FEW040 SCT150 BKN180 11/07 A3028 RMK AO2 SLP254 T01110067 This report shows the past weather for San Francisco, providing a weather history for January 2024.'}]", name='tavily_search_results_json'), AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\n  "temperature": 80,\n  "other_notes": "Mostly cloudy"\n}', 'name': 'Response'}})]}
----
In [ ]: