mistral_translator 0.3.2 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +42 -0
- data/README.md +9 -2
- data/docs/api-reference/configuration.md +19 -2
- data/docs/api-reference/errors.md +15 -24
- data/docs/api-reference/methods.md +10 -4
- data/docs/getting-started.md +8 -2
- data/lib/mistral_translator/client.rb +18 -19
- data/lib/mistral_translator/client_helpers.rb +39 -21
- data/lib/mistral_translator/configuration.rb +11 -1
- data/lib/mistral_translator/data/languages.json +912 -0
- data/lib/mistral_translator/helpers.rb +16 -13
- data/lib/mistral_translator/helpers_extensions.rb +1 -1
- data/lib/mistral_translator/locale_helper.rb +19 -31
- data/lib/mistral_translator/prompt.rb +48 -0
- data/lib/mistral_translator/prompt_builder.rb +40 -60
- data/lib/mistral_translator/prompt_helpers.rb +25 -4
- data/lib/mistral_translator/prompt_metadata_helpers.rb +15 -12
- data/lib/mistral_translator/proxy.rb +75 -0
- data/lib/mistral_translator/response_parser.rb +120 -140
- data/lib/mistral_translator/summarizer.rb +33 -109
- data/lib/mistral_translator/translator.rb +24 -72
- data/lib/mistral_translator/translator_helpers.rb +21 -60
- data/lib/mistral_translator/version.rb +1 -1
- data/lib/mistral_translator.rb +1 -1
- metadata +4 -1
|
@@ -9,7 +9,6 @@ module MistralTranslator
|
|
|
9
9
|
include TranslatorHelpers::InputValidator
|
|
10
10
|
include TranslatorHelpers::RetryHandler
|
|
11
11
|
include TranslatorHelpers::LoggingHelper
|
|
12
|
-
include TranslatorHelpers::PromptHandler
|
|
13
12
|
include TranslatorHelpers::AnalysisHelper
|
|
14
13
|
include TranslatorHelpers::RequestHelper
|
|
15
14
|
include TranslatorHelpers::MultiTargetHelper
|
|
@@ -67,6 +66,7 @@ module MistralTranslator
|
|
|
67
66
|
# Traduction vers plusieurs langues avec support du batch
|
|
68
67
|
def translate_to_multiple(text, from:, to:, **options)
|
|
69
68
|
validate_translation_inputs!(text, from, to)
|
|
69
|
+
Security::BasicValidator.validate_text!(text)
|
|
70
70
|
|
|
71
71
|
source_locale = LocaleHelper.validate_locale!(from)
|
|
72
72
|
target_locales = Array(to).map { |locale| LocaleHelper.validate_locale!(locale) }
|
|
@@ -82,33 +82,33 @@ module MistralTranslator
|
|
|
82
82
|
end
|
|
83
83
|
|
|
84
84
|
# Traduction en lot (plusieurs textes vers une langue)
|
|
85
|
-
def translate_batch(texts, from:, to:, context: nil, glossary: nil)
|
|
85
|
+
def translate_batch(texts, from:, to:, context: nil, glossary: nil, preserve_html: false) # rubocop:disable Metrics/ParameterLists
|
|
86
86
|
validated_texts = Security::BasicValidator.validate_batch!(texts)
|
|
87
87
|
source_locale = LocaleHelper.validate_locale!(from)
|
|
88
88
|
target_locale = LocaleHelper.validate_locale!(to)
|
|
89
89
|
|
|
90
90
|
return handle_same_language_batch(validated_texts) if source_locale == target_locale
|
|
91
91
|
|
|
92
|
-
|
|
92
|
+
batch_config = { source_locale: source_locale, target_locale: target_locale, context: context,
|
|
93
|
+
glossary: glossary, preserve_html: preserve_html }
|
|
94
|
+
process_mixed_batch(validated_texts, batch_config)
|
|
93
95
|
end
|
|
94
96
|
|
|
95
97
|
# Auto-détection de la langue source avec support du contexte
|
|
96
|
-
def translate_auto(text, to:, context: nil, glossary: nil)
|
|
98
|
+
def translate_auto(text, to:, context: nil, glossary: nil, preserve_html: false)
|
|
99
|
+
validated_text = Security::BasicValidator.validate_text!(text)
|
|
100
|
+
return "" if validated_text.empty?
|
|
101
|
+
|
|
97
102
|
target_locale = LocaleHelper.validate_locale!(to)
|
|
98
103
|
|
|
99
|
-
# Premier appel pour détecter la langue
|
|
100
|
-
|
|
101
|
-
detection_response = @client.complete(detection_prompt)
|
|
104
|
+
# Premier appel pour détecter la langue ; un échec lève une erreur
|
|
105
|
+
detection_response = @client.complete(build_language_detection_prompt(validated_text), json: true)
|
|
102
106
|
detected_language = parse_language_detection(detection_response)
|
|
103
|
-
|
|
104
|
-
# Vérifier que la langue détectée est différente de la cible
|
|
105
|
-
if detected_language == target_locale
|
|
106
|
-
# Si même langue, retourner le texte original
|
|
107
|
-
return text
|
|
108
|
-
end
|
|
107
|
+
return validated_text if detected_language == target_locale
|
|
109
108
|
|
|
110
109
|
# Puis traduction normale avec contexte
|
|
111
|
-
translate(
|
|
110
|
+
translate(validated_text, from: detected_language, to: target_locale, context: context, glossary: glossary,
|
|
111
|
+
preserve_html: preserve_html)
|
|
112
112
|
end
|
|
113
113
|
|
|
114
114
|
private
|
|
@@ -119,10 +119,8 @@ module MistralTranslator
|
|
|
119
119
|
validated_texts.each_with_index.to_h { |text, index| [index, text] }
|
|
120
120
|
end
|
|
121
121
|
|
|
122
|
-
def process_mixed_batch(validated_texts,
|
|
122
|
+
def process_mixed_batch(validated_texts, batch_config)
|
|
123
123
|
results = {}
|
|
124
|
-
batch_config = { source_locale: source_locale, target_locale: target_locale, context: context,
|
|
125
|
-
glossary: glossary }
|
|
126
124
|
requests = build_batch_requests(validated_texts, batch_config, results)
|
|
127
125
|
|
|
128
126
|
process_batch_requests(requests, results) if requests.any?
|
|
@@ -147,7 +145,8 @@ module MistralTranslator
|
|
|
147
145
|
def create_batch_request(text, batch_config, index)
|
|
148
146
|
{
|
|
149
147
|
prompt: build_translation_prompt(text, batch_config[:source_locale], batch_config[:target_locale],
|
|
150
|
-
context: batch_config[:context], glossary: batch_config[:glossary]
|
|
148
|
+
context: batch_config[:context], glossary: batch_config[:glossary],
|
|
149
|
+
preserve_html: batch_config[:preserve_html]),
|
|
151
150
|
from: batch_config[:source_locale],
|
|
152
151
|
to: batch_config[:target_locale],
|
|
153
152
|
index: index,
|
|
@@ -172,6 +171,9 @@ module MistralTranslator
|
|
|
172
171
|
parsed_result[:translated] if parsed_result
|
|
173
172
|
rescue EmptyTranslationError
|
|
174
173
|
""
|
|
174
|
+
rescue InvalidResponseError
|
|
175
|
+
# Une réponse illisible fait échouer son élément, pas tout le lot
|
|
176
|
+
nil
|
|
175
177
|
end
|
|
176
178
|
|
|
177
179
|
def translate_with_retry(text, source_locale, target_locale, attempt = 0, **options)
|
|
@@ -198,60 +200,10 @@ module MistralTranslator
|
|
|
198
200
|
# Log via LoggingHelper
|
|
199
201
|
|
|
200
202
|
def build_translation_prompt(text, source_locale, target_locale, **options)
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
context_present = options[:context] && !options[:context].to_s.strip.empty?
|
|
206
|
-
glossary_present =
|
|
207
|
-
(options[:glossary].is_a?(Hash) && options[:glossary].any?) ||
|
|
208
|
-
(options[:glossary].is_a?(String) && !options[:glossary].to_s.strip.empty?)
|
|
209
|
-
if context_present || glossary_present
|
|
210
|
-
enrich_prompt_with_context(base_prompt, options[:context], options[:glossary])
|
|
211
|
-
else
|
|
212
|
-
base_prompt
|
|
213
|
-
end
|
|
214
|
-
end
|
|
215
|
-
|
|
216
|
-
def enrich_prompt_with_context(base_prompt, context, glossary)
|
|
217
|
-
enriched_parts = []
|
|
218
|
-
context_part = build_context_enrichment(context)
|
|
219
|
-
glossary_part = build_glossary_enrichment(glossary)
|
|
220
|
-
enriched_parts << context_part if context_part
|
|
221
|
-
enriched_parts << glossary_part if glossary_part
|
|
222
|
-
|
|
223
|
-
return base_prompt if enriched_parts.empty?
|
|
224
|
-
|
|
225
|
-
enriched_context = enriched_parts.join("\n")
|
|
226
|
-
base_prompt.sub("RÈGLES :", "#{enriched_context}\n\nRÈGLES :")
|
|
227
|
-
end
|
|
228
|
-
|
|
229
|
-
def build_context_enrichment(context)
|
|
230
|
-
return nil if context.nil? || context.to_s.strip.empty?
|
|
231
|
-
|
|
232
|
-
"CONTEXTE : #{context}"
|
|
233
|
-
end
|
|
234
|
-
|
|
235
|
-
def build_glossary_enrichment(glossary)
|
|
236
|
-
return nil if glossary.nil?
|
|
237
|
-
return build_glossary_hash(glossary) if glossary.is_a?(Hash)
|
|
238
|
-
return build_glossary_string(glossary) if glossary.is_a?(String)
|
|
239
|
-
|
|
240
|
-
nil
|
|
241
|
-
end
|
|
242
|
-
|
|
243
|
-
def build_glossary_hash(glossary_hash)
|
|
244
|
-
return nil if glossary_hash.empty?
|
|
245
|
-
|
|
246
|
-
glossary_text = glossary_hash.map { |key, value| "#{key} → #{value}" }.join(", ")
|
|
247
|
-
"GLOSSAIRE (à respecter strictement) : #{glossary_text}"
|
|
248
|
-
end
|
|
249
|
-
|
|
250
|
-
def build_glossary_string(glossary_string)
|
|
251
|
-
value = glossary_string.to_s.strip
|
|
252
|
-
return nil if value.empty?
|
|
253
|
-
|
|
254
|
-
"GLOSSAIRE : #{value}"
|
|
203
|
+
PromptBuilder.translation_prompt(text, source_locale, target_locale,
|
|
204
|
+
context: options[:context],
|
|
205
|
+
glossary: options[:glossary],
|
|
206
|
+
preserve_html: options.fetch(:preserve_html, false))
|
|
255
207
|
end
|
|
256
208
|
end
|
|
257
209
|
end
|
|
@@ -91,34 +91,6 @@ module MistralTranslator
|
|
|
91
91
|
end
|
|
92
92
|
end
|
|
93
93
|
|
|
94
|
-
# Helper pour la gestion des prompts
|
|
95
|
-
module PromptHandler
|
|
96
|
-
def enrich_prompt_with_context(base_prompt, context, glossary)
|
|
97
|
-
context_section = build_context_section(context, glossary)
|
|
98
|
-
base_prompt + context_section
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
def build_context_section(context, glossary)
|
|
102
|
-
context_section = ""
|
|
103
|
-
context_section += add_context_text(context) if context && !context.to_s.strip.empty?
|
|
104
|
-
context_section += add_glossary_text(glossary) if glossary && !glossary.to_s.strip.empty?
|
|
105
|
-
context_section
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
def add_context_text(context)
|
|
109
|
-
"\nCONTEXTE : #{context}\n"
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
def add_glossary_text(glossary)
|
|
113
|
-
if glossary.is_a?(Hash) && glossary.any?
|
|
114
|
-
glossary_text = glossary.map { |key, value| "#{key} → #{value}" }.join(", ")
|
|
115
|
-
"\nGLOSSAIRE (à respecter strictement) : #{glossary_text}\n"
|
|
116
|
-
else
|
|
117
|
-
"\nGLOSSAIRE : #{glossary}\n"
|
|
118
|
-
end
|
|
119
|
-
end
|
|
120
|
-
end
|
|
121
|
-
|
|
122
94
|
# Helper pour l'analyse et la détection de langue
|
|
123
95
|
module AnalysisHelper
|
|
124
96
|
def calculate_confidence_score(original, translated, from_locale, to_locale)
|
|
@@ -152,16 +124,17 @@ module MistralTranslator
|
|
|
152
124
|
PromptBuilder.language_detection_prompt(text)
|
|
153
125
|
end
|
|
154
126
|
|
|
127
|
+
# Fail-closed : une détection illisible ou une langue non supportée lève une erreur,
|
|
128
|
+
# au lieu de supposer une langue et de renvoyer le texte non traduit.
|
|
155
129
|
def parse_language_detection(response)
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
detected = data.dig("metadata", "detected_language")
|
|
130
|
+
data = JSON.parse(response.to_s)
|
|
131
|
+
metadata = data["metadata"] if data.is_a?(Hash)
|
|
132
|
+
detected = LocaleHelper.normalize_locale(metadata["detected_language"]) if metadata.is_a?(Hash)
|
|
133
|
+
return detected if detected && LocaleHelper.locale_supported?(detected)
|
|
161
134
|
|
|
162
|
-
|
|
135
|
+
raise InvalidResponseError.new("Language detection failed", response)
|
|
163
136
|
rescue JSON::ParserError
|
|
164
|
-
"
|
|
137
|
+
raise InvalidResponseError.new("Language detection failed", response), cause: nil
|
|
165
138
|
end
|
|
166
139
|
end
|
|
167
140
|
|
|
@@ -187,31 +160,13 @@ module MistralTranslator
|
|
|
187
160
|
end
|
|
188
161
|
|
|
189
162
|
def perform_client_request(prompt, request_context)
|
|
190
|
-
|
|
191
|
-
begin
|
|
192
|
-
return @client.complete(prompt, context: request_context)
|
|
193
|
-
rescue StandardError => e
|
|
194
|
-
raise e unless e.class.name.include?("MockExpectationError") && @client.respond_to?(:make_request)
|
|
195
|
-
|
|
196
|
-
return @client.make_request(prompt, nil, nil)
|
|
197
|
-
end
|
|
198
|
-
end
|
|
199
|
-
|
|
200
|
-
return @client.make_request(prompt, nil, nil) if @client.respond_to?(:make_request)
|
|
201
|
-
|
|
202
|
-
@client.complete(prompt, context: request_context)
|
|
163
|
+
@client.complete(prompt, context: request_context, json: true)
|
|
203
164
|
end
|
|
204
165
|
|
|
166
|
+
# Une réponse inexploitable lève une erreur : jamais de réponse brute enregistrée comme traduction.
|
|
205
167
|
def extract_translated_text!(raw_response)
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
return result[:translated] if result && !result[:translated].to_s.empty?
|
|
209
|
-
rescue EmptyTranslationError, InvalidResponseError
|
|
210
|
-
# Fallback: accepter une réponse texte brute non vide
|
|
211
|
-
fallback_text = raw_response.to_s.strip
|
|
212
|
-
return fallback_text unless fallback_text.empty?
|
|
213
|
-
# sinon on relance l'erreur plus bas
|
|
214
|
-
end
|
|
168
|
+
result = ResponseParser.parse_translation_response(raw_response)
|
|
169
|
+
return result[:translated] if result && !result[:translated].to_s.empty?
|
|
215
170
|
|
|
216
171
|
raise EmptyTranslationError
|
|
217
172
|
end
|
|
@@ -248,14 +203,20 @@ module MistralTranslator
|
|
|
248
203
|
batch_results.each do |result|
|
|
249
204
|
next unless result[:success]
|
|
250
205
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
results[target_locale] = parsed_result[:translated] if parsed_result
|
|
206
|
+
translated = parse_batch_translation(result[:result])
|
|
207
|
+
results[result[:original_request][:to]] = translated if translated
|
|
254
208
|
end
|
|
255
209
|
|
|
256
210
|
results
|
|
257
211
|
end
|
|
258
212
|
|
|
213
|
+
# Une réponse illisible fait échouer sa langue, pas tout le lot
|
|
214
|
+
def parse_batch_translation(response)
|
|
215
|
+
ResponseParser.parse_translation_response(response)&.fetch(:translated)
|
|
216
|
+
rescue EmptyTranslationError, InvalidResponseError
|
|
217
|
+
nil
|
|
218
|
+
end
|
|
219
|
+
|
|
259
220
|
def translate_to_multiple_sequential(text, source_locale, target_locales, **options)
|
|
260
221
|
options = DEFAULT_MULTI_TARGET_OPTIONS.merge(options)
|
|
261
222
|
results = {}
|
data/lib/mistral_translator.rb
CHANGED
|
@@ -107,7 +107,7 @@ module MistralTranslator
|
|
|
107
107
|
|
|
108
108
|
# Health check
|
|
109
109
|
def health_check
|
|
110
|
-
client.
|
|
110
|
+
client.chat("Hello", max_tokens: 10)
|
|
111
111
|
{ status: :ok, message: "API connection successful" }
|
|
112
112
|
rescue AuthenticationError
|
|
113
113
|
{ status: :error, message: "Authentication failed - check your API key" }
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: mistral_translator
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Pierre Cherbero
|
|
@@ -159,15 +159,18 @@ files:
|
|
|
159
159
|
- lib/mistral_translator/client.rb
|
|
160
160
|
- lib/mistral_translator/client_helpers.rb
|
|
161
161
|
- lib/mistral_translator/configuration.rb
|
|
162
|
+
- lib/mistral_translator/data/languages.json
|
|
162
163
|
- lib/mistral_translator/errors.rb
|
|
163
164
|
- lib/mistral_translator/helpers.rb
|
|
164
165
|
- lib/mistral_translator/helpers_extensions.rb
|
|
165
166
|
- lib/mistral_translator/levenshtein_helpers.rb
|
|
166
167
|
- lib/mistral_translator/locale_helper.rb
|
|
167
168
|
- lib/mistral_translator/logger.rb
|
|
169
|
+
- lib/mistral_translator/prompt.rb
|
|
168
170
|
- lib/mistral_translator/prompt_builder.rb
|
|
169
171
|
- lib/mistral_translator/prompt_helpers.rb
|
|
170
172
|
- lib/mistral_translator/prompt_metadata_helpers.rb
|
|
173
|
+
- lib/mistral_translator/proxy.rb
|
|
171
174
|
- lib/mistral_translator/response_parser.rb
|
|
172
175
|
- lib/mistral_translator/security.rb
|
|
173
176
|
- lib/mistral_translator/summarizer.rb
|