ruby-spacy 0.3.0 → 0.4.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 +4 -4
- data/.github/FUNDING.yml +6 -0
- data/CHANGELOG.md +24 -0
- data/README.md +61 -1
- data/Rakefile +13 -0
- data/examples/get_started/lexeme.rb +0 -0
- data/examples/get_started/linguistic_annotations.rb +0 -0
- data/examples/get_started/morphology.rb +0 -0
- data/examples/get_started/most_similar.rb +0 -0
- data/examples/get_started/named_entities.rb +0 -0
- data/examples/get_started/outputs/test_dep.svg +0 -0
- data/examples/get_started/outputs/test_dep_compact.svg +0 -0
- data/examples/get_started/outputs/test_ent.html +0 -0
- data/examples/get_started/pos_tags_and_dependencies.rb +0 -0
- data/examples/get_started/similarity.rb +0 -0
- data/examples/get_started/tokenization.rb +0 -0
- data/examples/get_started/visualizing_dependencies.rb +0 -0
- data/examples/get_started/visualizing_dependencies_compact.rb +0 -0
- data/examples/get_started/visualizing_named_entities.rb +0 -0
- data/examples/get_started/vocab.rb +0 -0
- data/examples/get_started/word_vectors.rb +0 -0
- data/examples/japanese/ancestors.rb +0 -0
- data/examples/japanese/entity_annotations_and_labels.rb +0 -0
- data/examples/japanese/information_extraction.rb +0 -0
- data/examples/japanese/lemmatization.rb +0 -0
- data/examples/japanese/most_similar.rb +0 -0
- data/examples/japanese/named_entity_recognition.rb +0 -0
- data/examples/japanese/navigating_parse_tree.rb +0 -0
- data/examples/japanese/noun_chunks.rb +0 -0
- data/examples/japanese/outputs/test_dep.svg +0 -0
- data/examples/japanese/outputs/test_ent.html +0 -0
- data/examples/japanese/pos_tagging.rb +0 -0
- data/examples/japanese/sentence_segmentation.rb +0 -0
- data/examples/japanese/similarity.rb +0 -0
- data/examples/japanese/tokenization.rb +0 -0
- data/examples/japanese/visualizing_dependencies.rb +0 -0
- data/examples/japanese/visualizing_named_entities.rb +0 -0
- data/examples/linguistic_features/ancestors.rb +0 -0
- data/examples/linguistic_features/entity_annotations_and_labels.rb +0 -0
- data/examples/linguistic_features/finding_a_verb_with_a_subject.rb +0 -0
- data/examples/linguistic_features/information_extraction.rb +0 -0
- data/examples/linguistic_features/iterating_children.rb +0 -0
- data/examples/linguistic_features/iterating_lefts_and_rights.rb +0 -0
- data/examples/linguistic_features/lemmatization.rb +0 -0
- data/examples/linguistic_features/named_entity_recognition.rb +0 -0
- data/examples/linguistic_features/navigating_parse_tree.rb +0 -0
- data/examples/linguistic_features/noun_chunks.rb +0 -0
- data/examples/linguistic_features/outputs/test_ent.html +0 -0
- data/examples/linguistic_features/pos_tagging.rb +0 -0
- data/examples/linguistic_features/retokenize_1.rb +0 -0
- data/examples/linguistic_features/retokenize_2.rb +0 -0
- data/examples/linguistic_features/rule_based_morphology.rb +0 -0
- data/examples/linguistic_features/sentence_segmentation.rb +0 -0
- data/examples/linguistic_features/similarity.rb +0 -0
- data/examples/linguistic_features/similarity_between_lexemes.rb +0 -0
- data/examples/linguistic_features/similarity_between_spans.rb +0 -0
- data/examples/linguistic_features/tokenization.rb +0 -0
- data/examples/openai_integration/openai_embeddings.rb +0 -0
- data/examples/rule_based_matching/creating_spans_from_matches.rb +0 -0
- data/examples/rule_based_matching/matcher.rb +0 -0
- data/lib/ruby-spacy/openai_client.rb +57 -40
- data/lib/ruby-spacy/openai_helper.rb +91 -0
- data/lib/ruby-spacy/version.rb +1 -1
- data/lib/ruby-spacy.rb +282 -189
- data/ruby-spacy.gemspec +1 -0
- 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: 9bf4e97474302a2fffe019227e5afbb26c815036d13ec75bae6349f7186dff31
|
|
4
|
+
data.tar.gz: 2cd4012d9545d0a5ad9d2ebb03d07e40ae54acdfb18f0c9132c8848cce5cfd8e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 208f43e0efa0334ec159fb56e836f315bd5469a3768e77e0f6b7fa3ec936e722546e0323cc7bf5790393cc2fc52318e172355b821b6f3c83268de3cbf35ba139
|
|
7
|
+
data.tar.gz: 753515dcb7fb3e44ea2c4419e83baf9e0e8f9c6810a4f65cbfc3a631414b33019b5a0b4915c9a87edd8ac34fb8aac48673954c092c50e2e1cc152713bde48466
|
data/.github/FUNDING.yml
ADDED
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
|
+
## 0.4.1 - 2026-07-19
|
|
4
|
+
### Fixed
|
|
5
|
+
- Gem packaging: normalize file permissions at build time so packaged files
|
|
6
|
+
are world-readable (0644 / 0755). Version 0.4.0 shipped 11 owner-only
|
|
7
|
+
(0600) files, which made the gem unusable after `sudo gem install`
|
|
8
|
+
|
|
9
|
+
## 0.4.0 - 2026-02-23
|
|
10
|
+
### Added
|
|
11
|
+
- `Language#with_openai` block API with `OpenAIHelper` for streamlined OpenAI integration
|
|
12
|
+
- `Doc#linguistic_summary` for JSON-formatted spaCy analysis output
|
|
13
|
+
- `Token#idx`, `Span#text`/`Span#to_s`, `Language#memory_zone` (spaCy 3.8+)
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
- `Doc#ents` returns proper `Span` objects instead of raw Python objects
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
- Model name validation in `Language#initialize` for security
|
|
20
|
+
- OpenAI client: temperature support for o-series models, 429 retry with
|
|
21
|
+
exponential backoff, client reuse, `dimensions`/`response_format` parameters,
|
|
22
|
+
tool call depth limit
|
|
23
|
+
- Improved `respond_to_missing?` across all wrapper classes
|
|
24
|
+
- Added `instance_variables_to_inspect` for Ruby 4.0+ compatibility
|
|
25
|
+
- Added `base64` gem dependency (required for Ruby 3.4+)
|
|
26
|
+
|
|
3
27
|
## 0.3.0 - 2025-01-06
|
|
4
28
|
### Added
|
|
5
29
|
- Ruby 4.0 support
|
data/README.md
CHANGED
|
@@ -13,11 +13,12 @@
|
|
|
13
13
|
| ✅ | Access to pre-trained word vectors |
|
|
14
14
|
| ✅ | OpenAI Chat/Completion/Embeddings API integration |
|
|
15
15
|
|
|
16
|
-
Current Version: `0.
|
|
16
|
+
Current Version: `0.4.0`
|
|
17
17
|
|
|
18
18
|
- Ruby 4.0 supported
|
|
19
19
|
- spaCy 3.8 supported
|
|
20
20
|
- OpenAI GPT-5 API integration
|
|
21
|
+
- Block-based OpenAI API with linguistic analysis
|
|
21
22
|
|
|
22
23
|
## Installation of Prerequisites
|
|
23
24
|
|
|
@@ -843,6 +844,65 @@ Output:
|
|
|
843
844
|
...
|
|
844
845
|
```
|
|
845
846
|
|
|
847
|
+
### Block-based OpenAI API
|
|
848
|
+
|
|
849
|
+
The `Language#with_openai` block API provides a streamlined way to combine spaCy's linguistic analysis with OpenAI. The `Doc#linguistic_summary` method generates a JSON summary of spaCy's analysis (tokens, entities, noun chunks, etc.) that can be passed directly to the LLM as context.
|
|
850
|
+
|
|
851
|
+
**Basic usage:**
|
|
852
|
+
|
|
853
|
+
```ruby
|
|
854
|
+
require "ruby-spacy"
|
|
855
|
+
|
|
856
|
+
nlp = Spacy::Language.new("en_core_web_sm")
|
|
857
|
+
doc = nlp.read("Apple Inc. was founded by Steve Jobs in California.")
|
|
858
|
+
|
|
859
|
+
nlp.with_openai(model: "gpt-5-mini") do |ai|
|
|
860
|
+
result = ai.chat(
|
|
861
|
+
system: "You are a linguistic analyst. Analyze the given linguistic data.",
|
|
862
|
+
user: doc.linguistic_summary
|
|
863
|
+
)
|
|
864
|
+
puts result
|
|
865
|
+
end
|
|
866
|
+
```
|
|
867
|
+
|
|
868
|
+
**Batch processing with `pipe`:**
|
|
869
|
+
|
|
870
|
+
```ruby
|
|
871
|
+
require "ruby-spacy"
|
|
872
|
+
|
|
873
|
+
nlp = Spacy::Language.new("en_core_web_sm")
|
|
874
|
+
texts = ["The bank approved the loan.", "I sat on the river bank."]
|
|
875
|
+
|
|
876
|
+
nlp.with_openai(model: "gpt-5-mini") do |ai|
|
|
877
|
+
nlp.pipe(texts).each do |doc|
|
|
878
|
+
result = ai.chat(
|
|
879
|
+
system: "Identify the meaning of 'bank' in one word based on the linguistic context.",
|
|
880
|
+
user: doc.linguistic_summary
|
|
881
|
+
)
|
|
882
|
+
puts "#{doc.text} => #{result}"
|
|
883
|
+
end
|
|
884
|
+
end
|
|
885
|
+
```
|
|
886
|
+
|
|
887
|
+
**Customizing linguistic summary:**
|
|
888
|
+
|
|
889
|
+
```ruby
|
|
890
|
+
# Include sentences and morphology, exclude noun chunks
|
|
891
|
+
summary = doc.linguistic_summary(
|
|
892
|
+
sections: [:text, :tokens, :entities, :sentences],
|
|
893
|
+
token_attributes: [:text, :lemma, :pos, :dep, :head, :morphology]
|
|
894
|
+
)
|
|
895
|
+
```
|
|
896
|
+
|
|
897
|
+
**Embeddings:**
|
|
898
|
+
|
|
899
|
+
```ruby
|
|
900
|
+
nlp.with_openai do |ai|
|
|
901
|
+
vector = ai.embeddings("Hello world")
|
|
902
|
+
puts vector.length # => 1536
|
|
903
|
+
end
|
|
904
|
+
```
|
|
905
|
+
|
|
846
906
|
## Advanced Usage
|
|
847
907
|
|
|
848
908
|
### Setting a Timeout
|
data/Rakefile
CHANGED
|
@@ -10,3 +10,16 @@ Rake::TestTask.new(:test) do |t|
|
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
task default: :test
|
|
13
|
+
|
|
14
|
+
# Gem packaging preserves on-disk file modes; owner-only permissions here
|
|
15
|
+
# produce gems whose files are unreadable after a sudo install. Executable
|
|
16
|
+
# status is determined by path alone because local (Dropbox-synced) modes
|
|
17
|
+
# are unreliable.
|
|
18
|
+
task :normalize_permissions do
|
|
19
|
+
`git ls-files -z`.split("\x0").each do |f|
|
|
20
|
+
executable = f.start_with?("bin/", "exe/")
|
|
21
|
+
File.chmod(executable ? 0o755 : 0o644, f)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
Rake::Task["build"].enhance([:normalize_permissions])
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -13,7 +13,7 @@ module Spacy
|
|
|
13
13
|
API_ENDPOINT = "https://api.openai.com/v1"
|
|
14
14
|
DEFAULT_TIMEOUT = 120
|
|
15
15
|
MAX_RETRIES = 3
|
|
16
|
-
|
|
16
|
+
BASE_RETRY_DELAY = 1
|
|
17
17
|
|
|
18
18
|
class APIError < StandardError
|
|
19
19
|
attr_reader :status_code, :response_body
|
|
@@ -31,24 +31,25 @@ module Spacy
|
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
# Sends a chat completion request with optional tools support.
|
|
34
|
-
# Note: GPT-5 series models do not support the temperature parameter.
|
|
34
|
+
# Note: GPT-5 series and o-series models do not support the temperature parameter.
|
|
35
35
|
#
|
|
36
36
|
# @param model [String] The model to use (e.g., "gpt-5-mini")
|
|
37
37
|
# @param messages [Array<Hash>] The conversation messages
|
|
38
38
|
# @param max_completion_tokens [Integer] Maximum tokens in the response
|
|
39
|
-
# @param temperature [Float, nil] Sampling temperature (ignored for
|
|
39
|
+
# @param temperature [Float, nil] Sampling temperature (ignored for models that don't support it)
|
|
40
40
|
# @param tools [Array<Hash>, nil] Tool definitions for function calling
|
|
41
41
|
# @param tool_choice [String, Hash, nil] Tool selection strategy
|
|
42
|
+
# @param response_format [Hash, nil] Response format specification (e.g., { type: "json_object" })
|
|
42
43
|
# @return [Hash] The API response
|
|
43
|
-
def chat(model:, messages:, max_completion_tokens: 1000, temperature: nil, tools: nil, tool_choice: nil)
|
|
44
|
+
def chat(model:, messages:, max_completion_tokens: 1000, temperature: nil, tools: nil, tool_choice: nil, response_format: nil)
|
|
44
45
|
body = {
|
|
45
46
|
model: model,
|
|
46
47
|
messages: messages,
|
|
47
48
|
max_completion_tokens: max_completion_tokens
|
|
48
49
|
}
|
|
49
50
|
|
|
50
|
-
# GPT-5 series models do not support temperature parameter
|
|
51
|
-
unless
|
|
51
|
+
# GPT-5 series and o-series models do not support temperature parameter
|
|
52
|
+
unless temperature_unsupported?(model)
|
|
52
53
|
body[:temperature] = temperature || 0.7
|
|
53
54
|
end
|
|
54
55
|
|
|
@@ -57,25 +58,32 @@ module Spacy
|
|
|
57
58
|
body[:tool_choice] = tool_choice || "auto"
|
|
58
59
|
end
|
|
59
60
|
|
|
61
|
+
body[:response_format] = response_format if response_format
|
|
62
|
+
|
|
60
63
|
post("/chat/completions", body)
|
|
61
64
|
end
|
|
62
65
|
|
|
63
|
-
# Checks if the model
|
|
64
|
-
# GPT-5
|
|
65
|
-
|
|
66
|
-
|
|
66
|
+
# Checks if the model does not support the temperature parameter.
|
|
67
|
+
# This includes GPT-5 series and o-series (o1, o3, o4-mini, etc.) models.
|
|
68
|
+
# @param model [String] The model name
|
|
69
|
+
# @return [Boolean]
|
|
70
|
+
def temperature_unsupported?(model)
|
|
71
|
+
name = model.to_s
|
|
72
|
+
name.start_with?("gpt-5") || name.match?(/\Ao\d/)
|
|
67
73
|
end
|
|
68
74
|
|
|
69
75
|
# Sends an embeddings request.
|
|
70
76
|
#
|
|
71
77
|
# @param model [String] The embeddings model (e.g., "text-embedding-3-small")
|
|
72
78
|
# @param input [String] The text to embed
|
|
79
|
+
# @param dimensions [Integer, nil] The number of dimensions for the output embeddings
|
|
73
80
|
# @return [Hash] The API response
|
|
74
|
-
def embeddings(model:, input:)
|
|
81
|
+
def embeddings(model:, input:, dimensions: nil)
|
|
75
82
|
body = {
|
|
76
83
|
model: model,
|
|
77
84
|
input: input
|
|
78
85
|
}
|
|
86
|
+
body[:dimensions] = dimensions if dimensions
|
|
79
87
|
|
|
80
88
|
post("/embeddings", body)
|
|
81
89
|
end
|
|
@@ -94,36 +102,45 @@ module Spacy
|
|
|
94
102
|
uri = URI.parse("#{API_ENDPOINT}#{path}")
|
|
95
103
|
retries = 0
|
|
96
104
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
105
|
+
loop do
|
|
106
|
+
begin
|
|
107
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
108
|
+
http.use_ssl = true
|
|
109
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
|
110
|
+
http.cert_store = default_cert_store
|
|
111
|
+
http.open_timeout = @timeout
|
|
112
|
+
http.read_timeout = @timeout
|
|
113
|
+
|
|
114
|
+
request = Net::HTTP::Post.new(uri.path)
|
|
115
|
+
request["Content-Type"] = "application/json"
|
|
116
|
+
request["Authorization"] = "Bearer #{@access_token}"
|
|
117
|
+
request.body = body.to_json
|
|
118
|
+
|
|
119
|
+
response = http.request(request)
|
|
120
|
+
|
|
121
|
+
# Handle 429 rate limiting before general response handling
|
|
122
|
+
if response.code.to_i == 429
|
|
123
|
+
retries += 1
|
|
124
|
+
if retries <= MAX_RETRIES
|
|
125
|
+
retry_after = response["Retry-After"]&.to_f
|
|
126
|
+
delay = retry_after || (BASE_RETRY_DELAY * (2**(retries - 1)) + rand * 0.5)
|
|
127
|
+
sleep delay
|
|
128
|
+
next
|
|
129
|
+
end
|
|
130
|
+
raise APIError.new("Rate limited after #{MAX_RETRIES} retries",
|
|
131
|
+
status_code: 429, response_body: response.body)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
return handle_response(response)
|
|
135
|
+
rescue Net::OpenTimeout, Net::ReadTimeout, Errno::ECONNREFUSED, Errno::ECONNRESET, SocketError => e
|
|
136
|
+
retries += 1
|
|
137
|
+
if retries <= MAX_RETRIES
|
|
138
|
+
delay = BASE_RETRY_DELAY * (2**(retries - 1)) + rand * 0.5
|
|
139
|
+
sleep delay
|
|
140
|
+
next
|
|
141
|
+
end
|
|
142
|
+
raise APIError.new("Network error after #{MAX_RETRIES} retries: #{e.message}")
|
|
125
143
|
end
|
|
126
|
-
raise APIError.new("Network error after #{MAX_RETRIES} retries: #{e.message}")
|
|
127
144
|
end
|
|
128
145
|
end
|
|
129
146
|
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Spacy
|
|
4
|
+
# A helper class for OpenAI API interactions, designed to work with spaCy's
|
|
5
|
+
# linguistic analysis via the block-based {Language#with_openai} API.
|
|
6
|
+
#
|
|
7
|
+
# @example Basic usage with linguistic_summary
|
|
8
|
+
# nlp = Spacy::Language.new("en_core_web_sm")
|
|
9
|
+
# nlp.with_openai(model: "gpt-5-mini") do |ai|
|
|
10
|
+
# doc = nlp.read("Apple Inc. was founded by Steve Jobs.")
|
|
11
|
+
# ai.chat(system: "Analyze the linguistic data.", user: doc.linguistic_summary)
|
|
12
|
+
# end
|
|
13
|
+
class OpenAIHelper
|
|
14
|
+
# @return [String] the default model for chat requests
|
|
15
|
+
attr_reader :model
|
|
16
|
+
|
|
17
|
+
# Creates a new OpenAIHelper instance.
|
|
18
|
+
# @param access_token [String, nil] OpenAI API key (defaults to OPENAI_API_KEY env var)
|
|
19
|
+
# @param model [String] the default model for chat requests
|
|
20
|
+
# @param max_completion_tokens [Integer] default maximum tokens in responses
|
|
21
|
+
# @param temperature [Float] default sampling temperature
|
|
22
|
+
def initialize(access_token: nil, model: "gpt-5-mini",
|
|
23
|
+
max_completion_tokens: 1000, temperature: 0.7)
|
|
24
|
+
@access_token = access_token || ENV["OPENAI_API_KEY"]
|
|
25
|
+
raise "Error: OPENAI_API_KEY is not set" unless @access_token
|
|
26
|
+
|
|
27
|
+
@model = model
|
|
28
|
+
@default_max_completion_tokens = max_completion_tokens
|
|
29
|
+
@default_temperature = temperature
|
|
30
|
+
@client = OpenAIClient.new(access_token: @access_token)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Sends a chat completion request to OpenAI.
|
|
34
|
+
#
|
|
35
|
+
# Provides convenient `system:` and `user:` keyword arguments as shortcuts
|
|
36
|
+
# for building simple message arrays. For more complex conversations, pass
|
|
37
|
+
# a full `messages:` array directly.
|
|
38
|
+
#
|
|
39
|
+
# @param system [String, nil] system message content (shortcut)
|
|
40
|
+
# @param user [String, nil] user message content (shortcut)
|
|
41
|
+
# @param messages [Array<Hash>, nil] full message array (overrides system:/user:)
|
|
42
|
+
# @param model [String, nil] model override (defaults to instance model)
|
|
43
|
+
# @param max_completion_tokens [Integer, nil] token limit override
|
|
44
|
+
# @param temperature [Float, nil] temperature override
|
|
45
|
+
# @param response_format [Hash, nil] response format (e.g., { type: "json_object" })
|
|
46
|
+
# @param raw [Boolean] if true, returns the full API response Hash instead of text
|
|
47
|
+
# @return [String, Hash, nil] the response text, full response Hash (if raw:), or nil on error
|
|
48
|
+
def chat(system: nil, user: nil, messages: nil,
|
|
49
|
+
model: nil, max_completion_tokens: nil,
|
|
50
|
+
temperature: nil, response_format: nil, raw: false)
|
|
51
|
+
msgs = messages || build_messages(system: system, user: user)
|
|
52
|
+
raise ArgumentError, "No messages provided. Use system:/user: or messages:" if msgs.empty?
|
|
53
|
+
|
|
54
|
+
response = @client.chat(
|
|
55
|
+
model: model || @model,
|
|
56
|
+
messages: msgs,
|
|
57
|
+
max_completion_tokens: max_completion_tokens || @default_max_completion_tokens,
|
|
58
|
+
temperature: temperature || @default_temperature,
|
|
59
|
+
response_format: response_format
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
raw ? response : response.dig("choices", 0, "message", "content")
|
|
63
|
+
rescue OpenAIClient::APIError => e
|
|
64
|
+
puts "Error: OpenAI API call failed - #{e.message}"
|
|
65
|
+
nil
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Generates text embeddings using OpenAI's embeddings API.
|
|
69
|
+
#
|
|
70
|
+
# @param text [String] the text to embed
|
|
71
|
+
# @param model [String] the embeddings model
|
|
72
|
+
# @param dimensions [Integer, nil] number of dimensions (nil uses model default)
|
|
73
|
+
# @return [Array<Float>, nil] the embedding vector, or nil on error
|
|
74
|
+
def embeddings(text, model: "text-embedding-3-small", dimensions: nil)
|
|
75
|
+
response = @client.embeddings(model: model, input: text, dimensions: dimensions)
|
|
76
|
+
response.dig("data", 0, "embedding")
|
|
77
|
+
rescue OpenAIClient::APIError => e
|
|
78
|
+
puts "Error: OpenAI API call failed - #{e.message}"
|
|
79
|
+
nil
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
private
|
|
83
|
+
|
|
84
|
+
def build_messages(system: nil, user: nil)
|
|
85
|
+
msgs = []
|
|
86
|
+
msgs << { role: "system", content: system } if system
|
|
87
|
+
msgs << { role: "user", content: user } if user
|
|
88
|
+
msgs
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
data/lib/ruby-spacy/version.rb
CHANGED