Skip to content

Commit 69cf4f9

Browse files
authored
Add endpoint to retrieve all pretranslation confidences (#935)
1 parent fa53b59 commit 69cf4f9

3 files changed

Lines changed: 310 additions & 0 deletions

File tree

src/Serval/src/Serval.Client/Client.g.cs

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2919,6 +2919,16 @@ public partial interface ITranslationEnginesClient
29192919
[System.Obsolete]
29202920
System.Threading.Tasks.Task<System.Collections.Generic.IList<Pretranslation>> GetAllCorpusPretranslationsAsync(string id, string corpusId, string? textId = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
29212921

2922+
/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
2923+
/// <summary>
2924+
/// Get all pretranslation confidences in a parallel corpus of a translation engine
2925+
/// </summary>
2926+
/// <param name="id">The translation engine id</param>
2927+
/// <param name="parallelCorpusId">The parallel corpus id</param>
2928+
/// <returns>The confidence values.</returns>
2929+
/// <exception cref="ServalApiException">A server side error occurred.</exception>
2930+
System.Threading.Tasks.Task<System.Collections.Generic.IList<PretranslationConfidence>> GetAllPretranslationConfidencesAsync(string id, string parallelCorpusId, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
2931+
29222932
/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
29232933
/// <summary>
29242934
/// Get all pretranslations in a parallel corpus of a translation engine
@@ -5441,6 +5451,122 @@ public string BaseUrl
54415451
}
54425452
}
54435453

5454+
/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
5455+
/// <summary>
5456+
/// Get all pretranslation confidences in a parallel corpus of a translation engine
5457+
/// </summary>
5458+
/// <param name="id">The translation engine id</param>
5459+
/// <param name="parallelCorpusId">The parallel corpus id</param>
5460+
/// <returns>The confidence values.</returns>
5461+
/// <exception cref="ServalApiException">A server side error occurred.</exception>
5462+
public virtual async System.Threading.Tasks.Task<System.Collections.Generic.IList<PretranslationConfidence>> GetAllPretranslationConfidencesAsync(string id, string parallelCorpusId, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))
5463+
{
5464+
if (id == null)
5465+
throw new System.ArgumentNullException("id");
5466+
5467+
if (parallelCorpusId == null)
5468+
throw new System.ArgumentNullException("parallelCorpusId");
5469+
5470+
var client_ = _httpClient;
5471+
var disposeClient_ = false;
5472+
try
5473+
{
5474+
using (var request_ = new System.Net.Http.HttpRequestMessage())
5475+
{
5476+
request_.Method = new System.Net.Http.HttpMethod("GET");
5477+
request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json"));
5478+
5479+
var urlBuilder_ = new System.Text.StringBuilder();
5480+
if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl);
5481+
// Operation Path: "translation/engines/{id}/parallel-corpora/{parallelCorpusId}/confidences"
5482+
urlBuilder_.Append("translation/engines/");
5483+
urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(id, System.Globalization.CultureInfo.InvariantCulture)));
5484+
urlBuilder_.Append("/parallel-corpora/");
5485+
urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(parallelCorpusId, System.Globalization.CultureInfo.InvariantCulture)));
5486+
urlBuilder_.Append("/confidences");
5487+
5488+
PrepareRequest(client_, request_, urlBuilder_);
5489+
5490+
var url_ = urlBuilder_.ToString();
5491+
request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute);
5492+
5493+
PrepareRequest(client_, request_, url_);
5494+
5495+
var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
5496+
var disposeResponse_ = true;
5497+
try
5498+
{
5499+
var headers_ = new System.Collections.Generic.Dictionary<string, System.Collections.Generic.IEnumerable<string>>();
5500+
foreach (var item_ in response_.Headers)
5501+
headers_[item_.Key] = item_.Value;
5502+
if (response_.Content != null && response_.Content.Headers != null)
5503+
{
5504+
foreach (var item_ in response_.Content.Headers)
5505+
headers_[item_.Key] = item_.Value;
5506+
}
5507+
5508+
ProcessResponse(client_, response_);
5509+
5510+
var status_ = (int)response_.StatusCode;
5511+
if (status_ == 200)
5512+
{
5513+
var objectResponse_ = await ReadObjectResponseAsync<System.Collections.Generic.IList<PretranslationConfidence>>(response_, headers_, cancellationToken).ConfigureAwait(false);
5514+
if (objectResponse_.Object == null)
5515+
{
5516+
throw new ServalApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
5517+
}
5518+
return objectResponse_.Object;
5519+
}
5520+
else
5521+
if (status_ == 401)
5522+
{
5523+
string responseText_ = ( response_.Content == null ) ? string.Empty : await response_.Content.ReadAsStringAsync().ConfigureAwait(false);
5524+
throw new ServalApiException("The client is not authenticated.", status_, responseText_, headers_, null);
5525+
}
5526+
else
5527+
if (status_ == 403)
5528+
{
5529+
string responseText_ = ( response_.Content == null ) ? string.Empty : await response_.Content.ReadAsStringAsync().ConfigureAwait(false);
5530+
throw new ServalApiException("The authenticated client cannot perform the operation or does not own the translation engine.", status_, responseText_, headers_, null);
5531+
}
5532+
else
5533+
if (status_ == 404)
5534+
{
5535+
string responseText_ = ( response_.Content == null ) ? string.Empty : await response_.Content.ReadAsStringAsync().ConfigureAwait(false);
5536+
throw new ServalApiException("The engine or parallel corpus does not exist.", status_, responseText_, headers_, null);
5537+
}
5538+
else
5539+
if (status_ == 409)
5540+
{
5541+
string responseText_ = ( response_.Content == null ) ? string.Empty : await response_.Content.ReadAsStringAsync().ConfigureAwait(false);
5542+
throw new ServalApiException("The engine needs to be built first.", status_, responseText_, headers_, null);
5543+
}
5544+
else
5545+
if (status_ == 503)
5546+
{
5547+
string responseText_ = ( response_.Content == null ) ? string.Empty : await response_.Content.ReadAsStringAsync().ConfigureAwait(false);
5548+
throw new ServalApiException("A necessary service is currently unavailable. Check `/health` for more details.", status_, responseText_, headers_, null);
5549+
}
5550+
else
5551+
{
5552+
var responseData_ = response_.Content == null ? null : await ReadAsStringAsync(response_.Content, cancellationToken).ConfigureAwait(false);
5553+
throw new ServalApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null);
5554+
}
5555+
}
5556+
finally
5557+
{
5558+
if (disposeResponse_)
5559+
response_.Dispose();
5560+
}
5561+
}
5562+
}
5563+
finally
5564+
{
5565+
if (disposeClient_)
5566+
client_.Dispose();
5567+
}
5568+
}
5569+
54445570
/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
54455571
/// <summary>
54465572
/// Get all pretranslations in a parallel corpus of a translation engine
@@ -11561,6 +11687,23 @@ public partial class Pretranslation
1156111687

