Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
johnml1135 committed Jan 22, 2024
1 parent d17b3e6 commit 631372e
Show file tree
Hide file tree
Showing 6 changed files with 236 additions and 26 deletions.
150 changes: 150 additions & 0 deletions src/Serval.Client/Client.g.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1562,6 +1562,22 @@ public partial interface ITranslationEnginesClient
/// <exception cref="ServalApiException">A server side error occurred.</exception>
System.Threading.Tasks.Task CancelBuildAsync(string id, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));

/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
/// <summary>
/// Let a link to download the NMT translation model of the last build that was sucessfully saved.
/// </summary>
/// <remarks>
/// If a Nmt build was successful and included the build param `train_params: { save_strategy: yes} }`,
/// <br/>then the model will be available to download within 30 days of being created. After that, the model
/// <br/>will be deleted to not clutter the system.
/// <br/>The endpoint will return a presigned URL that can be used to download the model for up to 1 hour
/// <br/>after the request is made. If the URL is not used within that time, a new request will need to be made.
/// </remarks>
/// <param name="id">The translation engine id</param>
/// <returns>The build job was cancelled successfully</returns>
/// <exception cref="ServalApiException">A server side error occurred.</exception>
System.Threading.Tasks.Task<ModelInfo> DownloadModelAsync(string id, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));

}

[System.CodeDom.Compiler.GeneratedCode("NSwag", "13.20.0.0 (NJsonSchema v10.9.0.0 (Newtonsoft.Json v13.0.0.0))")]
Expand Down Expand Up @@ -3893,6 +3909,119 @@ public string BaseUrl
}
}

/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
/// <summary>
/// Let a link to download the NMT translation model of the last build that was sucessfully saved.
/// </summary>
/// <remarks>
/// If a Nmt build was successful and included the build param `train_params: { save_strategy: yes} }`,
/// <br/>then the model will be available to download within 30 days of being created. After that, the model
/// <br/>will be deleted to not clutter the system.
/// <br/>The endpoint will return a presigned URL that can be used to download the model for up to 1 hour
/// <br/>after the request is made. If the URL is not used within that time, a new request will need to be made.
/// </remarks>
/// <param name="id">The translation engine id</param>
/// <returns>The build job was cancelled successfully</returns>
/// <exception cref="ServalApiException">A server side error occurred.</exception>
public virtual async System.Threading.Tasks.Task<ModelInfo> DownloadModelAsync(string id, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))
{
if (id == null)
throw new System.ArgumentNullException("id");

var urlBuilder_ = new System.Text.StringBuilder();
urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/translation/engines/{id}/download-model");
urlBuilder_.Replace("{id}", System.Uri.EscapeDataString(ConvertToString(id, System.Globalization.CultureInfo.InvariantCulture)));

var client_ = _httpClient;
var disposeClient_ = false;
try
{
using (var request_ = new System.Net.Http.HttpRequestMessage())
{
request_.Content = new System.Net.Http.StringContent(string.Empty, System.Text.Encoding.UTF8, "application/json");
request_.Method = new System.Net.Http.HttpMethod("POST");
request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json"));

PrepareRequest(client_, request_, urlBuilder_);

var url_ = urlBuilder_.ToString();
request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute);

PrepareRequest(client_, request_, url_);

var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
var disposeResponse_ = true;
try
{
var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value);
if (response_.Content != null && response_.Content.Headers != null)
{
foreach (var item_ in response_.Content.Headers)
headers_[item_.Key] = item_.Value;
}

ProcessResponse(client_, response_);

var status_ = (int)response_.StatusCode;
if (status_ == 200)
{
var objectResponse_ = await ReadObjectResponseAsync<ModelInfo>(response_, headers_, cancellationToken).ConfigureAwait(false);
if (objectResponse_.Object == null)
{
throw new ServalApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
}
return objectResponse_.Object;
}
else
if (status_ == 401)
{
string responseText_ = ( response_.Content == null ) ? string.Empty : await response_.Content.ReadAsStringAsync().ConfigureAwait(false);
throw new ServalApiException("The client is not authenticated", status_, responseText_, headers_, null);
}
else
if (status_ == 403)
{
string responseText_ = ( response_.Content == null ) ? string.Empty : await response_.Content.ReadAsStringAsync().ConfigureAwait(false);
throw new ServalApiException("The authenticated client does not own the translation engine", status_, responseText_, headers_, null);
}
else
if (status_ == 404)
{
string responseText_ = ( response_.Content == null ) ? string.Empty : await response_.Content.ReadAsStringAsync().ConfigureAwait(false);
throw new ServalApiException("The engine does not exist or there is no active build ", status_, responseText_, headers_, null);
}
else
if (status_ == 405)
{
string responseText_ = ( response_.Content == null ) ? string.Empty : await response_.Content.ReadAsStringAsync().ConfigureAwait(false);
throw new ServalApiException("The translation engine does not support cancelling builds", status_, responseText_, headers_, null);
}
else
if (status_ == 503)
{
string responseText_ = ( response_.Content == null ) ? string.Empty : await response_.Content.ReadAsStringAsync().ConfigureAwait(false);
throw new ServalApiException("A necessary service is currently unavailable. Check `/health` for more details. ", status_, responseText_, headers_, null);
}
else
{
var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false);
throw new ServalApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null);
}
}
finally
{
if (disposeResponse_)
response_.Dispose();
}
}
}
finally
{
if (disposeClient_)
client_.Dispose();
}
}

