Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion samples/c/text_generation/benchmark_genai_c.c
Original file line number Diff line number Diff line change
Expand Up @@ -188,4 +188,4 @@ int main(int argc, char* argv[]) {
if (results)
ov_genai_decoded_results_free(results);
return EXIT_SUCCESS;
}
}
119 changes: 108 additions & 11 deletions samples/c/text_generation/chat_sample_c.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,68 @@
#include <string.h>

#include "openvino/genai/c/llm_pipeline.h"
#include "openvino/genai/c/chat_history.h"

#define MAX_PROMPT_LENGTH 64
#define MAX_PROMPT_LENGTH 1024
#define MAX_JSON_LENGTH 4096

#define CHECK_STATUS(return_status) \
if (return_status != OK) { \
fprintf(stderr, "[ERROR] return status %d, line %d\n", return_status, __LINE__); \
goto err; \
}

#define CHECK_CHAT_HISTORY_STATUS(return_status) \
if (return_status != OV_GENAI_CHAT_HISTORY_OK) { \
fprintf(stderr, "[ERROR] chat history status %d, line %d\n", return_status, __LINE__); \
goto err; \
}

// Simple JSON escape function for strings
static void json_escape_string(const char* input, char* output, size_t output_size) {
size_t i = 0;
size_t j = 0;
while (input[i] != '\0' && j < output_size - 1) {
switch (input[i]) {
case '"':
if (j < output_size - 2) {
output[j++] = '\\';
output[j++] = '"';
}
break;
case '\\':
if (j < output_size - 2) {
output[j++] = '\\';
output[j++] = '\\';
}
break;
case '\n':
if (j < output_size - 2) {
output[j++] = '\\';
output[j++] = 'n';
}
break;
case '\r':
if (j < output_size - 2) {
output[j++] = '\\';
output[j++] = 'r';
}
break;
case '\t':
if (j < output_size - 2) {
output[j++] = '\\';
output[j++] = 't';
}
break;
default:
output[j++] = input[i];
break;
}
i++;
}
output[j] = '\0';
}

