feat: custom Oracle AI robot logo
This commit is contained in:
373
backend/app.py
373
backend/app.py
@@ -1,10 +1,11 @@
|
||||
"""
|
||||
OCI CIS AI Agent - Backend API v2
|
||||
FastAPI with JWT auth, TOTP MFA, RBAC, OCI GenAI integration (Cohere/Meta/xAI/Google),
|
||||
MCP Server registry, Autonomous DB vector storage, CIS reports, chat agent, audit log.
|
||||
OCI CIS AI Agent - Backend API v1.1
|
||||
FastAPI with JWT auth, TOTP MFA, RBAC, OCI GenAI (exact SDK pattern),
|
||||
OCI Account Explorer, MCP Server registry with VectorDB tool integration,
|
||||
Autonomous DB vector storage, CIS reports, chat agent, audit log.
|
||||
"""
|
||||
import os, json, uuid, hashlib, hmac, time, base64, struct, secrets, subprocess
|
||||
import shutil, asyncio, sqlite3, logging, socket, re, importlib, sys
|
||||
import shutil, asyncio, sqlite3, logging, socket, re
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
from typing import Optional, List, Dict, Any
|
||||
@@ -30,6 +31,7 @@ OCI_DIR = DATA / "oci_configs"
|
||||
REPORTS = DATA / "reports"
|
||||
MCP_DIR = DATA / "mcp_servers"
|
||||
WALLET_DIR = DATA / "wallets"
|
||||
VERSION = "1.1"
|
||||
|
||||
for d in [DATA, OCI_DIR, REPORTS, MCP_DIR, WALLET_DIR]:
|
||||
d.mkdir(parents=True, exist_ok=True)
|
||||
@@ -37,7 +39,7 @@ for d in [DATA, OCI_DIR, REPORTS, MCP_DIR, WALLET_DIR]:
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
log = logging.getLogger("agent")
|
||||
|
||||
app = FastAPI(title="OCI CIS AI Agent", version="2.0.0")
|
||||
app = FastAPI(title="OCI CIS AI Agent", version=VERSION)
|
||||
app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_credentials=True,
|
||||
allow_methods=["*"], allow_headers=["*"])
|
||||
security = HTTPBearer()
|
||||
@@ -104,16 +106,21 @@ def init_db():
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS genai_configs (
|
||||
id TEXT PRIMARY KEY, user_id TEXT NOT NULL,
|
||||
name TEXT NOT NULL DEFAULT 'default',
|
||||
oci_config_id TEXT NOT NULL,
|
||||
model_id TEXT NOT NULL,
|
||||
model_ocid TEXT,
|
||||
compartment_id TEXT NOT NULL,
|
||||
genai_region TEXT NOT NULL,
|
||||
endpoint TEXT NOT NULL,
|
||||
serving_type TEXT DEFAULT 'ON_DEMAND',
|
||||
endpoint_id TEXT,
|
||||
temperature REAL DEFAULT 0.7,
|
||||
max_tokens INTEGER DEFAULT 2048,
|
||||
top_p REAL DEFAULT 0.9,
|
||||
top_k INTEGER DEFAULT -1,
|
||||
dedicated_endpoint_id TEXT,
|
||||
temperature REAL DEFAULT 1,
|
||||
max_tokens INTEGER DEFAULT 6000,
|
||||
top_p REAL DEFAULT 0.95,
|
||||
top_k INTEGER DEFAULT 1,
|
||||
frequency_penalty REAL DEFAULT 0,
|
||||
presence_penalty REAL DEFAULT 0,
|
||||
is_default INTEGER DEFAULT 0,
|
||||
created_at TEXT DEFAULT (datetime('now')),
|
||||
FOREIGN KEY (oci_config_id) REFERENCES oci_configs(id)
|
||||
@@ -146,6 +153,8 @@ def init_db():
|
||||
server_type TEXT NOT NULL DEFAULT 'stdio',
|
||||
command TEXT, args TEXT, env_vars TEXT,
|
||||
url TEXT, module_path TEXT,
|
||||
tools TEXT,
|
||||
linked_adb_id TEXT,
|
||||
is_active INTEGER DEFAULT 1,
|
||||
created_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
@@ -187,6 +196,12 @@ def _make_token(uid,role,sid):
|
||||
def _enc(v): return base64.b64encode(v.encode()).decode()
|
||||
def _dec(v): return base64.b64decode(v.encode()).decode()
|
||||
|
||||
# ── OCI SDK Client Helper ─────────────────────────────────────────────────────
|
||||
def _get_oci_config(oci_config_id: str) -> dict:
|
||||
import oci
|
||||
config_path = str(OCI_DIR / oci_config_id / "config")
|
||||
return oci.config.from_file(config_path, "DEFAULT")
|
||||
|
||||
# ── Auth deps ─────────────────────────────────────────────────────────────────
|
||||
async def current_user(cred: HTTPAuthorizationCredentials = Depends(security)):
|
||||
try: p = pyjwt.decode(cred.credentials, APP_SECRET, algorithms=[JWT_ALG])
|
||||
@@ -221,13 +236,17 @@ class ChangePwReq(BaseModel):
|
||||
class UserUpdateReq(BaseModel):
|
||||
email: Optional[str] = None; role: Optional[str] = None; is_active: Optional[bool] = None
|
||||
class ChatMsg(BaseModel):
|
||||
message: str; session_id: Optional[str] = None; model_id: Optional[str] = None; genai_config_id: Optional[str] = None
|
||||
message: str; session_id: Optional[str] = None; genai_config_id: Optional[str] = None
|
||||
class RunReportReq(BaseModel):
|
||||
config_id: str; mcp_server_id: Optional[str] = None; regions: Optional[List[str]] = None
|
||||
class GenAIConfigReq(BaseModel):
|
||||
oci_config_id: str; model_id: str; compartment_id: str; genai_region: str
|
||||
serving_type: str = "ON_DEMAND"; endpoint_id: Optional[str] = None
|
||||
temperature: float = 0.7; max_tokens: int = 2048; top_p: float = 0.9; top_k: int = -1
|
||||
name: str = "default"
|
||||
oci_config_id: str; model_id: str; model_ocid: Optional[str] = None
|
||||
compartment_id: str; genai_region: str
|
||||
endpoint: Optional[str] = None
|
||||
serving_type: str = "ON_DEMAND"; dedicated_endpoint_id: Optional[str] = None
|
||||
temperature: float = 1; max_tokens: int = 6000; top_p: float = 0.95
|
||||
top_k: int = 1; frequency_penalty: float = 0; presence_penalty: float = 0
|
||||
is_default: bool = False
|
||||
class ADBVectorReq(BaseModel):
|
||||
config_name: str; dsn: str; username: str; password: str
|
||||
@@ -235,7 +254,9 @@ class ADBVectorReq(BaseModel):
|
||||
class MCPServerReq(BaseModel):
|
||||
name: str; description: Optional[str] = None; server_type: str = "stdio"
|
||||
command: Optional[str] = None; args: Optional[List[str]] = None
|
||||
env_vars: Optional[Dict[str,str]] = None; url: Optional[str] = None; module_path: Optional[str] = None
|
||||
env_vars: Optional[Dict[str,str]] = None; url: Optional[str] = None
|
||||
module_path: Optional[str] = None; tools: Optional[List[str]] = None
|
||||
linked_adb_id: Optional[str] = None
|
||||
|
||||
# ── Auth endpoints ────────────────────────────────────────────────────────────
|
||||
@app.post("/api/auth/login")
|
||||
@@ -314,9 +335,9 @@ async def update_user(uid: str, req: UserUpdateReq, adm=Depends(require("admin")
|
||||
if req.role not in ("admin","user","viewer"): raise HTTPException(400, "Role inválida")
|
||||
sets.append("role=?"); vals.append(req.role)
|
||||
if req.is_active is not None: sets.append("is_active=?"); vals.append(int(req.is_active))
|
||||
if sets: vals.append(uid); c_ = db()
|
||||
with db() as c:
|
||||
if sets: c.execute(f"UPDATE users SET {','.join(sets)} WHERE id=?", vals)
|
||||
if sets:
|
||||
vals.append(uid)
|
||||
with db() as c: c.execute(f"UPDATE users SET {','.join(sets)} WHERE id=?", vals)
|
||||
_audit(adm["id"], adm["username"], "update_user", uid)
|
||||
return {"ok": True}
|
||||
|
||||
@@ -340,9 +361,11 @@ async def save_oci(
|
||||
kp = cdir / "oci_api_key.pem"
|
||||
kp.write_bytes(await private_key.read()); kp.chmod(0o600)
|
||||
if public_key: (cdir / "oci_api_key_public.pem").write_bytes(await public_key.read())
|
||||
(cdir / "config").write_text(
|
||||
cfg_file = cdir / "config"
|
||||
cfg_file.write_text(
|
||||
f"[DEFAULT]\nuser={user_ocid}\nfingerprint={fingerprint}\n"
|
||||
f"tenancy={tenancy_ocid}\nregion={region}\nkey_file={kp}\n")
|
||||
cfg_file.chmod(0o600)
|
||||
with db() as c:
|
||||
c.execute("INSERT INTO oci_configs (id,user_id,tenancy_name,tenancy_ocid,user_ocid,fingerprint,region,key_file_path,compartment_id) VALUES (?,?,?,?,?,?,?,?,?)",
|
||||
(cid, u["id"], tenancy_name, tenancy_ocid, user_ocid, fingerprint, region, str(kp), compartment_id or None))
|
||||
@@ -375,11 +398,98 @@ async def test_oci(cid: str, u=Depends(require("admin","user"))):
|
||||
r = subprocess.run(["oci","iam","region","list","--config-file",str(cp),"--output","json"], capture_output=True, text=True, timeout=30)
|
||||
if r.returncode == 0: return {"status":"success","message":"Conexão OK","data":json.loads(r.stdout)}
|
||||
return {"status":"error","message":r.stderr[:500]}
|
||||
except FileNotFoundError: return {"status":"error","message":"OCI CLI não instalado. Instale via admin panel."}
|
||||
except FileNotFoundError: return {"status":"error","message":"OCI CLI não disponível no container."}
|
||||
except subprocess.TimeoutExpired: return {"status":"error","message":"Timeout na conexão"}
|
||||
except Exception as e: return {"status":"error","message":str(e)}
|
||||
|
||||
# ── OCI GenAI Config ──────────────────────────────────────────────────────────
|
||||
# ── OCI Account Explorer ──────────────────────────────────────────────────────
|
||||
@app.get("/api/oci/explore/{cid}/compartments")
|
||||
async def explore_compartments(cid: str, u=Depends(current_user)):
|
||||
try:
|
||||
import oci
|
||||
config = _get_oci_config(cid)
|
||||
identity = oci.identity.IdentityClient(config)
|
||||
with db() as c:
|
||||
cfg = c.execute("SELECT tenancy_ocid,compartment_id FROM oci_configs WHERE id=?", (cid,)).fetchone()
|
||||
root = cfg["compartment_id"] or cfg["tenancy_ocid"]
|
||||
comps = identity.list_compartments(root, compartment_id_in_subtree=True).data
|
||||
return [{"id":cp.id,"name":cp.name,"lifecycle_state":cp.lifecycle_state,"description":cp.description or ""} for cp in comps if cp.lifecycle_state == "ACTIVE"]
|
||||
except Exception as e:
|
||||
return {"error": str(e)[:500]}
|
||||
|
||||
@app.get("/api/oci/explore/{cid}/regions")
|
||||
async def explore_regions(cid: str, u=Depends(current_user)):
|
||||
try:
|
||||
import oci
|
||||
config = _get_oci_config(cid)
|
||||
identity = oci.identity.IdentityClient(config)
|
||||
with db() as c:
|
||||
cfg = c.execute("SELECT tenancy_ocid FROM oci_configs WHERE id=?", (cid,)).fetchone()
|
||||
regions = identity.list_region_subscriptions(cfg["tenancy_ocid"]).data
|
||||
return [{"name":r.region_name,"key":r.region_key,"status":r.status,"is_home":r.is_home_region} for r in regions]
|
||||
except Exception as e:
|
||||
return {"error": str(e)[:500]}
|
||||
|
||||
@app.get("/api/oci/explore/{cid}/vcns")
|
||||
async def explore_vcns(cid: str, compartment_id: str = Query(None), u=Depends(current_user)):
|
||||
try:
|
||||
import oci
|
||||
config = _get_oci_config(cid)
|
||||
vn = oci.core.VirtualNetworkClient(config)
|
||||
with db() as c:
|
||||
cfg = c.execute("SELECT tenancy_ocid,compartment_id FROM oci_configs WHERE id=?", (cid,)).fetchone()
|
||||
comp = compartment_id or cfg["compartment_id"] or cfg["tenancy_ocid"]
|
||||
vcns = vn.list_vcns(comp).data
|
||||
return [{"id":v.id,"display_name":v.display_name,"cidr_blocks":v.cidr_blocks,"lifecycle_state":v.lifecycle_state} for v in vcns]
|
||||
except Exception as e:
|
||||
return {"error": str(e)[:500]}
|
||||
|
||||
@app.get("/api/oci/explore/{cid}/instances")
|
||||
async def explore_instances(cid: str, compartment_id: str = Query(None), u=Depends(current_user)):
|
||||
try:
|
||||
import oci
|
||||
config = _get_oci_config(cid)
|
||||
compute = oci.core.ComputeClient(config)
|
||||
with db() as c:
|
||||
cfg = c.execute("SELECT tenancy_ocid,compartment_id FROM oci_configs WHERE id=?", (cid,)).fetchone()
|
||||
comp = compartment_id or cfg["compartment_id"] or cfg["tenancy_ocid"]
|
||||
insts = compute.list_instances(comp).data
|
||||
return [{"id":i.id,"display_name":i.display_name,"shape":i.shape,"lifecycle_state":i.lifecycle_state,"region":i.region,"time_created":str(i.time_created)} for i in insts]
|
||||
except Exception as e:
|
||||
return {"error": str(e)[:500]}
|
||||
|
||||
@app.get("/api/oci/explore/{cid}/databases")
|
||||
async def explore_databases(cid: str, compartment_id: str = Query(None), u=Depends(current_user)):
|
||||
try:
|
||||
import oci
|
||||
config = _get_oci_config(cid)
|
||||
db_client = oci.database.DatabaseClient(config)
|
||||
with db() as c:
|
||||
cfg = c.execute("SELECT tenancy_ocid,compartment_id FROM oci_configs WHERE id=?", (cid,)).fetchone()
|
||||
comp = compartment_id or cfg["compartment_id"] or cfg["tenancy_ocid"]
|
||||
adbs = db_client.list_autonomous_databases(comp).data
|
||||
return [{"id":a.id,"display_name":a.display_name,"db_name":a.db_name,"lifecycle_state":a.lifecycle_state,
|
||||
"cpu_core_count":a.cpu_core_count,"data_storage_size_in_tbs":a.data_storage_size_in_tbs,
|
||||
"is_free_tier":a.is_free_tier} for a in adbs]
|
||||
except Exception as e:
|
||||
return {"error": str(e)[:500]}
|
||||
|
||||
@app.get("/api/oci/explore/{cid}/buckets")
|
||||
async def explore_buckets(cid: str, compartment_id: str = Query(None), u=Depends(current_user)):
|
||||
try:
|
||||
import oci
|
||||
config = _get_oci_config(cid)
|
||||
os_client = oci.object_storage.ObjectStorageClient(config)
|
||||
namespace = os_client.get_namespace().data
|
||||
with db() as c:
|
||||
cfg = c.execute("SELECT tenancy_ocid,compartment_id FROM oci_configs WHERE id=?", (cid,)).fetchone()
|
||||
comp = compartment_id or cfg["compartment_id"] or cfg["tenancy_ocid"]
|
||||
buckets = os_client.list_buckets(namespace, comp).data
|
||||
return [{"name":b.name,"namespace":b.namespace,"time_created":str(b.time_created)} for b in buckets]
|
||||
except Exception as e:
|
||||
return {"error": str(e)[:500]}
|
||||
|
||||
# ── OCI GenAI Config & Chat ───────────────────────────────────────────────────
|
||||
@app.get("/api/genai/models")
|
||||
async def list_genai_models(u=Depends(current_user)):
|
||||
return {"models": GENAI_MODELS, "regions": GENAI_REGIONS}
|
||||
@@ -387,15 +497,20 @@ async def list_genai_models(u=Depends(current_user)):
|
||||
@app.post("/api/genai/config")
|
||||
async def save_genai(req: GenAIConfigReq, u=Depends(require("admin","user"))):
|
||||
gid = str(uuid.uuid4())
|
||||
ep = req.endpoint or f"https://inference.generativeai.{req.genai_region}.oci.oraclecloud.com"
|
||||
with db() as c:
|
||||
if req.is_default:
|
||||
c.execute("UPDATE genai_configs SET is_default=0 WHERE user_id=?", (u["id"],))
|
||||
c.execute(
|
||||
"INSERT INTO genai_configs (id,user_id,oci_config_id,model_id,compartment_id,genai_region,serving_type,endpoint_id,temperature,max_tokens,top_p,top_k,is_default) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
||||
(gid, u["id"], req.oci_config_id, req.model_id, req.compartment_id, req.genai_region,
|
||||
req.serving_type, req.endpoint_id, req.temperature, req.max_tokens, req.top_p, req.top_k, int(req.is_default)))
|
||||
"""INSERT INTO genai_configs (id,user_id,name,oci_config_id,model_id,model_ocid,compartment_id,
|
||||
genai_region,endpoint,serving_type,dedicated_endpoint_id,temperature,max_tokens,top_p,top_k,
|
||||
frequency_penalty,presence_penalty,is_default) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
|
||||
(gid, u["id"], req.name, req.oci_config_id, req.model_id, req.model_ocid,
|
||||
req.compartment_id, req.genai_region, ep, req.serving_type, req.dedicated_endpoint_id,
|
||||
req.temperature, req.max_tokens, req.top_p, req.top_k,
|
||||
req.frequency_penalty, req.presence_penalty, int(req.is_default)))
|
||||
_audit(u["id"], u["username"], "save_genai_config", gid, req.model_id)
|
||||
return {"id": gid, "model_id": req.model_id}
|
||||
return {"id": gid, "model_id": req.model_id, "endpoint": ep}
|
||||
|
||||
@app.get("/api/genai/configs")
|
||||
async def list_genai(u=Depends(current_user)):
|
||||
@@ -411,79 +526,123 @@ async def del_genai(gid: str, u=Depends(require("admin","user"))):
|
||||
|
||||
@app.post("/api/genai/test/{gid}")
|
||||
async def test_genai(gid: str, u=Depends(require("admin","user"))):
|
||||
"""Test GenAI connection by sending a simple prompt"""
|
||||
with db() as c:
|
||||
gc = c.execute("SELECT g.*,o.key_file_path,o.tenancy_ocid,o.user_ocid,o.fingerprint FROM genai_configs g JOIN oci_configs o ON g.oci_config_id=o.id WHERE g.id=?",(gid,)).fetchone()
|
||||
gc = c.execute("SELECT * FROM genai_configs WHERE id=?",(gid,)).fetchone()
|
||||
if not gc: raise HTTPException(404)
|
||||
try:
|
||||
resp = await _call_genai(dict(gc), "Say 'connection successful' in one sentence.")
|
||||
return {"status":"success","message":"GenAI OK","response":resp[:200]}
|
||||
resp = _call_genai(dict(gc), "Say 'connection successful' in one short sentence.")
|
||||
return {"status":"success","message":"GenAI OK","response":resp[:300]}
|
||||
except Exception as e:
|
||||
return {"status":"error","message":str(e)[:500]}
|
||||
|
||||
async def _call_genai(gc: dict, message: str, history: list = None) -> str:
|
||||
"""Call OCI Generative AI chat endpoint using OCI SDK"""
|
||||
try:
|
||||
import oci
|
||||
except ImportError:
|
||||
return "❌ OCI SDK não instalado. Execute: pip install oci"
|
||||
def _call_genai(gc: dict, message: str, history: list = None) -> str:
|
||||
"""
|
||||
Call OCI Generative AI using the exact SDK pattern from chat_demo.py.
|
||||
Uses oci.generative_ai_inference with proper Message/TextContent objects.
|
||||
"""
|
||||
import oci
|
||||
|
||||
config_path = str(Path(gc["key_file_path"]).parent / "config")
|
||||
try:
|
||||
config = oci.config.from_file(config_path, "DEFAULT")
|
||||
except Exception:
|
||||
config = {
|
||||
"user": gc["user_ocid"], "tenancy": gc["tenancy_ocid"],
|
||||
"fingerprint": gc["fingerprint"], "key_file": gc["key_file_path"],
|
||||
"region": gc["genai_region"]
|
||||
}
|
||||
# Load OCI config from stored credentials (same as ~/.oci/config)
|
||||
config_path = str(OCI_DIR / gc["oci_config_id"] / "config")
|
||||
config = oci.config.from_file(config_path, "DEFAULT")
|
||||
|
||||
endpoint = f"https://inference.generativeai.{gc['genai_region']}.oci.oraclecloud.com"
|
||||
client = oci.generative_ai_inference.GenerativeAiInferenceClient(config, service_endpoint=endpoint)
|
||||
# Service endpoint - built from region
|
||||
endpoint = gc["endpoint"]
|
||||
|
||||
# Create inference client with retry strategy and timeout
|
||||
generative_ai_inference_client = oci.generative_ai_inference.GenerativeAiInferenceClient(
|
||||
config=config,
|
||||
service_endpoint=endpoint,
|
||||
retry_strategy=oci.retry.NoneRetryStrategy(),
|
||||
timeout=(10, 240)
|
||||
)
|
||||
|
||||
# Build ChatDetails
|
||||
chat_detail = oci.generative_ai_inference.models.ChatDetails()
|
||||
|
||||
# Determine API format from model catalog
|
||||
model_info = GENAI_MODELS.get(gc["model_id"], {})
|
||||
api_format = model_info.get("api_format", "GENERIC")
|
||||
|
||||
if gc["serving_type"] == "DEDICATED" and gc.get("endpoint_id"):
|
||||
serving_mode = oci.generative_ai_inference.models.DedicatedServingMode(endpoint_id=gc["endpoint_id"])
|
||||
else:
|
||||
serving_mode = oci.generative_ai_inference.models.OnDemandServingMode(model_id=gc["model_id"])
|
||||
|
||||
if api_format == "COHERE":
|
||||
chat_request = oci.generative_ai_inference.models.CohereChatRequest(
|
||||
message=message, max_tokens=gc.get("max_tokens", 2048),
|
||||
temperature=gc.get("temperature", 0.7), top_p=gc.get("top_p", 0.9),
|
||||
api_format="COHERE"
|
||||
)
|
||||
# ── Cohere models (CohereChatRequest) ──
|
||||
chat_request = oci.generative_ai_inference.models.CohereChatRequest()
|
||||
chat_request.api_format = oci.generative_ai_inference.models.BaseChatRequest.API_FORMAT_COHERE
|
||||
chat_request.message = message
|
||||
chat_request.max_tokens = int(gc.get("max_tokens", 6000))
|
||||
chat_request.temperature = float(gc.get("temperature", 1))
|
||||
chat_request.frequency_penalty = float(gc.get("frequency_penalty", 0))
|
||||
chat_request.presence_penalty = float(gc.get("presence_penalty", 0))
|
||||
chat_request.top_p = float(gc.get("top_p", 0.95))
|
||||
chat_request.top_k = int(gc.get("top_k", 1))
|
||||
if history:
|
||||
chat_history = []
|
||||
for h in history:
|
||||
entry = oci.generative_ai_inference.models.CohereMessage()
|
||||
entry.role = "USER" if h["role"] == "user" else "CHATBOT"
|
||||
entry.message = h["content"]
|
||||
chat_history.append(entry)
|
||||
chat_request.chat_history = chat_history
|
||||
else:
|
||||
msgs = []
|
||||
# ── Generic format (Meta Llama, Google, xAI) - exact chat_demo.py pattern ──
|
||||
chat_request = oci.generative_ai_inference.models.GenericChatRequest()
|
||||
chat_request.api_format = oci.generative_ai_inference.models.BaseChatRequest.API_FORMAT_GENERIC
|
||||
chat_request.max_tokens = int(gc.get("max_tokens", 6000))
|
||||
chat_request.temperature = float(gc.get("temperature", 1))
|
||||
chat_request.frequency_penalty = float(gc.get("frequency_penalty", 0))
|
||||
chat_request.presence_penalty = float(gc.get("presence_penalty", 0))
|
||||
chat_request.top_p = float(gc.get("top_p", 0.95))
|
||||
chat_request.top_k = int(gc.get("top_k", 1))
|
||||
|
||||
messages = []
|
||||
if history:
|
||||
for h in history:
|
||||
role = "USER" if h["role"] == "user" else "ASSISTANT"
|
||||
msgs.append(oci.generative_ai_inference.models.GenericChatMessage(
|
||||
role=role, content=[oci.generative_ai_inference.models.TextContent(text=h["content"])]
|
||||
))
|
||||
msgs.append(oci.generative_ai_inference.models.GenericChatMessage(
|
||||
role="USER", content=[oci.generative_ai_inference.models.TextContent(text=message)]
|
||||
))
|
||||
chat_request = oci.generative_ai_inference.models.GenericChatRequest(
|
||||
messages=msgs, max_tokens=gc.get("max_tokens", 2048),
|
||||
temperature=gc.get("temperature", 0.7), top_p=gc.get("top_p", 0.9),
|
||||
api_format="GENERIC"
|
||||
content = oci.generative_ai_inference.models.TextContent()
|
||||
content.text = h["content"]
|
||||
msg = oci.generative_ai_inference.models.Message()
|
||||
msg.role = "USER" if h["role"] == "user" else "ASSISTANT"
|
||||
msg.content = [content]
|
||||
messages.append(msg)
|
||||
|
||||
# Current user message
|
||||
content = oci.generative_ai_inference.models.TextContent()
|
||||
content.text = message
|
||||
user_message = oci.generative_ai_inference.models.Message()
|
||||
user_message.role = "USER"
|
||||
user_message.content = [content]
|
||||
messages.append(user_message)
|
||||
|
||||
chat_request.messages = messages
|
||||
|
||||
# Serving mode - supports model_id or model_ocid
|
||||
model_ref = gc.get("model_ocid") or gc["model_id"]
|
||||
if gc.get("serving_type") == "DEDICATED" and gc.get("dedicated_endpoint_id"):
|
||||
chat_detail.serving_mode = oci.generative_ai_inference.models.DedicatedServingMode(
|
||||
endpoint_id=gc["dedicated_endpoint_id"]
|
||||
)
|
||||
else:
|
||||
chat_detail.serving_mode = oci.generative_ai_inference.models.OnDemandServingMode(
|
||||
model_id=model_ref
|
||||
)
|
||||
|
||||
chat_detail = oci.generative_ai_inference.models.ChatDetails(
|
||||
compartment_id=gc["compartment_id"], serving_mode=serving_mode, chat_request=chat_request
|
||||
)
|
||||
response = client.chat(chat_detail)
|
||||
chat_response = response.data.chat_response
|
||||
chat_detail.chat_request = chat_request
|
||||
chat_detail.compartment_id = gc["compartment_id"]
|
||||
|
||||
# Execute
|
||||
chat_response = generative_ai_inference_client.chat(chat_detail)
|
||||
|
||||
# Extract text from response
|
||||
resp = chat_response.data.chat_response
|
||||
if api_format == "COHERE":
|
||||
return chat_response.text if hasattr(chat_response, 'text') else str(chat_response)
|
||||
return resp.text if hasattr(resp, 'text') else str(resp)
|
||||
else:
|
||||
if hasattr(chat_response, 'choices') and chat_response.choices:
|
||||
return chat_response.choices[0].message.content[0].text if chat_response.choices[0].message.content else ""
|
||||
return str(chat_response)
|
||||
if hasattr(resp, 'choices') and resp.choices:
|
||||
choice = resp.choices[0]
|
||||
if hasattr(choice, 'message') and choice.message and hasattr(choice.message, 'content'):
|
||||
contents = choice.message.content
|
||||
if contents and len(contents) > 0:
|
||||
return contents[0].text
|
||||
return str(resp)
|
||||
|
||||
# ── MCP Servers ───────────────────────────────────────────────────────────────
|
||||
@app.post("/api/mcp/servers")
|
||||
@@ -491,10 +650,11 @@ async def register_mcp(req: MCPServerReq, u=Depends(require("admin","user"))):
|
||||
mid = str(uuid.uuid4())
|
||||
with db() as c:
|
||||
c.execute(
|
||||
"INSERT INTO mcp_servers (id,user_id,name,description,server_type,command,args,env_vars,url,module_path) VALUES (?,?,?,?,?,?,?,?,?,?)",
|
||||
"INSERT INTO mcp_servers (id,user_id,name,description,server_type,command,args,env_vars,url,module_path,tools,linked_adb_id) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)",
|
||||
(mid, u["id"], req.name, req.description, req.server_type,
|
||||
req.command, json.dumps(req.args) if req.args else None,
|
||||
json.dumps(req.env_vars) if req.env_vars else None, req.url, req.module_path))
|
||||
json.dumps(req.env_vars) if req.env_vars else None, req.url, req.module_path,
|
||||
json.dumps(req.tools) if req.tools else None, req.linked_adb_id))
|
||||
_audit(u["id"], u["username"], "register_mcp", mid, req.name)
|
||||
return {"id": mid, "name": req.name, "server_type": req.server_type}
|
||||
|
||||
@@ -506,8 +666,10 @@ async def list_mcp(u=Depends(current_user)):
|
||||
res = []
|
||||
for r in rows:
|
||||
d = dict(r)
|
||||
if d.get("args"): d["args"] = json.loads(d["args"])
|
||||
if d.get("env_vars"): d["env_vars"] = json.loads(d["env_vars"])
|
||||
for k in ("args","env_vars","tools"):
|
||||
if d.get(k):
|
||||
try: d[k] = json.loads(d[k])
|
||||
except: pass
|
||||
res.append(d)
|
||||
return res
|
||||
|
||||
@@ -526,14 +688,17 @@ async def toggle_mcp(mid: str, u=Depends(require("admin","user"))):
|
||||
|
||||
@app.post("/api/mcp/servers/{mid}/upload")
|
||||
async def upload_mcp_file(mid: str, file: UploadFile = File(...), u=Depends(require("admin","user"))):
|
||||
"""Upload a Python MCP server script"""
|
||||
sdir = MCP_DIR / mid; sdir.mkdir(parents=True, exist_ok=True)
|
||||
fp = sdir / file.filename
|
||||
fp.write_bytes(await file.read())
|
||||
with db() as c:
|
||||
c.execute("UPDATE mcp_servers SET module_path=? WHERE id=?", (str(fp), mid))
|
||||
with db() as c: c.execute("UPDATE mcp_servers SET module_path=? WHERE id=?", (str(fp), mid))
|
||||
return {"ok": True, "path": str(fp)}
|
||||
|
||||
@app.put("/api/mcp/servers/{mid}/link-adb")
|
||||
async def link_mcp_adb(mid: str, adb_id: str = Query(...), u=Depends(require("admin","user"))):
|
||||
with db() as c: c.execute("UPDATE mcp_servers SET linked_adb_id=? WHERE id=?", (adb_id, mid))
|
||||
return {"ok": True, "mcp_id": mid, "linked_adb_id": adb_id}
|
||||
|
||||
# ── ADB Vector DB Config ─────────────────────────────────────────────────────
|
||||
@app.post("/api/adb/config")
|
||||
async def save_adb(req: ADBVectorReq, u=Depends(require("admin","user"))):
|
||||
@@ -580,7 +745,7 @@ async def test_adb(vid: str, u=Depends(require("admin","user"))):
|
||||
cur = conn.cursor(); cur.execute("SELECT 1 FROM DUAL"); cur.close(); conn.close()
|
||||
return {"status":"success","message":"Conexão Autonomous DB OK!"}
|
||||
except ImportError:
|
||||
return {"status":"error","message":"python-oracledb não instalado. Execute: pip install oracledb"}
|
||||
return {"status":"error","message":"python-oracledb não disponível no container."}
|
||||
except Exception as e:
|
||||
return {"status":"error","message":str(e)[:500]}
|
||||
|
||||
@@ -614,7 +779,13 @@ async def _exec_report(rid, cfg, regions, mcp_server_id):
|
||||
if mcp_server_id:
|
||||
with db() as c:
|
||||
mcp = c.execute("SELECT * FROM mcp_servers WHERE id=?",(mcp_server_id,)).fetchone()
|
||||
if mcp and mcp["module_path"]: cmd += ["--mcp-module", mcp["module_path"]]
|
||||
if mcp:
|
||||
if mcp["module_path"]: cmd += ["--mcp-module", mcp["module_path"]]
|
||||
if mcp.get("linked_adb_id"):
|
||||
adb_cfg = c.execute("SELECT * FROM adb_vector_configs WHERE id=?",(mcp["linked_adb_id"],)).fetchone()
|
||||
if adb_cfg:
|
||||
cmd += ["--adb-dsn", adb_cfg["dsn"], "--adb-user", adb_cfg["username"]]
|
||||
if adb_cfg.get("wallet_dir"): cmd += ["--adb-wallet", adb_cfg["wallet_dir"]]
|
||||
proc = await asyncio.create_subprocess_exec(*cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE)
|
||||
stdout, stderr = await proc.communicate()
|
||||
jp = rdir / "report.json"; hp = rdir / "report.html"
|
||||
@@ -668,36 +839,30 @@ async def chat(msg: ChatMsg, u=Depends(current_user)):
|
||||
sid = msg.session_id or str(uuid.uuid4())
|
||||
with db() as c:
|
||||
c.execute("INSERT INTO chat_messages (id,session_id,user_id,role,content,model_id) VALUES (?,?,?,?,?,?)",
|
||||
(str(uuid.uuid4()), sid, u["id"], "user", msg.message, msg.model_id))
|
||||
(str(uuid.uuid4()), sid, u["id"], "user", msg.message, None))
|
||||
|
||||
genai_cfg = None
|
||||
if msg.genai_config_id:
|
||||
with db() as c:
|
||||
genai_cfg = c.execute(
|
||||
"SELECT g.*,o.key_file_path,o.tenancy_ocid,o.user_ocid,o.fingerprint FROM genai_configs g JOIN oci_configs o ON g.oci_config_id=o.id WHERE g.id=?",
|
||||
(msg.genai_config_id,)).fetchone()
|
||||
elif msg.model_id:
|
||||
with db() as c:
|
||||
genai_cfg = c.execute(
|
||||
"SELECT g.*,o.key_file_path,o.tenancy_ocid,o.user_ocid,o.fingerprint FROM genai_configs g JOIN oci_configs o ON g.oci_config_id=o.id WHERE g.user_id=? AND g.model_id=? LIMIT 1",
|
||||
(u["id"], msg.model_id)).fetchone()
|
||||
genai_cfg = c.execute("SELECT * FROM genai_configs WHERE id=?", (msg.genai_config_id,)).fetchone()
|
||||
|
||||
if genai_cfg:
|
||||
try:
|
||||
history = []
|
||||
with db() as c:
|
||||
prev = c.execute("SELECT role,content FROM chat_messages WHERE session_id=? ORDER BY created_at ASC", (sid,)).fetchall()
|
||||
prev = c.execute("SELECT role,content FROM chat_messages WHERE session_id=? AND role IN ('user','assistant') ORDER BY created_at ASC", (sid,)).fetchall()
|
||||
history = [{"role":r["role"],"content":r["content"]} for r in prev]
|
||||
resp = await _call_genai(dict(genai_cfg), msg.message, history[:-1] if history else None)
|
||||
resp = _call_genai(dict(genai_cfg), msg.message, history[:-1] if len(history) > 1 else None)
|
||||
except Exception as e:
|
||||
resp = f"❌ Erro GenAI: {str(e)[:300]}"
|
||||
resp = f"❌ Erro GenAI: {str(e)[:400]}"
|
||||
else:
|
||||
resp = _agent_respond(msg.message, u)
|
||||
|
||||
mid = genai_cfg["model_id"] if genai_cfg else None
|
||||
with db() as c:
|
||||
c.execute("INSERT INTO chat_messages (id,session_id,user_id,role,content,model_id) VALUES (?,?,?,?,?,?)",
|
||||
(str(uuid.uuid4()), sid, u["id"], "assistant", resp, msg.model_id))
|
||||
return {"session_id": sid, "response": resp, "model_id": msg.model_id}
|
||||
(str(uuid.uuid4()), sid, u["id"], "assistant", resp, mid))
|
||||
return {"session_id": sid, "response": resp, "model_id": mid}
|
||||
|
||||
def _agent_respond(msg, user):
|
||||
m = msg.lower().strip()
|
||||
@@ -709,7 +874,7 @@ def _agent_respond(msg, user):
|
||||
nu=c.execute("SELECT COUNT(*) n FROM users WHERE is_active=1").fetchone()["n"]
|
||||
nm=c.execute("SELECT COUNT(*) n FROM mcp_servers WHERE is_active=1").fetchone()["n"]
|
||||
ng=c.execute("SELECT COUNT(*) n FROM genai_configs").fetchone()["n"]
|
||||
return (f"📊 **Status do Sistema**\n\n• OCI CLI: {cli}\n• Configs OCI: {nc}\n• GenAI Models: {ng}\n• MCP Servers: {nm}\n• Relatórios: {nr}\n• Usuários ativos: {nu}\n• Servidor: ✅ Online")
|
||||
return (f"📊 **Status do Sistema**\n\n• OCI CLI: {cli}\n• Configs OCI: {nc}\n• GenAI Models: {ng}\n• MCP Servers: {nm}\n• Relatórios: {nr}\n• Usuários ativos: {nu}\n• Servidor: ✅ Online\n• Versão: v{VERSION}")
|
||||
if any(k in m for k in ["ajuda","help","comandos"]):
|
||||
return ("🤖 **Comandos disponíveis:**\n\n• `status` — Status do sistema\n• `listar configs` — Configurações OCI\n• `verificar cis` — Checks CIS 3.0\n• `modelos` — Modelos GenAI disponíveis\n• `ajuda` — Esta mensagem\n\n💡 Configure um modelo GenAI para chat com IA.")
|
||||
if any(k in m for k in ["modelo","modelos","genai"]):
|
||||
@@ -720,7 +885,7 @@ def _agent_respond(msg, user):
|
||||
return "\n".join(lines)
|
||||
if any(k in m for k in ["cis","benchmark","checks","verificar"]):
|
||||
return ("🔒 **CIS OCI Foundations Benchmark 3.0**\n\n54 controles em 8 domínios:\n• IAM: 17 controles\n• Networking: 8 controles\n• Compute: 3 controles\n• Logging & Monitoring: 18 controles\n• Storage: 6 controles\n• Asset Management: 2 controles\n\nConfigure OCI e execute um relatório na aba **Report**.")
|
||||
return ("Sou o **OCI CIS AI Agent**. Sem modelo GenAI configurado, uso respostas locais.\n\n"
|
||||
return ("Sou o **OCI CIS AI Agent v" + VERSION + "**. Sem modelo GenAI configurado, uso respostas locais.\n\n"
|
||||
"Para chat com IA:\n1. Configure **OCI Credentials**\n2. Configure **GenAI** com modelo e região\n3. Selecione o modelo no chat\n\nDigite **ajuda** para ver os comandos.")
|
||||
|
||||
@app.delete("/api/chat/{sid}")
|
||||
@@ -758,12 +923,12 @@ async def audit_log(limit:int=Query(100,le=500), u=Depends(require("admin"))):
|
||||
# ── Health ────────────────────────────────────────────────────────────────────
|
||||
@app.get("/api/health")
|
||||
async def health():
|
||||
return {"status":"ok","ts":datetime.utcnow().isoformat(),"version":"2.0.0"}
|
||||
return {"status":"ok","ts":datetime.utcnow().isoformat(),"version":VERSION}
|
||||
|
||||
@app.on_event("startup")
|
||||
async def startup():
|
||||
init_db()
|
||||
log.info("OCI CIS AI Agent v2 API started")
|
||||
log.info(f"OCI CIS AI Agent v{VERSION} API started")
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
|
||||
Reference in New Issue
Block a user