Multimodal-RAG / tests /test_generators.py
Angshul's picture
Upload 42 files
c42f4ef verified
Raw History Blame Contribute Delete
5.45 kB
"""Generator backends against local HTTP servers that implement the Ollama and OpenAI wire protocols."""
import base64
import io
import json
import threading
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
import pytest
from PIL import Image
from mrag.config import Config
from mrag.generate import Generator, ImageHit, TextHit
class FakeLLM:
def __init__(self):
self.requests = []
outer = self
class H(BaseHTTPRequestHandler):
def log_message(self, *a):
pass
def do_POST(self):
body = json.loads(self.rfile.read(int(self.headers["Content-Length"])))
outer.requests.append((self.path, dict(self.headers), body))
words = ["The ", "warranty ", "is ", "36 ", "months ", "[1]."]
if self.path == "/api/chat":
if body["model"] == "missing":
return self._send(404, {"error": "model 'missing' not found"})
if not body["stream"]:
return self._send(200, {"message": {"role": "assistant", "content": "".join(words)},
"done": True})
self.send_response(200)
self.send_header("Content-Type", "application/x-ndjson")
self.end_headers()
for w in words:
self.wfile.write((json.dumps({"message": {"content": w}, "done": False}) + "\n").encode())
self.wfile.write((json.dumps({"message": {"content": ""}, "done": True}) + "\n").encode())
elif self.path == "/v1/chat/completions":
if not body["stream"]:
return self._send(200, {"choices": [{"message": {"content": "".join(words)}}]})
self.send_response(200)
self.send_header("Content-Type", "text/event-stream")
self.end_headers()
for w in words:
self.wfile.write(f"data: {json.dumps({'choices': [{'delta': {'content': w}}]})}\n\n".encode())
self.wfile.write(b"data: [DONE]\n\n")
else:
self._send(404, {"error": "no route"})
def _send(self, code, obj):
b = json.dumps(obj).encode()
self.send_response(code)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(b)))
self.end_headers()
self.wfile.write(b)
self.server = ThreadingHTTPServer(("127.0.0.1", 0), H)
self.url = f"http://127.0.0.1:{self.server.server_address[1]}"
threading.Thread(target=self.server.serve_forever, daemon=True).start()
@pytest.fixture(scope="module")
def fake():
f = FakeLLM()
yield f
f.server.shutdown()
@pytest.fixture
def hits(tmp_path):
p = tmp_path / "chart.png"
Image.new("RGB", (2000, 1000), "red").save(p)
texts = [TextHit("t1", "Every R2 ships with a 36-month warranty.", "/d/report.pdf", 3, "ocr", 0.8)]
images = [ImageHit("i1", str(p), "/d/report.pdf", 3, "warranty", 0.3)]
return texts, images
@pytest.mark.parametrize("stream", [False, True])
def test_ollama(fake, hits, stream):
g = Generator(Config(llm_backend="ollama", llm_url=fake.url, llm_model="qwen2.5vl:3b", max_image_side=512))
out = g.answer("How long is the warranty?", *hits, stream=stream)
text = "".join(out) if stream else out
assert text == "The warranty is 36 months [1]."
path, _, body = fake.requests[-1]
assert path == "/api/chat" and body["model"] == "qwen2.5vl:3b" and body["stream"] is stream
sys_msg, user = body["messages"]
assert sys_msg["role"] == "system" and "[1] (report.pdf, p.3, OCR of an image)" in user["content"]
img = Image.open(io.BytesIO(base64.b64decode(user["images"][0])))
assert img.size == (512, 256) # downscaled, aspect preserved
assert body["options"]["num_ctx"] == 8192
def test_ollama_text_only_model(fake, hits):
g = Generator(Config(llm_backend="ollama", llm_url=fake.url, llm_model="llama3.2:3b", llm_vision=False))
g.answer("q", *hits)
assert "images" not in fake.requests[-1][2]["messages"][1]
def test_ollama_errors(fake, hits):
g = Generator(Config(llm_backend="ollama", llm_url=fake.url, llm_model="missing"))
with pytest.raises(RuntimeError, match="404"):
g.answer("q", *hits)
g = Generator(Config(llm_backend="ollama", llm_url="http://127.0.0.1:9", llm_model="x"))
with pytest.raises(RuntimeError, match="ollama serve"):
g.answer("q", *hits)
@pytest.mark.parametrize("stream", [False, True])
@pytest.mark.parametrize("suffix", ["", "/v1"])
def test_openai_compatible(fake, hits, stream, suffix):
g = Generator(Config(llm_backend="openai", llm_url=fake.url + suffix, llm_model="local", llm_api_key="k"))
out = g.answer("How long is the warranty?", *hits, stream=stream)
assert ("".join(out) if stream else out) == "The warranty is 36 months [1]."
path, headers, body = fake.requests[-1]
assert path == "/v1/chat/completions" and headers["Authorization"] == "Bearer k"
parts = body["messages"][1]["content"]
assert parts[0]["type"] == "text" and parts[1]["image_url"]["url"].startswith("data:image/png;base64,")