Skip to main content

llm_api/
service.rs

1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3
4pub const COMPLETE_TARGET: &str = "/llm/complete";
5
6#[derive(Debug, Clone, Serialize, Deserialize, Default)]
7#[serde(rename_all = "camelCase", deny_unknown_fields)]
8pub struct LlmGenerationOptions {
9    #[serde(default)]
10    pub temperature: Option<f32>,
11    #[serde(default)]
12    pub max_tokens: Option<u32>,
13}
14
15#[derive(Debug, Clone, Serialize, Deserialize, Default)]
16#[serde(rename_all = "camelCase", deny_unknown_fields)]
17pub struct LlmCompleteRequest {
18    #[serde(default)]
19    pub constraints: crate::ModelConstraints,
20    #[serde(default)]
21    pub use_case: Option<String>,
22    #[serde(default)]
23    pub mode: Option<String>,
24    pub messages: Vec<crate::Message>,
25    #[serde(default)]
26    pub tools: Option<Vec<crate::ToolDefinition>>,
27    #[serde(default)]
28    pub provider: Option<Value>,
29    #[serde(default)]
30    pub response_format: Option<Value>,
31    #[serde(default)]
32    pub options: LlmGenerationOptions,
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize, Default)]
36#[serde(rename_all = "camelCase")]
37pub struct LlmRouteInfo {
38    pub provider: String,
39    pub model: String,
40    pub mode: String,
41}
42
43#[derive(Debug, Clone, Serialize, Deserialize, Default)]
44#[serde(rename_all = "camelCase")]
45pub struct LlmCompleteResponse {
46    pub message: crate::Message,
47    pub finish_reason: crate::FinishReason,
48    #[serde(default, skip_serializing_if = "Option::is_none")]
49    pub usage: Option<crate::TokenUsage>,
50    #[serde(default, skip_serializing_if = "Option::is_none")]
51    pub route: Option<LlmRouteInfo>,
52}
53
54#[cfg(test)]
55mod tests {
56    use super::*;
57
58    #[test]
59    fn request_rejects_removed_top_level_generation_options() {
60        assert!(
61            serde_json::from_value::<LlmCompleteRequest>(serde_json::json!({
62                "messages": [],
63                "options": {},
64                "temperature": 0.2
65            }))
66            .is_err()
67        );
68    }
69
70    #[test]
71    fn response_includes_normalized_usage() {
72        let response = LlmCompleteResponse {
73            usage: Some(crate::TokenUsage {
74                input_tokens: 2,
75                output_tokens: 3,
76                ..crate::TokenUsage::default()
77            }),
78            ..LlmCompleteResponse::default()
79        };
80        assert_eq!(response.usage.unwrap().total_tokens(), 5);
81    }
82}
83
84/// Finite audio is uploaded to the artifact service before transcription.
85#[derive(Debug, Clone, Serialize, Deserialize)]
86#[serde(rename_all = "camelCase", deny_unknown_fields)]
87pub struct AsrRequest {
88    pub audio: artifact_api::MediaReference,
89    #[serde(default, skip_serializing_if = "Option::is_none")]
90    pub mode: Option<String>,
91    #[serde(default, skip_serializing_if = "Option::is_none")]
92    pub language: Option<String>,
93}