Skip to main content

aacode_rs/llm/
types.rs

1// Copyright (c) 2026 xiefujin <490021684@qq.com>
2// Licensed under GPL-3.0, see LICENSE file for full license terms.
3
4//! Shared LLM types: chat messages, tool calls, response envelope.
5
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8
9/// One conversation message. Mirrors the OpenAI chat message shape plus the
10/// optional `reasoning_content` used by DeepSeek/Kimi thinking models.
11#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct ChatMessage {
13    pub role: String, // system | user | assistant | tool
14    #[serde(default)]
15    pub content: String,
16    #[serde(skip_serializing_if = "Option::is_none", default)]
17    pub tool_calls: Option<Vec<ToolCall>>,
18    #[serde(skip_serializing_if = "Option::is_none", default)]
19    pub tool_call_id: Option<String>,
20    #[serde(skip_serializing_if = "Option::is_none", default)]
21    pub reasoning_content: Option<String>,
22}
23
24impl ChatMessage {
25    pub fn system(content: impl Into<String>) -> Self {
26        Self::simple("system", content)
27    }
28    pub fn user(content: impl Into<String>) -> Self {
29        Self::simple("user", content)
30    }
31    pub fn assistant(content: impl Into<String>) -> Self {
32        Self::simple("assistant", content)
33    }
34    pub fn simple(role: &str, content: impl Into<String>) -> Self {
35        ChatMessage {
36            role: role.to_string(),
37            content: content.into(),
38            tool_calls: None,
39            tool_call_id: None,
40            reasoning_content: None,
41        }
42    }
43    /// A tool result message answering a specific tool_call id.
44    pub fn tool_result(tool_call_id: impl Into<String>, content: impl Into<String>) -> Self {
45        ChatMessage {
46            role: "tool".to_string(),
47            content: content.into(),
48            tool_calls: None,
49            tool_call_id: Some(tool_call_id.into()),
50            reasoning_content: None,
51        }
52    }
53    /// An assistant message that carries tool_calls.
54    pub fn assistant_with_tools(content: impl Into<String>, tool_calls: Vec<ToolCall>) -> Self {
55        ChatMessage {
56            role: "assistant".to_string(),
57            content: content.into(),
58            tool_calls: Some(tool_calls),
59            tool_call_id: None,
60            reasoning_content: None,
61        }
62    }
63}
64
65/// A single tool/function call requested by the model.
66#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
67pub struct ToolCall {
68    pub id: String,
69    pub name: String,
70    /// Raw JSON arguments string (as returned by the API). Parse before use.
71    pub arguments: String,
72}
73
74impl ToolCall {
75    /// Parse the arguments string into a JSON value. Returns an empty object
76    /// on parse failure (mirrors the Python fallback of `{}`).
77    pub fn parsed_args(&self) -> Value {
78        serde_json::from_str(&self.arguments).unwrap_or_else(|_| serde_json::json!({}))
79    }
80}
81
82/// The result of one streamed model call.
83#[derive(Debug, Clone, Default)]
84pub struct LlmResponse {
85    pub text: String,
86    pub tool_calls: Vec<ToolCall>,
87    pub reasoning_content: Option<String>,
88    /// "length"/"max_tokens" indicates truncation.
89    pub finish_reason: Option<String>,
90}
91
92impl LlmResponse {
93    pub fn is_truncated(&self) -> bool {
94        matches!(
95            self.finish_reason.as_deref(),
96            Some("length") | Some("max_tokens") | Some("connection_closed")
97        )
98    }
99}