64 lines
3.1 KiB
Python
64 lines
3.1 KiB
Python
"""
|
||
title: ZP Agent – overené odpovede
|
||
description: RAG/GraphRAG answers verified and rendered by ZP Agent before display.
|
||
version: 1.0.0
|
||
"""
|
||
import asyncio
|
||
import json
|
||
import urllib.request
|
||
from typing import Literal
|
||
|
||
from pydantic import BaseModel, Field
|
||
|
||
|
||
class Pipe:
|
||
class Valves(BaseModel):
|
||
BACKEND_URL: str = Field(default='', description='URL ZP Agent API reachable from the OpenWebUI server')
|
||
BACKEND_API_KEY: str = Field(default='', description='SEARCH_API_KEY of ZP Agent',
|
||
json_schema_extra={'input': {'type': 'password'}})
|
||
DEFAULT_ENGINE: Literal['rag', 'graphrag'] = 'rag'
|
||
TIMEOUT_SECONDS: int = Field(default=300, ge=10, le=600)
|
||
|
||
def __init__(self):
|
||
self.valves = self.Valves()
|
||
|
||
def pipes(self):
|
||
return [{'id': 'rag', 'name': 'ZP Agent / RAG – overené'},
|
||
{'id': 'graphrag', 'name': 'ZP Agent / GraphRAG – overené'}]
|
||
|
||
def _fetch(self, query: str, engine: str) -> dict:
|
||
url = self.valves.BACKEND_URL.rstrip('/')
|
||
if not url.startswith(('http://', 'https://')) or not self.valves.BACKEND_API_KEY:
|
||
raise ValueError('Missing backend configuration')
|
||
request = urllib.request.Request(url + '/answer', data=json.dumps(
|
||
{'query': query, 'engine': engine}, ensure_ascii=False).encode(),
|
||
headers={'Content-Type': 'application/json',
|
||
'Authorization': 'Bearer ' + self.valves.BACKEND_API_KEY})
|
||
with urllib.request.urlopen(request, timeout=self.valves.TIMEOUT_SECONDS) as response:
|
||
return json.load(response)
|
||
|
||
async def pipe(self, body: dict):
|
||
messages = [row for row in body.get('messages', []) if row.get('role') == 'user']
|
||
query = messages[-1].get('content', '') if messages else ''
|
||
if isinstance(query, list):
|
||
query = '\n'.join(row.get('text', '') for row in query if row.get('type') == 'text')
|
||
if not isinstance(query, str) or not query.strip():
|
||
return 'Zadajte textovú otázku.'
|
||
if len(query.strip()) > 500:
|
||
return 'Otázka môže mať najviac 500 znakov.'
|
||
model = str(body.get('model', '')).rsplit('.', 1)[-1]
|
||
engine = model if model in {'rag', 'graphrag'} else self.valves.DEFAULT_ENGINE
|
||
try:
|
||
result = await asyncio.to_thread(self._fetch, query.strip(), engine)
|
||
except Exception:
|
||
return 'Overovací backend nie je dostupný. Neoverená odpoveď sa nezobrazí.'
|
||
if not isinstance(result, dict):
|
||
return 'Backend neposkytol platnú overenú odpoveď.'
|
||
if result.get('accepted_for_delivery') is not True:
|
||
return 'Odpoveď neprešla overením podkladov. Neoverené údaje sa nezobrazia.'
|
||
if result.get('status') not in {'ok', 'no_answer', 'ambiguous'} or not isinstance(result.get('answer'), str):
|
||
return 'Backend neposkytol platnú overenú odpoveď.'
|
||
# Return the approved final text directly. Never send it to another LLM
|
||
# for paraphrasing: that would invalidate the fact/citation checks.
|
||
return result['answer'] or 'Backend neposkytol text odpovede.'
|