Move hardcoded LLM prompts to template files (#3934)

Co-authored-by: Douwe Osinga <douwe@squareup.com>
Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Douwe Osinga
2025-08-08 17:06:34 +02:00
committed by GitHub
parent b88c221005
commit f1b833f977
4 changed files with 44 additions and 12 deletions
@@ -4,6 +4,7 @@ use rmcp::model::Tool;
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use async_trait::async_trait; use async_trait::async_trait;
use serde::Serialize;
use serde_json::Value; use serde_json::Value;
use std::collections::HashMap; use std::collections::HashMap;
use std::collections::VecDeque; use std::collections::VecDeque;
@@ -14,8 +15,15 @@ use tokio::sync::RwLock;
use crate::agents::tool_vectordb::ToolVectorDB; use crate::agents::tool_vectordb::ToolVectorDB;
use crate::conversation::message::Message; use crate::conversation::message::Message;
use crate::model::ModelConfig; use crate::model::ModelConfig;
use crate::prompt_template::render_global_file;
use crate::providers::{self, base::Provider}; use crate::providers::{self, base::Provider};
#[derive(Serialize)]
struct ToolSelectorContext {
tools: String,
query: String,
}
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum RouterToolSelectionStrategy { pub enum RouterToolSelectionStrategy {
Vector, Vector,
@@ -282,15 +290,21 @@ impl RouterToolSelector for LLMToolSelector {
}; };
if let Some(tools) = relevant_tools { if let Some(tools) = relevant_tools {
// Use LLM to search through tools // Use template to generate the prompt
let prompt = format!( let context = ToolSelectorContext {
"Given the following tools:\n{}\n\nFind the most relevant tools for the query: {}\n\nReturn the tools in this exact format for each tool:\nTool: <tool_name>\nDescription: <tool_description>\nSchema: <tool_schema>", tools: tools.clone(),
tools, query query: query.to_string(),
); };
let system_message = Message::user().with_text("You are a tool selection assistant. Your task is to find the most relevant tools based on the user's query.");
let user_prompt =
render_global_file("router_tool_selector.md", &context).map_err(|e| {
ToolError::ExecutionError(format!("Failed to render prompt template: {}", e))
})?;
let user_message = Message::user().with_text(&user_prompt);
let response = self let response = self
.llm_provider .llm_provider
.complete(&prompt, &[system_message], &[]) .complete("", &[user_message], &[])
.await .await
.map_err(|e| ToolError::ExecutionError(format!("Failed to search tools: {}", e)))?; .map_err(|e| ToolError::ExecutionError(format!("Failed to search tools: {}", e)))?;
@@ -3,6 +3,7 @@ use crate::config::permission::PermissionLevel;
use crate::config::PermissionManager; use crate::config::PermissionManager;
use crate::conversation::message::{Message, MessageContent, ToolRequest}; use crate::conversation::message::{Message, MessageContent, ToolRequest};
use crate::conversation::Conversation; use crate::conversation::Conversation;
use crate::prompt_template::render_global_file;
use crate::providers::base::Provider; use crate::providers::base::Provider;
use chrono::Utc; use chrono::Utc;
use indoc::indoc; use indoc::indoc;
@@ -13,6 +14,11 @@ use serde_json::Value;
use std::collections::HashSet; use std::collections::HashSet;
use std::sync::Arc; use std::sync::Arc;
#[derive(Serialize)]
struct PermissionJudgeContext {
// Empty struct for now since the current template doesn't need variables
}
/// Creates the tool definition for checking read-only permissions. /// Creates the tool definition for checking read-only permissions.
fn create_read_only_tool() -> Tool { fn create_read_only_tool() -> Tool {
Tool::new( Tool::new(
@@ -133,12 +139,12 @@ pub async fn detect_read_only_tools(
let tool = create_read_only_tool(); let tool = create_read_only_tool();
let check_messages = create_check_messages(tool_requests); let check_messages = create_check_messages(tool_requests);
let context = PermissionJudgeContext {};
let system_prompt = render_global_file("permission_judge.md", &context)
.unwrap_or_else(|_| "You are a good analyst and can detect operations whether they have read-only operations.".to_string());
let res = provider let res = provider
.complete( .complete(&system_prompt, check_messages.messages(), &[tool.clone()])
"You are a good analyst and can detect operations whether they have read-only operations.",
check_messages.messages(),
&[tool.clone()],
)
.await; .await;
// Process the response and return an empty vector if the response is invalid // Process the response and return an empty vector if the response is invalid
@@ -0,0 +1 @@
You are a good analyst and can detect operations whether they have read-only operations.
@@ -0,0 +1,11 @@
You are a tool selection assistant. Your task is to find the most relevant tools based on the user's query.
Given the following tools:
{{ tools }}
Find the most relevant tools for the query: {{ query }}
Return the tools in this exact format for each tool:
Tool: <tool_name>
Description: <tool_description>
Schema: <tool_schema>