refactoring
This commit is contained in:
parent
04ece6afb1
commit
13e17770ca
12 changed files with 634 additions and 598 deletions
166
src/agent.rs
166
src/agent.rs
|
|
@ -1,166 +0,0 @@
|
|||
use chrono::Utc;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use crate::api::{ChatRequest, ChatResponse, Message, Tool};
|
||||
use crate::tools;
|
||||
|
||||
pub struct Agent {
|
||||
client: reqwest::Client,
|
||||
url: String,
|
||||
zen_api_key: Option<String>,
|
||||
tavily_api_key: Option<String>,
|
||||
messages: Vec<Message>,
|
||||
tools: Option<Vec<Tool>>,
|
||||
logs: String,
|
||||
answer: Option<String>,
|
||||
}
|
||||
|
||||
impl Agent {
|
||||
pub fn new(
|
||||
zen_api_key: Option<String>,
|
||||
tavily_api_key: Option<String>,
|
||||
initial_message: String,
|
||||
) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
let intro = format!(
|
||||
"You are an autonomous agent. You have access to tools that can help
|
||||
you achieve your goals. Use them wisely. The user is unable to respond to you
|
||||
so do not ask for clarification and use the
|
||||
answer tool once you to give your final answer. current date is {}",
|
||||
Utc::now().format("%B %d, %Y %H:%M:%S UTC").to_string()
|
||||
);
|
||||
let messages = vec![
|
||||
Message {
|
||||
role: "system".to_string(),
|
||||
content: Some(intro),
|
||||
tool_calls: None,
|
||||
tool_call_id: None,
|
||||
},
|
||||
Message {
|
||||
role: "user".to_string(),
|
||||
content: Some(initial_message),
|
||||
tool_calls: None,
|
||||
tool_call_id: None,
|
||||
},
|
||||
];
|
||||
|
||||
let tools = Some(tools::get_tools());
|
||||
|
||||
let client = reqwest::Client::builder()
|
||||
.timeout(std::time::Duration::from_secs(60))
|
||||
.build()?;
|
||||
|
||||
Ok(Self {
|
||||
client,
|
||||
url: "https://opencode.ai/zen/v1/chat/completions".to_string(),
|
||||
zen_api_key,
|
||||
tavily_api_key,
|
||||
messages,
|
||||
tools,
|
||||
logs: String::new(),
|
||||
answer: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn log(&mut self, message: &str) {
|
||||
self.logs.push_str(message);
|
||||
self.logs.push('\n');
|
||||
}
|
||||
|
||||
pub async fn run(
|
||||
&mut self,
|
||||
config: &crate::config::Config,
|
||||
) -> Result<(String, Option<String>), Box<dyn std::error::Error>> {
|
||||
let mut finished = false;
|
||||
let start_time = Instant::now();
|
||||
let max_duration = Duration::from_secs(config.agent_max_duration_secs);
|
||||
let max_turns = config.agent_max_turns;
|
||||
let mut turns = 0;
|
||||
|
||||
while !finished {
|
||||
if start_time.elapsed() > max_duration {
|
||||
return Err("Agent run timed out".into());
|
||||
}
|
||||
|
||||
if turns >= max_turns {
|
||||
return Err("Agent run exceeded max turns".into());
|
||||
}
|
||||
|
||||
turns += 1;
|
||||
let request = ChatRequest {
|
||||
model: "kimi-k2.5".to_string(),
|
||||
messages: self.messages.clone(),
|
||||
tools: self.tools.clone(),
|
||||
};
|
||||
|
||||
self.log(&format!(
|
||||
"--- Sending request to Zen API (Role: {}) ---",
|
||||
self.messages.last().unwrap().role
|
||||
));
|
||||
|
||||
let mut request_builder = self.client.post(&self.url).json(&request);
|
||||
|
||||
if let Some(key) = &self.zen_api_key {
|
||||
request_builder =
|
||||
request_builder.header("Authorization", format!("Bearer {}", key));
|
||||
}
|
||||
|
||||
let response = request_builder.send().await?;
|
||||
|
||||
if !response.status().is_success() {
|
||||
let status = response.status();
|
||||
let error_text = response.text().await?;
|
||||
self.log(&format!("Error: API request failed with status {}", status));
|
||||
self.log(&format!("Error details: {}", error_text));
|
||||
return Err(format!("API request failed: {}", status).into());
|
||||
}
|
||||
|
||||
let chat_response: ChatResponse = response.json().await?;
|
||||
let assistant_message = chat_response
|
||||
.choices
|
||||
.get(0)
|
||||
.ok_or("Missing assistant response")?
|
||||
.message
|
||||
.clone();
|
||||
|
||||
self.messages.push(assistant_message.clone());
|
||||
|
||||
if let Some(content) = &assistant_message.content {
|
||||
if !content.is_empty() {
|
||||
self.log(&format!("\nAssistant response:\n{}\n", content));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(tool_calls) = assistant_message.tool_calls {
|
||||
for tool_call in tool_calls {
|
||||
let (tool_message, written, tool_answer) =
|
||||
tools::handle_tool_call(&tool_call, &self.tavily_api_key).await?;
|
||||
|
||||
if let Some(ans) = tool_answer {
|
||||
self.answer = Some(ans);
|
||||
}
|
||||
|
||||
if let Some(content) = &tool_message.content {
|
||||
self.log(&format!(
|
||||
"Tool result ({}): {}",
|
||||
tool_call.function.name, content
|
||||
));
|
||||
}
|
||||
|
||||
self.messages.push(tool_message);
|
||||
if written {
|
||||
finished = true;
|
||||
}
|
||||
}
|
||||
// Continue the loop to send tool results back
|
||||
continue;
|
||||
}
|
||||
|
||||
// No tool calls from assistant, but we only exit if the task was finished
|
||||
if !finished {
|
||||
self.log("--- Assistant didn't finish yet. Waiting for next turn... ---");
|
||||
}
|
||||
}
|
||||
|
||||
Ok((self.logs.clone(), self.answer.clone()))
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue