Skip to content

Commit

Permalink
Add Language Info endpoint (#283)
Browse files Browse the repository at this point in the history
Move queue endpoint

Resolve reviewer comments

Updated from reviewer comments

IsSupportedNatively

InternalCode

UpdatedNames

Optional name and InternalCode

Update endpoint

Update controllers to kebab case - PascalCase internally
Update documentation
Add pascal case tests

updates from reviewer comments

respond to reviewer comments.

Strip out name from GRPC call
  • Loading branch information
johnml1135 authored Jan 30, 2024
1 parent d78c8ed commit 19016e8
Show file tree
Hide file tree
Showing 17 changed files with 727 additions and 241 deletions.
10 changes: 10 additions & 0 deletions samples/EchoTranslationEngine/TranslationEngineServiceV1.cs
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,16 @@ public override Task<GetQueueSizeResponse> GetQueueSize(GetQueueSizeRequest requ
return Task.FromResult(new GetQueueSizeResponse { Size = 0 });
}

public override Task<GetLanguageInfoResponse> GetLanguageInfo(
GetLanguageInfoRequest request,
ServerCallContext context
)
{
return Task.FromResult(
new GetLanguageInfoResponse { InternalCode = request.Language + "_echo", IsNative = false, }
);
}

public override async Task<HealthCheckResponse> HealthCheck(Empty request, ServerCallContext context)
{
HealthReport healthReport = await _healthCheckService.CheckHealthAsync();
Expand Down
577 changes: 444 additions & 133 deletions src/Serval.Client/Client.g.cs

Large diffs are not rendered by default.

12 changes: 12 additions & 0 deletions src/Serval.Grpc/Protos/serval/translation/v1/engine.proto
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ service TranslationEngineApi {
rpc StartBuild(StartBuildRequest) returns (google.protobuf.Empty);
rpc CancelBuild(CancelBuildRequest) returns (google.protobuf.Empty);
rpc GetQueueSize(GetQueueSizeRequest) returns (GetQueueSizeResponse);
rpc GetLanguageInfo(GetLanguageInfoRequest) returns (GetLanguageInfoResponse);
rpc HealthCheck(google.protobuf.Empty) returns (HealthCheckResponse);
}

Expand Down Expand Up @@ -79,6 +80,17 @@ message GetQueueSizeResponse {
int32 size = 1;
}

message GetLanguageInfoRequest {
string engine_type = 1;
string language = 2;
}

message GetLanguageInfoResponse {
bool is_native = 3;
optional string internal_code = 1;
}


message AlignedWordPair {
int32 source_index = 1;
int32 target_index = 2;
Expand Down
8 changes: 8 additions & 0 deletions src/Serval.Translation/Contracts/TranslationInfoDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Serval.Translation.Contracts;

public class LanguageInfoDto
{
public string EngineType { get; set; } = default!;
public bool IsNative { get; set; } = default!;
public string? InternalCode { get; set; } = default!;
}
104 changes: 104 additions & 0 deletions src/Serval.Translation/Controllers/TranslationEngineTypesController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
namespace Serval.Translation.Controllers;

[ApiVersion(1.0)]
[Route("api/v{version:apiVersion}/translation/engine-types")]
[OpenApiTag("Translation Engines")]
public class TranslationController(IAuthorizationService authService, IEngineService engineService)
: ServalControllerBase(authService)
{
private readonly IEngineService _engineService = engineService;

/// <summary>
/// Get queue information for a given engine type
/// </summary>
/// <param name="engineType">A valid engine type: smt-transfer, nmt, or echo</param>
/// <param name="cancellationToken"></param>
/// <response code="200">Queue information for the specified engine type</response>
/// <response code="401">The client is not authenticated</response>
/// <response code="403">The authenticated client cannot perform the operation</response>
/// <response code="503">A necessary service is currently unavailable. Check `/health` for more details. </response>
[Authorize(Scopes.ReadTranslationEngines)]
[HttpGet("{engineType}/queues")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
[ProducesResponseType(typeof(void), StatusCodes.Status403Forbidden)]
[ProducesResponseType(typeof(void), StatusCodes.Status503ServiceUnavailable)]
public async Task<ActionResult<QueueDto>> GetQueueAsync(
[NotNull] string engineType,
CancellationToken cancellationToken
)
{
try
{
return Map(
await _engineService.GetQueueAsync(engineType.ToPascalCase(), cancellationToken: cancellationToken)
);
}
catch (InvalidOperationException ioe)
{
return BadRequest(ioe.Message);
}
}

/// <summary>
/// Get infromation regarding a language for a given engine type
/// </summary>
/// <remarks>
/// This endpoint is to support Nmt models. It specifies the ISO 639-3 code that the language maps to
/// and whether it is supported in the NLLB 200 model without training. This is useful for determining if a
/// language is an appropriate candidate for a source language or if two languages can be translated between
/// **Base Models available**
/// * **NLLB-200**: This is the only current base transaltion model available.
/// * The languages included in the base model are [here](https://github.com/facebookresearch/flores/blob/main/nllb_seed/README.md)
/// without training.
/// Response format:
/// * **EngineType**: See above
/// * **IsNative**: Whether the base translation model supports this language without fine-tuning.
/// * **InternalCode**: The translation models language code that the language maps to according to [these rules](https://github.com/sillsdev/serval/wiki/FLORES%E2%80%90200-Language-Code-Resolution-for-NMT-Engine).
/// </remarks>
/// <param name="engineType">A valid engine type: nmt or echo</param>
/// <param name="language">The language to retrieve information on.</param>
/// <param name="cancellationToken"></param>
/// <response code="200">Language information for the specified engine type</response>
/// <response code="401">The client is not authenticated</response>
/// <response code="403">The authenticated client cannot perform the operation</response>
/// <response code="405">The method is not supported</response>
[Authorize(Scopes.ReadTranslationEngines)]
[HttpGet("{engineType}/languages/{language}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
[ProducesResponseType(typeof(void), StatusCodes.Status403Forbidden)]
[ProducesResponseType(typeof(void), StatusCodes.Status405MethodNotAllowed)]
public async Task<ActionResult<LanguageInfoDto>> GetLanguageInfoAsync(
[NotNull] string engineType,
[NotNull] string language,
CancellationToken cancellationToken
)
{
try
{
return Map(
await _engineService.GetLanguageInfoAsync(
engineType: engineType.ToPascalCase(),
language: language,
cancellationToken: cancellationToken
)
);
}
catch (InvalidOperationException ioe)
{
return BadRequest(ioe.Message);
}
}

private static QueueDto Map(Queue source) =>
new() { Size = source.Size, EngineType = source.EngineType.ToKebabCase() };

private static LanguageInfoDto Map(LanguageInfo source) =>
new()
{
EngineType = source.EngineType.ToKebabCase(),
IsNative = source.IsNative,
InternalCode = source.InternalCode
};
}
90 changes: 23 additions & 67 deletions src/Serval.Translation/Controllers/TranslationEnginesController.cs
Original file line number Diff line number Diff line change
@@ -1,34 +1,22 @@
using System.Net.Sockets;

namespace Serval.Translation.Controllers;
namespace Serval.Translation.Controllers;

[ApiVersion(1.0)]
[Route("api/v{version:apiVersion}/translation/engines")]
[OpenApiTag("Translation Engines")]
public class TranslationEnginesController : ServalControllerBase
public class TranslationEnginesController(
IAuthorizationService authService,
IEngineService engineService,
IBuildService buildService,
IPretranslationService pretranslationService,
IOptionsMonitor<ApiOptions> apiOptions,
IUrlService urlService
) : ServalControllerBase(authService)
{
private readonly IEngineService _engineService;
private readonly IBuildService _buildService;
private readonly IPretranslationService _pretranslationService;
private readonly IOptionsMonitor<ApiOptions> _apiOptions;
private readonly IUrlService _urlService;

public TranslationEnginesController(
IAuthorizationService authService,
IEngineService engineService,
IBuildService buildService,
IPretranslationService pretranslationService,
IOptionsMonitor<ApiOptions> apiOptions,
IUrlService urlService
)
: base(authService)
{
_engineService = engineService;
_buildService = buildService;
_pretranslationService = pretranslationService;
_apiOptions = apiOptions;
_urlService = urlService;
}
private readonly IEngineService _engineService = engineService;
private readonly IBuildService _buildService = buildService;
private readonly IPretranslationService _pretranslationService = pretranslationService;
private readonly IOptionsMonitor<ApiOptions> _apiOptions = apiOptions;
private readonly IUrlService _urlService = urlService;

/// <summary>
/// Get all translation engines
Expand Down Expand Up @@ -91,24 +79,24 @@ CancellationToken cancellationToken
/// * The name does not have to be unique, as the engine is uniquely identified by the auto-generated id
/// * **sourceLanguage**: The source language code (a valid [IETF language tag](https://en.wikipedia.org/wiki/IETF_language_tag) is recommended)
/// * **targetLanguage**: The target language code (a valid IETF language tag is recommended)
/// * **type**: **SmtTransfer** or **Nmt** or **Echo**
/// ### SmtTransfer
/// * **type**: **smt-transfer** or **nmt** or **echo**
/// ### smt-transfer
/// The Statistical Machine Translation Transfer Learning engine is primarily used for translation suggestions. Typical endpoints: translate, get-word-graph, train-segment
/// ### Nmt
/// ### nmt
/// The Neural Machine Translation engine is primarily used for pretranslations. It is fine-tuned from Meta's NLLB-200. Valid IETF language tags provided to Serval will be converted to [NLLB-200 codes](https://github.com/facebookresearch/flores/tree/main/flores200#languages-in-flores-200). See more about language tag resolution [here](https://github.com/sillsdev/serval/wiki/Language-Tag-Resolution-for-NLLB%E2%80%90200).
///
/// If you use a language among NLLB's supported languages, Serval will utilize everything the NLLB-200 model already knows about that language when translating. If the language you are working with is not among NLLB's supported languages, the language code will have no effect.
///
/// Typical endpoints: pretranslate
/// ### Echo
/// The Echo engine has full coverage of all Nmt and SmtTransfer endpoints. Endpoints like create and build return empty responses. Endpoints like translate and get-word-graph echo the sent content back to the user in a format that mocks Nmt or Smt. For example, translating a segment "test" with the Echo engine would yield a translation response with translation "test". This engine is useful for debugging and testing purposes.
/// ### echo
/// The echo engine has full coverage of all nmt and smt-transfer endpoints. Endpoints like create and build return empty responses. Endpoints like translate and get-word-graph echo the sent content back to the user in a format that mocks nmt or Smt. For example, translating a segment "test" with the echo engine would yield a translation response with translation "test". This engine is useful for debugging and testing purposes.
/// ## Sample request:
///
/// {
/// "name": "myTeam:myProject:myEngine",
/// "sourceLanguage": "el",
/// "targetLanguage": "en",
/// "type": "Nmt"
/// "type": "nmt"
/// }
///
/// </remarks>
Expand Down Expand Up @@ -182,36 +170,6 @@ public async Task<ActionResult> DeleteAsync([NotNull] string id, CancellationTok
return Ok();
}

/// <summary>
/// Get queue information for a given engine type
/// </summary>
/// <param name="engineType">A valid engine type: SmtTransfer, Nmt, or Echo</param>
/// <param name="cancellationToken"></param>
/// <response code="200">Queue information for the specified engine type</response>
/// <response code="401">The client is not authenticated</response>
/// <response code="403">The authenticated client cannot perform the operation</response>
/// <response code="503">A necessary service is currently unavailable. Check `/health` for more details. </response>
[Authorize(Scopes.ReadTranslationEngines)]
[HttpPost("queues")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
[ProducesResponseType(typeof(void), StatusCodes.Status403Forbidden)]
[ProducesResponseType(typeof(void), StatusCodes.Status503ServiceUnavailable)]
public async Task<ActionResult<QueueDto>> GetQueueAsync(
[FromBody] string engineType,
CancellationToken cancellationToken
)
{
try
{
return Map(await _engineService.GetQueueAsync(engineType, cancellationToken));
}
catch (InvalidOperationException ioe)
{
return BadRequest(ioe.Message);
}
}

/// <summary>
/// Translate a segment of text
/// </summary>
Expand Down Expand Up @@ -997,9 +955,9 @@ private Engine Map(TranslationEngineConfigDto source)
Name = source.Name,
SourceLanguage = source.SourceLanguage,
TargetLanguage = source.TargetLanguage,
Type = source.Type,
Type = source.Type.ToPascalCase(),
Owner = Owner,
Corpora = new List<Corpus>()
Corpora = []
};
}

Expand Down Expand Up @@ -1048,8 +1006,6 @@ private static Build Map(Engine engine, TranslationBuildConfigDto source)
return build;
}

private QueueDto Map(Queue source) => new() { Size = source.Size, EngineType = source.EngineType };

private TranslationEngineDto Map(Engine source)
{
return new TranslationEngineDto
Expand All @@ -1059,7 +1015,7 @@ private TranslationEngineDto Map(Engine source)
Name = source.Name,
SourceLanguage = source.SourceLanguage,
TargetLanguage = source.TargetLanguage,
Type = source.Type,
Type = source.Type.ToKebabCase(),
IsBuilding = source.IsBuilding,
ModelRevision = source.ModelRevision,
Confidence = Math.Round(source.Confidence, 8),
Expand Down
2 changes: 1 addition & 1 deletion src/Serval.Translation/Models/Engine.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
namespace Serval.Translation.Models;

public class Engine : IOwnedEntity
public partial class Engine : IOwnedEntity
{
public string Id { get; set; } = default!;
public int Revision { get; set; } = 1;
Expand Down
8 changes: 8 additions & 0 deletions src/Serval.Translation/Models/LanguageInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Serval.Translation.Models;

public class LanguageInfo
{
public string EngineType { get; set; } = default!;
public bool IsNative { get; set; } = default!;
public string? InternalCode { get; set; } = default!;
}
1 change: 1 addition & 0 deletions src/Serval.Translation/Serval.Translation.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

<ItemGroup>
<PackageReference Include="Asp.Versioning.Abstractions" Version="6.2.1" />
<PackageReference Include="CaseExtensions" Version="1.1.0" />
<PackageReference Include="Grpc.AspNetCore" Version="2.52.0" />
<PackageReference Include="MassTransit" Version="8.0.14" />
<PackageReference Include="NSwag.Annotations" Version="13.18.2" />
Expand Down
Loading

0 comments on commit 19016e8

Please sign in to comment.