Parcourir la source

添加tool 工具使用demo

devin.zhu@doverfs.com il y a 2 mois
Parent
commit
cfe3762c03

+ 141 - 4
DFS.AI.API/DFS.AI.API/Controllers/ModelMessageController.cs

@@ -5,7 +5,7 @@ using Microsoft.Extensions.AI;
 namespace DFS.AI.API.Controllers;
 
 [ApiController]
-[Route("[controller]")]
+[Route("[controller]/[action]")]
 public class ModelMessageController : ControllerBase
 {
     private static readonly string[] Summaries =
@@ -73,7 +73,7 @@ public class ModelMessageController : ControllerBase
     }
 
 
-    [HttpGet("{fuel}")]
+    [HttpGet()]
     public Task<ChatModelResponse> Fuel(string promt)
     {
         var promtStr = $$"""
@@ -174,7 +174,7 @@ public class ModelMessageController : ControllerBase
         意图:  
         {  
             "intention": "咨询意图",  
-            "reason": "用户询问油站友好活动",
+            "reason": "用户询问油站优惠活动",
             "Remark":"0/0"  
         }  
         </example>  
@@ -185,9 +185,13 @@ public class ModelMessageController : ControllerBase
         {  
             "intention": "售后意图",  
             "reason": "用户询问加油机错误代码的含义与维修方案",
-            "Remark":"0/0"  
+            "Remark":"51/532"  
         }  
         </example> 
+
+
+        用户请求:{{promt}}
+        若无法明确判断,则选择 "咨询意图",理由写 "输入不够明确,默认为咨询
         """;
         return new ChatHandle().ChatOllamaModel(promtStr, new ChatOptions()
         {
@@ -196,4 +200,137 @@ public class ModelMessageController : ControllerBase
             MaxOutputTokens = 120    // 限制输出长度
         });
     }
+
+
+    [HttpGet()]
+    public async Task<ChatModelResponse> Demo(string promt)
+    {
+        //var promtStr = $$"""
+        //你需要识别用户的加油意图,并根据用户请求分类为以下选项之一:
+        //* 加油意图
+        //* 发票意图
+        //* 咨询意图
+        //* 售后意图
+
+        //当遇到92,95,98,0号,柴油需识别对应的油品,
+
+        //请严格以如下 JSON 格式返回分析结果:
+        //{
+        //    "intention": "<加油意图/发票意图/咨询意图/售后意图>",
+        //    "reason": "<简要说明识别理由>",
+        //    "Remark":"<油品/金额>"
+        //}
+
+        //注意事项:  
+        //1. 仅从用户请求中提取明确的意图,不提供额外信息或推测。  
+        //2. 识别理由需简洁明了,直接引用或总结用户请求的关键词和语义。  
+
+        //以下为示例:  
+
+        //<example>  
+        //用户请求:帮我加92,200元?  
+        //意图:  
+        //{  
+        //    "intention": "加油意图",  
+        //    "reason": "用户询问定量加油",
+        //    "Remark":"92/200"  
+        //}  
+        //</example>  
+
+        //<example>  
+        //用户请求:帮我加200  
+        //意图:  
+        //{  
+        //    "intention": "加油意图",  
+        //    "reason": "用户询问加200元",
+        //    "Remark":"0/200"  
+        //}  
+        //</example>  
+
+        //<example>  
+        //用户请求:帮我加1百92  
+        //意图:  
+        //{  
+        //    "intention": "加油意图",  
+        //    "reason": "用户询问加100元",
+        //    "Remark":"92/100"  
+        //}  
+        //</example>  
+
+
+        //<example>  
+        //用户请求:帮我加20092?  
+        //意图:  
+        //{  
+        //    "intention": "加油意图",  
+        //    "reason": "用户询问加200元的92号汽油",
+        //    "Remark":"92/200"  
+        //}  
+        //</example>  
+
+        //<example>  
+        //用户请求:帮我加10095?  
+        //意图:  
+        //{  
+        //    "intention": "加油意图",  
+        //    "reason": "用户询问加100元的95号汽油",
+        //    "Remark":"95/100"  
+        //}  
+        //</example>  
+
+        //<example>  
+        //用户请求:帮我加92汽油  
+        //意图:  
+        //{  
+        //    "intention": "加油意图",  
+        //    "reason": "用户询问加92汽油",
+        //    "Remark":"92/0"  
+        //}  
+        //</example>  
+
+        //<example>  
+        //用户请求:帮我开发票  
+        //意图:  
+        //{  
+        //    "intention": "开票意图",  
+        //    "reason": "用户询问是否可以开票",
+        //    "Remark":"0/0"  
+        //}  
+        //</example>  
+
+        //<example>  
+        //用户请求:最近油站有什么优惠活动  
+        //意图:  
+        //{  
+        //    "intention": "咨询意图",  
+        //    "reason": "用户询问油站优惠活动",
+        //    "Remark":"0/0"  
+        //}  
+        //</example>  
+
+        //<example>  
+        //用户请求:加油报错误编码 51 532   
+        //意图:  
+        //{  
+        //    "intention": "售后意图",  
+        //    "reason": "用户询问加油机错误代码的含义与维修方案",
+        //    "Remark":"51/532"  
+        //}  
+        //</example> 
+
+
+        //用户请求:{{promt}}
+        //若无法明确判断,则选择 "咨询意图",理由写 "输入不够明确,默认为咨询
+        //""";
+        //return new ChatHandle().ChatOllamaModel(promtStr, new ChatOptions()
+        //{
+        //    Temperature = 0.1f,      // 低随机性,保证稳定输出
+        //    TopP = 0.9f,             // 核采样
+        //    MaxOutputTokens = 120    // 限制输出长度
+        //});
+
+        var funTool = new FunToolHandle();
+        funTool.Demo();
+        return new ChatModelResponse() { };
+    }
 }

