add preliminary llm controller
All checks were successful
Build and Deploy API / build-and-deploy (push) Successful in 8s
All checks were successful
Build and Deploy API / build-and-deploy (push) Successful in 8s
This commit is contained in:
@@ -29,6 +29,8 @@ builder.Services.AddScoped<UserService>();
|
|||||||
builder.Services.AddScoped<ItemService>();
|
builder.Services.AddScoped<ItemService>();
|
||||||
builder.Services.AddScoped<JwtService>();
|
builder.Services.AddScoped<JwtService>();
|
||||||
// if this grows sufficiently large we can put elsewhere
|
// if this grows sufficiently large we can put elsewhere
|
||||||
|
// misc services that I didnt make
|
||||||
|
builder.Services.AddHttpClient();
|
||||||
|
|
||||||
// configuration for jwt authentication
|
// configuration for jwt authentication
|
||||||
builder.Services.AddIdentity<User, IdentityRole>()
|
builder.Services.AddIdentity<User, IdentityRole>()
|
||||||
@@ -110,7 +112,7 @@ if (app.Environment.IsEnvironment("Development")) {
|
|||||||
app.UseHttpsRedirection();
|
app.UseHttpsRedirection();
|
||||||
}
|
}
|
||||||
|
|
||||||
app.MapControllers();
|
app.MapControllers(); // automatically scours src/Controllers for controller classes
|
||||||
|
|
||||||
// attempt enitity-framework migrations at startup. love you stack overflow
|
// attempt enitity-framework migrations at startup. love you stack overflow
|
||||||
using (var scope = app.Services.CreateScope()) {
|
using (var scope = app.Services.CreateScope()) {
|
||||||
@@ -125,7 +127,6 @@ using (var scope = app.Services.CreateScope()) {
|
|||||||
Thread.Sleep(5000);
|
Thread.Sleep(5000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
app.Run();
|
app.Run();
|
||||||
|
|||||||
@@ -6,9 +6,11 @@ using agologumApi.Models;
|
|||||||
using agologumApi.Services;
|
using agologumApi.Services;
|
||||||
|
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("api/[controller]")]
|
[Route("api/[controller]")] // generally good practice to make this explicit
|
||||||
public class ItemsController : ControllerBase {
|
public class ItemsController : ControllerBase {
|
||||||
|
|
||||||
|
// TODO: (global) controller's might eventually need more services so its gonna be a good idea to give them more meaningful names
|
||||||
|
// i.e. just name it itemService_
|
||||||
private readonly ItemService service_;
|
private readonly ItemService service_;
|
||||||
|
|
||||||
public ItemsController(ItemService service) {
|
public ItemsController(ItemService service) {
|
||||||
|
|||||||
89
api/src/Controllers/LlmController.cs
Normal file
89
api/src/Controllers/LlmController.cs
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
|
||||||
|
// system usings
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
// agologum usings
|
||||||
|
using agologumApi.Models;
|
||||||
|
//using agologumApi.Services;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/chat")]
|
||||||
|
public class LlmController : ControllerBase {
|
||||||
|
|
||||||
|
// eventually this will be where most of the app sits
|
||||||
|
// might create src/Services/Llm/... to better organize
|
||||||
|
// private readonly LlmService llmService_;
|
||||||
|
private readonly IHttpClientFactory httpClientFactory_;
|
||||||
|
private readonly string LLAMA_URL = "http://localhost:8010/completion"; // TODO: can just put this in appsettings
|
||||||
|
|
||||||
|
public LlmController(/* LlmService llmService, */ IHttpClientFactory httpClientFactory) {
|
||||||
|
// llmService_ = llmService;
|
||||||
|
httpClientFactory_ = httpClientFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
// posting to api/chat/stream with a prompt in the body will return an sse (server sent events) stream of the llm text
|
||||||
|
// [Authorize(Policy = Permission.SensitiveData_Read)] // will make secret after testing
|
||||||
|
[HttpPost("stream")]
|
||||||
|
public async Task streamPrompt([FromBody] PromptRequest prompt) {
|
||||||
|
|
||||||
|
var httpClient = httpClientFactory_.CreateClient(); // recreating this on every prompt request? idk seems 🐟y
|
||||||
|
|
||||||
|
// json object for llama to parse
|
||||||
|
var llamaRequest = new {
|
||||||
|
prompt = prompt.Prompt, // alternative would be a messages array
|
||||||
|
stream = true, // real time token streaming
|
||||||
|
n_predict = 256 // max amount of tokens
|
||||||
|
// other options are temperature, top_p and top_k for vocabulary diversity, stop for stop tokens
|
||||||
|
// https://github.com/ggml-org/llama.cpp/blob/master/tools/server/README.md
|
||||||
|
// llm loras lol
|
||||||
|
};
|
||||||
|
|
||||||
|
// http request to send to the llama server
|
||||||
|
var httpRequest = new HttpRequestMessage(HttpMethod.Post, LLAMA_URL) {
|
||||||
|
Content = new StringContent(JsonSerializer.Serialize(llamaRequest), Encoding.UTF8, "application/json")
|
||||||
|
};
|
||||||
|
|
||||||
|
// send request
|
||||||
|
var response = await httpClient.SendAsync(httpRequest, HttpCompletionOption.ResponseHeadersRead, HttpContext.RequestAborted);
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
Response.StatusCode = 200;
|
||||||
|
Response.ContentType = "text/plain"; // because its async dotnet lets us do this monstrosity
|
||||||
|
|
||||||
|
// now to handle the response stream
|
||||||
|
await using var responseStream = await response.Content.ReadAsStreamAsync();
|
||||||
|
using var streamReader = new StreamReader(responseStream);
|
||||||
|
string? line;
|
||||||
|
// until the stream ends ...
|
||||||
|
while((line = await streamReader.ReadLineAsync()) != null) {
|
||||||
|
if(string.IsNullOrWhiteSpace(line)) continue; // skip if line is empty
|
||||||
|
|
||||||
|
// llama returns streams in the format data: { "response" }, so ignore if otherwise
|
||||||
|
if(line.StartsWith("data: ")) {
|
||||||
|
var json = line.Substring(6); // clip the "data: " portion
|
||||||
|
try {
|
||||||
|
// parse the json sent back
|
||||||
|
using var doc = JsonDocument.Parse(json);
|
||||||
|
var root = doc.RootElement;
|
||||||
|
if(root.TryGetProperty("content", out var content)) {
|
||||||
|
var text = content.GetString();
|
||||||
|
if(!string.IsNullOrEmpty(text)) {
|
||||||
|
// theres actually something here, so respond
|
||||||
|
await Response.WriteAsync(text);
|
||||||
|
await Response.Body.FlushAsync(); // rely outwards as quickly as it flows in
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// malformed response data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PromptRequest {
|
||||||
|
public string Prompt { get; set; } = "";
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user