674 lines
20 KiB
JSON
674 lines
20 KiB
JSON
{
|
||
"swagger": "2.0",
|
||
"info": {
|
||
"title": "go-whisper-api",
|
||
"version": "1.0.0",
|
||
"description": "HTTP API распознавания речи на whisper.cpp: SPR (/spr/*) и OpenAI-совместимый STT (/v1/*). Swagger UI: GET /"
|
||
},
|
||
"host": "localhost:8080",
|
||
"schemes": ["http"],
|
||
"basePath": "/",
|
||
"consumes": ["application/json", "multipart/form-data"],
|
||
"produces": ["application/json", "text/plain", "audio/wav", "application/octet-stream"],
|
||
"tags": [
|
||
{
|
||
"name": "spr",
|
||
"description": "Short Phrase Recognizer (асинхронная очередь, модели, waveform)"
|
||
},
|
||
{
|
||
"name": "openai",
|
||
"description": "OpenAI Whisper API (синхронная транскрипция, Open WebUI)"
|
||
},
|
||
{
|
||
"name": "meta",
|
||
"description": "Документация API"
|
||
}
|
||
],
|
||
"paths": {
|
||
"/swagger.json": {
|
||
"get": {
|
||
"tags": ["meta"],
|
||
"summary": "OpenAPI/Swagger 2.0 спецификация",
|
||
"operationId": "getSwaggerJSON",
|
||
"produces": ["application/json"],
|
||
"responses": {
|
||
"200": {
|
||
"description": "Спецификация JSON",
|
||
"schema": { "type": "object" }
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"/spr/models": {
|
||
"get": {
|
||
"tags": ["spr"],
|
||
"summary": "Список моделей STT",
|
||
"description": "Имена файлов `*.bin` в корне `api.models_dir` (без подкаталогов vad/punctuation).",
|
||
"operationId": "sprListModels",
|
||
"responses": {
|
||
"200": {
|
||
"description": "OK",
|
||
"schema": { "$ref": "#/definitions/modelList" }
|
||
},
|
||
"500": {
|
||
"description": "Ошибка чтения каталога",
|
||
"schema": { "$ref": "#/definitions/plainError" }
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"/spr/hostname": {
|
||
"get": {
|
||
"tags": ["spr"],
|
||
"summary": "Информация о хосте и сервере",
|
||
"operationId": "sprHostname",
|
||
"responses": {
|
||
"200": {
|
||
"description": "OK",
|
||
"schema": { "$ref": "#/definitions/hostnameInfo" }
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"/spr/queue": {
|
||
"get": {
|
||
"tags": ["spr"],
|
||
"summary": "Список задач в кэше",
|
||
"description": "Все задачи в `cache/waiting` и `cache/ready`: для каждого id — `created`, `status`.",
|
||
"operationId": "sprListQueue",
|
||
"responses": {
|
||
"200": {
|
||
"description": "OK",
|
||
"schema": { "$ref": "#/definitions/queueList" }
|
||
},
|
||
"500": {
|
||
"description": "Ошибка",
|
||
"schema": { "$ref": "#/definitions/plainError" }
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"/spr/queue/{taskID}": {
|
||
"parameters": [
|
||
{
|
||
"name": "taskID",
|
||
"in": "path",
|
||
"required": true,
|
||
"type": "string",
|
||
"description": "UUID задачи"
|
||
}
|
||
],
|
||
"get": {
|
||
"tags": ["spr"],
|
||
"summary": "Статус задачи",
|
||
"description": "При `status=ready` и каталоге в waiting — промоут в ready. `message=Success` когда готово.",
|
||
"operationId": "sprGetQueueTask",
|
||
"responses": {
|
||
"200": {
|
||
"description": "OK",
|
||
"schema": { "$ref": "#/definitions/queueStatus" }
|
||
},
|
||
"404": {
|
||
"description": "Задача не найдена или ошибка транскрипции",
|
||
"schema": { "$ref": "#/definitions/apiError" }
|
||
}
|
||
}
|
||
},
|
||
"delete": {
|
||
"tags": ["spr"],
|
||
"summary": "Удалить задачу и каталог кэша",
|
||
"operationId": "sprDeleteQueueTask",
|
||
"responses": {
|
||
"200": {
|
||
"description": "OK",
|
||
"schema": { "$ref": "#/definitions/apiSuccess" }
|
||
},
|
||
"404": {
|
||
"description": "TaskNotFound",
|
||
"schema": { "$ref": "#/definitions/apiError" }
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"/spr/stt/{id}": {
|
||
"parameters": [
|
||
{
|
||
"name": "id",
|
||
"in": "path",
|
||
"required": true,
|
||
"type": "string",
|
||
"description": "ID модели (имя файла без .bin, например ggml-small)"
|
||
}
|
||
],
|
||
"post": {
|
||
"tags": ["spr"],
|
||
"summary": "Транскрипция аудио",
|
||
"description": "Загрузка multipart: одно из полей `audio`, `wav`, `file`. Аудио конвертируется в 16 kHz mono WAV. По умолчанию async (`async=1`) — ответ `taskID`; при `async=0` — синхронный JSON с `text` и `words`.",
|
||
"operationId": "sprTranscribe",
|
||
"consumes": ["multipart/form-data"],
|
||
"parameters": [
|
||
{
|
||
"name": "audio",
|
||
"in": "formData",
|
||
"type": "file",
|
||
"description": "Аудиофайл (wav, mp3, flac, ogg, m4a, mp4, aac)"
|
||
},
|
||
{
|
||
"name": "wav",
|
||
"in": "formData",
|
||
"type": "file",
|
||
"description": "Алиас для audio"
|
||
},
|
||
{
|
||
"name": "file",
|
||
"in": "formData",
|
||
"type": "file",
|
||
"description": "Алиас для audio"
|
||
},
|
||
{
|
||
"name": "async",
|
||
"in": "query",
|
||
"type": "integer",
|
||
"enum": [0, 1],
|
||
"default": 1,
|
||
"description": "1 — поставить в очередь; 0 — синхронный ответ"
|
||
},
|
||
{
|
||
"name": "language",
|
||
"in": "query",
|
||
"type": "string",
|
||
"description": "Язык распознавания (по умолчанию из config `api.language`, обычно ru). Значение `auto` — автоопределение whisper."
|
||
},
|
||
{
|
||
"name": "punctuation",
|
||
"in": "query",
|
||
"type": "integer",
|
||
"enum": [0, 1],
|
||
"description": "Восстановление пунктуации (если `punctuation.enabled` в config)"
|
||
},
|
||
{
|
||
"name": "speakers",
|
||
"in": "query",
|
||
"type": "integer",
|
||
"enum": [0, 1],
|
||
"description": "1 — диаризация и метки «Спикер N:» (нужен build-sherpa и модели)"
|
||
},
|
||
{
|
||
"name": "speaker_counter",
|
||
"in": "query",
|
||
"type": "integer",
|
||
"description": "Число спикеров: 0 — авто, -1 — отключить диаризацию для запроса, N>0 — подсказка"
|
||
}
|
||
],
|
||
"responses": {
|
||
"200": {
|
||
"description": "Синхронный результат или taskID (async)",
|
||
"schema": { "$ref": "#/definitions/sttResponse" }
|
||
},
|
||
"400": {
|
||
"description": "Нет файла / неверные параметры",
|
||
"schema": { "$ref": "#/definitions/apiError" }
|
||
},
|
||
"404": {
|
||
"description": "Модель не найдена",
|
||
"schema": { "$ref": "#/definitions/apiError" }
|
||
},
|
||
"405": {
|
||
"description": "Ошибка транскрипции (sync)",
|
||
"schema": { "$ref": "#/definitions/apiError" }
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"/spr/result/{taskID}": {
|
||
"parameters": [
|
||
{
|
||
"name": "taskID",
|
||
"in": "path",
|
||
"required": true,
|
||
"type": "string"
|
||
}
|
||
],
|
||
"get": {
|
||
"tags": ["spr"],
|
||
"summary": "Результат async-задачи",
|
||
"operationId": "sprGetResult",
|
||
"responses": {
|
||
"200": {
|
||
"description": "waiting/processing/ready",
|
||
"schema": { "$ref": "#/definitions/resultResponse" }
|
||
},
|
||
"404": {
|
||
"description": "TaskNotFound или ошибка",
|
||
"schema": { "$ref": "#/definitions/apiError" }
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"/spr/audio/{taskID}": {
|
||
"parameters": [
|
||
{
|
||
"name": "taskID",
|
||
"in": "path",
|
||
"required": true,
|
||
"type": "string"
|
||
}
|
||
],
|
||
"get": {
|
||
"tags": ["spr"],
|
||
"summary": "WAV задачи (16 kHz mono)",
|
||
"operationId": "sprGetTaskAudio",
|
||
"produces": ["audio/wav"],
|
||
"responses": {
|
||
"200": { "description": "Файл audio.wav" },
|
||
"404": {
|
||
"description": "Задача не найдена",
|
||
"schema": { "$ref": "#/definitions/plainError" }
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"/spr/waveform/{taskID}": {
|
||
"parameters": [
|
||
{
|
||
"name": "taskID",
|
||
"in": "path",
|
||
"required": true,
|
||
"type": "string"
|
||
}
|
||
],
|
||
"get": {
|
||
"tags": ["spr"],
|
||
"summary": "Пики waveform для UI",
|
||
"operationId": "sprGetWaveform",
|
||
"responses": {
|
||
"200": {
|
||
"description": "OK",
|
||
"schema": { "$ref": "#/definitions/waveformResponse" }
|
||
},
|
||
"400": {
|
||
"description": "Неверный taskID",
|
||
"schema": { "$ref": "#/definitions/apiError" }
|
||
},
|
||
"500": {
|
||
"description": "Ошибка чтения audio.json",
|
||
"schema": { "$ref": "#/definitions/plainError" }
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"/spr/import/{id}": {
|
||
"parameters": [
|
||
{
|
||
"name": "id",
|
||
"in": "path",
|
||
"required": true,
|
||
"type": "string",
|
||
"description": "ID новой модели (имя файла без расширения)"
|
||
}
|
||
],
|
||
"post": {
|
||
"tags": ["spr"],
|
||
"summary": "Загрузить модель .bin",
|
||
"operationId": "sprImportModel",
|
||
"consumes": ["multipart/form-data"],
|
||
"parameters": [
|
||
{
|
||
"name": "zip-model",
|
||
"in": "formData",
|
||
"type": "file",
|
||
"required": true,
|
||
"description": "Файл модели ggml (*.bin). ZIP не поддерживается."
|
||
},
|
||
{
|
||
"name": "model",
|
||
"in": "formData",
|
||
"type": "file",
|
||
"description": "Алиас для zip-model"
|
||
}
|
||
],
|
||
"responses": {
|
||
"200": { "description": "Модель сохранена" },
|
||
"400": {
|
||
"description": "Нет файла / zip / ошибка записи",
|
||
"schema": { "$ref": "#/definitions/apiError" }
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"/spr/export/{id}": {
|
||
"parameters": [
|
||
{
|
||
"name": "id",
|
||
"in": "path",
|
||
"required": true,
|
||
"type": "string"
|
||
}
|
||
],
|
||
"get": {
|
||
"tags": ["spr"],
|
||
"summary": "Скачать модель .bin",
|
||
"operationId": "sprExportModel",
|
||
"produces": ["application/octet-stream"],
|
||
"responses": {
|
||
"200": { "description": "Бинарный файл модели" },
|
||
"404": {
|
||
"description": "Модель не найдена",
|
||
"schema": { "$ref": "#/definitions/apiError" }
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"/spr/delete/{id}": {
|
||
"parameters": [
|
||
{
|
||
"name": "id",
|
||
"in": "path",
|
||
"required": true,
|
||
"type": "string"
|
||
}
|
||
],
|
||
"delete": {
|
||
"tags": ["spr"],
|
||
"summary": "Удалить файл модели",
|
||
"operationId": "sprDeleteModel",
|
||
"responses": {
|
||
"200": { "description": "OK" },
|
||
"404": {
|
||
"description": "Модель не найдена",
|
||
"schema": { "$ref": "#/definitions/apiError" }
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"/v1/models": {
|
||
"get": {
|
||
"tags": ["openai"],
|
||
"summary": "Список моделей (OpenAI format)",
|
||
"operationId": "openaiListModels",
|
||
"responses": {
|
||
"200": {
|
||
"description": "OK",
|
||
"schema": { "$ref": "#/definitions/openAIModelList" }
|
||
},
|
||
"500": {
|
||
"description": "server_error",
|
||
"schema": { "$ref": "#/definitions/openAIError" }
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"/v1/audio/transcriptions": {
|
||
"post": {
|
||
"tags": ["openai"],
|
||
"summary": "Транскрипция (OpenAI Whisper)",
|
||
"description": "Всегда синхронно. Поле `model`: id локальной модели или `whisper-1` (маппинг через `api.default_model`). Пунктуация: query `?punctuation=1`.",
|
||
"operationId": "openaiTranscribe",
|
||
"consumes": ["multipart/form-data"],
|
||
"parameters": [
|
||
{
|
||
"name": "file",
|
||
"in": "formData",
|
||
"type": "file",
|
||
"required": true,
|
||
"description": "Аудио (алиасы: audio, wav)"
|
||
},
|
||
{
|
||
"name": "model",
|
||
"in": "formData",
|
||
"type": "string",
|
||
"required": true,
|
||
"description": "Например whisper-1 или ggml-large-v3-turbo"
|
||
},
|
||
{
|
||
"name": "language",
|
||
"in": "formData",
|
||
"type": "string",
|
||
"description": "Код языка (ru, en, auto)"
|
||
},
|
||
{
|
||
"name": "response_format",
|
||
"in": "formData",
|
||
"type": "string",
|
||
"enum": ["json", "text"],
|
||
"default": "json",
|
||
"description": "json — {\"text\":\"...\"}; text — plain body"
|
||
},
|
||
{
|
||
"name": "punctuation",
|
||
"in": "query",
|
||
"type": "integer",
|
||
"enum": [0, 1],
|
||
"description": "Пунктуация (как в SPR)"
|
||
}
|
||
],
|
||
"responses": {
|
||
"200": {
|
||
"description": "Транскрипт",
|
||
"schema": { "$ref": "#/definitions/openAITranscription" }
|
||
},
|
||
"400": {
|
||
"description": "invalid_request_error",
|
||
"schema": { "$ref": "#/definitions/openAIError" }
|
||
},
|
||
"500": {
|
||
"description": "server_error",
|
||
"schema": { "$ref": "#/definitions/openAIError" }
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"/v1/audio/transcriptions/": {
|
||
"post": {
|
||
"tags": ["openai"],
|
||
"summary": "Транскрипция (trailing slash)",
|
||
"operationId": "openaiTranscribeSlash",
|
||
"consumes": ["multipart/form-data"],
|
||
"parameters": [
|
||
{ "$ref": "#/parameters/openAIFile" },
|
||
{ "$ref": "#/parameters/openAIModel" }
|
||
],
|
||
"responses": {
|
||
"200": {
|
||
"description": "Транскрипт",
|
||
"schema": { "$ref": "#/definitions/openAITranscription" }
|
||
},
|
||
"400": {
|
||
"schema": { "$ref": "#/definitions/openAIError" }
|
||
},
|
||
"500": {
|
||
"schema": { "$ref": "#/definitions/openAIError" }
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"parameters": {
|
||
"openAIFile": {
|
||
"name": "file",
|
||
"in": "formData",
|
||
"type": "file",
|
||
"required": true
|
||
},
|
||
"openAIModel": {
|
||
"name": "model",
|
||
"in": "formData",
|
||
"type": "string",
|
||
"required": true
|
||
}
|
||
},
|
||
"definitions": {
|
||
"modelList": {
|
||
"type": "object",
|
||
"properties": {
|
||
"models": {
|
||
"type": "array",
|
||
"items": { "type": "string" },
|
||
"description": "ID моделей"
|
||
}
|
||
}
|
||
},
|
||
"hostnameInfo": {
|
||
"type": "object",
|
||
"properties": {
|
||
"error": { "type": "integer", "example": 0 },
|
||
"message": { "type": "string", "example": "Success" },
|
||
"hostname": { "type": "string" },
|
||
"version": { "type": "string", "example": "go-whisper-api" },
|
||
"cwd": { "type": "string" },
|
||
"models": { "type": "string", "description": "api.models_dir" },
|
||
"cache": { "type": "string", "description": "api.cache_dir" }
|
||
}
|
||
},
|
||
"queueList": {
|
||
"type": "object",
|
||
"additionalProperties": {
|
||
"type": "object",
|
||
"properties": {
|
||
"created": { "type": "string", "description": "2006-01-02 15:04:05" },
|
||
"status": {
|
||
"type": "string",
|
||
"enum": ["waiting", "processing", "ready", "error"]
|
||
}
|
||
}
|
||
},
|
||
"description": "Ключ — taskID"
|
||
},
|
||
"queueStatus": {
|
||
"type": "object",
|
||
"properties": {
|
||
"error": { "type": "integer" },
|
||
"message": { "type": "string", "description": "Success, waiting, processing, …" },
|
||
"status": { "type": "string" }
|
||
}
|
||
},
|
||
"apiSuccess": {
|
||
"type": "object",
|
||
"properties": {
|
||
"error": { "type": "integer", "example": 0 },
|
||
"message": { "type": "string", "example": "Success" }
|
||
}
|
||
},
|
||
"apiError": {
|
||
"type": "object",
|
||
"properties": {
|
||
"error": { "type": "integer", "example": 1 },
|
||
"message": { "type": "string" }
|
||
}
|
||
},
|
||
"plainError": {
|
||
"type": "string",
|
||
"description": "Текст ошибки (http.Error)"
|
||
},
|
||
"sttResponse": {
|
||
"type": "object",
|
||
"description": "async: только taskID; sync: model, text, words",
|
||
"properties": {
|
||
"taskID": { "type": "string" },
|
||
"model": { "type": "string" },
|
||
"text": { "type": "string" },
|
||
"words": {
|
||
"type": "array",
|
||
"items": { "$ref": "#/definitions/wordToken" }
|
||
}
|
||
}
|
||
},
|
||
"resultResponse": {
|
||
"type": "object",
|
||
"properties": {
|
||
"status": {
|
||
"type": "string",
|
||
"enum": ["waiting", "processing", "ready", "error"],
|
||
"description": "Краткий статус (не готово)"
|
||
},
|
||
"model": { "type": "string" },
|
||
"text": { "type": "string" },
|
||
"words": {
|
||
"type": "array",
|
||
"items": { "$ref": "#/definitions/wordToken" }
|
||
},
|
||
"taskID": { "type": "string" },
|
||
"created": { "type": "string" },
|
||
"processed": { "type": "string" },
|
||
"toxicity": { "$ref": "#/definitions/toxicityStub" },
|
||
"emotion": { "type": "object" },
|
||
"voice_analysis": { "type": "object" }
|
||
}
|
||
},
|
||
"wordToken": {
|
||
"type": "object",
|
||
"properties": {
|
||
"word": { "type": "string" },
|
||
"start": { "type": "integer", "description": "мс" },
|
||
"stop": { "type": "integer", "description": "мс" }
|
||
}
|
||
},
|
||
"toxicityStub": {
|
||
"type": "object",
|
||
"description": "Заглушка SPR (всегда нули)",
|
||
"properties": {
|
||
"insult": { "type": "number" },
|
||
"obscenity": { "type": "number" },
|
||
"threat": { "type": "number" },
|
||
"politeness": { "type": "number" }
|
||
}
|
||
},
|
||
"waveformResponse": {
|
||
"type": "object",
|
||
"properties": {
|
||
"error": { "type": "integer", "example": 0 },
|
||
"waveform": {
|
||
"type": "array",
|
||
"items": { "type": "number" },
|
||
"description": "Пики для отрисовки"
|
||
}
|
||
}
|
||
},
|
||
"openAIModelList": {
|
||
"type": "object",
|
||
"properties": {
|
||
"object": { "type": "string", "example": "list" },
|
||
"data": {
|
||
"type": "array",
|
||
"items": { "$ref": "#/definitions/openAIModel" }
|
||
}
|
||
}
|
||
},
|
||
"openAIModel": {
|
||
"type": "object",
|
||
"properties": {
|
||
"id": { "type": "string" },
|
||
"object": { "type": "string", "example": "model" },
|
||
"created": { "type": "integer" },
|
||
"owned_by": { "type": "string", "example": "go-whisper-api" }
|
||
}
|
||
},
|
||
"openAITranscription": {
|
||
"type": "object",
|
||
"properties": {
|
||
"text": { "type": "string" }
|
||
}
|
||
},
|
||
"openAIError": {
|
||
"type": "object",
|
||
"properties": {
|
||
"error": {
|
||
"type": "object",
|
||
"properties": {
|
||
"message": { "type": "string" },
|
||
"type": {
|
||
"type": "string",
|
||
"enum": [
|
||
"invalid_request_error",
|
||
"authentication_error",
|
||
"not_found_error",
|
||
"server_error"
|
||
]
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|