<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      我給 AI 接上了一個 C# 運行器,結果它學會了自己上網、調試代碼

      昨天的文章中,我們介紹了我的新開源項目:C# Runner。這是一個強大的C#代碼運行器,不僅提供了前端UI,還內建了API和一個MCP服務端。

      大家可能知道,MCP (Model Context Protocol) 是由 Anthropic 公司推出的一個協議,旨在讓大語言模型(LLM)能夠以一種更通用的方式調用外部工具。我們的 C# Runner 正好實現了MCP協議,這使得任何大模型都能通過API來調用并執行C#代碼,從而獲得精確、可靠的外部能力。

      今天,我們就來深入探討如何將這個強大的C#運行器接入到大模型中,讓AI擁有執行代碼的“超能力”。image2

      大模型“幻覺”的困境

      通常,我們可能是這樣通過 OpenAIClient 調用聊天API的:

      // 注意:需要安裝 OpenAI 的 NuGet 包
      var api = new OpenAIClient(new ApiKeyCredential(Util.GetPassword("azure-ai-key")), new OpenAIClientOptions
      {
          Endpoint = new Uri($"https://{Util.GetPassword("azure-ai-resource")}.openai.azure.com/openai/v1?api-version=preview"),
      });
      ChatClient cc = api.GetChatClient("gpt-4.1");
      
      await foreach (StreamingChatCompletionUpdate delta in cc.CompleteChatStreamingAsync(
      [
          new SystemChatMessage("你是人工智能助理"),
          new UserChatMessage("1234567除以7654321=?(需要精確到5位小數)"),
      ]))
      {
          if (delta.ContentUpdate.Count > 0)
          {
              Console.Write(delta.ContentUpdate[0].Text);
          }
      }
      

      對于這個數學問題,大模型的輸出可能如下:

      用計算器直接計算:
      $$\frac{1234567}{7654321} \approx 0.16128$$
      精確到小數點后5位答案是:
      0.16128

      模型聲稱它“使用計算器”了,但實際上,這個結果(0.16128)是基于其內部的概率推理得出的,并非精確計算。我們知道,正確答案其實是 0.16129。這種在需要精確計算時產生的“幻覺”,正是我們需要外部工具來解決的痛點。

      通過MCP協議賦予大模型C#執行能力

      為了解決上述問題,我們可以將 C# 運行器作為工具接入大模型。第一步是讓我們的應用程序了解這個工具有什么功能。通過 MCP 協議,我們可以輕松獲取這些信息。

      首先,安裝 ModelContextProtocol.Core NuGet 包,然后用下面的代碼獲取工具的定義:

      // 安裝 NuGet 包: ModelContextProtocol.Core
      IMcpClient mcpClient = await McpClientFactory.CreateAsync(new SseClientTransport(new SseClientTransportOptions
      {
          Endpoint = new Uri("https://csharp.starworks.cc/mcp"),
      }));
      
      await foreach (var tool in mcpClient.EnumerateToolsAsync())
      {
          Console.WriteLine($"""
              Name: {tool.Name}
              Schema: {tool.JsonSchema.ToString()}
              Description: {tool.Description}
              """);
      }
      

      運行后,你會得到類似下面的輸出,它描述了工具的名稱、功能和參數:

      Name: run_code
      Schema: {"type":"object","properties":{"code":{"type":"string"},"timeout":{"type":"integer"}},"required":["code"]}
      Description: Run C# code in a sandboxed environment, default timeout: 30000(ms)……
      

      有了這些信息,我們就可以將其轉換為 OpenAI Chat Completion API 所要求的工具格式。

      var cco = new ChatCompletionOptions();
      await foreach (McpClientTool tool in mcpClient.EnumerateToolsAsync())
      {
          cco.Tools.Add(ChatTool.CreateFunctionTool(
              tool.Name, 
              tool.Description, 
              BinaryData.FromString(tool.JsonSchema.GetRawText())));
      }
      

      構建大模型與工具的交互循環

      當大模型決定使用工具時,整個交互過程并非一次完成,而是一個循環。模型可能會多次調用工具,直到它認為問題已經解決。我們需要構建一個循環來處理這個過程,并將每一次的對話、工具調用請求和工具返回結果都保存起來。

      下面是這個交互循環的邏輯骨架:

      // 歷史消息,包含系統指令和用戶初次提問
      var histories = new List<ChatMessage>
      {
          new SystemChatMessage("你是人工智能助理,請結合已有工具(如果存在)回復用戶的需求,如果工具錯誤,請盡量解決錯誤并重試"),
          new UserChatMessage("1234567除以7654321=?(需要精確到5位小數)")
      };
      
      ChatFinishReason? finishReason = null;
      do
      {
          // 異步流式獲取模型響應
          await foreach (StreamingChatCompletionUpdate delta in cc.CompleteChatStreamingAsync(histories, cco))
          {
              // ... 處理流式響應 ...
              
              // 1. 收集模型發出的工具調用請求
              // (需要將流式返回的Delta片段拼接成完整的工具調用)
              
              // 當模型確認需要調用工具時
              if (delta.FinishReason == ChatFinishReason.ToolCalls)
              {
                  // 2. 將模型的工具調用請求添加到歷史記錄中
                  // 3. 調用MCP客戶端,執行C#代碼
                  // 4. 將工具的執行結果添加到歷史記錄中
              }
              
              // ... 輸出模型的最終文本回復 ...
              
              finishReason = delta.FinishReason;
          }
      } while (finishReason == ChatFinishReason.ToolCalls || finishReason == null); // 如果模型還需要調用工具,則繼續循環
      

      這里有幾個關鍵點需要注意:

      1. 上下文管理:所有用戶輸入、模型回復、工具調用和工具結果都必須保存在 histories 列表中,確保模型在后續的每一次調用中都能理解完整的上下文。
      2. 循環與重試:交互是一個 do-while 循環。大模型可能會連續多次調用工具(甚至為了修正錯誤而重試),直到它認為不再需要工具,可以給出最終答案為止。
      3. 成本計算:由于可能發生多次模型調用,會產生多個 Usage 信息。你需要將它們累加,以計算總的 token 消耗和成本。
      4. 流式處理:OpenAI 的工具調用同樣支持流式輸出。你需要正確地將流式返回的 delta 片段聚合成一個或多個完整的工具調用請求。

      完整示例:精確計算與代碼糾錯

      讓我們把所有部分串聯起來,看看一個完整的、能夠工作的例子。

      安裝 NuGet 包:

      • OpenAI (2.2.0 或更高)
      • ModelContextProtocol.Core (0.3.0-preview.3 或更高)
      // --- 完整代碼 ---
      var api = new OpenAIClient(new ApiKeyCredential(Util.GetPassword("azure-ai-key")), new OpenAIClientOptions
      {
          Endpoint = new Uri($"https://{Util.GetPassword("azure-ai-resource")}.openai.azure.com/openai/v1?api-version=preview"),
      });
      var cc = api.GetChatClient("gpt-4.1");
      
      // 1. 初始化 MCP 客戶端
      IMcpClient mcpClient = await McpClientFactory.CreateAsync(new SseClientTransport(new SseClientTransportOptions
      {
          Endpoint = new Uri("https://csharp.starworks.cc/mcp"),
      }));
      
      // 2. 獲取工具定義并配置到 OpenAI 客戶端
      var cco = new ChatCompletionOptions();
      await foreach (McpClientTool tool in mcpClient.EnumerateToolsAsync())
      {
          cco.Tools.Add(ChatTool.CreateFunctionTool(tool.Name, tool.Description, BinaryData.FromString(tool.JsonSchema.GetRawText())));
      }
      
      var jso = new JsonSerializerOptions { Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping };
      var histories = new List<ChatMessage>
      {
          new SystemChatMessage("你是人工智能助理,請結合已有工具(如果存在)回復用戶的需求,如果工具錯誤,請盡量解決錯誤并重試"),
          new UserChatMessage("1234567除以7654321=?(需要精確到5位小數)")
      };
      
      // 3. 開始交互循環
      ChatFinishReason? finishReason = null;
      do
      {
          var toolCalls = new Dictionary<int, FunctionArgs>();
          await foreach (StreamingChatCompletionUpdate delta in cc.CompleteChatStreamingAsync(histories, cco))
          {
              foreach (StreamingChatToolCallUpdate tool in delta.ToolCallUpdates)
              {
                  byte[] argsDelta = tool.FunctionArgumentsUpdate.ToArray();
                  if (toolCalls.TryGetValue(tool.Index, out FunctionArgs? toolCall))
                  {
                      toolCall.Args.AddRange(argsDelta);
                  }
                  else
                  {
                      toolCalls.Add(tool.Index, new FunctionArgs(tool.ToolCallId, tool.FunctionName) { Args = argsDelta.ToList() });
                  }
              }
      
              if (delta.FinishReason == ChatFinishReason.ToolCalls)
              {
                  histories.Add(new AssistantChatMessage(toolCalls.Values.Select(x => ChatToolCall.CreateFunctionToolCall(x.Id, x.Name, BinaryData.FromBytes(x.Args.ToArray())))));
                  foreach (FunctionArgs func in toolCalls.Values)
                  {
                      // 調用 MCP 工具執行代碼
                      Console.WriteLine("--- C# Code to Run ---");
                      Console.WriteLine(JsonSerializer.Deserialize<JsonObject>(func.Args.ToArray())!["code"]!.ToString());
                      
                      CallToolResult result = await mcpClient.CallToolAsync(func.Name, BinaryData.FromBytes(func.Args.ToArray()).ToObjectFromJson<Dictionary<string, object>>()!);
      
                      Console.WriteLine("--- Execution Result ---");
                      Console.WriteLine(result.StructuredContent);
      
                      // 將結果添加回歷史記錄
                      histories.Add(ChatMessage.CreateToolMessage(func.Id, JsonSerializer.Serialize(result.StructuredContent, jso)));
                  }
              }
      
              if (delta.ContentUpdate.Count > 0)
              {
                  Console.Write(delta.ContentUpdate[0].Text);
              }
              
              if (delta.Usage != null)
              {
                  Console.WriteLine($"\n--- Usage: {delta.Usage.TotalTokenCount} tokens ---");
                  if (finishReason != null) break;
              }
              finishReason = delta.FinishReason;
          }
      } while (finishReason == ChatFinishReason.ToolCalls || finishReason == null);
      
      public record FunctionArgs(string Id, string Name)
      {
      	public List<byte> Args { get; set; } = [];
      }
      

      運行上述代碼,你會看到這樣的輸出:

      --- C# Code to Run ---
      double result = 1234567.0 / 7654321.0;
      return Math.Round(result, 5);
      
      --- Execution Result ---
      {"kind":"end","result":0.16129,"elapsed":150}
      
      --- Usage: 1487 tokens ---
      1234567 ÷ 7654321 = 0.16129(精確到小數點后5位)。
      --- Usage: 1538 tokens ---
      

      看!大模型首先生成了一段C#代碼,然后通過我們的C#運行器執行,得到了精確的結果 0.16129,并最終給出了正確的答案。這個過程涉及兩次對大模型的調用,一次用于生成代碼,一次用于總結答案,因此產生了兩次 Usage 記錄。

      更多有趣的騷操作

      1. 計算真實的SHA256哈希值

      如果你直接問 GPT-4.1 “C#” 的SHA256值是什么,它可能會“猜”一個答案:

      錯誤示范(模型猜測)
      ecddf76be50b529b129c5602778b0a8ddc52ae688ef31fa8c7c3d776b2115747

      這顯然不是一個真實計算出的哈希值。但當我們接入C#運行器后,模型會選擇編寫并執行代碼:

      --- C# Code to Run ---
      using System.Text;
      using System.Security.Cryptography;
      
      string input = "C#";
      using (SHA256 sha256 = SHA256.Create())
      {
          byte[] inputBytes = Encoding.UTF8.GetBytes(input);
          byte[] hashBytes = sha256.ComputeHash(inputBytes);
          // Convert to hex string
          StringBuilder sb = new StringBuilder();
          foreach (var b in hashBytes)
              sb.Append(b.ToString("x2"));
          return sb.ToString();
      }
      
      --- Execution Result ---
      {"kind":"end","result":"040228846ead4a4195145fe089343cb0894d00a9380176a41a8f6c5ee70b4824","elapsed":354}
      
      --- Usage: 1563 tokens ---
      字符串 "C#" 的 SHA256 哈希值是:
      040228846ead4a4195145fe089343cb0894d00a9380176a41a8f6c5ee70b4824
      --- Usage: 1664 tokens ---
      

      我們完全有理由相信,這一次,0402...4824 才是通過代碼堅實計算出的真實哈希值。

      2. 實時網絡爬蟲:獲取博客園頭條

      這是一個更有挑戰性的任務。沒有工具的大模型無法訪問實時互聯網數據,當你問它“今天博客園有哪些頭條”時,它只能抱歉地表示無能為力。

      但有了C#運行器這個唯一的工具,事情就變得有趣了。我使用了 o3 模型(一個代碼能力更強的模型),并向它發出了同樣的提問。接下來發生了一系列非常精彩的“自主調試”:

      • 第1次嘗試:模型編寫了爬蟲代碼,但使用了 System.Web.HttpUtility,這在 .NET Core 環境中不存在,導致編譯錯誤。
      • 第2次嘗試:模型接收到錯誤反饋,自動修正了代碼,改用 System.Net.WebUtility。這次編譯通過了,但因為HTML結構定位不準,沒有抓到內容。
      • 第3次嘗試:模型決定先看看網頁原始HTML長什么樣,于是寫代碼獲取了前1500個字符。
      • 第4次到第7次嘗試:基于對HTML結構的觀察,模型不斷調整它的正則表達式和字符串定位邏輯,期間還遇到了幾次自己寫的正則轉義錯誤。每一次失敗,它都根據錯誤信息進行調整。
      • 第8次嘗試成功! 模型終于編寫出了正確的代碼,成功提取了頭條標題和鏈接。
      • 第9次嘗試:模型對第8次的結果做了最后的美化和過濾,然后輸出。

      最終,模型給出了一份格式優美的報告:

      今天博客園頭條區塊顯示的最新 4 條內容:

      1. 【編輯推薦】通過抓包,深入揭秘 MCP 協議底層通信(5/17/1090)
        http://www.rzrgm.cn/sdcb/p/18995424/mcp-http-insights

      2. 【最多推薦】為大模型 MCP Code Interpreter 而生:C# Runner 開源發布(8/13/537)
        http://www.rzrgm.cn/sdcb/p/19003720/csharp-runner-mcp

      3. 【新聞頭條】反物質量子比特首次演示(0/1/210)
        https://news.cnblogs.com/n/797655/

      4. 【特別頭條】博客園眾包:誠征 3D 影像景深延拓實時處理方案(預算 8-15 萬)(41/9/5584)
        http://www.rzrgm.cn/cmt/p/18948571

      (括號內數字依次代表:評論數 / 推薦數 / 閱讀數)

      這個過程生動地展示了當大模型擁有一個強大的代碼執行工具后,它如何像一個真正的程序員一樣,通過不斷試錯、調試和迭代來完成一個復雜的任務。

      總結

      通過將 C# Runner 接入大語言模型,我們極大地擴展了模型的能力邊界。借助 MCP 協議的標準化,這種集成為模型賦予了執行精確計算、訪問實時數據、與外部API交互等關鍵能力,有效地克服了模型的“幻覺”問題。從簡單的數學計算到復雜的網絡爬蟲,我們看到了一個更強大、更可靠的AI應用范式正在形成。image


      感謝閱讀,希望本文對你有所幫助!如果你有任何問題或建議,歡迎在評論區留言討論。

      覺得有用的話,請給我的項目一個 Star ? 吧:
      https://github.com/sdcb/csharp-runner

      也歡迎加入 .NET 騷操作 QQ 群:495782587,一起交流 .NET 和 AI 的有趣玩法!

      posted @ 2025-07-26 08:45  .NET騷操作  閱讀(1781)  評論(9)    收藏  舉報
      主站蜘蛛池模板: 国产蜜臀一区二区在线播放| 久久精品蜜芽亚洲国产av| 国产无遮挡又黄又爽不要vip软件| 亚洲成人av综合一区| 韩国午夜福利片在线观看| 丁香婷婷色综合激情五月| 午夜精品久久久久久久爽| 久久精品国产精品亚洲| 通榆县| 色综合激情丁香七月色综合| 久久精品第九区免费观看| 精品人妻一区二区三区蜜臀| 亚洲成A人片在线观看的电影| 免费国产va在线观看| 国产精品成人一区二区三区| 重口SM一区二区三区视频 | 国产精品综合在线免费看| 好爽毛片一区二区三区四| 亚洲综合无码一区二区| 欧美成人精品手机在线| 日韩精品一二三黄色一级| 久久久久无码精品国产AV| 国产普通话对白刺激| 无套内谢少妇毛片aaaa片免费| 亚洲愉拍一区二区三区| 淮滨县| 无码AV中文字幕久久专区| 日韩在线视频观看免费网站| 庆元县| 国产a在视频线精品视频下载| 亚洲男人AV天堂午夜在| 绵阳市| Y111111国产精品久久久| 强开少妇嫩苞又嫩又紧九色| 97成人碰碰久久人人超级碰oo| 中文字幕国产精品一区二| 国产资源精品中文字幕| 泗洪县| 2020精品自拍视频曝光| 国产99在线 | 亚洲| 又黄又爽又色的免费网站|