Skip to content

Commit c332bde

Browse files
committed
Add API server setup, authentication, and RAG pipeline integration
- Updated README.md to include instructions for starting the API server and setting up authentication. - Introduced debug_import.py for testing import paths and module availability. - Added SQLModel and aiosqlite dependencies to pyproject.toml. - Implemented database initialization and RAG pipeline setup in app.py. - Created authentication logic in auth.py, including API key verification. - Configured API settings in config.py to include an optional API key. - Developed database connection and session management in database.py. - Defined database models for analysis and findings in db_models.py. - Established API routes for analysis and RAG functionalities in routes/analysis.py and routes/rag.py. - Implemented in-memory and PostgreSQL vector store interfaces for RAG in store/base.py, store/memory.py, and store/postgres.py. - Added tests for API functionality and authentication in test_api.py and test_auth.py. - Updated pytest.ini to include necessary python paths for testing. - Modified requirements.txt to include SQLModel and aiosqlite dependencies.
1 parent 42fffe1 commit c332bde

File tree

20 files changed

+824
-104
lines changed

20 files changed

+824
-104
lines changed

README.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,35 @@ python -m omniscient_llm pull codellama:7b-instruct
163163
python -m omniscient_llm recommend --category code
164164
```
165165

166+
### API Server
167+
168+
Start the REST API server:
169+
170+
```bash
171+
# Ensure packages are in PYTHONPATH
172+
export PYTHONPATH=$PYTHONPATH:packages/api/src:packages/core/src:packages/rag/src:packages/llm/src
173+
174+
# Run the server
175+
python -m omniscient_api.cli serve
176+
```
177+
178+
The API will be available at `http://localhost:8000`.
179+
Documentation is available at `http://localhost:8000/docs`.
180+
181+
#### Authentication
182+
183+
The API is protected by an API Key. Set the `OMNISCIENT_API_KEY` environment variable to enable authentication.
184+
185+
```bash
186+
export OMNISCIENT_API_KEY="your-secret-key"
187+
```
188+
189+
Pass the key in the `X-API-Key` header:
190+
191+
```bash
192+
curl -H "X-API-Key: your-secret-key" http://localhost:8000/api/v1/analyze ...
193+
```
194+
166195
---
167196

168197
## 🐳 Docker Deployment

debug_import.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import sys
2+
import os
3+
4+
# Add paths
5+
sys.path.append(os.path.abspath("packages/api/src"))
6+
sys.path.append(os.path.abspath("packages/rag/src"))
7+
8+
try:
9+
from omniscient_api.app import create_app
10+
print("Import create_app successful")
11+
from omniscient_api.auth import verify_api_key
12+
print("Import verify_api_key successful")
13+
import tests.test_auth
14+
print("Import tests.test_auth successful")
15+
except Exception as e:
16+
print(f"Import failed: {e}")
17+
import traceback
18+
traceback.print_exc()

packages/api/pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ dependencies = [
3434
"uvicorn>=0.24.0",
3535
"pydantic>=2.0.0",
3636
"python-multipart>=0.0.6",
37+
"sqlmodel>=0.0.14",
38+
"aiosqlite>=0.19.0",
3739
]
3840

3941
[project.optional-dependencies]

packages/api/src/omniscient_api/app.py

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@
1010
from omniscient_core.logging import get_logger
1111
from .config import APIConfig, load_api_config
1212
from .routes import router as api_router
13+
from .routes.rag import router as rag_router
1314
from .models import HealthResponse
15+
from .database import init_db
1416

1517
logger = get_logger(__name__)
1618

@@ -26,12 +28,46 @@ async def lifespan(app: FastAPI):
2628

2729
logger.info("Starting Omniscient API server")
2830

29-
# Initialize services
30-
# TODO: Initialize LLM client, cache, etc.
31+
# Initialize database
32+
await init_db()
33+
34+
# Initialize RAG Pipeline
35+
try:
36+
from omniscient_rag.pipeline import RAGPipeline
37+
from omniscient_llm.providers.ollama import OllamaProvider
38+
39+
config: APIConfig = app.state.config
40+
41+
# Initialize LLM provider
42+
llm_provider = OllamaProvider(
43+
base_url=config.get_llm_base_url(),
44+
model=config.llm.model,
45+
)
46+
47+
# Initialize RAG
48+
# Use memory:// if no DATABASE_URL provided or if explicitly requested
49+
import os
50+
db_url = os.getenv("DATABASE_URL", "memory://")
51+
52+
pipeline = RAGPipeline.create(
53+
db_url=db_url,
54+
embed_fn=llm_provider,
55+
llm_fn=llm_provider.generate,
56+
)
57+
await pipeline.initialize()
58+
app.state.rag_pipeline = pipeline
59+
logger.info(f"RAG Pipeline initialized with {db_url}")
60+
61+
except Exception as e:
62+
logger.error(f"Failed to initialize RAG pipeline: {e}")
63+
app.state.rag_pipeline = None
3164

3265
yield
3366

3467
# Cleanup
68+
if app.state.rag_pipeline:
69+
await app.state.rag_pipeline.close()
70+
3571
logger.info("Shutting down Omniscient API server")
3672

3773

@@ -97,6 +133,7 @@ async def root():
97133

