Files

93 lines
2.6 KiB
Python
Raw Permalink Normal View History

"""LLM Adapter
Thin abstraction over a concrete LLM backend (local or remote).
"""
from __future__ import annotations
from dataclasses import dataclass
from typing import Any, Callable, Dict, Optional
@dataclass
class LLMResponse:
"""Container for a single LLM interaction."""
prompt: str
response: str
tokens_prompt: Optional[int] = None
tokens_response: Optional[int] = None
model_name: Optional[str] = None
metadata: Dict[str, Any] = None
def to_dict(self) -> Dict[str, Any]:
return {
"prompt": self.prompt,
"response": self.response,
"tokens_prompt": self.tokens_prompt,
"tokens_response": self.tokens_response,
"model_name": self.model_name,
"metadata": self.metadata or {},
}
class LLMAdapter:
"""Adapter around a concrete LLM backend.
backend: Callable that takes (prompt, **kwargs) and returns:
- str
- or dict with keys like:
- response
- tokens_prompt
- tokens_response
- model_name
"""
def __init__(
self,
backend: Callable[..., Any],
model_name: Optional[str] = None,
) -> None:
self._backend = backend
self._model_name = model_name or "unknown"
def run(self, prompt: str, **kwargs: Any) -> LLMResponse:
"""Run the underlying LLM on a prompt and normalize the result."""
raw = self._backend(prompt, **kwargs)
if isinstance(raw, str):
return LLMResponse(
prompt=prompt,
response=raw,
model_name=self._model_name,
metadata={},
)
if isinstance(raw, dict):
return LLMResponse(
prompt=prompt,
response=str(raw.get("response", "")),
tokens_prompt=raw.get("tokens_prompt"),
tokens_response=raw.get("tokens_response"),
model_name=raw.get("model_name", self._model_name),
metadata={
k: v
for k, v in raw.items()
if k
not in {
"response",
"tokens_prompt",
"tokens_response",
"model_name",
}
},
)
# Fallback: best-effort stringification
return LLMResponse(
prompt=prompt,
response=str(raw),
model_name=self._model_name,
metadata={"raw_type": type(raw).__name__},
)