fix(inference): #87 redact image bytes from inference_log

Log full text + image_ref (photo_id/seq/bytes) only; wire path still
sends data-URLs. Call 1/2 SELECT id+seq for refs. Cap is safety net
after redaction (METADATA-66).
This commit is contained in:
Ty
2026-09-20 23:01:06 -07:00
parent 7287a55707
commit 41401a7266
4 changed files with 233 additions and 9 deletions
+106
View File
@@ -274,6 +274,112 @@ def test_fit_long_edge_png_leaves_small_unchanged_dims():
assert im.size == (400, 300)
def test_redact_request_for_log_strips_data_urls_keeps_text():
"""#87: log sanitizer replaces data-URL images with photo refs; keeps text."""
from backend.inference import _redact_request_for_log
b64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg=="
messages = [{
"role": "user",
"content": [
{"type": "image_url", "image_url": {"url": f"data:image/png;base64,{b64}"}},
{"type": "image_url", "image_url": {"url": f"data:image/png;base64,{b64 * 20}"}},
{"type": "text", "text": "LISTS:\n{\"species\":[]}\n\nSKU=X length_in=1 thickness_in=2"},
],
}]
meta = [
{"photo_id": "p1", "seq": 1, "bytes": 100, "infer_long_edge_px": 64},
{"photo_id": "p2", "seq": 2, "bytes": 200, "infer_long_edge_px": 128},
]
out = _redact_request_for_log(messages, meta)
blob = json.dumps(out)
assert "data:image" not in blob
assert "base64" not in blob.lower()
assert "LISTS:" in blob
assert "SKU=X" in blob
parts = out[0]["content"]
assert parts[0]["type"] == "image_ref"
assert parts[0]["photo_id"] == "p1"
assert parts[0]["seq"] == 1
assert parts[1]["photo_id"] == "p2"
assert parts[2]["type"] == "text"
assert "LISTS:" in parts[2]["text"]
def test_call1_log_has_text_and_image_refs_no_base64(env, monkeypatch):
"""#87 METADATA-66: inference_log stores text+refs, not image bytes."""
client, conn = env
conn.execute("INSERT OR REPLACE INTO settings(key,value,value_enc) VALUES ('inference_enabled','true',NULL)")
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')"
)
png = _rgba_png(400, 300)
conn.execute(
"INSERT INTO slab_photos(id,slab_id,seq,processed_blob,created_at) VALUES ('photo-abc','s1',1,?, 't')",
(png,),
)
conn.commit()
from backend.inference import InferenceClient
def fake_chat(self, messages):
# Wire path still has data-URL for the model
content = messages[0]["content"]
assert any(
isinstance(p, dict) and p.get("type") == "image_url"
and str(p.get("image_url", {}).get("url", "")).startswith("data:image")
for p in content
)
return json.dumps({"species_id": 1, "species_confidence": 0.9})
monkeypatch.setattr(InferenceClient, "chat", fake_chat)
r = client.post("/api/v1/slabs/s1/inference/call1")
assert r.status_code == 200, r.text
row = conn.execute(
"SELECT request_payload, length(request_payload) AS n FROM inference_log ORDER BY id DESC LIMIT 1"
).fetchone()
payload = row["request_payload"]
assert row["n"] < 50000
assert "data:image" not in payload
assert "LISTS:" in payload
assert "image_ref" in payload
assert "photo-abc" in payload
assert '"seq": 1' in payload or '"seq":1' in payload
logged = json.loads(payload)
refs = [p for p in logged[0]["content"] if p.get("type") == "image_ref"]
assert len(refs) == 1
assert refs[0]["photo_id"] == "photo-abc"
assert refs[0]["bytes"] == len(png)
def test_call2_log_redacts_images(env, monkeypatch):
"""#87: Call 2 log also redacts image data-URLs."""
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','ready',1,1,'t','t')"
)
png = _rgba_png(200, 200)
conn.execute(
"INSERT INTO slab_photos(id,slab_id,seq,processed_blob,created_at) VALUES ('p2','s1',1,?, 't')",
(png,),
)
conn.commit()
from backend.inference import InferenceClient
monkeypatch.setattr(InferenceClient, "chat", lambda self, messages: "<p>ok</p>")
r = client.post("/api/v1/slabs/s1/inference/call2")
assert r.status_code == 200, r.text
payload = conn.execute(
"SELECT request_payload FROM inference_log WHERE call=2 ORDER BY id DESC LIMIT 1"
).fetchone()["request_payload"]
assert "data:image" not in payload
assert "image_ref" in payload
assert "p2" in payload
assert "DIMENSION FACT BLOCK" in payload
def test_call1_sends_long_edge_at_most_1024(env, monkeypatch):
"""METADATA-35 end-to-end: messages to the model use <=1024 long edge."""
client, conn = env