← Back to Home
Python 3.12 FastAPI React 18 / Vite TypeScript strict SQLAlchemy async SSE real-time streaming Multimodal Β· Data Engineering

πŸ—‚ Multimodal Dataset Factory

mm_dataset_factory β€” Word/DOCX β†’ multimodal error-question image dataset in one click; 4-stage pipeline + SSE real-time streaming + Skill-as-contract delivery architecture

πŸ“„ Full technical docs (standalone repo)γ€€Architecture, pipeline internals, L1/L2/L3 error system, data model, SSE, API β€” bilingual, 9 deep docs + screenshots. Go to Docs Repo β†’
πŸ“Š A benchmark built from this platformγ€€The data this pipeline produces was curated into EduFig-IC β€” a graded image–text consistency benchmark over STEM figure questions (L1/L2/L3, 973 samples). View EduFig-IC β†’

Overview

Targeting multimodal model (LMM/VLM) training data production, the system automatically extracts image-bearing exam questions from Word/DOCX documents and produces training-ready error-question image datasets through a 4-stage pipeline: JSON / JSONL / XLSX annotation files + image directory (zip archive). Decoupled front/back-end architecture: FastAPI provides async REST API + SSE real-time streaming, React/Vite provides the UI β€” launched with a single python run.py.

Use Cases LMM/VLM error-question training data Β· Multimodal benchmark construction Β· Image-text dataset production pipeline

Tech Stack

Python 3.12 FastAPI (async REST + SSE) SQLAlchemy 2.x async (ORM) SQLite (persistence) React 18 TypeScript strict (no any) Vite (build + dev proxy) VLM API (image understanding) Text-to-Image API (image generation) python-docx / Pillow

4-Stage Production Pipeline

