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}