Skip to main content

lc_core/language_models/
boxed.rs

1// src/core/language_models/boxed.rs
2//! Forwarding `BaseChatModel` implementations for boxed trait objects.
3//!
4//! `BaseChatModel::bind_tools` returns `Box<dyn BaseChatModel<Error = Self::Error> + Send + Sync>`.
5//! For a wrapper (e.g. `TokenTrackingLLM`) to re-wrap that box and keep
6//! counting, the box itself must satisfy the `L: BaseChatModel` bound — this
7//! module is the thin glue forwarding every method to the boxed model.
8//!
9//! The orphan rule is satisfied here because `Runnable`, `BaseLanguageModel`
10//! and `BaseChatModel` are all **local** to `lc-core` (unlike
11//! `lc-providers::wrapper::BoundModel`, which needed a private adapter because
12//! those traits are foreign there).
13
14use crate::language_models::{BaseChatModel, BaseLanguageModel, LLMResult, StreamChunk};
15use crate::runnables::Runnable;
16use crate::tools::ToolDefinition;
17use crate::RunnableConfig;
18use async_trait::async_trait;
19use futures_util::Stream;
20use lc_schema::Message;
21use std::pin::Pin;
22
23#[async_trait]
24impl<E> Runnable<Vec<Message>, LLMResult> for Box<dyn BaseChatModel<Error = E> + Send + Sync>
25where
26    E: std::error::Error + Send + Sync + 'static,
27{
28    type Error = E;
29
30    async fn invoke(
31        &self,
32        input: Vec<Message>,
33        config: Option<RunnableConfig>,
34    ) -> Result<LLMResult, Self::Error> {
35        (**self).invoke(input, config).await
36    }
37
38    async fn stream(
39        &self,
40        input: Vec<Message>,
41        config: Option<RunnableConfig>,
42    ) -> Result<Pin<Box<dyn Stream<Item = Result<LLMResult, Self::Error>> + Send>>, Self::Error>
43    {
44        (**self).stream(input, config).await
45    }
46}
47
48#[async_trait]
49impl<E> BaseLanguageModel<Vec<Message>, LLMResult>
50    for Box<dyn BaseChatModel<Error = E> + Send + Sync>
51where
52    E: std::error::Error + Send + Sync + 'static,
53{
54    fn model_name(&self) -> &str {
55        (**self).model_name()
56    }
57
58    fn get_num_tokens(&self, text: &str) -> usize {
59        (**self).get_num_tokens(text)
60    }
61
62    fn temperature(&self) -> Option<f32> {
63        (**self).temperature()
64    }
65
66    fn max_tokens(&self) -> Option<usize> {
67        (**self).max_tokens()
68    }
69
70    // `with_temperature`/`with_max_tokens` are `Self: Sized` and, on a boxed
71    // trait object, would replace the box — returning `self` is the honest
72    // no-op for this glue; sampling overrides flow through `RunnableConfig`
73    // (same note as `lc-providers::wrapper::BoundModel`).
74    fn with_temperature(self, _temp: f32) -> Self
75    where
76        Self: Sized,
77    {
78        self
79    }
80
81    fn with_max_tokens(self, _max: usize) -> Self
82    where
83        Self: Sized,
84    {
85        self
86    }
87}
88
89#[async_trait]
90impl<E> BaseChatModel for Box<dyn BaseChatModel<Error = E> + Send + Sync>
91where
92    E: std::error::Error + Send + Sync + 'static,
93{
94    async fn chat(
95        &self,
96        messages: Vec<Message>,
97        config: Option<RunnableConfig>,
98    ) -> Result<LLMResult, Self::Error> {
99        (**self).chat(messages, config).await
100    }
101
102    async fn stream_chat(
103        &self,
104        messages: Vec<Message>,
105        config: Option<RunnableConfig>,
106    ) -> Result<Pin<Box<dyn Stream<Item = Result<StreamChunk, Self::Error>> + Send>>, Self::Error>
107    {
108        (**self).stream_chat(messages, config).await
109    }
110
111    fn bind_tools(
112        &self,
113        tools: Vec<ToolDefinition>,
114    ) -> Option<Box<dyn BaseChatModel<Error = Self::Error> + Send + Sync>> {
115        // `Self::Error = E`, so the inner `bind_tools` already returns exactly
116        // `Option<Box<dyn BaseChatModel<Error = E> + Send + Sync>>`.
117        (**self).bind_tools(tools)
118    }
119}