β‘ 
paper-structuring
Doc parse Β· image-bearing question extraction
β‘‘
vlm-error-planning
VLM error planning Β· L1/L2/L3 classification
β‘’
text2image-generation
AI image generation Β· error-image replacement
β‘£
quality-gate
Quality review Β· dataset export

Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Frontend Layer (React 18 + Vite) β”‚ β”‚ Task list / Samples browser / Plan details / Progress β”‚ β”‚ useTaskEvents() Hook ← SSE /api/events β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ HTTP port 8000 (5173 β†’ proxy) β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ FastAPI Main Service (apps/api/) β”‚ β”‚ β”‚ β”‚ /api/tasks/* Task CRUD + stage status β”‚ β”‚ /api/runs/* Execute pipeline stages β”‚ β”‚ /api/samples/* Sample query / filtering β”‚ β”‚ /api/datasets/* Export JSON/JSONL/XLSX/zip β”‚ β”‚ /api/events SSE real-time event stream β”‚ β”‚ /api/settings System configuration β”‚ β”‚ /static/* Task artifact static files (whitelist) β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Infrastructure Layer β”‚ β”‚ SQLAlchemy async + SQLite (tasks / sample_states) β”‚ β”‚ asyncio EventBus (in-process SSE broadcast) β”‚ β”‚ File system data/tasks/{task_id}/ β”‚ β”‚ images/ Β· extraction_debug/ Β· planning/ Β· generated/ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Technical Deep-Dive

β‘  Stage 1: Document Structuring (paper-structuring)

Reads Word/DOCX documents and extracts content at the granularity of individual sub-questions (one Sample per sub-question). Rule: only image-bearing questions are retained; text-only questions are skipped. Images are re-numbered per question (img_01, img_02 …), and question text retains placeholders in the form <img_01> to track image-text correspondence in downstream stages. Images are saved to tasks/{task_id}/images/ as q0001_img_01.png. Debug artifacts are also written: extracted text, imageMap JSON, raw image backup.

Key Sample fields: sample_no Β· source_file Β· question_no Β· subject Β· question_type Β· question_text Β· image_count Β· image_map

β‘‘ Stage 2: VLM Error Planning (vlm-error-planning) β€” L1/L2/L3 Error Taxonomy

Calls the VLM API to analyze each image-bearing question and outputs exactly 3 planning rows per sample (L1 / L2 / L3):

L1 Β· Image Swap
Substitutes another question's image to create an image-text content mismatch. No "post-generation evaluation" loop β€” direct swap.
L2 Β· Parameter Mismatch
Image is modifiable; question parameters conflict (e.g. length vs. angle contradiction). Triggers the evaluation loop: VLM verifies anchor consistency (numeric / symbolic / structural) β†’ decision: pass / repair / regenerate.
L3 Β· Unsolvable Logic
Image is modifiable; question-image combination has no solution. Requires an unsolvable_reason. Also enters the evaluation loop; reaches machine_reject after max retries.

Text enrichment mechanism: When an L2/L3 plan involves "modifying a numeric value in the image" but the question text lacks the corresponding numeric anchor, a controlled text enrichment pass is executed first and its output is merged into the exported question_text before evaluation. The enrichment must not be deferred to the image-generation stage β€” data consistency is enforced at planning time.

Planning artifacts: planning/plan_eval_report.jsonl (severity / decision / judge_trace) + questions_prompt_plan.xlsx/.jsonl

β‘’ Stage 3: Image Generation (text2image-generation)

Uses questions_prompt_plan.jsonl as input. L2/L3 rows invoke the Text-to-Image API to generate replacement images; L1 rows directly swap images from the task image library without calling the generation API. Generated images are saved to tasks/{task_id}/generated/, indexed by sample_no + error_level.

β‘£ SSE In-Process Event Bus (asyncio EventBus)

Each pipeline stage runner broadcasts progress events via EventBus.publish(Event). The SSE route /api/events maintains an independent asyncio.Queue per subscriber, achieving zero-latency in-process broadcast. The frontend subscribes via a useTaskEvents() Hook with auto-reconnect and 15s keepalive, showing per-question processing status and progress counts in real time.

Runner ──publish()──→ EventBus._subscribers: set[asyncio.Queue] └──→ Queue(client_1) ──→ SSE /api/events └──→ Queue(client_2) ──→ SSE /api/events Frontend useTaskEvents() ←── EventSource β†’ auto-reconnect + keepalive(15s)

β‘€ Data Model (SQLAlchemy async ORM)

TaskORM SampleStateORM task_id (PK, str) id (PK, autoincrement) title task_id (FK β†’ tasks) current_stage sample_no (idx) created_at stage (paper-structuring / vlm-error-planning / …) updated_at status (idle / running / done / failed / machine_reject) └── sample_states[] updated_at (cascade delete) [UQ: task_id + sample_no + stage]

Stage progress is aggregated on the frontend by counting SampleState.status values (total / done / failed / machine_reject) β€” no separate progress table needed. All ORM operations use async with session for non-blocking execution.

β‘₯ SKILL.md as Functional Delivery Contract

Each feature module ships a SKILL.md that formally defines: supported actions (create_task / run_task / list_samples / export_dataset), input field constraints, output schema (success / data / errors / meta), and stage artifact paths and formats. This lets AI Coding Agents validate features against the contract rather than guessing API behavior from code.

Engineering Highlights deliverable (artifacts) and process (docs) directories strictly separated; TypeScript strict mode bans all any; Python fully typed with from __future__ import annotations for forward compatibility.

⑦ Export Formats

After the quality gate passes, multiple export formats are supported:

  • JSON / JSONL β€” standard multimodal training data format
  • XLSX β€” human review and editing
  • images.zip β€” all generated images packaged
  • StaticFiles allowlist ensures only known-safe paths are accessible via /static/*

API Endpoints

GET /api/tasks Task list (with aggregated stage progress) POST /api/tasks Create task (upload Word document) GET /api/tasks/{id} Task detail + artifact URLs POST /api/runs/{id}/start Start / resume pipeline to specified stage GET /api/samples/{id} Paginated sample query GET /api/samples/{id}/filter Filter by subject / question_type / error_level GET /api/datasets/{id} Export dataset (JSON/JSONL/XLSX/zip) GET /api/events SSE real-time event stream GET /api/settings System config (LLM / storage paths) GET /health Health check

Screenshots

Task list
β‘  Task list β€” real data, stage status
Structuring
β‘‘ Structuring β€” parsed samples, image-bearing questions
VLM planning
β‘’ VLM error planning β€” L1/L2/L3 plan details
Generation
β‘£ AI generation β€” L1 swap / L2Β·L3 generate
Export
β‘€ Export β€” artifact list + one-click zip
New task
β‘₯ New task β€” upload Word, set defaults

Prompt Engineering & LLM Architecture

The planning stage is the heart of the prompt engineering. No LangChain β€” a thin hand-rolled wrapper (httpx + OpenAI-compatible Chat Completions + .prompt templates + Pydantic) for full control over prompts, JSON tolerance and multi-vendor adaptation.

  • Form: a deterministic workflow skeleton with an embedded planner–judge (LLM-as-Judge) self-correction loop (pass / repair / regenerate / machine_reject).
  • Prompt skeleton: role β†’ input slots β†’ objective β†’ tiered param strategy β†’ level hard-boundary (l2 = param layer / l3 = structural) β†’ 3-element writing constraints (anchor + concrete drawing + visual acceptance) β†’ positive/negative examples β†’ JSON field allowlist.
  • Control: system-forced JSON, temperature tiers (0.0 extraction/judging, 0.2 planner), judge emits alignment_table before the verdict, thinking disabled.
  • Image prompt: fixed "local edit" preamble + stem + image params + planner instruction; original image uploaded; L1 / text skip generation.
  • Fallbacks: 3-tier JSON tolerance, planner regex guard, judge exception β†’ regenerate, best-of on exhaustion, generation retry + backoff, provider auto-switch to DashScope.
  • Few-shot: l2 / l3 / judge prompts embed positive + negative examples ("style only, do not copy").
Full breakdown (every prompt's text, planner–judge flow chart, fallback list) in the docs repo: 10 Β· Prompt Engineering & LLM Architecture.

Key Highlights

  • 4-stage pipeline covers the full chain from document parsing to dataset export, with per-stage retry support
  • L1/L2/L3 three-tier error planning + post-generation evaluation loop ensures data quality
  • asyncio EventBus + SSE streaming β€” frontend shows per-question progress with zero polling
  • SKILL.md contract-driven development β€” AI agents can validate features against the formal spec
  • SQLAlchemy async ORM + SQLite β€” fully async, non-blocking
  • TypeScript strict bans all any; Python fully typed

Links

πŸ“„ Full Docs Repo (Markdown Β· bilingual) GitHub Profile