98134
# Include API routes
99135
app.include_router(api_router, prefix=config.api_prefix)
136+
app.include_router(rag_router, prefix=config.api_prefix)
100137

101138
logger.info(f"API configured at {config.host}:{config.port}{config.api_prefix}")
102139

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
"""Authentication dependencies."""
2+
3+
from typing import Optional
4+
from fastapi import Security, HTTPException, status
5+
from fastapi.security import APIKeyHeader
6+
from .config import get_config
7+
8+
api_key_header = APIKeyHeader(name="X-API-Key", auto_error=False)
9+
10+
async def verify_api_key(
11+
api_key: str = Security(api_key_header),
12+
) -> Optional[str]:
13+
"""Verify API key if configured."""
14+
config = get_config()
15+
16+
# If no API key is configured, allow all requests
17+
if not config.api_key:
18+
return api_key
19+
20+
if not api_key:
21+
raise HTTPException(
22+
status_code=status.HTTP_401_UNAUTHORIZED,
23+
detail="Missing API Key",
24+
)
25+
26+
if api_key != config.api_key:
27+
raise HTTPException(
28+
status_code=status.HTTP_403_FORBIDDEN,
29+
detail="Invalid API Key",
30+
)
31+
32+
return api_key

packages/api/src/omniscient_api/config.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ class APIConfig(BaseSettings):
5555
port: int = Field(default=8000, alias="OMNISCIENT_API_PORT")
5656
workers: int = Field(default=1, alias="OMNISCIENT_API_WORKERS")
5757
debug: bool = Field(default=False, alias="OMNISCIENT_DEBUG")
58+
api_key: Optional[str] = Field(default=None, description="API Key for authentication")
5859

5960
# API settings
6061
api_prefix: str = "/api/v1"
@@ -76,8 +77,8 @@ class APIConfig(BaseSettings):
7677

7778
model_config = SettingsConfigDict(
7879
env_prefix="OMNISCIENT_",
79-
env_file=".env",
80-
extra="ignore",
80+
env_nested_delimiter="__",
81+
case_sensitive=False
8182
)
8283

8384
def get_llm_base_url(self) -> str:
@@ -132,3 +133,11 @@ def load_api_config(config_path: Optional[str] = None) -> APIConfig:
132133
config.llm.model = llm["model"]
133134

134135
return config
136+
137+
138+
from functools import lru_cache
139+
140+
@lru_cache()
141+
def get_config() -> APIConfig:
142+
"""Get cached API configuration."""
143+
return load_api_config()
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from typing import AsyncGenerator
2+
from sqlmodel import SQLModel
3+
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
4+
from sqlalchemy.orm import sessionmaker
5+
6+
DATABASE_URL = "sqlite+aiosqlite:///./omniscient.db"
7+
8+
engine = create_async_engine(DATABASE_URL, echo=True, future=True)
9+
10+
async def init_db():
11+
async with engine.begin() as conn:
12+
# await conn.run_sync(SQLModel.metadata.drop_all)
13+
await conn.run_sync(SQLModel.metadata.create_all)
14+
15+
async def get_session() -> AsyncGenerator[AsyncSession, None]:
16+
async_session = sessionmaker(
17+
engine, class_=AsyncSession, expire_on_commit=False
18+
)
19+
async with async_session() as session:
20+
yield session
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
from typing import Optional, List
2+
from datetime import datetime
3+
from sqlmodel import SQLModel, Field, Relationship, JSON
4+
from omniscient_core import AnalysisStatus, Severity, FindingCategory
5+
import uuid
6+
7+
class AnalysisBase(SQLModel):
8+
repository_url: str
9+
branch: Optional[str] = None
10+
status: AnalysisStatus = AnalysisStatus.PENDING
11+
created_at: datetime = Field(default_factory=datetime.utcnow)
12+
started_at: Optional[datetime] = None
13+
completed_at: Optional[datetime] = None
14+
error: Optional[str] = None
15+
# Storing summary and metrics as JSON for simplicity for now
16+
summary: Optional[dict] = Field(default=None, sa_type=JSON)
17+
metrics: Optional[dict] = Field(default=None, sa_type=JSON)
18+
19+
class Analysis(AnalysisBase, table=True):
20+
id: Optional[str] = Field(default_factory=lambda: str(uuid.uuid4()), primary_key=True)
21+
findings: List["FindingModel"] = Relationship(back_populates="analysis")
22+
23+
class FindingModel(SQLModel, table=True):
24+
id: Optional[str] = Field(default_factory=lambda: str(uuid.uuid4()), primary_key=True)
25+
analysis_id: str = Field(foreign_key="analysis.id")
26+
27+
agent_name: str
28+
severity: Severity
29+
category: FindingCategory
30+
title: str
31+
description: str
32+
file_path: Optional[str] = None
33+
line_start: Optional[int] = None
34+
line_end: Optional[int] = None
35+
code_snippet: Optional[str] = None
36+
suggestions: List[str] = Field(default_factory=list, sa_type=JSON)
37+
metadata_json: dict = Field(default_factory=dict, sa_type=JSON, alias="metadata")
38+
39+
analysis: Analysis = Relationship(back_populates="findings")
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
"""Routes package."""
2+
3+
from .analysis import router

0 commit comments

Comments
 (0)