(request: AIQueryRequest)
| 558 | |
| 559 | @app.post("/api/ai_query") |
| 560 | async def ai_query(request: AIQueryRequest): |
| 561 | if not db_manager: |
| 562 | raise HTTPException(status_code=500, detail="Database not initialized") |
| 563 | |
| 564 | gemini_key = os.getenv("GEMINI_API_KEY") |
| 565 | openai_key = os.getenv("OPENAI_API_KEY") |
| 566 | atlascloud_key = os.getenv("ATLASCLOUD_API_KEY") or os.getenv("ATLAS_CLOUD_API_KEY") |
| 567 | |
| 568 | if not gemini_key and not openai_key and not atlascloud_key: |
| 569 | raise HTTPException( |
| 570 | status_code=400, |
| 571 | detail=( |
| 572 | "AI querying requires GEMINI_API_KEY, OPENAI_API_KEY, or ATLASCLOUD_API_KEY. " |
| 573 | "Please set one in your configuration or .env." |
| 574 | ) |
| 575 | ) |
| 576 | |
| 577 | query = request.query |
| 578 | repo_path = request.repo_path |
| 579 | |
| 580 | 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. |
| 581 | |
| 582 | The graph database represents the codebase structure with the following schema: |
| 583 | ### Nodes & Properties: |
| 584 | * **`Repository`**: `name` (string), `path` (string, absolute path), `is_dependency` (boolean) |
| 585 | * **`File`**: `name` (string), `path` (string, absolute path), `relative_path` (string), `is_dependency` (boolean) |
| 586 | * **`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) |
| 587 | * **`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) |
| 588 | * **`Interface`**, **`Struct`**, **`Enum`**, **`Trait`**: similar properties to Class/Function |
| 589 | * **`Module`**: `name` (string) |
| 590 | |
| 591 | ### Relationships: |
| 592 | * **`CONTAINS`**: (Repository)-[:CONTAINS]->(File), (File)-[:CONTAINS]->(Function), (File)-[:CONTAINS]->(Class) |
| 593 | * **`CALLS`**: (Function)-[:CALLS]->(Function) |
| 594 | * **`IMPORTS`**: (File)-[:IMPORTS]->(Module) |
| 595 | * **`INHERITS`**: (Class)-[:INHERITS]->(Class) |
| 596 | * **`IMPLEMENTS`**: (Class)-[:IMPLEMENTS]->(Interface) |
| 597 | |
| 598 | ### Query Writing Rules: |
| 599 | 1. Only write SELECT/MATCH read-only queries. Do NOT use CREATE, MERGE, DELETE, SET, REMOVE, or other modifying keywords. |
| 600 | 2. Return nodes using the variable names `n` and `m`, and relationships using `rel` where possible. This is critical for the visualizer. |
| 601 | Example: MATCH (n:Class)-[rel:INHERITS]->(m:Class) RETURN n, rel, m |
| 602 | 3. Keep the query simple and efficient. Limit results using `LIMIT 150` unless more are needed. |
| 603 | 4. If the user asks about dependencies or paths, try to return paths using variables, and return nodes/relationships. |
| 604 | 5. Make property matches case-insensitive or fuzzy if appropriate (e.g. using `toLower(n.name) CONTAINS 'auth'` or `n.name =~ '(?i).*auth.*'`). |
| 605 | 6. If the user is querying a specific repository, you can filter by `Repository.path` or `Repository.name` if path is supplied. |
| 606 | |
| 607 | Respond ONLY with a JSON object in this format: |
| 608 | { |
| 609 | "cypher_query": "MATCH ... RETURN n, rel, m LIMIT 100", |
| 610 | "explanation": "Brief description of what this Cypher query does" |
| 611 | } |
| 612 | """ |
| 613 | |
| 614 | try: |
| 615 | # Call LLM to get Cypher |
| 616 | llm_response = call_llm(translation_prompt, query) |
| 617 | parsed_response = parse_json_from_llm(llm_response) |
nothing calls this directly
no test coverage detected