This repository contains a prototype framework for automatically creating synthetic demonstrations for multimodal in-context learning (ICL).
Given:
- an
original_image - an
original_query
it builds demonstration candidates of the form:
<synthetic_image_i, original_query, known_answer_i>
The core design constraint is that the query is invariant: every synthetic example must use the exact same original_query. The generated image scenario and pre-committed answer may vary, but the question must not be rewritten.
The default SyntheticICLPipeline orchestrates the following modules:
- Image-Query Understanding: uses the MLLM to analyze the original image and query.
- Task Induction: abstracts the concrete image-query pair into a
TaskIR. - Scenario Expansion: proposes new visual scenarios that can still be queried with the unchanged original query.
- Answer Sampling: pre-commits known answers and visual constraints before any image is generated.
- Generation Prompt Construction: writes reference-image-conditioned generation prompts.
- Image Generation: defaults to a stub/placeholder, with an optional
qwen_editDiffusers backend for Qwen-Image-Edit generation. - Verification: checks generated images against the unchanged query and known answer. In dry-run mode, this is skipped because there is no generated image.
- Demonstration Selection: ranks candidates for ICL usefulness.
All reasoning modules call the same MLLMBackbone. The only exception is ImageGenerationModule, which is intentionally isolated so you can replace it with a real generation backend later.
Python 3.10+ is recommended.
Base dry-run / MLLM pipeline dependencies:
pip install -r requirements.txtOptional Qwen image-edit backend dependencies:
pip install -r requirements-qwen.txt
# equivalent core Diffusers install:
# pip install git+https://github.com/huggingface/diffusersYou can run the demo without export by using a single JSON config file. See synthetic_icl/demo_config.example.json for a complete example.
python -m synthetic_icl.demo --config synthetic_icl/demo_config.example.jsonIf run.log_json_path (or --log-json-path) is set, the demo will save detailed intermediate outputs from each pipeline stage to a JSON file for diagnosis.
Config schema:
mllm.api_keymllm.base_urlmllm.model_namerun.imagerun.queryrun.num_scenariosrun.scenario_regen_rounds(max refill rounds when aligned scenarios are insufficient; default3)run.num_answers_per_scenariorun.top_krun.preserve_original_query(trueby default; setfalseto allow scenario-matched query rewriting)run.original_image_verify(falseby default; settrueto verify candidates against original-image task/distribution alignment)run.image_generation_pipe(stuborqwen_edit)run.dry_runrun.output_dirrun.verboserun.log_json_path
CLI flags still work and can override values in the config file.
MLLMBackbone uses an OpenAI-compatible chat-completions API and reads configuration from environment variables:
export MLLM_API_KEY="your-api-key"
export MLLM_BASE_URL="https://ai.juguang.chat/v1"
export MLLM_MODEL_NAME="gemini-3-flash-preview-thinking"Defaults:
MLLM_BASE_URL=https://ai.juguang.chat/v1MLLM_MODEL_NAME=gemini-3-flash-preview-thinkingMLLM_API_KEYis not hardcoded and should be provided by you.
The demo reads a local image, accepts the original query, constructs the pipeline, and runs with dry_run=True by default when --image-generation-pipe stub is used:
python -m synthetic_icl.demo \
--config synthetic_icl/demo_config.example.jsonTo run real image generation during the image-generation stage, install the optional Qwen dependencies and pass --image-generation-pipe qwen_edit at the main entry point:
python -m synthetic_icl.demo \
--image /path/to/original.png \
--query "子图 A 和 B 谁更加平滑?" \
--image-generation-pipe qwen_edit \
--output-dir synthetic_outputs \
--num-scenarios 2 \
--top-k 2When --image-generation-pipe qwen_edit is used, the demo defaults to dry_run=False, loads Qwen/Qwen-Image-Edit-2511, passes the original image as the reference image list (image=[original_image]), and uses each GenerationPromptSpec.image_generation_prompt as the Qwen prompt. Generated images are saved under --output-dir. You can still force prompt-only execution with --dry-run.
For iterative edit runs, the demo also saves process artifacts under --output-dir/edit_traces/ (per-scenario image trajectory + trace.json with verification reasoning and per-round edit prompts). Scenarios filtered as invalid are saved under --output-dir/invalid_scenarios/ for manual diagnosis.
It prints:
TaskIRScenarioSpecsAnswerSpecs- image generation prompts
- selected example metadata
You can control how many recent edited images are fed back into verification/refinement context via history_image_window (CLI: --history-image-window, default 3).
dry_run=True is the recommended first step while developing prompts and task schemas.
In dry-run mode:
- the pipeline still calls MLLM reasoning modules;
- it does not call real image generation;
SyntheticExample.imageisNone;- verification returns a skipped status;
- all metadata, answers, scenarios, and generation prompts are still returned for inspection.
synthetic_icl/modules/image_generation.py contains both the default stub and an optional QwenImageEditGenerationModule. Use the factory to choose a backend programmatically:
from synthetic_icl.modules.image_generation import create_image_generation_module
image_generation_module = create_image_generation_module("qwen_edit")
pipeline = SyntheticICLPipeline(backbone, image_generation_module=image_generation_module)The default stub still raises NotImplementedError; this keeps prompt-only development safe and makes image generation opt-in. Any replacement backend should accept:
- the original reference image
- a
GenerationPromptSpec
and return a PIL.Image.Image. The rest of the pipeline does not need to change.
A real backend should follow GenerationPromptSpec.image_generation_prompt, especially:
- use the original image only as a task-related style/layout reference;
- do not copy exact original content;
- keep the original query unchanged;
- make the visual evidence clearly support the known answer.
This framework is query-driven rather than question-generation-driven. The goal is to synthesize demonstrations that teach the model how to answer the user's exact task form for the final original image. If synthetic examples rewrite the query, the ICL context may teach a different instruction pattern and reduce transfer to the final query.
For example, if the original query is:
子图 A 和 B 谁更加平滑?
then every synthetic demonstration should still use:
子图 A 和 B 谁更加平滑?
The synthetic image can change, such as a new figure with two subplots where A is smoother and B is sharper, and the known answer can be pre-committed as A.
synthetic_icl/
__init__.py
backbone.py
schemas.py
json_utils.py
modules/
__init__.py
understanding.py
task_induction.py
scenario_expansion.py
answer_sampling.py
prompt_construction.py
image_generation.py
verification.py
selection.py
pipeline.py
demo.py
README.md
requirements.txt
requirements-qwen.txt