Skip to main content

agnt_core/
backend_trait.rs

1//! The [`LlmBackend`] trait — abstract LLM inference provider.
2//!
3//! Implementors live in `agnt-net` (HTTP backends like Ollama/OpenAI/Anthropic)
4//! or can be user-provided (WASM-side fetch, local Candle, mock for testing).
5
6use crate::message::Message;
7use serde_json::Value;
8
9/// Error returned by [`LlmBackend::chat`].
10#[derive(Debug, Clone)]
11pub enum BackendError {
12    /// Transport-level failure (network, DNS, TLS handshake).
13    Transport(String),
14    /// HTTP status error from an upstream API.
15    Http { code: u16, body: String },
16    /// Response parsing failure (malformed JSON, missing fields).
17    Parse(String),
18    /// All retries exhausted.
19    Retry(String),
20    /// Provider returned a structured error (rate limit, auth, etc.).
21    Provider(String),
22}
23
24impl std::fmt::Display for BackendError {
25    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26        match self {
27            Self::Transport(e) => write!(f, "transport: {}", e),
28            Self::Http { code, body } => write!(f, "http {}: {}", code, body),
29            Self::Parse(e) => write!(f, "parse: {}", e),
30            Self::Retry(e) => write!(f, "retry: {}", e),
31            Self::Provider(e) => write!(f, "provider: {}", e),
32        }
33    }
34}
35
36impl std::error::Error for BackendError {}
37
38/// Abstract LLM backend.
39///
40/// Implementors translate the internal OpenAI-flavored [`Message`] format to
41/// whatever wire format the provider expects, stream the response, parse it
42/// back into a [`Message`], and return. The optional `on_token` callback is
43/// invoked as text tokens arrive so callers can render progressively.
44pub trait LlmBackend: Send + Sync {
45    /// The model identifier this backend is configured for (e.g. `gemma4:e4b`).
46    fn model(&self) -> &str;
47
48    /// Run one inference turn with the given conversation and tool schemas.
49    ///
50    /// `tools` is a JSON array in OpenAI's `function` tool format; backends
51    /// translate to provider-native format as needed.
52    ///
53    /// If `on_token` is `Some`, text deltas are pushed into it as they arrive
54    /// from the wire.
55    fn chat(
56        &self,
57        messages: &[Message],
58        tools: &Value,
59        on_token: Option<&mut dyn FnMut(&str)>,
60    ) -> Result<Message, BackendError>;
61}