feat(inference): S7 Call 1/Call 2 + prompt revision REST + test-inference + Generate text UI
- backend/inference.py: prompt seed (rev 0) + append-only revision REST
(POST /prompts/{call}/revisions, /activate, GET; METADATA-105/106). Call 1 vision
classification (1024px processed photos + synced taxonomy, 0.7 confidence gate,
METADATA-35/40/45), Call 2 long description (user-triggered, METADATA-55/60/65),
inference_log per call (METADATA-66), inference-off server_description (METADATA-70),
test-inference probe (SETTINGS-45). Stdlib urllib, no new deps.
- frontend: create/inference/GenerateText.svelte (control visible only when content
enabled + endpoint reachable, METADATA-110; Call 2 + Call 1 buttons) + dev toggle.
- tests: 6 inference tests (seed+revision, call1 gate, call2 stores desc, off->409,
server desc, test-inference unconfigured).
Verified: pytest 54 passed; gates exit 0; npm build ok.
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
"""S7 inference tests (METADATA-105/106, 45, 55, 70, SETTINGS-45)."""
|
||||
|
||||
import base64
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
import backend.db as db_mod
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def env(monkeypatch):
|
||||
tmp = tempfile.mkdtemp(prefix="inf_")
|
||||
os.environ["SLAB_DB_PATH"] = os.path.join(tmp, "i.db")
|
||||
os.environ["SLAB_IMAGES_DIR"] = tmp
|
||||
os.environ["WOO_BASE_URL"] = "https://x.com"
|
||||
os.environ["SLAB_AES_KEY"] = base64.b64encode(b"0123456789abcdef0123456789abcdef").decode()
|
||||
db_mod._reset_for_tests()
|
||||
from backend.crypto import encrypt
|
||||
|
||||
conn = db_mod.init_db()
|
||||
conn.execute("INSERT OR REPLACE INTO settings(key,value,value_enc) VALUES ('inference_base_url',NULL,?)", (encrypt("https://infer.example.com"),))
|
||||
conn.execute("INSERT OR REPLACE INTO settings(key,value,value_enc) VALUES ('inference_api_key',NULL,?)", (encrypt("sk-test"),))
|
||||
conn.execute("INSERT OR REPLACE INTO settings(key,value,value_enc) VALUES ('inference_model','gpt-test',NULL)")
|
||||
conn.commit()
|
||||
from backend.app import app
|
||||
|
||||
yield TestClient(app), conn
|
||||
db_mod._reset_for_tests()
|
||||
shutil.rmtree(tmp, ignore_errors=True)
|
||||
|
||||
|
||||
def test_prompt_seed_and_revision(env):
|
||||
client, conn = env
|
||||
r = client.get("/api/v1/prompts/call1")
|
||||
assert r.status_code == 200
|
||||
body = r.json()
|
||||
assert body["active_content"] is not None
|
||||
assert len(body["revisions"]) == 1 # seed only
|
||||
|
||||
r = client.post("/api/v1/prompts/call1/revisions", json={"content": "revised prompt v2"})
|
||||
assert r.status_code == 201
|
||||
new_rev = r.json()["id"]
|
||||
# new revision becomes active
|
||||
r = client.get("/api/v1/prompts/call1")
|
||||
assert r.json()["active_revision_id"] == new_rev
|
||||
assert r.json()["active_content"] == "revised prompt v2"
|
||||
assert len(r.json()["revisions"]) == 2
|
||||
|
||||
# activate back to seed
|
||||
seed_rev = 1
|
||||
r = client.post("/api/v1/prompts/call1/activate", json={"revision_id": seed_rev})
|
||||
assert r.status_code == 200
|
||||
assert client.get("/api/v1/prompts/call1").json()["active_content"] is not None
|
||||
|
||||
|
||||
def test_call1_confidence_gate(env, monkeypatch):
|
||||
client, conn = env
|
||||
# enable inference
|
||||
conn.execute("INSERT OR REPLACE INTO settings(key,value,value_enc) VALUES ('inference_enabled','true',NULL)")
|
||||
conn.execute("INSERT INTO woo_categories(id,name,slug,parent_id,woo_id) VALUES (1,'Black Walnut','black-walnut',NULL,100)")
|
||||
conn.execute("INSERT INTO slabs(id,sku,length_in,thickness_in,status,server_rev,client_rev,created_at,updated_at) VALUES ('s1','SLAB-01',100,2,'calibrated',1,1,'t','t')")
|
||||
conn.execute("INSERT INTO slab_photos(id,slab_id,seq,processed_blob,created_at) VALUES ('p1','s1',1,X'89504E47','t')")
|
||||
conn.commit()
|
||||
from backend.inference import InferenceClient
|
||||
|
||||
def fake_chat(self, messages):
|
||||
return json.dumps({
|
||||
"species_id": 1, "species_confidence": 0.9,
|
||||
"wood_category_ids": [1], "wood_category_confidence": 0.5, # below threshold
|
||||
"grade_term_id": 7, "grade_confidence": 0.3, # below
|
||||
"figure_term_ids": [2], "figure_confidence": 0.8, # at/above
|
||||
})
|
||||
monkeypatch.setattr(InferenceClient, "chat", fake_chat)
|
||||
r = client.post("/api/v1/slabs/s1/inference/call1")
|
||||
assert r.status_code == 200, r.text
|
||||
s = r.json()["suggestions"]
|
||||
assert s.get("species_id") == 1
|
||||
assert s.get("figure_term_ids") == [2]
|
||||
assert "wood_category_ids" not in s # gated out
|
||||
assert "grade_term_id" not in s # gated out
|
||||
# inference_log written with call=1
|
||||
assert conn.execute("SELECT call FROM inference_log").fetchone()["call"] == 1
|
||||
|
||||
|
||||
def test_call2_stores_description(env, monkeypatch):
|
||||
client, conn = env
|
||||
conn.execute("INSERT OR REPLACE INTO settings(key,value,value_enc) VALUES ('content_llm_enabled','true',NULL)")
|
||||
conn.execute("INSERT INTO slabs(id,sku,length_in,thickness_in,width_avg_in,bdft,title,status,server_rev,client_rev,created_at,updated_at) VALUES ('s1','SLAB-01',100,2,20,18,'Walnut Slab','ready',1,1,'t','t')")
|
||||
conn.execute("INSERT INTO slab_photos(id,slab_id,seq,processed_blob,created_at) VALUES ('p1','s1',1,X'89504E47','t')")
|
||||
conn.commit()
|
||||
from backend.inference import InferenceClient
|
||||
|
||||
monkeypatch.setattr(InferenceClient, "chat", lambda self, messages: "<p>A fine walnut slab.</p>")
|
||||
r = client.post("/api/v1/slabs/s1/inference/call2")
|
||||
assert r.status_code == 200, r.text
|
||||
assert r.json()["description"] == "<p>A fine walnut slab.</p>"
|
||||
row = conn.execute("SELECT description, content_source FROM slabs WHERE id='s1'").fetchone()
|
||||
assert row["description"] == "<p>A fine walnut slab.</p>"
|
||||
assert row["content_source"] == "llm"
|
||||
|
||||
|
||||
def test_inference_off_call1_409(env):
|
||||
client, conn = env
|
||||
conn.execute("INSERT INTO slabs(id,sku,status,server_rev,client_rev,created_at,updated_at) VALUES ('s1','SLAB-01','calibrated',1,1,'t','t')")
|
||||
conn.commit()
|
||||
r = client.post("/api/v1/slabs/s1/inference/call1")
|
||||
assert r.status_code == 409 # inference_enabled default off
|
||||
|
||||
|
||||
def test_server_description_inference_off(env):
|
||||
from backend.inference import server_description
|
||||
|
||||
d = server_description({"title": "Figured Walnut Slab", "length_in": 114.0, "width_avg_in": 24.0, "thickness_in": 1.75, "bdft": 18.4})
|
||||
assert "114.0in" in d and "18.4" in d
|
||||
|
||||
|
||||
def test_test_inference_unconfigured(env):
|
||||
client, conn = env
|
||||
conn.execute("UPDATE settings SET value_enc=NULL WHERE key='inference_api_key'")
|
||||
conn.commit()
|
||||
r = client.post("/api/v1/admin/test-inference")
|
||||
assert r.status_code == 200
|
||||
assert r.json()["ok"] is False and r.json()["mode"] == "fail"
|
||||
Reference in New Issue
Block a user