""" 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.'