daimon / model /client.py
davidquicast's picture
chore: initial commit
f0347b4
Raw
History Blame Contribute Delete
4.87 kB
"""Provider-aware OpenAI-compatible client(s) for Daimon's models (F0/F2x).
Each modality (text, vision, omni, tts) is routed independently via
<MODALITY>_MODEL_PROVIDER in .env:
local -> llama.cpp / llama-server on this machine (text only)
hf_inference -> Hugging Face Inference Providers / a ZeroGPU Space (any modality)
small-model-whisperer extends this with constrained decoding (GBNF / json-schema)
for the appraisal step in F2.
TEXT_THINKING_MODE=true switches MiniCPM5 into "thinking" mode (enable_thinking,
temp/top_p 0.9/0.95 per the model's deployment cookbook) for the main reply.
Callers that need deterministic, grammar-constrained output (e.g. F2's appraisal
step) pass enable_thinking=False explicitly to override this regardless of the
env switch - reasoning tokens would otherwise eat into a small max_tokens budget
before the grammar-constrained JSON.
Smoke test:
python model/client.py
"""
import os
from pathlib import Path
from dotenv import load_dotenv
from openai import OpenAI
load_dotenv(Path(__file__).resolve().parent.parent / ".env")
MODALITIES = ("text", "vision", "omni", "tts")
THINKING_MODE = os.environ.get("TEXT_THINKING_MODE", "false").strip().lower() in ("1", "true", "yes")
def provider_for(modality: str) -> str:
if modality not in MODALITIES:
raise ValueError(f"unknown modality {modality!r}, expected one of {MODALITIES}")
return os.environ.get(f"{modality.upper()}_MODEL_PROVIDER", "local")
def get_client(modality: str = "text") -> tuple[OpenAI, str]:
"""Return (OpenAI client, model name) for `modality`, based on its provider switch."""
provider = provider_for(modality)
if provider == "local":
if modality != "text":
raise ValueError("local provider only serves the text model (MiniCPM5-1B); "
f"set {modality.upper()}_MODEL_PROVIDER to hf_inference")
base_url = os.environ.get("MODEL_BASE_URL", "http://localhost:8080/v1")
return OpenAI(base_url=base_url, api_key="sk-no-key-needed"), "local-model"
if provider == "hf_inference":
token = os.environ["HF_TOKEN"]
base_url = os.environ.get("HF_INFERENCE_BASE_URL") or "https://router.huggingface.co/v1"
model = os.environ[f"HF_{modality.upper()}_MODEL"]
return OpenAI(base_url=base_url, api_key=token), model
raise ValueError(f"unknown provider {provider!r} for {modality.upper()}_MODEL_PROVIDER")
def _prepare_local_kwargs(kwargs: dict, enable_thinking: bool | None) -> dict:
"""Set chat_template_kwargs.enable_thinking and matching temp/top_p defaults
for the local llama.cpp endpoint. `enable_thinking=None` falls back to
TEXT_THINKING_MODE; explicit True/False (e.g. appraise.py) always wins."""
use_thinking = THINKING_MODE if enable_thinking is None else enable_thinking
extra_body = kwargs.pop("extra_body", {})
extra_body.setdefault("chat_template_kwargs", {}).setdefault("enable_thinking", use_thinking)
kwargs["extra_body"] = extra_body
if use_thinking:
kwargs.setdefault("temperature", 0.9)
else:
kwargs.setdefault("temperature", 0.7)
kwargs.setdefault("top_p", 0.95)
return kwargs
def chat(messages, modality: str = "text", *, enable_thinking: bool | None = None, **kwargs):
"""Send a chat completion for `modality` and return the text content."""
client, model = get_client(modality)
if provider_for(modality) == "local":
kwargs = _prepare_local_kwargs(kwargs, enable_thinking)
resp = client.chat.completions.create(model=model, messages=messages, **kwargs)
return resp.choices[0].message.content
def chat_stream(messages, modality: str = "text", *, enable_thinking: bool | None = None, **kwargs):
"""Yield (kind, text) chunks as the reply streams in.
`kind` is "thinking" for <think> reasoning tokens (only emitted when
enable_thinking is on and the server reports `delta.reasoning_content`)
and "content" for the actual reply text.
"""
client, model = get_client(modality)
if provider_for(modality) == "local":
kwargs = _prepare_local_kwargs(kwargs, enable_thinking)
stream = client.chat.completions.create(model=model, messages=messages, stream=True, **kwargs)
for chunk in stream:
delta = chunk.choices[0].delta
reasoning = getattr(delta, "reasoning_content", None)
if reasoning:
yield "thinking", reasoning
if delta.content:
yield "content", delta.content
if __name__ == "__main__":
client, model = get_client("text")
print(f"Hitting {client.base_url} (model={model}, provider={provider_for('text')}) ...")
out = chat(
[{"role": "user", "content": "Reply with exactly: ok"}],
max_tokens=8,
temperature=0.0,
)
print("Model replied:", repr(out))