feat: legg til direkte Ollama-verktøy for Emma (gemma3:27b), gemma3:4b og qwen2.5:3b på emma-gpu-vm
This commit is contained in:
parent
7231cd3b31
commit
9ff2da5985
|
|
@ -17,7 +17,7 @@ import logging
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
app = FastAPI(title="opax-mcp", version="3.0.2")
|
app = FastAPI(title="opax-mcp", version="3.1.0")
|
||||||
|
|
||||||
OPAX_BASE_URL = os.environ.get("OPAX_BASE_URL", "https://opax.vauco.no")
|
OPAX_BASE_URL = os.environ.get("OPAX_BASE_URL", "https://opax.vauco.no")
|
||||||
OPAX_IAP_CLIENT_ID = os.environ.get("OPAX_IAP_CLIENT_ID", "")
|
OPAX_IAP_CLIENT_ID = os.environ.get("OPAX_IAP_CLIENT_ID", "")
|
||||||
|
|
@ -27,7 +27,15 @@ GITEA_TOKEN = os.environ.get("GITEA_TOKEN", "")
|
||||||
GITEA_REPO = os.environ.get("GITEA_REPO", "chris/OSVauco")
|
GITEA_REPO = os.environ.get("GITEA_REPO", "chris/OSVauco")
|
||||||
GOOGLE_CLOUD_PROJECT = os.environ.get("GOOGLE_CLOUD_PROJECT", "propane-will-491900-m5")
|
GOOGLE_CLOUD_PROJECT = os.environ.get("GOOGLE_CLOUD_PROJECT", "propane-will-491900-m5")
|
||||||
|
|
||||||
|
# Emma-vm Ollama — direkte tilkobling
|
||||||
|
OLLAMA_BASE_URL = os.environ.get("OLLAMA_BASE_URL", "http://34.13.238.133:11434")
|
||||||
|
EMMA_MODEL = os.environ.get("EMMA_MODEL", "gemma3:27b")
|
||||||
|
EMMA_FAST_MODEL = os.environ.get("EMMA_FAST_MODEL", "gemma3:4b")
|
||||||
|
EMMA_LIGHT_MODEL = os.environ.get("EMMA_LIGHT_MODEL", "qwen2.5:3b")
|
||||||
|
|
||||||
logger.info(f"OPAX_IAP_CLIENT_ID: {OPAX_IAP_CLIENT_ID[:10]}...")
|
logger.info(f"OPAX_IAP_CLIENT_ID: {OPAX_IAP_CLIENT_ID[:10]}...")
|
||||||
|
logger.info(f"OLLAMA_BASE_URL: {OLLAMA_BASE_URL}")
|
||||||
|
logger.info(f"EMMA_MODEL: {EMMA_MODEL}")
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
@ -37,11 +45,9 @@ logger.info(f"OPAX_IAP_CLIENT_ID: {OPAX_IAP_CLIENT_ID[:10]}...")
|
||||||
def _verify_auth(request: Request):
|
def _verify_auth(request: Request):
|
||||||
if not MCP_SECRET:
|
if not MCP_SECRET:
|
||||||
return
|
return
|
||||||
# Authorization: Bearer <token>
|
|
||||||
auth = request.headers.get("Authorization", "")
|
auth = request.headers.get("Authorization", "")
|
||||||
if auth.startswith("Bearer ") and auth[7:] == MCP_SECRET:
|
if auth.startswith("Bearer ") and auth[7:] == MCP_SECRET:
|
||||||
return
|
return
|
||||||
# X-MCP-Secret or api-key
|
|
||||||
if request.headers.get("X-MCP-Secret") == MCP_SECRET:
|
if request.headers.get("X-MCP-Secret") == MCP_SECRET:
|
||||||
return
|
return
|
||||||
if request.headers.get("api-key") == MCP_SECRET:
|
if request.headers.get("api-key") == MCP_SECRET:
|
||||||
|
|
@ -88,6 +94,46 @@ async def _opax_post(path: str, body: dict) -> Any:
|
||||||
return r.json()
|
return r.json()
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Ollama helpers — direkte mot emma-gpu-vm
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
async def _ollama_chat(model: str, prompt: str, system: str = "") -> dict:
|
||||||
|
"""Kall Ollama chat-API direkte på emma-gpu-vm."""
|
||||||
|
messages = []
|
||||||
|
if system:
|
||||||
|
messages.append({"role": "system", "content": system})
|
||||||
|
messages.append({"role": "user", "content": prompt})
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
"model": model,
|
||||||
|
"messages": messages,
|
||||||
|
"stream": False,
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
async with httpx.AsyncClient(timeout=120) as c:
|
||||||
|
r = await c.post(f"{OLLAMA_BASE_URL}/api/chat", json=payload)
|
||||||
|
r.raise_for_status()
|
||||||
|
data = r.json()
|
||||||
|
return {
|
||||||
|
"model": model,
|
||||||
|
"response": data.get("message", {}).get("content", ""),
|
||||||
|
"done": data.get("done", True),
|
||||||
|
"total_duration_ms": round(data.get("total_duration", 0) / 1e6),
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"[OLLAMA ERROR] model={model} {type(e).__name__}: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
async def _ollama_models() -> list:
|
||||||
|
"""List alle modeller tilgjengelig på emma-gpu-vm."""
|
||||||
|
async with httpx.AsyncClient(timeout=10) as c:
|
||||||
|
r = await c.get(f"{OLLAMA_BASE_URL}/api/tags")
|
||||||
|
r.raise_for_status()
|
||||||
|
return r.json().get("models", [])
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Gitea helpers
|
# Gitea helpers
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
@ -157,7 +203,32 @@ async def run_jason(p):
|
||||||
return await _opax_post("/run", {"message": p.get("prompt", p.get("message", "")), "user_id": p.get("user_id", "opax"), "session_id": p.get("session_id", "mcp"), "mode": p.get("mode", "light")})
|
return await _opax_post("/run", {"message": p.get("prompt", p.get("message", "")), "user_id": p.get("user_id", "opax"), "session_id": p.get("session_id", "mcp"), "mode": p.get("mode", "light")})
|
||||||
|
|
||||||
async def run_emma(p):
|
async def run_emma(p):
|
||||||
return await _opax_post("/emma", {"message": p.get("prompt", p.get("message", "")), "session_id": p.get("session_id", "mcp")})
|
"""Emma Vauger — Gemma 3 27B via Ollama på emma-gpu-vm. Primær lokal AI."""
|
||||||
|
return await _ollama_chat(
|
||||||
|
model=EMMA_MODEL,
|
||||||
|
prompt=p.get("prompt", p.get("message", "")),
|
||||||
|
system=p.get("system", "Du er Emma Vauger, en intelligent og hjelpsom AI-assistent for Vauco. Du er lokal, rask og priveråd.")
|
||||||
|
)
|
||||||
|
|
||||||
|
async def run_emma_fast(p):
|
||||||
|
"""Emma Fast — Gemma 3 4B via Ollama. Rask inferens for enkle oppgaver."""
|
||||||
|
return await _ollama_chat(
|
||||||
|
model=EMMA_FAST_MODEL,
|
||||||
|
prompt=p.get("prompt", p.get("message", "")),
|
||||||
|
system=p.get("system", "Du er en rask og konsis AI-assistent. Svar kort og presist.")
|
||||||
|
)
|
||||||
|
|
||||||
|
async def run_qwen(p):
|
||||||
|
"""Qwen 2.5 3B via Ollama. Lett hjelper for enkle spørsmål og kodeoppgaver."""
|
||||||
|
return await _ollama_chat(
|
||||||
|
model=EMMA_LIGHT_MODEL,
|
||||||
|
prompt=p.get("prompt", p.get("message", "")),
|
||||||
|
system=p.get("system", "")
|
||||||
|
)
|
||||||
|
|
||||||
|
async def list_emma_models(p):
|
||||||
|
"""List alle Ollama-modeller tilgjengelig på emma-gpu-vm."""
|
||||||
|
return await _ollama_models()
|
||||||
|
|
||||||
async def get_health(p): return await _opax_get("/health")
|
async def get_health(p): return await _opax_get("/health")
|
||||||
async def get_build_status(p): return await _opax_get("/opax/build-status")
|
async def get_build_status(p): return await _opax_get("/opax/build-status")
|
||||||
|
|
@ -221,7 +292,13 @@ TOOLS = {
|
||||||
"send_sms": (send_sms, "Send SMS", {"type":"object","properties":{"to":{"type":"string"},"message":{"type":"string"}},"required":["to","message"]}),
|
"send_sms": (send_sms, "Send SMS", {"type":"object","properties":{"to":{"type":"string"},"message":{"type":"string"}},"required":["to","message"]}),
|
||||||
"get_notify_channels": (get_notify_channels, "Hent varslingkanaler", {}),
|
"get_notify_channels": (get_notify_channels, "Hent varslingkanaler", {}),
|
||||||
"run_jason": (run_jason, "Kjør Jason-agenten med en prompt", {"type":"object","properties":{"prompt":{"type":"string"},"mode":{"type":"string"}},"required":["prompt"]}),
|
"run_jason": (run_jason, "Kjør Jason-agenten med en prompt", {"type":"object","properties":{"prompt":{"type":"string"},"mode":{"type":"string"}},"required":["prompt"]}),
|
||||||
"run_emma": (run_emma, "Kjør Emma-agenten med en prompt", {"type":"object","properties":{"prompt":{"type":"string"}},"required":["prompt"]}),
|
|
||||||
|
# — Emma Vauger — direkte Ollama på emma-gpu-vm (34.13.238.133:11434)
|
||||||
|
"run_emma": (run_emma, "Emma Vauger (gemma3:27b) — primær lokal AI, ingen quota", {"type":"object","properties":{"prompt":{"type":"string"},"system":{"type":"string","description":"Valgfri systemprompt"}},"required":["prompt"]}),
|
||||||
|
"run_emma_fast": (run_emma_fast, "Emma Fast (gemma3:4b) — rask versjon for enkle oppgaver", {"type":"object","properties":{"prompt":{"type":"string"},"system":{"type":"string"}},"required":["prompt"]}),
|
||||||
|
"run_qwen": (run_qwen, "Qwen 2.5 3B — lett hjelper og kodeoppgaver", {"type":"object","properties":{"prompt":{"type":"string"},"system":{"type":"string"}},"required":["prompt"]}),
|
||||||
|
"list_emma_models": (list_emma_models, "List alle Ollama-modeller tilgjengelig på emma-gpu-vm", {}),
|
||||||
|
|
||||||
"get_health": (get_health, "Hent helsestatus for OPAX", {}),
|
"get_health": (get_health, "Hent helsestatus for OPAX", {}),
|
||||||
"get_build_status": (get_build_status, "Hent siste build-status", {}),
|
"get_build_status": (get_build_status, "Hent siste build-status", {}),
|
||||||
"get_state": (get_state, "Hent platform-tilstand", {}),
|
"get_state": (get_state, "Hent platform-tilstand", {}),
|
||||||
|
|
@ -271,19 +348,16 @@ async def mcp_handler(request: Request):
|
||||||
req_id = body.get("id")
|
req_id = body.get("id")
|
||||||
params = body.get("params", {})
|
params = body.get("params", {})
|
||||||
|
|
||||||
# initialize
|
|
||||||
if method == "initialize":
|
if method == "initialize":
|
||||||
return JSONResponse(_jsonrpc_ok(req_id, {
|
return JSONResponse(_jsonrpc_ok(req_id, {
|
||||||
"protocolVersion": "2024-11-05",
|
"protocolVersion": "2024-11-05",
|
||||||
"capabilities": {"tools": {}},
|
"capabilities": {"tools": {}},
|
||||||
"serverInfo": {"name": "opax-mcp", "version": "3.0.0"},
|
"serverInfo": {"name": "opax-mcp", "version": "3.1.0"},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
# tools/list
|
|
||||||
if method == "tools/list":
|
if method == "tools/list":
|
||||||
return JSONResponse(_jsonrpc_ok(req_id, {"tools": _tool_list_result()}))
|
return JSONResponse(_jsonrpc_ok(req_id, {"tools": _tool_list_result()}))
|
||||||
|
|
||||||
# tools/call
|
|
||||||
if method == "tools/call":
|
if method == "tools/call":
|
||||||
tool_name = params.get("name") or params.get("tool")
|
tool_name = params.get("name") or params.get("tool")
|
||||||
tool_args = params.get("arguments", params.get("params", {}))
|
tool_args = params.get("arguments", params.get("params", {}))
|
||||||
|
|
@ -303,7 +377,6 @@ async def mcp_handler(request: Request):
|
||||||
"content": [{"type": "text", "text": json.dumps(result, ensure_ascii=False)}]
|
"content": [{"type": "text", "text": json.dumps(result, ensure_ascii=False)}]
|
||||||
}))
|
}))
|
||||||
|
|
||||||
# notifications (fire-and-forget, no response needed)
|
|
||||||
if method.startswith("notifications/"):
|
if method.startswith("notifications/"):
|
||||||
return JSONResponse(status_code=202, content={})
|
return JSONResponse(status_code=202, content={})
|
||||||
|
|
||||||
|
|
@ -316,4 +389,4 @@ async def mcp_handler(request: Request):
|
||||||
|
|
||||||
@app.get("/health")
|
@app.get("/health")
|
||||||
async def health():
|
async def health():
|
||||||
return {"status": "ok", "service": "opax-mcp", "version": "3.0.0"}
|
return {"status": "ok", "service": "opax-mcp", "version": "3.1.0", "ollama": OLLAMA_BASE_URL, "emma_model": EMMA_MODEL}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user