Skip to content

Commit 38bd2b7

Browse files
committed
Add audio transcription fallback for chat input
1 parent 9744ba6 commit 38bd2b7

3 files changed

Lines changed: 78 additions & 8 deletions

File tree

tools/server/server-common.cpp

Lines changed: 74 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include "mtmd-helper.h"
77
#include "chat.h"
88
#include "base64.hpp"
9+
#include "http.h"
910

1011
#include "server-common.h"
1112

@@ -907,6 +908,69 @@ static void handle_media(
907908
}
908909
}
909910

911+
912+
bool server_audio_transcription_enabled() {
913+
const char * url = std::getenv("LLAMA_AUDIO_TRANSCRIPTION_URL");
914+
return url != nullptr && url[0] != '\0';
915+
}
916+
917+
static std::string audio_content_type_from_format(const std::string & format) {
918+
if (format == "wav") {
919+
return "audio/wav";
920+
}
921+
if (format == "mp3") {
922+
return "audio/mpeg";
923+
}
924+
throw std::invalid_argument("input_audio.format must be either 'wav' or 'mp3'");
925+
}
926+
927+
static std::string audio_filename_from_format(const std::string & format) {
928+
return "input-audio." + format;
929+
}
930+
931+
std::string server_transcribe_audio(const std::string & data, const std::string & format) {
932+
const char * url_env = std::getenv("LLAMA_AUDIO_TRANSCRIPTION_URL");
933+
if (url_env == nullptr || url_env[0] == '\0') {
934+
throw std::runtime_error("audio input is not supported and LLAMA_AUDIO_TRANSCRIPTION_URL is not configured");
935+
}
936+
937+
const char * model_env = std::getenv("LLAMA_AUDIO_TRANSCRIPTION_MODEL");
938+
const std::string model = model_env && model_env[0] != '\0' ? model_env : "whisper-1";
939+
const std::string content_type = audio_content_type_from_format(format);
940+
941+
auto [cli, parts] = common_http_client(url_env);
942+
cli.set_connection_timeout(10, 0);
943+
cli.set_read_timeout(120, 0);
944+
cli.set_write_timeout(120, 0);
945+
946+
httplib::Headers headers;
947+
const char * api_key = std::getenv("LLAMA_AUDIO_TRANSCRIPTION_API_KEY");
948+
if (api_key != nullptr && api_key[0] != '\0') {
949+
headers.emplace("Authorization", std::string("Bearer ") + api_key);
950+
}
951+
952+
httplib::UploadFormDataItems items = {
953+
{"model", model, "", ""},
954+
{"response_format", "json", "", ""},
955+
{"file", data, audio_filename_from_format(format), content_type},
956+
};
957+
958+
auto res = cli.Post(parts.path, headers, items);
959+
if (!res) {
960+
throw std::runtime_error("audio transcription request failed: " + httplib::to_string(res.error()));
961+
}
962+
if (res->status < 200 || res->status >= 300) {
963+
throw std::runtime_error("audio transcription request failed: HTTP " + std::to_string(res->status));
964+
}
965+
966+
const json body = json::parse(res->body);
967+
const std::string text = json_value(body, "text", std::string());
968+
if (text.empty()) {
969+
throw std::runtime_error("audio transcription request failed: missing transcript text");
970+
}
971+
return text;
972+
}
973+
910974
// used by /chat/completions endpoint
911975
json oaicompat_chat_params_parse(
912976
json & body, /* openai api json semantics */
@@ -1003,18 +1067,21 @@ json oaicompat_chat_params_parse(
10031067
p.erase("image_url");
10041068

10051069
} else if (type == "input_audio") {
1006-
if (!opt.allow_audio) {
1007-
throw std::runtime_error("audio input is not supported - hint: if this is unexpected, you may need to provide the mmproj");
1008-
}
1009-
10101070
json input_audio = json_value(p, "input_audio", json::object());
10111071
std::string data = json_value(input_audio, "data", std::string());
10121072
std::string format = json_value(input_audio, "format", std::string());
10131073
// while we also support flac, we don't allow it here so we matches the OAI spec
1014-
if (format != "wav" && format != "mp3") {
1015-
throw std::invalid_argument("input_audio.format must be either 'wav' or 'mp3'");
1016-
}
1074+
audio_content_type_from_format(format);
10171075
auto decoded_data = base64_decode(data); // expected to be base64 encoded
1076+
1077+
if (!opt.allow_audio) {
1078+
const std::string transcript = server_transcribe_audio(std::string(decoded_data.begin(), decoded_data.end()), format);
1079+
p["type"] = "text";
1080+
p["text"] = "Audio transcript:\n" + transcript;
1081+
p.erase("input_audio");
1082+
continue;
1083+
}
1084+
10181085
out_files.push_back(decoded_data);
10191086

10201087
// TODO: add audio_url support by reusing handle_media()

tools/server/server-common.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,9 @@ struct server_chat_params {
303303
// used by /completions endpoint
304304
json oaicompat_completion_params_parse(const json & body);
305305

306+
bool server_audio_transcription_enabled();
307+
std::string server_transcribe_audio(const std::string & data, const std::string & format);
308+
306309
// used by /chat/completions endpoint
307310
json oaicompat_chat_params_parse(
308311
json & body, /* openai api json semantics */

tools/server/server-context.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3912,7 +3912,7 @@ void server_routes::init_routes() {
39123912
{ "model_path", meta->model_path },
39133913
{ "modalities", json {
39143914
{"vision", meta->has_inp_image},
3915-
{"audio", meta->has_inp_audio},
3915+
{"audio", meta->has_inp_audio || server_audio_transcription_enabled()},
39163916
} },
39173917
{ "media_marker", get_media_marker() },
39183918
{ "endpoint_slots", params.endpoint_slots },

0 commit comments

Comments
 (0)