Files
chat_backend/llm_be/chat_backend/ollama_config.py
T
westfarn 093e5462a5
Deploy Beta / unit-tests (push) Successful in 11s
Unit Tests / test (push) Successful in 11s
Deploy Beta / docker (push) Successful in 30s
Deploy Beta / deploy-beta (push) Successful in 7m19s
Eval harness (#62 P4), status frames, and agentic runs (#63) (#71)
## Summary
- Closes Phase 4 of [#62](#62): `evals/suite.json` (≥40 graded questions), `run_evals` management command, and manually-triggered `.gitea/workflows/run-evals.yml`.
- Emits versioned WS `status` frames during grounded chat (evaluating / searching / reading_sources / refining / writing) for [chat_web_app#96](ai_ml_operations/chat_web_app#96).
- Implements [#63](#63): Redis/Celery optional infra, `AgentRun`/`AgentStep`, tool registry (SSRF-safe `fetch_url`, tenant-scoped docs), LangGraph orchestrator, progress frames, REST `GET/POST /api/agent_runs/…`, gated by `ALLOW_AGENTIC_TASKS` (default off).

## Test plan
- [x] `SKIP_RAG_INIT=1 uv run python manage.py test` for evals, ws frames, agent tools, consumers, grounding
- [ ] Manual: with `ALLOW_AGENTIC_TASKS=false`, chat identical to today
- [ ] Manual: status frames visible in FE with #96 branch
- [ ] Manual (GPU): `python manage.py run_evals --runs 3`
- [ ] Manual: `ALLOW_AGENTIC_TASKS=true` multi-step research prompt creates AgentRun + framesReviewed-on: #71
2026-08-04 04:08:50 -07:00

122 lines
3.7 KiB
Python

"""Shared Ollama client helpers — always use settings.OLLAMA_BASE_URL.
Role-scoped models (#62): THINKING / FAST / UTILITY / EMBED each resolve
independently, with ``OLLAMA_MODEL`` kept as a fallback so existing deploys
keep working until they set the role-specific vars.
"""
from __future__ import annotations
from django.conf import settings
ROLE_THINKING = "thinking"
ROLE_FAST = "fast"
ROLE_UTILITY = "utility"
ROLE_EMBED = "embed"
# Agentic task execution (#63): orchestrator plans/synthesises; sub-agents run
# independent plan steps concurrently on a smaller/cheaper model.
ROLE_ORCHESTRATOR = "orchestrator"
ROLE_SUBAGENT = "subagent"
_VALID_ROLES = {
ROLE_THINKING,
ROLE_FAST,
ROLE_UTILITY,
ROLE_EMBED,
ROLE_ORCHESTRATOR,
ROLE_SUBAGENT,
}
def ollama_base_url() -> str:
return getattr(settings, "OLLAMA_BASE_URL", "http://127.0.0.1:11434")
def ollama_model(default: str | None = None) -> str:
"""Legacy single-model accessor. Prefer :func:`ollama_model_for_role`."""
if default:
return default
return getattr(settings, "OLLAMA_MODEL", "gpt-oss:20b")
def ollama_model_for_role(role: str) -> str:
"""Resolve the model name for a generation role.
Lookup order: role-specific setting → ``OLLAMA_MODEL`` fallback →
hard-coded role default (never falls back from embed → chat model).
"""
role = (role or ROLE_THINKING).lower()
if role not in _VALID_ROLES:
raise ValueError(f"Unknown Ollama role: {role!r}")
role_setting = {
ROLE_THINKING: "OLLAMA_MODEL_THINKING",
ROLE_FAST: "OLLAMA_MODEL_FAST",
ROLE_UTILITY: "OLLAMA_MODEL_UTILITY",
ROLE_EMBED: "OLLAMA_EMBED_MODEL",
ROLE_ORCHESTRATOR: "OLLAMA_MODEL_ORCHESTRATOR",
ROLE_SUBAGENT: "OLLAMA_MODEL_SUBAGENT",
}[role]
role_default = {
ROLE_THINKING: "gpt-oss:20b",
ROLE_FAST: "gemma4:latest",
ROLE_UTILITY: "llama3.2",
ROLE_EMBED: "nomic-embed-text",
ROLE_ORCHESTRATOR: "gpt-oss:20b",
ROLE_SUBAGENT: "llama3.2",
}[role]
configured = getattr(settings, role_setting, None)
if configured:
return configured
# Embeddings must never silently fall back to a chat model (#62).
if role == ROLE_EMBED:
return role_default
legacy = getattr(settings, "OLLAMA_MODEL", None)
if legacy:
return legacy
return role_default
def ollama_embed_model() -> str:
return ollama_model_for_role(ROLE_EMBED)
def ollama_num_ctx_for_role(role: str) -> int:
role = (role or ROLE_THINKING).lower()
if role == ROLE_FAST:
return int(getattr(settings, "OLLAMA_NUM_CTX_FAST", 8192) or 8192)
if role == ROLE_UTILITY:
return int(getattr(settings, "OLLAMA_NUM_CTX_UTILITY", 4096) or 4096)
if role == ROLE_SUBAGENT:
return int(getattr(settings, "OLLAMA_NUM_CTX_SUBAGENT", 8192) or 8192)
if role == ROLE_ORCHESTRATOR:
return int(getattr(settings, "OLLAMA_NUM_CTX_ORCHESTRATOR", 16384) or 16384)
return int(getattr(settings, "OLLAMA_NUM_CTX_THINKING", 16384) or 16384)
def resolve_chat_role(model_name: str | None) -> str:
"""Map FE mode selector (FAST / THINKING / …) to an Ollama role."""
if (model_name or "").upper() == "FAST":
return ROLE_FAST
return ROLE_THINKING
def ollama_llm_kwargs(role: str = ROLE_THINKING, **extra):
"""Keyword args for langchain_ollama.OllamaLLM / ChatOllama."""
kwargs = {
"base_url": ollama_base_url(),
"model": ollama_model_for_role(role),
"num_ctx": ollama_num_ctx_for_role(role),
}
kwargs.update(extra)
return kwargs
def ollama_embeddings_kwargs(**extra):
kwargs = {"base_url": ollama_base_url(), "model": ollama_embed_model()}
kwargs.update(extra)
return kwargs