translation_api 0.1.1
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 +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +32 -0
- data/CHANGELOG.md +27 -0
- data/LICENSE.txt +21 -0
- data/README.md +28 -0
- data/Rakefile +39 -0
- data/example.rb +19 -0
- data/how_to_publish.txt +10 -0
- data/lib/translation_api/calculator.rb +66 -0
- data/lib/translation_api/deepl.rb +62 -0
- data/lib/translation_api/mediator.rb +64 -0
- data/lib/translation_api/openai.rb +102 -0
- data/lib/translation_api/version.rb +5 -0
- data/lib/translation_api/writer.rb +127 -0
- data/lib/translation_api.rb +6 -0
- data/rake_helper.rb +29 -0
- data/sig/translation_api.rbs +4 -0
- metadata +89 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: b64c85dab791be051f77b9e20ad887cfe8c7ee817ae6099641633642d0291680
|
4
|
+
data.tar.gz: 66c9c79aba80565a03875bca2d4fdf64bc64a9b315b39faf3a8f5a06f8645855
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ccad42571b28c04503c8f8d6d256e1a54a711305d1fd186b1522f85f68dd57b4a0f1b816572c860ca1907ad01f7b94989d638fd6272cd655d6533ee286cb0784
|
7
|
+
data.tar.gz: ff862ecc8885bbc7f5f8ba25a7a31ab57f920340a09b7f23e0f412d05b0f390625ac46f18cdcc5a4ab9e29a30ddd7128e850db2cb36fda22a6c0385b9a4002a6
|
data/.rspec
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
AllCops:
|
2
|
+
TargetRubyVersion: 3.0
|
3
|
+
NewCops: enable
|
4
|
+
|
5
|
+
Style/StringLiterals:
|
6
|
+
EnforcedStyle: double_quotes
|
7
|
+
|
8
|
+
Style/StringLiteralsInInterpolation:
|
9
|
+
EnforcedStyle: double_quotes
|
10
|
+
|
11
|
+
Style/Documentation:
|
12
|
+
Enabled: false
|
13
|
+
|
14
|
+
Style/ParallelAssignment:
|
15
|
+
Enabled: false
|
16
|
+
|
17
|
+
Metrics/MethodLength:
|
18
|
+
CountComments: true
|
19
|
+
Max: 20
|
20
|
+
|
21
|
+
Lint/ScriptPermission:
|
22
|
+
Enabled: false
|
23
|
+
|
24
|
+
Style/FetchEnvVar:
|
25
|
+
Enabled: false
|
26
|
+
|
27
|
+
RSpec/ExampleLength:
|
28
|
+
Max: 10
|
29
|
+
|
30
|
+
require:
|
31
|
+
- rubocop-rake
|
32
|
+
- rubocop-rspec
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
## [Unreleased]
|
2
|
+
|
3
|
+
## [0.1.0] - 2024-09-29
|
4
|
+
|
5
|
+
- Initial release
|
6
|
+
|
7
|
+
## [1.0.0] - 2024-09-30
|
8
|
+
|
9
|
+
- ログの出力をするかどうかと除外する単語をオプションとして追加
|
10
|
+
|
11
|
+
## [1.0.2] - 2024-09-30
|
12
|
+
|
13
|
+
- リファクタリングのみ
|
14
|
+
|
15
|
+
## [1.1.0] - 2024-10-4
|
16
|
+
|
17
|
+
- 日本語以外に翻訳できるように、`exchange_language`オプションを追加
|
18
|
+
- それに伴い、翻訳のメソッド名を`translate_to_jp` -> `translate`に変更
|
19
|
+
- 空白文字はそのまま返すように変更
|
20
|
+
|
21
|
+
## [0.1.0] - 2024-11-12
|
22
|
+
|
23
|
+
- 他のAPIに対応できるようにリファクタリング
|
24
|
+
|
25
|
+
## [0.1.1] - 2025-03-31
|
26
|
+
|
27
|
+
- ライブラリの更新
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2024 milkeclair
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
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
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# TranslationAPI
|
2
|
+
|
3
|
+
Translate using APIs.
|
4
|
+
Requires api key.
|
5
|
+
|
6
|
+
## For OpenAI
|
7
|
+
|
8
|
+
1. `touch .env`
|
9
|
+
2. Add `OPENAI_API_KEY=YOUR_API_KEY`
|
10
|
+
3. Optional: `ENV["OPENAI_MODEL"]`(default: gpt-4o-mini)
|
11
|
+
4. `TranslationAPI::Mediator.new.translate("text")`
|
12
|
+
|
13
|
+
### Init Options
|
14
|
+
|
15
|
+
* output_logs (default: true)
|
16
|
+
* language (default: "japanese")
|
17
|
+
* agent (default: :openai)
|
18
|
+
* except_words (default: [])
|
19
|
+
|
20
|
+
### Output
|
21
|
+
|
22
|
+
* Translated_text
|
23
|
+
* Used Tokens
|
24
|
+
* Cost Spent(https://openai.com/api/pricing/)
|
25
|
+
|
26
|
+
## Example
|
27
|
+
|
28
|
+
Exec `ruby example.rb "text"`
|
data/Rakefile
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bundler/gem_tasks"
|
4
|
+
require "rspec/core/rake_task"
|
5
|
+
require "rubocop/rake_task"
|
6
|
+
require "yard"
|
7
|
+
require_relative "rake_helper"
|
8
|
+
|
9
|
+
desc "analysis"
|
10
|
+
task :analysis do
|
11
|
+
sh "bundle install"
|
12
|
+
|
13
|
+
RakeHelper.init_rake_tasks
|
14
|
+
|
15
|
+
puts "--- rspec ---"
|
16
|
+
Rake::Task[:spec].invoke
|
17
|
+
|
18
|
+
puts "--- rubocop ---"
|
19
|
+
Rake::Task[:rubocop].invoke
|
20
|
+
|
21
|
+
puts "--- yard ---"
|
22
|
+
Rake::Task[:yard].invoke
|
23
|
+
end
|
24
|
+
|
25
|
+
desc "push to github packages and rubygems"
|
26
|
+
task :push do
|
27
|
+
sh "bundle install"
|
28
|
+
|
29
|
+
puts "--- build ---"
|
30
|
+
RakeHelper.build_gem
|
31
|
+
|
32
|
+
puts "--- push to github packages ---"
|
33
|
+
RakeHelper.push_to_github_packages
|
34
|
+
|
35
|
+
puts "--- push to rubygems ---"
|
36
|
+
RakeHelper.push_to_rubygems
|
37
|
+
end
|
38
|
+
|
39
|
+
task default: :analysis
|
data/example.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/translation_api/mediator"
|
4
|
+
|
5
|
+
if ARGV.empty?
|
6
|
+
puts "引数が必要です: ruby example.rb \"text\""
|
7
|
+
exit
|
8
|
+
end
|
9
|
+
|
10
|
+
text = ARGV.join(" ")
|
11
|
+
translator =
|
12
|
+
TranslationAPI::Mediator.new(
|
13
|
+
output_logs: true,
|
14
|
+
language: "japanese",
|
15
|
+
agent: :openai,
|
16
|
+
except_words: %w[hoge fuga]
|
17
|
+
)
|
18
|
+
translated_text = translator.translate(text)
|
19
|
+
p translated_text
|
data/how_to_publish.txt
ADDED
@@ -0,0 +1,66 @@
|
|
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
|
@@ -0,0 +1,62 @@
|
|
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
|
+
p ::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
|
@@ -0,0 +1,64 @@
|
|
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
|
@@ -0,0 +1,102 @@
|
|
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
|
@@ -0,0 +1,127 @@
|
|
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/rake_helper.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class RakeHelper
|
4
|
+
GITHUB_PACKAGES_PUSH_COMMAND =
|
5
|
+
"gem push --key github --host https://rubygems.pkg.github.com/milkeclair " \
|
6
|
+
"pkg/translation_api-#{TranslationAPI::VERSION}.gem".freeze
|
7
|
+
|
8
|
+
RUBYGEMS_PUSH_COMMAND =
|
9
|
+
"gem push --host https://rubygems.org " \
|
10
|
+
"pkg/translation_api-#{TranslationAPI::VERSION}.gem".freeze
|
11
|
+
|
12
|
+
def self.init_rake_tasks
|
13
|
+
RSpec::Core::RakeTask.new(:spec) { |task| task.verbose = false }
|
14
|
+
RuboCop::RakeTask.new
|
15
|
+
YARD::Rake::YardocTask.new
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.build_gem
|
19
|
+
abort("gemのビルドに失敗しました") unless system("rake build")
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.push_to_github_packages
|
23
|
+
abort("githubへのgemのpushに失敗しました") unless system(GITHUB_PACKAGES_PUSH_COMMAND)
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.push_to_rubygems
|
27
|
+
abort("rubygemsへのgemのpushに失敗しました") unless system(RUBYGEMS_PUSH_COMMAND)
|
28
|
+
end
|
29
|
+
end
|
metadata
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: translation_api
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- milkeclair
|
8
|
+
bindir: exe
|
9
|
+
cert_chain: []
|
10
|
+
date: 2025-03-30 00:00:00.000000000 Z
|
11
|
+
dependencies:
|
12
|
+
- !ruby/object:Gem::Dependency
|
13
|
+
name: dotenv
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - "~>"
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: 3.1.4
|
19
|
+
type: :runtime
|
20
|
+
prerelease: false
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
requirements:
|
23
|
+
- - "~>"
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: 3.1.4
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: ruby-openai
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
29
|
+
requirements:
|
30
|
+
- - "~>"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 7.1.0
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: 7.1.0
|
40
|
+
description: translate
|
41
|
+
email:
|
42
|
+
- milkeclair.noreply@gmail.com
|
43
|
+
executables: []
|
44
|
+
extensions: []
|
45
|
+
extra_rdoc_files: []
|
46
|
+
files:
|
47
|
+
- ".rspec"
|
48
|
+
- ".rubocop.yml"
|
49
|
+
- CHANGELOG.md
|
50
|
+
- LICENSE.txt
|
51
|
+
- README.md
|
52
|
+
- Rakefile
|
53
|
+
- example.rb
|
54
|
+
- how_to_publish.txt
|
55
|
+
- lib/translation_api.rb
|
56
|
+
- lib/translation_api/calculator.rb
|
57
|
+
- lib/translation_api/deepl.rb
|
58
|
+
- lib/translation_api/mediator.rb
|
59
|
+
- lib/translation_api/openai.rb
|
60
|
+
- lib/translation_api/version.rb
|
61
|
+
- lib/translation_api/writer.rb
|
62
|
+
- rake_helper.rb
|
63
|
+
- sig/translation_api.rbs
|
64
|
+
homepage: https://github.com/milkeclair/translation_api
|
65
|
+
licenses:
|
66
|
+
- MIT
|
67
|
+
metadata:
|
68
|
+
homepage_uri: https://github.com/milkeclair/translation_api
|
69
|
+
source_code_uri: https://github.com/milkeclair/translation_api/blob/main
|
70
|
+
changelog_uri: https://github.com/milkeclair/translation_api/blob/main/CHANGELOG.md
|
71
|
+
rubygems_mfa_required: 'true'
|
72
|
+
rdoc_options: []
|
73
|
+
require_paths:
|
74
|
+
- lib
|
75
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
76
|
+
requirements:
|
77
|
+
- - ">="
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: 3.0.0
|
80
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
81
|
+
requirements:
|
82
|
+
- - ">="
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: '0'
|
85
|
+
requirements: []
|
86
|
+
rubygems_version: 3.6.2
|
87
|
+
specification_version: 4
|
88
|
+
summary: translate
|
89
|
+
test_files: []
|