feat: switch LLM to OpenWebUI with live model selection
Rebuild & Deploy / rebuild (push) Successful in 3m18s

- Point the agent at OpenWebUI's OpenAI-compatible API (LLM_BASE_URL,
  LLM_API_KEY, LLM_MODEL) with GEMMA_* env fallbacks
- Add list_available_models() fetching {base_url}/models, filtering
  non-chat ids (embedding/reranker)
- Rename GemmaLLM -> HopeLLM with update_options(model=...) for runtime
  model switching (mutates _opts.model) and instance-based compaction
- Data channel: agent publishes {type: models} after connect and on
  get_models; set_model switches live and acks model_changed
- Web UI: Brain section in the settings sheet with a model picker that
  persists the choice in localStorage
- Update tests, CI workflow env, and docs for the new endpoint
This commit is contained in:
snowspeeder committed 2026-08-29 12:09:03 -04:00
1 parent 024c42ffb1
commit 360e3276b0
14 files changed
+334 -76

No files matched your search

+12 -11
View File
@@ -1,26 +1,27 @@
"""Tests for GemmaLLM context compaction (agent.GemmaLLM._compact_context).
"""Tests for HopeLLM context compaction (agent.HopeLLM._compact_context).
Compaction runs a real side LLM call to the xNAS, so these are marked slow.
The agent module is imported inside the container via docker exec.
Compaction runs a real side LLM call to the configured endpoint, so these
are marked slow. The agent module is imported inside the container via
docker exec.
"""
import pytest
from conftest import GEMMA_BASE_URL, GEMMA_MODEL, docker_exec_python
from conftest import LLM_BASE_URL, LLM_MODEL, docker_exec_python
# {num_messages} and {do_chat} are filled per test; all other braces are literal.
TEMPLATE = '''
import logging, sys
import logging, os, sys
from livekit.agents import llm as lk_llm
sys.path.insert(0, "/opt/voice-agent")
from agent import GemmaLLM
from agent import HopeLLM
logging.basicConfig(level=logging.INFO, stream=sys.stdout)
llm = GemmaLLM(
llm = HopeLLM(
model="{model}",
base_url="{base_url}",
api_key="not-needed",
api_key=os.environ.get("LLM_API_KEY", "not-needed"),
max_completion_tokens=100,
extra_body={{"chat_template_kwargs": {{"enable_thinking": False}}}},
)
@@ -44,7 +45,7 @@ async def main():
print("AFTER items=" + str(len(compacted.items)))
for it in compacted.items:
role = getattr(it, "role", None)
content = GemmaLLM._msg_content_text(it)
content = HopeLLM._msg_content_text(it)
print("ITEM " + str(role) + ": " + content[:120])
{do_chat}
@@ -57,8 +58,8 @@ CHAT_SNIPPET = ' stream = llm.chat(chat_ctx=compacted)\n collected = await
def _run(num_messages: int, *, do_chat: bool = False) -> str:
code = TEMPLATE.format(
model=GEMMA_MODEL,
base_url=GEMMA_BASE_URL,
model=LLM_MODEL,
base_url=LLM_BASE_URL,
num_messages=num_messages,
do_chat=CHAT_SNIPPET if do_chat else "pass",
)