"""
Intent recognition functionality for the Agentic RAG system.
This module contains the intent classification logic.
"""
import logging
from typing import Dict, Any, Optional, Literal
from langchain_core.messages import SystemMessage
from langchain_core.runnables import RunnableConfig
from pydantic import BaseModel
from .state import AgentState
from ..llm_client import LLMClient
from ..config import get_config
from ..utils.error_handler import StructuredLogger
logger = StructuredLogger(__name__)
# Intent Recognition Models
class Intent(BaseModel):
"""Intent classification model for routing user queries"""
label: Literal["Standard_Regulation_RAG", "User_Manual_RAG"]
confidence: Optional[float] = None
def get_last_user_message(messages) -> str:
"""Extract the last user message from conversation history"""
for message in reversed(messages):
if hasattr(message, 'content'):
content = message.content
# Handle both string and list content
if isinstance(content, str):
return content
elif isinstance(content, list):
# Extract string content from list
return " ".join([str(item) for item in content if isinstance(item, str)])
return ""
def render_conversation_history(messages, max_messages: int = 10) -> str:
"""Render conversation history for context"""
recent_messages = messages[-max_messages:] if len(messages) > max_messages else messages
lines = []
for msg in recent_messages:
if hasattr(msg, 'content'):
content = msg.content
if isinstance(content, str):
# Determine message type by class name or other attributes
if 'Human' in str(type(msg)):
lines.append(f"{content}")
elif 'AI' in str(type(msg)):
lines.append(f"{content}")
elif isinstance(content, list):
content_str = " ".join([str(item) for item in content if isinstance(item, str)])
if 'Human' in str(type(msg)):
lines.append(f"{content_str}")
elif 'AI' in str(type(msg)):
lines.append(f"{content_str}")
return "\n".join(lines)
async def intent_recognition_node(state: AgentState, config: Optional[RunnableConfig] = None) -> Dict[str, Any]:
"""
Intent recognition node that uses LLM to classify user queries into specific domains
"""
try:
logger.info("🎯 INTENT_RECOGNITION_NODE: Starting intent classification")
app_config = get_config()
llm_client = LLMClient()
# Get current user query and conversation history
current_query = get_last_user_message(state["messages"])
conversation_context = render_conversation_history(state["messages"])
# Get intent classification prompt from configuration
rag_prompts = app_config.get_rag_prompts()
intent_prompt_template = rag_prompts.get("intent_recognition_prompt")
if not intent_prompt_template:
logger.error("Intent recognition prompt not found in configuration")
return {"intent": "Standard_Regulation_RAG"}
# Format the prompt with instruction to return only the label
system_prompt = intent_prompt_template.format(
current_query=current_query,
conversation_context=conversation_context
) + "\n\nIMPORTANT: You must respond with ONLY one of these two exact labels: 'Standard_Regulation_RAG' or 'User_Manual_RAG'. Do not include any other text or explanation."
# Classify intent using regular LLM call
intent_result = await llm_client.llm.ainvoke([
SystemMessage(content=system_prompt)
])
# Parse the response to extract the intent label
response_text = ""
if hasattr(intent_result, 'content') and intent_result.content:
if isinstance(intent_result.content, str):
response_text = intent_result.content.strip()
elif isinstance(intent_result.content, list):
# Handle list content by joining string elements
response_text = " ".join([str(item) for item in intent_result.content if isinstance(item, str)]).strip()
# Extract intent label from response
if "User_Manual_RAG" in response_text:
intent_label = "User_Manual_RAG"
elif "Standard_Regulation_RAG" in response_text:
intent_label = "Standard_Regulation_RAG"
else:
# Default fallback
logger.warning(f"Could not parse intent from response: {response_text}, defaulting to Standard_Regulation_RAG")
intent_label = "Standard_Regulation_RAG"
logger.info(f"🎯 INTENT_RECOGNITION_NODE: Classified intent as '{intent_label}'")
return {"intent": intent_label}
except Exception as e:
logger.error(f"Intent recognition error: {e}")
# Default to Standard_Regulation_RAG if classification fails
logger.info("🎯 INTENT_RECOGNITION_NODE: Defaulting to Standard_Regulation_RAG due to error")
return {"intent": "Standard_Regulation_RAG"}
def intent_router(state: AgentState) -> Literal["Standard_Regulation_RAG", "User_Manual_RAG"]:
"""
Route based on intent classification result
"""
intent = state.get("intent")
if intent is None:
logger.warning("🎯 INTENT_ROUTER: No intent found, defaulting to Standard_Regulation_RAG")
return "Standard_Regulation_RAG"
logger.info(f"🎯 INTENT_ROUTER: Routing to {intent}")
return intent