Sulaiman8's picture
Update graph.py
cbebf59 verified
Raw
History Blame Contribute Delete
7.87 kB
import os
from langgraph.graph import StateGraph, END
from langgraph.prebuilt import ToolNode
from langchain_core.messages import AIMessage
from typing import Literal
from nodes.intent import CreditCardState,intent_classifier_node,general_info_handler_node,oos_handler_node
from recommender.vectror_db import query_refiner_node
from recommender.graph_retrieval import neo4j_error_handler_node, neo4j_retrieval_node
from recommender.vectordb_retrieval import ranked_card_retrieval_node
from nodes.agent import agent_node,TOOLS
from nodes.format import format_output_node
from data import debug_print
from nodes.chat import chat_agent_node,chat_tool_node
from nodes.compare import compare_node_fn
# --- Graph Definition ---
graph = StateGraph(CreditCardState)
graph.add_node("intent_classifier", intent_classifier_node)
graph.add_node("general_info_handler", general_info_handler_node)
graph.add_node("oos_handler", oos_handler_node)
graph.add_node("query_refiner", query_refiner_node)
graph.add_node("neo4j_retriever", neo4j_retrieval_node)
graph.add_node("neo4j_error_handler", neo4j_error_handler_node)
graph.add_node("ranked_card_retrieval", ranked_card_retrieval_node)
graph.add_node("agent", agent_node) # Using our new React agent implementation
graph.add_node("tools", ToolNode(TOOLS))
graph.add_node("format_output", format_output_node)
graph.set_entry_point("intent_classifier")
def route_after_intent_classification(state: CreditCardState):
intent = state["intent"]
debug_print("ROUTE", f"Intent classification routing with intent: '{intent}'")
if intent == "credit-card-recommendation":
return "query_refiner"
elif intent == "general-credit-related":
return "general_info_handler"
else: # This will catch 'out-of-scope' and any other unexpected values
return "oos_handler"
def route_after_format_output(state: CreditCardState):
if state.get("trigger_compare", False):
return "compare_node"
elif state.get("trigger_chat", False):
return "chat_node"
else:
return END
graph.add_conditional_edges(
"intent_classifier",
route_after_intent_classification,
{
"query_refiner": "query_refiner",
"general_info_handler": "general_info_handler",
"oos_handler": "oos_handler",
},
)
graph.add_edge("general_info_handler", END)
graph.add_edge("oos_handler", END)
graph.add_edge("query_refiner", "neo4j_retriever")
def route_after_neo4j_retriever(state: CreditCardState):
debug_print("ROUTE", f"neo4j_error: {state.get('neo4j_error')}")
if state.get("neo4j_error", False):
return "neo4j_error_handler"
else:
return "ranked_card_retrieval"
graph.add_conditional_edges(
"neo4j_retriever",
route_after_neo4j_retriever,
{
"neo4j_error_handler": "neo4j_error_handler",
"ranked_card_retrieval": "ranked_card_retrieval",
},
)
graph.add_edge("neo4j_error_handler", END)
graph.add_edge("ranked_card_retrieval", "agent")
def route_agent_output(state: CreditCardState) -> Literal["format_output", "tools"]:
"""Determine the next node based on the model's output.
This function checks if the model's last message contains tool calls.
Args:
state (CreditCardState): The current state of the conversation.
Returns:
str: The name of the next node to call ("format_output" or "tools").
"""
last_message = state["messages"][-1]
if not isinstance(last_message, AIMessage):
raise ValueError(
f"Expected AIMessage in output edges, but got {type(last_message).__name__}"
)
# If there is no tool call, then we move to format_output
if not last_message.tool_calls:
return "format_output"
# Otherwise we execute the requested actions
return "tools"
graph.add_conditional_edges(
"agent",
route_agent_output
)
graph.add_edge("tools", "agent")
graph.add_edge("format_output",END)
app = graph.compile()
# --- Pipeline Function ---
def run_langgraph_pipeline(
query: str,
preferences: str,
query_intent: bool,
include_cobranded: bool,
use_eligibility: bool = False,
age=None,
income=None,
cibil=None,
min_joining_fee=None,
max_joining_fee=None,
min_annual_fee=None,
max_annual_fee=None
):
debug_print("PIPELINE", f"Starting pipeline with query: '{query}'")
debug_print("PIPELINE", f"Preferences: '{preferences}'")
debug_print("PIPELINE", f"Query intent: {query_intent}, Include cobranded: {include_cobranded}")
debug_print("PIPELINE", f"Eligibility: {use_eligibility}, Age: {age}, Income: {income}, CIBIL: {cibil}")
debug_print("PIPELINE", f"Join fee: {min_joining_fee}-{max_joining_fee}, Annual fee: {min_annual_fee}-{max_annual_fee}")
inputs = {
"query": query,
"preferences": preferences,
"query_intent": query_intent,
"include_cobranded": include_cobranded,
"use_eligibility": use_eligibility,
"age": age,
"income": income,
"cibil": cibil,
"min_joining_fee": min_joining_fee,
"max_joining_fee": max_joining_fee,
"min_annual_fee": min_annual_fee,
"max_annual_fee": max_annual_fee,
"agent_outcome": None,
"messages": [],
"trigger_chat": False,
"trigger_compare": False,
"selected_cards": [],
"user_message": "",
}
debug_print("PIPELINE", f"Invoking LangGraph app")
result = app.invoke(inputs)
debug_print("PIPELINE", f"LangGraph execution complete")
# Ensure card_file is a valid file path or None
card_file = result.get("card_file", None)
if card_file and os.path.isfile(card_file):
debug_print("PIPELINE", f"Valid card file found: {card_file}")
else:
debug_print("PIPELINE", f"Invalid or missing card file: {card_file}")
card_file = None
debug_print("PIPELINE", f"Pipeline complete, returning results")
return (
result.get("top_card_html", ""),
result.get("card_rows", []),
card_file, # Use the validated file path
result.get("card_names", []),
result.get("card_lookup", {}),
)
def passthrough_node(state: CreditCardState) -> CreditCardState:
return state
def utility_router(state: CreditCardState):
if state.get("trigger_compare", False):
return "compare_node"
elif state.get("trigger_chat", False):
return "chat_agent" # Start with the agent
else:
raise ValueError("No trigger flag set for utility graph.")
utility_graph = StateGraph(CreditCardState)
# Correct: Add a valid node that returns the full state
utility_graph.add_node("router", passthrough_node)
# Add your real action nodes
utility_graph.add_node("compare_node", compare_node_fn)
utility_graph.add_node("chat_agent", chat_agent_node)
utility_graph.add_node("chat_tools", chat_tool_node)
# Set router as entry point
utility_graph.set_entry_point("router")
# Add conditional edges using utility_router (not as a node)
utility_graph.add_conditional_edges(
"router",
utility_router,
{
"compare_node": "compare_node",
"chat_agent": "chat_agent",
},
)
def route_chat_agent_output(state: CreditCardState) -> Literal["chat_tools", "__end__"]:
last_message = state["messages"][-1]
if not isinstance(last_message, AIMessage):
# This can happen if the tool call fails with an error
return "__end__"
if not last_message.tool_calls:
return "__end__"
return "chat_tools"
utility_graph.add_conditional_edges(
"chat_agent",
route_chat_agent_output,
{
"chat_tools": "chat_tools",
"__end__": "__end__"
}
)
utility_graph.add_edge("chat_tools", "chat_agent")
utility_graph.add_edge("compare_node", END)
utility_app = utility_graph.compile()