diff --git a/langchain-example-pyproject/agent.py b/langchain-example-pyproject/agent.py deleted file mode 100644 index b7ce159..0000000 --- a/langchain-example-pyproject/agent.py +++ /dev/null @@ -1,102 +0,0 @@ -from typing import TypedDict, Annotated, Sequence, Literal - -from functools import lru_cache -from langchain_core.messages import BaseMessage -from langchain_anthropic import ChatAnthropic -from langchain_openai import ChatOpenAI -from langchain_community.tools.tavily_search import TavilySearchResults -from langgraph.prebuilt import ToolNode -from langgraph.graph import StateGraph, END, add_messages - -tools = [TavilySearchResults(max_results=1)] - -@lru_cache(maxsize=4) -def _get_model(model_name: str): - if model_name == "openai": - model = ChatOpenAI(temperature=0, model_name="gpt-4o") - elif model_name == "anthropic": - model = ChatAnthropic(temperature=0, model_name="claude-3-sonnet-20240229") - else: - raise ValueError(f"Unsupported model type: {model_name}") - - model = model.bind_tools(tools) - return model - - -class AgentState(TypedDict): - messages: Annotated[Sequence[BaseMessage], add_messages] - - -# Define the function that determines whether to continue or not -def should_continue(state): - messages = state["messages"] - last_message = messages[-1] - # If there are no tool calls, then we finish - if not last_message.tool_calls: - return "end" - # Otherwise if there is, we continue - else: - return "continue" - - -system_prompt = """Be a helpful assistant""" - -# Define the function that calls the model -def call_model(state, config): - messages = state["messages"] - messages = [{"role": "system", "content": system_prompt}] + messages - model_name = config.get('configurable', {}).get("model_name", "anthropic") - model = _get_model(model_name) - 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 -tool_node = ToolNode(tools) - -# Define the config -class GraphConfig(TypedDict): - model_name: Literal["anthropic", "openai"] - - -# Define a new graph -workflow = StateGraph(AgentState, config_schema=GraphConfig) - -# Define the two nodes we will cycle between -workflow.add_node("agent", call_model) -workflow.add_node("action", tool_node) - -# 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 -graph = workflow.compile() diff --git a/langgraph.json b/langgraph.json index e494ba8..3480c8f 100644 --- a/langgraph.json +++ b/langgraph.json @@ -1,7 +1,7 @@ { "dependencies": ["."], "graphs": { - "agent": "./langchain-example-pyproject/agent.py:graph" + "agent": "./my_agent/agent.py:graph" }, "env": ".env" } diff --git a/langchain-example-pyproject/__init__.py b/my_agent/__init__.py similarity index 100% rename from langchain-example-pyproject/__init__.py rename to my_agent/__init__.py diff --git a/my_agent/agent.py b/my_agent/agent.py new file mode 100644 index 0000000..e48c215 --- /dev/null +++ b/my_agent/agent.py @@ -0,0 +1,52 @@ +from typing import TypedDict, Literal + +from langgraph.graph import StateGraph, END +from my_agent.utils.nodes import call_model, should_continue, tool_node +from my_agent.utils.state import AgentState + + +# Define the config +class GraphConfig(TypedDict): + model_name: Literal["anthropic", "openai"] + + +# Define a new graph +workflow = StateGraph(AgentState, config_schema=GraphConfig) + +# Define the two nodes we will cycle between +workflow.add_node("agent", call_model) +workflow.add_node("action", tool_node) + +# 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 +graph = workflow.compile() diff --git a/my_agent/utils/__init__.py b/my_agent/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/my_agent/utils/nodes.py b/my_agent/utils/nodes.py new file mode 100644 index 0000000..c44ba6e --- /dev/null +++ b/my_agent/utils/nodes.py @@ -0,0 +1,45 @@ +from functools import lru_cache +from langchain_anthropic import ChatAnthropic +from langchain_openai import ChatOpenAI +from my_agent.utils.tools import tools +from langgraph.prebuilt import ToolNode + + +@lru_cache(maxsize=4) +def _get_model(model_name: str): + if model_name == "openai": + model = ChatOpenAI(temperature=0, model_name="gpt-4o") + elif model_name == "anthropic": + model = ChatAnthropic(temperature=0, model_name="claude-3-sonnet-20240229") + else: + raise ValueError(f"Unsupported model type: {model_name}") + + model = model.bind_tools(tools) + return model + +# Define the function that determines whether to continue or not +def should_continue(state): + messages = state["messages"] + last_message = messages[-1] + # If there are no tool calls, then we finish + if not last_message.tool_calls: + return "end" + # Otherwise if there is, we continue + else: + return "continue" + + +system_prompt = """Be a helpful assistant""" + +# Define the function that calls the model +def call_model(state, config): + messages = state["messages"] + messages = [{"role": "system", "content": system_prompt}] + messages + model_name = config.get('configurable', {}).get("model_name", "anthropic") + model = _get_model(model_name) + 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 +tool_node = ToolNode(tools) \ No newline at end of file diff --git a/my_agent/utils/state.py b/my_agent/utils/state.py new file mode 100644 index 0000000..7d21270 --- /dev/null +++ b/my_agent/utils/state.py @@ -0,0 +1,6 @@ +from langgraph.graph import add_messages +from langchain_core.messages import BaseMessage +from typing import TypedDict, Annotated, Sequence + +class AgentState(TypedDict): + messages: Annotated[Sequence[BaseMessage], add_messages] diff --git a/my_agent/utils/tools.py b/my_agent/utils/tools.py new file mode 100644 index 0000000..5850682 --- /dev/null +++ b/my_agent/utils/tools.py @@ -0,0 +1,3 @@ +from langchain_community.tools.tavily_search import TavilySearchResults + +tools = [TavilySearchResults(max_results=1)] \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index f7f36f9..9c8d4a5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,19 +1,17 @@ [tool.poetry] -name = "langchain-example-pyproject" +name = "my_agent" version = "0.1.0" description = "Example LangGraph project for deployment to LangGraph Cloud" authors = [ "langchain-ai" ] packages = [ - { include = "langchain-example-pyproject" }, + { include = "my_agent" }, ] - - [tool.poetry.dependencies] python = ">=3.9.0,<3.13" -langgraph = "^0.1.7" +langgraph = "^0.2.0" langchain_anthropic = "^0.1.0" langchain_core = "^0.2.0" langchain_openai = "^0.1.0"