Loistava lisC$ys! TC$mC$ nostaa Valvomo-proxyn tC$ysin uudelle tasolle. Nyt
jC$rjestelmC$stC$ saadaan tC$ysin joustava: se voi toimia tC$ysin
manuaalisesti (ihminen hyvC$ksyy kaiken), tC$ysin automaattisesti tai
C$lykkC$C$sti niin, ettC$ rutiininomaiset ja turvalliset promptit livahtavat
lC$pi automaattisesti, kun taas epC$ilyttC$vC$t tai uudet tilanteet
pysC$htyvC$t tarkastettavaksi ja kuratoitavaksi.
TC$ssC$ on pC$ivitetty, entistC$kin kovempi versio `valvomo_proxy.py`-tiedosto
sta, joka sisC$ltC$C$ kaikkien nC$iden uusien vaatimusten hallinnan:
### PC$ivitetty Valvomo Proxy (`valvomo_proxy.py`)
```python
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import json
import os
import time
import requests
import re
app = FastAPI(title="Valvomo LLM Proxy with HITL & Dynamic Routing")
STATE_FILE = "valvomo_state.json"
def load_state():
if not os.path.exists(STATE_FILE):
return {
"prompts": {},
"responses": {},
"llm_endpoints": {
"default": "http://localhost:11434/v1/chat/completions"
},
"mode": "manual", # "manual" (kaikki jonoon), "all" (kaikki
lC$pi), "regex" (ehtojen mukaan lC$pi)
"approval_regex": ""
}
with open(STATE_FILE, "r", encoding="utf-8") as f:
return json.load(f)
def save_state(state):
with open(STATE_FILE, "w", encoding="utf-8") as f:
json.dump(state, f, indent=4, ensure_ascii=False)
class ChatCompletionRequest(BaseModel):
model: str = "default"
messages: list
temperature: float = 0.7
class ModeRequest(BaseModel):
mode: str # "manual", "all", "regex"
regex_pattern: str = ""
class LLMEndpointRequest(BaseModel):
name: str
url: str
# ==========================================
# APUPROSESSI: LLM-KUTSU JA OHJAUS
# ==========================================
def process_prompt_to_llm(prompt_id: str, state: dict, target_llm_name: str =
None):
p = state["prompts"][prompt_id]
p["status"] = "approved"
# Valitaan kC$ytettC$vC$ LLM-pC$C$tepiste (oletus tai kC$yttC$jC$n
override)
llm_endpoints = state["llm_endpoints"]
if not target_llm_name or target_llm_name not in llm_endpoints:
target_llm_name = list(llm_endpoints.keys())[0] # Otetaan
ensimmC$inen jos ei mC$C$ritelty
target_url = llm_endpoints[target_llm_name]
p["assigned_llm"] = target_llm_name
# LC$hetetC$C$n pyyntC6 eteenpC$in valitulle LLM:lle
try:
llm_resp = requests.post(target_url, json=p["request"], timeout=60)
llm_data = llm_resp.json()
# Tuetaan OpenAI/Ollama yhteensopivaa vastausrakennetta
llm_content = llm_data["choices"][0]["message"]["content"]
except Exception as e:
llm_content = f"[VIRHE LLM-KUTSUSSA ({target_llm_name})]: {str(e)}"
# Tallennetaan vastaus ulosmenevien vastausten jonoon (Outbound Queue)
kuratointia varten
state["responses"][prompt_id] = {
"id": prompt_id,
"status": "pending_response_approval",
"content": llm_content,
"source": target_llm_name
}
save_state(state)
# ==========================================
# 1. CLIENT-RAJAPINTA (Matkii LLM API:ta)
# ==========================================
@app.post("/v1/chat/completions")
def intercept_prompt(req: ChatCompletionRequest):
state = load_state()
prompt_id = str(int(time.time() * 1000))
# YhdistetC$C$n viestit yhdeksi merkkijonoksi regex-tarkistusta varten
full_prompt_text = " ".join([m.get("content", "") for m in req.messages])
prompt_data = {
"id": prompt_id,
"status": "pending_approval",
"request": req.dict(),
"created_at": time.time(),
"manual_response": None,
"assigned_llm": "default"
}
mode = state.get("mode", "manual")