""" Ollama LLM Client — wraps the Ollama REST API running in Docker. Provides a unified interface for all experiment scripts to call local models. """ import json import time from typing import Optional import ollama # ── Default Configuration ───────────────────────────────────────────── DEFAULT_BASE_URL = "http://localhost:11434" DEFAULT_MODEL = "qwen2.5:32b" class LLMClient: """A lightweight wrapper around the Ollama API.""" def __init__( self, model: str = DEFAULT_MODEL, base_url: str = DEFAULT_BASE_URL, temperature: float = 0.7, top_p: float = 0.9, max_tokens: int = 2048, num_ctx: int = 8192, seed: Optional[int] = None, ): self.model = model self.client = ollama.Client(host=base_url) self.options = dict( temperature=temperature, top_p=top_p, num_predict=max_tokens, num_ctx=num_ctx, ) if seed is not None: self.options["seed"] = seed # ── Public API ──────────────────────────────────────────────────── def generate(self, prompt: str, system: Optional[str] = None) -> str: """Simple text generation (no chat history).""" messages = [] if system: messages.append({"role": "system", "content": system}) messages.append({"role": "user", "content": prompt}) return self._chat(messages) def chat(self, messages: list) -> str: """Multi-turn chat. messages = [{"role": "user"/"assistant", "content": ...}].""" return self._chat(messages) def generate_with_metadata(self, prompt: str, system: Optional[str] = None) -> dict: """Like generate() but returns full response metadata.""" messages = [] if system: messages.append({"role": "system", "content": system}) messages.append({"role": "user", "content": prompt}) return self._chat_full(messages) def count_tokens(self, text: str) -> int: """Estimate token count (rough: ~4 chars per token for Chinese+English).""" return len(text) // 3 + 1 @property def model_name(self) -> str: return self.model # ── Internal ────────────────────────────────────────────────────── def _chat(self, messages: list) -> str: resp = self._chat_full(messages) return resp["message"]["content"] def _chat_full(self, messages: list) -> dict: resp = self.client.chat( model=self.model, messages=messages, options=self.options, ) return resp def __repr__(self) -> str: return f"LLMClient(model={self.model})" # ── Convenience Factory ─────────────────────────────────────────────── def make_client( model: str = DEFAULT_MODEL, temperature: float = 0.0, max_tokens: int = 2048, seed: int = 42, ) -> LLMClient: """Create a deterministic client (temperature=0) suitable for evaluation.""" return LLMClient( model=model, temperature=temperature, max_tokens=max_tokens, seed=seed, ) # ── Quick Test ──────────────────────────────────────────────────────── if __name__ == "__main__": # Test the client client = make_client() print(f"Model: {client.model_name}") resp = client.generate("Say hello in exactly one word.") print(f"Response: {resp}")