1156211688
}
1156311689

11690+
[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.6.3.0 (NJsonSchema v11.5.2.0 (Newtonsoft.Json v13.0.0.0))")]
11691+
public partial class PretranslationConfidence
11692+
{
11693+
11694+
[Newtonsoft.Json.JsonProperty("sourceRefs", Required = Newtonsoft.Json.Required.Always)]
11695+
[System.ComponentModel.DataAnnotations.Required]
11696+
public System.Collections.Generic.IList<string> SourceRefs { get; set; } = new System.Collections.ObjectModel.Collection<string>();
11697+
11698+
[Newtonsoft.Json.JsonProperty("targetRefs", Required = Newtonsoft.Json.Required.Always)]
11699+
[System.ComponentModel.DataAnnotations.Required]
11700+
public System.Collections.Generic.IList<string> TargetRefs { get; set; } = new System.Collections.ObjectModel.Collection<string>();
11701+
11702+
[Newtonsoft.Json.JsonProperty("confidence", Required = Newtonsoft.Json.Required.Always)]
11703+
public double Confidence { get; set; } = default!;
11704+
11705+
}
11706+
1156411707
[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.6.3.0 (NJsonSchema v11.5.2.0 (Newtonsoft.Json v13.0.0.0))")]
1156511708
public enum PretranslationUsfmTextOrigin
1156611709
{
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
namespace Serval.Translation.Features.Engines;
2+
3+
public record PretranslationConfidenceDto
4+
{
5+
public required IReadOnlyList<string> SourceRefs { get; init; }
6+
public required IReadOnlyList<string> TargetRefs { get; init; }
7+
public required double Confidence { get; init; }
8+
}
9+
10+
public record GetAllPretranslationConfidences(string Owner, string EngineId, string ParallelCorpusId)
11+
: IRequest<GetAllPretranslationConfidencesResponse>;
12+
13+
public record GetAllPretranslationConfidencesResponse(
14+
PretranslationStatus Status,
15+
IReadOnlyList<PretranslationConfidenceDto>? PretranslationConfidences = null
16+
);
17+
18+
public class GetAllPretranslationConfidencesHandler(
19+
IRepository<Engine> engines,
20+
IRepository<Pretranslation> pretranslations,
21+
ILogger<GetAllPretranslationConfidencesHandler> logger
22+
) : IRequestHandler<GetAllPretranslationConfidences, GetAllPretranslationConfidencesResponse>
23+
{
24+
public async Task<GetAllPretranslationConfidencesResponse> HandleAsync(
25+
GetAllPretranslationConfidences request,
26+
CancellationToken cancellationToken = default
27+
)
28+
{
29+
Engine engine = await engines.CheckOwnerAsync(request.EngineId, request.Owner, cancellationToken);
30+
31+
PretranslationStatus status = engine.GetParallelCorpusPretranslationStatus(request.ParallelCorpusId);
32+
if (status != PretranslationStatus.Found)
33+
return new(status);
34+
35+
IReadOnlyList<Pretranslation> results = await pretranslations.GetAllAsync(
36+
pt =>
37+
pt.EngineRef == request.EngineId
38+
&& pt.ModelRevision == engine.ModelRevision
39+
&& pt.CorpusRef == request.ParallelCorpusId,
40+
cancellationToken
41+
);
42+
logger.LogInformation(
43+
"Returning {Count} pretranslation confidences for engine {EngineId}, and parallel corpus {ParallelCorpusId}",
44+
results.Count,
45+
request.EngineId,
46+
request.ParallelCorpusId
47+
);
48+
return new(status, PretranslationConfidences: [.. results.Select(Map)]);
49+
}
50+
51+
private static PretranslationConfidenceDto Map(Pretranslation source) =>
52+
new()
53+
{
54+
SourceRefs = source.SourceRefs ?? [],
55+
TargetRefs = source.TargetRefs ?? [],
56+
Confidence = source.Confidence ?? -1.0,
57+
};
58+
}
59+
60+
public partial class TranslationEnginesController
61+
{
62+
/// <summary>
63+
/// Get all pretranslation confidences in a parallel corpus of a translation engine
64+
/// </summary>
65+
/// <param name="id">The translation engine id</param>
66+
/// <param name="parallelCorpusId">The parallel corpus id</param>
67+
/// <response code="200">The confidence values.</response>
68+
/// <response code="401">The client is not authenticated.</response>
69+
/// <response code="403">The authenticated client cannot perform the operation or does not own the translation engine.</response>
70+
/// <response code="404">The engine or parallel corpus does not exist.</response>
71+
/// <response code="409">The engine needs to be built first.</response>
72+
/// <response code="503">A necessary service is currently unavailable. Check `/health` for more details.</response>
73+
[Authorize(Scopes.ReadTranslationEngines)]
74+
[HttpGet("{id}/parallel-corpora/{parallelCorpusId}/confidences")]
75+
[ProducesResponseType(StatusCodes.Status200OK)]
76+
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
77+
[ProducesResponseType(typeof(void), StatusCodes.Status403Forbidden)]
78+
[ProducesResponseType(typeof(void), StatusCodes.Status404NotFound)]
79+
[ProducesResponseType(typeof(void), StatusCodes.Status409Conflict)]
80+
[ProducesResponseType(typeof(void), StatusCodes.Status503ServiceUnavailable)]
81+
public async Task<ActionResult<IEnumerable<PretranslationConfidenceDto>>> GetAllPretranslationConfidencesAsync(
82+
[NotNull] string id,
83+
[NotNull] string parallelCorpusId,
84+
[FromServices]
85+
IRequestHandler<GetAllPretranslationConfidences, GetAllPretranslationConfidencesResponse> handler,
86+
CancellationToken cancellationToken
87+
)
88+
{
89+
GetAllPretranslationConfidencesResponse response = await handler.HandleAsync(
90+
new(Owner, id, parallelCorpusId),
91+
cancellationToken
92+
);
93+
if (response.Status == PretranslationStatus.CorpusNotFound)
94+
return NotFound();
95+
if (response.Status == PretranslationStatus.NotBuilt)
96+
return Conflict();
97+
return Ok(response.PretranslationConfidences);
98+
}
99+
}

src/Serval/test/Serval.ApiServer.IntegrationTests/TranslationEngineTests.cs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2515,6 +2515,74 @@ await MongoMigrations.MigrateTargetQuoteConvention(
25152515
});
25162516
}
25172517

2518+
[Test]
2519+
public async Task GetAllPretranslationConfidencesAsync_Exists()
2520+
{
2521+
TranslationEnginesClient client = _env.CreateTranslationEnginesClient();
2522+
TranslationParallelCorpus addedCorpus = await client.AddParallelCorpusAsync(
2523+
ECHO_ENGINE1_ID,
2524+
TestParallelCorpusConfig
2525+
);
2526+
2527+
await _env.Engines.UpdateAsync(ECHO_ENGINE1_ID, u => u.Set(e => e.ModelRevision, 1));
2528+
var pret = new Translation.Models.Pretranslation
2529+
{
2530+
CorpusRef = addedCorpus.Id,
2531+
TextId = "all",
2532+
EngineRef = ECHO_ENGINE1_ID,
2533+
SourceRefs = ["ref1", "ref2"],
2534+
TargetRefs = ["ref1", "ref2"],
2535+
Refs = ["ref1", "ref2"],
2536+
Translation = "translation",
2537+
ModelRevision = 1,
2538+
Confidence = 0.5,
2539+
};
2540+
await _env.Pretranslations.InsertAsync(pret);
2541+
2542+
ICollection<Client.PretranslationConfidence> results = await client.GetAllPretranslationConfidencesAsync(
2543+
ECHO_ENGINE1_ID,
2544+
addedCorpus.Id
2545+
);
2546+
Assert.That(results.All(p => p.Confidence == 0.5), Is.True);
2547+
}
2548+
2549+
[Test]
2550+
public void GetAllPretranslationConfidencesAsync_EngineDoesNotExist()
2551+
{
2552+
TranslationEnginesClient client = _env.CreateTranslationEnginesClient();
2553+
2554+
ServalApiException? ex = Assert.ThrowsAsync<ServalApiException>(() =>
2555+
client.GetAllPretranslationConfidencesAsync(DOES_NOT_EXIST_ENGINE_ID, "cccccccccccccccccccccccc")
2556+
);
2557+
Assert.That(ex?.StatusCode, Is.EqualTo(404));
2558+
}
2559+
2560+
[Test]
2561+
public void GetAllPretranslationConfidencesAsync_CorpusDoesNotExist()
2562+
{
2563+
TranslationEnginesClient client = _env.CreateTranslationEnginesClient();
2564+
2565+
ServalApiException? ex = Assert.ThrowsAsync<ServalApiException>(() =>
2566+
client.GetAllPretranslationConfidencesAsync(ECHO_ENGINE1_ID, "cccccccccccccccccccccccc")
2567+
);
2568+
Assert.That(ex?.StatusCode, Is.EqualTo(404));
2569+
}
2570+
2571+
[Test]
2572+
public async Task GetAllPretranslationConfidencesAsync_EngineNotBuilt()
2573+
{
2574+
TranslationEnginesClient client = _env.CreateTranslationEnginesClient();
2575+
TranslationParallelCorpus addedCorpus = await client.AddParallelCorpusAsync(
2576+
ECHO_ENGINE2_ID,
2577+
TestParallelCorpusConfig
2578+
);
2579+
2580+
ServalApiException? ex = Assert.ThrowsAsync<ServalApiException>(() =>
2581+
client.GetAllPretranslationConfidencesAsync(ECHO_ENGINE2_ID, addedCorpus.Id)
2582+
);
2583+
Assert.That(ex?.StatusCode, Is.EqualTo(409));
2584+
}
2585+
25182586
[TearDown]
25192587
public void TearDown()
25202588
{

0 commit comments

Comments
 (0)