From 52803d87f80d4a7c04a11e6b2ef56c6ed6a81f12 Mon Sep 17 00:00:00 2001 From: Vuong Hoang Date: Mon, 11 May 2026 15:33:08 -0700 Subject: [PATCH] catalog-contract: section_groups + Field.section for progressive disclosure; ace-step v2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit asset_engine consumer (althing thread 01KRCJF7NGMXYE9F62Q1A6KFD4) needed structure for ace-step's 27-field form. Two additive Pydantic changes — backward-compatible, no catalog_version bump per the policy table: - CatalogField.section: str | None = None - CatalogService.section_groups: list[CatalogSectionGroup] = [] - new CatalogSectionGroup model: {id, label, hint?} Validator: every Field.section value must reference a declared section_groups[].id within the same service; section_groups[].id values are unique. CATALOG-CONTRACT.md updated with both the new service-fields row and a versioning-policy row covering "add optional Field/Service keys -> no bump." ace-step entry rewritten to use the new schema: - bumped version 1 -> 2 - declared 6 section groups (basic / generation / conditioning / a2a / lora / output) with hints - tagged every field with a section - added previously-missing checkpoint_path (required: true, default: "/app/checkpoints" — the container's mount path). Wrapper-side cleanup (default in infer-api.py) queued as follow-up. - changed lyrics from optional: true -> required: true with default "" to match upstream's `lyrics: str` shape (empty string satisfies it). JSON Schema regenerated. Pydantic-model side of this change lives in asset_engine at src/asset_engine/catalog.py — committed there separately. --- docs/asset-engine/CATALOG-CONTRACT.md | 2 + docs/asset-engine/services.schema.json | 51 +++++++++++++++++ docs/asset-engine/services.yaml | 76 ++++++++++++++++++++++++-- 3 files changed, 123 insertions(+), 6 deletions(-) diff --git a/docs/asset-engine/CATALOG-CONTRACT.md b/docs/asset-engine/CATALOG-CONTRACT.md index fd9f113..f718461 100644 --- a/docs/asset-engine/CATALOG-CONTRACT.md +++ b/docs/asset-engine/CATALOG-CONTRACT.md @@ -46,6 +46,7 @@ reproducibility_audit: [Audit] # one entry per service | `model.revision` | string \| null | no | SHA when known | | `model.image` | string | yes | container image ref this is hosted from | | `fields` | list[Field] | no | request parameters; empty for catalog-deferred | +| `section_groups` | list[SectionGroup] | no | named groups for progressive disclosure; consumers render fields under tabs/accordions per group. SectionGroup = `{id, label, hint?}`. Field.section refs must resolve here. | | `response.type` | enum: audio, image, video, text, json, file | yes | renderer dispatch (closed vocabulary) | | `response.mime` | string | no | static response MIME (raw-bytes wire) | | `response.mime_from_field` | string | no | name of a field whose value determines the MIME (raw-bytes wire) | @@ -95,6 +96,7 @@ Same change-management as field types. | Add a new service | nothing | | Add a non-required field to an existing service | service `version:` | | Add an optional key to the `response:` schema (e.g. audio_field, timestamps_field) | nothing — additive, backward-compatible | +| Add an optional key to a Field (e.g. section) or to Service (e.g. section_groups) | nothing — additive, backward-compatible | | Change a field's type, range, or default | service `version:` | | Remove a service | service `version:` (sentinel: removed=true), then drop in next catalog_version bump | | Add a new entry to the field-type vocabulary | `catalog_version:` | diff --git a/docs/asset-engine/services.schema.json b/docs/asset-engine/services.schema.json index 87a28e9..3feb093 100644 --- a/docs/asset-engine/services.schema.json +++ b/docs/asset-engine/services.schema.json @@ -218,6 +218,18 @@ ], "default": null, "title": "Accepted Types" + }, + "section": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Section" } }, "required": [ @@ -425,6 +437,38 @@ "title": "CatalogResponse", "type": "object" }, + "CatalogSectionGroup": { + "additionalProperties": false, + "description": "A named group of fields, for progressive disclosure in form rendering.\n\nServices with many fields (ace-step has 27) declare section_groups and\ntag each field with a `section: `. Consumers render fields grouped\nunder tabs / accordions / etc. Fields without a section go into an\nimplicit 'main' group rendered first.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "label": { + "title": "Label", + "type": "string" + }, + "hint": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Hint" + } + }, + "required": [ + "id", + "label" + ], + "title": "CatalogSectionGroup", + "type": "object" + }, "CatalogService": { "additionalProperties": false, "properties": { @@ -502,6 +546,13 @@ "title": "Fields", "type": "array" }, + "section_groups": { + "items": { + "$ref": "#/$defs/CatalogSectionGroup" + }, + "title": "Section Groups", + "type": "array" + }, "response": { "$ref": "#/$defs/CatalogResponse" }, diff --git a/docs/asset-engine/services.yaml b/docs/asset-engine/services.yaml index 4eb0c4a..433aa75 100644 --- a/docs/asset-engine/services.yaml +++ b/docs/asset-engine/services.yaml @@ -752,7 +752,7 @@ services: Apache-2.0 hybrid diffusion+LLM music generation. Multi-minute lyric-aware songs with vocals + instrumentation. category: music - version: 1 + version: 2 host: irv-ml1 endpoint: http://10.100.79.3:8210/generate method: POST @@ -761,120 +761,184 @@ services: id: ace-step/ACE-Step revision: main image: local/ace-step:v1 + section_groups: + - id: basic + label: Basic + hint: "Prompt + lyrics + duration. The 80% case." + - id: generation + label: Generation + hint: "Sampler steps, guidance, scheduler, seed." + - id: conditioning + label: Conditioning + hint: "ERG flags, OSS schedule, guidance shape." + - id: a2a + label: "Audio → Audio" + hint: "Generate from a reference clip." + - id: lora + label: LoRA + hint: "Adapter weights." + - id: output + label: Output + hint: "Format and device." fields: - name: prompt type: textarea label: Musical Prompt required: true + section: basic description: Style/mood/instrumentation, e.g. "uplifting pop with synth leads". - name: lyrics type: textarea label: Lyrics - optional: true + required: true + default: "" + section: basic + description: > + Required by the upstream Pydantic model (`lyrics: str`) but empty + string is a valid value (instrumental). Default '' so the inference + client always sends a string. - name: audio_duration type: slider min: 5.0 max: 600.0 default: 30.0 label: Duration (seconds) - - name: audio_format - type: select - options: [wav, mp3, flac] - default: wav + section: basic - name: infer_step type: number default: 20 label: Inference Steps + section: generation - name: guidance_scale type: slider min: 1.0 max: 15.0 default: 7.5 + section: generation - name: scheduler_type type: select options: [linear, squared, sqrt] default: linear + section: generation - name: cfg_type type: select options: [none, cfg, cfg_rw] default: cfg + section: generation - name: omega_scale type: slider min: 0.0 max: 1.0 default: 0.5 + section: generation - name: actual_seeds type: json label: Seeds default: [42] + section: generation - name: guidance_interval type: slider min: 0.0 max: 1.0 default: 0.0 + section: conditioning - name: guidance_interval_decay type: slider min: 0.0 max: 1.0 default: 1.0 + section: conditioning - name: min_guidance_scale type: slider min: 0.0 max: 10.0 default: 1.0 + section: conditioning - name: use_erg_tag type: bool default: false + section: conditioning - name: use_erg_lyric type: bool default: false + section: conditioning - name: use_erg_diffusion type: bool default: false + section: conditioning - name: oss_steps type: json default: [] + section: conditioning - name: guidance_scale_text type: slider min: 0.0 max: 15.0 default: 0.0 + section: conditioning - name: guidance_scale_lyric type: slider min: 0.0 max: 15.0 default: 0.0 + section: conditioning - name: audio2audio_enable type: bool default: false + section: a2a - name: ref_audio_strength type: slider min: 0.0 max: 1.0 default: 0.5 + section: a2a - name: ref_audio_input type: text label: Reference Audio Path optional: true + section: a2a - name: lora_name_or_path type: text label: LoRA Repo/Path optional: true + section: lora - name: lora_weight type: slider min: 0.0 max: 2.0 default: 1.0 + section: lora + - name: audio_format + type: select + options: [wav, mp3, flac] + default: wav + section: output - name: bf16 type: bool default: true + section: output - name: torch_compile type: bool default: false + section: output - name: device_id type: number default: 0 label: GPU Device Index + section: output + - name: checkpoint_path + type: text + label: Checkpoint Path + required: true + default: /app/checkpoints + section: output + description: > + Required by the upstream wrapper (`infer-api.py:29`, no default in + the Pydantic model). The container always mounts checkpoints at + /app/checkpoints, so the catalog default works out-of-the-box. + Only override if you've mounted an alternate checkpoint path. + Wrapper-side cleanup queued — once the upstream model defaults this, + the catalog field will become optional or be dropped entirely. response: type: audio mime_from_field: audio_format