Spaces:
Sleeping
Sleeping
| """Phase 5 test script. Run from backend/ with venv activated: | |
| python test_phase5.py | |
| """ | |
| from openai import OpenAI | |
| from app.config.settings import get_settings | |
| from app.llm.cypher_generator import CypherGenerator | |
| from app.llm.guardrails import Guardrails | |
| from app.llm.intent_extractor import IntentExtractor | |
| from app.llm.prompt_manager import PromptManager | |
| from app.llm.template_registry import QueryTemplateRegistry | |
| from app.modeling.graph_schema import GraphSchema | |
| def main(): | |
| s = get_settings() | |
| # Load the exported graph schema | |
| import json | |
| with open("graph_schema.json", "r") as f: | |
| schema = GraphSchema.model_validate(json.load(f)) | |
| print(f"Schema: {len(schema.nodes)} nodes, {len(schema.edges)} edges") | |
| # Setup LLM client (OpenRouter uses OpenAI-compatible API) | |
| llm_client = OpenAI( | |
| api_key=s.openrouter_api_key, | |
| base_url=s.openrouter_base_url, | |
| ) | |
| # Setup components | |
| pm = PromptManager() | |
| guardrails = Guardrails( | |
| blocked_patterns=["ignore previous", "forget your instructions"], | |
| allowed_domains=schema.node_labels(), | |
| ) | |
| extractor = IntentExtractor(pm, llm_client, s.llm_model) | |
| registry = QueryTemplateRegistry(s.cypher_template_dir) | |
| generator = CypherGenerator() | |
| # Test queries | |
| test_queries = [ | |
| "How many sales orders are there?", | |
| "Show me sales order 740506", | |
| "Find the path from sales order 740506 to product B8907367041603", | |
| "What is the total net amount of all billing document headers?", | |
| "Show me all neighbors of business partner 310000108", | |
| ] | |
| for query in test_queries: | |
| print(f"\n{'='*60}") | |
| print(f"Query: {query}") | |
| # 1. Input guardrail | |
| input_check = guardrails.validate_input(query) | |
| if not input_check.passed: | |
| print(f" BLOCKED (input): {input_check.reason}") | |
| continue | |
| # 2. Extract intent | |
| intent = extractor.extract(query, schema) | |
| print(f" Intent: {intent.intent_type} (confidence={intent.confidence})") | |
| print(f" Entities: {intent.entities}") | |
| print(f" Filters: {intent.filters}") | |
| # 3. Domain guardrail | |
| domain_check = guardrails.validate_domain(intent.entities) | |
| if not domain_check.passed: | |
| print(f" BLOCKED (domain): {domain_check.reason}") | |
| continue | |
| # 4. Select template | |
| template = registry.select_template(intent) | |
| print(f" Template: {template.name}") | |
| # 5. Build parameters from intent | |
| params = _build_params(intent, template) | |
| print(f" Params: {params}") | |
| # 6. Generate Cypher | |
| try: | |
| cypher = generator.generate(template, params) | |
| print(f" Cypher: {cypher}") | |
| except ValueError as e: | |
| print(f" FAILED (generation): {e}") | |
| continue | |
| # 7. Output guardrail | |
| output_check = guardrails.validate_output(cypher) | |
| if not output_check.passed: | |
| print(f" BLOCKED (output): {output_check.reason}") | |
| continue | |
| print(" STATUS: Ready for execution") | |
| def _build_params(intent, template) -> dict: | |
| """Map intent fields to template parameters. This is a simple mapper.""" | |
| params = {} | |
| filters = intent.filters | |
| if template.name == "count": | |
| params["label"] = intent.entities[0] if intent.entities else "SalesOrderHeaders" | |
| elif template.name == "find_node": | |
| params["label"] = intent.entities[0] if intent.entities else "SalesOrderHeaders" | |
| # Use first filter as the property filter | |
| if filters: | |
| key = next(iter(filters)) | |
| params["property_name"] = key | |
| params["property_value"] = str(filters[key]) | |
| else: | |
| params["property_name"] = "salesOrder" | |
| params["property_value"] = "" | |
| elif template.name == "find_path": | |
| entities = intent.entities | |
| params["start_label"] = entities[0] if len(entities) > 0 else "SalesOrderHeaders" | |
| params["end_label"] = entities[1] if len(entities) > 1 else "Products" | |
| # Try to extract start/end values from filters | |
| params["start_property"] = filters.get("start_property", "salesOrder") | |
| params["start_value"] = str(filters.get("start_value", "")) | |
| params["end_property"] = filters.get("end_property", "product") | |
| params["end_value"] = str(filters.get("end_value", "")) | |
| elif template.name == "list_neighbors": | |
| params["label"] = intent.entities[0] if intent.entities else "SalesOrderHeaders" | |
| if filters: | |
| key = next(iter(filters)) | |
| params["property_name"] = key | |
| params["property_value"] = str(filters[key]) | |
| else: | |
| params["property_name"] = "businessPartner" | |
| params["property_value"] = "" | |
| elif template.name == "aggregate": | |
| params["label"] = intent.entities[0] if intent.entities else "SalesOrderHeaders" | |
| params["aggregate_function"] = filters.get("aggregate_function", "SUM") | |
| params["property_name"] = filters.get("property_name", "totalNetAmount") | |
| return params | |
| if __name__ == "__main__": | |
| main() | |