Skip to content

draft extract dimensions nooa - #1169

Draft
mborodii-prog wants to merge 2 commits into
mainfrom
feature/extract-dimensions-nooa
Draft

draft extract dimensions nooa#1169
mborodii-prog wants to merge 2 commits into
mainfrom
feature/extract-dimensions-nooa

Conversation

@mborodii-prog

Copy link
Copy Markdown
Contributor

Add extract.dimensions, an AI-powered wrangle built on NOOA

Summary

Adds extract.dimensions, a new wrangle for extracting structured dimensional measurements (length, width, height, diameter, depth, explicitly-stated volume, and labeled misc measurements like thickness, radius, bore, area, clearance, gauge) from messy product text.

This is also a prototype evaluation of NVIDIA's NOOA agent framework (nooa==0.0.10) as a lightweight alternative to this repo's existing raw-HTTP extract.ai implementation, for cases where a single typed Predict call (no tool-use loop) is enough.

Examples

Python API

import wrangles

results = wrangles.extract.dimensions(
    [
        "SS sink bowl 18 x 14 x 8 in deep; drain opening DIA 3.5 in",
        "Bottle, 750 mL capacity, 9.7 in H x 3.2 in OD",
        "Shelf 12-14 in wide, 3/4 in thick",
    ],
    model="gpt-5-mini",
    api_key="...",
    threads=3,
)

Output shape - one {"measurements": [...]} object per input row, in the
same order as the input:

{
  "measurements": [
    {
      "kind": "diameter",
      "label": null,
      "value": 3.2,
      "minimum": null,
      "maximum": null,
      "unit": "in",
      "qualifier": "outside",
      "source": "3.2 in OD"
    }
  ]
}

A range (instead of a single value) leaves value null and populates both minimum and maximum:

{"kind": "width", "value": null, "minimum": 12, "maximum": 14, "unit": "in", "source": "12-14 in wide"}

Compact L x W x H groups produce one measurement per dimension:

{
  "measurements": [
    {"kind": "length", "value": 18, "unit": "in", "source": "18 x 14 x 8 in"},
    {"kind": "width",  "value": 14, "unit": "in", "source": "18 x 14 x 8 in"},
    {"kind": "height", "value": 8,  "unit": "in", "source": "18 x 14 x 8 in"}
  ]
}

A misc measurement always carries a descriptive label:

{"kind": "misc", "label": "thickness", "value": 0.75, "unit": "in", "source": "3/4 in thick"}

Input with no supported dimensional fact returns an empty list:

{"measurements": []}

Recipe API

wrangles:
  - extract.dimensions:
      input:
        - Description
        - Size
        - Packaging
      output: Dimensions
      model: gpt-5.4-mini
      api_key: ${OPENAI_API_KEY}
      threads: 4
  • input: one column, several columns, or omitted (uses every column).
    A single column's raw values are used as-is; multiple columns are combined into one record per row.
  • output: exactly one column, holding the whole {"measurements": [...]} object per row - unlike extract.ai, there's
    no output_format (columns/concatenate/dictionary) mode, since an open-ended measurements list doesn't map cleanly onto fixed output columns the way extract.ai's per-field schema does.

What's explicitly out of scope

# No unit conversion - "12 in" stays "in", never converted to cm/mm.
# No derived volume - only returned if the source text states it explicitly,
# never calculated from length x width x height.
# Not treated as dimensions: counts, model numbers, electrical ratings,
# weights, ordinary pack quantities (e.g. "pack of 6" is not a measurement).

Design notes

  • Lazy, optional dependency. nooa==0.0.10; python_version >= "3.12" lives only in requirements-full.txt - a plain pip install wrangles never installs it, and a plain import wrangles never imports nooa or litellm. wrangles/nooa_client.py defers every NOOA import to inside functions, memoized behind a lock so it happens once.
  • Real Windows incompatibility in nooa 0.0.10 itself, fixed with a narrow guard. Verified directly (cloned NOOA's source, reproduced both failures on a real Windows machine) - import nooa unconditionally imports fcntl (POSIX-only) via its SQLite storage backend, and registers a SIGUSR2 handler that doesn't exist on Windows' signal module. _ensure_windows_guard() stubs just enough of both to let import succeed, without enabling real file locking or Unix signal handling (not needed - this integration only uses the default in-memorymevent store). Full writeup: docs/explain_extract_dimensions_nooa.md.
  • No custom retry logic. NOOA's PredictStrategy already retries internally on invalid structured output (PredictConfig.max_retries, raising GenerationError after exhausting) - confirmed by reading NOOA's own source, not assumed.
  • Row order preserved under bounded concurrency, using the same pattern the batch wrangle already uses: submit each row's future in original order, collect results by that same list order (not completion order).
  • A provider exception fails the whole call, not just that row - deliberately different from extract.ai's per-row error-string
    behavior. This is an explicit prototype-scope limitation, not an oversight (see the original issue's "Intentional boundaries").
  • Strict Pydantic output contract (extra="forbid") with a model_validator enforcing the value/range shape (exactly one of value or minimum+maximum) and that misc measurements carry a label.

Tests

  • tests/test_nooa_extract_dimensions.py - fully offline, three tiers: pure Pydantic contract tests (no nooa needed), plumbing tests mocking the nooa_client boundary (row order, bounded concurrency, empty input, missing-dependency error, base-import-unaffected), and 2 tests against the real installed nooa package using its own FakeLLMClient, guarded with pytest.mark.skipif(importlib.util.find_spec('nooa') is None, ...). Verified end-to-end by actually installing nooa==0.0.10 in an isolated venv and running the suite against it (both real-nooa tests pass) - and separately confirmed clean skipping in the normal, nooa-less venv.
  • tests/recipes/wrangles/test_extract_dimensions.py - recipe-YAML wiring only (input column handling, output shape), fully mocked.
  • schema/generate_recipe_schema.py run successfully in a venv with no nooa installed, confirming the new wrangle's docstring is valid and this step never needs nooa.
  • Full local suite: no regressions.

Flagged for reviewer attention

  • No output_format modes on the recipe wrangle (single output column only) - deliberate simplification, not a missing feature.
  • No setup.py extras_require - followed this repo's existing precedent (every other optional dependency is requirements-file-only + lazy-loaded), rather than introducing a first-of-its-kind pip install wrangles[nooa] mechanism.
  • api_base param added to both the plain function and the recipe wrangle, beyond the original issue's literal examples, for parity with NOOA's get_llm_client(..., api_base=...) and extract.ai's url override.

@mborodii-prog mborodii-prog linked an issue Sep 8, 2026 that may be closed by this pull request
11 tasks
@mborodii-prog
mborodii-prog requested review from ebhills and thomasstvr and removed request for ebhills September 8, 2026 15:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add NOOA-backed extract.dimensions wrangle

1 participant