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
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 / 能力映射表契约
这一版改了什么 / What changed in this revision
上一版(
66ed78c)把键定成(stage, route)。按上游 headd1104f8取证后,这个键不成立,已整体替换:STAGE_ROUTES,键(stage, route)OPERATION_PROFILES,键(operation, profile),覆盖上游全部 12 个 profilevllm_omni/serving_capabilities.py:43-54text/image/tts/music/score)参与能力判定stages[]只作拓扑(topology),不参与任何能力判定stages[]只有id/type/depends_on,且每个 model 拿到同一份全量列表(entrypoints/openai/serving_capabilities.py:400)/v1/audio/transcriptions在score下读成audio.scoreasr报unmapped_stage,audio.score恒unknownapi_server.py无该路由;recipes/.../Ming-3.0-audio-flash.md:331parameter_onlyno_upstream_profile,状态unknown(不是missing)docs/serving/capabilities.md:41-43stages:[{name, routes}]stages[]=id/type/depends_on、models[].operations六个齐全、profile 带routes[])protocol/capabilities.py:36-79上游真实词表(取证) / The evidenced vocabulary
vllm-project/vllm-omniPR #10102,headd1104f8: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/instructions,不是本仓audio.musicPOST /v1/audio/generate为什么键是 (operation, profile) / Why the key is the pair
不是 route:
/v1/audio/speech一条路径挂 3 个 profile —— 配音、音乐、音效。上游自己的测试里,MOSS-SoundEffect 在 speech 上只声明caption_to_audio,同时把text_to_speech标成unsupported(tests/config/test_serving_capabilities.py:233,251):同一路径,两种答案。只按路径匹配,「只做音效」的部署会看起来会配音。路由仍然有用,但只作为一致性校验:OPERATION_ROUTES记录 operation 对路径的所有权,profile 引用了别的 operation 的路径就报route_mismatch且不产生能力。不是 stage:
stages[]是部署拓扑,id 是数字字符串,每个 model 都拿到同一份全量列表。用它判定,所有模型共享一个状态、既不能区分也不能诊断。本模块把stages[]只放进topology供展示。音乐走
/v1/audio/speech,不是/v1/audio/generate:音乐生成不是独立 operation,它是speech下的lyrics_to_musicprofile(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:510-537)POST 的正是/v1/audio/speech,且必带歌词与instructions,而这条 profile 恰恰没有这两样。所以text_to_audio留空,support=supported时报no_local_capability,audio.music只由lyrics_to_music决定(无证据即unknown)。改了什么 / What changed
packages/core/src/opendirector/domain/capability_contract.pyOPERATION_PROFILES(12 行,键(operation, profile))、OPERATION_ROUTES(6 个 operation,键是(transport, method, path),含/v1/videos/sync与websocket WEBSOCKET /v1/realtime/video)、UNROUTED、capabilities_for()、declarations()、resolve()、classified_capabilities()、route_path()tests/fixtures/omni-capabilities/{full,partial,absent}.jsonprofiles为[]而非缺席;exclude_none只能省掉request这类None字段;required_inputs/outputs照上游STANDARD_PROFILES含顺序);partial覆盖unsupported/exposure=disabled/ profile 路由全disabled/no_local_capability/ 全unknown枚举;absent覆盖没答、只答拓扑、profiles: []tests/fixtures/omni-capabilities/malformed.jsondisabled旁边挂着一条本表不认识的enabled路径)。只被防御性测试读取,不进样本 oracletests/test_capability_contract.pyrequired_inputs/outputs字面量表与两份文档表格/全部样本 fixture 的比对、fixture 结构不变量校验(observed_at/reason_code/execution/route 三元组)、路由级exposure与 transport/method 用例docs/architecture/capability-contract.mddocs/README.md状态判定:只有正面证据才是
missingavailable:某个 model 的 profilesupport=supported,其 operation 是supported+exposure=enabled,且该 profile 至少一条自己的路由exposure=enabled;missing:profile 或 operation 正面说不(support=unsupported/exposure=disabled/ 自己的路由全disabled);unknown:其余全部 —— 404、字段缺席、枚举为unknown、路由可达性无法确认、声明无法翻译、能力不可路由。missing的两类「正面说不」不再坍缩成一个标签:resolve()['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区分「没答」与「答了但没声明」。八种无法翻译的声明分别进
unresolved(不丢弃):unknown_route、route_mismatch、unmapped_stage、unknown_profile、operation_mismatch、no_local_capability、no_route_declared、malformed_route,每条带operation/profile/route/reason。有路由、没有 operation 的能力
/v1/images/segmentations、/v1/videos/segmentations、/v1/videos/depth都注册了,但没有任何 operation 拥有它们。image.segment/video.matting/video.motion_capture恒为unknown,reasonno_upstream_profile—— 端点不发布该能力,无法判定,不能写成missing。ASR 是上游缺口,不是本表漏行
上游词表没有识别 operation,omni router 也不注册
/v1/audio/transcriptions(上游 recipe 明写该端点不可用)。请求侧写asr得到unmapped_stage,audio.score恒unknown(no_upstream_operation)。等上游发布识别 operation 再谈映射。防「假覆盖」的 5 条回归 / Five regressions against false coverage
test_real_shaped_body_resolves_non_empty_pairstest_route_alone_cannot_distinguish_speech_profilestest_stage_ids_do_not_collapse_capabilitiesstage_ids相同,能力状态仍各自独立;数字 stage id 不再冒成翻译失败test_capabilities_without_an_upstream_profile_are_unknown_not_missingunknown(字面量表,不从被测常量现算)test_a_profile_without_support_is_unknown_even_when_its_operation_is_supportedunknown;写清unsupported才是missing不碰什么 / Out of scope
domain/omni_capabilities.py、integrations/capability_probe.py、application/model_availability.py、src/**(均为 feat(providers): discover capabilities and validate generation inputs #266 负责);测试怎么跑 / How to verify
全量
pytest tests:1602 passed / 98 skipped / 18 failed,18 个失败全部在test_native_runtime.py、test_deepseek_runtime.py、test_jiuwenswarm_runtime.py、test_timeline_packages.py(容器里没有真实 agent CLI),把这些文件放到不含本改动的树上跑,失败集合完全相同。(本机 python 3.13,pydantic 2.14.3 / pytest 9.2.0。
generate_contracts.py只覆盖 creation 契约,本模块不产生生成物。)与 #266 的关系 / Relationship to #266
互补 PR(非 stacked,因 #266 来自 fork,GitHub 不允许跨仓 base):#266 定义发现文档与匹配,本 PR 提供
(operation, profile) → capability这一层,两者可独立合入。合并路径 ——capabilities_for(operation, profile),requests_for()从「按 kind+protocol 猜 profile 名」变成表驱动;resolve()的逐能力状态接给展示层,匹配仍留在 feat(providers): discover capabilities and validate generation inputs #266 里。本 PR 不再消费
Model.stage_ids:它是拓扑,不是能力键。给 @bread-lxy 的三个问题 / Three questions
stage_ids怎么处理? 上游每个 model 拿到的都是同一份全量列表,用它判定能力会让所有模型共享一个状态,所以本 PR 只把它当拓扑。若 feat(providers): discover capabilities and validate generation inputs #266 希望 stage 参与逐模型判定,需要上游先做到按模型差异化 —— 否则只能得到「全员同状态」。caption_to_audio要不要补 id? 上游确实发布这个 profile(音效/环境声),本仓没有对应 capability id,现在如实报no_local_capability并保持unknown。(text_to_audio同样留空:它是扩散侧/v1/audio/generate契约,没有 lyrics/instructions,喂不了本仓audio.music需要的/v1/audio/speech请求。)是补audio.effect,还是长期留白?后者更保守,但会看不见「只做音效」的部署。asr按上游缺口处理,接受吗? 上游词表里没有识别 operation,/v1/audio/transcriptions在 omni 下不注册。本 PR 的答案是unmapped_stage+audio.score恒unknown,等上游发布后再补行。后续(本 PR 不做) / Follow-ups
audio.transcribe)后,OPERATION_PROFILES加一行即可,unmapped_stage自然消失;docs/architecture/test-contracts.md的「一个语义只有一个所有者」要求:表一旦合并,requests_for()里那 30 行(kind, protocol)分支应当删除,而不是与本表并存;available仍是部署级 OR(不回答「哪个 model」):本模块不读 model id,如需要served_by得由 feat(providers): discover capabilities and validate generation inputs #266 一侧决定脱敏口径。已知限制(第 3 轮窄修后仍存) / Known limits after this pass
本轮只修 5 条接口面问题(
full档caption_to_audio一致性、fixture 构造不变量 + 守门测试、unrecognised路由的unknown半边用例、两处同义反复断言、malformed.json纳入required_inputs行校验)。以下仍是已知限制,未在本轮改动:malformed_route与no_route_declared同时出现:一条读不出path的路由条目会报两条unresolved,文档映射表只写了前者(capability_contract.py:476-478)。test_documented_required_inputs_match_fixtures只比required_inputs → outputs单元格,把某行 operation 改错不会红。model_adapters.py:547应为:555(POST/audio/transcriptions);serving_capabilities.py:341-343应用全路径entrypoints/openai/serving_capabilities.py,短名会与vllm_omni/serving_capabilities.py(176 行)撞行号。method缺省、profile id 大小写三条防御分支仍无用例(上轮记 M10 medium / M12 low / M13 low):上游declare_capabilities会补齐 profile,method缺省被 pydantic 拒收,id 大小写只会「少给能力」。expected是脚本生成后冻结的:fixture 里没有reviewedBy一类标记,重跑生成器会把回归静默吸收。test_fixtures_are_schema_valid的seen_body >= 5没有跟随实际 body 数(现为 5),掉一个 case 不会被发现。test_a_profile_without_support_is_unknown_even_when_its_operation_is_supported里的!= AVAILABLE与紧邻的== UNKNOWN是同一件事(本轮只清了新增的两处)。