From 5a41c21d30cb959c5eb34485a44cf8e010463dc8 Mon Sep 17 00:00:00 2001 From: zhaohb Date: Wed, 19 Nov 2025 10:29:25 +0800 Subject: [PATCH 1/2] Update C chat samples with using ChatHistory class --- samples/c/text_generation/benchmark_genai_c.c | 2 +- samples/c/text_generation/chat_sample_c.c | 119 +++++- .../c/text_generation/greedy_causal_lm_c.c | 2 +- src/c/include/openvino/genai/c/chat_history.h | 213 ++++++++++ src/c/include/openvino/genai/c/llm_pipeline.h | 19 + src/c/src/chat_history.cpp | 377 ++++++++++++++++++ src/c/src/llm_pipeline.cpp | 33 ++ src/c/src/types_c.h | 9 + 8 files changed, 761 insertions(+), 13 deletions(-) create mode 100644 src/c/include/openvino/genai/c/chat_history.h create mode 100644 src/c/src/chat_history.cpp diff --git a/samples/c/text_generation/benchmark_genai_c.c b/samples/c/text_generation/benchmark_genai_c.c index 4a9d557cd9..b3df6c5471 100644 --- a/samples/c/text_generation/benchmark_genai_c.c +++ b/samples/c/text_generation/benchmark_genai_c.c @@ -188,4 +188,4 @@ int main(int argc, char* argv[]) { if (results) ov_genai_decoded_results_free(results); return EXIT_SUCCESS; -} +} \ No newline at end of file diff --git a/samples/c/text_generation/chat_sample_c.c b/samples/c/text_generation/chat_sample_c.c index 850930c3bb..5a9ae16ad1 100644 --- a/samples/c/text_generation/chat_sample_c.c +++ b/samples/c/text_generation/chat_sample_c.c @@ -6,14 +6,68 @@ #include #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. @@ -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 \n", argv[0]); + if (argc < 2 || argc > 3) { + fprintf(stderr, "Usage: %s [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) diff --git a/samples/c/text_generation/greedy_causal_lm_c.c b/samples/c/text_generation/greedy_causal_lm_c.c index 66e08e4a18..8ec968ee6b 100644 --- a/samples/c/text_generation/greedy_causal_lm_c.c +++ b/samples/c/text_generation/greedy_causal_lm_c.c @@ -53,4 +53,4 @@ int main(int argc, char* argv[]) { free(output); return EXIT_SUCCESS; -} +} \ No newline at end of file diff --git a/src/c/include/openvino/genai/c/chat_history.h b/src/c/include/openvino/genai/c/chat_history.h new file mode 100644 index 0000000000..19d1b826ac --- /dev/null +++ b/src/c/include/openvino/genai/c/chat_history.h @@ -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 + +/** + * @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); + +/** + * @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); + diff --git a/src/c/include/openvino/genai/c/llm_pipeline.h b/src/c/include/openvino/genai/c/llm_pipeline.h index 1891ab028c..532438d188 100644 --- a/src/c/include/openvino/genai/c/llm_pipeline.h +++ b/src/c/include/openvino/genai/c/llm_pipeline.h @@ -11,6 +11,7 @@ #pragma once #include "generation_config.h" #include "perf_metrics.h" +#include "chat_history.h" /** * @struct ov_genai_decoded_results @@ -148,6 +149,24 @@ OPENVINO_GENAI_C_EXPORTS ov_status_e ov_genai_llm_pipeline_generate(ov_genai_llm const ov_genai_generation_config* config, const streamer_callback* streamer, ov_genai_decoded_results** results); + +/** + * @brief Generate results by ov_genai_llm_pipeline using ChatHistory + * @param pipe A pointer to the ov_genai_llm_pipeline instance. + * @param history A pointer to the ov_genai_chat_history instance. + * @param config A pointer to the ov_genai_generation_config, the pointer can be NULL. + * @param streamer A pointer to the stream callback. Set to NULL if no callback is needed. Either this or results must + * be non-NULL. + * @param results A pointer to the ov_genai_decoded_results, which retrieves the results of the generation. Either this + * or streamer must be non-NULL. + * @return Status code of the operation: OK(0) for success. + */ +OPENVINO_GENAI_C_EXPORTS ov_status_e ov_genai_llm_pipeline_generate_with_history(ov_genai_llm_pipeline* pipe, + const ov_genai_chat_history* history, + const ov_genai_generation_config* config, + const streamer_callback* streamer, + ov_genai_decoded_results** results); + /** * @brief Start chat with keeping history in kv cache. * @param pipe A pointer to the ov_genai_llm_pipeline instance. diff --git a/src/c/src/chat_history.cpp b/src/c/src/chat_history.cpp new file mode 100644 index 0000000000..75869cfcdd --- /dev/null +++ b/src/c/src/chat_history.cpp @@ -0,0 +1,377 @@ +// Copyright (C) 2025 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "openvino/genai/c/chat_history.h" + +#include "openvino/genai/chat_history.hpp" +#include "openvino/genai/json_container.hpp" +#include "openvino/core/except.hpp" +#include "types_c.h" +#include +#include +#include + +ov_genai_chat_history_status_e ov_genai_chat_history_create(ov_genai_chat_history** history) { + if (!history) { + return OV_GENAI_CHAT_HISTORY_INVALID_PARAM; + } + try { + std::unique_ptr _history = std::make_unique(); + _history->object = std::make_shared(); + *history = _history.release(); + } catch (const std::exception& e) { + return OV_GENAI_CHAT_HISTORY_ERROR; + } catch (...) { + return OV_GENAI_CHAT_HISTORY_ERROR; + } + return OV_GENAI_CHAT_HISTORY_OK; +} + +ov_genai_chat_history_status_e ov_genai_chat_history_create_from_json( + const char* messages_json, + ov_genai_chat_history** history) { + if (!messages_json || !history) { + return OV_GENAI_CHAT_HISTORY_INVALID_PARAM; + } + try { + ov::genai::JsonContainer messages = ov::genai::JsonContainer::from_json_string(std::string(messages_json)); + if (!messages.is_array()) { + return OV_GENAI_CHAT_HISTORY_INVALID_JSON; + } + std::unique_ptr _history = std::make_unique(); + _history->object = std::make_shared(messages); + *history = _history.release(); + } catch (const ov::Exception& e) { + return OV_GENAI_CHAT_HISTORY_INVALID_JSON; + } catch (const std::exception& e) { + return OV_GENAI_CHAT_HISTORY_ERROR; + } catch (...) { + return OV_GENAI_CHAT_HISTORY_ERROR; + } + return OV_GENAI_CHAT_HISTORY_OK; +} + +void ov_genai_chat_history_free(ov_genai_chat_history* history) { + if (history) { + delete history; + } +} + +ov_genai_chat_history_status_e ov_genai_chat_history_push_back( + ov_genai_chat_history* history, + const char* message_json) { + if (!history || !(history->object) || !message_json) { + return OV_GENAI_CHAT_HISTORY_INVALID_PARAM; + } + try { + ov::genai::JsonContainer message = ov::genai::JsonContainer::from_json_string(std::string(message_json)); + history->object->push_back(message); + } catch (const ov::Exception& e) { + return OV_GENAI_CHAT_HISTORY_INVALID_JSON; + } catch (const std::exception& e) { + return OV_GENAI_CHAT_HISTORY_ERROR; + } catch (...) { + return OV_GENAI_CHAT_HISTORY_ERROR; + } + return OV_GENAI_CHAT_HISTORY_OK; +} + +ov_genai_chat_history_status_e ov_genai_chat_history_pop_back(ov_genai_chat_history* history) { + if (!history || !(history->object)) { + return OV_GENAI_CHAT_HISTORY_INVALID_PARAM; + } + try { + if (history->object->empty()) { + return OV_GENAI_CHAT_HISTORY_EMPTY; + } + history->object->pop_back(); + } catch (const ov::Exception& e) { + return OV_GENAI_CHAT_HISTORY_EMPTY; + } catch (const std::exception& e) { + return OV_GENAI_CHAT_HISTORY_ERROR; + } catch (...) { + return OV_GENAI_CHAT_HISTORY_ERROR; + } + return OV_GENAI_CHAT_HISTORY_OK; +} + +ov_genai_chat_history_status_e ov_genai_chat_history_get_messages( + const ov_genai_chat_history* history, + char* output, + size_t* output_size) { + if (!history || !(history->object) || !output_size) { + return OV_GENAI_CHAT_HISTORY_INVALID_PARAM; + } + try { + const ov::genai::JsonContainer& messages = history->object->get_messages(); + std::string json_str = messages.to_json_string(); + + if (!output) { + *output_size = json_str.length() + 1; + } else { + if (*output_size < json_str.length() + 1) { + return OV_GENAI_CHAT_HISTORY_OUT_OF_BOUNDS; + } + std::strncpy(output, json_str.c_str(), json_str.length() + 1); + output[json_str.length()] = '\0'; + *output_size = json_str.length() + 1; + } + } catch (const std::exception& e) { + return OV_GENAI_CHAT_HISTORY_ERROR; + } catch (...) { + return OV_GENAI_CHAT_HISTORY_ERROR; + } + return OV_GENAI_CHAT_HISTORY_OK; +} + +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) { + if (!history || !(history->object) || !output_size) { + return OV_GENAI_CHAT_HISTORY_INVALID_PARAM; + } + try { + if (index >= history->object->size()) { + return OV_GENAI_CHAT_HISTORY_OUT_OF_BOUNDS; + } + ov::genai::JsonContainer message = (*history->object)[index]; + std::string json_str = message.to_json_string(); + + if (!output) { + *output_size = json_str.length() + 1; + } else { + if (*output_size < json_str.length() + 1) { + return OV_GENAI_CHAT_HISTORY_OUT_OF_BOUNDS; + } + std::strncpy(output, json_str.c_str(), json_str.length() + 1); + output[json_str.length()] = '\0'; + *output_size = json_str.length() + 1; + } + } catch (const ov::Exception& e) { + return OV_GENAI_CHAT_HISTORY_OUT_OF_BOUNDS; + } catch (const std::exception& e) { + return OV_GENAI_CHAT_HISTORY_ERROR; + } catch (...) { + return OV_GENAI_CHAT_HISTORY_ERROR; + } + return OV_GENAI_CHAT_HISTORY_OK; +} + +ov_genai_chat_history_status_e ov_genai_chat_history_get_first( + const ov_genai_chat_history* history, + char* output, + size_t* output_size) { + if (!history || !(history->object) || !output_size) { + return OV_GENAI_CHAT_HISTORY_INVALID_PARAM; + } + try { + if (history->object->empty()) { + return OV_GENAI_CHAT_HISTORY_EMPTY; + } + ov::genai::JsonContainer message = history->object->first(); + std::string json_str = message.to_json_string(); + + if (!output) { + *output_size = json_str.length() + 1; + } else { + if (*output_size < json_str.length() + 1) { + return OV_GENAI_CHAT_HISTORY_OUT_OF_BOUNDS; + } + std::strncpy(output, json_str.c_str(), json_str.length() + 1); + output[json_str.length()] = '\0'; + *output_size = json_str.length() + 1; + } + } catch (const ov::Exception& e) { + return OV_GENAI_CHAT_HISTORY_EMPTY; + } catch (const std::exception& e) { + return OV_GENAI_CHAT_HISTORY_ERROR; + } catch (...) { + return OV_GENAI_CHAT_HISTORY_ERROR; + } + return OV_GENAI_CHAT_HISTORY_OK; +} + +ov_genai_chat_history_status_e ov_genai_chat_history_get_last( + const ov_genai_chat_history* history, + char* output, + size_t* output_size) { + if (!history || !(history->object) || !output_size) { + return OV_GENAI_CHAT_HISTORY_INVALID_PARAM; + } + try { + if (history->object->empty()) { + return OV_GENAI_CHAT_HISTORY_EMPTY; + } + ov::genai::JsonContainer message = history->object->last(); + std::string json_str = message.to_json_string(); + + if (!output) { + *output_size = json_str.length() + 1; + } else { + if (*output_size < json_str.length() + 1) { + return OV_GENAI_CHAT_HISTORY_OUT_OF_BOUNDS; + } + std::strncpy(output, json_str.c_str(), json_str.length() + 1); + output[json_str.length()] = '\0'; + *output_size = json_str.length() + 1; + } + } catch (const ov::Exception& e) { + return OV_GENAI_CHAT_HISTORY_EMPTY; + } catch (const std::exception& e) { + return OV_GENAI_CHAT_HISTORY_ERROR; + } catch (...) { + return OV_GENAI_CHAT_HISTORY_ERROR; + } + return OV_GENAI_CHAT_HISTORY_OK; +} + +ov_genai_chat_history_status_e ov_genai_chat_history_clear(ov_genai_chat_history* history) { + if (!history || !(history->object)) { + return OV_GENAI_CHAT_HISTORY_INVALID_PARAM; + } + try { + history->object->clear(); + } catch (const std::exception& e) { + return OV_GENAI_CHAT_HISTORY_ERROR; + } catch (...) { + return OV_GENAI_CHAT_HISTORY_ERROR; + } + return OV_GENAI_CHAT_HISTORY_OK; +} + +ov_genai_chat_history_status_e ov_genai_chat_history_size( + const ov_genai_chat_history* history, + size_t* size) { + if (!history || !(history->object) || !size) { + return OV_GENAI_CHAT_HISTORY_INVALID_PARAM; + } + try { + *size = history->object->size(); + } catch (const std::exception& e) { + return OV_GENAI_CHAT_HISTORY_ERROR; + } catch (...) { + return OV_GENAI_CHAT_HISTORY_ERROR; + } + return OV_GENAI_CHAT_HISTORY_OK; +} + +ov_genai_chat_history_status_e ov_genai_chat_history_empty( + const ov_genai_chat_history* history, + int* empty) { + if (!history || !(history->object) || !empty) { + return OV_GENAI_CHAT_HISTORY_INVALID_PARAM; + } + try { + *empty = history->object->empty() ? 1 : 0; + } catch (const std::exception& e) { + return OV_GENAI_CHAT_HISTORY_ERROR; + } catch (...) { + return OV_GENAI_CHAT_HISTORY_ERROR; + } + return OV_GENAI_CHAT_HISTORY_OK; +} + +ov_genai_chat_history_status_e ov_genai_chat_history_set_tools( + ov_genai_chat_history* history, + const char* tools_json) { + if (!history || !(history->object) || !tools_json) { + return OV_GENAI_CHAT_HISTORY_INVALID_PARAM; + } + try { + ov::genai::JsonContainer tools = ov::genai::JsonContainer::from_json_string(std::string(tools_json)); + if (!tools.is_array()) { + return OV_GENAI_CHAT_HISTORY_INVALID_JSON; + } + history->object->set_tools(tools); + } catch (const ov::Exception& e) { + return OV_GENAI_CHAT_HISTORY_INVALID_JSON; + } catch (const std::exception& e) { + return OV_GENAI_CHAT_HISTORY_ERROR; + } catch (...) { + return OV_GENAI_CHAT_HISTORY_ERROR; + } + return OV_GENAI_CHAT_HISTORY_OK; +} + +ov_genai_chat_history_status_e ov_genai_chat_history_get_tools( + const ov_genai_chat_history* history, + char* output, + size_t* output_size) { + if (!history || !(history->object) || !output_size) { + return OV_GENAI_CHAT_HISTORY_INVALID_PARAM; + } + try { + const ov::genai::JsonContainer& tools = history->object->get_tools(); + std::string json_str = tools.to_json_string(); + + if (!output) { + *output_size = json_str.length() + 1; + } else { + if (*output_size < json_str.length() + 1) { + return OV_GENAI_CHAT_HISTORY_OUT_OF_BOUNDS; + } + std::strncpy(output, json_str.c_str(), json_str.length() + 1); + output[json_str.length()] = '\0'; + *output_size = json_str.length() + 1; + } + } catch (const std::exception& e) { + return OV_GENAI_CHAT_HISTORY_ERROR; + } catch (...) { + return OV_GENAI_CHAT_HISTORY_ERROR; + } + return OV_GENAI_CHAT_HISTORY_OK; +} + +ov_genai_chat_history_status_e ov_genai_chat_history_set_extra_context( + ov_genai_chat_history* history, + const char* extra_context_json) { + if (!history || !(history->object) || !extra_context_json) { + return OV_GENAI_CHAT_HISTORY_INVALID_PARAM; + } + try { + ov::genai::JsonContainer extra_context = ov::genai::JsonContainer::from_json_string(std::string(extra_context_json)); + if (!extra_context.is_object()) { + return OV_GENAI_CHAT_HISTORY_INVALID_JSON; + } + history->object->set_extra_context(extra_context); + } catch (const ov::Exception& e) { + return OV_GENAI_CHAT_HISTORY_INVALID_JSON; + } catch (const std::exception& e) { + return OV_GENAI_CHAT_HISTORY_ERROR; + } catch (...) { + return OV_GENAI_CHAT_HISTORY_ERROR; + } + return OV_GENAI_CHAT_HISTORY_OK; +} + +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) { + if (!history || !(history->object) || !output_size) { + return OV_GENAI_CHAT_HISTORY_INVALID_PARAM; + } + try { + const ov::genai::JsonContainer& extra_context = history->object->get_extra_context(); + std::string json_str = extra_context.to_json_string(); + + if (!output) { + *output_size = json_str.length() + 1; + } else { + if (*output_size < json_str.length() + 1) { + return OV_GENAI_CHAT_HISTORY_OUT_OF_BOUNDS; + } + std::strncpy(output, json_str.c_str(), json_str.length() + 1); + output[json_str.length()] = '\0'; + *output_size = json_str.length() + 1; + } + } catch (const std::exception& e) { + return OV_GENAI_CHAT_HISTORY_ERROR; + } catch (...) { + return OV_GENAI_CHAT_HISTORY_ERROR; + } + return OV_GENAI_CHAT_HISTORY_OK; +} + diff --git a/src/c/src/llm_pipeline.cpp b/src/c/src/llm_pipeline.cpp index bd34011f7f..e582205209 100644 --- a/src/c/src/llm_pipeline.cpp +++ b/src/c/src/llm_pipeline.cpp @@ -5,6 +5,7 @@ #include "openvino/genai/generation_config.hpp" #include "openvino/genai/llm_pipeline.hpp" +#include "openvino/genai/chat_history.hpp" #include "types_c.h" #include @@ -144,6 +145,38 @@ ov_status_e ov_genai_llm_pipeline_generate(ov_genai_llm_pipeline* pipe, } return ov_status_e::OK; } +ov_status_e ov_genai_llm_pipeline_generate_with_history(ov_genai_llm_pipeline* pipe, + const ov_genai_chat_history* history, + const ov_genai_generation_config* config, + const streamer_callback* streamer, + ov_genai_decoded_results** results) { + if (!pipe || !(pipe->object) || !history || !(history->object) || !(streamer || results)) { + return ov_status_e::INVALID_C_PARAM; + } + try { + std::unique_ptr _results = std::make_unique(); + _results->object = std::make_shared(); + + if (streamer) { + auto callback = [streamer](std::string word) -> ov::genai::StreamingStatus { + return static_cast((streamer->callback_func)(word.c_str(), streamer->args)); + }; + *(_results->object) = (config && config->object) + ? pipe->object->generate(*(history->object), *(config->object), callback) + : pipe->object->generate(*(history->object), {}, callback); + } else { + *(_results->object) = (config && config->object) ? pipe->object->generate(*(history->object), *(config->object)) + : pipe->object->generate(*(history->object)); + } + if (results) { + *results = _results.release(); + } + } catch (...) { + return ov_status_e::UNKNOW_EXCEPTION; + } + return ov_status_e::OK; +} + ov_status_e ov_genai_llm_pipeline_start_chat(ov_genai_llm_pipeline* pipe) { if (!pipe || !(pipe->object)) { return ov_status_e::INVALID_C_PARAM; diff --git a/src/c/src/types_c.h b/src/c/src/types_c.h index 7301478ba0..cc63664b42 100644 --- a/src/c/src/types_c.h +++ b/src/c/src/types_c.h @@ -8,6 +8,7 @@ #include "openvino/genai/whisper_generation_config.hpp" #include "openvino/genai/visibility.hpp" #include "openvino/genai/visual_language/pipeline.hpp" +#include "openvino/genai/chat_history.hpp" #define GET_PROPERTY_FROM_ARGS_LIST \ std::string property_key = va_arg(args_ptr, char*); \ @@ -127,4 +128,12 @@ struct ov_genai_vlm_decoded_results_opaque { */ struct ov_genai_vlm_pipeline_opaque { std::shared_ptr object; +}; + +/** + * @struct ov_genai_chat_history_opaque + * @brief This is an interface of ov::genai::ChatHistory + */ +struct ov_genai_chat_history_opaque { + std::shared_ptr object; }; \ No newline at end of file From c28ec23882e439846f7f9532c8c602064891befd Mon Sep 17 00:00:00 2001 From: zhaohb Date: Wed, 19 Nov 2025 13:45:48 +0800 Subject: [PATCH 2/2] update --- src/c/src/chat_history.cpp | 6 ------ src/c/src/llm_pipeline.cpp | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/c/src/chat_history.cpp b/src/c/src/chat_history.cpp index 75869cfcdd..4c206badb9 100644 --- a/src/c/src/chat_history.cpp +++ b/src/c/src/chat_history.cpp @@ -113,7 +113,6 @@ ov_genai_chat_history_status_e ov_genai_chat_history_get_messages( return OV_GENAI_CHAT_HISTORY_OUT_OF_BOUNDS; } std::strncpy(output, json_str.c_str(), json_str.length() + 1); - output[json_str.length()] = '\0'; *output_size = json_str.length() + 1; } } catch (const std::exception& e) { @@ -146,7 +145,6 @@ ov_genai_chat_history_status_e ov_genai_chat_history_get_message( return OV_GENAI_CHAT_HISTORY_OUT_OF_BOUNDS; } std::strncpy(output, json_str.c_str(), json_str.length() + 1); - output[json_str.length()] = '\0'; *output_size = json_str.length() + 1; } } catch (const ov::Exception& e) { @@ -180,7 +178,6 @@ ov_genai_chat_history_status_e ov_genai_chat_history_get_first( return OV_GENAI_CHAT_HISTORY_OUT_OF_BOUNDS; } std::strncpy(output, json_str.c_str(), json_str.length() + 1); - output[json_str.length()] = '\0'; *output_size = json_str.length() + 1; } } catch (const ov::Exception& e) { @@ -214,7 +211,6 @@ ov_genai_chat_history_status_e ov_genai_chat_history_get_last( return OV_GENAI_CHAT_HISTORY_OUT_OF_BOUNDS; } std::strncpy(output, json_str.c_str(), json_str.length() + 1); - output[json_str.length()] = '\0'; *output_size = json_str.length() + 1; } } catch (const ov::Exception& e) { @@ -313,7 +309,6 @@ ov_genai_chat_history_status_e ov_genai_chat_history_get_tools( return OV_GENAI_CHAT_HISTORY_OUT_OF_BOUNDS; } std::strncpy(output, json_str.c_str(), json_str.length() + 1); - output[json_str.length()] = '\0'; *output_size = json_str.length() + 1; } } catch (const std::exception& e) { @@ -364,7 +359,6 @@ ov_genai_chat_history_status_e ov_genai_chat_history_get_extra_context( return OV_GENAI_CHAT_HISTORY_OUT_OF_BOUNDS; } std::strncpy(output, json_str.c_str(), json_str.length() + 1); - output[json_str.length()] = '\0'; *output_size = json_str.length() + 1; } } catch (const std::exception& e) { diff --git a/src/c/src/llm_pipeline.cpp b/src/c/src/llm_pipeline.cpp index e582205209..39e6970fe7 100644 --- a/src/c/src/llm_pipeline.cpp +++ b/src/c/src/llm_pipeline.cpp @@ -150,7 +150,7 @@ ov_status_e ov_genai_llm_pipeline_generate_with_history(ov_genai_llm_pipeline* p const ov_genai_generation_config* config, const streamer_callback* streamer, ov_genai_decoded_results** results) { - if (!pipe || !(pipe->object) || !history || !(history->object) || !(streamer || results)) { + if (!pipe || !(pipe->object) || !history || !(history->object) || (!streamer && !results)) { return ov_status_e::INVALID_C_PARAM; } try {