chore: oppdater opax-mcp server.py og yaml
This commit is contained in:
parent
371965e2f4
commit
2a9138b290
|
|
@ -305,3 +305,110 @@ GCP-event → Jason → Delivery layer → Webhook (CG5a) / E-post (CG5b) / SMS
|
|||
---
|
||||
|
||||
*OSVauco | propane-will-491900-m5 | us-central1 | Autoritativ fra 2026-05-31 | Oppdatert 2026-06-12*
|
||||
|
||||
---
|
||||
|
||||
## 13. Telefoni & AI-agent Plan — Phase E tillegg
|
||||
|
||||
### Konsept: Twilio som kommunikasjonsryggrad
|
||||
Et Twilio-nummer er grunnmuren for SMS, tale og AI-agent telefoni.
|
||||
Alt bygger på samme konto og nummer.
|
||||
|
||||
---
|
||||
|
||||
### Lag 1 — Grunnmur (gjøres NÅ, Phase D)
|
||||
Allerede planlagt:
|
||||
- Twilio-konto opprettet
|
||||
- TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, TWILIO_FROM_NUMBER
|
||||
lagret i Secret Manager
|
||||
- send_sms fungerer via opax-mcp
|
||||
|
||||
---
|
||||
|
||||
### Lag 2 — Inngående AI-telefonsvarer (Phase E, Uke 3-4)
|
||||
|
||||
#### Konsept
|
||||
Kunder/kontakter ringer Twilio-nummeret.
|
||||
Emma-agenten tar samtalen, transkriberer og svarer med tale.
|
||||
|
||||
#### Teknisk stack
|
||||
- Twilio Programmable Voice + Conversation Relay (WebSocket)
|
||||
- Ollama (emma modell) på osvauco-dev-vm som LLM
|
||||
- Whisper eller Twilio Speech Recognition for STT
|
||||
- Twilio TTS (eller Google TTS) for tale tilbake
|
||||
|
||||
#### Flyt
|
||||
Innringer → Twilio → WebSocket → opax-mcp → Ollama (Emma)
|
||||
Emma svarer → TTS → Twilio → Innringer
|
||||
|
||||
#### Hva Emma kan gjøre under samtalen
|
||||
- Svare på spørsmål om OSVauco-tjenester
|
||||
- Slå opp kundedata i backend
|
||||
- Booke møter og sende SMS-bekreftelse
|
||||
- Si "ett øyeblikk" og hente live data via MCP-tools
|
||||
- Eskalere til Chris eller Jason med full samtale-kontekst
|
||||
|
||||
#### Implementasjon
|
||||
1. Legg til /voice webhook-endepunkt i opax-mcp
|
||||
2. Konfigurer Twilio Voice URL til opax-mcp Cloud Run
|
||||
3. Bygg ConversationRelay WebSocket-handler
|
||||
4. Koble til Ollama via eksisterende MCP-infrastruktur
|
||||
5. Definer Emma sin persona og instrukser for telefon
|
||||
6. Test med internt nummer først
|
||||
|
||||
---
|
||||
|
||||
### Lag 3 — Utgående AI-anrop (Phase E, Uke 5-6)
|
||||
|
||||
#### Konsept
|
||||
Emma ringer proaktivt — f.eks. kostnadsvarsler,
|
||||
møtepåminnelser, oppfølging av kunder.
|
||||
|
||||
#### Flyt
|
||||
Trigger (kostnadsspike / planlagt tid)
|
||||
→ Emma initierer anrop via Twilio Outbound API
|
||||
→ Samme ConversationRelay-stack som inngående
|
||||
|
||||
#### Bruksområder
|
||||
- "Hei, jeg ringer fra Vauco — dere nærmer dere
|
||||
budsjettgrensen på GCP denne måneden"
|
||||
- Møtepåminnelse dagen før
|
||||
- Oppfølging etter demo
|
||||
|
||||
---
|
||||
|
||||
### Lag 4 — Multikanal-orkestrering (Phase F)
|
||||
|
||||
#### Konsept
|
||||
Emma bestemmer selv hvilken kanal som passer:
|
||||
- Lav prioritet → SMS
|
||||
- Medium → E-post (Gmail API, allerede oppe)
|
||||
- Høy prioritet → Telefonanrop
|
||||
- Intern → Google Chat webhook
|
||||
|
||||
#### Implementasjon
|
||||
Legg til kanalvalg-logikk i Emma sin system-prompt:
|
||||
"Velg kanal basert på hastegrad og brukerpreferanse"
|
||||
|
||||
---
|
||||
|
||||
### Personas per kanal
|
||||
|
||||
| Kanal | Avsender | Persona |
|
||||
|---|---|---|
|
||||
| SMS | +47-Twilio-nummer | Nøytral, kort |
|
||||
| E-post | jason.vauger@vauco.no | Profesjonell |
|
||||
| Telefon inngående | Emma | Vennlig, hjelpsom |
|
||||
| Telefon utgående | Emma | Proaktiv, konsis |
|
||||
|
||||
---
|
||||
|
||||
### Kostnadsestimat Twilio
|
||||
|
||||
| Tjeneste | Kostnad |
|
||||
|---|---|
|
||||
| Telefonnummer | ~$1/mnd |
|
||||
| SMS utgående (NO) | ~$0.07/SMS |
|
||||
| Voice inngående | ~$0.0085/min |
|
||||
| Voice utgående | ~$0.014/min |
|
||||
| Trial credits | $15 gratis ved oppstart |
|
||||
|
|
|
|||
96
opax-mcp.yaml
Normal file
96
opax-mcp.yaml
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
apiVersion: serving.knative.dev/v1
|
||||
kind: Service
|
||||
metadata:
|
||||
annotations:
|
||||
run.googleapis.com/build-enable-automatic-updates: 'false'
|
||||
run.googleapis.com/build-id: 3deeb5f6-0351-4580-8dab-2dcad7e478dc
|
||||
run.googleapis.com/build-image-uri: us-central1-docker.pkg.dev/propane-will-491900-m5/cloud-run-source-deploy/opax-mcp
|
||||
run.googleapis.com/build-name: projects/357036551735/locations/us-central1/builds/3deeb5f6-0351-4580-8dab-2dcad7e478dc
|
||||
run.googleapis.com/build-source-location: gs://run-sources-propane-will-491900-m5-us-central1/services/opax-mcp/1782754930.916629-ba7acea7e3534f21b658e90dd918d952.zip#1782754931015737
|
||||
run.googleapis.com/ingress: all
|
||||
run.googleapis.com/ingress-status: all
|
||||
run.googleapis.com/maxScale: '20'
|
||||
run.googleapis.com/operation-id: 931f0012-fb7c-422c-b857-8e75cc298a5f
|
||||
run.googleapis.com/urls: '["https://opax-mcp-357036551735.us-central1.run.app","https://opax-mcp-zjbqp3prqq-uc.a.run.app"]'
|
||||
serving.knative.dev/creator: 357036551735-compute@developer.gserviceaccount.com
|
||||
serving.knative.dev/lastModifier: chris.christiansen@vauco.no
|
||||
creationTimestamp: '2026-06-10T01:03:48.098765Z'
|
||||
generation: 57
|
||||
labels:
|
||||
cloud.googleapis.com/location: us-central1
|
||||
name: opax-mcp
|
||||
namespace: '357036551735'
|
||||
resourceVersion: AAZVlY5b+Ro
|
||||
selfLink: /apis/serving.knative.dev/v1/namespaces/357036551735/services/opax-mcp
|
||||
uid: c95aae34-95db-474f-93dc-f8d89b55b2dd
|
||||
spec:
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
autoscaling.knative.dev/maxScale: '5'
|
||||
run.googleapis.com/client-name: gcloud
|
||||
run.googleapis.com/client-version: 570.0.0
|
||||
run.googleapis.com/startup-cpu-boost: 'true'
|
||||
labels:
|
||||
client.knative.dev/nonce: zcdkbqbeua
|
||||
run.googleapis.com/startupProbeType: Default
|
||||
spec:
|
||||
containerConcurrency: 80
|
||||
containers:
|
||||
- env:
|
||||
- name: GOOGLE_CLOUD_PROJECT
|
||||
value: propane-will-491900-m5
|
||||
- name: STATIC_BUCKET
|
||||
value: opax-vauco-static
|
||||
- name: CLOUD_RUN_SERVICE
|
||||
value: osvauco-agent
|
||||
- name: CLOUD_BUILD_TRIGGER_ID
|
||||
value: 38423976-91ff-4ff4-859e-1f262344c609
|
||||
- name: OPAX_IAP_CLIENT_ID
|
||||
value: 357036551735-kq8nt7ld38hfqlcfb3n52ef7tala4meo.apps.googleusercontent.com
|
||||
- name: GITEA_URL
|
||||
value: http://34.158.137.245:3000
|
||||
- name: GITEA_TOKEN
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
key: latest
|
||||
name: gitea-token
|
||||
- name: GMAIL_DEFAULT_SENDER
|
||||
value: jason.vauger@vauco.no
|
||||
- name: GMAIL_ALLOWED_SENDERS
|
||||
value: jason.vauger@vauco.no,emma.vauger@vauco.no
|
||||
- name: TWILIO_ACCOUNT_SID
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
key: latest
|
||||
name: TWILIO_ACCOUNT_SID
|
||||
- name: TWILIO_AUTH_TOKEN
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
key: latest
|
||||
name: TWILIO_AUTH_TOKEN
|
||||
- name: TWILIO_FROM_NUMBER
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
key: latest
|
||||
name: TWILIO_FROM_NUMBER
|
||||
image: us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp:latest
|
||||
name: opax-mcp-1
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
name: http1
|
||||
resources:
|
||||
limits:
|
||||
cpu: '1'
|
||||
memory: 512Mi
|
||||
startupProbe:
|
||||
failureThreshold: 1
|
||||
periodSeconds: 240
|
||||
tcpSocket:
|
||||
port: 8080
|
||||
timeoutSeconds: 240
|
||||
serviceAccountName: jason-vauger@propane-will-491900-m5.iam.gserviceaccount.com
|
||||
timeoutSeconds: 60
|
||||
traffic:
|
||||
- latestRevision: true
|
||||
percent: 100
|
||||
|
|
@ -9,6 +9,7 @@ import httpx
|
|||
import base64
|
||||
import google.auth
|
||||
import google.auth.transport.requests
|
||||
import google.oauth2.id_token
|
||||
from fastapi import FastAPI, Request, HTTPException
|
||||
from fastapi.responses import JSONResponse
|
||||
from typing import Any, Optional
|
||||
|
|
@ -17,9 +18,10 @@ import logging
|
|||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
app = FastAPI(title="opax-mcp", version="3.1.0")
|
||||
app = FastAPI(title="opax-mcp", version="3.2.0")
|
||||
|
||||
OPAX_BASE_URL = os.environ.get("OPAX_BASE_URL", "https://opax.vauco.no")
|
||||
OSVAUCO_AGENT_URL = os.environ.get("OSVAUCO_AGENT_URL", "")
|
||||
OPAX_IAP_CLIENT_ID = os.environ.get("OPAX_IAP_CLIENT_ID", "")
|
||||
MCP_SECRET = os.environ.get("MCP_SECRET", "")
|
||||
GITEA_URL = os.environ.get("GITEA_URL", "http://34.59.131.162:3000")
|
||||
|
|
@ -34,6 +36,7 @@ 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"OSVAUCO_AGENT_URL: {OSVAUCO_AGENT_URL}")
|
||||
logger.info(f"OLLAMA_BASE_URL: {OLLAMA_BASE_URL}")
|
||||
logger.info(f"EMMA_MODEL: {EMMA_MODEL}")
|
||||
|
||||
|
|
@ -56,7 +59,7 @@ def _verify_auth(request: Request):
|
|||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# OPAX helpers
|
||||
# OPAX helpers (for opax.vauco.no via IAP)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _opax_identity_token() -> str:
|
||||
|
|
@ -93,6 +96,36 @@ async def _opax_post(path: str, body: dict) -> Any:
|
|||
r.raise_for_status()
|
||||
return r.json()
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# OSVauco Agent helpers (for direct service-to-service calls)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _osvauco_agent_identity_token() -> str:
|
||||
"""Hent identity token for direkte kall til osvauco-agent."""
|
||||
try:
|
||||
audience = os.environ.get("OSVAUCO_AGENT_URL")
|
||||
if not audience:
|
||||
raise ValueError("OSVAUCO_AGENT_URL environment variable is not set.")
|
||||
auth_req = google.auth.transport.requests.Request()
|
||||
token = google.oauth2.id_token.fetch_id_token(auth_req, audience)
|
||||
if not token:
|
||||
raise ValueError("Empty token returned from fetch_id_token")
|
||||
return token
|
||||
except Exception as e:
|
||||
logger.error(f"[SERVICE-TO-SERVICE TOKEN ERROR] {type(e).__name__}: {e}")
|
||||
raise
|
||||
|
||||
def _osvauco_agent_headers() -> dict:
|
||||
return {"Authorization": f"Bearer {_osvauco_agent_identity_token()}", "Content-Type": "application/json"}
|
||||
|
||||
async def _osvauco_agent_post(path: str, body: dict) -> Any:
|
||||
"""Kaller osvauco-agent direkte med service-to-service auth."""
|
||||
url = f"{os.environ.get('OSVAUCO_AGENT_URL')}{path}"
|
||||
async with httpx.AsyncClient(timeout=30) as c:
|
||||
r = await c.post(url, json=body, headers=_osvauco_agent_headers())
|
||||
r.raise_for_status()
|
||||
return r.json()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Ollama helpers — direkte mot emma-gpu-vm
|
||||
|
|
@ -195,7 +228,7 @@ async def send_email(p):
|
|||
return await _opax_post("/notify/email", {"to": p.get("to"), "subject": p.get("subject"), "event": p.get("event", "digest")})
|
||||
|
||||
async def send_sms(p):
|
||||
return await _opax_post("/notify/sms", {"to": p.get("to"), "body": p.get("message", p.get("body", "")), "event": p.get("event", "spike"), "tier": p.get("tier", "guard")})
|
||||
return await _osvauco_agent_post("/notify/sms", {"to": p.get("to"), "body": p.get("message", p.get("body", "")), "event": p.get("event", "spike"), "tier": p.get("tier", "guard")})
|
||||
|
||||
async def get_notify_channels(p): return await _opax_get("/notify/channels")
|
||||
|
||||
|
|
@ -352,7 +385,7 @@ async def mcp_handler(request: Request):
|
|||
return JSONResponse(_jsonrpc_ok(req_id, {
|
||||
"protocolVersion": "2024-11-05",
|
||||
"capabilities": {"tools": {}},
|
||||
"serverInfo": {"name": "opax-mcp", "version": "3.1.0"},
|
||||
"serverInfo": {"name": "opax-mcp", "version": "3.2.0"},
|
||||
}))
|
||||
|
||||
if method == "tools/list":
|
||||
|
|
@ -389,4 +422,4 @@ async def mcp_handler(request: Request):
|
|||
|
||||
@app.get("/health")
|
||||
async def health():
|
||||
return {"status": "ok", "service": "opax-mcp", "version": "3.1.0", "ollama": OLLAMA_BASE_URL, "emma_model": EMMA_MODEL}
|
||||
return {"status": "ok", "service": "opax-mcp", "version": "3.2.0", "ollama": OLLAMA_BASE_URL, "emma_model": EMMA_MODEL}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user