jp_translator_from_gpt 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: d259dbbb5640c1f7e1b806984fb3f98ea12499fa8c4c722d0e28ffe116f3139c
4
+ data.tar.gz: '059bf1056a77bb969665d72dce098ea79bee0db6a0dcbf05ad612242a9fc187b'
5
+ SHA512:
6
+ metadata.gz: 78cdc0c0c022749eca2864321466214e8ba9c314fc9dfbd4634f91fa756a38455be2fe21de31446b87d3d596f589e83782fa6aa8824f95800bbf9f71e2dfcfee
7
+ data.tar.gz: e96f63804629b36de2733dec301387dda7161fe98cd6fb0b59a7b90eda8282bfadd0c08ceb51d582f8a75a358ab0c25763f8659d91b7a089697e666e8708cd04
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
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,13 @@
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
+ - リファクタリングのみ
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,17 @@
1
+ ## 概要
2
+
3
+ ChatGPTを使って文字列を日本語に翻訳する
4
+ OpenAIのAPIキーが必要
5
+ `.env`を作成し、`OPENAI_API_KEY=YOUR_API_KEY`を追加する
6
+ `ENV["OPENAI_MODEL"]`がない場合は、[gpt-4o-mini](https://openai.com/index/gpt-4o-mini-advancing-cost-efficient-intelligence/)をデフォルトで使用する
7
+
8
+ * 翻訳されたテキスト
9
+ * 使用したトークン数
10
+ * 掛かった金額([参考](https://openai.com/api/pricing/))
11
+
12
+ が出力される
13
+
14
+ ## 使用例
15
+
16
+ 詳しくは、example.rbを参照
17
+ `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,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/jp_translator_from_gpt"
4
+
5
+ if ARGV.empty?
6
+ puts "引数が必要です: ruby example.rb \"text\""
7
+ exit
8
+ end
9
+
10
+ text = ARGV.join(" ")
11
+ translator =
12
+ JpTranslatorFromGpt::Translator.new(output_logs: false, except_words: ["hoge"])
13
+ translator.translate_to_jp(text)
@@ -0,0 +1,10 @@
1
+ some code change
2
+ version.rb change
3
+ changelog.md change
4
+
5
+ $rake analysis
6
+
7
+ $git commit
8
+ $git push
9
+
10
+ $rake push
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "writer"
4
+
5
+ module JpTranslatorFromGpt
6
+ class Calculator
7
+ class ArgumentError < StandardError; end
8
+ MODEL_ERROR_MESSAGE =
9
+ "設定に無いモデルです。モデルをmodules/writerに追加するか、ENV[\"OPENAI_MODEL\"]を変更してください。"
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
+ clear_files
64
+ raise Calculator::ArgumentError, MODEL_ERROR_MESSAGE
65
+ end
66
+
67
+ # ファイルの内容を削除する
68
+ #
69
+ # @return [void]
70
+ def self.clear_files
71
+ File.write(Writer.text_path("translated_text.txt"), "")
72
+ File.write(Writer.text_path("tokens.txt"), "")
73
+ File.write(Writer.text_path("cost.txt"), "")
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dotenv"
4
+ require "openai"
5
+ require_relative "writer"
6
+
7
+ module JpTranslatorFromGpt
8
+ class Translator
9
+ SYSTEM_CONTENT_BASE = <<~TEXT
10
+ Translate only. Return result only, no extra info. Keep symbols.
11
+ TEXT
12
+
13
+ def initialize(output_logs: true, except_words: [])
14
+ # 環境変数の読み込み
15
+ Dotenv.load
16
+
17
+ @client = OpenAI::Client.new(
18
+ access_token: ENV["OPENAI_API_KEY"],
19
+ log_errors: true # 好み
20
+ )
21
+ @output_logs = output_logs
22
+ @system_content = SYSTEM_CONTENT_BASE + except_option_text(except_words)
23
+ end
24
+
25
+ # テキストを日本語に翻訳し、結果をファイルに書き込む
26
+ #
27
+ # @param [String] text 翻訳するテキスト
28
+ # @return [void]
29
+ def translate_to_jp(text)
30
+ response = chat_to_api(text)
31
+ Writer.write_logs(response) if @output_logs
32
+
33
+ translated_text = response["choices"][0]["message"]["content"]
34
+ puts translated_text
35
+ translated_text
36
+ end
37
+
38
+ # レスポンスから使用したトークン数を取得する
39
+ #
40
+ # @param [Hash] response OpenAI APIからのレスポンス
41
+ # @param [String] token_type トークンの種類 (input or output)
42
+ # @return [Integer] 使用したトークン数
43
+ def self.dig_used_tokens(response, token_type)
44
+ if token_type == "input"
45
+ response["usage"]["prompt_tokens"]
46
+ elsif token_type == "output"
47
+ response["usage"]["completion_tokens"]
48
+ end
49
+ end
50
+
51
+ private
52
+
53
+ # OpenAI APIにテキストを送信し、翻訳結果を取得する
54
+ #
55
+ # @param [String] text 翻訳するテキスト
56
+ # @return [Hash] OpenAI APIからのレスポンス
57
+ def chat_to_api(text)
58
+ @client.chat(
59
+ parameters: {
60
+ model: ENV["OPENAI_MODEL"] || "gpt-4o-mini",
61
+ messages: [
62
+ { role: "system", content: @system_content },
63
+ { role: "user", content: "please translate this to Japanese: #{text}" }
64
+ ]
65
+ }
66
+ )
67
+ end
68
+
69
+ # 除外する単語を指定するプロンプト
70
+ #
71
+ # @param [Array<String>] except_words 除外する単語のリスト
72
+ # @return [String] 除外する単語を指定するテキスト
73
+ def except_option_text(except_words)
74
+ return "" if except_words.empty?
75
+
76
+ <<~TEXT
77
+ Words listed next are not translated: #{except_words.join(", ")}
78
+ TEXT
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JpTranslatorFromGpt
4
+ VERSION = "1.0.3"
5
+ end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require_relative "calculator"
5
+
6
+ module JpTranslatorFromGpt
7
+ class Writer
8
+ # ログをファイルに書き込む
9
+ #
10
+ # @param [Hash] response OpenAI APIからのレスポンス
11
+ # @return [void]
12
+ def self.write_logs(response)
13
+ input_tokens = Translator.dig_used_tokens(response, "input")
14
+ output_tokens = Translator.dig_used_tokens(response, "output")
15
+
16
+ write_translated_text(response)
17
+ write_used_tokens(input_tokens, output_tokens)
18
+ write_total_cost(input_tokens, output_tokens)
19
+ end
20
+
21
+ # 出力先のテキストファイルのパスを返す
22
+ # main.rbから見たパスで指定している
23
+ #
24
+ # @param [String] under_logs_path translator_logsディレクトリ配下のパス
25
+ # @return [String] 出力先のテキストファイルのパス
26
+ def self.text_path(under_logs_path)
27
+ output_dir = "translator_logs"
28
+ FileUtils.mkdir_p(output_dir) unless File.directory?(output_dir)
29
+ File.join("translator_logs", under_logs_path)
30
+ end
31
+
32
+ # 翻訳されたテキストをファイルに書き込み、ターミナルに出力する
33
+ # テキストはファイルの末尾に追記される
34
+ #
35
+ # @param [Hash] response OpenAI APIからのレスポンス
36
+ # @return [void]
37
+ def self.write_translated_text(response)
38
+ log_file_path = text_path("translated_text.txt")
39
+ File.open(log_file_path, "a") do |file|
40
+ translated_text = response["choices"][0]["message"]["content"]
41
+ file.puts(translated_text)
42
+ end
43
+ end
44
+
45
+ # 使用したトークン数をファイルに書き込む
46
+ # ファイルのテキストは上書きされる
47
+ #
48
+ # @param [Integer] input_tokens 入力トークン数
49
+ # @param [Integer] output_tokens 出力トークン数
50
+ # @return [void]
51
+ def self.write_used_tokens(input_tokens, output_tokens)
52
+ log_file_path = text_path("tokens.txt")
53
+ existing_input_tokens, existing_output_tokens = read_existing_tokens(log_file_path)
54
+
55
+ total_input_tokens = existing_input_tokens + input_tokens
56
+ total_output_tokens = existing_output_tokens + output_tokens
57
+
58
+ File.open(log_file_path, "w") do |file|
59
+ file.puts("input: #{total_input_tokens}")
60
+ file.puts("output: #{total_output_tokens}")
61
+ end
62
+ end
63
+
64
+ # ファイルにあるトークン数を読み込む
65
+ #
66
+ # @param [String] log_file_path トークン数が書かれたファイルのパス
67
+ # @return [Array<Integer>] 入力トークン数と出力トークン数
68
+ def self.read_existing_tokens(log_file_path)
69
+ existing_input_tokens, existing_output_tokens = 0, 0
70
+
71
+ if File.exist?(log_file_path)
72
+ File.readlines(log_file_path).each do |line|
73
+ existing_input_tokens = line.split(":").last.strip.to_i if line.start_with?("input:")
74
+ existing_output_tokens = line.split(":").last.strip.to_i if line.start_with?("output:")
75
+ end
76
+ end
77
+
78
+ [existing_input_tokens, existing_output_tokens]
79
+ end
80
+
81
+ # トークン数から利用料金を計算し、ファイルにある合計金額に加算して書き込む
82
+ # ファイルのテキストは上書きされる
83
+ #
84
+ # @param [Integer] input_tokens 入力トークン数
85
+ # @param [Integer] output_tokens 出力トークン数
86
+ # @return [void]
87
+ def self.write_total_cost(input_tokens, output_tokens)
88
+ log_file_path = text_path("cost.txt")
89
+ this_cost =
90
+ Calculator.calc_total_cost(input_tokens, "input") + Calculator.calc_total_cost(output_tokens, "output")
91
+ existing_cost =
92
+ if File.exist?(log_file_path)
93
+ File.read(log_file_path).gsub("$", "").to_f
94
+ else
95
+ 0.0
96
+ end
97
+ total_cost = this_cost + existing_cost
98
+
99
+ File.open(log_file_path, "w") do |file|
100
+ # 小数点以下8桁まで表示
101
+ file.puts("$#{format("%.8f", total_cost)}")
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "jp_translator_from_gpt/version"
4
+ require_relative "jp_translator_from_gpt/translator"
5
+
6
+ module JpTranslatorFromGpt
7
+ 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/jp_translator_from_gpt-#{JpTranslatorFromGpt::VERSION}.gem".freeze
7
+
8
+ RUBYGEMS_PUSH_COMMAND =
9
+ "gem push --host https://rubygems.org " \
10
+ "pkg/jp_translator_from_gpt-#{JpTranslatorFromGpt::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
@@ -0,0 +1,4 @@
1
+ module JpTranslatorFromGpt
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,90 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jp_translator_from_gpt
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.3
5
+ platform: ruby
6
+ authors:
7
+ - milkeclair
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2024-10-03 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: dotenv
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 3.1.4
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 3.1.4
27
+ - !ruby/object:Gem::Dependency
28
+ name: ruby-openai
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 7.1.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 7.1.0
41
+ description: gpt-4o-miniを使って日本語に翻訳する
42
+ email:
43
+ - milkeclair.noreply@gmail.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - ".rspec"
49
+ - ".rubocop.yml"
50
+ - CHANGELOG.md
51
+ - LICENSE.txt
52
+ - README.md
53
+ - Rakefile
54
+ - example.rb
55
+ - how_to_publish.txt
56
+ - lib/jp_translator_from_gpt.rb
57
+ - lib/jp_translator_from_gpt/calculator.rb
58
+ - lib/jp_translator_from_gpt/translator.rb
59
+ - lib/jp_translator_from_gpt/version.rb
60
+ - lib/jp_translator_from_gpt/writer.rb
61
+ - rake_helper.rb
62
+ - sig/jp_translator_from_gpt.rbs
63
+ homepage: https://github.com/milkeclair/jp_translator_from_gpt
64
+ licenses:
65
+ - MIT
66
+ metadata:
67
+ homepage_uri: https://github.com/milkeclair/jp_translator_from_gpt
68
+ source_code_uri: https://github.com/milkeclair/jp_translator_from_gpt/blob/main
69
+ changelog_uri: https://github.com/milkeclair/jp_translator_from_gpt/blob/main/CHANGELOG.md
70
+ rubygems_mfa_required: 'true'
71
+ post_install_message:
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.5.16
87
+ signing_key:
88
+ specification_version: 4
89
+ summary: translate to japanese
90
+ test_files: []