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