Equip an Agent with Built-In Tools for Data Processing and Automation
LM-Kit.NET ships a growing catalog of built-in tools across multiple categories. The catalog is designed for continuous growth, with a strong focus on security and scalability. Built-in tools give your agent fine-grained capabilities: parse JSON, query databases, validate emails, compute statistics, encrypt data, call HTTP APIs, read files, process PDFs, perform OCR, and more, all without writing custom tool code. This guide shows how to register built-in tools, configure IO tools securely, and build practical data processing agents.
Why This Matters
Two enterprise problems that built-in tools solve:
- Repetitive tool authoring. Every agent project reinvents the same utilities: JSON parsing, date formatting, regex matching, URL building. Built-in tools provide production-tested implementations for dozens of common operations, saving weeks of development time and eliminating bugs in foundational plumbing.
- Data pipeline automation. Business workflows require reading CSVs, validating records, computing summaries, and writing output files. An agent equipped with data, text, and IO tools can orchestrate these steps autonomously, turning a multi-script ETL process into a single natural language instruction.
Prerequisites
| Requirement | Minimum |
|---|---|
| .NET SDK | 8.0+ |
| VRAM | 6+ GB (for a tool-calling model like Qwen 3 8B) |
| Model | Must support tool calling (model.HasToolCalls == true) |
Step 1: Create the Project
dotnet new console -n BuiltInToolsAgent
cd BuiltInToolsAgent
dotnet add package LM-Kit.NET
Step 2: Understand the Built-In Tools Ecosystem
All built-in tools are accessible through the BuiltInTools static class. They fall into two groups: safe tools (no file system or network access) and IO tools (require explicit configuration for security). Every built-in tool also exposes rich metadata (category, risk level, side effects) through the IToolMetadata interface, and you can use filtering methods like GetByCategory(), GetByMaxRisk(), and GetReadOnly() to select tools programmatically.
The built-in tool catalog is designed to evolve over time, enabling fine-grained permission control.
| Category | Description | Access |
|---|---|---|
| Data | Parsing and transformation: JSON, XML, CSV, YAML, HTML, Markdown, databases, spreadsheets, QR codes | Safe (Database and Spreadsheet have I/O) |
| Text | String operations: text manipulation, diff, regex, templating, encoding, slugification, fuzzy matching, phonetics | Safe |
| Numeric | Computation: calculator, unit conversion, statistics, financial math, random, expressions, IP calc | Safe |
| Security | Hashing, encryption, JWT, validation, password generation, checksums | Safe |
| Utility | Date/time, cron, URL, color, locale, MIME types, paths, scheduling, time zones, humanization | Safe |
| Document | PDF manipulation (split, merge, extract, info, render, unlock), image preprocessing (deskew, crop, resize), text extraction, OCR | Has I/O |
| IO | File system operations, process execution, compression, clipboard, environment variables, file watching | Requires config |
| Net | HTTP operations, FTP, web search, network diagnostics, SMTP, RSS feeds | Requires config |
Step 3: Register Safe Tools with an Agent
Register only the tools your agent actually needs. Fewer tools means the model selects the right tool faster and produces better results. Start with a small set of tools per agent, then add more only when required by the workflow.
Important: Never register dozens of tools at once. Each registered tool adds its full JSON schema to the model's context, consuming tokens and degrading tool selection accuracy.
using System.Text;
using LMKit.Model;
using LMKit.Agents;
using LMKit.Agents.Tools.BuiltIn;
using LMKit.TextGeneration.Chat;
LMKit.Licensing.LicenseManager.SetLicenseKey("");
Console.InputEncoding = Encoding.UTF8;
Console.OutputEncoding = Encoding.UTF8;
// ──────────────────────────────────────
// 1. Load a tool-calling model
// ──────────────────────────────────────
Console.WriteLine("Loading model...");
using LM model = LM.LoadFromModelID("qwen3:8b",
downloadingProgress: (_, len, read) =>
{
if (len.HasValue) Console.Write($"\r Downloading: {(double)read / len.Value * 100:F1}% ");
return true;
},
loadingProgress: p => { Console.Write($"\r Loading: {p * 100:F0}% "); return true; });
Console.WriteLine("\n");
// ──────────────────────────────────────
// 2. Create agent with two focused tools
// ──────────────────────────────────────
var agent = Agent.CreateBuilder(model)
.WithPersona("Data Processing Assistant")
.WithInstruction("You are a helpful assistant with access to a calculator tool. " +
"Always use the calculator for arithmetic operations.")
.WithTools(tools =>
{
tools.Register(BuiltInTools.CalcArithmetic);
})
.Build();
// ──────────────────────────────────────
// 3. Run with real-time feedback
// ──────────────────────────────────────
using var executor = new AgentExecutor();
executor.AfterTextCompletion += (sender, e) =>
{
if (e.SegmentType == TextSegmentType.UserVisible)
Console.Write(e.Text);
};
executor.BeforeToolInvocation += (sender, e) =>
{
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine($"\n ⚙ Calling tool: {e.ToolCall.Name}");
Console.ResetColor();
};
executor.AfterToolInvocation += (sender, e) =>
{
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine($" ✓ Result: {e.ToolCallResult.ResultJson}");
Console.ResetColor();
};
var result = await executor.ExecuteAsync(agent,
"A store sold 3 items: $59.99, $124.50, and $89.00. " +
"What is the total revenue and the average price per item?");
Console.WriteLine($"\n\nFinal answer: {result.Content}");
Step 4: Pick Specific Tools for a Focused Agent
For production agents, register only the tools the agent needs. Fewer tools means less confusion for the model and faster tool selection.
using System.Text;
using LMKit.Model;
using LMKit.Agents;
using LMKit.Agents.Tools.BuiltIn;
using LMKit.TextGeneration.Chat;
LMKit.Licensing.LicenseManager.SetLicenseKey("");
Console.InputEncoding = Encoding.UTF8;
Console.OutputEncoding = Encoding.UTF8;
Console.WriteLine("Loading model...");
using LM model = LM.LoadFromModelID("qwen3:8b",
downloadingProgress: (_, len, read) =>
{
if (len.HasValue) Console.Write($"\r Downloading: {(double)read / len.Value * 100:F1}% ");
return true;
},
loadingProgress: p => { Console.Write($"\r Loading: {p * 100:F0}% "); return true; });
Console.WriteLine("\n");
var agent = Agent.CreateBuilder(model)
.WithPersona("CSV Data Analyst")
.WithInstruction("You analyze CSV data. Parse it, compute statistics, validate fields, and output results as JSON.")
.WithTools(tools =>
{
tools.Register(BuiltInTools.CsvParse);
tools.Register(BuiltInTools.ValidatorData);
tools.Register(BuiltInTools.CalcArithmetic);
})
.Build();
using var executor = new AgentExecutor();
executor.AfterTextCompletion += (sender, e) =>
{
if (e.SegmentType == TextSegmentType.UserVisible)
Console.Write(e.Text);
};
executor.BeforeToolInvocation += (sender, e) =>
{
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine($"\n ⚙ {e.ToolCall.Name}");
Console.ResetColor();
};
var result = await executor.ExecuteAsync(agent,
"Here is a CSV of customer orders:\n" +
"customer,email,amount,date\n" +
"Alice,alice@example.com,150.00,2025-01-15\n" +
"Bob,invalid-email,89.50,2025-01-16\n" +
"Carol,carol@test.org,220.75,2025-01-17\n\n" +
"Parse this data, validate all email addresses, and compute the average order amount.");
Console.WriteLine($"\n\nResult: {result.Content}");
Step 5: Configure IO Tools with Security Restrictions
IO tools (FileSystem, Http, Process) are disabled by default. Enable them with explicit restrictions to control what the agent can access.
using System.Text;
using LMKit.Model;
using LMKit.Agents;
using LMKit.Agents.Tools.BuiltIn;
using LMKit.Agents.Tools.BuiltIn.IO;
using LMKit.Agents.Tools.BuiltIn.Net;
using LMKit.TextGeneration.Chat;
LMKit.Licensing.LicenseManager.SetLicenseKey("");
Console.InputEncoding = Encoding.UTF8;
Console.OutputEncoding = Encoding.UTF8;
Console.WriteLine("Loading model...");
using LM model = LM.LoadFromModelID("qwen3:8b",
downloadingProgress: (_, len, read) =>
{
if (len.HasValue) Console.Write($"\r Downloading: {(double)read / len.Value * 100:F1}% ");
return true;
},
loadingProgress: p => { Console.Write($"\r Loading: {p * 100:F0}% "); return true; });
Console.WriteLine("\n");
var agent = Agent.CreateBuilder(model)
.WithPersona("File Processing Agent")
.WithTools(tools =>
{
// Safe tools for data processing
tools.Register(BuiltInTools.JsonParse);
tools.Register(BuiltInTools.CsvParse);
// FileSystem: read-only, restricted to specific directories
tools.Register(BuiltInTools.CreateFileSystemRead(new FileSystemToolOptions
{
AllowWrite = false,
AllowDelete = false,
AllowedPaths = new HashSet<string> { "/data/input", "/data/output" },
MaxFileSize = 10 * 1024 * 1024 // 10 MB limit
}));
// HTTP: restricted to specific hosts
tools.Register(BuiltInTools.CreateHttpGet(new HttpToolOptions
{
AllowedHosts = new HashSet<string> { "api.example.com", "data.example.com" },
Timeout = TimeSpan.FromSeconds(15),
MaxResponseSize = 50_000
}));
})
.Build();
using var executor = new AgentExecutor();
executor.AfterTextCompletion += (sender, e) =>
{
if (e.SegmentType == TextSegmentType.UserVisible)
Console.Write(e.Text);
};
executor.BeforeToolInvocation += (sender, e) =>
{
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine($"\n ⚙ {e.ToolCall.Name}");
Console.ResetColor();
};
var result = await executor.ExecuteAsync(agent,
"List all CSV files in /data/input, then read the first one and summarize its contents as JSON.");
Console.WriteLine($"\n\nResult: {result.Content}");
Security defaults for IO tools:
| Tool | Default Behavior | How to Restrict |
|---|---|---|
| FileSystem | Read-only, no path restrictions | AllowedPaths, BlockedPaths, MaxFileSize |
| Http | Blocks localhost/127.0.0.1 | AllowedHosts, BlockedHosts, AllowedSchemes |
| Process | Full access | ProcessToolOptions |
| Network | Full access | NetworkToolOptions |
Step 6: Build a Data Transformation Agent
A complete example: an agent that reads CSV data, validates records, computes statistics, and outputs structured JSON.
using System.Text;
using LMKit.Model;
using LMKit.Agents;
using LMKit.Agents.Tools.BuiltIn;
using LMKit.TextGeneration.Chat;
LMKit.Licensing.LicenseManager.SetLicenseKey("");
Console.InputEncoding = Encoding.UTF8;
Console.OutputEncoding = Encoding.UTF8;
Console.WriteLine("Loading model...");
using LM model = LM.LoadFromModelID("qwen3:8b",
downloadingProgress: (_, len, read) =>
{
if (len.HasValue) Console.Write($"\r Downloading: {(double)read / len.Value * 100:F1}% ");
return true;
},
loadingProgress: p => { Console.Write($"\r Loading: {p * 100:F0}% "); return true; });
Console.WriteLine("\n");
var agent = Agent.CreateBuilder(model)
.WithPersona("Data Quality Engineer")
.WithInstruction(
"You process and validate datasets. For each task:\n" +
"1. Parse the input data using the appropriate tool\n" +
"2. Validate all fields (emails, dates, numeric ranges)\n" +
"3. Compute summary statistics on numeric columns\n" +
"4. Output a JSON report with valid records, invalid records, and statistics")
.WithTools(tools =>
{
tools.Register(BuiltInTools.CsvParse);
tools.Register(BuiltInTools.ValidatorData);
tools.Register(BuiltInTools.CalcArithmetic);
})
.Build();
string csvData = @"name,email,revenue,signup_date
Acme Corp,sales@acme.com,125000.50,2024-03-15
BadEmail Inc,not-an-email,89000.00,2024-06-01
Tech Solutions,info@techsol.io,210000.75,2024-01-20
Null Fields,,0,invalid-date
Global Ltd,contact@global.com,175000.25,2024-09-10";
using var executor = new AgentExecutor();
executor.AfterTextCompletion += (sender, e) =>
{
if (e.SegmentType == TextSegmentType.UserVisible)
Console.Write(e.Text);
};
executor.BeforeToolInvocation += (sender, e) =>
{
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine($"\n ⚙ {e.ToolCall.Name}");
Console.ResetColor();
};
var report = await executor.ExecuteAsync(agent,
$"Process this customer dataset and generate a quality report:\n\n{csvData}");
Console.WriteLine($"\n\nReport: {report.Content}");
Step 7: Process JSON Data with the Json Tool
The Json tool supports parsing, querying with dot-notation paths, formatting, minifying, validating, and extracting keys/values.
using System.Text;
using LMKit.Model;
using LMKit.Agents;
using LMKit.Agents.Tools.BuiltIn;
using LMKit.TextGeneration.Chat;
LMKit.Licensing.LicenseManager.SetLicenseKey("");
Console.InputEncoding = Encoding.UTF8;
Console.OutputEncoding = Encoding.UTF8;
Console.WriteLine("Loading model...");
using LM model = LM.LoadFromModelID("qwen3:8b",
downloadingProgress: (_, len, read) =>
{
if (len.HasValue) Console.Write($"\r Downloading: {(double)read / len.Value * 100:F1}% ");
return true;
},
loadingProgress: p => { Console.Write($"\r Loading: {p * 100:F0}% "); return true; });
Console.WriteLine("\n");
var agent = Agent.CreateBuilder(model)
.WithPersona("JSON Data Processor")
.WithTools(tools =>
{
tools.Register(BuiltInTools.JsonParse);
tools.Register(BuiltInTools.CalcArithmetic);
})
.Build();
string apiResponse = @"{
""status"": ""success"",
""data"": {
""users"": [
{""name"": ""Alice"", ""score"": 92.5, ""active"": true},
{""name"": ""Bob"", ""score"": 87.3, ""active"": false},
{""name"": ""Carol"", ""score"": 95.1, ""active"": true}
],
""metadata"": {""total"": 3, ""page"": 1}
}
}";
using var executor = new AgentExecutor();
executor.AfterTextCompletion += (sender, e) =>
{
if (e.SegmentType == TextSegmentType.UserVisible)
Console.Write(e.Text);
};
executor.BeforeToolInvocation += (sender, e) =>
{
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine($"\n ⚙ {e.ToolCall.Name}");
Console.ResetColor();
};
var result = await executor.ExecuteAsync(agent,
$"From this API response, extract the list of active users and calculate their average score:\n\n{apiResponse}");
Console.WriteLine($"\n\nResult: {result.Content}");
Step 8: Validate and Transform Data with Text Tools
Combine Regex, Template, Encoding, and Slug tools for text processing workflows.
using System.Text;
using LMKit.Model;
using LMKit.Agents;
using LMKit.Agents.Tools.BuiltIn;
using LMKit.TextGeneration.Chat;
LMKit.Licensing.LicenseManager.SetLicenseKey("");
Console.InputEncoding = Encoding.UTF8;
Console.OutputEncoding = Encoding.UTF8;
Console.WriteLine("Loading model...");
using LM model = LM.LoadFromModelID("qwen3:8b",
downloadingProgress: (_, len, read) =>
{
if (len.HasValue) Console.Write($"\r Downloading: {(double)read / len.Value * 100:F1}% ");
return true;
},
loadingProgress: p => { Console.Write($"\r Loading: {p * 100:F0}% "); return true; });
Console.WriteLine("\n");
var agent = Agent.CreateBuilder(model)
.WithPersona("Text Processing Specialist")
.WithTools(tools =>
{
tools.Register(BuiltInTools.SlugGenerate);
tools.Register(BuiltInTools.RegexMatch);
})
.Build();
using var executor = new AgentExecutor();
executor.AfterTextCompletion += (sender, e) =>
{
if (e.SegmentType == TextSegmentType.UserVisible)
Console.Write(e.Text);
};
executor.BeforeToolInvocation += (sender, e) =>
{
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine($"\n ⚙ {e.ToolCall.Name}");
Console.ResetColor();
};
var result = await executor.ExecuteAsync(agent,
"I have these product names that need URL-friendly slugs:\n" +
"1. 'Café Délicieux™ Premium Blend'\n" +
"2. 'Über-Fresh Organic Juice (500ml)'\n" +
"3. '日本茶 Green Tea Extract'\n\n" +
"For each: generate a URL slug, then check if any contain non-ASCII characters using regex.");
Console.WriteLine($"\n\nResult: {result.Content}");
Step 9: Use Security Tools for Data Compliance
The Security category provides hashing, encryption, validation, and JWT operations for compliance workflows.
using System.Text;
using LMKit.Model;
using LMKit.Agents;
using LMKit.Agents.Tools.BuiltIn;
using LMKit.TextGeneration.Chat;
LMKit.Licensing.LicenseManager.SetLicenseKey("");
Console.InputEncoding = Encoding.UTF8;
Console.OutputEncoding = Encoding.UTF8;
Console.WriteLine("Loading model...");
using LM model = LM.LoadFromModelID("qwen3:8b",
downloadingProgress: (_, len, read) =>
{
if (len.HasValue) Console.Write($"\r Downloading: {(double)read / len.Value * 100:F1}% ");
return true;
},
loadingProgress: p => { Console.Write($"\r Loading: {p * 100:F0}% "); return true; });
Console.WriteLine("\n");
var agent = Agent.CreateBuilder(model)
.WithPersona("Data Security Auditor")
.WithTools(tools =>
{
tools.Register(BuiltInTools.HashCompute);
tools.Register(BuiltInTools.ValidatorData);
tools.Register(BuiltInTools.PasswordAnalyze);
})
.Build();
using var executor = new AgentExecutor();
executor.AfterTextCompletion += (sender, e) =>
{
if (e.SegmentType == TextSegmentType.UserVisible)
Console.Write(e.Text);
};
executor.BeforeToolInvocation += (sender, e) =>
{
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine($"\n ⚙ {e.ToolCall.Name}");
Console.ResetColor();
};
var result = await executor.ExecuteAsync(agent,
"Audit this data for security compliance:\n" +
"1. Validate these emails: 'admin@company.com', 'bad@@email', 'user@test.org'\n" +
"2. Hash the valid emails with SHA-256 for anonymized storage\n" +
"3. Generate a secure 16-character password for the new admin account");
Console.WriteLine($"\n\nResult: {result.Content}");
Step 10: Use Document Tools for PDF and Image Processing
The Document category provides tools for PDF manipulation, image preprocessing, text extraction, and OCR. These tools operate on files and enable document automation workflows.
using System.Text;
using LMKit.Model;
using LMKit.Agents;
using LMKit.Agents.Tools.BuiltIn;
using LMKit.TextGeneration.Chat;
LMKit.Licensing.LicenseManager.SetLicenseKey("");
Console.InputEncoding = Encoding.UTF8;
Console.OutputEncoding = Encoding.UTF8;
Console.WriteLine("Loading model...");
using LM model = LM.LoadFromModelID("qwen3:8b",
downloadingProgress: (_, len, read) =>
{
if (len.HasValue) Console.Write($"\r Downloading: {(double)read / len.Value * 100:F1}% ");
return true;
},
loadingProgress: p => { Console.Write($"\r Loading: {p * 100:F0}% "); return true; });
Console.WriteLine("\n");
var agent = Agent.CreateBuilder(model)
.WithPersona("Document Processing Agent")
.WithInstruction(
"You process documents and images. You can split PDFs, " +
"extract text, and get PDF metadata.")
.WithTools(tools =>
{
tools.Register(BuiltInTools.PdfSplit);
tools.Register(BuiltInTools.PdfMetadata);
tools.Register(BuiltInTools.DocumentTextExtract);
})
.Build();
using var executor = new AgentExecutor();
executor.AfterTextCompletion += (sender, e) =>
{
if (e.SegmentType == TextSegmentType.UserVisible)
Console.Write(e.Text);
};
executor.BeforeToolInvocation += (sender, e) =>
{
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine($"\n ⚙ {e.ToolCall.Name}");
Console.ResetColor();
};
var result = await executor.ExecuteAsync(agent,
"Extract pages 1-3 from 'report.pdf' into a new file called 'summary.pdf', " +
"then get the text content from page 1.");
Console.WriteLine($"\n\nResult: {result.Content}");
The Document category includes tools for PDF operations (split, merge, extract, info, render, unlock), image preprocessing (deskew, crop, resize), format conversion (Markdown to PDF, EML to PDF, Markdown to/from HTML, Markdown to/from DOCX, EML/MBOX to Markdown), text extraction from multiple formats, and OCR. See the Process PDFs and Images with Built-In Document Tools guide for detailed examples.
Step 11: Monitor Tool Invocations
Track which tools the agent calls, how often, and what results they return. This is essential for debugging, auditing, and cost tracking.
using System.Text;
using LMKit.Model;
using LMKit.Agents;
using LMKit.Agents.Tools.BuiltIn;
LMKit.Licensing.LicenseManager.SetLicenseKey("");
Console.InputEncoding = Encoding.UTF8;
Console.OutputEncoding = Encoding.UTF8;
Console.WriteLine("Loading model...");
using LM model = LM.LoadFromModelID("qwen3:8b",
downloadingProgress: (_, len, read) =>
{
if (len.HasValue) Console.Write($"\r Downloading: {(double)read / len.Value * 100:F1}% ");
return true;
},
loadingProgress: p => { Console.Write($"\r Loading: {p * 100:F0}% "); return true; });
Console.WriteLine("\n");
using var executor = new AgentExecutor();
var agent = Agent.CreateBuilder(model)
.WithPersona("Data Analyst")
.WithInstruction("Use the calculator tool for all arithmetic computations.")
.WithTools(tools =>
{
tools.Register(BuiltInTools.CalcArithmetic);
})
.Build();
int toolCallCount = 0;
// Monitor every tool call via the executor events
executor.BeforeToolInvocation += (sender, e) =>
{
toolCallCount++;
Console.ForegroundColor = ConsoleColor.DarkGray;
Console.WriteLine($" [TOOL] {e.ToolCall.Name}({e.ToolCall.ArgumentsJson})");
Console.ResetColor();
};
executor.AfterToolInvocation += (sender, e) =>
{
Console.ForegroundColor = ConsoleColor.DarkGray;
Console.WriteLine($" → {e.ToolCallResult.ResultJson}");
Console.ResetColor();
};
var result = await executor.ExecuteAsync(agent,
"What is the compound interest on a $10,000 investment at 5.5% annual rate over 3 years?");
Console.WriteLine($"\nFinal answer: {result.Content}");
Console.WriteLine($"Tools called: {toolCallCount}");
Step 12: Inspect Tool Metadata
Every built-in tool implements the IToolMetadata interface, which exposes structured information about a tool's behavior, risk profile, and side effects. You can use this metadata to make informed decisions about which tools to register or to build automated policies.
IToolMetadata properties:
| Property | Type | Description |
|---|---|---|
Category |
string |
Tool category (e.g., "Data", "IO", "Net", "Security") |
SideEffect |
ToolSideEffect |
What kind of side effect the tool produces |
RiskLevel |
ToolRiskLevel |
How risky the tool is to execute (Low, Medium, High, Critical) |
DefaultApproval |
ToolApprovalMode |
Whether the tool requires approval by default |
IsIdempotent |
bool |
True if calling the tool multiple times with the same input yields the same result |
IsReadOnly |
bool |
True if the tool does not modify any external state |
ToolSideEffect values:
| Value | Meaning |
|---|---|
None |
Pure computation, no external interaction |
LocalRead |
Reads from the local file system or environment |
LocalWrite |
Writes to the local file system or environment |
NetworkRead |
Reads from the network (HTTP GET, DNS lookup) |
NetworkWrite |
Writes to the network (HTTP POST, SMTP send) |
Irreversible |
Performs an action that cannot be undone (e.g., delete, send email) |
ToolRiskLevel values:
| Value | Meaning |
|---|---|
Low |
Safe, no side effects (Calculator, DateTime, Json) |
Medium |
Local reads or idempotent network reads (FileSystem read, Http GET) |
High |
Local writes or network writes (FileSystem write, Http POST) |
Critical |
Irreversible actions (Process execution, SMTP send, file delete) |
You can access metadata on any ToolInfo object returned by the tool registry:
foreach (var toolInfo in agent.Tools)
{
Console.WriteLine($"{toolInfo.Name}: Category={toolInfo.Category}, " +
$"Risk={toolInfo.RiskLevel}, ReadOnly={toolInfo.IsReadOnly}");
}
Step 13: Filter Tools by Metadata
The BuiltInTools class provides filtering methods that let you select tools based on their metadata. This is useful when you want to register only tools that meet certain safety criteria.
using LMKit.Agents;
using LMKit.Agents.Tools;
using LMKit.Agents.Tools.BuiltIn;
// Get all read-only tools (safe for untrusted environments)
IReadOnlyList<ITool> readOnlyTools = BuiltInTools.GetReadOnly();
// Get tools by category
IReadOnlyList<ITool> dataTools = BuiltInTools.GetByCategory("Data");
IReadOnlyList<ITool> textTools = BuiltInTools.GetByCategory("Text");
// Get tools up to a maximum risk level
IReadOnlyList<ITool> lowRiskTools = BuiltInTools.GetByMaxRisk(ToolRiskLevel.Low);
IReadOnlyList<ITool> mediumOrLower = BuiltInTools.GetByMaxRisk(ToolRiskLevel.Medium);
Register filtered tools with an agent:
// Build a safe agent that can only use low-risk, read-only tools
var safeAgent = Agent.CreateBuilder(model)
.WithPersona("Safe Data Processor")
.WithTools(tools =>
{
foreach (var tool in BuiltInTools.GetByMaxRisk(ToolRiskLevel.Low))
tools.Register(tool);
})
.Build();
// Build an agent with only Data and Text category tools
var dataTextAgent = Agent.CreateBuilder(model)
.WithPersona("Data and Text Specialist")
.WithTools(tools =>
{
foreach (var tool in BuiltInTools.GetByCategory("Data"))
tools.Register(tool);
foreach (var tool in BuiltInTools.GetByCategory("Text"))
tools.Register(tool);
})
.Build();
Tip: Combining
GetByMaxRisk()withGetByCategory()gives you fine-grained control. For example, register all low-risk Data tools for a sandboxed analytics agent.
Step 14: Configure Permission Policies
A ToolPermissionPolicy governs which tools are allowed, denied, or require approval before execution. Attach a policy to the agent builder with WithPermissionPolicy() or set it directly on a ToolRegistry. The policy is evaluated before every tool call, and the result is one of three values in ToolPermissionResult: Allowed, Denied, or ApprovalRequired.
Fluent API methods on ToolPermissionPolicy:
| Method | Description |
|---|---|
Allow(toolName) |
Explicitly allow a specific tool by name |
Deny(toolName) |
Explicitly deny a specific tool by name |
AllowCategory(category) |
Allow all tools in a category |
DenyCategory(category) |
Deny all tools in a category |
RequireApproval(toolName) |
Require user approval for a specific tool |
RequireApprovalForCategory(category) |
Require user approval for all tools in a category |
SetMaxRiskLevel(riskLevel) |
Deny any tool above the specified risk level |
Example: Safe Chat Agent
A chatbot that can only use pure-computation tools. No file access, no network, no writes.
var policy = new ToolPermissionPolicy()
.SetMaxRiskLevel(ToolRiskLevel.Low)
.DenyCategory("IO")
.DenyCategory("Net");
var chatAgent = Agent.CreateBuilder(model)
.WithPersona("Safe Chat Assistant")
.WithPermissionPolicy(policy)
.WithTools(tools =>
{
tools.Register(BuiltInTools.CalcArithmetic);
tools.Register(BuiltInTools.DateTimeNow);
tools.Register(BuiltInTools.JsonParse);
})
.Build();
Example: Developer Assistant
A developer helper that can read files and make HTTP GET requests, but requires approval for any write operation.
var policy = new ToolPermissionPolicy()
.SetMaxRiskLevel(ToolRiskLevel.High)
.AllowCategory("Data")
.AllowCategory("Text")
.AllowCategory("Numeric")
.RequireApprovalForCategory("IO")
.RequireApprovalForCategory("Net")
.Deny("process_execute");
var devAgent = Agent.CreateBuilder(model)
.WithPersona("Developer Assistant")
.WithPermissionPolicy(policy)
.WithTools(tools =>
{
tools.Register(BuiltInTools.JsonParse);
tools.Register(BuiltInTools.RegexMatch);
tools.Register(BuiltInTools.CalcArithmetic);
tools.Register(BuiltInTools.CreateFileSystemRead(new FileSystemToolOptions
{
AllowWrite = true,
AllowedPaths = new HashSet<string> { "/project/src" }
}));
tools.Register(BuiltInTools.CreateHttpGet(new HttpToolOptions
{
AllowedHosts = new HashSet<string> { "api.github.com" }
}));
})
.Build();
Example: Ops Bot
An operations bot that can do everything, but requires human approval for critical actions (sending emails, running processes).
var policy = new ToolPermissionPolicy()
.AllowCategory("Data")
.AllowCategory("Text")
.AllowCategory("Numeric")
.AllowCategory("Utility")
.AllowCategory("Security")
.AllowCategory("IO")
.AllowCategory("Net")
.RequireApproval("smtp_send")
.RequireApproval("process_execute")
.RequireApproval("ftp_upload");
var opsAgent = Agent.CreateBuilder(model)
.WithPersona("Operations Bot")
.WithPermissionPolicy(policy)
.WithTools(tools =>
{
tools.Register(BuiltInTools.CalcArithmetic);
tools.Register(BuiltInTools.JsonParse);
tools.Register(BuiltInTools.HttpGet);
tools.Register(BuiltInTools.CreateSmtpSend("smtp.company.com", 587, "bot@company.com", "password"));
})
.Build();
Step 15: Handle Approval Requests
When a permission policy returns ApprovalRequired for a tool call, the runtime raises the ToolApprovalRequired event. You must handle this event to implement human-in-the-loop approval workflows. If no handler is subscribed, the tool call is denied by default.
using var executor = new AgentExecutor();
executor.ToolApprovalRequired += (sender, e) =>
{
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine($"\n [APPROVAL REQUIRED] Tool: {e.ToolCall.Name}");
Console.WriteLine($" Risk: {e.RiskLevel}, Side Effect: {e.SideEffect}");
Console.WriteLine($" Arguments: {e.ToolCall.ArgumentsJson}");
Console.Write(" Allow this call? (y/n): ");
Console.ResetColor();
string? response = Console.ReadLine();
e.Approved = response?.Trim().Equals("y", StringComparison.OrdinalIgnoreCase) == true;
if (!e.Approved)
e.DenialReason = "denied by operator";
};
executor.BeforeToolInvocation += (sender, e) =>
{
// The PermissionResult property shows the policy evaluation outcome
Console.ForegroundColor = ConsoleColor.DarkGray;
Console.WriteLine($" [PERMISSION] {e.Tool?.Name}: {e.PermissionResult}");
Console.ResetColor();
};
var result = await executor.ExecuteAsync(opsAgent, "Send a status report email to team@company.com");
When the agent attempts to call smtp_send, the ToolApprovalRequired event fires. If the user types "y", the tool executes. If the user types "n" (or anything else), the tool call is blocked and the model receives a denial result so it can adapt its approach.
You can also evaluate permissions programmatically through the ToolRegistry:
// Check permission for a specific tool before the agent runs
ToolPermissionResult result = agent.Tools.EvaluatePermission("smtp_send");
Console.WriteLine($"SMTP permission: {result}");
// Output: "SMTP permission: ApprovalRequired"
Model Selection
| Model ID | VRAM | Tool Calling | Best For |
|---|---|---|---|
qwen3:4b |
~4 GB | Yes | Lightweight tool agent, simple data tasks |
qwen3:8b |
~6.5 GB | Yes | Balanced speed and accuracy for multi-tool workflows |
qwen3:14b |
~11 GB | Yes | Complex reasoning across many tools |
gptoss:20b |
~15 GB | Yes | Advanced multi-step data pipelines |
glm4.7-flash |
~18 GB | Yes | Strongest 30B-class MoE, agentic tasks |
Verify tool-calling support after loading: Console.WriteLine($"Tool calling: {model.HasToolCalls}");
Common Issues
| Problem | Cause | Fix |
|---|---|---|
| Agent ignores available tools | Too many tools registered | Register only the tools needed for the task |
| FileSystem tool returns "access denied" | Default is read-only, paths not allowed | Set AllowWrite = true and add paths to AllowedPaths |
| HTTP tool blocks requests | Localhost blocked by default | Add target hosts to AllowedHosts |
| Agent calls wrong tool | Tool descriptions overlap | Use fewer, more targeted tools per agent |
| JSON parsing fails | Input not valid JSON | Use the Validator tool first to check format |
| Slow response with many tools | Model evaluates all tool schemas | Start with a focused tool set and add tools incrementally while monitoring latency and accuracy |
Next Steps
- Create an AI Agent with Tools: fundamentals of agent + tool integration.
- Build an Agent with Web Search and Live Data Access: deep dive into the WebSearchTool provider ecosystem.
- Intercept and Control Tool Invocations: add pre/post invocation hooks, permission policies, and approval workflows.
- Build a Security and Compliance Toolkit Agent: use Security tools for data validation and protection.
- Process PDFs and Images with Built-In Document Tools: build agents that split PDFs, run OCR, and preprocess scanned images.
- Automatically Split Multi-Document PDFs with AI Vision: detect and split mixed-document PDFs with
PdfSplitter.