Skip to content
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

Add Language Info endpoint #283

Merged
merged 1 commit into from
Jan 30, 2024
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
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
Loading