diff --git a/backend/app/agents/devrel/agent.py b/backend/app/agents/devrel/agent.py index 53a5a93f..0dfae887 100644 --- a/backend/app/agents/devrel/agent.py +++ b/backend/app/agents/devrel/agent.py @@ -28,7 +28,7 @@ def __init__(self, config: Dict[str, Any] = None): google_api_key=settings.gemini_api_key ) self.search_tool = TavilySearchTool() - self.faq_tool = FAQTool() + self.faq_tool = FAQTool(search_tool=self.search_tool) self.github_toolkit = GitHubToolkit() self.checkpointer = InMemorySaver() super().__init__("DevRelAgent", self.config) diff --git a/backend/app/agents/devrel/nodes/handlers/faq.py b/backend/app/agents/devrel/nodes/handlers/faq.py index 8855c323..3288b88a 100644 --- a/backend/app/agents/devrel/nodes/handlers/faq.py +++ b/backend/app/agents/devrel/nodes/handlers/faq.py @@ -1,10 +1,11 @@ import logging +from typing import Dict, Any from app.agents.state import AgentState logger = logging.getLogger(__name__) -async def handle_faq_node(state: AgentState, faq_tool) -> dict: - """Handle FAQ requests""" +async def handle_faq_node(state: AgentState, faq_tool) -> Dict[str, Any]: + """Handle FAQ requests with enhanced organizational query support""" logger.info(f"Handling FAQ for session {state.session_id}") latest_message = "" @@ -13,14 +14,79 @@ async def handle_faq_node(state: AgentState, faq_tool) -> dict: elif state.context.get("original_message"): latest_message = state.context["original_message"] - # faq_tool will be passed from the agent, similar to llm for classify_intent - faq_response = await faq_tool.get_response(latest_message) - - return { - "task_result": { - "type": "faq", - "response": faq_response, - "source": "faq_database" - }, - "current_task": "faq_handled" - } + try: + # Get enhanced response with metadata + enhanced_response = await faq_tool.get_enhanced_response(latest_message) + + # Extract response details + response_text = enhanced_response.get("response") + response_type = enhanced_response.get("type", "unknown") + sources = enhanced_response.get("sources", []) + search_queries = enhanced_response.get("search_queries", []) + + # Log the type of response for monitoring + logger.info(f"FAQ response type: {response_type} for session {state.session_id}") + + if response_text: + # Successfully got a response + return { + "task_result": { + "type": response_type, + "response": response_text, + "source": enhanced_response.get("source", "enhanced_faq"), + "sources": sources, + "search_queries": search_queries, + "has_sources": len(sources) > 0 + }, + "current_task": "faq_handled", + "tools_used": ["enhanced_faq_tool"] + } + else: + # No response found + logger.info(f"No FAQ response found for: {latest_message[:100]}") + return { + "task_result": { + "type": "no_match", + "response": None, + "source": "enhanced_faq", + "sources": [], + "search_queries": [], + "has_sources": False + }, + "current_task": "faq_no_match" + } + + except Exception as e: + logger.error(f"Error in enhanced FAQ handler: {str(e)}") + + # Fallback to simple response + try: + simple_response = await faq_tool.get_response(latest_message) + if simple_response: + return { + "task_result": { + "type": "fallback_faq", + "response": simple_response, + "source": "faq_fallback", + "sources": [], + "search_queries": [], + "has_sources": False + }, + "current_task": "faq_handled" + } + except Exception as fallback_error: + logger.error(f"Fallback FAQ also failed: {str(fallback_error)}") + + # Return error state + return { + "task_result": { + "type": "error", + "response": None, + "source": "error", + "error": str(e), + "sources": [], + "search_queries": [], + "has_sources": False + }, + "current_task": "faq_error" + } diff --git a/backend/app/agents/devrel/nodes/handlers/organizational_faq.py b/backend/app/agents/devrel/nodes/handlers/organizational_faq.py new file mode 100644 index 00000000..1d6c7db5 --- /dev/null +++ b/backend/app/agents/devrel/nodes/handlers/organizational_faq.py @@ -0,0 +1,121 @@ +import logging +from typing import Dict, Any, List +from app.agents.state import AgentState +from langchain_core.messages import HumanMessage +from app.agents.devrel.prompts.organizational_faq_prompt import ORGANIZATIONAL_SYNTHESIS_PROMPT + +logger = logging.getLogger(__name__) + +async def handle_organizational_faq_node( + state: AgentState, + enhanced_faq_tool: Any, + llm: Any +) -> Dict[str, Any]: + """Handle organizational FAQ requests with web search and LLM synthesis""" + logger.info(f"Handling organizational FAQ for session {state.session_id}") + + latest_message = "" + if state.messages: + latest_message = state.messages[-1].get("content", "") + elif state.context.get("original_message"): + latest_message = state.context["original_message"] + + # Get response from enhanced FAQ tool + faq_response = await enhanced_faq_tool.get_response(latest_message) + + # If it's an organizational query, enhance with LLM synthesis + if faq_response.get("type") == "organizational_faq": + search_results = faq_response.get("sources", []) + + if search_results: + # Format search results for LLM + formatted_results = _format_search_results_for_llm(search_results) + + # Use LLM to synthesize a better response + synthesis_prompt = ORGANIZATIONAL_SYNTHESIS_PROMPT.format( + question=latest_message, + search_results=formatted_results + ) + + try: + llm_response = await llm.ainvoke([HumanMessage(content=synthesis_prompt)]) + synthesized_answer = llm_response.content.strip() + + # Update the response with the synthesized answer + faq_response["response"] = synthesized_answer + faq_response["synthesis_method"] = "llm_enhanced" + + logger.info( + f"Enhanced organizational response with LLM synthesis " + f"for session {state.session_id}" + ) + except (ValueError, AttributeError, TypeError) as e: + logger.error(f"Error in LLM synthesis: {str(e)}") + # Keep the original response if LLM synthesis fails + faq_response["synthesis_method"] = "basic" + except Exception as e: + logger.error(f"Unexpected error in LLM synthesis: {str(e)}") + faq_response["synthesis_method"] = "basic" + + return { + "task_result": { + "type": "organizational_faq", + "response": faq_response.get("response"), + "source": faq_response.get("source", "enhanced_faq"), + "sources": faq_response.get("sources", []), + "search_queries": faq_response.get("search_queries", []), + "synthesis_method": faq_response.get("synthesis_method", "none"), + "query_type": faq_response.get("type", "unknown") + }, + "current_task": "organizational_faq_handled" + } + +def _format_search_results_for_llm(search_results: List[Dict[str, Any]]) -> str: + """Format search results for LLM synthesis""" + if not search_results: + return "No search results available." + + formatted_parts = [] + for i, result in enumerate(search_results, 1): + title = result.get('title', 'No title') + url = result.get('url', 'No URL') + content = result.get('content', 'No content available') + + formatted_part = f""" +Result {i}: +Title: {title} +URL: {url} +Content: {content[:500]}{"..." if len(content) > 500 else ""} +""" + formatted_parts.append(formatted_part) + + return "\n".join(formatted_parts) + +def create_organizational_response(task_result: Dict[str, Any]) -> str: + """Create a user-friendly response string from organizational FAQ results""" + response = task_result.get("response", "") + sources = task_result.get("sources", []) + query_type = task_result.get("query_type", "") + + if not response: + return ("I couldn't find specific information about that. Please try rephrasing your " + "question or check our official documentation.") + + # Start with the main response + response_parts = [response] + + # Add sources if available + if sources: + response_parts.append("\n\n**Sources:**") + for i, source in enumerate(sources[:3], 1): + title = source.get('title', 'Source') + url = source.get('url', '') + response_parts.append(f"{i}. [{title}]({url})") + + # Add helpful footer for organizational queries + if query_type == "organizational_faq": + response_parts.append( + "\n\nFor more information, you can also visit our official website or GitHub repository." + ) + + return "\n".join(response_parts) diff --git a/backend/app/agents/devrel/prompts/organizational_faq_prompt.py b/backend/app/agents/devrel/prompts/organizational_faq_prompt.py new file mode 100644 index 00000000..c2674660 --- /dev/null +++ b/backend/app/agents/devrel/prompts/organizational_faq_prompt.py @@ -0,0 +1,101 @@ +# Prompts for organizational FAQ handling and synthesis + +# Prompt for detecting organizational queries +ORGANIZATIONAL_QUERY_DETECTION_PROMPT = """You are an AI assistant that helps classify user questions. +Determine if the following question is asking about organizational information (about the company, +projects, mission, goals, etc.) or technical support. + +User Question: "{question}" + +Classification Guidelines: +- ORGANIZATIONAL: Questions about the company, its mission, projects, team, goals, platforms, + general information about what the organization does +- TECHNICAL: Questions about how to use the product, troubleshooting, implementation details, + contribution guidelines, specific feature requests + +Examples of ORGANIZATIONAL questions: +- "What is Devr.AI?" +- "What projects does this organization work on?" +- "What are the main goals of Devr.AI?" +- "What platforms does Devr.AI support?" +- "Tell me about this organization" + +Examples of TECHNICAL questions: +- "How do I contribute to the project?" +- "How do I report a bug?" +- "What is LangGraph?" +- "How do I get started with development?" + +Respond with only: ORGANIZATIONAL or TECHNICAL""" + +# Prompt for generating targeted search queries for organizational information +ORGANIZATIONAL_SEARCH_QUERY_GENERATION_PROMPT = """You are an AI assistant that helps generate effective +search queries. Based on the user's organizational question, generate 2-3 specific search queries that +would find relevant information about Devr.AI. + +User Question: "{question}" + +Guidelines for search queries: +1. Include "Devr.AI" in each query +2. Focus on official sources (website, GitHub, documentation) +3. Be specific to the type of information requested +4. Avoid overly broad or generic terms + +Generate search queries that would find information about: +- Official website content +- GitHub repositories and documentation +- Project descriptions and goals +- Platform integrations and capabilities + +Format your response as a JSON list of strings: +["query1", "query2", "query3"]""" + +# Enhanced synthesis prompt for organizational responses +ORGANIZATIONAL_SYNTHESIS_PROMPT = """You are the official AI representative for Devr.AI. +Your task is to provide a comprehensive, accurate, and helpful answer to the user's question about +our organization based on the search results provided. + +User Question: "{question}" + +Search Results: +{search_results} + +Instructions: +1. **Accuracy First**: Only use information directly found in the search results +2. **Comprehensive Coverage**: Address all aspects of the user's question if information is available +3. **Professional Tone**: Maintain a friendly but professional tone appropriate for developer relations +4. **Structured Response**: Organize information logically with clear sections if needed +5. **Source Attribution**: If specific claims are made, they should be traceable to the search results +6. **Acknowledge Limitations**: If search results don't contain enough information, be honest about it +7. **Call to Action**: When appropriate, guide users to official resources for more information + +Response Format: +- Start with a direct answer to the main question +- Provide supporting details from search results +- Include relevant examples or specifics when available +- End with helpful next steps or resources if appropriate + +Avoid: +- Making up information not present in search results +- Being overly promotional or sales-like +- Providing outdated information +- Generic or vague responses + +Response:""" + +# Prompt for fallback responses when search results are insufficient +ORGANIZATIONAL_FALLBACK_PROMPT = """You are the AI representative for Devr.AI. The user asked an +organizational question, but the search results didn't provide sufficient information to answer +comprehensively. + +User Question: "{question}" + +Provide a helpful fallback response that: +1. Acknowledges their question +2. Provides any basic information you know about Devr.AI (AI-powered DevRel assistant) +3. Directs them to official sources for complete information +4. Maintains a helpful and professional tone + +Keep the response concise but useful, and avoid making specific claims without evidence. + +Response:""" diff --git a/backend/app/agents/devrel/prompts/react_prompt.py b/backend/app/agents/devrel/prompts/react_prompt.py index b92e5f68..53e9e9e0 100644 --- a/backend/app/agents/devrel/prompts/react_prompt.py +++ b/backend/app/agents/devrel/prompts/react_prompt.py @@ -13,19 +13,28 @@ {tool_results} AVAILABLE ACTIONS: -1. web_search - Search the web for external information -2. faq_handler - Answer common questions from knowledge base -3. onboarding - Welcome new users and guide exploration +1. web_search - Search the web for external information not related to our organization +2. faq_handler - Answer questions using knowledge base AND web search for organizational queries +3. onboarding - Welcome new users and guide exploration 4. github_toolkit - Handle GitHub operations (issues, PRs, repos, docs) 5. complete - Task is finished, format final response +ENHANCED FAQ HANDLER CAPABILITIES: +The faq_handler now has advanced capabilities for organizational queries: +- Detects questions about Devr.AI, our projects, mission, goals, and platforms +- Automatically searches the web for current organizational information +- Synthesizes responses from official sources (website, GitHub, docs) +- Provides static answers for technical FAQ questions +- Returns structured responses with source citations + THINK: Analyze the user's request and current context. What needs to be done? -Then choose ONE action: -- If you need external information or recent updates → web_search -- If this is a common question with a known answer → faq_handler -- If this is a new user needing guidance → onboarding -- If this involves GitHub repositories, issues, PRs, or code → github_toolkit +Choose ONE action based on these guidelines: +- If asking about Devr.AI organization, projects, mission, goals, or "what is..." → faq_handler +- If asking technical questions like "how to contribute", "report bugs" → faq_handler +- If you need external information unrelated to our organization → web_search +- If this is a new user needing general guidance → onboarding +- If this involves GitHub repositories, issues, PRs, or code operations → github_toolkit - If you have enough information to fully answer → complete Respond in this exact format: diff --git a/backend/app/agents/devrel/tools/enhanced_faq_tool.py b/backend/app/agents/devrel/tools/enhanced_faq_tool.py new file mode 100644 index 00000000..43e65b4f --- /dev/null +++ b/backend/app/agents/devrel/tools/enhanced_faq_tool.py @@ -0,0 +1,231 @@ +import logging +import re +from typing import Dict, Any, List +from .search_tool import TavilySearchTool + +logger = logging.getLogger(__name__) + +class EnhancedFAQTool: + """Enhanced FAQ handling tool with web search for organizational queries""" + + def __init__(self, search_tool: TavilySearchTool | None = None): + self.search_tool = search_tool or TavilySearchTool() + + # Static FAQ responses for technical questions + self.technical_faq_responses = { + "how do i contribute": ( + "You can contribute by visiting our GitHub repository, checking open issues, " + "and submitting pull requests. We welcome all types of contributions including " + "code, documentation, and bug reports." + ), + "how do i report a bug": ( + "You can report a bug by opening an issue on our GitHub repository. " + "Please include detailed information about the bug, steps to reproduce it, " + "and your environment." + ), + "how to get started": ( + "To get started with Devr.AI: 1) Check our documentation, 2) Join our Discord " + "community, 3) Explore the GitHub repository, 4) Try contributing to open issues." + ), + "what is langgraph": ( + "LangGraph is a framework for building stateful, multi-actor applications " + "with large language models. We use it to create intelligent agent workflows " + "for our DevRel automation." + ) + } + + # Patterns that indicate organizational queries + self.organizational_patterns = [ + r"what.*is.*(devr\.ai|organization|company|this.*project)", + r"what.*does.*(devr\.ai|organization|company|this.*project).*do", + r"(about|tell me about).*(devr\.ai|organization|company|this.*project)", + r"what.*projects.*(do you have|does.*have|are there)", + r"what.*goals.*(devr\.ai|organization|company)", + r"what.*mission.*(devr\.ai|organization|company)", + r"how.*does.*(devr\.ai|organization|company).*work", + r"what.*platforms.*(devr\.ai|support|integrate)", + r"who.*maintains.*(devr\.ai|organization|company)", + r"what.*kind.*projects.*(devr\.ai|organization|company)" + ] + + def _is_organizational_query(self, question: str) -> bool: + """Check if the question is about the organization using pattern matching""" + question_lower = question.lower().strip() + + for pattern in self.organizational_patterns: + if re.search(pattern, question_lower): + return True + + # Additional keyword-based detection + org_keywords = ["devr.ai", "organization", "company", "projects", "mission", "goals", "about us"] + question_keywords = ["what", "how", "tell me", "explain", "describe"] + + has_org_keyword = any(keyword in question_lower for keyword in org_keywords) + has_question_keyword = any(keyword in question_lower for keyword in question_keywords) + + return has_org_keyword and has_question_keyword + + def _generate_search_queries(self, question: str) -> List[str]: + """Generate targeted search queries for organizational information""" + base_queries = [] + question_lower = question.lower() + + # Project-related queries + if any(word in question_lower for word in ["project", "projects", "work on", "building"]): + base_queries.extend([ + "Devr.AI open source projects", + "Devr.AI GitHub repositories", + "Devr.AI projects developer relations" + ]) + + # Mission/goals queries + if any(word in question_lower for word in ["mission", "goal", "purpose", "about"]): + base_queries.extend([ + "Devr.AI mission developer relations", + "About Devr.AI organization", + "Devr.AI goals AI automation" + ]) + + # Platform/integration queries + if any(word in question_lower for word in ["platform", "platforms", "integrate", "support"]): + base_queries.extend([ + "Devr.AI supported platforms integrations", + "Devr.AI Discord Slack GitHub integration", + "Devr.AI platform compatibility" + ]) + + # General organizational info + if any(word in question_lower for word in ["what is", "tell me about", "how does", "work"]): + base_queries.extend([ + "Devr.AI developer relations AI assistant", + "Devr.AI official website", + "Devr.AI community automation" + ]) + + # If no specific patterns matched, use generic queries + if not base_queries: + base_queries = [ + "Devr.AI developer relations", + "Devr.AI AI automation", + "Devr.AI open source" + ] + + return base_queries + + async def _search_organizational_info(self, question: str) -> Dict[str, Any]: + """Search for organizational information using web search""" + search_queries = self._generate_search_queries(question) + all_results = [] + + for query in search_queries[:3]: # Limit to 3 queries to avoid rate limits + try: + results = await self.search_tool.search(query, max_results=3) + all_results.extend(results) + except Exception as e: + logger.error(f"Error searching for '{query}': {str(e)}") + + # Remove duplicates based on URL + seen_urls = set() + unique_results = [] + for result in all_results: + url = result.get('url', '') + if url not in seen_urls: + seen_urls.add(url) + unique_results.append(result) + + return { + "type": "organizational_info", + "query": question, + "search_queries": search_queries, + "results": unique_results[:5], # Limit to top 5 results + "source": "web_search" + } + + def _synthesize_organizational_response(self, question: str, search_results: List[Dict[str, Any]]) -> str: + """Create a synthesized response from search results""" + if not search_results: + return self._get_fallback_response(question) + + # Extract relevant information from search results + response_parts = [] + sources = [] + + for result in search_results[:3]: # Use top 3 results + title = result.get('title', '') + content = result.get('content', '') + url = result.get('url', '') + + if content and len(content) > 50: # Only use substantial content + # Take first 200 characters of content + snippet = content[:200] + "..." if len(content) > 200 else content + response_parts.append(snippet) + sources.append({"title": title, "url": url}) + + if response_parts: + synthesized_answer = " ".join(response_parts) + return synthesized_answer + else: + return self._get_fallback_response(question) + + def _get_fallback_response(self, question: str) -> str: + """Provide a fallback response when search results are insufficient""" + return ("Devr.AI is an AI-powered Developer Relations assistant that helps open-source communities " + "by automating engagement, issue tracking, and providing intelligent support to developers. " + "For the most up-to-date information about our projects and organization, please visit our " + "official website and GitHub repository.") + + async def get_response(self, question: str) -> Dict[str, Any]: + """Get FAQ response - either static or dynamic based on query type""" + question_lower = question.lower().strip() + + # First, check static technical FAQ responses + for faq_key, response in self.technical_faq_responses.items(): + if self._is_similar_question(question_lower, faq_key): + return { + "type": "static_faq", + "response": response, + "source": "faq_database", + "sources": [] + } + + # Check if this is an organizational query + if self._is_organizational_query(question): + logger.info(f"Detected organizational query: {question}") + search_result = await self._search_organizational_info(question) + + synthesized_response = self._synthesize_organizational_response( + question, search_result.get("results", []) + ) + + # Format sources for response + sources = [] + for result in search_result.get("results", [])[:3]: + if result.get('title') and result.get('url'): + sources.append({ + "title": result.get('title', ''), + "url": result.get('url', '') + }) + + return { + "type": "organizational_faq", + "response": synthesized_response, + "source": "web_search", + "sources": sources, + "search_queries": search_result.get("search_queries", []) + } + + # If no match found, return None to indicate no FAQ response available + return { + "type": "no_match", + "response": None, + "source": "none", + "sources": [] + } + + def _is_similar_question(self, question: str, faq_key: str) -> bool: + """Check if question is similar to FAQ key using simple keyword matching""" + question_words = set(question.split()) + faq_words = set(faq_key.split()) + + common_words = question_words.intersection(faq_words) + return len(common_words) >= 2 # At least 2 common words diff --git a/backend/app/agents/devrel/tools/faq_tool.py b/backend/app/agents/devrel/tools/faq_tool.py index df331a5a..b814cd5e 100644 --- a/backend/app/agents/devrel/tools/faq_tool.py +++ b/backend/app/agents/devrel/tools/faq_tool.py @@ -1,44 +1,70 @@ import logging -from typing import Optional +from typing import Optional, Dict, Any +from .enhanced_faq_tool import EnhancedFAQTool +from .search_tool import TavilySearchTool logger = logging.getLogger(__name__) class FAQTool: - """FAQ handling tool""" - - # TODO: Add FAQ responses from a database to refer organization's FAQ and Repo's FAQ - - def __init__(self): - self.faq_responses = { - "what is devr.ai": "Devr.AI is an AI-powered Developer Relations assistant that helps open-source communities by automating engagement, issue tracking, and providing intelligent support to developers.", - "how do i contribute": "You can contribute by visiting our GitHub repository, checking open issues, and submitting pull requests. We welcome all types of contributions including code, documentation, and bug reports.", - "what platforms does devr.ai support": "Devr.AI integrates with Discord, Slack, GitHub, and can be extended to other platforms. We use these integrations to provide seamless developer support across multiple channels.", - "who maintains devr.ai": "Devr.AI is maintained by an open-source community of developers passionate about improving developer relations and community engagement.", - "how do i report a bug": "You can report a bug by opening an issue on our GitHub repository. Please include detailed information about the bug, steps to reproduce it, and your environment.", - "how to get started": "To get started with Devr.AI: 1) Check our documentation, 2) Join our Discord community, 3) Explore the GitHub repository, 4) Try contributing to open issues.", - "what is langgraph": "LangGraph is a framework for building stateful, multi-actor applications with large language models. We use it to create intelligent agent workflows for our DevRel automation." + """FAQ handling tool with enhanced organizational query support""" + + def __init__(self, search_tool: TavilySearchTool | None = None): + """Initialize FAQ tool with enhanced functionality""" + self.enhanced_faq_tool = EnhancedFAQTool(search_tool) + + # Legacy FAQ responses for backward compatibility + self.legacy_faq_responses = { + "what is devr.ai": ( + "Devr.AI is an AI-powered Developer Relations assistant that helps open-source " + "communities by automating engagement, issue tracking, and providing intelligent " + "support to developers." + ), + "what platforms does devr.ai support": ( + "Devr.AI integrates with Discord, Slack, GitHub, and can be extended to other " + "platforms. We use these integrations to provide seamless developer support " + "across multiple channels." + ), + "who maintains devr.ai": ( + "Devr.AI is maintained by an open-source community of developers passionate " + "about improving developer relations and community engagement." + ) } async def get_response(self, question: str) -> Optional[str]: - """Get FAQ response for a question""" - question_lower = question.lower().strip() + """Get FAQ response for a question - enhanced with web search for organizational queries""" + try: + # Use the enhanced FAQ tool for better responses + enhanced_response = await self.enhanced_faq_tool.get_response(question) + + if enhanced_response and enhanced_response.get("response"): + return enhanced_response.get("response") - # Direct match - if question_lower in self.faq_responses: - return self.faq_responses[question_lower] + # Fallback to legacy responses for backward compatibility + question_lower = question.lower().strip() - # Fuzzy matching - for faq_key, response in self.faq_responses.items(): - if self._is_similar_question(question_lower, faq_key): - return response + # Direct match + if question_lower in self.legacy_faq_responses: + return self.legacy_faq_responses[question_lower] - return None + # Fuzzy matching + for faq_key, response in self.legacy_faq_responses.items(): + if self._is_similar_question(question_lower, faq_key): + return response + + return None + + except Exception as e: + logger.error(f"Error in FAQ tool: {str(e)}") + return None def _is_similar_question(self, question: str, faq_key: str) -> bool: """Check if question is similar to FAQ key""" - # Simple keyword matching - in production, use better similarity question_words = set(question.split()) faq_words = set(faq_key.split()) common_words = question_words.intersection(faq_words) return len(common_words) >= 2 # At least 2 common words + + async def get_enhanced_response(self, question: str) -> Dict[str, Any]: + """Get enhanced FAQ response with metadata for advanced usage""" + return await self.enhanced_faq_tool.get_response(question)