192 lines
5.9 KiB
Python
192 lines
5.9 KiB
Python
"""
|
|
Conditional LangGraph Agent
|
|
Complexity: HIGH - Multiple conditional edges and routing logic
|
|
"""
|
|
|
|
from typing import Literal, Annotated, Sequence
|
|
from typing_extensions import TypedDict
|
|
|
|
from langgraph.graph import StateGraph, END, START, add_messages
|
|
from langgraph.prebuilt import ToolNode
|
|
from langchain_openai import ChatOpenAI
|
|
from langchain_core.tools import tool
|
|
from langchain_core.messages import HumanMessage, BaseMessage
|
|
from langchain_core.runnables import RunnableConfig
|
|
|
|
|
|
class ConditionalState(TypedDict):
|
|
"""State for the conditional agent with messages and intent."""
|
|
|
|
messages: Annotated[Sequence[BaseMessage], add_messages]
|
|
intent: str
|
|
|
|
|
|
@tool
|
|
def research_topic(topic: str) -> str:
|
|
"""Research a topic and return findings."""
|
|
research_data = {
|
|
"ai": "AI research shows rapid advancement in large language models.",
|
|
"climate": "Climate research indicates rising global temperatures.",
|
|
"space": "Space research reveals new exoplanets in habitable zones.",
|
|
"quantum": "Quantum computing achieves new milestone in error correction.",
|
|
}
|
|
for key, value in research_data.items():
|
|
if key in topic.lower():
|
|
return value
|
|
return f"Research findings for {topic}: General information available."
|
|
|
|
|
|
@tool
|
|
def summarize_text(text: str) -> str:
|
|
"""Summarize the given text."""
|
|
if len(text) > 100:
|
|
return f"Summary: {text[:100]}..."
|
|
return f"Summary: {text}"
|
|
|
|
|
|
@tool
|
|
def fact_check(claim: str) -> str:
|
|
"""Fact check a claim."""
|
|
# Simple mock fact checker
|
|
if "true" in claim.lower() or "correct" in claim.lower():
|
|
return "Fact check: VERIFIED - This claim appears to be accurate."
|
|
elif "false" in claim.lower() or "wrong" in claim.lower():
|
|
return "Fact check: FALSE - This claim is inaccurate."
|
|
return "Fact check: UNVERIFIED - Unable to confirm this claim."
|
|
|
|
|
|
tools = [research_topic, summarize_text, fact_check]
|
|
|
|
llm = ChatOpenAI(model="gpt-5.4-mini", temperature=0, seed=42)
|
|
llm_with_tools = llm.bind_tools(tools)
|
|
|
|
|
|
def classify_intent(state: dict) -> dict:
|
|
"""Classify the user's intent to route appropriately."""
|
|
messages = state["messages"]
|
|
last_message = messages[-1]
|
|
content = last_message.content.lower()
|
|
|
|
# Simple intent classification
|
|
if "research" in content or "find" in content or "learn" in content:
|
|
intent = "research"
|
|
elif "summarize" in content or "summary" in content:
|
|
intent = "summarize"
|
|
elif "fact" in content or "check" in content or "verify" in content:
|
|
intent = "fact_check"
|
|
else:
|
|
intent = "general"
|
|
|
|
return {"messages": messages, "intent": intent}
|
|
|
|
|
|
def research_node(state: dict, config: RunnableConfig) -> dict:
|
|
"""Handle research queries."""
|
|
messages = state["messages"]
|
|
system_prompt = HumanMessage(
|
|
content="You are a research assistant. Use the research_topic tool to find information."
|
|
)
|
|
response = llm_with_tools.invoke([system_prompt] + messages, config=config)
|
|
return {"messages": [response]}
|
|
|
|
|
|
def summarize_node(state: dict, config: RunnableConfig) -> dict:
|
|
"""Handle summarization queries."""
|
|
messages = state["messages"]
|
|
system_prompt = HumanMessage(
|
|
content="You are a summarization assistant. Use the summarize_text tool."
|
|
)
|
|
response = llm_with_tools.invoke([system_prompt] + messages, config=config)
|
|
return {"messages": [response]}
|
|
|
|
|
|
def fact_check_node(state: dict, config: RunnableConfig) -> dict:
|
|
"""Handle fact checking queries."""
|
|
messages = state["messages"]
|
|
system_prompt = HumanMessage(
|
|
content="You are a fact checker. Use the fact_check tool to verify claims."
|
|
)
|
|
response = llm_with_tools.invoke([system_prompt] + messages, config=config)
|
|
return {"messages": [response]}
|
|
|
|
|
|
def general_node(state: dict, config: RunnableConfig) -> dict:
|
|
"""Handle general queries."""
|
|
messages = state["messages"]
|
|
response = llm_with_tools.invoke(messages, config=config)
|
|
return {"messages": [response]}
|
|
|
|
|
|
def route_by_intent(
|
|
state: dict,
|
|
) -> Literal["research", "summarize", "fact_check", "general"]:
|
|
"""Route based on classified intent."""
|
|
return state.get("intent", "general")
|
|
|
|
|
|
def should_continue(state: dict) -> Literal["tools", "__end__"]:
|
|
"""Determine if we should continue to tools or end."""
|
|
messages = state["messages"]
|
|
last_message = messages[-1]
|
|
|
|
if hasattr(last_message, "tool_calls") and last_message.tool_calls:
|
|
return "tools"
|
|
return "__end__"
|
|
|
|
|
|
def route_after_tools(state: dict) -> str:
|
|
"""Route back to the appropriate node after tool execution."""
|
|
intent = state.get("intent", "general")
|
|
return intent
|
|
|
|
|
|
def build_app():
|
|
"""Build the conditional routing graph."""
|
|
graph = StateGraph(ConditionalState)
|
|
|
|
# Add nodes
|
|
graph.add_node("classifier", classify_intent)
|
|
graph.add_node("research", research_node)
|
|
graph.add_node("summarize", summarize_node)
|
|
graph.add_node("fact_check", fact_check_node)
|
|
graph.add_node("general", general_node)
|
|
graph.add_node("tools", ToolNode(tools))
|
|
|
|
# Entry point
|
|
graph.add_edge(START, "classifier")
|
|
|
|
# Route from classifier based on intent
|
|
graph.add_conditional_edges(
|
|
"classifier",
|
|
route_by_intent,
|
|
{
|
|
"research": "research",
|
|
"summarize": "summarize",
|
|
"fact_check": "fact_check",
|
|
"general": "general",
|
|
},
|
|
)
|
|
|
|
# Each specialized node can go to tools or end
|
|
for node in ["research", "summarize", "fact_check", "general"]:
|
|
graph.add_conditional_edges(
|
|
node, should_continue, {"tools": "tools", "__end__": END}
|
|
)
|
|
|
|
# After tools, route back based on intent
|
|
graph.add_conditional_edges(
|
|
"tools",
|
|
route_after_tools,
|
|
{
|
|
"research": "research",
|
|
"summarize": "summarize",
|
|
"fact_check": "fact_check",
|
|
"general": "general",
|
|
},
|
|
)
|
|
|
|
return graph.compile()
|
|
|
|
|
|
app = build_app()
|