-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathChatController.cs
More file actions
71 lines (57 loc) · 2.77 KB
/
ChatController.cs
File metadata and controls
71 lines (57 loc) · 2.77 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
using Backend.Data;
using Backend.Models;
using Backend.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Backend.Controllers;
[ApiController]
[Route("api/chat")]
[Authorize(Policy = AuthPolicies.AdminOnly)]
public class ChatController(
GeminiChatService geminiChat,
ChatQueryService queryService,
ChatValidationService validation) : ControllerBase
{
private const string UnknownResponse =
"I can help with resident risk levels, donor retention, caseload summaries, " +
"incidents, and safehouse capacity. Try asking one of those.";
// Resident-context endpoint: skips intent classification, fetches the full case
// bundle for the given resident, and returns a concise summary + suggested actions.
[HttpPost("resident/{residentId:int}")]
public async Task<IActionResult> ChatResident(int residentId, CancellationToken ct)
{
var (summary, refs) = await queryService.QueryResidentDetailAsync(residentId, ct);
if (refs.Count == 0)
return NotFound("Resident not found.");
var rawAnswer = await geminiChat.GenerateResidentAdviceAsync(summary, ct);
var validatedAnswer = validation.ValidateAndStrip(rawAnswer, refs);
return Ok(new ChatResponse(validatedAnswer, refs));
}
[HttpPost]
public async Task<IActionResult> Chat([FromBody] ChatRequest request, CancellationToken ct)
{
if (string.IsNullOrWhiteSpace(request.Question))
return BadRequest("Question is required.");
// Short-circuit for unknown intent before any data query
var intent = await geminiChat.ClassifyIntentAsync(request.Question, ct);
if (intent.Category == "unknown")
return Ok(new ChatResponse(UnknownResponse, []));
// Run the targeted EF Core query
var (summary, refs) = await queryService.RunQueryAsync(intent, ct);
// If the query came back empty, tell the user rather than hallucinating
if (refs.Count == 0)
return Ok(new ChatResponse(
"I found no records matching that query. The data may not be available yet.",
[]));
// Generate natural-language answer from Gemini
var rawAnswer = intent.Category switch
{
"resident_detail" => await geminiChat.GenerateResidentAdviceAsync(summary, ct),
"supporter_detail" => await geminiChat.GenerateDonorAdviceAsync(summary, ct),
_ => await geminiChat.GenerateAnswerAsync(request.Question, summary, intent, ct)
};
// Strip any IDs Gemini hallucinated that weren't in our result set
var validatedAnswer = validation.ValidateAndStrip(rawAnswer, refs);
return Ok(new ChatResponse(validatedAnswer, refs));
}
}