Skip to content

Commit 4030162

Browse files
committed
Refactor
1 parent ec17370 commit 4030162

8 files changed

Lines changed: 103 additions & 41 deletions

File tree

README.md

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,19 @@ text = "Apple CEO Tim Cook announced iPhone 15 in Cupertino yesterday."
2929
labels = ["company", "person", "product", "location"]
3030

3131
model = Gliner[labels]
32-
pp model[text]
32+
entities = model[text]
3333

34-
# => {"company"=>["Apple"], "person"=>["Tim Cook"], "product"=>["iPhone 15"], "location"=>["Cupertino"]}
34+
pp entities["person"]
35+
# => [#<data Gliner::Entity ...>]
36+
37+
entities["person"].first.text
38+
# => "Tim Cook"
39+
40+
entities["person"].first.confidence
41+
# => 92.4
42+
43+
entities["person"].first.offsets
44+
# => [10, 18]
3545
```
3646

3747
You can also pass per-entity configs:
@@ -43,9 +53,13 @@ labels = {
4353
}
4454

4555
model = Gliner[labels]
46-
pp model["Email John Doe at john@example.com.", threshold: 0.5]
56+
entities = model["Email John Doe at john@example.com.", threshold: 0.5]
4757

48-
# => {"email"=>["john@example.com"], "person"=>"John Doe"}
58+
entities["person"].text
59+
# => "John Doe"
60+
61+
entities["email"].map(&:text)
62+
# => ["john@example.com"]
4963
```
5064

5165
### Classification
@@ -59,7 +73,30 @@ result = model["This laptop has amazing performance but terrible battery life!"]
5973

6074
pp result
6175

62-
# => {"sentiment"=>"negative"}
76+
# => {"sentiment"=>#<data Gliner::Label ...>}
77+
78+
result["sentiment"].label
79+
# => "negative"
80+
81+
result["sentiment"].confidence
82+
# => 87.1
83+
```
84+
85+
Multiple classification tasks:
86+
87+
```ruby
88+
text = "Breaking: Tech giant announces major layoffs amid market downturn"
89+
90+
tasks = {
91+
"sentiment" => %w[positive negative neutral],
92+
"urgency" => %w[high medium low],
93+
"category" => { "labels" => %w[tech finance politics sports], "multi_label" => false }
94+
}
95+
96+
results = Gliner.classify[tasks][text]
97+
98+
results.transform_values { |value| value.label }
99+
# => {"sentiment"=>"negative", "urgency"=>"high", "category"=>"tech"}
63100
```
64101

65102
### Structured extraction
@@ -77,10 +114,21 @@ structure = {
77114
}
78115

79116
result = Gliner[structure][text]
117+
product = result.fetch("product").first
80118

81119
pp result
82120

83-
# => {"product"=>[{"name"=>"iPhone 15 Pro Max", "storage"=>"256GB", "processor"=>"A17 Pro", "price"=>"1199"}]}
121+
product["name"].text
122+
# => "iPhone 15 Pro Max"
123+
124+
product["storage"].text
125+
# => "256GB"
126+
127+
product["processor"].text
128+
# => "A17 Pro"
129+
130+
product["price"].text
131+
# => "$1199"
84132
```
85133

86134
Choices can be included in field specs:

SPEC.md

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@ text = "Apple CEO Tim Cook announced iPhone 15 in Cupertino yesterday."
55

66
entities = ["company", "person", "product", "location"]
77
model = Gliner[entities]
8-
model[text]
8+
entities = model[text]
99

10-
# => {company"=>["Apple"], "person"=>["Tim Cook"], "product"=>["iPhone 15"], "location"=>["Cupertino"]}
10+
# => {"company"=>[#<data Gliner::Entity ...>], "person"=>[#<data Gliner::Entity ...>], ...}
11+
12+
entities["person"].first.text
13+
# => "Tim Cook"
1114
```
1215

1316
# Per entity config
@@ -21,9 +24,13 @@ entities = {
2124
}
2225

2326
model = Gliner[entities]
24-
model[text]
27+
entities = model[text]
28+
29+
entities["person"].text
30+
# => "John Doe"
2531

26-
# => {company"=>["Apple"], "person"=>["Tim Cook"], "product"=>["iPhone 15"], "location"=>["Cupertino"]}
32+
entities["email"].map(&:text)
33+
# => ["john@example.com"]
2734
```
2835

2936
# Classfication
@@ -32,9 +39,15 @@ model[text]
3239
text = "This laptop has amazing performance but terrible battery life!",
3340
concept = { "sentiment" => %w[positive negative neutral] }
3441
model = Gliner.classify[concept]
35-
model[text]
42+
result = model[text]
43+
44+
# => {"sentiment"=>#<data Gliner::Label ...>}
45+
46+
result["sentiment"].label
47+
# => "negative"
3648

37-
# => {"sentiment"=>"negative"}
49+
result["sentiment"].confidence
50+
# => 87.1
3851
```
3952

4053
# Structured
@@ -52,7 +65,18 @@ structure = {
5265
}
5366

5467
model = Gliner[structure]
55-
model[text]
68+
result = model[text]
69+
product = result.fetch("product").first
70+
71+
product["name"].text
72+
# => "iPhone 15 Pro Max"
73+
74+
product["storage"].text
75+
# => "256GB"
76+
77+
product["processor"].text
78+
# => "A17 Pro chip"
5679

57-
# => {"product"=>[{"name"=>"iPhone 15 Pro Max", "storage"=>"256GB", "processor"=>"A17 Pro chip", "price"=>"$1199"}]}
80+
product["price"].text
81+
# => "$1199"
5882
```

