Add LLM endpoints, web frontend, and rate limiting config
Some checks failed
Helm Chart Build / lint-only (push) Has been skipped
Helm Chart Build / build-helm (push) Successful in 9s
Build and Deploy / build-api (push) Successful in 33s
Build and Deploy / build-web (push) Failing after 41s

- Added OpenAI-compatible LLM endpoints to API backend - Introduced web
frontend with Jinja2 templates and static assets - Implemented API proxy
routes in web service - Added sample db.json data for items, users,
orders, reviews, categories, llm_requests - Updated ADC and Helm configs
for separate AI and standard rate limiting - Upgraded FastAPI, Uvicorn,
and added httpx, Jinja2, python-multipart dependencies - Added API
configuration modal and client-side JS for web app
This commit is contained in:
d.viti
2025-10-07 17:29:12 +02:00
parent 78baa5ad21
commit ed660dce5a
16 changed files with 1551 additions and 138 deletions

199
api/db.json Normal file
View File

@@ -0,0 +1,199 @@
{
"items": [
{
"id": 1,
"name": "Gaming Laptop RTX 4090",
"description": "High-performance gaming laptop with RTX 4090",
"price": 2999.99,
"in_stock": true,
"category": "electronics",
"tags": ["gaming", "laptop", "nvidia"]
},
{
"id": 2,
"name": "Mechanical Keyboard RGB",
"description": "Cherry MX switches with RGB backlighting",
"price": 149.99,
"in_stock": true,
"category": "peripherals",
"tags": ["keyboard", "mechanical", "rgb"]
},
{
"id": 3,
"name": "Wireless Gaming Mouse",
"description": "25K DPI wireless gaming mouse",
"price": 89.99,
"in_stock": false,
"category": "peripherals",
"tags": ["mouse", "wireless", "gaming"]
},
{
"id": 4,
"name": "4K Gaming Monitor 32\"",
"description": "144Hz refresh rate, HDR support",
"price": 599.99,
"in_stock": true,
"category": "displays",
"tags": ["monitor", "4k", "gaming"]
},
{
"id": 5,
"name": "Gaming Headset 7.1",
"description": "Surround sound gaming headset with noise cancellation",
"price": 129.99,
"in_stock": true,
"category": "audio",
"tags": ["headset", "audio", "gaming"]
}
],
"users": [
{
"id": 1,
"username": "john_doe",
"email": "john@example.com",
"active": true,
"role": "user",
"created_at": "2024-01-15T10:30:00Z"
},
{
"id": 2,
"username": "jane_smith",
"email": "jane@example.com",
"active": true,
"role": "admin",
"created_at": "2024-02-20T14:22:00Z"
},
{
"id": 3,
"username": "bob_wilson",
"email": "bob@example.com",
"active": false,
"role": "user",
"created_at": "2024-03-10T09:15:00Z"
},
{
"id": 4,
"username": "alice_johnson",
"email": "alice@example.com",
"active": true,
"role": "moderator",
"created_at": "2024-04-05T16:45:00Z"
}
],
"orders": [
{
"id": 1,
"user_id": 1,
"items": [
{"item_id": 1, "quantity": 1, "price": 2999.99},
{"item_id": 2, "quantity": 1, "price": 149.99}
],
"total": 3149.98,
"status": "shipped",
"created_at": "2024-09-15T12:00:00Z"
},
{
"id": 2,
"user_id": 2,
"items": [
{"item_id": 4, "quantity": 2, "price": 599.99}
],
"total": 1199.98,
"status": "delivered",
"created_at": "2024-09-20T15:30:00Z"
},
{
"id": 3,
"user_id": 4,
"items": [
{"item_id": 5, "quantity": 1, "price": 129.99},
{"item_id": 3, "quantity": 1, "price": 89.99}
],
"total": 219.98,
"status": "pending",
"created_at": "2024-10-01T10:15:00Z"
}
],
"reviews": [
{
"id": 1,
"item_id": 1,
"user_id": 1,
"rating": 5,
"comment": "Amazing laptop! Best purchase ever!",
"created_at": "2024-09-20T14:30:00Z"
},
{
"id": 2,
"item_id": 2,
"user_id": 2,
"rating": 4,
"comment": "Great keyboard, but a bit loud",
"created_at": "2024-09-22T09:45:00Z"
},
{
"id": 3,
"item_id": 4,
"user_id": 2,
"rating": 5,
"comment": "Crystal clear display, perfect for gaming",
"created_at": "2024-09-25T18:20:00Z"
},
{
"id": 4,
"item_id": 5,
"user_id": 4,
"rating": 4,
"comment": "Good sound quality, comfortable to wear",
"created_at": "2024-10-02T11:00:00Z"
}
],
"categories": [
{
"id": 1,
"name": "electronics",
"description": "Electronic devices and gadgets"
},
{
"id": 2,
"name": "peripherals",
"description": "Computer peripherals and accessories"
},
{
"id": 3,
"name": "displays",
"description": "Monitors and display devices"
},
{
"id": 4,
"name": "audio",
"description": "Audio devices and accessories"
}
],
"llm_requests": [
{
"id": 1,
"user_id": 1,
"model": "videogame-expert",
"prompt": "What are the best RPG games of 2024?",
"tokens_used": 250,
"timestamp": "2024-10-05T10:00:00Z"
},
{
"id": 2,
"user_id": 2,
"model": "videogame-expert",
"prompt": "Recommend me games similar to Dark Souls",
"tokens_used": 180,
"timestamp": "2024-10-05T11:30:00Z"
},
{
"id": 3,
"user_id": 1,
"model": "videogame-expert",
"prompt": "What's the best strategy for Elden Ring bosses?",
"tokens_used": 320,
"timestamp": "2024-10-05T14:15:00Z"
}
]
}

