Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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