lib/gliner.rb

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ module Gliner
3131
:text_len
3232
)
3333

34-
Entity = Data.define(:index, :offsets, :text, :name, :probability) do
34+
Entity = Data.define(:index, :offsets, :text, :name, :confidence) do
3535
def to_s = text.to_s
3636
def to_str = text.to_s
3737
end
@@ -47,17 +47,6 @@ def overlaps?(other)
4747
end
4848
end
4949

50-
FormatOptions = Data.define(:include_confidence, :include_spans) do
51-
def self.from(input)
52-
return input if input.is_a?(FormatOptions)
53-
54-
new(
55-
include_confidence: input.fetch(:include_confidence, false),
56-
include_spans: input.fetch(:include_spans, false)
57-
)
58-
end
59-
end
60-
6150
class << self
6251
attr_writer :model, :config
6352

lib/gliner/classifier.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,9 @@ def sorted_label_scores(scores, labels)
4747
def format_multi_label(label_scores, cls_threshold, include_confidence)
4848
chosen = labels_above_threshold(label_scores, cls_threshold)
4949

50-
chosen.map { |label, score| format_label(label, score, include_confidence) }
50+
chosen
51+
.sort_by { |(_label, score)| -score }
52+
.map { |label, score| format_label(label, score, include_confidence) }
5153
end
5254

5355
def labels_above_threshold(label_scores, threshold)

lib/gliner/span_extractor.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ def format_span(span, _opts, label:, index:)
109109
offsets: [span.start, span.end],
110110
text: span.text,
111111
name: label&.to_s,
112-
probability: span.score * 100.0
112+
confidence: span.score * 100.0
113113
)
114114
end
115115

lib/gliner/structured_extractor.rb

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
module Gliner
44
Structure = Data.define(:fields) do
5+
include Enumerable
6+
57
def [](key) = fields[key]
68
def fetch(key, *args, &block) = fields.fetch(key, *args, &block)
79
def to_h = fields
@@ -42,15 +44,14 @@ def filter_spans_by_choices(spans, choices)
4244
end
4345

4446
def build_structure_instances(parsed_fields, spans_by_label, opts)
45-
format_opts = FormatOptions.from(opts)
4647
anchor_field = anchor_field_for(parsed_fields)
4748
return [Gliner::Structure.new(fields: {})] unless anchor_field
4849

4950
anchors = spans_by_label.fetch(anchor_field[:name], [])
50-
return [format_structure_object(parsed_fields, spans_by_label, format_opts)] if anchors.empty?
51+
return [format_structure_object(parsed_fields, spans_by_label, opts)] if anchors.empty?
5152

5253
instance_spans = build_instance_spans(anchors, spans_by_label)
53-
format_instances(parsed_fields, instance_spans, format_opts)
54+
format_instances(parsed_fields, instance_spans, opts)
5455
end
5556

5657
def format_structure_object(parsed_fields, spans_by_label, _opts)

lib/gliner/tasks/json_extraction.rb

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,7 @@ def labels(parsed)
4747
def process_output(logits, parsed, prepared, options)
4848
spans_by_label = extract_spans(logits, parsed, prepared, options)
4949
filtered_spans = @structured_extractor.apply_choice_filters(spans_by_label, parsed[:parsed_fields])
50-
format_opts = FormatOptions.from(options)
51-
52-
@structured_extractor.build_structure_instances(parsed[:parsed_fields], filtered_spans, format_opts)
50+
@structured_extractor.build_structure_instances(parsed[:parsed_fields], filtered_spans, options)
5351
end
5452

5553
def execute_all(pipeline, text, structures_config, **options)

spec/gliner/model_spec.rb

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
offsets: [0, 8],
2323
text: 'Tim Cook',
2424
name: 'person',
25-
probability: 90.0
25+
confidence: 90.0
2626
)
2727
)
2828
end
@@ -109,21 +109,21 @@
109109
offsets: [0, 5],
110110
text: 'Jan 5',
111111
name: 'date',
112-
probability: 90.0
112+
confidence: 90.0
113113
),
114114
'merchant' => Gliner::Entity.new(
115115
index: 0,
116116
offsets: [7, 16],
117117
text: 'Starbucks',
118118
name: 'merchant',
119-
probability: 90.0
119+
confidence: 90.0
120120
),
121121
'amount' => Gliner::Entity.new(
122122
index: 0,
123123
offsets: [17, 22],
124124
text: '$5.50',
125125
name: 'amount',
126-
probability: 90.0
126+
confidence: 90.0
127127
)
128128
})
129129
)
@@ -134,21 +134,21 @@
134134
offsets: [40, 45],
135135
text: 'Jan 6',
136136
name: 'date',
137-
probability: 80.0
137+
confidence: 80.0
138138
),
139139
'merchant' => Gliner::Entity.new(
140140
index: 0,
141141
offsets: [47, 53],
142142
text: 'Amazon',
143143
name: 'merchant',
144-
probability: 70.0
144+
confidence: 70.0
145145
),
146146
'amount' => Gliner::Entity.new(
147147
index: 0,
148148
offsets: [54, 61],
149149
text: '$156.99',
150150
name: 'amount',
151-
probability: 80.0
151+
confidence: 80.0
152152
)
153153
})
154154
)

0 commit comments

Comments
 (0)