ai: live-chat fixes from first real-model run

Three findings from running Qwen3-1.7B against the docker stack:
the llm image lost libgomp when the build tools were removed
(llama-server needs the OpenMP runtime); the Help prompt let the
small model answer from memory instead of the docs, so it now
requires search_help before any answer; and help search results
called the slug "doc" while get_help_doc's argument is "slug", so
the model invented slugs — the names now match.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Roland 2026-08-27 12:21:44 -06:00
parent 6d14467046
commit f49ae3aee5
No known key found for this signature in database
GPG key ID: 1BA620CE6C9A4680
5 changed files with 13 additions and 10 deletions

View file

@ -6,7 +6,9 @@ RUN apt-get update && apt-get install -y curl ca-certificates
# Build llama-server from the pinned release (single source of truth with native installs).
COPY wrolpi/scripts/install_llama_cpp.sh /tmp/install_llama_cpp.sh
RUN /tmp/install_llama_cpp.sh && rm /tmp/install_llama_cpp.sh \
&& apt-get remove -y build-essential cmake && apt-get autoremove -y && apt-get clean
&& apt-get remove -y build-essential cmake && apt-get autoremove -y \
# llama-server needs the OpenMP runtime, which the build-tool cleanup just removed.
&& apt-get install -y libgomp1 && apt-get clean
# The same startup scripts the native install uses.
COPY scripts/start_llama_server.sh /opt/wrolpi/scripts/start_llama_server.sh

View file

@ -396,7 +396,7 @@ async def get_inventory(_: Request, slug: str):
@openapi.definition(
summary='Search the WROLPi help documentation',
description='Search the WROLPi help documentation for how to use and repair WROLPi. Returns matching'
' help pages with a snippet; read a full page with get_help_doc. Links are pages on the'
' help pages with a snippet; read a full page by passing its slug to get_help_doc. Links are pages on the'
' WROLPi help site.',
body=schema.AIHelpSearchRequest,
)

View file

@ -133,7 +133,7 @@ def search_help(search_str: str, limit: int = 5) -> (List[dict], int):
results = []
for score, doc in scored[:limit]:
result = dict(
doc=doc.slug,
slug=doc.slug,
title=doc.title,
link=help_doc_link(doc.slug),
snippet=_snippet(doc.body, tokens),

View file

@ -14,7 +14,8 @@ MODES = {
system_prompt=(
f'{COMMON_PROMPT}'
' Your job is to teach the user how to use WROLPi.'
' Search the help documentation with search_help, read pages with get_help_doc, and answer'
' You do NOT know how WROLPi works from memory: before answering ANY question, you MUST'
' first call search_help, then read the matching page with get_help_doc, and answer only'
' from what the docs actually say. Always include the link of every help page you used.'
' If the docs do not cover the question, say so.'
),

View file

@ -31,14 +31,14 @@ def test_search_help_scoring(help_corpus):
"""Title matches outrank body matches; snippets show context."""
results, total = help_docs.search_help('logs')
assert total == 1
assert results[0]['doc'] == 'system/logs'
assert results[0]['slug'] == 'system/logs'
assert results[0]['title'] == 'Logs'
assert results[0]['link'] == '/system/logs/'
assert 'journalctl' in results[0]['snippet'] or 'Logs' in results[0]['snippet']
# 'videos' matches the videos doc far better than any other.
results, _ = help_docs.search_help('downloading videos')
assert results[0]['doc'] == 'modules/videos'
assert results[0]['slug'] == 'modules/videos'
# No matches.
results, total = help_docs.search_help('xyzzy')
@ -48,7 +48,7 @@ def test_search_help_scoring(help_corpus):
def test_help_doc_cache_invalidation(help_corpus):
"""Edited files are re-parsed; deleted files leave the corpus."""
results, _ = help_docs.search_help('welcome')
assert results[0]['doc'] == 'index'
assert results[0]['slug'] == 'index'
assert results[0]['link'] == '/'
(help_corpus / 'index.md').write_text('# Home\n\nGreetings, traveler.\n')
@ -57,7 +57,7 @@ def test_help_doc_cache_invalidation(help_corpus):
results, total = help_docs.search_help('welcome')
assert total == 0
results, _ = help_docs.search_help('greetings')
assert results[0]['doc'] == 'index'
assert results[0]['slug'] == 'index'
(help_corpus / 'system/logs.md').unlink()
_, total = help_docs.search_help('logs')
@ -77,9 +77,9 @@ async def test_ai_help_endpoints(async_client, help_corpus):
assert response.status_code == HTTPStatus.OK
assert response.json['total'] == 1
result = response.json['results'][0]
assert result['doc'] == 'system/logs'
assert result['slug'] == 'system/logs'
request, response = await async_client.get(f'/api/ai/help/{result["doc"]}')
request, response = await async_client.get(f'/api/ai/help/{result["slug"]}')
assert response.status_code == HTTPStatus.OK
assert response.json['title'] == 'Logs'
assert 'journalctl' in response.json['content']