Skip to main content

kopitiam_ai/
message.rs

1use serde::{Deserialize, Serialize};
2
3/// Who authored a [`Message`] in a [`CompletionRequest`].
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
5#[serde(rename_all = "snake_case")]
6pub enum Role {
7    /// Instructions/context assembled by `kopitiam-workflow`'s context
8    /// builder — never hand-typed by a human in this crate.
9    System,
10    /// The task or question being posed to the model.
11    User,
12    /// A prior model response, present when a workflow is multi-turn.
13    Assistant,
14}
15
16/// One turn in a [`CompletionRequest`].
17#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
18pub struct Message {
19    pub role: Role,
20    pub content: String,
21}
22
23impl Message {
24    pub fn system(content: impl Into<String>) -> Self {
25        Self { role: Role::System, content: content.into() }
26    }
27
28    pub fn user(content: impl Into<String>) -> Self {
29        Self { role: Role::User, content: content.into() }
30    }
31
32    pub fn assistant(content: impl Into<String>) -> Self {
33        Self { role: Role::Assistant, content: content.into() }
34    }
35}
36
37/// A request to a [`crate::ModelAdapter`].
38///
39/// This is deliberately shaped like a chat completion, since every adapter
40/// KOPITIAM plans to support (local Qwen, Claude, GPT, Gemini) speaks that
41/// shape natively. `messages` is expected to already contain whatever
42/// context `kopitiam-workflow`'s `ContextBuilder` assembled — adapters must
43/// not go looking for additional context themselves.
44#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
45pub struct CompletionRequest {
46    pub messages: Vec<Message>,
47    /// Upper bound on generated tokens, if the caller wants one. `None`
48    /// leaves the limit to the adapter/model's own default.
49    #[serde(default)]
50    pub max_tokens: Option<u32>,
51}
52
53impl CompletionRequest {
54    pub fn new(messages: impl IntoIterator<Item = Message>) -> Self {
55        Self { messages: messages.into_iter().collect(), max_tokens: None }
56    }
57
58    pub fn with_max_tokens(mut self, max_tokens: u32) -> Self {
59        self.max_tokens = Some(max_tokens);
60        self
61    }
62}
63
64/// A [`crate::ModelAdapter`]'s reply to a [`CompletionRequest`].
65#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
66pub struct CompletionResponse {
67    pub content: String,
68    /// Identifies which model actually produced `content` (e.g.
69    /// `"qwen2.5-coder-7b"`, `"claude-sonnet-5"`), independent of the
70    /// adapter's own [`crate::ModelAdapter::name`] — an adapter may serve
71    /// more than one model.
72    pub model: String,
73}