|
6 | 6 | #include "mtmd-helper.h" |
7 | 7 | #include "chat.h" |
8 | 8 | #include "base64.hpp" |
| 9 | +#include "http.h" |
9 | 10 |
|
10 | 11 | #include "server-common.h" |
11 | 12 |
|
@@ -907,6 +908,69 @@ static void handle_media( |
907 | 908 | } |
908 | 909 | } |
909 | 910 |
|
| 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 | + |
910 | 974 | // used by /chat/completions endpoint |
911 | 975 | json oaicompat_chat_params_parse( |
912 | 976 | json & body, /* openai api json semantics */ |
@@ -1003,18 +1067,21 @@ json oaicompat_chat_params_parse( |
1003 | 1067 | p.erase("image_url"); |
1004 | 1068 |
|
1005 | 1069 | } 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 | | - |
1010 | 1070 | json input_audio = json_value(p, "input_audio", json::object()); |
1011 | 1071 | std::string data = json_value(input_audio, "data", std::string()); |
1012 | 1072 | std::string format = json_value(input_audio, "format", std::string()); |
1013 | 1073 | // 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); |
1017 | 1075 | 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 | + |
1018 | 1085 | out_files.push_back(decoded_data); |
1019 | 1086 |
|
1020 | 1087 | // TODO: add audio_url support by reusing handle_media() |
|
0 commit comments