@polymorphism-tech/morph-spec 8.45.0 → 8.47.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/CLAUDE.md +6 -5
- package/README.md +2 -2
- package/bin/morph-spec.js +2 -4
- package/bin/validate.js +4 -25
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +2 -2
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +2 -2
- package/framework/MORPH.md +9 -9
- package/framework/commands/create-umbraco-lp.md +2 -0
- package/framework/commands/migrate-lp-to-umbraco.md +2 -0
- package/framework/commands/morph-archive.md +1 -1
- package/framework/commands/morph-hotfix.md +1 -1
- package/framework/commands/morph-infra.md +17 -203
- package/framework/commands/morph-media.md +3 -0
- package/framework/commands/morph-preflight.md +5 -28
- package/framework/commands/morph-proposal.md +2 -2
- package/framework/commands/morph-troubleshoot.md +2 -14
- package/framework/docs/CLI.md +8 -7
- package/framework/hooks/claude-code/core/state-sync.js +13 -1
- package/framework/hooks/shared/e2e-check.js +3 -4
- package/framework/hooks/shared/state-writer.js +1 -0
- package/framework/rules/ai-agents-standards.md +18 -16
- package/framework/rules/asaas-standards.md +3 -1
- package/framework/rules/csharp-standards.md +10 -8
- package/framework/rules/data-standards.md +5 -5
- package/framework/rules/dotnet-templates.md +1 -1
- package/framework/rules/frontend-standards.md +4 -2
- package/framework/rules/gsap-standards.md +10 -5
- package/framework/rules/hangfire-standards.md +3 -1
- package/framework/rules/infrastructure-standards.md +4 -2
- package/framework/rules/morph-workflow.md +2 -2
- package/framework/rules/neon-auth-standards.md +3 -1
- package/framework/rules/neon-standards.md +6 -4
- package/framework/rules/nextjs-standards.md +13 -11
- package/framework/rules/nextjs-templates.md +1 -1
- package/framework/rules/resend-standards.md +3 -1
- package/framework/schemas/config.schema.json +4 -0
- package/framework/schemas/feature.schema.json +4 -4
- package/framework/skills/README.md +1 -1
- package/framework/skills/core/create-umbraco-lp/SKILL.md +3 -3
- package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +3 -3
- package/framework/skills/core/morph-apply/SKILL.md +3 -3
- package/framework/skills/core/morph-brainstorming/SKILL.md +3 -3
- package/framework/skills/core/morph-hotfix/SKILL.md +4 -4
- package/framework/skills/core/morph-implement/SKILL.md +3 -3
- package/framework/skills/core/morph-machine/SKILL.md +3 -3
- package/framework/skills/core/morph-media/SKILL.md +3 -3
- package/framework/skills/core/morph-plan/SKILL.md +3 -3
- package/framework/skills/core/morph-proposal/SKILL.md +3 -3
- package/framework/skills/core/morph-review/SKILL.md +30 -17
- package/framework/skills/core/morph-stack-scan/SKILL.md +3 -3
- package/framework/skills/core/morph-standards/SKILL.md +6 -6
- package/framework/skills/core/morph-uiux/SKILL.md +3 -3
- package/framework/standards/STANDARDS.json +59 -56
- package/framework/standards/ai-agents/evals-with-cache.md +1 -1
- package/framework/standards/ai-agents/llm-runtime-defaults.md +1 -1
- package/framework/standards/ai-agents/modalities-speech-to-text.md +36 -26
- package/framework/standards/ai-agents/prompt-sources.md +6 -4
- package/framework/standards/ai-agents/providers/model-registry.md +1 -1
- package/framework/standards/ai-agents/rag-custom-pgvector.md +67 -39
- package/framework/standards/ai-agents/setup.md +1 -1
- package/framework/standards/ai-agents/structured-output.md +1 -1
- package/framework/standards/ai-agents/sweet-spot.md +1 -1
- package/framework/standards/architecture/code-comments.md +2 -2
- package/framework/standards/architecture/vertical-slice/vertical-slice.md +18 -9
- package/framework/standards/backend/api/validation.md +135 -47
- package/framework/standards/backend/database/postgresql/database.md +11 -7
- package/framework/standards/backend/database/vector-search-rag.md +207 -324
- package/framework/standards/backend/dotnet/fluent-validation-vsa.md +14 -8
- package/framework/standards/backend/dotnet/result-pattern.md +40 -6
- package/framework/standards/backend/dotnet/scalar-ui.md +13 -7
- package/framework/standards/backend/dotnet/scrutor.md +1 -1
- package/framework/standards/backend/dotnet/testing.md +1 -1
- package/framework/standards/backend/dotnet/vsa-handler-patterns.md +7 -7
- package/framework/standards/backend/integrations/asaas/asaas-api.md +309 -165
- package/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +134 -154
- package/framework/standards/data/nosql/cache/redis.md +120 -44
- package/framework/standards/data/nosql/cosmos-db.md +114 -71
- package/framework/standards/frontend/design-system/animations.md +1 -1
- package/framework/standards/frontend/design-system/naming.md +1 -1
- package/framework/standards/frontend/nextjs/motion-patterns.md +45 -29
- package/framework/standards/frontend/nextjs/naming-conventions.md +1 -1
- package/framework/standards/infrastructure/docker/coolify-deploy.md +8 -6
- package/framework/standards/infrastructure/neon/neon-pgvector.md +120 -75
- package/framework/standards/infrastructure/neon/neon-setup.md +53 -31
- package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +2 -2
- package/framework/templates/code/dotnet/extensions/database-extensions.cs +1 -1
- package/framework/templates/code/dotnet/extensions/result-extensions.cs +27 -0
- package/framework/templates/code/dotnet/feature/endpoint.cs.hbs +2 -2
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaDisclosureTests.cs +1 -1
- package/framework/templates/infrastructure/docker/coolify.template.json +3 -3
- package/framework/templates/infrastructure/github/README.md +2 -1
- package/framework/templates/infrastructure/github/workflows/deploy-coolify.yml.hbs +2 -1
- package/package.json +1 -1
- package/src/commands/observability/telemetry.js +3 -5
- package/src/commands/project/init.js +2 -1
- package/src/commands/project/update.js +16 -18
- package/src/commands/state/approve.js +17 -0
- package/src/commands/state/gate-check.js +4 -14
- package/src/commands/validation/verify.js +99 -57
- package/src/core/paths/output-schema.js +1 -1
- package/src/dashboard/app/App.jsx +0 -2
- package/src/dashboard/app/components/CommandPalette.jsx +2 -3
- package/src/dashboard/app/components/EntityLink.jsx +0 -1
- package/src/dashboard/app/components/Markdown.jsx +1 -1
- package/src/dashboard/app/components/Sidebar.jsx +1 -2
- package/src/dashboard/app/pages/RulesMap.jsx +3 -3
- package/src/dashboard/app/pages/SkillsLibrary.jsx +3 -3
- package/src/dashboard/scanner/activity-scanner.js +1 -1
- package/src/dashboard/scanner/docs-scanner.js +5 -23
- package/src/dashboard/scanner/features-scanner.js +1 -1
- package/src/dashboard/scanner/index.js +0 -2
- package/src/dashboard/scanner/rules-scanner.js +15 -10
- package/src/dashboard/scanner/skills-scanner.js +2 -3
- package/src/lib/ci/ci-verify.js +118 -0
- package/src/lib/ci/github-ci.js +192 -0
- package/src/lib/ci/workflow-scaffold.js +122 -0
- package/src/lib/evaluator-report.js +13 -0
- package/src/lib/risk-detector.js +8 -0
- package/src/lib/stack/stack-profile.js +1 -1
- package/src/lib/telemetry/verify-event.js +3 -2
- package/src/lib/validators/reuse/reuse-validator.js +2 -3
- package/src/lib/validators/reuse/symbol-scanner.js +2 -4
- package/src/lib/validators/validation-runner.js +8 -88
- package/src/lib/validators/verify-runner.js +22 -72
- package/src/scripts/setup-infra.js +33 -22
- package/src/utils/agents-installer.js +20 -8
- package/src/utils/file-copier.js +7 -5
- package/framework/commands/commit.md +0 -166
- package/framework/hooks/claude-code/__pycache__/statusline.cpython-313.pyc +0 -0
- package/framework/rubrics/backend.json +0 -41
- package/framework/rubrics/default.json +0 -36
- package/framework/rubrics/frontend.json +0 -36
- package/framework/templates/project/validate.js +0 -97
- package/src/dashboard/app/pages/Rubrics.jsx +0 -97
- package/src/dashboard/scanner/rubrics-scanner.js +0 -12
- package/src/lib/validators/architecture/architecture-validator.js +0 -270
- package/src/lib/validators/architecture/index.js +0 -7
- package/src/lib/validators/css/css-validator.js +0 -340
- package/src/lib/validators/css/index.js +0 -7
- package/src/lib/validators/design-system/design-system-validator.js +0 -237
- package/src/lib/validators/design-system/index.js +0 -7
- package/src/lib/validators/score-aggregator.js +0 -244
- package/src/lib/validators/ui/index.js +0 -7
- package/src/lib/validators/ui/ui-contrast-validator.js +0 -422
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
# Vector Search + RAG — EF Core 10 + pgvector (.NET 10)
|
|
2
2
|
|
|
3
|
-
> **Scope:**
|
|
3
|
+
> **Scope:** backend/database — pipeline RAG sobre pgvector
|
|
4
4
|
> **Layer:** 2 (on keyword)
|
|
5
5
|
> **Keywords:** postgres, pgvector, vector, embedding, search, rag, ai
|
|
6
6
|
> **Load When:** vector search or rag keywords detected
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
**Verified against:** .NET 10 (SDK 10.0.400-preview) + Npgsql.EntityFrameworkCore.PostgreSQL 10.0.3 + Pgvector.EntityFrameworkCore 0.3.0 + Microsoft.Extensions.AI 10.9.0 + Microsoft.Agents.AI 1.20.0 (ai-pin 2026-09-08). Os blocos de indexação, indexação em lote, busca+geração (RAG), endpoint e job compilam com `TreatWarningsAsErrors` sobre o `Result`/`IHandler`/`ErrorHttpMapping` de `result-pattern.md`/`vertical-slice.md` e o `Document`/`ApplicationDbContext` de `neon-pgvector.md`; o registro do `IEmbeddingGenerator` por provedor, os custos e o SQL são verificação documental. Last-verified: 2026-10-03.
|
|
9
|
+
|
|
10
|
+
EF Core 10 (.NET 10) com PostgreSQL + extensão `pgvector` para vector search em workloads de AI. A modelagem da coluna `vector` (`Document`, `DocumentConfiguration`, `UseVector()`, colunas snake_case), os índices `hnsw`/`ivfflat` e os operadores de distância estão em `infrastructure/neon/neon-pgvector.md` — este standard cobre o pipeline RAG end-to-end (embeddings, busca, geração) em slices VSA sobre essa base: cada operação é um handler que injeta o `ApplicationDbContext` e o `IEmbeddingGenerator` direto, sem `EmbeddingService`/`SearchService` no meio.
|
|
9
11
|
|
|
10
12
|
---
|
|
11
13
|
|
|
@@ -42,76 +44,23 @@ Texto → Embedding (vetor de números) → Busca por similaridade
|
|
|
42
44
|
|
|
43
45
|
```xml
|
|
44
46
|
<!-- .csproj -->
|
|
45
|
-
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.
|
|
46
|
-
<PackageReference Include="Pgvector.EntityFrameworkCore" Version="0.
|
|
47
|
-
<PackageReference Include="Microsoft.Extensions.AI" Version="
|
|
47
|
+
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.3" />
|
|
48
|
+
<PackageReference Include="Pgvector.EntityFrameworkCore" Version="0.3.0" />
|
|
49
|
+
<PackageReference Include="Microsoft.Extensions.AI" Version="10.9.0" />
|
|
50
|
+
<PackageReference Include="Microsoft.Agents.AI" Version="1.20.0" />
|
|
48
51
|
```
|
|
49
52
|
|
|
50
|
-
|
|
53
|
+
As versões de IA vêm do pin (`framework/ai-pin.json`). A ponte do provedor (`Microsoft.Extensions.AI.OpenAI` para `.AsIEmbeddingGenerator()`) não está no pin e tem uma armadilha de restore (NU1608 contra `OpenAI` 2.13.0): ver `ai-agents/setup.md`.
|
|
51
54
|
|
|
52
|
-
|
|
53
|
-
Neon, Supabase e Azure Database for PostgreSQL já trazem `pgvector` disponível.
|
|
55
|
+
### 2. PostgreSQL + pgvector Requerido
|
|
54
56
|
|
|
55
|
-
**
|
|
57
|
+
**Requisito:** PostgreSQL com a extensão `pgvector` (`CREATE EXTENSION vector;`). Neon já traz `pgvector` disponível. Registre `modelBuilder.HasPostgresExtension("vector")` no `OnModelCreating` e a migration EF Core cria a extensão antes das colunas de vetor.
|
|
56
58
|
|
|
57
59
|
---
|
|
58
60
|
|
|
59
61
|
## 🗄️ Modelo de Dados com Vectors
|
|
60
62
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
```csharp
|
|
64
|
-
using Microsoft.EntityFrameworkCore;
|
|
65
|
-
using Pgvector;
|
|
66
|
-
|
|
67
|
-
public class Document
|
|
68
|
-
{
|
|
69
|
-
public int Id { get; set; }
|
|
70
|
-
public string Title { get; set; } = null!;
|
|
71
|
-
public string Content { get; set; } = null!;
|
|
72
|
-
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
|
73
|
-
|
|
74
|
-
// Vector embedding (1536 dimensões para text-embedding-3-small).
|
|
75
|
-
// O tipo `Vector` vem do pacote Pgvector — EF Core não mapeia `float[]`.
|
|
76
|
-
public Vector Embedding { get; set; } = null!;
|
|
77
|
-
}
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
### DbContext Configuração
|
|
81
|
-
|
|
82
|
-
```csharp
|
|
83
|
-
public class AppDbContext : DbContext
|
|
84
|
-
{
|
|
85
|
-
public DbSet<Document> Documents { get; set; }
|
|
86
|
-
|
|
87
|
-
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
|
88
|
-
{
|
|
89
|
-
// Registra a extensão pgvector — gera `CREATE EXTENSION vector` na migration
|
|
90
|
-
modelBuilder.HasPostgresExtension("vector");
|
|
91
|
-
|
|
92
|
-
modelBuilder.Entity<Document>(entity =>
|
|
93
|
-
{
|
|
94
|
-
entity.HasKey(d => d.Id);
|
|
95
|
-
|
|
96
|
-
// Configurar coluna de vector
|
|
97
|
-
entity.Property(d => d.Embedding)
|
|
98
|
-
.HasColumnType("vector(1536)") // 1536 = dimensões do embedding
|
|
99
|
-
.IsRequired();
|
|
100
|
-
|
|
101
|
-
// Índice de vector para performance — operador de distância cosseno
|
|
102
|
-
entity.HasIndex(d => d.Embedding)
|
|
103
|
-
.HasMethod("hnsw") // ou "ivfflat"
|
|
104
|
-
.HasOperators("vector_cosine_ops")
|
|
105
|
-
.HasStorageParameter("m", 16)
|
|
106
|
-
.HasStorageParameter("ef_construction", 64);
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
> O provider Npgsql precisa de `UseVector()` no `NpgsqlDataSourceBuilder` (ou
|
|
113
|
-
> no `UseNpgsql(...)`) para mapear o tipo `vector` — ver a configuração do
|
|
114
|
-
> `Program.cs` abaixo e `infrastructure/neon/neon-pgvector.md`.
|
|
63
|
+
Use o `Document` e o `DocumentConfiguration` de `infrastructure/neon/neon-pgvector.md`: `Id` `Guid` gerado no handler com `Guid.CreateVersion7()`, `Embedding` do tipo `Pgvector.Vector` com `vector(1536)` (EF Core não mapeia `float[]`), índice `hnsw` com `vector_cosine_ops`, `UseVector()` dentro do `UseNpgsql(...)` e colunas snake_case via `HasColumnName`.
|
|
115
64
|
|
|
116
65
|
### Migration
|
|
117
66
|
|
|
@@ -120,149 +69,60 @@ dotnet ef migrations add AddVectorSearch
|
|
|
120
69
|
dotnet ef database update
|
|
121
70
|
```
|
|
122
71
|
|
|
123
|
-
**SQL Gerado:**
|
|
72
|
+
**SQL Gerado (essencial):**
|
|
124
73
|
```sql
|
|
125
74
|
CREATE EXTENSION IF NOT EXISTS vector;
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
75
|
+
CREATE TABLE documents (
|
|
76
|
+
id uuid NOT NULL PRIMARY KEY,
|
|
77
|
+
title text NOT NULL,
|
|
78
|
+
content text NOT NULL,
|
|
79
|
+
metadata jsonb,
|
|
80
|
+
embedding vector(1536),
|
|
81
|
+
created_at timestamp with time zone NOT NULL
|
|
82
|
+
);
|
|
83
|
+
CREATE INDEX ix_documents_embedding ON documents
|
|
84
|
+
USING hnsw (embedding vector_cosine_ops) WITH (m = 16, ef_construction = 64);
|
|
129
85
|
```
|
|
130
86
|
|
|
131
87
|
---
|
|
132
88
|
|
|
133
89
|
## 🔢 Gerando Embeddings
|
|
134
90
|
|
|
135
|
-
|
|
91
|
+
O handler injeta `IEmbeddingGenerator<string, Embedding<float>>` (de `Microsoft.Extensions.AI`) e converte o resultado em `Pgvector.Vector`:
|
|
136
92
|
|
|
137
93
|
```csharp
|
|
138
|
-
|
|
139
|
-
using Pgvector;
|
|
140
|
-
|
|
141
|
-
public interface IEmbeddingService
|
|
142
|
-
{
|
|
143
|
-
Task<Vector> GenerateEmbeddingAsync(string text, CancellationToken ct = default);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
public class EmbeddingService : IEmbeddingService
|
|
147
|
-
{
|
|
148
|
-
private readonly IEmbeddingGenerator<string, Embedding<float>> _embeddingGenerator;
|
|
149
|
-
|
|
150
|
-
public EmbeddingService(IEmbeddingGenerator<string, Embedding<float>> embeddingGenerator)
|
|
151
|
-
{
|
|
152
|
-
_embeddingGenerator = embeddingGenerator;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
public async Task<Vector> GenerateEmbeddingAsync(
|
|
156
|
-
string text,
|
|
157
|
-
CancellationToken ct = default)
|
|
158
|
-
{
|
|
159
|
-
// GenerateVectorAsync devolve o ReadOnlyMemory<float> diretamente
|
|
160
|
-
var vector = await _embeddingGenerator.GenerateVectorAsync(text, cancellationToken: ct);
|
|
161
|
-
return new Vector(vector);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
94
|
+
var vector = new Vector(await embeddings.GenerateVectorAsync(text, cancellationToken: cancellationToken));
|
|
164
95
|
```
|
|
165
96
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
```csharp
|
|
169
|
-
using Azure.AI.OpenAI;
|
|
170
|
-
using Microsoft.Extensions.AI;
|
|
171
|
-
using System.ClientModel;
|
|
172
|
-
|
|
173
|
-
// DbContext com o type handler do pgvector registrado
|
|
174
|
-
builder.Services.AddDbContext<AppDbContext>(options =>
|
|
175
|
-
options.UseNpgsql(
|
|
176
|
-
builder.Configuration.GetConnectionString("Default"),
|
|
177
|
-
npgsql => npgsql.UseVector()));
|
|
97
|
+
`GenerateVectorAsync` devolve `ReadOnlyMemory<float>` para um texto; para muitos textos use `GenerateAsync(IEnumerable<string>)`, que faz **uma** chamada ao provedor (ver *Indexação em lote*).
|
|
178
98
|
|
|
179
|
-
|
|
180
|
-
builder.Services.AddSingleton<IEmbeddingGenerator<string, Embedding<float>>>(sp =>
|
|
181
|
-
{
|
|
182
|
-
var config = sp.GetRequiredService<IConfiguration>();
|
|
183
|
-
|
|
184
|
-
var azureClient = new AzureOpenAIClient(
|
|
185
|
-
new Uri(config["AzureOpenAI:Endpoint"]!),
|
|
186
|
-
new ApiKeyCredential(config["AzureOpenAI:ApiKey"]!));
|
|
187
|
-
|
|
188
|
-
return azureClient
|
|
189
|
-
.GetEmbeddingClient("text-embedding-3-small") // 1536 dimensões
|
|
190
|
-
.AsIEmbeddingGenerator();
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
builder.Services.AddScoped<IEmbeddingService, EmbeddingService>();
|
|
194
|
-
```
|
|
99
|
+
**Registro no DI:** o projeto registra o `IEmbeddingGenerator` do provedor escolhido pelo alias `embeddings` do Model Registry — o exemplo de registro OpenAI está em `ai-agents/rag-custom-pgvector.md` (*EmbeddingService*, a parte do `AddSingleton<IEmbeddingGenerator<...>>`), e a escolha de modelo em `ai-agents-providers-model-registry`. A chave do provedor vem de configuração (user-secrets em dev, variável de ambiente no Coolify), nunca do código. O `DbContext` é registrado como em `neon-pgvector.md` (*DbContext Setup*).
|
|
195
100
|
|
|
196
101
|
---
|
|
197
102
|
|
|
198
103
|
## 🔍 Vector Search com EF Core 10
|
|
199
104
|
|
|
200
|
-
|
|
105
|
+
A busca semântica pura (endpoint de busca, sem LLM) é a slice `SearchDocuments` de `infrastructure/neon/neon-pgvector.md` (*Search handler*). O padrão de query, que o RAG abaixo repete dentro da própria slice:
|
|
201
106
|
|
|
202
107
|
```csharp
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
private readonly AppDbContext _context;
|
|
208
|
-
private readonly IEmbeddingService _embeddingService;
|
|
209
|
-
|
|
210
|
-
public DocumentSearchService(
|
|
211
|
-
AppDbContext context,
|
|
212
|
-
IEmbeddingService embeddingService)
|
|
213
|
-
{
|
|
214
|
-
_context = context;
|
|
215
|
-
_embeddingService = embeddingService;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
public async Task<List<DocumentSearchResult>> SearchAsync(
|
|
219
|
-
string query,
|
|
220
|
-
int limit = 5,
|
|
221
|
-
CancellationToken ct = default)
|
|
222
|
-
{
|
|
223
|
-
// 1. Gerar embedding da query
|
|
224
|
-
var queryEmbedding = await _embeddingService.GenerateEmbeddingAsync(query, ct);
|
|
225
|
-
|
|
226
|
-
// 2. Buscar documentos similares — EF.Functions.CosineDistance traduz para
|
|
227
|
-
// o operador `<=>` do pgvector e usa o índice HNSW vector_cosine_ops
|
|
228
|
-
var results = await _context.Documents
|
|
229
|
-
.Select(d => new DocumentSearchResult
|
|
230
|
-
{
|
|
231
|
-
Document = d,
|
|
232
|
-
// Distância cosseno (menor = mais similar)
|
|
233
|
-
Distance = EF.Functions.CosineDistance(d.Embedding, queryEmbedding)
|
|
234
|
-
})
|
|
235
|
-
.OrderBy(r => r.Distance)
|
|
236
|
-
.Take(limit)
|
|
237
|
-
.ToListAsync(ct);
|
|
238
|
-
|
|
239
|
-
return results;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
public class DocumentSearchResult
|
|
244
|
-
{
|
|
245
|
-
public Document Document { get; set; } = null!;
|
|
246
|
-
public double Distance { get; set; }
|
|
247
|
-
public double Similarity => 1 - Distance; // Converter distância em similaridade
|
|
248
|
-
}
|
|
108
|
+
.Where(d => d.Embedding != null)
|
|
109
|
+
.Select(d => new { d.Id, d.Title, d.Content, Distance = d.Embedding!.CosineDistance(query) })
|
|
110
|
+
.OrderBy(x => x.Distance)
|
|
111
|
+
.Take(limit)
|
|
249
112
|
```
|
|
250
113
|
|
|
114
|
+
Traduz para `ORDER BY d.embedding <=> @query` (ASC), que usa o índice HNSW `vector_cosine_ops`. Similaridade = `1 - distância`.
|
|
115
|
+
|
|
251
116
|
### Funções de Distância
|
|
252
117
|
|
|
253
|
-
O `Pgvector.EntityFrameworkCore` expõe as
|
|
254
|
-
`EF.Functions.*`, cada uma traduzida para o operador correspondente do `pgvector`:
|
|
118
|
+
O `Pgvector.EntityFrameworkCore` expõe as distâncias como métodos de extensão sobre `Vector` (namespace `Pgvector.EntityFrameworkCore`), cada um traduzido para o operador correspondente do `pgvector`:
|
|
255
119
|
|
|
256
|
-
|
|
|
257
|
-
|
|
120
|
+
| Método | Operador pgvector | Quando Usar |
|
|
121
|
+
|--------|-------------------|-------------|
|
|
258
122
|
| `CosineDistance` | `<=>` | **Recomendado** para texto / embeddings normalizados |
|
|
259
123
|
| `L2Distance` | `<->` | Distância euclidiana — dados numéricos |
|
|
260
124
|
| `MaxInnerProduct` | `<#>` | Produto interno negativo — vetores normalizados |
|
|
261
125
|
|
|
262
|
-
```csharp
|
|
263
|
-
EF.Functions.CosineDistance(d.Embedding, queryEmbedding)
|
|
264
|
-
```
|
|
265
|
-
|
|
266
126
|
> O `HasOperators` do índice precisa casar com a função usada na query —
|
|
267
127
|
> `vector_cosine_ops` para `CosineDistance`, `vector_l2_ops` para `L2Distance`,
|
|
268
128
|
> `vector_ip_ops` para `MaxInnerProduct`. Operador e índice divergentes fazem o
|
|
@@ -272,201 +132,224 @@ EF.Functions.CosineDistance(d.Embedding, queryEmbedding)
|
|
|
272
132
|
|
|
273
133
|
## 🤖 RAG Pattern Completo
|
|
274
134
|
|
|
275
|
-
###
|
|
135
|
+
### Slice `AskDocuments` (retrieval + augmentation + generation)
|
|
276
136
|
|
|
277
137
|
```csharp
|
|
138
|
+
// Features/KnowledgeFeature/AskDocuments/AskDocumentsHandler.cs
|
|
139
|
+
using Microsoft.Agents.AI;
|
|
140
|
+
using Microsoft.EntityFrameworkCore;
|
|
278
141
|
using Microsoft.Extensions.AI;
|
|
142
|
+
using Pgvector;
|
|
143
|
+
using Pgvector.EntityFrameworkCore;
|
|
144
|
+
|
|
145
|
+
public sealed record AskDocumentsRequest(string Question);
|
|
146
|
+
public sealed record AskDocumentsResponse(string Answer, IReadOnlyList<Guid> SourceDocumentIds);
|
|
279
147
|
|
|
280
|
-
public
|
|
148
|
+
public static class KnowledgeErrors
|
|
281
149
|
{
|
|
282
|
-
|
|
150
|
+
public static readonly Error NoRelevantDocuments =
|
|
151
|
+
Error.NotFound("Knowledge.NoRelevantDocuments", "Nenhum documento relevante para a pergunta.");
|
|
283
152
|
}
|
|
284
153
|
|
|
285
|
-
public class
|
|
154
|
+
public sealed class AskDocumentsHandler(
|
|
155
|
+
ApplicationDbContext db,
|
|
156
|
+
IEmbeddingGenerator<string, Embedding<float>> embeddings,
|
|
157
|
+
IChatClient chatClient)
|
|
158
|
+
: IHandler<AskDocumentsRequest, Result<AskDocumentsResponse>>
|
|
286
159
|
{
|
|
287
|
-
private
|
|
288
|
-
private
|
|
289
|
-
private readonly ILogger<DocumentAssistantAgent> _logger;
|
|
290
|
-
|
|
291
|
-
public DocumentAssistantAgent(
|
|
292
|
-
IChatClient chatClient,
|
|
293
|
-
DocumentSearchService searchService,
|
|
294
|
-
ILogger<DocumentAssistantAgent> logger)
|
|
295
|
-
{
|
|
296
|
-
_chatClient = chatClient;
|
|
297
|
-
_searchService = searchService;
|
|
298
|
-
_logger = logger;
|
|
299
|
-
}
|
|
160
|
+
private const int TopK = 3;
|
|
161
|
+
private const double MaxDistance = 0.5;
|
|
300
162
|
|
|
301
|
-
public async Task<
|
|
163
|
+
public async Task<Result<AskDocumentsResponse>> HandleAsync(
|
|
164
|
+
AskDocumentsRequest request, CancellationToken cancellationToken)
|
|
302
165
|
{
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
var agent = _chatClient.CreateAgent(
|
|
322
|
-
instructions: """
|
|
166
|
+
var query = new Vector(await embeddings.GenerateVectorAsync(request.Question, cancellationToken: cancellationToken));
|
|
167
|
+
|
|
168
|
+
var relevant = await db.Documents
|
|
169
|
+
.Where(d => d.Embedding != null)
|
|
170
|
+
.Select(d => new { d.Id, d.Title, d.Content, Distance = d.Embedding!.CosineDistance(query) })
|
|
171
|
+
.Where(x => x.Distance <= MaxDistance)
|
|
172
|
+
.OrderBy(x => x.Distance)
|
|
173
|
+
.Take(TopK)
|
|
174
|
+
.ToListAsync(cancellationToken);
|
|
175
|
+
|
|
176
|
+
if (relevant.Count == 0)
|
|
177
|
+
return KnowledgeErrors.NoRelevantDocuments;
|
|
178
|
+
|
|
179
|
+
var context = string.Join("\n\n", relevant.Select(r =>
|
|
180
|
+
$"[Documento {r.Id} - Similaridade: {1 - r.Distance:P0}]\nTítulo: {r.Title}\nConteúdo: {r.Content}"));
|
|
181
|
+
|
|
182
|
+
var agent = chatClient.AsAIAgent(
|
|
183
|
+
instructions: $"""
|
|
323
184
|
Você é um assistente que responde perguntas baseado em documentos fornecidos.
|
|
324
185
|
|
|
325
186
|
Regras:
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
187
|
+
- Use APENAS informações dos documentos fornecidos
|
|
188
|
+
- Se a informação não estiver nos documentos, diga "Não encontrei informação sobre isso"
|
|
189
|
+
- Cite o identificador do documento ao responder
|
|
190
|
+
- Seja conciso e objetivo
|
|
330
191
|
|
|
331
192
|
Documentos disponíveis:
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
193
|
+
{context}
|
|
194
|
+
""",
|
|
195
|
+
name: "DocumentAssistant");
|
|
335
196
|
|
|
336
|
-
|
|
337
|
-
var response = await agent.RunAsync(question, cancellationToken: ct);
|
|
197
|
+
var response = await agent.RunAsync(request.Question, cancellationToken: cancellationToken);
|
|
338
198
|
|
|
339
|
-
return response.
|
|
199
|
+
return new AskDocumentsResponse(response.Text, relevant.Select(r => r.Id).ToList());
|
|
340
200
|
}
|
|
341
201
|
}
|
|
342
202
|
```
|
|
343
203
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
@page "/ask"
|
|
348
|
-
@inject IDocumentAssistantAgent Assistant
|
|
204
|
+
- Sem documento relevante, a slice devolve `NotFound` **antes** de chamar o LLM — não paga uma geração para responder "não sei".
|
|
205
|
+
- O `IChatClient` vem do DI; com Model Registry, resolva-o pelo alias (`ai-agents/rag-custom-pgvector.md` → *Exemplo de uso*). Se o agente deve decidir **quando** buscar, exponha a busca como tool (`AIFunctionFactory.Create`) em vez de injetar o contexto — também em `rag-custom-pgvector.md`.
|
|
206
|
+
- O prompt fica inline aqui por brevidade; em produção ele vem de `IPromptRepository` (`ai-agents/prompt-sources.md`).
|
|
349
207
|
|
|
350
|
-
|
|
208
|
+
### Endpoint
|
|
351
209
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
210
|
+
```csharp
|
|
211
|
+
// Features/KnowledgeFeature/AskDocuments/AskDocumentsEndpoint.cs
|
|
212
|
+
using ProjectName.Abstractions;
|
|
213
|
+
using ProjectName.Extensions;
|
|
356
214
|
|
|
357
|
-
|
|
358
|
-
{
|
|
359
|
-
<p>Buscando resposta...</p>
|
|
360
|
-
}
|
|
361
|
-
else if (!string.IsNullOrEmpty(_answer))
|
|
215
|
+
internal sealed class AskDocumentsEndpoint : IApiEndpoint
|
|
362
216
|
{
|
|
363
|
-
|
|
364
|
-
<strong>Resposta:</strong>
|
|
365
|
-
<p>@_answer</p>
|
|
366
|
-
</div>
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
@code {
|
|
370
|
-
private QuestionInput _input = new();
|
|
371
|
-
private string _answer = "";
|
|
372
|
-
private bool _isLoading;
|
|
373
|
-
|
|
374
|
-
private async Task AskQuestion()
|
|
217
|
+
public void MapEndpoint(IEndpointRouteBuilder app)
|
|
375
218
|
{
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
219
|
+
app.MapPost("knowledge/ask", async (
|
|
220
|
+
IHandler<AskDocumentsRequest, Result<AskDocumentsResponse>> handler,
|
|
221
|
+
AskDocumentsRequest request,
|
|
222
|
+
CancellationToken cancellationToken) =>
|
|
380
223
|
{
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
public class QuestionInput
|
|
390
|
-
{
|
|
391
|
-
public string Question { get; set; } = "";
|
|
224
|
+
var result = await handler.HandleAsync(request, cancellationToken);
|
|
225
|
+
return result.Match(
|
|
226
|
+
onSuccess: response => Results.Ok(response),
|
|
227
|
+
onFailure: error => error.ToHttpResult());
|
|
228
|
+
})
|
|
229
|
+
.Produces<AskDocumentsResponse>(StatusCodes.Status200OK)
|
|
230
|
+
.ProducesProblem(StatusCodes.Status400BadRequest)
|
|
231
|
+
.ProducesProblem(StatusCodes.Status404NotFound);
|
|
392
232
|
}
|
|
393
233
|
}
|
|
394
234
|
```
|
|
395
235
|
|
|
236
|
+
O frontend Next.js chama `knowledge/ask` pelo proxy com TanStack Query (`useMutation`), como qualquer outro endpoint — a UI não fala com o banco nem com o provedor de IA.
|
|
237
|
+
|
|
396
238
|
---
|
|
397
239
|
|
|
398
240
|
## 📥 Indexação de Documentos
|
|
399
241
|
|
|
400
|
-
###
|
|
242
|
+
### Slice `IndexDocument`
|
|
401
243
|
|
|
402
244
|
```csharp
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
245
|
+
// Features/KnowledgeFeature/IndexDocument/IndexDocumentHandler.cs
|
|
246
|
+
using Microsoft.Extensions.AI;
|
|
247
|
+
using Pgvector;
|
|
248
|
+
|
|
249
|
+
public sealed record IndexDocumentRequest(string Title, string Content);
|
|
250
|
+
public sealed record IndexDocumentResponse(Guid Id);
|
|
407
251
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
252
|
+
public sealed class IndexDocumentHandler(
|
|
253
|
+
ApplicationDbContext db,
|
|
254
|
+
IEmbeddingGenerator<string, Embedding<float>> embeddings,
|
|
255
|
+
TimeProvider timeProvider)
|
|
256
|
+
: IHandler<IndexDocumentRequest, Result<IndexDocumentResponse>>
|
|
257
|
+
{
|
|
258
|
+
public async Task<Result<IndexDocumentResponse>> HandleAsync(
|
|
259
|
+
IndexDocumentRequest request, CancellationToken cancellationToken)
|
|
412
260
|
{
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
$"{title}\n{content}", // Combinar título e conteúdo
|
|
416
|
-
ct
|
|
417
|
-
);
|
|
261
|
+
var embedding = await embeddings.GenerateVectorAsync(
|
|
262
|
+
$"{request.Title}\n{request.Content}", cancellationToken: cancellationToken);
|
|
418
263
|
|
|
419
|
-
// 2. Criar documento
|
|
420
264
|
var document = new Document
|
|
421
265
|
{
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
266
|
+
Id = Guid.CreateVersion7(),
|
|
267
|
+
Title = request.Title,
|
|
268
|
+
Content = request.Content,
|
|
269
|
+
Embedding = new Vector(embedding),
|
|
270
|
+
CreatedAt = timeProvider.GetUtcNow()
|
|
426
271
|
};
|
|
427
272
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
273
|
+
db.Documents.Add(document);
|
|
274
|
+
await db.SaveChangesAsync(cancellationToken);
|
|
275
|
+
|
|
276
|
+
return new IndexDocumentResponse(document.Id);
|
|
431
277
|
}
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### Indexação em lote
|
|
432
282
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
283
|
+
Uma chamada ao provedor por lote, um `SaveChangesAsync` por lote — nunca um loop que chama a slice unitária N vezes:
|
|
284
|
+
|
|
285
|
+
```csharp
|
|
286
|
+
// Features/KnowledgeFeature/IndexDocuments/IndexDocumentsHandler.cs
|
|
287
|
+
using Microsoft.Extensions.AI;
|
|
288
|
+
using Pgvector;
|
|
289
|
+
|
|
290
|
+
public sealed record IndexDocumentsRequest(IReadOnlyList<IndexDocumentRequest> Documents);
|
|
291
|
+
|
|
292
|
+
public sealed class IndexDocumentsHandler(
|
|
293
|
+
ApplicationDbContext db,
|
|
294
|
+
IEmbeddingGenerator<string, Embedding<float>> embeddings,
|
|
295
|
+
TimeProvider timeProvider)
|
|
296
|
+
: IHandler<IndexDocumentsRequest, Result<int>>
|
|
297
|
+
{
|
|
298
|
+
public async Task<Result<int>> HandleAsync(IndexDocumentsRequest request, CancellationToken cancellationToken)
|
|
436
299
|
{
|
|
437
|
-
|
|
300
|
+
var generated = await embeddings.GenerateAsync(
|
|
301
|
+
request.Documents.Select(d => $"{d.Title}\n{d.Content}"), cancellationToken: cancellationToken);
|
|
302
|
+
|
|
303
|
+
var now = timeProvider.GetUtcNow();
|
|
304
|
+
db.Documents.AddRange(request.Documents.Zip(generated, (doc, embedding) => new Document
|
|
438
305
|
{
|
|
439
|
-
|
|
440
|
-
|
|
306
|
+
Id = Guid.CreateVersion7(),
|
|
307
|
+
Title = doc.Title,
|
|
308
|
+
Content = doc.Content,
|
|
309
|
+
Embedding = new Vector(embedding.Vector),
|
|
310
|
+
CreatedAt = now
|
|
311
|
+
}));
|
|
312
|
+
|
|
313
|
+
return await db.SaveChangesAsync(cancellationToken);
|
|
441
314
|
}
|
|
442
315
|
}
|
|
443
316
|
```
|
|
444
317
|
|
|
445
|
-
|
|
318
|
+
Lotes grandes: fatie a lista (ex.: 100 textos por chamada) para ficar abaixo do limite de tokens por request do provedor. Chunking por tipo de documento e ingestão delta por lookup-key: `ai-agents/rag-custom-pgvector.md`.
|
|
319
|
+
|
|
320
|
+
### Job de Indexação com Hangfire (só com razão real)
|
|
321
|
+
|
|
322
|
+
Reindexar uma fonte externa todo dia é razão real para um job; indexar o upload de um usuário não é — chame a slice no próprio request. O job é um ponto de entrada fino que chama o handler (`backend/integrations/hangfire/hangfire-jobs.md`):
|
|
446
323
|
|
|
447
324
|
```csharp
|
|
448
|
-
|
|
325
|
+
// Features/KnowledgeFeature/IndexDocuments/ReindexKnowledgeJob.cs
|
|
326
|
+
using Microsoft.Extensions.Logging;
|
|
327
|
+
|
|
328
|
+
public interface IKnowledgeSource
|
|
449
329
|
{
|
|
450
|
-
|
|
451
|
-
|
|
330
|
+
Task<IReadOnlyList<IndexDocumentRequest>> FetchAllAsync(CancellationToken cancellationToken);
|
|
331
|
+
}
|
|
452
332
|
|
|
453
|
-
|
|
333
|
+
public sealed class ReindexKnowledgeJob(
|
|
334
|
+
IKnowledgeSource source,
|
|
335
|
+
IHandler<IndexDocumentsRequest, Result<int>> handler,
|
|
336
|
+
ILogger<ReindexKnowledgeJob> logger)
|
|
337
|
+
{
|
|
338
|
+
public async Task RunAsync(CancellationToken cancellationToken)
|
|
454
339
|
{
|
|
455
|
-
|
|
456
|
-
var
|
|
340
|
+
var documents = await source.FetchAllAsync(cancellationToken);
|
|
341
|
+
var result = await handler.HandleAsync(new IndexDocumentsRequest(documents), cancellationToken);
|
|
457
342
|
|
|
458
|
-
|
|
343
|
+
if (result.IsFailure)
|
|
344
|
+
logger.LogWarning("Reindexação falhou: {Code}", result.Error.Code);
|
|
345
|
+
else
|
|
346
|
+
logger.LogInformation("Reindexados {Count} documentos", result.Value);
|
|
459
347
|
}
|
|
460
348
|
}
|
|
461
|
-
|
|
462
|
-
// Program.cs - Agendar job diário
|
|
463
|
-
RecurringJob.AddOrUpdate<DocumentIndexingJob>(
|
|
464
|
-
"index-documents",
|
|
465
|
-
job => job.IndexAllDocumentsAsync(),
|
|
466
|
-
Cron.Daily
|
|
467
|
-
);
|
|
468
349
|
```
|
|
469
350
|
|
|
351
|
+
Agendamento diário (UTC), no `Program.cs`: `RecurringJob.AddOrUpdate<ReindexKnowledgeJob>("reindex-knowledge", job => job.RunAsync(CancellationToken.None), Cron.Daily());` — o Hangfire troca o `CancellationToken.None` pelo token de cancelamento do job.
|
|
352
|
+
|
|
470
353
|
---
|
|
471
354
|
|
|
472
355
|
## 📊 Índices de Performance
|
|
@@ -522,7 +405,7 @@ HNSW é o padrão recomendado: melhor recall e não exige dados pré-carregados.
|
|
|
522
405
|
| text-embedding-3-small | 1536 | $0.02 / 1M tokens |
|
|
523
406
|
| text-embedding-3-large | 3072 | $0.13 / 1M tokens |
|
|
524
407
|
|
|
525
|
-
**Recomendação:** Use `text-embedding-3-small` (melhor custo-benefício).
|
|
408
|
+
**Recomendação:** Use `text-embedding-3-small` (melhor custo-benefício). Confira o preço vigente no provedor antes de orçar.
|
|
526
409
|
|
|
527
410
|
### Storage
|
|
528
411
|
|
|
@@ -535,15 +418,15 @@ HNSW é o padrão recomendado: melhor recall e não exige dados pré-carregados.
|
|
|
535
418
|
|
|
536
419
|
## ✅ Checklist de Implementação
|
|
537
420
|
|
|
538
|
-
- [ ]
|
|
421
|
+
- [ ] `Npgsql.EntityFrameworkCore.PostgreSQL` + `Pgvector.EntityFrameworkCore` instalados; versões de IA do pin
|
|
539
422
|
- [ ] PostgreSQL com extensão `pgvector` (`HasPostgresExtension("vector")`)
|
|
540
423
|
- [ ] `UseVector()` chamado no `UseNpgsql(...)`
|
|
541
|
-
- [ ] Entidade com propriedade `Pgvector.Vector`
|
|
424
|
+
- [ ] Entidade com propriedade `Pgvector.Vector`, `Id` via `Guid.CreateVersion7()`, `CreatedAt` via `TimeProvider`
|
|
542
425
|
- [ ] Índice de vector criado (`hnsw` ou `ivfflat`) com `HasOperators("vector_cosine_ops")`
|
|
543
|
-
- [ ] `
|
|
544
|
-
- [ ]
|
|
545
|
-
- [ ] RAG
|
|
546
|
-
- [ ]
|
|
426
|
+
- [ ] `IEmbeddingGenerator<string, Embedding<float>>` registrado no DI e injetado **direto** nos handlers (sem `EmbeddingService`/`SearchService`)
|
|
427
|
+
- [ ] Busca com `CosineDistance` + `OrderBy` da distância
|
|
428
|
+
- [ ] Slice RAG devolve `Result`, com `NotFound` quando não há documento relevante; endpoint mapeia com `error.ToHttpResult()`
|
|
429
|
+
- [ ] Indexação em lote com `GenerateAsync(IEnumerable<string>)`; job Hangfire só com razão real
|
|
547
430
|
- [ ] Testes de similaridade funcionando
|
|
548
431
|
|
|
549
432
|
---
|
|
@@ -554,22 +437,21 @@ HNSW é o padrão recomendado: melhor recall e não exige dados pré-carregados.
|
|
|
554
437
|
|
|
555
438
|
**Causa:** extensão `pgvector` não instalada no banco.
|
|
556
439
|
|
|
557
|
-
**Solução:**
|
|
558
|
-
no `OnModelCreating` para que a migration EF Core a crie automaticamente.
|
|
440
|
+
**Solução:** registre `HasPostgresExtension("vector")` no `OnModelCreating` para que a migration EF Core a crie (ou rode `CREATE EXTENSION vector;`).
|
|
559
441
|
|
|
560
442
|
### Erro: "column ... is of type vector but expression is of type ..."
|
|
561
443
|
|
|
562
444
|
**Causa:** `UseVector()` não foi chamado — o Npgsql não mapeia o tipo `vector`.
|
|
563
445
|
|
|
564
446
|
**Solução:** chame `npgsql.UseVector()` no `UseNpgsql(...)` (ou `UseVector()` no
|
|
565
|
-
`NpgsqlDataSourceBuilder`).
|
|
447
|
+
`NpgsqlDataSourceBuilder` quando usa Npgsql sem EF Core).
|
|
566
448
|
|
|
567
449
|
### Performance lenta em queries
|
|
568
450
|
|
|
569
451
|
**Causa:** índice não criado, ou operador da query difere do `HasOperators` do índice.
|
|
570
452
|
|
|
571
453
|
**Solução:**
|
|
572
|
-
- Verifique se o índice existe: `SELECT * FROM pg_indexes WHERE indexname LIKE '%
|
|
454
|
+
- Verifique se o índice existe: `SELECT * FROM pg_indexes WHERE indexname LIKE '%embedding%'`
|
|
573
455
|
- Confirme com `EXPLAIN ANALYZE` que a query usa o índice (e não `Seq Scan`)
|
|
574
456
|
- Garanta que o operador casa: `CosineDistance` ↔ `vector_cosine_ops`
|
|
575
457
|
- Ajuste parâmetros `m`/`ef_construction` (HNSW) ou `lists` (IVFFlat)
|
|
@@ -581,18 +463,19 @@ no `OnModelCreating` para que a migration EF Core a crie automaticamente.
|
|
|
581
463
|
**Solução:**
|
|
582
464
|
- `text-embedding-3-small` → 1536 dimensões
|
|
583
465
|
- `text-embedding-3-large` → 3072 dimensões
|
|
584
|
-
- Atualize `vector(N)` na migration
|
|
466
|
+
- Atualize `vector(N)` na configuração e gere nova migration
|
|
585
467
|
|
|
586
468
|
---
|
|
587
469
|
|
|
588
470
|
## 📚 Referências
|
|
589
471
|
|
|
590
472
|
- [pgvector](https://github.com/pgvector/pgvector) — extensão PostgreSQL para vector search
|
|
591
|
-
- [Pgvector.EntityFrameworkCore](https://github.com/pgvector/pgvector-dotnet) — type handler +
|
|
473
|
+
- [Pgvector.EntityFrameworkCore](https://github.com/pgvector/pgvector-dotnet) — type handler + métodos de distância
|
|
592
474
|
- [Npgsql EF Core Provider](https://www.npgsql.org/efcore/) — `UseVector()`, `HasPostgresExtension`
|
|
593
475
|
- [Microsoft.Extensions.AI - Embeddings](https://learn.microsoft.com/dotnet/ai/microsoft-extensions-ai)
|
|
594
476
|
- [OpenAI Embeddings](https://platform.openai.com/docs/guides/embeddings)
|
|
595
|
-
- `infrastructure/neon/neon-pgvector.md` — modelagem da coluna, índices e
|
|
477
|
+
- `infrastructure/neon/neon-pgvector.md` — modelagem da coluna, índices, operadores e a slice de busca
|
|
478
|
+
- `ai-agents/rag-custom-pgvector.md` — chunking, ingestão delta, RAG como tool, registro do `IEmbeddingGenerator`
|
|
596
479
|
|
|
597
480
|
---
|
|
598
481
|
|