@polymorphism-tech/morph-spec 7.0.0 → 7.1.1
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 +368 -368
- package/claude-plugin.json +14 -14
- package/docs/CHEATSHEET.md +199 -199
- package/docs/QUICKSTART.md +200 -200
- package/framework/CLAUDE.md +1 -1
- package/framework/MORPH.md +4 -1
- package/framework/agents.json +132 -127
- package/framework/rubrics/backend.json +41 -36
- package/framework/rules/ai-agents-standards.md +45 -15
- package/framework/rules/neon-standards.md +25 -26
- package/framework/skills/core/morph-brainstorming/SKILL.md +1 -1
- package/framework/skills/core/morph-plan/SKILL.md +3 -1
- package/framework/skills/core/morph-stack-scan/SKILL.md +3 -3
- package/framework/standards/STANDARDS.json +1729 -1133
- package/framework/standards/ai-agents/agent-session.md +160 -0
- package/framework/standards/ai-agents/batching.md +193 -0
- package/framework/standards/ai-agents/context-providers.md +188 -0
- package/framework/standards/ai-agents/mcp-server.md +250 -0
- package/framework/standards/ai-agents/mcp-tools.md +197 -0
- package/framework/standards/ai-agents/middleware-patterns.md +207 -0
- package/framework/standards/ai-agents/modalities-image-gen.md +173 -0
- package/framework/standards/ai-agents/modalities-speech-to-text.md +171 -0
- package/framework/standards/ai-agents/modalities-text-to-speech.md +181 -0
- package/framework/standards/ai-agents/modalities-vision.md +201 -0
- package/framework/standards/ai-agents/multi-agent-patterns.md +155 -0
- package/framework/standards/ai-agents/production.md +114 -258
- package/framework/standards/ai-agents/prompts-in-db.md +214 -0
- package/framework/standards/ai-agents/providers/model-registry.md +158 -0
- package/framework/standards/ai-agents/rag-custom-pgvector.md +243 -0
- package/framework/standards/ai-agents/service-tier-flex.md +135 -0
- package/framework/standards/ai-agents/setup.md +142 -316
- package/framework/standards/ai-agents/structured-output.md +249 -0
- package/framework/standards/ai-agents/sweet-spot.md +109 -0
- package/framework/standards/ai-agents/workflows.md +114 -284
- package/framework/standards/architecture/vertical-slice/vertical-slice.md +439 -429
- package/framework/standards/backend/api/validation.md +92 -88
- package/framework/standards/backend/database/postgresql/database.md +336 -332
- package/framework/standards/backend/database/vector-search-rag.md +599 -541
- package/framework/standards/backend/dotnet/fluent-validation-vsa.md +4 -0
- package/framework/standards/backend/dotnet/result-pattern.md +4 -0
- package/framework/standards/backend/dotnet/scalar-ui.md +176 -172
- package/framework/standards/backend/dotnet/scrutor.md +4 -0
- package/framework/standards/backend/dotnet/vsa-handler-patterns.md +310 -274
- package/framework/standards/backend/integrations/asaas/asaas-api.md +260 -216
- package/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +354 -350
- package/framework/standards/backend/integrations/neon-auth/neon-auth.md +30 -19
- package/framework/standards/backend/integrations/resend/resend-email.md +400 -385
- package/framework/standards/data/nosql/cache/redis.md +106 -97
- package/framework/standards/data/nosql/cosmos-db.md +127 -118
- package/framework/standards/data/vector-search/rag-chunking.md +113 -104
- package/framework/standards/frontend/design-system/ai-image-generation.md +4 -0
- package/framework/standards/frontend/design-system/animations.md +164 -160
- package/framework/standards/frontend/design-system/naming.md +68 -64
- package/framework/standards/frontend/gsap/gsap-core.md +14 -8
- package/framework/standards/frontend/gsap/gsap-plugins.md +26 -21
- package/framework/standards/frontend/gsap/gsap-scrolltrigger.md +9 -7
- package/framework/standards/frontend/nextjs/app-router.md +127 -123
- package/framework/standards/frontend/nextjs/components.md +136 -132
- package/framework/standards/frontend/nextjs/data-fetching.md +130 -126
- package/framework/standards/frontend/nextjs/forms.md +134 -128
- package/framework/standards/frontend/nextjs/motion-patterns.md +328 -322
- package/framework/standards/frontend/nextjs/naming-conventions.md +71 -67
- package/framework/standards/frontend/nextjs/nextjs-patterns.md +229 -225
- package/framework/standards/frontend/nextjs/project-structure.md +106 -102
- package/framework/standards/frontend/nextjs/state-management.md +76 -72
- package/framework/standards/frontend/nextjs/testing.md +115 -111
- package/framework/standards/infrastructure/docker/easypanel-deploy.md +196 -192
- package/framework/standards/infrastructure/neon/neon-pgrag.md +6 -2
- package/framework/standards/infrastructure/neon/neon-pgvector.md +4 -0
- package/framework/standards/infrastructure/neon/neon-setup.md +5 -1
- package/framework/standards/integration/mcp/mcp-tools.md +471 -462
- package/framework/templates/CLAUDE.md.core.template +1 -1
- package/framework/templates/REGISTRY.json +1745 -1239
- package/framework/templates/code/dotnet/ai-agents/AgentBootstrap.cs.template +39 -0
- package/framework/templates/code/dotnet/ai-agents/EmbeddingService.cs.template +68 -0
- package/framework/templates/code/dotnet/ai-agents/ModelRegistry.cs.template +117 -0
- package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +95 -0
- package/framework/templates/code/dotnet/ai-agents/ResilientToolMiddleware.cs.template +61 -0
- package/framework/templates/code/dotnet/ai-agents/VectorSearchTool.cs.template +107 -0
- package/framework/templates/code/dotnet/ai-agents/model-registry.json.template +58 -0
- package/framework/templates/code/dotnet/backend/repository.cs +3 -10
- package/framework/templates/code/sql/ai-agents/agent-prompts-migration.sql.template +58 -0
- package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -0
- package/package.json +89 -83
- package/src/core/templates/template-registry.js +289 -256
- package/src/lib/generators/context-generator.js +516 -518
- package/src/lib/generators/scaffold-generator.js +23 -26
- package/framework/standards/infrastructure/neon/neon-semantic-kernel.md +0 -310
- package/framework/templates/integrations/semantic-kernel-neon.cs +0 -125
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# Agent Session — Conversation state and chat reducers
|
|
2
|
+
|
|
3
|
+
> **Scope:** stacks=["dotnet"]
|
|
4
|
+
> **Layer:** 2 (on-keyword)
|
|
5
|
+
> **Keywords:** agent session, chat history, conversation state, chat reducer, history truncation, AgentThread
|
|
6
|
+
> **Read by Claude in:** implement
|
|
7
|
+
|
|
8
|
+
**Verified against:** MAF 1.0 GA. Last-verified: 2026-05-19.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Quando usar AgentSession
|
|
13
|
+
|
|
14
|
+
Use `AgentSession` quando o agente é **conversacional** — há uma UI de chat que mantém histórico entre turnos do usuário.
|
|
15
|
+
|
|
16
|
+
**Use AgentSession quando:**
|
|
17
|
+
- Existe uma interface de chat (web, mobile, CLI interativo)
|
|
18
|
+
- O agente precisa lembrar o que foi dito nos turnos anteriores da mesma conversa
|
|
19
|
+
- Há perguntas de follow-up ("e agora?", "altere o segundo ponto", "lembra do que disse sobre X?")
|
|
20
|
+
|
|
21
|
+
**NÃO use AgentSession quando:**
|
|
22
|
+
- O agente é one-shot (gerador, executor, classificador) — recebe uma entrada, devolve uma saída, fim
|
|
23
|
+
- Agentes stateless em pipeline multi-agent — cada chamada é independente
|
|
24
|
+
- Jobs de batch/background — não há conversa; histórico não faz sentido
|
|
25
|
+
|
|
26
|
+
> "Add Memory/Session because agents need state" é um anti-pattern de `sweet-spot.md`.
|
|
27
|
+
> Só adicione quando uma UI de chat real existe.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## A API
|
|
32
|
+
|
|
33
|
+
**Pacote:** `Microsoft.Agents.AI` (base; já requerido por `ai-agents-setup`).
|
|
34
|
+
|
|
35
|
+
**Tipos confirmados via context7:**
|
|
36
|
+
- `AgentSession` — container leve de estado de conversa; obtido via `agent.CreateSessionAsync()`
|
|
37
|
+
- `AgentThread` — equivalente para agentes persistentes (PersistentAgentsClient); use `agent.GetNewThread()`
|
|
38
|
+
- `ChatClientAgentOptions` — aceita `ChatHistoryProvider` e `AIContextProviders`
|
|
39
|
+
- `InMemoryChatHistoryProvider` — implementação embutida de histórico em memória
|
|
40
|
+
- `AgentResponse` — tipo retornado por `AIAgent.RunAsync` (non-generic); acesse o texto via `.Text`
|
|
41
|
+
|
|
42
|
+
```csharp
|
|
43
|
+
// Construção do agente com histórico em memória
|
|
44
|
+
AIAgent agent = _modelRegistry.GetChatClient("text-default")
|
|
45
|
+
.AsAIAgent(new ChatClientAgentOptions
|
|
46
|
+
{
|
|
47
|
+
ChatOptions = new() { Instructions = "You are a helpful assistant." },
|
|
48
|
+
ChatHistoryProvider = new InMemoryChatHistoryProvider(),
|
|
49
|
+
});
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Exemplo C# — agente conversacional com session persistente entre turnos
|
|
55
|
+
|
|
56
|
+
```csharp
|
|
57
|
+
using Microsoft.Agents.AI;
|
|
58
|
+
using Microsoft.Extensions.AI;
|
|
59
|
+
|
|
60
|
+
// ── 1. Registrar o agente conversacional no DI ──
|
|
61
|
+
// Program.cs
|
|
62
|
+
builder.Services.AddSingleton<AIAgent>(sp =>
|
|
63
|
+
{
|
|
64
|
+
var registry = sp.GetRequiredService<ModelRegistry>();
|
|
65
|
+
return registry.GetChatClient("text-default")
|
|
66
|
+
.AsAIAgent(new ChatClientAgentOptions
|
|
67
|
+
{
|
|
68
|
+
ChatOptions = new() { Instructions = "You are a helpful customer support assistant." },
|
|
69
|
+
// InMemoryChatHistoryProvider: histórico vive em memória, por session.
|
|
70
|
+
// Seguro registrar como parte do singleton AIAgent: cada AgentSession criada via
|
|
71
|
+
// agent.CreateSessionAsync() isola o histórico de sua conversa — conversas distintas
|
|
72
|
+
// não compartilham estado. O provider em si é stateless; o estado fica na session.
|
|
73
|
+
// Para persistência entre restarts, substitua por um provider customizado (ex: DB).
|
|
74
|
+
ChatHistoryProvider = new InMemoryChatHistoryProvider(),
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// ── 2. Criar uma session por conversa (ex: por conexão SignalR / por request de "start chat") ──
|
|
79
|
+
// ChatController.cs ou ChatHub.cs
|
|
80
|
+
public class ChatService(AIAgent _agent)
|
|
81
|
+
{
|
|
82
|
+
// Uma session por conversa ativa — armazene no escopo do usuário (cache, memória da conexão)
|
|
83
|
+
public async Task<AgentSession> StartConversationAsync()
|
|
84
|
+
=> await _agent.CreateSessionAsync();
|
|
85
|
+
|
|
86
|
+
// Cada mensagem do usuário passa a mesma session
|
|
87
|
+
public async Task<string> SendMessageAsync(AgentSession session, string userMessage)
|
|
88
|
+
{
|
|
89
|
+
AgentResponse response = await _agent.RunAsync(userMessage, session);
|
|
90
|
+
return response.Text;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ── 3. Uso no controller/hub ──
|
|
95
|
+
// Primeiro turno
|
|
96
|
+
AgentSession session = await chatService.StartConversationAsync();
|
|
97
|
+
string reply1 = await chatService.SendMessageAsync(session, "Meu pedido #1234 está atrasado.");
|
|
98
|
+
// reply1: "Vou verificar o pedido #1234 para você..."
|
|
99
|
+
|
|
100
|
+
// Segundo turno — o agente lembra do contexto anterior
|
|
101
|
+
string reply2 = await chatService.SendMessageAsync(session, "E quando foi despachado?");
|
|
102
|
+
// reply2: "O pedido #1234 foi despachado em..." ← contexto mantido pela session
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## History management
|
|
108
|
+
|
|
109
|
+
O histórico cresce a cada turno. Quando estoura o context window do modelo, as chamadas falham ou truncam silenciosamente.
|
|
110
|
+
|
|
111
|
+
### Estratégias
|
|
112
|
+
|
|
113
|
+
| Estratégia | Quando usar | Como implementar |
|
|
114
|
+
|------------|-------------|------------------|
|
|
115
|
+
| **Sliding window** | Conversas longas com contexto recente suficiente | Limitar `InMemoryChatHistoryProvider` a N mensagens mais recentes |
|
|
116
|
+
| **Token budget** | Modelos com context window pequeno | Contar tokens acumulados; truncar quando ultrapassa threshold |
|
|
117
|
+
| **Summarization** | Histórico precisa de contexto antigo comprimido | Agente periódico que resume N turnos anteriores em uma mensagem única |
|
|
118
|
+
|
|
119
|
+
> **Custom history management:** Para o caso comum, `InMemoryChatHistoryProvider` cobre a
|
|
120
|
+
> necessidade de histórico em memória por sessão. Se o projeto exigir truncation customizada
|
|
121
|
+
> (ex: sliding window limitando a N mensagens mais recentes ou por token budget), o mecanismo
|
|
122
|
+
> de extensão correto — seja subclasse de `ChatHistoryProvider`, um `IChatReducer`, ou outro
|
|
123
|
+
> ponto de override — deve ser verificado contra a versão instalada de `Microsoft.Agents.AI`.
|
|
124
|
+
> Consulte a documentação MAF atual antes de implementar um provider customizado, pois a API
|
|
125
|
+
> exata de extensão não foi confirmada na versão GA e pode variar entre releases.
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## Anti-patterns
|
|
130
|
+
|
|
131
|
+
| Anti-pattern | Problema |
|
|
132
|
+
|-------------|----------|
|
|
133
|
+
| Adicionar session em agente one-shot (gerador, executor) | Complexidade desnecessária; estado nunca é usado |
|
|
134
|
+
| Histórico ilimitado sem truncation | Context window estoura; custo aumenta linearmente com a conversa |
|
|
135
|
+
| Compartilhar uma `AgentSession` entre múltiplos usuários | Histórico de um usuário vaza para outro |
|
|
136
|
+
| Usar `InMemoryChatHistoryProvider` sem criar uma `AgentSession` por conversa | Sem `AgentSession` por conversa, o histórico é compartilhado entre todos os usuários; a isolation depende de `AgentSession` — sempre crie uma session por conversa via `agent.CreateSessionAsync()` |
|
|
137
|
+
| Usar `AgentThread` (PersistentAgentsClient) sem necessidade | `AgentThread` requer hosted agent service; prefira `AgentSession` com direct model |
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## Checklist (verifiable by morph-eval)
|
|
142
|
+
|
|
143
|
+
- [ ] `AgentSession` usada somente em agentes conversacionais com UI de chat real
|
|
144
|
+
- [ ] `InMemoryChatHistoryProvider` (ou equivalente) registrado em `ChatClientAgentOptions`
|
|
145
|
+
- [ ] Uma `AgentSession` por conversa/usuário — nunca compartilhada
|
|
146
|
+
- [ ] Estratégia de truncation definida (sliding window, token budget ou summarization)
|
|
147
|
+
- [ ] Agentes one-shot (gerador, executor, classificador) **não** recebem session
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## References
|
|
152
|
+
|
|
153
|
+
- `ai-agents-setup` — packages e DI base
|
|
154
|
+
- `ai-agents-sweet-spot` — decisão de quando adicionar estado ao agente
|
|
155
|
+
- `ai-agents-middleware-patterns` (Onda 2) — pipeline de contexto e middleware
|
|
156
|
+
- MAF Conversations docs: https://learn.microsoft.com/en-us/agent-framework/agents/conversations/
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
*MORPH-SPEC by Polymorphism Tech — ai-agents/agent-session.md v1.0 (2026-05-19)*
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# Batching — Bulk prompt processing
|
|
2
|
+
|
|
3
|
+
> **Scope:** stacks=["dotnet"]
|
|
4
|
+
> **Layer:** 2 (on-keyword)
|
|
5
|
+
> **Keywords:** batching, batch api, bulk prompts, batch processing, async batch
|
|
6
|
+
> **Read by Claude in:** plan/implement
|
|
7
|
+
|
|
8
|
+
**Verified against:** MAF 1.0 GA + OpenAI .NET SDK 2.* (fluxo da Batch API confirmado pela REST API; nomes de tipos/métodos .NET marcados `// VERIFY:` — ver seção A API). Last-verified: 2026-05-19.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Quando usar batch
|
|
13
|
+
|
|
14
|
+
Use a Batch API quando você tem **N prompts independentes sem necessidade de resposta imediata**:
|
|
15
|
+
|
|
16
|
+
- Relatório mensal que agrega análise de N documentos/itens
|
|
17
|
+
- Reprocessamento de dados históricos com LLM (re-classificação, re-extração)
|
|
18
|
+
- Geração em massa de sumários, traduções ou avaliações offline
|
|
19
|
+
- Pipeline noturno de enriquecimento de dados
|
|
20
|
+
|
|
21
|
+
**Vantagens:**
|
|
22
|
+
- **Desconto de custo significativo** — OpenAI Batch API custa 50% menos que completions síncronas
|
|
23
|
+
- **SLA relaxado** — resultados em até 24 h; adequado para trabalho assíncrono
|
|
24
|
+
- **Rate limits separados** — não compete com a cota síncrona da aplicação
|
|
25
|
+
|
|
26
|
+
> Regra prática: se o usuário não está esperando o resultado na tela, considere batch.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## A API
|
|
31
|
+
|
|
32
|
+
> **AVISO:** Batch API é **OpenAI-specific**. O `IChatClient` do MAF não expõe batch.
|
|
33
|
+
> Obtenha o `OpenAIClient` direto via configuração (mesma chave/modelo que o Model Registry usa)
|
|
34
|
+
> em vez de `_modelRegistry.GetChatClient(alias)`.
|
|
35
|
+
|
|
36
|
+
O fluxo tem 4 etapas: **upload do arquivo JSONL → submissão do batch → poll de status → download dos resultados**.
|
|
37
|
+
|
|
38
|
+
> ### ⚠️ Status de verificação da API .NET
|
|
39
|
+
>
|
|
40
|
+
> O fluxo abaixo é o **padrão correto da OpenAI Batch API** (confirmado pela REST API e pelos
|
|
41
|
+
> docs oficiais). O que **não** foi confirmável via context7 são os nomes exatos de tipos/métodos
|
|
42
|
+
> do **OpenAI .NET SDK 2.\*** para batch — o context7 retornou apenas as APIs de Assistants/Files.
|
|
43
|
+
>
|
|
44
|
+
> **Confirmado:** `openAIClient.GetOpenAIFileClient()` → `OpenAIFileClient`; upload de arquivo
|
|
45
|
+
> via `UploadFileAsync(stream, filename, FileUploadPurpose.*)`.
|
|
46
|
+
>
|
|
47
|
+
> **A confirmar contra o pacote `OpenAI` 2.\* instalado** (todos marcados `// VERIFY:` no código):
|
|
48
|
+
> nome do método que retorna o batch client (`GetBatchClient()`), namespace do batch
|
|
49
|
+
> (`OpenAI.Batch`), assinatura de `CreateBatchAsync`, nomes de `GetBatchAsync`/`DownloadFileAsync`,
|
|
50
|
+
> tipo/valores do status, e o valor de `FileUploadPurpose` para batch.
|
|
51
|
+
> **Antes de codar:** abra o pacote no IntelliSense ou no [openai-dotnet](https://github.com/openai/openai-dotnet) e confirme cada `// VERIFY:`.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Exemplo C# completo
|
|
56
|
+
|
|
57
|
+
```csharp
|
|
58
|
+
using OpenAI;
|
|
59
|
+
using OpenAI.Batch; // VERIFY namespace — confirme contra o pacote OpenAI 2.* instalado
|
|
60
|
+
using OpenAI.Files;
|
|
61
|
+
using System.ClientModel;
|
|
62
|
+
using System.Text;
|
|
63
|
+
using System.Text.Json;
|
|
64
|
+
|
|
65
|
+
// ── 1. Construir o cliente OpenAI direto (mesma config que o Model Registry usa) ──
|
|
66
|
+
var apiKey = configuration["OpenAI:ApiKey"]!;
|
|
67
|
+
var openAIClient = new OpenAIClient(new ApiKeyCredential(apiKey));
|
|
68
|
+
var fileClient = openAIClient.GetOpenAIFileClient(); // confirmado
|
|
69
|
+
var batchClient = openAIClient.GetBatchClient(); // VERIFY método — pode ser GetOpenAIBatchClient()
|
|
70
|
+
|
|
71
|
+
// ── 2. Montar o arquivo JSONL com N requests independentes ──
|
|
72
|
+
var items = new[] { "Item A", "Item B", "Item C" }; // substitua pela sua coleção
|
|
73
|
+
var jsonlBuilder = new StringBuilder();
|
|
74
|
+
foreach (var item in items)
|
|
75
|
+
{
|
|
76
|
+
var request = new
|
|
77
|
+
{
|
|
78
|
+
custom_id = $"req-{Guid.CreateVersion7()}",
|
|
79
|
+
method = "POST",
|
|
80
|
+
url = "/v1/chat/completions",
|
|
81
|
+
body = new
|
|
82
|
+
{
|
|
83
|
+
model = configuration["OpenAI:Model"] ?? "gpt-4o-mini",
|
|
84
|
+
messages = new[]
|
|
85
|
+
{
|
|
86
|
+
new { role = "user", content = $"Analyze this item and return a JSON summary: {item}" }
|
|
87
|
+
},
|
|
88
|
+
max_tokens = 256
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
jsonlBuilder.AppendLine(JsonSerializer.Serialize(request));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ── 3. Upload do arquivo JSONL ──
|
|
95
|
+
var jsonlBytes = Encoding.UTF8.GetBytes(jsonlBuilder.ToString());
|
|
96
|
+
using var jsonlStream = new MemoryStream(jsonlBytes);
|
|
97
|
+
// VERIFY valor do enum — FileUploadPurpose.Batch (confirme; o SDK 2.* expõe outros purposes)
|
|
98
|
+
OpenAIFile batchFile = await fileClient.UploadFileAsync(
|
|
99
|
+
jsonlStream, "batch_requests.jsonl", FileUploadPurpose.Batch);
|
|
100
|
+
|
|
101
|
+
// ── 4. Submeter o batch ──
|
|
102
|
+
// VERIFY assinatura de CreateBatchAsync — provável (fileId, endpoint, completionWindow)
|
|
103
|
+
var batch = await batchClient.CreateBatchAsync(
|
|
104
|
+
batchFile.Id,
|
|
105
|
+
endpoint: "/v1/chat/completions",
|
|
106
|
+
completionWindow: "24h");
|
|
107
|
+
|
|
108
|
+
// ── 5. Poll de status (em background job / Hangfire / Worker) ──
|
|
109
|
+
OpenAIBatch? completedBatch = null; // VERIFY nome do tipo de retorno
|
|
110
|
+
do
|
|
111
|
+
{
|
|
112
|
+
await Task.Delay(TimeSpan.FromSeconds(30)); // poll interval para batch — não interativo
|
|
113
|
+
completedBatch = await batchClient.GetBatchAsync(batch.Id); // VERIFY nome do método
|
|
114
|
+
} while (completedBatch?.Status is not ("completed" or "failed" or "cancelled")); // VERIFY tipo/valores do Status
|
|
115
|
+
|
|
116
|
+
// ── 6. Recuperar e processar resultados ──
|
|
117
|
+
if (completedBatch?.OutputFileId is { } outputFileId)
|
|
118
|
+
{
|
|
119
|
+
var resultFile = await fileClient.DownloadFileAsync(outputFileId); // VERIFY nome do método
|
|
120
|
+
using var reader = new StreamReader(resultFile.Value.ToStream());
|
|
121
|
+
string? line;
|
|
122
|
+
while ((line = await reader.ReadLineAsync()) is not null)
|
|
123
|
+
{
|
|
124
|
+
using var doc = JsonDocument.Parse(line);
|
|
125
|
+
var customId = doc.RootElement.GetProperty("custom_id").GetString();
|
|
126
|
+
var content = doc.RootElement
|
|
127
|
+
.GetProperty("response")
|
|
128
|
+
.GetProperty("body")
|
|
129
|
+
.GetProperty("choices")[0]
|
|
130
|
+
.GetProperty("message")
|
|
131
|
+
.GetProperty("content")
|
|
132
|
+
.GetString();
|
|
133
|
+
// processe content...
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// ── 7. Tratar falhas de itens individuais ──
|
|
138
|
+
if (completedBatch?.ErrorFileId is { } errorFileId)
|
|
139
|
+
{
|
|
140
|
+
var errorFile = await fileClient.DownloadFileAsync(errorFileId); // VERIFY nome do método
|
|
141
|
+
// log itens que falharam; retente individualmente se necessário
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
> O poll de status (`do...while`) deve rodar num **background job** (Hangfire, Worker Service),
|
|
146
|
+
> não em uma requisição HTTP. O usuário não espera; o resultado é entregue via event/webhook/DB.
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## Quando NÃO usar
|
|
151
|
+
|
|
152
|
+
| Situação | Por quê não usar batch |
|
|
153
|
+
|----------|------------------------|
|
|
154
|
+
| Resposta interativa — usuário aguarda na tela | Latência de batch (minutos a horas) é inaceitável |
|
|
155
|
+
| Volume baixo (< ~20 requests/dia) | Overhead de JSONL + poll não compensa |
|
|
156
|
+
| Requests com dependência entre si | Batch é para prompts **independentes** |
|
|
157
|
+
| Provider não-OpenAI (Anthropic, Google) | Batch API é OpenAI-specific; use completions síncronas |
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## Anti-patterns
|
|
162
|
+
|
|
163
|
+
| Anti-pattern | Problema |
|
|
164
|
+
|-------------|----------|
|
|
165
|
+
| Usar batch para requests síncronos de usuário | Alta latência — batch pode demorar horas |
|
|
166
|
+
| Não tratar `error_file_id` | Itens que falharam são silenciados; reprocessamento nunca acontece |
|
|
167
|
+
| Poll com `Task.Delay(1s)` em loop | Abuso de rate limits; use intervalo ≥ 30 s para batch |
|
|
168
|
+
| Assumir que todos os itens do batch concluem | Batch pode ter falhas parciais; itere o arquivo de erros |
|
|
169
|
+
| Incluir prompt com dados do usuário sem sanitização | Prompt injection em batch; aplique `ai-agents-middleware-patterns` |
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## Checklist (verifiable by morph-eval)
|
|
174
|
+
|
|
175
|
+
- [ ] Batch é usado somente para prompts independentes, sem usuário aguardando resposta
|
|
176
|
+
- [ ] O poll de status roda em background job (Worker/Hangfire), não em request HTTP
|
|
177
|
+
- [ ] `error_file_id` é verificado e erros de itens individuais são logados/retentados
|
|
178
|
+
- [ ] Nenhum `Guid.NewGuid()` — custom_id usa `Guid.CreateVersion7()`
|
|
179
|
+
- [ ] Provider é OpenAI; cada `// VERIFY:` da API .NET de batch confirmado contra o pacote instalado antes de codar
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## References
|
|
184
|
+
|
|
185
|
+
- `ai-agents-setup` — setup do agente MAF e packages
|
|
186
|
+
- `ai-agents-service-tier-flex` — otimização de custo para jobs async
|
|
187
|
+
- `ai-agents-providers-model-registry` — configuração provider-agnostic
|
|
188
|
+
- OpenAI Batch API: https://platform.openai.com/docs/guides/batch
|
|
189
|
+
- OpenAI .NET SDK `OpenAI.Batch`: https://github.com/openai/openai-dotnet
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
*MORPH-SPEC by Polymorphism Tech — ai-agents/batching.md v1.0 (2026-05-19)*
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# Context Providers — Dynamic context injection
|
|
2
|
+
|
|
3
|
+
> **Scope:** stacks=["dotnet"]
|
|
4
|
+
> **Layer:** 2 (on-keyword)
|
|
5
|
+
> **Keywords:** context provider, dynamic context, context injection, AIContextProvider, memory provider
|
|
6
|
+
> **Read by Claude in:** implement
|
|
7
|
+
|
|
8
|
+
**Verified against:** MAF 1.0 GA. Last-verified: 2026-05-19.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Quando usar Context Provider
|
|
13
|
+
|
|
14
|
+
Use um `AIContextProvider` quando o agente precisa de **contexto dinâmico computado a cada chamada** — informação que varia por request, sessão ou tenant, sem poluir as instructions estáticas do agente.
|
|
15
|
+
|
|
16
|
+
**Exemplos de uso correto:**
|
|
17
|
+
- Data/hora atual injetada nas instructions ("Today is Monday, 2026-05-19")
|
|
18
|
+
- Perfil do tenant/usuário logado (nome, plano, preferências de idioma)
|
|
19
|
+
- Estado de sistema em tempo real (saldo de conta, status de pedido)
|
|
20
|
+
- Contexto de segurança (permissões do usuário para o turno atual)
|
|
21
|
+
|
|
22
|
+
**Distinção crítica:**
|
|
23
|
+
|
|
24
|
+
| Mecanismo | Quando usar |
|
|
25
|
+
|-----------|-------------|
|
|
26
|
+
| **Context Provider** | Contexto **determinístico/computado** — sempre injetado, independe da entrada do usuário |
|
|
27
|
+
| **Tool** | O agente **decide chamar** baseado na entrada; retorno é dinâmico mas acionado pelo LLM |
|
|
28
|
+
| **RAG** | Busca por **similaridade** em base de conhecimento; o contexto varia com o que foi perguntado |
|
|
29
|
+
|
|
30
|
+
> Regra prática: se você sabe exatamente *o que* injetar antes de chamar o LLM, use Context Provider.
|
|
31
|
+
> Se precisa do LLM para decidir *se* e *como* buscar, use Tool ou RAG.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## A API
|
|
36
|
+
|
|
37
|
+
**Pacote:** `Microsoft.Agents.AI` + `Microsoft.Agents.AI.Hosting` (para o builder).
|
|
38
|
+
|
|
39
|
+
**Tipos confirmados via context7:**
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
AIContextProvider — classe base abstrata (sealed override ProvideAIContextAsync + StoreAIContextAsync)
|
|
43
|
+
AIContext — objeto retornado; contém Messages, Instructions, etc.
|
|
44
|
+
InvokingContext — contexto disponível no before-run hook (acesso à session, mensagens atuais)
|
|
45
|
+
InvokedContext — contexto disponível no after-run hook (request + response messages)
|
|
46
|
+
ProviderSessionState<T> — helper tipado para armazenar estado por provider na AgentSession
|
|
47
|
+
ChatClientAgentOptions.AIContextProviders — lista de providers passados na construção do agente
|
|
48
|
+
.UseAIContextProviders(...) — extension method via builder (Microsoft.Agents.AI.Hosting)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**Método a sobrescrever:**
|
|
52
|
+
|
|
53
|
+
```csharp
|
|
54
|
+
// ProvideAIContextAsync → chamado ANTES do LLM (before-run hook) — injeta contexto
|
|
55
|
+
// StoreAIContextAsync → chamado APÓS o LLM (after-run hook) — persiste estado se necessário
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Exemplo C# — context provider que injeta data atual e perfil do tenant
|
|
61
|
+
|
|
62
|
+
```csharp
|
|
63
|
+
using Microsoft.Agents.AI;
|
|
64
|
+
using Microsoft.Extensions.AI;
|
|
65
|
+
|
|
66
|
+
// ── 1. Definir o provider ──
|
|
67
|
+
public sealed class TenantContextProvider : AIContextProvider
|
|
68
|
+
{
|
|
69
|
+
private readonly ITenantService _tenantService;
|
|
70
|
+
private readonly ITenantContext _tenantContext;
|
|
71
|
+
|
|
72
|
+
// ITenantContext deve ser implementado via IHttpContextAccessor ou AsyncLocal
|
|
73
|
+
// para resolver o tenant atual em runtime — não registre como instância estática.
|
|
74
|
+
// O AIAgent é singleton, então o provider também é singleton; o tenant corrente
|
|
75
|
+
// é lido de forma ambient (IHttpContextAccessor/AsyncLocal) a cada chamada.
|
|
76
|
+
public TenantContextProvider(ITenantService tenantService, ITenantContext tenantContext)
|
|
77
|
+
{
|
|
78
|
+
_tenantService = tenantService;
|
|
79
|
+
_tenantContext = tenantContext;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Sobrescreva: injetado ANTES de cada chamada ao LLM
|
|
83
|
+
protected override async ValueTask<AIContext> ProvideAIContextAsync(
|
|
84
|
+
InvokingContext context,
|
|
85
|
+
CancellationToken cancellationToken = default)
|
|
86
|
+
{
|
|
87
|
+
// Tenant resolvido via serviço injetado (ambient context, ex: IHttpContextAccessor)
|
|
88
|
+
var tenantId = _tenantContext.TenantId ?? "unknown";
|
|
89
|
+
var tenant = await _tenantService.GetTenantAsync(tenantId, cancellationToken);
|
|
90
|
+
|
|
91
|
+
var instructions = $"""
|
|
92
|
+
Today is {DateTimeOffset.UtcNow:yyyy-MM-dd HH:mm} UTC.
|
|
93
|
+
Tenant: {tenant.Name} (plan: {tenant.Plan}, language: {tenant.Language}).
|
|
94
|
+
Always respond in {tenant.Language}.
|
|
95
|
+
""";
|
|
96
|
+
|
|
97
|
+
return new AIContext
|
|
98
|
+
{
|
|
99
|
+
// Injeta como instrução adicional — não como mensagem do usuário
|
|
100
|
+
Instructions = instructions,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Sobrescreva (opcional): chamado APÓS o LLM — use para persistir estado derivado da resposta
|
|
105
|
+
protected override ValueTask StoreAIContextAsync(
|
|
106
|
+
InvokedContext context,
|
|
107
|
+
CancellationToken cancellationToken = default)
|
|
108
|
+
=> ValueTask.CompletedTask; // stateless provider — nada a persistir
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ── 2. Registrar o agente com o provider no DI ──
|
|
112
|
+
// Program.cs
|
|
113
|
+
builder.Services.AddSingleton<AIAgent>(sp =>
|
|
114
|
+
{
|
|
115
|
+
var registry = sp.GetRequiredService<ModelRegistry>();
|
|
116
|
+
var tenantSvc = sp.GetRequiredService<ITenantService>();
|
|
117
|
+
var tenantContext = sp.GetRequiredService<ITenantContext>();
|
|
118
|
+
|
|
119
|
+
return registry.GetChatClient("text-default")
|
|
120
|
+
.AsAIAgent(new ChatClientAgentOptions
|
|
121
|
+
{
|
|
122
|
+
ChatOptions = new() { Instructions = "You are a helpful assistant." },
|
|
123
|
+
AIContextProviders = [new TenantContextProvider(tenantSvc, tenantContext)],
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// ── Alternativa: builder/middleware (útil para adicionar providers a agente existente) ──
|
|
128
|
+
// var enrichedAgent = existingAgent
|
|
129
|
+
// .AsBuilder()
|
|
130
|
+
// .UseAIContextProviders(new TenantContextProvider(tenantSvc))
|
|
131
|
+
// .Build();
|
|
132
|
+
|
|
133
|
+
// ── 3. Uso normal — provider é invocado automaticamente a cada RunAsync ──
|
|
134
|
+
AgentSession session = await agent.CreateSessionAsync();
|
|
135
|
+
AgentResponse response = await agent.RunAsync("Quais são meus benefícios do plano?", session);
|
|
136
|
+
Console.WriteLine(response.Text);
|
|
137
|
+
// O agente já sabe a data, o nome do tenant e o idioma — sem código extra no caller
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Context Provider vs Tools vs RAG
|
|
143
|
+
|
|
144
|
+
| Mecanismo | Quem decide chamar | Quando é acionado | Exemplo |
|
|
145
|
+
|-----------|-------------------|-------------------|---------|
|
|
146
|
+
| **Context Provider** | Framework (sempre) | Antes de cada chamada ao LLM | Data/hora, perfil do tenant |
|
|
147
|
+
| **Tool** | O LLM (quando achar necessário) | Quando o LLM decide invocar | Consultar saldo, buscar pedido |
|
|
148
|
+
| **RAG** | Framework + similaridade semântica | Antes do LLM, busca por embedding | Documentos de ajuda, base de conhecimento |
|
|
149
|
+
|
|
150
|
+
> Se você está em dúvida entre Context Provider e Tool:
|
|
151
|
+
> - Pergunta: "O agente *sempre* precisa dessa informação nesse contexto?" → Context Provider
|
|
152
|
+
> - Pergunta: "O agente *pode ou não* precisar dependendo do que o usuário pediu?" → Tool
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## Anti-patterns
|
|
157
|
+
|
|
158
|
+
| Anti-pattern | Problema |
|
|
159
|
+
|-------------|----------|
|
|
160
|
+
| Context Provider para o que uma tool resolve | Injeta contexto desnecessário a cada chamada; desperdiça tokens |
|
|
161
|
+
| Injetar contexto gigante a cada chamada | Aumenta custo e pode esgotar context window; prefira tools para contexto on-demand |
|
|
162
|
+
| Provider com side-effects de escrita em `ProvideAIContextAsync` | Hook before-run deve ser read-only; use `StoreAIContextAsync` para escrita |
|
|
163
|
+
| Compartilhar estado mutável entre providers sem `ProviderSessionState<T>` | Race condition entre providers; use o helper tipado |
|
|
164
|
+
| Usar Context Provider para lógica de negócio complexa | Providers devem ser leves e rápidos; lógica pesada vai em tools |
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Checklist (verifiable by morph-eval)
|
|
169
|
+
|
|
170
|
+
- [ ] O provider herda da classe base abstrata `AIContextProvider` (não implementa uma interface genérica)
|
|
171
|
+
- [ ] `ProvideAIContextAsync` é read-only — sem writes a banco ou estado global
|
|
172
|
+
- [ ] `StoreAIContextAsync` usado somente quando o provider precisa persistir estado
|
|
173
|
+
- [ ] Provider registrado em `ChatClientAgentOptions.AIContextProviders` ou via `.UseAIContextProviders()`
|
|
174
|
+
- [ ] Contexto injetado é **determinístico** — se depende de busca por similaridade, é RAG, não provider
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## References
|
|
179
|
+
|
|
180
|
+
- `ai-agents-setup` — packages e DI base
|
|
181
|
+
- `ai-agents-rag-custom-pgvector` (Onda 2) — busca por similaridade (distinto de context provider)
|
|
182
|
+
- `ai-agents-multi-agent-patterns` (Onda 2) — composição de agentes
|
|
183
|
+
- MAF Context Providers docs: https://learn.microsoft.com/en-us/agent-framework/agents/conversations/context-providers
|
|
184
|
+
- MAF Agent Pipeline docs: https://learn.microsoft.com/en-us/agent-framework/agents/agent-pipeline
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
*MORPH-SPEC by Polymorphism Tech — ai-agents/context-providers.md v1.0 (2026-05-19)*
|