Skip to main content

rune_chain_core/
chain.rs

1use std::{collections::HashMap, pin::Pin};
2
3use async_trait::async_trait;
4use futures::Stream;
5use serde_json::Value;
6
7use crate::{ChainError, GenerateResult, PromptArgs, StreamData};
8
9const DEFAULT_OUTPUT_KEY: &str = "output";
10const DEFAULT_RESULT_KEY: &str = "generate_result";
11
12/// A composable unit of LLM work that maps [`PromptArgs`] to a [`GenerateResult`].
13///
14/// Chains are the core abstraction of the `rune-chain` ecosystem. A chain may wrap
15/// a single LLM call, a retrieval step, a sequence of sub-chains, or any other
16/// unit of work that accepts named variables and produces generated text.
17///
18/// Implementors must provide [`Chain::call`]; all other methods have default
19/// implementations built on top of it.
20///
21/// # Example
22///
23/// ```rust,ignore
24/// use rune_chain_core::{Chain, ChainError, GenerateResult, PromptArgs, prompt_args};
25/// use async_trait::async_trait;
26///
27/// struct EchoChain;
28///
29/// #[async_trait]
30/// impl Chain for EchoChain {
31///     async fn call(&self, input: PromptArgs) -> Result<GenerateResult, ChainError> {
32///         let text = input["input"].as_str().unwrap_or("").to_string();
33///         Ok(GenerateResult::from_text(text))
34///     }
35/// }
36///
37/// # tokio_test::block_on(async {
38/// let chain = EchoChain;
39/// let result = chain.invoke(prompt_args! { "input" => "hello" }).await.unwrap();
40/// assert_eq!(result, "hello");
41/// # });
42/// ```
43#[async_trait]
44pub trait Chain: Sync + Send {
45    /// Run the chain and return the full [`GenerateResult`] including token usage.
46    async fn call(&self, input_variables: PromptArgs) -> Result<GenerateResult, ChainError>;
47
48    /// Run the chain and return the generated text string only.
49    ///
50    /// Convenience wrapper around [`Chain::call`] that discards token usage.
51    async fn invoke(&self, input_variables: PromptArgs) -> Result<String, ChainError> {
52        self.call(input_variables).await.map(|r| r.generation)
53    }
54
55    /// Run the chain and return a named-output map ready to pipe into the next step.
56    ///
57    /// The returned map contains:
58    /// - the key from [`Chain::output_keys`] (default: `"output"`) → generated text
59    /// - `"generate_result"` → the full serialised [`GenerateResult`]
60    async fn execute(
61        &self,
62        input_variables: PromptArgs,
63    ) -> Result<HashMap<String, Value>, ChainError> {
64        let result = self.call(input_variables).await?;
65        let output_key = self
66            .output_keys()
67            .into_iter()
68            .next()
69            .unwrap_or_else(|| DEFAULT_OUTPUT_KEY.to_string());
70        let mut map = HashMap::new();
71        map.insert(output_key, Value::String(result.generation.clone()));
72        map.insert(
73            DEFAULT_RESULT_KEY.to_string(),
74            serde_json::to_value(&result).unwrap_or(Value::Null),
75        );
76        Ok(map)
77    }
78
79    /// Stream tokens as they are produced rather than waiting for the full completion.
80    ///
81    /// Returns a [`Stream`] of [`StreamData`] chunks. Chains that do not support
82    /// streaming return [`ChainError::Other`] by default.
83    ///
84    /// > [!NOTE]
85    /// > Memory layers cannot be updated automatically during a stream because the
86    /// > complete output is only known when the stream ends. Callers are responsible
87    /// > for persisting the accumulated output after draining the stream.
88    async fn stream(
89        &self,
90        _input_variables: PromptArgs,
91    ) -> Result<Pin<Box<dyn Stream<Item = Result<StreamData, ChainError>> + Send>>, ChainError>
92    {
93        Err(ChainError::Other(
94            "streaming is not implemented for this chain".into(),
95        ))
96    }
97
98    /// The variable names this chain reads from its [`PromptArgs`] input.
99    fn input_keys(&self) -> Vec<String> {
100        vec![]
101    }
102
103    /// The keys this chain writes into the map returned by [`Chain::execute`].
104    fn output_keys(&self) -> Vec<String> {
105        vec![
106            DEFAULT_OUTPUT_KEY.to_string(),
107            DEFAULT_RESULT_KEY.to_string(),
108        ]
109    }
110}
111
112impl<C: Chain + 'static> From<C> for Box<dyn Chain> {
113    fn from(chain: C) -> Self {
114        Box::new(chain)
115    }
116}