Skip to content

Commit c516c07

Browse files
Merge branch 'feature/phase-2-embedding-service'
2 parents 2d7b1f4 + 0a8fee4 commit c516c07

6 files changed

Lines changed: 956 additions & 7 deletions

File tree

docs/plans/v1-core-implementation-plan.md

Lines changed: 182 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -180,40 +180,81 @@ builder.AiChatMiddleware()
180180

181181
---
182182

183-
## Phase 2: IAiEmbeddingService Implementation
183+
## Phase 2: IAiEmbeddingService Implementation ✅ COMPLETED
184+
185+
### Implementation Status: COMPLETE (2025-11-26)
186+
187+
**Completed Changes:**
188+
-`IAiEmbeddingService.cs` - Created interface with 5 methods for single/batch embedding generation and direct generator access
189+
-`AiEmbeddingService.cs` - Created internal implementation following `AiChatService` pattern
190+
-`UmbracoBuilderExtensions.cs` - Registered `IAiEmbeddingService` in DI container
191+
-`FakeEmbeddingCapability.cs` - Enhanced `FakeEmbeddingGenerator` to track received values/options for testing
192+
-`AiEmbeddingServiceTests.cs` - Created 16 comprehensive unit tests
193+
194+
**Branch:** `feature/phase-2-embedding-service`
184195

185196
### New Files
186197

187198
**`src/Umbraco.Ai.Core/Services/IAiEmbeddingService.cs`**
188199
```csharp
189200
public interface IAiEmbeddingService
190201
{
202+
// Single value - default profile
203+
Task<Embedding<float>> GenerateEmbeddingAsync(
204+
string value,
205+
EmbeddingGenerationOptions? options = null,
206+
CancellationToken cancellationToken = default);
207+
208+
// Single value - specific profile
209+
Task<Embedding<float>> GenerateEmbeddingAsync(
210+
Guid profileId,
211+
string value,
212+
EmbeddingGenerationOptions? options = null,
213+
CancellationToken cancellationToken = default);
214+
215+
// Multiple values - default profile
191216
Task<GeneratedEmbeddings<Embedding<float>>> GenerateEmbeddingsAsync(
192217
IEnumerable<string> values,
193218
EmbeddingGenerationOptions? options = null,
194219
CancellationToken cancellationToken = default);
195220

221+
// Multiple values - specific profile
196222
Task<GeneratedEmbeddings<Embedding<float>>> GenerateEmbeddingsAsync(
197223
Guid profileId,
198224
IEnumerable<string> values,
199225
EmbeddingGenerationOptions? options = null,
200226
CancellationToken cancellationToken = default);
201227

202-
Task<Embedding<float>> GenerateEmbeddingAsync(string value, ...);
203-
228+
// Direct generator access for advanced scenarios
204229
Task<IEmbeddingGenerator<string, Embedding<float>>> GetEmbeddingGeneratorAsync(
205-
Guid? profileId = null, CancellationToken cancellationToken = default);
230+
Guid? profileId = null,
231+
CancellationToken cancellationToken = default);
206232
}
207233
```
208234

209235
**`src/Umbraco.Ai.Core/Services/AiEmbeddingService.cs`**
210236
- Inject `IAiEmbeddingGeneratorFactory`, `IAiProfileService`, `IOptionsMonitor<AiOptions>`
211-
- Resolve default profile via `AiOptions.DefaultEmbeddingProfileAlias`
237+
- Resolve default profile via `IAiProfileService.GetDefaultProfileAsync(AiCapability.Embedding, ...)`
212238
- Delegate to factory for generator creation
239+
- Options merging: caller options override profile defaults
213240

214241
### Modified Files
215242

