YOKPilot3 / llm.py
mfirat007's picture
Upload 98 files
eff511c
Raw
History Blame Contribute Delete
4.68 kB
import time
try:
from huggingface_hub import InferenceClient
except ImportError: # pragma: no cover
InferenceClient = None
try:
from openai import OpenAI
except ImportError: # pragma: no cover
OpenAI = None
from config import (
HF_MODEL, HF_TOKEN, GROQ_API_KEY, GROQ_MODEL, OPENAI_API_KEY, OPENAI_MODEL,
LLM_PROVIDER, MAX_NEW_TOKENS
)
# Canlı demoda tek bir asılı isteğin arayüzü kilitlememesi için açık bir
# istek aşımı. Önceden hiç tanımlı değildi (client kütüphanelerinin
# varsayılanına bırakılmıştı) — bu, ağ sorunlarında belirsiz bekleme
# sürelerine yol açabiliyordu.
REQUEST_TIMEOUT_SECONDS = 25.0
HF_CLIENT = (
InferenceClient(model=HF_MODEL, token=HF_TOKEN, timeout=REQUEST_TIMEOUT_SECONDS)
if HF_TOKEN and InferenceClient
else None
)
GROQ_CLIENT = (
OpenAI(api_key=GROQ_API_KEY, base_url="https://api.groq.com/openai/v1", timeout=REQUEST_TIMEOUT_SECONDS)
if GROQ_API_KEY and OpenAI
else None
)
OPENAI_CLIENT = (
OpenAI(api_key=OPENAI_API_KEY, timeout=REQUEST_TIMEOUT_SECONDS)
if OPENAI_API_KEY and OpenAI
else None
)
# Geçici ağ/oran-sınırı hatalarında tek seferlik yeniden deneme. Bu sayı
# bilinçli olarak düşük tutuldu (1 deneme, kısa bekleme) — amaç kalıcı bir
# sağlamlık katmanı değil, "bir kerelik kesinti yüzünden zengin LLM
# cevabını kaybetme" riskini azaltmak. Kalıcı hatalarda (ör. geçersiz API
# anahtarı) yine de yukarı fırlatılır, sessizce yutulmaz.
RETRY_ATTEMPTS = 2
RETRY_BACKOFF_SECONDS = 1.5
def llm_available() -> bool:
if LLM_PROVIDER == "auto":
return any((HF_CLIENT, GROQ_CLIENT, OPENAI_CLIENT))
if LLM_PROVIDER == "groq":
return GROQ_CLIENT is not None
if LLM_PROVIDER == "openai":
return OPENAI_CLIENT is not None
return HF_CLIENT is not None
def call_chat_completion(messages: list[dict], response_format: str = "text") -> str:
"""Call the configured chat completion provider.
response_format:
"text" (default) — no format constraint, unchanged behavior.
"json" — for providers that support it (OpenAI, Groq's OpenAI-
compatible API), enforce {"type": "json_object"} at the API
level rather than relying solely on prompt wording. Callers
that expect strict JSON (e.g. proof_bundle.llm_claim_prompt)
should pass response_format="json".
"""
provider = LLM_PROVIDER
if provider == "auto":
provider = "huggingface" if HF_CLIENT is not None else "groq" if GROQ_CLIENT is not None else "openai"
last_error: Exception | None = None
for attempt in range(RETRY_ATTEMPTS):
try:
return _dispatch_chat_completion(provider, messages, response_format)
except Exception as error: # noqa: BLE001 - deliberately broad, see docstring
last_error = error
if attempt < RETRY_ATTEMPTS - 1:
time.sleep(RETRY_BACKOFF_SECONDS)
continue
assert last_error is not None
raise last_error
def _dispatch_chat_completion(provider: str, messages: list[dict], response_format: str) -> str:
extra_kwargs = {}
if response_format == "json" and provider in {"groq", "openai"}:
extra_kwargs["response_format"] = {"type": "json_object"}
if provider == "groq":
if GROQ_CLIENT is None:
raise RuntimeError("GROQ_API_KEY tanımlı değil.")
response = GROQ_CLIENT.chat.completions.create(
model=GROQ_MODEL,
messages=messages,
max_tokens=MAX_NEW_TOKENS,
temperature=0.12,
top_p=0.9,
**extra_kwargs,
)
return response.choices[0].message.content.strip()
if provider == "openai":
if OPENAI_CLIENT is None:
raise RuntimeError("OPENAI_API_KEY tanımlı değil.")
response = OPENAI_CLIENT.chat.completions.create(
model=OPENAI_MODEL,
messages=messages,
max_tokens=MAX_NEW_TOKENS,
temperature=0.0,
top_p=0.9,
**extra_kwargs,
)
return response.choices[0].message.content.strip()
if HF_CLIENT is None:
raise RuntimeError("HF_TOKEN veya HUGGINGFACEHUB_API_TOKEN tanımlı değil.")
response = HF_CLIENT.chat.completions.create(
model=HF_MODEL,
messages=messages,
max_tokens=MAX_NEW_TOKENS,
temperature=0.12,
top_p=0.9,
)
return response.choices[0].message.content.strip()