This PR updates the LangChain Arcade integration to v1.0.0, making the following key changes: • Bumped the package version in pyproject.toml from 0.2.0 to 1.0.0. • Changed the default parameter in ArcadeToolManager from langgraph=False to langgraph=True. • Updated dependencies to require langgraph≥0.2.67,<0.3.0 and simplified extras. • Adjusted example scripts to remove explicit authorization_url references in favor of a unified URL field. • Updated docs and environment references to align with new usage patterns and emphasize environment variables. These changes unify and streamline the LangGraph-based tooling while ensuring compatibility with the latest 1.0.0 release.
104 lines
3.7 KiB
Python
104 lines
3.7 KiB
Python
import os
|
|
|
|
# Import necessary classes and modules
|
|
from langchain_arcade import ArcadeToolManager
|
|
from langchain_openai import ChatOpenAI
|
|
from langgraph.checkpoint.memory import MemorySaver
|
|
from langgraph.graph import END, START, MessagesState, StateGraph
|
|
from langgraph.prebuilt import ToolNode
|
|
|
|
arcade_api_key = os.environ["ARCADE_API_KEY"]
|
|
|
|
# Initialize the tool manager and fetch tools compatible with langgraph
|
|
tool_manager = ArcadeToolManager(api_key=arcade_api_key)
|
|
tools = tool_manager.get_tools(
|
|
toolkits=["Google"], langgraph=True
|
|
) # use langgraph-specific behavior
|
|
tool_node = ToolNode(tools)
|
|
|
|
# Create a language model instance and bind it with the tools
|
|
model = ChatOpenAI(model="gpt-4o")
|
|
model_with_tools = model.bind_tools(tools)
|
|
|
|
|
|
#### Workflow ####
|
|
|
|
|
|
# Function to invoke the model and get a response
|
|
def call_agent(state: MessagesState):
|
|
messages = state["messages"]
|
|
response = model_with_tools.invoke(messages)
|
|
# Return the updated message history
|
|
return {"messages": [response]}
|
|
|
|
|
|
# Function to determine the next step in the workflow based on the last message
|
|
def should_continue(state: MessagesState):
|
|
if state["messages"][-1].tool_calls:
|
|
for tool_call in state["messages"][-1].tool_calls:
|
|
if tool_manager.requires_auth(tool_call["name"]):
|
|
return "authorization"
|
|
return "tools" # Proceed to tool execution if no authorization is needed
|
|
return END # End the workflow if no tool calls are present
|
|
|
|
|
|
# Function to handle authorization for tools that require it
|
|
def authorize(state: MessagesState, config: dict):
|
|
user_id = config["configurable"].get("user_id")
|
|
for tool_call in state["messages"][-1].tool_calls:
|
|
tool_name = tool_call["name"]
|
|
if not tool_manager.requires_auth(tool_name):
|
|
continue
|
|
auth_response = tool_manager.authorize(tool_name, user_id)
|
|
if auth_response.status != "completed":
|
|
# Prompt the user to visit the authorization URL
|
|
print(f"Visit the following URL to authorize: {auth_response.url}")
|
|
|
|
# wait for the user to complete the authorization
|
|
# and then check the authorization status again
|
|
tool_manager.wait_for_auth(auth_response.id)
|
|
if not tool_manager.is_authorized(auth_response.id):
|
|
# node interrupt?
|
|
raise ValueError("Authorization failed")
|
|
|
|
return {"messages": []}
|
|
|
|
|
|
if __name__ == "__main__":
|
|
# Build the workflow graph using StateGraph
|
|
workflow = StateGraph(MessagesState)
|
|
|
|
# Add nodes (steps) to the graph
|
|
workflow.add_node("agent", call_agent)
|
|
workflow.add_node("tools", tool_node)
|
|
workflow.add_node("authorization", authorize)
|
|
|
|
# Define the edges and control flow between nodes
|
|
workflow.add_edge(START, "agent")
|
|
workflow.add_conditional_edges("agent", should_continue, ["authorization", "tools", END])
|
|
workflow.add_edge("authorization", "tools")
|
|
workflow.add_edge("tools", "agent")
|
|
|
|
# Set up memory for checkpointing the state
|
|
memory = MemorySaver()
|
|
|
|
# Compile the graph with the checkpointer
|
|
graph = workflow.compile(checkpointer=memory)
|
|
|
|
# Define the input messages from the user
|
|
inputs = {
|
|
"messages": [
|
|
{
|
|
"role": "user",
|
|
"content": "Check and see if I have any important emails in my inbox",
|
|
}
|
|
],
|
|
}
|
|
|
|
# Configuration with thread and user IDs for authorization purposes
|
|
config = {"configurable": {"thread_id": "4", "user_id": "user@example.com"}}
|
|
|
|
# Run the graph and stream the outputs
|
|
for chunk in graph.stream(inputs, config=config, stream_mode="values"):
|
|
# Pretty-print the last message in the chunk
|
|
chunk["messages"][-1].pretty_print()
|