uprava rag runnera
This commit is contained in:
parent
312cb52843
commit
98da217151
@ -12,94 +12,49 @@ from pathlib import Path
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
PROJECT_ROOT = Path(
|
PROJECT_ROOT = Path(__file__).resolve().parents[1]
|
||||||
__file__
|
|
||||||
).resolve().parents[1]
|
|
||||||
|
|
||||||
|
OPENWEBUI_URL = "https://ui.tukekemt.xyz/api/chat/completions"
|
||||||
|
LOCAL_OPENAPI_URL = "http://localhost:8000/openapi.json"
|
||||||
|
LOCAL_RAG_URL = "http://localhost:8000/rag"
|
||||||
|
|
||||||
OPENWEBUI_URL = (
|
DEFAULT_MODEL = "model120-fast"
|
||||||
"https://ui.tukekemt.xyz/api/chat/completions"
|
|
||||||
)
|
|
||||||
|
|
||||||
LOCAL_OPENAPI_URL = (
|
|
||||||
"http://localhost:8000/openapi.json"
|
|
||||||
)
|
|
||||||
|
|
||||||
LOCAL_RAG_URL = (
|
|
||||||
"http://localhost:8000/rag"
|
|
||||||
)
|
|
||||||
|
|
||||||
DEFAULT_MODEL = (
|
|
||||||
"model120-fast"
|
|
||||||
)
|
|
||||||
|
|
||||||
DEFAULT_TIMEOUT = 180
|
DEFAULT_TIMEOUT = 180
|
||||||
|
|
||||||
DEFAULT_MAX_ATTEMPTS = 4
|
DEFAULT_MAX_ATTEMPTS = 4
|
||||||
|
|
||||||
DEFAULT_BACKOFF_BASE = 1.0
|
DEFAULT_BACKOFF_BASE = 1.0
|
||||||
|
|
||||||
DEFAULT_BACKOFF_MAX = 8.0
|
DEFAULT_BACKOFF_MAX = 8.0
|
||||||
|
|
||||||
|
|
||||||
RETRYABLE_HTTP_STATUS_CODES = {
|
RETRYABLE_HTTP_STATUS_CODES = {
|
||||||
408,
|
408,
|
||||||
425,
|
425,
|
||||||
429,
|
429,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
MARKDOWN_URL_RE = re.compile(
|
MARKDOWN_URL_RE = re.compile(
|
||||||
r"\[[^\]]*\]\((https?://[^)]+)\)"
|
r"\[[^\]]*\]\((https?://[^)]+)\)"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
TOOL_ROUTING_SYSTEM_PROMPT = """
|
TOOL_ROUTING_SYSTEM_PROMPT = (
|
||||||
Si prvá fáza RAG agenta pre ZP Wiki.
|
"Si evaluačný klient pre ZP Agent. Pri každej otázke najprv použi "
|
||||||
|
"nástroj retrieve_zpwiki_context na vyhľadanie dôkazov v ZP Wiki. "
|
||||||
Tvojou úlohou je pri otázkach, ktoré vyžadujú informácie
|
"Neodpovedaj z vlastnej pamäte a pred prvým vyhľadaním nežiadaj "
|
||||||
zo ZP Wiki, zavolať dostupný retrieval tool.
|
"používateľa o spresnenie. Pri tvorbe argumentu query zachovaj všetky významovo "
|
||||||
|
"dôležité obmedzenia pôvodnej otázky a zachovaj celý význam pôvodnej otázky: "
|
||||||
Pri vytváraní argumentu `query` zachovaj všetky významovo
|
"meno osoby alebo názov dokumentu, typ práce "
|
||||||
rozlišujúce informácie z používateľovej otázky.
|
"(bakalárska, diplomová, diplomový projekt, stáž), požadovaný atribút "
|
||||||
|
"(názov, rok, téma, hardvér, databáza, backend, počet, nástroj), rok "
|
||||||
Zachovaj najmä:
|
"a dôležité odborné kľúčové slová. Nezredukuj otázku iba na meno osoby. "
|
||||||
- meno alebo názov osoby, dokumentu alebo témy,
|
"Ak je otázka krátka alebo kontextová, napríklad sa pýta na chatbot, "
|
||||||
- typ práce alebo projektu, napríklad bakalárska práca,
|
"Question Answering, anotáciu alebo projekt bez mena osoby, aj tak najprv "
|
||||||
diplomová práca, dizertačná práca, diplomový projekt
|
"vyhľadaj pôvodnú otázku alebo jej hlavné pojmy v ZP Wiki. Pri otázke na "
|
||||||
alebo tímový projekt,
|
"viacero dokumentov zachovaj v query všetky tematické pojmy. Po získaní "
|
||||||
- požadovaný atribút, napríklad názov, téma, rok, autor,
|
"výsledku odpovedz iba podľa vráteného kontextu a cituj iba source_url, "
|
||||||
cieľ, metóda alebo zadanie,
|
"ktoré tvoju odpoveď podporujú."
|
||||||
- explicitne uvedený rok,
|
)
|
||||||
- ďalšie odborné alebo rozlišujúce kľúčové slová.
|
|
||||||
|
|
||||||
Query môže byť stručnejší než pôvodná otázka, ale nesmie
|
|
||||||
stratiť významové obmedzenia potrebné na správne vyhľadanie.
|
|
||||||
|
|
||||||
Napríklad otázku:
|
|
||||||
"Aká téma diplomovej práce je uvedená pri osobe Ján Novák?"
|
|
||||||
je vhodné previesť na query podobný:
|
|
||||||
"Ján Novák diplomová práca téma"
|
|
||||||
|
|
||||||
Nevhodné je zredukovať ju iba na:
|
|
||||||
"Ján Novák"
|
|
||||||
|
|
||||||
Podobne pri otázke na rok diplomovej práce musí query
|
|
||||||
zachovať meno osoby, pojem diplomová práca a informáciu,
|
|
||||||
že sa hľadá rok.
|
|
||||||
|
|
||||||
Nevymýšľaj hodnoty, ktoré používateľ v otázke neuviedol.
|
|
||||||
Nevkladaj do query predpokladanú odpoveď.
|
|
||||||
|
|
||||||
Ak je potrebné použiť ZP Wiki, najprv zavolaj retrieval tool.
|
|
||||||
Po získaní výsledku odpovedaj iba podľa informácií,
|
|
||||||
inštrukcií, kontextu a zdrojov vrátených toolom.
|
|
||||||
""".strip()
|
|
||||||
|
|
||||||
|
|
||||||
class RequestError(
|
class RequestError(RuntimeError):
|
||||||
RuntimeError
|
|
||||||
):
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
message: str,
|
message: str,
|
||||||
@ -107,17 +62,9 @@ class RequestError(
|
|||||||
retryable: bool,
|
retryable: bool,
|
||||||
status_code: int | None = None,
|
status_code: int | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(
|
super().__init__(message)
|
||||||
message
|
self.retryable = retryable
|
||||||
)
|
self.status_code = status_code
|
||||||
|
|
||||||
self.retryable = (
|
|
||||||
retryable
|
|
||||||
)
|
|
||||||
|
|
||||||
self.status_code = (
|
|
||||||
status_code
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def load_env_value(
|
def load_env_value(
|
||||||
@ -126,34 +73,25 @@ def load_env_value(
|
|||||||
*,
|
*,
|
||||||
allow_env_file: bool = True,
|
allow_env_file: bool = True,
|
||||||
) -> str:
|
) -> str:
|
||||||
value = os.environ.get(
|
value = os.environ.get(key)
|
||||||
key
|
|
||||||
)
|
|
||||||
|
|
||||||
if value:
|
if value:
|
||||||
return value
|
return value
|
||||||
|
|
||||||
if not allow_env_file:
|
if not allow_env_file:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
f"{key} musí byť nastavený "
|
f"{key} musí byť nastavený priamo v environment."
|
||||||
"priamo v environment."
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if env_path is None:
|
if env_path is None:
|
||||||
env_path = (
|
env_path = PROJECT_ROOT / ".env"
|
||||||
PROJECT_ROOT
|
|
||||||
/ ".env"
|
|
||||||
)
|
|
||||||
|
|
||||||
if not env_path.exists():
|
if not env_path.exists():
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
f"{key} nie je v environment "
|
f"{key} nie je v environment a {env_path} neexistuje."
|
||||||
f"a {env_path} neexistuje."
|
|
||||||
)
|
)
|
||||||
|
|
||||||
for raw_line in env_path.read_text(
|
for raw_line in env_path.read_text(encoding="utf-8").splitlines():
|
||||||
encoding="utf-8"
|
|
||||||
).splitlines():
|
|
||||||
line = raw_line.strip()
|
line = raw_line.strip()
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@ -163,10 +101,7 @@ def load_env_value(
|
|||||||
):
|
):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
name, value = line.split(
|
name, value = line.split("=", 1)
|
||||||
"=",
|
|
||||||
1,
|
|
||||||
)
|
|
||||||
|
|
||||||
if name.strip() != key:
|
if name.strip() != key:
|
||||||
continue
|
continue
|
||||||
@ -176,14 +111,9 @@ def load_env_value(
|
|||||||
if (
|
if (
|
||||||
len(value) >= 2
|
len(value) >= 2
|
||||||
and value[0] == value[-1]
|
and value[0] == value[-1]
|
||||||
and value[0] in {
|
and value[0] in {"'", '"'}
|
||||||
"'",
|
|
||||||
'"',
|
|
||||||
}
|
|
||||||
):
|
):
|
||||||
value = value[
|
value = value[1:-1]
|
||||||
1:-1
|
|
||||||
]
|
|
||||||
|
|
||||||
if value:
|
if value:
|
||||||
return value
|
return value
|
||||||
@ -202,21 +132,8 @@ def retry_delay_seconds(
|
|||||||
if attempt <= 0:
|
if attempt <= 0:
|
||||||
return 0.0
|
return 0.0
|
||||||
|
|
||||||
delay = (
|
delay = backoff_base * (2 ** (attempt - 1))
|
||||||
backoff_base
|
return min(delay, backoff_max)
|
||||||
* (
|
|
||||||
2
|
|
||||||
** (
|
|
||||||
attempt
|
|
||||||
- 1
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
return min(
|
|
||||||
delay,
|
|
||||||
backoff_max,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def request_json(
|
def request_json(
|
||||||
@ -231,24 +148,16 @@ def request_json(
|
|||||||
backoff_max: float = DEFAULT_BACKOFF_MAX,
|
backoff_max: float = DEFAULT_BACKOFF_MAX,
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
if timeout <= 0:
|
if timeout <= 0:
|
||||||
raise ValueError(
|
raise ValueError("timeout musí byť > 0")
|
||||||
"timeout musí byť > 0"
|
|
||||||
)
|
|
||||||
|
|
||||||
if max_attempts <= 0:
|
if max_attempts <= 0:
|
||||||
raise ValueError(
|
raise ValueError("max_attempts musí byť > 0")
|
||||||
"max_attempts musí byť > 0"
|
|
||||||
)
|
|
||||||
|
|
||||||
if backoff_base < 0:
|
if backoff_base < 0:
|
||||||
raise ValueError(
|
raise ValueError("backoff_base nesmie byť záporné")
|
||||||
"backoff_base nesmie byť záporné"
|
|
||||||
)
|
|
||||||
|
|
||||||
if backoff_max < 0:
|
if backoff_max < 0:
|
||||||
raise ValueError(
|
raise ValueError("backoff_max nesmie byť záporné")
|
||||||
"backoff_max nesmie byť záporné"
|
|
||||||
)
|
|
||||||
|
|
||||||
data = None
|
data = None
|
||||||
|
|
||||||
@ -256,24 +165,16 @@ def request_json(
|
|||||||
data = json.dumps(
|
data = json.dumps(
|
||||||
payload,
|
payload,
|
||||||
ensure_ascii=False,
|
ensure_ascii=False,
|
||||||
).encode(
|
).encode("utf-8")
|
||||||
"utf-8"
|
|
||||||
)
|
|
||||||
|
|
||||||
last_error: RequestError | None = None
|
last_error: RequestError | None = None
|
||||||
|
|
||||||
for attempt in range(
|
for attempt in range(1, max_attempts + 1):
|
||||||
1,
|
|
||||||
max_attempts + 1,
|
|
||||||
):
|
|
||||||
request = urllib.request.Request(
|
request = urllib.request.Request(
|
||||||
url,
|
url,
|
||||||
data=data,
|
data=data,
|
||||||
method=method,
|
method=method,
|
||||||
headers=(
|
headers=headers or {},
|
||||||
headers
|
|
||||||
or {}
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -281,49 +182,25 @@ def request_json(
|
|||||||
request,
|
request,
|
||||||
timeout=timeout,
|
timeout=timeout,
|
||||||
) as response:
|
) as response:
|
||||||
raw = (
|
raw = response.read().decode("utf-8")
|
||||||
response
|
|
||||||
.read()
|
|
||||||
.decode(
|
|
||||||
"utf-8"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
except urllib.error.HTTPError as exc:
|
except urllib.error.HTTPError as exc:
|
||||||
body = (
|
body = exc.read().decode(
|
||||||
exc.read()
|
|
||||||
.decode(
|
|
||||||
"utf-8",
|
"utf-8",
|
||||||
errors="replace",
|
errors="replace",
|
||||||
)
|
)
|
||||||
)
|
|
||||||
|
|
||||||
status_code = int(
|
status_code = int(exc.code)
|
||||||
exc.code
|
|
||||||
)
|
|
||||||
|
|
||||||
retryable = (
|
retryable = (
|
||||||
status_code
|
status_code in RETRYABLE_HTTP_STATUS_CODES
|
||||||
in RETRYABLE_HTTP_STATUS_CODES
|
or 500 <= status_code <= 599
|
||||||
or (
|
|
||||||
500
|
|
||||||
<= status_code
|
|
||||||
<= 599
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
error = RequestError(
|
error = RequestError(
|
||||||
(
|
f"HTTP {status_code} pre {url}: {body[:1500]}",
|
||||||
f"HTTP {status_code} "
|
retryable=retryable,
|
||||||
f"pre {url}: "
|
status_code=status_code,
|
||||||
f"{body[:1500]}"
|
|
||||||
),
|
|
||||||
retryable=(
|
|
||||||
retryable
|
|
||||||
),
|
|
||||||
status_code=(
|
|
||||||
status_code
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
except (
|
except (
|
||||||
@ -332,49 +209,30 @@ def request_json(
|
|||||||
socket.timeout,
|
socket.timeout,
|
||||||
) as exc:
|
) as exc:
|
||||||
error = RequestError(
|
error = RequestError(
|
||||||
(
|
f"Sieťová chyba pre {url}: {exc}",
|
||||||
"Sieťová chyba "
|
|
||||||
f"pre {url}: "
|
|
||||||
f"{exc}"
|
|
||||||
),
|
|
||||||
retryable=True,
|
retryable=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if not raw.strip():
|
if not raw.strip():
|
||||||
raise RequestError(
|
raise RequestError(
|
||||||
(
|
f"Prázdna odpoveď z {url}.",
|
||||||
"Prázdna odpoveď "
|
|
||||||
f"z {url}."
|
|
||||||
),
|
|
||||||
retryable=False,
|
retryable=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
parsed = json.loads(
|
parsed = json.loads(raw)
|
||||||
raw
|
|
||||||
)
|
|
||||||
|
|
||||||
except json.JSONDecodeError as exc:
|
except json.JSONDecodeError as exc:
|
||||||
raise RequestError(
|
raise RequestError(
|
||||||
(
|
f"Neplatný JSON z {url}: {raw[:1000]}",
|
||||||
"Neplatný JSON "
|
|
||||||
f"z {url}: "
|
|
||||||
f"{raw[:1000]}"
|
|
||||||
),
|
|
||||||
retryable=False,
|
retryable=False,
|
||||||
) from exc
|
) from exc
|
||||||
|
|
||||||
if not isinstance(
|
if not isinstance(parsed, dict):
|
||||||
parsed,
|
|
||||||
dict,
|
|
||||||
):
|
|
||||||
raise RequestError(
|
raise RequestError(
|
||||||
(
|
|
||||||
"Očakávaný JSON objekt "
|
"Očakávaný JSON objekt "
|
||||||
f"z {url}, dostal som "
|
f"z {url}, dostal som {type(parsed).__name__}.",
|
||||||
f"{type(parsed).__name__}."
|
|
||||||
),
|
|
||||||
retryable=False,
|
retryable=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -390,36 +248,25 @@ def request_json(
|
|||||||
|
|
||||||
delay = retry_delay_seconds(
|
delay = retry_delay_seconds(
|
||||||
attempt,
|
attempt,
|
||||||
backoff_base=(
|
backoff_base=backoff_base,
|
||||||
backoff_base
|
backoff_max=backoff_max,
|
||||||
),
|
|
||||||
backoff_max=(
|
|
||||||
backoff_max
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
print(
|
print(
|
||||||
(
|
|
||||||
" retry HTTP request: "
|
" retry HTTP request: "
|
||||||
f"pokus {attempt + 1}/"
|
f"pokus {attempt + 1}/{max_attempts} "
|
||||||
f"{max_attempts} "
|
f"za {delay:.1f}s ({error})",
|
||||||
f"za {delay:.1f}s "
|
|
||||||
f"({error})"
|
|
||||||
),
|
|
||||||
file=sys.stderr,
|
file=sys.stderr,
|
||||||
)
|
)
|
||||||
|
|
||||||
if delay > 0:
|
if delay > 0:
|
||||||
time.sleep(
|
time.sleep(delay)
|
||||||
delay
|
|
||||||
)
|
|
||||||
|
|
||||||
if last_error is not None:
|
if last_error is not None:
|
||||||
raise last_error
|
raise last_error
|
||||||
|
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"HTTP request skončil "
|
"HTTP request skončil v neočakávanom stave."
|
||||||
"v neočakávanom stave."
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -427,45 +274,27 @@ def resolve_refs(
|
|||||||
value: Any,
|
value: Any,
|
||||||
document: dict[str, Any],
|
document: dict[str, Any],
|
||||||
) -> Any:
|
) -> Any:
|
||||||
if isinstance(
|
if isinstance(value, list):
|
||||||
value,
|
|
||||||
list,
|
|
||||||
):
|
|
||||||
return [
|
return [
|
||||||
resolve_refs(
|
resolve_refs(item, document)
|
||||||
item,
|
|
||||||
document,
|
|
||||||
)
|
|
||||||
for item in value
|
for item in value
|
||||||
]
|
]
|
||||||
|
|
||||||
if not isinstance(
|
if not isinstance(value, dict):
|
||||||
value,
|
|
||||||
dict,
|
|
||||||
):
|
|
||||||
return value
|
return value
|
||||||
|
|
||||||
ref = value.get(
|
ref = value.get("$ref")
|
||||||
"$ref"
|
|
||||||
)
|
|
||||||
|
|
||||||
if ref:
|
if ref:
|
||||||
if not ref.startswith(
|
if not ref.startswith("#/"):
|
||||||
"#/"
|
|
||||||
):
|
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"Nepodporovaný "
|
f"Nepodporovaný OpenAPI $ref: {ref}"
|
||||||
f"OpenAPI $ref: {ref}"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
current: Any = document
|
current: Any = document
|
||||||
|
|
||||||
for part in ref[
|
for part in ref[2:].split("/"):
|
||||||
2:
|
current = current[part]
|
||||||
].split("/"):
|
|
||||||
current = current[
|
|
||||||
part
|
|
||||||
]
|
|
||||||
|
|
||||||
resolved = resolve_refs(
|
resolved = resolve_refs(
|
||||||
current,
|
current,
|
||||||
@ -474,18 +303,11 @@ def resolve_refs(
|
|||||||
|
|
||||||
extra = {
|
extra = {
|
||||||
key: item
|
key: item
|
||||||
for key, item
|
for key, item in value.items()
|
||||||
in value.items()
|
|
||||||
if key != "$ref"
|
if key != "$ref"
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if extra and isinstance(resolved, dict):
|
||||||
extra
|
|
||||||
and isinstance(
|
|
||||||
resolved,
|
|
||||||
dict,
|
|
||||||
)
|
|
||||||
):
|
|
||||||
resolved = {
|
resolved = {
|
||||||
**resolved,
|
**resolved,
|
||||||
**resolve_refs(
|
**resolve_refs(
|
||||||
@ -501,32 +323,25 @@ def resolve_refs(
|
|||||||
item,
|
item,
|
||||||
document,
|
document,
|
||||||
)
|
)
|
||||||
for key, item
|
for key, item in value.items()
|
||||||
in value.items()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def build_rag_tool(
|
def build_rag_tool(
|
||||||
openapi: dict[str, Any],
|
openapi: dict[str, Any],
|
||||||
) -> tuple[
|
) -> tuple[str, dict[str, Any]]:
|
||||||
str,
|
|
||||||
dict[str, Any],
|
|
||||||
]:
|
|
||||||
try:
|
try:
|
||||||
operation = (
|
operation = openapi[
|
||||||
openapi[
|
|
||||||
"paths"
|
"paths"
|
||||||
][
|
][
|
||||||
"/rag"
|
"/rag"
|
||||||
][
|
][
|
||||||
"post"
|
"post"
|
||||||
]
|
]
|
||||||
)
|
|
||||||
|
|
||||||
except KeyError as exc:
|
except KeyError as exc:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"OpenAPI schéma neobsahuje "
|
"OpenAPI schéma neobsahuje POST /rag."
|
||||||
"POST /rag."
|
|
||||||
) from exc
|
) from exc
|
||||||
|
|
||||||
operation_id = operation.get(
|
operation_id = operation.get(
|
||||||
@ -539,8 +354,7 @@ def build_rag_tool(
|
|||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
schema = (
|
schema = operation[
|
||||||
operation[
|
|
||||||
"requestBody"
|
"requestBody"
|
||||||
][
|
][
|
||||||
"content"
|
"content"
|
||||||
@ -549,12 +363,10 @@ def build_rag_tool(
|
|||||||
][
|
][
|
||||||
"schema"
|
"schema"
|
||||||
]
|
]
|
||||||
)
|
|
||||||
|
|
||||||
except KeyError as exc:
|
except KeyError as exc:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"POST /rag nemá "
|
"POST /rag nemá request JSON schema."
|
||||||
"request JSON schema."
|
|
||||||
) from exc
|
) from exc
|
||||||
|
|
||||||
parameters = resolve_refs(
|
parameters = resolve_refs(
|
||||||
@ -579,12 +391,8 @@ def build_rag_tool(
|
|||||||
"type": "function",
|
"type": "function",
|
||||||
"function": {
|
"function": {
|
||||||
"name": operation_id,
|
"name": operation_id,
|
||||||
"description": (
|
"description": description,
|
||||||
description
|
"parameters": parameters,
|
||||||
),
|
|
||||||
"parameters": (
|
|
||||||
parameters
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -599,11 +407,9 @@ def normalize_url(
|
|||||||
) -> str:
|
) -> str:
|
||||||
value = value.strip()
|
value = value.strip()
|
||||||
|
|
||||||
match = (
|
match = MARKDOWN_URL_RE.search(
|
||||||
MARKDOWN_URL_RE.search(
|
|
||||||
value
|
value
|
||||||
)
|
)
|
||||||
)
|
|
||||||
|
|
||||||
if match:
|
if match:
|
||||||
value = match.group(
|
value = match.group(
|
||||||
@ -618,14 +424,9 @@ def normalize_url(
|
|||||||
def extract_urls_from_object(
|
def extract_urls_from_object(
|
||||||
value: Any,
|
value: Any,
|
||||||
) -> list[str]:
|
) -> list[str]:
|
||||||
result: list[
|
result: list[str] = []
|
||||||
str
|
|
||||||
] = []
|
|
||||||
|
|
||||||
if isinstance(
|
if isinstance(value, dict):
|
||||||
value,
|
|
||||||
dict,
|
|
||||||
):
|
|
||||||
for key, item in value.items():
|
for key, item in value.items():
|
||||||
if (
|
if (
|
||||||
key == "source_url"
|
key == "source_url"
|
||||||
@ -646,10 +447,7 @@ def extract_urls_from_object(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
elif isinstance(
|
elif isinstance(value, list):
|
||||||
value,
|
|
||||||
list,
|
|
||||||
):
|
|
||||||
for item in value:
|
for item in value:
|
||||||
result.extend(
|
result.extend(
|
||||||
extract_urls_from_object(
|
extract_urls_from_object(
|
||||||
@ -781,8 +579,7 @@ def parse_tool_arguments(
|
|||||||
str,
|
str,
|
||||||
):
|
):
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"Neplatný formát "
|
"Neplatný formát tool arguments."
|
||||||
"tool arguments."
|
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -802,8 +599,7 @@ def parse_tool_arguments(
|
|||||||
dict,
|
dict,
|
||||||
):
|
):
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"Tool arguments nie sú "
|
"Tool arguments nie sú JSON objekt."
|
||||||
"JSON objekt."
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return parsed
|
return parsed
|
||||||
@ -812,9 +608,7 @@ def parse_tool_arguments(
|
|||||||
def build_initial_messages(
|
def build_initial_messages(
|
||||||
question: str,
|
question: str,
|
||||||
) -> list[dict[str, Any]]:
|
) -> list[dict[str, Any]]:
|
||||||
question = (
|
question = question.strip()
|
||||||
question.strip()
|
|
||||||
)
|
|
||||||
|
|
||||||
if not question:
|
if not question:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
@ -835,6 +629,17 @@ def build_initial_messages(
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def forced_tool_choice(
|
||||||
|
operation_id: str,
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
return {
|
||||||
|
"type": "function",
|
||||||
|
"function": {
|
||||||
|
"name": operation_id,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def run_question(
|
def run_question(
|
||||||
question: str,
|
question: str,
|
||||||
*,
|
*,
|
||||||
@ -855,9 +660,7 @@ def run_question(
|
|||||||
"Otázka je prázdna."
|
"Otázka je prázdna."
|
||||||
)
|
)
|
||||||
|
|
||||||
started = (
|
started = time.perf_counter()
|
||||||
time.perf_counter()
|
|
||||||
)
|
|
||||||
|
|
||||||
usage_total = {
|
usage_total = {
|
||||||
"prompt_tokens": 0,
|
"prompt_tokens": 0,
|
||||||
@ -873,23 +676,18 @@ def run_question(
|
|||||||
str
|
str
|
||||||
] = []
|
] = []
|
||||||
|
|
||||||
messages = (
|
messages = build_initial_messages(
|
||||||
build_initial_messages(
|
|
||||||
question
|
question
|
||||||
)
|
)
|
||||||
)
|
|
||||||
|
|
||||||
first_started = (
|
first_started = time.perf_counter()
|
||||||
time.perf_counter()
|
|
||||||
)
|
|
||||||
|
|
||||||
first_response = request_json(
|
first_response = request_json(
|
||||||
OPENWEBUI_URL,
|
OPENWEBUI_URL,
|
||||||
method="POST",
|
method="POST",
|
||||||
headers={
|
headers={
|
||||||
"Authorization": (
|
"Authorization": (
|
||||||
"Bearer "
|
f"Bearer {openwebui_api_key}"
|
||||||
f"{openwebui_api_key}"
|
|
||||||
),
|
),
|
||||||
"Content-Type": (
|
"Content-Type": (
|
||||||
"application/json"
|
"application/json"
|
||||||
@ -942,47 +740,25 @@ def run_question(
|
|||||||
tool_calls = []
|
tool_calls = []
|
||||||
|
|
||||||
if not tool_calls:
|
if not tool_calls:
|
||||||
answer = str(
|
tool_calls = [
|
||||||
first_message.get(
|
{
|
||||||
"content"
|
"id": (
|
||||||
)
|
"fallback_zpwiki_search"
|
||||||
or ""
|
|
||||||
)
|
|
||||||
|
|
||||||
total_latency = (
|
|
||||||
time.perf_counter()
|
|
||||||
- started
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
|
||||||
"answer": answer,
|
|
||||||
"tool_called": False,
|
|
||||||
"tool_call_count": 0,
|
|
||||||
"tool_calls": [],
|
|
||||||
"rag_source_urls": [],
|
|
||||||
"first_model_latency_seconds": (
|
|
||||||
round(
|
|
||||||
first_latency,
|
|
||||||
6,
|
|
||||||
)
|
|
||||||
),
|
),
|
||||||
"tool_latency_seconds": 0.0,
|
"type": "function",
|
||||||
"final_model_latency_seconds": 0.0,
|
"function": {
|
||||||
"total_latency_seconds": (
|
"name": (
|
||||||
round(
|
operation_id
|
||||||
total_latency,
|
|
||||||
6,
|
|
||||||
)
|
|
||||||
),
|
),
|
||||||
"usage": (
|
"arguments": json.dumps(
|
||||||
usage_total
|
{
|
||||||
),
|
"query": question,
|
||||||
"response_model": (
|
},
|
||||||
first_response.get(
|
ensure_ascii=False,
|
||||||
"model"
|
|
||||||
)
|
|
||||||
),
|
),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
]
|
||||||
|
|
||||||
messages.append(
|
messages.append(
|
||||||
{
|
{
|
||||||
@ -1041,6 +817,16 @@ def run_question(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Query vždy prepíšeme celou
|
||||||
|
# pôvodnou používateľskou otázkou.
|
||||||
|
# Model si môže ponechať iba
|
||||||
|
# neškodné voliteľné parametre
|
||||||
|
# ako limit/published_only/
|
||||||
|
# max_per_document.
|
||||||
|
arguments[
|
||||||
|
"query"
|
||||||
|
] = question
|
||||||
|
|
||||||
tool_started = (
|
tool_started = (
|
||||||
time.perf_counter()
|
time.perf_counter()
|
||||||
)
|
)
|
||||||
@ -1091,11 +877,9 @@ def run_question(
|
|||||||
{
|
{
|
||||||
"name": name,
|
"name": name,
|
||||||
"arguments": arguments,
|
"arguments": arguments,
|
||||||
"latency_seconds": (
|
"latency_seconds": round(
|
||||||
round(
|
|
||||||
tool_latency,
|
tool_latency,
|
||||||
6,
|
6,
|
||||||
)
|
|
||||||
),
|
),
|
||||||
"source_urls": (
|
"source_urls": (
|
||||||
current_urls
|
current_urls
|
||||||
@ -1137,8 +921,7 @@ def run_question(
|
|||||||
method="POST",
|
method="POST",
|
||||||
headers={
|
headers={
|
||||||
"Authorization": (
|
"Authorization": (
|
||||||
"Bearer "
|
f"Bearer {openwebui_api_key}"
|
||||||
f"{openwebui_api_key}"
|
|
||||||
),
|
),
|
||||||
"Content-Type": (
|
"Content-Type": (
|
||||||
"application/json"
|
"application/json"
|
||||||
@ -1197,33 +980,23 @@ def run_question(
|
|||||||
"rag_source_urls": (
|
"rag_source_urls": (
|
||||||
rag_source_urls
|
rag_source_urls
|
||||||
),
|
),
|
||||||
"first_model_latency_seconds": (
|
"first_model_latency_seconds": round(
|
||||||
round(
|
|
||||||
first_latency,
|
first_latency,
|
||||||
6,
|
6,
|
||||||
)
|
|
||||||
),
|
),
|
||||||
"tool_latency_seconds": (
|
"tool_latency_seconds": round(
|
||||||
round(
|
|
||||||
tool_latency_total,
|
tool_latency_total,
|
||||||
6,
|
6,
|
||||||
)
|
|
||||||
),
|
),
|
||||||
"final_model_latency_seconds": (
|
"final_model_latency_seconds": round(
|
||||||
round(
|
|
||||||
final_latency,
|
final_latency,
|
||||||
6,
|
6,
|
||||||
)
|
|
||||||
),
|
),
|
||||||
"total_latency_seconds": (
|
"total_latency_seconds": round(
|
||||||
round(
|
|
||||||
total_latency,
|
total_latency,
|
||||||
6,
|
6,
|
||||||
)
|
|
||||||
),
|
|
||||||
"usage": (
|
|
||||||
usage_total
|
|
||||||
),
|
),
|
||||||
|
"usage": usage_total,
|
||||||
"response_model": (
|
"response_model": (
|
||||||
final_response.get(
|
final_response.get(
|
||||||
"model"
|
"model"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user