Download src/fgao/integrations/huggingface.py from Angshul/FGAO-API: direct link, hf CLI and curl.
- Browser
- Download file 6.4 kB
-
https://huggingface.co/spaces/Angshul/FGAO-API/resolve/main/src/fgao/integrations/huggingface.py
- Command line
-
hf download hf://spaces/Angshul/FGAO-API/src/fgao/integrations/huggingface.py
-
curl -L -o huggingface.py https://huggingface.co/spaces/Angshul/FGAO-API/resolve/main/src/fgao/integrations/huggingface.py
6.4 kB
| """Hugging Face components (install ``fgao[huggingface]``; ``transformers`` for local models). | |
| * ``HFInferenceLLM`` chat model behind the Hugging Face Inference API / Providers | |
| * ``TransformersLLM`` local ``transformers`` text-generation model | |
| * ``hf_embedder`` embedding function for ``DenseRetriever`` (Inference API) | |
| * ``st_embedder`` embedding function from a local ``sentence-transformers`` model | |
| * ``load_hf_corpus`` documents from a Hugging Face dataset | |
| """ | |
| from __future__ import annotations | |
| import os | |
| from typing import Any, Callable, List, Optional | |
| from ..rag.documents import Document | |
| from ..rag.prompts import SYSTEM_PROMPT | |
| class HFInferenceLLM: | |
| """``huggingface_hub`` chat completion (Inference API / Providers / a TGI endpoint) as an FGAO LLM. | |
| Sync calls use ``InferenceClient``, async calls ``AsyncInferenceClient``. | |
| The token defaults to ``HF_TOKEN``. ``temperature=None`` leaves the | |
| provider default; set ``temperature=0`` where the provider accepts it for | |
| deterministic calls. ``base_url`` targets a dedicated endpoint. | |
| """ | |
| def __init__(self, model: str, *, token: Optional[str] = None, provider: Optional[str] = None, | |
| base_url: Optional[str] = None, max_tokens: int = 128, temperature: Optional[float] = None, | |
| system: Optional[str] = SYSTEM_PROMPT, timeout: Optional[float] = None, client: Any = None, | |
| aclient: Any = None, **kwargs: Any) -> None: | |
| self.model = model | |
| self._ckw: dict = {"token": token or os.environ.get("HF_TOKEN")} | |
| for k, v in (("provider", provider), ("base_url", base_url), ("timeout", timeout)): | |
| if v is not None: | |
| self._ckw[k] = v | |
| self._client, self._aclient = client, aclient | |
| self.max_tokens, self.temperature, self.system, self.kwargs = max_tokens, temperature, system, kwargs | |
| def client(self): | |
| if self._client is None: | |
| from huggingface_hub import InferenceClient | |
| self._client = InferenceClient(**self._ckw) | |
| return self._client | |
| def aclient(self): | |
| if self._aclient is None: | |
| from huggingface_hub import AsyncInferenceClient | |
| self._aclient = AsyncInferenceClient(**self._ckw) | |
| return self._aclient | |
| def _args(self, prompt: str) -> dict: | |
| messages = ([{"role": "system", "content": self.system}] if self.system else []) + [ | |
| {"role": "user", "content": prompt}] | |
| kw = dict(self.kwargs) | |
| if self.temperature is not None: | |
| kw["temperature"] = self.temperature | |
| kw.update(messages=messages, max_tokens=self.max_tokens) | |
| if "base_url" not in self._ckw: | |
| kw["model"] = self.model | |
| return kw | |
| def complete(self, prompt: str) -> str: | |
| out = self.client.chat_completion(**self._args(prompt)) | |
| return (out.choices[0].message.content or "").strip() | |
| async def acomplete(self, prompt: str) -> str: | |
| out = await self.aclient.chat_completion(**self._args(prompt)) | |
| return (out.choices[0].message.content or "").strip() | |
| class TransformersLLM: | |
| """A local ``transformers`` model (greedy decoding, chat template when available).""" | |
| def __init__(self, model: Any, *, max_new_tokens: int = 64, system: Optional[str] = SYSTEM_PROMPT, | |
| **pipeline_kwargs: Any) -> None: | |
| if isinstance(model, str): | |
| from transformers import pipeline | |
| model = pipeline("text-generation", model=model, **pipeline_kwargs) | |
| self.pipe, self.max_new_tokens, self.system = model, max_new_tokens, system | |
| def complete(self, prompt: str) -> str: | |
| tok = getattr(self.pipe, "tokenizer", None) | |
| if tok is not None and getattr(tok, "chat_template", None): | |
| msgs = ([{"role": "system", "content": self.system}] if self.system else []) + [ | |
| {"role": "user", "content": prompt}] | |
| out = self.pipe(msgs, max_new_tokens=self.max_new_tokens, do_sample=False) | |
| gen = out[0]["generated_text"] | |
| return (gen[-1]["content"] if isinstance(gen, list) else str(gen)).strip() | |
| out = self.pipe(prompt, max_new_tokens=self.max_new_tokens, do_sample=False, return_full_text=False) | |
| return out[0]["generated_text"].strip() | |
| def hf_embedder(model: str, *, token: Optional[str] = None, client: Any = None) -> Callable[[List[str]], List[List[float]]]: | |
| """Embedding function backed by ``InferenceClient.feature_extraction``.""" | |
| if client is None: | |
| from huggingface_hub import InferenceClient | |
| client = InferenceClient(token=token or os.environ.get("HF_TOKEN")) | |
| def embed(texts: List[str]) -> List[List[float]]: | |
| rows = [] | |
| for t in texts: | |
| v = client.feature_extraction(t, model=model) | |
| v = v.tolist() if hasattr(v, "tolist") else v | |
| while isinstance(v, list) and v and isinstance(v[0], list) and len(v) == 1: | |
| v = v[0] | |
| if v and isinstance(v[0], list): # token embeddings: mean-pool | |
| v = [sum(col) / len(v) for col in zip(*v)] | |
| rows.append([float(x) for x in v]) | |
| return rows | |
| return embed | |
| def st_embedder(model: str, **kwargs: Any) -> Callable[[List[str]], Any]: | |
| """Embedding function from a local ``sentence-transformers`` model.""" | |
| from sentence_transformers import SentenceTransformer | |
| m = SentenceTransformer(model, **kwargs) | |
| return lambda texts: m.encode(list(texts), normalize_embeddings=True) | |
| def load_hf_corpus(path: str, *, split: str = "train", text_field: str = "text", title_field: Optional[str] = None, | |
| id_field: Optional[str] = None, limit: Optional[int] = None, **kwargs: Any) -> List[Document]: | |
| """Documents from a Hugging Face dataset (``datasets.load_dataset``).""" | |
| from datasets import load_dataset | |
| ds = load_dataset(path, split=split, **kwargs) | |
| out: List[Document] = [] | |
| for i, row in enumerate(ds): | |
| if limit is not None and i >= limit: | |
| break | |
| out.append(Document(text=str(row[text_field]), title=str(row[title_field]) if title_field else "", | |
| id=str(row[id_field]) if id_field else str(i))) | |
| return out | |
| __all__ = ["HFInferenceLLM", "TransformersLLM", "hf_embedder", "st_embedder", "load_hf_corpus"] | |