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