Files
2026-07-13 13:32:05 +08:00

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()