using System.Text.Json; using MAF1.Core.Agents; using MAF1.Tools; using MAF1.Utils; using Microsoft.Agents.AI; using Microsoft.Extensions.AI; namespace MAF1.Agents.FileCity; /// /// 「读文件抽城市」Agent。学习路径:Create 注册工具和系统提示 → RunAsync 调模型 → Parse 把模型文本收成结构化结果。 /// CLI、网页系统节点、file-city 插件三处都调用同一套逻辑,避免各写一份 prompt。 /// public static class FileCityAgent { /// 创建带 ReadTextFile 工具的聊天 Agent。模型必须先读文件,不能凭文件名猜内容。 public static AIAgent Create(AgentFactory factory) { return factory.CreateAgent( name: "FileCityAgent", instructions: """ 你负责读取用户指定的本地文件,并从中提取有效城市名。 必须先调用 ReadTextFile 读取文件,不要猜测文件内容。 有效城市名:现实世界中真实存在的城市(如 成都、北京、Amsterdam)。 忽略空行、注释、人名、水果、随意单词等不是城市的内容。 只输出一个 JSON 对象,不要 Markdown,不要其它说明: {"hasValidCities": true, "cities": ["成都"], "reason": "说明"} 若没有有效城市:{"hasValidCities": false, "cities": [], "reason": "原因"} cities 去重,保持文件中的出现顺序。 """, tools: [AIFunctionFactory.Create(FileTools.ReadTextFile)]); } /// 跑一轮:读 filePath 输入,把解析后的 cities / hasValidCities 放进 Outputs。 public static async Task RunAsync( AIAgent agent, IReadOnlyDictionary inputs, CancellationToken cancellationToken = default) { string filePath = AgentInputs.ReadString(inputs, "filePath") ?? "Data/cities.txt"; AgentResponse response = await agent.RunAsync( $"请读取这个文件并提取有效城市名:{filePath}", cancellationToken: cancellationToken); CityExtraction extraction = Parse(response.Text); return new AgentStepResult { Message = response.Text, Inputs = new Dictionary { ["filePath"] = filePath }, Outputs = new Dictionary { ["hasValidCities"] = extraction.HasValidCities, ["cities"] = extraction.Cities, ["reason"] = extraction.Reason, ["raw"] = response.Text, }, }; } /// 从模型原文里抠 JSON。模型偶尔会包 Markdown 代码块,所以先走 JsonText.UnwrapObject。 public static CityExtraction Parse(string text) { try { CityExtraction? result = JsonSerializer.Deserialize( JsonText.UnwrapObject(text), new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); if (result is null) { return Invalid("无法解析文件 Agent 的输出。"); } result.Cities = result.Cities .Where(city => !string.IsNullOrWhiteSpace(city)) .Select(city => city.Trim()) .Distinct(StringComparer.OrdinalIgnoreCase) .ToList(); result.HasValidCities = result.HasValidCities && result.Cities.Count > 0; return result; } catch (JsonException ex) { return Invalid($"文件 Agent 没有返回合法 JSON:{ex.Message}"); } } private static CityExtraction Invalid(string reason) => new() { HasValidCities = false, Reason = reason }; }