216243
- `src/Umbraco.Ai.Core/Configuration/UmbracoBuilderExtensions.cs` - Register `IAiEmbeddingService`
244+
- `tests/Umbraco.Ai.Tests.Common/Fakes/FakeEmbeddingCapability.cs` - Enhanced `FakeEmbeddingGenerator` with `ReceivedValues` and `ReceivedOptions` tracking
245+
246+
### Tests Implemented
247+
248+
**Unit Tests (`tests/Umbraco.Ai.Tests.Unit/Services/AiEmbeddingServiceTests.cs`):**
249+
250+
16 tests covering:
251+
- ✅ Default embedding profile resolution
252+
- ✅ Named profile resolution by ID
253+
- ✅ Error handling: profile not found (`InvalidOperationException`)
254+
- ✅ Error handling: profile has wrong capability (Chat instead of Embedding)
255+
- ✅ Options merging when caller provides custom `EmbeddingGenerationOptions`
256+
- ✅ Multiple values returning embeddings for each value
257+
- ✅ Direct generator access via `GetEmbeddingGeneratorAsync`
217258

218259
---
219260

@@ -311,6 +352,48 @@ src/Umbraco.Ai.Web/Api/Management/
311352
| Chat | POST | `/chat/complete` | Chat completion (non-streaming) |
312353
| Chat | POST | `/chat/stream` | Chat completion with SSE streaming |
313354

355+
### Testing Requirements
356+
357+
**Unit Tests (`tests/Umbraco.Ai.Tests.Unit/Api/`):**
358+
359+
For each controller, test the critical request/response mapping and validation:
360+
361+
**Connection Controllers:**
362+
- `AllConnectionController` - Returns paginated list
363+
- `ByIdConnectionController` - Returns 404 when not found
364+
- `CreateConnectionController` - Validates required fields, returns created entity with ID
365+
- `UpdateConnectionController` - Returns 404 when not found
366+
- `DeleteConnectionController` - Returns 404 when not found, handles in-use connections
367+
- `TestConnectionController` - Returns test result with success/failure status
368+
369+
**Profile Controllers:**
370+
- `AllProfileController` - Filters by capability query param
371+
- `ByIdProfileController` - Returns 404 when not found
372+
- `ByAliasProfileController` - Returns 404 when alias not found
373+
- `CreateProfileController` - Validates required fields, alias uniqueness
374+
- `UpdateProfileController` - Returns 404 when not found
375+
- `DeleteProfileController` - Returns 404 when not found
376+
377+
**Provider Controllers:**
378+
- `AllProviderController` - Returns all registered providers
379+
- `ByIdProviderController` - Returns settings schema, 404 when not found
380+
- `ModelsByProviderController` - Returns available models for provider
381+
382+
**Embedding Controller:**
383+
- `GenerateEmbeddingController` - Validates input, returns embeddings array
384+
385+
**Chat Controllers:**
386+
- `CompleteChatController` - Validates messages array, returns response
387+
- `StreamChatController` - Returns SSE stream (integration test preferred)
388+
389+
**Integration Tests (`tests/Umbraco.Ai.Tests.Integration/Api/`):**
390+
391+
Create integration tests for full HTTP request/response cycles:
392+
- Connection CRUD workflow (create → read → update → delete)
393+
- Profile CRUD workflow with connection dependency
394+
- Test connection with fake provider
395+
- Generate embeddings with fake provider
396+
314397
---
315398

316399
## Phase 4: EF Core Database Persistence
@@ -536,6 +619,84 @@ The `MigrationsAssembly()` call in each provider's setup ensures EF Core looks f
536619

537620
Migrations auto-apply on startup via `RunAiMigrationNotificationHandler`.
538621

