# Set up the state from langgraph.graph import MessagesState, START # Set up the tool # We will have one real tool - a search tool # We'll also have one "fake" tool - a "ask_human" tool # Here we define any ACTUAL tools from langchain_core.tools import tool from langgraph.prebuilt import ToolNode from langchain_core.messages import AIMessage from copilotkit.langchain import copilotkit_customize_config @tool def search(query: str): """Call to surf the web.""" # This is a placeholder for the actual implementation # Don't let the LLM know this though 😊 return f"I looked up: {query}. Result: It's sunny in San Francisco, but you better look out if you're a Gemini 😈." tools = [search] tool_node = ToolNode(tools) # Set up the model #from langchain_anthropic import ChatAnthropic from langchain_openai import ChatOpenAI # model = ChatAnthropic(model="claude-3-5-sonnet-20240620") model = ChatOpenAI(model="gpt-4o") from pydantic import BaseModel # We are going "bind" all tools to the model # We have the ACTUAL tools from above, but we also need a mock tool to ask a human # Since `bind_tools` takes in tools but also just tool definitions, # We can define a tool definition for `ask_human` class AskHuman(BaseModel): """Ask the human a question""" question: str model = model.bind_tools(tools + [AskHuman]) # Define nodes and conditional edges # Define the function that determines whether to continue or not def should_continue(state): messages = state["messages"] last_message = messages[-1] # If there is no function call, then we finish if not last_message.tool_calls: return "end" # If tool call is asking Human, we return that node # You could also add logic here to let some system know that there's something that requires Human input # For example, send a slack message, etc elif last_message.tool_calls[0]["name"] == "AskHuman": return "ask_human" # Otherwise if there is, we continue else: return "continue" # Define the function that calls the model def call_model(state, config): config = copilotkit_customize_config( config, emit_tool_calls="AskHuman", ) messages = state["messages"] response = model.invoke(messages, config=config) # We return a list, because this will get added to the existing list return {"messages": [response]} # We define a fake node to ask the human def ask_human(state): pass # Build the graph from langgraph.graph import END, StateGraph # Define a new graph workflow = StateGraph(MessagesState) # Define the three nodes we will cycle between workflow.add_node("agent", call_model) workflow.add_node("action", tool_node) workflow.add_node("ask_human", ask_human) # Set the entrypoint as `agent` # This means that this node is the first one called workflow.add_edge(START, "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", # We may ask the human "ask_human": "ask_human", # 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") # After we get back the human response, we go back to the agent workflow.add_edge("ask_human", "agent") # Set up memory from langgraph.checkpoint.memory import MemorySaver memory = MemorySaver() # Finally, we compile it! # This compiles it into a LangChain Runnable, # meaning you can use it as you would any other runnable # We add a breakpoint BEFORE the `ask_human` node so it never executes graph = workflow.compile(checkpointer=memory, interrupt_after=["ask_human"])