Skip to main content

llm_api/
lib.rs

1//! Canonical model invocation and continuation contract. No Agent or transport implementation.
2use serde::{Deserialize, Serialize};
3use serde_json::Value;
4mod continuation;
5mod policy;
6pub use continuation::Continuation;
7pub use policy::{GenerationParameters, ModelCapabilities};
8pub use service::Image;
9
10/// Logical model use case resolved by the deployment's LLM implementation.
11#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
12#[serde(transparent)]
13pub struct UseCase(pub String);
14
15/// Logical model mode selected by the product, independent of provider and model identifiers.
16#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
17#[serde(transparent)]
18pub struct ModelMode(pub String);
19
20/// Caller-declared requirements. True requires confirmed support; false imposes no requirement.
21/// Adapters must not infer or override these flags from message content.
22#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
23#[serde(deny_unknown_fields)]
24pub struct ModelConstraints {
25    /// The model must accept image input.
26    pub vision: bool,
27    /// The model must support tool calls.
28    pub tool_calling: bool,
29    /// The model must support schema-constrained output.
30    pub structured_output: bool,
31}
32
33/// Role of a message in a model conversation.
34#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
35#[serde(rename_all = "snake_case")]
36#[derive(Default)]
37pub enum MessageRole {
38    /// Runtime-supplied instruction.
39    System,
40    /// User-supplied input.
41    #[default]
42    User,
43    /// Model output.
44    Assistant,
45    /// Tool execution output.
46    Tool,
47}
48
49/// One typed part of a model message.
50#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
51#[serde(tag = "type", rename_all = "snake_case")]
52pub enum ContentPart {
53    /// Plain text content.
54    Text {
55        /// Text value.
56        text: String,
57    },
58    /// Scope-owned content-addressed artifact managed by the application artifact service.
59    Artifact {
60        /// Canonical `meow-artifact://` URI.
61        uri: String,
62        /// MIME type copied from the validated artifact metadata.
63        mime_type: String,
64    },
65    /// An image already materialized by the caller.
66    Image { image: Image },
67    /// A model-requested tool invocation.
68    ToolCall(ToolCall),
69    /// Result of an earlier tool invocation.
70    ToolResult {
71        /// Provider-neutral call identifier.
72        call_id: String,
73        /// Structured result payload.
74        result: Value,
75        /// Whether the tool execution failed.
76        is_error: bool,
77    },
78}
79
80/// Provider-neutral conversation message.
81#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
82pub struct Message {
83    /// Speaker role.
84    pub role: MessageRole,
85    /// Ordered message content.
86    pub content: Vec<ContentPart>,
87    /// Private model continuation; never part of a user transcript.
88    #[serde(default, skip_serializing_if = "Option::is_none")]
89    pub continuation: Option<Continuation>,
90}
91
92/// A callable function exposed to the model. Execution policy belongs to the caller.
93#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
94pub struct ToolDefinition {
95    pub name: String,
96    pub description: String,
97    pub input_schema: Value,
98}
99/// Tool call returned by an LLM.
100#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
101pub struct ToolCall {
102    /// Provider-neutral identifier used to correlate the result.
103    pub id: String,
104    /// Requested tool name.
105    pub name: String,
106    /// Structured arguments.
107    pub arguments: Value,
108}
109
110/// One logical LLM invocation.
111#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
112pub struct CompletionRequest {
113    /// Logical routing key resolved by the LLM adapter.
114    pub use_case: UseCase,
115    /// Logical quality/cost mode resolved by the LLM adapter adapter.
116    pub model_mode: ModelMode,
117    /// Conversation input.
118    pub messages: Vec<Message>,
119    /// Tools available to the model.
120    pub tools: Vec<ToolDefinition>,
121    /// Required model capabilities.
122    pub constraints: ModelConstraints,
123    /// Optional maximum number of output tokens.
124    pub max_output_tokens: Option<u32>,
125    /// Whether the caller requested sanitized diagnostic metadata.
126    pub diagnostics: bool,
127}
128
129/// Provider-neutral limits for one frozen logical model route.
130///
131/// `profile_key` is opaque to Runtime. Implementations must change it whenever the concrete
132/// model, tokenizer, or another input-serialization detail changes.
133#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
134pub struct ModelProfile {
135    /// Opaque stable identity of the frozen model and tokenizer route.
136    pub profile_key: String,
137    /// Maximum total context accepted by the concrete model.
138    pub context_window_tokens: u32,
139}
140
141/// Why a model invocation stopped.
142#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
143#[serde(rename_all = "snake_case")]
144#[derive(Default)]
145pub enum FinishReason {
146    /// The model completed normally.
147    #[default]
148    Stop,
149    /// The model requested one or more tools.
150    ToolCalls,
151    /// The configured output limit was reached.
152    Length,
153    /// The LLM adapter cannot map the provider result to a more specific reason.
154    Other,
155}
156
157/// Normalized token accounting reported by an implementation.
158#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
159pub struct TokenUsage {
160    /// Input token count.
161    pub input_tokens: u64,
162    /// Output token count.
163    pub output_tokens: u64,
164    /// Input tokens served from a provider cache when reported.
165    pub cached_input_tokens: Option<u64>,
166    /// Reasoning output tokens when reported separately.
167    pub reasoning_output_tokens: Option<u64>,
168    /// Provider-normalized billable credits consumed by this request.
169    pub credits: Option<u64>,
170}
171
172/// Provider-neutral completion returned to Runtime.
173#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
174pub struct Completion {
175    /// Model response message.
176    pub message: Message,
177    /// Normalized finish reason.
178    pub finish_reason: FinishReason,
179    /// Normalized token accounting.
180    pub usage: Option<TokenUsage>,
181    /// Optional sanitized diagnostics without credentials or provider internals.
182    pub diagnostics: Option<Value>,
183}
184
185pub mod service;
186
187impl MessageRole {
188    pub const fn as_str(self) -> &'static str {
189        match self {
190            Self::System => "system",
191            Self::User => "user",
192            Self::Assistant => "assistant",
193            Self::Tool => "tool",
194        }
195    }
196}
197
198impl Default for Message {
199    fn default() -> Self {
200        Self::text(MessageRole::User, "")
201    }
202}
203impl Message {
204    pub fn text(role: MessageRole, text: impl Into<String>) -> Self {
205        Self {
206            role,
207            content: vec![ContentPart::Text { text: text.into() }],
208            continuation: None,
209        }
210    }
211    pub fn text_content(&self) -> String {
212        self.content
213            .iter()
214            .filter_map(|part| match part {
215                ContentPart::Text { text } => Some(text.as_str()),
216                _ => None,
217            })
218            .collect::<Vec<_>>()
219            .join("\n")
220    }
221}
222
223impl TokenUsage {
224    pub fn total_tokens(self) -> u64 {
225        self.input_tokens.saturating_add(self.output_tokens)
226    }
227}
228impl Message {
229    pub fn with_images(mut self, images: impl IntoIterator<Item = Image>) -> Self {
230        self.content
231            .extend(images.into_iter().map(|image| ContentPart::Image { image }));
232        self
233    }
234}
235
236/// Offline normative schema for persisted model messages.
237pub const MESSAGE_SCHEMA: &str = include_str!("../schema/message.v1.schema.json");