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