Files
conjurer/tests/integration/test_ai_query_endpoint.py
T
gitea b13a8afa01
CI / integration (push) Failing after 2m23s
CI / compile (push) Failing after 1h53m43s
CI / unit (push) Failing after 1h53m32s
build / build (push) Failing after 1h54m2s
bot: queued AI query interface + librarian AI review of results
Two connected features.

1) AI query interface (via the comm layer). communication_subroutine gains an
AI_QUERY_Q, a submit_ai_query() in-process entry point, and an authed
POST /ai_query endpoint ({prompt, channel_id, request_type?, username?}). The
prompt is queued and answered asynchronously by a new tasks.loop worker in the
always-loaded AI cog (Events), which calls handle_response - so it runs on
whichever backend $gadaj_teraz currently selects (GPT or Claude) - and posts the
answer to the requested channel, chunked to Discord's limit. request_type "NONE"
(default) is a clean one-shot: no persona system prompt, no memory write. The
worker starts before the OpenAI guard in cog_load, so it also runs on a
Claude-only box; cog_unload cancels it.

2) Librarian AI review. New command $wyszukaj_z_recenzja mirrors
$wyszukaj_linki_do_dokumentow but sets ai_review=True on the QueryControl, which
rides the round-trip and is matched back by UUID. When the hits return,
check_data_q sends the raw list as before, then - if flagged - hands the same
list (already in Crossref-relevance order) plus the search phrase to the AI
queue for a weighted re-rank and per-source review, delivered to the same
channel. QueryControl gains an ai_review flag (default False, so the orphan path
and all existing callers are unaffected).

Confirmed separately (and noted in the docs): the DOI list the AI receives is
pre-sorted by Crossref relevance - the librarian pipeline only filters (drops
title-less items) and splits (in-db / not-in-db), never re-sorts, and relies on
insertion-ordered dicts (Py 3.7+).

Verified: /ai_query auth (401/200/400/open), submit_ai_query and the queued
dict shape, and the QueryControl flag - via a Flask test client and
tests/integration/test_ai_query_endpoint.py (5 tests, all pass; integration
suite 11 passed, the 3 failures are the pre-existing /clear_pr_pls musician
tests fixed on a separate branch). Full first-party compile clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-30 09:27:11 +00:00

71 lines
1.9 KiB
Python

"""Integration: the bot's /ai_query endpoint enforces the shared key, validates
the payload, and queues accepted prompts onto AI_QUERY_Q for the AI worker.
"""
import communication_subroutine as cs
def _client(key="test-secret"):
cs.API_KEY = key
return cs.app.test_client()
def _drain():
while not cs.AI_QUERY_Q.empty():
cs.AI_QUERY_Q.get()
def test_ai_query_rejected_without_key():
_drain()
client = _client()
resp = client.post("/ai_query", json={"prompt": "x", "channel_id": 1})
assert resp.status_code == 401
assert cs.AI_QUERY_Q.empty() # nothing queued on a rejected call
def test_ai_query_accepted_with_key_and_queued():
_drain()
client = _client()
resp = client.post(
"/ai_query",
json={"prompt": "posortuj DOI", "channel_id": 42, "username": "siara"},
headers={"X-Conjurer-Api-Key": "test-secret"},
)
assert resp.status_code == 200
item = cs.AI_QUERY_Q.get()
assert item["prompt"] == "posortuj DOI"
assert item["channel_id"] == 42
assert item["username"] == "siara"
def test_ai_query_missing_prompt_is_rejected():
_drain()
client = _client()
resp = client.post(
"/ai_query",
json={"channel_id": 1},
headers={"X-Conjurer-Api-Key": "test-secret"},
)
assert resp.status_code == 400
assert cs.AI_QUERY_Q.empty()
def test_ai_query_open_when_key_unset():
_drain()
client = _client(key=None)
resp = client.post("/ai_query", json={"prompt": "y", "channel_id": 1})
assert resp.status_code == 200
def test_submit_ai_query_enqueues_expected_shape():
_drain()
cs.submit_ai_query(
prompt="P", channel_id=7, request_type="NONE", username="u", query_uuid="uid-1"
)
assert cs.AI_QUERY_Q.get() == {
"uuid": "uid-1",
"prompt": "P",
"channel_id": 7,
"request_type": "NONE",
"username": "u",
}