Skip to content

Commit 3818f42

Browse files
rustyconoverclaude
andcommitted
Bring vision worker to vgi-lint-clean; wire metadata-quality CI gate
Bump vgi-python floor to >=0.8.4 (declarative Catalog gains source_url) and refresh uv.lock. Add catalog/schema/table-function metadata so `vgi-lint --fail-on info` reports 0 findings (100/100): - Catalog: comment, source_url, and tags (vgi.description_llm/_md, author, copyright, license=MIT, support_contact, support_policy_url). - Schema main: vgi.description_llm / vgi.description_md tags. - Each dynamic-schema table function (classify x4, image_classes): a vgi.columns_md Markdown table of its returned columns. Add a separate `metadata-quality` CI job that builds the worker and runs Query-farm/vgi-lint-check@v1 with fail-on: info. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 22a2c93 commit 3818f42

5 files changed

Lines changed: 104 additions & 8 deletions

File tree

.github/workflows/ci.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,3 +139,27 @@ jobs:
139139
run: ci/run-integration.sh
140140
env:
141141
TRANSPORT: ${{ matrix.transport }}
142+
143+
# Catalog/function metadata quality gate. Builds the worker (same as the
144+
# integration job) and runs the signed vgi-lint metadata linter, failing on
145+
# any finding at info severity or above.
146+
metadata-quality:
147+
name: Metadata quality (vgi-lint)
148+
runs-on: ubuntu-latest
149+
steps:
150+
- uses: actions/checkout@v6
151+
152+
- name: Install uv
153+
uses: astral-sh/setup-uv@v8.2.0
154+
155+
- name: Set up Python 3.13
156+
run: uv python install 3.13
157+
158+
- name: Install the worker (from the lockfile, with the http extra)
159+
run: uv sync --frozen --python 3.13 --extra http
160+
161+
- name: vgi-lint
162+
uses: Query-farm/vgi-lint-check@v1
163+
with:
164+
location: "uv run --python 3.13 vision_worker.py"
165+
fail-on: info

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ classifiers = [
3030
]
3131
requires-python = ">=3.13"
3232
dependencies = [
33-
"vgi-python>=0.8.3",
33+
"vgi-python>=0.8.4",
3434
"onnxruntime>=1.17",
3535
"pillow>=10",
3636
"numpy>=1.26",
@@ -43,7 +43,7 @@ dependencies = [
4343
# in addition to stdio. That path needs vgi-python's `http` extra (waitress).
4444
# CI's http transport leg installs this via `uv sync --extra http`.
4545
http = [
46-
"vgi-python[http]>=0.8.3",
46+
"vgi-python[http]>=0.8.4",
4747
]
4848
dev = [
4949
"pytest>=8",

uv.lock

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vgi_vision/tables.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,23 @@ def _emit_cursor(state: ScanState, out: OutputCollector, schema: pa.Schema) -> N
131131
]
132132
)
133133

134+
# Markdown column docs for the dynamic table-function schemas. DuckDB can't expose a
135+
# table function's returned columns statically, so each function advertises them via a
136+
# `vgi.columns_md` tag (consumed by describe/listing tooling and the metadata linter).
137+
_CLASSIFY_COLUMNS_MD = (
138+
"| column | type | description |\n"
139+
"|---|---|---|\n"
140+
"| `label` | VARCHAR | Predicted ImageNet class label. |\n"
141+
"| `confidence` | DOUBLE | Softmax probability in [0, 1]; rows are confidence descending. |"
142+
)
143+
144+
_CLASSES_COLUMNS_MD = (
145+
"| column | type | description |\n"
146+
"|---|---|---|\n"
147+
"| `idx` | INTEGER | 0-based class index into the model's output. |\n"
148+
"| `label` | VARCHAR | ImageNet class label. |"
149+
)
150+
134151

135152
def _classify_batch(preds: list[tuple[str, float]] | None, schema: pa.Schema) -> pa.RecordBatch:
136153
"""Build the full ``(label, confidence)`` batch for a set of predictions.
@@ -188,6 +205,7 @@ class Meta:
188205
name = "classify"
189206
description = "Top-5 ImageNet predictions (label, confidence) for an image BLOB"
190207
categories = ["vision", "classification"]
208+
tags = {"vgi.columns_md": _CLASSIFY_COLUMNS_MD}
191209
examples = [
192210
FunctionExample(
193211
sql="SELECT * FROM vision.classify((SELECT image FROM photos LIMIT 1))",
@@ -226,6 +244,7 @@ class Meta:
226244
name = "classify"
227245
description = "Top-k ImageNet predictions (label, confidence) for an image BLOB"
228246
categories = ["vision", "classification"]
247+
tags = {"vgi.columns_md": _CLASSIFY_COLUMNS_MD}
229248
examples = [
230249
FunctionExample(
231250
sql="SELECT * FROM vision.classify((SELECT image FROM photos LIMIT 1), 10)",
@@ -281,6 +300,7 @@ class Meta:
281300
name = "classify"
282301
description = "Top-5 ImageNet predictions for an image file path"
283302
categories = ["vision", "classification"]
303+
tags = {"vgi.columns_md": _CLASSIFY_COLUMNS_MD}
284304
examples = [
285305
FunctionExample(
286306
sql="SELECT * FROM vision.classify('/tmp/cat.jpg')",
@@ -319,6 +339,7 @@ class Meta:
319339
name = "classify"
320340
description = "Top-k ImageNet predictions for an image file path"
321341
categories = ["vision", "classification"]
342+
tags = {"vgi.columns_md": _CLASSIFY_COLUMNS_MD}
322343
examples = [
323344
FunctionExample(
324345
sql="SELECT * FROM vision.classify('/tmp/cat.jpg', 10)",
@@ -368,6 +389,7 @@ class Meta:
368389
name = "image_classes"
369390
description = "The model's ImageNet label set: (idx, label), 1000 rows"
370391
categories = ["vision", "classification"]
392+
tags = {"vgi.columns_md": _CLASSES_COLUMNS_MD}
371393
examples = [
372394
FunctionExample(
373395
sql="SELECT count(*) FROM vision.image_classes()",

vision_worker.py

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# /// script
22
# requires-python = ">=3.13"
33
# dependencies = [
4-
# "vgi-python[http]>=0.8.3",
4+
# "vgi-python[http]>=0.8.4",
55
# "onnxruntime>=1.17",
66
# "pillow>=10",
77
# "numpy>=1.26",
@@ -42,13 +42,63 @@
4242
from vgi_vision.scalars import SCALAR_FUNCTIONS
4343
from vgi_vision.tables import TABLE_FUNCTIONS
4444

45+
_CATALOG_DESCRIPTION_LLM = (
46+
"Run image classification on image blobs (or image file paths) directly in SQL. "
47+
"Given the raw bytes of a PNG/JPEG/etc. image, returns ImageNet-1k labels: the single "
48+
"most likely label (`top_label`), the top-k (label, confidence) predictions "
49+
"(`classify`), or the model's full 1000-class label set (`image_classes`). The "
50+
"classifier is a permissively-licensed MobileNetV2 ONNX model. Use it to tag, filter, "
51+
"or group images by what they depict — e.g. find photos of cats, label a column of "
52+
"image blobs, or rank an image's most probable subjects."
53+
)
54+
55+
_CATALOG_DESCRIPTION_MD = (
56+
"# vision\n\n"
57+
"Image classification (ImageNet-1k) on image blobs as DuckDB SQL functions, via VGI. "
58+
"Inference runs out-of-process on a permissively-licensed MobileNetV2 ONNX model "
59+
"(Apache-2.0 weights) through onnxruntime.\n\n"
60+
"- **`top_label(image)` / `top_label(path)`** — scalar: the #1 predicted label per row.\n"
61+
"- **`classify(image[, top_k])` / `classify(path[, top_k])`** — table: top-k "
62+
"`(label, confidence)` predictions, confidence descending.\n"
63+
"- **`image_classes()`** — table: the model's full `(idx, label)` label set (1000 rows).\n\n"
64+
"Untrusted/malformed images yield SQL NULL (or no rows), never a crash."
65+
)
66+
67+
_SCHEMA_DESCRIPTION_LLM = (
68+
"Image-classification functions over image blobs and file paths: predict the top "
69+
"ImageNet label (`top_label`), the top-k (label, confidence) predictions (`classify`), "
70+
"and enumerate the model's class set (`image_classes`)."
71+
)
72+
73+
_SCHEMA_DESCRIPTION_MD = (
74+
"Image-classification functions: `top_label` (scalar), `classify` (table), and "
75+
"`image_classes` (table) over a MobileNetV2 ImageNet-1k ONNX model."
76+
)
77+
78+
_CATALOG_TAGS = {
79+
"vgi.description_llm": _CATALOG_DESCRIPTION_LLM,
80+
"vgi.description_md": _CATALOG_DESCRIPTION_MD,
81+
"vgi.author": "Query.Farm",
82+
"vgi.copyright": "Copyright 2026 Query Farm LLC - https://query.farm",
83+
"vgi.license": "MIT",
84+
"vgi.support_contact": "https://github.com/Query-farm/vgi-vision/issues",
85+
"vgi.support_policy_url": "https://github.com/Query-farm/vgi-vision/blob/main/README.md",
86+
}
87+
4588
_VISION_CATALOG = Catalog(
4689
name="vision",
4790
default_schema="main",
91+
comment="Image classification (ImageNet) on image blobs and file paths for SQL.",
92+
source_url="https://github.com/Query-farm/vgi-vision",
93+
tags=_CATALOG_TAGS,
4894
schemas=[
4995
Schema(
5096
name="main",
5197
comment="Image classification (ImageNet) on image blobs for SQL",
98+
tags={
99+
"vgi.description_llm": _SCHEMA_DESCRIPTION_LLM,
100+
"vgi.description_md": _SCHEMA_DESCRIPTION_MD,
101+
},
52102
functions=[*SCALAR_FUNCTIONS, *TABLE_FUNCTIONS],
53103
),
54104
],

0 commit comments

Comments
 (0)