electblake commited on
Commit
2c91fea
·
0 Parent(s):
.agents/skills/gradio/SKILL.md ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ name: gradio
3
+ description: Build Gradio web UIs and demos in Python. Use when creating, modifying, debugging, or answering questions about Gradio and its capabilties, components, event listeners, or layouts.
4
+ ---
5
+
6
+ # Gradio
7
+
8
+ Gradio is a Python library for building interactive web UIs and ML demos. This skill covers the core API, patterns, and examples.
9
+
10
+ ## References
11
+ - `references/examples.md` - Illustrative examples showcasing the core Gradio API.
12
+ - `references/api-signatures.md` - API signatures of commonly used components.
13
+ - `references/event-listeners.md` - API signatures of events supported by each component.
14
+
15
+ ## Guides
16
+
17
+ Detailed guides on specific topics (read these when relevant):
18
+
19
+ - [Quickstart](https://www.gradio.app/guides/quickstart)
20
+ - [The Interface Class](https://www.gradio.app/guides/the-interface-class)
21
+ - [Blocks and Event Listeners](https://www.gradio.app/guides/blocks-and-event-listeners)
22
+ - [Controlling Layout](https://www.gradio.app/guides/controlling-layout)
23
+ - [More Blocks Features](https://www.gradio.app/guides/more-blocks-features)
24
+ - [Custom CSS and JS](https://www.gradio.app/guides/custom-CSS-and-JS)
25
+ - [Streaming Outputs](https://www.gradio.app/guides/streaming-outputs)
26
+ - [Streaming Inputs](https://www.gradio.app/guides/streaming-inputs)
27
+ - [Sharing Your App](https://www.gradio.app/guides/sharing-your-app)
28
+ - [Custom HTML Components](https://www.gradio.app/guides/custom-HTML-components)
29
+ - [Getting Started with the Python Client](https://www.gradio.app/guides/getting-started-with-the-python-client)
30
+ - [Getting Started with the JS Client](https://www.gradio.app/guides/getting-started-with-the-js-client)
31
+
32
+ ## Core Patterns
33
+
34
+ **Interface** (high-level): wraps a function with input/output components.
35
+
36
+ ```python
37
+ import gradio as gr
38
+
39
+ def greet(name):
40
+ return f"Hello {name}!"
41
+
42
+ gr.Interface(fn=greet, inputs="text", outputs="text").launch()
43
+ ```
44
+
45
+ **Blocks** (low-level): flexible layout with explicit event wiring.
46
+
47
+ ```python
48
+ import gradio as gr
49
+
50
+ with gr.Blocks() as demo:
51
+ name = gr.Textbox(label="Name")
52
+ output = gr.Textbox(label="Greeting")
53
+ btn = gr.Button("Greet")
54
+ btn.click(fn=lambda n: f"Hello {n}!", inputs=name, outputs=output)
55
+
56
+ demo.launch()
57
+ ```
58
+
59
+ **ChatInterface**: high-level wrapper for chatbot UIs.
60
+
61
+ ```python
62
+ import gradio as gr
63
+
64
+ def respond(message, history):
65
+ return f"You said: {message}"
66
+
67
+ gr.ChatInterface(fn=respond).launch()
68
+ ```
69
+
70
+ ## Custom HTML Components
71
+
72
+ If a task requires significant customization of an existing component or a component that doesn't exist in Gradio, you can create one with `gr.HTML`. It supports `html_template` (with `${}` JS expressions and `{{}}` Handlebars syntax), `css_template` for scoped styles, and `js_on_load` for interactivity — where `props.value` updates the component value and `trigger('event_name')` fires Gradio events. For reuse, subclass `gr.HTML` and define `api_info()` for API/MCP support.
73
+
74
+ See the [full guide](https://www.gradio.app/guides/custom-HTML-components) as well as example in `references/examples.md`
75
+
76
+ ## Server Mode
77
+
78
+ Use `gr.Server` instead of gr.Blocks when the users requests any of the following:
79
+ - Completely custom UI (your own HTML, React, Svelte, etc.) powered by Gradio's backend.
80
+ - Full control of FastAPI server (custom GET/POST routes, middleware, dependency injection) alongside Gradio API endpoints
81
+
82
+ See the [full guide](https://www.gradio.app/guides/server-mode) and example in `references/examples.md`.
83
+
84
+ If the user's use case can be handled by Gradio's built-in components or customizable HTML components, prefer not to use `gr.Server`.
.agents/skills/gradio/references/api-signatures.md ADDED
@@ -0,0 +1,115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Component API Signatures
2
+
3
+ Quick reference for common Gradio component constructors.
4
+
5
+ ## `Textbox`
6
+
7
+ ```python
8
+ Textbox(value: str | I18nData | Callable | None = None, type: Literal['text', 'password', 'email'] = "text", lines: int = 1, max_lines: int | None = None, placeholder: str | I18nData | None = None, label: str | I18nData | None = None, info: str | I18nData | None = None, every: Timer | float | None = None, inputs: Component | Sequence[Component] | set[Component] | None = None, show_label: bool | None = None, container: bool = True, scale: int | None = None, min_width: int = 160, interactive: bool | None = None, visible: bool | Literal['hidden'] = True, elem_id: str | None = None, autofocus: bool = False, autoscroll: bool = True, elem_classes: list[str] | str | None = None, render: bool = True, key: int | str | tuple[int | str, ...] | None = None, preserved_by_key: list[str] | str | None = "value", text_align: Literal['left', 'right'] | None = None, rtl: bool = False, buttons: list[Literal['copy'] | Button] | None = None, max_length: int | None = None, submit_btn: str | bool | None = False, stop_btn: str | bool | None = False, html_attributes: InputHTMLAttributes | None = None)
9
+ ```
10
+
11
+ Creates a textarea for user to enter string input or display string output.
12
+
13
+ ## `Number`
14
+
15
+ ```python
16
+ Number(value: float | Callable | None = None, label: str | I18nData | None = None, placeholder: str | I18nData | None = None, info: str | I18nData | None = None, every: Timer | float | None = None, inputs: Component | Sequence[Component] | set[Component] | None = None, show_label: bool | None = None, container: bool = True, scale: int | None = None, min_width: int = 160, interactive: bool | None = None, visible: bool | Literal['hidden'] = True, elem_id: str | None = None, elem_classes: list[str] | str | None = None, render: bool = True, key: int | str | tuple[int | str, ...] | None = None, preserved_by_key: list[str] | str | None = "value", buttons: list[Button] | None = None, precision: int | None = None, minimum: float | None = None, maximum: float | None = None, step: float = 1)
17
+ ```
18
+
19
+ Creates a numeric field for user to enter numbers as input or display numeric output.
20
+
21
+ ## `Slider`
22
+
23
+ ```python
24
+ Slider(minimum: float = 0, maximum: float = 100, value: float | Callable | None = None, step: float | None = None, precision: int | None = None, label: str | I18nData | None = None, info: str | I18nData | None = None, every: Timer | float | None = None, inputs: Component | Sequence[Component] | set[Component] | None = None, show_label: bool | None = None, container: bool = True, scale: int | None = None, min_width: int = 160, interactive: bool | None = None, visible: bool | Literal['hidden'] = True, elem_id: str | None = None, elem_classes: list[str] | str | None = None, render: bool = True, key: int | str | tuple[int | str, ...] | None = None, preserved_by_key: list[str] | str | None = "value", randomize: bool = False, buttons: list[Literal['reset']] | None = None)
25
+ ```
26
+
27
+ Creates a slider that ranges from {minimum} to {maximum} with a step size of {step}.
28
+
29
+ ## `Checkbox`
30
+
31
+ ```python
32
+ Checkbox(value: bool | Callable = False, label: str | I18nData | None = None, info: str | I18nData | None = None, every: Timer | float | None = None, inputs: Component | Sequence[Component] | set[Component] | None = None, show_label: bool | None = None, container: bool = True, scale: int | None = None, min_width: int = 160, interactive: bool | None = None, visible: bool | Literal['hidden'] = True, elem_id: str | None = None, elem_classes: list[str] | str | None = None, render: bool = True, key: int | str | tuple[int | str, ...] | None = None, preserved_by_key: list[str] | str | None = "value", buttons: list[Button] | None = None)
33
+ ```
34
+
35
+ Creates a checkbox that can be set to `True` or `False`. Can be used as an input to pass a boolean value to a function or as an output to display a boolean value.
36
+
37
+ ## `Dropdown`
38
+
39
+ ```python
40
+ Dropdown(choices: Sequence[str | int | float | tuple[str | I18nData, str | int | float]] | None = None, value: str | int | float | Sequence[str | int | float] | Callable | DefaultValue | None = DefaultValue(), type: Literal['value', 'index'] = "value", multiselect: bool | None = None, allow_custom_value: bool = False, max_choices: int | None = None, filterable: bool = True, label: str | I18nData | None = None, info: str | I18nData | None = None, every: Timer | float | None = None, inputs: Component | Sequence[Component] | set[Component] | None = None, show_label: bool | None = None, container: bool = True, scale: int | None = None, min_width: int = 160, interactive: bool | None = None, visible: bool | Literal['hidden'] = True, elem_id: str | None = None, elem_classes: list[str] | str | None = None, render: bool = True, key: int | str | tuple[int | str, ...] | None = None, preserved_by_key: list[str] | str | None = "value", buttons: list[Button] | None = None)
41
+ ```
42
+
43
+ Creates a dropdown of choices from which a single entry or multiple entries can be selected (as an input component) or displayed (as an output component).
44
+
45
+ ## `Radio`
46
+
47
+ ```python
48
+ Radio(choices: Sequence[str | int | float | tuple[str | I18nData, str | int | float]] | None = None, value: str | int | float | Callable | None = None, type: Literal['value', 'index'] = "value", label: str | I18nData | None = None, info: str | I18nData | None = None, every: Timer | float | None = None, inputs: Component | Sequence[Component] | set[Component] | None = None, show_label: bool | None = None, container: bool = True, scale: int | None = None, min_width: int = 160, interactive: bool | None = None, visible: bool | Literal['hidden'] = True, elem_id: str | None = None, elem_classes: list[str] | str | None = None, render: bool = True, key: int | str | tuple[int | str, ...] | None = None, preserved_by_key: list[str] | str | None = "value", rtl: bool = False, buttons: list[Button] | None = None)
49
+ ```
50
+
51
+ Creates a set of (string or numeric type) radio buttons of which only one can be selected.
52
+
53
+ ## `Image`
54
+
55
+ ```python
56
+ Image(value: str | PIL.Image.Image | np.ndarray | Callable | None = None, format: str = "webp", height: int | str | None = None, width: int | str | None = None, image_mode: Literal['1', 'L', 'P', 'RGB', 'RGBA', 'CMYK', 'YCbCr', 'LAB', 'HSV', 'I', 'F'] | None = "RGB", sources: list[Literal['upload', 'webcam', 'clipboard']] | Literal['upload', 'webcam', 'clipboard'] | None = None, type: Literal['numpy', 'pil', 'filepath'] = "numpy", label: str | I18nData | None = None, every: Timer | float | None = None, inputs: Component | Sequence[Component] | set[Component] | None = None, show_label: bool | None = None, buttons: list[Literal['download', 'share', 'fullscreen'] | Button] | None = None, container: bool = True, scale: int | None = None, min_width: int = 160, interactive: bool | None = None, visible: bool | Literal['hidden'] = True, streaming: bool = False, elem_id: str | None = None, elem_classes: list[str] | str | None = None, render: bool = True, key: int | str | tuple[int | str, ...] | None = None, preserved_by_key: list[str] | str | None = "value", webcam_options: WebcamOptions | None = None, placeholder: str | None = None, watermark: WatermarkOptions | None = None)
57
+ ```
58
+
59
+ Creates an image component that can be used to upload images (as an input) or display images (as an output).
60
+
61
+ ## `Audio`
62
+
63
+ ```python
64
+ Audio(value: str | Path | tuple[int, np.ndarray] | Callable | None = None, sources: list[Literal['upload', 'microphone']] | Literal['upload', 'microphone'] | None = None, type: Literal['numpy', 'filepath'] = "numpy", label: str | I18nData | None = None, every: Timer | float | None = None, inputs: Component | Sequence[Component] | set[Component] | None = None, show_label: bool | None = None, container: bool = True, scale: int | None = None, min_width: int = 160, interactive: bool | None = None, visible: bool | Literal['hidden'] = True, streaming: bool = False, elem_id: str | None = None, elem_classes: list[str] | str | None = None, render: bool = True, key: int | str | tuple[int | str, ...] | None = None, preserved_by_key: list[str] | str | None = "value", format: Literal['wav', 'mp3'] | None = None, autoplay: bool = False, editable: bool = True, buttons: list[Literal['download', 'share'] | Button] | None = None, waveform_options: WaveformOptions | dict | None = None, loop: bool = False, recording: bool = False, subtitles: str | Path | list[dict[str, Any]] | None = None, playback_position: float = 0)
65
+ ```
66
+
67
+ Creates an audio component that can be used to upload/record audio (as an input) or display audio (as an output).
68
+
69
+ ## `Video`
70
+
71
+ ```python
72
+ Video(value: str | Path | Callable | None = None, format: str | None = None, sources: list[Literal['upload', 'webcam']] | Literal['upload', 'webcam'] | None = None, height: int | str | None = None, width: int | str | None = None, label: str | I18nData | None = None, every: Timer | float | None = None, inputs: Component | Sequence[Component] | set[Component] | None = None, show_label: bool | None = None, container: bool = True, scale: int | None = None, min_width: int = 160, interactive: bool | None = None, visible: bool | Literal['hidden'] = True, elem_id: str | None = None, elem_classes: list[str] | str | None = None, render: bool = True, key: int | str | tuple[int | str, ...] | None = None, preserved_by_key: list[str] | str | None = "value", webcam_options: WebcamOptions | None = None, include_audio: bool | None = None, autoplay: bool = False, buttons: list[Literal['download', 'share'] | Button] | None = None, loop: bool = False, streaming: bool = False, watermark: WatermarkOptions | None = None, subtitles: str | Path | list[dict[str, Any]] | None = None, playback_position: float = 0)
73
+ ```
74
+
75
+ Creates a video component that can be used to upload/record videos (as an input) or display videos (as an output). For the video to be playable in the browser it must have a compatible container and codec combination. Allowed combinations are .mp4 with h264 codec, .ogg with theora codec, and .webm with vp9 codec. If the component detects that the output video would not be playable in the browser it will attempt to convert it to a playable mp4 video. If the conversion fails, the original video is returned.
76
+
77
+ ## `File`
78
+
79
+ ```python
80
+ File(value: str | list[str] | Callable | None = None, file_count: Literal['single', 'multiple', 'directory'] = "single", file_types: list[str] | None = None, type: Literal['filepath', 'binary'] = "filepath", label: str | I18nData | None = None, every: Timer | float | None = None, inputs: Component | Sequence[Component] | set[Component] | None = None, show_label: bool | None = None, container: bool = True, scale: int | None = None, min_width: int = 160, height: int | str | float | None = None, interactive: bool | None = None, visible: bool | Literal['hidden'] = True, elem_id: str | None = None, elem_classes: list[str] | str | None = None, render: bool = True, key: int | str | tuple[int | str, ...] | None = None, preserved_by_key: list[str] | str | None = "value", allow_reordering: bool = False, buttons: list[Button] | None = None)
81
+ ```
82
+
83
+ Creates a file component that allows uploading one or more generic files (when used as an input) or displaying generic files or URLs for download (as output). Demo: zip_files, zip_to_json
84
+
85
+ ## `Chatbot`
86
+
87
+ ```python
88
+ Chatbot(value: list[MessageDict | Message] | Callable | None = None, label: str | I18nData | None = None, every: Timer | float | None = None, inputs: Component | Sequence[Component] | set[Component] | None = None, show_label: bool | None = None, container: bool = True, scale: int | None = None, min_width: int = 160, visible: bool | Literal['hidden'] = True, elem_id: str | None = None, elem_classes: list[str] | str | None = None, autoscroll: bool = True, render: bool = True, key: int | str | tuple[int | str, ...] | None = None, preserved_by_key: list[str] | str | None = "value", height: int | str | None = 400, resizable: bool = False, max_height: int | str | None = None, min_height: int | str | None = None, editable: Literal['user', 'all'] | None = None, latex_delimiters: list[dict[str, str | bool]] | None = None, rtl: bool = False, buttons: list[Literal['share', 'copy', 'copy_all'] | Button] | None = None, watermark: str | None = None, avatar_images: tuple[str | Path | None, str | Path | None] | None = None, sanitize_html: bool = True, render_markdown: bool = True, feedback_options: list[str] | tuple[str, ...] | None = ('Like', 'Dislike'), feedback_value: Sequence[str | None] | None = None, line_breaks: bool = True, layout: Literal['panel', 'bubble'] | None = None, placeholder: str | None = None, examples: list[ExampleMessage] | None = None, allow_file_downloads: bool = True, group_consecutive_messages: bool = True, allow_tags: list[str] | bool = True, reasoning_tags: list[tuple[str, str]] | None = None, like_user_message: bool = False)
89
+ ```
90
+
91
+ Creates a chatbot that displays user-submitted messages and responses. Supports a subset of Markdown including bold, italics, code, tables. Also supports audio/video/image files, which are displayed in the Chatbot, and other kinds of files which are displayed as links. This component is usually used as an output component.
92
+
93
+ ## `Button`
94
+
95
+ ```python
96
+ Button(value: str | I18nData | Callable = "Run", every: Timer | float | None = None, inputs: Component | Sequence[Component] | set[Component] | None = None, variant: Literal['primary', 'secondary', 'stop', 'huggingface'] = "secondary", size: Literal['sm', 'md', 'lg'] = "lg", icon: str | Path | None = None, link: str | None = None, link_target: Literal['_self', '_blank', '_parent', '_top'] = "_self", visible: bool | Literal['hidden'] = True, interactive: bool = True, elem_id: str | None = None, elem_classes: list[str] | str | None = None, render: bool = True, key: int | str | tuple[int | str, ...] | None = None, preserved_by_key: list[str] | str | None = "value", scale: int | None = None, min_width: int | None = None)
97
+ ```
98
+
99
+ Creates a button that can be assigned arbitrary .click() events. The value (label) of the button can be used as an input to the function (rarely used) or set via the output of a function.
100
+
101
+ ## `Markdown`
102
+
103
+ ```python
104
+ Markdown(value: str | I18nData | Callable | None = None, label: str | I18nData | None = None, every: Timer | float | None = None, inputs: Component | Sequence[Component] | set[Component] | None = None, show_label: bool | None = None, scale: int | None = None, min_width: int | None = None, rtl: bool = False, latex_delimiters: list[dict[str, str | bool]] | None = None, visible: bool | Literal['hidden'] = True, elem_id: str | None = None, elem_classes: list[str] | str | None = None, render: bool = True, key: int | str | tuple[int | str, ...] | None = None, preserved_by_key: list[str] | str | None = "value", sanitize_html: bool = True, line_breaks: bool = False, header_links: bool = False, height: int | str | None = None, max_height: int | str | None = None, min_height: int | str | None = None, buttons: list[Literal['copy']] | None = None, container: bool = False, padding: bool = False)
105
+ ```
106
+
107
+ Used to render arbitrary Markdown output. Can also render latex enclosed by dollar signs as well as code blocks with syntax highlighting. Supported languages are bash, c, cpp, go, java, javascript, json, php, python, rust, sql, and yaml. As this component does not accept user input, it is rarely used as an input component.
108
+
109
+ ## `HTML`
110
+
111
+ ```python
112
+ HTML(value: Any | Callable | None = None, label: str | I18nData | None = None, html_template: str = "${value}", css_template: str = "", js_on_load: str | None = "element.addEventListener('click', function() { trigger('click') });", apply_default_css: bool = True, every: Timer | float | None = None, inputs: Component | Sequence[Component] | set[Component] | None = None, show_label: bool = False, scale: int | None = None, min_width: int | None = None, visible: bool | Literal['hidden'] = True, elem_id: str | None = None, elem_classes: list[str] | str | None = None, render: bool = True, key: int | str | tuple[int | str, ...] | None = None, preserved_by_key: list[str] | str | None = "value", min_height: int | None = None, max_height: int | None = None, container: bool = False, padding: bool = False, autoscroll: bool = False, buttons: list[Button] | None = None, head: str | None = None, server_functions: list[Callable] | None = None, props: Any)
113
+ ```
114
+
115
+ Creates a component with arbitrary HTML. Can include CSS and JavaScript to create highly customized and interactive components.
.agents/skills/gradio/references/event-listeners.md ADDED
@@ -0,0 +1,132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Event Listeners
2
+
3
+ Events supported by each component.
4
+
5
+
6
+ ## Event Listener Signature
7
+
8
+ ```python
9
+ component.event_name(
10
+ fn: Callable | None | Literal["decorator"] = "decorator",
11
+ inputs: Component | Sequence[Component] | set[Component] | None = None,
12
+ outputs: Component | Sequence[Component] | set[Component] | None = None,
13
+ api_name: str | None = None,
14
+ api_description: str | None | Literal[False] = None,
15
+ scroll_to_output: bool = False,
16
+ show_progress: Literal["full", "minimal", "hidden"] = "full",
17
+ show_progress_on: Component | Sequence[Component] | None = None,
18
+ queue: bool = True,
19
+ batch: bool = False,
20
+ max_batch_size: int = 4,
21
+ preprocess: bool = True,
22
+ postprocess: bool = True,
23
+ cancels: dict[str, Any] | list[dict[str, Any]] | None = None,
24
+ trigger_mode: Literal["once", "multiple", "always_last"] | None = None,
25
+ js: str | Literal[True] | None = None,
26
+ concurrency_limit: int | None | Literal["default"] = "default",
27
+ concurrency_id: str | None = None,
28
+ api_visibility: Literal["public", "private", "undocumented"] = "public",
29
+ time_limit: int | None = None,
30
+ stream_every: float = 0.5,
31
+ key: int | str | tuple[int | str, ...] | None = None,
32
+ validator: Callable | None = None,
33
+ ) -> Dependency
34
+ ```
35
+
36
+ ## Supported Events by Component
37
+
38
+ - **AnnotatedImage**: change, select
39
+
40
+ - **Audio**: stream, change, clear, play, pause, stop, pause, start_recording, pause_recording, stop_recording, upload, input
41
+
42
+ - **BarPlot**: change, select, double_click
43
+
44
+ - **BrowserState**: change
45
+
46
+ - **Button**: change, click
47
+
48
+ - **Chatbot**: change, select, like, retry, undo, example_select, option_select, clear, copy, edit
49
+
50
+ - **Checkbox**: change, input, select
51
+
52
+ - **CheckboxGroup**: change, input, select
53
+
54
+ - **ClearButton**: change, click
55
+
56
+ - **Code**: change, input, focus, blur
57
+
58
+ - **ColorPicker**: change, input, release, submit, focus, blur
59
+
60
+ - **Dataframe**: change, input, select, edit
61
+
62
+ - **Dataset**: change, click, select
63
+
64
+ - **DateTime**: change, submit
65
+
66
+ - **DeepLinkButton**: change, click
67
+
68
+ - **Dialogue**: change, input, submit
69
+
70
+ - **DownloadButton**: change, click
71
+
72
+ - **Dropdown**: change, input, select, focus, blur, key_up
73
+
74
+ - **DuplicateButton**: change, click
75
+
76
+ - **File**: change, select, clear, upload, delete, download
77
+
78
+ - **FileExplorer**: change, input, select
79
+
80
+ - **Gallery**: select, upload, change, delete, preview_close, preview_open
81
+
82
+ - **HTML**: change, input, click, double_click, submit, stop, edit, clear, play, pause, end, start_recording, pause_recording, stop_recording, focus, blur, upload, release, select, stream, like, example_select, option_select, load, key_up, apply, delete, tick, undo, retry, expand, collapse, download, copy
83
+
84
+ - **HighlightedText**: change, select
85
+
86
+ - **Image**: clear, change, stream, select, upload, input
87
+
88
+ - **ImageEditor**: clear, change, input, select, upload, apply
89
+
90
+ - **ImageSlider**: clear, change, stream, select, upload, input
91
+
92
+ - **JSON**: change
93
+
94
+ - **Label**: change, select
95
+
96
+ - **LinePlot**: change, select, double_click
97
+
98
+ - **LoginButton**: change, click
99
+
100
+ - **Markdown**: change, copy
101
+
102
+ - **Model3D**: change, upload, edit, clear
103
+
104
+ - **MultimodalTextbox**: change, input, select, submit, focus, blur, stop
105
+
106
+ - **Navbar**: change
107
+
108
+ - **Number**: change, input, submit, focus, blur
109
+
110
+ - **ParamViewer**: change, upload
111
+
112
+ - **Plot**: change
113
+
114
+ - **Radio**: select, change, input
115
+
116
+ - **ScatterPlot**: change, select, double_click
117
+
118
+ - **SimpleImage**: clear, change, upload
119
+
120
+ - **Slider**: change, input, release
121
+
122
+ - **State**: change
123
+
124
+ - **Textbox**: change, input, select, submit, focus, blur, stop, copy
125
+
126
+ - **Timer**: change, tick
127
+
128
+ - **UploadButton**: change, click, upload
129
+
130
+ - **Video**: change, clear, start_recording, stop_recording, stop, play, pause, end, upload, input
131
+
132
+ - **WorkflowCanvas**: change
.agents/skills/gradio/references/examples.md ADDED
@@ -0,0 +1,645 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Gradio End-to-End Examples
2
+
3
+ Complete working Gradio apps for reference.
4
+
5
+ ## Blocks Essay Simple
6
+
7
+ ```python
8
+ import gradio as gr
9
+
10
+ def change_textbox(choice):
11
+ if choice == "short":
12
+ return gr.Textbox(lines=2, visible=True)
13
+ elif choice == "long":
14
+ return gr.Textbox(lines=8, visible=True, value="Lorem ipsum dolor sit amet")
15
+ else:
16
+ return gr.Textbox(visible=False)
17
+
18
+ with gr.Blocks() as demo:
19
+ radio = gr.Radio(
20
+ ["short", "long", "none"], label="What kind of essay would you like to write?"
21
+ )
22
+ text = gr.Textbox(lines=2, interactive=True, buttons=["copy"])
23
+ radio.change(fn=change_textbox, inputs=radio, outputs=text)
24
+
25
+ demo.launch()
26
+ ```
27
+
28
+ ## Blocks Flipper
29
+
30
+ ```python
31
+ import numpy as np
32
+ import gradio as gr
33
+
34
+ def flip_text(x):
35
+ return x[::-1]
36
+
37
+ def flip_image(x):
38
+ return np.fliplr(x)
39
+
40
+ with gr.Blocks() as demo:
41
+ gr.Markdown("Flip text or image files using this demo.")
42
+ with gr.Tab("Flip Text"):
43
+ text_input = gr.Textbox()
44
+ text_output = gr.Textbox()
45
+ text_button = gr.Button("Flip")
46
+ with gr.Tab("Flip Image"):
47
+ with gr.Row():
48
+ image_input = gr.Image()
49
+ image_output = gr.Image()
50
+ image_button = gr.Button("Flip")
51
+
52
+ with gr.Accordion("Open for More!", open=False):
53
+ gr.Markdown("Look at me...")
54
+ temp_slider = gr.Slider(
55
+ 0, 1,
56
+ value=0.1,
57
+ step=0.1,
58
+ interactive=True,
59
+ label="Slide me",
60
+ )
61
+
62
+ text_button.click(flip_text, inputs=text_input, outputs=text_output)
63
+ image_button.click(flip_image, inputs=image_input, outputs=image_output)
64
+
65
+ demo.launch()
66
+ ```
67
+
68
+ ## Blocks Form
69
+
70
+ ```python
71
+ import gradio as gr
72
+
73
+ with gr.Blocks() as demo:
74
+ name_box = gr.Textbox(label="Name")
75
+ age_box = gr.Number(label="Age", minimum=0, maximum=100)
76
+ symptoms_box = gr.CheckboxGroup(["Cough", "Fever", "Runny Nose"])
77
+ submit_btn = gr.Button("Submit")
78
+
79
+ with gr.Column(visible=False) as output_col:
80
+ diagnosis_box = gr.Textbox(label="Diagnosis")
81
+ patient_summary_box = gr.Textbox(label="Patient Summary")
82
+
83
+ def submit(name, age, symptoms):
84
+ return {
85
+ submit_btn: gr.Button(visible=False),
86
+ output_col: gr.Column(visible=True),
87
+ diagnosis_box: "covid" if "Cough" in symptoms else "flu",
88
+ patient_summary_box: f"{name}, {age} y/o",
89
+ }
90
+
91
+ submit_btn.click(
92
+ submit,
93
+ [name_box, age_box, symptoms_box],
94
+ [submit_btn, diagnosis_box, patient_summary_box, output_col],
95
+ )
96
+
97
+ demo.launch()
98
+ ```
99
+
100
+ ## Blocks Hello
101
+
102
+ ```python
103
+ import gradio as gr
104
+
105
+ def welcome(name):
106
+ return f"Welcome to Gradio, {name}!"
107
+
108
+ with gr.Blocks() as demo:
109
+ gr.Markdown(
110
+ """
111
+ # Hello World!
112
+ Start typing below to see the output.
113
+ """)
114
+ inp = gr.Textbox(placeholder="What is your name?")
115
+ out = gr.Textbox()
116
+ inp.change(welcome, inp, out)
117
+
118
+ demo.launch()
119
+ ```
120
+
121
+ ## Blocks Layout
122
+
123
+ ```python
124
+ import gradio as gr
125
+
126
+ demo = gr.Blocks()
127
+
128
+ with demo:
129
+ with gr.Row():
130
+ gr.Image(interactive=True, scale=2)
131
+ gr.Image()
132
+ with gr.Row():
133
+ gr.Textbox(label="Text")
134
+ gr.Number(label="Count", scale=2)
135
+ gr.Radio(choices=["One", "Two"])
136
+ with gr.Row():
137
+ gr.Button("500", scale=0, min_width=500)
138
+ gr.Button("A", scale=0)
139
+ gr.Button("grow")
140
+ with gr.Row():
141
+ gr.Textbox()
142
+ gr.Textbox()
143
+ gr.Button()
144
+ with gr.Row():
145
+ with gr.Row():
146
+ with gr.Column():
147
+ gr.Textbox(label="Text")
148
+ gr.Number(label="Count")
149
+ gr.Radio(choices=["One", "Two"])
150
+ gr.Image()
151
+ with gr.Column():
152
+ gr.Image(interactive=True)
153
+ gr.Image()
154
+ gr.Image()
155
+ gr.Textbox(label="Text")
156
+ gr.Number(label="Count")
157
+ gr.Radio(choices=["One", "Two"])
158
+
159
+ demo.launch()
160
+ ```
161
+
162
+ ## Calculator
163
+
164
+ ```python
165
+ import gradio as gr
166
+
167
+ def calculator(num1, operation, num2):
168
+ if operation == "add":
169
+ return num1 + num2
170
+ elif operation == "subtract":
171
+ return num1 - num2
172
+ elif operation == "multiply":
173
+ return num1 * num2
174
+ elif operation == "divide":
175
+ if num2 == 0:
176
+ raise gr.Error("Cannot divide by zero!")
177
+ return num1 / num2
178
+
179
+ demo = gr.Interface(
180
+ calculator,
181
+ [
182
+ "number",
183
+ gr.Radio(["add", "subtract", "multiply", "divide"]),
184
+ "number"
185
+ ],
186
+ "number",
187
+ examples=[
188
+ [45, "add", 3],
189
+ [3.14, "divide", 2],
190
+ [144, "multiply", 2.5],
191
+ [0, "subtract", 1.2],
192
+ ],
193
+ title="Toy Calculator",
194
+ description="Here's a sample toy calculator.",
195
+ api_name="predict"
196
+ )
197
+
198
+ demo.launch()
199
+ ```
200
+
201
+ ## Chatbot Simple
202
+
203
+ ```python
204
+ import gradio as gr
205
+ import random
206
+ import time
207
+
208
+ with gr.Blocks() as demo:
209
+ chatbot = gr.Chatbot()
210
+ msg = gr.Textbox()
211
+ clear = gr.ClearButton([msg, chatbot])
212
+
213
+ def respond(message, chat_history):
214
+ bot_message = random.choice(["How are you?", "Today is a great day", "I'm very hungry"])
215
+ chat_history.append({"role": "user", "content": message})
216
+ chat_history.append({"role": "assistant", "content": bot_message})
217
+ time.sleep(2)
218
+ return "", chat_history
219
+
220
+ msg.submit(respond, [msg, chatbot], [msg, chatbot])
221
+
222
+ demo.launch()
223
+ ```
224
+
225
+ ## Chatbot Streaming
226
+
227
+ ```python
228
+ import gradio as gr
229
+ import random
230
+ import time
231
+
232
+ with gr.Blocks() as demo:
233
+ chatbot = gr.Chatbot()
234
+ msg = gr.Textbox()
235
+ clear = gr.Button("Clear")
236
+
237
+ def user(user_message, history: list):
238
+ return "", history + [{"role": "user", "content": user_message}]
239
+
240
+ def bot(history: list):
241
+ bot_message = random.choice(["How are you?", "I love you", "I'm very hungry"])
242
+ history.append({"role": "assistant", "content": ""})
243
+ for character in bot_message:
244
+ history[-1]['content'] += character
245
+ time.sleep(0.05)
246
+ yield history
247
+
248
+ msg.submit(user, [msg, chatbot], [msg, chatbot], queue=False).then(
249
+ bot, chatbot, chatbot
250
+ )
251
+ clear.click(lambda: None, None, chatbot, queue=False)
252
+
253
+ demo.launch()
254
+ ```
255
+
256
+ ## Custom Css
257
+
258
+ ```python
259
+ import gradio as gr
260
+
261
+ with gr.Blocks() as demo:
262
+ with gr.Column(elem_classes="cool-col"):
263
+ gr.Markdown("### Gradio Demo with Custom CSS", elem_classes="darktest")
264
+ gr.Markdown(
265
+ elem_classes="markdown",
266
+ value="Resize the browser window to see the CSS media query in action.",
267
+ )
268
+
269
+ if __name__ == "__main__":
270
+ demo.launch(css_paths=["demo/custom_css/custom_css.css"])
271
+ ```
272
+
273
+ ## Fake Diffusion
274
+
275
+ ```python
276
+ import gradio as gr
277
+ import numpy as np
278
+ import time
279
+
280
+ def fake_diffusion(steps):
281
+ rng = np.random.default_rng()
282
+ for i in range(steps):
283
+ time.sleep(1)
284
+ image = rng.random(size=(600, 600, 3))
285
+ yield image
286
+ image = np.ones((1000,1000,3), np.uint8)
287
+ image[:] = [255, 124, 0]
288
+ yield image
289
+
290
+ demo = gr.Interface(fake_diffusion,
291
+ inputs=gr.Slider(1, 10, 3, step=1),
292
+ outputs="image",
293
+ api_name="predict")
294
+
295
+ demo.launch()
296
+ ```
297
+
298
+ ## Hello World
299
+
300
+ ```python
301
+ import gradio as gr
302
+
303
+
304
+ def greet(name):
305
+ return "Hello " + name + "!"
306
+
307
+
308
+ demo = gr.Interface(fn=greet, inputs="textbox", outputs="textbox", api_name="predict")
309
+
310
+ demo.launch()
311
+ ```
312
+
313
+ ## Image Editor
314
+
315
+ ```python
316
+ import gradio as gr
317
+ import time
318
+
319
+
320
+ def sleep(im):
321
+ time.sleep(5)
322
+ return [im["background"], im["layers"][0], im["layers"][1], im["composite"]]
323
+
324
+
325
+ def predict(im):
326
+ return im["composite"]
327
+
328
+
329
+ with gr.Blocks() as demo:
330
+ with gr.Row():
331
+ im = gr.ImageEditor(
332
+ type="numpy",
333
+ )
334
+ im_preview = gr.Image()
335
+ n_upload = gr.Number(0, label="Number of upload events", step=1)
336
+ n_change = gr.Number(0, label="Number of change events", step=1)
337
+ n_input = gr.Number(0, label="Number of input events", step=1)
338
+
339
+ im.upload(lambda x: x + 1, outputs=n_upload, inputs=n_upload)
340
+ im.change(lambda x: x + 1, outputs=n_change, inputs=n_change)
341
+ im.input(lambda x: x + 1, outputs=n_input, inputs=n_input)
342
+ im.change(predict, outputs=im_preview, inputs=im, show_progress="hidden")
343
+
344
+ demo.launch()
345
+ ```
346
+
347
+ ## On Listener Decorator
348
+
349
+ ```python
350
+ import gradio as gr
351
+
352
+ with gr.Blocks() as demo:
353
+ name = gr.Textbox(label="Name")
354
+ output = gr.Textbox(label="Output Box")
355
+ greet_btn = gr.Button("Greet")
356
+
357
+ @gr.on(triggers=[name.submit, greet_btn.click], inputs=name, outputs=output)
358
+ def greet(name):
359
+ return "Hello " + name + "!"
360
+
361
+ demo.launch()
362
+ ```
363
+
364
+ ## Render Merge
365
+
366
+ ```python
367
+ import gradio as gr
368
+ import time
369
+
370
+
371
+ JS_ON_LOAD = """
372
+ element.querySelectorAll('button').forEach((button) => {
373
+ button.addEventListener('click', () => {
374
+ trigger('click', {clicked: button.innerText});
375
+ });
376
+ });
377
+ """
378
+
379
+
380
+ with gr.Blocks() as demo:
381
+ text_count = gr.Slider(1, 5, value=1, step=1, label="Textbox Count")
382
+
383
+ @gr.render(inputs=text_count)
384
+ def render_count(count):
385
+ boxes = []
386
+ for i in range(count):
387
+ box = gr.Textbox(label=f"Box {i}")
388
+ boxes.append(box)
389
+
390
+ def merge(*args):
391
+ time.sleep(0.2) # simulate a delay
392
+ return " ".join(args)
393
+
394
+ merge_btn.click(merge, boxes, output)
395
+
396
+ def clear():
397
+ time.sleep(0.2) # simulate a delay
398
+ return [" "] * count
399
+
400
+ clear_btn.click(clear, None, boxes)
401
+
402
+ def countup():
403
+ time.sleep(0.2) # simulate a delay
404
+ return list(range(count))
405
+
406
+ count_btn.click(countup, None, boxes, queue=False)
407
+
408
+ with gr.Row():
409
+ merge_btn = gr.Button("Merge")
410
+ clear_btn = gr.Button("Clear")
411
+ count_btn = gr.Button("Count")
412
+
413
+ output = gr.Textbox()
414
+
415
+ html_button_count = gr.State(2)
416
+
417
+ @gr.render(inputs=html_button_count)
418
+ def render_html_buttons(count):
419
+ buttons = gr.HTML(
420
+ "".join(f"<button>HTML button {i}</button>" for i in range(count)),
421
+ js_on_load=JS_ON_LOAD,
422
+ )
423
+ clicked = gr.Textbox(label="Clicked HTML button")
424
+
425
+ def select_button(evt: gr.EventData):
426
+ return evt.clicked
427
+
428
+ buttons.click(select_button, outputs=clicked)
429
+
430
+ add_html_button = gr.Button("Add HTML button")
431
+ add_html_button.click(
432
+ lambda count: count + 1,
433
+ inputs=html_button_count,
434
+ outputs=html_button_count,
435
+ )
436
+
437
+ demo.launch()
438
+ ```
439
+
440
+ ## Reverse Audio 2
441
+
442
+ ```python
443
+ import gradio as gr
444
+ import numpy as np
445
+
446
+ def reverse_audio(audio):
447
+ sr, data = audio
448
+ return (sr, np.flipud(data))
449
+
450
+ demo = gr.Interface(fn=reverse_audio,
451
+ inputs="microphone",
452
+ outputs="audio", api_name="predict")
453
+
454
+ demo.launch()
455
+ ```
456
+
457
+ ## Sepia Filter
458
+
459
+ ```python
460
+ import numpy as np
461
+ import gradio as gr
462
+
463
+ def sepia(input_img):
464
+ sepia_filter = np.array([
465
+ [0.393, 0.769, 0.189],
466
+ [0.349, 0.686, 0.168],
467
+ [0.272, 0.534, 0.131]
468
+ ])
469
+ sepia_img = input_img.dot(sepia_filter.T)
470
+ sepia_img /= sepia_img.max()
471
+ return sepia_img
472
+
473
+ demo = gr.Interface(sepia, gr.Image(), "image", api_name="predict")
474
+ demo.launch()
475
+ ```
476
+
477
+ ## Sort Records
478
+
479
+ ```python
480
+ import gradio as gr
481
+
482
+ def sort_records(records):
483
+ return records.sort("Quantity")
484
+
485
+ demo = gr.Interface(
486
+ sort_records,
487
+ gr.Dataframe(
488
+ headers=["Item", "Quantity"],
489
+ datatype=["str", "number"],
490
+ row_count=3,
491
+ column_count=2,
492
+ column_limits=(2, 2),
493
+ type="polars"
494
+ ),
495
+ "dataframe",
496
+ description="Sort by Quantity"
497
+ )
498
+
499
+ demo.launch()
500
+ ```
501
+
502
+ ## Streaming Simple
503
+
504
+ ```python
505
+ import gradio as gr
506
+
507
+ with gr.Blocks() as demo:
508
+ with gr.Row():
509
+ with gr.Column():
510
+ input_img = gr.Image(label="Input", sources="webcam")
511
+ with gr.Column():
512
+ output_img = gr.Image(label="Output")
513
+ input_img.stream(lambda s: s, input_img, output_img, time_limit=15, stream_every=0.1, concurrency_limit=30)
514
+
515
+ if __name__ == "__main__":
516
+
517
+ demo.launch()
518
+ ```
519
+
520
+ ## Tabbed Interface Lite
521
+
522
+ ```python
523
+ import gradio as gr
524
+
525
+ hello_world = gr.Interface(lambda name: "Hello " + name, "text", "text", api_name="predict")
526
+ bye_world = gr.Interface(lambda name: "Bye " + name, "text", "text", api_name="predict")
527
+ chat = gr.ChatInterface(lambda *args: "Hello " + args[0], api_name="chat")
528
+
529
+ demo = gr.TabbedInterface([hello_world, bye_world, chat], ["Hello World", "Bye World", "Chat"])
530
+
531
+ demo.launch()
532
+ ```
533
+
534
+ ## Tax Calculator
535
+
536
+ ```python
537
+ import gradio as gr
538
+
539
+ def tax_calculator(income, marital_status, assets):
540
+ tax_brackets = [(10, 0), (25, 8), (60, 12), (120, 20), (250, 30)]
541
+ total_deductible = sum(cost for cost, deductible in zip(assets["Cost"], assets["Deductible"]) if deductible)
542
+ taxable_income = income - total_deductible
543
+
544
+ total_tax = 0
545
+ for bracket, rate in tax_brackets:
546
+ if taxable_income > bracket:
547
+ total_tax += (taxable_income - bracket) * rate / 100
548
+
549
+ if marital_status == "Married":
550
+ total_tax *= 0.75
551
+ elif marital_status == "Divorced":
552
+ total_tax *= 0.8
553
+
554
+ return round(total_tax)
555
+
556
+ demo = gr.Interface(
557
+ tax_calculator,
558
+ [
559
+ "number",
560
+ gr.Radio(["Single", "Married", "Divorced"]),
561
+ gr.Dataframe(
562
+ headers=["Item", "Cost", "Deductible"],
563
+ datatype=["str", "number", "bool"],
564
+ label="Assets Purchased this Year",
565
+ ),
566
+ ],
567
+ gr.Number(label="Tax due"),
568
+ examples=[
569
+ [10000, "Married", [["Suit", 5000, True], ["Laptop (for work)", 800, False], ["Car", 1800, True]]],
570
+ [80000, "Single", [["Suit", 800, True], ["Watch", 1800, True], ["Food", 800, True]]],
571
+ ],
572
+ live=True,
573
+ api_name="predict"
574
+ )
575
+
576
+ demo.launch()
577
+ ```
578
+
579
+ ## Timer Simple
580
+
581
+ ```python
582
+ import gradio as gr
583
+ import random
584
+ import time
585
+
586
+ with gr.Blocks() as demo:
587
+ timer = gr.Timer(1)
588
+ timestamp = gr.Number(label="Time")
589
+ timer.tick(lambda: round(time.time()), outputs=timestamp, api_name="timestamp")
590
+
591
+ number = gr.Number(lambda: random.randint(1, 10), every=timer, label="Random Number")
592
+ with gr.Row():
593
+ gr.Button("Start").click(lambda: gr.Timer(active=True), None, timer)
594
+ gr.Button("Stop").click(lambda: gr.Timer(active=False), None, timer)
595
+ gr.Button("Go Fast").click(lambda: 0.2, None, timer)
596
+
597
+ if __name__ == "__main__":
598
+ demo.launch()
599
+ ```
600
+
601
+ ## Variable Outputs
602
+
603
+ ```python
604
+ import gradio as gr
605
+
606
+ max_textboxes = 10
607
+
608
+ def variable_outputs(k):
609
+ k = int(k)
610
+ return [gr.Textbox(visible=True)]*k + [gr.Textbox(visible=False)]*(max_textboxes-k)
611
+
612
+ with gr.Blocks() as demo:
613
+ s = gr.Slider(1, max_textboxes, value=max_textboxes, step=1, label="How many textboxes to show:")
614
+ textboxes = []
615
+ for i in range(max_textboxes):
616
+ t = gr.Textbox(f"Textbox {i}")
617
+ textboxes.append(t)
618
+
619
+ s.change(variable_outputs, s, textboxes)
620
+
621
+ if __name__ == "__main__":
622
+ demo.launch()
623
+ ```
624
+
625
+ ## Video Identity
626
+
627
+ ```python
628
+ import gradio as gr
629
+ from gradio.media import get_video
630
+
631
+ def video_identity(video):
632
+ return video
633
+
634
+ # get_video() returns file paths to sample media included with Gradio
635
+ demo = gr.Interface(video_identity,
636
+ gr.Video(),
637
+ "playable_video",
638
+ examples=[
639
+ get_video("world.mp4")
640
+ ],
641
+ cache_examples=True,
642
+ api_name="predict",)
643
+
644
+ demo.launch()
645
+ ```
.agents/skills/hf-gradio/SKILL.md ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ name: hf-gradio
3
+ description: Use Gradio applications via API. Use when the user asks for to generate a prediction from a Gradio app on Hugging Face spaces or public URL. For example, "Generate an image using black-forest-labs/FLUX.2-dev".
4
+ ---
5
+
6
+ ## hf-gradio CLI Skill
7
+
8
+ The `hf-gradio` CLI `gradio` CLI includes `info` and `predict` commands for interacting with Gradio apps programmatically.
9
+
10
+ ## Step 1 - Verify installation
11
+
12
+ Verify that either `hf-gradio` or `gradio` are installed in the current virtual environment.
13
+
14
+ If the `hf` CLI app is installed. The `hf-gradio` extension can be installed via
15
+
16
+ ```bash
17
+ hf extensions install gradio-app/hf-gradio
18
+ ```
19
+
20
+ ### Step 2 - Use `info` to discover endpoints and payload format
21
+
22
+ ```bash
23
+ gradio info <space_id_or_url>
24
+ ```
25
+
26
+ ```bash
27
+ hf-gradio info <space_id_or_url>
28
+ ```
29
+
30
+ ```bash
31
+ hf gradio info <space_id_or_url>
32
+ ```
33
+
34
+ Returns a JSON payload describing all endpoints, their parameters (with types and defaults), and return values.
35
+
36
+ ```bash
37
+ gradio info gradio/calculator
38
+ # {
39
+ # "/predict": {
40
+ # "parameters": [
41
+ # {"name": "num1", "required": true, "default": null, "type": {"type": "number"}},
42
+ # {"name": "operation", "required": true, "default": null, "type": {"enum": ["add", "subtract", "multiply", "divide"], "type": "string"}},
43
+ # {"name": "num2", "required": true, "default": null, "type": {"type": "number"}}
44
+ # ],
45
+ # "returns": [{"name": "output", "type": {"type": "number"}}],
46
+ # "description": ""
47
+ # }
48
+ # }
49
+ ```
50
+
51
+ File-type parameters show `"type": "filepath"` with instructions to include `"meta": {"_type": "gradio.FileData"}` — this signals the file will be uploaded to the remote server.
52
+
53
+ ## Step 3 - Use `predict` to generate the prediction
54
+
55
+ ```bash
56
+ gradio predict <space_id_or_url> <endpoint> <json_payload>
57
+ ```
58
+
59
+ ```bash
60
+ hf-gradio predict <space_id_or_url> <endpoint> <json_payload>
61
+ ```
62
+
63
+ ```bash
64
+ hf gradio predict <space_id_or_url> <endpoint> <json_payload>
65
+ ```
66
+
67
+ Returns a JSON object with named output keys.
68
+
69
+ ```bash
70
+ # Simple numeric prediction
71
+ gradio predict gradio/calculator /predict '{"num1": 5, "operation": "multiply", "num2": 3}'
72
+ # {"output": 15}
73
+
74
+ # Image generation
75
+ gradio predict black-forest-labs/FLUX.2-dev /infer '{"prompt": "A majestic dragon"}'
76
+ # {"Result": "/tmp/gradio/.../image.webp", "Seed": 1117868604}
77
+
78
+ # File upload (must include meta key)
79
+ gradio predict gradio/image_mod /predict '{"image": {"path": "/path/to/image.png", "meta": {"_type": "gradio.FileData"}}}'
80
+ # {"output": "/tmp/gradio/.../output.png"}
81
+ ```
82
+
83
+ Both commands accept `--token` for accessing private Spaces.
.codex/skills/gradio ADDED
@@ -0,0 +1 @@
 
 
1
+ ../../.agents/skills/gradio
.codex/skills/hf-gradio ADDED
@@ -0,0 +1 @@
 
 
1
+ ../../.agents/skills/hf-gradio
.gitattributes ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ *.png filter=lfs diff=lfs merge=lfs -text
2
+ *.webp filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Virtual environments
10
+ .venv
app/__init__.py ADDED
File without changes
app/__main__.py ADDED
File without changes
app/config.py ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ import os
2
+ TRANSFORMERS_MODEL = os.environ.get("TRANSFORMERS_MODEL", "mradermacher/NuMarkdown-8B-Thinking-i1-GGUF")
app/gradio.py ADDED
File without changes
app/nuextract.py ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ from transformers import AutoModel
2
+ from app.config import TRANSFORMERS_MODEL
3
+
4
+
5
+
6
+ model = AutoModel.from_pretrained(TRANSFORMERS_MODEL, dtype="auto")
7
+
data/samples/Screenshot 2025-06-25 003204.png ADDED

Git LFS Details

  • SHA256: 9688cb8030429bde6f4f8dc096767c77585853b21a4cfa7c02748a6e5311911f
  • Pointer size: 131 Bytes
  • Size of remote file: 239 kB
data/samples/Screenshot 2026-05-27 175656.png ADDED

Git LFS Details

  • SHA256: 896cbe19a11563a31de2eaf4e203ee7cea177483cea7e934b70b90278bf2fb4e
  • Pointer size: 130 Bytes
  • Size of remote file: 79.5 kB
data/samples/Screenshot 2026-06-01 150055.png ADDED

Git LFS Details

  • SHA256: 32b6c3e0a4dd8946c9b2eccc88dfb1acf542ac3a233818decb754bbb4ce54136
  • Pointer size: 132 Bytes
  • Size of remote file: 1.23 MB
data/samples/Screenshot 2026-07-15 163643.png ADDED

Git LFS Details

  • SHA256: 2948fb218c36dd1aa3569ee50b5a2a2fb342205354899fb70aa5c26bc195f363
  • Pointer size: 131 Bytes
  • Size of remote file: 255 kB
data/samples/Screenshot 2026-07-18 171416.png ADDED

Git LFS Details

  • SHA256: 36c814defb08d1cd9933e3071e557a6f9a76e5cb2641fe9915f22df9870040ad
  • Pointer size: 131 Bytes
  • Size of remote file: 352 kB
launch.py ADDED
File without changes
mise.toml ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ [tools]
2
+ powershell = "7"
3
+ python = "3.12"
4
+ uv = "latest"
5
+
6
+ [env]
7
+ UV_LINK_MODE="copy"
notebooks/nuextract-2.0_inference.ipynb ADDED
@@ -0,0 +1,876 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "markdown",
5
+ "id": "cbe8b1f6-ec66-4fbc-a9f8-774acd8a255a",
6
+ "metadata": {},
7
+ "source": [
8
+ "# NuExtract 2.0 Inference\n",
9
+ "\n",
10
+ "In this notebook we will provide examples of how to use the NuExtract 2.0 models for inference.\n",
11
+ "\n",
12
+ "First, let's load a model."
13
+ ]
14
+ },
15
+ {
16
+ "cell_type": "code",
17
+ "execution_count": null,
18
+ "id": "a561e38e-58e5-4f7e-80ee-eabc2d05e49a",
19
+ "metadata": {
20
+ "tags": []
21
+ },
22
+ "outputs": [
23
+ {
24
+ "name": "stderr",
25
+ "output_type": "stream",
26
+ "text": [
27
+ "s:\\Spaces\\Data-Extraction\\NuMarkApp\\.venv\\Lib\\site-packages\\tqdm\\auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
28
+ " from .autonotebook import tqdm as notebook_tqdm\n"
29
+ ]
30
+ }
31
+ ],
32
+ "source": [
33
+ "import torch\n",
34
+ "from transformers import AutoModelForImageTextToText, AutoProcessor\n",
35
+ "model_name = \"numind/NuExtract-2.0-2B\"\n",
36
+ "# model_name = \"numind/NuExtract-2.0-8B\"\n",
37
+ "\n",
38
+ "processor = AutoProcessor.from_pretrained(model_name,\n",
39
+ " trust_remote_code=True,\n",
40
+ " padding_side='left',\n",
41
+ " use_fast=True)\n",
42
+ "model = AutoModelForImageTextToText.from_pretrained(model_name,\n",
43
+ " trust_remote_code=True,\n",
44
+ " dtype=torch.bfloat16,\n",
45
+ " attn_implementation=\"flash_attention_2\",\n",
46
+ " device_map=\"auto\")\n",
47
+ "\n",
48
+ "# we choose greedy sampling here, which works well for most information extraction tasks\n",
49
+ "generation_config = {\n",
50
+ " \"do_sample\": False,\n",
51
+ " \"num_beams\": 1,\n",
52
+ " \"max_new_tokens\": 2048,\n",
53
+ " \"temperature\": None,\n",
54
+ " \"top_p\": None,\n",
55
+ " \"top_k\": None\n",
56
+ "}"
57
+ ]
58
+ },
59
+ {
60
+ "cell_type": "markdown",
61
+ "id": "d739f582-16ad-4e52-b012-429757945a84",
62
+ "metadata": {},
63
+ "source": [
64
+ "## Preparing Model Inputs\n",
65
+ "\n",
66
+ "Before using the model, we also need to make sure our prompts are properly formatted to work with NuExtract. NuExtract expects all input information to come as a single user chat prompt, formatted as follows:\n",
67
+ "\n",
68
+ "```python\n",
69
+ "f\"\"\"\n",
70
+ "# Template:\n",
71
+ "{template}\n",
72
+ "# Context:\n",
73
+ "{text}\n",
74
+ "\"\"\"\n",
75
+ "```\n",
76
+ "and if in-context examples are provided:\n",
77
+ "```python\n",
78
+ "f\"\"\"\n",
79
+ "# Template:\n",
80
+ "{template}\n",
81
+ "# Examples\n",
82
+ "## Input:\n",
83
+ "{input1}\n",
84
+ "## Output:\n",
85
+ "{output1}\n",
86
+ "## Input:\n",
87
+ "{input2}\n",
88
+ "## Output:\n",
89
+ "{output2}\n",
90
+ "# Context:\n",
91
+ "{text}\n",
92
+ "\"\"\"\n",
93
+ "```\n",
94
+ "\n",
95
+ "If you are working with image inputs, you should use image placeholders for `text`, `input1`, etc. Later, we will inject tokens representing the actual image content in the location of these placeholders.\n",
96
+ "\n",
97
+ "The following function can make this formatting more convenient for us (the model can actually do this for us internally, but we will use this function initially to illustrate how things work). "
98
+ ]
99
+ },
100
+ {
101
+ "cell_type": "code",
102
+ "execution_count": null,
103
+ "id": "58b017a4-4802-4afe-b2b4-8f034f572c24",
104
+ "metadata": {
105
+ "tags": []
106
+ },
107
+ "outputs": [],
108
+ "source": [
109
+ "def construct_messages(document, template, examples=None, image_placeholder=\"<|vision_start|><|image_pad|><|vision_end|>\"):\n",
110
+ " \"\"\"\n",
111
+ " Construct the individual NuExtract message texts, prior to chat template formatting.\n",
112
+ " \"\"\"\n",
113
+ " images = []\n",
114
+ " # add few-shot examples if needed\n",
115
+ " if examples is not None and len(examples) > 0:\n",
116
+ " icl = \"# Examples:\\n\"\n",
117
+ " for row in examples:\n",
118
+ " example_input = row['input']\n",
119
+ "\n",
120
+ " if not isinstance(row['input'], str):\n",
121
+ " example_input = image_placeholder\n",
122
+ " images.append(row['input'])\n",
123
+ "\n",
124
+ " icl += f\"## Input:\\n{example_input}\\n## Output:\\n{row['output']}\\n\"\n",
125
+ " else:\n",
126
+ " icl = \"\"\n",
127
+ "\n",
128
+ " # if input document is an image, set text to an image placeholder\n",
129
+ " text = document\n",
130
+ " if not isinstance(document, str):\n",
131
+ " text = image_placeholder\n",
132
+ " images.append(document)\n",
133
+ " text = f\"\"\"# Template:\\n{template}\\n{icl}# Context:\\n{text}\"\"\"\n",
134
+ "\n",
135
+ " messages = [\n",
136
+ " {\n",
137
+ " \"role\": \"system\",\n",
138
+ " \"content\": \"You are NuExtract, an information extraction tool created by NuMind.\"\n",
139
+ " },\n",
140
+ " {\n",
141
+ " \"role\": \"user\",\n",
142
+ " \"content\": [{\"type\": \"text\", \"text\": text}] + images,\n",
143
+ " }\n",
144
+ " ]\n",
145
+ " return messages"
146
+ ]
147
+ },
148
+ {
149
+ "cell_type": "markdown",
150
+ "id": "e918d64e-bc31-4d68-8ef5-2d485b5e5745",
151
+ "metadata": {},
152
+ "source": [
153
+ "## Inference\n",
154
+ "### Basic Example\n",
155
+ "\n",
156
+ "Now we are ready to run the model!\n",
157
+ "\n",
158
+ "Let's start with a basic text-only example, where we want to extract peoples' names from a short text."
159
+ ]
160
+ },
161
+ {
162
+ "cell_type": "code",
163
+ "execution_count": null,
164
+ "id": "b1cad1ec-4753-4064-aca0-c89fec4b5bfd",
165
+ "metadata": {
166
+ "tags": []
167
+ },
168
+ "outputs": [],
169
+ "source": [
170
+ "from qwen_vl_utils import process_vision_info\n",
171
+ "\n",
172
+ "template = \"\"\"{\"names\": [\"verbatim-string\"]}\"\"\"\n",
173
+ "document = \"John went to the restaurant with Mary. James went to the cinema.\"\n",
174
+ "\n",
175
+ "# prepare the user message content\n",
176
+ "messages = construct_messages(document, template)\n",
177
+ "text = processor.apply_chat_template(\n",
178
+ " messages, tokenize=False, add_generation_prompt=True,\n",
179
+ ")\n",
180
+ "\n",
181
+ "image_inputs = process_vision_info(messages)[0]\n",
182
+ "inputs = processor(\n",
183
+ " text=[text],\n",
184
+ " images=image_inputs,\n",
185
+ " padding=True,\n",
186
+ " return_tensors=\"pt\",\n",
187
+ ").to(\"cuda\")"
188
+ ]
189
+ },
190
+ {
191
+ "cell_type": "markdown",
192
+ "id": "6c0e4f92-94dd-4b37-8772-591246f9b71a",
193
+ "metadata": {},
194
+ "source": [
195
+ "Our NuExtract message is now formatted in standard chat template formatting; the tokenized version (`inputs`) will be given directly to the model."
196
+ ]
197
+ },
198
+ {
199
+ "cell_type": "code",
200
+ "execution_count": null,
201
+ "id": "42bd5add-b48a-44e4-9843-2f187e768678",
202
+ "metadata": {
203
+ "tags": []
204
+ },
205
+ "outputs": [
206
+ {
207
+ "name": "stdout",
208
+ "output_type": "stream",
209
+ "text": [
210
+ "<|im_start|>system\n",
211
+ "You are NuExtract, an information extraction tool created by NuMind.<|im_end|>\n",
212
+ "<|im_start|>user\n",
213
+ "# Template:\n",
214
+ "{\"names\": [\"verbatim-string\"]}\n",
215
+ "# Context:\n",
216
+ "John went to the restaurant with Mary. James went to the cinema.<|im_end|>\n",
217
+ "<|im_start|>assistant\n",
218
+ "\n"
219
+ ]
220
+ }
221
+ ],
222
+ "source": [
223
+ "print(text)"
224
+ ]
225
+ },
226
+ {
227
+ "cell_type": "markdown",
228
+ "id": "df0979a7-ea46-4d25-a838-baeaeeca6ee5",
229
+ "metadata": {},
230
+ "source": [
231
+ "The other `image_inputs` are empty in this case because this is a text-only example."
232
+ ]
233
+ },
234
+ {
235
+ "cell_type": "code",
236
+ "execution_count": null,
237
+ "id": "e3493013-313f-4318-a813-d492732bb0c1",
238
+ "metadata": {
239
+ "tags": []
240
+ },
241
+ "outputs": [
242
+ {
243
+ "name": "stdout",
244
+ "output_type": "stream",
245
+ "text": [
246
+ "None\n"
247
+ ]
248
+ }
249
+ ],
250
+ "source": [
251
+ "print(image_inputs)"
252
+ ]
253
+ },
254
+ {
255
+ "cell_type": "markdown",
256
+ "id": "0e300775-48a9-42fb-9c92-3e1ceb530101",
257
+ "metadata": {},
258
+ "source": [
259
+ "Now let's actually run the model."
260
+ ]
261
+ },
262
+ {
263
+ "cell_type": "code",
264
+ "execution_count": null,
265
+ "id": "ed374bf3-1af1-4a3e-a9eb-9e2e7ca113b8",
266
+ "metadata": {
267
+ "tags": []
268
+ },
269
+ "outputs": [
270
+ {
271
+ "name": "stdout",
272
+ "output_type": "stream",
273
+ "text": [
274
+ "['{\"names\": [\"John\", \"Mary\", \"James\"]}']\n"
275
+ ]
276
+ }
277
+ ],
278
+ "source": [
279
+ "# Inference: Generation of the output\n",
280
+ "generated_ids = model.generate(\n",
281
+ " **inputs,\n",
282
+ " **generation_config\n",
283
+ ")\n",
284
+ "generated_ids_trimmed = [\n",
285
+ " out_ids[len(in_ids) :] for in_ids, out_ids in zip(inputs.input_ids, generated_ids)\n",
286
+ "]\n",
287
+ "output_text = processor.batch_decode(\n",
288
+ " generated_ids_trimmed, skip_special_tokens=True, clean_up_tokenization_spaces=False\n",
289
+ ")\n",
290
+ "print(output_text)"
291
+ ]
292
+ },
293
+ {
294
+ "cell_type": "markdown",
295
+ "id": "21d37d33-85ad-421c-a014-523ddbec5a2f",
296
+ "metadata": {},
297
+ "source": [
298
+ "Alternatively, you can directly provide the template and in-context examples to `.apply_chat_template()`, rather then manually preparing the prompt via `construct_messages()`. This is a bit more convenient and so we will use this from now on."
299
+ ]
300
+ },
301
+ {
302
+ "cell_type": "code",
303
+ "execution_count": null,
304
+ "id": "27fecb60-b0eb-46bc-ba2d-ef6a32d04f0c",
305
+ "metadata": {
306
+ "tags": []
307
+ },
308
+ "outputs": [
309
+ {
310
+ "name": "stdout",
311
+ "output_type": "stream",
312
+ "text": [
313
+ "<|im_start|>system\n",
314
+ "You are NuExtract, an information extraction tool created by NuMind.<|im_end|>\n",
315
+ "<|im_start|>user\n",
316
+ "# Template:\n",
317
+ "{\"names\": [\"verbatim-string\"]}\n",
318
+ "# Context:\n",
319
+ "John went to the restaurant with Mary. James went to the cinema.<|im_end|>\n",
320
+ "<|im_start|>assistant\n",
321
+ "\n",
322
+ "['{\"names\": [\"John\", \"Mary\", \"James\"]}']\n"
323
+ ]
324
+ }
325
+ ],
326
+ "source": [
327
+ "template = \"\"\"{\"names\": [\"verbatim-string\"]}\"\"\"\n",
328
+ "document = \"John went to the restaurant with Mary. James went to the cinema.\"\n",
329
+ "\n",
330
+ "# prepare the user message content\n",
331
+ "messages = [{\"role\": \"user\", \"content\": document}]\n",
332
+ "text = processor.tokenizer.apply_chat_template(\n",
333
+ " messages,\n",
334
+ " template=template, # template is specified here\n",
335
+ " tokenize=False,\n",
336
+ " add_generation_prompt=True,\n",
337
+ ")\n",
338
+ "\n",
339
+ "print(text)\n",
340
+ "\n",
341
+ "image_inputs = process_vision_info(messages)[0]\n",
342
+ "inputs = processor(\n",
343
+ " text=[text],\n",
344
+ " images=image_inputs,\n",
345
+ " padding=True,\n",
346
+ " return_tensors=\"pt\",\n",
347
+ ").to(\"cuda\")\n",
348
+ "\n",
349
+ "# Inference: Generation of the output\n",
350
+ "generated_ids = model.generate(\n",
351
+ " **inputs,\n",
352
+ " **generation_config\n",
353
+ ")\n",
354
+ "generated_ids_trimmed = [\n",
355
+ " out_ids[len(in_ids) :] for in_ids, out_ids in zip(inputs.input_ids, generated_ids)\n",
356
+ "]\n",
357
+ "output_text = processor.batch_decode(\n",
358
+ " generated_ids_trimmed, skip_special_tokens=True, clean_up_tokenization_spaces=False\n",
359
+ ")\n",
360
+ "\n",
361
+ "print(output_text)"
362
+ ]
363
+ },
364
+ {
365
+ "cell_type": "markdown",
366
+ "id": "c18002f2-447a-45c2-90f6-0ff5a63c5dc2",
367
+ "metadata": {},
368
+ "source": [
369
+ "### In-Context Examples\n",
370
+ "\n",
371
+ "Sometimes the model might not perform as well as we want because our task is challenging or involves some degree of ambiguity. Alternatively, we may want the model to follow some specific formatting, or just give it a bit more help. In cases like this it can be valuable to provide \"in-context examples\" to help NuExtract better understand the task.\n",
372
+ "\n",
373
+ "To do so, we can provide a list `examples` to `apply_chat_template()` (or `construct_messages()`) which contains dictionaries of input/output pairs. In the example below, we show to the model that we want the extracted names to be in captial letters with `-` on either side (for the sake of illustration)."
374
+ ]
375
+ },
376
+ {
377
+ "cell_type": "code",
378
+ "execution_count": null,
379
+ "id": "146baa04-2206-4eca-86bd-66f94924e293",
380
+ "metadata": {
381
+ "tags": []
382
+ },
383
+ "outputs": [],
384
+ "source": [
385
+ "template = \"\"\"{\"names\": [\"verbatim-string\"]}\"\"\"\n",
386
+ "document = \"John went to the restaurant with Mary. James went to the cinema.\"\n",
387
+ "examples = [\n",
388
+ " {\n",
389
+ " \"input\": \"Stephen is the manager at Susan's store.\",\n",
390
+ " \"output\": \"\"\"{\"names\": [\"-STEPHEN-\", \"-SUSAN-\"]}\"\"\"\n",
391
+ " }\n",
392
+ "]\n",
393
+ "\n",
394
+ "messages = [{\"role\": \"user\", \"content\": document}]\n",
395
+ "text = processor.tokenizer.apply_chat_template(\n",
396
+ " messages,\n",
397
+ " template=template,\n",
398
+ " examples=examples, # examples provided here\n",
399
+ " tokenize=False,\n",
400
+ " add_generation_prompt=True,\n",
401
+ ")\n",
402
+ "\n",
403
+ "image_inputs = process_vision_info(messages)[0]\n",
404
+ "inputs = processor(\n",
405
+ " text=[text],\n",
406
+ " images=image_inputs,\n",
407
+ " padding=True,\n",
408
+ " return_tensors=\"pt\",\n",
409
+ ").to(\"cuda\")"
410
+ ]
411
+ },
412
+ {
413
+ "cell_type": "markdown",
414
+ "id": "1d2561cc-a662-4898-b226-41d12394fdb4",
415
+ "metadata": {},
416
+ "source": [
417
+ "We can see below that the in-context example has now been included in the model prompt, specifically between the template and context components."
418
+ ]
419
+ },
420
+ {
421
+ "cell_type": "code",
422
+ "execution_count": null,
423
+ "id": "79b10e44-5bb4-40eb-af74-e013897948ea",
424
+ "metadata": {
425
+ "tags": []
426
+ },
427
+ "outputs": [
428
+ {
429
+ "name": "stdout",
430
+ "output_type": "stream",
431
+ "text": [
432
+ "<|im_start|>system\n",
433
+ "You are NuExtract, an information extraction tool created by NuMind.<|im_end|>\n",
434
+ "<|im_start|>user\n",
435
+ "# Template:\n",
436
+ "{\"names\": [\"verbatim-string\"]}\n",
437
+ "# Examples:\n",
438
+ "## Input:\n",
439
+ "Stephen is the manager at Susan's store.\n",
440
+ "## Output:\n",
441
+ "{\"names\": [\"-STEPHEN-\", \"-SUSAN-\"]}\n",
442
+ "# Context:\n",
443
+ "John went to the restaurant with Mary. James went to the cinema.<|im_end|>\n",
444
+ "<|im_start|>assistant\n",
445
+ "\n"
446
+ ]
447
+ }
448
+ ],
449
+ "source": [
450
+ "print(text)"
451
+ ]
452
+ },
453
+ {
454
+ "cell_type": "code",
455
+ "execution_count": null,
456
+ "id": "3f1364bf-2588-4935-a478-7a85b87866fc",
457
+ "metadata": {
458
+ "tags": []
459
+ },
460
+ "outputs": [
461
+ {
462
+ "name": "stdout",
463
+ "output_type": "stream",
464
+ "text": [
465
+ "['{\"names\": [\"-JOHN-\", \"-MARY-\", \"-JAMES-\"]}']\n"
466
+ ]
467
+ }
468
+ ],
469
+ "source": [
470
+ "# Inference: Generation of the output\n",
471
+ "generated_ids = model.generate(\n",
472
+ " **inputs,\n",
473
+ " **generation_config\n",
474
+ ")\n",
475
+ "generated_ids_trimmed = [\n",
476
+ " out_ids[len(in_ids) :] for in_ids, out_ids in zip(inputs.input_ids, generated_ids)\n",
477
+ "]\n",
478
+ "output_text = processor.batch_decode(\n",
479
+ " generated_ids_trimmed, skip_special_tokens=True, clean_up_tokenization_spaces=False\n",
480
+ ")\n",
481
+ "print(output_text)"
482
+ ]
483
+ },
484
+ {
485
+ "cell_type": "markdown",
486
+ "id": "bc0c3ecf-80ee-40b7-b240-12b5a9e10042",
487
+ "metadata": {},
488
+ "source": [
489
+ "To get even better performance, add multiple in-context examples to your input."
490
+ ]
491
+ },
492
+ {
493
+ "cell_type": "markdown",
494
+ "id": "99faf117-18b7-4b5e-8cd0-b6f8941db581",
495
+ "metadata": {
496
+ "tags": []
497
+ },
498
+ "source": [
499
+ "### Image Inputs\n",
500
+ "\n",
501
+ "If we want to give image inputs to NuExtract, instead of text, we simply provide a dictionary specifying the desired image file as the message content, instead of a string. E.g. `{\"type\": \"image\", \"image\": \"file://image.jpg\"}`.\n",
502
+ "\n",
503
+ "You can also specify an image URL (e.g. `{\"type\": \"image\", \"image\": \"http://path/to/your/image.jpg\"}`) or base64 encoding (e.g. `{\"type\": \"image\", \"image\": \"data:image;base64,/9j/...\"}`).\n",
504
+ "\n",
505
+ "First, we will need a modified version of `process_vision_info()` that handles image-based in-context examples as well as primary inputs."
506
+ ]
507
+ },
508
+ {
509
+ "cell_type": "code",
510
+ "execution_count": null,
511
+ "id": "1aaf1912-4f50-483f-a9a0-f5f57caf66e0",
512
+ "metadata": {
513
+ "tags": []
514
+ },
515
+ "outputs": [],
516
+ "source": [
517
+ "def process_all_vision_info(messages, examples=None):\n",
518
+ " \"\"\"\n",
519
+ " Process vision information from both messages and in-context examples, supporting batch processing.\n",
520
+ "\n",
521
+ " Args:\n",
522
+ " messages: List of message dictionaries (single input) OR list of message lists (batch input)\n",
523
+ " examples: Optional list of example dictionaries (single input) OR list of example lists (batch)\n",
524
+ "\n",
525
+ " Returns:\n",
526
+ " A flat list of all images in the correct order:\n",
527
+ " - For single input: example images followed by message images\n",
528
+ " - For batch input: interleaved as (item1 examples, item1 input, item2 examples, item2 input, etc.)\n",
529
+ " - Returns None if no images were found\n",
530
+ " \"\"\"\n",
531
+ " from qwen_vl_utils import process_vision_info, fetch_image\n",
532
+ "\n",
533
+ " # Helper function to extract images from examples\n",
534
+ " def extract_example_images(example_item):\n",
535
+ " if not example_item:\n",
536
+ " return []\n",
537
+ "\n",
538
+ " # Handle both list of examples and single example\n",
539
+ " examples_to_process = example_item if isinstance(example_item, list) else [example_item]\n",
540
+ " images = []\n",
541
+ "\n",
542
+ " for example in examples_to_process:\n",
543
+ " if isinstance(example.get('input'), dict) and example['input'].get('type') == 'image':\n",
544
+ " images.append(fetch_image(example['input']))\n",
545
+ "\n",
546
+ " return images\n",
547
+ "\n",
548
+ " # Normalize inputs to always be batched format\n",
549
+ " is_batch = messages and isinstance(messages[0], list)\n",
550
+ " messages_batch = messages if is_batch else [messages]\n",
551
+ " is_batch_examples = examples and isinstance(examples, list) and (isinstance(examples[0], list) or examples[0] is None)\n",
552
+ " examples_batch = examples if is_batch_examples else ([examples] if examples is not None else None)\n",
553
+ "\n",
554
+ " # Ensure examples batch matches messages batch if provided\n",
555
+ " if examples and len(examples_batch) != len(messages_batch):\n",
556
+ " if not is_batch and len(examples_batch) == 1:\n",
557
+ " # Single example set for a single input is fine\n",
558
+ " pass\n",
559
+ " else:\n",
560
+ " raise ValueError(\"Examples batch length must match messages batch length\")\n",
561
+ "\n",
562
+ " # Process all inputs, maintaining correct order\n",
563
+ " all_images = []\n",
564
+ " for i, message_group in enumerate(messages_batch):\n",
565
+ " # Get example images for this input\n",
566
+ " if examples and i < len(examples_batch):\n",
567
+ " input_example_images = extract_example_images(examples_batch[i])\n",
568
+ " all_images.extend(input_example_images)\n",
569
+ "\n",
570
+ " # Get message images for this input\n",
571
+ " input_message_images = process_vision_info(message_group)[0] or []\n",
572
+ " all_images.extend(input_message_images)\n",
573
+ "\n",
574
+ " return all_images if all_images else None\n"
575
+ ]
576
+ },
577
+ {
578
+ "cell_type": "markdown",
579
+ "id": "8db9904c-451d-4ff4-b776-5a557abc1437",
580
+ "metadata": {},
581
+ "source": [
582
+ "In the example below, we give an image of a receipt from Trader Joe's (`data/1.jpg`) and ask the model to extract the name of the store. We also provide an ICL example of a receipt from Walmart (`data/0.jpg`)."
583
+ ]
584
+ },
585
+ {
586
+ "cell_type": "code",
587
+ "execution_count": null,
588
+ "id": "fd11e855-594b-4fd7-af01-7695a65b75a9",
589
+ "metadata": {
590
+ "tags": []
591
+ },
592
+ "outputs": [],
593
+ "source": [
594
+ "from pathlib import Path\n",
595
+ "\n",
596
+ "project_root = Path.cwd().parent if Path.cwd().name == \"notebooks\" else Path.cwd()\n",
597
+ "samples_dir = project_root / \"data\" / \"samples\"\n",
598
+ "sample_images = sorted(\n",
599
+ " path for path in samples_dir.iterdir()\n",
600
+ " if path.suffix.lower() in {\".jpg\", \".jpeg\", \".png\", \".webp\"}\n",
601
+ ")\n",
602
+ "if not sample_images:\n",
603
+ " raise FileNotFoundError(f\"No supported images found in {samples_dir}\")\n",
604
+ "\n",
605
+ "# Pick a different entry from sample_images to use another file.\n",
606
+ "selected_image = sample_images[0]\n",
607
+ "input_image_uri = selected_image.resolve().as_uri()\n",
608
+ "print(f\"Using input image: {selected_image}\")\n",
609
+ "\n",
610
+ "template = \"\"\"{\"store\": \"verbatim-string\"}\"\"\"\n",
611
+ "document = {\"type\": \"image\", \"image\": input_image_uri}\n",
612
+ "examples = [\n",
613
+ " {\n",
614
+ " \"input\": {\"type\": \"image\", \"image\": input_image_uri},\n",
615
+ " \"output\": \"\"\"{\"store\": \"WALMART\"}\"\"\"\n",
616
+ " }\n",
617
+ "]\n",
618
+ "\n",
619
+ "messages = [{\"role\": \"user\", \"content\": [document]}]\n",
620
+ "text = processor.tokenizer.apply_chat_template(\n",
621
+ " messages,\n",
622
+ " template=template,\n",
623
+ " examples=examples,\n",
624
+ " tokenize=False,\n",
625
+ " add_generation_prompt=True,\n",
626
+ ")\n",
627
+ "\n",
628
+ "image_inputs = process_all_vision_info(messages, examples)\n",
629
+ "inputs = processor(\n",
630
+ " text=[text],\n",
631
+ " images=image_inputs,\n",
632
+ " padding=True,\n",
633
+ " return_tensors=\"pt\",\n",
634
+ ").to(\"cuda\")"
635
+ ]
636
+ },
637
+ {
638
+ "cell_type": "markdown",
639
+ "id": "f14a8e8a-a091-410a-a964-9d275b936e75",
640
+ "metadata": {},
641
+ "source": [
642
+ "Just like in the text-only case above, our in-context example has been included in the prompt before the main context."
643
+ ]
644
+ },
645
+ {
646
+ "cell_type": "code",
647
+ "execution_count": null,
648
+ "id": "ba00c8d6-56e2-43fc-a646-61f693c6b51a",
649
+ "metadata": {
650
+ "tags": []
651
+ },
652
+ "outputs": [
653
+ {
654
+ "name": "stdout",
655
+ "output_type": "stream",
656
+ "text": [
657
+ "<|im_start|>system\n",
658
+ "You are NuExtract, an information extraction tool created by NuMind.<|im_end|>\n",
659
+ "<|im_start|>user\n",
660
+ "# Template:\n",
661
+ "{\"store\": \"verbatim-string\"}\n",
662
+ "# Examples:\n",
663
+ "## Input:\n",
664
+ "<|vision_start|><|image_pad|><|vision_end|>\n",
665
+ "## Output:\n",
666
+ "{\"store\": \"WALMART\"}\n",
667
+ "# Context:\n",
668
+ "<|vision_start|><|image_pad|><|vision_end|><|im_end|>\n",
669
+ "<|im_start|>assistant\n",
670
+ "\n"
671
+ ]
672
+ }
673
+ ],
674
+ "source": [
675
+ "print(text)"
676
+ ]
677
+ },
678
+ {
679
+ "cell_type": "markdown",
680
+ "id": "4820e6da-544b-431a-86fb-a6f9f0eb6bdd",
681
+ "metadata": {},
682
+ "source": [
683
+ "Now if we look at `image_inputs` we will see that it contains actual images. When we pass this along with `text` to `processor()` it automatically encodes the images and injects a tokenized representation into the image placeholders within `text`."
684
+ ]
685
+ },
686
+ {
687
+ "cell_type": "code",
688
+ "execution_count": null,
689
+ "id": "1aa9fba6-eb58-4401-abc2-79b7fcd205c0",
690
+ "metadata": {
691
+ "tags": []
692
+ },
693
+ "outputs": [
694
+ {
695
+ "name": "stdout",
696
+ "output_type": "stream",
697
+ "text": [
698
+ "[<PIL.Image.Image image mode=RGB size=588x896 at 0x7FDD295E5600>, <PIL.Image.Image image mode=RGB size=476x980 at 0x7FDD295E4460>]\n"
699
+ ]
700
+ }
701
+ ],
702
+ "source": [
703
+ "print(image_inputs)"
704
+ ]
705
+ },
706
+ {
707
+ "cell_type": "code",
708
+ "execution_count": null,
709
+ "id": "fbed800c-519b-4ee1-90d9-7c27e383d904",
710
+ "metadata": {
711
+ "tags": []
712
+ },
713
+ "outputs": [
714
+ {
715
+ "name": "stdout",
716
+ "output_type": "stream",
717
+ "text": [
718
+ "['{\"store\": \"TRADER JOE\\'S\"}']\n"
719
+ ]
720
+ }
721
+ ],
722
+ "source": [
723
+ "# Inference: Generation of the output\n",
724
+ "generated_ids = model.generate(\n",
725
+ " **inputs,\n",
726
+ " **generation_config\n",
727
+ ")\n",
728
+ "generated_ids_trimmed = [\n",
729
+ " out_ids[len(in_ids) :] for in_ids, out_ids in zip(inputs.input_ids, generated_ids)\n",
730
+ "]\n",
731
+ "output_text = processor.batch_decode(\n",
732
+ " generated_ids_trimmed, skip_special_tokens=True, clean_up_tokenization_spaces=False\n",
733
+ ")\n",
734
+ "print(output_text)"
735
+ ]
736
+ },
737
+ {
738
+ "cell_type": "markdown",
739
+ "id": "c6beb77f-2de5-4846-b244-2cda27f5f553",
740
+ "metadata": {
741
+ "tags": []
742
+ },
743
+ "source": [
744
+ "### Batched Inference\n",
745
+ "\n",
746
+ "Finally, we can run batched inference over a list of input examples, regardless of whether they contain text, images, and/or ICL examples."
747
+ ]
748
+ },
749
+ {
750
+ "cell_type": "code",
751
+ "execution_count": null,
752
+ "id": "947b8090-236c-4821-b295-5a8fb5247943",
753
+ "metadata": {
754
+ "tags": []
755
+ },
756
+ "outputs": [
757
+ {
758
+ "name": "stdout",
759
+ "output_type": "stream",
760
+ "text": [
761
+ "{\"store_name\": \"WAL-MART\"}\n",
762
+ "{\"store_name\": \"Walmart\"}\n",
763
+ "{\"names\": [\"John\", \"Mary\", \"James\"]}\n",
764
+ "{\"names\": [\"-JOHN-\", \"-MARY-\", \"-JAMES-\"]}\n"
765
+ ]
766
+ }
767
+ ],
768
+ "source": [
769
+ "inputs = [\n",
770
+ " # image input with no ICL examples\n",
771
+ " {\n",
772
+ " \"document\": {\"type\": \"image\", \"image\": input_image_uri},\n",
773
+ " \"template\": \"\"\"{\"store_name\": \"verbatim-string\"}\"\"\",\n",
774
+ " },\n",
775
+ " # image input with 1 ICL example\n",
776
+ " {\n",
777
+ " \"document\": {\"type\": \"image\", \"image\": input_image_uri},\n",
778
+ " \"template\": \"\"\"{\"store_name\": \"verbatim-string\"}\"\"\",\n",
779
+ " \"examples\": [\n",
780
+ " {\n",
781
+ " \"input\": {\"type\": \"image\", \"image\": input_image_uri},\n",
782
+ " \"output\": \"\"\"{\"store_name\": \"Trader Joe's\"}\"\"\",\n",
783
+ " }\n",
784
+ " ],\n",
785
+ " },\n",
786
+ " # text input with no ICL examples\n",
787
+ " {\n",
788
+ " \"document\": {\"type\": \"text\", \"text\": \"John went to the restaurant with Mary. James went to the cinema.\"},\n",
789
+ " \"template\": \"\"\"{\"names\": [\"verbatim-string\"]}\"\"\",\n",
790
+ " },\n",
791
+ " # text input with ICL example\n",
792
+ " {\n",
793
+ " \"document\": {\"type\": \"text\", \"text\": \"John went to the restaurant with Mary. James went to the cinema.\"},\n",
794
+ " \"template\": \"\"\"{\"names\": [\"verbatim-string\"]}\"\"\",\n",
795
+ " \"examples\": [\n",
796
+ " {\n",
797
+ " \"input\": \"Stephen is the manager at Susan's store.\",\n",
798
+ " \"output\": \"\"\"{\"names\": [\"-STEPHEN-\", \"-SUSAN-\"]}\"\"\"\n",
799
+ " }\n",
800
+ " ],\n",
801
+ " },\n",
802
+ "]\n",
803
+ "\n",
804
+ "# messages should be a list of lists for batch processing\n",
805
+ "messages = [[{\"role\": \"user\", \"content\": [x['document']]}] for x in inputs]\n",
806
+ "\n",
807
+ "# apply chat template to each example individually\n",
808
+ "texts = [\n",
809
+ " processor.tokenizer.apply_chat_template(\n",
810
+ " messages[i], # Now this is a list containing one message\n",
811
+ " template=x['template'],\n",
812
+ " examples=x.get('examples', None),\n",
813
+ " tokenize=False,\n",
814
+ " add_generation_prompt=True)\n",
815
+ " for i, x in enumerate(inputs)\n",
816
+ "]\n",
817
+ "\n",
818
+ "image_inputs = process_all_vision_info(messages, [x.get('examples') for x in inputs])\n",
819
+ "inputs = processor(\n",
820
+ " text=texts,\n",
821
+ " images=image_inputs,\n",
822
+ " padding=True,\n",
823
+ " return_tensors=\"pt\",\n",
824
+ ").to(\"cuda\")\n",
825
+ "\n",
826
+ "# Batch Inference\n",
827
+ "generated_ids = model.generate(**inputs, **generation_config)\n",
828
+ "generated_ids_trimmed = [\n",
829
+ " out_ids[len(in_ids) :] for in_ids, out_ids in zip(inputs.input_ids, generated_ids)\n",
830
+ "]\n",
831
+ "output_texts = processor.batch_decode(\n",
832
+ " generated_ids_trimmed, skip_special_tokens=True, clean_up_tokenization_spaces=False\n",
833
+ ")\n",
834
+ "for y in output_texts:\n",
835
+ " print(y)"
836
+ ]
837
+ },
838
+ {
839
+ "cell_type": "code",
840
+ "execution_count": null,
841
+ "id": "cf456dcc-52ce-495d-872f-a641c6f40f7d",
842
+ "metadata": {
843
+ "tags": []
844
+ },
845
+ "outputs": [],
846
+ "source": []
847
+ }
848
+ ],
849
+ "metadata": {
850
+ "environment": {
851
+ "kernel": "conda-base-py",
852
+ "name": "workbench-notebooks.m127",
853
+ "type": "gcloud",
854
+ "uri": "us-docker.pkg.dev/deeplearning-platform-release/gcr.io/workbench-notebooks:m127"
855
+ },
856
+ "kernelspec": {
857
+ "display_name": ".venv",
858
+ "language": "python",
859
+ "name": "python3"
860
+ },
861
+ "language_info": {
862
+ "codemirror_mode": {
863
+ "name": "ipython",
864
+ "version": 3
865
+ },
866
+ "file_extension": ".py",
867
+ "mimetype": "text/x-python",
868
+ "name": "python",
869
+ "nbconvert_exporter": "python",
870
+ "pygments_lexer": "ipython3",
871
+ "version": "3.12.10"
872
+ }
873
+ },
874
+ "nbformat": 4,
875
+ "nbformat_minor": 5
876
+ }
notebooks/nuextract-2.0_sft.ipynb ADDED
@@ -0,0 +1,619 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "markdown",
5
+ "id": "313eccc5-d4c7-4ee9-8082-2a1e3670d26d",
6
+ "metadata": {},
7
+ "source": [
8
+ "# NuExtract 2.0 Supervised Fine-tuning (SFT)\n",
9
+ "\n",
10
+ "This notebook will show a basic example of how to perform supervised fine-tuning (SFT) on top of the base NuExtract 2.0 models, with your own data.\n",
11
+ "\n",
12
+ "## Prepare Model\n",
13
+ "First, load the model you want to fine-tune, along with the processor."
14
+ ]
15
+ },
16
+ {
17
+ "cell_type": "code",
18
+ "execution_count": 1,
19
+ "id": "cd9abd77-b0d6-4fac-9964-05da8e23f1de",
20
+ "metadata": {
21
+ "tags": []
22
+ },
23
+ "outputs": [],
24
+ "source": [
25
+ "import torch\n",
26
+ "from transformers import AutoProcessor, AutoModelForVision2Seq\n",
27
+ "from qwen_vl_utils import process_vision_info\n",
28
+ "\n",
29
+ "model_name = \"numind/NuExtract-2.0-2B\"\n",
30
+ "# model_name = \"numind/NuExtract-2.0-8B\"\n",
31
+ "\n",
32
+ "model = AutoModelForVision2Seq.from_pretrained(model_name, \n",
33
+ " trust_remote_code=True, \n",
34
+ " torch_dtype=torch.bfloat16,\n",
35
+ " attn_implementation=\"flash_attention_2\",\n",
36
+ " device_map=\"auto\",\n",
37
+ " use_cache=False, # for training\n",
38
+ " )\n",
39
+ "\n",
40
+ "processor = AutoProcessor.from_pretrained(model_name, \n",
41
+ " trust_remote_code=True, \n",
42
+ " padding_side='right', # make sure to set padding to right for training\n",
43
+ " use_fast=True,\n",
44
+ " )\n",
45
+ "processor.eos_token = processor.tokenizer.eos_token\n",
46
+ "processor.eos_token_id = processor.tokenizer.eos_token_id"
47
+ ]
48
+ },
49
+ {
50
+ "cell_type": "markdown",
51
+ "id": "861218a0-9323-495a-b82e-d9fc4bde5670",
52
+ "metadata": {},
53
+ "source": [
54
+ "## Prepare Data\n",
55
+ "\n",
56
+ "The `construct_messages()` function below will help us to format the messages to be fed into the model."
57
+ ]
58
+ },
59
+ {
60
+ "cell_type": "code",
61
+ "execution_count": 2,
62
+ "id": "05ad2f4c-116b-48ec-9a6b-e70ec0a3f996",
63
+ "metadata": {
64
+ "tags": []
65
+ },
66
+ "outputs": [],
67
+ "source": [
68
+ "def construct_messages(document, template, label=None, examples=None, image_placeholder=\"<|vision_start|><|image_pad|><|vision_end|>\"):\n",
69
+ " \"\"\"\n",
70
+ " Construct the individual NuExtract message texts, prior to chat template formatting.\n",
71
+ " \"\"\"\n",
72
+ " images = []\n",
73
+ " \n",
74
+ " # add few-shot examples if needed\n",
75
+ " icl = \"\"\n",
76
+ " if examples is not None and len(examples) > 0:\n",
77
+ " icl = \"# Examples:\\n\"\n",
78
+ " for row in examples:\n",
79
+ " example_input = row['input']\n",
80
+ " \n",
81
+ " if not isinstance(row['input'], str):\n",
82
+ " example_input = image_placeholder\n",
83
+ " images.append(row['input'])\n",
84
+ " \n",
85
+ " icl += f\"## Input:\\n{example_input}\\n## Output:\\n{row['output']}\\n\"\n",
86
+ " \n",
87
+ " # if input document is an image, set text to an image placeholder\n",
88
+ " text = document\n",
89
+ " if not isinstance(document, str):\n",
90
+ " text = image_placeholder\n",
91
+ " images.append(document)\n",
92
+ " text = f\"\"\"# Template:\\n{template}\\n{icl}# Context:\\n{text}\"\"\"\n",
93
+ " \n",
94
+ " messages = [\n",
95
+ " {\n",
96
+ " \"role\": \"user\",\n",
97
+ " \"content\": [{\"type\": \"text\", \"text\": text}] + images,\n",
98
+ " }\n",
99
+ " ]\n",
100
+ " if label is not None:\n",
101
+ " messages.append({\n",
102
+ " \"role\": \"assistant\",\n",
103
+ " \"content\": [{\"type\": \"text\", \"text\": label}],\n",
104
+ " \n",
105
+ " })\n",
106
+ " return messages"
107
+ ]
108
+ },
109
+ {
110
+ "cell_type": "markdown",
111
+ "id": "ee16acd5-c327-4a6b-b2c9-448a766fa3c0",
112
+ "metadata": {},
113
+ "source": [
114
+ "For illustration purposes, we will use a small dataset of manually created examples. You should prepare your own data in a similar way before fine-tuning your own model.\n",
115
+ "\n",
116
+ "In the custom data below, we will only provide examples that return strings in full lowercase characters (unless ICL examples suggest otherwise). If we fine-tune on this, we would ideally alter the model to favour returning strings in lowercase by default.\n",
117
+ "\n",
118
+ "*Note: training on a very small dataset like this is for illustration purposes only and will almost always result in a poorly performing model in real use-cases.*"
119
+ ]
120
+ },
121
+ {
122
+ "cell_type": "code",
123
+ "execution_count": 3,
124
+ "id": "4443f697-b11e-4818-b679-b0de2d8eb055",
125
+ "metadata": {
126
+ "tags": []
127
+ },
128
+ "outputs": [],
129
+ "source": [
130
+ "inputs = [\n",
131
+ " # image input with no ICL examples\n",
132
+ " {\n",
133
+ " \"document\": {\"type\": \"image\", \"image\": \"file://data/0.jpg\"},\n",
134
+ " \"template\": \"\"\"{\"store_name\": \"verbatim-string\"}\"\"\",\n",
135
+ " \"label\": \"\"\"{\"store_name\": \"walmart\"}\"\"\", # lowercase result\n",
136
+ " },\n",
137
+ " # image input with 1 ICL example\n",
138
+ " {\n",
139
+ " \"document\": {\"type\": \"image\", \"image\": \"file://data/1.jpg\"},\n",
140
+ " \"template\": \"\"\"{\"store_name\": \"verbatim-string\"}\"\"\",\n",
141
+ " \"examples\": [\n",
142
+ " {\n",
143
+ " \"input\": {\"type\": \"image\", \"image\": \"file://data/0.jpg\"},\n",
144
+ " \"output\": \"\"\"{\"store_name\": \"Walmart\"}\"\"\",\n",
145
+ " }\n",
146
+ " ],\n",
147
+ " \"label\": \"\"\"{\"store_name\": \"Trader Joe's\"}\"\"\",\n",
148
+ " },\n",
149
+ " # text input with no ICL examples\n",
150
+ " {\n",
151
+ " \"document\": \"John went to the restaurant with Mary. James went to the cinema.\",\n",
152
+ " \"template\": \"\"\"{\"names\": [\"verbatim-string\"]}\"\"\",\n",
153
+ " \"label\": \"\"\"{\"names\": [\"john\", \"mary\", \"james\"]}\"\"\", # lowercase result\n",
154
+ " },\n",
155
+ " # text input with ICL example\n",
156
+ " {\n",
157
+ " \"document\": \"John went to the restaurant with Mary. James went to the cinema.\",\n",
158
+ " \"template\": \"\"\"{\"names\": [\"verbatim-string\"]}\"\"\",\n",
159
+ " \"examples\": [\n",
160
+ " {\n",
161
+ " \"input\": \"Stephen is the manager at Susan's store.\",\n",
162
+ " \"output\": \"\"\"{\"names\": [\"STEPHEN\", \"SUSAN\"]}\"\"\"\n",
163
+ " }\n",
164
+ " ],\n",
165
+ " \"label\": \"\"\"{\"names\": [\"JOHN\", \"MARY\", \"JAMES\"]}\"\"\",\n",
166
+ " },\n",
167
+ "] * 2 # double examples to have dataset of size 8\n",
168
+ "\n",
169
+ "messages = [\n",
170
+ " construct_messages(\n",
171
+ " x[\"document\"], \n",
172
+ " x[\"template\"], \n",
173
+ " x[\"label\"],\n",
174
+ " x[\"examples\"] if \"examples\" in x else None\n",
175
+ " ) for x in inputs\n",
176
+ "]"
177
+ ]
178
+ },
179
+ {
180
+ "cell_type": "code",
181
+ "execution_count": 4,
182
+ "id": "e4428274-62b1-4fe7-9296-e702b1d9aba5",
183
+ "metadata": {
184
+ "tags": []
185
+ },
186
+ "outputs": [
187
+ {
188
+ "data": {
189
+ "text/plain": [
190
+ "[{'role': 'user',\n",
191
+ " 'content': [{'type': 'text',\n",
192
+ " 'text': '# Template:\\n{\"store_name\": \"verbatim-string\"}\\n# Context:\\n<|vision_start|><|image_pad|><|vision_end|>'},\n",
193
+ " {'type': 'image', 'image': 'file://data/0.jpg'}]},\n",
194
+ " {'role': 'assistant',\n",
195
+ " 'content': [{'type': 'text', 'text': '{\"store_name\": \"walmart\"}'}]}]"
196
+ ]
197
+ },
198
+ "execution_count": 4,
199
+ "metadata": {},
200
+ "output_type": "execute_result"
201
+ }
202
+ ],
203
+ "source": [
204
+ "messages[0]"
205
+ ]
206
+ },
207
+ {
208
+ "cell_type": "markdown",
209
+ "id": "751f3ec5-9e3e-4951-b21a-53b8b03209dc",
210
+ "metadata": {},
211
+ "source": [
212
+ "Let's also add a couple of validation examples that we can use to confirm our model is generalizing to unseen data."
213
+ ]
214
+ },
215
+ {
216
+ "cell_type": "code",
217
+ "execution_count": 5,
218
+ "id": "d619853f-d76c-4d28-aebb-0d26b99248fa",
219
+ "metadata": {
220
+ "tags": []
221
+ },
222
+ "outputs": [],
223
+ "source": [
224
+ "val_inputs = [\n",
225
+ " {\n",
226
+ " \"document\": \"Jack went to the hill with Jill. Rupert went to the diner.\",\n",
227
+ " \"template\": \"\"\"{\"names\": [\"verbatim-string\"]}\"\"\",\n",
228
+ " \"label\": \"\"\"{\"names\": [\"jack\", \"jill\", \"rupert\"]}\"\"\", # lowercase result\n",
229
+ " },\n",
230
+ " {\n",
231
+ " \"document\": \"My dog Clifford likes to play fetch with Emily and Peter.\",\n",
232
+ " \"template\": \"\"\"{\"names\": [\"verbatim-string\"]}\"\"\",\n",
233
+ " \"label\": \"\"\"{\"names\": [\"clifford\", \"emily\", \"peter\"]}\"\"\", # lowercase result\n",
234
+ " },\n",
235
+ "]\n",
236
+ "\n",
237
+ "val_messages = [\n",
238
+ " construct_messages(\n",
239
+ " x[\"document\"], \n",
240
+ " x[\"template\"], \n",
241
+ " x[\"label\"],\n",
242
+ " x[\"examples\"] if \"examples\" in x else None\n",
243
+ " ) for x in val_inputs\n",
244
+ "]"
245
+ ]
246
+ },
247
+ {
248
+ "cell_type": "markdown",
249
+ "id": "8c129755-5a99-43e6-98d4-02fa8e1e16d1",
250
+ "metadata": {},
251
+ "source": [
252
+ "The data is now structued in message format that the processor will be able to reformat via the chat template before tokenization. We will do that on the fly during training via the collate function below."
253
+ ]
254
+ },
255
+ {
256
+ "cell_type": "code",
257
+ "execution_count": 6,
258
+ "id": "dfac13eb-8ede-4939-b40b-a515c0d73110",
259
+ "metadata": {
260
+ "tags": []
261
+ },
262
+ "outputs": [],
263
+ "source": [
264
+ "def collate_fn(examples):\n",
265
+ " # process input/prompt part of conversations\n",
266
+ " user_texts = [processor.apply_chat_template(example[:1], tokenize=False) for example in examples]\n",
267
+ " \n",
268
+ " # process full conversations (user + assistant)\n",
269
+ " full_texts = [processor.apply_chat_template(example, tokenize=False) for example in examples]\n",
270
+ " \n",
271
+ " # process images\n",
272
+ " images = process_vision_info(examples)[0]\n",
273
+ " \n",
274
+ " # tokenize sequences\n",
275
+ " user_batch = processor(text=user_texts, images=images, return_tensors=\"pt\", padding=True)\n",
276
+ " full_batch = processor(text=full_texts, images=images, return_tensors=\"pt\", padding=True)\n",
277
+ " \n",
278
+ " # mask padding tokens\n",
279
+ " labels = full_batch[\"input_ids\"].clone()\n",
280
+ " labels[labels == processor.tokenizer.pad_token_id] = -100\n",
281
+ " \n",
282
+ " # mask user message tokens for each example in the batch\n",
283
+ " for i in range(len(examples)):\n",
284
+ " # length of prompt message (accounting for possible padding)\n",
285
+ " user_len = user_batch[\"attention_mask\"][i].sum().item()\n",
286
+ " \n",
287
+ " # mask prompt part of label\n",
288
+ " labels[i, :user_len - 1] = -100\n",
289
+ " \n",
290
+ " full_batch[\"labels\"] = labels\n",
291
+ " return full_batch"
292
+ ]
293
+ },
294
+ {
295
+ "cell_type": "code",
296
+ "execution_count": 7,
297
+ "id": "a3311e1d-8d53-4a0f-bc5a-4197e316ae1d",
298
+ "metadata": {
299
+ "tags": []
300
+ },
301
+ "outputs": [
302
+ {
303
+ "data": {
304
+ "text/plain": [
305
+ "torch.Size([2688, 1176])"
306
+ ]
307
+ },
308
+ "execution_count": 7,
309
+ "metadata": {},
310
+ "output_type": "execute_result"
311
+ }
312
+ ],
313
+ "source": [
314
+ "collate_fn(messages[:1])['pixel_values'].shape"
315
+ ]
316
+ },
317
+ {
318
+ "cell_type": "markdown",
319
+ "id": "6139f83c-d83c-4b4b-8e87-e28320799b5c",
320
+ "metadata": {},
321
+ "source": [
322
+ "## Fine-Tune the Model\n",
323
+ "\n",
324
+ "We will use the `SFTTrainer` from the `trl` library, which abstracts a lot of the complexities of training for us. For your own use-case you should adjust various hyper-parameters like learning rate, epochs, etc. according to your problem."
325
+ ]
326
+ },
327
+ {
328
+ "cell_type": "code",
329
+ "execution_count": 8,
330
+ "id": "8df597dd-7eac-4331-ad2c-3fc6800ea4e3",
331
+ "metadata": {
332
+ "tags": []
333
+ },
334
+ "outputs": [],
335
+ "source": [
336
+ "from trl import SFTConfig, SFTTrainer\n",
337
+ "\n",
338
+ "# Configure training arguments\n",
339
+ "training_args = SFTConfig(\n",
340
+ " output_dir=\"test_finetune\", # Directory to save the model\n",
341
+ " num_train_epochs=5, # Number of training epochs\n",
342
+ " per_device_train_batch_size=1, # Batch size for training\n",
343
+ " per_device_eval_batch_size=1, # Batch size for evaluation\n",
344
+ " gradient_accumulation_steps=4, # Steps to accumulate gradients\n",
345
+ " learning_rate=1e-5, # Learning rate for training\n",
346
+ " lr_scheduler_type=\"constant\", # Type of learning rate scheduler\n",
347
+ " logging_steps=1, # Steps interval for logging\n",
348
+ " eval_steps=2, # Steps interval for evaluation\n",
349
+ " eval_strategy=\"steps\", # Strategy for evaluation\n",
350
+ " # save_strategy=\"steps\", # Strategy for saving the model\n",
351
+ " # save_steps=20, # Steps interval for saving\n",
352
+ " bf16=True, # Use bfloat16 precision\n",
353
+ " max_grad_norm=0.3, # Maximum norm for gradient clipping\n",
354
+ " warmup_ratio=0.03, # Ratio of total steps for warmup\n",
355
+ " report_to=\"none\", # Reporting tool for tracking metrics\n",
356
+ " gradient_checkpointing=True, # Enable gradient checkpointing for memory efficiency\n",
357
+ " gradient_checkpointing_kwargs={\"use_reentrant\": False}, # Options for gradient checkpointing\n",
358
+ " # max_seq_length=1024 # Maximum sequence length for input\n",
359
+ ")\n",
360
+ "\n",
361
+ "# allow for proper loading of images during collation\n",
362
+ "training_args.remove_unused_columns = False\n",
363
+ "training_args.dataset_kwargs = {\"skip_prepare_dataset\": True}\n",
364
+ "\n",
365
+ "trainer = SFTTrainer(\n",
366
+ " model=model,\n",
367
+ " args=training_args,\n",
368
+ " data_collator=collate_fn,\n",
369
+ " train_dataset=messages,\n",
370
+ " eval_dataset=val_messages,\n",
371
+ " processing_class=processor.tokenizer,\n",
372
+ ")"
373
+ ]
374
+ },
375
+ {
376
+ "cell_type": "code",
377
+ "execution_count": 9,
378
+ "id": "0d05bdce-dc6e-4979-a573-8aed27972291",
379
+ "metadata": {
380
+ "tags": []
381
+ },
382
+ "outputs": [
383
+ {
384
+ "data": {
385
+ "text/html": [
386
+ "\n",
387
+ " <div>\n",
388
+ " \n",
389
+ " <progress value='10' max='10' style='width:300px; height:20px; vertical-align: middle;'></progress>\n",
390
+ " [10/10 01:34, Epoch 5/5]\n",
391
+ " </div>\n",
392
+ " <table border=\"1\" class=\"dataframe\">\n",
393
+ " <thead>\n",
394
+ " <tr style=\"text-align: left;\">\n",
395
+ " <th>Step</th>\n",
396
+ " <th>Training Loss</th>\n",
397
+ " <th>Validation Loss</th>\n",
398
+ " </tr>\n",
399
+ " </thead>\n",
400
+ " <tbody>\n",
401
+ " <tr>\n",
402
+ " <td>2</td>\n",
403
+ " <td>0.170400</td>\n",
404
+ " <td>0.269275</td>\n",
405
+ " </tr>\n",
406
+ " <tr>\n",
407
+ " <td>4</td>\n",
408
+ " <td>0.018300</td>\n",
409
+ " <td>0.042456</td>\n",
410
+ " </tr>\n",
411
+ " <tr>\n",
412
+ " <td>6</td>\n",
413
+ " <td>0.001800</td>\n",
414
+ " <td>0.012193</td>\n",
415
+ " </tr>\n",
416
+ " <tr>\n",
417
+ " <td>8</td>\n",
418
+ " <td>0.000300</td>\n",
419
+ " <td>0.011337</td>\n",
420
+ " </tr>\n",
421
+ " <tr>\n",
422
+ " <td>10</td>\n",
423
+ " <td>0.000300</td>\n",
424
+ " <td>0.011667</td>\n",
425
+ " </tr>\n",
426
+ " </tbody>\n",
427
+ "</table><p>"
428
+ ],
429
+ "text/plain": [
430
+ "<IPython.core.display.HTML object>"
431
+ ]
432
+ },
433
+ "metadata": {},
434
+ "output_type": "display_data"
435
+ },
436
+ {
437
+ "data": {
438
+ "text/plain": [
439
+ "TrainOutput(global_step=10, training_loss=0.057332569236314156, metrics={'train_runtime': 96.9988, 'train_samples_per_second': 0.412, 'train_steps_per_second': 0.103, 'total_flos': 261136381470720.0, 'train_loss': 0.057332569236314156})"
440
+ ]
441
+ },
442
+ "execution_count": 9,
443
+ "metadata": {},
444
+ "output_type": "execute_result"
445
+ }
446
+ ],
447
+ "source": [
448
+ "trainer.train()"
449
+ ]
450
+ },
451
+ {
452
+ "cell_type": "code",
453
+ "execution_count": 10,
454
+ "id": "7c800e94-9494-4178-b987-d1eebbd80ec0",
455
+ "metadata": {
456
+ "tags": []
457
+ },
458
+ "outputs": [],
459
+ "source": [
460
+ "trainer.save_model(training_args.output_dir)"
461
+ ]
462
+ },
463
+ {
464
+ "cell_type": "markdown",
465
+ "id": "1c8cb789-9168-4b68-ae57-2d67ab104743",
466
+ "metadata": {
467
+ "tags": []
468
+ },
469
+ "source": [
470
+ "## Test Generation\n",
471
+ "\n",
472
+ "Now, let's run actual generation of outputs for our validation examples to see if what the model has learned."
473
+ ]
474
+ },
475
+ {
476
+ "cell_type": "code",
477
+ "execution_count": 11,
478
+ "id": "a66eb36d-8301-4e43-8bcd-8dc831a625b7",
479
+ "metadata": {
480
+ "tags": []
481
+ },
482
+ "outputs": [],
483
+ "source": [
484
+ "# reload processor with left padding (for generation)\n",
485
+ "processor = AutoProcessor.from_pretrained(model_name, \n",
486
+ " trust_remote_code=True, \n",
487
+ " padding_side='left',\n",
488
+ " use_fast=True)\n",
489
+ "\n",
490
+ "# reconstruct validation messages without labels\n",
491
+ "test_messages = [\n",
492
+ " construct_messages(\n",
493
+ " x[\"document\"], \n",
494
+ " x[\"template\"], \n",
495
+ " ) for x in val_inputs\n",
496
+ "]\n",
497
+ "\n",
498
+ "texts = processor.tokenizer.apply_chat_template(\n",
499
+ " test_messages,\n",
500
+ " tokenize=False,\n",
501
+ " add_generation_prompt=True,\n",
502
+ ")\n",
503
+ "\n",
504
+ "image_inputs = process_vision_info(messages[2][:1])[0]\n",
505
+ "inputs = processor(\n",
506
+ " text=texts,\n",
507
+ " images=image_inputs,\n",
508
+ " padding=True,\n",
509
+ " return_tensors=\"pt\",\n",
510
+ ").to(\"cuda\")\n",
511
+ "\n",
512
+ "# we choose greedy sampling here, which works well for most information extraction tasks\n",
513
+ "generation_config = {\"do_sample\": True, \"temperature\": 1.0, \"max_new_tokens\": 2048}\n",
514
+ "\n",
515
+ "# Inference: Generation of the output\n",
516
+ "generated_ids = model.generate(\n",
517
+ " **inputs,\n",
518
+ " **generation_config\n",
519
+ ")\n",
520
+ "generated_ids_trimmed = [\n",
521
+ " out_ids[len(in_ids) :] for in_ids, out_ids in zip(inputs.input_ids, generated_ids)\n",
522
+ "]\n",
523
+ "output_texts = processor.batch_decode(\n",
524
+ " generated_ids_trimmed, skip_special_tokens=True, clean_up_tokenization_spaces=False\n",
525
+ ")"
526
+ ]
527
+ },
528
+ {
529
+ "cell_type": "markdown",
530
+ "id": "14609ef3-7121-4956-b25e-6ded1a12bc70",
531
+ "metadata": {},
532
+ "source": [
533
+ "As can be seen below, the model is now generating extractions with lowercase strings by default."
534
+ ]
535
+ },
536
+ {
537
+ "cell_type": "code",
538
+ "execution_count": 12,
539
+ "id": "091058ee-347f-45dc-aed8-21693a818334",
540
+ "metadata": {
541
+ "tags": []
542
+ },
543
+ "outputs": [
544
+ {
545
+ "name": "stdout",
546
+ "output_type": "stream",
547
+ "text": [
548
+ "=== Prompt ===\n",
549
+ "<|im_start|>system\n",
550
+ "You are a helpful assistant.<|im_end|>\n",
551
+ "<|im_start|>user\n",
552
+ "# Template:\n",
553
+ "{\"names\": [\"verbatim-string\"]}\n",
554
+ "# Context:\n",
555
+ "Jack went to the hill with Jill. Rupert went to the diner.<|im_end|>\n",
556
+ "<|im_start|>assistant\n",
557
+ "\n",
558
+ "=== Output ===\n",
559
+ "{\"names\": [\"jack\", \"jill\", \"rupert\"]}\n",
560
+ "\n",
561
+ "=== Prompt ===\n",
562
+ "<|im_start|>system\n",
563
+ "You are a helpful assistant.<|im_end|>\n",
564
+ "<|im_start|>user\n",
565
+ "# Template:\n",
566
+ "{\"names\": [\"verbatim-string\"]}\n",
567
+ "# Context:\n",
568
+ "My dog Clifford likes to play fetch with Emily and Peter.<|im_end|>\n",
569
+ "<|im_start|>assistant\n",
570
+ "\n",
571
+ "=== Output ===\n",
572
+ "{\"names\": [\"clifford\", \"emily\", \"peter\"]}\n",
573
+ "\n"
574
+ ]
575
+ }
576
+ ],
577
+ "source": [
578
+ "for i in range(len(texts)):\n",
579
+ " print(f\"=== Prompt ===\\n{texts[i]}\")\n",
580
+ " print(f\"=== Output ===\\n{output_texts[i]}\\n\")"
581
+ ]
582
+ },
583
+ {
584
+ "cell_type": "code",
585
+ "execution_count": null,
586
+ "id": "eb987296-d418-4cbb-b97c-37f4dd27632b",
587
+ "metadata": {},
588
+ "outputs": [],
589
+ "source": []
590
+ }
591
+ ],
592
+ "metadata": {
593
+ "environment": {
594
+ "kernel": "conda-base-py",
595
+ "name": "workbench-notebooks.m127",
596
+ "type": "gcloud",
597
+ "uri": "us-docker.pkg.dev/deeplearning-platform-release/gcr.io/workbench-notebooks:m127"
598
+ },
599
+ "kernelspec": {
600
+ "display_name": "Python 3 (ipykernel) (Local)",
601
+ "language": "python",
602
+ "name": "conda-base-py"
603
+ },
604
+ "language_info": {
605
+ "codemirror_mode": {
606
+ "name": "ipython",
607
+ "version": 3
608
+ },
609
+ "file_extension": ".py",
610
+ "mimetype": "text/x-python",
611
+ "name": "python",
612
+ "nbconvert_exporter": "python",
613
+ "pygments_lexer": "ipython3",
614
+ "version": "3.10.16"
615
+ }
616
+ },
617
+ "nbformat": 4,
618
+ "nbformat_minor": 5
619
+ }
pyproject.toml ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [project]
2
+ name = "numarkapp"
3
+ version = "0.1.0"
4
+ description = "Add your description here"
5
+ readme = "README.md"
6
+ requires-python = ">=3.12,<3.13"
7
+ dependencies = [
8
+ "flash-attn",
9
+ "gradio",
10
+ "huggingface-hub",
11
+ "qwen-vl-utils>=0.0.14",
12
+ "torch==2.9.0",
13
+ "transformers>=5.14.1",
14
+ ]
15
+
16
+ [tool.uv.sources]
17
+ torch = { index = "pytorch-cu130" }
18
+ flash-attn = { url = "https://github.com/mjun0812/flash-attention-prebuild-wheels/releases/download/v0.9.28/flash_attn-2.8.3+cu130torch2.9-cp312-cp312-win_amd64.whl" }
19
+
20
+ [[tool.uv.index]]
21
+ name = "pytorch-cu130"
22
+ url = "https://download.pytorch.org/whl/cu130"
23
+ explicit = true
uv.lock ADDED
The diff for this file is too large to render. See raw diff