Microsoft Agent Framework 接入DeepSeek的優雅姿勢
一、前言
? Microsoft Agent Framework 框架發布也有一陣子了,在觀望(摸魚)過后,也是果斷(在老板的威脅下)將幾個AI應用微服務完成了從Semantic Kernel 框架到Microsoft Agent Framework 框架中的遷移工作。
所以這篇文章,我想記錄一下在開發過程中的我總結的一下工程化用法。
二、Agent Framework是什么
簡單來講,Microsoft Agent Framework 是微軟在 Semantic Kernel 之后推出的一個 新一代智能體(Agent)開發框架。它其實就是 SK 的“進化版”——思路差不多,但更直接、更好用,也更符合現在大家在做 多智能體(Multi-Agent)系統 的趨勢。
如果你用過 Semantic Kernel,大概還記得那種層層嵌套的概念:Kernel、Skill、Function、Context…… 用起來就像在拼一堆樂高磚塊。
三、對比Semantic Kernel
-
結構更加直觀和優雅
以前 SK 的 “Function” / “Skill” 概念太抽象。
在 Agent Framework 里,你可以直接定義一個Agent類,然后給它掛上工具(Tool)、記憶(Memory)。 -
Prompt 與邏輯分離更自然
在 SK 里常常要寫一堆 Template Function,還要用 YAML 或 JSON 去配置。
在 Agent Framework 中,你直接在創建 Agent 時傳入instructions(提示詞),框架會自動封裝上下文調用,大幅減少模板樣板代碼。 -
內置的多 Agent 協作更順手
它原生支持多個 Agent 之間的消息傳遞,你可以像寫微服務一樣寫“智能體服務”。
四、使用姿勢
在使用SK框架的時候我就很討厭構建一個“kernel”,什么都找他實現,那種方法一開始很方便和簡潔,但是復用和調試就是災難。所以我的做法是:每個任務一個 Agent,職責單一、結構清晰、方便測試。然后再把這些 Agent 都注冊進 IOC 容器里,像注入普通服務一樣調用。
4.1 Agent任務分配
以一個從文檔上解析公司名做示例:
namespace STD.AI.Implementations
{
public class CompanyExtractionAgent : BaseAgentFunction, ICompanyExtractionAgent
{
private readonly AIAgent _agent;
public CompanyExtractionAgent(IOptions<LLMConfiguration> config)
{
var openAIClient = new OpenAIClient(new ApiKeyCredential(config.Value.ApiKey), new OpenAIClientOptions
{
Endpoint = new Uri(config.Value.Endpoint),
});
var responseClient = openAIClient.GetChatClient(config.Value.Model);
_agent = responseClient.CreateAIAgent(instructions:
"你是一個信息抽取助手,請從文本中提取所有公司名稱,必須返回合法 JSON 數組,如 [\"公司A\", \"公司B\"]。不要輸出解釋或額外內容。");
}
public async Task<List<string>> ExtractCompanyNamesAsync(string filePath)
{
if (!File.Exists(filePath))
throw new FileNotFoundException("找不到指定文件", filePath);
string content = DocumentReader.ExtractText(filePath);
if (string.IsNullOrWhiteSpace(content))
return new List<string>();
var thread = _agent.GetNewThread();
var chunks = SplitDocumentIntoChunks(content);
var allCompanies = new HashSet<string>();
foreach (var chunk in chunks)
{
string prompt = @$"
請從以下文本片段中中提取所有公司名稱,并嚴格以 JSON 數組形式輸出:
示例輸出:
[""阿里巴巴集團"", ""騰訊科技有限公司"", ""百度公司""]
以下是正文:{chunk}";
try
{
var response = await _agent.RunAsync(prompt, thread);
string raw = response.Text ?? string.Empty;
string cleaned = CleanJsonResponseList(raw);
var companies = JsonSerializer.Deserialize<List<string>>(cleaned) ?? new List<string>();
LogProvider.Info(raw);
foreach (var c in companies) allCompanies.Add(c);
}
catch (JsonException ex)
{
LogProvider.Warning($"解析失敗: {ex.Message}");
}
}
return allCompanies.ToList();
}
}
}
4.2 給Agent 裝上手和眼
4.2.1 添加MCP服務
namespace STD.AI
{
public static class MCPConfigExtension
{
public static string _pluginPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
public static string _userDataDir = Path.Combine(_pluginPath, "browser-data");
public static async Task AddMcpClientAsync(this IServiceCollection services, bool Headless)
{
try
{
var config = new List<string>
{
"@playwright/mcp",
"--caps", "pdf",
"--output-dir",Path.GetTempPath(),
"--user-data-dir", _userDataDir,
};
if (Headless)
{
config.Add("--headless");
}
var transport = new StdioClientTransport(new StdioClientTransportOptions
{
Name = "PlaywrightMCP",
Command = "npx",
Arguments = config
});
var mcpClient = await McpClient.CreateAsync(transport);
services.AddSingleton(mcpClient);
}
catch (Exception ex)
{
LogProvider.Error($"AddMcpClientfail:{ex.ToString()}");
}
}
}
}
4.2.2 注冊MCP工具
namespace STD.AI.Implementations
{
/// <summary>
/// 公司信息查詢 Agent,使用 MCP 瀏覽器工具自動查詢公司信息
/// </summary>
public class CompanyInfoQueryAgent : BaseAgentFunction, ICompanyInfoQueryAgent
{
private readonly AIAgent _agent;
private readonly McpClient _mcpClient;
public CompanyInfoQueryAgent(IOptions<LLMConfiguration> config, McpClient mcpClient)
{
_mcpClient = mcpClient ?? throw new ArgumentNullException(nameof(mcpClient));
var openAIClient = new OpenAIClient(new ApiKeyCredential(config.Value.ApiKey), new OpenAIClientOptions
{
Endpoint = new Uri(config.Value.Endpoint)
});
var responseClient = openAIClient.GetChatClient(config.Value.Model);
// 獲取 MCP 工具并注冊到 Agent
var tools = _mcpClient.ListToolsAsync().GetAwaiter().GetResult();
_agent = responseClient.CreateAIAgent(
instructions: @"
你是一個專業的商業信息采集 AI 助手,擁有網絡訪問能力 (MCP 瀏覽器工具)。
你的任務是:自動訪問多個公開來源(如企業官網、天眼查、企查查、維基百科、新聞報道等),
提取公司相關信息,并輸出為嚴格 JSON 格式,映射到以下 CompanyInfo 結構。
請嚴格返回合法 JSON(不包含解釋性文字或 Markdown)。
### CompanyInfo 字段定義與說明:
{
""companyName"": ""公司中文名稱(必須字段)"",
""englishName"": ""英文名稱,如有"",
""officialWebsite"": ""公司官網 URL,如未知可留空"",
""contactPhone"": ""公司主要聯系電話"",
""email"": ""公司官方郵箱"",
""address"": ""公司總部地址"",
""businessScope"": ""經營范圍,描述主營業務及服務"",
""registrationNumber"": ""工商注冊號(如可獲得)"",
""unifiedSocialCreditCode"": ""統一社會信用代碼(如可獲得)"",
""companyType"": ""公司類型(如有限責任公司、股份有限公司等)"",
""legalRepresentative"": ""法定代表人姓名"",
""registeredCapital"": ""注冊資本(含幣種)"",
""establishedDate"": ""公司成立日期(ISO格式,如 2020-05-12)"",
""industry"": ""所屬行業(如互聯網、制造業等)"",
""mainBusiness"": ""主營產品或服務"",
""employeeCount"": ""員工數量(大約范圍,如 '100-500人')"",
""stockCode"": ""股票代碼(如上市公司)"",
""stockExchange"": ""交易所(如上交所、納斯達克)"",
""lastUpdated"": ""數據最后處理時間(ISO 8601 格式)""
}
返回的 JSON 必須能直接被 C# System.Text.Json 反序列化為 CompanyInfo 對象。
",
name: "mcpAgent",
description: "調用 MCP 工具實現公司數據查詢",
tools: tools.Cast<AITool>().ToList()
);
}
public async Task<CompanyInfo?> QueryCompanyInfoAsync(string companyName)
{
if (string.IsNullOrWhiteSpace(companyName))
throw new ArgumentException("公司名稱不能為空", nameof(companyName));
var thread = _agent.GetNewThread();
string userPrompt = $@"
請使用 MCP 瀏覽器工具搜索并訪問多個網頁,
綜合提取公司 “{companyName}” 的完整工商及公開資料。
請整合不同來源的數據,確保字段盡量完整,并返回合法 JSON。
";
var response = await _agent.RunAsync(userPrompt, thread);
string raw = response.Text ?? string.Empty;
raw = CleanJsonResponse(raw);
return JsonSerializer.Deserialize<CompanyInfo>(raw);
}
}
}
4.3 注冊函數工具
4.3.1 編寫函數工具
using Microsoft.Extensions.AI;
namespace STD.AI.Tools
{
public class CompanyInfoTool : AITool
{
private readonly HttpClient _httpClient;
public CompanyInfoTool(HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task<string> QueryCompanyInfoAsync(string companyName)
{
var response = await _httpClient.GetAsync($"https://api.example.com/company/{companyName}");
return await response.Content.ReadAsStringAsync();
}
}
}
4.3.2 注冊函數工具
namespace STD.AI.Implementations
{
public class CompanyInfoAgent : BaseAgentFunction, ICompanyInfoAgent
{
private readonly AIAgent _agent;
private readonly CompanyInfoTool _companyInfoTool;
public CompanyInfoAgent(IOptions<LLMConfiguration> config, CompanyInfoTool companyInfoTool)
{
_companyInfoTool = companyInfoTool ?? throw new ArgumentNullException(nameof(companyInfoTool));
var openAIClient = new OpenAIClient(new ApiKeyCredential(config.Value.ApiKey), new OpenAIClientOptions
{
Endpoint = new Uri(config.Value.Endpoint)
});
var responseClient = openAIClient.GetChatClient(config.Value.Model);
// 創建 Agent,并注冊工具
_agent = responseClient.CreateAIAgent(
instructions: "你是一個公司信息查詢助手,請使用工具查詢公司相關信息。",
name: "companyInfoAgent",
description: "使用公司信息查詢工具來獲取公司資料",
tools: new List<AITool> { _companyInfoTool }
);
}
public async Task<string> GetCompanyInfoAsync(string companyName)
{
var thread = _agent.GetNewThread();
// AI 通過工具查詢公司信息
var response = await _agent.RunAsync($"請查詢公司 {companyName} 的詳細信息", thread);
return response.Text;
}
}
}
4.4 記憶功能
namespace STD.AI.Implementations
{
public class CompanyInfoAgentWithMemory : BaseAgentFunction
{
private readonly AIAgent _agent;
private readonly CompanyInfoTool _companyInfoTool;
public CompanyInfoAgentWithMemory(IOptions<LLMConfiguration> config, CompanyInfoTool companyInfoTool)
{
_companyInfoTool = companyInfoTool ?? throw new ArgumentNullException(nameof(companyInfoTool));
var openAIClient = new OpenAIClient(new ApiKeyCredential(config.Value.ApiKey), new OpenAIClientOptions
{
Endpoint = new Uri(config.Value.Endpoint)
});
var responseClient = openAIClient.GetChatClient(config.Value.Model);
// 創建代理
_agent = responseClient.CreateAIAgent(
instructions: "你是一個公司信息查詢助手,請使用工具查詢公司相關信息。",
name: "companyInfoAgentWithMemory",
description: "使用公司信息查詢工具,并且記住用戶的歷史對話。",
tools: new List<AITool> { _companyInfoTool }
);
}
// 查詢公司信息并使用記憶存儲對話內容
public async Task<string> GetCompanyInfoAsync(string companyName)
{
var thread = _agent.GetNewThread();
// AI 通過工具查詢公司信息
var response = await _agent.RunAsync($"請查詢公司 {companyName} 的詳細信息", thread);
// 序列化并保存當前對話狀態到持久存儲(例如文件、數據庫等)
var serializedThread = thread.Serialize(JsonSerializerOptions.Web).GetRawText();
await SaveThreadStateAsync(serializedThread);
return response.Text;
}
// 恢復之前的對話上下文并繼續對話
public async Task<string> ResumePreviousConversationAsync(string companyName)
{
var thread = _agent.GetNewThread();
// 從存儲中加載之前的對話狀態
var previousThread = await LoadThreadStateAsync();
// 反序列化并恢復對話
var reloadedThread = _agent.DeserializeThread(JsonSerializer.Deserialize<JsonElement>(previousThread));
// 使用恢復的上下文繼續對話
var response = await _agent.RunAsync($"繼續查詢公司 {companyName} 的信息", reloadedThread);
return response.Text;
}
// 模擬保存線程狀態到持久存儲
private async Task SaveThreadStateAsync(string serializedThread)
{
// 示例:保存到文件(可以替換為數據庫或其他存儲介質)
var filePath = Path.Combine(Path.GetTempPath(), "agent_thread.json");
await File.WriteAllTextAsync(filePath, serializedThread);
}
// 模擬加載存儲的線程狀態
private async Task<string> LoadThreadStateAsync()
{
// 示例:從文件加載(可以替換為數據庫或其他存儲介質)
var filePath = Path.Combine(Path.GetTempPath(), "agent_thread.json");
return await File.ReadAllTextAsync(filePath);
}
}
}
內存上下文實現:
五、一些小坑
5.1 API地址配置
namespace STD.Model
{
public class LLMConfiguration
{
public string Model { get; set; }
public string Endpoint { get; set; }
public string ApiKey { get; set; }
public bool IsValid()
{
return !string.IsNullOrWhiteSpace(Model) &&
!string.IsNullOrWhiteSpace(Endpoint) &&
Uri.IsWellFormedUriString(Endpoint, UriKind.Absolute) &&
!string.IsNullOrWhiteSpace(ApiKey);
}
}
}
填寫Endpoint(OpenAI規范):
SK框架:https://api.deepseek.com/v1
AgentFramework框架:https://api.deepseek.com
5.2 結構化輸出
namespace STD.AI.Implementations
{
public class CompanyInfoQueryAgent : BaseAgentFunction, ICompanyInfoQueryAgent
{
private readonly AIAgent _agent;
private readonly McpClient _mcpClient;
public CompanyInfoQueryAgent(IOptions<LLMConfiguration> config, McpClient mcpClient)
{
_mcpClient = mcpClient ?? throw new ArgumentNullException(nameof(mcpClient));
var openAIClient = new OpenAIClient(
new ApiKeyCredential(config.Value.ApiKey),
new OpenAIClientOptions { Endpoint = new Uri(config.Value.Endpoint ?? "https://api.deepseek.com") }
);
// 獲取 chat client(DeepSeek/Azure/OpenAI 的封裝)
var chatClient = openAIClient.GetChatClient(config.Value.Model);
// 從你的 MCP client 獲取工具列表(假設返回 IList<AITool> 或 可轉換的集合)
var tools = _mcpClient.ListToolsAsync().GetAwaiter().GetResult()
.Cast<AITool>()
.ToList();
JsonElement companySchema = AIJsonUtilities.CreateJsonSchema(typeof(CompanyInfo));
//定義規范輸出
ChatOptions chatOptions = new()
{
ResponseFormat = ChatResponseFormat.ForJsonSchema(
schema: companySchema,
schemaName: nameof(CompanyInfo),
schemaDescription: "Structured CompanyInfo output"),
};
chatOptions.Tools = tools;
var agentOptions = new ChatClientAgentOptions
{
Name = "CompanyInfoAgent",
Instructions = @"你是商業信息采集助手。請使用已注冊的瀏覽器/網頁工具搜索并整合公司信息,嚴格返回符合 CompanyInfo JSON schema 的對象。",
Description = "使用 MCP 工具檢索公司公開信息,返回結構化 CompanyInfo。",
ChatOptions = chatOptions
};
// 創建 Agent(使用 chatClient 的 CreateAIAgent 重載)
_agent = chatClient.CreateAIAgent(agentOptions);
}
public async Task<CompanyInfo?> QueryCompanyInfoAsync(string companyName)
{
if (string.IsNullOrWhiteSpace(companyName))
throw new ArgumentException("公司名稱不能為空", nameof(companyName));
var thread = _agent.GetNewThread();
string prompt = $@"
請使用已注冊的網頁/瀏覽器工具(MCP 工具集合),訪問多個來源(官網、企查查/天眼查、維基/百科、相關新聞等),
綜合提取公司 ""{companyName}"" 的信息并嚴格返回符合 CompanyInfo 模型的 JSON 對象。";
var response = await _agent.RunAsync(prompt, thread);
// 框架內置反序列化(結構化輸出),使用 System.Text.Json Web 選項
var company = response.Deserialize<CompanyInfo>(JsonSerializerOptions.Web);
return company;
}
}
}
RunAsync報錯,經排查DeepseekAPI不支持,但官方文檔是支持JsonFormat type:jsonobject 的
如有大佬,望告知解惑

浙公網安備 33010602011771號