View File

@@ -1,8 +1,15 @@
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import List, Optional
from pydantic import BaseModel
import uvicorn
from datetime import datetime
from fastapi import FastAPI, HTTPException
import os
import httpx
# OpenAI API configuration
OPENAI_API_BASE = os.getenv("OPENAI_API_BASE", "http://localhost/api")
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY", "your-api-key")
DEFAULT_MODEL = os.getenv("DEFAULT_LLM_MODEL", "your-model-id")
app = FastAPI(
title="API Demo Application",
@@ -119,5 +126,84 @@ async def create_user(user: User):
users_db.append(user_dict)
return user_dict
# LLM endpoints
class LLMRequest(BaseModel):
prompt: str
max_tokens: Optional[int] = 150
temperature: Optional[float] = 0.7
model: Optional[str] = DEFAULT_MODEL
class LLMResponse(BaseModel):
response: str
tokens_used: int
model: str
timestamp: str
@app.post("/llm/chat", response_model=LLMResponse, tags=["LLM"])
async def llm_chat(request: LLMRequest):
"""
LLM Chat endpoint - connects to OpenAI-compatible API (Open WebUI)
This endpoint is rate limited by AI token usage via API7 Gateway
"""
try:
async with httpx.AsyncClient() as client:
response = await client.post(
f"{OPENAI_API_BASE}/chat/completions",
headers={
"Authorization": f"Bearer {OPENAI_API_KEY}",
"Content-Type": "application/json"
},
json={
"model": request.model,
"messages": [
{"role": "user", "content": request.prompt}
],
"max_tokens": request.max_tokens,
"temperature": request.temperature
},
timeout=30.0
)
response.raise_for_status()
data = response.json()
# Extract response and token usage
llm_response = data["choices"][0]["message"]["content"]
tokens_used = data.get("usage", {}).get("total_tokens", 0)
return LLMResponse(
response=llm_response,
tokens_used=tokens_used,
model=request.model,
timestamp=datetime.now().isoformat()
)
except httpx.HTTPStatusError as e:
raise HTTPException(status_code=e.response.status_code, detail=f"OpenAI API error: {e.response.text}")
except Exception as e:
raise HTTPException(status_code=500, detail=f"LLM service error: {str(e)}")
@app.get("/llm/models", tags=["LLM"])
async def list_llm_models():
"""List available LLM models"""
return {
"models": [
{"id": "videogame-expert", "name": "Videogame Expert", "max_tokens": 4096, "provider": "Open WebUI"}
],
"default_model": DEFAULT_MODEL,
"timestamp": datetime.now().isoformat()
}
@app.get("/llm/health", tags=["LLM"])
async def llm_health():
"""LLM service health check"""
return {
"status": "healthy",
"service": "llm-api",
"provider": "Open WebUI",
"endpoint": OPENAI_API_BASE,
"default_model": DEFAULT_MODEL,
"rate_limit": "ai-rate-limiting enabled (100 tokens/60s)",
"timestamp": datetime.now().isoformat()
}
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8001)

View File

@@ -1,3 +1,4 @@
fastapi==0.104.1
uvicorn[standard]==0.24.0
fastapi==0.109.0
uvicorn==0.27.0
pydantic==2.5.0
httpx==0.26.0