mistral_translator 0.2.1 → 0.3.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 +23 -0
- data/CONTRIBUTING.md +70 -0
- data/LICENSE.txt +6 -6
- data/README.md +40 -15
- data/docs/advanced-usage/concurrent-async.md +270 -0
- data/lib/mistral_translator/client_helpers.rb +35 -5
- data/lib/mistral_translator/configuration.rb +54 -34
- data/lib/mistral_translator/logger.rb +11 -4
- data/lib/mistral_translator/version.rb +1 -1
- metadata +17 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5e52138d192d693042abbc570da3a9ed1f7f56acaed881c46a59df100dc476a0
|
|
4
|
+
data.tar.gz: 74a169d74c5248dc7ec1cab84c5892018eeacf3ba398bfb0b0a3c41a98d87ae9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3f324d7cb87b75c340d1875fc6189c13c39dfe26c1878ff187c30c04674b88549c813be6f73c644b2b01a5f28ed5e88da55351c86b37800d9648835113f58b3e
|
|
7
|
+
data.tar.gz: 0d9ac452969b457ed81cd34295e564d869c3e8f254e0807a8d473af8c99fbcb10f0d2ea5bc5abea518a0f7bd59d978428dac8e5c25864c63a4fa61d0dfb1617a
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.3.0] - 2025-12-09
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Thread-safe metrics with Mutex protection for concurrent usage
|
|
8
|
+
- Connection pooling with net-http-persistent (automatic connection reuse)
|
|
9
|
+
- SSL/TLS configuration options: `ssl_verify_mode`, `ssl_ca_file`, `ssl_ca_path`, `ssl_timeout`
|
|
10
|
+
- Thread-safe logger cache for `warn_once` method
|
|
11
|
+
- Comprehensive concurrent/async documentation with SolidQueue, Sidekiq, Concurrent Ruby examples
|
|
12
|
+
- 39 new tests for thread-safety, SSL configuration, and connection pooling
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- HTTP client now uses Net::HTTP::Persistent for better performance
|
|
17
|
+
- Configuration metrics now thread-safe across concurrent requests
|
|
18
|
+
- Documentation reorganized with SolidQueue as recommended background job backend
|
|
19
|
+
|
|
20
|
+
### Performance
|
|
21
|
+
|
|
22
|
+
- Connection pooling reduces TCP handshake overhead
|
|
23
|
+
- Thread-safe implementation enables safe concurrent translations
|
|
24
|
+
- Configurable SSL timeout for production environments
|
|
25
|
+
|
|
3
26
|
## [0.2.0] - 2025-09-09
|
|
4
27
|
|
|
5
28
|
### Added
|
data/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Contributing to MistralTranslator
|
|
2
|
+
|
|
3
|
+
Thank you for your interest in contributing to MistralTranslator! We welcome contributions of all kinds.
|
|
4
|
+
|
|
5
|
+
## Development Setup
|
|
6
|
+
|
|
7
|
+
### Prerequisites
|
|
8
|
+
|
|
9
|
+
- Ruby 3.2+
|
|
10
|
+
- Bundler
|
|
11
|
+
|
|
12
|
+
### Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
git clone https://github.com/your-username/mistral_translator.git
|
|
16
|
+
cd mistral_translator
|
|
17
|
+
bundle install
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Running Tests
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
# Unit tests (no API key required)
|
|
24
|
+
bundle exec rspec
|
|
25
|
+
|
|
26
|
+
# Integration tests (requires real API key)
|
|
27
|
+
export MISTRAL_API_KEY=your_key && bundle exec rspec spec/integration/mistral_api_integration_spec.rb
|
|
28
|
+
|
|
29
|
+
# All tests with coverage
|
|
30
|
+
bundle exec rspec --format documentation
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Contributing Guidelines
|
|
34
|
+
|
|
35
|
+
### Reporting Issues
|
|
36
|
+
|
|
37
|
+
- Use GitHub Issues for bug reports and feature requests
|
|
38
|
+
- Include Ruby version, gem version, and relevant code snippets
|
|
39
|
+
- For API-related issues, include sanitized request/response examples
|
|
40
|
+
|
|
41
|
+
### Pull Requests
|
|
42
|
+
|
|
43
|
+
1. Fork the repository
|
|
44
|
+
2. Create a feature branch: `git checkout -b feature/your-feature`
|
|
45
|
+
3. Make your changes with tests
|
|
46
|
+
4. Ensure all tests pass: `bundle exec rspec`
|
|
47
|
+
5. Follow Ruby style conventions (we use RuboCop - no error) : `bundle exec rubocop`
|
|
48
|
+
6. Submit a pull request with a clear description
|
|
49
|
+
|
|
50
|
+
### Code Standards
|
|
51
|
+
|
|
52
|
+
- Write tests for new features and bug fixes
|
|
53
|
+
- Follow existing code patterns and architecture
|
|
54
|
+
- Update documentation for public API changes
|
|
55
|
+
- Keep commits atomic and well-described
|
|
56
|
+
|
|
57
|
+
### Testing
|
|
58
|
+
|
|
59
|
+
- Unit tests should not require API calls (use mocks/stubs)
|
|
60
|
+
- Integration tests should use VCR cassettes when possible
|
|
61
|
+
- Test edge cases and error conditions
|
|
62
|
+
- Test coverage must be at 100% (for now)
|
|
63
|
+
|
|
64
|
+
## Release Process
|
|
65
|
+
|
|
66
|
+
Releases are handled by maintainers following semantic versioning.
|
|
67
|
+
|
|
68
|
+
## Questions?
|
|
69
|
+
|
|
70
|
+
Feel free to open an issue for any questions about contributing.
|
data/LICENSE.txt
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c)
|
|
3
|
+
Copyright (c) 2024 Pierre Cherbero (peyochanchan)
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
|
@@ -9,13 +9,13 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
|
9
9
|
copies of the Software, and to permit persons to whom the Software is
|
|
10
10
|
furnished to do so, subject to the following conditions:
|
|
11
11
|
|
|
12
|
-
The above copyright notice and this permission notice shall be included in
|
|
13
|
-
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
14
|
|
|
15
15
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
16
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
17
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
18
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
19
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
-
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
CHANGED
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
Ruby gem for AI-powered translation and text summarization using Mistral AI API, with advanced Rails support.
|
|
4
4
|
|
|
5
|
+
[](https://www.ruby-lang.org/)
|
|
5
6
|
[](https://badge.fury.io/rb/mistral_translator)
|
|
6
|
-
|
|
7
|
+
[](https://rspec.info/)
|
|
8
|
+
[](https://rubocop.org/)
|
|
7
9
|
[](https://opensource.org/licenses/MIT)
|
|
8
10
|
|
|
9
11
|
## Quick Start
|
|
@@ -30,7 +32,7 @@ MistralTranslator.translate("Bonjour le monde", from: "fr", to: "en")
|
|
|
30
32
|
- **Multi-level summarization** with translation
|
|
31
33
|
- **Robust error handling** with automatic retry and fallback
|
|
32
34
|
- **Complete monitoring** with metrics and callbacks
|
|
33
|
-
- **
|
|
35
|
+
- **Thread-safe & concurrent** with connection pooling ([docs](https://peyochanchan.github.io/mistral_translator/#/advanced-usage/concurrent-async))
|
|
34
36
|
- **Built-in rate limiting** and security
|
|
35
37
|
|
|
36
38
|
## Supported Languages
|
|
@@ -222,11 +224,11 @@ The complete documentation includes:
|
|
|
222
224
|
|
|
223
225
|
### Quick Links
|
|
224
226
|
|
|
225
|
-
- [Installation & Setup](https://peyochanchan.github.io/mistral_translator
|
|
226
|
-
- [Rails Integration Guide](https://peyochanchan.github.io/mistral_translator
|
|
227
|
-
- [API Methods Reference](https://peyochanchan.github.io/mistral_translator
|
|
228
|
-
- [Error Handling Guide](https://peyochanchan.github.io/mistral_translator
|
|
229
|
-
- [Monitoring Setup](https://peyochanchan.github.io/mistral_translator
|
|
227
|
+
- [Installation & Setup](https://peyochanchan.github.io/mistral_translator/#/installation)
|
|
228
|
+
- [Rails Integration Guide](https://peyochanchan.github.io/mistral_translator/#/rails-integration/setup)
|
|
229
|
+
- [API Methods Reference](https://peyochanchan.github.io/mistral_translator/#/api-reference/methods)
|
|
230
|
+
- [Error Handling Guide](https://peyochanchan.github.io/mistral_translator/#/api-reference/errors)
|
|
231
|
+
- [Monitoring Setup](https://peyochanchan.github.io/mistral_translator/#/advanced-usage/monitoring)
|
|
230
232
|
|
|
231
233
|
## Requirements
|
|
232
234
|
|
|
@@ -237,14 +239,28 @@ The complete documentation includes:
|
|
|
237
239
|
## Testing
|
|
238
240
|
|
|
239
241
|
```bash
|
|
240
|
-
#
|
|
241
|
-
bundle
|
|
242
|
+
# Install dependencies
|
|
243
|
+
bundle install
|
|
242
244
|
|
|
243
|
-
# Run
|
|
244
|
-
|
|
245
|
+
# Run all unit tests (no API key required)
|
|
246
|
+
bundle exec rspec spec/mistral_translator/
|
|
245
247
|
|
|
246
|
-
#
|
|
247
|
-
|
|
248
|
+
# Run all tests with documentation format
|
|
249
|
+
bundle exec rspec --format documentation
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### Integration Tests
|
|
253
|
+
|
|
254
|
+
Integration tests require a real Mistral API key. **Never commit API keys to version control.**
|
|
255
|
+
|
|
256
|
+
```bash
|
|
257
|
+
# Set API key via environment variable (recommended)
|
|
258
|
+
export MISTRAL_API_KEY="your_api_key_here"
|
|
259
|
+
|
|
260
|
+
# Run integration tests
|
|
261
|
+
bundle exec rspec spec/integration/
|
|
262
|
+
|
|
263
|
+
**Security Note:** Use a dedicated test API key with limited quotas for integration testing.
|
|
248
264
|
```
|
|
249
265
|
|
|
250
266
|
## Contributing
|
|
@@ -259,15 +275,24 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for detailed guidelines.
|
|
|
259
275
|
|
|
260
276
|
## License
|
|
261
277
|
|
|
262
|
-
MIT License. See [LICENSE](LICENSE) for details.
|
|
278
|
+
MIT License. See [LICENSE](LICENSE.txt) for details.
|
|
263
279
|
|
|
264
280
|
## Support
|
|
265
281
|
|
|
266
282
|
- 📖 [Documentation](https://peyochanchan.github.io/mistral_translator/)
|
|
267
283
|
- 🐛 [Issues](../../issues)
|
|
268
|
-
- 💬 [Discussions](../../discussions)
|
|
269
284
|
- 📧 Support: Create an issue for help
|
|
270
285
|
|
|
286
|
+
### ☕ Support the Project
|
|
287
|
+
|
|
288
|
+
If this gem helps you, consider supporting its development:
|
|
289
|
+
|
|
290
|
+
[](https://buymeacoffee.com/peyochanchan)
|
|
291
|
+
|
|
271
292
|
---
|
|
272
293
|
|
|
273
294
|
Built with Ruby ❤️ by [@peyochanchan](https://github.com/peyochanchan)
|
|
295
|
+
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
```
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
# Concurrent & Asynchronous Processing
|
|
2
|
+
|
|
3
|
+
MistralTranslator is thread-safe and optimized for concurrent usage with protected metrics, connection pooling, and built-in rate limiting.
|
|
4
|
+
|
|
5
|
+
## Thread Safety
|
|
6
|
+
|
|
7
|
+
**Protected components:**
|
|
8
|
+
- Metrics tracking with Mutex
|
|
9
|
+
- Logger cache with concurrent access control
|
|
10
|
+
- Rate limiter with thread synchronization
|
|
11
|
+
- HTTP connection pooling (Net::HTTP::Persistent)
|
|
12
|
+
|
|
13
|
+
## Ruby Threads
|
|
14
|
+
|
|
15
|
+
### Basic Usage
|
|
16
|
+
|
|
17
|
+
```ruby
|
|
18
|
+
languages = ['fr', 'es', 'de', 'it']
|
|
19
|
+
threads = languages.map do |lang|
|
|
20
|
+
Thread.new do
|
|
21
|
+
MistralTranslator.translate("Hello", from: 'en', to: lang)
|
|
22
|
+
rescue MistralTranslator::Error => e
|
|
23
|
+
{ error: e.message }
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
results = threads.map(&:value)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Thread Pool Pattern
|
|
31
|
+
|
|
32
|
+
```ruby
|
|
33
|
+
# Safe: Limited threads
|
|
34
|
+
texts.each_slice(5) do |batch|
|
|
35
|
+
threads = batch.map { |t| Thread.new { translate(t) } }
|
|
36
|
+
threads.each(&:join)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Dangerous: Unlimited threads can exhaust memory
|
|
40
|
+
texts.map { |t| Thread.new { translate(t) } } # Avoid this
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Concurrent Ruby (Recommended)
|
|
44
|
+
|
|
45
|
+
### Installation
|
|
46
|
+
|
|
47
|
+
```ruby
|
|
48
|
+
# Gemfile
|
|
49
|
+
gem 'concurrent-ruby', '~> 1.2'
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Fixed Thread Pool
|
|
53
|
+
|
|
54
|
+
```ruby
|
|
55
|
+
require 'concurrent'
|
|
56
|
+
|
|
57
|
+
pool = Concurrent::FixedThreadPool.new(5)
|
|
58
|
+
|
|
59
|
+
futures = languages.map do |lang|
|
|
60
|
+
Concurrent::Future.execute(executor: pool) do
|
|
61
|
+
MistralTranslator.translate(text, from: 'en', to: lang)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
results = futures.map(&:value)
|
|
66
|
+
|
|
67
|
+
pool.shutdown
|
|
68
|
+
pool.wait_for_termination
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Promise Chains
|
|
72
|
+
|
|
73
|
+
```ruby
|
|
74
|
+
promise = Concurrent::Promise.execute do
|
|
75
|
+
MistralTranslator.translate_auto(text, to: 'en')
|
|
76
|
+
end.then do |english|
|
|
77
|
+
target_langs.map do |lang|
|
|
78
|
+
MistralTranslator.translate(english, from: 'en', to: lang)
|
|
79
|
+
end
|
|
80
|
+
end.rescue do |error|
|
|
81
|
+
Rails.logger.error "Pipeline failed: #{error}"
|
|
82
|
+
[]
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
results = promise.value
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Background Jobs
|
|
89
|
+
|
|
90
|
+
### SolidQueue (Rails 8+, Recommended)
|
|
91
|
+
|
|
92
|
+
SolidQueue is the default ActiveJob backend in Rails 8, database-backed and Redis-free.
|
|
93
|
+
|
|
94
|
+
```ruby
|
|
95
|
+
# config/database.yml - SolidQueue uses your existing database
|
|
96
|
+
production:
|
|
97
|
+
primary:
|
|
98
|
+
<<: *default
|
|
99
|
+
queue: # Separate DB for jobs (optional)
|
|
100
|
+
<<: *default
|
|
101
|
+
database: app_queue
|
|
102
|
+
migrations_paths: db/queue_migrate
|
|
103
|
+
|
|
104
|
+
# app/jobs/translation_job.rb
|
|
105
|
+
class TranslationJob < ApplicationJob
|
|
106
|
+
queue_as :translations
|
|
107
|
+
|
|
108
|
+
retry_on MistralTranslator::RateLimitError, wait: :polynomially_longer
|
|
109
|
+
retry_on MistralTranslator::ApiError, wait: 5.seconds, attempts: 3
|
|
110
|
+
|
|
111
|
+
def perform(text, from_locale, to_locale)
|
|
112
|
+
MistralTranslator.translate(text, from: from_locale, to: to_locale)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Usage
|
|
117
|
+
languages.each { |lang| TranslationJob.perform_later(text, 'en', lang) }
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
**Configuration:**
|
|
121
|
+
```ruby
|
|
122
|
+
# config/recurring.yml - Schedule periodic translations
|
|
123
|
+
production:
|
|
124
|
+
sync_translations:
|
|
125
|
+
class: TranslationSyncJob
|
|
126
|
+
schedule: every day at 3am
|
|
127
|
+
queue: translations
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Sidekiq / Other Backends
|
|
131
|
+
|
|
132
|
+
Compatible with any ActiveJob backend:
|
|
133
|
+
|
|
134
|
+
```ruby
|
|
135
|
+
class TranslationJob < ApplicationJob
|
|
136
|
+
queue_as :translations
|
|
137
|
+
retry_on MistralTranslator::RateLimitError, wait: :exponentially_longer
|
|
138
|
+
|
|
139
|
+
def perform(text, from, to)
|
|
140
|
+
MistralTranslator.translate(text, from: from, to: to)
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Performance Best Practices
|
|
146
|
+
|
|
147
|
+
### Configuration for Concurrent Use
|
|
148
|
+
|
|
149
|
+
```ruby
|
|
150
|
+
MistralTranslator.configure do |config|
|
|
151
|
+
config.enable_metrics = true
|
|
152
|
+
config.retry_delays = [1, 2, 4, 8] # Shorter for concurrent use
|
|
153
|
+
|
|
154
|
+
config.on_rate_limit = ->(from, to, wait, attempt, ts) {
|
|
155
|
+
Rails.logger.warn "Rate limit: #{wait}s wait"
|
|
156
|
+
}
|
|
157
|
+
end
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Recommendations
|
|
161
|
+
|
|
162
|
+
**Do:**
|
|
163
|
+
- Use fixed thread pools (5-10 threads)
|
|
164
|
+
- Enable metrics for monitoring
|
|
165
|
+
- Use background jobs for non-critical tasks
|
|
166
|
+
- Batch similar requests
|
|
167
|
+
|
|
168
|
+
**Avoid:**
|
|
169
|
+
- Unlimited thread creation
|
|
170
|
+
- Blocking user requests with translations
|
|
171
|
+
- Sharing API keys across environments
|
|
172
|
+
|
|
173
|
+
## Rate Limiting
|
|
174
|
+
|
|
175
|
+
The built-in rate limiter (50 requests/60s) is thread-safe but per-process.
|
|
176
|
+
|
|
177
|
+
**Multiple processes:** Each has its own limiter. With 4 Puma workers = 200 requests/min total.
|
|
178
|
+
|
|
179
|
+
### Custom Rate Limiter
|
|
180
|
+
|
|
181
|
+
```ruby
|
|
182
|
+
class RedisRateLimiter
|
|
183
|
+
def wait_and_record!
|
|
184
|
+
key = "mistral:#{Time.now.to_i / 60}"
|
|
185
|
+
count = REDIS.incr(key)
|
|
186
|
+
REDIS.expire(key, 60) if count == 1
|
|
187
|
+
|
|
188
|
+
sleep(60 - Time.now.to_i % 60) if count > 50
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
client = MistralTranslator::Client.new(
|
|
193
|
+
rate_limiter: RedisRateLimiter.new
|
|
194
|
+
)
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## Monitoring
|
|
198
|
+
|
|
199
|
+
```ruby
|
|
200
|
+
MistralTranslator.configure do |config|
|
|
201
|
+
config.enable_metrics = true
|
|
202
|
+
|
|
203
|
+
config.on_translation_complete = ->(from, to, orig, trans, duration) {
|
|
204
|
+
Rails.logger.info "[#{Thread.current.object_id}] #{from}→#{to}: #{duration.round(2)}s"
|
|
205
|
+
}
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# Thread-safe metrics
|
|
209
|
+
metrics = MistralTranslator.metrics
|
|
210
|
+
puts "Total: #{metrics[:total_translations]}"
|
|
211
|
+
puts "Avg time: #{metrics[:average_translation_time]}s"
|
|
212
|
+
puts "Error rate: #{metrics[:error_rate]}%"
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## Example: High-Performance Service
|
|
216
|
+
|
|
217
|
+
```ruby
|
|
218
|
+
class TranslationService
|
|
219
|
+
def initialize(max_threads: 5)
|
|
220
|
+
@pool = Concurrent::FixedThreadPool.new(max_threads)
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def translate_all(texts, from:, to_languages:)
|
|
224
|
+
futures = texts.flat_map do |text|
|
|
225
|
+
to_languages.map do |lang|
|
|
226
|
+
Concurrent::Future.execute(executor: @pool) do
|
|
227
|
+
MistralTranslator.translate(text, from: from, to: lang)
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
futures.map(&:value)
|
|
232
|
+
ensure
|
|
233
|
+
@pool.shutdown
|
|
234
|
+
@pool.wait_for_termination(30)
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
## Troubleshooting
|
|
240
|
+
|
|
241
|
+
**"Too many open files"**
|
|
242
|
+
```bash
|
|
243
|
+
ulimit -n 4096
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
**Rate limits despite low volume**
|
|
247
|
+
Check if multiple processes share the API key. Consider Redis-based rate limiting.
|
|
248
|
+
|
|
249
|
+
**Memory growth**
|
|
250
|
+
```ruby
|
|
251
|
+
# Join threads periodically
|
|
252
|
+
threads = []
|
|
253
|
+
texts.each do |text|
|
|
254
|
+
threads << Thread.new { translate(text) }
|
|
255
|
+
|
|
256
|
+
if threads.size >= 10
|
|
257
|
+
threads.each(&:join)
|
|
258
|
+
threads.clear
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
## Summary
|
|
264
|
+
|
|
265
|
+
- Thread-safe with protected metrics and configuration
|
|
266
|
+
- Connection pooling automatic with net-http-persistent
|
|
267
|
+
- Use fixed-size thread pools (5-10 recommended)
|
|
268
|
+
- Built-in rate limiting works across threads
|
|
269
|
+
- Background jobs recommended for non-critical tasks
|
|
270
|
+
- Separate API keys per environment
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "net/http/persistent"
|
|
4
|
+
|
|
3
5
|
module MistralTranslator
|
|
4
6
|
module ClientHelpers
|
|
5
7
|
# Helper pour la gestion des requêtes HTTP
|
|
@@ -9,14 +11,10 @@ module MistralTranslator
|
|
|
9
11
|
|
|
10
12
|
request_body = build_request_body(prompt, max_tokens, temperature)
|
|
11
13
|
|
|
12
|
-
http = Net::HTTP.new(uri.host, uri.port)
|
|
13
|
-
http.use_ssl = (uri.scheme == "https")
|
|
14
|
-
http.read_timeout = 60 # 60 secondes de timeout
|
|
15
|
-
|
|
16
14
|
request = Net::HTTP::Post.new(uri.path, headers)
|
|
17
15
|
request.body = request_body.to_json
|
|
18
16
|
|
|
19
|
-
response =
|
|
17
|
+
response = http_pool.request(uri, request)
|
|
20
18
|
log_request_response(request_body, response)
|
|
21
19
|
|
|
22
20
|
response
|
|
@@ -26,6 +24,38 @@ module MistralTranslator
|
|
|
26
24
|
raise ApiError, "HTTP error: #{e.message}"
|
|
27
25
|
end
|
|
28
26
|
|
|
27
|
+
def http_pool
|
|
28
|
+
@http_pool ||= begin
|
|
29
|
+
pool = Net::HTTP::Persistent.new(name: "mistral_translator")
|
|
30
|
+
pool.read_timeout = 60
|
|
31
|
+
pool.idle_timeout = 30
|
|
32
|
+
pool.open_timeout = MistralTranslator.configuration.ssl_timeout
|
|
33
|
+
pool.max_requests = 100
|
|
34
|
+
|
|
35
|
+
# SSL/TLS configuration
|
|
36
|
+
configure_ssl(pool)
|
|
37
|
+
|
|
38
|
+
pool
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def configure_ssl(pool)
|
|
43
|
+
config = MistralTranslator.configuration
|
|
44
|
+
|
|
45
|
+
# SSL verify mode
|
|
46
|
+
pool.verify_mode = case config.ssl_verify_mode
|
|
47
|
+
when :peer then OpenSSL::SSL::VERIFY_PEER
|
|
48
|
+
when :none then OpenSSL::SSL::VERIFY_NONE
|
|
49
|
+
else config.ssl_verify_mode
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Custom CA certificate file
|
|
53
|
+
pool.ca_file = config.ssl_ca_file if config.ssl_ca_file
|
|
54
|
+
|
|
55
|
+
# Custom CA certificates directory
|
|
56
|
+
pool.ca_path = config.ssl_ca_path if config.ssl_ca_path
|
|
57
|
+
end
|
|
58
|
+
|
|
29
59
|
def build_request_body(prompt, max_tokens, temperature)
|
|
30
60
|
body = {
|
|
31
61
|
model: @model,
|
|
@@ -4,7 +4,8 @@ module MistralTranslator
|
|
|
4
4
|
class Configuration
|
|
5
5
|
attr_accessor :api_key, :model, :default_max_tokens, :default_temperature, :retry_delays,
|
|
6
6
|
:on_translation_start, :on_translation_complete, :on_translation_error,
|
|
7
|
-
:on_rate_limit, :on_batch_complete, :enable_metrics
|
|
7
|
+
:on_rate_limit, :on_batch_complete, :enable_metrics,
|
|
8
|
+
:ssl_verify_mode, :ssl_ca_file, :ssl_ca_path, :ssl_timeout
|
|
8
9
|
attr_reader :api_url
|
|
9
10
|
|
|
10
11
|
def initialize
|
|
@@ -23,7 +24,14 @@ module MistralTranslator
|
|
|
23
24
|
@on_batch_complete = nil
|
|
24
25
|
@enable_metrics = false
|
|
25
26
|
|
|
26
|
-
#
|
|
27
|
+
# SSL/TLS configuration
|
|
28
|
+
@ssl_verify_mode = :peer # :peer, :none, or OpenSSL constant
|
|
29
|
+
@ssl_ca_file = nil # Path to CA certificate file
|
|
30
|
+
@ssl_ca_path = nil # Path to CA certificates directory
|
|
31
|
+
@ssl_timeout = 60 # SSL handshake timeout in seconds
|
|
32
|
+
|
|
33
|
+
# Métriques intégrées (thread-safe avec mutex)
|
|
34
|
+
@metrics_mutex = Mutex.new
|
|
27
35
|
@metrics = {
|
|
28
36
|
total_translations: 0,
|
|
29
37
|
total_characters: 0,
|
|
@@ -54,9 +62,11 @@ module MistralTranslator
|
|
|
54
62
|
|
|
55
63
|
return unless @enable_metrics
|
|
56
64
|
|
|
57
|
-
@
|
|
58
|
-
|
|
59
|
-
|
|
65
|
+
@metrics_mutex.synchronize do
|
|
66
|
+
@metrics[:total_translations] += 1
|
|
67
|
+
@metrics[:total_characters] += text_length
|
|
68
|
+
@metrics[:translations_by_language]["#{from_locale}->#{to_locale}"] += 1
|
|
69
|
+
end
|
|
60
70
|
end
|
|
61
71
|
|
|
62
72
|
def trigger_translation_complete(from_locale, to_locale, original_length, translated_length, duration)
|
|
@@ -64,7 +74,9 @@ module MistralTranslator
|
|
|
64
74
|
|
|
65
75
|
return unless @enable_metrics
|
|
66
76
|
|
|
67
|
-
@
|
|
77
|
+
@metrics_mutex.synchronize do
|
|
78
|
+
@metrics[:total_duration] += duration
|
|
79
|
+
end
|
|
68
80
|
end
|
|
69
81
|
|
|
70
82
|
def trigger_translation_error(from_locale, to_locale, error, attempt)
|
|
@@ -72,7 +84,9 @@ module MistralTranslator
|
|
|
72
84
|
|
|
73
85
|
return unless @enable_metrics
|
|
74
86
|
|
|
75
|
-
@
|
|
87
|
+
@metrics_mutex.synchronize do
|
|
88
|
+
@metrics[:errors_count] += 1
|
|
89
|
+
end
|
|
76
90
|
end
|
|
77
91
|
|
|
78
92
|
def trigger_rate_limit(from_locale, to_locale, wait_time, attempt)
|
|
@@ -80,7 +94,9 @@ module MistralTranslator
|
|
|
80
94
|
|
|
81
95
|
return unless @enable_metrics
|
|
82
96
|
|
|
83
|
-
@
|
|
97
|
+
@metrics_mutex.synchronize do
|
|
98
|
+
@metrics[:rate_limits_hit] += 1
|
|
99
|
+
end
|
|
84
100
|
end
|
|
85
101
|
|
|
86
102
|
def trigger_batch_complete(batch_size, total_duration, success_count, error_count)
|
|
@@ -91,35 +107,39 @@ module MistralTranslator
|
|
|
91
107
|
def metrics
|
|
92
108
|
return {} unless @enable_metrics
|
|
93
109
|
|
|
94
|
-
@
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
@metrics[:
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
110
|
+
@metrics_mutex.synchronize do
|
|
111
|
+
@metrics.merge({
|
|
112
|
+
average_translation_time: if @metrics[:total_translations].positive?
|
|
113
|
+
(@metrics[:total_duration] / @metrics[:total_translations]).round(3)
|
|
114
|
+
else
|
|
115
|
+
0
|
|
116
|
+
end,
|
|
117
|
+
average_characters_per_translation: if @metrics[:total_translations].positive?
|
|
118
|
+
(@metrics[:total_characters] /
|
|
119
|
+
@metrics[:total_translations]).round(0)
|
|
120
|
+
else
|
|
121
|
+
0
|
|
122
|
+
end,
|
|
123
|
+
error_rate: if @metrics[:total_translations].positive?
|
|
124
|
+
((@metrics[:errors_count].to_f / @metrics[:total_translations]) * 100).round(2)
|
|
125
|
+
else
|
|
126
|
+
0
|
|
127
|
+
end
|
|
128
|
+
})
|
|
129
|
+
end
|
|
112
130
|
end
|
|
113
131
|
|
|
114
132
|
def reset_metrics!
|
|
115
|
-
@
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
133
|
+
@metrics_mutex.synchronize do
|
|
134
|
+
@metrics = {
|
|
135
|
+
total_translations: 0,
|
|
136
|
+
total_characters: 0,
|
|
137
|
+
total_duration: 0.0,
|
|
138
|
+
rate_limits_hit: 0,
|
|
139
|
+
errors_count: 0,
|
|
140
|
+
translations_by_language: Hash.new(0)
|
|
141
|
+
}
|
|
142
|
+
end
|
|
123
143
|
end
|
|
124
144
|
|
|
125
145
|
# Configuration helpers pour les callbacks les plus communs
|
|
@@ -17,12 +17,15 @@ module MistralTranslator
|
|
|
17
17
|
|
|
18
18
|
# Log seulement si pas déjà loggé récemment (évite la spam)
|
|
19
19
|
def warn_once(message, key: nil, sensitive: false, ttl: 300)
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
cache_mutex.synchronize do
|
|
21
|
+
@warn_cache ||= {}
|
|
22
|
+
cache_key = key || message
|
|
22
23
|
|
|
23
|
-
|
|
24
|
+
return unless should_log_warning?(cache_key, ttl)
|
|
25
|
+
|
|
26
|
+
@warn_cache[cache_key] = Time.now
|
|
27
|
+
end
|
|
24
28
|
|
|
25
|
-
@warn_cache[cache_key] = Time.now
|
|
26
29
|
log(:warn, message, sensitive)
|
|
27
30
|
end
|
|
28
31
|
|
|
@@ -74,6 +77,10 @@ module MistralTranslator
|
|
|
74
77
|
|
|
75
78
|
Time.now - @warn_cache[key] > ttl
|
|
76
79
|
end
|
|
80
|
+
|
|
81
|
+
def cache_mutex
|
|
82
|
+
@cache_mutex ||= Mutex.new
|
|
83
|
+
end
|
|
77
84
|
end
|
|
78
85
|
end
|
|
79
86
|
end
|
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.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Peyochanchan
|
|
@@ -23,6 +23,20 @@ dependencies:
|
|
|
23
23
|
- - "~>"
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
25
|
version: '0.4'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: net-http-persistent
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '4.0'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '4.0'
|
|
26
40
|
- !ruby/object:Gem::Dependency
|
|
27
41
|
name: rake
|
|
28
42
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -108,6 +122,7 @@ files:
|
|
|
108
122
|
- ".ruby-version"
|
|
109
123
|
- CHANGELOG.md
|
|
110
124
|
- CODE_OF_CONDUCT.md
|
|
125
|
+
- CONTRIBUTING.md
|
|
111
126
|
- LICENSE.txt
|
|
112
127
|
- README.md
|
|
113
128
|
- README_TESTING.md
|
|
@@ -117,6 +132,7 @@ files:
|
|
|
117
132
|
- docs/404.html
|
|
118
133
|
- docs/README.md
|
|
119
134
|
- docs/advanced-usage/batch-processing.md
|
|
135
|
+
- docs/advanced-usage/concurrent-async.md
|
|
120
136
|
- docs/advanced-usage/error-handling.md
|
|
121
137
|
- docs/advanced-usage/monitoring.md
|
|
122
138
|
- docs/advanced-usage/summarization.md
|