mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
907654a489
* Initial working version with tests.
* Updates to validate class data once instead of for each handler method. Also updated Diagnostics Ids to format of MAFGENWF{NUM}
* Formatting and trying to fix generation project pack.
* Another atempt at getting the genrators project to build.
* More attempts to fix generator build and pack.
* Fixing file encodings.
* Initail round of cleanup.
* Trying to fix packing.
* Still trying to fix pipeline pack.
* Remove obsolescence markers, sample updates, and docs from generator branch.
This commit separates the generator core functionality from the
deprecation of ReflectingExecutor. The removed changes will be
re-added in a dependent branch (wf-obsolete-reflector).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Mark ReflectingExecutor and IMessageHandler as obsolete.
This commit deprecates the reflection-based handler discovery approach
in favor of the new [MessageHandler] attribute with source generation.
Changes:
- Add [Obsolete] to ReflectingExecutor<T>, IMessageHandler<T>, IMessageHandler<T,R>
- Add #pragma to suppress warnings in internal reflection code
- Update Concurrent sample to use new [MessageHandler] pattern
- Add Directory.Build.props for samples to include generator
- Add documentation files explaining the migration
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Obsoleteing Reflector-based workflow code generation in favor of Source Generators and updating some samples to use new pattern.
This commit deprecates the reflection-based handler discovery approach
in favor of the new [MessageHandler] attribute with source generation.
Changes:
- Add [Obsolete] to ReflectingExecutor<T>, IMessageHandler<T>, IMessageHandler<T,R>
- Add #pragma to suppress warnings in internal reflection code
- Update Concurrent sample to use new [MessageHandler] pattern
- Add Directory.Build.props for samples to include generator
- Add documentation files explaining the migration
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Cleaning up temporary design and progress files.
---------
Co-authored-by: alliscode <bentho@microsoft.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Chris <66376200+crickman@users.noreply.github.com>
294 lines
11 KiB
Markdown
294 lines
11 KiB
Markdown
# Roslyn Source Generator for Workflow Executor Routes
|
|
|
|
## Overview
|
|
|
|
Replace the reflection-based `ReflectingExecutor<T>` pattern with a compile-time source generator that discovers `[MessageHandler]` attributed methods and generates `ConfigureRoutes`, `ConfigureSentTypes`, and `ConfigureYieldTypes` implementations.
|
|
|
|
## Design Decisions (Confirmed)
|
|
|
|
- **Attribute syntax**: Inline properties on `[MessageHandler(Yield=[...], Send=[...])]`
|
|
- **Class-level attributes**: Generate `ConfigureSentTypes()`/`ConfigureYieldTypes()` from `[SendsMessage]`/`[YieldsMessage]`
|
|
- **Migration**: Clean break - requires direct `Executor` inheritance (not `ReflectingExecutor<T>`)
|
|
- **Handler accessibility**: Any (private, protected, internal, public)
|
|
|
|
---
|
|
|
|
## Implementation Steps
|
|
|
|
### Phase 1: Create Source Generator Project
|
|
|
|
**1.1 Create project structure:**
|
|
```
|
|
dotnet/src/Microsoft.Agents.AI.Workflows.Generators/
|
|
├── Microsoft.Agents.AI.Workflows.Generators.csproj
|
|
├── ExecutorRouteGenerator.cs # Main incremental generator
|
|
├── Models/
|
|
│ ├── ExecutorInfo.cs # Data model for executor analysis
|
|
│ └── HandlerInfo.cs # Data model for handler methods
|
|
├── Analysis/
|
|
│ ├── SyntaxDetector.cs # Syntax-based candidate detection
|
|
│ └── SemanticAnalyzer.cs # Semantic model analysis
|
|
├── Generation/
|
|
│ └── SourceBuilder.cs # Code generation logic
|
|
└── Diagnostics/
|
|
└── DiagnosticDescriptors.cs # Analyzer diagnostics
|
|
```
|
|
|
|
**1.2 Project file configuration:**
|
|
- Target `netstandard2.0`
|
|
- Reference `Microsoft.CodeAnalysis.CSharp` 4.8.0+
|
|
- Set `IsRoslynComponent=true`, `EnforceExtendedAnalyzerRules=true`
|
|
- Package as analyzer in `analyzers/dotnet/cs`
|
|
|
|
### Phase 2: Define Attributes
|
|
|
|
**2.1 Create `MessageHandlerAttribute`:**
|
|
```
|
|
dotnet/src/Microsoft.Agents.AI.Workflows/Attributes/MessageHandlerAttribute.cs
|
|
```
|
|
```csharp
|
|
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
|
|
public sealed class MessageHandlerAttribute : Attribute
|
|
{
|
|
public Type[]? Yield { get; set; } // Types yielded as workflow outputs
|
|
public Type[]? Send { get; set; } // Types sent to other executors
|
|
}
|
|
```
|
|
|
|
**2.2 Create `SendsMessageAttribute`:**
|
|
```
|
|
dotnet/src/Microsoft.Agents.AI.Workflows/Attributes/SendsMessageAttribute.cs
|
|
```
|
|
```csharp
|
|
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
|
|
public sealed class SendsMessageAttribute : Attribute
|
|
{
|
|
public Type Type { get; }
|
|
public SendsMessageAttribute(Type type) => this.Type = type;
|
|
}
|
|
```
|
|
|
|
**2.3 Create `YieldsMessageAttribute`:**
|
|
```
|
|
dotnet/src/Microsoft.Agents.AI.Workflows/Attributes/YieldsMessageAttribute.cs
|
|
```
|
|
```csharp
|
|
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
|
|
public sealed class YieldsMessageAttribute : Attribute
|
|
{
|
|
public Type Type { get; }
|
|
public YieldsMessageAttribute(Type type) => this.Type = type;
|
|
}
|
|
```
|
|
|
|
### Phase 3: Implement Source Generator
|
|
|
|
**3.1 Detection criteria (syntax level):**
|
|
- Class has `partial` modifier
|
|
- Class has at least one method with `[MessageHandler]` attribute
|
|
|
|
**3.2 Validation criteria (semantic level):**
|
|
- Class derives from `Executor` (directly or transitively)
|
|
- Class does NOT already define `ConfigureRoutes` with a body
|
|
- Handler method has valid signature: `(TMessage, IWorkflowContext[, CancellationToken])`
|
|
- Handler returns `void`, `ValueTask`, or `ValueTask<T>`
|
|
|
|
**3.3 Handler signature mapping:**
|
|
|
|
| Method Signature | Generated AddHandler Call |
|
|
|-----------------|---------------------------|
|
|
| `void Handler(T, IWorkflowContext)` | `AddHandler<T>(this.Handler)` |
|
|
| `void Handler(T, IWorkflowContext, CT)` | `AddHandler<T>(this.Handler)` |
|
|
| `ValueTask Handler(T, IWorkflowContext)` | `AddHandler<T>(this.Handler)` |
|
|
| `ValueTask Handler(T, IWorkflowContext, CT)` | `AddHandler<T>(this.Handler)` |
|
|
| `TResult Handler(T, IWorkflowContext)` | `AddHandler<T, TResult>(this.Handler)` |
|
|
| `ValueTask<TResult> Handler(T, IWorkflowContext, CT)` | `AddHandler<T, TResult>(this.Handler)` |
|
|
|
|
**3.4 Generated code structure:**
|
|
```csharp
|
|
// <auto-generated/>
|
|
#nullable enable
|
|
|
|
namespace MyNamespace;
|
|
|
|
partial class MyExecutor
|
|
{
|
|
protected override RouteBuilder ConfigureRoutes(RouteBuilder routeBuilder)
|
|
{
|
|
// Call base if inheriting from another executor with routes
|
|
// routeBuilder = base.ConfigureRoutes(routeBuilder);
|
|
|
|
return routeBuilder
|
|
.AddHandler<InputType1, OutputType1>(this.Handler1)
|
|
.AddHandler<InputType2>(this.Handler2);
|
|
}
|
|
|
|
protected override ISet<Type> ConfigureSentTypes()
|
|
{
|
|
var types = base.ConfigureSentTypes();
|
|
types.Add(typeof(SentType1));
|
|
return types;
|
|
}
|
|
|
|
protected override ISet<Type> ConfigureYieldTypes()
|
|
{
|
|
var types = base.ConfigureYieldTypes();
|
|
types.Add(typeof(YieldType1));
|
|
return types;
|
|
}
|
|
}
|
|
```
|
|
|
|
**3.5 Inheritance handling:**
|
|
|
|
| Scenario | Generated `ConfigureRoutes` |
|
|
|----------|----------------------------|
|
|
| Directly extends `Executor` | No base call (abstract) |
|
|
| Extends executor with `[MessageHandler]` methods | `routeBuilder = base.ConfigureRoutes(routeBuilder);` |
|
|
| Extends executor with manual `ConfigureRoutes` | `routeBuilder = base.ConfigureRoutes(routeBuilder);` |
|
|
|
|
### Phase 4: Analyzer Diagnostics
|
|
|
|
| ID | Severity | Condition |
|
|
|----|----------|-----------|
|
|
| `WFGEN001` | Error | Handler missing `IWorkflowContext` parameter |
|
|
| `WFGEN002` | Error | Handler has invalid return type |
|
|
| `WFGEN003` | Error | Executor with `[MessageHandler]` must be `partial` |
|
|
| `WFGEN004` | Warning | `[MessageHandler]` on non-Executor class |
|
|
| `WFGEN005` | Error | Handler has fewer than 2 parameters |
|
|
| `WFGEN006` | Info | `ConfigureRoutes` already defined, handlers ignored |
|
|
|
|
### Phase 5: Integration & Migration
|
|
|
|
**5.1 Wire generator to main project:**
|
|
```xml
|
|
<!-- Microsoft.Agents.AI.Workflows.csproj -->
|
|
<ItemGroup>
|
|
<ProjectReference Include="..\Microsoft.Agents.AI.Workflows.Generators\..."
|
|
OutputItemType="Analyzer"
|
|
ReferenceOutputAssembly="false" />
|
|
</ItemGroup>
|
|
```
|
|
|
|
**5.2 Mark `ReflectingExecutor<T>` obsolete:**
|
|
```csharp
|
|
[Obsolete("Use [MessageHandler] attribute on methods in a partial class deriving from Executor. " +
|
|
"See migration guide. This type will be removed in v1.0.", error: false)]
|
|
public class ReflectingExecutor<TExecutor> : Executor ...
|
|
```
|
|
|
|
**5.3 Mark `IMessageHandler<T>` interfaces obsolete:**
|
|
```csharp
|
|
[Obsolete("Use [MessageHandler] attribute instead.")]
|
|
public interface IMessageHandler<TMessage> { ... }
|
|
```
|
|
|
|
### Phase 6: Testing
|
|
|
|
**6.1 Generator unit tests:**
|
|
```
|
|
dotnet/tests/Microsoft.Agents.AI.Workflows.Generators.UnitTests/
|
|
├── ExecutorRouteGeneratorTests.cs
|
|
├── SyntaxDetectorTests.cs
|
|
├── SemanticAnalyzerTests.cs
|
|
└── TestHelpers/
|
|
└── GeneratorTestHelper.cs
|
|
```
|
|
|
|
Test cases:
|
|
- Simple single handler
|
|
- Multiple handlers on one class
|
|
- Handlers with different signatures (void, ValueTask, ValueTask<T>)
|
|
- Nested classes
|
|
- Generic executors
|
|
- Inheritance chains (Executor -> CustomBase -> Concrete)
|
|
- Class-level `[SendsMessage]`/`[YieldsMessage]` attributes
|
|
- Manual `ConfigureRoutes` present (should skip generation)
|
|
- Invalid signatures (should produce diagnostics)
|
|
|
|
**6.2 Integration tests:**
|
|
- Port existing `ReflectingExecutor` test cases to use `[MessageHandler]`
|
|
- Verify generated routes match reflection-discovered routes
|
|
|
|
---
|
|
|
|
## Files to Create
|
|
|
|
| Path | Purpose |
|
|
|------|---------|
|
|
| `dotnet/src/Microsoft.Agents.AI.Workflows.Generators/Microsoft.Agents.AI.Workflows.Generators.csproj` | Generator project |
|
|
| `dotnet/src/Microsoft.Agents.AI.Workflows.Generators/ExecutorRouteGenerator.cs` | Main generator |
|
|
| `dotnet/src/Microsoft.Agents.AI.Workflows.Generators/Models/ExecutorInfo.cs` | Data model |
|
|
| `dotnet/src/Microsoft.Agents.AI.Workflows.Generators/Models/HandlerInfo.cs` | Data model |
|
|
| `dotnet/src/Microsoft.Agents.AI.Workflows.Generators/Analysis/SyntaxDetector.cs` | Syntax analysis |
|
|
| `dotnet/src/Microsoft.Agents.AI.Workflows.Generators/Analysis/SemanticAnalyzer.cs` | Semantic analysis |
|
|
| `dotnet/src/Microsoft.Agents.AI.Workflows.Generators/Generation/SourceBuilder.cs` | Code gen |
|
|
| `dotnet/src/Microsoft.Agents.AI.Workflows.Generators/Diagnostics/DiagnosticDescriptors.cs` | Diagnostics |
|
|
| `dotnet/src/Microsoft.Agents.AI.Workflows/Attributes/MessageHandlerAttribute.cs` | Handler attribute |
|
|
| `dotnet/src/Microsoft.Agents.AI.Workflows/Attributes/SendsMessageAttribute.cs` | Class-level send |
|
|
| `dotnet/src/Microsoft.Agents.AI.Workflows/Attributes/YieldsMessageAttribute.cs` | Class-level yield |
|
|
| `dotnet/tests/Microsoft.Agents.AI.Workflows.Generators.UnitTests/*.cs` | Generator tests |
|
|
|
|
## Files to Modify
|
|
|
|
| Path | Changes |
|
|
|------|---------|
|
|
| `dotnet/src/Microsoft.Agents.AI.Workflows/Microsoft.Agents.AI.Workflows.csproj` | Add generator reference |
|
|
| `dotnet/src/Microsoft.Agents.AI.Workflows/Reflection/ReflectingExecutor.cs` | Add `[Obsolete]` |
|
|
| `dotnet/src/Microsoft.Agents.AI.Workflows/Reflection/IMessageHandler.cs` | Add `[Obsolete]` |
|
|
| `dotnet/Microsoft.Agents.sln` | Add new projects |
|
|
|
|
---
|
|
|
|
## Example Usage (End State)
|
|
|
|
```csharp
|
|
[SendsMessage(typeof(PollToken))]
|
|
public partial class MyChatExecutor : ChatProtocolExecutor
|
|
{
|
|
[MessageHandler]
|
|
private async ValueTask<ChatResponse> HandleQueryAsync(
|
|
ChatQuery query, IWorkflowContext ctx, CancellationToken ct)
|
|
{
|
|
// Return type automatically inferred as output
|
|
return new ChatResponse(...);
|
|
}
|
|
|
|
[MessageHandler(Yield = [typeof(StreamChunk)], Send = [typeof(InternalMessage)])]
|
|
private void HandleStream(StreamRequest req, IWorkflowContext ctx)
|
|
{
|
|
// Explicit Yield/Send for complex handlers
|
|
}
|
|
}
|
|
```
|
|
|
|
Generated:
|
|
```csharp
|
|
partial class MyChatExecutor
|
|
{
|
|
protected override RouteBuilder ConfigureRoutes(RouteBuilder routeBuilder)
|
|
{
|
|
routeBuilder = base.ConfigureRoutes(routeBuilder);
|
|
return routeBuilder
|
|
.AddHandler<ChatQuery, ChatResponse>(this.HandleQueryAsync)
|
|
.AddHandler<StreamRequest>(this.HandleStream);
|
|
}
|
|
|
|
protected override ISet<Type> ConfigureSentTypes()
|
|
{
|
|
var types = base.ConfigureSentTypes();
|
|
types.Add(typeof(PollToken));
|
|
types.Add(typeof(InternalMessage)); // From handler attribute
|
|
return types;
|
|
}
|
|
|
|
protected override ISet<Type> ConfigureYieldTypes()
|
|
{
|
|
var types = base.ConfigureYieldTypes();
|
|
types.Add(typeof(ChatResponse)); // From return type
|
|
types.Add(typeof(StreamChunk)); // From handler attribute
|
|
return types;
|
|
}
|
|
}
|
|
```
|