您现在的位置是:网站首页> C#技术
C# AI编程技术收集
- C#技术
- 2025-11-24
- 560人已阅读
C# AI编程技术收集
C# Agent框架
Semantic Kernel :微软亲儿子,功能全,适合企业级
AutoGen :社区活跃,支持多模型
LlmTornado :轻量、易上手,适合快速验证想法
LangChain.NET 主要支持 .NET 6.0 及以上版本

客户端将MCP工具列表和用户问题-》大语言模型-》需要调用MCP的信息返回给调用模型的客户端,客户端调用指定MCP->客户端获得MCP结果-》大语言模型-》返回给最终客户
LangChain.NET如何调用Ollama制作知识库和调用MCP服务
LangChain.NET如何调用LM Studio制作知识库和调用MCP服务
C#LangChain调用Ollama并使用LangChain.Connectors.Mcp分别调用studio、sse 、streamable_http类型的MCP服务的例子
给出LangChain调用Ollama模型并支持MCP那三种服务的完整例子
最简单的MCP服务及调用,使用FastMCP编写MCP服务,C#成功调用
三个MCP分别是stdio sse streamable 方式部署,实现大模型可以调用他们
C#使用LangChain调用大模型包括MCP
- 实现MCP Server:可以基于C#的gRPC或HTTP框架(如ASP.NET Core)实现MCP Server,定义工具接口和实现逻辑。
- 实现MCP Client:使用C#的gRPC或HTTP客户端库与MCP Server通信,调用工具。
- 集成LangChain.NET:将MCP Client与LangChain.NET结合,创建自定义工具类,使其能够在LangChain.NET中使用。
- 调用大模型:使用LangChain.NET的模型调用功能,结合MCP工具进行复杂任务处理。
以下是一个简化的示例代码结构:
// MCP Server示例(仅供参考)
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
public class Startup{
public void ConfigureServices(IServiceCollection services)
{
services.AddGrpc();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapGrpcService<MCPService>();
});
}}
public class MCPService : MCP.MCPBase{
public override async Task<ToolResponse> ExecuteTool(ToolRequest request, ServerCallContext context)
{
// 工具执行逻辑
return new ToolResponse { Result = "工具执行结果" };
}}
// MCP Client示例(仅供参考)
using Grpc.Net.Client;
public class MCPClient{
private readonly MCP.MCPClient _client;
public MCPClient(string serverAddress)
{
var channel = GrpcChannel.ForAddress(serverAddress);
_client = new MCP.MCPClient(channel);
}
public async Task<string> ExecuteToolAsync(string toolName, Dictionary<string, object> parameters)
{
var request = new ToolRequest
{
ToolName = toolName,
Parameters = { parameters }
};
var response = await _client.ExecuteToolAsync(request);
return response.Result;
}}
// LangChain.NET集成示例(仅供参考)
using LangChain.NET;
public class Program{
public static async Task Main(string[] args)
{
// 初始化LangChain模型
var llm = new OpenAIModel("your_openai_api_key");
// 初始化MCP Client
var mcpClient = new MCPClient("https://mcp-server-address");
// 创建自定义工具
var tools = new List<ITool>
{
new MCPTool(mcpClient, "math", "执行数学计算"),
new MCPTool(mcpClient, "weather", "获取天气信息")
};
// 创建代理
var agent = new ReactAgent(llm, tools);
// 执行查询
var result = await agent.AskAsync("请计算3+5的值");
Console.WriteLine(result);
}}
public class MCPTool : ITool{
private readonly MCPClient _mcpClient;
private readonly string _toolName;
private readonly string _description;
public MCPTool(MCPClient mcpClient, string toolName, string description)
{
_mcpClient = mcpClient;
_toolName = toolName;
_description = description;
}
public async Task<object> ExecuteAsync(object input)
{
var parameters = new Dictionary<string, object>
{
{ "input", input }
};
return await _mcpClient.ExecuteToolAsync(_toolName, parameters);
}
public string Description => _description;
}
大模型调用MCP的代码
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using System.IO;
namespace EnhancedOllamaMcpExample
{
// Ollama API请求模型
public class OllamaRequest
{
public string Model { get; set; }
public string Prompt { get; set; }
public bool Stream { get; set; } = false;
public Dictionary<string, object> Options { get; set; } = new Dictionary<string, object>();
}
// Ollama API响应模型
public class OllamaResponse
{
public string Model { get; set; }
public string Created_At { get; set; }
public string Response { get; set; }
public bool Done { get; set; }
}
// MCP指令模型
public class McpCommand
{
public string Action { get; set; }
public Dictionary<string, string> Parameters { get; set; } = new Dictionary<string, string>();
}
class EnhancedOllamaMcpClient
{
private readonly HttpClient _httpClient;
private readonly string _ollamaBaseUrl;
private readonly string _modelName;
private readonly string _mcpInstructions; // MCP使用说明
public EnhancedOllamaMcpClient(string ollamaBaseUrl = "http://localhost:11434", string modelName = "qwen")
{
_ollamaBaseUrl = ollamaBaseUrl;
_modelName = modelName;
_httpClient = new HttpClient();
_httpClient.BaseAddress = new Uri(_ollamaBaseUrl);
// 详细的MCP使用说明,告诉模型如何调用工具
_mcpInstructions = @"
当你需要完成以下任务时,请使用MCP指令:
1. 读取本地文件内容
2. 写入内容到本地文件
3. 获取系统信息
4. 执行其他需要外部资源的操作
MCP指令格式:
<mcp>
[
{
""action"": ""操作名称"",
""parameters"": {
""参数1"": ""值1"",
""参数2"": ""值2""
}
}
]
</mcp>
支持的操作及参数:
- readfile: {""path"": ""文件路径""}
- writefile: {""path"": ""文件路径"", ""content"": ""文件内容""}
- getsysteminfo: 无参数
";
}
// 发送提示到Ollama并获取响应
public async Task<string> SendPromptToModelAsync(string prompt)
{
try
{
var requestBody = new OllamaRequest
{
Model = _modelName,
Prompt = prompt,
Stream = false,
Options = new Dictionary<string, object>
{
{ "temperature", 0.7 }
}
};
var jsonRequest = JsonSerializer.Serialize(requestBody);
var content = new StringContent(jsonRequest, Encoding.UTF8, "application/json");
var response = await _httpClient.PostAsync("/api/generate", content);
response.EnsureSuccessStatusCode();
var jsonResponse = await response.Content.ReadAsStringAsync();
var ollamaResponse = JsonSerializer.Deserialize<OllamaResponse>(jsonResponse);
return ollamaResponse?.Response ?? string.Empty;
}
catch (Exception ex)
{
return $"与模型通信出错: {ex.Message}";
}
}
// 解析模型响应中的MCP指令
public bool TryExtractMcpCommands(string modelResponse, out List<McpCommand> commands)
{
commands = new List<McpCommand>();
const string mcpStartTag = "<mcp>";
const string mcpEndTag = "</mcp>";
int startIndex = modelResponse.IndexOf(mcpStartTag, StringComparison.OrdinalIgnoreCase);
if (startIndex == -1)
return false;
int endIndex = modelResponse.IndexOf(mcpEndTag, startIndex + mcpStartTag.Length, StringComparison.OrdinalIgnoreCase);
if (endIndex == -1)
return false;
// 提取并清理MCP内容
string mcpContent = modelResponse.Substring(
startIndex + mcpStartTag.Length,
endIndex - (startIndex + mcpStartTag.Length)
).Trim();
try
{
// 尝试解析MCP指令
commands = JsonSerializer.Deserialize<List<McpCommand>>(mcpContent);
return commands != null && commands.Count > 0;
}
catch (Exception ex)
{
Console.WriteLine($"解析MCP指令失败: {ex.Message}");
return false;
}
}
// 执行MCP指令并返回结果
public async Task<string> ExecuteMcpCommands(List<McpCommand> commands)
{
var executionResults = new List<string>();
foreach (var command in commands)
{
Console.WriteLine($"\n[MCP执行器] 收到模型的MCP指令: {command.Action}");
string result = command.Action.ToLower() switch
{
"readfile" => await ExecuteFileRead(command.Parameters),
"writefile" => await ExecuteFileWrite(command.Parameters),
"getsysteminfo" => await ExecuteSystemInfoRetrieval(),
_ => $"不支持的MCP指令: {command.Action}"
};
executionResults.Add($"指令 '{command.Action}' 执行结果: {result}");
}
return string.Join("\n", executionResults);
}
// 执行文件读取操作
private async Task<string> ExecuteFileRead(Dictionary<string, string> parameters)
{
if (!parameters.TryGetValue("path", out string filePath))
return "错误: 缺少文件路径参数";
try
{
if (!File.Exists(filePath))
return $"错误: 文件不存在 - {filePath}";
string content = await File.ReadAllTextAsync(filePath);
return content.Length > 500 ? $"{content.Substring(0, 500)}..." : content;
}
catch (Exception ex)
{
return $"读取失败: {ex.Message}";
}
}
// 执行文件写入操作
private async Task<string> ExecuteFileWrite(Dictionary<string, string> parameters)
{
if (!parameters.TryGetValue("path", out string filePath))
return "错误: 缺少文件路径参数";
if (!parameters.TryGetValue("content", out string content))
return "错误: 缺少文件内容参数";
try
{
// 确保目录存在
string directory = Path.GetDirectoryName(filePath);
if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
await File.WriteAllTextAsync(filePath, content);
return $"成功写入文件: {filePath}";
}
catch (Exception ex)
{
return $"写入失败: {ex.Message}";
}
}
// 执行系统信息获取操作
private Task<string> ExecuteSystemInfoRetrieval()
{
return Task.FromResult($"操作系统: {Environment.OSVersion}\n" +
$"处理器核心数: {Environment.ProcessorCount}\n" +
$"当前工作目录: {Environment.CurrentDirectory}\n" +
$"系统版本: {Environment.OSVersion.VersionString}");
}
// 处理带MCP调用的完整对话流程
public async Task<string> ProcessConversationTurn(string userQuery)
{
// 1. 构建完整提示,包含MCP使用说明
string prompt = $"用户问: {userQuery}\n" +
"请分析是否需要调用工具来回答这个问题。\n" +
"如果需要,请使用MCP指令格式。如果不需要,可以直接回答。\n" +
_mcpInstructions;
// 2. 发送提示给模型,让模型决定是否调用MCP
Console.WriteLine("\n[系统] 正在向模型发送查询,等待模型响应...");
string modelInitialResponse = await SendPromptToModelAsync(prompt);
Console.WriteLine($"\n[模型原始响应]\n{modelInitialResponse}\n");
// 3. 检查模型是否决定使用MCP
if (TryExtractMcpCommands(modelInitialResponse, out var mcpCommands))
{
Console.WriteLine($"[系统] 检测到模型发起了 {mcpCommands.Count} 个MCP调用");
// 4. 执行MCP指令
string executionResults = await ExecuteMcpCommands(mcpCommands);
Console.WriteLine($"[MCP执行结果]\n{executionResults}\n");
// 5. 将执行结果返回给模型,获取最终回答
string finalPrompt = $"你之前发起的MCP指令已经执行,结果如下:\n{executionResults}\n" +
"请根据这些结果,用自然语言回答用户的原始问题。";
return await SendPromptToModelAsync(finalPrompt);
}
// 如果模型没有使用MCP,直接返回其响应
return modelInitialResponse;
}
// 启动交互式对话
public async Task StartInteractiveConversation()
{
Console.WriteLine($"=== Ollama MCP交互对话 (模型: {_modelName}) ===");
Console.WriteLine("输入你的问题,或输入 'exit' 退出对话");
Console.WriteLine("示例问题:");
Console.WriteLine("- 请读取当前目录下的test.txt文件");
Console.WriteLine("- 帮我创建一个名为note.txt的文件,内容是'Hello MCP'");
Console.WriteLine("- 我的系统信息是什么?");
while (true)
{
Console.Write("\n你: ");
string userInput = Console.ReadLine();
if (string.Equals(userInput, "exit", StringComparison.OrdinalIgnoreCase))
{
Console.WriteLine("对话结束,再见!");
break;
}
string response = await ProcessConversationTurn(userInput);
Console.WriteLine($"\n模型: {response}");
}
}
}
class Program
{
static async Task Main(string[] args)
{
// 初始化客户端,连接到本地Ollama服务
var mcpClient = new EnhancedOllamaMcpClient(
ollamaBaseUrl: "http://localhost:11434",
modelName: "qwen" // 可以替换为其他支持的模型如"llama3"、"gemma"等
);
// 启动交互式对话
await mcpClient.StartInteractiveConversation();
}
}
}
三个MCP分别是stdio sse streamable 方式部署,实现大模型可以调用他们
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using System.IO;
using System.Threading;
namespace MultiModeMcpExample
{
// MCP部署模式枚举
public enum McpMode
{
Stdio, // 标准输入输出模式
Sse, // Server-Sent Events模式
Streamable // 流式传输模式
}
// 扩展的MCP指令模型,包含部署模式信息
public class AdvancedMcpCommand
{
public string Action { get; set; }
public McpMode Mode { get; set; } = McpMode.Stdio; // 默认使用stdio模式
public Dictionary<string, string> Parameters { get; set; } = new Dictionary<string, string>();
public bool Stream { get; set; } = false; // 是否启用流式响应
}
// Ollama API模型定义
public class OllamaRequest
{
public string Model { get; set; }
public string Prompt { get; set; }
public bool Stream { get; set; } = false;
public Dictionary<string, object> Options { get; set; } = new Dictionary<string, object>();
}
public class OllamaResponse
{
public string Model { get; set; }
public string Created_At { get; set; }
public string Response { get; set; }
public bool Done { get; set; }
}
class MultiModeMcpClient
{
private readonly HttpClient _httpClient;
private readonly string _ollamaBaseUrl;
private readonly string _modelName;
private readonly string _mcpInstructions;
public MultiModeMcpClient(string ollamaBaseUrl = "http://localhost:11434", string modelName = "qwen")
{
_ollamaBaseUrl = ollamaBaseUrl;
_modelName = modelName;
_httpClient = new HttpClient();
_httpClient.BaseAddress = new Uri(_ollamaBaseUrl);
_httpClient.Timeout = TimeSpan.FromMinutes(5);
// 详细的MCP多模式使用说明
_mcpInstructions = @"
当需要调用外部工具时,请使用MCP指令。支持三种部署方式:
1. stdio: 通过标准输入输出与本地程序通信
2. sse: 通过Server-Sent Events接收实时推送
3. streamable: 通过流式传输进行持续数据交换
MCP指令格式:
<mcp>
[
{
""action"": ""操作名称"",
""mode"": ""stdio|sse|streamable"",
""stream"": true|false, // 是否需要流式响应
""parameters"": {
""参数1"": ""值1"",
""参数2"": ""值2""
}
}
]
</mcp>
支持的操作示例:
- 运行本地程序: {""action"": ""runprogram"", ""mode"": ""stdio"", ""parameters"": {""path"": ""程序路径"", ""args"": ""参数""}}
- 订阅实时数据: {""action"": ""subscribedata"", ""mode"": ""sse"", ""parameters"": {""url"": ""SSE服务地址""}}
- 传输大文件: {""action"": ""transferfile"", ""mode"": ""streamable"", ""stream"": true, ""parameters"": {""url"": ""流服务地址"", ""path"": ""文件路径""}}
";
}
// 发送提示到Ollama模型
public async Task<string> SendPromptToModelAsync(string prompt)
{
try
{
var requestBody = new OllamaRequest
{
Model = _modelName,
Prompt = prompt,
Stream = false,
Options = new Dictionary<string, object>
{
{ "temperature", 0.7 },
{ "num_ctx", 8192 }
}
};
var jsonRequest = JsonSerializer.Serialize(requestBody);
var content = new StringContent(jsonRequest, Encoding.UTF8, "application/json");
var response = await _httpClient.PostAsync("/api/generate", content);
response.EnsureSuccessStatusCode();
var jsonResponse = await response.Content.ReadAsStringAsync();
var ollamaResponse = JsonSerializer.Deserialize<OllamaResponse>(jsonResponse);
return ollamaResponse?.Response ?? string.Empty;
}
catch (Exception ex)
{
return $"与模型通信出错: {ex.Message}";
}
}
// 解析模型响应中的MCP指令
public bool TryExtractMcpCommands(string modelResponse, out List<AdvancedMcpCommand> commands)
{
commands = new List<AdvancedMcpCommand>();
const string mcpStartTag = "<mcp>";
const string mcpEndTag = "</mcp>";
int startIndex = modelResponse.IndexOf(mcpStartTag, StringComparison.OrdinalIgnoreCase);
if (startIndex == -1)
return false;
int endIndex = modelResponse.IndexOf(mcpEndTag, startIndex + mcpStartTag.Length, StringComparison.OrdinalIgnoreCase);
if (endIndex == -1)
return false;
string mcpContent = modelResponse.Substring(
startIndex + mcpStartTag.Length,
endIndex - (startIndex + mcpStartTag.Length)
).Trim();
try
{
var options = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
};
commands = JsonSerializer.Deserialize<List<AdvancedMcpCommand>>(mcpContent, options);
return commands != null && commands.Count > 0;
}
catch (Exception ex)
{
Console.WriteLine($"解析MCP指令失败: {ex.Message}");
return false;
}
}
// 执行MCP指令(根据不同模式路由)
public async Task<string> ExecuteMcpCommands(List<AdvancedMcpCommand> commands)
{
var results = new List<string>();
foreach (var command in commands)
{
Console.WriteLine($"\n[MCP执行器] 处理指令: {command.Action} (模式: {command.Mode})");
string result = command.Mode switch
{
McpMode.Stdio => await ExecuteStdioCommand(command),
McpMode.Sse => await ExecuteSseCommand(command),
McpMode.Streamable => await ExecuteStreamableCommand(command),
_ => $"不支持的MCP模式: {command.Mode}"
};
results.Add($"[{command.Mode}:{command.Action}] 结果: {result}");
}
return string.Join("\n\n", results);
}
#region STDIO模式处理(标准输入输出)
private async Task<string> ExecuteStdioCommand(AdvancedMcpCommand command)
{
if (command.Action.ToLower() != "runprogram")
return $"stdio模式仅支持runprogram操作,收到: {command.Action}";
if (!command.Parameters.TryGetValue("path", out string programPath))
return "缺少程序路径参数 (path)";
if (!File.Exists(programPath))
return $"程序不存在: {programPath}";
try
{
var processStartInfo = new ProcessStartInfo
{
FileName = programPath,
Arguments = command.Parameters.TryGetValue("args", out string args) ? args : "",
RedirectStandardInput = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true,
StandardOutputEncoding = Encoding.UTF8,
StandardErrorEncoding = Encoding.UTF8
};
using (var process = new Process { StartInfo = processStartInfo })
{
process.Start();
// 如果有输入参数,发送到标准输入
if (command.Parameters.TryGetValue("input", out string input) && !string.IsNullOrEmpty(input))
{
await process.StandardInput.WriteLineAsync(input);
await process.StandardInput.FlushAsync();
process.StandardInput.Close();
}
// 读取输出(异步)
var outputTask = process.StandardOutput.ReadToEndAsync();
var errorTask = process.StandardError.ReadToEndAsync();
// 等待程序执行完成
if (!await Task.Run(() => process.WaitForExit(5000))) // 5秒超时
{
process.Kill();
return "程序执行超时";
}
string output = await outputTask;
string error = await errorTask;
if (process.ExitCode != 0)
return $"程序执行错误 (代码: {process.ExitCode}): {error}";
// 限制输出长度
return output.Length > 1000 ? output.Substring(0, 1000) + "...[输出被截断]" : output;
}
}
catch (Exception ex)
{
return $"stdio命令执行失败: {ex.Message}";
}
}
#endregion
#region SSE模式处理(Server-Sent Events)
private async Task<string> ExecuteSseCommand(AdvancedMcpCommand command)
{
if (command.Action.ToLower() != "subscribedata")
return $"sse模式仅支持subscribedata操作,收到: {command.Action}";
if (!command.Parameters.TryGetValue("url", out string sseUrl))
return "缺少SSE服务地址参数 (url)";
try
{
using (var request = new HttpRequestMessage(HttpMethod.Get, sseUrl))
{
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("text/event-stream"));
request.Headers.CacheControl = new CacheControlHeaderValue { NoCache = true };
using (var response = await _httpClient.SendAsync(
request,
HttpCompletionOption.ResponseHeadersRead,
CancellationToken.None))
{
response.EnsureSuccessStatusCode();
using (var stream = await response.Content.ReadAsStreamAsync())
using (var reader = new StreamReader(stream))
{
var eventData = new List<string>();
var cancellationTokenSource = new CancellationTokenSource();
// 设置超时(10秒)
cancellationTokenSource.CancelAfter(10000);
// 如果不是流式响应,最多接收3条消息
int maxEvents = command.Stream ? int.MaxValue : 3;
int eventCount = 0;
try
{
while (!reader.EndOfStream && !cancellationTokenSource.Token.IsCancellationRequested)
{
string line = await reader.ReadLineAsync(cancellationTokenSource.Token);
if (string.IsNullOrWhiteSpace(line))
continue;
// 解析SSE格式: data: {内容}
if (line.StartsWith("data:", StringComparison.OrdinalIgnoreCase))
{
string data = line.Substring(5).Trim();
eventData.Add(data);
Console.WriteLine($"[SSE接收] {data}");
eventCount++;
if (eventCount >= maxEvents)
break;
}
}
}
catch (OperationCanceledException)
{
eventData.Add("[SSE接收超时]");
}
return eventData.Count > 0
? $"共接收 {eventData.Count} 条SSE事件:\n{string.Join("\n", eventData)}"
: "未接收到SSE事件数据";
}
}
}
}
catch (Exception ex)
{
return $"SSE命令执行失败: {ex.Message}";
}
}
#endregion
#region Streamable模式处理(流式传输)
private async Task<string> ExecuteStreamableCommand(AdvancedMcpCommand command)
{
if (command.Action.ToLower() != "transferfile")
return $"streamable模式仅支持transferfile操作,收到: {command.Action}";
if (!command.Parameters.TryGetValue("url", out string streamUrl))
return "缺少流服务地址参数 (url)";
if (!command.Parameters.TryGetValue("path", out string filePath))
return "缺少文件路径参数 (path)";
try
{
// 判断是上传还是下载(根据参数或URL推断)
bool isUpload = command.Parameters.TryGetValue("direction", out string dir) &&
dir.Equals("upload", StringComparison.OrdinalIgnoreCase);
if (isUpload)
{
// 上传文件(流式)
if (!File.Exists(filePath))
return $"上传文件不存在: {filePath}";
using (var fileStream = File.OpenRead(filePath))
using (var content = new StreamContent(fileStream))
{
content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
content.Headers.ContentLength = fileStream.Length;
var response = await _httpClient.PostAsync(streamUrl, content);
response.EnsureSuccessStatusCode();
return $"文件上传成功: {filePath} (大小: {FormatFileSize(fileStream.Length)})";
}
}
else
{
// 下载文件(流式)
using (var response = await _httpClient.GetAsync(streamUrl, HttpCompletionOption.ResponseHeadersRead))
{
response.EnsureSuccessStatusCode();
// 创建目录
string directory = Path.GetDirectoryName(filePath);
if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
Directory.CreateDirectory(directory);
using (var stream = await response.Content.ReadAsStreamAsync())
using (var fileStream = File.Create(filePath))
{
var buffer = new byte[8192];
int bytesRead;
long totalBytes = 0;
// 显示下载进度
while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length)) > 0)
{
await fileStream.WriteAsync(buffer, 0, bytesRead);
totalBytes += bytesRead;
if (command.Stream) // 如果启用流式显示
Console.WriteLine($"[文件下载] 已接收: {FormatFileSize(totalBytes)}");
}
return $"文件下载成功: {filePath} (大小: {FormatFileSize(totalBytes)})";
}
}
}
}
catch (Exception ex)
{
return $"流式传输命令执行失败: {ex.Message}";
}
}
// 格式化文件大小显示
private string FormatFileSize(long bytes)
{
if (bytes < 1024) return $"{bytes} B";
if (bytes < 1048576) return $"{bytes / 1024.0:F1} KB";
if (bytes < 1073741824) return $"{bytes / 1048576.0:F1} MB";
return $"{bytes / 1073741824.0:F1} GB";
}
#endregion
// 处理带MCP调用的对话轮次
public async Task<string> ProcessConversationTurn(string userQuery)
{
// 1. 构建包含多模式MCP说明的提示
string prompt = $"用户问: {userQuery}\n" +
"请分析是否需要调用外部工具。如果需要,请根据任务类型选择合适的MCP模式:\n" +
"- 本地程序交互: 使用stdio模式\n" +
"- 实时数据订阅: 使用sse模式\n" +
"- 大文件传输: 使用streamable模式\n" +
_mcpInstructions;
// 2. 发送提示给模型
Console.WriteLine("\n[系统] 等待模型响应...");
string modelInitialResponse = await SendPromptToModelAsync(prompt);
Console.WriteLine($"\n[模型原始响应]\n{modelInitialResponse}\n");
// 3. 检查并处理MCP指令
if (TryExtractMcpCommands(modelInitialResponse, out var mcpCommands))
{
Console.WriteLine($"[系统] 检测到 {mcpCommands.Count} 个MCP指令,开始执行...");
// 4. 执行MCP指令
string executionResults = await ExecuteMcpCommands(mcpCommands);
Console.WriteLine($"[MCP执行结果]\n{executionResults}\n");
// 5. 获取最终回答
string finalPrompt = $"你发起的MCP指令已执行,结果如下:\n{executionResults}\n" +
"请根据结果用自然语言回答用户问题。";
return await SendPromptToModelAsync(finalPrompt);
}
return modelInitialResponse;
}
// 启动交互式对话
public async Task StartInteractiveConversation()
{
Console.WriteLine($"=== 多模式MCP客户端 (模型: {_modelName}) ===");
Console.WriteLine("支持stdio、sse和streamable三种MCP部署方式");
Console.WriteLine("输入问题或 'exit' 退出");
Console.WriteLine("示例问题:");
Console.WriteLine("- 用stdio模式运行notepad.exe");
Console.WriteLine("- 用sse模式订阅http://localhost:8080/events的实时数据");
Console.WriteLine("- 用streamable模式下载http://example.com/file.zip到本地downloads文件夹");
while (true)
{
Console.Write("\n你: ");
string userInput = Console.ReadLine();
if (string.Equals(userInput, "exit", StringComparison.OrdinalIgnoreCase))
{
Console.WriteLine("对话结束");
break;
}
string response = await ProcessConversationTurn(userInput);
Console.WriteLine($"\n模型: {response}");
}
}
}
class Program
{
static async Task Main(string[] args)
{
var client = new MultiModeMcpClient(
ollamaBaseUrl: "http://localhost:11434",
modelName: "qwen" // 可替换为支持的模型
);
await client.StartInteractiveConversation();
}
}
}
为什么 C# 开发者必须关注 Agentic AI
Agentic AI 框架,.NET 生态现在已经有成熟方案,让你不用从零开始造轮子
它们各有优势:
Semantic Kernel :微软亲儿子,功能全,适合企业级
AutoGen :社区活跃,支持多模型
LlmTornado :轻量、易上手,适合快速验证想法
关键不是选哪个,而是先上手、先跑通、再优化。
⚙️ 环境准备 —— 三行代码搞定
别被“AI”两个字吓到,其实搭环境比你想象的简单。
在你的 .NET 8+ 项目中,运行:
dotnet add package LlmTornado
dotnet add package LlmTornado.Agents
这两个包能帮你用最少的代码,快速构建复杂的 AI 智能体。
更多示例,可以去 LlmTornado GitHub 看官方文档。
构建你的第一个 AI Agent —— 三分钟上手
让我们从一个最简单的例子开始:创建一个“研究助理”智能体。
using LlmTornado;
using LlmTornado.Agents;
using LlmTornado.Chat;
using LlmTornado.Chat.Models;
// 初始化 OpenAI 客户端
varapi =new TornadoApi(
apiKey: Environment.GetEnvironmentVariable("OPENAI_API_KEY"),
provider: LLmProviders.OpenAi
);// 创建智能体
var agent =new TornadoAgent(
client: api,
model: ChatModel.OpenAi.Gpt41.V41Mini,
name: "ResearchAssistant",
instructions: "你是一位乐于助人的研究助理,回答要详细且结构清晰。",
streaming:true
);// 让它干活
Conversation result =await agent.Run("使用 AI 智能体进行软件开发有什么好处?");
Console.WriteLine(result.Messages.Last.Content);
效果 :
它会记住上下文,下次对话还能接着聊
支持流式输出,用户看到内容“一行行出来”,体验丝滑
你可以随时改指令,让它变“严肃专家”或“活泼小助手”
为智能体添加工具 —— 真正的魔法开始了
最酷的部分来了: 让 AI 能调用你的 C# 方法!
using System.ComponentModel;
using LlmTornado.ChatFunctions;
[Description("获取指定城市的当前天气")]
public static string GetCurrentWeather(
[Description("城市和州,例如 Boston, MA")]stringlocation,
[Description("温度单位:Celsius 或 Fahrenheit")]stringunit = "Celsius")
{// 实际项目中可调用真实天气 API
return$"当前{location}的天气为 22°{unit[0]},多云。";
}
// 创建一个带工具的智能体
varagent =new TornadoAgent(
client: api,
model: ChatModel.OpenAi.Gpt41.V41Mini,
name: "WeatherAssistant",
instructions: "你帮助用户查询天气,请始终使用 GetCurrentWeather 工具。",
tools:newList
);
// 让它干活
Conversation result =awaitagent.Run("波士顿的天气怎么样?");
Console.WriteLine(result.Messages.Last.Content);
核心亮点 :
SDK 会自动把你的方法包装成“工具”,AI 能理解并调用
[Description] 属性告诉 AI 这个工具是干啥的,怎么用
你可以加任意 C# 方法,比如查数据库、发邮件、调第三方服务
这就是“AI + 业务逻辑”的真正融合。
多智能体编排:迈向更高层次
如果你想玩得更大一点——比如让多个智能体协作:
一个负责规划(Plan)
一个负责调研(Research)
一个负责写报告(Write)
那就该上 Orchestration(编排) 了!
下面是一个实战例子: 研究助理系统
public class ResearchWorkflow:OrchestrationRuntimeConfiguration
{private TornadoAgent plannerAgent;
private TornadoAgent researcherAgent;
private TornadoAgent reporterAgent;
public ResearchWorkflow(TornadoApi client)
{plannerAgent =new TornadoAgent(
client, ChatModel.OpenAi.Gpt5.V5Mini,"Planner", "为主题生成研究查询列表。"
);researcherAgent =new TornadoAgent(
client, ChatModel.OpenAi.Gpt5.V5Mini,"Researcher", "搜索网络并简洁总结发现。"
);researcherAgent.ResponseOptions =new ResponseRequest
{ Tools =new {newResponseWebSearchTool } };
reporterAgent =newT ornadoAgent(
client, ChatModel.OpenAi.Gpt5.V5,"Reporter", "将研究结果整合成一份结构化报告。", streaming:true
);}}
✅ 效果 :
智能体之间自动传递数据
每个角色专注自己的任务
最终输出一份完整报告
★
这就是未来软件开发的样子: AI 不只是“回答问题”,而是“完成任务”
⏱️ 实时流式输出(Streaming)—— 让应用更“活”
传统 AI 应用,用户得等 10 秒、20 秒才看到结果,体验很差。
而流式输出,让用户“看着内容一点点生成”,体验感直接拉满。
async Value TaskStreamingHandler(AgentRunnerEvents runEvent)
{if(runEventisAgentRunnerStreamingEvent se &&
se.ModelStreamingEventisModelStreamingOutputTextDeltaEvent delta)
{Console.Write(delta.DeltaText);// 逐字输出,用户体验飞起
}
}
★
适用于:聊天机器人、实时报告、代码生成器 —— 用户不再干等,而是“参与过程”。
⚠️ 实战中可能遇到的挑战
别急着兴奋,现实总是有点骨感:
编排复杂性 :多个智能体协作时,如何处理异常、顺序、数据传递?
数据接入问题 :API 要认证、有速率限制、需要缓存、要考虑安全。
成本管理 :每个工具调用、每次交互都要花钱,得监控用量,别让老板破产。
调试困难 :AI 行为非确定性,日志和防护措施必须跟上。
✅ 实践建议 :
从简单开始 :先实现单一智能体,熟悉工具调用。
结构化输出 :确保返回格式一致,方便前端解析。
加防护栏 :输入校验、输出过滤、防滥用。
监控一切 :记录调用次数、成本、错误日志。
展望未来
随着 .NET AI 工具生态的成熟,我们现在有了更多选择。 无论你用 Semantic Kernel、AutoGen 还是 LlmTornado,关键是理解核心模式:
★
Agent → Tool → Orchestration → Streaming
开始很简单——
1.创建一个基础 Agent
2.加上记忆与工具
3.尝试多智能体协作
4.实现实时流式输出
未来的软件开发,AI 智能体将不再是“辅助”,而是“一等公民”。
现在掌握这些模式,你就能在未来的 AI 驱动开发中立于不败之地。
最后说一句
每个人一开始都很懵。 我也是从“这玩意儿能干啥?”走到“这玩意儿太强了!”的。
构建 → 失败 → 修复 → 学习,这就是我们成长的方式
####【豆包】
Q:C# 使用LlmTornado 调用Ollama例子
A:...
前提条件
1.安装 Ollama:从Ollama 官网下载并安装,确保服务正常运行(默认地址:http://localhost:11434)。
2.拉取模型:通过命令行拉取需要的模型(例如llama3):
ollama pull llama3
3.安装 LlmTornado:通过 NuGet 安装LlmTornado库(用于简化 Ollama API 调用):
Install-Package LlmTornado
4.示例代码
using System;
using System.Threading.Tasks;
using LlmTornado.Clients;
using LlmTornado.Options;
class OllamaExample
{
static async Task Main(string[] args)
{
// 1. 配置Ollama客户端(指定服务地址和模型)
var options = new OllamaClientOptions
{
BaseUrl = "http://localhost:11434", // Ollama默认地址
Model = "llama3" // 已拉取的模型名称(如llama3、qwen等)
};
var ollamaClient = new OllamaClient(options);
try
{
// 2. 普通文本生成(一次性获取结果)
Console.WriteLine("===== 普通生成 =====");
var prompt = "用3句话介绍C#";
var response = await ollamaClient.GenerateAsync(prompt);
Console.WriteLine($"提示:{prompt}");
Console.WriteLine($"结果:{response.Content}\n");
// 3. 流式输出(逐段获取结果,适合实时展示)
Console.WriteLine("===== 流式生成 =====");
var streamPrompt = "用2句话介绍Ollama";
Console.WriteLine($"提示:{streamPrompt}");
Console.WriteLine("结果:");
await foreach (var chunk in ollamaClient.StreamGenerateAsync(streamPrompt))
{
// 流式返回的每个片段(可能是单个字/词/句)
Console.Write(chunk.Content);
}
}
catch (Exception ex)
{
Console.WriteLine($"调用失败:{ex.Message}");
}
}
}
上一篇:C#开源库及辅助程序
下一篇:C#开发技术收集目录结构