Skip to content

Instruct & Stateless web example implemented #59

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jul 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions LLama.Web/Common/LLamaExecutorType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace LLama.Web.Common
{
public enum LLamaExecutorType
{
Interactive = 0,
Instruct = 1,
Stateless = 2
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace LLama.Web.Models
namespace LLama.Web.Common
{
public class LLamaOptions
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using LLama.Common;

namespace LLama.Web.Models
namespace LLama.Web.Common
{
public class ModelOptions : ModelParams
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using LLama.Common;

namespace LLama.Web.Models
namespace LLama.Web.Common
{
public class ParameterOptions : InferenceParams
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace LLama.Web.Models
namespace LLama.Web.Common
{
public class PromptOptions
{
Expand Down
41 changes: 41 additions & 0 deletions LLama.Web/Common/ServiceResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
namespace LLama.Web.Common
{
public class ServiceResult<T> : ServiceResult, IServiceResult<T>
{
public T Value { get; set; }
}


public class ServiceResult
{
public string Error { get; set; }

public bool HasError
{
get { return !string.IsNullOrEmpty(Error); }
}

public static IServiceResult<T> FromValue<T>(T value)
{
return new ServiceResult<T>
{
Value = value,
};
}

public static IServiceResult<T> FromError<T>(string error)
{
return new ServiceResult<T>
{
Error = error,
};
}
}

public interface IServiceResult<T>
{
T Value { get; set; }
string Error { get; set; }
bool HasError { get; }
}
}
9 changes: 9 additions & 0 deletions LLama.Web/Common/SessionConnectionStatus.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace LLama.Web.Common
{
public enum SessionConnectionStatus
{
Disconnected = 0,
Loaded = 4,
Connected = 10
}
}
5 changes: 3 additions & 2 deletions LLama.Web/Hubs/ISessionClient.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
using LLama.Web.Models;
using LLama.Web.Common;
using LLama.Web.Models;

namespace LLama.Web.Hubs
{
public interface ISessionClient
{
Task OnStatus(string status, string data = null);
Task OnStatus(string connectionId, SessionConnectionStatus status);
Task OnResponse(ResponseFragment fragment);
Task OnError(string error);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,84 +1,85 @@
using LLama.Web.Models;
using LLama.Web.Common;
using LLama.Web.Models;
using LLama.Web.Services;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Options;
using System.Diagnostics;

namespace LLama.Web.Hubs
{
public class InteractiveHub : Hub<ISessionClient>
public class SessionConnectionHub : Hub<ISessionClient>
{
private readonly LLamaOptions _options;
private readonly ILogger<InteractiveHub> _logger;
private readonly IModelSessionService _modelSessionService;
private readonly ILogger<SessionConnectionHub> _logger;
private readonly ConnectionSessionService _modelSessionService;

public InteractiveHub(ILogger<InteractiveHub> logger, IOptions<LLamaOptions> options, IModelSessionService modelSessionService)
public SessionConnectionHub(ILogger<SessionConnectionHub> logger, ConnectionSessionService modelSessionService)
{
_logger = logger;
_options = options.Value;
_modelSessionService = modelSessionService;
}


public override async Task OnConnectedAsync()
{
_logger.Log(LogLevel.Information, "OnConnectedAsync, Id: {0}", Context.ConnectionId);
_logger.Log(LogLevel.Information, "[OnConnectedAsync], Id: {0}", Context.ConnectionId);

// Notify client of successful connection
await Clients.Caller.OnStatus(Context.ConnectionId, SessionConnectionStatus.Connected);
await base.OnConnectedAsync();
await Clients.Caller.OnStatus("Connected", Context.ConnectionId);
}


public override async Task OnDisconnectedAsync(Exception? exception)
{
_logger.Log(LogLevel.Information, "[OnDisconnectedAsync], Id: {0}", Context.ConnectionId);
await _modelSessionService.RemoveAsync(Context.ConnectionId);

// Remove connections session on dissconnect
await _modelSessionService.RemoveAsync(Context.ConnectionId);
await base.OnDisconnectedAsync(exception);
}


[HubMethodName("LoadModel")]
public async Task OnLoadModel(string modelName, string promptName, string parameterName)
public async Task OnLoadModel(LLamaExecutorType executorType, string modelName, string promptName, string parameterName)
{
_logger.Log(LogLevel.Information, "[OnLoadModel] - Load new model, Connection: {0}, Model: {1}, Prompt: {2}, Parameter: {3}", Context.ConnectionId, modelName, promptName, parameterName);

// Remove existing connections session
await _modelSessionService.RemoveAsync(Context.ConnectionId);

var modelOption = _options.Models.First(x => x.Name == modelName);
var promptOption = _options.Prompts.First(x => x.Name == promptName);
var parameterOption = _options.Parameters.First(x => x.Name == parameterName);
var interactiveExecutor = new InteractiveExecutor(new LLamaModel(modelOption));
var modelSession = await _modelSessionService.CreateAsync(Context.ConnectionId, interactiveExecutor, modelOption, promptOption, parameterOption);
if (modelSession is null)
// Create model session
var modelSessionResult = await _modelSessionService.CreateAsync(executorType, Context.ConnectionId, modelName, promptName, parameterName);
if (modelSessionResult.HasError)
{
_logger.Log(LogLevel.Error, "[OnLoadModel] - Failed to add new model session, Connection: {0}", Context.ConnectionId);
await Clients.Caller.OnError("No model has been loaded");
await Clients.Caller.OnError(modelSessionResult.Error);
return;

}
_logger.Log(LogLevel.Information, "[OnLoadModel] - New model session added, Connection: {0}", Context.ConnectionId);
await Clients.Caller.OnStatus("Loaded", Context.ConnectionId);

// Notify client
await Clients.Caller.OnStatus(Context.ConnectionId, SessionConnectionStatus.Loaded);
}


[HubMethodName("SendPrompt")]
public async Task OnSendPrompt(string prompt)
{
var stopwatch = Stopwatch.GetTimestamp();
_logger.Log(LogLevel.Information, "[OnSendPrompt] - New prompt received, Connection: {0}", Context.ConnectionId);

// Get connections session
var modelSession = await _modelSessionService.GetAsync(Context.ConnectionId);
if (modelSession is null)
{
_logger.Log(LogLevel.Warning, "[OnSendPrompt] - No model has been loaded for this connection, Connection: {0}", Context.ConnectionId);
await Clients.Caller.OnError("No model has been loaded");
return;
}


// Create unique response id
var responseId = Guid.NewGuid().ToString();

// Send begin of response
await Clients.Caller.OnResponse(new ResponseFragment(responseId, isFirst: true));

// Send content of response
var stopwatch = Stopwatch.GetTimestamp();
await foreach (var fragment in modelSession.InferAsync(prompt, CancellationTokenSource.CreateLinkedTokenSource(Context.ConnectionAborted)))
{
await Clients.Caller.OnResponse(new ResponseFragment(responseId, fragment));
Expand All @@ -92,6 +93,6 @@ public async Task OnSendPrompt(string prompt)
await Clients.Caller.OnResponse(new ResponseFragment(responseId, signature, isLast: true));
_logger.Log(LogLevel.Information, "[OnSendPrompt] - Inference complete, Connection: {0}, Elapsed: {1}, Canceled: {2}", Context.ConnectionId, elapsedTime, modelSession.IsInferCanceled());
}

}
}
9 changes: 6 additions & 3 deletions LLama.Web/Models/ModelSession.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using LLama.Abstractions;
using LLama.Web.Common;

namespace LLama.Web.Models
{
Expand All @@ -12,9 +13,8 @@ public class ModelSession : IDisposable
private ILLamaExecutor _executor;
private CancellationTokenSource _cancellationTokenSource;

public ModelSession(string connectionId, ILLamaExecutor executor, ModelOptions modelOptions, PromptOptions promptOptions, ParameterOptions parameterOptions)
public ModelSession(ILLamaExecutor executor, ModelOptions modelOptions, PromptOptions promptOptions, ParameterOptions parameterOptions)
{
ConnectionId = connectionId;
_executor = executor;
_modelOptions = modelOptions;
_promptOptions = promptOptions;
Expand All @@ -25,7 +25,10 @@ public ModelSession(string connectionId, ILLamaExecutor executor, ModelOptions m
_outputTransform = new LLamaTransforms.KeywordTextOutputStreamTransform(_promptOptions.OutputFilter, redundancyLength: 5);
}

public string ConnectionId { get; }
public string ModelName
{
get { return _modelOptions.Name; }
}

public IAsyncEnumerable<string> InferAsync(string message, CancellationTokenSource cancellationTokenSource)
{
Expand Down
96 changes: 96 additions & 0 deletions LLama.Web/Pages/Executor/Instruct.cshtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
@page
@model InstructModel
@{

}
@Html.AntiForgeryToken()
<div class="d-flex flex-row h-100 pt-1 pb-1">

<div class="d-flex flex-column h-100 border me-1 w-25 overflow-auto">
<div class="d-flex flex-row justify-content-between border-bottom p-1 align-items-center">
<h4>Instruct</h4>
<div>
<span>Hub: <b id="socket">Disconnected</b></span>
</div>
</div>

<div class="m-1">
<small>Model</small>
<select id="Model" class="form-control form-select input-control" required="required" autocomplete="off">
<option value="" disabled selected hidden>Please Select</option>
@foreach (var modelOption in Model.Options.Models)
{
<option value="@modelOption.Name">@modelOption.Name</option>
}
</select>
</div>

<div class="m-1">
<small>Parameters</small>
<select id="Parameter" class="form-control form-select input-control" required="required" autocomplete="off">
<option value="" disabled selected hidden>Please Select</option>
@foreach (var parameterOption in Model.Options.Parameters)
{
<option value="@parameterOption.Name">@parameterOption.Name</option>
}
</select>
</div>

<div class="m-1">
<small>Prompt</small>
<select id="Prompt" class="form-control form-select input-control" required="required" autocomplete="off">
<option value="" disabled selected hidden>Please Select</option>
@foreach (var promptOption in Model.Options.Prompts)
{
<option value="@promptOption.Name" data-prompt="@promptOption.Prompt">@promptOption.Name</option>
}
</select>
<textarea id="PromptText" class="form-control mt-1" rows="12" disabled="disabled" style="font-size:13px;resize:none"></textarea>
</div>

<div class="d-flex flex-grow-1"></div>
<div id="session-details" class="m-1"></div>
<div class="m-1">
<button class="btn btn-outline-secondary input-control w-100" type="button" id="load">Create Session</button>
</div>
</div>

<div class="d-flex flex-column h-100 w-75">
<div class="section-head">
</div>

<div id="scroll-container" class="section-content border">
<div id="output-container" class="d-flex flex-column gap-1 p-1">
</div>
</div>

<div class="section-foot">
<div class="input-group mt-2">
<textarea id="input" type="text" class="form-control" value="what is a tree?" style="resize:none" rows="4">What is an apple?</textarea>
<div class="d-flex flex-column">
<div class="d-flex flex-fill">
<button class="btn btn-outline-secondary input-control w-100" type="button" id="send" disabled="disabled" autocomplete="off">Send Message</button>
</div>
<div class="d-flex">
<button class="btn btn-outline-secondary w-100" type="button" id="cancel" autocomplete="off">
<i class="bi-x-circle"></i>
</button>
<button class="btn btn-outline-secondary input-control w-100" type="button" id="clear" disabled="disabled" autocomplete="off">
<i class="bi-trash3"></i>
</button>
</div>
</div>
</div>
</div>

</div>
</div>

@{ await Html.RenderPartialAsync("_ChatTemplates"); }

@section Scripts {
<script src="~/js/sessionconnectionchat.js"></script>
<script>
createConnectionSessionChat(Enums.LLamaExecutorType.Instruct);
</script>
}
34 changes: 34 additions & 0 deletions LLama.Web/Pages/Executor/Instruct.cshtml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using LLama.Web.Common;
using LLama.Web.Models;
using LLama.Web.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Options;

namespace LLama.Web.Pages
{
public class InstructModel : PageModel
{
private readonly ILogger<InstructModel> _logger;
private readonly ConnectionSessionService _modelSessionService;

public InstructModel(ILogger<InstructModel> logger, IOptions<LLamaOptions> options, ConnectionSessionService modelSessionService)
{
_logger = logger;
Options = options.Value;
_modelSessionService = modelSessionService;
}

public LLamaOptions Options { get; set; }

public void OnGet()
{
}

public async Task<IActionResult> OnPostCancel(CancelModel model)
{
await _modelSessionService.CancelAsync(model.ConnectionId);
return new JsonResult(default);
}
}
}
Loading