@atlashub/smartstack-cli 1.4.0 → 1.5.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/.documentation/agents.html +8 -4
- package/.documentation/apex.html +8 -4
- package/.documentation/business-analyse.html +833 -406
- package/.documentation/commands.html +8 -4
- package/.documentation/css/styles.css +153 -15
- package/.documentation/efcore.html +8 -4
- package/.documentation/gitflow.html +795 -230
- package/.documentation/hooks.html +8 -4
- package/.documentation/index.html +13 -9
- package/.documentation/installation.html +23 -19
- package/.documentation/ralph-loop.html +530 -0
- package/.documentation/test-web.html +8 -4
- package/README.md +52 -10
- package/dist/index.js +813 -283
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/templates/agents/efcore/conflicts.md +44 -17
- package/templates/agents/efcore/db-status.md +27 -6
- package/templates/agents/efcore/scan.md +43 -13
- package/templates/commands/ai-prompt.md +315 -315
- package/templates/commands/application/create.md +362 -362
- package/templates/commands/controller/create.md +216 -216
- package/templates/commands/controller.md +59 -0
- package/templates/commands/create/agent.md +138 -0
- package/templates/commands/create/command.md +166 -0
- package/templates/commands/create/hook.md +234 -0
- package/templates/commands/create/plugin.md +329 -0
- package/templates/commands/create/project.md +507 -0
- package/templates/commands/create/skill.md +199 -0
- package/templates/commands/create.md +220 -0
- package/templates/commands/documentation/module.md +202 -202
- package/templates/commands/efcore/_env-check.md +153 -153
- package/templates/commands/efcore/conflicts.md +109 -192
- package/templates/commands/efcore/db-status.md +101 -89
- package/templates/commands/efcore/migration.md +23 -11
- package/templates/commands/efcore/scan.md +115 -119
- package/templates/commands/efcore.md +54 -6
- package/templates/commands/feature-full.md +267 -267
- package/templates/commands/gitflow/11-finish.md +145 -11
- package/templates/commands/gitflow/13-sync.md +216 -216
- package/templates/commands/gitflow/14-rebase.md +251 -251
- package/templates/commands/gitflow/2-status.md +120 -10
- package/templates/commands/gitflow/3-commit.md +150 -0
- package/templates/commands/gitflow/7-pull-request.md +134 -5
- package/templates/commands/gitflow/9-merge.md +142 -1
- package/templates/commands/implement.md +663 -663
- package/templates/commands/init.md +562 -0
- package/templates/commands/mcp-integration.md +330 -0
- package/templates/commands/notification.md +129 -129
- package/templates/commands/validate.md +233 -0
- package/templates/commands/workflow.md +193 -193
- package/templates/skills/ai-prompt/SKILL.md +778 -778
- package/templates/skills/application/SKILL.md +563 -563
- package/templates/skills/application/templates-backend.md +450 -450
- package/templates/skills/application/templates-frontend.md +531 -531
- package/templates/skills/application/templates-i18n.md +520 -520
- package/templates/skills/application/templates-seed.md +647 -647
- package/templates/skills/controller/SKILL.md +240 -240
- package/templates/skills/controller/postman-templates.md +614 -614
- package/templates/skills/controller/templates.md +1468 -1468
- package/templates/skills/documentation/SKILL.md +133 -133
- package/templates/skills/documentation/templates.md +476 -476
- package/templates/skills/feature-full/SKILL.md +838 -838
- package/templates/skills/notification/SKILL.md +555 -555
- package/templates/skills/ui-components/SKILL.md +870 -870
- package/templates/skills/workflow/SKILL.md +582 -582
|
@@ -1,778 +1,778 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: ai-prompt
|
|
3
|
-
description: |
|
|
4
|
-
Integre les capacites IA dans les features SmartStack.
|
|
5
|
-
Utiliser ce skill quand:
|
|
6
|
-
- L'utilisateur veut integrer l'IA dans une fonctionnalite
|
|
7
|
-
- L'utilisateur mentionne "prompt", "GPT", "Claude", "IA", "AI"
|
|
8
|
-
- Creation d'un assistant ou chatbot
|
|
9
|
-
- Generation de contenu automatique
|
|
10
|
-
- Validation de reponses IA avec schema
|
|
11
|
-
Types: Prompt, OutputSchema, Provider, Model
|
|
12
|
-
---
|
|
13
|
-
|
|
14
|
-
# Skill AI Prompt SmartStack
|
|
15
|
-
|
|
16
|
-
> **Architecture:** Prompt + Blocks → Provider + Model → Completion → Schema Validation
|
|
17
|
-
> Multi-provider: OpenAI, Anthropic (Claude), Azure OpenAI, Google Gemini
|
|
18
|
-
|
|
19
|
-
## QUAND CE SKILL S'ACTIVE
|
|
20
|
-
|
|
21
|
-
Claude invoque automatiquement ce skill quand il detecte :
|
|
22
|
-
|
|
23
|
-
| Declencheur | Exemple |
|
|
24
|
-
|-------------|---------|
|
|
25
|
-
| Demande explicite | "Integre GPT pour generer des descriptions" |
|
|
26
|
-
| Chatbot/Assistant | "Cree un assistant pour le support" |
|
|
27
|
-
| Generation contenu | "Genere automatiquement des reponses" |
|
|
28
|
-
| Analyse texte | "Analyse le sentiment des commentaires" |
|
|
29
|
-
| Mots-cles | "prompt", "GPT", "Claude", "IA", "completion", "LLM" |
|
|
30
|
-
|
|
31
|
-
---
|
|
32
|
-
|
|
33
|
-
## ARCHITECTURE IA
|
|
34
|
-
|
|
35
|
-
```
|
|
36
|
-
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
37
|
-
│ AI COMPLETION FLOW │
|
|
38
|
-
├─────────────────────────────────────────────────────────────────────────────┤
|
|
39
|
-
│ │
|
|
40
|
-
│ [FEATURE] ─────────────────────────────────────────────────────────────┐ │
|
|
41
|
-
│ │ │ │
|
|
42
|
-
│ ▼ │ │
|
|
43
|
-
│ ┌─────────────────────────────┐ │ │
|
|
44
|
-
│ │ IAiCompletionService │ │ │
|
|
45
|
-
│ │ ExecutePromptAsync() │ │ │
|
|
46
|
-
│ └─────────────┬───────────────┘ │ │
|
|
47
|
-
│ │ │ │
|
|
48
|
-
│ ┌────────┴────────┐ │ │
|
|
49
|
-
│ ▼ ▼ │ │
|
|
50
|
-
│ ┌─────────────┐ ┌─────────────────┐ │ │
|
|
51
|
-
│ │ Prompt │ │ Provider │ │ │
|
|
52
|
-
│ │ + Blocks │ │ Instance │ │ │
|
|
53
|
-
│ └──────┬──────┘ └────────┬────────┘ │ │
|
|
54
|
-
│ │ │ │ │
|
|
55
|
-
│ ▼ ▼ │ │
|
|
56
|
-
│ ┌─────────────────────────────────────┐ │ │
|
|
57
|
-
│ │ RENDERED PROMPT │ │ │
|
|
58
|
-
│ │ System: {{instructions}} │ │ │
|
|
59
|
-
│ │ User: {{userInput}} │ │ │
|
|
60
|
-
│ └─────────────────┬───────────────────┘ │ │
|
|
61
|
-
│ │ │ │
|
|
62
|
-
│ ▼ │ │
|
|
63
|
-
│ ┌─────────────────────────────────────┐ │ │
|
|
64
|
-
│ │ EXTERNAL API │ │ │
|
|
65
|
-
│ │ OpenAI / Claude / Azure / Gemini │ │ │
|
|
66
|
-
│ └─────────────────┬───────────────────┘ │ │
|
|
67
|
-
│ │ │ │
|
|
68
|
-
│ ▼ │ │
|
|
69
|
-
│ ┌─────────────────────────────────────┐ │ │
|
|
70
|
-
│ │ AI RESPONSE │ │ │
|
|
71
|
-
│ │ { "content": "...", ... } │ │ │
|
|
72
|
-
│ └─────────────────┬───────────────────┘ │ │
|
|
73
|
-
│ │ │ │
|
|
74
|
-
│ ┌────────────┴────────────┐ │ │
|
|
75
|
-
│ ▼ ▼ │ │
|
|
76
|
-
│ ┌─────────────┐ ┌─────────────────┐ │ │
|
|
77
|
-
│ │ RAW │ │ OutputSchema │ │ │
|
|
78
|
-
│ │ Response │ │ Validation │ │ │
|
|
79
|
-
│ └─────────────┘ └────────┬────────┘ │ │
|
|
80
|
-
│ │ │ │
|
|
81
|
-
│ ▼ │ │
|
|
82
|
-
│ ┌─────────────────┐ │ │
|
|
83
|
-
│ │ Typed Result<T> │ │ │
|
|
84
|
-
│ │ + Validation │ │ │
|
|
85
|
-
│ └─────────────────┘ │ │
|
|
86
|
-
│ │ │
|
|
87
|
-
└────────────────────────────────────────────────────────────────────────────┘
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
---
|
|
91
|
-
|
|
92
|
-
## ENTITES PRINCIPALES
|
|
93
|
-
|
|
94
|
-
### AiProvider
|
|
95
|
-
|
|
96
|
-
```csharp
|
|
97
|
-
// Providers disponibles (seed data)
|
|
98
|
-
- OpenAI (gpt-4o, gpt-4-turbo, gpt-3.5-turbo)
|
|
99
|
-
- Anthropic (claude-3-opus, claude-3-sonnet, claude-3-haiku)
|
|
100
|
-
- Azure OpenAI (deploiements custom)
|
|
101
|
-
- Google (gemini-pro, gemini-1.5-pro)
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
### AiModel
|
|
105
|
-
|
|
106
|
-
```csharp
|
|
107
|
-
// Proprietes cles
|
|
108
|
-
public string Code { get; } // "gpt-4o"
|
|
109
|
-
public string ShortCode { get; } // "GPT4O"
|
|
110
|
-
public ModelCategory Category { get; } // TextGeneration, Embedding, etc.
|
|
111
|
-
public int ContextWindow { get; } // 128000
|
|
112
|
-
public int MaxOutputTokens { get; } // 16384
|
|
113
|
-
public decimal InputCostPerMillion { get; }
|
|
114
|
-
public decimal OutputCostPerMillion { get; }
|
|
115
|
-
|
|
116
|
-
// Capabilities
|
|
117
|
-
public bool SupportsVision { get; }
|
|
118
|
-
public bool SupportsFunctionCalling { get; }
|
|
119
|
-
public bool SupportsStreaming { get; }
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
### AiProviderInstance
|
|
123
|
-
|
|
124
|
-
```csharp
|
|
125
|
-
// Instance configuree pour un use-case
|
|
126
|
-
public string Code { get; } // "support-assistant"
|
|
127
|
-
public string Name { get; } // "Assistant Support"
|
|
128
|
-
public string SystemContext { get; } // Prompt systeme personnalise
|
|
129
|
-
public string DefaultModel { get; } // "gpt-4o"
|
|
130
|
-
public decimal MonthlyBudgetLimit { get; }
|
|
131
|
-
public decimal CurrentMonthUsage { get; }
|
|
132
|
-
|
|
133
|
-
// Securite
|
|
134
|
-
public string EncryptedApiKey { get; } // Cle chiffree
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
### Prompt
|
|
138
|
-
|
|
139
|
-
```csharp
|
|
140
|
-
// Structure d'un prompt
|
|
141
|
-
public string Code { get; } // "ticket-analyzer"
|
|
142
|
-
public string Name { get; } // "Ticket Analyzer"
|
|
143
|
-
public string Version { get; } // "1.0.0"
|
|
144
|
-
public PromptStatus Status { get; } // Draft, Active, Deprecated
|
|
145
|
-
public bool IsTemplate { get; } // Template reutilisable
|
|
146
|
-
public Guid? OutputSchemaId { get; } // Schema de validation
|
|
147
|
-
|
|
148
|
-
// Blocks = Sections du prompt
|
|
149
|
-
public ICollection<PromptBlock> Blocks { get; }
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
### PromptBlock
|
|
153
|
-
|
|
154
|
-
```csharp
|
|
155
|
-
// Types de blocks
|
|
156
|
-
public enum PromptBlockType
|
|
157
|
-
{
|
|
158
|
-
System, // Instructions systeme
|
|
159
|
-
User, // Message utilisateur
|
|
160
|
-
Assistant, // Reponse exemple
|
|
161
|
-
Tool, // Definition d'outil
|
|
162
|
-
Context, // Contexte additionnel
|
|
163
|
-
Examples // Exemples few-shot
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// Proprietes
|
|
167
|
-
public string Label { get; } // "Instructions"
|
|
168
|
-
public string Content { get; } // "Tu es un assistant..."
|
|
169
|
-
public int DisplayOrder { get; }
|
|
170
|
-
public bool IsRequired { get; }
|
|
171
|
-
public string Condition { get; } // Condition d'inclusion
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
### OutputSchema
|
|
175
|
-
|
|
176
|
-
```csharp
|
|
177
|
-
// Schema JSON pour validation
|
|
178
|
-
public string Code { get; } // "ticket-analysis"
|
|
179
|
-
public string Name { get; } // "Ticket Analysis Schema"
|
|
180
|
-
public string JsonSchema { get; } // JSON Schema valide
|
|
181
|
-
public string DotNetType { get; } // "SmartStack.Application.AI.TicketAnalysisResult"
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
---
|
|
185
|
-
|
|
186
|
-
## WORKFLOW INTEGRATION IA
|
|
187
|
-
|
|
188
|
-
### ETAPE 1: Definir le Use Case
|
|
189
|
-
|
|
190
|
-
| Question | Impact |
|
|
191
|
-
|----------|--------|
|
|
192
|
-
| Quel type de contenu generer ? | Choix du modele |
|
|
193
|
-
| Reponse structuree requise ? | OutputSchema |
|
|
194
|
-
| Multi-langue ? | Variables langue |
|
|
195
|
-
| Budget limite ? | ProviderInstance avec limit |
|
|
196
|
-
| Temps reel requis ? | Streaming |
|
|
197
|
-
|
|
198
|
-
### ETAPE 2: Creer le Prompt
|
|
199
|
-
|
|
200
|
-
```csharp
|
|
201
|
-
// Via IPromptService
|
|
202
|
-
var promptId = await _promptService.CreatePromptAsync(new CreatePromptRequest
|
|
203
|
-
{
|
|
204
|
-
Code = "ticket-analyzer",
|
|
205
|
-
Name = "Ticket Analyzer",
|
|
206
|
-
Description = "Analyse les tickets support pour categorisation",
|
|
207
|
-
IsTemplate = false,
|
|
208
|
-
Blocks = new[]
|
|
209
|
-
{
|
|
210
|
-
new CreateBlockRequest
|
|
211
|
-
{
|
|
212
|
-
BlockType = PromptBlockType.System,
|
|
213
|
-
Label = "Instructions",
|
|
214
|
-
Content = @"Tu es un expert en support technique.
|
|
215
|
-
Analyse le ticket fourni et retourne:
|
|
216
|
-
- Une categorie (technical, billing, general, urgent)
|
|
217
|
-
- Un score de priorite (1-5)
|
|
218
|
-
- Un resume en une phrase
|
|
219
|
-
- Des tags pertinents
|
|
220
|
-
|
|
221
|
-
Reponds UNIQUEMENT en JSON valide.",
|
|
222
|
-
DisplayOrder = 1,
|
|
223
|
-
IsRequired = true
|
|
224
|
-
},
|
|
225
|
-
new CreateBlockRequest
|
|
226
|
-
{
|
|
227
|
-
BlockType = PromptBlockType.User,
|
|
228
|
-
Label = "Ticket",
|
|
229
|
-
Content = "Ticket: {{ticketTitle}}\nDescription: {{ticketDescription}}",
|
|
230
|
-
DisplayOrder = 2,
|
|
231
|
-
IsRequired = true
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
});
|
|
235
|
-
```
|
|
236
|
-
|
|
237
|
-
### ETAPE 3: Definir le Schema de Validation
|
|
238
|
-
|
|
239
|
-
```csharp
|
|
240
|
-
// JSON Schema pour validation automatique
|
|
241
|
-
var schemaId = await _schemaService.CreateAsync(new CreateSchemaRequest
|
|
242
|
-
{
|
|
243
|
-
Code = "ticket-analysis-result",
|
|
244
|
-
Name = "Ticket Analysis Result",
|
|
245
|
-
JsonSchema = @"{
|
|
246
|
-
""$schema"": ""http://json-schema.org/draft-07/schema#"",
|
|
247
|
-
""type"": ""object"",
|
|
248
|
-
""required"": [""category"", ""priority"", ""summary"", ""tags""],
|
|
249
|
-
""properties"": {
|
|
250
|
-
""category"": {
|
|
251
|
-
""type"": ""string"",
|
|
252
|
-
""enum"": [""technical"", ""billing"", ""general"", ""urgent""]
|
|
253
|
-
},
|
|
254
|
-
""priority"": {
|
|
255
|
-
""type"": ""integer"",
|
|
256
|
-
""minimum"": 1,
|
|
257
|
-
""maximum"": 5
|
|
258
|
-
},
|
|
259
|
-
""summary"": {
|
|
260
|
-
""type"": ""string"",
|
|
261
|
-
""maxLength"": 200
|
|
262
|
-
},
|
|
263
|
-
""tags"": {
|
|
264
|
-
""type"": ""array"",
|
|
265
|
-
""items"": { ""type"": ""string"" },
|
|
266
|
-
""maxItems"": 5
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
}",
|
|
270
|
-
DotNetType = "SmartStack.Application.AI.TicketAnalysisResult"
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
// Lier au prompt
|
|
274
|
-
await _promptService.UpdatePromptAsync(promptId, new UpdatePromptRequest
|
|
275
|
-
{
|
|
276
|
-
OutputSchemaId = schemaId
|
|
277
|
-
});
|
|
278
|
-
```
|
|
279
|
-
|
|
280
|
-
### ETAPE 4: Executer le Prompt
|
|
281
|
-
|
|
282
|
-
```csharp
|
|
283
|
-
// Execution simple (retourne string)
|
|
284
|
-
var result = await _aiCompletionService.ExecutePromptAsync(
|
|
285
|
-
promptId,
|
|
286
|
-
new Dictionary<string, object>
|
|
287
|
-
{
|
|
288
|
-
["ticketTitle"] = ticket.Title,
|
|
289
|
-
["ticketDescription"] = ticket.Description
|
|
290
|
-
});
|
|
291
|
-
|
|
292
|
-
if (result.Success)
|
|
293
|
-
{
|
|
294
|
-
var content = result.Content; // JSON string
|
|
295
|
-
_logger.LogInformation(
|
|
296
|
-
"AI completed in {Ms}ms, {Input}/{Output} tokens",
|
|
297
|
-
result.ExecutionTimeMs,
|
|
298
|
-
result.InputTokens,
|
|
299
|
-
result.OutputTokens);
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
// Execution avec validation typee
|
|
303
|
-
var typedResult = await _aiCompletionService
|
|
304
|
-
.ExecutePromptWithValidationAsync<TicketAnalysisResult>(
|
|
305
|
-
promptId,
|
|
306
|
-
new Dictionary<string, object>
|
|
307
|
-
{
|
|
308
|
-
["ticketTitle"] = ticket.Title,
|
|
309
|
-
["ticketDescription"] = ticket.Description
|
|
310
|
-
});
|
|
311
|
-
|
|
312
|
-
if (typedResult.Success && typedResult.IsValid)
|
|
313
|
-
{
|
|
314
|
-
var analysis = typedResult.Data; // TicketAnalysisResult type
|
|
315
|
-
ticket.SetCategory(analysis.Category);
|
|
316
|
-
ticket.SetPriority(analysis.Priority);
|
|
317
|
-
ticket.AddTags(analysis.Tags);
|
|
318
|
-
}
|
|
319
|
-
else if (!typedResult.IsValid)
|
|
320
|
-
{
|
|
321
|
-
_logger.LogWarning(
|
|
322
|
-
"AI response validation failed: {Errors}",
|
|
323
|
-
string.Join(", ", typedResult.ValidationErrors));
|
|
324
|
-
}
|
|
325
|
-
```
|
|
326
|
-
|
|
327
|
-
---
|
|
328
|
-
|
|
329
|
-
## SERVICES IA
|
|
330
|
-
|
|
331
|
-
### IAiCompletionService
|
|
332
|
-
|
|
333
|
-
```csharp
|
|
334
|
-
public interface IAiCompletionService
|
|
335
|
-
{
|
|
336
|
-
// Execution basique
|
|
337
|
-
Task<AiCompletionResult> CompleteAsync(
|
|
338
|
-
AiCompletionRequest request,
|
|
339
|
-
CancellationToken ct = default);
|
|
340
|
-
|
|
341
|
-
// Execution par prompt ID
|
|
342
|
-
Task<AiCompletionResult> ExecutePromptAsync(
|
|
343
|
-
Guid promptId,
|
|
344
|
-
Dictionary<string, object>? variables = null,
|
|
345
|
-
Guid? providerId = null,
|
|
346
|
-
Guid? modelId = null,
|
|
347
|
-
CancellationToken ct = default);
|
|
348
|
-
|
|
349
|
-
// Execution avec validation schema
|
|
350
|
-
Task<AiValidatedCompletionResult<T>> ExecutePromptWithValidationAsync<T>(
|
|
351
|
-
Guid promptId,
|
|
352
|
-
Dictionary<string, object>? variables = null,
|
|
353
|
-
Guid? providerId = null,
|
|
354
|
-
Guid? modelId = null,
|
|
355
|
-
CancellationToken ct = default);
|
|
356
|
-
|
|
357
|
-
// Par code de prompt
|
|
358
|
-
Task<AiValidatedCompletionResult<T>> ExecutePromptByCodeWithValidationAsync<T>(
|
|
359
|
-
string promptCode,
|
|
360
|
-
Dictionary<string, object>? variables = null,
|
|
361
|
-
Guid? providerId = null,
|
|
362
|
-
Guid? modelId = null,
|
|
363
|
-
CancellationToken ct = default);
|
|
364
|
-
|
|
365
|
-
// Provider systeme par defaut
|
|
366
|
-
Task<Guid?> GetSystemProviderIdAsync(CancellationToken ct = default);
|
|
367
|
-
}
|
|
368
|
-
```
|
|
369
|
-
|
|
370
|
-
### IPromptService
|
|
371
|
-
|
|
372
|
-
```csharp
|
|
373
|
-
public interface IPromptService
|
|
374
|
-
{
|
|
375
|
-
// CRUD Prompts
|
|
376
|
-
Task<List<PromptDto>> GetAllPromptsAsync(PromptStatus? status, bool? isTemplate);
|
|
377
|
-
Task<PromptDto> GetPromptByIdAsync(Guid promptId);
|
|
378
|
-
Task<PromptDto> GetPromptByCodeAsync(string code, string? version = null);
|
|
379
|
-
Task<PromptDto> GetActivePromptByCodeAsync(string code);
|
|
380
|
-
|
|
381
|
-
// Rendu
|
|
382
|
-
Task<string> RenderPromptAsync(string code, Dictionary<string, object>? variables = null);
|
|
383
|
-
|
|
384
|
-
// Lifecycle
|
|
385
|
-
Task<Guid> CreatePromptAsync(CreatePromptRequest request);
|
|
386
|
-
Task UpdatePromptAsync(Guid promptId, UpdatePromptRequest request);
|
|
387
|
-
Task<Guid> DuplicatePromptAsync(Guid promptId, string newVersion);
|
|
388
|
-
Task ActivatePromptAsync(Guid promptId);
|
|
389
|
-
Task DeactivatePromptAsync(Guid promptId);
|
|
390
|
-
Task DeprecatePromptAsync(Guid promptId);
|
|
391
|
-
Task DeletePromptAsync(Guid promptId);
|
|
392
|
-
|
|
393
|
-
// Blocks
|
|
394
|
-
Task AddBlockToPromptAsync(Guid promptId, CreateBlockRequest request);
|
|
395
|
-
Task UpdateBlockAsync(Guid blockId, UpdateBlockRequest request);
|
|
396
|
-
Task RemoveBlockFromPromptAsync(Guid promptId, Guid blockId);
|
|
397
|
-
Task ReorderBlocksAsync(Guid promptId, Guid[] orderedBlockIds);
|
|
398
|
-
}
|
|
399
|
-
```
|
|
400
|
-
|
|
401
|
-
---
|
|
402
|
-
|
|
403
|
-
## TEMPLATES INTEGRATION
|
|
404
|
-
|
|
405
|
-
### Template Service avec IA
|
|
406
|
-
|
|
407
|
-
```csharp
|
|
408
|
-
public class TicketAnalysisService : ITicketAnalysisService
|
|
409
|
-
{
|
|
410
|
-
private readonly IAiCompletionService _aiService;
|
|
411
|
-
private readonly ITicketRepository _ticketRepository;
|
|
412
|
-
private readonly ILogger<TicketAnalysisService> _logger;
|
|
413
|
-
|
|
414
|
-
public TicketAnalysisService(
|
|
415
|
-
IAiCompletionService aiService,
|
|
416
|
-
ITicketRepository ticketRepository,
|
|
417
|
-
ILogger<TicketAnalysisService> logger)
|
|
418
|
-
{
|
|
419
|
-
_aiService = aiService;
|
|
420
|
-
_ticketRepository = ticketRepository;
|
|
421
|
-
_logger = logger;
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
public async Task<TicketAnalysisResult?> AnalyzeTicketAsync(
|
|
425
|
-
Guid ticketId,
|
|
426
|
-
CancellationToken ct)
|
|
427
|
-
{
|
|
428
|
-
var ticket = await _ticketRepository.GetByIdAsync(ticketId, ct);
|
|
429
|
-
if (ticket == null) return null;
|
|
430
|
-
|
|
431
|
-
try
|
|
432
|
-
{
|
|
433
|
-
var result = await _aiService
|
|
434
|
-
.ExecutePromptByCodeWithValidationAsync<TicketAnalysisResult>(
|
|
435
|
-
"ticket-analyzer",
|
|
436
|
-
new Dictionary<string, object>
|
|
437
|
-
{
|
|
438
|
-
["ticketTitle"] = ticket.Title,
|
|
439
|
-
["ticketDescription"] = ticket.Description,
|
|
440
|
-
["ticketCategory"] = ticket.Category?.Name ?? "Unknown"
|
|
441
|
-
},
|
|
442
|
-
cancellationToken: ct);
|
|
443
|
-
|
|
444
|
-
if (result.Success && result.IsValid)
|
|
445
|
-
{
|
|
446
|
-
_logger.LogInformation(
|
|
447
|
-
"Ticket {TicketId} analyzed: category={Category}, priority={Priority}",
|
|
448
|
-
ticketId, result.Data.Category, result.Data.Priority);
|
|
449
|
-
|
|
450
|
-
return result.Data;
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
if (!result.Success)
|
|
454
|
-
{
|
|
455
|
-
_logger.LogError(
|
|
456
|
-
"AI analysis failed for ticket {TicketId}: {Error}",
|
|
457
|
-
ticketId, result.Error);
|
|
458
|
-
}
|
|
459
|
-
else if (!result.IsValid)
|
|
460
|
-
{
|
|
461
|
-
_logger.LogWarning(
|
|
462
|
-
"AI response invalid for ticket {TicketId}: {Errors}",
|
|
463
|
-
ticketId, string.Join(", ", result.ValidationErrors));
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
return null;
|
|
467
|
-
}
|
|
468
|
-
catch (Exception ex)
|
|
469
|
-
{
|
|
470
|
-
_logger.LogError(ex, "Exception analyzing ticket {TicketId}", ticketId);
|
|
471
|
-
return null;
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
```
|
|
476
|
-
|
|
477
|
-
### Template DTO Result
|
|
478
|
-
|
|
479
|
-
```csharp
|
|
480
|
-
// Application/AI/TicketAnalysisResult.cs
|
|
481
|
-
public class TicketAnalysisResult
|
|
482
|
-
{
|
|
483
|
-
[JsonPropertyName("category")]
|
|
484
|
-
public string Category { get; set; } = string.Empty;
|
|
485
|
-
|
|
486
|
-
[JsonPropertyName("priority")]
|
|
487
|
-
public int Priority { get; set; }
|
|
488
|
-
|
|
489
|
-
[JsonPropertyName("summary")]
|
|
490
|
-
public string Summary { get; set; } = string.Empty;
|
|
491
|
-
|
|
492
|
-
[JsonPropertyName("tags")]
|
|
493
|
-
public List<string> Tags { get; set; } = new();
|
|
494
|
-
|
|
495
|
-
[JsonPropertyName("confidence")]
|
|
496
|
-
public double? Confidence { get; set; }
|
|
497
|
-
}
|
|
498
|
-
```
|
|
499
|
-
|
|
500
|
-
### Template Controller
|
|
501
|
-
|
|
502
|
-
```csharp
|
|
503
|
-
[ApiController]
|
|
504
|
-
[Route("api/[controller]")]
|
|
505
|
-
public class TicketAnalysisController : ControllerBase
|
|
506
|
-
{
|
|
507
|
-
private readonly ITicketAnalysisService _analysisService;
|
|
508
|
-
|
|
509
|
-
[HttpPost("{ticketId}/analyze")]
|
|
510
|
-
[RequirePermission(Permissions.Support.Tickets.Update)]
|
|
511
|
-
public async Task<ActionResult<TicketAnalysisResult>> AnalyzeTicket(
|
|
512
|
-
Guid ticketId,
|
|
513
|
-
CancellationToken ct)
|
|
514
|
-
{
|
|
515
|
-
var result = await _analysisService.AnalyzeTicketAsync(ticketId, ct);
|
|
516
|
-
|
|
517
|
-
if (result == null)
|
|
518
|
-
return BadRequest(new { message = "Analysis failed" });
|
|
519
|
-
|
|
520
|
-
return Ok(result);
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
```
|
|
524
|
-
|
|
525
|
-
---
|
|
526
|
-
|
|
527
|
-
## FRONTEND INTEGRATION
|
|
528
|
-
|
|
529
|
-
### API IA
|
|
530
|
-
|
|
531
|
-
```typescript
|
|
532
|
-
// services/api/aiApi.ts
|
|
533
|
-
export const aiApi = {
|
|
534
|
-
// Prompts
|
|
535
|
-
getPrompts: (status?: PromptStatus, isTemplate?: boolean) =>
|
|
536
|
-
apiClient.get<PromptDto[]>('/admin/ai/prompts', { params: { status, isTemplate } }),
|
|
537
|
-
|
|
538
|
-
getPromptById: (id: string) =>
|
|
539
|
-
apiClient.get<PromptDto>(`/admin/ai/prompts/${id}`),
|
|
540
|
-
|
|
541
|
-
createPrompt: (data: CreatePromptRequest) =>
|
|
542
|
-
apiClient.post<{ id: string }>('/admin/ai/prompts', data),
|
|
543
|
-
|
|
544
|
-
// Execution
|
|
545
|
-
executePrompt: (promptId: string, variables: Record<string, unknown>) =>
|
|
546
|
-
apiClient.post<AiCompletionResult>(`/admin/ai/prompts/${promptId}/execute`, { variables }),
|
|
547
|
-
|
|
548
|
-
previewPrompt: (promptId: string, variables: Record<string, unknown>) =>
|
|
549
|
-
apiClient.post<{ rendered: string }>(`/admin/ai/prompts/${promptId}/preview`, { variables }),
|
|
550
|
-
|
|
551
|
-
// Providers
|
|
552
|
-
getProviders: () =>
|
|
553
|
-
apiClient.get<AiProviderDto[]>('/admin/ai/providers'),
|
|
554
|
-
|
|
555
|
-
getModels: (providerId?: string) =>
|
|
556
|
-
apiClient.get<AiModelDto[]>('/admin/ai/models', { params: { providerId } }),
|
|
557
|
-
|
|
558
|
-
// Schemas
|
|
559
|
-
getSchemas: () =>
|
|
560
|
-
apiClient.get<OutputSchemaDto[]>('/admin/ai/schemas'),
|
|
561
|
-
|
|
562
|
-
createSchema: (data: CreateSchemaRequest) =>
|
|
563
|
-
apiClient.post<{ id: string }>('/admin/ai/schemas', data),
|
|
564
|
-
};
|
|
565
|
-
```
|
|
566
|
-
|
|
567
|
-
### Hook useAiCompletion
|
|
568
|
-
|
|
569
|
-
```typescript
|
|
570
|
-
// hooks/useAiCompletion.ts
|
|
571
|
-
import { useState } from 'react';
|
|
572
|
-
import { aiApi } from '@/services/api/aiApi';
|
|
573
|
-
|
|
574
|
-
export function useAiCompletion<T = unknown>(promptCode: string) {
|
|
575
|
-
const [loading, setLoading] = useState(false);
|
|
576
|
-
const [error, setError] = useState<string | null>(null);
|
|
577
|
-
const [result, setResult] = useState<T | null>(null);
|
|
578
|
-
|
|
579
|
-
const execute = async (variables: Record<string, unknown>) => {
|
|
580
|
-
setLoading(true);
|
|
581
|
-
setError(null);
|
|
582
|
-
|
|
583
|
-
try {
|
|
584
|
-
const response = await aiApi.executePrompt(promptCode, variables);
|
|
585
|
-
|
|
586
|
-
if (response.success && response.isValid) {
|
|
587
|
-
setResult(response.data as T);
|
|
588
|
-
return response.data as T;
|
|
589
|
-
} else {
|
|
590
|
-
setError(response.error || 'Validation failed');
|
|
591
|
-
return null;
|
|
592
|
-
}
|
|
593
|
-
} catch (err) {
|
|
594
|
-
setError(err instanceof Error ? err.message : 'Unknown error');
|
|
595
|
-
return null;
|
|
596
|
-
} finally {
|
|
597
|
-
setLoading(false);
|
|
598
|
-
}
|
|
599
|
-
};
|
|
600
|
-
|
|
601
|
-
return { execute, loading, error, result };
|
|
602
|
-
}
|
|
603
|
-
```
|
|
604
|
-
|
|
605
|
-
### Composant AI Assistant
|
|
606
|
-
|
|
607
|
-
```tsx
|
|
608
|
-
// components/ai/AiAssistantButton.tsx
|
|
609
|
-
import { Sparkles, Loader2 } from 'lucide-react';
|
|
610
|
-
import { useAiCompletion } from '@/hooks/useAiCompletion';
|
|
611
|
-
|
|
612
|
-
interface AiAssistantButtonProps {
|
|
613
|
-
promptCode: string;
|
|
614
|
-
variables: Record<string, unknown>;
|
|
615
|
-
onResult: (result: unknown) => void;
|
|
616
|
-
label?: string;
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
export function AiAssistantButton({
|
|
620
|
-
promptCode,
|
|
621
|
-
variables,
|
|
622
|
-
onResult,
|
|
623
|
-
label = 'Analyser avec IA'
|
|
624
|
-
}: AiAssistantButtonProps) {
|
|
625
|
-
const { execute, loading, error } = useAiCompletion(promptCode);
|
|
626
|
-
|
|
627
|
-
const handleClick = async () => {
|
|
628
|
-
const result = await execute(variables);
|
|
629
|
-
if (result) {
|
|
630
|
-
onResult(result);
|
|
631
|
-
}
|
|
632
|
-
};
|
|
633
|
-
|
|
634
|
-
return (
|
|
635
|
-
<div>
|
|
636
|
-
<button
|
|
637
|
-
onClick={handleClick}
|
|
638
|
-
disabled={loading}
|
|
639
|
-
className="inline-flex items-center gap-2 px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 disabled:opacity-50"
|
|
640
|
-
>
|
|
641
|
-
{loading ? (
|
|
642
|
-
<Loader2 className="w-4 h-4 animate-spin" />
|
|
643
|
-
) : (
|
|
644
|
-
<Sparkles className="w-4 h-4" />
|
|
645
|
-
)}
|
|
646
|
-
{label}
|
|
647
|
-
</button>
|
|
648
|
-
{error && (
|
|
649
|
-
<p className="mt-2 text-sm text-red-500">{error}</p>
|
|
650
|
-
)}
|
|
651
|
-
</div>
|
|
652
|
-
);
|
|
653
|
-
}
|
|
654
|
-
```
|
|
655
|
-
|
|
656
|
-
---
|
|
657
|
-
|
|
658
|
-
## PATTERNS AVANCES
|
|
659
|
-
|
|
660
|
-
### Pattern: Streaming Response
|
|
661
|
-
|
|
662
|
-
```typescript
|
|
663
|
-
// Pour les reponses longues, utiliser le streaming
|
|
664
|
-
async function streamCompletion(promptId: string, variables: Record<string, unknown>) {
|
|
665
|
-
const response = await fetch(`/api/ai/prompts/${promptId}/stream`, {
|
|
666
|
-
method: 'POST',
|
|
667
|
-
headers: { 'Content-Type': 'application/json' },
|
|
668
|
-
body: JSON.stringify({ variables })
|
|
669
|
-
});
|
|
670
|
-
|
|
671
|
-
const reader = response.body?.getReader();
|
|
672
|
-
const decoder = new TextDecoder();
|
|
673
|
-
|
|
674
|
-
while (true) {
|
|
675
|
-
const { done, value } = await reader!.read();
|
|
676
|
-
if (done) break;
|
|
677
|
-
|
|
678
|
-
const chunk = decoder.decode(value);
|
|
679
|
-
// Afficher progressivement
|
|
680
|
-
appendToOutput(chunk);
|
|
681
|
-
}
|
|
682
|
-
}
|
|
683
|
-
```
|
|
684
|
-
|
|
685
|
-
### Pattern: Fallback Provider
|
|
686
|
-
|
|
687
|
-
```csharp
|
|
688
|
-
// Si le provider principal echoue, fallback sur un autre
|
|
689
|
-
public async Task<AiCompletionResult> CompleteWithFallbackAsync(
|
|
690
|
-
Guid promptId,
|
|
691
|
-
Dictionary<string, object> variables)
|
|
692
|
-
{
|
|
693
|
-
var providers = await _providerService.GetActiveProvidersAsync();
|
|
694
|
-
|
|
695
|
-
foreach (var provider in providers.OrderBy(p => p.Priority))
|
|
696
|
-
{
|
|
697
|
-
try
|
|
698
|
-
{
|
|
699
|
-
var result = await _aiService.ExecutePromptAsync(
|
|
700
|
-
promptId, variables, providerId: provider.Id);
|
|
701
|
-
|
|
702
|
-
if (result.Success)
|
|
703
|
-
return result;
|
|
704
|
-
}
|
|
705
|
-
catch (Exception ex)
|
|
706
|
-
{
|
|
707
|
-
_logger.LogWarning(ex,
|
|
708
|
-
"Provider {Provider} failed, trying next",
|
|
709
|
-
provider.Name);
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
return new AiCompletionResult { Success = false, Error = "All providers failed" };
|
|
714
|
-
}
|
|
715
|
-
```
|
|
716
|
-
|
|
717
|
-
---
|
|
718
|
-
|
|
719
|
-
## CHECKLIST INTEGRATION IA
|
|
720
|
-
|
|
721
|
-
```
|
|
722
|
-
□ Use case defini (generation, analyse, classification)
|
|
723
|
-
□ Prompt cree avec:
|
|
724
|
-
□ Code unique
|
|
725
|
-
□ Blocks structures (System, User, etc.)
|
|
726
|
-
□ Variables documentees
|
|
727
|
-
□ Version definie
|
|
728
|
-
□ OutputSchema cree si reponse structuree
|
|
729
|
-
□ Service cree avec injection IAiCompletionService
|
|
730
|
-
□ Gestion erreurs implementee
|
|
731
|
-
□ Logging des executions (tokens, temps, erreurs)
|
|
732
|
-
□ Frontend:
|
|
733
|
-
□ Hook ou composant pour l'execution
|
|
734
|
-
□ Loading state
|
|
735
|
-
□ Error handling
|
|
736
|
-
□ Affichage resultat
|
|
737
|
-
□ Tests:
|
|
738
|
-
□ Prompt execute correctement
|
|
739
|
-
□ Variables substituees
|
|
740
|
-
□ Schema valide la reponse
|
|
741
|
-
□ Fallback fonctionne
|
|
742
|
-
□ Budget/Usage:
|
|
743
|
-
□ ProviderInstance avec limite
|
|
744
|
-
□ Tracking des tokens
|
|
745
|
-
```
|
|
746
|
-
|
|
747
|
-
---
|
|
748
|
-
|
|
749
|
-
## REGLES ABSOLUES
|
|
750
|
-
|
|
751
|
-
1. **TOUJOURS** utiliser IAiCompletionService (jamais appel API direct)
|
|
752
|
-
2. **TOUJOURS** definir un OutputSchema pour les reponses structurees
|
|
753
|
-
3. **TOUJOURS** gerer les erreurs (API down, quota depasse, validation)
|
|
754
|
-
4. **TOUJOURS** logger les executions avec tokens et duree
|
|
755
|
-
5. **TOUJOURS** specifier la version du prompt
|
|
756
|
-
6. **TOUJOURS** utiliser des variables pour le contenu dynamique
|
|
757
|
-
7. **JAMAIS** hardcoder les cles API
|
|
758
|
-
8. **JAMAIS** exposer les prompts systeme au frontend
|
|
759
|
-
9. **JAMAIS** ignorer les limites de tokens
|
|
760
|
-
10. **JAMAIS** faire confiance aux reponses sans validation
|
|
761
|
-
|
|
762
|
-
---
|
|
763
|
-
|
|
764
|
-
## FICHIERS CLES
|
|
765
|
-
|
|
766
|
-
| Fichier | Role |
|
|
767
|
-
|---------|------|
|
|
768
|
-
| `Domain/AI/Prompts/Prompt.cs` | Entite prompt |
|
|
769
|
-
| `Domain/AI/Prompts/PromptBlock.cs` | Blocks du prompt |
|
|
770
|
-
| `Domain/AI/Schemas/OutputSchema.cs` | Schema validation |
|
|
771
|
-
| `Domain/AI/AiProvider.cs` | Provider IA |
|
|
772
|
-
| `Domain/AI/AiModel.cs` | Modele IA |
|
|
773
|
-
| `Domain/AI/AiProviderInstance.cs` | Instance configuree |
|
|
774
|
-
| `Application/Common/Interfaces/IAiCompletionService.cs` | Interface service |
|
|
775
|
-
| `Application/Common/Interfaces/IPromptService.cs` | Interface prompts |
|
|
776
|
-
| `Infrastructure/Services/AI/AiCompletionService.cs` | Implementation |
|
|
777
|
-
| `Infrastructure/Services/AI/PromptService.cs` | Implementation prompts |
|
|
778
|
-
| `web/src/services/api/aiApi.ts` | API frontend |
|
|
1
|
+
---
|
|
2
|
+
name: ai-prompt
|
|
3
|
+
description: |
|
|
4
|
+
Integre les capacites IA dans les features SmartStack.
|
|
5
|
+
Utiliser ce skill quand:
|
|
6
|
+
- L'utilisateur veut integrer l'IA dans une fonctionnalite
|
|
7
|
+
- L'utilisateur mentionne "prompt", "GPT", "Claude", "IA", "AI"
|
|
8
|
+
- Creation d'un assistant ou chatbot
|
|
9
|
+
- Generation de contenu automatique
|
|
10
|
+
- Validation de reponses IA avec schema
|
|
11
|
+
Types: Prompt, OutputSchema, Provider, Model
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Skill AI Prompt SmartStack
|
|
15
|
+
|
|
16
|
+
> **Architecture:** Prompt + Blocks → Provider + Model → Completion → Schema Validation
|
|
17
|
+
> Multi-provider: OpenAI, Anthropic (Claude), Azure OpenAI, Google Gemini
|
|
18
|
+
|
|
19
|
+
## QUAND CE SKILL S'ACTIVE
|
|
20
|
+
|
|
21
|
+
Claude invoque automatiquement ce skill quand il detecte :
|
|
22
|
+
|
|
23
|
+
| Declencheur | Exemple |
|
|
24
|
+
|-------------|---------|
|
|
25
|
+
| Demande explicite | "Integre GPT pour generer des descriptions" |
|
|
26
|
+
| Chatbot/Assistant | "Cree un assistant pour le support" |
|
|
27
|
+
| Generation contenu | "Genere automatiquement des reponses" |
|
|
28
|
+
| Analyse texte | "Analyse le sentiment des commentaires" |
|
|
29
|
+
| Mots-cles | "prompt", "GPT", "Claude", "IA", "completion", "LLM" |
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## ARCHITECTURE IA
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
37
|
+
│ AI COMPLETION FLOW │
|
|
38
|
+
├─────────────────────────────────────────────────────────────────────────────┤
|
|
39
|
+
│ │
|
|
40
|
+
│ [FEATURE] ─────────────────────────────────────────────────────────────┐ │
|
|
41
|
+
│ │ │ │
|
|
42
|
+
│ ▼ │ │
|
|
43
|
+
│ ┌─────────────────────────────┐ │ │
|
|
44
|
+
│ │ IAiCompletionService │ │ │
|
|
45
|
+
│ │ ExecutePromptAsync() │ │ │
|
|
46
|
+
│ └─────────────┬───────────────┘ │ │
|
|
47
|
+
│ │ │ │
|
|
48
|
+
│ ┌────────┴────────┐ │ │
|
|
49
|
+
│ ▼ ▼ │ │
|
|
50
|
+
│ ┌─────────────┐ ┌─────────────────┐ │ │
|
|
51
|
+
│ │ Prompt │ │ Provider │ │ │
|
|
52
|
+
│ │ + Blocks │ │ Instance │ │ │
|
|
53
|
+
│ └──────┬──────┘ └────────┬────────┘ │ │
|
|
54
|
+
│ │ │ │ │
|
|
55
|
+
│ ▼ ▼ │ │
|
|
56
|
+
│ ┌─────────────────────────────────────┐ │ │
|
|
57
|
+
│ │ RENDERED PROMPT │ │ │
|
|
58
|
+
│ │ System: {{instructions}} │ │ │
|
|
59
|
+
│ │ User: {{userInput}} │ │ │
|
|
60
|
+
│ └─────────────────┬───────────────────┘ │ │
|
|
61
|
+
│ │ │ │
|
|
62
|
+
│ ▼ │ │
|
|
63
|
+
│ ┌─────────────────────────────────────┐ │ │
|
|
64
|
+
│ │ EXTERNAL API │ │ │
|
|
65
|
+
│ │ OpenAI / Claude / Azure / Gemini │ │ │
|
|
66
|
+
│ └─────────────────┬───────────────────┘ │ │
|
|
67
|
+
│ │ │ │
|
|
68
|
+
│ ▼ │ │
|
|
69
|
+
│ ┌─────────────────────────────────────┐ │ │
|
|
70
|
+
│ │ AI RESPONSE │ │ │
|
|
71
|
+
│ │ { "content": "...", ... } │ │ │
|
|
72
|
+
│ └─────────────────┬───────────────────┘ │ │
|
|
73
|
+
│ │ │ │
|
|
74
|
+
│ ┌────────────┴────────────┐ │ │
|
|
75
|
+
│ ▼ ▼ │ │
|
|
76
|
+
│ ┌─────────────┐ ┌─────────────────┐ │ │
|
|
77
|
+
│ │ RAW │ │ OutputSchema │ │ │
|
|
78
|
+
│ │ Response │ │ Validation │ │ │
|
|
79
|
+
│ └─────────────┘ └────────┬────────┘ │ │
|
|
80
|
+
│ │ │ │
|
|
81
|
+
│ ▼ │ │
|
|
82
|
+
│ ┌─────────────────┐ │ │
|
|
83
|
+
│ │ Typed Result<T> │ │ │
|
|
84
|
+
│ │ + Validation │ │ │
|
|
85
|
+
│ └─────────────────┘ │ │
|
|
86
|
+
│ │ │
|
|
87
|
+
└────────────────────────────────────────────────────────────────────────────┘
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## ENTITES PRINCIPALES
|
|
93
|
+
|
|
94
|
+
### AiProvider
|
|
95
|
+
|
|
96
|
+
```csharp
|
|
97
|
+
// Providers disponibles (seed data)
|
|
98
|
+
- OpenAI (gpt-4o, gpt-4-turbo, gpt-3.5-turbo)
|
|
99
|
+
- Anthropic (claude-3-opus, claude-3-sonnet, claude-3-haiku)
|
|
100
|
+
- Azure OpenAI (deploiements custom)
|
|
101
|
+
- Google (gemini-pro, gemini-1.5-pro)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### AiModel
|
|
105
|
+
|
|
106
|
+
```csharp
|
|
107
|
+
// Proprietes cles
|
|
108
|
+
public string Code { get; } // "gpt-4o"
|
|
109
|
+
public string ShortCode { get; } // "GPT4O"
|
|
110
|
+
public ModelCategory Category { get; } // TextGeneration, Embedding, etc.
|
|
111
|
+
public int ContextWindow { get; } // 128000
|
|
112
|
+
public int MaxOutputTokens { get; } // 16384
|
|
113
|
+
public decimal InputCostPerMillion { get; }
|
|
114
|
+
public decimal OutputCostPerMillion { get; }
|
|
115
|
+
|
|
116
|
+
// Capabilities
|
|
117
|
+
public bool SupportsVision { get; }
|
|
118
|
+
public bool SupportsFunctionCalling { get; }
|
|
119
|
+
public bool SupportsStreaming { get; }
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### AiProviderInstance
|
|
123
|
+
|
|
124
|
+
```csharp
|
|
125
|
+
// Instance configuree pour un use-case
|
|
126
|
+
public string Code { get; } // "support-assistant"
|
|
127
|
+
public string Name { get; } // "Assistant Support"
|
|
128
|
+
public string SystemContext { get; } // Prompt systeme personnalise
|
|
129
|
+
public string DefaultModel { get; } // "gpt-4o"
|
|
130
|
+
public decimal MonthlyBudgetLimit { get; }
|
|
131
|
+
public decimal CurrentMonthUsage { get; }
|
|
132
|
+
|
|
133
|
+
// Securite
|
|
134
|
+
public string EncryptedApiKey { get; } // Cle chiffree
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Prompt
|
|
138
|
+
|
|
139
|
+
```csharp
|
|
140
|
+
// Structure d'un prompt
|
|
141
|
+
public string Code { get; } // "ticket-analyzer"
|
|
142
|
+
public string Name { get; } // "Ticket Analyzer"
|
|
143
|
+
public string Version { get; } // "1.0.0"
|
|
144
|
+
public PromptStatus Status { get; } // Draft, Active, Deprecated
|
|
145
|
+
public bool IsTemplate { get; } // Template reutilisable
|
|
146
|
+
public Guid? OutputSchemaId { get; } // Schema de validation
|
|
147
|
+
|
|
148
|
+
// Blocks = Sections du prompt
|
|
149
|
+
public ICollection<PromptBlock> Blocks { get; }
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### PromptBlock
|
|
153
|
+
|
|
154
|
+
```csharp
|
|
155
|
+
// Types de blocks
|
|
156
|
+
public enum PromptBlockType
|
|
157
|
+
{
|
|
158
|
+
System, // Instructions systeme
|
|
159
|
+
User, // Message utilisateur
|
|
160
|
+
Assistant, // Reponse exemple
|
|
161
|
+
Tool, // Definition d'outil
|
|
162
|
+
Context, // Contexte additionnel
|
|
163
|
+
Examples // Exemples few-shot
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Proprietes
|
|
167
|
+
public string Label { get; } // "Instructions"
|
|
168
|
+
public string Content { get; } // "Tu es un assistant..."
|
|
169
|
+
public int DisplayOrder { get; }
|
|
170
|
+
public bool IsRequired { get; }
|
|
171
|
+
public string Condition { get; } // Condition d'inclusion
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### OutputSchema
|
|
175
|
+
|
|
176
|
+
```csharp
|
|
177
|
+
// Schema JSON pour validation
|
|
178
|
+
public string Code { get; } // "ticket-analysis"
|
|
179
|
+
public string Name { get; } // "Ticket Analysis Schema"
|
|
180
|
+
public string JsonSchema { get; } // JSON Schema valide
|
|
181
|
+
public string DotNetType { get; } // "SmartStack.Application.AI.TicketAnalysisResult"
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## WORKFLOW INTEGRATION IA
|
|
187
|
+
|
|
188
|
+
### ETAPE 1: Definir le Use Case
|
|
189
|
+
|
|
190
|
+
| Question | Impact |
|
|
191
|
+
|----------|--------|
|
|
192
|
+
| Quel type de contenu generer ? | Choix du modele |
|
|
193
|
+
| Reponse structuree requise ? | OutputSchema |
|
|
194
|
+
| Multi-langue ? | Variables langue |
|
|
195
|
+
| Budget limite ? | ProviderInstance avec limit |
|
|
196
|
+
| Temps reel requis ? | Streaming |
|
|
197
|
+
|
|
198
|
+
### ETAPE 2: Creer le Prompt
|
|
199
|
+
|
|
200
|
+
```csharp
|
|
201
|
+
// Via IPromptService
|
|
202
|
+
var promptId = await _promptService.CreatePromptAsync(new CreatePromptRequest
|
|
203
|
+
{
|
|
204
|
+
Code = "ticket-analyzer",
|
|
205
|
+
Name = "Ticket Analyzer",
|
|
206
|
+
Description = "Analyse les tickets support pour categorisation",
|
|
207
|
+
IsTemplate = false,
|
|
208
|
+
Blocks = new[]
|
|
209
|
+
{
|
|
210
|
+
new CreateBlockRequest
|
|
211
|
+
{
|
|
212
|
+
BlockType = PromptBlockType.System,
|
|
213
|
+
Label = "Instructions",
|
|
214
|
+
Content = @"Tu es un expert en support technique.
|
|
215
|
+
Analyse le ticket fourni et retourne:
|
|
216
|
+
- Une categorie (technical, billing, general, urgent)
|
|
217
|
+
- Un score de priorite (1-5)
|
|
218
|
+
- Un resume en une phrase
|
|
219
|
+
- Des tags pertinents
|
|
220
|
+
|
|
221
|
+
Reponds UNIQUEMENT en JSON valide.",
|
|
222
|
+
DisplayOrder = 1,
|
|
223
|
+
IsRequired = true
|
|
224
|
+
},
|
|
225
|
+
new CreateBlockRequest
|
|
226
|
+
{
|
|
227
|
+
BlockType = PromptBlockType.User,
|
|
228
|
+
Label = "Ticket",
|
|
229
|
+
Content = "Ticket: {{ticketTitle}}\nDescription: {{ticketDescription}}",
|
|
230
|
+
DisplayOrder = 2,
|
|
231
|
+
IsRequired = true
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### ETAPE 3: Definir le Schema de Validation
|
|
238
|
+
|
|
239
|
+
```csharp
|
|
240
|
+
// JSON Schema pour validation automatique
|
|
241
|
+
var schemaId = await _schemaService.CreateAsync(new CreateSchemaRequest
|
|
242
|
+
{
|
|
243
|
+
Code = "ticket-analysis-result",
|
|
244
|
+
Name = "Ticket Analysis Result",
|
|
245
|
+
JsonSchema = @"{
|
|
246
|
+
""$schema"": ""http://json-schema.org/draft-07/schema#"",
|
|
247
|
+
""type"": ""object"",
|
|
248
|
+
""required"": [""category"", ""priority"", ""summary"", ""tags""],
|
|
249
|
+
""properties"": {
|
|
250
|
+
""category"": {
|
|
251
|
+
""type"": ""string"",
|
|
252
|
+
""enum"": [""technical"", ""billing"", ""general"", ""urgent""]
|
|
253
|
+
},
|
|
254
|
+
""priority"": {
|
|
255
|
+
""type"": ""integer"",
|
|
256
|
+
""minimum"": 1,
|
|
257
|
+
""maximum"": 5
|
|
258
|
+
},
|
|
259
|
+
""summary"": {
|
|
260
|
+
""type"": ""string"",
|
|
261
|
+
""maxLength"": 200
|
|
262
|
+
},
|
|
263
|
+
""tags"": {
|
|
264
|
+
""type"": ""array"",
|
|
265
|
+
""items"": { ""type"": ""string"" },
|
|
266
|
+
""maxItems"": 5
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}",
|
|
270
|
+
DotNetType = "SmartStack.Application.AI.TicketAnalysisResult"
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
// Lier au prompt
|
|
274
|
+
await _promptService.UpdatePromptAsync(promptId, new UpdatePromptRequest
|
|
275
|
+
{
|
|
276
|
+
OutputSchemaId = schemaId
|
|
277
|
+
});
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### ETAPE 4: Executer le Prompt
|
|
281
|
+
|
|
282
|
+
```csharp
|
|
283
|
+
// Execution simple (retourne string)
|
|
284
|
+
var result = await _aiCompletionService.ExecutePromptAsync(
|
|
285
|
+
promptId,
|
|
286
|
+
new Dictionary<string, object>
|
|
287
|
+
{
|
|
288
|
+
["ticketTitle"] = ticket.Title,
|
|
289
|
+
["ticketDescription"] = ticket.Description
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
if (result.Success)
|
|
293
|
+
{
|
|
294
|
+
var content = result.Content; // JSON string
|
|
295
|
+
_logger.LogInformation(
|
|
296
|
+
"AI completed in {Ms}ms, {Input}/{Output} tokens",
|
|
297
|
+
result.ExecutionTimeMs,
|
|
298
|
+
result.InputTokens,
|
|
299
|
+
result.OutputTokens);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Execution avec validation typee
|
|
303
|
+
var typedResult = await _aiCompletionService
|
|
304
|
+
.ExecutePromptWithValidationAsync<TicketAnalysisResult>(
|
|
305
|
+
promptId,
|
|
306
|
+
new Dictionary<string, object>
|
|
307
|
+
{
|
|
308
|
+
["ticketTitle"] = ticket.Title,
|
|
309
|
+
["ticketDescription"] = ticket.Description
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
if (typedResult.Success && typedResult.IsValid)
|
|
313
|
+
{
|
|
314
|
+
var analysis = typedResult.Data; // TicketAnalysisResult type
|
|
315
|
+
ticket.SetCategory(analysis.Category);
|
|
316
|
+
ticket.SetPriority(analysis.Priority);
|
|
317
|
+
ticket.AddTags(analysis.Tags);
|
|
318
|
+
}
|
|
319
|
+
else if (!typedResult.IsValid)
|
|
320
|
+
{
|
|
321
|
+
_logger.LogWarning(
|
|
322
|
+
"AI response validation failed: {Errors}",
|
|
323
|
+
string.Join(", ", typedResult.ValidationErrors));
|
|
324
|
+
}
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
---
|
|
328
|
+
|
|
329
|
+
## SERVICES IA
|
|
330
|
+
|
|
331
|
+
### IAiCompletionService
|
|
332
|
+
|
|
333
|
+
```csharp
|
|
334
|
+
public interface IAiCompletionService
|
|
335
|
+
{
|
|
336
|
+
// Execution basique
|
|
337
|
+
Task<AiCompletionResult> CompleteAsync(
|
|
338
|
+
AiCompletionRequest request,
|
|
339
|
+
CancellationToken ct = default);
|
|
340
|
+
|
|
341
|
+
// Execution par prompt ID
|
|
342
|
+
Task<AiCompletionResult> ExecutePromptAsync(
|
|
343
|
+
Guid promptId,
|
|
344
|
+
Dictionary<string, object>? variables = null,
|
|
345
|
+
Guid? providerId = null,
|
|
346
|
+
Guid? modelId = null,
|
|
347
|
+
CancellationToken ct = default);
|
|
348
|
+
|
|
349
|
+
// Execution avec validation schema
|
|
350
|
+
Task<AiValidatedCompletionResult<T>> ExecutePromptWithValidationAsync<T>(
|
|
351
|
+
Guid promptId,
|
|
352
|
+
Dictionary<string, object>? variables = null,
|
|
353
|
+
Guid? providerId = null,
|
|
354
|
+
Guid? modelId = null,
|
|
355
|
+
CancellationToken ct = default);
|
|
356
|
+
|
|
357
|
+
// Par code de prompt
|
|
358
|
+
Task<AiValidatedCompletionResult<T>> ExecutePromptByCodeWithValidationAsync<T>(
|
|
359
|
+
string promptCode,
|
|
360
|
+
Dictionary<string, object>? variables = null,
|
|
361
|
+
Guid? providerId = null,
|
|
362
|
+
Guid? modelId = null,
|
|
363
|
+
CancellationToken ct = default);
|
|
364
|
+
|
|
365
|
+
// Provider systeme par defaut
|
|
366
|
+
Task<Guid?> GetSystemProviderIdAsync(CancellationToken ct = default);
|
|
367
|
+
}
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
### IPromptService
|
|
371
|
+
|
|
372
|
+
```csharp
|
|
373
|
+
public interface IPromptService
|
|
374
|
+
{
|
|
375
|
+
// CRUD Prompts
|
|
376
|
+
Task<List<PromptDto>> GetAllPromptsAsync(PromptStatus? status, bool? isTemplate);
|
|
377
|
+
Task<PromptDto> GetPromptByIdAsync(Guid promptId);
|
|
378
|
+
Task<PromptDto> GetPromptByCodeAsync(string code, string? version = null);
|
|
379
|
+
Task<PromptDto> GetActivePromptByCodeAsync(string code);
|
|
380
|
+
|
|
381
|
+
// Rendu
|
|
382
|
+
Task<string> RenderPromptAsync(string code, Dictionary<string, object>? variables = null);
|
|
383
|
+
|
|
384
|
+
// Lifecycle
|
|
385
|
+
Task<Guid> CreatePromptAsync(CreatePromptRequest request);
|
|
386
|
+
Task UpdatePromptAsync(Guid promptId, UpdatePromptRequest request);
|
|
387
|
+
Task<Guid> DuplicatePromptAsync(Guid promptId, string newVersion);
|
|
388
|
+
Task ActivatePromptAsync(Guid promptId);
|
|
389
|
+
Task DeactivatePromptAsync(Guid promptId);
|
|
390
|
+
Task DeprecatePromptAsync(Guid promptId);
|
|
391
|
+
Task DeletePromptAsync(Guid promptId);
|
|
392
|
+
|
|
393
|
+
// Blocks
|
|
394
|
+
Task AddBlockToPromptAsync(Guid promptId, CreateBlockRequest request);
|
|
395
|
+
Task UpdateBlockAsync(Guid blockId, UpdateBlockRequest request);
|
|
396
|
+
Task RemoveBlockFromPromptAsync(Guid promptId, Guid blockId);
|
|
397
|
+
Task ReorderBlocksAsync(Guid promptId, Guid[] orderedBlockIds);
|
|
398
|
+
}
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
---
|
|
402
|
+
|
|
403
|
+
## TEMPLATES INTEGRATION
|
|
404
|
+
|
|
405
|
+
### Template Service avec IA
|
|
406
|
+
|
|
407
|
+
```csharp
|
|
408
|
+
public class TicketAnalysisService : ITicketAnalysisService
|
|
409
|
+
{
|
|
410
|
+
private readonly IAiCompletionService _aiService;
|
|
411
|
+
private readonly ITicketRepository _ticketRepository;
|
|
412
|
+
private readonly ILogger<TicketAnalysisService> _logger;
|
|
413
|
+
|
|
414
|
+
public TicketAnalysisService(
|
|
415
|
+
IAiCompletionService aiService,
|
|
416
|
+
ITicketRepository ticketRepository,
|
|
417
|
+
ILogger<TicketAnalysisService> logger)
|
|
418
|
+
{
|
|
419
|
+
_aiService = aiService;
|
|
420
|
+
_ticketRepository = ticketRepository;
|
|
421
|
+
_logger = logger;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
public async Task<TicketAnalysisResult?> AnalyzeTicketAsync(
|
|
425
|
+
Guid ticketId,
|
|
426
|
+
CancellationToken ct)
|
|
427
|
+
{
|
|
428
|
+
var ticket = await _ticketRepository.GetByIdAsync(ticketId, ct);
|
|
429
|
+
if (ticket == null) return null;
|
|
430
|
+
|
|
431
|
+
try
|
|
432
|
+
{
|
|
433
|
+
var result = await _aiService
|
|
434
|
+
.ExecutePromptByCodeWithValidationAsync<TicketAnalysisResult>(
|
|
435
|
+
"ticket-analyzer",
|
|
436
|
+
new Dictionary<string, object>
|
|
437
|
+
{
|
|
438
|
+
["ticketTitle"] = ticket.Title,
|
|
439
|
+
["ticketDescription"] = ticket.Description,
|
|
440
|
+
["ticketCategory"] = ticket.Category?.Name ?? "Unknown"
|
|
441
|
+
},
|
|
442
|
+
cancellationToken: ct);
|
|
443
|
+
|
|
444
|
+
if (result.Success && result.IsValid)
|
|
445
|
+
{
|
|
446
|
+
_logger.LogInformation(
|
|
447
|
+
"Ticket {TicketId} analyzed: category={Category}, priority={Priority}",
|
|
448
|
+
ticketId, result.Data.Category, result.Data.Priority);
|
|
449
|
+
|
|
450
|
+
return result.Data;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
if (!result.Success)
|
|
454
|
+
{
|
|
455
|
+
_logger.LogError(
|
|
456
|
+
"AI analysis failed for ticket {TicketId}: {Error}",
|
|
457
|
+
ticketId, result.Error);
|
|
458
|
+
}
|
|
459
|
+
else if (!result.IsValid)
|
|
460
|
+
{
|
|
461
|
+
_logger.LogWarning(
|
|
462
|
+
"AI response invalid for ticket {TicketId}: {Errors}",
|
|
463
|
+
ticketId, string.Join(", ", result.ValidationErrors));
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
return null;
|
|
467
|
+
}
|
|
468
|
+
catch (Exception ex)
|
|
469
|
+
{
|
|
470
|
+
_logger.LogError(ex, "Exception analyzing ticket {TicketId}", ticketId);
|
|
471
|
+
return null;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
### Template DTO Result
|
|
478
|
+
|
|
479
|
+
```csharp
|
|
480
|
+
// Application/AI/TicketAnalysisResult.cs
|
|
481
|
+
public class TicketAnalysisResult
|
|
482
|
+
{
|
|
483
|
+
[JsonPropertyName("category")]
|
|
484
|
+
public string Category { get; set; } = string.Empty;
|
|
485
|
+
|
|
486
|
+
[JsonPropertyName("priority")]
|
|
487
|
+
public int Priority { get; set; }
|
|
488
|
+
|
|
489
|
+
[JsonPropertyName("summary")]
|
|
490
|
+
public string Summary { get; set; } = string.Empty;
|
|
491
|
+
|
|
492
|
+
[JsonPropertyName("tags")]
|
|
493
|
+
public List<string> Tags { get; set; } = new();
|
|
494
|
+
|
|
495
|
+
[JsonPropertyName("confidence")]
|
|
496
|
+
public double? Confidence { get; set; }
|
|
497
|
+
}
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
### Template Controller
|
|
501
|
+
|
|
502
|
+
```csharp
|
|
503
|
+
[ApiController]
|
|
504
|
+
[Route("api/[controller]")]
|
|
505
|
+
public class TicketAnalysisController : ControllerBase
|
|
506
|
+
{
|
|
507
|
+
private readonly ITicketAnalysisService _analysisService;
|
|
508
|
+
|
|
509
|
+
[HttpPost("{ticketId}/analyze")]
|
|
510
|
+
[RequirePermission(Permissions.Support.Tickets.Update)]
|
|
511
|
+
public async Task<ActionResult<TicketAnalysisResult>> AnalyzeTicket(
|
|
512
|
+
Guid ticketId,
|
|
513
|
+
CancellationToken ct)
|
|
514
|
+
{
|
|
515
|
+
var result = await _analysisService.AnalyzeTicketAsync(ticketId, ct);
|
|
516
|
+
|
|
517
|
+
if (result == null)
|
|
518
|
+
return BadRequest(new { message = "Analysis failed" });
|
|
519
|
+
|
|
520
|
+
return Ok(result);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
---
|
|
526
|
+
|
|
527
|
+
## FRONTEND INTEGRATION
|
|
528
|
+
|
|
529
|
+
### API IA
|
|
530
|
+
|
|
531
|
+
```typescript
|
|
532
|
+
// services/api/aiApi.ts
|
|
533
|
+
export const aiApi = {
|
|
534
|
+
// Prompts
|
|
535
|
+
getPrompts: (status?: PromptStatus, isTemplate?: boolean) =>
|
|
536
|
+
apiClient.get<PromptDto[]>('/admin/ai/prompts', { params: { status, isTemplate } }),
|
|
537
|
+
|
|
538
|
+
getPromptById: (id: string) =>
|
|
539
|
+
apiClient.get<PromptDto>(`/admin/ai/prompts/${id}`),
|
|
540
|
+
|
|
541
|
+
createPrompt: (data: CreatePromptRequest) =>
|
|
542
|
+
apiClient.post<{ id: string }>('/admin/ai/prompts', data),
|
|
543
|
+
|
|
544
|
+
// Execution
|
|
545
|
+
executePrompt: (promptId: string, variables: Record<string, unknown>) =>
|
|
546
|
+
apiClient.post<AiCompletionResult>(`/admin/ai/prompts/${promptId}/execute`, { variables }),
|
|
547
|
+
|
|
548
|
+
previewPrompt: (promptId: string, variables: Record<string, unknown>) =>
|
|
549
|
+
apiClient.post<{ rendered: string }>(`/admin/ai/prompts/${promptId}/preview`, { variables }),
|
|
550
|
+
|
|
551
|
+
// Providers
|
|
552
|
+
getProviders: () =>
|
|
553
|
+
apiClient.get<AiProviderDto[]>('/admin/ai/providers'),
|
|
554
|
+
|
|
555
|
+
getModels: (providerId?: string) =>
|
|
556
|
+
apiClient.get<AiModelDto[]>('/admin/ai/models', { params: { providerId } }),
|
|
557
|
+
|
|
558
|
+
// Schemas
|
|
559
|
+
getSchemas: () =>
|
|
560
|
+
apiClient.get<OutputSchemaDto[]>('/admin/ai/schemas'),
|
|
561
|
+
|
|
562
|
+
createSchema: (data: CreateSchemaRequest) =>
|
|
563
|
+
apiClient.post<{ id: string }>('/admin/ai/schemas', data),
|
|
564
|
+
};
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
### Hook useAiCompletion
|
|
568
|
+
|
|
569
|
+
```typescript
|
|
570
|
+
// hooks/useAiCompletion.ts
|
|
571
|
+
import { useState } from 'react';
|
|
572
|
+
import { aiApi } from '@/services/api/aiApi';
|
|
573
|
+
|
|
574
|
+
export function useAiCompletion<T = unknown>(promptCode: string) {
|
|
575
|
+
const [loading, setLoading] = useState(false);
|
|
576
|
+
const [error, setError] = useState<string | null>(null);
|
|
577
|
+
const [result, setResult] = useState<T | null>(null);
|
|
578
|
+
|
|
579
|
+
const execute = async (variables: Record<string, unknown>) => {
|
|
580
|
+
setLoading(true);
|
|
581
|
+
setError(null);
|
|
582
|
+
|
|
583
|
+
try {
|
|
584
|
+
const response = await aiApi.executePrompt(promptCode, variables);
|
|
585
|
+
|
|
586
|
+
if (response.success && response.isValid) {
|
|
587
|
+
setResult(response.data as T);
|
|
588
|
+
return response.data as T;
|
|
589
|
+
} else {
|
|
590
|
+
setError(response.error || 'Validation failed');
|
|
591
|
+
return null;
|
|
592
|
+
}
|
|
593
|
+
} catch (err) {
|
|
594
|
+
setError(err instanceof Error ? err.message : 'Unknown error');
|
|
595
|
+
return null;
|
|
596
|
+
} finally {
|
|
597
|
+
setLoading(false);
|
|
598
|
+
}
|
|
599
|
+
};
|
|
600
|
+
|
|
601
|
+
return { execute, loading, error, result };
|
|
602
|
+
}
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
### Composant AI Assistant
|
|
606
|
+
|
|
607
|
+
```tsx
|
|
608
|
+
// components/ai/AiAssistantButton.tsx
|
|
609
|
+
import { Sparkles, Loader2 } from 'lucide-react';
|
|
610
|
+
import { useAiCompletion } from '@/hooks/useAiCompletion';
|
|
611
|
+
|
|
612
|
+
interface AiAssistantButtonProps {
|
|
613
|
+
promptCode: string;
|
|
614
|
+
variables: Record<string, unknown>;
|
|
615
|
+
onResult: (result: unknown) => void;
|
|
616
|
+
label?: string;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
export function AiAssistantButton({
|
|
620
|
+
promptCode,
|
|
621
|
+
variables,
|
|
622
|
+
onResult,
|
|
623
|
+
label = 'Analyser avec IA'
|
|
624
|
+
}: AiAssistantButtonProps) {
|
|
625
|
+
const { execute, loading, error } = useAiCompletion(promptCode);
|
|
626
|
+
|
|
627
|
+
const handleClick = async () => {
|
|
628
|
+
const result = await execute(variables);
|
|
629
|
+
if (result) {
|
|
630
|
+
onResult(result);
|
|
631
|
+
}
|
|
632
|
+
};
|
|
633
|
+
|
|
634
|
+
return (
|
|
635
|
+
<div>
|
|
636
|
+
<button
|
|
637
|
+
onClick={handleClick}
|
|
638
|
+
disabled={loading}
|
|
639
|
+
className="inline-flex items-center gap-2 px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 disabled:opacity-50"
|
|
640
|
+
>
|
|
641
|
+
{loading ? (
|
|
642
|
+
<Loader2 className="w-4 h-4 animate-spin" />
|
|
643
|
+
) : (
|
|
644
|
+
<Sparkles className="w-4 h-4" />
|
|
645
|
+
)}
|
|
646
|
+
{label}
|
|
647
|
+
</button>
|
|
648
|
+
{error && (
|
|
649
|
+
<p className="mt-2 text-sm text-red-500">{error}</p>
|
|
650
|
+
)}
|
|
651
|
+
</div>
|
|
652
|
+
);
|
|
653
|
+
}
|
|
654
|
+
```
|
|
655
|
+
|
|
656
|
+
---
|
|
657
|
+
|
|
658
|
+
## PATTERNS AVANCES
|
|
659
|
+
|
|
660
|
+
### Pattern: Streaming Response
|
|
661
|
+
|
|
662
|
+
```typescript
|
|
663
|
+
// Pour les reponses longues, utiliser le streaming
|
|
664
|
+
async function streamCompletion(promptId: string, variables: Record<string, unknown>) {
|
|
665
|
+
const response = await fetch(`/api/ai/prompts/${promptId}/stream`, {
|
|
666
|
+
method: 'POST',
|
|
667
|
+
headers: { 'Content-Type': 'application/json' },
|
|
668
|
+
body: JSON.stringify({ variables })
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
const reader = response.body?.getReader();
|
|
672
|
+
const decoder = new TextDecoder();
|
|
673
|
+
|
|
674
|
+
while (true) {
|
|
675
|
+
const { done, value } = await reader!.read();
|
|
676
|
+
if (done) break;
|
|
677
|
+
|
|
678
|
+
const chunk = decoder.decode(value);
|
|
679
|
+
// Afficher progressivement
|
|
680
|
+
appendToOutput(chunk);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
### Pattern: Fallback Provider
|
|
686
|
+
|
|
687
|
+
```csharp
|
|
688
|
+
// Si le provider principal echoue, fallback sur un autre
|
|
689
|
+
public async Task<AiCompletionResult> CompleteWithFallbackAsync(
|
|
690
|
+
Guid promptId,
|
|
691
|
+
Dictionary<string, object> variables)
|
|
692
|
+
{
|
|
693
|
+
var providers = await _providerService.GetActiveProvidersAsync();
|
|
694
|
+
|
|
695
|
+
foreach (var provider in providers.OrderBy(p => p.Priority))
|
|
696
|
+
{
|
|
697
|
+
try
|
|
698
|
+
{
|
|
699
|
+
var result = await _aiService.ExecutePromptAsync(
|
|
700
|
+
promptId, variables, providerId: provider.Id);
|
|
701
|
+
|
|
702
|
+
if (result.Success)
|
|
703
|
+
return result;
|
|
704
|
+
}
|
|
705
|
+
catch (Exception ex)
|
|
706
|
+
{
|
|
707
|
+
_logger.LogWarning(ex,
|
|
708
|
+
"Provider {Provider} failed, trying next",
|
|
709
|
+
provider.Name);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
return new AiCompletionResult { Success = false, Error = "All providers failed" };
|
|
714
|
+
}
|
|
715
|
+
```
|
|
716
|
+
|
|
717
|
+
---
|
|
718
|
+
|
|
719
|
+
## CHECKLIST INTEGRATION IA
|
|
720
|
+
|
|
721
|
+
```
|
|
722
|
+
□ Use case defini (generation, analyse, classification)
|
|
723
|
+
□ Prompt cree avec:
|
|
724
|
+
□ Code unique
|
|
725
|
+
□ Blocks structures (System, User, etc.)
|
|
726
|
+
□ Variables documentees
|
|
727
|
+
□ Version definie
|
|
728
|
+
□ OutputSchema cree si reponse structuree
|
|
729
|
+
□ Service cree avec injection IAiCompletionService
|
|
730
|
+
□ Gestion erreurs implementee
|
|
731
|
+
□ Logging des executions (tokens, temps, erreurs)
|
|
732
|
+
□ Frontend:
|
|
733
|
+
□ Hook ou composant pour l'execution
|
|
734
|
+
□ Loading state
|
|
735
|
+
□ Error handling
|
|
736
|
+
□ Affichage resultat
|
|
737
|
+
□ Tests:
|
|
738
|
+
□ Prompt execute correctement
|
|
739
|
+
□ Variables substituees
|
|
740
|
+
□ Schema valide la reponse
|
|
741
|
+
□ Fallback fonctionne
|
|
742
|
+
□ Budget/Usage:
|
|
743
|
+
□ ProviderInstance avec limite
|
|
744
|
+
□ Tracking des tokens
|
|
745
|
+
```
|
|
746
|
+
|
|
747
|
+
---
|
|
748
|
+
|
|
749
|
+
## REGLES ABSOLUES
|
|
750
|
+
|
|
751
|
+
1. **TOUJOURS** utiliser IAiCompletionService (jamais appel API direct)
|
|
752
|
+
2. **TOUJOURS** definir un OutputSchema pour les reponses structurees
|
|
753
|
+
3. **TOUJOURS** gerer les erreurs (API down, quota depasse, validation)
|
|
754
|
+
4. **TOUJOURS** logger les executions avec tokens et duree
|
|
755
|
+
5. **TOUJOURS** specifier la version du prompt
|
|
756
|
+
6. **TOUJOURS** utiliser des variables pour le contenu dynamique
|
|
757
|
+
7. **JAMAIS** hardcoder les cles API
|
|
758
|
+
8. **JAMAIS** exposer les prompts systeme au frontend
|
|
759
|
+
9. **JAMAIS** ignorer les limites de tokens
|
|
760
|
+
10. **JAMAIS** faire confiance aux reponses sans validation
|
|
761
|
+
|
|
762
|
+
---
|
|
763
|
+
|
|
764
|
+
## FICHIERS CLES
|
|
765
|
+
|
|
766
|
+
| Fichier | Role |
|
|
767
|
+
|---------|------|
|
|
768
|
+
| `Domain/AI/Prompts/Prompt.cs` | Entite prompt |
|
|
769
|
+
| `Domain/AI/Prompts/PromptBlock.cs` | Blocks du prompt |
|
|
770
|
+
| `Domain/AI/Schemas/OutputSchema.cs` | Schema validation |
|
|
771
|
+
| `Domain/AI/AiProvider.cs` | Provider IA |
|
|
772
|
+
| `Domain/AI/AiModel.cs` | Modele IA |
|
|
773
|
+
| `Domain/AI/AiProviderInstance.cs` | Instance configuree |
|
|
774
|
+
| `Application/Common/Interfaces/IAiCompletionService.cs` | Interface service |
|
|
775
|
+
| `Application/Common/Interfaces/IPromptService.cs` | Interface prompts |
|
|
776
|
+
| `Infrastructure/Services/AI/AiCompletionService.cs` | Implementation |
|
|
777
|
+
| `Infrastructure/Services/AI/PromptService.cs` | Implementation prompts |
|
|
778
|
+
| `web/src/services/api/aiApi.ts` | API frontend |
|