mistral_translator 0.3.1 → 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 +64 -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/adapters.rb +73 -33
- data/lib/mistral_translator/client.rb +18 -19
- data/lib/mistral_translator/client_helpers.rb +41 -23
- data/lib/mistral_translator/configuration.rb +11 -1
- data/lib/mistral_translator/data/languages.json +912 -0
- data/lib/mistral_translator/helpers.rb +20 -16
- 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 -12
- data/lib/mistral_translator/prompt_metadata_helpers.rb +16 -17
- data/lib/mistral_translator/proxy.rb +75 -0
- data/lib/mistral_translator/response_parser.rb +120 -140
- data/lib/mistral_translator/security.rb +16 -14
- data/lib/mistral_translator/summarizer.rb +37 -110
- data/lib/mistral_translator/translator.rb +33 -77
- data/lib/mistral_translator/translator_helpers.rb +30 -64
- data/lib/mistral_translator/version.rb +1 -1
- data/lib/mistral_translator.rb +1 -1
- metadata +4 -1
|
@@ -6,10 +6,10 @@ module MistralTranslator
|
|
|
6
6
|
module ClientHelpers
|
|
7
7
|
# Helper pour la gestion des requêtes HTTP
|
|
8
8
|
module RequestHandler
|
|
9
|
-
def make_request(prompt, max_tokens, temperature)
|
|
9
|
+
def make_request(prompt, max_tokens, temperature, json: false)
|
|
10
10
|
uri = URI("#{@base_uri}/v1/chat/completions")
|
|
11
11
|
|
|
12
|
-
request_body = build_request_body(prompt, max_tokens, temperature)
|
|
12
|
+
request_body = build_request_body(prompt, max_tokens, temperature, json: json)
|
|
13
13
|
|
|
14
14
|
request = Net::HTTP::Post.new(uri.path, headers)
|
|
15
15
|
request.body = request_body.to_json
|
|
@@ -22,6 +22,9 @@ module MistralTranslator
|
|
|
22
22
|
raise ApiError, "Request timeout: #{e.message}"
|
|
23
23
|
rescue Net::HTTPError => e
|
|
24
24
|
raise ApiError, "HTTP error: #{e.message}"
|
|
25
|
+
rescue Net::HTTP::Persistent::Error => e
|
|
26
|
+
# Proxy ou API injoignable ; le message d'origine peut citer l'adresse du proxy
|
|
27
|
+
raise ApiError, "Connection failed (#{e.class})", cause: nil
|
|
25
28
|
end
|
|
26
29
|
|
|
27
30
|
def http_pool
|
|
@@ -34,6 +37,7 @@ module MistralTranslator
|
|
|
34
37
|
|
|
35
38
|
# SSL/TLS configuration
|
|
36
39
|
configure_ssl(pool)
|
|
40
|
+
configure_proxy(pool)
|
|
37
41
|
|
|
38
42
|
pool
|
|
39
43
|
end
|
|
@@ -56,18 +60,32 @@ module MistralTranslator
|
|
|
56
60
|
pool.ca_path = config.ssl_ca_path if config.ssl_ca_path
|
|
57
61
|
end
|
|
58
62
|
|
|
59
|
-
def
|
|
63
|
+
def configure_proxy(pool)
|
|
64
|
+
proxy_uri = Proxy.resolve(MistralTranslator.configuration.proxy, host: URI(@base_uri).host)
|
|
65
|
+
pool.proxy = proxy_uri if proxy_uri
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def build_request_body(prompt, max_tokens, temperature, json: false)
|
|
60
69
|
body = {
|
|
61
70
|
model: @model,
|
|
62
|
-
messages:
|
|
71
|
+
messages: request_messages(prompt)
|
|
63
72
|
}
|
|
64
73
|
|
|
65
74
|
body[:max_tokens] = max_tokens if max_tokens
|
|
66
75
|
body[:temperature] = temperature if temperature
|
|
76
|
+
# L'API garantit alors un objet JSON syntaxiquement valide
|
|
77
|
+
body[:response_format] = { type: "json_object" } if json
|
|
67
78
|
|
|
68
79
|
body
|
|
69
80
|
end
|
|
70
81
|
|
|
82
|
+
# Un Prompt sépare consignes et texte ; une String part en un seul message.
|
|
83
|
+
def request_messages(prompt)
|
|
84
|
+
return prompt.messages if prompt.respond_to?(:messages)
|
|
85
|
+
|
|
86
|
+
[{ role: "user", content: prompt }]
|
|
87
|
+
end
|
|
88
|
+
|
|
71
89
|
def headers
|
|
72
90
|
{
|
|
73
91
|
"Authorization" => "Bearer #{@api_key}",
|
|
@@ -79,15 +97,17 @@ module MistralTranslator
|
|
|
79
97
|
|
|
80
98
|
# Helper pour la gestion des erreurs et retry
|
|
81
99
|
module ErrorHandler
|
|
82
|
-
|
|
100
|
+
# request_options : max_tokens, temperature, json
|
|
101
|
+
def make_request_with_retry(prompt, request_options, context, attempt = 0)
|
|
83
102
|
context[:attempt] = attempt
|
|
84
|
-
response = make_request(prompt, max_tokens, temperature
|
|
103
|
+
response = make_request(prompt, request_options[:max_tokens], request_options[:temperature],
|
|
104
|
+
json: request_options.fetch(:json, false))
|
|
85
105
|
|
|
86
106
|
# Vérifier les erreurs dans la réponse
|
|
87
107
|
check_response_for_errors(response)
|
|
88
108
|
|
|
89
109
|
if rate_limit_exceeded?(response)
|
|
90
|
-
handle_rate_limit(prompt,
|
|
110
|
+
handle_rate_limit(prompt, request_options, context, attempt)
|
|
91
111
|
else
|
|
92
112
|
response
|
|
93
113
|
end
|
|
@@ -101,24 +121,19 @@ module MistralTranslator
|
|
|
101
121
|
# Rate limit sera géré séparément
|
|
102
122
|
nil
|
|
103
123
|
when 400..499
|
|
104
|
-
raise ApiError, "Client error (#{response.code})
|
|
124
|
+
raise ApiError, "Client error (#{response.code})"
|
|
105
125
|
when 500..599
|
|
106
|
-
raise ApiError, "Server error (#{response.code})
|
|
126
|
+
raise ApiError, "Server error (#{response.code})"
|
|
107
127
|
end
|
|
108
128
|
end
|
|
109
129
|
|
|
130
|
+
# Seul le statut compte : le corps d'une réponse 200 contient le texte traduit,
|
|
131
|
+
# qui peut très bien parler de « rate limit ».
|
|
110
132
|
def rate_limit_exceeded?(response)
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
return false unless response.code.to_i == 200
|
|
114
|
-
|
|
115
|
-
body_content = response.body.to_s
|
|
116
|
-
return false if body_content.length > 1000
|
|
117
|
-
|
|
118
|
-
body_content.match?(/rate.?limit|quota.?exceeded/i)
|
|
133
|
+
response.code.to_i == 429
|
|
119
134
|
end
|
|
120
135
|
|
|
121
|
-
def handle_rate_limit(prompt,
|
|
136
|
+
def handle_rate_limit(prompt, request_options, context, attempt)
|
|
122
137
|
unless attempt < @retry_delays.length
|
|
123
138
|
raise RateLimitError, "API rate limit exceeded after #{@retry_delays.length} retries"
|
|
124
139
|
end
|
|
@@ -135,7 +150,7 @@ module MistralTranslator
|
|
|
135
150
|
|
|
136
151
|
log_rate_limit_retry(wait_time, attempt)
|
|
137
152
|
sleep(wait_time)
|
|
138
|
-
make_request_with_retry(prompt,
|
|
153
|
+
make_request_with_retry(prompt, request_options, context, attempt + 1)
|
|
139
154
|
end
|
|
140
155
|
end
|
|
141
156
|
|
|
@@ -153,7 +168,7 @@ module MistralTranslator
|
|
|
153
168
|
attempt: 0
|
|
154
169
|
}
|
|
155
170
|
|
|
156
|
-
result = complete(request[:prompt], context: context)
|
|
171
|
+
result = complete(request[:prompt], context: context, json: true)
|
|
157
172
|
results << {
|
|
158
173
|
success: true,
|
|
159
174
|
result: result,
|
|
@@ -175,10 +190,13 @@ module MistralTranslator
|
|
|
175
190
|
|
|
176
191
|
# Helper pour le logging
|
|
177
192
|
module LoggingHelper
|
|
193
|
+
# Jamais le contenu des messages : seulement le modèle et les tailles
|
|
178
194
|
def log_request_response(request_body, response)
|
|
179
|
-
|
|
180
|
-
Logger.debug_if_verbose(
|
|
181
|
-
|
|
195
|
+
sizes = request_body[:messages].map { |message| "#{message[:role]}=#{message[:content].to_s.length}" }
|
|
196
|
+
Logger.debug_if_verbose(
|
|
197
|
+
"Request sent to API: model=#{request_body[:model]} #{sizes.join(" ")}, response #{response.code}",
|
|
198
|
+
sensitive: false
|
|
199
|
+
)
|
|
182
200
|
end
|
|
183
201
|
|
|
184
202
|
def log_rate_limit_retry(wait_time, attempt)
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "proxy"
|
|
4
|
+
|
|
3
5
|
module MistralTranslator
|
|
4
6
|
class Configuration
|
|
5
7
|
attr_accessor :api_key, :model, :default_max_tokens, :default_temperature, :retry_delays,
|
|
6
8
|
:on_translation_start, :on_translation_complete, :on_translation_error,
|
|
7
9
|
:on_rate_limit, :on_batch_complete, :enable_metrics,
|
|
8
10
|
:ssl_verify_mode, :ssl_ca_file, :ssl_ca_path, :ssl_timeout
|
|
9
|
-
attr_reader :api_url
|
|
11
|
+
attr_reader :api_url, :proxy
|
|
10
12
|
|
|
11
13
|
def initialize
|
|
12
14
|
@api_key = nil
|
|
@@ -30,6 +32,9 @@ module MistralTranslator
|
|
|
30
32
|
@ssl_ca_path = nil # Path to CA certificates directory
|
|
31
33
|
@ssl_timeout = 60 # SSL handshake timeout in seconds
|
|
32
34
|
|
|
35
|
+
# Proxy : nil (aucun), URI ou String, ou :env (HTTPS_PROXY, HTTP_PROXY, NO_PROXY)
|
|
36
|
+
@proxy = nil
|
|
37
|
+
|
|
33
38
|
# Métriques intégrées (thread-safe avec mutex)
|
|
34
39
|
@metrics_mutex = Mutex.new
|
|
35
40
|
@metrics = {
|
|
@@ -56,6 +61,11 @@ module MistralTranslator
|
|
|
56
61
|
@api_url = url
|
|
57
62
|
end
|
|
58
63
|
|
|
64
|
+
def proxy=(value)
|
|
65
|
+
Proxy.validate!(value)
|
|
66
|
+
@proxy = value
|
|
67
|
+
end
|
|
68
|
+
|
|
59
69
|
# Méthodes pour les callbacks
|
|
60
70
|
def trigger_translation_start(from_locale, to_locale, text_length)
|
|
61
71
|
@on_translation_start&.call(from_locale, to_locale, text_length, Time.now)
|