(request: AIQueryRequest)
| 493 | |
| 494 | @app.post("/api/ai_query") |
| 495 | async def ai_query(request: AIQueryRequest): |
| 496 | if not db_manager: |
| 497 | raise HTTPException(status_code=500, detail="Database not initialized") |
| 498 | |
| 499 | gemini_key = os.getenv("GEMINI_API_KEY") |
| 500 | openai_key = os.getenv("OPENAI_API_KEY") |
| 501 | |
| 502 | if not gemini_key and not openai_key: |
| 503 | raise HTTPException( |
| 504 | status_code=400, |
| 505 | detail="AI querying requires GEMINI_API_KEY or OPENAI_API_KEY environment variable. Please set it in your configuration or .env." |
| 506 | ) |
| 507 | |
| 508 | query = request.query |
| 509 | repo_path = request.repo_path |
| 510 | |
| 511 | translation_prompt = """You are an expert graph database administrator. Translate the user's natural language question about a codebase into a valid read-only Cypher query. |
| 512 | |
| 513 | The graph database represents the codebase structure with the following schema: |
| 514 | ### Nodes & Properties: |
| 515 | * **`Repository`**: `name` (string), `path` (string, absolute path), `is_dependency` (boolean) |
| 516 | * **`File`**: `name` (string), `path` (string, absolute path), `relative_path` (string), `is_dependency` (boolean) |
| 517 | * **`Function`**: `name` (string), `path` (string, absolute path), `line_number` (int), `end_line` (int), `args` (list of strings), `cyclomatic_complexity` (int), `decorators` (list of strings), `lang` (string), `source` (string), `is_dependency` (boolean) |
| 518 | * **`Class`**: `name` (string), `path` (string, absolute path), `line_number` (int), `end_line` (int), `bases` (list of strings), `decorators` (list of strings), `lang` (string), `source` (string), `is_dependency` (boolean) |
| 519 | * **`Interface`**, **`Struct`**, **`Enum`**, **`Trait`**: similar properties to Class/Function |
| 520 | * **`Module`**: `name` (string) |
| 521 | |
| 522 | ### Relationships: |
| 523 | * **`CONTAINS`**: (Repository)-[:CONTAINS]->(File), (File)-[:CONTAINS]->(Function), (File)-[:CONTAINS]->(Class) |
| 524 | * **`CALLS`**: (Function)-[:CALLS]->(Function) |
| 525 | * **`IMPORTS`**: (File)-[:IMPORTS]->(Module) |
| 526 | * **`INHERITS`**: (Class)-[:INHERITS]->(Class) |
| 527 | * **`IMPLEMENTS`**: (Class)-[:IMPLEMENTS]->(Interface) |
| 528 | |
| 529 | ### Query Writing Rules: |
| 530 | 1. Only write SELECT/MATCH read-only queries. Do NOT use CREATE, MERGE, DELETE, SET, REMOVE, or other modifying keywords. |
| 531 | 2. Return nodes using the variable names `n` and `m`, and relationships using `rel` where possible. This is critical for the visualizer. |
| 532 | Example: MATCH (n:Class)-[rel:INHERITS]->(m:Class) RETURN n, rel, m |
| 533 | 3. Keep the query simple and efficient. Limit results using `LIMIT 150` unless more are needed. |
| 534 | 4. If the user asks about dependencies or paths, try to return paths using variables, and return nodes/relationships. |
| 535 | 5. Make property matches case-insensitive or fuzzy if appropriate (e.g. using `toLower(n.name) CONTAINS 'auth'` or `n.name =~ '(?i).*auth.*'`). |
| 536 | 6. If the user is querying a specific repository, you can filter by `Repository.path` or `Repository.name` if path is supplied. |
| 537 | |
| 538 | Respond ONLY with a JSON object in this format: |
| 539 | { |
| 540 | "cypher_query": "MATCH ... RETURN n, rel, m LIMIT 100", |
| 541 | "explanation": "Brief description of what this Cypher query does" |
| 542 | } |
| 543 | """ |
| 544 | |
| 545 | try: |
| 546 | # Call LLM to get Cypher |
| 547 | llm_response = call_llm(translation_prompt, query) |
| 548 | parsed_response = parse_json_from_llm(llm_response) |
| 549 | cypher_query = parsed_response.get("cypher_query", "") |
| 550 | translation_explanation = parsed_response.get("explanation", "") |
| 551 | |
| 552 | if not cypher_query: |
nothing calls this directly
no test coverage detected