translation_api 0.1.2 → 1.0.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/.rubocop.yml +3 -3
- data/CHANGELOG.md +10 -1
- data/README.md +15 -4
- data/example.rb +8 -9
- data/lib/translation_api/config.rb +23 -0
- data/lib/translation_api/provider/deepl.rb +71 -0
- data/lib/translation_api/provider/openai/cost.rb +85 -0
- data/lib/translation_api/provider/openai/log.rb +99 -0
- data/lib/translation_api/provider/openai.rb +97 -0
- data/lib/translation_api/version.rb +2 -2
- data/lib/translation_api.rb +62 -2
- metadata +21 -22
- data/lib/translation_api/calculator.rb +0 -66
- data/lib/translation_api/deepl.rb +0 -62
- data/lib/translation_api/mediator.rb +0 -64
- data/lib/translation_api/openai.rb +0 -102
- data/lib/translation_api/writer.rb +0 -127
- data/sig/translation_api.rbs +0 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c9c1c9798882d3591828278748d1aa763227e85a2c37be0d7583859c78fc0013
|
|
4
|
+
data.tar.gz: ac5378596044a829e4f653e258ade5074e023b2ca0d3ccb1a55b603c8e707c5d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 94379f1ffbd3741c72fc87289b7887b434eb65e3bb8fb55438a98fdede95e4220f473aae4f56aeed5a126099d95fdc7abde0d2b9fd05f53d8ec7286bb03af54a
|
|
7
|
+
data.tar.gz: 17d351d60dc25125577f5a9c8a43490e86a91a8d77420d795131c17de6c7b1a33e3f9050c5d836272fd87ec825846a25379fd44d42f1725580bde08b4fe5c06a
|
data/.rubocop.yml
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
AllCops:
|
|
2
|
-
TargetRubyVersion: 3.
|
|
2
|
+
TargetRubyVersion: 3.4
|
|
3
3
|
NewCops: enable
|
|
4
4
|
|
|
5
5
|
Style/StringLiterals:
|
|
@@ -16,7 +16,7 @@ Style/ParallelAssignment:
|
|
|
16
16
|
|
|
17
17
|
Metrics/MethodLength:
|
|
18
18
|
CountComments: true
|
|
19
|
-
Max:
|
|
19
|
+
Max: 30
|
|
20
20
|
|
|
21
21
|
Lint/ScriptPermission:
|
|
22
22
|
Enabled: false
|
|
@@ -29,4 +29,4 @@ RSpec/ExampleLength:
|
|
|
29
29
|
|
|
30
30
|
require:
|
|
31
31
|
- rubocop-rake
|
|
32
|
-
- rubocop-rspec
|
|
32
|
+
- rubocop-rspec
|
data/CHANGELOG.md
CHANGED
|
@@ -29,4 +29,13 @@
|
|
|
29
29
|
## [0.1.2] - 2025-03-31
|
|
30
30
|
|
|
31
31
|
- deeplを使用可能に
|
|
32
|
-
- gemspecにdeepl-rbを書き忘れていたのを修正
|
|
32
|
+
- gemspecにdeepl-rbを書き忘れていたのを修正
|
|
33
|
+
|
|
34
|
+
## [0.2.0] - 2025-07-26
|
|
35
|
+
|
|
36
|
+
- いろいろバージョン更新
|
|
37
|
+
- OpenAIのモデル追加
|
|
38
|
+
|
|
39
|
+
## [1.0.0] - 2025-11-10
|
|
40
|
+
- 全体的な設計を刷新
|
|
41
|
+
- よりシンプルなインターフェースに変更
|
data/README.md
CHANGED
|
@@ -7,14 +7,14 @@ Requires api key.
|
|
|
7
7
|
|
|
8
8
|
1. `touch .env`
|
|
9
9
|
2. Add `OPENAI_API_KEY=YOUR_API_KEY`
|
|
10
|
-
3. Optional: `ENV["OPENAI_MODEL"]`
|
|
11
|
-
4. `TranslationAPI
|
|
10
|
+
3. Optional: `ENV["OPENAI_MODEL"]`
|
|
11
|
+
4. `TranslationAPI.translate("text")`
|
|
12
12
|
|
|
13
|
-
###
|
|
13
|
+
### Configuration Options
|
|
14
14
|
|
|
15
15
|
* output_logs (default: true)
|
|
16
16
|
* language (default: "japanese")
|
|
17
|
-
*
|
|
17
|
+
* provider (default: :openai)
|
|
18
18
|
* except_words (default: [])
|
|
19
19
|
|
|
20
20
|
### Output
|
|
@@ -26,3 +26,14 @@ Requires api key.
|
|
|
26
26
|
## Example
|
|
27
27
|
|
|
28
28
|
Exec `ruby example.rb "text"`
|
|
29
|
+
|
|
30
|
+
```ruby
|
|
31
|
+
TranslationAPI.configure do |config|
|
|
32
|
+
config.language = "english"
|
|
33
|
+
config.provider = :deepl
|
|
34
|
+
config.output_logs = false
|
|
35
|
+
config.except_words = %w[hoge fuga]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
TranslationAPI.translate("text")
|
|
39
|
+
```
|
data/example.rb
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "lib/translation_api
|
|
3
|
+
require_relative "lib/translation_api"
|
|
4
4
|
|
|
5
5
|
if ARGV.empty?
|
|
6
6
|
puts "引数が必要です: ruby example.rb \"text\""
|
|
@@ -8,12 +8,11 @@ if ARGV.empty?
|
|
|
8
8
|
end
|
|
9
9
|
|
|
10
10
|
text = ARGV.join(" ")
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
translated_text = translator.translate(text)
|
|
11
|
+
TranslationAPI.configure do |config|
|
|
12
|
+
config.language = "english"
|
|
13
|
+
config.provider = :deepl
|
|
14
|
+
config.except_words = %w[hoge fuga]
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
translated_text = TranslationAPI.translate(text)
|
|
19
18
|
p translated_text
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "singleton"
|
|
4
|
+
|
|
5
|
+
class TranslationAPI
|
|
6
|
+
class Config
|
|
7
|
+
include Singleton
|
|
8
|
+
|
|
9
|
+
attr_accessor :language, :provider, :output_logs, :except_words, :deepl_pro
|
|
10
|
+
|
|
11
|
+
def self.configure(&block)
|
|
12
|
+
block.call(instance)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def initialize
|
|
16
|
+
@language = "japanese"
|
|
17
|
+
@provider = :openai
|
|
18
|
+
@output_logs = true
|
|
19
|
+
@except_words = []
|
|
20
|
+
@deepl_pro = false
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "dotenv"
|
|
4
|
+
require "deepl"
|
|
5
|
+
|
|
6
|
+
class TranslationAPI
|
|
7
|
+
module Provider
|
|
8
|
+
class DeepL
|
|
9
|
+
SYSTEM_PROMPT_BASE = <<~TEXT
|
|
10
|
+
Keep symbols
|
|
11
|
+
TEXT
|
|
12
|
+
|
|
13
|
+
API_KEY_ERROR_MESSAGE = "API key is not found."
|
|
14
|
+
|
|
15
|
+
LANGUAGE_UNSUPPORTED_MESSAGE = "This language is unsupported by DeepL."
|
|
16
|
+
|
|
17
|
+
def initialize(pro:, except_words: [], language: "japanese")
|
|
18
|
+
@pro = pro
|
|
19
|
+
@language = language
|
|
20
|
+
|
|
21
|
+
setup_deepl_config!
|
|
22
|
+
validate_supported_language!
|
|
23
|
+
|
|
24
|
+
@system_content = SYSTEM_PROMPT_BASE + except_option_text(except_words)
|
|
25
|
+
@language = supported_languages[language.to_sym]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def translate(text)
|
|
29
|
+
return text if text.strip.empty?
|
|
30
|
+
|
|
31
|
+
::DeepL.translate(text, nil, @language, context: @system_content).text
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def setup_deepl_config!
|
|
37
|
+
validate_api_key!
|
|
38
|
+
|
|
39
|
+
::DeepL.configure do |config|
|
|
40
|
+
config.auth_key = ENV["DEEPL_API_KEY"] || ENV["DEEPL_AUTH_KEY"]
|
|
41
|
+
config.host = @pro ? "https://api.deepl.com" : "https://api-free.deepl.com"
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def validate_api_key!
|
|
46
|
+
raise API_KEY_ERROR_MESSAGE unless ENV["DEEPL_API_KEY"] || ENV["DEEPL_AUTH_KEY"]
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def validate_supported_language!
|
|
50
|
+
raise LANGUAGE_UNSUPPORTED_MESSAGE unless supported_language?
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def supported_languages
|
|
54
|
+
@supported_languages ||=
|
|
55
|
+
::DeepL.languages.to_h { [it.name.downcase.to_sym, it.code] }
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def supported_language?
|
|
59
|
+
supported_languages.key?(@language.to_sym)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def except_option_text(except_words)
|
|
63
|
+
return "" if except_words.empty?
|
|
64
|
+
|
|
65
|
+
<<~TEXT
|
|
66
|
+
Words listed next are not translated: [#{except_words.join(", ")}]
|
|
67
|
+
TEXT
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class TranslationAPI
|
|
4
|
+
module Provider
|
|
5
|
+
class OpenAI
|
|
6
|
+
class Cost
|
|
7
|
+
BASE_MODEL_NAME = "gpt-5"
|
|
8
|
+
ONE_MILLION = 1_000_000
|
|
9
|
+
BASE_MODEL_COST = 1.25 / ONE_MILLION
|
|
10
|
+
|
|
11
|
+
def initialize(provider)
|
|
12
|
+
@provider = provider
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def input_cost(used_tokens)
|
|
16
|
+
calculate_cost(used_tokens, :input)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def output_cost(used_tokens)
|
|
20
|
+
calculate_cost(used_tokens, :output)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def calculate_cost(used_tokens, type)
|
|
26
|
+
used_tokens * token_rates[@provider.using_model][type]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def token_rates
|
|
30
|
+
normal_models = base_model.merge(mini_model).merge(nano_model)
|
|
31
|
+
normal_models.merge(other_models)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def base_model
|
|
35
|
+
{
|
|
36
|
+
BASE_MODEL_NAME => {
|
|
37
|
+
input: BASE_MODEL_COST,
|
|
38
|
+
output: BASE_MODEL_COST * normal_io_ratio[:output]
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def mini_model
|
|
44
|
+
{
|
|
45
|
+
"#{BASE_MODEL_NAME}-mini" => {
|
|
46
|
+
input: BASE_MODEL_COST / normal_cost_diff_ratio,
|
|
47
|
+
output: (BASE_MODEL_COST * normal_io_ratio[:output]) / normal_cost_diff_ratio
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def nano_model
|
|
53
|
+
mini_model_cost = mini_model.values[0][:input]
|
|
54
|
+
|
|
55
|
+
{
|
|
56
|
+
"#{BASE_MODEL_NAME}-nano" => {
|
|
57
|
+
input: mini_model_cost / normal_cost_diff_ratio,
|
|
58
|
+
output: (mini_model_cost * normal_io_ratio[:output]) / normal_cost_diff_ratio
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def other_models
|
|
64
|
+
{
|
|
65
|
+
"#{BASE_MODEL_NAME}-chat-latest" => {
|
|
66
|
+
input: 1.25 / ONE_MILLION,
|
|
67
|
+
output: (1.25 * normal_io_ratio[:output]) / ONE_MILLION
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def normal_io_ratio
|
|
73
|
+
{
|
|
74
|
+
input: 1.0,
|
|
75
|
+
output: 8.0
|
|
76
|
+
}
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def normal_cost_diff_ratio
|
|
80
|
+
5.0
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require_relative "cost"
|
|
5
|
+
|
|
6
|
+
class TranslationAPI
|
|
7
|
+
module Provider
|
|
8
|
+
class OpenAI
|
|
9
|
+
class Log
|
|
10
|
+
def initialize(provider)
|
|
11
|
+
@provider = provider
|
|
12
|
+
@cost = Cost.new(@provider)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def write
|
|
16
|
+
write_translated_text
|
|
17
|
+
write_used_tokens
|
|
18
|
+
write_total_cost
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def write_translated_text
|
|
24
|
+
log_file_path = text_path("translated_text.txt")
|
|
25
|
+
|
|
26
|
+
File.open(log_file_path, "a") do |file|
|
|
27
|
+
file.puts(@provider.translated_text)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def write_used_tokens
|
|
32
|
+
log_file_path = text_path("tokens.txt")
|
|
33
|
+
|
|
34
|
+
existing_input_tokens, existing_output_tokens = read_existing_tokens
|
|
35
|
+
tokens => { input_tokens:, output_tokens: }
|
|
36
|
+
|
|
37
|
+
total_input_tokens = existing_input_tokens + input_tokens
|
|
38
|
+
total_output_tokens = existing_output_tokens + output_tokens
|
|
39
|
+
|
|
40
|
+
File.open(log_file_path, "w") do |file|
|
|
41
|
+
file.puts("input: #{total_input_tokens}")
|
|
42
|
+
file.puts("output: #{total_output_tokens}")
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def read_existing_tokens
|
|
47
|
+
log_file_path = text_path("tokens.txt")
|
|
48
|
+
input_tokens, output_tokens = 0, 0
|
|
49
|
+
|
|
50
|
+
if File.exist?(log_file_path)
|
|
51
|
+
File.readlines(log_file_path).each do |line|
|
|
52
|
+
tokens = line.split(":").last.strip.to_i
|
|
53
|
+
input_tokens = tokens if line.start_with?("input:")
|
|
54
|
+
output_tokens = tokens if line.start_with?("output:")
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
[input_tokens, output_tokens]
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def write_total_cost
|
|
62
|
+
log_file_path = text_path("cost.txt")
|
|
63
|
+
tokens => { input_tokens:, output_tokens: }
|
|
64
|
+
|
|
65
|
+
this_cost = @cost.input_cost(input_tokens) + @cost.output_cost(output_tokens)
|
|
66
|
+
total_cost = this_cost + existing_cost
|
|
67
|
+
|
|
68
|
+
File.open(log_file_path, "w") do |file|
|
|
69
|
+
file.puts(format_cost(total_cost))
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def format_cost(cost)
|
|
74
|
+
"$#{format("%.8f", cost)}"
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def existing_cost
|
|
78
|
+
log_file_path = text_path("cost.txt")
|
|
79
|
+
|
|
80
|
+
File.exist?(log_file_path) ? File.read(log_file_path).gsub("$", "").to_f : 0.0
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def tokens
|
|
84
|
+
{
|
|
85
|
+
input_tokens: @provider.dig_used_tokens(type: :input),
|
|
86
|
+
output_tokens: @provider.dig_used_tokens(type: :output)
|
|
87
|
+
}
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def text_path(under_logs_path)
|
|
91
|
+
output_dir = "translator_logs/openai"
|
|
92
|
+
FileUtils.mkdir_p(output_dir) unless File.directory?(output_dir)
|
|
93
|
+
|
|
94
|
+
File.join(output_dir, under_logs_path)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "openai"
|
|
4
|
+
require_relative "openai/log"
|
|
5
|
+
|
|
6
|
+
class TranslationAPI
|
|
7
|
+
module Provider
|
|
8
|
+
class OpenAI
|
|
9
|
+
SYSTEM_PROMPT_BASE = <<~TEXT
|
|
10
|
+
Translate only.
|
|
11
|
+
Return result only, no extra info
|
|
12
|
+
Keep symbols
|
|
13
|
+
TEXT
|
|
14
|
+
|
|
15
|
+
API_KEY_ERROR_MESSAGE = "API key is not found."
|
|
16
|
+
|
|
17
|
+
MODEL_ERROR_MESSAGE =
|
|
18
|
+
"Specified model is not supported. Please check the model name."
|
|
19
|
+
|
|
20
|
+
def initialize(output_logs:, except_words:, language:)
|
|
21
|
+
validate_api_key!
|
|
22
|
+
|
|
23
|
+
@client = init_client
|
|
24
|
+
@output_logs = output_logs
|
|
25
|
+
@system_prompt = SYSTEM_PROMPT_BASE + except_option_text(except_words)
|
|
26
|
+
@user_prompt = user_prompt_text(language)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def translate(text)
|
|
30
|
+
return text if text.strip.empty?
|
|
31
|
+
|
|
32
|
+
@response = chat_to_api(text)
|
|
33
|
+
Log.new(self).write if @output_logs
|
|
34
|
+
|
|
35
|
+
translated_text
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def translated_text
|
|
39
|
+
@response["choices"][0]["message"]["content"]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def using_model
|
|
43
|
+
ENV["OPENAI_MODEL"] || "gpt-5-mini"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def dig_used_tokens(type:)
|
|
47
|
+
case type
|
|
48
|
+
when :input
|
|
49
|
+
@response["usage"]["prompt_tokens"]
|
|
50
|
+
when :output
|
|
51
|
+
@response["usage"]["completion_tokens"]
|
|
52
|
+
else
|
|
53
|
+
raise ArgumentError, "Invalid token type: #{type}"
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
def validate_api_key!
|
|
60
|
+
raise API_KEY_ERROR_MESSAGE unless ENV["OPENAI_API_KEY"]
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def init_client
|
|
64
|
+
::OpenAI::Client.new(
|
|
65
|
+
access_token: ENV["OPENAI_API_KEY"],
|
|
66
|
+
log_errors: true
|
|
67
|
+
)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def chat_to_api(text)
|
|
71
|
+
@client.chat(
|
|
72
|
+
parameters: {
|
|
73
|
+
model: using_model,
|
|
74
|
+
messages: [
|
|
75
|
+
{ role: "system", content: @system_prompt },
|
|
76
|
+
{ role: "user", content: @user_prompt + text }
|
|
77
|
+
]
|
|
78
|
+
}
|
|
79
|
+
)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def except_option_text(except_words)
|
|
83
|
+
return "" if except_words.empty?
|
|
84
|
+
|
|
85
|
+
<<~TEXT
|
|
86
|
+
Words listed next are not translated: [#{except_words.join(", ")}]
|
|
87
|
+
TEXT
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def user_prompt_text(language)
|
|
91
|
+
<<~TEXT
|
|
92
|
+
Please translate this text to #{language}:
|
|
93
|
+
TEXT
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
data/lib/translation_api.rb
CHANGED
|
@@ -1,6 +1,66 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "dotenv"
|
|
3
4
|
require_relative "translation_api/version"
|
|
4
|
-
require_relative "translation_api/
|
|
5
|
+
require_relative "translation_api/config"
|
|
6
|
+
require_relative "translation_api/provider/openai"
|
|
7
|
+
require_relative "translation_api/provider/deepl"
|
|
5
8
|
|
|
6
|
-
|
|
9
|
+
class TranslationAPI
|
|
10
|
+
UNSUPPORTED_PROVIDER_MESSAGE = "This provider is unsupported."
|
|
11
|
+
|
|
12
|
+
Dotenv.load
|
|
13
|
+
|
|
14
|
+
def self.config
|
|
15
|
+
Config.instance
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.configure(&)
|
|
19
|
+
Config.configure(&)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.translate(text, **)
|
|
23
|
+
new(**).translate(text)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def initialize(
|
|
27
|
+
language: config.language,
|
|
28
|
+
provider: config.provider,
|
|
29
|
+
output_logs: config.output_logs,
|
|
30
|
+
except_words: config.except_words
|
|
31
|
+
)
|
|
32
|
+
@language = language
|
|
33
|
+
@output_logs = output_logs
|
|
34
|
+
@except_words = except_words
|
|
35
|
+
@provider = init_provider(provider)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def config
|
|
39
|
+
self.class.config
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def translate(text)
|
|
43
|
+
@provider.translate(text)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def init_provider(provider)
|
|
49
|
+
case provider
|
|
50
|
+
when :openai
|
|
51
|
+
Provider::OpenAI.new(
|
|
52
|
+
output_logs: @output_logs,
|
|
53
|
+
except_words: @except_words,
|
|
54
|
+
language: @language
|
|
55
|
+
)
|
|
56
|
+
when :deepl
|
|
57
|
+
Provider::DeepL.new(
|
|
58
|
+
pro: config.deepl_pro,
|
|
59
|
+
except_words: @except_words,
|
|
60
|
+
language: @language
|
|
61
|
+
)
|
|
62
|
+
else
|
|
63
|
+
raise UNSUPPORTED_PROVIDER_MESSAGE
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
metadata
CHANGED
|
@@ -1,56 +1,56 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: translation_api
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 1.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- milkeclair
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: deepl-rb
|
|
14
14
|
requirement: !ruby/object:Gem::Requirement
|
|
15
15
|
requirements:
|
|
16
|
-
- - "
|
|
16
|
+
- - ">="
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version:
|
|
18
|
+
version: '0'
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
|
-
- - "
|
|
23
|
+
- - ">="
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version:
|
|
25
|
+
version: '0'
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: dotenv
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
29
29
|
requirements:
|
|
30
|
-
- - "
|
|
30
|
+
- - ">="
|
|
31
31
|
- !ruby/object:Gem::Version
|
|
32
|
-
version:
|
|
32
|
+
version: '0'
|
|
33
33
|
type: :runtime
|
|
34
34
|
prerelease: false
|
|
35
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
36
36
|
requirements:
|
|
37
|
-
- - "
|
|
37
|
+
- - ">="
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
|
-
version:
|
|
39
|
+
version: '0'
|
|
40
40
|
- !ruby/object:Gem::Dependency
|
|
41
41
|
name: ruby-openai
|
|
42
42
|
requirement: !ruby/object:Gem::Requirement
|
|
43
43
|
requirements:
|
|
44
|
-
- - "
|
|
44
|
+
- - ">="
|
|
45
45
|
- !ruby/object:Gem::Version
|
|
46
|
-
version:
|
|
46
|
+
version: '0'
|
|
47
47
|
type: :runtime
|
|
48
48
|
prerelease: false
|
|
49
49
|
version_requirements: !ruby/object:Gem::Requirement
|
|
50
50
|
requirements:
|
|
51
|
-
- - "
|
|
51
|
+
- - ">="
|
|
52
52
|
- !ruby/object:Gem::Version
|
|
53
|
-
version:
|
|
53
|
+
version: '0'
|
|
54
54
|
description: translate
|
|
55
55
|
email:
|
|
56
56
|
- milkeclair.noreply@gmail.com
|
|
@@ -67,14 +67,13 @@ files:
|
|
|
67
67
|
- example.rb
|
|
68
68
|
- how_to_publish.txt
|
|
69
69
|
- lib/translation_api.rb
|
|
70
|
-
- lib/translation_api/
|
|
71
|
-
- lib/translation_api/deepl.rb
|
|
72
|
-
- lib/translation_api/
|
|
73
|
-
- lib/translation_api/openai.rb
|
|
70
|
+
- lib/translation_api/config.rb
|
|
71
|
+
- lib/translation_api/provider/deepl.rb
|
|
72
|
+
- lib/translation_api/provider/openai.rb
|
|
73
|
+
- lib/translation_api/provider/openai/cost.rb
|
|
74
|
+
- lib/translation_api/provider/openai/log.rb
|
|
74
75
|
- lib/translation_api/version.rb
|
|
75
|
-
- lib/translation_api/writer.rb
|
|
76
76
|
- rake_helper.rb
|
|
77
|
-
- sig/translation_api.rbs
|
|
78
77
|
homepage: https://github.com/milkeclair/translation_api
|
|
79
78
|
licenses:
|
|
80
79
|
- MIT
|
|
@@ -90,14 +89,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
90
89
|
requirements:
|
|
91
90
|
- - ">="
|
|
92
91
|
- !ruby/object:Gem::Version
|
|
93
|
-
version: 3.
|
|
92
|
+
version: 3.4.1
|
|
94
93
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
95
94
|
requirements:
|
|
96
95
|
- - ">="
|
|
97
96
|
- !ruby/object:Gem::Version
|
|
98
97
|
version: '0'
|
|
99
98
|
requirements: []
|
|
100
|
-
rubygems_version: 3.6.
|
|
99
|
+
rubygems_version: 3.6.9
|
|
101
100
|
specification_version: 4
|
|
102
101
|
summary: translate
|
|
103
102
|
test_files: []
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "writer"
|
|
4
|
-
|
|
5
|
-
module TranslationAPI
|
|
6
|
-
class Calculator
|
|
7
|
-
class ArgumentError < StandardError; end
|
|
8
|
-
MODEL_ERROR_MESSAGE =
|
|
9
|
-
"設定に無いモデルです。.envを確認してください。"
|
|
10
|
-
|
|
11
|
-
# トークン数から利用料金を計算する
|
|
12
|
-
#
|
|
13
|
-
# @param [Integer] used_tokens 使用したトークン数
|
|
14
|
-
# @param [String] token_type トークンの種類
|
|
15
|
-
# @return [Float] 利用料金
|
|
16
|
-
def self.calc_total_cost(used_tokens, token_type)
|
|
17
|
-
model = ENV["OPENAI_MODEL"] || "gpt-4o-mini"
|
|
18
|
-
rate = get_token_rate(model, token_type)
|
|
19
|
-
used_tokens * rate
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
# モデルとトークンの種類からトークンの単価を取得する
|
|
23
|
-
# モデルが設定に無い場合はエラーを投げる
|
|
24
|
-
#
|
|
25
|
-
# @param [String] model モデル名
|
|
26
|
-
# @param [String] token_type トークンの種類
|
|
27
|
-
# @return [Float] トークンの単価
|
|
28
|
-
def self.get_token_rate(model, token_type)
|
|
29
|
-
token_rate = token_rate_hash
|
|
30
|
-
validate_model(model, token_rate)
|
|
31
|
-
token_rate[model][token_type.to_sym]
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
# トークン単価のハッシュを返す
|
|
35
|
-
#
|
|
36
|
-
# @return [Hash] トークン単価のハッシュ
|
|
37
|
-
def self.token_rate_hash
|
|
38
|
-
one_million = 1_000_000
|
|
39
|
-
{
|
|
40
|
-
"gpt-4o" => {
|
|
41
|
-
input: 5.0 / one_million,
|
|
42
|
-
output: 15.0 / one_million
|
|
43
|
-
},
|
|
44
|
-
"gpt-4o-2024-08-06" => {
|
|
45
|
-
input: 2.5 / one_million,
|
|
46
|
-
output: 10.0 / one_million
|
|
47
|
-
},
|
|
48
|
-
"gpt-4o-mini" => {
|
|
49
|
-
input: 0.15 / one_million,
|
|
50
|
-
output: 0.6 / one_million
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
# モデルが存在するかどうかを確認する
|
|
56
|
-
#
|
|
57
|
-
# @param [String] model モデル名
|
|
58
|
-
# @param [Hash] token_rate トークンレートのハッシュ
|
|
59
|
-
# @raise [Calculator::ArgumentError] モデルが存在しない場合
|
|
60
|
-
def self.validate_model(model, token_rate)
|
|
61
|
-
return if token_rate.key?(model)
|
|
62
|
-
|
|
63
|
-
raise Calculator::ArgumentError, MODEL_ERROR_MESSAGE
|
|
64
|
-
end
|
|
65
|
-
end
|
|
66
|
-
end
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "dotenv"
|
|
4
|
-
require "deepl"
|
|
5
|
-
|
|
6
|
-
module TranslationAPI
|
|
7
|
-
class DeepL
|
|
8
|
-
SYSTEM_CONTENT_BASE = <<~TEXT
|
|
9
|
-
Keep symbols
|
|
10
|
-
TEXT
|
|
11
|
-
|
|
12
|
-
def initialize(output_logs: true, except_words: [], language: "japanese", pro: false)
|
|
13
|
-
Dotenv.load
|
|
14
|
-
setup_deepl_config!(pro: pro)
|
|
15
|
-
@supported_languages = fetch_supported_languages
|
|
16
|
-
validate_supported!(language)
|
|
17
|
-
@system_content = SYSTEM_CONTENT_BASE + except_option_text(except_words)
|
|
18
|
-
@language = @supported_languages[language.to_sym]
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
def translate(text)
|
|
22
|
-
return text if text.strip.empty?
|
|
23
|
-
|
|
24
|
-
::DeepL.translate(text, nil, @language, context: @system_content).text
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
private
|
|
28
|
-
|
|
29
|
-
def setup_deepl_config!(pro:)
|
|
30
|
-
validate_api_key!
|
|
31
|
-
|
|
32
|
-
::DeepL.configure do |config|
|
|
33
|
-
config.auth_key = ENV["DEEPL_API_KEY"] || ENV["DEEPL_AUTH_KEY"]
|
|
34
|
-
config.host = pro ? "https://api.deepl.com" : "https://api-free.deepl.com"
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
def validate_supported!(lang)
|
|
39
|
-
raise "This language is unsupported by DeepL" unless supported?(lang)
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def validate_api_key!
|
|
43
|
-
raise "API key is not found" unless ENV["DEEPL_API_KEY"] || ENV["DEEPL_AUTH_KEY"]
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
def fetch_supported_languages
|
|
47
|
-
::DeepL.languages.to_h { |lang| [lang.name.downcase.to_sym, lang.code] }
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def supported?(lang)
|
|
51
|
-
@supported_languages.key?(lang.to_sym)
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def except_option_text(except_words)
|
|
55
|
-
return "" if except_words.empty?
|
|
56
|
-
|
|
57
|
-
<<~TEXT
|
|
58
|
-
Words listed next are not translated: [#{except_words.join(", ")}]
|
|
59
|
-
TEXT
|
|
60
|
-
end
|
|
61
|
-
end
|
|
62
|
-
end
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "writer"
|
|
4
|
-
require_relative "openai"
|
|
5
|
-
require_relative "deepl"
|
|
6
|
-
|
|
7
|
-
module TranslationAPI
|
|
8
|
-
class Mediator
|
|
9
|
-
# @param [Boolean] output_logs ログを出力するかどうか
|
|
10
|
-
# @param [String] language 翻訳先の言語
|
|
11
|
-
# @param [Symbol] agent 翻訳エージェント
|
|
12
|
-
# @param [Array<String>] except_words 除外する単語のリスト
|
|
13
|
-
# @return [TranslationAPI::Mediator]
|
|
14
|
-
def initialize(
|
|
15
|
-
output_logs: true, language: "japanese", agent: :openai, except_words: []
|
|
16
|
-
)
|
|
17
|
-
@output_logs = output_logs
|
|
18
|
-
@language = language
|
|
19
|
-
@agent = agent
|
|
20
|
-
@except_words = except_words
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
# テキストを翻訳する
|
|
24
|
-
#
|
|
25
|
-
# @param [String] text 翻訳するテキスト
|
|
26
|
-
# @return [String] 翻訳されたテキスト
|
|
27
|
-
def translate(text)
|
|
28
|
-
agent = init_agent
|
|
29
|
-
agent.translate(text)
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
# エージェントのインスタンスを初期化する
|
|
33
|
-
#
|
|
34
|
-
# @return [Object] 翻訳エージェントのインスタンス
|
|
35
|
-
def init_agent
|
|
36
|
-
agent_class.new(
|
|
37
|
-
output_logs: @output_logs, except_words: @except_words, language: @language
|
|
38
|
-
)
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
# エージェントのクラスを返す
|
|
42
|
-
#
|
|
43
|
-
# @return [Class] エージェントのクラス
|
|
44
|
-
def agent_class
|
|
45
|
-
case @agent
|
|
46
|
-
when :openai
|
|
47
|
-
OpenAI
|
|
48
|
-
when :deepl
|
|
49
|
-
DeepL
|
|
50
|
-
else
|
|
51
|
-
class_name = camelize(@agent.to_s)
|
|
52
|
-
Object.const_get("TranslationAPI::#{class_name}")
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
# スネークケースの文字列をキャメルケースに変換する
|
|
57
|
-
#
|
|
58
|
-
# @param [String] str スネークケースの文字列
|
|
59
|
-
# @return [String] キャメルケースの文字列
|
|
60
|
-
def camelize(str)
|
|
61
|
-
str.split("_").map(&:capitalize).join
|
|
62
|
-
end
|
|
63
|
-
end
|
|
64
|
-
end
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "dotenv"
|
|
4
|
-
require "openai"
|
|
5
|
-
require_relative "writer"
|
|
6
|
-
|
|
7
|
-
module TranslationAPI
|
|
8
|
-
class OpenAI
|
|
9
|
-
SYSTEM_CONTENT_BASE = <<~TEXT
|
|
10
|
-
Translate only.
|
|
11
|
-
Return result only, no extra info
|
|
12
|
-
Keep symbols
|
|
13
|
-
TEXT
|
|
14
|
-
|
|
15
|
-
# OpenAI APIを使用してテキストを翻訳する
|
|
16
|
-
#
|
|
17
|
-
# @param [Boolean] output_logs ログを出力するかどうか
|
|
18
|
-
# @param [Array<String>] except_words 除外する単語のリスト
|
|
19
|
-
# @param [String] language 翻訳先の言語
|
|
20
|
-
# @return [void]
|
|
21
|
-
def initialize(output_logs: true, except_words: [], language: "japanese")
|
|
22
|
-
# 環境変数の読み込み
|
|
23
|
-
Dotenv.load
|
|
24
|
-
raise "API key is not found" unless ENV["OPENAI_API_KEY"]
|
|
25
|
-
|
|
26
|
-
@client = ::OpenAI::Client.new(
|
|
27
|
-
access_token: ENV["OPENAI_API_KEY"],
|
|
28
|
-
log_errors: true # 好み
|
|
29
|
-
)
|
|
30
|
-
@output_logs = output_logs
|
|
31
|
-
@system_content = SYSTEM_CONTENT_BASE + except_option_text(except_words)
|
|
32
|
-
@language = language
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
# テキストを日本語に翻訳し、結果をファイルに書き込む
|
|
36
|
-
#
|
|
37
|
-
# @param [String] text 翻訳するテキスト
|
|
38
|
-
# @return [void]
|
|
39
|
-
def translate(text)
|
|
40
|
-
# 空白文字は翻訳する必要がない
|
|
41
|
-
return text if text.strip.empty?
|
|
42
|
-
|
|
43
|
-
response = chat_to_api(text)
|
|
44
|
-
Writer.write_logs(self, response) if @output_logs
|
|
45
|
-
|
|
46
|
-
response["choices"][0]["message"]["content"]
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
# レスポンスから使用したトークン数を取得する
|
|
50
|
-
#
|
|
51
|
-
# @param [Hash] response OpenAI APIからのレスポンス
|
|
52
|
-
# @param [String] token_type トークンの種類 (input or output)
|
|
53
|
-
# @return [Integer] 使用したトークン数
|
|
54
|
-
def self.dig_used_tokens(response, token_type)
|
|
55
|
-
if token_type == "input"
|
|
56
|
-
response["usage"]["prompt_tokens"]
|
|
57
|
-
elsif token_type == "output"
|
|
58
|
-
response["usage"]["completion_tokens"]
|
|
59
|
-
end
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
private
|
|
63
|
-
|
|
64
|
-
# OpenAI APIにテキストを送信し、翻訳結果を取得する
|
|
65
|
-
#
|
|
66
|
-
# @param [String] text 翻訳するテキスト
|
|
67
|
-
# @return [Hash] OpenAI APIからのレスポンス
|
|
68
|
-
def chat_to_api(text)
|
|
69
|
-
@client.chat(
|
|
70
|
-
parameters: {
|
|
71
|
-
model: ENV["OPENAI_MODEL"] || "gpt-4o-mini",
|
|
72
|
-
messages: [
|
|
73
|
-
{ role: "system", content: @system_content },
|
|
74
|
-
{ role: "user", content: user_prompt_text(text) }
|
|
75
|
-
]
|
|
76
|
-
}
|
|
77
|
-
)
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
# 除外する単語を指定するプロンプト
|
|
81
|
-
#
|
|
82
|
-
# @param [Array<String>] except_words 除外する単語のリスト
|
|
83
|
-
# @return [String] 除外する単語を指定するテキスト
|
|
84
|
-
def except_option_text(except_words)
|
|
85
|
-
return "" if except_words.empty?
|
|
86
|
-
|
|
87
|
-
<<~TEXT
|
|
88
|
-
Words listed next are not translated: [#{except_words.join(", ")}]
|
|
89
|
-
TEXT
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
# ユーザー入力のプロンプト
|
|
93
|
-
#
|
|
94
|
-
# @param [String] text テキスト
|
|
95
|
-
# @return [String] ユーザー入力のプロンプト
|
|
96
|
-
def user_prompt_text(text)
|
|
97
|
-
<<~TEXT
|
|
98
|
-
Please translate this text to #{@language}: #{text}
|
|
99
|
-
TEXT
|
|
100
|
-
end
|
|
101
|
-
end
|
|
102
|
-
end
|
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "fileutils"
|
|
4
|
-
require_relative "calculator"
|
|
5
|
-
require_relative "openai"
|
|
6
|
-
|
|
7
|
-
module TranslationAPI
|
|
8
|
-
class Writer
|
|
9
|
-
@agent = nil
|
|
10
|
-
|
|
11
|
-
# ログをファイルに書き込む
|
|
12
|
-
#
|
|
13
|
-
# @param [Object] agent 翻訳エージェントのインスタンス
|
|
14
|
-
# @param [Hash] response 翻訳した結果
|
|
15
|
-
# @return [void]
|
|
16
|
-
def self.write_logs(agent, response)
|
|
17
|
-
# 例: "Hoge::Fuga" => "fuga"
|
|
18
|
-
@agent = agent.class.to_s.split("::").last.downcase
|
|
19
|
-
handle_agent(response)
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
# エージェントに対応したログ書き込み用メソッドを呼び出す
|
|
23
|
-
#
|
|
24
|
-
# @param [Hash] response 翻訳した結果
|
|
25
|
-
# @return [void]
|
|
26
|
-
def self.handle_agent(response)
|
|
27
|
-
method_name = "write_#{@agent}_logs"
|
|
28
|
-
send(method_name, response)
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
# OpenAIのログをファイルに書き込む
|
|
32
|
-
#
|
|
33
|
-
# @param [Hash] response OpenAI APIからのレスポンス
|
|
34
|
-
# @return [void]
|
|
35
|
-
def self.write_openai_logs(response)
|
|
36
|
-
input_tokens = OpenAI.dig_used_tokens(response, "input")
|
|
37
|
-
output_tokens = OpenAI.dig_used_tokens(response, "output")
|
|
38
|
-
|
|
39
|
-
write_translated_text(response["choices"][0]["message"]["content"])
|
|
40
|
-
write_used_tokens(input_tokens, output_tokens)
|
|
41
|
-
write_total_cost(input_tokens, output_tokens)
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
# 出力先のテキストファイルのパスを返す
|
|
45
|
-
# example.rbから見たパスで指定している
|
|
46
|
-
#
|
|
47
|
-
# @param [String] under_logs_path translator_logsディレクトリ配下のパス
|
|
48
|
-
# @return [String] 出力先のテキストファイルのパス
|
|
49
|
-
def self.text_path(under_logs_path)
|
|
50
|
-
output_dir = "translator_logs/#{@agent}"
|
|
51
|
-
FileUtils.mkdir_p(output_dir) unless File.directory?(output_dir)
|
|
52
|
-
File.join(output_dir, under_logs_path)
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
# 翻訳されたテキストをファイルに書き込み、ターミナルに出力する
|
|
56
|
-
# テキストはファイルの末尾に追記される
|
|
57
|
-
#
|
|
58
|
-
# @param [Hash] translated_text 翻訳されたテキスト
|
|
59
|
-
# @return [void]
|
|
60
|
-
def self.write_translated_text(translated_text)
|
|
61
|
-
log_file_path = text_path("translated_text.txt")
|
|
62
|
-
File.open(log_file_path, "a") do |file|
|
|
63
|
-
file.puts(translated_text)
|
|
64
|
-
end
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
# 使用したトークン数をファイルに書き込む
|
|
68
|
-
# ファイルのテキストは上書きされる
|
|
69
|
-
#
|
|
70
|
-
# @param [Integer] input_tokens 入力トークン数
|
|
71
|
-
# @param [Integer] output_tokens 出力トークン数
|
|
72
|
-
# @return [void]
|
|
73
|
-
def self.write_used_tokens(input_tokens, output_tokens)
|
|
74
|
-
log_file_path = text_path("tokens.txt")
|
|
75
|
-
existing_input_tokens, existing_output_tokens = read_existing_tokens(log_file_path)
|
|
76
|
-
|
|
77
|
-
total_input_tokens = existing_input_tokens + input_tokens
|
|
78
|
-
total_output_tokens = existing_output_tokens + output_tokens
|
|
79
|
-
|
|
80
|
-
File.open(log_file_path, "w") do |file|
|
|
81
|
-
file.puts("input: #{total_input_tokens}")
|
|
82
|
-
file.puts("output: #{total_output_tokens}")
|
|
83
|
-
end
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
# ファイルにあるトークン数を読み込む
|
|
87
|
-
#
|
|
88
|
-
# @param [String] log_file_path トークン数が書かれたファイルのパス
|
|
89
|
-
# @return [Array<Integer>] 入力トークン数と出力トークン数
|
|
90
|
-
def self.read_existing_tokens(log_file_path)
|
|
91
|
-
existing_input_tokens, existing_output_tokens = 0, 0
|
|
92
|
-
|
|
93
|
-
if File.exist?(log_file_path)
|
|
94
|
-
File.readlines(log_file_path).each do |line|
|
|
95
|
-
existing_input_tokens = line.split(":").last.strip.to_i if line.start_with?("input:")
|
|
96
|
-
existing_output_tokens = line.split(":").last.strip.to_i if line.start_with?("output:")
|
|
97
|
-
end
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
[existing_input_tokens, existing_output_tokens]
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
# トークン数から利用料金を計算し、ファイルにある合計金額に加算して書き込む
|
|
104
|
-
# ファイルのテキストは上書きされる
|
|
105
|
-
#
|
|
106
|
-
# @param [Integer] input_tokens 入力トークン数
|
|
107
|
-
# @param [Integer] output_tokens 出力トークン数
|
|
108
|
-
# @return [void]
|
|
109
|
-
def self.write_total_cost(input_tokens, output_tokens)
|
|
110
|
-
log_file_path = text_path("cost.txt")
|
|
111
|
-
this_cost =
|
|
112
|
-
Calculator.calc_total_cost(input_tokens, "input") + Calculator.calc_total_cost(output_tokens, "output")
|
|
113
|
-
existing_cost =
|
|
114
|
-
if File.exist?(log_file_path)
|
|
115
|
-
File.read(log_file_path).gsub("$", "").to_f
|
|
116
|
-
else
|
|
117
|
-
0.0
|
|
118
|
-
end
|
|
119
|
-
total_cost = this_cost + existing_cost
|
|
120
|
-
|
|
121
|
-
File.open(log_file_path, "w") do |file|
|
|
122
|
-
# 小数点以下8桁まで表示
|
|
123
|
-
file.puts("$#{format("%.8f", total_cost)}")
|
|
124
|
-
end
|
|
125
|
-
end
|
|
126
|
-
end
|
|
127
|
-
end
|
data/sig/translation_api.rbs
DELETED