The Engineering Knowledge Graph uses Gemini 2.5 Flash to transform natural language questions into structured graph queries. This allows engineers to ask questions like “Who owns the payment service?” or “What breaks if redis goes down?” without writing Cypher or understanding the graph schema.
The LLM is instructed via a comprehensive system prompt that defines all supported query types (chat/llm.py:37-92):
chat/llm.py
system_prompt = """You are an expert at parsing natural language queries about engineering infrastructure and converting them to structured queries.Given a user query, determine the query type and extract relevant parameters.Query types and their parameters:1. "get_node" - Get single node by ID - node_id: string2. "get_nodes" - List nodes by type - node_type: string (service, database, cache, team, deployment) - filters: dict (optional)3. "downstream" - What does X depend on? - node_id: string - edge_types: list (optional: calls, uses, depends_on)4. "upstream" - What depends on X? - node_id: string - edge_types: list (optional: calls, uses, depends_on)5. "blast_radius" - Full impact analysis - node_id: string6. "path" - How does X connect to Y? - from_id: string - to_id: string7. "get_owner" - Who owns X? - node_id: string8. "get_team_assets" - What does team X own? - team_name: string9. "find_by_property" - Find nodes with specific property - property_name: string - property_value: any10. "services_using_database" - What services use database X? - database_name: stringNode ID format: "type:name" (e.g., "service:order-service", "database:users-db")Respond with JSON only:{ "query_type": "...", "parameters": {...}, "confidence": 0.0-1.0, "reasoning": "brief explanation"}"""
{ "query_type": "get_owner", "parameters": { "node_id": "service:payment-service" }, "confidence": 0.95, "reasoning": "User is asking about team ownership of a service"}
{ "query_type": "unknown", "parameters": {}, "confidence": 0.2, "reasoning": "Query is ambiguous or outside supported query types"}
if intent['confidence'] < 0.3: return { 'response': "I'm not sure I understand your question. Could you rephrase it?", 'query_type': 'clarification', 'confidence': intent['confidence'] }
def format_response(self, query_result: Any, user_query: str, query_type: str) -> str: """ Format query results into human-readable response. Args: query_result: Result from graph query user_query: Original user query query_type: Type of query executed Returns: Human-readable response string """ system_prompt = f"""You are a helpful assistant explaining engineering infrastructure query results.The user asked: "{user_query}"Query type: {query_type}Format the following query result into a clear, helpful response:- Use bullet points for lists- Include relevant details like team ownership, ports, etc.- Be concise but informative- If no results, explain why and suggest alternatives- For blast radius queries, emphasize the impact and affected teamsQuery result:{json.dumps(query_result, indent=2)}Provide a natural, conversational response.""" try: response = self.client.models.generate_content( model='gemini-2.5-flash', contents=system_prompt ) return response.text.strip()
The QueryParser maintains session-based context for follow-up questions (chat/query_parser.py:183):
chat/query_parser.py
def _update_context(self, session_id: str, user_query: str, intent: Dict[str, Any], result: Any): """Update conversation context for follow-up queries.""" if session_id not in self.conversation_context: self.conversation_context[session_id] = {} context = self.conversation_context[session_id] context['last_query'] = user_query context['last_intent'] = intent context['last_result'] = result # Extract entities for follow-up queries if isinstance(result, list) and result: context['last_entities'] = [ item.get('name') for item in result if isinstance(item, dict) and 'name' in item ] elif isinstance(result, dict) and 'name' in result: context['last_entities'] = [result['name']]
Example Context Usage:
# First queryUser: "List all databases"Context: {}# Follow-up queryUser: "Who uses the first one?"Context: { 'last_query': 'List all databases', 'last_entities': ['users-db', 'payments-db', 'sessions-db']}# LLM can infer "first one" = "users-db"
Queries with confidence < 0.3 trigger clarification:
chat/query_parser.py
if intent['confidence'] < 0.3: return { 'response': "I'm not sure I understand your question. Could you rephrase it?", 'query_type': 'clarification', 'confidence': intent['confidence'] }
Empty Results
The system suggests alternatives when queries return no results:
chat/query_parser.py
def _handle_empty_result(self, user_query: str, intent: Dict[str, Any]): # Try to suggest alternatives if query_type in ['get_node', 'get_owner']: node_id = params.get('node_id', '') if ':' in node_id: node_type, node_name = node_id.split(':', 1) # Look for similar nodes similar_nodes = self.query_engine.get_nodes(node_type) similar_names = [ node['name'] for node in similar_nodes if node_name.lower() in node['name'].lower() ] if similar_names: suggestions.append( f"Did you mean one of these {node_type}s: {', '.join(similar_names[:5])}?" )
Query Execution Failures
Graph query errors are caught and returned as user-friendly messages:
chat/query_parser.py
try: query_result = self._execute_graph_query(intent)except Exception as e: logger.error(f"Error processing query '{user_query}': {e}") return { 'response': f"I encountered an error processing your query: {str(e)}", 'query_type': 'error', 'confidence': 0.0 }