埃尔布鲁诺。模型上下文协议
   ](https://github.com/elbruno/ElBruno.ModelContextProtocol)
MCP工具的语义路由🔀
埃尔布鲁诺。ModelContextProtocol是一个。NET库,可以很容易地从模型上下文协议(MCP)工具定义中找到合适的工具。它使用由本地嵌入提供支持的语义搜索来将提示路由到最相关的工具,从而在没有外部API调用的情况下实现智能工具选择。 通过在发送给LLM之前将提示仅路由到相关工具,您可以将令牌成本降低70-85%。
包裹
| 程序包 | NuGet | 下载 | 说明 |
|---|---|---|---|
| 埃尔布鲁诺。模型上下文协议。MCPToolRouter |  | ](https://www.nuget.org/packages/ElBruno.ModelContextProtocol.MCPToolRouter) | MCP的语义工具路由 |
MCPToolRouter
用于模型上下文协议工具的高性能语义搜索引擎。MCPToolRouter为您的MCP工具定义建立索引,并使用向量相似性搜索为任何提示返回最相关的工具。
安装
dotnet add package ElBruno.ModelContextProtocol.MCPToolRouter太长,读不下去了
// Mode 1: Embeddings only — fast, no LLM needed
var results = await ToolRouter.SearchAsync(prompt, tools, topK: 3);
// Mode 2: LLM-assisted — best for verbose/complex prompts
var results = await ToolRouter.SearchUsingLLMAsync(prompt, tools, topK: 5);______________________________________________________________________
工作原理——两种模式
MCPToolRouter支持 两种不同的模式 寻找合适的工具。根据您的即时复杂性和速度要求进行选择:
管道
┌─────────────────────────────────────────────────────────────────┐
│ Mode 1: Embeddings Filter (Fast, Simple) │
│ │
│ User Prompt ──► Embed ──► Cosine Similarity ──► Top-K Tools │
│ │
│ "What's the weather?" → [0.89 get_weather, 0.45 send_email] │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Mode 2: Hybrid Search (Precise, Complex) │
│ │
│ User Prompt ──┬──► Embed (baseline) ────────────────┐ │
│ └──► LLM Distill ──► Split Phrases ──►├─► Merge │
│ ──► Embed Each ──────────────────┘ ──► TopK │
│ │
│ "Hey, I was thinking about my trip and need to know if it's │
│ going to rain in Tokyo..." → "check weather Tokyo, │
│ set reminder" → search each + merge with baseline │
└─────────────────────────────────────────────────────────────────┘______________________________________________________________________
模式1:嵌入滤波器(不需要LLM)——一个线性
在以下情况下使用: 你的提示是明确和单一的意图。
它的作用: 使用ONNX在本地嵌入您的提示,并通过余弦相似度找到顶级匹配工具。
速度: 每次查询约1-5ms\ 依赖关系: 仅限本地嵌入(~90MB,自动下载)
静态API(推荐):
using ElBruno.ModelContextProtocol.MCPToolRouter;
using ModelContextProtocol.Protocol;
var tools = new[]
{
new Tool { Name = "get_weather", Description = "Get current weather for a location" },
new Tool { Name = "send_email", Description = "Send an email message to a recipient" },
new Tool { Name = "search_files", Description = "Search for files by name or content" },
new Tool { Name = "calculate", Description = "Perform mathematical calculations" },
new Tool { Name = "translate_text", Description = "Translate text between languages" }
};
// One-liner: route and get results immediately
var results = await ToolRouter.SearchAsync("What's the temperature outside?", tools, topK: 3);
foreach (var r in results)
Console.WriteLine($" {r.Tool.Name}: {r.Score:F3}");
// Output:
// get_weather: 0.847
// calculate: 0.312
// translate_text: 0.201高级:可重用实例(用于性能关键场景)
对于服务器、代理或批处理操作,构建并重用索引以避免重新嵌入:
// Build once, reuse many times (e.g., in a web API or agent loop)
await using var index = await ToolIndex.CreateAsync(tools);
var results = await index.SearchAsync("What's the temperature outside?", topK: 3);______________________________________________________________________
模式2:混合搜索(LLM+多查询)——单行
在以下情况下使用: 你的提示冗长、多部分或对话式。
它的作用: 使用一个小的本地LLM(例如Qwen 2.5 0.5B)将提示提取为逗号分隔的动作短语,然后运行混合搜索:将原始提示作为基线进行搜索,单独搜索每个提取的短语,并合并结果。基线工具保持满分;仅短语工具可享受85%的折扣。这意味着模式2只能 改进 超过模式1,永远不会降级。
速度: ~50–200ms(LLM推理+嵌入)——在GPU上更快\ 依赖关系: 本地嵌入(~90MB)+本地LLM(~1GB,自动下载)
静态API-零设置(推荐):
using ElBruno.ModelContextProtocol.MCPToolRouter;
using ModelContextProtocol.Protocol;
var tools = new Tool[]
{
new Tool { Name = "get_weather", Description = "Get current weather for a location" },
new Tool { Name = "send_email", Description = "Send an email message to a recipient" },
new Tool { Name = "search_files", Description = "Search for files by name or content" },
new Tool { Name = "calculate", Description = "Perform mathematical calculations" },
new Tool { Name = "translate_text", Description = "Translate text between languages" }
};
// One-liner: the local LLM is downloaded and managed automatically
var results = await ToolRouter.SearchUsingLLMAsync(
"Hey, I was thinking about my trip next week and I need to know " +
"if it's going to rain in Tokyo. Also remind me to call the dentist.",
tools, topK: 5);
// The LLM distills this to action phrases: "check weather Tokyo, set reminder call dentist"
// Then each phrase + the original prompt are searched and results merged
foreach (var r in results)
Console.WriteLine($" {r.Tool.Name}: {r.Score:F3}");
// Output:
// get_weather: 0.912
// calculate: 0.287
// translate_text: 0.156高级:自带IChatClient
使用任何 IChatClient (Azure OpenAI、Ollama等)而不是内置的本地LLM:
// Pass your own IChatClient for prompt distillation
var results = await ToolRouter.SearchUsingLLMAsync(
"Complex prompt here...",
tools, chatClient, topK: 5);高级:可重用实例(用于性能关键场景)
对于运行多个查询的服务器或代理,构建并重用路由器实例:
await using var router = await ToolRouter.CreateAsync(tools, chatClient);
var results = await router.RouteAsync("Complex prompt here...", topK: 5);模型元数据(v0.7.1+)
使用时 ElBruno.LocalLLMs v0.7.1+版本,该库公开了具有精确运行时限制的模型元数据:
using var client = await LocalChatClient.CreateAsync();
var info = client.ModelInfo;
Console.WriteLine($"Model: {info?.ModelName}");
Console.WriteLine($" Effective Max Sequence Length: {info?.MaxSequenceLength} tokens");
Console.WriteLine($" Config Max Sequence Length: {info?.ConfigMaxSequenceLength} tokens");在v0.7.1+版本中, MaxSequenceLength 返回 有效运行时间限制 (例如,Phi-3.5迷你ONNX的128个令牌),而 ConfigMaxSequenceLength 保留原始配置值(例如131072)。这确保了元数据的准确性,以便进行正确的输入验证和上下文窗口管理。模式2(SearchUsingLLMAsync)自动使用这些值来验证提示是否符合模型的容量。
GPU加速(可选)
样本默认为 仅CPU ONNX运行时(Microsoft.ML.OnnxRuntimeGenAI),它在任何地方都有效。GPU加速是可选的——如果您的机器支持它,请将运行时包换成模式2推理的2-5倍加速:
| 硬件 | 软件包 | 命令 |
|---|---|---|
| 仅CPU(默认) | Microsoft.ML.OnnxRuntimeGenAI | dotnet add package Microsoft.ML.OnnxRuntimeGenAI |
| Windows GPU(DirectML) | Microsoft.ML.OnnxRuntimeGenAI.DirectML | dotnet add package Microsoft.ML.OnnxRuntimeGenAI.DirectML |
| 英伟达GPU | Microsoft.ML.OnnxRuntimeGenAI.Cuda | dotnet add package Microsoft.ML.OnnxRuntimeGenAI.Cuda |
不要混合使用CPU和GPU变体 --加一个。注意:DirectML和CUDA需要兼容的硬件和驱动程序;如果您的计算机不支持它们,您将收到“不支持指定的提供程序”错误。如有疑问,请使用CPU包。
______________________________________________________________________
何时使用哪种模式
| 模式1:嵌入 | 模式2:混合搜索 | |
|---|---|---|
| 最佳 | 清晰、单一意图的提示 | 详细、多部分、对话式的提示 |
| 速度 | 每次查询约1-5ms | CPU上约50-200ms(GPU上约20-100ms) |
| 依赖项 | 仅限本地嵌入(~90MB) | 本地嵌入+本地LLM(~1GB)+可选GPU运行时 |
| 静态API | ToolRouter.SearchAsync() | ToolRouter.SearchUsingLLMAsync() |
| 实例API | ToolIndex.CreateAsync() + SearchAsync() | ToolRouter.CreateAsync() + RouteAsync() |
| 示例提示 | “发送电子邮件” | “我需要给爱丽丝发一封关于截止日期的电子邮件,并查看天气” |
| 准确度 | 清晰意图为高 | 模糊/复杂意图为高(永远不会比模式1差) |
______________________________________________________________________
在Azure OpenAI中使用筛选工具
这两种模式都与Azure OpenAI无缝协作——先路由,然后只发送经过筛选的工具以降低令牌成本。
using Azure;
using Azure.AI.OpenAI;
using OpenAI.Chat;
// Create Azure OpenAI ChatClient
var chatClient = new AzureOpenAIClient(
new Uri("https://your-resource.openai.azure.com/"),
new AzureKeyCredential("your-api-key"))
.GetChatClient("gpt-5-mini");
// Route to relevant tools only (using Mode 1 as example)
var relevant = await ToolRouter.SearchAsync(userPrompt, allTools, topK: 3);
// Add only filtered tools to the chat call — saving tokens!
var chatOptions = new ChatCompletionOptions();
foreach (var r in relevant)
chatOptions.Tools.Add(ChatTool.CreateFunctionTool(r.Tool.Name, r.Tool.Description ?? ""));
var response = await chatClient.CompleteChatAsync([new UserChatMessage(userPrompt)], chatOptions);工作原理——技术细节
核心机制: MCPToolRouter将工具描述和用户提示嵌入到向量空间中,然后通过余弦相似度找到前K个工具——所有这些都使用本地嵌入(ONNX,没有外部API)。
模式2的混合管道: 使用LLM蒸馏时,本地LLM从详细提示中提取逗号分隔的动作短语。将原始提示作为基线进行搜索(模式1),然后单独搜索每个提取的短语。结果被合并:基线工具保持满分,仅短语工具获得85%的折扣。后处理会自动清理尾随单词重复和重复短语(常见于小型本地LLM)。这种混合方法保证了模式2只能比模式1有所改进。
代币节省: 通过在将提示发送到外部LLM之前仅将其路由到相关工具,您可以在保持准确性的同时将令牌使用量减少70-85%。
高级功能
工具索引选项
使用配置索引行为 ToolIndexOptions:
var options = new ToolIndexOptions
{
QueryCacheSize = 20, // LRU cache for repeated queries (0 = disabled)
EmbeddingTextTemplate = "{Name}: {Description}" // Customize how tools are embedded
};
await using var index = await ToolIndex.CreateAsync(tools, options);保存/加载索引
保留预构建的索引,以避免在启动时重新嵌入:
// Save
using var file = File.Create("tools.bin");
await index.SaveAsync(file);
// Load (instant warm-start — no re-embedding)
using var stream = File.OpenRead("tools.bin");
await using var loaded = await ToolIndex.LoadAsync(stream);动态索引(添加/删除工具)
在运行时修改索引而不重新生成:
await index.AddToolsAsync(new[] { new Tool { Name = "new_tool", Description = "..." } });
index.RemoveTools(new[] { "obsolete_tool" });依赖注入
注册 IToolIndex 作为ASP中的单例。NET Core或任何DI容器:
builder.Services.AddMcpToolRouter(tools, opts =>
{
opts.QueryCacheSize = 20;
});
// Inject IToolIndex anywhere
app.MapGet("/search", async (IToolIndex index, string query)
=> await index.SearchAsync(query, topK: 3));自定义嵌入生成器
带上你自己的 IEmbeddingGenerator> 来自微软。扩展。人工智能生态系统:
IEmbeddingGenerator> myGenerator = /* your provider */;
await using var index = await ToolIndex.CreateAsync(tools, myGenerator, options);______________________________________________________________________
⚡ 性能指南
MCPToolRouter专为一次性查询和高吞吐量场景而设计。了解为您的用例选择正确的API的权衡。
静态与实例API
这 静态API (SearchAsync, SearchUsingLLMAsync)非常适合简单的脚本和一次性调用——只需传递提示和工具,即可立即获得结果。在幕后,静态方法现在默认重用共享的单例ONNX会话(UseSharedResources = true),因此重复的静态调用 快得多 比以前。
这 实例API (ToolIndex.CreateAsync + SearchAsync,或 ToolRouter.CreateAsync + RouteAsync)针对服务器、代理和批处理操作进行了优化,您可以在其中进行许多查询。通过重用相同的索引,您可以避免重新嵌入工具和重新初始化LLM——这提供了 15-35倍加速 与每次创建新索引相比,在后续查询中。
专业提示: 如需清理,请致电 await ToolRouter.ResetSharedResourcesAsync() 在应用程序关闭时释放单例资源。
查询缓存
集 QueryCacheSize > 0 在 ToolIndexOptions 缓存查询嵌入。相同的提示完全跳过嵌入生成(每个查询约0ms对约10-20ms):
var options = new ToolIndexOptions { QueryCacheSize = 20 };
await using var index = await ToolIndex.CreateAsync(tools, options);
var result1 = await index.SearchAsync("weather in Tokyo"); // ~10ms: embedding generated
var result2 = await index.SearchAsync("weather in Tokyo"); // ~0ms: cache hit添加或删除工具时,缓存会自动清除。
快速推荐
| 场景 | 推荐API | 原因 |
|---|---|---|
| CLI工具,一次性查询 | ToolRouter.SearchAsync() | 最简单的共享资源处理性能 |
| 服务器/代理,许多查询 | ToolRouter.CreateAsync() + RouteAsync() | 完全控制,最佳性能 |
| 冗长或复杂的提示 | ToolRouter.SearchUsingLLMAsync() | 混合搜索:LLM提取+多查询合并 |
______________________________________________________________________
🔒 安全注意事项
MCPToolRouter完全在本地运行——模型和搜索永远不会离开您的机器。然而,一些安全实践值得考虑:
提示注入
模式2使用本地LLM进行快速蒸馏。虽然LLM不能直接执行工具,但从理论上讲,对抗性提示可能会影响选择哪些工具。在执行任何工具调用之前,在应用程序的下游验证工具选择。示例:如果工具需要身份验证或权限检查,请在执行之前强制执行。
模型下载
嵌入和LLM模型在首次使用时会自动下载。使用 EmbeddingModelCacheDirectory 控制模型存储位置的选项:
var options = new ToolIndexOptions { EmbeddingModelCacheDirectory = "/secure/models" };
await using var index = await ToolIndex.CreateAsync(tools, options);模型通过HTTPS下载并验证。如果再现性至关重要,请指定特定的型号版本。
输入验证
ToolIndex.LoadAsync() 验证所有数值边界。默认蒸馏设置:
| 选项 | 默认值 | 描述 |
|---|---|---|
MaxPromptLength | 500 | 最大输入字符数(超过时截断) |
DistillationMaxOutputTokens | 384 | LLM蒸馏输出的最大代币 |
DistillationTemperature | 0.1 | LLM采样温度 |
SystemPrompt | *(见下文)* | 提取逗号分隔的动作短语 |
默认系统提示指示LLM将关键任务提取为逗号分隔的动作短语(每个2-5个单词),这些短语针对嵌入余弦相似性进行了优化。如果以Azure OpenAI或Ollama等云LLM为目标,请增加 MaxPromptLength:
var options = new ToolRouterOptions { MaxPromptLength = 2000 };超过配置限制的提示将自动截断。如果LLM遇到任何错误,try-catch安全网可确保优雅的回退。随着 ElBruno.LocalLLMs v0.7.1+,通过双通道确保元数据准确性 MaxSequenceLength / ConfigMaxSequenceLength 属性,实现可靠的上下文窗口管理。
供应链
通过以下方式确保构建的可重复性 NuGet锁文件 (.csproj 和 RestorePackagesWithLockFile=true).锁定文件锁定确切的包版本,防止可能影响行为或引入漏洞的意外更新。
______________________________________________________________________
样品
九个示例应用程序展示了MCPToolRouter的不同用例:
|示例|描述|需要Azure| |--------|-------------|:-:| | 基础 |入门-索引工具和搜索|❌ | | McpToolRouting |用于复杂快速路由的本地LLM蒸馏|❌ | | LLM列表演示 |模式1与模式2的详细提示比较|❌ | | LLM列表Max |缩放模式2——120多种工具,12段长度提示,幽灵。控制台用户体验|❌ | | 令牌比较 |比较令牌使用情况:所有工具与路由|✅ | | TokenComparisonMax |具有丰富Spectre的极端120+工具场景。控制台用户体验|✅ | | 筛选函数调用 |使用筛选工具进行端到端函数调用|✅ | | 带有ToolRouter的代理 |带语义工具路由的Microsoft代理框架|✅ | | 功能工具验证 |52个具有执行验证的真实工具——标准与路由|✅ |
基础
入门——索引工具和语义相似性搜索。不需要Azure。
McpToolRouting
演示本地LLM驱动的工具路由和快速蒸馏。28个现实的MCP工具、复杂的多部分提示处理和令牌节省分析。不需要Azure。
LLM列表演示
在7个场景中,通过冗长、冗长的对话式提示,对模式1(仅嵌入)和模式2(LLM提取)进行面对面比较。展示了模式2存在的原因——当用户漫游时,LLM蒸馏会提取核心意图,以更好地选择工具。不需要Azure。
LLM列表Max
LLMDistillationDemo的扩展: 120+工具 跨越12个域, 12段长度提示 (每个100-200字),丰富的幽灵。控制台表格和比较模式1与模式2准确性的最终记分板。使用静态单行API(ToolRouter.SearchAsync / ToolRouter.SearchUsingLLMAsync).不需要Azure。
令牌比较
显示令牌节省的选框示例:所有18个工具(标准模式,约1800个令牌)与前3个路由工具(约500个令牌,约72%的节省)。
TokenComparisonMax
极端规模的演示,包含12个类别的120多种工具定义、实时代币跟踪和Spectre。控制台丰富的终端用户界面。
筛选函数调用
端到端示例:使用MCPToolRouter路由工具,仅将筛选后的工具发送到Azure OpenAI,并处理工具调用响应。
带有ToolRouter的代理
将MCPToolRouter与Microsoft Agent Framework集成。11个功能工具、语义路由和多回合对话演示。
功能工具验证
通过8个领域(数学、字符串、集合、日期、转换、编码、统计、哈希)的52个真实工具实现进行全面验证。通过12个测试场景验证路由模式与标准模式。
______________________________________________________________________
Azure OpenAI设置
某些示例需要Azure OpenAI凭据(TokenComparison、TokenComparisonMax、FilteredFunctionCalling、AgentWithToolRouter、FunctionalToolsValidation)。所有样本共享相同的UserSecretsId(elbruno-mcp-samples),因此您只需配置机密 一次:
dotnet user-secrets set "AzureOpenAI:Endpoint" "https://your-resource.openai.azure.com/" --id elbruno-mcp-samples
dotnet user-secrets set "AzureOpenAI:ApiKey" "your-api-key" --id elbruno-mcp-samples
dotnet user-secrets set "AzureOpenAI:DeploymentName" "gpt-5-mini" --id elbruno-mcp-samples替换:
your-resource使用您的Azure OpenAI资源名称your-api-key使用API密钥gpt-5-mini使用您部署的模型名称
注: 不运行 dotnet user-secrets init --共享的UserSecretsId已在每个示例的项目文件中配置。有关其他设置详细信息,请参阅每个示例的文件夹。
______________________________________________________________________
从源头构建
克隆存储库并使用构建。NET-CLI:
dotnet restore ElBruno.ModelContextProtocol.slnx
dotnet build ElBruno.ModelContextProtocol.slnx
dotnet test ElBruno.ModelContextProtocol.slnx文档
更详细的文档和示例可在 docs/ 文件夹。
许可证
此项目根据MIT许可证获得许可——请参阅 许可证 了解详情。
作者
布鲁诺·卡普阿诺 (埃尔布鲁诺)
- 💻 博客:https://elbruno.com
- 📺 油管https://youtube.com/elbruno
- 💼 领英:https://linkedin.com/in/elbruno
- 🐦 推特:https://twitter.com/elbruno
- 🎙️ 播客:https://notienenombre.com
致谢
这个库建立在以下基础之上:
- 埃尔布鲁诺。局部嵌入 --无需外部API的本地嵌入生成
- 模型上下文协议。NET SDK --官方MCP。NET支持
