生成时间: 2026-06-02 · 分析范围: OpenRouter /v1/models 接口、oh-my-pi (packages/ai)、pi-mono (packages/ai)
architecture 字段OpenRouter 的 GET /v1/models 在每个模型条目中返回一个 architecture 对象,这是模型能力的主要声明方式:
{
"architecture": {
"modality": "text+image+video->text",
"input_modalities": ["text", "image", "video"],
"output_modalities": ["text"],
"tokenizer": "Qwen3",
"instruct_type": null
}
}
当前 OpenRouter 目录(342 个模型)中存在 15 种独特的 architecture 组合:
| modality | input_modalities | output_modalities | 典型模型 |
|---|---|---|---|
text->text | text | text | MiniMax M2.7, Qwen3.6 Max, DeepSeek V3 |
text+image->text | text, image | text | GPT-4o, MiniMax-01, Qwen3 VL |
text+image+video->text | text, image, video | text | Qwen3.6 Flash/Plus/27B, MiniMax M3 |
text+image+file->text | text, image, file | text | GPT-4o-mini, Claude 4 Sonnet |
text+image+file+audio+video->text | full 5-modality | text | Gemini 2.5/3.x 系列 |
text+audio->text+audio | text, audio | text, audio | GPT Audio / GPT Audio Mini |
text+file+audio->text | text, audio, file | text | Mistral Voxtral |
| ...以及其他 8 种组合 | |||
除了 architecture,还有其他字段携带能力信息:
| 字段 | 含义 | 示例 |
|---|---|---|
supported_parameters | API 参数支持列表 | ["tools", "reasoning", "tool_choice", "structured_outputs"] |
supported_voices | TTS 声音列表 | 当前绝大多数为 null |
top_provider.max_completion_tokens | 最大输出 token | 16384 |
context_length | 上下文长度 | 128000 |
✅ 千问 3 ASR Flash 确实存在于 OpenRouter 上!
模型 ID: qwen/qwen3-asr-flash-2026-02-10 · 名称: Qwen: Qwen3 ASR Flash
发布日期: 2026-05-14 · 定价: $0.000035/音频秒(非 token 定价)
但它不在 /v1/models 的 Chat 模型列表中出现——因为它属于 Transcription(语音转录)类别,与 Chat/Text 模型是不同的业务范畴。
| 特征 | Chat 模型 (Qwen3.6 等) | Transcription 模型 (Qwen3 ASR) |
|---|---|---|
| API 端点 | POST /v1/chat/completions | POST /v1/audio/transcriptions |
| 在 /v1/models 列表中? | ✅ 是 | ❌ 否(或因 output=transcription 被过滤) |
| architecture.modality | text+image...->text | 未知(不在标准 Chat 模型列表) |
| 定价方式 | 按 token | 按音频秒 ($0.000035/s) |
| 输入格式 | JSON messages | base64 编码音频 (wav/mp3/flac) |
| 核心参数 | messages, tools, temperature | input_audio.data, input_audio.format |
OpenRouter 的模型分类体系实际上有多个输出类别:Text、Image、Embeddings、Audio、Video、Rerank、Speech、Transcription。当前 /v1/models 接口主要返回 Chat/Text 类模型,ASR 类的 Transcription 模型可能在其他端点或需要显式查询。
OpenRouter 网站搜索 "qwen3-asr" 能看到这个模型被归类在 "Transcription" 过滤器下(另外 341 个模型属于 Text 类别)。这说明 OpenRouter 有能力在多个 API 端点下承载不同类型的模型。
文件: packages/ai/src/types.ts L881-944
export interface Model<TApi extends Api = any> {
id: string;
name: string;
api: TApi;
provider: Provider;
baseUrl: string;
reasoning: boolean;
input: ("text" | "image")[]; // ← 只支持 text 和 image!
cost: { ... };
contextWindow: number;
maxTokens: number;
thinking?: ThinkingConfig; // 思考能力元数据
compat?: OpenAICompat | AnthropicCompat;
}
核心局限: input 字段的类型是 ("text" | "image")[],不包含 "audio"、"video"、"file"。整个项目的数据模型中不支持表达音频/视频/文件输入能力。
文件: packages/ai/src/provider-models/openai-compat.ts L1349-1401
运行时调用 openrouterModelManagerOptions() → fetchOpenAICompatibleModels(),逐模型映射:
// L1371-1381: 图像支持的判定
const modality = String(entry.architecture?.modality ?? "");
return {
...defaults,
reasoning: params.includes("reasoning"),
input: modality.includes("image") ? ["text", "image"] : ["text"],
// ↑ 仅检查 modality 字符串是否包含 "image"
// 完全不检查 audio / video / file
};
判定规则:
architecture.modality 是否包含 "image"supported_parameters 是否包含 "reasoning"通过 toInputCapabilities() (L50-56) 处理的 providers(models.dev 数据源、ZenMux 等):
function toInputCapabilities(value: unknown): ("text" | "image")[] {
if (!Array.isArray(value)) return ["text"];
const supportsImage = value.some(item => item === "image");
return supportsImage ? ["text", "image"] : ["text"];
}
Ollama: 检查 capabilities.includes("vision") (L324-325)。
文件: packages/ai/src/models.ts — 从 models.json 加载静态预生成目录,不做运行时动态发现。
OpenRouter 被配置为 allowUnauthenticated: true 的 catalog provider(见 descriptors.ts L217-221),可以在构建期拉取模型列表。
文件: packages/ai/src/types.ts L546-576
export interface Model<TApi extends Api> {
id: string;
name: string;
api: TApi;
provider: Provider;
reasoning: boolean;
input: ("text" | "image")[]; // ← 相同的两值联合类型
// ...
}
文件: packages/ai/scripts/generate-models.ts L281-337
pi-mono 在构建时调用 fetchOpenRouterModels() 拉取 OpenRouter 的 /v1/models,然后写入 models.generated.ts:
async function fetchOpenRouterModels(): Promise<Model<any>[]> {
const response = await fetch("https://openrouter.ai/api/v1/models");
const data = await response.json();
for (const model of data.data) {
// 仅包含支持 tools 的模型
if (!model.supported_parameters?.includes("tools")) continue;
const input: ("text" | "image")[] = ["text"];
if (model.architecture?.modality?.includes("image")) {
input.push("image");
} // ← 同样的 image-only 检查
models.push({
id: model.id,
reasoning: model.supported_parameters?.includes("reasoning") || false,
input,
// ...
});
}
}
pi-mono 有一个完全独立的图像生成子系统:
| 文件 | 作用 |
|---|---|
src/image-models.generated.ts | 预生成的图像生成模型目录(Flux, DALL-E 等) |
src/images.ts | 图像生成入口 generateImages() |
src/images-api-registry.ts | 图像 API 提供者注册表 |
src/providers/images/openrouter.ts | OpenRouter 图像生成实现(chat completions → base64 图片) |
ImagesModel 类型有 input 和 output 两个字段,但同样限 ("text" | "image")[]。
| 维度 | OpenRouter 声明 | oh-my-pi 模型 | pi-mono 模型 |
|---|---|---|---|
| 输入模态 | text, image, video, audio, file (5 种) | text, image (2 种) | text, image (2 种) |
| 输出模态 | text, audio, image (3 种) | text (隐式) | text (隐式,图像输出在 ImagesModel) |
| 视觉判定 | modality.includes("image") |
✅ 相同逻辑 | ✅ 相同逻辑 |
| 推理判定 | supported_parameters |
✅ 相同逻辑 | ✅ 相同逻辑 |
| 音频判定 | modality.includes("audio") |
❌ 不支持 | ❌ 不支持 |
| 视频判定 | modality.includes("video") |
❌ 不支持 | ❌ 不支持 |
| 文件判定 | modality.includes("file") |
❌ 不支持 | ❌ 不支持 |
| 语音输出(TTS) | supported_voices (大部分为 null) |
❌ 无概念 | ❌ 无概念 |
核心问题: 两个项目的 Model 类型定义在架构层面就排除了 audio/video/file 模态。
即使 OpenRouter 返回了完整的能力声明,下游代码在映射阶段就把这些信息丢弃了。要支持音频/ASR 模型,需要:
Model.input 的联合类型architecture.input_modalities 而不仅仅是 modality 字符串✅ 存在于 OpenRouter,但需要特殊处理。
模型 ID: qwen/qwen3-asr-flash-2026-02-10
类别: Transcription(不在 /v1/models Chat 列表中)
端点: POST https://openrouter.ai/api/v1/audio/transcriptions
定价: $0.000035/音频秒
能力: 11 种语言(含中文方言),自动语言检测,支持噪声环境
调用方式(Python 示例):
import requests, base64, json
with open("audio.wav", "rb") as f:
base64_audio = base64.b64encode(f.read()).decode("utf-8")
response = requests.post(
url="https://openrouter.ai/api/v1/audio/transcriptions",
headers={"Authorization": "Bearer $OPENROUTER_API_KEY"},
data=json.dumps({
"model": "qwen/qwen3-asr-flash-2026-02-10",
"input_audio": {"data": base64_audio, "format": "wav"}
})
)
print(response.json()["text"])
对下游应用的影响:这是一个完全不同的 API 契约。无法通过检查 /v1/models 的 architecture 字段发现它。下游需要:
/v1/audio/transcriptions 而非 /v1/chat/completions| 模型 ID | modality | oh-my-pi 识别为 |
|---|---|---|
qwen/qwen3.6-flash | text+image+video->text | input: ["text", "image"] |
qwen/qwen3.6-27b | text+image+video->text | input: ["text", "image"] |
qwen/qwen3.6-max-preview | text->text | input: ["text"] |
qwen3.6-max-preview 是唯一的纯文本版本。 其他 Qwen 3.6 变体都有 image+video 能力,但下游只标记了 image。video 能力信息在映射阶段被丢弃。
| 模型 ID | modality | oh-my-pi 识别为 |
|---|---|---|
minimax/minimax-m2.7 | text->text | input: ["text"] |
minimax/minimax-m3 | text+image+video->text | input: ["text", "image"] |
区分正确:纯文本 vs 多模态可以依据 modality.includes("image") 准确判断。
如果你的下游应用需要精确区分三类模型:
function classifyModel(entry) {
const m = entry.architecture?.modality ?? "";
const inputs = entry.architecture?.input_modalities ?? [];
return {
isPureText: inputs.length === 1 && inputs[0] === "text",
hasVision: m.includes("image") || inputs.includes("image"),
hasAudio: m.includes("audio") || inputs.includes("audio"),
hasVideo: m.includes("video") || inputs.includes("video"),
hasFile: m.includes("file") || inputs.includes("file"),
hasReasoning: entry.supported_parameters?.includes("reasoning"),
};
}
最佳实践: 优先使用 architecture.input_modalities 数组做能力判定,architecture.modality 字符串作为快速索引。
input_modalities 是显式枚举,不依赖字符串匹配(比如 "image" vs "images" 不会有歧义)。
分析完成。相关文件路径:
~/GitHub/oh-my-pi/packages/ai/src/types.ts (Model 类型定义)
~/GitHub/oh-my-pi/packages/ai/src/provider-models/openai-compat.ts (OpenRouter 发现 → L1371)
~/GitHub/pi-mono/packages/ai/scripts/generate-models.ts (静态目录生成 → L281)
~/GitHub/pi-mono/packages/ai/src/types.ts (pi-mono Model 类型)