ov_genai_streaming_status_e print_callback(const char* str, void* args) {
if (str) {
// If args is not null, it needs to be cast to its actual type.
Expand All @@ -27,37 +81,80 @@ ov_genai_streaming_status_e print_callback(const char* str, void* args) {
}

int main(int argc, char* argv[]) {
if (argc != 2) {
fprintf(stderr, "Usage: %s <MODEL_DIR>\n", argv[0]);
if (argc < 2 || argc > 3) {
fprintf(stderr, "Usage: %s <MODEL_DIR> [DEVICE]\n", argv[0]);
return EXIT_FAILURE;
}
const char* models_path = argv[1];
const char* device = "CPU"; // GPU, NPU can be used as well
const char* device = (argc == 3) ? argv[2] : "CPU"; // GPU, NPU can be used as well

ov_genai_generation_config* config = NULL;
ov_genai_llm_pipeline* pipeline = NULL;
ov_genai_chat_history* chat_history = NULL;
ov_genai_decoded_results* results = NULL;
streamer_callback streamer;
streamer.callback_func = print_callback;
streamer.args = NULL;
char prompt[MAX_PROMPT_LENGTH];
char message_json[MAX_JSON_LENGTH];
char output_buffer[MAX_JSON_LENGTH];
size_t output_size = 0;
char assistant_message_json[MAX_JSON_LENGTH];
char escaped_prompt[MAX_PROMPT_LENGTH * 2]; // Escaped string may be longer
char escaped_output[MAX_JSON_LENGTH * 2]; // Escaped string may be longer

CHECK_STATUS(ov_genai_llm_pipeline_create(models_path, device, 0, &pipeline));
CHECK_STATUS(ov_genai_generation_config_create(&config));
CHECK_STATUS(ov_genai_generation_config_set_max_new_tokens(config, 100));

CHECK_STATUS(ov_genai_llm_pipeline_start_chat(pipeline));
CHECK_CHAT_HISTORY_STATUS(ov_genai_chat_history_create(&chat_history));

printf("question:\n");
while (fgets(prompt, MAX_PROMPT_LENGTH, stdin)) {
// Remove newline character
prompt[strcspn(prompt, "\n")] = 0;
CHECK_STATUS(ov_genai_llm_pipeline_generate(pipeline,
prompt,
config,
&streamer,
NULL)); // Only the streamer functionality is used here.

// Skip empty lines
if (strlen(prompt) == 0) {
continue;
}

json_escape_string(prompt, escaped_prompt, sizeof(escaped_prompt));

snprintf(message_json, sizeof(message_json),
"{\"role\": \"user\", \"content\": \"%s\"}", escaped_prompt);

CHECK_CHAT_HISTORY_STATUS(ov_genai_chat_history_push_back(chat_history, message_json));

results = NULL;
CHECK_STATUS(ov_genai_llm_pipeline_generate_with_history(pipeline,
chat_history,
config,
&streamer,
&results));

if (results) {
output_size = sizeof(output_buffer);
CHECK_STATUS(ov_genai_decoded_results_get_string(results, output_buffer, &output_size));

json_escape_string(output_buffer, escaped_output, sizeof(escaped_output));

snprintf(assistant_message_json, sizeof(assistant_message_json),
"{\"role\": \"assistant\", \"content\": \"%s\"}", escaped_output);
CHECK_CHAT_HISTORY_STATUS(ov_genai_chat_history_push_back(chat_history, assistant_message_json));

ov_genai_decoded_results_free(results);
results = NULL;
}

printf("\n----------\nquestion:\n");
}
CHECK_STATUS(ov_genai_llm_pipeline_finish_chat(pipeline));

err:
if (results)
ov_genai_decoded_results_free(results);
if (chat_history)
ov_genai_chat_history_free(chat_history);
if (pipeline)
ov_genai_llm_pipeline_free(pipeline);
if (config)
Expand Down
2 changes: 1 addition & 1 deletion samples/c/text_generation/greedy_causal_lm_c.c
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,4 @@ int main(int argc, char* argv[]) {
free(output);

return EXIT_SUCCESS;
}
}
213 changes: 213 additions & 0 deletions src/c/include/openvino/genai/c/chat_history.h
Copy link
Contributor

@yatarkan yatarkan Nov 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In C++ ChatHistory mainly works with JsonContainer instances. I think in C we should also expose JsonContainer and use it in C implementation for ChatHistory.

Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
// Copyright (C) 2025 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
// This is a C wrapper for ov::genai::ChatHistory class.

#pragma once

#include "visibility.h"
#include <stddef.h>

/**
* @struct ov_genai_chat_history
* @brief Opaque type for ChatHistory
*/
typedef struct ov_genai_chat_history_opaque ov_genai_chat_history;

/**
* @brief Status codes for chat history operations
*/
typedef enum {
OV_GENAI_CHAT_HISTORY_OK = 0,
OV_GENAI_CHAT_HISTORY_INVALID_PARAM = -1,
OV_GENAI_CHAT_HISTORY_OUT_OF_BOUNDS = -2,
OV_GENAI_CHAT_HISTORY_EMPTY = -3,
OV_GENAI_CHAT_HISTORY_INVALID_JSON = -4,
OV_GENAI_CHAT_HISTORY_ERROR = -5
} ov_genai_chat_history_status_e;

/**
* @brief Create a new empty ChatHistory instance.
* @param history A pointer to the newly created ov_genai_chat_history.
* @return ov_genai_chat_history_status_e A status code, return OK(0) if successful.
*/
OPENVINO_GENAI_C_EXPORTS ov_genai_chat_history_status_e ov_genai_chat_history_create(ov_genai_chat_history** history);

/**
* @brief Create a ChatHistory instance from a JSON array string.
* @param messages_json A JSON string containing an array of message objects.
* @param history A pointer to the newly created ov_genai_chat_history.
* @return ov_genai_chat_history_status_e A status code, return OK(0) if successful.
*/
OPENVINO_GENAI_C_EXPORTS ov_genai_chat_history_status_e ov_genai_chat_history_create_from_json(
const char* messages_json,
ov_genai_chat_history** history);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other functions have ov_genai_chat_history param in the first place - let's align


/**
* @brief Release the memory allocated by ov_genai_chat_history.
* @param history A pointer to the ov_genai_chat_history to free memory.
*/
OPENVINO_GENAI_C_EXPORTS void ov_genai_chat_history_free(ov_genai_chat_history* history);

/**
* @brief Add a message to the chat history from a JSON object string.
* @param history A pointer to the ov_genai_chat_history instance.
* @param message_json A JSON string containing a message object (e.g., {"role": "user", "content": "Hello"}).
* @return ov_genai_chat_history_status_e A status code, return OK(0) if successful.
*/
OPENVINO_GENAI_C_EXPORTS ov_genai_chat_history_status_e ov_genai_chat_history_push_back(
ov_genai_chat_history* history,
const char* message_json);

/**
* @brief Remove the last message from the chat history.
* @param history A pointer to the ov_genai_chat_history instance.
* @return ov_genai_chat_history_status_e A status code, return OK(0) if successful.
*/
OPENVINO_GENAI_C_EXPORTS ov_genai_chat_history_status_e ov_genai_chat_history_pop_back(ov_genai_chat_history* history);

/**
* @brief Get all messages as a JSON array string.
* @param history A pointer to the ov_genai_chat_history instance.
* @param output A pointer to the pre-allocated output string buffer. It can be set to NULL, in which case the
* *output_size will provide the needed buffer size. The user should then allocate the required buffer size and call
* this function again to obtain the entire output.
* @param output_size A pointer to the size of the output string, including the null terminator. If output is not NULL,
* *output_size should be greater than or equal to the result string size; otherwise, the function will return
* OUT_OF_BOUNDS(-2).
* @return ov_genai_chat_history_status_e A status code, return OK(0) if successful.
*/
OPENVINO_GENAI_C_EXPORTS ov_genai_chat_history_status_e ov_genai_chat_history_get_messages(
const ov_genai_chat_history* history,
char* output,
size_t* output_size);

/**
* @brief Get a message at a specific index as a JSON object string.
* @param history A pointer to the ov_genai_chat_history instance.
* @param index The index of the message to retrieve.
* @param output A pointer to the pre-allocated output string buffer. It can be set to NULL, in which case the
* *output_size will provide the needed buffer size. The user should then allocate the required buffer size and call
* this function again to obtain the entire output.
* @param output_size A pointer to the size of the output string, including the null terminator. If output is not NULL,
* *output_size should be greater than or equal to the result string size; otherwise, the function will return
* OUT_OF_BOUNDS(-2).
* @return ov_genai_chat_history_status_e A status code, return OK(0) if successful.
*/
OPENVINO_GENAI_C_EXPORTS ov_genai_chat_history_status_e ov_genai_chat_history_get_message(
const ov_genai_chat_history* history,
size_t index,
char* output,
size_t* output_size);

/**
* @brief Get the first message as a JSON object string.
* @param history A pointer to the ov_genai_chat_history instance.
* @param output A pointer to the pre-allocated output string buffer. It can be set to NULL, in which case the
* *output_size will provide the needed buffer size. The user should then allocate the required buffer size and call
* this function again to obtain the entire output.
* @param output_size A pointer to the size of the output string, including the null terminator. If output is not NULL,
* *output_size should be greater than or equal to the result string size; otherwise, the function will return
* OUT_OF_BOUNDS(-2).
* @return ov_genai_chat_history_status_e A status code, return OK(0) if successful.
*/
OPENVINO_GENAI_C_EXPORTS ov_genai_chat_history_status_e ov_genai_chat_history_get_first(
const ov_genai_chat_history* history,
char* output,
size_t* output_size);

/**
* @brief Get the last message as a JSON object string.
* @param history A pointer to the ov_genai_chat_history instance.
* @param output A pointer to the pre-allocated output string buffer. It can be set to NULL, in which case the
* *output_size will provide the needed buffer size. The user should then allocate the required buffer size and call
* this function again to obtain the entire output.
* @param output_size A pointer to the size of the output string, including the null terminator. If output is not NULL,
* *output_size should be greater than or equal to the result string size; otherwise, the function will return
* OUT_OF_BOUNDS(-2).
* @return ov_genai_chat_history_status_e A status code, return OK(0) if successful.
*/
OPENVINO_GENAI_C_EXPORTS ov_genai_chat_history_status_e ov_genai_chat_history_get_last(
const ov_genai_chat_history* history,
char* output,
size_t* output_size);

/**
* @brief Clear all messages from the chat history.
* @param history A pointer to the ov_genai_chat_history instance.
* @return ov_genai_chat_history_status_e A status code, return OK(0) if successful.
*/
OPENVINO_GENAI_C_EXPORTS ov_genai_chat_history_status_e ov_genai_chat_history_clear(ov_genai_chat_history* history);

/**
* @brief Get the number of messages in the chat history.
* @param history A pointer to the ov_genai_chat_history instance.
* @param size A pointer to store the size (number of messages).
* @return ov_genai_chat_history_status_e A status code, return OK(0) if successful.
*/
OPENVINO_GENAI_C_EXPORTS ov_genai_chat_history_status_e ov_genai_chat_history_size(
const ov_genai_chat_history* history,
size_t* size);

/**
* @brief Check if the chat history is empty.
* @param history A pointer to the ov_genai_chat_history instance.
* @param empty A pointer to store the boolean result (1 for empty, 0 for not empty).
* @return ov_genai_chat_history_status_e A status code, return OK(0) if successful.
*/
OPENVINO_GENAI_C_EXPORTS ov_genai_chat_history_status_e ov_genai_chat_history_empty(
const ov_genai_chat_history* history,
int* empty);

/**
* @brief Set tools definitions (for function calling) as a JSON array string.
* @param history A pointer to the ov_genai_chat_history instance.
* @param tools_json A JSON string containing an array of tool definitions.
* @return ov_genai_chat_history_status_e A status code, return OK(0) if successful.
*/
OPENVINO_GENAI_C_EXPORTS ov_genai_chat_history_status_e ov_genai_chat_history_set_tools(
ov_genai_chat_history* history,
const char* tools_json);

/**
* @brief Get tools definitions as a JSON array string.
* @param history A pointer to the ov_genai_chat_history instance.
* @param output A pointer to the pre-allocated output string buffer. It can be set to NULL, in which case the
* *output_size will provide the needed buffer size. The user should then allocate the required buffer size and call
* this function again to obtain the entire output.
* @param output_size A pointer to the size of the output string, including the null terminator. If output is not NULL,
* *output_size should be greater than or equal to the result string size; otherwise, the function will return
* OUT_OF_BOUNDS(-2).
* @return ov_genai_chat_history_status_e A status code, return OK(0) if successful.
*/
OPENVINO_GENAI_C_EXPORTS ov_genai_chat_history_status_e ov_genai_chat_history_get_tools(
const ov_genai_chat_history* history,
char* output,
size_t* output_size);

/**
* @brief Set extra context (for custom template variables) as a JSON object string.
* @param history A pointer to the ov_genai_chat_history instance.
* @param extra_context_json A JSON string containing an object with extra context.
* @return ov_genai_chat_history_status_e A status code, return OK(0) if successful.
*/
OPENVINO_GENAI_C_EXPORTS ov_genai_chat_history_status_e ov_genai_chat_history_set_extra_context(
ov_genai_chat_history* history,
const char* extra_context_json);

/**
* @brief Get extra context as a JSON object string.
* @param history A pointer to the ov_genai_chat_history instance.
* @param output A pointer to the pre-allocated output string buffer. It can be set to NULL, in which case the
* *output_size will provide the needed buffer size. The user should then allocate the required buffer size and call
* this function again to obtain the entire output.
* @param output_size A pointer to the size of the output string, including the null terminator. If output is not NULL,
* *output_size should be greater than or equal to the result string size; otherwise, the function will return
* OUT_OF_BOUNDS(-2).
* @return ov_genai_chat_history_status_e A status code, return OK(0) if successful.
*/
OPENVINO_GENAI_C_EXPORTS ov_genai_chat_history_status_e ov_genai_chat_history_get_extra_context(
const ov_genai_chat_history* history,
char* output,
size_t* output_size);

Loading
Loading