From bbae737643a8cd4353489064fee4de3c7c66ff4f Mon Sep 17 00:00:00 2001 From: Sewer56 Date: Sun, 18 Jan 2026 11:31:16 +0000 Subject: [PATCH 1/3] Changed: Migrate examples to agent builder pattern - Added AgentBuilderExt trait for serdesai to bridge Tool to AgentBuilder - Updated rig examples to use client.agent().tool() instead of ToolSet::builder() - Updated serdesai examples to use AgentBuilderExt::tool_impl() - Updated READMEs and doc comments to reflect the new patterns - Simplified convert.rs tests by removing unnecessary Result wrapping The agent builder pattern is the idiomatic way to construct agents with tools, replacing the deprecated ToolSet::builder().static_tool() approach. --- README.MD | 34 ++++---- src/llm-coding-tools-core/src/lib.rs | 13 +-- src/llm-coding-tools-core/src/preamble.rs | 8 +- src/llm-coding-tools-rig/README.md | 28 +++--- .../examples/rig-basic.rs | 44 +++++----- .../examples/rig-sandboxed.rs | 32 ++++--- src/llm-coding-tools-rig/src/lib.rs | 19 +---- src/llm-coding-tools-serdesai/README.md | 59 +++++++------ .../examples/serdesai-basic.rs | 85 +++++++++---------- .../examples/serdesai-sandboxed.rs | 58 +++++++------ .../src/agent_ext.rs | 79 +++++++++++++++++ src/llm-coding-tools-serdesai/src/convert.rs | 12 +-- src/llm-coding-tools-serdesai/src/lib.rs | 20 +---- 13 files changed, 269 insertions(+), 222 deletions(-) create mode 100644 src/llm-coding-tools-serdesai/src/agent_ext.rs diff --git a/README.MD b/README.MD index c2f926b4..377e4b7a 100644 --- a/README.MD +++ b/README.MD @@ -38,32 +38,30 @@ Add to your `Cargo.toml`: llm-coding-tools-rig = "0.1" ``` -```rust +```rust,no_run use llm_coding_tools_rig::absolute::{ReadTool, WriteTool, GlobTool}; use llm_coding_tools_rig::{BashTool, PreambleBuilder, TodoTools}; -use rig::tool::ToolSet; +use rig::providers::openai; +use rig::completion::Prompt; // Track tools and generate LLM guidance -let mut pb = PreambleBuilder::new(); +let mut pb = PreambleBuilder::::new(); let todos = TodoTools::new(); -let toolset = ToolSet::builder() - .static_tool(pb.track(ReadTool::::new())) - .static_tool(pb.track(WriteTool::new())) - .static_tool(pb.track(GlobTool::new())) - .static_tool(pb.track(BashTool::new())) - .static_tool(pb.track(todos.read)) - .static_tool(pb.track(todos.write)) +let client = openai::Client::from_env(); +let agent = client + .agent("gpt-4o") + .tool(pb.track(ReadTool::::new())) + .tool(pb.track(WriteTool::new())) + .tool(pb.track(GlobTool::new())) + .tool(pb.track(BashTool::new())) + .tool(pb.track(todos.read)) + .tool(pb.track(todos.write)) + .preamble(&pb.build()) .build(); -// Generate preamble for agent system prompt -let preamble = pb.build(); - -// Use with rig agent: -// let agent = client.agent("gpt-4o") -// .preamble(&preamble) -// .tools(toolset) -// .build(); +// Use the agent +// let response = agent.prompt("List all files").await?; ``` ## Examples diff --git a/src/llm-coding-tools-core/src/lib.rs b/src/llm-coding-tools-core/src/lib.rs index 711607ec..32a835de 100644 --- a/src/llm-coding-tools-core/src/lib.rs +++ b/src/llm-coding-tools-core/src/lib.rs @@ -1,16 +1,5 @@ +#![doc = include_str!(concat!("../", env!("CARGO_PKG_README")))] #![warn(missing_docs)] -//! Core types and utilities for coding tools. -//! -//! This crate provides framework-agnostic building blocks: -//! - [`ToolError`] and [`ToolResult`] for error handling -//! - [`ToolOutput`] for tool responses with truncation metadata -//! - Utility functions for text processing -//! -//! # Features -//! -//! - `async`: Enables async function signatures and async-only modules. -//! - `tokio` (default): Enables async via tokio runtime (implies `async`). -//! When disabled, all operations are synchronous. // Validate feature combinations at compile time #[cfg(all(feature = "async", not(feature = "tokio")))] diff --git a/src/llm-coding-tools-core/src/preamble.rs b/src/llm-coding-tools-core/src/preamble.rs index b5fbd699..1f0cb578 100644 --- a/src/llm-coding-tools-core/src/preamble.rs +++ b/src/llm-coding-tools-core/src/preamble.rs @@ -119,11 +119,13 @@ impl PreambleBuilder { /// // register _my_tool with your tool collection /// ``` /// - /// For example, if working with rig's ToolSet builder: + /// For example, if working with rig's agent builder: /// ```text /// let mut pb = PreambleBuilder::new(); - /// let toolset = ToolSet::builder() - /// .static_tool(pb.track(ReadTool::new())) + /// let agent = client + /// .agent("gpt-4o") + /// .tool(pb.track(ReadTool::new())) + /// .preamble(&pb.build()) /// .build(); /// ``` pub fn track(&mut self, tool: T) -> T { diff --git a/src/llm-coding-tools-rig/README.md b/src/llm-coding-tools-rig/README.md index 0b9ac470..917efe3a 100644 --- a/src/llm-coding-tools-rig/README.md +++ b/src/llm-coding-tools-rig/README.md @@ -29,33 +29,29 @@ llm-coding-tools-rig = "0.1" Minimal runnable agent (requires `OPENAI_API_KEY`): -```rust +```rust,no_run use llm_coding_tools_rig::absolute::{GlobTool, GrepTool, ReadTool}; use llm_coding_tools_rig::{BashTool, PreambleBuilder, TodoTools}; use rig::providers::openai; -use rig::tool::ToolSet; +use rig::client::{ProviderClient, CompletionClient}; +use rig::completion::Prompt; #[tokio::main] async fn main() -> Result<(), Box> { let todos = TodoTools::new(); let mut pb = PreambleBuilder::::new(); - let toolset = ToolSet::builder() - .static_tool(pb.track(ReadTool::::new())) - .static_tool(pb.track(GlobTool::new())) - .static_tool(pb.track(GrepTool::::new())) - .static_tool(pb.track(BashTool::new())) - .static_tool(pb.track(todos.read)) - .static_tool(pb.track(todos.write)) - .build(); - - let preamble = pb.build(); - + // Build agent with preamble tracking let client = openai::Client::from_env(); let agent = client .agent("gpt-4o") - .preamble(&preamble) - .tools(toolset) + .tool(pb.track(ReadTool::::new())) + .tool(pb.track(GlobTool::new())) + .tool(pb.track(GrepTool::::new())) + .tool(pb.track(BashTool::new())) + .tool(pb.track(todos.read)) + .tool(pb.track(todos.write)) + .preamble(&pb.build()) // Build preamble after tracking tools .build(); let response = agent @@ -85,7 +81,7 @@ Executes shell commands. File tools come in `absolute::*` (unrestricted) and `allowed::*` (sandboxed) variants: -```rust +```rust,no_run use llm_coding_tools_rig::absolute::{ReadTool, WriteTool}; use llm_coding_tools_rig::allowed::{ReadTool as AllowedReadTool, WriteTool as AllowedWriteTool}; use llm_coding_tools_rig::AllowedPathResolver; diff --git a/src/llm-coding-tools-rig/examples/rig-basic.rs b/src/llm-coding-tools-rig/examples/rig-basic.rs index 7a9fa227..f553768d 100644 --- a/src/llm-coding-tools-rig/examples/rig-basic.rs +++ b/src/llm-coding-tools-rig/examples/rig-basic.rs @@ -1,40 +1,46 @@ -//! PreambleBuilder example - pass-through tracking for ToolSet. +//! PreambleBuilder example - building a complete rig agent. //! //! Demonstrates: -//! - Using PreambleBuilder alongside ToolSet::builder() -//! - Full access to Rig's API (no wrapper limitations) +//! - Using PreambleBuilder with rig's agent builder +//! - Chained .tool() calls for registering tools //! - TodoTools with shared state //! - Generating and using the preamble string //! -//! Run: cargo run --example rig-basic -p llm-coding-tools-rig +//! Run: OPENAI_API_KEY=... cargo run --example rig-basic -p llm-coding-tools-rig use llm_coding_tools_rig::absolute::{GlobTool, GrepTool, ReadTool}; use llm_coding_tools_rig::{BashTool, PreambleBuilder, TodoTools}; -use rig::tool::ToolSet; +use rig::client::{CompletionClient, ProviderClient}; +use rig::completion::Prompt; +use rig::providers::openai; #[tokio::main] -async fn main() { +async fn main() -> Result<(), Box> { // === Create shared state for todos === let todos = TodoTools::new(); // === Create preamble builder to track tools === let mut pb = PreambleBuilder::::new(); - // === Use ToolSet::builder() directly - full Rig API! === - let _toolset = ToolSet::builder() - .static_tool(pb.track(ReadTool::::new())) - .static_tool(pb.track(GlobTool::new())) - .static_tool(pb.track(GrepTool::::new())) - .static_tool(pb.track(BashTool::new())) + // === Build agent with chained .tool() calls === + let client = openai::Client::from_env(); + let agent = client + .agent("gpt-4o") + .tool(pb.track(ReadTool::::new())) + .tool(pb.track(GlobTool::new())) + .tool(pb.track(GrepTool::::new())) + .tool(pb.track(BashTool::new())) // Todo tools share state for read/write coordination - .static_tool(pb.track(todos.read)) - .static_tool(pb.track(todos.write)) - // Can use any ToolSet method here - dynamic_tool, etc. + .tool(pb.track(todos.read)) + .tool(pb.track(todos.write)) + .preamble(&pb.build()) .build(); - // === Generate preamble string === - let preamble = pb.build(); + // === Use the agent === + let response = agent + .prompt("What files are in the current directory?") + .await?; + println!("{response}"); - // Print the preamble - println!("{preamble}"); + Ok(()) } diff --git a/src/llm-coding-tools-rig/examples/rig-sandboxed.rs b/src/llm-coding-tools-rig/examples/rig-sandboxed.rs index 7c09d29a..7ac370a0 100644 --- a/src/llm-coding-tools-rig/examples/rig-sandboxed.rs +++ b/src/llm-coding-tools-rig/examples/rig-sandboxed.rs @@ -7,11 +7,13 @@ //! - Security-conscious deployments limiting filesystem exposure //! - Project-scoped agents that shouldn't touch system files //! -//! Run: cargo run --example rig-sandboxed -p llm-coding-tools-rig +//! Run: OPENAI_API_KEY=... cargo run --example rig-sandboxed -p llm-coding-tools-rig use llm_coding_tools_rig::allowed::{EditTool, GlobTool, GrepTool, ReadTool, WriteTool}; use llm_coding_tools_rig::{AllowedPathResolver, PreambleBuilder}; -use rig::tool::ToolSet; +use rig::client::{CompletionClient, ProviderClient}; +use rig::completion::Prompt; +use rig::providers::openai; use std::path::PathBuf; #[tokio::main] @@ -40,20 +42,24 @@ async fn main() -> Result<(), Box> { let glob = GlobTool::with_resolver(resolver.clone()); let grep: GrepTool = GrepTool::with_resolver(resolver); - // === Build toolset === + // === Build agent with sandboxed tools === let mut pb = PreambleBuilder::::new(); - let _toolset = ToolSet::builder() - .static_tool(pb.track(read)) - .static_tool(pb.track(write)) - .static_tool(pb.track(edit)) - .static_tool(pb.track(glob)) - .static_tool(pb.track(grep)) + let client = openai::Client::from_env(); + let agent = client + .agent("gpt-4o") + .tool(pb.track(read)) + .tool(pb.track(write)) + .tool(pb.track(edit)) + .tool(pb.track(glob)) + .tool(pb.track(grep)) + .preamble(&pb.build()) .build(); - let preamble = pb.build(); - - // Print the preamble - println!("{preamble}"); + // === Use the agent === + let response = agent + .prompt("List all Rust files in the current directory") + .await?; + println!("{response}"); Ok(()) } diff --git a/src/llm-coding-tools-rig/src/lib.rs b/src/llm-coding-tools-rig/src/lib.rs index bb347d34..a7c4c3d2 100644 --- a/src/llm-coding-tools-rig/src/lib.rs +++ b/src/llm-coding-tools-rig/src/lib.rs @@ -1,21 +1,4 @@ -//! Rig framework Tool implementations for coding tools. -//! -//! This crate provides `rig_core::tool::Tool` implementations wrapping -//! the core operations from [`llm_coding_tools_core`]. -//! -//! # Module Organization -//! -//! - [`absolute`] - Tools requiring absolute paths (no path restriction) -//! - [`allowed`] - Tools restricted to allowed directories -//! - Standalone tools (bash, todo, webfetch) at crate root -//! -//! # Example -//! -//! ```no_run -//! use llm_coding_tools_rig::absolute::ReadTool; -//! use llm_coding_tools_rig::BashTool; -//! ``` - +#![doc = include_str!(concat!("../", env!("CARGO_PKG_README")))] #![warn(missing_docs)] pub mod absolute; diff --git a/src/llm-coding-tools-serdesai/README.md b/src/llm-coding-tools-serdesai/README.md index 016134c9..99f98a69 100644 --- a/src/llm-coding-tools-serdesai/README.md +++ b/src/llm-coding-tools-serdesai/README.md @@ -28,39 +28,47 @@ llm-coding-tools-serdesai = "0.1" ## Quick Start -```rust +Minimal runnable agent (requires `OPENAI_API_KEY`): + +```rust,no_run use llm_coding_tools_serdesai::absolute::{GlobTool, GrepTool, ReadTool}; +use llm_coding_tools_serdesai::agent_ext::AgentBuilderExt; use llm_coding_tools_serdesai::{BashTool, PreambleBuilder, create_todo_tools}; -use serdes_ai::tools::ToolRegistry; +use serdes_ai::prelude::*; #[tokio::main] -async fn main() { - let mut pb = PreambleBuilder::::new(); - let mut registry = ToolRegistry::<()>::new(); - - registry.register(pb.track(ReadTool::::new())); - registry.register(pb.track(GlobTool::new())); - registry.register(pb.track(GrepTool::::new())); - registry.register(pb.track(BashTool::new())); - +async fn main() -> std::result::Result<(), Box> { let (todo_read, todo_write, _state) = create_todo_tools(); - registry.register(pb.track(todo_read)); - registry.register(pb.track(todo_write)); - - let preamble = pb.build(); + let mut pb = PreambleBuilder::::new(); - // Pass `preamble` to your agent's system prompt - // Pass `registry` to your agent's tools + // Build agent with tools - call .system_prompt() last + let agent = AgentBuilder::<(), String>::from_model("openai:gpt-4o")? + .tool_impl(pb.track(ReadTool::::new())) + .tool_impl(pb.track(GlobTool::new())) + .tool_impl(pb.track(GrepTool::::new())) + .tool_impl(pb.track(BashTool::new())) + .tool_impl(pb.track(todo_read)) + .tool_impl(pb.track(todo_write)) + .system_prompt(&pb.build()) // Last, after tracking all tools + .build(); + + // Run agent with tools + let response = agent + .run("Search for TODO comments in src/", ()) + .await?; + println!("{}", response.output()); + + Ok(()) } ``` -See the [basic example](examples/basic.rs) for a complete working setup. +See the [serdesai-basic example](examples/serdesai-basic.rs) for a complete working setup. ## Usage File tools come in `absolute::*` (unrestricted) and `allowed::*` (sandboxed) variants: -```rust +```rust,no_run use llm_coding_tools_serdesai::absolute::{ReadTool, WriteTool}; use llm_coding_tools_serdesai::allowed::{ReadTool as AllowedReadTool, WriteTool as AllowedWriteTool}; use std::path::PathBuf; @@ -70,22 +78,23 @@ let read = ReadTool::::new(); // Sandboxed access (paths relative to allowed directories) let allowed_paths = vec![PathBuf::from("/home/user/project"), PathBuf::from("/tmp")]; -let sandboxed_read: AllowedReadTool = AllowedReadTool::new(allowed_paths.clone()); -let sandboxed_write = AllowedWriteTool::new(allowed_paths); +let sandboxed_read: AllowedReadTool = AllowedReadTool::new(allowed_paths.clone()).unwrap(); +let sandboxed_write = AllowedWriteTool::new(allowed_paths).unwrap(); ``` Other tools: `BashTool`, `WebFetchTool`, `TaskTool`, `TodoReadTool`, `TodoWriteTool`. -Use `PreambleBuilder` to register tools and pass `pb.build()` to your agent's system prompt. +Use `PreambleBuilder` to track tools and pass `pb.build()` to `.system_prompt()`. +Use `AgentBuilderExt::tool_impl()` to add tools that implement `Tool` to the agent. Context strings are re-exported in `llm_coding_tools_serdesai::context` (e.g., `BASH`, `READ_ABSOLUTE`). ## Examples ```bash -# Basic toolset setup with PreambleBuilder -cargo run --example basic -p llm-coding-tools-serdesai +# Basic agent setup with AgentBuilderExt +cargo run --example serdesai-basic -p llm-coding-tools-serdesai # Sandboxed file access with allowed::* tools -cargo run --example sandboxed -p llm-coding-tools-serdesai +cargo run --example serdesai-sandboxed -p llm-coding-tools-serdesai ``` ## License diff --git a/src/llm-coding-tools-serdesai/examples/serdesai-basic.rs b/src/llm-coding-tools-serdesai/examples/serdesai-basic.rs index ef180888..c95eaa32 100644 --- a/src/llm-coding-tools-serdesai/examples/serdesai-basic.rs +++ b/src/llm-coding-tools-serdesai/examples/serdesai-basic.rs @@ -3,62 +3,53 @@ //! Shows: //! - Creating tools individually //! - Using [`PreambleBuilder`] for context generation -//! - Registering tools with [`ToolRegistry`] +//! - Using [`AgentBuilderExt`] to add tools to an agent +//! - Running the agent with tools //! -//! Run: cargo run --example basic -p llm-coding-tools-serdesai +//! Run: OPENAI_API_KEY=... cargo run --example serdesai-basic -p llm-coding-tools-serdesai use llm_coding_tools_serdesai::absolute::{GlobTool, GrepTool, ReadTool}; +use llm_coding_tools_serdesai::agent_ext::AgentBuilderExt; use llm_coding_tools_serdesai::{BashTool, PreambleBuilder, WebFetchTool, create_todo_tools}; -use serdes_ai::tools::ToolRegistry; +use serdes_ai::prelude::*; #[tokio::main] -async fn main() { +async fn main() -> std::result::Result<(), Box> { // === Create preamble builder to track tools === let mut pb = PreambleBuilder::::new(); - // === Create and register tools with ToolRegistry === - let mut registry = ToolRegistry::<()>::new(); - - // File operations - registry.register(pb.track(ReadTool::::new())); - registry.register(pb.track(GlobTool::new())); - registry.register(pb.track(GrepTool::::new())); - - // Shell execution - registry.register(pb.track(BashTool::new())); - - // Web content fetching - registry.register(pb.track(WebFetchTool::new())); - - // Todo tools with shared state + // === Create todo tools with shared state === let (todo_read, todo_write, _state) = create_todo_tools(); - registry.register(pb.track(todo_read)); - registry.register(pb.track(todo_write)); - - // === Generate preamble string === - let preamble = pb.build(); - - // === Print tool definitions from registry === - println!("=== Tools in Registry ({}) ===", registry.len()); - for def in registry.definitions() { - println!(" - {}: {}", def.name, def.description); - } - - // === Print generated preamble === - println!( - "\n=== Generated Preamble ({} chars) ===\n", - preamble.chars().count() - ); - println!("{}", preamble); - // === Integration with serdesAI Agent === - // IMPORTANT: Pass the preamble to your agent's system prompt! - // - // let agent = Agent::builder() - // .model("openai:gpt-4o") - // .system_prompt(&preamble) - // .tools(registry) - // .build()?; - // - // let response = agent.run("Read Cargo.toml", ()).await?; + // === Build agent with tools - call .system_prompt() last === + let agent = AgentBuilder::<(), String>::from_model("openai:gpt-4o")? + // File operations + .tool_impl(pb.track(ReadTool::::new())) + .tool_impl(pb.track(GlobTool::new())) + .tool_impl(pb.track(GrepTool::::new())) + // Shell execution + .tool_impl(pb.track(BashTool::new())) + // Web content fetching + .tool_impl(pb.track(WebFetchTool::new())) + // Todo tools with shared state + .tool_impl(pb.track(todo_read)) + .tool_impl(pb.track(todo_write)) + // System prompt last (after tracking all tools) + .system_prompt(&pb.build()) + .build(); + + // === Print tool info === + println!("=== Agent Ready ({} tools) ===", agent.tools().len()); + + // === Run the agent === + println!("\n=== Running Agent ==="); + let result = agent + .run( + "List the Rust files in the current directory using glob", + (), + ) + .await?; + println!("\n=== Response ===\n{}", result.output()); + + Ok(()) } diff --git a/src/llm-coding-tools-serdesai/examples/serdesai-sandboxed.rs b/src/llm-coding-tools-serdesai/examples/serdesai-sandboxed.rs index 154fe251..6f0b2ca0 100644 --- a/src/llm-coding-tools-serdesai/examples/serdesai-sandboxed.rs +++ b/src/llm-coding-tools-serdesai/examples/serdesai-sandboxed.rs @@ -7,14 +7,15 @@ //! - Security-conscious deployments limiting filesystem exposure //! - Project-scoped agents that shouldn't touch system files //! -//! Run: cargo run --example sandboxed -p llm-coding-tools-serdesai +//! Run: OPENAI_API_KEY=... cargo run --example serdesai-sandboxed -p llm-coding-tools-serdesai use llm_coding_tools_serdesai::PreambleBuilder; +use llm_coding_tools_serdesai::agent_ext::AgentBuilderExt; use llm_coding_tools_serdesai::allowed::{EditTool, GlobTool, GrepTool, ReadTool, WriteTool}; -use serdes_ai::tools::ToolRegistry; +use serdes_ai::prelude::*; #[tokio::main] -async fn main() -> Result<(), Box> { +async fn main() -> std::result::Result<(), Box> { // === Define allowed directories === // // Only these directories (and their subdirectories) will be accessible. @@ -25,29 +26,38 @@ async fn main() -> Result<(), Box> { ]; // === Create tools with allowed paths === - // - // Each tool is initialized with the same set of allowed directories. - // The `allowed` module tools use `AllowedPathResolver` internally. - let read: ReadTool = ReadTool::new(allowed_paths.clone())?; - let write = WriteTool::new(allowed_paths.clone())?; - let edit = EditTool::new(allowed_paths.clone())?; - let glob = GlobTool::new(allowed_paths.clone())?; - let grep: GrepTool = GrepTool::new(allowed_paths)?; + let read: ReadTool = ReadTool::new(allowed_paths.clone()).unwrap(); + let write = WriteTool::new(allowed_paths.clone()).unwrap(); + let edit = EditTool::new(allowed_paths.clone()).unwrap(); + let glob = GlobTool::new(allowed_paths.clone()).unwrap(); + let grep: GrepTool = GrepTool::new(allowed_paths).unwrap(); - // === Build registry with preamble tracking === + // === Build agent with sandboxed tools - call .system_prompt() last === let mut pb = PreambleBuilder::::new(); - let mut registry = ToolRegistry::<()>::new(); - - registry.register(pb.track(read)); - registry.register(pb.track(write)); - registry.register(pb.track(edit)); - registry.register(pb.track(glob)); - registry.register(pb.track(grep)); - - let preamble = pb.build(); - - // Print the preamble - println!("{preamble}"); + let agent = AgentBuilder::<(), String>::from_model("openai:gpt-4o")? + .tool_impl(pb.track(read)) + .tool_impl(pb.track(write)) + .tool_impl(pb.track(edit)) + .tool_impl(pb.track(glob)) + .tool_impl(pb.track(grep)) + .system_prompt(&pb.build()) + .build(); + + // === Print info === + println!( + "=== Sandboxed Agent Ready ({} tools) ===", + agent.tools().len() + ); + println!("Allowed paths:"); + println!(" - Current directory: {:?}", std::env::current_dir()?); + println!(" - Temp directory: {:?}", std::env::temp_dir()); + + // === Run the agent === + println!("\n=== Running Agent ==="); + let result = agent + .run("List all Rust source files in the current directory", ()) + .await?; + println!("\n=== Response ===\n{}", result.output()); Ok(()) } diff --git a/src/llm-coding-tools-serdesai/src/agent_ext.rs b/src/llm-coding-tools-serdesai/src/agent_ext.rs new file mode 100644 index 00000000..b2452e3f --- /dev/null +++ b/src/llm-coding-tools-serdesai/src/agent_ext.rs @@ -0,0 +1,79 @@ +//! Extension traits for integrating tools with serdes-ai AgentBuilder. +//! +//! This module provides adapters to use [`Tool`] implementations with +//! serdes-ai's [`AgentBuilder`]. +//! +//! # Example +//! +//! ```ignore +//! use llm_coding_tools_serdesai::absolute::ReadTool; +//! use llm_coding_tools_serdesai::agent_ext::AgentBuilderExt; +//! use serdes_ai::prelude::*; +//! +//! let agent = AgentBuilder::<(), String>::from_model("openai:gpt-4o")? +//! .system_prompt("You are helpful.") +//! .tool_impl(ReadTool::::new()) +//! .build(); +//! ``` + +use async_trait::async_trait; +use serde_json::Value as JsonValue; +use serdes_ai::agent::ToolExecutor; +use serdes_ai::tools::{RunContext as ToolsRunContext, Tool, ToolError, ToolReturn}; +use serdes_ai::{AgentBuilder, RunContext as AgentRunContext}; + +/// Adapter that wraps a [`Tool`] to implement [`ToolExecutor`]. +/// +/// This bridges the gap between `serdes_ai::tools::Tool` (which uses +/// `tools::RunContext`) and `serdes_ai::agent::ToolExecutor` (which uses +/// `agent::RunContext`). +struct ToolAsExecutor(T); + +#[async_trait] +impl> ToolExecutor for ToolAsExecutor { + async fn execute( + &self, + args: JsonValue, + ctx: &AgentRunContext, + ) -> Result { + // Convert agent::RunContext to tools::RunContext + let tools_ctx = ToolsRunContext::from_arc(ctx.deps.clone(), &ctx.model_name) + .with_run_id(&ctx.run_id) + .with_model_settings(ctx.model_settings.clone()); + + self.0.call(&tools_ctx, args).await + } +} + +/// Extension trait for [`AgentBuilder`] to add tools that implement [`Tool`]. +pub trait AgentBuilderExt { + /// Add a tool that implements the [`Tool`] trait. + /// + /// This is a convenience method that extracts the tool's definition + /// and wraps it with an executor adapter. + /// + /// # Example + /// + /// ```ignore + /// use llm_coding_tools_serdesai::absolute::ReadTool; + /// use llm_coding_tools_serdesai::agent_ext::AgentBuilderExt; + /// use serdes_ai::prelude::*; + /// + /// let agent = AgentBuilder::<(), String>::from_model("openai:gpt-4o")? + /// .tool_impl(ReadTool::::new()) + /// .tool_impl(GlobTool::new()) + /// .build(); + /// ``` + fn tool_impl + 'static>(self, tool: T) -> Self; +} + +impl AgentBuilderExt for AgentBuilder +where + Deps: Send + Sync + 'static, + Output: Send + Sync + 'static, +{ + fn tool_impl + 'static>(self, tool: T) -> Self { + let definition = tool.definition(); + self.tool_with_executor(definition, ToolAsExecutor(tool)) + } +} diff --git a/src/llm-coding-tools-serdesai/src/convert.rs b/src/llm-coding-tools-serdesai/src/convert.rs index 1e1b8ec0..4ac25563 100644 --- a/src/llm-coding-tools-serdesai/src/convert.rs +++ b/src/llm-coding-tools-serdesai/src/convert.rs @@ -144,9 +144,7 @@ mod tests { #[test] fn invalid_path_error_maps_to_validation_error() { let core_err = CoreError::InvalidPath("not absolute".into()); - let result: Result = - Err(core_error_to_serdes("test_tool", core_err)); - let serdes_err = result.unwrap_err(); + let serdes_err = core_error_to_serdes("test_tool", core_err); // Use pattern matching - is_validation_error() doesn't exist assert!(matches!(serdes_err, SerdesError::ValidationFailed { .. })); } @@ -154,18 +152,14 @@ mod tests { #[test] fn invalid_pattern_error_maps_to_validation_error() { let core_err = CoreError::InvalidPattern("bad regex".into()); - let result: Result = - Err(core_error_to_serdes("test_tool", core_err)); - let serdes_err = result.unwrap_err(); + let serdes_err = core_error_to_serdes("test_tool", core_err); assert!(matches!(serdes_err, SerdesError::ValidationFailed { .. })); } #[test] fn out_of_bounds_error_maps_to_validation_error() { let core_err = CoreError::OutOfBounds("offset too large".into()); - let result: Result = - Err(core_error_to_serdes("test_tool", core_err)); - let serdes_err = result.unwrap_err(); + let serdes_err = core_error_to_serdes("test_tool", core_err); assert!(matches!(serdes_err, SerdesError::ValidationFailed { .. })); } diff --git a/src/llm-coding-tools-serdesai/src/lib.rs b/src/llm-coding-tools-serdesai/src/lib.rs index 61db6508..e821d2b8 100644 --- a/src/llm-coding-tools-serdesai/src/lib.rs +++ b/src/llm-coding-tools-serdesai/src/lib.rs @@ -1,24 +1,8 @@ -//! serdesAI framework Tool implementations for coding tools. -//! -//! This crate provides `serdes_ai::Tool` implementations wrapping -//! the core operations from [`llm_coding_tools_core`]. -//! -//! # Module Organization -//! -//! - [`absolute`] - Tools requiring absolute paths (no path restriction) -//! - [`allowed`] - Tools restricted to allowed directories -//! - Standalone tools (bash, todo, webfetch) at crate root -//! -//! # Example -//! -//! ```no_run -//! use llm_coding_tools_serdesai::absolute::ReadTool; -//! use llm_coding_tools_serdesai::BashTool; -//! ``` - +#![doc = include_str!(concat!("../", env!("CARGO_PKG_README")))] #![warn(missing_docs)] pub mod absolute; +pub mod agent_ext; pub mod allowed; pub mod bash; pub mod convert; From e071eb70c4ca43e9991d0a66bb8322a4d7402bf1 Mon Sep 17 00:00:00 2001 From: Sewer56 Date: Sun, 18 Jan 2026 11:38:48 +0000 Subject: [PATCH 2/3] Changed: Rename tool_impl to tool for consistent API - Renamed AgentBuilderExt::tool_impl() to tool() to match rig's API - Changed ignore to no_run in doc examples - Removed needless borrows in system_prompt() calls --- src/llm-coding-tools-serdesai/README.md | 16 ++++++------ .../examples/serdesai-basic.rs | 16 ++++++------ .../examples/serdesai-sandboxed.rs | 12 ++++----- .../src/agent_ext.rs | 25 ++++++++++++------- 4 files changed, 38 insertions(+), 31 deletions(-) diff --git a/src/llm-coding-tools-serdesai/README.md b/src/llm-coding-tools-serdesai/README.md index 99f98a69..68a212fb 100644 --- a/src/llm-coding-tools-serdesai/README.md +++ b/src/llm-coding-tools-serdesai/README.md @@ -43,13 +43,13 @@ async fn main() -> std::result::Result<(), Box> { // Build agent with tools - call .system_prompt() last let agent = AgentBuilder::<(), String>::from_model("openai:gpt-4o")? - .tool_impl(pb.track(ReadTool::::new())) - .tool_impl(pb.track(GlobTool::new())) - .tool_impl(pb.track(GrepTool::::new())) - .tool_impl(pb.track(BashTool::new())) - .tool_impl(pb.track(todo_read)) - .tool_impl(pb.track(todo_write)) - .system_prompt(&pb.build()) // Last, after tracking all tools + .tool(pb.track(ReadTool::::new())) + .tool(pb.track(GlobTool::new())) + .tool(pb.track(GrepTool::::new())) + .tool(pb.track(BashTool::new())) + .tool(pb.track(todo_read)) + .tool(pb.track(todo_write)) + .system_prompt(pb.build()) // Last, after tracking all tools .build(); // Run agent with tools @@ -84,7 +84,7 @@ let sandboxed_write = AllowedWriteTool::new(allowed_paths).unwrap(); Other tools: `BashTool`, `WebFetchTool`, `TaskTool`, `TodoReadTool`, `TodoWriteTool`. Use `PreambleBuilder` to track tools and pass `pb.build()` to `.system_prompt()`. -Use `AgentBuilderExt::tool_impl()` to add tools that implement `Tool` to the agent. +Use `AgentBuilderExt::tool()` to add tools that implement `Tool` to the agent. Context strings are re-exported in `llm_coding_tools_serdesai::context` (e.g., `BASH`, `READ_ABSOLUTE`). ## Examples diff --git a/src/llm-coding-tools-serdesai/examples/serdesai-basic.rs b/src/llm-coding-tools-serdesai/examples/serdesai-basic.rs index c95eaa32..87d23e7a 100644 --- a/src/llm-coding-tools-serdesai/examples/serdesai-basic.rs +++ b/src/llm-coding-tools-serdesai/examples/serdesai-basic.rs @@ -24,18 +24,18 @@ async fn main() -> std::result::Result<(), Box> { // === Build agent with tools - call .system_prompt() last === let agent = AgentBuilder::<(), String>::from_model("openai:gpt-4o")? // File operations - .tool_impl(pb.track(ReadTool::::new())) - .tool_impl(pb.track(GlobTool::new())) - .tool_impl(pb.track(GrepTool::::new())) + .tool(pb.track(ReadTool::::new())) + .tool(pb.track(GlobTool::new())) + .tool(pb.track(GrepTool::::new())) // Shell execution - .tool_impl(pb.track(BashTool::new())) + .tool(pb.track(BashTool::new())) // Web content fetching - .tool_impl(pb.track(WebFetchTool::new())) + .tool(pb.track(WebFetchTool::new())) // Todo tools with shared state - .tool_impl(pb.track(todo_read)) - .tool_impl(pb.track(todo_write)) + .tool(pb.track(todo_read)) + .tool(pb.track(todo_write)) // System prompt last (after tracking all tools) - .system_prompt(&pb.build()) + .system_prompt(pb.build()) .build(); // === Print tool info === diff --git a/src/llm-coding-tools-serdesai/examples/serdesai-sandboxed.rs b/src/llm-coding-tools-serdesai/examples/serdesai-sandboxed.rs index 6f0b2ca0..e10e82a7 100644 --- a/src/llm-coding-tools-serdesai/examples/serdesai-sandboxed.rs +++ b/src/llm-coding-tools-serdesai/examples/serdesai-sandboxed.rs @@ -35,12 +35,12 @@ async fn main() -> std::result::Result<(), Box> { // === Build agent with sandboxed tools - call .system_prompt() last === let mut pb = PreambleBuilder::::new(); let agent = AgentBuilder::<(), String>::from_model("openai:gpt-4o")? - .tool_impl(pb.track(read)) - .tool_impl(pb.track(write)) - .tool_impl(pb.track(edit)) - .tool_impl(pb.track(glob)) - .tool_impl(pb.track(grep)) - .system_prompt(&pb.build()) + .tool(pb.track(read)) + .tool(pb.track(write)) + .tool(pb.track(edit)) + .tool(pb.track(glob)) + .tool(pb.track(grep)) + .system_prompt(pb.build()) .build(); // === Print info === diff --git a/src/llm-coding-tools-serdesai/src/agent_ext.rs b/src/llm-coding-tools-serdesai/src/agent_ext.rs index b2452e3f..f270ceea 100644 --- a/src/llm-coding-tools-serdesai/src/agent_ext.rs +++ b/src/llm-coding-tools-serdesai/src/agent_ext.rs @@ -5,15 +5,19 @@ //! //! # Example //! -//! ```ignore -//! use llm_coding_tools_serdesai::absolute::ReadTool; +//! ```no_run +//! use llm_coding_tools_serdesai::absolute::{ReadTool, GlobTool}; //! use llm_coding_tools_serdesai::agent_ext::AgentBuilderExt; //! use serdes_ai::prelude::*; //! +//! # fn main() -> std::result::Result<(), Box> { //! let agent = AgentBuilder::<(), String>::from_model("openai:gpt-4o")? +//! .tool(ReadTool::::new()) +//! .tool(GlobTool::new()) //! .system_prompt("You are helpful.") -//! .tool_impl(ReadTool::::new()) //! .build(); +//! # Ok(()) +//! # } //! ``` use async_trait::async_trait; @@ -54,17 +58,20 @@ pub trait AgentBuilderExt { /// /// # Example /// - /// ```ignore - /// use llm_coding_tools_serdesai::absolute::ReadTool; + /// ```no_run + /// use llm_coding_tools_serdesai::absolute::{ReadTool, GlobTool}; /// use llm_coding_tools_serdesai::agent_ext::AgentBuilderExt; /// use serdes_ai::prelude::*; /// + /// # fn main() -> std::result::Result<(), Box> { /// let agent = AgentBuilder::<(), String>::from_model("openai:gpt-4o")? - /// .tool_impl(ReadTool::::new()) - /// .tool_impl(GlobTool::new()) + /// .tool(ReadTool::::new()) + /// .tool(GlobTool::new()) /// .build(); + /// # Ok(()) + /// # } /// ``` - fn tool_impl + 'static>(self, tool: T) -> Self; + fn tool + 'static>(self, tool: T) -> Self; } impl AgentBuilderExt for AgentBuilder @@ -72,7 +79,7 @@ where Deps: Send + Sync + 'static, Output: Send + Sync + 'static, { - fn tool_impl + 'static>(self, tool: T) -> Self { + fn tool + 'static>(self, tool: T) -> Self { let definition = tool.definition(); self.tool_with_executor(definition, ToolAsExecutor(tool)) } From 3b3f71d16b770d713cf320342f7b2dce18a4e60d Mon Sep 17 00:00:00 2001 From: Sewer56 Date: Sun, 18 Jan 2026 11:42:50 +0000 Subject: [PATCH 3/3] Changed: Replace unwrap() with ? operator in sandboxed example Propagate errors from tool constructors instead of panicking. --- .../examples/serdesai-sandboxed.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/llm-coding-tools-serdesai/examples/serdesai-sandboxed.rs b/src/llm-coding-tools-serdesai/examples/serdesai-sandboxed.rs index e10e82a7..d7e690a7 100644 --- a/src/llm-coding-tools-serdesai/examples/serdesai-sandboxed.rs +++ b/src/llm-coding-tools-serdesai/examples/serdesai-sandboxed.rs @@ -26,11 +26,11 @@ async fn main() -> std::result::Result<(), Box> { ]; // === Create tools with allowed paths === - let read: ReadTool = ReadTool::new(allowed_paths.clone()).unwrap(); - let write = WriteTool::new(allowed_paths.clone()).unwrap(); - let edit = EditTool::new(allowed_paths.clone()).unwrap(); - let glob = GlobTool::new(allowed_paths.clone()).unwrap(); - let grep: GrepTool = GrepTool::new(allowed_paths).unwrap(); + let read: ReadTool = ReadTool::new(allowed_paths.clone())?; + let write = WriteTool::new(allowed_paths.clone())?; + let edit = EditTool::new(allowed_paths.clone())?; + let glob = GlobTool::new(allowed_paths.clone())?; + let grep: GrepTool = GrepTool::new(allowed_paths)?; // === Build agent with sandboxed tools - call .system_prompt() last === let mut pb = PreambleBuilder::::new();