use std::{
collections::HashMap,
sync::{Arc, Mutex},
};
use async_trait::async_trait;
use tiny_agent::{
Agent, AgentRunTimeBuilder, StorageError,
checkpoint::CheckpointStorage,
providers::openai_compatible::OpenAiCompatibleProvider,
sandbox::HostSandbox,
tools::{ToolRegistry, register_default_tools},
transcript::{Transcript, TranscriptStorage},
};
use tokio_util::sync::CancellationToken;
#[derive(Default)]
struct MemoryStore {
checkpoints: Mutex<HashMap<String, String>>,
transcripts: Mutex<HashMap<String, Transcript>>,
}
#[async_trait]
impl CheckpointStorage for MemoryStore {
async fn save_checkpoint(&self, session_id: &str, agent: &Agent) -> Result<(), StorageError> {
let json = serde_json::to_string(agent).map_err(|e| StorageError::serde(e.to_string()))?;
self.checkpoints
.lock()
.unwrap()
.insert(session_id.to_string(), json);
Ok(())
}
async fn get_checkpoint(&self, session_id: &str) -> Result<Option<Agent>, StorageError> {
let json = self.checkpoints.lock().unwrap().get(session_id).cloned();
match json {
Some(json) => {
let agent =
serde_json::from_str(&json).map_err(|e| StorageError::serde(e.to_string()))?;
Ok(Some(agent))
}
None => Ok(None),
}
}
async fn delete_checkpoint(&self, session_id: &str) -> Result<(), StorageError> {
self.checkpoints.lock().unwrap().remove(session_id);
Ok(())
}
}
#[async_trait]
impl TranscriptStorage for MemoryStore {
async fn save_transcript(
&self,
session_id: &str,
transcript: &Transcript,
) -> Result<(), StorageError> {
self.transcripts
.lock()
.unwrap()
.insert(session_id.to_string(), transcript.clone());
Ok(())
}
async fn get_transcript(&self, session_id: &str) -> Result<Option<Transcript>, StorageError> {
Ok(self.transcripts.lock().unwrap().get(session_id).cloned())
}
async fn delete_transcript(&self, session_id: &str) -> Result<(), StorageError> {
self.transcripts.lock().unwrap().remove(session_id);
Ok(())
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let api_key = std::env::var("LLM_API_KEY")
.or_else(|_| std::env::var("DEEPSEEK_API_KEY"))
.expect("请设置 LLM_API_KEY 环境变量");
let base_url =
std::env::var("LLM_BASE_URL").unwrap_or_else(|_| "https://api.deepseek.com/v1".to_string());
let model = std::env::var("LLM_MODEL").unwrap_or_else(|_| "deepseek-chat".to_string());
let provider = Arc::new(OpenAiCompatibleProvider::new(api_key, base_url));
let store = Arc::new(MemoryStore::default());
let mut tools = ToolRegistry::new();
register_default_tools(&mut tools);
let runtime = AgentRunTimeBuilder::new()
.provider(provider)
.model(model)
.checkpoint_storage(store.clone())
.transcript_storage(store.clone())
.sandbox(Arc::new(HostSandbox::new()))
.tools(tools)
.with_system_prompt("你是 tiny-agent 的最小示例助手,请简洁作答。")
.build()?;
let session_id = runtime.create_session().await?;
let prompt = std::env::args()
.nth(1)
.unwrap_or_else(|| "用 bash 执行 echo hello,然后用一句话总结。".to_string());
let ready = runtime.submit(&session_id, prompt).await?;
let outcome = runtime.run(ready, CancellationToken::new()).await?;
match outcome {
Agent::Success(state) => {
println!("\n=== 完成 ===\n{}", state.final_resp.unwrap_or_default());
}
Agent::WaitingForUser(_, interaction) => {
println!(
"\n=== 等待用户 ===\n{}: {}",
interaction.kind, interaction.payload
);
}
Agent::Fail(_, failure) => {
println!("\n=== 失败 ===\n{}: {}", failure.kind, failure.message);
}
other => println!("\n=== 状态 ===\n{other:?}"),
}
Ok(())
}