protected struct ObjectResponseResult<T>
{
public ObjectResponseResult(T responseObject, string responseText)
Expand Down Expand Up @@ -5100,6 +5229,27 @@ public partial class PretranslateCorpusConfig

}

[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "13.20.0.0 (NJsonSchema v10.9.0.0 (Newtonsoft.Json v13.0.0.0))")]
public partial class ModelInfo
{
[Newtonsoft.Json.JsonProperty("presignedUrl", Required = Newtonsoft.Json.Required.Always)]
[System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)]
public string PresignedUrl { get; set; } = default!;

[Newtonsoft.Json.JsonProperty("buildId", Required = Newtonsoft.Json.Required.Always)]
[System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)]
public string BuildId { get; set; } = default!;

[Newtonsoft.Json.JsonProperty("createdOn", Required = Newtonsoft.Json.Required.Always)]
[System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)]
public string CreatedOn { get; set; } = default!;

[Newtonsoft.Json.JsonProperty("toBeDeletedOn", Required = Newtonsoft.Json.Required.Always)]
[System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)]
public string ToBeDeletedOn { get; set; } = default!;

}

[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "13.20.0.0 (NJsonSchema v10.9.0.0 (Newtonsoft.Json v13.0.0.0))")]
public partial class Webhook
{
Expand Down
14 changes: 14 additions & 0 deletions src/Serval.Grpc/Protos/serval/translation/v1/engine.proto
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ service TranslationEngineApi {
rpc TrainSegmentPair(TrainSegmentPairRequest) returns (google.protobuf.Empty);
rpc StartBuild(StartBuildRequest) returns (google.protobuf.Empty);
rpc CancelBuild(CancelBuildRequest) returns (google.protobuf.Empty);
rpc GetModelInfo(GetModelInfoRequest) returns (GetModelInfoResponse);
rpc GetQueueSize(GetQueueSizeRequest) returns (GetQueueSizeResponse);
rpc HealthCheck(google.protobuf.Empty) returns (HealthCheckResponse);
}
Expand Down Expand Up @@ -71,6 +72,19 @@ message CancelBuildRequest {
string engine_id = 2;
}


message GetModelInfoRequest {
string engine_type = 1;
string engine_id = 2;
}

message GetModelInfoResponse {
string PresignedUrl = 1;
string BuildId = 2;
string CreatedOn = 3;
string ToBeDeletedOn = 4;
}

message GetQueueSizeRequest {
string engine_type = 1;
}
Expand Down
9 changes: 9 additions & 0 deletions src/Serval.Translation/Contracts/ModelnfoDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Serval.Translation.Models;

public class ModelInfoDto
{
public string PresignedUrl { get; set; } = default!;
public string BuildId { get; set; } = default!;
public string CreatedOn { get; set; } = default!;
public string ToBeDeletedOn { get; set; } = default!;
}
41 changes: 41 additions & 0 deletions src/Serval.Translation/Controllers/TranslationEnginesController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -929,6 +929,47 @@ public async Task<ActionResult> CancelBuildAsync([NotNull] string id, Cancellati
return Ok();
}

/// <summary>
/// Let a link to download the NMT translation model of the last build that was sucessfully saved.
/// </summary>
/// <remarks>
/// If a Nmt build was successful and included the build param `train_params: { save_strategy: yes} }`,
/// then the model will be available to download within 30 days of being created. After that, the model
/// will be deleted to not clutter the system.
/// The endpoint will return a presigned URL that can be used to download the model for up to 1 hour
/// after the request is made. If the URL is not used within that time, a new request will need to be made.
/// </remarks>
/// <param name="id">The translation engine id</param>
/// <param name="cancellationToken"></param>
/// <response code="200">The build job was cancelled successfully</response>
/// <response code="401">The client is not authenticated</response>
/// <response code="403">The authenticated client does not own the translation engine</response>
/// <response code="404">The engine does not exist or there is no active build </response>
/// <response code="405">The translation engine does not support cancelling builds</response>
/// <response code="503">A necessary service is currently unavailable. Check `/health` for more details. </response>
[Authorize(Scopes.UpdateTranslationEngines)]
[HttpPost("{id}/download-model")]
[ProducesResponseType(typeof(void), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
[ProducesResponseType(typeof(void), StatusCodes.Status403Forbidden)]
[ProducesResponseType(typeof(void), StatusCodes.Status404NotFound)]
[ProducesResponseType(typeof(void), StatusCodes.Status405MethodNotAllowed)]
[ProducesResponseType(typeof(void), StatusCodes.Status503ServiceUnavailable)]
public async Task<ActionResult<ModelInfoDto>> DownloadModelAsync(
[NotNull] string id,
CancellationToken cancellationToken
)
{
if (!(await AuthorizeAsync(id, cancellationToken)).IsSuccess(out ActionResult? errorResult))
return errorResult;

if (await _buildService.GetActiveAsync(id) == null)
return NotFound();

ModelInfoDto modelInfo = await _engineService.GetModelUrlAsync(id, cancellationToken);
return Ok(modelInfo);
}

private async Task<(bool, ActionResult?)> AuthorizeAsync(string id, CancellationToken cancellationToken)
{
Engine? engine = await _engineService.GetAsync(id, cancellationToken);
Expand Down
46 changes: 20 additions & 26 deletions src/Serval.Translation/Services/EngineService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,22 @@

namespace Serval.Translation.Services;

public class EngineService : EntityServiceBase<Engine>, IEngineService
public class EngineService(
IRepository<Engine> engines,
IRepository<Build> builds,
IRepository<Pretranslation> pretranslations,
GrpcClientFactory grpcClientFactory,
IOptionsMonitor<DataFileOptions> dataFileOptions,
IDataAccessContext dataAccessContext,
ILoggerFactory loggerFactory
) : EntityServiceBase<Engine>(engines), IEngineService
{
private readonly IRepository<Build> _builds;
private readonly IRepository<Pretranslation> _pretranslations;
private readonly GrpcClientFactory _grpcClientFactory;
private readonly IOptionsMonitor<DataFileOptions> _dataFileOptions;
private readonly IDataAccessContext _dataAccessContext;
private readonly ILogger<EngineService> _logger;

public EngineService(
IRepository<Engine> engines,
IRepository<Build> builds,
IRepository<Pretranslation> pretranslations,
GrpcClientFactory grpcClientFactory,
IOptionsMonitor<DataFileOptions> dataFileOptions,
IDataAccessContext dataAccessContext,
ILoggerFactory loggerFactory
)
: base(engines)
{
_builds = builds;
_pretranslations = pretranslations;
_grpcClientFactory = grpcClientFactory;
_dataFileOptions = dataFileOptions;
_dataAccessContext = dataAccessContext;
_logger = loggerFactory.CreateLogger<EngineService>();
}
private readonly IRepository<Build> _builds = builds;
private readonly IRepository<Pretranslation> _pretranslations = pretranslations;
private readonly GrpcClientFactory _grpcClientFactory = grpcClientFactory;
private readonly IOptionsMonitor<DataFileOptions> _dataFileOptions = dataFileOptions;
private readonly IDataAccessContext _dataAccessContext = dataAccessContext;
private readonly ILogger<EngineService> _logger = loggerFactory.CreateLogger<EngineService>();

public async Task<Models.TranslationResult?> TranslateAsync(
string engineId,
Expand Down Expand Up @@ -281,6 +270,11 @@ await client.CancelBuildAsync(
);
}

public Task<ModelInfoDto> GetModelUrlAsync(string engineId, CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
}

public Task AddCorpusAsync(string engineId, Models.Corpus corpus, CancellationToken cancellationToken = default)
{
return Entities.UpdateAsync(engineId, u => u.Add(e => e.Corpora, corpus), cancellationToken: cancellationToken);
Expand Down
2 changes: 2 additions & 0 deletions src/Serval.Translation/Services/IEngineService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ Task<bool> TrainSegmentPairAsync(

Task CancelBuildAsync(string engineId, CancellationToken cancellationToken = default);

Task<ModelInfoDto> GetModelUrlAsync(string engineId, CancellationToken cancellationToken = default);

Task AddCorpusAsync(string engineId, Corpus corpus, CancellationToken cancellationToken = default);
Task<Corpus?> UpdateCorpusAsync(
string engineId,
Expand Down

0 comments on commit 631372e

Please sign in to comment.