Skip to content

Commit 90b69f0

Browse files
committed
Refactor endpoint definitions and update dependencies
- Separated endpoint definitions into `AskEndpoints` and `DocumentEndpoints` classes for better organization and maintainability. - Removed inline endpoint definitions in `Program.cs` and replaced with `app.MapEndpoints();`. - Updated `Microsoft.SemanticKernel` package from version `1.40.0` to `1.40.1`. - Replaced `MinimalHelpers.OpenApi` with `MinimalHelpers.Routing.Analyzers` in project dependencies. - Defined new POST endpoints for asking questions and document management with detailed API documentation. - Enhanced code structure for easier management and future extensibility.
1 parent 3683d16 commit 90b69f0

File tree

4 files changed

+112
-88
lines changed

4 files changed

+112
-88
lines changed
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+

2+
using System.ComponentModel;
3+
using SqlDatabaseVectorSearch.Models;
4+
using SqlDatabaseVectorSearch.Services;
5+
6+
namespace SqlDatabaseVectorSearch.Endpoints;
7+
8+
public class AskEndpoints : IEndpointRouteHandlerBuilder
9+
{
10+
public static void MapEndpoints(IEndpointRouteBuilder endpoints)
11+
{
12+
endpoints.MapPost("/api/ask", async (Question question, VectorSearchService vectorSearchService, CancellationToken cancellationToken,
13+
[Description("If true, the question will be reformulated taking into account the context of the chat identified by the given ConversationId.")] bool reformulate = true) =>
14+
{
15+
var response = await vectorSearchService.AskQuestionAsync(question, reformulate, cancellationToken);
16+
return TypedResults.Ok(response);
17+
})
18+
.WithSummary("Asks a question")
19+
.WithDescription("The question will be reformulated taking into account the context of the chat identified by the given ConversationId.")
20+
.WithTags("Ask");
21+
22+
endpoints.MapPost("/api/ask-streaming", (Question question, VectorSearchService vectorSearchService, CancellationToken cancellationToken,
23+
[Description("If true, the question will be reformulated taking into account the context of the chat identified by the given ConversationId.")] bool reformulate = true) =>
24+
{
25+
async IAsyncEnumerable<QuestionResponse> Stream()
26+
{
27+
// Requests a streaming response.
28+
var responseStream = vectorSearchService.AskStreamingAsync(question, reformulate, cancellationToken);
29+
30+
await foreach (var delta in responseStream)
31+
{
32+
yield return delta;
33+
}
34+
}
35+
36+
return Stream();
37+
})
38+
.WithSummary("Asks a question and gets the response as streaming")
39+
.WithDescription("The question will be reformulated taking into account the context of the chat identified by the given ConversationId.")
40+
.WithTags("Ask");
41+
}
42+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+

2+
using System.ComponentModel;
3+
using Microsoft.AspNetCore.Http.HttpResults;
4+
using MimeMapping;
5+
using SqlDatabaseVectorSearch.Models;
6+
using SqlDatabaseVectorSearch.Services;
7+
8+
namespace SqlDatabaseVectorSearch.Endpoints;
9+
10+
public class DocumentEndpoints : IEndpointRouteHandlerBuilder
11+
{
12+
public static void MapEndpoints(IEndpointRouteBuilder endpoints)
13+
{
14+
var documentsApiGroup = endpoints.MapGroup("/api/documents").WithTags("Documents");
15+
16+
documentsApiGroup.MapGet(string.Empty, async (DocumentService documentService, CancellationToken cancellationToken) =>
17+
{
18+
var documents = await documentService.GetAsync(cancellationToken);
19+
return TypedResults.Ok(documents);
20+
})
21+
.WithSummary("Gets the list of documents");
22+
23+
documentsApiGroup.MapPost(string.Empty, async (IFormFile file, VectorSearchService vectorSearchService, CancellationToken cancellationToken,
24+
[Description("The unique identifier of the document. If not provided, a new one will be generated. If you specify an existing documentId, the corresponding document will be overwritten.")] Guid? documentId = null) =>
25+
{
26+
using var stream = file.OpenReadStream();
27+
28+
// Note: file.ContentType is not 100% reliable (for example, for markdown file).
29+
var response = await vectorSearchService.ImportAsync(stream, file.FileName, MimeUtility.GetMimeMapping(file.FileName), documentId, cancellationToken);
30+
31+
return TypedResults.Ok(response);
32+
})
33+
.DisableAntiforgery()
34+
.ProducesProblem(StatusCodes.Status400BadRequest)
35+
.WithSummary("Uploads a document")
36+
.WithDescription("Uploads a document to SQL Database and saves its embedding using the native VECTOR type. The document will be indexed and used to answer questions. Currently, PDF, DOCX, TXT and MD files are supported.");
37+
38+
documentsApiGroup.MapGet("{documentId:guid}/chunks", async (Guid documentId, DocumentService documentService, CancellationToken cancellationToken) =>
39+
{
40+
var documents = await documentService.GetChunksAsync(documentId, cancellationToken);
41+
return TypedResults.Ok(documents);
42+
})
43+
.WithSummary("Gets the list of chunks of a given document")
44+
.WithDescription("The list does not contain embedding. Use '/api/documents/{documentId}/chunks/{documentChunkId}' to get the embedding for a given chunk.");
45+
46+
documentsApiGroup.MapGet("{documentId:guid}/chunks/{documentChunkId:guid}", async Task<Results<Ok<DocumentChunk>, NotFound>> (Guid documentId, Guid documentChunkId, DocumentService documentService, CancellationToken cancellationToken) =>
47+
{
48+
var chunk = await documentService.GetChunkEmbeddingAsync(documentId, documentChunkId, cancellationToken);
49+
if (chunk is null)
50+
{
51+
return TypedResults.NotFound();
52+
}
53+
54+
return TypedResults.Ok(chunk);
55+
})
56+
.ProducesProblem(StatusCodes.Status404NotFound)
57+
.WithSummary("Gets the details of a given chunk, includings its embedding");
58+
59+
documentsApiGroup.MapDelete("{documentId:guid}", async (Guid documentId, DocumentService documentService, CancellationToken cancellationToken) =>
60+
{
61+
await documentService.DeleteAsync(documentId, cancellationToken);
62+
return TypedResults.NoContent();
63+
})
64+
.WithSummary("Deletes a document")
65+
.WithDescription("This endpoint deletes the document and all its chunks.");
66+
}
67+
}

SqlDatabaseVectorSearch/Program.cs

Lines changed: 1 addition & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,11 @@
1-
using System.ComponentModel;
21
using System.Net.Mime;
32
using System.Text.Json.Serialization;
4-
using Microsoft.AspNetCore.Http.HttpResults;
53
using Microsoft.EntityFrameworkCore;
64
using Microsoft.SemanticKernel;
7-
using MimeMapping;
85
using SqlDatabaseVectorSearch.Components;
96
using SqlDatabaseVectorSearch.ContentDecoders;
107
using SqlDatabaseVectorSearch.DataAccessLayer;
118
using SqlDatabaseVectorSearch.Extensions;
12-
using SqlDatabaseVectorSearch.Models;
139
using SqlDatabaseVectorSearch.Services;
1410
using SqlDatabaseVectorSearch.Settings;
1511
using SqlDatabaseVectorSearch.TextChunkers;
@@ -135,88 +131,7 @@
135131
app.MapRazorComponents<App>()
136132
.AddInteractiveServerRenderMode();
137133

138-
app.MapPost("/api/ask", async (Question question, VectorSearchService vectorSearchService, CancellationToken cancellationToken,
139-
[Description("If true, the question will be reformulated taking into account the context of the chat identified by the given ConversationId.")] bool reformulate = true) =>
140-
{
141-
var response = await vectorSearchService.AskQuestionAsync(question, reformulate, cancellationToken);
142-
return TypedResults.Ok(response);
143-
})
144-
.WithSummary("Asks a question")
145-
.WithDescription("The question will be reformulated taking into account the context of the chat identified by the given ConversationId.")
146-
.WithTags("Ask");
147-
148-
app.MapPost("/api/ask-streaming", (Question question, VectorSearchService vectorSearchService, CancellationToken cancellationToken,
149-
[Description("If true, the question will be reformulated taking into account the context of the chat identified by the given ConversationId.")] bool reformulate = true) =>
150-
{
151-
async IAsyncEnumerable<QuestionResponse> Stream()
152-
{
153-
// Requests a streaming response.
154-
var responseStream = vectorSearchService.AskStreamingAsync(question, reformulate, cancellationToken);
155-
156-
await foreach (var delta in responseStream)
157-
{
158-
yield return delta;
159-
}
160-
}
161-
162-
return Stream();
163-
})
164-
.WithSummary("Asks a question and gets the response as streaming")
165-
.WithDescription("The question will be reformulated taking into account the context of the chat identified by the given ConversationId.")
166-
.WithTags("Ask");
167-
168-
var documentsApiGroup = app.MapGroup("/api/documents").WithTags("Documents");
169-
170-
documentsApiGroup.MapGet(string.Empty, async (DocumentService documentService, CancellationToken cancellationToken) =>
171-
{
172-
var documents = await documentService.GetAsync(cancellationToken);
173-
return TypedResults.Ok(documents);
174-
})
175-
.WithSummary("Gets the list of documents");
176-
177-
documentsApiGroup.MapPost(string.Empty, async (IFormFile file, VectorSearchService vectorSearchService, CancellationToken cancellationToken,
178-
[Description("The unique identifier of the document. If not provided, a new one will be generated. If you specify an existing documentId, the corresponding document will be overwritten.")] Guid? documentId = null) =>
179-
{
180-
using var stream = file.OpenReadStream();
181-
182-
// Note: file.ContentType is not 100% reliable (for example, for markdown file).
183-
var response = await vectorSearchService.ImportAsync(stream, file.FileName, MimeUtility.GetMimeMapping(file.FileName), documentId, cancellationToken);
184-
185-
return TypedResults.Ok(response);
186-
})
187-
.DisableAntiforgery()
188-
.ProducesProblem(StatusCodes.Status400BadRequest)
189-
.WithSummary("Uploads a document")
190-
.WithDescription("Uploads a document to SQL Database and saves its embedding using the native VECTOR type. The document will be indexed and used to answer questions. Currently, PDF, DOCX, TXT and MD files are supported.");
191-
192-
documentsApiGroup.MapGet("{documentId:guid}/chunks", async (Guid documentId, DocumentService documentService, CancellationToken cancellationToken) =>
193-
{
194-
var documents = await documentService.GetChunksAsync(documentId, cancellationToken);
195-
return TypedResults.Ok(documents);
196-
})
197-
.WithSummary("Gets the list of chunks of a given document")
198-
.WithDescription("The list does not contain embedding. Use '/api/documents/{documentId}/chunks/{documentChunkId}' to get the embedding for a given chunk.");
199-
200-
documentsApiGroup.MapGet("{documentId:guid}/chunks/{documentChunkId:guid}", async Task<Results<Ok<DocumentChunk>, NotFound>> (Guid documentId, Guid documentChunkId, DocumentService documentService, CancellationToken cancellationToken) =>
201-
{
202-
var chunk = await documentService.GetChunkEmbeddingAsync(documentId, documentChunkId, cancellationToken);
203-
if (chunk is null)
204-
{
205-
return TypedResults.NotFound();
206-
}
207-
208-
return TypedResults.Ok(chunk);
209-
})
210-
.ProducesProblem(StatusCodes.Status404NotFound)
211-
.WithSummary("Gets the details of a given chunk, includings its embedding");
212-
213-
documentsApiGroup.MapDelete("{documentId:guid}", async (Guid documentId, DocumentService documentService, CancellationToken cancellationToken) =>
214-
{
215-
await documentService.DeleteAsync(documentId, cancellationToken);
216-
return TypedResults.NoContent();
217-
})
218-
.WithSummary("Deletes a document")
219-
.WithDescription("This endpoint deletes the document and all its chunks.");
134+
app.MapEndpoints();
220135

221136
app.Run();
222137

SqlDatabaseVectorSearch/SqlDatabaseVectorSearch.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@
2323
<PackageReference Include="Microsoft.ML.Tokenizers" Version="1.0.2" />
2424
<PackageReference Include="Microsoft.ML.Tokenizers.Data.Cl100kBase" Version="1.0.2" />
2525
<PackageReference Include="Microsoft.ML.Tokenizers.Data.O200kBase" Version="1.0.2" />
26-
<PackageReference Include="Microsoft.SemanticKernel" Version="1.40.0" />
26+
<PackageReference Include="Microsoft.SemanticKernel" Version="1.40.1" />
2727
<PackageReference Include="MimeMapping" Version="3.1.0" />
28-
<PackageReference Include="MinimalHelpers.OpenApi" Version="2.1.4" />
28+
<PackageReference Include="MinimalHelpers.Routing.Analyzers" Version="1.1.3" />
2929
<PackageReference Include="PdfPig" Version="0.1.9" />
3030
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="7.3.1" />
3131
<PackageReference Include="TinyHelpers.AspNetCore" Version="4.0.20" />

0 commit comments

Comments
 (0)