forked from sagnik/Project_Velocity
feat: Oracle Canvas Component Schema and Qwen 3.6 integration (#31)
Co-authored-by: Sagnik <sagnik7896@gmail.com> Reviewed-on: sagnik/Project_Velocity#31
This commit is contained in:
@@ -7,6 +7,7 @@ from backend.oracle.action_service import oracle_action_service
|
||||
from backend.oracle.persona_service import persona_service
|
||||
from backend.services.mcp_registry import mcp_registry
|
||||
from backend.services.nemoclaw_runtime import nemoclaw_runtime
|
||||
from backend.services.runtime_llm_service import runtime_llm_service
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@@ -38,6 +39,7 @@ async def oracle_health() -> dict:
|
||||
"status": "ok",
|
||||
"persona": await persona_service.health(),
|
||||
"mcp_tools": mcp_registry.list_tools(),
|
||||
"runtime_llm": await runtime_llm_service.list_providers(),
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ from __future__ import annotations
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
from typing import Any, Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, Request, status
|
||||
@@ -32,6 +33,7 @@ from backend.auth.dependencies import get_current_user
|
||||
logger = logging.getLogger("velocity.oracle_templates")
|
||||
|
||||
router = APIRouter()
|
||||
_DEFAULT_TENANT_ID = os.getenv("ORACLE_DEFAULT_TENANT_ID", "tenant_velocity")
|
||||
|
||||
|
||||
# ── Helpers ───────────────────────────────────────────────────────────────────
|
||||
@@ -43,6 +45,10 @@ def _pool(request: Request):
|
||||
return pool
|
||||
|
||||
|
||||
def _tenant_id() -> str:
|
||||
return _DEFAULT_TENANT_ID
|
||||
|
||||
|
||||
# ── Models ────────────────────────────────────────────────────────────────────
|
||||
|
||||
class ChapterCreate(BaseModel):
|
||||
@@ -112,7 +118,7 @@ async def list_template_chapters(
|
||||
GROUP BY ch.chapter_id
|
||||
ORDER BY ch.sort_order ASC
|
||||
""",
|
||||
user.role,
|
||||
_tenant_id(),
|
||||
)
|
||||
return {"chapters": [dict(r) for r in rows]}
|
||||
|
||||
@@ -132,7 +138,7 @@ async def create_template_chapter(
|
||||
VALUES ($1,$2,$3,$4)
|
||||
RETURNING chapter_id, created_at
|
||||
""",
|
||||
user.role, body.name, body.description, body.sort_order,
|
||||
_tenant_id(), body.name, body.description, body.sort_order,
|
||||
)
|
||||
return {"chapter_id": str(row["chapter_id"]), "created_at": str(row["created_at"])}
|
||||
|
||||
@@ -149,7 +155,7 @@ async def list_template_subchapters(
|
||||
pool = _pool(request)
|
||||
async with pool.acquire() as conn:
|
||||
where = "WHERE sub.tenant_id=$1"
|
||||
params: list[Any] = [user.role]
|
||||
params: list[Any] = [_tenant_id()]
|
||||
idx = 2
|
||||
if not include_inactive:
|
||||
where += " AND sub.is_active=TRUE"
|
||||
@@ -186,7 +192,7 @@ async def create_template_subchapter(
|
||||
# Verify chapter exists and belongs to tenant
|
||||
ch_exists = await conn.fetchval(
|
||||
"SELECT 1 FROM oracle_template_chapters WHERE chapter_id=$1 AND tenant_id=$2",
|
||||
body.chapter_id, user.role,
|
||||
body.chapter_id, _tenant_id(),
|
||||
)
|
||||
if not ch_exists:
|
||||
raise HTTPException(404, "Chapter not found")
|
||||
@@ -198,7 +204,7 @@ async def create_template_subchapter(
|
||||
VALUES ($1,$2,$3,$4,$5)
|
||||
RETURNING subchapter_id, created_at
|
||||
""",
|
||||
body.chapter_id, user.role, body.name, body.description, body.sort_order,
|
||||
body.chapter_id, _tenant_id(), body.name, body.description, body.sort_order,
|
||||
)
|
||||
return {"subchapter_id": str(row["subchapter_id"]), "created_at": str(row["created_at"])}
|
||||
|
||||
@@ -218,7 +224,7 @@ async def list_component_templates(
|
||||
):
|
||||
pool = _pool(request)
|
||||
where = "WHERE t.tenant_id=$1"
|
||||
params: list[Any] = [user.role]
|
||||
params: list[Any] = [_tenant_id()]
|
||||
idx = 2
|
||||
|
||||
if chapter_id:
|
||||
@@ -270,7 +276,7 @@ async def create_component_template(
|
||||
) VALUES ($1,$2,$3,$4,$5,$6,$7::jsonb,$8,$9,$10,'draft')
|
||||
RETURNING template_id, created_at
|
||||
""",
|
||||
user.role, body.name, body.category, body.chapter_id, body.subchapter_id,
|
||||
_tenant_id(), body.name, body.category, body.chapter_id, body.subchapter_id,
|
||||
body.accepted_shapes,
|
||||
json.dumps(body.json_template) if body.json_template else None,
|
||||
body.description, body.origin, body.version,
|
||||
@@ -294,7 +300,7 @@ async def get_component_template(
|
||||
LEFT JOIN oracle_template_subchapters sub ON sub.subchapter_id = t.subchapter_id
|
||||
WHERE t.template_id=$1 AND t.tenant_id=$2
|
||||
""",
|
||||
template_id, user.role,
|
||||
template_id, _tenant_id(),
|
||||
)
|
||||
if not row:
|
||||
raise HTTPException(404, "Template not found")
|
||||
@@ -315,7 +321,7 @@ async def add_seed_example(
|
||||
async with pool.acquire() as conn:
|
||||
exists = await conn.fetchval(
|
||||
"SELECT 1 FROM oracle_component_templates WHERE template_id=$1 AND tenant_id=$2",
|
||||
template_id, user.role,
|
||||
template_id, _tenant_id(),
|
||||
)
|
||||
if not exists:
|
||||
raise HTTPException(404, "Template not found")
|
||||
@@ -371,7 +377,7 @@ async def trigger_synthetic_job(
|
||||
async with pool.acquire() as conn:
|
||||
exists = await conn.fetchval(
|
||||
"SELECT 1 FROM oracle_component_templates WHERE template_id=$1 AND tenant_id=$2",
|
||||
body.template_id, user.role,
|
||||
body.template_id, _tenant_id(),
|
||||
)
|
||||
if not exists:
|
||||
raise HTTPException(404, "Template not found")
|
||||
@@ -384,7 +390,7 @@ async def trigger_synthetic_job(
|
||||
) VALUES ($1,$2,$3,$4,$5,$6,$7)
|
||||
RETURNING job_id, status, created_at
|
||||
""",
|
||||
user.role, body.template_id, body.chapter_id, body.subchapter_id,
|
||||
_tenant_id(), body.template_id, body.chapter_id, body.subchapter_id,
|
||||
body.model, body.requested_count, user.user_id,
|
||||
)
|
||||
logger.info(
|
||||
|
||||
140
backend/api/routes_runtime_llm.py
Normal file
140
backend/api/routes_runtime_llm.py
Normal file
@@ -0,0 +1,140 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request, status
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from backend.auth.dependencies import UserPrincipal, get_current_user
|
||||
from backend.services.runtime_llm_service import runtime_llm_service
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
class ChatMessage(BaseModel):
|
||||
role: str = Field(..., pattern="^(system|user|assistant)$")
|
||||
content: str = Field(..., min_length=1)
|
||||
|
||||
|
||||
class RuntimeChatRequest(BaseModel):
|
||||
provider: str | None = None
|
||||
model: str | None = None
|
||||
system_prompt: str | None = None
|
||||
messages: list[ChatMessage]
|
||||
temperature: float = Field(default=0.2, ge=0.0, le=2.0)
|
||||
response_format: str | None = Field(default=None, pattern="^(json|text)$")
|
||||
metadata: dict[str, Any] = Field(default_factory=dict)
|
||||
|
||||
|
||||
class BatchItemRequest(BaseModel):
|
||||
request_id: str
|
||||
messages: list[ChatMessage]
|
||||
system_prompt: str | None = None
|
||||
temperature: float = Field(default=0.2, ge=0.0, le=2.0)
|
||||
response_format: str | None = Field(default=None, pattern="^(json|text)$")
|
||||
metadata: dict[str, Any] = Field(default_factory=dict)
|
||||
|
||||
|
||||
class RuntimeBatchRequest(BaseModel):
|
||||
provider: str | None = None
|
||||
model: str | None = None
|
||||
job_type: str = Field(..., min_length=1, max_length=128)
|
||||
metadata: dict[str, Any] = Field(default_factory=dict)
|
||||
items: list[BatchItemRequest] = Field(..., min_length=1, max_length=128)
|
||||
|
||||
|
||||
def _normalize_user(user: UserPrincipal) -> dict[str, str]:
|
||||
return {
|
||||
"user_id": user.user_id,
|
||||
"role": user.role,
|
||||
}
|
||||
|
||||
|
||||
@router.get("/providers", summary="List configured runtime LLM providers and models")
|
||||
async def list_runtime_providers(_: UserPrincipal = Depends(get_current_user)) -> dict:
|
||||
return {"status": "ok", "data": await runtime_llm_service.list_providers()}
|
||||
|
||||
|
||||
@router.post("/chat", summary="Execute a single runtime LLM chat completion")
|
||||
async def runtime_chat(
|
||||
payload: RuntimeChatRequest,
|
||||
user: UserPrincipal = Depends(get_current_user),
|
||||
) -> dict:
|
||||
response = await runtime_llm_service.chat(
|
||||
provider_id=payload.provider,
|
||||
model=payload.model,
|
||||
system_prompt=payload.system_prompt,
|
||||
messages=[message.model_dump() for message in payload.messages],
|
||||
temperature=payload.temperature,
|
||||
response_format=payload.response_format,
|
||||
metadata={
|
||||
**payload.metadata,
|
||||
"requested_by": _normalize_user(user),
|
||||
},
|
||||
)
|
||||
return {"status": "ok", "data": response}
|
||||
|
||||
|
||||
@router.post("/batch", status_code=status.HTTP_202_ACCEPTED, summary="Submit a persisted runtime LLM batch job")
|
||||
async def runtime_batch(
|
||||
payload: RuntimeBatchRequest,
|
||||
request: Request,
|
||||
user: UserPrincipal = Depends(get_current_user),
|
||||
) -> dict:
|
||||
pool = getattr(request.app.state, "db_pool", None)
|
||||
result = await runtime_llm_service.submit_batch(
|
||||
provider_id=payload.provider,
|
||||
model=payload.model,
|
||||
job_type=payload.job_type,
|
||||
items=[item.model_dump() for item in payload.items],
|
||||
metadata={
|
||||
**payload.metadata,
|
||||
"requested_by": _normalize_user(user),
|
||||
},
|
||||
pool=pool,
|
||||
actor_id=user.user_id,
|
||||
)
|
||||
return {"status": "ok", "data": result}
|
||||
|
||||
|
||||
@router.get("/jobs/{job_id}", summary="Get runtime LLM batch job status")
|
||||
async def get_runtime_job(
|
||||
job_id: str,
|
||||
request: Request,
|
||||
_: UserPrincipal = Depends(get_current_user),
|
||||
) -> dict:
|
||||
pool = getattr(request.app.state, "db_pool", None)
|
||||
job = await runtime_llm_service.get_job(job_id, pool=pool)
|
||||
if not job:
|
||||
raise HTTPException(status_code=404, detail=f"Runtime LLM job '{job_id}' not found.")
|
||||
|
||||
return {
|
||||
"status": "ok",
|
||||
"data": {
|
||||
"job_id": job["job_id"],
|
||||
"status": job["status"],
|
||||
"provider": job["provider"],
|
||||
"model": job["model"],
|
||||
"job_type": job["job_type"],
|
||||
"submitted_count": job["submitted_count"],
|
||||
"completed_count": job["completed_count"],
|
||||
"failed_count": job["failed_count"],
|
||||
"created_at": job["created_at"],
|
||||
"started_at": job["started_at"],
|
||||
"completed_at": job["completed_at"],
|
||||
"metadata": job.get("metadata") or {},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@router.get("/jobs/{job_id}/results", summary="Get runtime LLM batch job item results")
|
||||
async def get_runtime_job_results(
|
||||
job_id: str,
|
||||
request: Request,
|
||||
_: UserPrincipal = Depends(get_current_user),
|
||||
) -> dict:
|
||||
pool = getattr(request.app.state, "db_pool", None)
|
||||
results = await runtime_llm_service.list_job_results(job_id, pool=pool)
|
||||
if results is None:
|
||||
raise HTTPException(status_code=404, detail=f"Runtime LLM job '{job_id}' not found.")
|
||||
return {"status": "ok", "data": results, "meta": {"count": len(results)}}
|
||||
Reference in New Issue
Block a user