Skip to content

yggdrasil.loki.engines.ollama_engine

ollama_engine

Local Ollama -backed :class:TokenEngine.

Talks to a local Ollama <https://ollama.com>_ server (default http://localhost:11434, override with OLLAMA_HOST) over its native chat API — so any open model you've ollama pull-ed runs on this machine, free and private. Available only when the server answers.

Every call rides the project's :class:~yggdrasil.http_.HTTPSession (its connection pooling, retry budget, and response parsing) — no bespoke HTTP — so probes get a quick single-shot waiting profile and the real calls reuse the shared pool.

OllamaEngine

OllamaEngine(
    *,
    model: Optional[str] = None,
    tier: Optional[str] = None,
    host: Optional[str] = None
)

Bases: LocalEngine

Reason with a local model served by Ollama, sized to the workstation.

bootstrap_model property

bootstrap_model: str

The default model for this box — the resource-sized row, or the engine's :attr:default_model fallback.

installed_models

installed_models() -> list[str]

Models already pulled onto this Ollama server (empty if unreachable).

has_model

has_model(model: str) -> bool

True when model (with or without a :tag) is already pulled.

pull

pull(
    model: Optional[str] = None,
    *,
    timeout: float = 1800.0,
    on_progress: "Optional[Callable[[dict[str, Any]], None]]" = None
) -> str

Download model onto the Ollama server (lazy — the heavy bit).

Defaults to :attr:bootstrap_model, the lightweight brain. Without on_progress this does the non-streaming pull (one final status object). With on_progress it streams Ollama's NDJSON progress events — {"status", "completed", "total"} per chunk — so a caller can render a live download bar; the final status string is returned either way.

ensure

ensure(
    model: Optional[str] = None,
    on_progress: "Optional[Callable[[dict[str, Any]], None]]" = None,
) -> dict[str, Any]

Make sure model is available, pulling it only if missing.

Returns {"model", "was_present", "status"} — the lazy-install receipt so a caller (the setup skill) can report what it did. An on_progress callback streams the pull's download progress.

choose_tier

choose_tier(
    messages: Optional[list[dict[str, Any]]] = None,
    system: Optional[str] = None,
) -> str

Adaptive tier for this request: "deep" or "fast".

Sizes on the message content (the actual work, not the fixed system boilerplate) and scans both message and system text for reasoning signals. Long or signalled requests get the deep tier; the rest stay fast. Override for a smarter policy.

resolve_model

resolve_model(
    *,
    messages: Optional[list[dict[str, Any]]] = None,
    system: Optional[str] = None,
    tier: Optional[str] = None
) -> Optional[str]

An explicit pin wins; otherwise the model sized to this workstation.

Local models are resource-bound, so the remote fast/deep cost tier doesn't apply — the box, not the prompt, picks the size.

usage

usage() -> list[Any]

This engine's per-model usage rows from the global meter.

generate

generate(
    prompt: str,
    *,
    system: Optional[str] = None,
    tier: Optional[str] = None,
    **options: Any
) -> str

Convenience: complete a single user prompt → reply text.

stream

stream(
    messages: list[dict[str, Any]],
    *,
    system: Optional[str] = None,
    max_tokens: int = DEFAULT_MAX_TOKENS,
    tier: Optional[str] = None,
    **options: Any
) -> "Iterator[str]"

Yield reply text incrementally as it is produced.

The default has no real streaming — it runs :meth:complete and yields the whole reply once. Engines whose SDK streams override this to yield token deltas live (and still record usage on completion).

generate_stream

generate_stream(
    prompt: str,
    *,
    system: Optional[str] = None,
    tier: Optional[str] = None,
    **options: Any
) -> "Iterator[str]"

Convenience: stream a single user prompt → text chunks.