artificial_core/
generic.rs

1//! Generic message and role types used by the *artificial-core* crate.
2//!
3//! They deliberately mirror the concepts exposed by most provider APIs:
4//! “system”, “user”, “assistant”, and “tool”.  By staying minimal and
5//! provider-agnostic we can:
6//!
7//! * convert them into provider-specific structs via a simple `From`/`Into`,
8//! * serialize them without pulling in heavyweight dependencies, and
9//! * use them in unit tests without mocking a full transport layer.
10//!
11//! ## When to add more fields?
12//!
13//! Only if the additional data is **required by multiple back-ends** or
14//! **fundamentally provider-independent**.  Otherwise extend the
15//! provider-specific message type instead of bloating this one.
16use std::fmt::Display;
17
18use serde::{Deserialize, Serialize};
19
20/// Lightweight container representing a single chat message that is
21/// independent of any specific LLM provider.
22///
23/// * `message` – the raw UTF-8 content. Markdown is fine, but keep newlines
24///   and indentation portable.
25/// * `role` – see [`GenericRole`] for permitted values.
26#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct GenericMessage {
28    pub content: Option<String>,
29    pub role: GenericRole,
30    pub name: Option<String>,
31    pub tool_calls: Option<Vec<GenericFunctionCallIntent>>,
32    pub tool_call_id: Option<String>,
33}
34
35impl GenericMessage {
36    /// Convenience constructor mirroring the field order used by common HTTP
37    /// APIs (`role`, then `content`).
38    ///
39    /// ```rust
40    /// use artificial_core::generic::{GenericMessage, GenericRole};
41    ///
42    /// let sys = GenericMessage::new("You are a helpful bot.".into(),
43    ///                               GenericRole::System);
44    /// ```
45    pub fn new(message: String, role: GenericRole) -> Self {
46        Self {
47            content: Some(message),
48            role,
49            name: None,
50            tool_call_id: None,
51            tool_calls: None,
52        }
53    }
54
55    pub fn new_tool_call(tool_call_id: String, tool_calls: Vec<GenericFunctionCallIntent>) -> Self {
56        Self {
57            content: None,
58            role: GenericRole::Assistant,
59            name: None,
60            tool_calls: Some(tool_calls),
61            tool_call_id: Some(tool_call_id),
62        }
63    }
64
65    pub fn with_name(mut self, name: impl ToString) -> Self {
66        self.name = Some(name.to_string());
67        self
68    }
69
70    pub fn with_tool_call_id(mut self, tool_call_id: impl ToString) -> Self {
71        self.tool_call_id = Some(tool_call_id.to_string());
72        self
73    }
74}
75
76/// High-level chat roles recognised by most LLM providers.
77///
78/// The `Display` implementation renders the canonical lowercase name so you
79/// can feed it directly into JSON without extra mapping logic.
80#[derive(Debug, Clone, Serialize, Deserialize, Copy, PartialEq, Eq)]
81#[serde(rename_all = "snake_case")]
82pub enum GenericRole {
83    /// “System” messages define global behaviour and style guidelines.
84    System,
85    /// Messages produced by the assistant / model.
86    Assistant,
87    /// Messages originating from the human user.
88    User,
89    /// Special role used when a **tool call** or similar structured result is
90    /// injected into the conversation.
91    Tool,
92}
93
94impl Display for GenericRole {
95    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
96        match self {
97            GenericRole::System => write!(f, "system"),
98            GenericRole::Assistant => write!(f, "assistant"),
99            GenericRole::User => write!(f, "user"),
100            GenericRole::Tool => write!(f, "tool"),
101        }
102    }
103}
104
105#[derive(Debug)]
106pub struct GenericChatCompletionResponse<T> {
107    pub content: ResponseContent<T>,
108    pub usage: Option<GenericUsageReport>,
109}
110
111#[derive(Debug)]
112pub enum ResponseContent<T> {
113    Finished(T),
114    ToolCalls(GenericMessage),
115}
116
117#[derive(Debug, Clone)]
118pub struct GenericUsageReport {
119    pub prompt_tokens: i64,
120    pub completion_tokens: i64,
121    pub total_tokens: i64,
122}
123
124#[derive(Debug, Clone, Serialize, Deserialize)]
125pub struct GenericFunctionCallIntent {
126    pub id: String,
127    pub function: GenericFunctionCall,
128}
129
130#[derive(Debug, Clone, Serialize, Deserialize)]
131pub struct GenericFunctionCall {
132    pub name: String,
133    pub arguments: serde_json::Value,
134}
135
136pub enum GenericStramingChatChunk {
137    Created,
138    Completed,
139    Failed,
140    OutputTextDelta,
141    OutputTextDone,
142}
143
144#[derive(Debug, Clone)]
145pub struct GenericFunctionSpec {
146    pub name: String,
147    pub description: String,
148    pub parameters: serde_json::Value,
149}
150
151#[derive(Debug, Clone)]
152pub enum StreamEvent {
153    /// Plain text delta emitted by the assistant.
154    TextDelta(String),
155
156    /// A tool-call was initiated (OpenAI-style indexed stream).
157    ToolCallStart {
158        index: usize,
159        id: Option<String>,
160        name: Option<String>,
161    },
162
163    /// A partial arguments JSON fragment for tool-call at `index`.
164    ToolCallArgumentsDelta {
165        index: usize,
166        arguments_fragment: String,
167    },
168
169    /// A completed tool-call intent (arguments parsed into JSON).
170    ToolCallComplete {
171        index: usize,
172        intent: GenericFunctionCallIntent,
173    },
174
175    /// The assistant finished the message (e.g. stop or tool_calls).
176    MessageEnd,
177
178    /// Optional token usage report at the end of the stream.
179    Usage(GenericUsageReport),
180}
181
182/// Provider-agnostic trait for streaming structured events (text + tool-calls).
183/// This complements the existing text-only `StreamingChatProvider` trait.
184pub trait StreamingEventsProvider: crate::provider::ChatCompletionProvider {
185    type EventStream<'s>: futures_core::stream::Stream<Item = crate::error::Result<StreamEvent>>
186        + Send
187        + 's
188    where
189        Self: 's;
190
191    fn chat_complete_events_stream<'p, M>(
192        &self,
193        params: crate::provider::ChatCompleteParameters<M>,
194    ) -> Self::EventStream<'p>
195    where
196        M: Into<Self::Message> + Send + Sync + 'p;
197}