+ 4 - 0
DFS.AI.API/DFS.AI.API/DFS.AI.API.csproj

@@ -8,11 +8,15 @@
 
   <ItemGroup>
     <PackageReference Include="Azure.AI.OpenAI" Version="2.8.0-beta.1" />
+    <PackageReference Include="Microsoft.Agents.AI" Version="1.0.0-preview.251219.1" />
+    <PackageReference Include="Microsoft.Agents.AI.Workflows" Version="1.0.0-preview.251219.1" />
     <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.1" />
     <PackageReference Include="Microsoft.Extensions.AI" Version="10.1.1" />
     <PackageReference Include="Microsoft.Extensions.AI.OpenAI" Version="10.1.1-preview.1.25612.2" />
+    <PackageReference Include="ModelContextProtocol" Version="0.5.0-preview.1" />
     <PackageReference Include="NLog.Extensions.Logging" Version="6.1.0" />
     <PackageReference Include="OllamaSharp" Version="5.4.12" />
+    <PackageReference Include="OllamaSharp.ModelContextProtocol" Version="5.4.12" />
   </ItemGroup>
 
   <ItemGroup>

+ 12 - 0
DFS.AI.API/DFS.AI.API/FunTool/FuelToolServer.cs

@@ -0,0 +1,12 @@
+namespace DFS.AI.API.FunTool
+{
+    /// <summary>
+    /// 调用油机的指令Tool
+    /// </summary>
+    public class FuelToolServer
+    {
+        public Task FuelCalculate() {
+            return Task.CompletedTask;
+        }
+    }
+}

+ 10 - 2
DFS.AI.API/DFS.AI.API/Helper/AIClientHelper.cs

@@ -108,8 +108,16 @@ public static class AIClientHelper
         return aiClient;
     }
 
-    public static IChatClient GetOllamaApiClient( string model) {
+   
+
+    /// <summary>
+    /// 默认qwen2.5 测试
+    /// </summary>
+    /// <param name="model"></param>
+    /// <returns></returns>
+    public static IChatClient GetOllamaApiClient(string model="qwen2.5")
+    {
         return new OllamaApiClient(Keys.OllamaUri, model);
     }
-    
+
 }

