Spaces:
Sleeping
Sleeping
| """ | |
| inference.py | |
| ------------ | |
| Pure LLM inference layer for the Codebase Oracle system. | |
| No HTTP server, no FastAPI β just context + query β response. | |
| Responsibilities: | |
| 1. Accept query type + subtype + function/module name | |
| 2. Retrieve relevant context from vector store + call graph | |
| 3. Build correct prompt via prompts/ modules | |
| 4. Call OpenRouter API | |
| 5. Return clean markdown response string | |
| This module is the single entry point that main.py (FastAPI) calls. | |
| Depends on: | |
| - retriever.py | |
| - call_graph.py | |
| - macro_prompts.py | |
| - micro_prompts.py | |
| - cross_module_prompts.py | |
| - openai (OpenRouter is OpenAI-compatible) | |
| - rich (logging only) | |
| Install: | |
| pip install openai rich | |
| """ | |
| import os | |
| from dataclasses import dataclass, field | |
| from dotenv import load_dotenv | |
| from openai import OpenAI | |
| from rich.console import Console | |
| load_dotenv() # loads OPENROUTER_API_KEY from .env file | |
| from retrieve.retrieve import Retriever, QueryType | |
| from store.call_graph import get_call_graph | |
| from prompt.macro_prompt import ( | |
| get_macro_system_prompt, | |
| build_macro_user_prompt, | |
| ) | |
| from prompt.micro_prompt import ( | |
| get_micro_system_prompt, | |
| build_micro_user_prompt, | |
| build_micro_followup_prompt, | |
| ) | |
| from prompt.cross_module import ( | |
| get_cross_module_system_prompt, | |
| build_cross_module_user_prompt, | |
| build_cross_module_followup_prompt, | |
| build_call_graph_payload, | |
| ) | |
| console = Console() | |
| # ββ Constants βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1" | |
| DEFAULT_MODEL = "nvidia/nemotron-3-super-120b-a12b:free" | |
| MAX_TOKENS = 4096 | |
| CONTEXT_MAX_CHARS = 6000 # max chars fed to LLM as retrieved context | |
| N_RESULTS_DEFAULT = 5 # default number of chunks to retrieve | |
| # ββ Request / Response Models βββββββββββββββββββββββββββββββββββββββββββββββββ | |
| class InferenceRequest: | |
| """ | |
| Unified request model for all query types. | |
| Fields: | |
| query_type : "macro" | "micro" | "cross_module" | |
| query : Developer's natural language question | |
| subtype : macro subtype or "" for micro/cross_module | |
| function_name : Target function/method name (micro + cross_module) | |
| class_name : Target class name if method (optional) | |
| module_name : Target module name (macro module_responsibility) | |
| followup : True if this is a follow-up to a previous response | |
| previous_response: Previous LLM response for follow-up context | |
| """ | |
| query_type: str | |
| query: str | |
| subtype: str = "" | |
| function_name: str = "" | |
| class_name: str = "" | |
| module_name: str = "" | |
| followup: bool = False | |
| previous_response: str = "" | |
| class InferenceResponse: | |
| """ | |
| Unified response model returned to FastAPI / caller. | |
| Fields: | |
| success : True if inference succeeded | |
| content : Markdown response string | |
| error : Error message if success is False | |
| metadata : Optional dict with retrieval stats | |
| """ | |
| success: bool | |
| content: str = "" | |
| error: str = "" | |
| metadata: dict = field(default_factory=dict) | |
| # ββ OpenRouter Client βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def _get_client() -> OpenAI: | |
| """ | |
| Build and return an OpenAI-compatible client pointed at OpenRouter. | |
| Reads OPENROUTER_API_KEY from .env file via load_dotenv. | |
| Raises: | |
| EnvironmentError: If OPENROUTER_API_KEY is not set. | |
| """ | |
| api_key = os.getenv("OPENROUTER_API_KEY") | |
| if not api_key: | |
| raise EnvironmentError( | |
| "OPENROUTER_API_KEY environment variable is not set. " | |
| "Add OPENROUTER_API_KEY=your_key to your .env file." | |
| ) | |
| return OpenAI( | |
| base_url=OPENROUTER_BASE_URL, | |
| api_key=api_key, | |
| ) | |
| def _call_llm(system_prompt: str, | |
| user_prompt: str, | |
| model: str = DEFAULT_MODEL) -> str: | |
| """ | |
| Make a single LLM call via OpenRouter and return the response text. | |
| Args: | |
| system_prompt : System prompt string. | |
| user_prompt : User prompt string. | |
| model : OpenRouter model identifier. | |
| Returns: | |
| Raw response text from LLM. | |
| Raises: | |
| Exception: On API errors, propagated to caller. | |
| """ | |
| client = _get_client() | |
| response = client.chat.completions.create( | |
| model=model, | |
| max_tokens=MAX_TOKENS, | |
| messages=[ | |
| {"role": "system", "content": system_prompt}, | |
| {"role": "user", "content": user_prompt}, | |
| ], | |
| ) | |
| return response.choices[0].message.content or "" | |
| # ββ Inference Engine ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| class InferenceEngine: | |
| """ | |
| Core inference engine for the Codebase Oracle. | |
| Retrieves context, builds prompts, calls LLM, returns response. | |
| Single instance shared across all FastAPI requests. | |
| """ | |
| def __init__(self, model: str = DEFAULT_MODEL): | |
| self.model = model | |
| self.retriever = Retriever() | |
| console.print( | |
| f"[green]β[/green] InferenceEngine ready " | |
| f"(model: [cyan]{model}[/cyan])\n" | |
| ) | |
| # ββ Public Entry Point ββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def infer(self, request: InferenceRequest) -> InferenceResponse: | |
| """ | |
| Main entry point. Routes to the correct handler based on query_type. | |
| Args: | |
| request: InferenceRequest with all query parameters. | |
| Returns: | |
| InferenceResponse with markdown content or error. | |
| """ | |
| try: | |
| if request.query_type == QueryType.MACRO: | |
| return self._handle_macro(request) | |
| elif request.query_type == QueryType.MICRO: | |
| return self._handle_micro(request) | |
| elif request.query_type == QueryType.CROSS_MODULE: | |
| return self._handle_cross_module(request) | |
| else: | |
| return InferenceResponse( | |
| success=False, | |
| error=f"Unknown query_type: '{request.query_type}'. " | |
| f"Use 'macro', 'micro', or 'cross_module'." | |
| ) | |
| except EnvironmentError as e: | |
| return InferenceResponse(success=False, error=str(e)) | |
| except Exception as e: | |
| console.print(f"[red]β Inference error: {e}[/red]") | |
| return InferenceResponse( | |
| success=False, | |
| error=f"Inference failed: {str(e)}" | |
| ) | |
| # ββ Macro Handler βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def _handle_macro(self, req: InferenceRequest) -> InferenceResponse: | |
| """Handle macro-level queries (architecture, module, data flow).""" | |
| if not req.subtype: | |
| return InferenceResponse( | |
| success=False, | |
| error="Macro queries require a subtype: " | |
| "'overall_architecture', 'module_responsibility', " | |
| "or 'data_flow'." | |
| ) | |
| # Retrieve class-level chunks β macro uses class collection | |
| chunks = self.retriever.retrieve( | |
| query=req.query, | |
| query_type=QueryType.MACRO, | |
| n_results=N_RESULTS_DEFAULT, | |
| filters={"module": {"$eq": req.module_name}} | |
| if req.module_name and req.subtype == "module_responsibility" | |
| else None, | |
| ) | |
| context = self.retriever.build_context(chunks, CONTEXT_MAX_CHARS) | |
| system = get_macro_system_prompt(req.subtype) | |
| user = build_macro_user_prompt( | |
| subtype=req.subtype, | |
| context=context, | |
| query=req.query, | |
| module_name=req.module_name, | |
| ) | |
| console.print( | |
| f"[cyan]β Macro [{req.subtype}][/cyan] " | |
| f"| {len(chunks)} chunks | {len(context)} chars" | |
| ) | |
| content = _call_llm(system, user, self.model) | |
| return InferenceResponse( | |
| success=True, | |
| content=content, | |
| metadata={ | |
| "query_type": "macro", | |
| "subtype": req.subtype, | |
| "chunks_used": len(chunks), | |
| "context_chars": len(context), | |
| } | |
| ) | |
| # ββ Micro Handler βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def _handle_micro(self, req: InferenceRequest) -> InferenceResponse: | |
| """Handle micro-level queries (function definition, body, consideration).""" | |
| if not req.function_name: | |
| return InferenceResponse( | |
| success=False, | |
| error="Micro queries require a function_name." | |
| ) | |
| # Follow-up path | |
| if req.followup and req.previous_response: | |
| complete_chunks = self.retriever.retrieve_complete_function( | |
| req.function_name, req.class_name, n_results=1 | |
| ) | |
| context = self.retriever.build_context(complete_chunks, CONTEXT_MAX_CHARS) | |
| system = get_micro_system_prompt(followup=True) | |
| user = build_micro_followup_prompt( | |
| previous_response=req.previous_response, | |
| followup_query=req.query, | |
| context=context, | |
| ) | |
| else: | |
| # Primary path β retrieve complete function + complete class context | |
| complete_func_chunks = self.retriever.retrieve_complete_function( | |
| req.function_name, req.class_name, n_results=1 | |
| ) | |
| complete_class_chunks = ( | |
| self.retriever.retrieve_complete_class(req.class_name, n_results=1) | |
| if req.class_name else [] | |
| ) | |
| all_chunks = complete_func_chunks + complete_class_chunks | |
| context = self.retriever.build_context(all_chunks, CONTEXT_MAX_CHARS) | |
| system = get_micro_system_prompt(followup=False) | |
| user = build_micro_user_prompt( | |
| context=context, | |
| query=req.query, | |
| function_name=req.function_name, | |
| class_name=req.class_name, | |
| ) | |
| console.print( | |
| f"[cyan]β Micro[/cyan] " | |
| f"| fn: {req.function_name} " | |
| f"| {len(context)} chars" | |
| ) | |
| content = _call_llm(system, user, self.model) | |
| return InferenceResponse( | |
| success=True, | |
| content=content, | |
| metadata={ | |
| "query_type": "micro", | |
| "function_name": req.function_name, | |
| "class_name": req.class_name, | |
| "context_chars": len(context), | |
| "followup": req.followup, | |
| } | |
| ) | |
| # ββ Cross-Module Handler ββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def _handle_cross_module(self, req: InferenceRequest) -> InferenceResponse: | |
| """Handle cross-module queries (dependency + impact analysis).""" | |
| if not req.function_name: | |
| return InferenceResponse( | |
| success=False, | |
| error="Cross-module queries require a function_name." | |
| ) | |
| # Primary context β the target function | |
| primary_chunks = self.retriever.retrieve_by_function( | |
| req.function_name, req.class_name, n_results=2 | |
| ) | |
| primary_context = self.retriever.build_context( | |
| primary_chunks, CONTEXT_MAX_CHARS // 2 | |
| ) | |
| # Dependency context β callers and callees | |
| dep_chunks = self.retriever.retrieve_dependencies(req.function_name) | |
| dep_context = self.retriever.build_context( | |
| dep_chunks, CONTEXT_MAX_CHARS // 2 | |
| ) | |
| # Call graph data | |
| try: | |
| graph = get_call_graph() | |
| calls = graph.get_calls(req.function_name, req.class_name) | |
| called_by = graph.get_called_by(req.function_name, req.class_name) | |
| impact = graph.get_impact(req.function_name, req.class_name, depth=2) | |
| except FileNotFoundError: | |
| calls, called_by, impact = [], [], {} | |
| console.print( | |
| "[yellow]β call_graph.json not found β " | |
| "proceeding without graph data.[/yellow]" | |
| ) | |
| call_graph_payload = build_call_graph_payload( | |
| function_name=req.function_name, | |
| calls=calls, | |
| called_by=called_by, | |
| impact=impact, | |
| ) | |
| # Follow-up path | |
| if req.followup and req.previous_response: | |
| system = get_cross_module_system_prompt(followup=True) | |
| user = build_cross_module_followup_prompt( | |
| previous_response=req.previous_response, | |
| followup_query=req.query, | |
| primary_context=primary_context, | |
| dependency_context=dep_context, | |
| call_graph_data=call_graph_payload, | |
| ) | |
| else: | |
| system = get_cross_module_system_prompt(followup=False) | |
| user = build_cross_module_user_prompt( | |
| primary_context=primary_context, | |
| dependency_context=dep_context, | |
| call_graph_data=call_graph_payload, | |
| query=req.query, | |
| function_name=req.function_name, | |
| class_name=req.class_name, | |
| ) | |
| console.print( | |
| f"[cyan]β Cross-Module[/cyan] " | |
| f"| fn: {req.function_name} " | |
| f"| deps: {len(dep_chunks)} " | |
| f"| graph nodes: {len(calls) + len(called_by)}" | |
| ) | |
| content = _call_llm(system, user, self.model) | |
| return InferenceResponse( | |
| success=True, | |
| content=content, | |
| metadata={ | |
| "query_type": "cross_module", | |
| "function_name": req.function_name, | |
| "calls": len(calls), | |
| "called_by": len(called_by), | |
| "dep_chunks": len(dep_chunks), | |
| "followup": req.followup, | |
| } | |
| ) | |
| # ββ Singleton Access ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| _engine_instance: InferenceEngine | None = None | |
| def get_engine(model: str = DEFAULT_MODEL) -> InferenceEngine: | |
| """ | |
| Return a singleton InferenceEngine instance. | |
| Creates it on first call, reuses on subsequent calls. | |
| Args: | |
| model: OpenRouter model identifier. | |
| Returns: | |
| Shared InferenceEngine instance. | |
| """ | |
| global _engine_instance | |
| if _engine_instance is None: | |
| _engine_instance = InferenceEngine(model) | |
| return _engine_instance | |
| # ββ Entry Point (manual test) βββββββββββββββββββββββββββββββββββββββββββββββββ | |
| if __name__ == "__main__": | |
| import sys | |
| from rich.markdown import Markdown | |
| console.rule("[bold cyan]Inference Engine β Manual Test[/bold cyan]") | |
| engine = get_engine() | |
| # Test micro query | |
| console.rule("[cyan]Test β Micro Query[/cyan]") | |
| req = InferenceRequest( | |
| query_type="micro", | |
| query="What does this function do and how do I use it?", | |
| function_name=sys.argv[1] if len(sys.argv) > 1 else "parse_file", | |
| class_name="", | |
| ) | |
| resp = engine.infer(req) | |
| if resp.success: | |
| console.print(Markdown(resp.content)) | |
| console.print(f"\n[dim]Metadata: {resp.metadata}[/dim]") | |
| else: | |
| console.print(f"[red]β Error: {resp.error}[/red]") |