Skip to main content

lc_core/language_models/
chat.rs

1// src/core/language_models/chat.rs
2//! Chat model base trait.
3
4use super::BaseLanguageModel;
5use crate::tools::ToolDefinition;
6use crate::RunnableConfig;
7use async_trait::async_trait;
8use futures_util::Stream;
9use lc_schema::Message;
10use lc_shared::tools::ToolCall;
11use serde::{Deserialize, Serialize};
12use std::pin::Pin;
13
14/// LLM result containing response content and metadata.
15#[derive(Debug, Clone, Serialize, Deserialize, Default)]
16pub struct LLMResult {
17    /// The generated response content.
18    #[serde(default)]
19    pub content: String,
20    /// The model identifier that produced the result.
21    #[serde(default)]
22    pub model: String,
23    /// Token usage statistics, if reported.
24    #[serde(default)]
25    pub token_usage: Option<TokenUsage>,
26    /// Tool calls requested by the model, if any.
27    #[serde(default)]
28    pub tool_calls: Option<Vec<ToolCall>>,
29    /// Model reasoning/thinking content, if present.
30    #[serde(default, skip_serializing_if = "Option::is_none")]
31    pub thinking_content: Option<String>,
32}
33
34/// Token usage statistics.
35#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct TokenUsage {
37    /// Input token count.
38    pub prompt_tokens: usize,
39
40    /// Output token count.
41    pub completion_tokens: usize,
42
43    /// Total token count.
44    pub total_tokens: usize,
45}
46
47/// Base trait for chat models.
48///
49/// Extends BaseLanguageModel for chat scenarios.
50/// Accepts message list as input, returns AI message.
51#[async_trait]
52pub trait BaseChatModel: BaseLanguageModel<Vec<Message>, LLMResult> {
53    /// Chat with the model.
54    ///
55    /// # Arguments
56    /// * `messages` - Message list.
57    /// * `config` - Optional configuration.
58    ///
59    /// # Returns
60    /// LLM result.
61    async fn chat(
62        &self,
63        messages: Vec<Message>,
64        config: Option<RunnableConfig>,
65    ) -> Result<LLMResult, Self::Error>;
66
67    /// Stream chat with the model.
68    ///
69    /// # Arguments
70    /// * `messages` - Message list.
71    /// * `config` - Optional configuration.
72    ///
73    /// # Returns
74    /// Stream of output chunks.
75    async fn stream_chat(
76        &self,
77        messages: Vec<Message>,
78        config: Option<RunnableConfig>,
79    ) -> Result<Pin<Box<dyn Stream<Item = Result<String, Self::Error>> + Send>>, Self::Error>;
80
81    /// Chat with system prompt.
82    ///
83    /// # Arguments
84    /// * `system` - System prompt.
85    /// * `messages` - Message list.
86    ///
87    /// # Returns
88    /// LLM result.
89    async fn chat_with_system(
90        &self,
91        system: String,
92        messages: Vec<Message>,
93    ) -> Result<LLMResult, Self::Error> {
94        let full_messages = vec![Message::system(system)]
95            .into_iter()
96            .chain(messages)
97            .collect();
98
99        self.chat(full_messages, None).await
100    }
101
102    /// Bind tool definitions for function calling.
103    ///
104    /// Returns `Some(model)` with the tools attached when the provider
105    /// supports tool calling; returns `None` when it does not. **The default
106    /// returns `None`, signalling a hard capability limit** — callers MUST
107    /// treat `None` as "this model cannot call tools" and branch accordingly
108    /// (e.g. fall back to text-only prompting). Providers that support
109    /// function calling (OpenAI, Ollama) override this.
110    ///
111    /// This is an explicit result, not a silent degrade: `None` is the honest
112    /// answer that tool-calling is unavailable on this model.
113    fn bind_tools(
114        &self,
115        _tools: Vec<ToolDefinition>,
116    ) -> Option<Box<dyn BaseChatModel<Error = Self::Error> + Send + Sync>> {
117        None
118    }
119}