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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user