from types import SimpleNamespace from typing import Any from huggingface_hub import InferenceClient from config import HF_MODEL, HF_PROVIDER, HF_TOKEN class LLMClient: """ Hugging Face Inference Providers üzerinden sohbet tamamlama ve structured output üretir. Dönen veri yapısı eski Ollama istemcisiyle uyumludur: response.message.content response.message.thinking """ def __init__(self) -> None: if not HF_TOKEN: raise RuntimeError( "HF_TOKEN bulunamadı. Hugging Face Space ayarlarında " "HF_TOKEN adında bir Secret oluşturun." ) self.client = InferenceClient( provider=HF_PROVIDER, api_key=HF_TOKEN, timeout=90, ) self.model = HF_MODEL @staticmethod def _create_response( content: str, thinking: str = "", ) -> SimpleNamespace: return SimpleNamespace( message=SimpleNamespace( content=content, thinking=thinking, ) ) def _chat_completion( self, request_kwargs: dict[str, Any], ) -> Any: """ Planner için JSON Schema kullanır. Responder için standart chat completion çağrısı yapar. """ schema = request_kwargs.pop("_schema", None) try: if schema is not None: request_kwargs["response_format"] = { "type": "json_schema", "json_schema": { "name": "ToolPlan", "schema": schema, "strict": True, }, } return self.client.chat_completion( **request_kwargs ) except Exception as exc: call_type = ( "structured output" if schema is not None else "standart chat completion" ) raise RuntimeError( "Hugging Face LLM çağrısı başarısız oldu.\n" f"Çağrı türü: {call_type}\n" f"Model: {self.model}\n" f"Provider: {HF_PROVIDER}\n" f"Hata türü: {type(exc).__name__}\n" f"Hata: {repr(exc)}" ) from exc def generate( self, messages: list[dict[str, str]], schema: dict[str, Any] | None = None, think: bool = True, ) -> SimpleNamespace: """ Modele mesaj gönderir. `think` parametresi Planner ve Responder kodlarıyla geriye dönük uyumluluk amacıyla korunmaktadır. """ request_kwargs: dict[str, Any] = { "model": self.model, "messages": messages, "temperature": 0.0, "max_tokens": 1024, "_schema": schema, } response = self._chat_completion( request_kwargs=request_kwargs, ) choices = getattr(response, "choices", None) if not choices: raise RuntimeError( "Hugging Face modeli boş bir choices listesi döndürdü." ) message = getattr(choices[0], "message", None) if message is None: raise RuntimeError( "Hugging Face yanıtında message alanı bulunamadı." ) content = getattr(message, "content", None) if content is None: raise RuntimeError( "Hugging Face yanıtında content alanı boş döndü." ) if not isinstance(content, str): content = str(content) content = content.strip() if not content: raise RuntimeError( "Hugging Face modeli boş metin döndürdü." ) return self._create_response( content=content, thinking="", ) llm = LLMClient()