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 };
}