@polymorphism-tech/morph-spec 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +279 -0
- package/bin/morph-spec.js +53 -0
- package/content/.claude/commands/morph-apply.md +66 -0
- package/content/.claude/commands/morph-archive.md +79 -0
- package/content/.claude/commands/morph-costs.md +206 -0
- package/content/.claude/commands/morph-infra.md +209 -0
- package/content/.claude/commands/morph-proposal.md +60 -0
- package/content/.claude/commands/morph-status.md +71 -0
- package/content/.claude/settings.local.json +15 -0
- package/content/.claude/skills/infra/bicep-architect.md +419 -0
- package/content/.claude/skills/infra/container-specialist.md +437 -0
- package/content/.claude/skills/infra/devops-engineer.md +405 -0
- package/content/.claude/skills/integrations/asaas-financial.md +333 -0
- package/content/.claude/skills/integrations/azure-identity.md +309 -0
- package/content/.claude/skills/integrations/clerk-auth.md +290 -0
- package/content/.claude/skills/specialists/azure-architect.md +142 -0
- package/content/.claude/skills/specialists/cost-guardian.md +110 -0
- package/content/.claude/skills/specialists/ef-modeler.md +200 -0
- package/content/.claude/skills/specialists/hangfire-orchestrator.md +245 -0
- package/content/.claude/skills/specialists/ms-agent-expert.md +209 -0
- package/content/.claude/skills/specialists/po-pm-advisor.md +197 -0
- package/content/.claude/skills/specialists/standards-architect.md +78 -0
- package/content/.claude/skills/specialists/ui-ux-designer.md +325 -0
- package/content/.claude/skills/stacks/dotnet-blazor.md +352 -0
- package/content/.claude/skills/stacks/dotnet-nextjs.md +402 -0
- package/content/.claude/skills/stacks/shopify.md +445 -0
- package/content/.morph/archive/.gitkeep +25 -0
- package/content/.morph/config/agents.json +149 -0
- package/content/.morph/config/config.template.json +96 -0
- package/content/.morph/examples/api-nextjs/README.md +241 -0
- package/content/.morph/examples/api-nextjs/contracts.ts +307 -0
- package/content/.morph/examples/api-nextjs/spec.md +399 -0
- package/content/.morph/examples/api-nextjs/tasks.md +168 -0
- package/content/.morph/examples/micro-saas/README.md +125 -0
- package/content/.morph/examples/micro-saas/contracts.cs +358 -0
- package/content/.morph/examples/micro-saas/decisions.md +246 -0
- package/content/.morph/examples/micro-saas/spec.md +236 -0
- package/content/.morph/examples/micro-saas/tasks.md +150 -0
- package/content/.morph/examples/multi-agent/README.md +309 -0
- package/content/.morph/examples/multi-agent/contracts.cs +433 -0
- package/content/.morph/examples/multi-agent/spec.md +479 -0
- package/content/.morph/examples/multi-agent/tasks.md +185 -0
- package/content/.morph/features/.gitkeep +25 -0
- package/content/.morph/project.md +159 -0
- package/content/.morph/specs/.gitkeep +20 -0
- package/content/.morph/standards/architecture.md +190 -0
- package/content/.morph/standards/azure.md +184 -0
- package/content/.morph/standards/coding.md +342 -0
- package/content/.morph/templates/agent.cs +172 -0
- package/content/.morph/templates/component.razor +239 -0
- package/content/.morph/templates/contracts.cs +217 -0
- package/content/.morph/templates/decisions.md +106 -0
- package/content/.morph/templates/infra/app-insights.bicep +63 -0
- package/content/.morph/templates/infra/container-app-env.bicep +49 -0
- package/content/.morph/templates/infra/container-app.bicep +156 -0
- package/content/.morph/templates/infra/key-vault.bicep +91 -0
- package/content/.morph/templates/infra/main.bicep +155 -0
- package/content/.morph/templates/infra/parameters.dev.json +23 -0
- package/content/.morph/templates/infra/parameters.prod.json +23 -0
- package/content/.morph/templates/infra/sql-database.bicep +103 -0
- package/content/.morph/templates/infra/storage.bicep +106 -0
- package/content/.morph/templates/integrations/asaas-client.cs +387 -0
- package/content/.morph/templates/integrations/asaas-webhook.cs +351 -0
- package/content/.morph/templates/integrations/azure-identity-config.cs +288 -0
- package/content/.morph/templates/integrations/clerk-config.cs +258 -0
- package/content/.morph/templates/job.cs +171 -0
- package/content/.morph/templates/migration.cs +83 -0
- package/content/.morph/templates/proposal.md +155 -0
- package/content/.morph/templates/recap.md +105 -0
- package/content/.morph/templates/repository.cs +141 -0
- package/content/.morph/templates/saas/subscription.cs +347 -0
- package/content/.morph/templates/saas/tenant.cs +338 -0
- package/content/.morph/templates/service.cs +139 -0
- package/content/.morph/templates/spec.md +147 -0
- package/content/.morph/templates/tasks.md +235 -0
- package/content/.morph/templates/test.cs +239 -0
- package/content/CLAUDE.md +318 -0
- package/package.json +50 -0
- package/src/commands/doctor.js +132 -0
- package/src/commands/init.js +121 -0
- package/src/commands/update.js +84 -0
- package/src/utils/file-copier.js +50 -0
- package/src/utils/logger.js +32 -0
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
# Padrões de Código - MORPH Framework
|
|
2
|
+
|
|
3
|
+
## 📝 Nomenclatura
|
|
4
|
+
|
|
5
|
+
### Classes e Interfaces
|
|
6
|
+
```csharp
|
|
7
|
+
// Interfaces: sempre com prefixo I
|
|
8
|
+
public interface IReportService { }
|
|
9
|
+
public interface IReportRepository { }
|
|
10
|
+
|
|
11
|
+
// Classes: PascalCase, sufixo indica tipo
|
|
12
|
+
public class ReportService : IReportService { }
|
|
13
|
+
public class ReportRepository : IReportRepository { }
|
|
14
|
+
public class ReportController : ControllerBase { }
|
|
15
|
+
public class ReportGeneratorJob { } // Hangfire job
|
|
16
|
+
public class ReportAnalyzerAgent { } // AI Agent
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### Métodos
|
|
20
|
+
```csharp
|
|
21
|
+
// Async sempre com sufixo Async
|
|
22
|
+
public async Task<Report> GetByIdAsync(int id) { }
|
|
23
|
+
public async Task CreateAsync(Report report) { }
|
|
24
|
+
|
|
25
|
+
// Métodos síncronos sem sufixo
|
|
26
|
+
public Report GetById(int id) { }
|
|
27
|
+
public void Validate(Report report) { }
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Variáveis e Parâmetros
|
|
31
|
+
```csharp
|
|
32
|
+
// camelCase para variáveis locais e parâmetros
|
|
33
|
+
public async Task ProcessAsync(string reportId, CancellationToken cancellationToken)
|
|
34
|
+
{
|
|
35
|
+
var report = await _repository.GetByIdAsync(reportId);
|
|
36
|
+
var analysisResult = await _analyzer.AnalyzeAsync(report);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// _camelCase para campos privados
|
|
40
|
+
private readonly IReportRepository _repository;
|
|
41
|
+
private readonly ILogger<ReportService> _logger;
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## 📁 Estrutura de Projeto
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
src/
|
|
50
|
+
├── MyProject.Web/ # Blazor Server
|
|
51
|
+
│ ├── Components/
|
|
52
|
+
│ │ ├── Pages/ # Páginas (rotas)
|
|
53
|
+
│ │ ├── Shared/ # Componentes compartilhados
|
|
54
|
+
│ │ └── Layout/ # Layouts
|
|
55
|
+
│ └── Program.cs
|
|
56
|
+
│
|
|
57
|
+
├── MyProject.Application/ # Casos de uso
|
|
58
|
+
│ ├── Features/
|
|
59
|
+
│ │ └── Reports/
|
|
60
|
+
│ │ ├── Commands/
|
|
61
|
+
│ │ ├── Queries/
|
|
62
|
+
│ │ └── Services/
|
|
63
|
+
│ └── Common/
|
|
64
|
+
│
|
|
65
|
+
├── MyProject.Domain/ # Entidades e regras
|
|
66
|
+
│ ├── Entities/
|
|
67
|
+
│ ├── ValueObjects/
|
|
68
|
+
│ └── Enums/
|
|
69
|
+
│
|
|
70
|
+
├── MyProject.Infrastructure/ # Implementações
|
|
71
|
+
│ ├── Data/
|
|
72
|
+
│ │ ├── Configurations/
|
|
73
|
+
│ │ ├── Migrations/
|
|
74
|
+
│ │ └── AppDbContext.cs
|
|
75
|
+
│ └── Services/
|
|
76
|
+
│
|
|
77
|
+
└── MyProject.Agents/ # MS Agent Framework
|
|
78
|
+
└── ReportAnalyzer/
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## 💉 Dependency Injection
|
|
84
|
+
|
|
85
|
+
```csharp
|
|
86
|
+
// Usar primary constructor (C# 12+)
|
|
87
|
+
public class ReportService(
|
|
88
|
+
IReportRepository repository,
|
|
89
|
+
ILogger<ReportService> logger) : IReportService
|
|
90
|
+
{
|
|
91
|
+
public async Task<Report> GetByIdAsync(int id)
|
|
92
|
+
{
|
|
93
|
+
logger.LogInformation("Getting report {ReportId}", id);
|
|
94
|
+
return await repository.GetByIdAsync(id);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## 🗄️ Entity Framework Core
|
|
102
|
+
|
|
103
|
+
### Entidades
|
|
104
|
+
```csharp
|
|
105
|
+
public class ReportSchedule
|
|
106
|
+
{
|
|
107
|
+
public int Id { get; private set; }
|
|
108
|
+
public string Name { get; private set; } = string.Empty;
|
|
109
|
+
public ReportType ReportType { get; private set; }
|
|
110
|
+
public string CronExpression { get; private set; } = string.Empty;
|
|
111
|
+
public List<string> Recipients { get; private set; } = [];
|
|
112
|
+
public bool IsActive { get; private set; }
|
|
113
|
+
public DateTime CreatedAt { get; private set; }
|
|
114
|
+
|
|
115
|
+
// Factory method para criação
|
|
116
|
+
public static ReportSchedule Create(string name, ReportType reportType,
|
|
117
|
+
string cronExpression, List<string> recipients)
|
|
118
|
+
{
|
|
119
|
+
return new ReportSchedule
|
|
120
|
+
{
|
|
121
|
+
Name = name,
|
|
122
|
+
ReportType = reportType,
|
|
123
|
+
CronExpression = cronExpression,
|
|
124
|
+
Recipients = recipients,
|
|
125
|
+
IsActive = true,
|
|
126
|
+
CreatedAt = DateTime.UtcNow
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Métodos de domínio
|
|
131
|
+
public void Activate() => IsActive = true;
|
|
132
|
+
public void Deactivate() => IsActive = false;
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Configuração Fluent API
|
|
137
|
+
```csharp
|
|
138
|
+
public class ReportScheduleConfiguration : IEntityTypeConfiguration<ReportSchedule>
|
|
139
|
+
{
|
|
140
|
+
public void Configure(EntityTypeBuilder<ReportSchedule> builder)
|
|
141
|
+
{
|
|
142
|
+
builder.ToTable("ReportSchedules");
|
|
143
|
+
builder.HasKey(x => x.Id);
|
|
144
|
+
|
|
145
|
+
builder.Property(x => x.Name)
|
|
146
|
+
.IsRequired()
|
|
147
|
+
.HasMaxLength(200);
|
|
148
|
+
|
|
149
|
+
// JSON column para lista
|
|
150
|
+
builder.Property(x => x.Recipients)
|
|
151
|
+
.HasConversion(
|
|
152
|
+
v => JsonSerializer.Serialize(v, (JsonSerializerOptions?)null),
|
|
153
|
+
v => JsonSerializer.Deserialize<List<string>>(v, (JsonSerializerOptions?)null) ?? [])
|
|
154
|
+
.HasColumnType("nvarchar(max)");
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## 🔥 Componentes Blazor
|
|
162
|
+
|
|
163
|
+
```razor
|
|
164
|
+
@page "/reports/schedules"
|
|
165
|
+
@attribute [Authorize(Policy = "CanManageReports")]
|
|
166
|
+
@inject IReportScheduleService ReportService
|
|
167
|
+
@inject ILogger<ReportScheduleList> Logger
|
|
168
|
+
|
|
169
|
+
<PageTitle>Report Schedules</PageTitle>
|
|
170
|
+
|
|
171
|
+
@if (_isLoading)
|
|
172
|
+
{
|
|
173
|
+
<LoadingSpinner />
|
|
174
|
+
}
|
|
175
|
+
else if (_schedules is null || !_schedules.Any())
|
|
176
|
+
{
|
|
177
|
+
<EmptyState Message="No schedules configured" />
|
|
178
|
+
}
|
|
179
|
+
else
|
|
180
|
+
{
|
|
181
|
+
<div class="schedule-list">
|
|
182
|
+
@foreach (var schedule in _schedules)
|
|
183
|
+
{
|
|
184
|
+
<ScheduleCard Schedule="schedule" OnToggle="HandleToggleAsync" />
|
|
185
|
+
}
|
|
186
|
+
</div>
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
@code {
|
|
190
|
+
private List<ReportScheduleDto>? _schedules;
|
|
191
|
+
private bool _isLoading = true;
|
|
192
|
+
|
|
193
|
+
protected override async Task OnInitializedAsync()
|
|
194
|
+
{
|
|
195
|
+
try
|
|
196
|
+
{
|
|
197
|
+
_schedules = await ReportService.GetAllAsync();
|
|
198
|
+
}
|
|
199
|
+
catch (Exception ex)
|
|
200
|
+
{
|
|
201
|
+
Logger.LogError(ex, "Failed to load schedules");
|
|
202
|
+
}
|
|
203
|
+
finally
|
|
204
|
+
{
|
|
205
|
+
_isLoading = false;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### Padrões Obrigatórios
|
|
212
|
+
- ✅ Loading state em toda página
|
|
213
|
+
- ✅ Empty state quando lista vazia
|
|
214
|
+
- ✅ Error handling com try/catch
|
|
215
|
+
- ✅ `@inject` para DI (não construtor)
|
|
216
|
+
- ✅ `@attribute [Authorize]` quando necessário
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## 🤖 MS Agent Framework
|
|
221
|
+
|
|
222
|
+
```csharp
|
|
223
|
+
public class ReportAnalyzerAgent(
|
|
224
|
+
Kernel kernel,
|
|
225
|
+
ILogger<ReportAnalyzerAgent> logger) : IReportAnalyzerAgent
|
|
226
|
+
{
|
|
227
|
+
public async Task<AnalysisResult> AnalyzeAsync(
|
|
228
|
+
ReportData reportData,
|
|
229
|
+
CancellationToken cancellationToken = default)
|
|
230
|
+
{
|
|
231
|
+
var prompt = $"""
|
|
232
|
+
Analyze the following report data:
|
|
233
|
+
{reportData.ToJson()}
|
|
234
|
+
|
|
235
|
+
Provide: summary, top 3 insights, recommendations.
|
|
236
|
+
Respond in JSON format.
|
|
237
|
+
""";
|
|
238
|
+
|
|
239
|
+
try
|
|
240
|
+
{
|
|
241
|
+
var response = await kernel.InvokePromptAsync(prompt, cancellationToken: cancellationToken);
|
|
242
|
+
return ParseResponse(response.ToString());
|
|
243
|
+
}
|
|
244
|
+
catch (Exception ex)
|
|
245
|
+
{
|
|
246
|
+
logger.LogError(ex, "Analysis failed");
|
|
247
|
+
throw new ReportAnalysisException("Failed to analyze", ex);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
## ⏰ Hangfire Jobs
|
|
256
|
+
|
|
257
|
+
```csharp
|
|
258
|
+
public class ReportGeneratorJob(
|
|
259
|
+
IReportScheduleService scheduleService,
|
|
260
|
+
IReportAnalyzerAgent analyzer,
|
|
261
|
+
ILogger<ReportGeneratorJob> logger) : IReportGeneratorJob
|
|
262
|
+
{
|
|
263
|
+
[AutomaticRetry(Attempts = 3, DelaysInSeconds = new[] { 60, 300, 900 })]
|
|
264
|
+
[Queue("reports")]
|
|
265
|
+
public async Task ExecuteAsync(int scheduleId, CancellationToken cancellationToken)
|
|
266
|
+
{
|
|
267
|
+
logger.LogInformation("Starting report generation for {ScheduleId}", scheduleId);
|
|
268
|
+
|
|
269
|
+
var schedule = await scheduleService.GetByIdAsync(scheduleId);
|
|
270
|
+
if (schedule is null || !schedule.IsActive)
|
|
271
|
+
{
|
|
272
|
+
logger.LogWarning("Schedule {ScheduleId} not found or inactive", scheduleId);
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// ... implementação
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
---
|
|
282
|
+
|
|
283
|
+
## 🧪 Testes
|
|
284
|
+
|
|
285
|
+
### Nomenclatura
|
|
286
|
+
```csharp
|
|
287
|
+
// Classe: {ClasseTestada}Tests
|
|
288
|
+
public class ReportServiceTests
|
|
289
|
+
{
|
|
290
|
+
// Método: {Método}_{Cenário}_{ResultadoEsperado}
|
|
291
|
+
[Fact]
|
|
292
|
+
public async Task GetByIdAsync_WithValidId_ReturnsReport() { }
|
|
293
|
+
|
|
294
|
+
[Fact]
|
|
295
|
+
public async Task GetByIdAsync_WithInvalidId_ThrowsNotFoundException() { }
|
|
296
|
+
}
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### Estrutura AAA
|
|
300
|
+
```csharp
|
|
301
|
+
[Fact]
|
|
302
|
+
public async Task CreateAsync_WithValidData_CreatesSchedule()
|
|
303
|
+
{
|
|
304
|
+
// Arrange
|
|
305
|
+
var request = new CreateReportScheduleRequest("Daily Sales", ReportType.Sales);
|
|
306
|
+
|
|
307
|
+
// Act
|
|
308
|
+
var result = await _service.CreateAsync(request);
|
|
309
|
+
|
|
310
|
+
// Assert
|
|
311
|
+
Assert.NotNull(result);
|
|
312
|
+
Assert.Equal(request.Name, result.Name);
|
|
313
|
+
}
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
## 🚫 Anti-Patterns a Evitar
|
|
319
|
+
|
|
320
|
+
```csharp
|
|
321
|
+
// ❌ Service Locator
|
|
322
|
+
var service = serviceProvider.GetService<IReportService>();
|
|
323
|
+
|
|
324
|
+
// ✅ Constructor Injection
|
|
325
|
+
public class MyClass(IReportService reportService) { }
|
|
326
|
+
|
|
327
|
+
// ❌ Async void
|
|
328
|
+
public async void ProcessReport() { }
|
|
329
|
+
|
|
330
|
+
// ✅ Async Task
|
|
331
|
+
public async Task ProcessReportAsync() { }
|
|
332
|
+
|
|
333
|
+
// ❌ Catching generic Exception sem log
|
|
334
|
+
catch (Exception) { }
|
|
335
|
+
|
|
336
|
+
// ✅ Log and handle
|
|
337
|
+
catch (Exception ex)
|
|
338
|
+
{
|
|
339
|
+
_logger.LogError(ex, "Operation failed");
|
|
340
|
+
throw;
|
|
341
|
+
}
|
|
342
|
+
```
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// MS AGENT FRAMEWORK TEMPLATE
|
|
3
|
+
// Generated by MORPH Framework
|
|
4
|
+
// ============================================================
|
|
5
|
+
|
|
6
|
+
using Microsoft.Extensions.Logging;
|
|
7
|
+
using Microsoft.SemanticKernel;
|
|
8
|
+
using System.Text.Json;
|
|
9
|
+
|
|
10
|
+
namespace MyProject.Agents.{Feature}Analyzer;
|
|
11
|
+
|
|
12
|
+
/// <summary>
|
|
13
|
+
/// AI Agent for analyzing {Feature} data.
|
|
14
|
+
/// Uses Semantic Kernel with Azure OpenAI (gpt-4o-mini by default).
|
|
15
|
+
/// </summary>
|
|
16
|
+
public class {Feature}AnalyzerAgent(
|
|
17
|
+
Kernel kernel,
|
|
18
|
+
ILogger<{Feature}AnalyzerAgent> logger) : I{Feature}AnalyzerAgent
|
|
19
|
+
{
|
|
20
|
+
private const int MaxRetries = 3;
|
|
21
|
+
private static readonly TimeSpan[] RetryDelays =
|
|
22
|
+
{
|
|
23
|
+
TimeSpan.FromSeconds(1),
|
|
24
|
+
TimeSpan.FromSeconds(2),
|
|
25
|
+
TimeSpan.FromSeconds(4)
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/// <inheritdoc />
|
|
29
|
+
public async Task<{Feature}AnalysisResult> AnalyzeAsync(
|
|
30
|
+
{Feature}Data data,
|
|
31
|
+
CancellationToken cancellationToken = default)
|
|
32
|
+
{
|
|
33
|
+
logger.LogInformation("Starting {Feature} analysis for: {Content}",
|
|
34
|
+
data.Content.Length > 100 ? data.Content[..100] + "..." : data.Content);
|
|
35
|
+
|
|
36
|
+
var prompt = BuildPrompt(data);
|
|
37
|
+
|
|
38
|
+
for (int attempt = 0; attempt < MaxRetries; attempt++)
|
|
39
|
+
{
|
|
40
|
+
try
|
|
41
|
+
{
|
|
42
|
+
var response = await kernel.InvokePromptAsync(
|
|
43
|
+
prompt,
|
|
44
|
+
cancellationToken: cancellationToken);
|
|
45
|
+
|
|
46
|
+
var result = ParseResponse(response.ToString());
|
|
47
|
+
|
|
48
|
+
logger.LogInformation(
|
|
49
|
+
"Analysis completed. Confidence: {Confidence:P0}",
|
|
50
|
+
result.ConfidenceScore);
|
|
51
|
+
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
54
|
+
catch (Exception ex) when (attempt < MaxRetries - 1)
|
|
55
|
+
{
|
|
56
|
+
logger.LogWarning(ex,
|
|
57
|
+
"Analysis attempt {Attempt} failed, retrying...",
|
|
58
|
+
attempt + 1);
|
|
59
|
+
|
|
60
|
+
await Task.Delay(RetryDelays[attempt], cancellationToken);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
throw new {Feature}ProcessingException("Analysis failed after all retries");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
#region Private Methods
|
|
68
|
+
|
|
69
|
+
private static string BuildPrompt({Feature}Data data)
|
|
70
|
+
{
|
|
71
|
+
return $"""
|
|
72
|
+
You are an expert analyst for {Feature} data.
|
|
73
|
+
|
|
74
|
+
Analyze the following data and provide insights:
|
|
75
|
+
|
|
76
|
+
<data>
|
|
77
|
+
{data.Content}
|
|
78
|
+
</data>
|
|
79
|
+
|
|
80
|
+
Provide your analysis in the following JSON format:
|
|
81
|
+
{{
|
|
82
|
+
"summary": "Brief summary of the analysis (1-2 sentences)",
|
|
83
|
+
"insights": ["insight 1", "insight 2", "insight 3"],
|
|
84
|
+
"recommendations": ["recommendation 1", "recommendation 2"],
|
|
85
|
+
"confidenceScore": 0.85
|
|
86
|
+
}}
|
|
87
|
+
|
|
88
|
+
Guidelines:
|
|
89
|
+
- Be concise and actionable
|
|
90
|
+
- Confidence score should be between 0 and 1
|
|
91
|
+
- Provide at least 2 insights
|
|
92
|
+
- Provide at least 1 recommendation
|
|
93
|
+
- Focus on practical, business-relevant observations
|
|
94
|
+
|
|
95
|
+
Respond ONLY with the JSON object, no additional text.
|
|
96
|
+
""";
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
private {Feature}AnalysisResult ParseResponse(string response)
|
|
100
|
+
{
|
|
101
|
+
try
|
|
102
|
+
{
|
|
103
|
+
// Clean up response (remove markdown code blocks if present)
|
|
104
|
+
var json = response
|
|
105
|
+
.Replace("```json", "")
|
|
106
|
+
.Replace("```", "")
|
|
107
|
+
.Trim();
|
|
108
|
+
|
|
109
|
+
var parsed = JsonSerializer.Deserialize<AnalysisResponse>(json,
|
|
110
|
+
new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
|
|
111
|
+
|
|
112
|
+
if (parsed is null)
|
|
113
|
+
{
|
|
114
|
+
throw new {Feature}ProcessingException("Failed to parse analysis response");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return new {Feature}AnalysisResult(
|
|
118
|
+
parsed.Summary ?? "Analysis completed",
|
|
119
|
+
parsed.Insights ?? new List<string>(),
|
|
120
|
+
parsed.Recommendations ?? new List<string>(),
|
|
121
|
+
parsed.ConfidenceScore
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
catch (JsonException ex)
|
|
125
|
+
{
|
|
126
|
+
logger.LogError(ex, "Failed to parse AI response: {Response}", response);
|
|
127
|
+
throw new {Feature}ProcessingException("Failed to parse analysis response", ex);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
private class AnalysisResponse
|
|
132
|
+
{
|
|
133
|
+
public string? Summary { get; set; }
|
|
134
|
+
public List<string>? Insights { get; set; }
|
|
135
|
+
public List<string>? Recommendations { get; set; }
|
|
136
|
+
public double ConfidenceScore { get; set; }
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
#endregion
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ============================================================
|
|
143
|
+
// DEPENDENCY INJECTION SETUP
|
|
144
|
+
// ============================================================
|
|
145
|
+
//
|
|
146
|
+
// In Program.cs or a DI extension:
|
|
147
|
+
//
|
|
148
|
+
// public static IServiceCollection AddAgents(
|
|
149
|
+
// this IServiceCollection services,
|
|
150
|
+
// IConfiguration configuration)
|
|
151
|
+
// {
|
|
152
|
+
// // Configure Semantic Kernel with Azure OpenAI
|
|
153
|
+
// var builder = Kernel.CreateBuilder();
|
|
154
|
+
//
|
|
155
|
+
// builder.AddAzureOpenAIChatCompletion(
|
|
156
|
+
// deploymentName: "gpt-4o-mini",
|
|
157
|
+
// endpoint: configuration["AzureOpenAI:Endpoint"]!,
|
|
158
|
+
// apiKey: configuration["AzureOpenAI:ApiKey"]!);
|
|
159
|
+
//
|
|
160
|
+
// // Or with Managed Identity (preferred):
|
|
161
|
+
// // builder.AddAzureOpenAIChatCompletion(
|
|
162
|
+
// // deploymentName: "gpt-4o-mini",
|
|
163
|
+
// // endpoint: configuration["AzureOpenAI:Endpoint"]!,
|
|
164
|
+
// // credentials: new DefaultAzureCredential());
|
|
165
|
+
//
|
|
166
|
+
// services.AddSingleton(builder.Build());
|
|
167
|
+
// services.AddScoped<I{Feature}AnalyzerAgent, {Feature}AnalyzerAgent>();
|
|
168
|
+
//
|
|
169
|
+
// return services;
|
|
170
|
+
// }
|
|
171
|
+
//
|
|
172
|
+
// ============================================================
|