Skip to main content

yallm_ir/
lib.rs

1//! yallm-ir: Intermediate Representation for unified LLM API conversion
2//!
3//! This crate provides the Intermediate Representation (IR) for converting
4//! between different LLM API formats (OpenAI, Anthropic, Ollama).
5
6use serde::{Deserialize, Serialize};
7
8/// Source API type
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
10#[serde(rename_all = "lowercase")]
11pub enum Source {
12    OpenAI,
13    Anthropic,
14    Ollama,
15}
16
17/// Message role in conversation
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
19#[serde(rename_all = "lowercase")]
20pub enum Role {
21    System,
22    User,
23    Assistant,
24    Tool,
25}
26
27impl Role {
28    pub fn as_str(&self) -> &'static str {
29        match self {
30            Role::System => "system",
31            Role::User => "user",
32            Role::Assistant => "assistant",
33            Role::Tool => "tool",
34        }
35    }
36}
37
38/// Text content block
39#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
40pub struct TextContent {
41    pub text: String,
42}
43
44impl TextContent {
45    pub fn new(text: impl Into<String>) -> Self {
46        Self { text: text.into() }
47    }
48}
49
50/// Image source type
51#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
52#[serde(rename_all = "lowercase")]
53pub enum ImageSourceType {
54    Base64,
55    Url,
56}
57
58/// Image content block
59#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
60pub struct ImageContent {
61    pub source_type: ImageSourceType,
62    pub media_type: String,
63    pub data: String,
64}
65
66/// Tool call in assistant message
67#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
68pub struct ToolCallContent {
69    pub id: String,
70    pub name: String,
71    pub arguments: String,
72}
73
74/// Tool result in tool message
75#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
76pub struct ToolResultContent {
77    pub tool_call_id: String,
78    pub content: String,
79}
80
81/// Content block types
82#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
83#[serde(tag = "type", rename_all = "snake_case")]
84pub enum Content {
85    Text(TextContent),
86    Image(ImageContent),
87    ToolCall(ToolCallContent),
88    ToolResult(ToolResultContent),
89}
90
91impl Content {
92    pub fn text(text: impl Into<String>) -> Self {
93        Content::Text(TextContent::new(text))
94    }
95}
96
97/// Unified message representation
98#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
99pub struct Message {
100    pub role: Role,
101    pub content: Vec<Content>,
102    #[serde(skip_serializing_if = "Option::is_none")]
103    pub source: Option<Source>,
104    #[serde(skip_serializing_if = "Option::is_none")]
105    pub raw: Option<serde_json::Value>,
106}
107
108impl Message {
109    pub fn new(role: Role, content: Vec<Content>) -> Self {
110        Self {
111            role,
112            content,
113            source: None,
114            raw: None,
115        }
116    }
117
118    pub fn text(role: Role, text: impl Into<String>) -> Self {
119        Self::new(role, vec![Content::text(text)])
120    }
121
122    pub fn with_source(mut self, source: Source) -> Self {
123        self.source = Some(source);
124        self
125    }
126
127    pub fn with_raw(mut self, raw: serde_json::Value) -> Self {
128        self.raw = Some(raw);
129        self
130    }
131}
132
133/// Unified chat completion request
134#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
135pub struct ChatRequest {
136    pub model: String,
137    pub messages: Vec<Message>,
138    #[serde(skip_serializing_if = "Option::is_none")]
139    pub max_tokens: Option<u32>,
140    #[serde(skip_serializing_if = "Option::is_none")]
141    pub temperature: Option<f32>,
142    #[serde(skip_serializing_if = "Option::is_none")]
143    pub top_p: Option<f32>,
144    #[serde(default)]
145    pub stream: bool,
146}
147
148/// Token usage information
149#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
150pub struct Usage {
151    pub prompt_tokens: u32,
152    pub completion_tokens: u32,
153    pub total_tokens: u32,
154}
155
156/// A single completion choice
157#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
158pub struct Choice {
159    pub index: u32,
160    pub message: Message,
161    #[serde(skip_serializing_if = "Option::is_none")]
162    pub finish_reason: Option<String>,
163}
164
165/// Unified chat completion response
166#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
167pub struct ChatResponse {
168    pub id: String,
169    pub model: String,
170    pub choices: Vec<Choice>,
171    #[serde(skip_serializing_if = "Option::is_none")]
172    pub usage: Option<Usage>,
173}
174
175// ============================================================================
176// Stream IR Types
177// ============================================================================
178
179/// Text content delta in streaming response
180#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
181pub struct TextDelta {
182    pub text: String,
183}
184
185/// Tool call delta in streaming response
186#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
187pub struct ToolCallDelta {
188    pub index: u32,
189    #[serde(skip_serializing_if = "Option::is_none")]
190    pub id: Option<String>,
191    #[serde(skip_serializing_if = "Option::is_none")]
192    pub name: Option<String>,
193    #[serde(skip_serializing_if = "Option::is_none")]
194    pub arguments: Option<String>,
195}
196
197/// Delta content types
198#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
199#[serde(tag = "type", rename_all = "snake_case")]
200pub enum DeltaContent {
201    TextDelta(TextDelta),
202    ToolCallDelta(ToolCallDelta),
203}
204
205/// A single choice delta in streaming response
206#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
207pub struct ChoiceDelta {
208    pub index: u32,
209    pub content: Vec<DeltaContent>,
210    #[serde(skip_serializing_if = "Option::is_none")]
211    pub finish_reason: Option<String>,
212}
213
214/// Unified streaming response chunk
215#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
216pub struct StreamChunk {
217    pub id: String,
218    pub model: String,
219    pub choices: Vec<ChoiceDelta>,
220    #[serde(skip_serializing_if = "Option::is_none")]
221    pub usage: Option<Usage>,
222    #[serde(skip_serializing_if = "Option::is_none")]
223    pub source: Option<Source>,
224    #[serde(skip_serializing_if = "Option::is_none")]
225    pub raw: Option<serde_json::Value>,
226}