LazyBoy2/crates/grokboy-core/src/agent.rs

69 lines
2.3 KiB
Rust
Raw Normal View History

//! Multi-step OpenAI-compatible tool-calling ReAct loop.
use crate::config::Config;
use crate::model::{ChatMessage, chat_completion};
use crate::tools::{ToolContext, execute_tool, tool_definitions};
use anyhow::Result;
pub const DEFAULT_MAX_ROUNDS: usize = 12;
pub const AGENT_SYSTEM: &str = "\
You are GrokBoy, a concise local coding assistant with tools.
Use tools when they help solve the task; otherwise answer directly.
Prefer short, clear answers. Traditional Chinese is welcome when the user writes in Chinese.
Available tools: shell, list_dir, read_file, write_file.
Do not invent tool results call the tools. Stop when you can give a final answer.";
/// Run the agent loop until the model returns text without tool_calls or max rounds.
/// Appends all intermediate messages (assistant tool_calls + tool results + final) to `messages`.
/// Returns the final assistant text (may be empty if stopped on round limit with only tools).
pub async fn run_agent(
config: &Config,
messages: &mut Vec<ChatMessage>,
tool_ctx: &ToolContext,
max_rounds: usize,
) -> Result<String> {
let tools = tool_definitions();
let mut last_text = String::new();
for _round in 0..max_rounds {
let reply = chat_completion(config, messages, Some(&tools)).await?;
let tool_calls = reply.tool_calls.clone().unwrap_or_default();
if tool_calls.is_empty() {
last_text = reply.text().to_string();
messages.push(reply);
return Ok(last_text);
}
// Keep any content the model sent alongside tool_calls.
if let Some(c) = reply.content.as_ref().filter(|s| !s.is_empty()) {
last_text = c.clone();
}
messages.push(reply);
for call in &tool_calls {
let result = execute_tool(tool_ctx, &call.function.name, &call.function.arguments).await;
messages.push(ChatMessage::tool(&call.id, result));
}
}
Ok(last_text)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn system_prompt_mentions_tools() {
assert!(AGENT_SYSTEM.contains("shell"));
assert!(AGENT_SYSTEM.contains("Traditional Chinese") || AGENT_SYSTEM.contains("Chinese"));
}
#[test]
fn default_max_rounds_is_12() {
assert_eq!(DEFAULT_MAX_ROUNDS, 12);
}
}