初始提交

This commit is contained in:
2026-08-26 18:06:02 +08:00
commit 5544788917
53 changed files with 4101 additions and 0 deletions
+47
View File
@@ -0,0 +1,47 @@
using MAF1.Agents.FileCity;
using Microsoft.Agents.AI.Workflows;
using Microsoft.Extensions.AI;
namespace MAF1.Workflows;
internal sealed class CityGateExecutor() : ChatProtocolExecutor(
"CityGate",
new ChatProtocolExecutorOptions { AutoSendTurnToken = false })
{
protected override ProtocolBuilder ConfigureProtocol(ProtocolBuilder builder)
{
return base.ConfigureProtocol(builder)
.SendsMessage<ChatMessage>()
.SendsMessage<TurnToken>()
.YieldsOutput<string>();
}
protected override async ValueTask TakeTurnAsync(
List<ChatMessage> messages,
IWorkflowContext context,
bool? emitEvents,
CancellationToken cancellationToken)
{
string text = string.Join(
Environment.NewLine,
messages
.Where(message => !string.IsNullOrWhiteSpace(message.Text))
.Select(message => message.Text));
CityExtraction extraction = FileCityAgent.Parse(text);
if (!extraction.HasValidCities)
{
string reason = string.IsNullOrWhiteSpace(extraction.Reason)
? "文件里没有可查询的城市。"
: extraction.Reason;
await context.YieldOutputAsync(
$"没有有效城市,工作流结束,不执行 WeatherAgent。{reason}",
cancellationToken);
return;
}
string prompt = $"请查询这些城市的天气:{string.Join("", extraction.Cities)}";
await context.SendMessageAsync(new ChatMessage(ChatRole.User, prompt), cancellationToken: cancellationToken);
await context.SendMessageAsync(new TurnToken(emitEvents), cancellationToken: cancellationToken);
}
}
+33
View File
@@ -0,0 +1,33 @@
using MAF1.Agents.FileCity;
using Microsoft.Agents.AI.Workflows;
using Microsoft.Extensions.AI;
namespace MAF1.Workflows;
/// <summary>
/// 边条件模式:这里只解析,不做 if。走哪条边由 AddEdge 的 condition 决定。
/// </summary>
internal sealed class CityParseExecutor() : ChatProtocolExecutor(
"CityParse",
new ChatProtocolExecutorOptions { AutoSendTurnToken = false })
{
protected override ProtocolBuilder ConfigureProtocol(ProtocolBuilder builder)
{
return base.ConfigureProtocol(builder).SendsMessage<CityExtraction>();
}
protected override async ValueTask TakeTurnAsync(
List<ChatMessage> messages,
IWorkflowContext context,
bool? emitEvents,
CancellationToken cancellationToken)
{
string text = string.Join(
Environment.NewLine,
messages
.Where(message => !string.IsNullOrWhiteSpace(message.Text))
.Select(message => message.Text));
await context.SendMessageAsync(FileCityAgent.Parse(text), cancellationToken: cancellationToken);
}
}
+51
View File
@@ -0,0 +1,51 @@
using Microsoft.Agents.AI;
using Microsoft.Agents.AI.Workflows;
using MAF1.Agents.FileCity;
namespace MAF1.Workflows;
public static class CityWeatherWorkflow
{
public static Workflow BuildNodeCondition(AIAgent fileCityAgent, AIAgent weatherAgent)
{
(ExecutorBinding fileCity, ExecutorBinding weather) = BindAgents(fileCityAgent, weatherAgent);
CityGateExecutor gate = new();
return new WorkflowBuilder(fileCity)
.AddEdge(fileCity, gate)
.AddEdge(gate, weather)
.WithOutputFrom(gate, weather)
.WithName("CityWeather-Node")
.Build();
}
public static Workflow BuildEdgeCondition(AIAgent fileCityAgent, AIAgent weatherAgent)
{
(ExecutorBinding fileCity, ExecutorBinding weather) = BindAgents(fileCityAgent, weatherAgent);
CityParseExecutor parse = new();
ToWeatherPromptExecutor toWeather = new();
SkipWeatherExecutor skip = new();
return new WorkflowBuilder(fileCity)
.AddEdge(fileCity, parse)
.AddEdge<CityExtraction>(parse, toWeather, extraction => extraction is { HasValidCities: true })
.AddEdge<CityExtraction>(parse, skip, extraction => extraction is not { HasValidCities: true })
.AddEdge(toWeather, weather)
.WithOutputFrom(skip, weather)
.WithName("CityWeather-Edge")
.Build();
}
private static (ExecutorBinding FileCity, ExecutorBinding Weather) BindAgents(AIAgent fileCityAgent, AIAgent weatherAgent)
{
AIAgentHostOptions agentOptions = new()
{
ForwardIncomingMessages = false,
ReassignOtherAgentsAsUsers = true,
EmitAgentUpdateEvents = true,
EmitAgentResponseEvents = true,
};
return (fileCityAgent.BindAsExecutor(agentOptions), weatherAgent.BindAsExecutor(agentOptions));
}
}
+28
View File
@@ -0,0 +1,28 @@
using MAF1.Agents.FileCity;
using Microsoft.Agents.AI.Workflows;
namespace MAF1.Workflows;
/// <summary>
/// 只有「没有城市」那条边会进到这里,所以这里不再判断。
/// </summary>
internal sealed class SkipWeatherExecutor() : Executor<CityExtraction>("SkipWeather")
{
protected override ProtocolBuilder ConfigureProtocol(ProtocolBuilder builder)
{
return base.ConfigureProtocol(builder).YieldsOutput<string>();
}
public override async ValueTask HandleAsync(
CityExtraction extraction,
IWorkflowContext context,
CancellationToken cancellationToken = default)
{
string reason = string.IsNullOrWhiteSpace(extraction.Reason)
? "文件里没有可查询的城市。"
: extraction.Reason;
await context.YieldOutputAsync(
$"没有有效城市,工作流结束,不执行 WeatherAgent。{reason}",
cancellationToken);
}
}
+28
View File
@@ -0,0 +1,28 @@
using MAF1.Agents.FileCity;
using Microsoft.Agents.AI.Workflows;
using Microsoft.Extensions.AI;
namespace MAF1.Workflows;
/// <summary>
/// 只有「有城市」那条边会进到这里,所以这里不再判断。
/// </summary>
internal sealed class ToWeatherPromptExecutor() : Executor<CityExtraction>("ToWeatherPrompt")
{
protected override ProtocolBuilder ConfigureProtocol(ProtocolBuilder builder)
{
return base.ConfigureProtocol(builder)
.SendsMessage<ChatMessage>()
.SendsMessage<TurnToken>();
}
public override async ValueTask HandleAsync(
CityExtraction extraction,
IWorkflowContext context,
CancellationToken cancellationToken = default)
{
string prompt = $"请查询这些城市的天气:{string.Join("", extraction.Cities)}";
await context.SendMessageAsync(new ChatMessage(ChatRole.User, prompt), cancellationToken: cancellationToken);
await context.SendMessageAsync(new TurnToken(emitEvents: true), cancellationToken: cancellationToken);
}
}