Spaces:
Running on Zero
Running on Zero
electblake commited on
Commit ·
2c91fea
0
Parent(s):
init
Browse files- .agents/skills/gradio/SKILL.md +84 -0
- .agents/skills/gradio/references/api-signatures.md +115 -0
- .agents/skills/gradio/references/event-listeners.md +132 -0
- .agents/skills/gradio/references/examples.md +645 -0
- .agents/skills/hf-gradio/SKILL.md +83 -0
- .codex/skills/gradio +1 -0
- .codex/skills/hf-gradio +1 -0
- .gitattributes +2 -0
- .gitignore +10 -0
- app/__init__.py +0 -0
- app/__main__.py +0 -0
- app/config.py +2 -0
- app/gradio.py +0 -0
- app/nuextract.py +7 -0
- data/samples/Screenshot 2025-06-25 003204.png +3 -0
- data/samples/Screenshot 2026-05-27 175656.png +3 -0
- data/samples/Screenshot 2026-06-01 150055.png +3 -0
- data/samples/Screenshot 2026-07-15 163643.png +3 -0
- data/samples/Screenshot 2026-07-18 171416.png +3 -0
- launch.py +0 -0
- mise.toml +7 -0
- notebooks/nuextract-2.0_inference.ipynb +876 -0
- notebooks/nuextract-2.0_sft.ipynb +619 -0
- pyproject.toml +23 -0
- uv.lock +0 -0
.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
|
data/samples/Screenshot 2026-05-27 175656.png
ADDED
|
Git LFS Details
|
data/samples/Screenshot 2026-06-01 150055.png
ADDED
|
Git LFS Details
|
data/samples/Screenshot 2026-07-15 163643.png
ADDED
|
Git LFS Details
|
data/samples/Screenshot 2026-07-18 171416.png
ADDED
|
Git LFS Details
|
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
|
|
|