latex-ocr-env / validate.py
AdithyaSK's picture
AdithyaSK HF Staff
Upload folder using huggingface_hub
e07d48e verified
Raw
History Blame Contribute Delete
5.14 kB
# Copyright (c) Meta Platforms, Inc. and affiliates.
# All rights reserved.
#
# This source code is licensed under the BSD-style license found in the
# LICENSE file in the root directory of this source tree.
"""
End-to-end driver for the LaTeX OCR environment.
Connects to a running server, exercises the Task API, resets to a task, runs a
policy over the image, steps, and prints the reward. The policy is either a
real vision-LLM served through the Hugging Face Inference Router
(OpenAI-compatible) or, if no HF_TOKEN is set, a no-op placeholder so the
plumbing can still be verified.
Usage:
# Start the server first (see README), then:
python validate.py --base-url http://localhost:8000 \\
--split test --num 3 --model Qwen/Qwen2.5-VL-7B-Instruct
"""
from __future__ import annotations
import argparse
import os
import sys
# Make `latex_ocr_env` importable when run from the env directory.
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from latex_ocr_env import LatexOCRAction, LatexOCREnv # noqa: E402
def vlm_transcribe(image_base64: str, prompt: str, model: str) -> str:
"""Run a VLM over the image via the Hugging Face Inference Router."""
from openai import OpenAI
client = OpenAI(
base_url="https://router.huggingface.co/v1",
api_key=os.environ["HF_TOKEN"],
)
completion = client.chat.completions.create(
model=model,
messages=[
{
"role": "user",
"content": [
{"type": "text", "text": prompt},
{
"type": "image_url",
"image_url": {"url": f"data:image/png;base64,{image_base64}"},
},
],
}
],
)
text = completion.choices[0].message.content or ""
# Strip common code-fence wrapping so scoring sees raw LaTeX.
text = text.strip()
for fence in ("```latex", "```LaTeX", "```"):
if text.startswith(fence):
text = text[len(fence) :]
if text.endswith("```"):
text = text[:-3]
return text.strip()
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--base-url", default="http://localhost:8000")
parser.add_argument("--split", default="test")
parser.add_argument("--num", type=int, default=3, help="Tasks to run")
parser.add_argument(
"--model",
default=os.environ.get("LATEX_OCR_MODEL", "Qwen/Qwen2.5-VL-7B-Instruct"),
help="VLM served via the HF router (append :provider to pin, e.g. ':nebius').",
)
args = parser.parse_args()
use_vlm = bool(os.environ.get("HF_TOKEN"))
if not use_vlm:
print("HF_TOKEN not set -> running plumbing-only policy (no VLM).\n")
with LatexOCREnv(base_url=args.base_url).sync() as env:
# --- Task API ---
splits = env.list_splits()
n = env.num_tasks(args.split)
print(f"splits = {splits}")
print(f"num_tasks({args.split}) = {n}")
print(f"get_task({args.split}, 0) = {env.get_task(args.split, 0)}\n")
rewards = []
seen_targets = []
# n <= 0 means unknown count (stream metadata); fall back to --num.
count = args.num if n <= 0 else min(args.num, n)
stream_mode = False
for i in range(count):
if stream_mode:
result = env.reset(split=args.split)
else:
try:
result = env.reset(split=args.split, index=i)
except Exception:
# stream-mode server rejects random index -> pull sequentially
stream_mode = True
result = env.reset(split=args.split)
obs = result.observation
prog = ""
if obs.total and obs.total > 0:
prog = (
f" | progress: {obs.index}/{obs.total} "
f"({obs.pct_done:.4%}), remaining={obs.remaining}"
)
print(
f"[task {obs.task_id}] image bytes(b64)={len(obs.image_base64)}{prog}"
)
if use_vlm:
prediction = vlm_transcribe(obs.image_base64, obs.prompt, args.model)
else:
prediction = "" # plumbing check only
result = env.step(LatexOCRAction(latex=prediction))
o = result.observation
rewards.append(result.reward)
seen_targets.append(o.target_latex)
print(f" predicted : {o.predicted_latex[:80]!r}")
print(f" target : {o.target_latex[:80]!r}")
print(
f" reward={result.reward:.4f} exact={o.exact_match} "
f"cer={o.char_error_rate:.4f}\n"
)
if rewards:
print(
f"\nmean reward over {len(rewards)} tasks = {sum(rewards) / len(rewards):.4f}"
)
uniq = len(set(seen_targets))
print(f"no-repeat check: {uniq}/{len(seen_targets)} distinct targets")
if __name__ == "__main__":
main()