Skip to content

Commit de24b8a

Browse files
committed
Refactor how DocumentationWebhost is reusing the proxy
1 parent 6a3727b commit de24b8a

File tree

6 files changed

+39
-68
lines changed

6 files changed

+39
-68
lines changed

src/Elastic.Documentation.Site/Assets/web-components/SearchOrAskAi/useLlmGateway.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ export const useLlmGateway = (props: Props): UseLlmGatewayResponse => {
142142
)
143143

144144
const { sendMessage, abort } = useFetchEventSource<AskAiRequest>({
145-
apiEndpoint: '/chat',
145+
apiEndpoint: '/_api/v1/ask-ai/stream',
146146
onMessage,
147147
onError: (error) => {
148148
setError(error)

src/api/Elastic.Documentation.Api.Core/SerializationContext.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,5 @@ namespace Elastic.Documentation.Api.Core;
99

1010

1111
[JsonSerializable(typeof(AskAiRequest))]
12-
[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.SnakeCaseLower)]
12+
[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)]
1313
public partial class ApiJsonContext : JsonSerializerContext;

src/api/Elastic.Documentation.Api.Infrastructure/ServicesExtension.cs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// See the LICENSE file in the project root for more information
44

55
using System.ComponentModel.DataAnnotations;
6+
using Elastic.Documentation.Api.Core;
67
using Elastic.Documentation.Api.Core.AskAi;
78
using Elastic.Documentation.Api.Infrastructure.Adapters.AskAi;
89
using Elastic.Documentation.Api.Infrastructure.Aws;
@@ -23,16 +24,21 @@ public enum AppEnvironment
2324

2425
public static class ServicesExtension
2526
{
26-
public static void AddUsecases(this IServiceCollection services, string? appEnvironment) =>
27-
AddUsecases(
27+
public static void AddApiUsecases(this IServiceCollection services, string? appEnvironment) =>
28+
AddApiUsecases(
2829
services,
2930
AppEnvironmentExtensions.TryParse(appEnvironment, out var parsedEnvironment, true)
3031
? parsedEnvironment
3132
: AppEnvironment.Dev
3233
);
3334

34-
private static void AddUsecases(this IServiceCollection services, AppEnvironment appEnvironment)
35+
private static void AddApiUsecases(this IServiceCollection services, AppEnvironment appEnvironment)
3536
{
37+
_ = services.ConfigureHttpJsonOptions(options =>
38+
{
39+
options.SerializerOptions.TypeInfoResolverChain.Insert(0, ApiJsonContext.Default);
40+
});
41+
_ = services.AddHttpClient();
3642
AddParameterProvider(services, appEnvironment);
3743
AddAskAiUsecases(services, appEnvironment);
3844
}

src/api/Elastic.Documentation.Api.Lambda/AskAiEndpoint.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ namespace Elastic.Documentation.Api.Lambda;
88

99
public static class AskAiEndpoint
1010
{
11-
public static void MapAskAiEndpoint(this IEndpointRouteBuilder app)
11+
public static void MapAskAiEndpoint(this RouteGroupBuilder parentGroup)
1212
{
13-
var askAiGroup = app.MapGroup("/ask-ai");
13+
var askAiGroup = parentGroup.MapGroup("/ask-ai");
1414
_ = askAiGroup.MapPost("/stream", async (AskAiRequest askAiRequest, AskAiUsecase askAiUsecase, Cancel ctx) =>
1515
{
1616
var stream = await askAiUsecase.AskAi(askAiRequest, ctx);

src/api/Elastic.Documentation.Api.Lambda/Program.cs

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,18 @@
55
using System.Text.Json.Serialization;
66
using Amazon.Lambda.APIGatewayEvents;
77
using Amazon.Lambda.Serialization.SystemTextJson;
8-
using Elastic.Documentation.Api.Core;
98
using Elastic.Documentation.Api.Infrastructure;
109
using Elastic.Documentation.Api.Lambda;
1110

1211
var builder = WebApplication.CreateSlimBuilder(args);
1312

14-
builder.Services.ConfigureHttpJsonOptions(options =>
15-
{
16-
options.SerializerOptions.TypeInfoResolverChain.Insert(0, ApiJsonContext.Default);
17-
});
18-
19-
builder.Services.AddHttpClient();
20-
builder.Services.AddUsecases(Environment.GetEnvironmentVariable("APP_ENVIRONMENT"));
21-
2213
builder.Services.AddAWSLambdaHosting(LambdaEventSource.RestApi, new SourceGeneratorLambdaJsonSerializer<LambdaJsonSerializerContext>());
14+
builder.Services.AddApiUsecases(Environment.GetEnvironmentVariable("APP_ENVIRONMENT"));
2315

2416
var app = builder.Build();
2517

26-
app.MapAskAiEndpoint();
18+
var v1 = app.MapGroup("/v1");
19+
v1.MapAskAiEndpoint();
2720

2821
app.Run();
2922

src/tooling/docs-builder/Http/DocumentationWebHost.cs

Lines changed: 23 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using Documentation.Builder.Diagnostics.LiveMode;
1111
using Elastic.Documentation.Api.Core;
1212
using Elastic.Documentation.Api.Core.AskAi;
13+
using Elastic.Documentation.Api.Infrastructure;
1314
using Elastic.Documentation.Api.Infrastructure.Adapters.AskAi;
1415
using Elastic.Documentation.Api.Infrastructure.Gcp;
1516
using Elastic.Documentation.Configuration;
@@ -33,16 +34,14 @@ public class DocumentationWebHost
3334

3435
private readonly IHostedService _hostedService;
3536
private readonly IFileSystem _writeFileSystem;
36-
private readonly ILoggerFactory _logFactory;
3737

3838
public DocumentationWebHost(ILoggerFactory logFactory, string? path, int port, IFileSystem readFs, IFileSystem writeFs,
3939
VersionsConfiguration versionsConfig)
4040
{
41-
_logFactory = logFactory;
4241
_writeFileSystem = writeFs;
4342
var builder = WebApplication.CreateSlimBuilder();
43+
builder.Services.AddApiUsecases("dev");
4444
DocumentationTooling.CreateServiceCollection(builder.Services, LogLevel.Information);
45-
4645
_ = builder.Logging
4746
.AddFilter("Microsoft.AspNetCore.Hosting.Diagnostics", LogLevel.Error)
4847
.AddFilter("Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware", LogLevel.Error)
@@ -101,6 +100,23 @@ private void SetUpRoutes()
101100
_ = _webApplication
102101
.UseLiveReloadWithManualScriptInjection(_webApplication.Lifetime)
103102
.UseDeveloperExceptionPage(new DeveloperExceptionPageOptions())
103+
.Use(async (context, next) =>
104+
{
105+
try
106+
{
107+
await next(context);
108+
}
109+
catch (Exception ex)
110+
{
111+
Console.WriteLine($"[UNHANDLED EXCEPTION] {ex.GetType().Name}: {ex.Message}");
112+
Console.WriteLine($"[STACK TRACE] {ex.StackTrace}");
113+
if (ex.InnerException != null)
114+
{
115+
Console.WriteLine($"[INNER EXCEPTION] {ex.InnerException.GetType().Name}: {ex.InnerException.Message}");
116+
}
117+
throw; // Re-throw to let ASP.NET Core handle it
118+
}
119+
})
104120
.UseStaticFiles(
105121
new StaticFileOptions
106122
{
@@ -118,8 +134,7 @@ private void SetUpRoutes()
118134
_ = _webApplication.MapGet("/api/{**slug}", (string slug, ReloadableGeneratorState holder, Cancel ctx) =>
119135
ServeApiFile(holder, slug, ctx));
120136

121-
_ = _webApplication.MapPost("/chat", async (HttpContext context, Cancel ctx) =>
122-
await ProxyChatRequest(context, ctx));
137+
_ = _webApplication.MapPost("/_api/v1/ask-ai/stream", ProxyChatRequest);
123138

124139
_ = _webApplication.MapGet("{**slug}", (string slug, ReloadableGeneratorState holder, Cancel ctx) =>
125140
ServeDocumentationFile(holder, slug, ctx));
@@ -227,52 +242,9 @@ private static IResult LiveReloadHtml(string content, Encoding? encoding = null,
227242
return Results.Content(content, "text/html", encoding, statusCode);
228243
}
229244

230-
private async Task<IResult> ProxyChatRequest(HttpContext context, CancellationToken ctx)
245+
private static async Task<IResult> ProxyChatRequest(AskAiRequest request, AskAiUsecase usecase, Cancel ctx)
231246
{
232-
// Read the frontend request body
233-
var requestBody = await new StreamReader(context.Request.Body).ReadToEndAsync(ctx);
234-
var askAiRequest = JsonSerializer.Deserialize<AskAiRequest>(requestBody, ApiJsonContext.Default.AskAiRequest);
235-
236-
// Load GCP service account credentials
237-
var serviceAccountKeyPath = Environment.GetEnvironmentVariable("LLM_GATEWAY_SERVICE_ACCOUNT_KEY_PATH")
238-
?? "service-account-key.json";
239-
240-
if (!File.Exists(serviceAccountKeyPath))
241-
{
242-
context.Response.StatusCode = 500;
243-
await context.Response.WriteAsync("GCP credentials not configured", cancellationToken: ctx);
244-
return Results.Empty;
245-
}
246-
247-
// Get GCP function URL
248-
var gcpFunctionUrl = Environment.GetEnvironmentVariable("LLM_GATEWAY_FUNCTION_URL");
249-
if (string.IsNullOrEmpty(gcpFunctionUrl))
250-
{
251-
context.Response.StatusCode = 500;
252-
await context.Response.WriteAsync("GCP function URL not configured", cancellationToken: ctx);
253-
return Results.Empty;
254-
}
255-
256-
var functionUri = new Uri(gcpFunctionUrl);
257-
var audienceUrl = $"{functionUri.Scheme}://{functionUri.Host}";
258-
var httpClient = new HttpClient();
259-
var gcpIdTokenProvider = new GcpIdTokenProvider(
260-
httpClient,
261-
await File.ReadAllTextAsync(serviceAccountKeyPath, ctx),
262-
audienceUrl
263-
);
264-
var llmGatewayAskAiGateway = new LlmGatewayAskAiGateway(
265-
httpClient,
266-
gcpIdTokenProvider,
267-
gcpFunctionUrl
268-
);
269-
270-
var askAiUsecase = new AskAiUsecase(llmGatewayAskAiGateway, _logFactory.CreateLogger<AskAiUsecase>());
271-
272-
if (askAiRequest == null)
273-
return Results.BadRequest("Invalid chat request.");
274-
275-
var responseStream = await askAiUsecase.AskAi(askAiRequest, ctx);
276-
return Results.Stream(responseStream, "text/event-stream");
247+
var stream = await usecase.AskAi(request, ctx);
248+
return Results.Stream(stream, "text/event-stream");
277249
}
278250
}

0 commit comments

Comments
 (0)