tiny-agent 0.3.0

一个小而完整的 Rust LLM Agent 运行时:可中断、可恢复、可观测、可插拔的 agent loop / A small but complete LLM agent runtime in Rust — an interruptible, resumable, observable, pluggable agent loop.
Documentation
pub use super::shared::{Message, ToolDefinition};
use async_trait::async_trait;
use futures_util::stream::BoxStream;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::fmt;
pub mod anthropic;
pub mod openai_compatible;

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct LLMRequest {
    pub model: String,
    pub system: Option<String>,
    pub messages: Vec<Message>,
    pub tools: Vec<ToolDefinition>,
    pub temperature: Option<f32>,
    pub max_tokens: Option<u32>,
}

#[derive(Debug, Clone)]
pub enum ProviderStreamChunk {
    ContentDelta(String),
    ThinkingDelta(String),
    ToolCallDelta {
        index: usize,
        id: Option<String>,
        name: Option<String>,
        arguments_delta: String,
    },
    Done {
        finish_reason: FinishReason,
        usage: Option<TokenUsage>,
    },
}

#[derive(Debug, Clone)]
pub enum FinishReason {
    Stop,
    ToolCalls,
    Length,
    ContentFilter,
    Error,
    Unknown(String),
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TokenUsage {
    pub input_tokens: Option<u32>,
    pub output_tokens: Option<u32>,
    pub total_tokens: Option<u32>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ProviderErrorKind {
    Transient,
    Permanent,
    RateLimited { retry_after_secs: Option<u64> },
}

#[derive(Debug, Clone)]
pub struct ProviderError {
    pub kind: ProviderErrorKind,
    pub message: String,
}

impl ProviderError {
    pub fn transient(message: impl Into<String>) -> Self {
        Self {
            kind: ProviderErrorKind::Transient,
            message: message.into(),
        }
    }

    pub fn permanent(message: impl Into<String>) -> Self {
        Self {
            kind: ProviderErrorKind::Permanent,
            message: message.into(),
        }
    }

    pub fn rate_limited(message: impl Into<String>, retry_after_secs: Option<u64>) -> Self {
        Self {
            kind: ProviderErrorKind::RateLimited { retry_after_secs },
            message: message.into(),
        }
    }
}

impl fmt::Display for ProviderError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match &self.kind {
            ProviderErrorKind::Transient | ProviderErrorKind::Permanent => {
                f.write_str(&self.message)
            }
            ProviderErrorKind::RateLimited { retry_after_secs } => {
                if let Some(seconds) = retry_after_secs {
                    write!(f, "{} (retry_after={}s)", self.message, seconds)
                } else {
                    f.write_str(&self.message)
                }
            }
        }
    }
}

impl std::error::Error for ProviderError {}

#[async_trait]
pub trait LlmProvider: Send + Sync {
    fn name(&self) -> &str;
    fn request_snapshot(&self, req: &LLMRequest) -> Value {
        serde_json::to_value(req).unwrap_or(Value::Null)
    }
    async fn chat_completion(
        &self,
        req: LLMRequest,
    ) -> Result<BoxStream<'static, Result<ProviderStreamChunk, ProviderError>>, ProviderError>;
}