- Split zclaw-kernel/kernel.rs (1486 lines) into 9 domain modules - Split zclaw-kernel/generation.rs (1080 lines) into 3 modules - Add DeerFlow-inspired middleware: DanglingTool, SubagentLimit, ToolError, ToolOutputGuard - Add PromptBuilder for structured system prompt assembly - Add FactStore (zclaw-memory) for persistent fact extraction - Add task builtin tool for agent task management - Driver improvements: Anthropic/OpenAI extended thinking, Gemini safety settings - Replace let _ = with proper log::warn! across SaaS handlers - Remove unused dependency (url) from zclaw-hands
180 lines
4.7 KiB
Rust
180 lines
4.7 KiB
Rust
//! LLM Driver trait and implementations
|
|
//!
|
|
//! This module provides a unified interface for multiple LLM providers.
|
|
|
|
use async_trait::async_trait;
|
|
use futures::Stream;
|
|
use secrecy::SecretString;
|
|
use serde::{Deserialize, Serialize};
|
|
use std::pin::Pin;
|
|
use zclaw_types::Result;
|
|
|
|
use crate::stream::StreamChunk;
|
|
|
|
mod anthropic;
|
|
mod openai;
|
|
mod gemini;
|
|
mod local;
|
|
|
|
pub use anthropic::AnthropicDriver;
|
|
pub use openai::OpenAiDriver;
|
|
pub use gemini::GeminiDriver;
|
|
pub use local::LocalDriver;
|
|
|
|
/// LLM Driver trait - unified interface for all providers
|
|
#[async_trait]
|
|
pub trait LlmDriver: Send + Sync {
|
|
/// Get the provider name
|
|
fn provider(&self) -> &str;
|
|
|
|
/// Send a completion request
|
|
async fn complete(&self, request: CompletionRequest) -> Result<CompletionResponse>;
|
|
|
|
/// Send a streaming completion request
|
|
/// Returns a stream of chunks
|
|
fn stream(
|
|
&self,
|
|
request: CompletionRequest,
|
|
) -> Pin<Box<dyn Stream<Item = Result<StreamChunk>> + Send + '_>>;
|
|
|
|
/// Check if the driver is properly configured
|
|
fn is_configured(&self) -> bool;
|
|
}
|
|
|
|
/// Completion request
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct CompletionRequest {
|
|
/// Model identifier
|
|
pub model: String,
|
|
/// System prompt
|
|
pub system: Option<String>,
|
|
/// Conversation messages
|
|
pub messages: Vec<zclaw_types::Message>,
|
|
/// Available tools
|
|
pub tools: Vec<ToolDefinition>,
|
|
/// Maximum tokens to generate
|
|
pub max_tokens: Option<u32>,
|
|
/// Temperature (0.0 - 1.0)
|
|
pub temperature: Option<f32>,
|
|
/// Stop sequences
|
|
pub stop: Vec<String>,
|
|
/// Enable streaming
|
|
pub stream: bool,
|
|
/// Enable extended thinking/reasoning
|
|
#[serde(default)]
|
|
pub thinking_enabled: bool,
|
|
/// Reasoning effort level (for providers that support it)
|
|
#[serde(default)]
|
|
pub reasoning_effort: Option<String>,
|
|
/// Enable plan mode
|
|
#[serde(default)]
|
|
pub plan_mode: bool,
|
|
}
|
|
|
|
impl Default for CompletionRequest {
|
|
fn default() -> Self {
|
|
Self {
|
|
model: String::new(),
|
|
system: None,
|
|
messages: Vec::new(),
|
|
tools: Vec::new(),
|
|
max_tokens: Some(4096),
|
|
temperature: Some(0.7),
|
|
stop: Vec::new(),
|
|
stream: false,
|
|
thinking_enabled: false,
|
|
reasoning_effort: None,
|
|
plan_mode: false,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Tool definition for LLM function calling.
|
|
/// Re-exported from `zclaw_types::tool::ToolDefinition` (canonical definition).
|
|
pub use zclaw_types::tool::ToolDefinition;
|
|
|
|
/// Completion response
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct CompletionResponse {
|
|
/// Generated content blocks
|
|
pub content: Vec<ContentBlock>,
|
|
/// Model used
|
|
pub model: String,
|
|
/// Input tokens
|
|
pub input_tokens: u32,
|
|
/// Output tokens
|
|
pub output_tokens: u32,
|
|
/// Stop reason
|
|
pub stop_reason: StopReason,
|
|
}
|
|
|
|
/// LLM driver response content block (subset of canonical zclaw_types::ContentBlock).
|
|
/// Used internally by Anthropic/OpenAI/Gemini/Local drivers for API response parsing.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
#[serde(tag = "type", rename_all = "snake_case")]
|
|
pub enum ContentBlock {
|
|
Text { text: String },
|
|
Thinking { thinking: String },
|
|
ToolUse { id: String, name: String, input: serde_json::Value },
|
|
}
|
|
|
|
/// Stop reason
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
#[serde(rename_all = "snake_case")]
|
|
pub enum StopReason {
|
|
EndTurn,
|
|
MaxTokens,
|
|
StopSequence,
|
|
ToolUse,
|
|
Error,
|
|
}
|
|
|
|
/// Driver configuration
|
|
#[derive(Debug, Clone)]
|
|
pub enum DriverConfig {
|
|
Anthropic { api_key: SecretString },
|
|
OpenAi { api_key: SecretString, base_url: Option<String> },
|
|
Gemini { api_key: SecretString },
|
|
Local { base_url: String },
|
|
}
|
|
|
|
impl DriverConfig {
|
|
pub fn anthropic(api_key: impl Into<String>) -> Self {
|
|
Self::Anthropic {
|
|
api_key: SecretString::new(api_key.into()),
|
|
}
|
|
}
|
|
|
|
pub fn openai(api_key: impl Into<String>) -> Self {
|
|
Self::OpenAi {
|
|
api_key: SecretString::new(api_key.into()),
|
|
base_url: None,
|
|
}
|
|
}
|
|
|
|
pub fn openai_with_base(api_key: impl Into<String>, base_url: impl Into<String>) -> Self {
|
|
Self::OpenAi {
|
|
api_key: SecretString::new(api_key.into()),
|
|
base_url: Some(base_url.into()),
|
|
}
|
|
}
|
|
|
|
pub fn gemini(api_key: impl Into<String>) -> Self {
|
|
Self::Gemini {
|
|
api_key: SecretString::new(api_key.into()),
|
|
}
|
|
}
|
|
|
|
pub fn ollama() -> Self {
|
|
Self::Local {
|
|
base_url: "http://localhost:11434".to_string(),
|
|
}
|
|
}
|
|
|
|
pub fn local(base_url: impl Into<String>) -> Self {
|
|
Self::Local {
|
|
base_url: base_url.into(),
|
|
}
|
|
}
|
|
}
|