1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
//! Safe wrapper for [`ov_genai_chat_history`].
use crate::{drop_using_function, util::Result, ChatMessage, InferenceError, JsonContainer};
use openvino_genai_sys::{
self, ov_genai_chat_history, ov_genai_chat_history_clear, ov_genai_chat_history_create,
ov_genai_chat_history_free, ov_genai_chat_history_get_extra_context,
ov_genai_chat_history_get_tools, ov_genai_chat_history_push_back,
ov_genai_chat_history_set_extra_context, ov_genai_chat_history_set_tools,
ov_genai_chat_history_size, ov_genai_chat_history_status_e,
};
/// A chat history for multi-turn conversations.
pub struct ChatHistory {
ptr: *mut ov_genai_chat_history,
}
drop_using_function!(ChatHistory, ov_genai_chat_history_free);
// SAFETY: The underlying C object is not accessed concurrently by the C library;
// callers must ensure exclusive access (e.g., via Mutex) when sharing across threads.
unsafe impl Send for ChatHistory {}
/// Convert a chat history status code to a Result.
fn convert_chat_status(status: ov_genai_chat_history_status_e) -> Result<()> {
match status {
ov_genai_chat_history_status_e::OV_GENAI_CHAT_HISTORY_OK => Ok(()),
ov_genai_chat_history_status_e::OV_GENAI_CHAT_HISTORY_INVALID_PARAM => {
Err(InferenceError::InvalidCParam)
}
ov_genai_chat_history_status_e::OV_GENAI_CHAT_HISTORY_OUT_OF_BOUNDS => {
Err(InferenceError::OutOfBounds)
}
ov_genai_chat_history_status_e::OV_GENAI_CHAT_HISTORY_EMPTY => {
Err(InferenceError::NotFound)
}
ov_genai_chat_history_status_e::OV_GENAI_CHAT_HISTORY_INVALID_JSON => {
Err(InferenceError::ParameterMismatch)
}
ov_genai_chat_history_status_e::OV_GENAI_CHAT_HISTORY_ERROR => {
Err(InferenceError::GeneralError)
}
}
}
impl ChatHistory {
/// Create a new empty chat history.
pub fn new() -> Result<Self> {
let mut ptr = std::ptr::null_mut();
convert_chat_status(unsafe { ov_genai_chat_history_create(std::ptr::addr_of_mut!(ptr)) })?;
Ok(Self { ptr })
}
/// Add a typed [`ChatMessage`] to the chat history.
///
/// # Example
///
/// ```no_run
/// openvino_genai::load().unwrap();
/// use openvino_genai::{ChatMessage, ChatHistory};
///
/// let mut history = ChatHistory::new().unwrap();
/// history.push(&ChatMessage::user("Hello")).unwrap();
/// ```
pub fn push(&mut self, message: &ChatMessage) -> Result<()> {
let container = message.to_json_container()?;
self.push_back(&container)
}
/// Add a raw [`JsonContainer`] message to the chat history.
///
/// Prefer [`push`](Self::push) for most use cases. This method is available as an escape
/// hatch for message shapes not covered by [`ChatMessage`].
pub fn push_back(&mut self, message: &JsonContainer) -> Result<()> {
convert_chat_status(unsafe { ov_genai_chat_history_push_back(self.ptr, message.as_ptr()) })
}
/// Get the number of messages in the history.
pub fn size(&self) -> Result<usize> {
let mut size: usize = 0;
convert_chat_status(unsafe { ov_genai_chat_history_size(self.ptr, &mut size) })?;
Ok(size)
}
/// Clear all messages from the history.
pub fn clear(&mut self) -> Result<()> {
convert_chat_status(unsafe { ov_genai_chat_history_clear(self.ptr) })
}
/// Set tool definitions for function calling.
///
/// The `tools` parameter should be a [`JsonContainer`] containing an array of tool
/// definitions (e.g., OpenAI-compatible function schemas). These are incorporated into
/// the model's chat template during generation.
///
/// # Example
///
/// ```no_run
/// use openvino_genai::{ChatHistory, JsonContainer};
///
/// let mut history = ChatHistory::new().unwrap();
/// let tools = JsonContainer::from_json_str(r#"[{
/// "type": "function",
/// "function": {
/// "name": "search_hotkeys",
/// "description": "Search for keyboard shortcuts",
/// "parameters": {
/// "type": "object",
/// "properties": { "query": { "type": "string" } },
/// "required": ["query"]
/// }
/// }
/// }]"#).unwrap();
/// history.set_tools(&tools).unwrap();
/// ```
pub fn set_tools(&mut self, tools: &JsonContainer) -> Result<()> {
convert_chat_status(unsafe { ov_genai_chat_history_set_tools(self.ptr, tools.as_ptr()) })
}
/// Get the current tool definitions.
///
/// Returns a [`JsonContainer`] with the array of tool definitions previously set
/// via [`set_tools`](Self::set_tools).
pub fn get_tools(&self) -> Result<JsonContainer> {
let mut ptr = std::ptr::null_mut();
convert_chat_status(unsafe {
ov_genai_chat_history_get_tools(self.ptr, std::ptr::addr_of_mut!(ptr))
})?;
Ok(JsonContainer::from_raw_ptr(ptr))
}
/// Set extra context for custom template variables.
///
/// The `extra_context` parameter should be a [`JsonContainer`] containing an object
/// with additional variables to be passed to the model's chat template.
pub fn set_extra_context(&mut self, extra_context: &JsonContainer) -> Result<()> {
convert_chat_status(unsafe {
ov_genai_chat_history_set_extra_context(self.ptr, extra_context.as_ptr())
})
}
/// Get the current extra context.
///
/// Returns a [`JsonContainer`] with the extra context object previously set
/// via [`set_extra_context`](Self::set_extra_context).
pub fn get_extra_context(&self) -> Result<JsonContainer> {
let mut ptr = std::ptr::null_mut();
convert_chat_status(unsafe {
ov_genai_chat_history_get_extra_context(self.ptr, std::ptr::addr_of_mut!(ptr))
})?;
Ok(JsonContainer::from_raw_ptr(ptr))
}
/// Get the raw pointer. For internal use.
pub(crate) fn as_ptr(&self) -> *const ov_genai_chat_history {
self.ptr
}
}