2025-03-12 09:30:24 +01:00
#include "arg.h"
2026-01-14 20:29:35 +01:00
#include "debug.h"
2025-03-12 09:30:24 +01:00
#include "log.h"
#include "common.h"
#include "sampling.h"
#include "llama.h"
#include "ggml.h"
#include "console.h"
2025-04-10 22:57:16 +02:00
#include "chat.h"
#include "mtmd.h"
2025-05-28 22:35:22 +02:00
#include "mtmd-helper.h"
2025-03-12 09:30:24 +01:00
#include <vector>
#include <limits.h>
2025-04-05 23:46:00 +08:00
#include <cinttypes>
2026-03-04 14:18:04 +01:00
#include <clocale>
2025-03-12 09:30:24 +01:00
#if defined (__unix__) || (defined (__APPLE__) && defined (__MACH__))
#include <signal.h>
#include <unistd.h>
#elif defined (_WIN32)
#define WIN32_LEAN_AND_MEAN
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#include <signal.h>
#endif
2025-04-24 02:32:35 +05:00
// volatile, because of signal being an interrupt
static volatile bool g_is_generating = false ;
static volatile bool g_is_interrupted = false ;
2025-03-12 09:30:24 +01:00
/**
2026-06-18 21:55:04 +02:00
* Please note that this is NOT a production-ready binary.
2025-04-21 15:32:58 +02:00
* It is a playground for trying multimodal support in llama.cpp.
2026-06-18 21:55:04 +02:00
* For contributors: please keep this code simple and easy to understand. Do not add unnecessary complexity. The goal is to have a simple CLI for testing multimodal support.
2025-03-12 09:30:24 +01:00
*/
static void show_additional_info ( int /*argc*/ , char ** argv ) {
LOG (
2025-04-21 15:32:58 +02:00
"Experimental CLI for multimodal \n\n "
2025-05-22 20:42:48 +02:00
"Usage: %s [options] -m <model> --mmproj <mmproj> --image <image> --audio <audio> -p <prompt> \n\n "
2025-03-12 09:30:24 +01:00
" -m and --mmproj are required \n "
2025-04-21 15:32:58 +02:00
" -hf user/repo can replace both -m and --mmproj in most cases \n "
2025-05-22 20:42:48 +02:00
" --image, --audio and -p are optional, if NOT provided, the CLI will run in chat mode \n "
2025-04-24 14:04:14 +02:00
" to disable using GPU for mmproj model, add --no-mmproj-offload \n " ,
2025-03-12 09:30:24 +01:00
argv [ 0 ]
);
}
#if defined (__unix__) || (defined (__APPLE__) && defined (__MACH__)) || defined (_WIN32)
static void sigint_handler ( int signo ) {
if ( signo == SIGINT ) {
if ( g_is_generating ) {
g_is_generating = false ;
} else {
console :: cleanup ();
2025-04-24 02:32:35 +05:00
if ( g_is_interrupted ) {
_exit ( 1 );
}
g_is_interrupted = true ;
2025-03-12 09:30:24 +01:00
}
}
}
#endif
2026-06-18 21:55:04 +02:00
// this is only used by tests.sh to capture the response ; it's not meant to be used in production
static void inject_test_response_marker () {
const char * env = std :: getenv ( "MTMD_TEST_RESPONSE_MARKER" );
if ( env ) {
LOG ( "%s \n " , env );
}
}
2025-04-21 15:32:58 +02:00
struct mtmd_cli_context {
2025-05-04 23:43:42 +02:00
mtmd :: context_ptr ctx_vision ;
2025-12-14 10:11:13 +02:00
common_init_result_ptr llama_init ;
2025-03-12 09:30:24 +01:00
llama_model * model ;
llama_context * lctx ;
const llama_vocab * vocab ;
2025-06-02 16:29:28 +02:00
common_sampler * smpl ;
2025-03-12 09:30:24 +01:00
llama_batch batch ;
2025-04-10 22:57:16 +02:00
int n_batch ;
2025-05-04 23:43:42 +02:00
mtmd :: bitmaps bitmaps ;
2026-06-08 13:40:12 +02:00
std :: vector < mtmd_helper :: video_ptr > videos ;
2025-05-02 14:20:27 +06:00
2026-06-18 21:55:04 +02:00
mtmd :: batch_ptr mbatch ;
2025-10-23 15:00:49 +02:00
// chat template
2025-04-10 22:57:16 +02:00
common_chat_templates_ptr tmpls ;
2025-10-23 15:00:49 +02:00
std :: vector < common_chat_msg > chat_history ;
bool use_jinja = false ;
// TODO: support for --system-prompt with /clear command
2025-03-12 09:30:24 +01:00
2025-04-21 15:32:58 +02:00
// support for legacy templates (models not having EOT token)
llama_tokens antiprompt_tokens ;
2025-03-12 09:30:24 +01:00
int n_threads = 1 ;
llama_pos n_past = 0 ;
2026-04-26 22:06:39 -07:00
common_debug_cb_user_data cb_data ;
2026-01-14 20:29:35 +01:00
2025-04-21 15:32:58 +02:00
mtmd_cli_context ( common_params & params ) : llama_init ( common_init_from_params ( params )) {
2025-12-14 10:11:13 +02:00
model = llama_init -> model ();
lctx = llama_init -> context ();
2025-03-12 09:30:24 +01:00
vocab = llama_model_get_vocab ( model );
2025-06-02 16:29:28 +02:00
smpl = common_sampler_init ( model , params . sampling );
2025-03-12 09:30:24 +01:00
n_threads = params . cpuparams . n_threads ;
2025-06-02 16:29:28 +02:00
batch = llama_batch_init ( 1 , 0 , 1 ); // batch for next token generation
2025-04-10 22:57:16 +02:00
n_batch = params . n_batch ;
2025-04-21 15:32:58 +02:00
2025-05-05 12:54:44 +02:00
if ( ! model || ! lctx ) {
exit ( 1 );
}
2025-04-21 15:32:58 +02:00
if ( ! llama_model_chat_template ( model , nullptr ) && params . chat_template . empty ()) {
LOG_ERR ( "Model does not have chat template. \n " );
LOG_ERR ( " For old llava models, you may need to use '--chat-template vicuna' \n " );
LOG_ERR ( " For MobileVLM models, use '--chat-template deepseek' \n " );
2025-05-01 17:05:42 +02:00
LOG_ERR ( " For Mistral Small 3.1, use '--chat-template mistral-v7' \n " );
2025-04-21 15:32:58 +02:00
exit ( 1 );
}
2025-04-10 22:57:16 +02:00
tmpls = common_chat_templates_init ( model , params . chat_template );
2025-10-23 15:00:49 +02:00
use_jinja = params . use_jinja ;
chat_history . clear ();
2025-08-14 10:28:29 -07:00
LOG_INF ( "%s: chat template example: \n %s \n " , __func__ , common_chat_format_example ( tmpls . get (), params . use_jinja , params . default_template_kwargs ). c_str ());
2025-04-21 15:32:58 +02:00
2025-04-10 22:57:16 +02:00
init_vision_context ( params );
2025-04-21 15:32:58 +02:00
// load antiprompt tokens for legacy templates
if ( params . chat_template == "vicuna" ) {
antiprompt_tokens = common_tokenize ( lctx , "ASSISTANT:" , false , true );
} else if ( params . chat_template == "deepseek" ) {
antiprompt_tokens = common_tokenize ( lctx , "###" , false , true );
}
2025-03-12 09:30:24 +01:00
}
2025-06-02 16:29:28 +02:00
~ mtmd_cli_context () {
llama_batch_free ( batch );
common_sampler_free ( smpl );
}
2025-04-10 22:57:16 +02:00
void init_vision_context ( common_params & params ) {
2025-04-01 23:44:05 +02:00
const char * clip_path = params . mmproj . path . c_str ();
2025-05-04 23:43:42 +02:00
mtmd_context_params mparams = mtmd_context_params_default ();
2025-11-03 11:11:18 +01:00
mparams . use_gpu = params . mmproj_use_gpu ;
mparams . print_timings = true ;
mparams . n_threads = params . cpuparams . n_threads ;
mparams . flash_attn_type = params . flash_attn_type ;
2025-12-01 21:32:25 +01:00
mparams . warmup = params . warmup ;
2025-11-03 11:11:18 +01:00
mparams . image_min_tokens = params . image_min_tokens ;
mparams . image_max_tokens = params . image_max_tokens ;
2026-01-14 20:29:35 +01:00
if ( std :: getenv ( "MTMD_DEBUG_GRAPH" ) != nullptr ) {
mparams . cb_eval_user_data = & cb_data ;
2026-04-26 22:06:39 -07:00
mparams . cb_eval = common_debug_cb_eval ;
2026-01-14 20:29:35 +01:00
}
2025-05-04 23:43:42 +02:00
ctx_vision . reset ( mtmd_init_from_file ( clip_path , model , mparams ));
2025-04-10 22:57:16 +02:00
if ( ! ctx_vision . get ()) {
LOG_ERR ( "Failed to load vision model from %s \n " , clip_path );
2025-04-05 17:17:40 +02:00
exit ( 1 );
}
2025-03-12 09:30:24 +01:00
}
2025-04-21 15:32:58 +02:00
bool check_antiprompt ( const llama_tokens & generated_tokens ) {
if ( antiprompt_tokens . empty () || generated_tokens . size () < antiprompt_tokens . size ()) {
return false ;
}
return std :: equal (
generated_tokens . end () - antiprompt_tokens . size (),
generated_tokens . end (),
antiprompt_tokens . begin ()
);
}
2025-05-02 14:20:27 +06:00
2025-05-22 20:42:48 +02:00
bool load_media ( const std :: string & fname ) {
2026-06-08 13:40:12 +02:00
auto res = mtmd_helper_bitmap_init_from_file ( ctx_vision . get (), fname . c_str (), false );
if ( ! res . bitmap ) {
2025-05-02 14:20:27 +06:00
return false ;
}
2026-06-08 13:40:12 +02:00
bitmaps . entries . emplace_back ( res . bitmap );
if ( res . video_ctx ) {
videos . emplace_back ( res . video_ctx );
}
2025-05-02 14:20:27 +06:00
return true ;
}
2025-03-12 09:30:24 +01:00
};
2025-06-02 16:29:28 +02:00
static int generate_response ( mtmd_cli_context & ctx , int n_predict ) {
2025-04-21 15:32:58 +02:00
llama_tokens generated_tokens ;
2025-03-12 09:30:24 +01:00
for ( int i = 0 ; i < n_predict ; i ++ ) {
2025-04-24 02:32:35 +05:00
if ( i > n_predict || ! g_is_generating || g_is_interrupted ) {
2025-05-02 14:20:27 +06:00
LOG ( " \n " );
2025-03-12 09:30:24 +01:00
break ;
}
2025-06-02 16:29:28 +02:00
llama_token token_id = common_sampler_sample ( ctx . smpl , ctx . lctx , - 1 );
2025-04-21 15:32:58 +02:00
generated_tokens . push_back ( token_id );
2025-06-02 16:29:28 +02:00
common_sampler_accept ( ctx . smpl , token_id , true );
2025-03-12 09:30:24 +01:00
2025-04-21 15:32:58 +02:00
if ( llama_vocab_is_eog ( ctx . vocab , token_id ) || ctx . check_antiprompt ( generated_tokens )) {
2025-05-02 14:20:27 +06:00
LOG ( " \n " );
2025-03-12 09:30:24 +01:00
break ; // end of generation
}
2025-05-02 14:20:27 +06:00
LOG ( "%s" , common_token_to_piece ( ctx . lctx , token_id ). c_str ());
2025-03-12 09:30:24 +01:00
fflush ( stdout );
2025-04-24 02:32:35 +05:00
if ( g_is_interrupted ) {
2025-05-02 14:20:27 +06:00
LOG ( " \n " );
2025-04-24 02:32:35 +05:00
break ;
}
2025-03-12 09:30:24 +01:00
// eval the token
common_batch_clear ( ctx . batch );
common_batch_add ( ctx . batch , token_id , ctx . n_past ++ , { 0 }, true );
if ( llama_decode ( ctx . lctx , ctx . batch )) {
LOG_ERR ( "failed to decode token \n " );
return 1 ;
}
}
2025-10-23 15:00:49 +02:00
std :: string generated_text = common_detokenize ( ctx . lctx , generated_tokens );
common_chat_msg msg ;
msg . role = "assistant" ;
msg . content = generated_text ;
ctx . chat_history . push_back ( std :: move ( msg ));
2025-03-12 09:30:24 +01:00
return 0 ;
}
2025-10-23 15:00:49 +02:00
static std :: string chat_add_and_format ( mtmd_cli_context & ctx , common_chat_msg & new_msg ) {
LOG_DBG ( "chat_add_and_format: new_msg.role='%s', new_msg.content='%s' \n " ,
new_msg . role . c_str (), new_msg . content . c_str ());
auto formatted = common_chat_format_single ( ctx . tmpls . get (), ctx . chat_history ,
new_msg , new_msg . role == "user" ,
ctx . use_jinja );
ctx . chat_history . push_back ( new_msg );
return formatted ;
}
static int eval_message ( mtmd_cli_context & ctx , common_chat_msg & msg ) {
2026-06-18 21:55:04 +02:00
inject_test_response_marker ();
2025-10-23 15:00:49 +02:00
bool add_bos = ctx . chat_history . empty ();
auto formatted_chat = chat_add_and_format ( ctx , msg );
LOG_DBG ( "formatted_chat.prompt: %s \n " , formatted_chat . c_str ());
2025-04-10 22:57:16 +02:00
mtmd_input_text text ;
2026-07-13 00:47:25 +02:00
text . text = formatted_chat . data ();
text . text_len = formatted_chat . size ();
2025-04-10 22:57:16 +02:00
text . add_special = add_bos ;
text . parse_special = true ;
2025-04-24 02:32:35 +05:00
if ( g_is_interrupted ) return 0 ;
2025-05-04 23:43:42 +02:00
mtmd :: input_chunks chunks ( mtmd_input_chunks_init ());
auto bitmaps_c_ptr = ctx . bitmaps . c_ptr ();
int32_t res = mtmd_tokenize ( ctx . ctx_vision . get (),
chunks . ptr . get (), // output
& text , // text
bitmaps_c_ptr . data (),
bitmaps_c_ptr . size ());
2025-04-18 10:04:51 +02:00
if ( res != 0 ) {
LOG_ERR ( "Unable to tokenize prompt, res = %d \n " , res );
2025-04-10 22:57:16 +02:00
return 1 ;
}
2025-05-04 23:43:42 +02:00
ctx . bitmaps . entries . clear ();
2026-06-08 13:40:12 +02:00
ctx . videos . clear ();
2025-05-02 14:20:27 +06:00
2026-06-18 21:55:04 +02:00
// batch encode all media chunks, then decode each
size_t n_chunks = mtmd_input_chunks_size ( chunks . ptr . get ());
for ( size_t i = 0 ; i < n_chunks ; i ++ ) {
auto chunk = mtmd_input_chunks_get ( chunks . ptr . get (), i );
auto chunk_type = mtmd_input_chunk_get_type ( chunk );
2025-04-10 22:57:16 +02:00
2026-06-18 21:55:04 +02:00
if ( chunk_type == MTMD_INPUT_CHUNK_TYPE_TEXT ) {
// decode text chunk
llama_pos new_n_past = ctx . n_past ;
res = mtmd_helper_eval_chunk_single ( ctx . ctx_vision . get (),
ctx . lctx ,
chunk ,
ctx . n_past ,
0 , // seq_id
ctx . n_batch ,
i == n_chunks - 1 , // logits_last
& new_n_past );
if ( res != 0 ) {
LOG_ERR ( "Unable to eval text chunk %zu \n " , i );
return 1 ;
}
ctx . n_past = new_n_past ;
} else {
// media chunk: try to get embd from existing batch, or create a new batch
float * embd = nullptr ;
if ( ctx . mbatch ) {
embd = mtmd_batch_get_output_embd ( ctx . mbatch . get (), chunk );
if ( embd ) {
LOG_DBG ( "found embd for media chunk %zu in existing batch \n " , i );
} else {
LOG_DBG ( "media chunk %zu not found in existing batch, creating new batch \n " , i );
}
}
if ( ! embd ) {
// create and encode a new batch with as many media chunks as possible
ctx . mbatch . reset ( mtmd_batch_init ( ctx . ctx_vision . get ()));
res = mtmd_batch_add_chunk ( ctx . mbatch . get (), chunk );
GGML_ASSERT ( res == 0 ); // first chunk must always succeed
int n_added = 1 ;
// add as many subsequent media chunks as possible
for ( size_t j = i + 1 ; j < n_chunks ; j ++ ) {
auto next_chunk = mtmd_input_chunks_get ( chunks . ptr . get (), j );
auto next_type = mtmd_input_chunk_get_type ( next_chunk );
if ( next_type == MTMD_INPUT_CHUNK_TYPE_TEXT ) {
break ; // text chunk splits the batch
}
res = mtmd_batch_add_chunk ( ctx . mbatch . get (), next_chunk );
if ( res != 0 ) {
break ; // batch full or incompatible
}
n_added ++ ;
}
int64_t time_start = ggml_time_ms ();
LOG_INF ( "encoding mtmd batch, n_chunks = %d (done = %zu, total = %zu) \n " , n_added , i , n_chunks );
res = mtmd_batch_encode ( ctx . mbatch . get ());
if ( res != 0 ) {
LOG_ERR ( "Failed to encode mtmd batch, res = %d \n " , res );
return 1 ;
}
LOG_INF ( "mtmd batch encoding done in %d ms \n " , ( int )( ggml_time_ms () - time_start ));
embd = mtmd_batch_get_output_embd ( ctx . mbatch . get (), chunk );
}
GGML_ASSERT ( embd != nullptr );
llama_pos new_n_past = ctx . n_past ;
res = mtmd_helper_decode_image_chunk ( ctx . ctx_vision . get (),
ctx . lctx ,
chunk ,
embd ,
ctx . n_past ,
0 , // seq_id
ctx . n_batch ,
& new_n_past ,
nullptr , // callback
nullptr // user_data
);
if ( res != 0 ) {
LOG_ERR ( "Unable to decode media chunk %zu \n " , i );
return 1 ;
}
ctx . n_past = new_n_past ;
}
}
2025-04-10 22:57:16 +02:00
2025-05-02 14:20:27 +06:00
LOG ( " \n " );
2025-04-10 22:57:16 +02:00
return 0 ;
}
2025-03-12 09:30:24 +01:00
int main ( int argc , char ** argv ) {
2026-03-04 02:30:40 -06:00
std :: setlocale ( LC_NUMERIC , "C" );
2025-03-12 09:30:24 +01:00
ggml_time_init ();
common_params params ;
2026-03-31 12:53:41 +02:00
common_init ();
2025-05-22 20:42:48 +02:00
if ( ! common_params_parse ( argc , argv , params , LLAMA_EXAMPLE_MTMD , show_additional_info )) {
2025-03-12 09:30:24 +01:00
return 1 ;
}
2025-11-14 15:56:19 +01:00
mtmd_helper_log_set ( common_log_default_callback , nullptr );
2025-03-12 09:30:24 +01:00
2025-04-01 23:44:05 +02:00
if ( params . mmproj . path . empty ()) {
2025-03-12 09:30:24 +01:00
show_additional_info ( argc , argv );
2025-04-24 12:14:13 +02:00
LOG_ERR ( "ERR: Missing --mmproj argument \n " );
2025-03-12 09:30:24 +01:00
return 1 ;
}
2026-05-05 09:23:50 +02:00
ggml_backend_load_all ();
2025-04-21 15:32:58 +02:00
mtmd_cli_context ctx ( params );
2025-11-15 03:41:16 -08:00
LOG_INF ( "%s: loading model: %s \n " , __func__ , params . model . path . c_str ());
2025-03-12 09:30:24 +01:00
bool is_single_turn = ! params . prompt . empty () && ! params . image . empty ();
int n_predict = params . n_predict < 0 ? INT_MAX : params . n_predict ;
2026-06-19 22:28:38 +02:00
console :: init ( params . simple_io , params . use_color );
atexit ([]() { console :: cleanup (); });
2025-05-04 23:43:42 +02:00
// Ctrl+C handling
2025-03-12 09:30:24 +01:00
{
#if defined (__unix__) || (defined (__APPLE__) && defined (__MACH__))
struct sigaction sigint_action ;
sigint_action . sa_handler = sigint_handler ;
sigemptyset ( & sigint_action . sa_mask );
sigint_action . sa_flags = 0 ;
sigaction ( SIGINT , & sigint_action , NULL );
#elif defined (_WIN32)
auto console_ctrl_handler = + []( DWORD ctrl_type ) -> BOOL {
return ( ctrl_type == CTRL_C_EVENT ) ? ( sigint_handler ( SIGINT ), true ) : false ;
};
SetConsoleCtrlHandler ( reinterpret_cast < PHANDLER_ROUTINE > ( console_ctrl_handler ), true );
#endif
}
2025-04-24 02:32:35 +05:00
if ( g_is_interrupted ) return 130 ;
2025-12-19 00:18:01 +01:00
auto eval_system_prompt_if_present = [ & ] {
if ( params . system_prompt . empty ()) {
return 0 ;
}
common_chat_msg msg ;
msg . role = "system" ;
msg . content = params . system_prompt ;
return eval_message ( ctx , msg );
};
2025-12-10 15:28:59 +01:00
LOG_WRN ( "WARN: This is an experimental CLI for testing multimodal capability. \n " );
LOG_WRN ( " For normal use cases, please use the standard llama-cli \n " );
2025-12-19 00:18:01 +01:00
if ( eval_system_prompt_if_present ()) {
return 1 ;
}
2025-03-12 09:30:24 +01:00
if ( is_single_turn ) {
g_is_generating = true ;
2025-05-22 20:42:48 +02:00
if ( params . prompt . find ( mtmd_default_marker ()) == std :: string :: npos ) {
2025-05-27 14:06:10 +02:00
for ( size_t i = 0 ; i < params . image . size (); i ++ ) {
2025-12-10 22:20:06 +01:00
// most models require the marker before each image
// ref: https://github.com/ggml-org/llama.cpp/pull/17616
params . prompt = mtmd_default_marker () + params . prompt ;
2025-05-27 14:06:10 +02:00
}
2025-03-12 09:30:24 +01:00
}
2025-12-19 00:18:01 +01:00
2025-04-10 22:57:16 +02:00
common_chat_msg msg ;
msg . role = "user" ;
msg . content = params . prompt ;
2025-05-02 14:20:27 +06:00
for ( const auto & image : params . image ) {
2025-05-22 20:42:48 +02:00
if ( ! ctx . load_media ( image )) {
2025-05-02 14:20:27 +06:00
return 1 ; // error is already printed by libmtmd
}
}
2025-10-23 15:00:49 +02:00
if ( eval_message ( ctx , msg )) {
2025-03-12 09:30:24 +01:00
return 1 ;
}
2025-06-02 16:29:28 +02:00
if ( ! g_is_interrupted && generate_response ( ctx , n_predict )) {
2025-03-12 09:30:24 +01:00
return 1 ;
}
} else {
LOG ( " \n Running in chat mode, available commands:" );
2025-05-22 20:42:48 +02:00
if ( mtmd_support_vision ( ctx . ctx_vision . get ())) {
LOG ( " \n /image <path> load an image" );
}
if ( mtmd_support_audio ( ctx . ctx_vision . get ())) {
LOG ( " \n /audio <path> load an audio" );
}
2026-06-08 13:40:12 +02:00
if ( mtmd_helper_support_video ( ctx . ctx_vision . get ())) {
LOG ( " \n /video <path> load a video" );
}
2025-03-12 09:30:24 +01:00
LOG ( " \n /clear clear the chat history" );
LOG ( " \n /quit or /exit exit the program" );
LOG ( " \n " );
2025-04-10 22:57:16 +02:00
std :: string content ;
2025-03-12 09:30:24 +01:00
2025-04-24 02:32:35 +05:00
while ( ! g_is_interrupted ) {
2025-03-12 09:30:24 +01:00
g_is_generating = false ;
LOG ( " \n > " );
2025-12-10 15:28:59 +01:00
console :: set_display ( DISPLAY_TYPE_USER_INPUT );
2025-03-12 09:30:24 +01:00
std :: string line ;
console :: readline ( line , false );
2025-04-24 02:32:35 +05:00
if ( g_is_interrupted ) break ;
2025-12-10 15:28:59 +01:00
console :: set_display ( DISPLAY_TYPE_RESET );
2025-03-12 09:30:24 +01:00
line = string_strip ( line );
if ( line . empty ()) {
continue ;
}
if ( line == "/quit" || line == "/exit" ) {
break ;
}
if ( line == "/clear" ) {
ctx . n_past = 0 ;
2025-10-23 15:00:49 +02:00
ctx . chat_history . clear ();
llama_memory_clear ( llama_get_memory ( ctx . lctx ), true );
2025-12-19 00:18:01 +01:00
if ( eval_system_prompt_if_present ()) {
return 1 ;
}
2025-03-12 09:30:24 +01:00
LOG ( "Chat history cleared \n\n " );
continue ;
}
g_is_generating = true ;
2025-05-22 20:42:48 +02:00
bool is_image = line == "/image" || line . find ( "/image " ) == 0 ;
bool is_audio = line == "/audio" || line . find ( "/audio " ) == 0 ;
2026-06-08 13:40:12 +02:00
bool is_video = line == "/video" || line . find ( "/video " ) == 0 ;
if ( is_image || is_audio || is_video ) {
2025-05-02 14:20:27 +06:00
if ( line . size () < 8 ) {
2025-05-22 20:42:48 +02:00
LOG_ERR ( "ERR: Missing media filename \n " );
2025-05-02 14:20:27 +06:00
continue ;
}
2025-05-22 20:42:48 +02:00
std :: string media_path = line . substr ( 7 );
if ( ctx . load_media ( media_path )) {
2026-06-08 13:40:12 +02:00
LOG ( "%s %s loaded \n " , media_path . c_str (), is_image ? "image" : is_audio ? "audio" : "video" );
2025-05-22 20:42:48 +02:00
content += mtmd_default_marker ();
2025-05-02 14:20:27 +06:00
}
// else, error is already printed by libmtmd
2025-04-10 22:57:16 +02:00
continue ;
} else {
content += line ;
}
common_chat_msg msg ;
msg . role = "user" ;
msg . content = content ;
2025-10-23 15:00:49 +02:00
int ret = eval_message ( ctx , msg );
2025-04-10 22:57:16 +02:00
if ( ret ) {
2025-03-12 09:30:24 +01:00
return 1 ;
}
2025-05-02 14:20:27 +06:00
if ( g_is_interrupted ) break ;
2025-06-02 16:29:28 +02:00
if ( generate_response ( ctx , n_predict )) {
2025-03-12 09:30:24 +01:00
return 1 ;
}
2025-04-10 22:57:16 +02:00
content . clear ();
2025-03-12 09:30:24 +01:00
}
}
2025-04-24 02:32:35 +05:00
if ( g_is_interrupted ) LOG ( " \n Interrupted by user \n " );
2025-04-29 11:47:04 +02:00
LOG ( " \n\n " );
2025-04-15 01:18:20 +08:00
llama_perf_context_print ( ctx . lctx );
2025-04-24 02:32:35 +05:00
return g_is_interrupted ? 130 : 0 ;
2025-03-12 09:30:24 +01:00
}