Repository navigation
Conversation
Xingkai98
requested review from
hsliuustc0106 and
princepride
as code owners
November 10, 2026 14:35
…abulary vllm-omni#10102 publishes six operations and twelve profiles; `stages[]` is deployment topology (`id`/`type`/`depends_on`, numeric strings, the same full list for every model) and a route cannot separate the three profiles that share `/v1/audio/speech`. The table is therefore keyed by (operation, profile), with OPERATION_ROUTES kept as the ownership check that reports `route_mismatch`. - OPERATION_PROFILES replaces STAGE_ROUTES, covering all twelve profiles - capabilities are available only on a supported profile in an enabled operation; `missing` needs positive evidence, everything else stays `unknown` - segmentations/depth keep a route but no operation, so image.segment, video.matting and video.motion_capture are `unknown` with `no_upstream_profile` - `asr` reports `unmapped_stage`: upstream publishes no recognition operation - fixtures follow the real response shape; five regressions pin the pairs, the shared speech path, topology-only stages and absent fields
…res schema-valid Round-2 fixes for the three independent reviews of ThinkFlowLab#278. Product decisions (P1-P5): - P1: image_response / audio_response no longer map to text.chat; they stay unknown with no_local_capability, since text.chat is served by a text-only adapter and an image-only deployment must not read as able to chat. - P2: text_to_audio (audio_generation, /v1/audio/generate) maps to audio.music alongside lyrics_to_music; the StableAudio/LTX2-t2a shape no longer reads audio.music as missing. - P3: support=unsupported and exposure=disabled stay "missing" but are told apart: resolve() now returns a `reasons` map (unsupported / endpoint_disabled / route_disabled) instead of dropping reason_code. - P4: profiles[*].routes[*].exposure is read: all routes disabled -> missing + route_disabled, all unknown or routes: [] -> unknown, one enabled -> available. - P5: one unrecognised route no longer voids a profile that answered; only when nothing usable remains does it fall to unknown. Contract and implementation (I1-I3): - I1: OPERATION_ROUTES is (transport, method, path); _check_route compares all three, so DELETE /v1/chat/completions and a websocket-declared http route are route_mismatch. - I2: endpoint distinguishes absent (no answer: None, no models, models not a list) from empty (answered, no operation declared) from present. - I3: the unreachable `item in UNROUTED` branch is deleted; unrouted ids never collect evidence, and the disjointness invariant is enforced by test_every_capability_id_is_routed_or_explained instead of dead runtime code. Malformed routes and supported profiles with no route are now reported (malformed_route / no_route_declared) instead of silently unknown. Fixtures (F1-F3): - All three fixtures are upstream-shaped: six operations per model, support and exposure on every operation, profile support present, profiles: [] instead of a missing key, image_response/audio_response outputs ["image"]/["audio"]. - Defensive shapes (asr key, misplaced profile, absent required support) moved out of the fixtures into the tests that defend against malformed input. - test_fixtures_are_schema_valid asserts upstream's structural invariants only. - exclude_none is described correctly (it drops None-valued optional fields such as `request`; default_factory lists serialize as []). Tests (T1-T5): - The tautological order assertion is now a literal vocabulary list, and the twelve pairs are literal too instead of an echo of OPERATION_PROFILES. - test_a_profile_without_support_is_unknown_even_when_its_operation_is_supported covers profile-level absence (old N4 mutant now dies). - test_a_profile_that_declares_no_route_is_unknown covers `supported + routes: []` (old M7/P3 mutant now dies). - New cases: profile routes all disabled / all unknown, one unknown route does not void a profile, DELETE and websocket mismatches, image-only chat, text_to_audio -> audio.music, absent vs empty endpoints, reason separation, non-dict operation values, profile order independence. - UNROUTED expectations are a literal table, not recomputed from the constant. Docs (D1-D2): - Corrected the exclude_none claim and the audio.score / /v1/audio/transcriptions statement (the capabilities body never publishes that path, so audio.score can only be unknown). - Added the status/reason mapping table (CN + EN) covering every declaration shape, plus the profile/route/profile-output tables. Tests: 60 passed (was 40); related subset 122 passed, 1 skipped. Nine mutations of the new rules (including removing route_disabled) all fail the suite.
…puts with upstream
…erts it
`/v1/realtime/video` is the one route whose exposure upstream rewrites:
`entrypoints/openai/serving_capabilities.py:342-347` forces `disabled` when
the profile's `required_inputs` intersect `{video, audio}` and otherwise keeps
the route's own value. Three fixture rows disagreed with that rule:
- full/`image_to_video` (`text`,`image`) carried `disabled` -> `enabled`
- partial/`mixed-operations`/`image_to_video` same
- partial/`unknown-enums`/`video_to_video` (`video`) carried `unknown` ->
`disabled` (the override is unconditional on the route's own value)
`test_fixtures_obey_upstream_construction_invariants` claimed in its docstring
to pin this rule but only checked route-key sets, so the drift went unseen. It
now asserts the rule itself plus same-path exposure consistency inside one
operation; reverting any of the three rows turns it red (verified).
Resolution output is unchanged by these rows: no `expected` block moved.
Docs/PR body refreshed: upstream is now `43ff7b9` (vocabulary byte-identical to
`d1104f8`), test/doc counts and line references re-measured.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
feat(contract): (operation, profile) capability mapping table / 能力映射表契约
独立于 #266:本模块一行 import 都没有,可先合;#266 合入后
requests_for()可直接吃这张表。Independent of #266: imports nothing from it, so it can merge first.
为什么键必须是
(operation, profile)/v1/audio/speech一条路径挂 3 个 profile(配音 / 音乐 / 音效)。上游自己的测试里 MOSS-SoundEffect 在 speech 上只声明caption_to_audio、把text_to_speech标成unsupported(tests/config/test_serving_capabilities.py:251,269)——同一路径两种答案,按路径匹配会把「只做音效」的部署读成会配音。stages[]只有id/type/depends_on,是拓扑;每个 model 拿到的是同一份全量列表(entrypoints/openai/serving_capabilities.py:400),用它判定所有模型必然同状态。路由仍然有用,但只做一致性校验:
OPERATION_ROUTES记录 operation 对路径的所有权,profile 引用了别的 operation 的路径就报route_mismatch且不产生能力。上游词表(vllm-omni PR #10102,按
d1104f8取证;现已43ff7b9,词表未变)chattext_responsetext.chatPOST /v1/chat/completionschatimage_response/audio_responsetext.chat的消费者是纯文本适配器POST /v1/chat/completionsimage_generationtext_to_imageimage.generatePOST /v1/images/generationsimage_editimage_to_imageimage.editPOST /v1/images/editsvideo_generationtext_to_video/image_to_video/video_to_videovideo.generatePOST /v1/videos、POST /v1/videos/sync、WS /v1/realtime/videospeechtext_to_speechaudio.speechPOST /v1/audio/speechspeechlyrics_to_musicaudio.musicPOST /v1/audio/speechspeechcaption_to_audioPOST /v1/audio/speechaudio_generationtext_to_audio/v1/audio/generate契约,没有 lyrics/instructionsPOST /v1/audio/generate音乐走
/v1/audio/speech,不是/v1/audio/generate:音乐不是独立 operation,它是speech下的lyrics_to_music(MiniMax Music3 / YuE2,model_executor/models/minimax_music3/pipeline.py:50-55)。/v1/audio/generate属audio_generation的text_to_audio,没有 voice / lyrics / instructions 映射(docs/serving/capabilities.md:59-60),既不能映射到audio.speech也不能映射到audio.music—— 本仓audio.music的适配器VllmOmniMusic(integrations/model_adapters.py:633-662)POST 的正是/v1/audio/speech且必带歌词与instructions。改了什么
packages/core/src/opendirector/domain/capability_contract.pyOPERATION_PROFILES(12 行,键(operation, profile))、OPERATION_ROUTES(6 个 operation × 路由,含/v1/videos/sync、WEBSOCKET /v1/realtime/video)、UNROUTED、capabilities_for()、declarations()、resolve()、classified_capabilities()、route_path()tests/fixtures/omni-capabilities/{full,partial,absent}.jsonprofiles为[]而非缺席;partial覆盖unsupported/exposure=disabled/ profile 路由全disabled/no_local_capability/ 全unknown枚举;absent覆盖没答、只答拓扑、profiles: []tests/fixtures/omni-capabilities/malformed.jsondisabled旁边挂着一条本表不认识的enabled路径)tests/test_capability_contract.pyrequired_inputs/outputs字面量表、与文档表格和全部样本的比对、fixture 构造不变量、路由级 exposure/transport/method 用例、5 条防「假覆盖」回归docs/architecture/capability-contract.mddocs/README.md5 条防「假覆盖」回归:12 个成对声明非空;同一路径 3 个 profile 解析出 3 个不同答案;两个 model 的
stage_ids相同但状态各自独立;8 个无上游 profile 的 id 恒unknown;profile 缺support只报unknown。状态判定
available:profilesupport=supported,operation 是supported+exposure=enabled,且该 profile 至少一条自己的路由exposure=enabled;missing:profile 或 operation 正面说不(support=unsupported/exposure=disabled/ 自己的路由全disabled);unknown:其余全部。missing的两类正面说不分开记:reasons给unsupported(模型不行)/endpoint_disabled(部署关了 operation 的路由)/route_disabled(profile 自己的路由全关)。unknown的原因在unresolved(no_route_declared、malformed_route、unknown_route、route_mismatch、no_local_capability等)。endpoint三值absent/empty/present区分「没答」与「答了但没声明」。/v1/images/segmentations、/v1/videos/segmentations、/v1/videos/depth都注册了但没有任何 operation 拥有它们 →image.segment/video.matting/video.motion_capture恒unknown(no_upstream_profile),不是missing。ASR 是上游缺口:上游词表没有识别 operation,omni router 也不注册
/v1/audio/transcriptions,请求侧写asr得unmapped_stage,audio.score恒unknown。怎么验
全量
pytest tests在本机不干净(缺真实 agent CLI,test_native_runtime.py等有既有失败),与本改动无关,故不拿它当证据。与 #266 的关系
互补,非 stacked(#266 来自 fork,GitHub 不允许跨仓 base)。接线两条路:
capabilities_for(operation, profile),requests_for()从「按 kind+protocol 猜 profile 名」变成表驱动;resolve()的逐能力状态接给展示层。本 PR 不消费
Model.stage_ids:它是拓扑,不是能力键。想确认的三点
stage_ids怎么处理? 上游每个 model 拿到的都是同一份全量列表,用它判定会让所有模型共享状态,所以这里只当拓扑。caption_to_audio要不要补 id? 上游确实发布这个 profile(音效/环境声),本仓没有对应 capability id,现在如实报no_local_capability并保持unknown。补audio.effect还是长期留白?asr按上游缺口处理,接受吗? 现在报unmapped_stage+audio.score恒unknown,等上游发布识别 operation 再补行。已知限制
malformed_route与no_route_declared可能同时出现(capability_contract.py:476-478),文档映射表只写了前者。method缺省、profile id 大小写三条防御分支没有用例(上游会补齐 profile,method缺省被 pydantic 拒收)。expected是生成后冻结的,没有reviewedBy标记,重跑生成器会把回归静默吸收。test_fixtures_are_schema_valid里的seen_body >= 5没跟随实际 body 数,掉一个 case 不会被发现。available仍是部署级 OR,不回答「哪个 model」。