refactor: enhance system prompt handling and tool integration in LlmChatAgent

This commit is contained in:
2026-07-09 18:24:33 +08:00
parent ae4538a59a
commit c3493d5458
@@ -70,7 +70,7 @@ impl Hook for ToolValidationHook {
struct LlmChatAgentBuilder {
client: Option<LlmClient>,
custom_system_prompt: Option<String>,
system_prompt_parts: Vec<String>,
tools: Vec<Box<dyn ToolDyn>>,
hooks: Vec<Box<dyn Hook>>,
max_turns: usize,
@@ -84,7 +84,7 @@ impl AgentBuilder<LlmMessage, LlmCompletionModel> for LlmChatAgentBuilder {
fn new() -> Self {
Self {
client: None,
custom_system_prompt: None,
system_prompt_parts: Vec::new(),
tools: Vec::new(),
hooks: Vec::new(),
max_turns: 10,
@@ -99,7 +99,7 @@ impl AgentBuilder<LlmMessage, LlmCompletionModel> for LlmChatAgentBuilder {
}
fn system_prompt(mut self, system_prompt: impl Into<String>) -> Self {
self.custom_system_prompt = Some(system_prompt.into());
self.system_prompt_parts.push(system_prompt.into());
self
}
@@ -133,13 +133,23 @@ impl AgentBuilder<LlmMessage, LlmCompletionModel> for LlmChatAgentBuilder {
client: self.client.expect("call .with_client() first"),
};
let mut agent = Agent::new(model);
let tool_specs = self
.tools
.iter()
.map(|tool| ToolPromptSpec {
name: tool.tool_name().to_string(),
description: tool.description(),
parameters: tool.parameters(),
})
.collect::<Vec<_>>();
let base_prompt = self.system_prompt_parts.join("\n\n");
let system_prompt = build_tools_system_prompt(&base_prompt, &tool_specs);
agent.tools = self.tools;
agent.hooks = self.hooks;
agent.default_max_turns = self.max_turns;
agent.memory = self.memory_data;
if let Some(sp) = self.custom_system_prompt {
agent.system_prompt = Some(LlmMessage::system(&sp));
}
agent.context = self.context;
agent.system_prompt = Some(LlmMessage::system(&system_prompt));
agent
}
}
@@ -196,6 +206,12 @@ impl Tool for EchoTool {
// 6. System prompt builder
// ---------------------------------------------------------------------------
struct ToolPromptSpec {
name: String,
description: String,
parameters: serde_json::Value,
}
fn tool_names(agent: &Agent<LlmCompletionModel, LlmMessage>) -> Vec<String> {
agent
.tools()
@@ -204,12 +220,12 @@ fn tool_names(agent: &Agent<LlmCompletionModel, LlmMessage>) -> Vec<String> {
.collect()
}
fn build_tools_system_prompt(base_prompt: &str, tool_names: &[String]) -> String {
fn build_tools_system_prompt(base_prompt: &str, tool_specs: &[ToolPromptSpec]) -> String {
let mut prompt = String::new();
prompt.push_str(base_prompt);
prompt.push_str("\n\nYou have access to the following tools.\n\n");
if tool_names.is_empty() {
if tool_specs.is_empty() {
prompt.push_str("(No tools available.)\n");
} else {
prompt.push_str("## JSON Response Format\n");
@@ -220,7 +236,7 @@ fn build_tools_system_prompt(base_prompt: &str, tool_names: &[String]) -> String
prompt.push_str(" \"reply_text\": \"your message to the user\",\n");
prompt.push_str(" \"tool_calls\": [\n {\n");
prompt.push_str(" \"tool_name\": \"tool_name_here\",\n");
prompt.push_str(" \"args\": { /* tool-specific arguments */ }\n");
prompt.push_str(" \"args\": { \"argument_name\": \"argument_value\" }\n");
prompt.push_str(" }\n ]\n");
prompt.push_str("}\n\n");
prompt.push_str("If you don't need to use a tool, respond with:\n");
@@ -230,8 +246,14 @@ fn build_tools_system_prompt(base_prompt: &str, tool_names: &[String]) -> String
prompt.push_str("}\n\n");
prompt.push_str("## Available Tools\n\n");
for name in tool_names {
prompt.push_str(&format!("- {name}\n"));
for tool in tool_specs {
prompt.push_str(&format!("- {}\n", tool.name));
prompt.push_str(&format!(" Description: {}\n", tool.description));
prompt.push_str(" Arguments JSON Schema:\n");
let parameters = serde_json::to_string_pretty(&tool.parameters)
.unwrap_or_else(|_| tool.parameters.to_string());
prompt.push_str(&indent_multiline(&parameters, " "));
prompt.push_str("\n");
}
prompt.push_str(
@@ -251,6 +273,13 @@ fn build_tools_system_prompt(base_prompt: &str, tool_names: &[String]) -> String
prompt
}
fn indent_multiline(text: &str, indent: &str) -> String {
text.lines()
.map(|line| format!("{indent}{line}"))
.collect::<Vec<_>>()
.join("\n")
}
// ---------------------------------------------------------------------------
// 7. Config loading via dotenvy
// ---------------------------------------------------------------------------
@@ -318,16 +347,12 @@ async fn run_chat_agent(config: LlmConfig) -> Result<(), LlmError> {
.build();
let names = tool_names(&agent);
let system_prompt_text =
build_tools_system_prompt("You are a helpful assistant with an echo tool.", &names);
let tool_hook = ToolValidationHook::new(names.clone());
let output = agent
.prompt(LlmMessage::user(
"Use the echo tool to echo 'Hello from the JSON harness!', then tell me what it said.",
))
.system_prompt(LlmMessage::system(&system_prompt_text))
.max_turns(5)
.add_hook(tool_hook)
.await