622+
### Testing Requirements
623+
624+
**Unit Tests (`tests/Umbraco.Ai.Tests.Unit/Repositories/`):**
625+
626+
Test repository methods with in-memory SQLite:
627+
628+
**EfCoreAiConnectionRepository:**
629+
- `GetAsync` - Returns null when not found
630+
- `GetAllAsync` - Returns empty list when no data
631+
- `SaveAsync` - Creates new entity (insert)
632+
- `SaveAsync` - Updates existing entity (upsert)
633+
- `DeleteAsync` - Removes entity, returns true
634+
- `DeleteAsync` - Returns false when not found
635+
- Settings JSON serialization/deserialization
636+
637+
**EfCoreAiProfileRepository:**
638+
- `GetByIdAsync` - Returns null when not found
639+
- `GetByAliasAsync` - Case-insensitive alias lookup
640+
- `GetAllAsync` - Filters by capability
641+
- `SaveAsync` - Enforces unique alias constraint
642+
- `DeleteAsync` - Handles foreign key constraint (connection reference)
643+
- Tags JSON serialization/deserialization
644+
645+
**Migration Tests:**
646+
- Verify migrations apply cleanly to empty database
647+
- Verify schema matches entity configuration
648+
649+
**Test Fixture (`tests/Umbraco.Ai.Tests.Common/Fixtures/EfCoreTestFixture.cs`):**
650+
651+
```csharp
652+
public class EfCoreTestFixture : IDisposable
653+
{
654+
private readonly SqliteConnection _connection;
655+
656+
public UmbracoAiDbContext CreateContext()
657+
{
658+
var options = new DbContextOptionsBuilder<UmbracoAiDbContext>()
659+
.UseSqlite(_connection)
660+
.Options;
661+
return new UmbracoAiDbContext(options);
662+
}
663+
664+
public EfCoreTestFixture()
665+
{
666+
_connection = new SqliteConnection("DataSource=:memory:");
667+
_connection.Open();
668+
669+
using var context = CreateContext();
670+
context.Database.EnsureCreated();
671+
}
672+
673+
public void Dispose() => _connection.Dispose();
674+
}
675+
```
676+
677+
Usage in tests:
678+
```csharp
679+
public class EfCoreAiConnectionRepositoryTests : IClassFixture<EfCoreTestFixture>
680+
{
681+
private readonly EfCoreTestFixture _fixture;
682+
683+
public EfCoreAiConnectionRepositoryTests(EfCoreTestFixture fixture)
684+
=> _fixture = fixture;
685+
686+
[Fact]
687+
public async Task GetAsync_WhenNotFound_ReturnsNull()
688+
{
689+
await using var context = _fixture.CreateContext();
690+
var scopeProvider = CreateScopeProvider(context);
691+
var repository = new EfCoreAiConnectionRepository(scopeProvider);
692+
693+
var result = await repository.GetAsync(Guid.NewGuid());
694+
695+
result.ShouldBeNull();
696+
}
697+
}
698+
```
699+
539700
---
540701

541702
## Phase 5: Frontend UI Implementation
@@ -728,6 +889,22 @@ export class AiConnectionWorkspaceContext
728889
- Dynamically renders `<umb-property>` for each setting based on `EditorUiAlias`
729890
- Emits change events with updated values
730891

892+
### Testing Requirements
893+
894+
**No automated tests required for v1.**
895+
896+
Rationale: Frontend tests (Playwright/WebdriverIO) add significant complexity and maintenance overhead. For v1, rely on manual QA testing of the UI. Consider adding E2E tests in v2 once the UI patterns stabilize.
897+
898+
**Manual Test Checklist:**
899+
- Connections collection view loads and displays data
900+
- Connection create/edit workspace saves correctly
901+
- Connection delete shows confirmation and removes entity
902+
- Profiles collection view loads with capability filter
903+
- Profile create/edit workspace validates required fields
904+
- Profile delete handles connection dependency warning
905+
- Dynamic settings form renders based on provider schema
906+
- Model picker filters by provider and capability
907+
731908
---
732909

733910
## Implementation Order

src/Umbraco.Ai.Core/Configuration/UmbracoBuilderExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ internal static IUmbracoBuilder AddUmbracoAiCore(this IUmbracoBuilder builder)
6262

6363
// High-level services
6464
services.AddSingleton<IAiChatService, AiChatService>();
65-
// TODO: services.AddSingleton<IAiEmbeddingService, AiEmbeddingService>();
65+
services.AddSingleton<IAiEmbeddingService, AiEmbeddingService>();
6666
// TODO: services.AddSingleton<IAiToolService, AiToolService>();
6767

6868
return builder;

0 commit comments

Comments
 (0)