69 lines
2.3 KiB
Rust
69 lines
2.3 KiB
Rust
|
|
//! 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);
|
||
|
|
}
|
||
|
|
}
|