Bug fix: - GemmaLLM.chat(): len(chat_ctx) → len(chat_ctx.items) - GemmaLLM._compact_context(): list(chat_ctx) → list(chat_ctx.items) ChatContext in livekit-agents 1.7 is not iterable or sized directly; it exposes an .items list. The TypeError was silently killing all LLM responses (agent heard user but never spoke back). Test suite (tests/): - test_web_ui.py: static assets, token endpoint, LiveKit WS proxy - test_llm_api.py: completion, tool calling, thinking-disabled latency, streaming - test_mcp_tools.py: weather (Fahrenheit), time, memory, skills — all via docker exec + in-container MCP client - test_agent_integration.py: process alive, worker registered, supervisord, container health, LiveKit API - test_compaction.py: size-triggered compaction, system prompt preservation, short-context no-op - conftest.py: shared fixtures (HTTPS client, token, LLM, docker exec helpers) - Run with: .venv-tests/bin/python -m pytest tests/ -v
94 lines
3.1 KiB
Python
94 lines
3.1 KiB
Python
"""Tests for GemmaLLM context compaction (agent.GemmaLLM._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.
|
|
"""
|
|
|
|
import pytest
|
|
|
|
from conftest import GEMMA_BASE_URL, GEMMA_MODEL, docker_exec_python
|
|
|
|
# {num_messages} and {do_chat} are filled per test; all other braces are literal.
|
|
TEMPLATE = '''
|
|
import logging, sys
|
|
from livekit.agents import llm as lk_llm
|
|
sys.path.insert(0, "/opt/voice-agent")
|
|
from agent import GemmaLLM
|
|
|
|
logging.basicConfig(level=logging.INFO, stream=sys.stdout)
|
|
|
|
llm = GemmaLLM(
|
|
model="{model}",
|
|
base_url="{base_url}",
|
|
api_key="not-needed",
|
|
max_completion_tokens=100,
|
|
extra_body={{"chat_template_kwargs": {{"enable_thinking": False}}}},
|
|
)
|
|
|
|
items = [lk_llm.ChatMessage(role="system", content=["You are Hope, a voice assistant."])]
|
|
for i in range({num_messages}):
|
|
items.append(lk_llm.ChatMessage(role="user", content=["user message number " + str(i)]))
|
|
items.append(lk_llm.ChatMessage(role="assistant", content=["agent reply number " + str(i)]))
|
|
|
|
ctx = lk_llm.ChatContext(items)
|
|
print("BUILT ctx with " + str(len(ctx.items)) + " items")
|
|
|
|
import asyncio
|
|
|
|
async def main():
|
|
try:
|
|
compacted = llm._compact_context(ctx, None)
|
|
except Exception as e:
|
|
print("COMPACT_ERROR " + repr(e))
|
|
return
|
|
print("AFTER items=" + str(len(compacted.items)))
|
|
for it in compacted.items:
|
|
role = getattr(it, "role", None)
|
|
content = GemmaLLM._msg_content_text(it)
|
|
print("ITEM " + str(role) + ": " + content[:120])
|
|
|
|
{do_chat}
|
|
|
|
asyncio.run(main())
|
|
'''
|
|
|
|
CHAT_SNIPPET = ' stream = llm.chat(chat_ctx=compacted)\n collected = await stream.collect()\n print("CHAT_OK text_len=" + str(len(collected.text)))'
|
|
|
|
|
|
def _run(num_messages: int, *, do_chat: bool = False) -> str:
|
|
code = TEMPLATE.format(
|
|
model=GEMMA_MODEL,
|
|
base_url=GEMMA_BASE_URL,
|
|
num_messages=num_messages,
|
|
do_chat=CHAT_SNIPPET if do_chat else "pass",
|
|
)
|
|
proc = docker_exec_python(code, timeout=120, check=False)
|
|
return proc.stdout + (("\n" + proc.stderr) if proc.stderr else "")
|
|
|
|
|
|
@pytest.mark.slow
|
|
def test_compaction_triggers_on_size():
|
|
"""A context with > 24 items must compact without crashing and chat() must still work."""
|
|
# 13 pairs = 26 non-system + 1 system = 27 items (> threshold of 24)
|
|
out = _run(13, do_chat=True)
|
|
assert "BUILT ctx with 27 items" in out
|
|
assert "Context compacted:" in out
|
|
assert "CHAT_OK text_len=" in out
|
|
|
|
|
|
@pytest.mark.slow
|
|
def test_compaction_preserves_system_prompt():
|
|
out = _run(28)
|
|
lines = [l for l in out.splitlines() if l.startswith("ITEM system:")]
|
|
assert lines, f"no system message survived compaction:\n{out}"
|
|
assert "You are Hope" in lines[0]
|
|
|
|
|
|
def test_short_context_no_compaction():
|
|
"""Context with non_sys <= KEEP_RECENT must NOT be compacted."""
|
|
# system + 4 user/assistant pairs = 9 items, non_sys=8 <= KEEP_RECENT(10)
|
|
out = _run(4)
|
|
assert "BUILT ctx with 9 items" in out
|
|
# _compact_context should return the context unchanged (early return)
|
|
assert "AFTER items=9" in out, f"expected no compaction:\n{out}"
|