+ 122 - 0
DFS.AI.API/DFS.AI.API/Helper/FunToolHandle.cs

@@ -0,0 +1,122 @@
+using Microsoft.Extensions.AI;
+using OpenAI.Chat;
+using System.Diagnostics;
+using System.Reflection.Metadata.Ecma335;
+using System.Text.Json;
+using System.Threading;
+
+namespace DFS.AI.API.Helper
+{
+
+    /// <summary>
+    /// 大模型工具应用
+    /// </summary>
+    public class FunToolHandle
+    {
+
+        public async Task<string> Demo()
+        {
+            var chatClient = AIClientHelper.GetOllamaApiClient();
+            // 创建模拟工具集
+            var monitoringTools = new[]
+            {
+                AIFunctionFactory.Create((string city) =>
+                {
+                    Thread.Sleep(500); // 模拟API延迟
+                     return new { Temperature = Random.Shared.Next(15, 35), Humidity = Random.Shared.Next(40, 80) };
+                }, "get_weather", "获取指定城市的天气信息"),
+
+                AIFunctionFactory.Create((string city) =>
+                {
+                    Thread.Sleep(300);
+                    return new { Hotels = Random.Shared.Next(50, 200), AvgPrice = Random.Shared.Next(300, 1500) };
+                }, "get_hotels", "查询指定城市的酒店数量和平均价格"),
+
+                 AIFunctionFactory.Create((int temperature) =>
+                {
+                    return temperature switch
+                    {
+                        < 15 => "建议穿冬装,携带保暖衣物",
+                        < 25 => "建议穿春秋装,温度适宜",
+                        _ => "建议穿夏装,注意防晒"
+                    };
+                }, "suggest_clothing", "根据温度推荐穿搭")
+            };
+
+            // 构建带监控的客户端
+            int iterationCount = 0;
+            int functionCallCount = 0;
+
+            var monitoredClient = chatClient.AsBuilder()
+                .UseFunctionInvocation(configure: options =>
+                {
+                    options.AllowConcurrentInvocation = true;
+                    options.MaximumIterationsPerRequest = 10;
+                    options.FunctionInvoker = async (context, cancellationToken) =>
+                    {
+                        functionCallCount++;
+                        var sw = Stopwatch.StartNew();
+
+                        Console.WriteLine($"\n🔵 [函数调用 #{functionCallCount}]");
+                        Console.WriteLine($"   函数名: {context.Function.Name}");
+                        Console.WriteLine($"   参数: {JsonSerializer.Serialize(context.Arguments)}");
+
+                        var result = await context.Function.InvokeAsync(context.Arguments, cancellationToken);
+                        sw.Stop();
+
+                        Console.WriteLine($"   结果: {result}");
+                        Console.WriteLine($"   耗时: {sw.ElapsedMilliseconds}ms");
+
+                        return result;
+                    };
+                })
+                .Build();
+
+            // 执行复杂查询
+            Console.WriteLine("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
+            Console.WriteLine("📋 用户查询:帮我查询北京和上海的天气,并根据北京的温度推荐穿搭\n");
+
+            var monitoringOptions = new ChatOptions
+            {
+                ToolMode = ChatToolMode.Auto,
+                AllowMultipleToolCalls = true,
+                Tools = monitoringTools
+            };
+
+            var monitoringResult = await monitoredClient.GetResponseAsync(
+                "帮我查询北京和上海的天气,并根据北京的温度推荐穿搭",
+                monitoringOptions
+            );
+
+            Console.WriteLine("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
+            Console.WriteLine("✅ 最终响应:");
+            Console.WriteLine(monitoringResult.Text);
+            Console.WriteLine($"\n📊 统计: 共 {iterationCount} 次迭代,{functionCallCount} 次函数调用");
+
+            return monitoringResult.Text;
+        }
+
+
+        /// <summary>
+        /// 自助加油识别计算总价,返回定量升数
+        /// </summary>
+        /// <returns></returns>
+        public async Task FuelPrices()
+        {
+            var chatClient = AIClientHelper.GetOllamaApiClient();
+            // 创建模拟工具集
+            var monitoringTools = new[]
+            {
+                AIFunctionFactory.Create((decimal prices,int fuel) =>
+                {
+                    //var L=0;
+                    //switch (switch_on)
+                    //{
+                    //    default:
+                    //}
+
+                }, "cal_liters", "根据金额计算油品升数"),
+            };
+        }
+    }
+}

+ 1 - 1
DFS.AI.API/DFS.AI.API/Helper/MafHelper.cs

@@ -62,7 +62,7 @@
 //        ChatOptions chatOptions = new()
 //        {
 //            // Tools = [AIFunctionFactory.Create(GetDateTime, name: nameof(GetDateTime))],
-//            ResponseFormat = ChatResponseFormatJson.ForJsonSchema(schema: jsonSchema)                        
+//            ResponseFormat = ChatResponseFormatJson.ForJsonSchema(schema: jsonSchema)
 //        };
 
 //        var agent = chatClient.CreateAIAgent(

+ 143 - 143
DFS.AI.API/DFS.AI.API/Helper/McpHelper.cs

@@ -1,145 +1,145 @@
-////#r "nuget: ModelContextProtocol, *-*"
-////#r "nuget: Microsoft.Extensions.DependencyInjection"
-////#r "nuget: Microsoft.Extensions.Hosting"
-
-//using Microsoft.Extensions.DependencyInjection;
-//using Microsoft.Extensions.Hosting;
-//using Microsoft.Extensions.Logging;
-//using ModelContextProtocol;
-//using ModelContextProtocol.Client;
-//using ModelContextProtocol.Server;
-//using ModelContextProtocol.Protocol;
-//using System;
-//using System.Collections.Generic;
-//using System.Threading.Tasks;
-
-//using System.IO.Pipelines;
-//using System.Reflection;
-//using System.Threading.Channels;
-
-///// <summary>
-///// Model Context Protocol (MCP) 相关的帮助方法
-///// 统一管理 MCP 相关的 NuGet 包版本和常用功能
-///// </summary>
-//public static class McpHelper
-//{
-//    /// <summary>
-//    /// 使用反射从指定类型中提取所有 MCP 工具
-//    /// </summary>
-//    public static List<McpServerTool> GetToolsForType<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(
-//        System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods)] T>()
-//    {
-//        var tools = new List<McpServerTool>();
-//        var toolType = typeof(T);
-
-//        // 获取所有标记了 McpServerToolAttribute 的公共静态方法
-//        var methods = toolType.GetMethods(BindingFlags.Public | BindingFlags.Static)
-//            .Where(m => m.GetCustomAttributes(typeof(McpServerToolAttribute), false).Any());
-
-//        foreach (var method in methods)
-//        {
-//            try
-//            {
-//                var tool = McpServerTool.Create(method, target: null, new McpServerToolCreateOptions());
-//                tools.Add(tool);
-//            }
-//            catch (Exception ex)
-//            {
-//                throw new InvalidOperationException($"添加工具失败 {toolType.Name}.{method.Name}: {ex.Message}", ex);
-//            }
-//        }
-
-//        return tools;
-//    }
-
-//    /// <summary>
-//    /// 创建并初始化 InMemory Transport 的 Server 和 Client
-//    /// </summary>
-//    /// <param name="tools">要注册的工具集合</param>
-//    /// <param name="serverOptions">Server 配置选项(可选)</param>
-//    /// <param name="loggerFactory">日志工厂(可选)</param>
-//    /// <returns>返回已连接的 Client 和运行中的 Server</returns>
-//    public static async Task<(McpClient Client, McpServer Server)> CreateInMemoryClientAndServerAsync(
-//        List<McpServerTool> tools,
-//        McpServerOptions? serverOptions = null,
-//        McpClientOptions? clientOptions = null,
-//        ILoggerFactory? loggerFactory = null)
-//    {
-//        // 步骤 1:创建两个 Pipe(双向管道)
-//        Pipe clientToServerPipe = new();
-//        Pipe serverToClientPipe = new();
-
-//        // 步骤 2:创建 Server,使用 StreamServerTransport
-//        var serverTransport = new StreamServerTransport(
-//            clientToServerPipe.Reader.AsStream(),
-//            serverToClientPipe.Writer.AsStream());
-
-//        var toolCollection = new McpServerPrimitiveCollection<McpServerTool>();
-        
-//        tools.ForEach(toolCollection.Add);
-
-//        var options = serverOptions ?? new McpServerOptions();
-//        options.ToolCollection = toolCollection;
-
-//        var server = McpServer.Create(serverTransport, options);
-
-//        // 步骤 3:启动 Server(后台运行)
-//        _ = server.RunAsync();
-
-//        // 步骤 4:创建 Client,使用 StreamClientTransport
-//        var client = await McpClient.CreateAsync(
-//            new StreamClientTransport(
-//                clientToServerPipe.Writer.AsStream(),
-//                serverToClientPipe.Reader.AsStream()),
-//            clientOptions: clientOptions,
-//            loggerFactory: loggerFactory);
-
-//        return (client, server);
-//    }
-
-//}
-
-
-//public static class InMemoryMcpClientServerBuilder
-//{
-//    private static readonly Pipe ClientToServerPipe = new();
-//    private static readonly Pipe ServerToClientPipe = new();
-
-//    public static IMcpServerBuilder CreateMcpServerBuilder(Action<McpServerOptions>? options = null)
-//    {
-//        ServiceCollection sc = new();
-//        sc.AddLogging();
-//        var builder = sc.AddMcpServer(options)
-//            .WithStreamServerTransport(ClientToServerPipe.Reader.AsStream(), ServerToClientPipe.Writer.AsStream());
-
-//        return builder;
-//    }
-
-//    public static Task StartMcpServer(IMcpServerBuilder mcpServerBuilder)
-//    {
-//        var serviceProvider = mcpServerBuilder.Services.BuildServiceProvider();
-        
-//        var server = serviceProvider.GetRequiredService<McpServer>();
-
-//        return server.RunAsync();
-//    }
-    
-    
-//    public static async Task<McpClient> CreateMcpClientForServer(McpClientOptions? clientOptions = null)
-//    {
-//        var loggerFactory = LoggerFactory.Create(loggingBuilder =>
-//        {
-//            loggingBuilder.AddConsole();
-//        });
-        
-//        return await McpClient.CreateAsync(
-//            new StreamClientTransport(
-//                serverInput: ClientToServerPipe.Writer.AsStream(),
-//                ServerToClientPipe.Reader.AsStream(),
-//                loggerFactory),
-//            clientOptions: clientOptions,
-//            loggerFactory: loggerFactory);
-//    }
-//}
+//#r "nuget: ModelContextProtocol, *-*"
+//#r "nuget: Microsoft.Extensions.DependencyInjection"
+//#r "nuget: Microsoft.Extensions.Hosting"
+
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+using ModelContextProtocol;
+using ModelContextProtocol.Client;
+using ModelContextProtocol.Server;
+using ModelContextProtocol.Protocol;
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+using System.IO.Pipelines;
+using System.Reflection;
+using System.Threading.Channels;
+
+/// <summary>
+/// Model Context Protocol (MCP) 相关的帮助方法
+/// 统一管理 MCP 相关的 NuGet 包版本和常用功能
+/// </summary>
+public static class McpHelper
+{
+    /// <summary>
+    /// 使用反射从指定类型中提取所有 MCP 工具
+    /// </summary>
+    public static List<McpServerTool> GetToolsForType<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(
+        System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods)] T>()
+    {
+        var tools = new List<McpServerTool>();
+        var toolType = typeof(T);
+
+        // 获取所有标记了 McpServerToolAttribute 的公共静态方法
+        var methods = toolType.GetMethods(BindingFlags.Public | BindingFlags.Static)
+            .Where(m => m.GetCustomAttributes(typeof(McpServerToolAttribute), false).Any());
+
+        foreach (var method in methods)
+        {
+            try
+            {
+                var tool = McpServerTool.Create(method, target: null, new McpServerToolCreateOptions());
+                tools.Add(tool);
+            }
+            catch (Exception ex)
+            {
+                throw new InvalidOperationException($"添加工具失败 {toolType.Name}.{method.Name}: {ex.Message}", ex);
+            }
+        }
+
+        return tools;
+    }
+
+    /// <summary>
+    /// 创建并初始化 InMemory Transport 的 Server 和 Client
+    /// </summary>
+    /// <param name="tools">要注册的工具集合</param>
+    /// <param name="serverOptions">Server 配置选项(可选)</param>
+    /// <param name="loggerFactory">日志工厂(可选)</param>
+    /// <returns>返回已连接的 Client 和运行中的 Server</returns>
+    public static async Task<(McpClient Client, McpServer Server)> CreateInMemoryClientAndServerAsync(
+        List<McpServerTool> tools,
+        McpServerOptions? serverOptions = null,
+        McpClientOptions? clientOptions = null,
+        ILoggerFactory? loggerFactory = null)
+    {
+        // 步骤 1:创建两个 Pipe(双向管道)
+        Pipe clientToServerPipe = new();
+        Pipe serverToClientPipe = new();
+
+        // 步骤 2:创建 Server,使用 StreamServerTransport
+        var serverTransport = new StreamServerTransport(
+            clientToServerPipe.Reader.AsStream(),
+            serverToClientPipe.Writer.AsStream());
+
+        var toolCollection = new McpServerPrimitiveCollection<McpServerTool>();
+
+        tools.ForEach(toolCollection.Add);
+
+        var options = serverOptions ?? new McpServerOptions();
+        options.ToolCollection = toolCollection;
+
+        var server = McpServer.Create(serverTransport, options);
+
+        // 步骤 3:启动 Server(后台运行)
+        _ = server.RunAsync();
+
+        // 步骤 4:创建 Client,使用 StreamClientTransport
+        var client = await McpClient.CreateAsync(
+            new StreamClientTransport(
+                clientToServerPipe.Writer.AsStream(),
+                serverToClientPipe.Reader.AsStream()),
+            clientOptions: clientOptions,
+            loggerFactory: loggerFactory);
+
+        return (client, server);
+    }
+
+}
+
+
+public static class InMemoryMcpClientServerBuilder
+{
+    private static readonly Pipe ClientToServerPipe = new();
+    private static readonly Pipe ServerToClientPipe = new();
+
+    public static IMcpServerBuilder CreateMcpServerBuilder(Action<McpServerOptions>? options = null)
+    {
+        ServiceCollection sc = new();
+        sc.AddLogging();
+        var builder = sc.AddMcpServer(options)
+            .WithStreamServerTransport(ClientToServerPipe.Reader.AsStream(), ServerToClientPipe.Writer.AsStream());
+
+        return builder;
+    }
+
+    public static Task StartMcpServer(IMcpServerBuilder mcpServerBuilder)
+    {
+        var serviceProvider = mcpServerBuilder.Services.BuildServiceProvider();
+
+        var server = serviceProvider.GetRequiredService<McpServer>();
+
+        return server.RunAsync();
+    }
+
+
+    public static async Task<McpClient> CreateMcpClientForServer(McpClientOptions? clientOptions = null)
+    {
+        var loggerFactory = LoggerFactory.Create(loggingBuilder =>
+        {
+            loggingBuilder.AddConsole();
+        });
+
+        return await McpClient.CreateAsync(
+            new StreamClientTransport(
+                serverInput: ClientToServerPipe.Writer.AsStream(),
+                ServerToClientPipe.Reader.AsStream(),
+                loggerFactory),
+            clientOptions: clientOptions,
+            loggerFactory: loggerFactory);
+    }
+}