agent_chain_core/tools/
render.rs

1//! Utilities to render tools.
2//!
3//! This module provides functions for rendering tool descriptions
4//! in various text formats, mirroring `langchain_core.tools.render`.
5
6use std::sync::Arc;
7
8use super::base::BaseTool;
9
10/// Type alias for a tools renderer function.
11pub type ToolsRenderer = fn(&[Arc<dyn BaseTool>]) -> String;
12
13/// Render the tool name and description in plain text.
14///
15/// Output will be in the format of:
16/// ```text
17/// search: This tool is used for search
18/// calculator: This tool is used for math
19/// ```
20pub fn render_text_description(tools: &[Arc<dyn BaseTool>]) -> String {
21    let descriptions: Vec<String> = tools
22        .iter()
23        .map(|tool| format!("{} - {}", tool.name(), tool.description()))
24        .collect();
25
26    descriptions.join("\n")
27}
28
29/// Render the tool name, description, and args in plain text.
30///
31/// Output will be in the format of:
32/// ```text
33/// search: This tool is used for search, args: {"query": {"type": "string"}}
34/// calculator: This tool is used for math, args: {"expression": {"type": "string"}}
35/// ```
36pub fn render_text_description_and_args(tools: &[Arc<dyn BaseTool>]) -> String {
37    let tool_strings: Vec<String> = tools
38        .iter()
39        .map(|tool| {
40            let args_schema =
41                serde_json::to_string(&tool.args()).unwrap_or_else(|_| "{}".to_string());
42            format!(
43                "{} - {}, args: {}",
44                tool.name(),
45                tool.description(),
46                args_schema
47            )
48        })
49        .collect();
50
51    tool_strings.join("\n")
52}
53
54/// Render tools as a JSON array of tool definitions.
55pub fn render_json(tools: &[Arc<dyn BaseTool>]) -> String {
56    let definitions: Vec<_> = tools.iter().map(|t| t.definition()).collect();
57    serde_json::to_string_pretty(&definitions).unwrap_or_else(|_| "[]".to_string())
58}
59
60/// Render tools as a compact JSON array.
61pub fn render_json_compact(tools: &[Arc<dyn BaseTool>]) -> String {
62    let definitions: Vec<_> = tools.iter().map(|t| t.definition()).collect();
63    serde_json::to_string(&definitions).unwrap_or_else(|_| "[]".to_string())
64}
65
66/// Render a single tool as a formatted string.
67pub fn render_tool(tool: &dyn BaseTool) -> String {
68    format!(
69        "Tool: {}\nDescription: {}\nArguments: {}",
70        tool.name(),
71        tool.description(),
72        serde_json::to_string_pretty(&tool.args()).unwrap_or_else(|_| "{}".to_string())
73    )
74}
75
76/// Render tools in a format suitable for system prompts.
77pub fn render_for_prompt(tools: &[Arc<dyn BaseTool>]) -> String {
78    let mut output = String::from("Available tools:\n\n");
79
80    for (i, tool) in tools.iter().enumerate() {
81        output.push_str(&format!("{}. {}\n", i + 1, tool.name()));
82        output.push_str(&format!("   Description: {}\n", tool.description()));
83
84        let args = tool.args();
85        if !args.is_empty() {
86            output.push_str("   Arguments:\n");
87            for (name, schema) in args {
88                let type_str = schema.get("type").and_then(|t| t.as_str()).unwrap_or("any");
89                let desc = schema
90                    .get("description")
91                    .and_then(|d| d.as_str())
92                    .unwrap_or("");
93                output.push_str(&format!("     - {} ({}): {}\n", name, type_str, desc));
94            }
95        }
96        output.push('\n');
97    }
98
99    output
100}
101
102/// Render tools as a numbered list.
103pub fn render_numbered_list(tools: &[Arc<dyn BaseTool>]) -> String {
104    tools
105        .iter()
106        .enumerate()
107        .map(|(i, tool)| format!("{}. {} - {}", i + 1, tool.name(), tool.description()))
108        .collect::<Vec<_>>()
109        .join("\n")
110}
111
112/// Render tools with their full schemas.
113pub fn render_with_schemas(tools: &[Arc<dyn BaseTool>]) -> String {
114    let mut output = String::new();
115
116    for tool in tools {
117        output.push_str(&format!("## {}\n\n", tool.name()));
118        output.push_str(&format!("{}\n\n", tool.description()));
119        output.push_str("### Schema\n\n");
120        output.push_str("```json\n");
121        output.push_str(&serde_json::to_string_pretty(&tool.definition()).unwrap_or_default());
122        output.push_str("\n```\n\n");
123    }
124
125    output
126}
127
128#[cfg(test)]
129mod tests {
130    use super::*;
131    use crate::tools::simple::Tool;
132
133    fn create_test_tools() -> Vec<Arc<dyn BaseTool>> {
134        vec![
135            Arc::new(Tool::from_function(
136                |input| Ok(format!("Searched: {}", input)),
137                "search",
138                "Search for information",
139            )) as Arc<dyn BaseTool>,
140            Arc::new(Tool::from_function(
141                |input| Ok(format!("Calculated: {}", input)),
142                "calculator",
143                "Perform calculations",
144            )) as Arc<dyn BaseTool>,
145        ]
146    }
147
148    #[test]
149    fn test_render_text_description() {
150        let tools = create_test_tools();
151        let rendered = render_text_description(&tools);
152
153        assert!(rendered.contains("search - Search for information"));
154        assert!(rendered.contains("calculator - Perform calculations"));
155    }
156
157    #[test]
158    fn test_render_text_description_and_args() {
159        let tools = create_test_tools();
160        let rendered = render_text_description_and_args(&tools);
161
162        assert!(rendered.contains("search -"));
163        assert!(rendered.contains("args:"));
164    }
165
166    #[test]
167    fn test_render_json() {
168        let tools = create_test_tools();
169        let rendered = render_json(&tools);
170
171        assert!(rendered.contains("\"name\": \"search\""));
172        assert!(rendered.contains("\"name\": \"calculator\""));
173    }
174
175    #[test]
176    fn test_render_for_prompt() {
177        let tools = create_test_tools();
178        let rendered = render_for_prompt(&tools);
179
180        assert!(rendered.contains("Available tools:"));
181        assert!(rendered.contains("1. search"));
182        assert!(rendered.contains("2. calculator"));
183    }
184
185    #[test]
186    fn test_render_numbered_list() {
187        let tools = create_test_tools();
188        let rendered = render_numbered_list(&tools);
189
190        assert!(rendered.starts_with("1."));
191        assert!(rendered.contains("2. calculator"));
192    }
193
194    #[test]
195    fn test_render_tool() {
196        let tool = Tool::from_function(Ok, "test", "A test tool");
197
198        let rendered = render_tool(&tool);
199
200        assert!(rendered.contains("Tool: test"));
201        assert!(rendered.contains("Description: A test tool"));
202    }
203}