Skip to content

Commit 35ff935

Browse files
committed
feat(llama-cpp): forward video input to mtmd (template + non-template paths)
Wire request->videos() into grpc-server.cpp mirroring the existing image and audio handling: a video_data build + non-template files extraction, and input_video chat chunks on the tokenizer-template path. allow_video is auto-set at model load by the vendored upstream chat_params. Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
1 parent 37158c2 commit 35ff935

1 file changed

Lines changed: 71 additions & 2 deletions

File tree

backend/cpp/llama-cpp/grpc-server.cpp

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,15 @@ json parse_options(bool streaming, const backend::PredictOptions* predict, const
381381
});
382382
}
383383

384+
// for each video in the request, add the video data
385+
for (int i = 0; i < predict->videos_size(); i++) {
386+
data["video_data"].push_back(json
387+
{
388+
{"id", i},
389+
{"data", predict->videos(i)},
390+
});
391+
}
392+
384393
data["stop"] = predict->stopprompts();
385394
// data["n_probs"] = predict->nprobs();
386395
//TODO: images,
@@ -1503,7 +1512,7 @@ class BackendServiceImpl final : public backend::Backend::Service {
15031512
msg_json["role"] = msg.role();
15041513

15051514
bool is_last_user_msg = (i == last_user_msg_idx);
1506-
bool has_images_or_audio = (request->images_size() > 0 || request->audios_size() > 0);
1515+
bool has_images_or_audio = (request->images_size() > 0 || request->audios_size() > 0 || request->videos_size() > 0);
15071516

15081517
// Handle content - can be string, null, or array
15091518
// For multimodal content, we'll embed images/audio from separate fields
@@ -1554,6 +1563,16 @@ class BackendServiceImpl final : public backend::Backend::Service {
15541563
content_array.push_back(audio_chunk);
15551564
}
15561565
}
1566+
if (request->videos_size() > 0) {
1567+
for (int j = 0; j < request->videos_size(); j++) {
1568+
json video_chunk;
1569+
video_chunk["type"] = "input_video";
1570+
json input_video;
1571+
input_video["data"] = request->videos(j);
1572+
video_chunk["input_video"] = input_video;
1573+
content_array.push_back(video_chunk);
1574+
}
1575+
}
15571576
msg_json["content"] = content_array;
15581577
} else {
15591578
// Use content as-is (already array or not last user message)
@@ -1588,6 +1607,16 @@ class BackendServiceImpl final : public backend::Backend::Service {
15881607
content_array.push_back(audio_chunk);
15891608
}
15901609
}
1610+
if (request->videos_size() > 0) {
1611+
for (int j = 0; j < request->videos_size(); j++) {
1612+
json video_chunk;
1613+
video_chunk["type"] = "input_video";
1614+
json input_video;
1615+
input_video["data"] = request->videos(j);
1616+
video_chunk["input_video"] = input_video;
1617+
content_array.push_back(video_chunk);
1618+
}
1619+
}
15911620
msg_json["content"] = content_array;
15921621
} else if (msg.role() == "tool") {
15931622
// Tool role messages must have content field set, even if empty
@@ -2039,6 +2068,16 @@ class BackendServiceImpl final : public backend::Backend::Service {
20392068
files.push_back(decoded_data);
20402069
}
20412070
}
2071+
2072+
const auto &video_data = data.find("video_data");
2073+
if (video_data != data.end() && video_data->is_array())
2074+
{
2075+
for (const auto &video : *video_data)
2076+
{
2077+
auto decoded_data = base64_decode(video["data"].get<std::string>());
2078+
files.push_back(decoded_data);
2079+
}
2080+
}
20422081
}
20432082

20442083
const bool has_mtmd = ctx_server.impl->mctx != nullptr;
@@ -2291,7 +2330,7 @@ class BackendServiceImpl final : public backend::Backend::Service {
22912330
}
22922331

22932332
bool is_last_user_msg = (i == last_user_msg_idx);
2294-
bool has_images_or_audio = (request->images_size() > 0 || request->audios_size() > 0);
2333+
bool has_images_or_audio = (request->images_size() > 0 || request->audios_size() > 0 || request->videos_size() > 0);
22952334

22962335
// Handle content - can be string, null, or array
22972336
// For multimodal content, we'll embed images/audio from separate fields
@@ -2344,6 +2383,16 @@ class BackendServiceImpl final : public backend::Backend::Service {
23442383
content_array.push_back(audio_chunk);
23452384
}
23462385
}
2386+
if (request->videos_size() > 0) {
2387+
for (int j = 0; j < request->videos_size(); j++) {
2388+
json video_chunk;
2389+
video_chunk["type"] = "input_video";
2390+
json input_video;
2391+
input_video["data"] = request->videos(j);
2392+
video_chunk["input_video"] = input_video;
2393+
content_array.push_back(video_chunk);
2394+
}
2395+
}
23472396
msg_json["content"] = content_array;
23482397
} else {
23492398
// Use content as-is (already array or not last user message)
@@ -2383,6 +2432,16 @@ class BackendServiceImpl final : public backend::Backend::Service {
23832432
content_array.push_back(audio_chunk);
23842433
}
23852434
}
2435+
if (request->videos_size() > 0) {
2436+
for (int j = 0; j < request->videos_size(); j++) {
2437+
json video_chunk;
2438+
video_chunk["type"] = "input_video";
2439+
json input_video;
2440+
input_video["data"] = request->videos(j);
2441+
video_chunk["input_video"] = input_video;
2442+
content_array.push_back(video_chunk);
2443+
}
2444+
}
23862445
msg_json["content"] = content_array;
23872446
SRV_INF("[CONTENT DEBUG] Predict: Message %d created content array with media\n", i);
23882447
} else if (!msg.tool_calls().empty()) {
@@ -2845,6 +2904,16 @@ class BackendServiceImpl final : public backend::Backend::Service {
28452904
files.push_back(decoded_data);
28462905
}
28472906
}
2907+
2908+
const auto &video_data = data.find("video_data");
2909+
if (video_data != data.end() && video_data->is_array())
2910+
{
2911+
for (const auto &video : *video_data)
2912+
{
2913+
auto decoded_data = base64_decode(video["data"].get<std::string>());
2914+
files.push_back(decoded_data);
2915+
}
2916+
}
28482917
}
28492918

28502919
// process files

0 commit comments

Comments
 (0)