diff --git a/server-rs/crates/module-editor-agent/examples/llm_chat_agent.rs b/server-rs/crates/module-editor-agent/examples/llm_chat_agent.rs index 19b6d4080..53eca84b6 100644 --- a/server-rs/crates/module-editor-agent/examples/llm_chat_agent.rs +++ b/server-rs/crates/module-editor-agent/examples/llm_chat_agent.rs @@ -70,7 +70,7 @@ impl Hook for ToolValidationHook { struct LlmChatAgentBuilder { client: Option, - custom_system_prompt: Option, + system_prompt_parts: Vec, tools: Vec>, hooks: Vec>, max_turns: usize, @@ -84,7 +84,7 @@ impl AgentBuilder 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 for LlmChatAgentBuilder { } fn system_prompt(mut self, system_prompt: impl Into) -> Self { - self.custom_system_prompt = Some(system_prompt.into()); + self.system_prompt_parts.push(system_prompt.into()); self } @@ -133,13 +133,23 @@ impl AgentBuilder 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::>(); + 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) -> Vec { agent .tools() @@ -204,12 +220,12 @@ fn tool_names(agent: &Agent) -> Vec { .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(¶meters, " ")); + 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::>() + .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