opensearch-sugar 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 +7 -0
- data/.agents/skills/diataxis/SKILL.md +142 -0
- data/.agents/skills/diataxis/references/examples.md +420 -0
- data/.agents/skills/diataxis/references/explanation-template.md +96 -0
- data/.agents/skills/diataxis/references/framework.md +400 -0
- data/.agents/skills/diataxis/references/how-to-guide-template.md +105 -0
- data/.agents/skills/diataxis/references/reference-template.md +110 -0
- data/.agents/skills/diataxis/references/tutorial-template.md +101 -0
- data/.agents/skills/diataxis/scripts/generate_index.py +139 -0
- data/.rspec +3 -0
- data/.standard.yml +3 -0
- data/AGENTS.md +120 -0
- data/CHANGELOG.md +5 -0
- data/Dockerfile.opensearch +4 -0
- data/Increase_Coverage.md +311 -0
- data/README.md +143 -0
- data/Rakefile +27 -0
- data/Steepfile +23 -0
- data/adrs/ADR-000-template.md +87 -0
- data/adrs/ADR-001-simpledelegator-for-client.md +138 -0
- data/adrs/ADR-002-facade-pattern-for-index.md +126 -0
- data/adrs/ADR-003-repository-pattern-for-models.md +148 -0
- data/adrs/ADR-004-integration-tests-no-mocking.md +91 -0
- data/adrs/ADR-005-exceptions-over-result-objects.md +107 -0
- data/adrs/ADR-006-ssl-on-by-default.md +95 -0
- data/adrs/ADR-007-selective-sugar-surface.md +118 -0
- data/adrs/ADR-008-integration-test-design.md +178 -0
- data/compose.yml +2 -0
- data/compose_opensearch.yml +31 -0
- data/docs/HOWTO.md +844 -0
- data/docs/REFERENCE.md +725 -0
- data/docs/TUTORIAL.md +327 -0
- data/docs/alias-api-design-notes.md +119 -0
- data/lib/opensearch/sugar/client.rb +300 -0
- data/lib/opensearch/sugar/index/include/utilities.rb +6 -0
- data/lib/opensearch/sugar/index.rb +339 -0
- data/lib/opensearch/sugar/models.rb +209 -0
- data/lib/opensearch/sugar/version.rb +8 -0
- data/lib/opensearch/sugar.rb +61 -0
- data/old_docs/DELEGATED_METHODS_ANALYSIS.md +361 -0
- data/old_docs/EXPLANATION.md +685 -0
- data/old_docs/README.md +155 -0
- data/old_docs/docs/CLI-PROPOSAL.md +257 -0
- data/old_docs/docs/HOWTO.md +798 -0
- data/old_docs/docs/REFERENCE.md +901 -0
- data/old_docs/docs/TUTORIAL.md +493 -0
- data/sig/opensearch/sugar.rbs +162 -0
- metadata +240 -0
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
module OpenSearch::Sugar
|
|
2
|
+
# Manages ML models deployed via the
|
|
3
|
+
# {https://opensearch.org/docs/latest/ml-commons-plugin/index/ OpenSearch ML Commons plugin}.
|
|
4
|
+
#
|
|
5
|
+
# Provides methods to register, deploy, list, and delete models, as well as to build
|
|
6
|
+
# text-embedding ingest pipelines that use a deployed model.
|
|
7
|
+
#
|
|
8
|
+
# Access an instance via {OpenSearch::Sugar::Client#models}:
|
|
9
|
+
#
|
|
10
|
+
# @example
|
|
11
|
+
# models = client.models
|
|
12
|
+
# model = models.register(name: "all-MiniLM-L6-v2", version: "1.0.0")
|
|
13
|
+
# puts model.id
|
|
14
|
+
class Models
|
|
15
|
+
# Struct representing a deployed ML model with +:name+, +:version+, and +:id+ members.
|
|
16
|
+
#
|
|
17
|
+
# @!attribute [r] name
|
|
18
|
+
# @return [String] The model name
|
|
19
|
+
# @!attribute [r] version
|
|
20
|
+
# @return [String] The model version string
|
|
21
|
+
# @!attribute [r] id
|
|
22
|
+
# @return [String] The internal OpenSearch model ID (used in API calls)
|
|
23
|
+
ML_INFO = Struct.new(:name, :version, :id)
|
|
24
|
+
|
|
25
|
+
# @param os [OpenSearch::Sugar::Client] The client used for ML API calls
|
|
26
|
+
def initialize(os)
|
|
27
|
+
@os = os
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Registers and deploys an ML model via the ML Commons plugin.
|
|
31
|
+
#
|
|
32
|
+
# Idempotent — if a model matching +name+ is already registered, returns the
|
|
33
|
+
# existing {ML_INFO} without re-registering. Polls the task status every 5 seconds
|
|
34
|
+
# until deployment completes or fails.
|
|
35
|
+
#
|
|
36
|
+
# @param name [String] The model name (e.g. +"all-MiniLM-L6-v2"+)
|
|
37
|
+
# @param version [String] The model version (e.g. +"1.0.0"+)
|
|
38
|
+
# @param format [String] The model format (default: +"TORCH_SCRIPT"+)
|
|
39
|
+
# @return [ML_INFO, nil] The registered model info, or +nil+ if lookup fails after registration
|
|
40
|
+
# @raise [RuntimeError] If the registration task reports a +FAILED+ state
|
|
41
|
+
# @see https://opensearch.org/docs/latest/ml-commons-plugin/api/model-apis/register-model/ OpenSearch register model API
|
|
42
|
+
# @example
|
|
43
|
+
# model = client.models.register(name: "all-MiniLM-L6-v2", version: "1.0.0")
|
|
44
|
+
# puts model.id
|
|
45
|
+
def register(name:, version:, format: "TORCH_SCRIPT")
|
|
46
|
+
config = {
|
|
47
|
+
name: name,
|
|
48
|
+
version: version,
|
|
49
|
+
model_format: format
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
current = self[name]
|
|
53
|
+
return current if current
|
|
54
|
+
|
|
55
|
+
resp = @os.http.post("/_plugins/_ml/models/_register?deploy=true", body: config)
|
|
56
|
+
taskid = resp["task_id"]
|
|
57
|
+
loop do
|
|
58
|
+
model_install_response = @os.http.get("_plugins/_ml/tasks/#{taskid}")
|
|
59
|
+
break if model_install_response["state"] == "COMPLETED"
|
|
60
|
+
raise model_install_response["error"].to_s if model_install_response["state"] == "FAILED"
|
|
61
|
+
sleep(5)
|
|
62
|
+
end
|
|
63
|
+
self[name]
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Alias for {#register}.
|
|
67
|
+
# @see #register
|
|
68
|
+
alias_method :deploy, :register
|
|
69
|
+
|
|
70
|
+
# Get info about the latest version of a model by name, id, or partial name
|
|
71
|
+
# @todo make sure models are unique by nickname if nickname is found
|
|
72
|
+
# @param id_or_fullname_or_nickname [String] Exact model name, exact model ID, or
|
|
73
|
+
# a case-insensitive partial name. When multiple partial matches exist, returns the
|
|
74
|
+
# one with the highest version.
|
|
75
|
+
# @return [ML_INFO, nil] Matching model info, or +nil+ if not found
|
|
76
|
+
# @example Look up by exact name
|
|
77
|
+
# model = client.models["all-MiniLM-L6-v2"]
|
|
78
|
+
# @example Look up by partial name
|
|
79
|
+
# model = client.models["MiniLM"]
|
|
80
|
+
def [](id_or_fullname_or_nickname)
|
|
81
|
+
mlm = list
|
|
82
|
+
name = mlm.find { |x| x.name == id_or_fullname_or_nickname }
|
|
83
|
+
return name if name
|
|
84
|
+
|
|
85
|
+
id = mlm.find { |m| m.id == id_or_fullname_or_nickname }
|
|
86
|
+
return id if id
|
|
87
|
+
|
|
88
|
+
nickname_pattern = Regexp.new(id_or_fullname_or_nickname, "i")
|
|
89
|
+
nicks = mlm.find_all { |m| nickname_pattern.match(m.name) }.sort { |a, b| b.version <=> a.version }
|
|
90
|
+
nicks.first # could be nil
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Returns all deployed ML models in the cluster.
|
|
94
|
+
#
|
|
95
|
+
# @return [Array<ML_INFO>] Unique name/version/id triples for all deployed models
|
|
96
|
+
# @example
|
|
97
|
+
# client.models.list.each { |m| puts "#{m.name} #{m.version} (#{m.id})" }
|
|
98
|
+
def list
|
|
99
|
+
lst = raw_list.dig("hits", "hits").map { |x| x["_source"] }.each_with_object([]) do |ml, a|
|
|
100
|
+
model = ML_INFO.new(ml["name"], ml["model_version"], ml["model_id"])
|
|
101
|
+
a << model
|
|
102
|
+
end
|
|
103
|
+
lst.uniq
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Returns the raw OpenSearch response from the ML models search endpoint.
|
|
107
|
+
#
|
|
108
|
+
# This is the unprocessed response from +/_plugins/_ml/models/_search+, filtered
|
|
109
|
+
# to chunk 0 to avoid returning embedding chunks. Prefer {#list} or {#[]} for
|
|
110
|
+
# normal usage.
|
|
111
|
+
#
|
|
112
|
+
# @return [Hash] Raw OpenSearch search response
|
|
113
|
+
def raw_list
|
|
114
|
+
@os.http.get("/_plugins/_ml/models/_search",
|
|
115
|
+
body: {"query" => {"term" => {"chunk_number" => 0}}})
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Undeploys (unloads from memory) a model without deleting its registration.
|
|
119
|
+
#
|
|
120
|
+
# @param name_or_id [String] Model name, ID, or partial name accepted by {#[]}
|
|
121
|
+
# @return [Hash] The OpenSearch undeploy response
|
|
122
|
+
# @raise [NoMethodError] If no model matching +name_or_id+ is found
|
|
123
|
+
# @example
|
|
124
|
+
# client.models.undeploy!("all-MiniLM-L6-v2")
|
|
125
|
+
def undeploy!(name_or_id)
|
|
126
|
+
m = self[name_or_id]
|
|
127
|
+
@os.http.post("/_plugins/_ml/models/#{m.id}/_undeploy")
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Undeploys and permanently deletes a model from the cluster.
|
|
131
|
+
#
|
|
132
|
+
# @param name_or_id [String] Model name, ID, or partial name accepted by {#[]}
|
|
133
|
+
# @return [Hash] The OpenSearch delete response
|
|
134
|
+
# @raise [NoMethodError] If no model matching +name_or_id+ is found
|
|
135
|
+
# @example
|
|
136
|
+
# client.models.delete!("all-MiniLM-L6-v2")
|
|
137
|
+
def delete!(name_or_id)
|
|
138
|
+
m = self[name_or_id]
|
|
139
|
+
undeploy!(m.id)
|
|
140
|
+
@os.http.delete("/_plugins/_ml/models/#{m.id}")
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Deletes an ingest pipeline by name.
|
|
144
|
+
#
|
|
145
|
+
# @param pipeline_name [String] The name of the pipeline to delete
|
|
146
|
+
# @return [Hash] The OpenSearch acknowledgement response
|
|
147
|
+
# @example
|
|
148
|
+
# client.models.delete_pipeline!("my-embedding-pipeline")
|
|
149
|
+
def delete_pipeline!(pipeline_name)
|
|
150
|
+
@os.ingest.delete_pipeline(id: pipeline_name)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Creates a text-embedding ingest pipeline backed by a deployed ML model.
|
|
154
|
+
#
|
|
155
|
+
# The pipeline uses the ML Commons +text_embedding+ processor to generate embeddings
|
|
156
|
+
# for each field in +field_map+. Because +text_embedding+ writes to a temporary field,
|
|
157
|
+
# the pipeline also inserts +copy+ processors to move the resulting +.knn+ vectors to
|
|
158
|
+
# the intended target fields.
|
|
159
|
+
#
|
|
160
|
+
# @param name [String] The name to give the ingest pipeline
|
|
161
|
+
# @param model [String] Model name, ID, or partial name accepted by {#[]}
|
|
162
|
+
# @param description [String] Human-readable description of the pipeline
|
|
163
|
+
# @param field_map [Hash{String => String}] Mapping of source text fields to target
|
|
164
|
+
# vector fields (e.g. +{ "title" => "title_embedding" }+)
|
|
165
|
+
# @return [Hash] The OpenSearch response
|
|
166
|
+
# @raise [RuntimeError] If no model matching +model+ is found
|
|
167
|
+
# @see https://opensearch.org/docs/latest/ml-commons-plugin/semantic-search/ OpenSearch semantic search docs
|
|
168
|
+
# @example
|
|
169
|
+
# client.models.create_pipeline(
|
|
170
|
+
# name: "product-embeddings",
|
|
171
|
+
# model: "all-MiniLM-L6-v2",
|
|
172
|
+
# description: "Generate embeddings for product titles",
|
|
173
|
+
# field_map: { "title" => "title_knn" }
|
|
174
|
+
# )
|
|
175
|
+
def create_pipeline(name:, model:, description:, field_map:)
|
|
176
|
+
m = self[model]
|
|
177
|
+
raise "Can't find model #{model}" unless m
|
|
178
|
+
url = "/_ingest/pipeline/#{name.gsub(/\s+/, " ").gsub(/\s+/, "_")}"
|
|
179
|
+
field_map_to_temp = field_map.transform_values { |actual_target| "#{actual_target}_temp" }
|
|
180
|
+
temp_to_field_map = field_map.values.each_with_object({}) do |actual_target, h|
|
|
181
|
+
h["#{actual_target}_temp"] = actual_target
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
payload = {
|
|
185
|
+
description: description,
|
|
186
|
+
processors: [
|
|
187
|
+
{
|
|
188
|
+
text_embedding: {
|
|
189
|
+
model_id: m.id,
|
|
190
|
+
field_map: field_map_to_temp
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
]
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
temp_to_field_map.each_pair do |tmp, real|
|
|
197
|
+
payload[:processors] << {
|
|
198
|
+
copy: {
|
|
199
|
+
source_field: "#{tmp}.knn",
|
|
200
|
+
target_field: real,
|
|
201
|
+
ignore_missing: true,
|
|
202
|
+
remove_source: true
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
end
|
|
206
|
+
@os.http.put(url, body: payload)
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OpenSearch
|
|
4
|
+
module Sugar
|
|
5
|
+
end
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
require "opensearch"
|
|
9
|
+
|
|
10
|
+
require_relative "sugar/version"
|
|
11
|
+
require_relative "sugar/index"
|
|
12
|
+
require_relative "sugar/client"
|
|
13
|
+
|
|
14
|
+
module OpenSearch
|
|
15
|
+
# Top-level namespace for the opensearch-sugar gem.
|
|
16
|
+
#
|
|
17
|
+
# Provides a thin, object-oriented wrapper around the {https://github.com/opensearch-project/opensearch-ruby opensearch-ruby}
|
|
18
|
+
# client. The main entry point is {OpenSearch::Sugar.client}, which returns a
|
|
19
|
+
# fully configured {OpenSearch::Sugar::Client}.
|
|
20
|
+
#
|
|
21
|
+
# @see OpenSearch::Sugar::Client
|
|
22
|
+
# @see OpenSearch::Sugar::Index
|
|
23
|
+
# @see OpenSearch::Sugar::Models
|
|
24
|
+
module Sugar
|
|
25
|
+
# Base error class for all opensearch-sugar exceptions.
|
|
26
|
+
#
|
|
27
|
+
# Raised when operations such as settings or mappings updates fail.
|
|
28
|
+
# Rescuing this class catches all gem-specific errors without catching
|
|
29
|
+
# unrelated OpenSearch transport-layer exceptions.
|
|
30
|
+
#
|
|
31
|
+
# @example
|
|
32
|
+
# begin
|
|
33
|
+
# client.update_settings(bad_settings, "my_index")
|
|
34
|
+
# rescue OpenSearch::Sugar::Error => e
|
|
35
|
+
# puts "Settings update failed: #{e.message}"
|
|
36
|
+
# end
|
|
37
|
+
class Error < StandardError; end
|
|
38
|
+
|
|
39
|
+
# Convenience factory that returns a new {OpenSearch::Sugar::Client}.
|
|
40
|
+
#
|
|
41
|
+
# Accepts the same keyword arguments as {OpenSearch::Sugar::Client#initialize}.
|
|
42
|
+
#
|
|
43
|
+
# @param kwargs [Hash] Keyword arguments forwarded to {OpenSearch::Sugar::Client#initialize}
|
|
44
|
+
# @return [OpenSearch::Sugar::Client]
|
|
45
|
+
# @example
|
|
46
|
+
# client = OpenSearch::Sugar.client(host: "https://localhost:9200")
|
|
47
|
+
def self.client(**kwargs)
|
|
48
|
+
OpenSearch::Sugar::Client.new(**kwargs)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Alias for {.client}. Allows idiomatic +OpenSearch::Sugar.new(...)+ construction.
|
|
52
|
+
#
|
|
53
|
+
# @param kwargs [Hash] Keyword arguments forwarded to {OpenSearch::Sugar::Client#initialize}
|
|
54
|
+
# @return [OpenSearch::Sugar::Client]
|
|
55
|
+
# @example
|
|
56
|
+
# client = OpenSearch::Sugar.new
|
|
57
|
+
def self.new(**kwargs)
|
|
58
|
+
client(**kwargs)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
# Delegated Method Calls in HOWTO.md
|
|
2
|
+
|
|
3
|
+
*(Documentation written by GitHub Copilot, powered by Claude Sonnet 4.5)*
|
|
4
|
+
|
|
5
|
+
This document identifies all method calls in HOWTO.md that rely on delegation to the underlying `OpenSearch::Client` rather than using OpenSearch::Sugar's own implemented methods.
|
|
6
|
+
|
|
7
|
+
## OpenSearch::Sugar Implemented Methods
|
|
8
|
+
|
|
9
|
+
### Client Methods (OpenSearch::Sugar::Client)
|
|
10
|
+
- `set_log_level(logger:, level:)`
|
|
11
|
+
- `has_index?(name)`
|
|
12
|
+
- `index_names`
|
|
13
|
+
- `[](index_name)` - index accessor
|
|
14
|
+
- `open_or_create(index_name)`
|
|
15
|
+
- `update_settings(settings, index_name)`
|
|
16
|
+
- `update_mappings(mappings, index_name)`
|
|
17
|
+
- `models` - accessor for Models instance
|
|
18
|
+
- `raw_client` - accessor for underlying client
|
|
19
|
+
|
|
20
|
+
### Index Methods (OpenSearch::Sugar::Index)
|
|
21
|
+
- `update_settings(settings)`
|
|
22
|
+
- `settings`
|
|
23
|
+
- `update_mappings(mappings)`
|
|
24
|
+
- `mappings`
|
|
25
|
+
- `delete!`
|
|
26
|
+
- `count`
|
|
27
|
+
- `aliases`
|
|
28
|
+
- `create_alias(alias_name)`
|
|
29
|
+
- `all_available_analyzers` / `analyzers`
|
|
30
|
+
- `analyze_text(analyzer:, text:)`
|
|
31
|
+
- `analyze_text_field(field:, text:)`
|
|
32
|
+
- `delete_by_id(id)`
|
|
33
|
+
- `clear!`
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Delegated Method Calls in HOWTO.md
|
|
38
|
+
|
|
39
|
+
### Connection and Configuration Section
|
|
40
|
+
|
|
41
|
+
**Line 94: `client.cluster.health`**
|
|
42
|
+
```ruby
|
|
43
|
+
response = client.cluster.health
|
|
44
|
+
```
|
|
45
|
+
- **Delegated to:** `OpenSearch::Client#cluster`
|
|
46
|
+
- **OpenSearch API:** [Cluster Health API](https://opensearch.org/docs/latest/api-reference/cluster-api/cluster-health/)
|
|
47
|
+
- **Purpose:** Get cluster health status
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
### Index Management Section
|
|
52
|
+
|
|
53
|
+
**Line 175: `client.indices.delete(index: 'my_index')`**
|
|
54
|
+
```ruby
|
|
55
|
+
client.indices.delete(index: 'my_index')
|
|
56
|
+
```
|
|
57
|
+
- **Delegated to:** `OpenSearch::Client#indices.delete`
|
|
58
|
+
- **OpenSearch API:** [Delete Index API](https://opensearch.org/docs/latest/api-reference/index-apis/delete-index/)
|
|
59
|
+
- **Note:** Sugar alternative exists: `index.delete!`
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
### Document Operations Section
|
|
64
|
+
|
|
65
|
+
**Line 196-203: `client.index(...)`**
|
|
66
|
+
```ruby
|
|
67
|
+
# Add with explicit ID
|
|
68
|
+
client.index(
|
|
69
|
+
index: 'my_index',
|
|
70
|
+
id: 'doc123',
|
|
71
|
+
body: {
|
|
72
|
+
title: 'My Document',
|
|
73
|
+
content: 'Document content here'
|
|
74
|
+
}
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
# Add with auto-generated ID
|
|
78
|
+
response = client.index(
|
|
79
|
+
index: 'my_index',
|
|
80
|
+
body: { title: 'My Document' }
|
|
81
|
+
)
|
|
82
|
+
```
|
|
83
|
+
- **Delegated to:** `OpenSearch::Client#index`
|
|
84
|
+
- **OpenSearch API:** [Index Document API](https://opensearch.org/docs/latest/api-reference/document-apis/index-document/)
|
|
85
|
+
- **Purpose:** Index a single document
|
|
86
|
+
|
|
87
|
+
**Line 220: `client.delete(index: 'my_index', id: 'doc123')`**
|
|
88
|
+
```ruby
|
|
89
|
+
client.delete(index: 'my_index', id: 'doc123')
|
|
90
|
+
```
|
|
91
|
+
- **Delegated to:** `OpenSearch::Client#delete`
|
|
92
|
+
- **OpenSearch API:** [Delete Document API](https://opensearch.org/docs/latest/api-reference/document-apis/delete-document/)
|
|
93
|
+
- **Note:** Sugar alternative exists: `index.delete_by_id('doc123')`
|
|
94
|
+
|
|
95
|
+
**Line 237-242: `client.get(...)`**
|
|
96
|
+
```ruby
|
|
97
|
+
response = client.get(
|
|
98
|
+
index: 'my_index',
|
|
99
|
+
id: 'doc123'
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
document = response['_source']
|
|
103
|
+
```
|
|
104
|
+
- **Delegated to:** `OpenSearch::Client#get`
|
|
105
|
+
- **OpenSearch API:** [Get Document API](https://opensearch.org/docs/latest/api-reference/document-apis/get-documents/)
|
|
106
|
+
- **Purpose:** Retrieve a document by ID
|
|
107
|
+
|
|
108
|
+
**Line 250-271: `client.update(...)`**
|
|
109
|
+
```ruby
|
|
110
|
+
# Partial update
|
|
111
|
+
client.update(
|
|
112
|
+
index: 'my_index',
|
|
113
|
+
id: 'doc123',
|
|
114
|
+
body: {
|
|
115
|
+
doc: {
|
|
116
|
+
title: 'Updated Title'
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
# Full replacement
|
|
122
|
+
client.index(
|
|
123
|
+
index: 'my_index',
|
|
124
|
+
id: 'doc123',
|
|
125
|
+
body: {
|
|
126
|
+
title: 'New Title',
|
|
127
|
+
content: 'New content'
|
|
128
|
+
}
|
|
129
|
+
)
|
|
130
|
+
```
|
|
131
|
+
- **Delegated to:** `OpenSearch::Client#update`
|
|
132
|
+
- **OpenSearch API:** [Update Document API](https://opensearch.org/docs/latest/api-reference/document-apis/update-document/)
|
|
133
|
+
- **Purpose:** Update an existing document
|
|
134
|
+
|
|
135
|
+
**Line 283: `client.bulk(body: operations)`**
|
|
136
|
+
```ruby
|
|
137
|
+
response = client.bulk(body: operations)
|
|
138
|
+
```
|
|
139
|
+
- **Delegated to:** `OpenSearch::Client#bulk`
|
|
140
|
+
- **OpenSearch API:** [Bulk API](https://opensearch.org/docs/latest/api-reference/document-apis/bulk/)
|
|
141
|
+
- **Purpose:** Perform multiple index/delete operations in a single request
|
|
142
|
+
|
|
143
|
+
**Line 301-304: `client.indices.refresh(...)`**
|
|
144
|
+
```ruby
|
|
145
|
+
# Make documents immediately searchable
|
|
146
|
+
client.indices.refresh(index: 'my_index')
|
|
147
|
+
|
|
148
|
+
# Refresh all indexes
|
|
149
|
+
client.indices.refresh
|
|
150
|
+
```
|
|
151
|
+
- **Delegated to:** `OpenSearch::Client#indices.refresh`
|
|
152
|
+
- **OpenSearch API:** [Refresh API](https://opensearch.org/docs/latest/api-reference/index-apis/refresh/)
|
|
153
|
+
- **Purpose:** Make recent changes searchable
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
### Search and Analysis Section
|
|
158
|
+
|
|
159
|
+
**Line 320-334: `client.search(...)`**
|
|
160
|
+
```ruby
|
|
161
|
+
response = client.search(
|
|
162
|
+
index: 'my_index',
|
|
163
|
+
body: {
|
|
164
|
+
query: {
|
|
165
|
+
match: {
|
|
166
|
+
title: 'search terms'
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
hits = response['hits']['hits']
|
|
173
|
+
```
|
|
174
|
+
- **Delegated to:** `OpenSearch::Client#search`
|
|
175
|
+
- **OpenSearch API:** [Search API](https://opensearch.org/docs/latest/api-reference/search/)
|
|
176
|
+
- **Purpose:** Execute a search query
|
|
177
|
+
|
|
178
|
+
**Line 391-406: `client.search(...)` with aggregations**
|
|
179
|
+
```ruby
|
|
180
|
+
response = client.search(
|
|
181
|
+
index: 'products',
|
|
182
|
+
body: {
|
|
183
|
+
size: 0,
|
|
184
|
+
aggs: {
|
|
185
|
+
categories: {
|
|
186
|
+
terms: {
|
|
187
|
+
field: 'category.keyword',
|
|
188
|
+
size: 10
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
)
|
|
194
|
+
```
|
|
195
|
+
- **Delegated to:** `OpenSearch::Client#search`
|
|
196
|
+
- **OpenSearch API:** [Search API with Aggregations](https://opensearch.org/docs/latest/aggregations/)
|
|
197
|
+
- **Purpose:** Perform aggregations
|
|
198
|
+
|
|
199
|
+
**Line 416-426: `client.search(...)` with multi_match**
|
|
200
|
+
```ruby
|
|
201
|
+
response = client.search(
|
|
202
|
+
index: 'my_index',
|
|
203
|
+
body: {
|
|
204
|
+
query: {
|
|
205
|
+
multi_match: {
|
|
206
|
+
query: 'search terms',
|
|
207
|
+
fields: ['title^3', 'description^2', 'content'],
|
|
208
|
+
type: 'best_fields'
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
)
|
|
213
|
+
```
|
|
214
|
+
- **Delegated to:** `OpenSearch::Client#search`
|
|
215
|
+
- **OpenSearch API:** [Multi-Match Query](https://opensearch.org/docs/latest/query-dsl/full-text/multi-match/)
|
|
216
|
+
- **Purpose:** Search across multiple fields
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
### Aliases Section
|
|
221
|
+
|
|
222
|
+
**Line 547-550: `client.indices.put_alias(...)`**
|
|
223
|
+
```ruby
|
|
224
|
+
client.indices.put_alias(
|
|
225
|
+
index: 'my_index',
|
|
226
|
+
name: 'my_alias'
|
|
227
|
+
)
|
|
228
|
+
```
|
|
229
|
+
- **Delegated to:** `OpenSearch::Client#indices.put_alias`
|
|
230
|
+
- **OpenSearch API:** [Create Alias API](https://opensearch.org/docs/latest/api-reference/alias/)
|
|
231
|
+
- **Note:** Sugar alternative exists: `index.create_alias('my_alias')`
|
|
232
|
+
|
|
233
|
+
**Line 564-567: `client.indices.delete_alias(...)`**
|
|
234
|
+
```ruby
|
|
235
|
+
client.indices.delete_alias(
|
|
236
|
+
index: 'my_index',
|
|
237
|
+
name: 'my_alias'
|
|
238
|
+
)
|
|
239
|
+
```
|
|
240
|
+
- **Delegated to:** `OpenSearch::Client#indices.delete_alias`
|
|
241
|
+
- **OpenSearch API:** [Delete Alias API](https://opensearch.org/docs/latest/api-reference/alias/)
|
|
242
|
+
- **Purpose:** Remove an alias
|
|
243
|
+
|
|
244
|
+
**Line 574-582: `client.indices.update_aliases(...)`**
|
|
245
|
+
```ruby
|
|
246
|
+
client.indices.update_aliases(
|
|
247
|
+
body: {
|
|
248
|
+
actions: [
|
|
249
|
+
{ remove: { index: 'old_index', alias: 'my_alias' } },
|
|
250
|
+
{ add: { index: 'new_index', alias: 'my_alias' } }
|
|
251
|
+
]
|
|
252
|
+
}
|
|
253
|
+
)
|
|
254
|
+
```
|
|
255
|
+
- **Delegated to:** `OpenSearch::Client#indices.update_aliases`
|
|
256
|
+
- **OpenSearch API:** [Update Aliases API](https://opensearch.org/docs/latest/api-reference/alias/)
|
|
257
|
+
- **Purpose:** Atomic alias operations (swap aliases)
|
|
258
|
+
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
### ML Models Section
|
|
262
|
+
|
|
263
|
+
**Line 652-660: `client.index(...)` with pipeline**
|
|
264
|
+
```ruby
|
|
265
|
+
client.index(
|
|
266
|
+
index: 'my_index',
|
|
267
|
+
pipeline: 'text_embedding',
|
|
268
|
+
body: {
|
|
269
|
+
text: 'This is my document text',
|
|
270
|
+
title: 'Document Title'
|
|
271
|
+
}
|
|
272
|
+
)
|
|
273
|
+
```
|
|
274
|
+
- **Delegated to:** `OpenSearch::Client#index`
|
|
275
|
+
- **OpenSearch API:** [Index with Pipeline](https://opensearch.org/docs/latest/ingest-pipelines/)
|
|
276
|
+
- **Purpose:** Index document using an ingest pipeline
|
|
277
|
+
|
|
278
|
+
---
|
|
279
|
+
|
|
280
|
+
### Error Handling Section
|
|
281
|
+
|
|
282
|
+
**Line 722: `client.bulk(body: operations)`**
|
|
283
|
+
```ruby
|
|
284
|
+
response = client.bulk(body: operations)
|
|
285
|
+
```
|
|
286
|
+
- **Delegated to:** `OpenSearch::Client#bulk`
|
|
287
|
+
- **OpenSearch API:** [Bulk API](https://opensearch.org/docs/latest/api-reference/document-apis/bulk/)
|
|
288
|
+
- **Purpose:** Bulk operations error handling example
|
|
289
|
+
|
|
290
|
+
**Line 769-774: `client.index(...)` in retry example**
|
|
291
|
+
```ruby
|
|
292
|
+
client.index(
|
|
293
|
+
index: 'my_index',
|
|
294
|
+
id: 'doc123',
|
|
295
|
+
body: { title: 'My Document' }
|
|
296
|
+
)
|
|
297
|
+
```
|
|
298
|
+
- **Delegated to:** `OpenSearch::Client#index`
|
|
299
|
+
- **OpenSearch API:** [Index Document API](https://opensearch.org/docs/latest/api-reference/document-apis/index-document/)
|
|
300
|
+
- **Purpose:** Retry pattern example
|
|
301
|
+
|
|
302
|
+
---
|
|
303
|
+
|
|
304
|
+
## Summary Statistics
|
|
305
|
+
|
|
306
|
+
### Total Delegated Method Calls: 19 unique examples
|
|
307
|
+
|
|
308
|
+
### Breakdown by Category:
|
|
309
|
+
- **Connection & Configuration:** 1
|
|
310
|
+
- **Index Management:** 1
|
|
311
|
+
- **Document Operations:** 7
|
|
312
|
+
- **Search & Analysis:** 3
|
|
313
|
+
- **Aliases:** 3
|
|
314
|
+
- **ML Models:** 1
|
|
315
|
+
- **Error Handling:** 2 (duplicates of above)
|
|
316
|
+
|
|
317
|
+
### Most Common Delegated Methods:
|
|
318
|
+
1. `client.index(...)` - 5 occurrences (indexing documents)
|
|
319
|
+
2. `client.search(...)` - 3 occurrences (searching)
|
|
320
|
+
3. `client.bulk(...)` - 2 occurrences (bulk operations)
|
|
321
|
+
4. `client.indices.*` - 6 occurrences (various index operations)
|
|
322
|
+
5. `client.cluster.*` - 1 occurrence (cluster health)
|
|
323
|
+
6. `client.get(...)` - 1 occurrence (get document)
|
|
324
|
+
7. `client.update(...)` - 1 occurrence (update document)
|
|
325
|
+
8. `client.delete(...)` - 1 occurrence (delete document)
|
|
326
|
+
|
|
327
|
+
### Methods with Sugar Alternatives Available:
|
|
328
|
+
- `client.indices.delete(index: 'my_index')` → `index.delete!`
|
|
329
|
+
- `client.delete(index: 'my_index', id: 'doc123')` → `index.delete_by_id('doc123')`
|
|
330
|
+
- `client.indices.put_alias(...)` → `index.create_alias('my_alias')`
|
|
331
|
+
|
|
332
|
+
### Methods Without Sugar Alternatives (Must Use Delegation):
|
|
333
|
+
- `client.index(...)` - Document indexing
|
|
334
|
+
- `client.get(...)` - Get document by ID
|
|
335
|
+
- `client.update(...)` - Update document
|
|
336
|
+
- `client.search(...)` - Search queries
|
|
337
|
+
- `client.bulk(...)` - Bulk operations
|
|
338
|
+
- `client.indices.refresh(...)` - Refresh index
|
|
339
|
+
- `client.indices.delete_alias(...)` - Delete alias
|
|
340
|
+
- `client.indices.update_aliases(...)` - Atomic alias updates
|
|
341
|
+
- `client.cluster.health` - Cluster health
|
|
342
|
+
|
|
343
|
+
---
|
|
344
|
+
|
|
345
|
+
## Recommendation
|
|
346
|
+
|
|
347
|
+
The documentation correctly demonstrates the delegation pattern by showing:
|
|
348
|
+
1. **When Sugar methods exist** - it generally uses them (like `index.count`, `index.delete!`, `index.create_alias`)
|
|
349
|
+
2. **When Sugar methods don't exist** - it demonstrates delegation (like `client.search`, `client.index`, `client.bulk`)
|
|
350
|
+
3. **Both options when available** - showing users they have choices
|
|
351
|
+
|
|
352
|
+
This aligns with OpenSearch::Sugar's design philosophy: "Use sugar where you want it, raw client where you need it."
|
|
353
|
+
|
|
354
|
+
The documentation could be improved by adding a note in each delegated example indicating:
|
|
355
|
+
```ruby
|
|
356
|
+
# Using delegation - OpenSearch::Sugar::Client forwards this to OpenSearch::Client
|
|
357
|
+
client.search(...)
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
This would make it clearer to users which methods are delegated vs. implemented.
|
|
361
|
+
|