mirror of
https://github.com/lrnselfreliance/wrolpi.git
synced 2026-10-26 19:01:39 -04:00
Response-quality work, all aimed at giving the model enough context to answer accurately: - Every chat's system prompt now carries a live library map (today's date, counts, and the exact names of channels, domains, zims, and inventories, capped and cached 60s). Exact spellings are in context before the first token, and the list_collections round-trip — minutes on slow hardware — is usually unnecessary. - The request is budgeted against the model's real context: oldest turns are trimmed first, the current question is never dropped, and an over-budget turn gets a clear "start a new conversation" error instead of a silently truncated prompt. cache_prompt reuses the KV cache for the stable prefix, which is most of the latency on a Pi. - context_size plumbing fixed: selecting a model adopts its catalog default (the 4B was running at 8k instead of 16k), and llama-server runs --parallel 1 so the single user gets the whole context. - temperature is configurable in ai.yaml (default 0.3 — small models call tools far more reliably near 0.2-0.3 than at server defaults). - One worked tool-use example per mode is prepended to every conversation; small models imitate far better than they obey. - scripts/ai_eval.py scores the assistant against a live WROLPi on mechanical checks (finished, called the right tool first, included a WROLPi link, no external URLs) so prompt changes stop being validated by anecdote. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
60 lines
2.4 KiB
Python
60 lines
2.4 KiB
Python
"""Tests for the library map injected into every chat."""
|
|
import time
|
|
from unittest import mock
|
|
|
|
import pytest
|
|
|
|
from modules.ai import context
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_build_library_context(async_client, test_session, video_factory, archive_factory,
|
|
test_zim, food_inventory_factory):
|
|
"""The map carries today's date, counts, and exact names."""
|
|
from wrolpi.collections.models import Collection
|
|
test_session.add(Collection(name='Talking Sasquach', kind='channel'))
|
|
video_factory(title='a video')
|
|
archive_factory(domain='example.com', title='a page', contents='contents')
|
|
food_inventory_factory(name='Pantry')
|
|
test_session.commit()
|
|
|
|
text = context.build_library_context()
|
|
assert 'Today is' in text
|
|
assert '1 videos, 1 archived web pages' in text
|
|
assert 'Talking Sasquach' in text
|
|
assert 'example.com' in text
|
|
assert 'WROLPi example ZIM' in text
|
|
assert 'Pantry' in text
|
|
assert 'user spellings may differ' in text
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_library_context_caps_names(async_client, test_session, archive_factory):
|
|
"""A huge library lists at most MAX_NAMES names per category, plus a count of the rest."""
|
|
for i in range(context.MAX_NAMES + 5):
|
|
archive_factory(domain=f'site{i:02}.com', title=f'page {i}', contents='x')
|
|
test_session.commit()
|
|
|
|
text = context.build_library_context()
|
|
assert f'Archived websites ({context.MAX_NAMES + 5}):' in text
|
|
assert 'and 5 more' in text
|
|
listed = [i for i in range(context.MAX_NAMES + 5) if f'site{i:02}.com' in text]
|
|
assert len(listed) == context.MAX_NAMES
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_library_context_cache_and_degradation(async_client, test_session):
|
|
"""The map is cached briefly; a build failure serves the stale copy rather than crashing chat."""
|
|
context._cache.update(text=None, expires=0.0)
|
|
first = context.get_library_context()
|
|
assert first and 'Today is' in first
|
|
|
|
with mock.patch('modules.ai.context.build_library_context', side_effect=RuntimeError('db down')) as build:
|
|
# Cached: the builder is not called.
|
|
assert context.get_library_context() == first
|
|
build.assert_not_called()
|
|
|
|
# Expired + failing builder: the stale copy is served.
|
|
context._cache['expires'] = time.time() - 1
|
|
assert context.get_library_context() == first
|
|
build.assert_called_once()
|