patient_llm 0.2.0 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7ba366158096a79e3180cb63c1a03741d88536fb787b42852f2bcb5dc61b0181
4
- data.tar.gz: e507e79956d2e3534c5de85646bbfb0973c7253cb4db266ac4c721cea9201cf0
3
+ metadata.gz: 87bcd18afe8c5a4c46e191946f51201e077b355d223ac9d1817bd4041041143f
4
+ data.tar.gz: 51a67045f05fc5f55ec1aee9391006eecf60170c3d3e47f3248d2169046ffa0e
5
5
  SHA512:
6
- metadata.gz: edfb6fc58609f0b7b7733b9e5729877318f2bbe7bdec15a7aeddff05f0a1d75f1c8c47683a3431154a5267e8f5099c1a0271b4520cc3b56fec4df9e1b3228048
7
- data.tar.gz: 5075e4c627595ee0d99795726a0071378e6529ca2db9ac11228740781e8e0764d44e44fa624156c30ce4675aa85978f6bdaf93d4693e8068e5305791413cadbf
6
+ metadata.gz: 594c2deb985655a5396c587e6bce19f603f2f2ae9af3c6df1d8f268f5ac81d9a2542bedeed966f84700ed6f971cc1e1916bf0be3f217397fd07840a794a1f005
7
+ data.tar.gz: 2e4d20a58e2ce23bb882c1035a98aca401644b85171661b039aa1bc0b7ac5a55a207095ca5facf54723e6ff6baa06e39e752948411dbf3100183bcb041c082f5
data/CHANGELOG.md CHANGED
@@ -4,6 +4,23 @@ All notable changes to this project will be documented in this file.
4
4
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
5
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## 0.4.0
8
+
9
+ ### Added
10
+
11
+ - Added `preprocessors:` option to provider configuration and `PatientLLM.ask` for applying named PatientHttp request preprocessors (e.g., AWS SigV4 request signing) at dispatch time. A per-request value replaces the provider default; pass an empty array to clear it for a single request.
12
+
13
+ ### Changed
14
+
15
+ - Requires patient_http version 1.2 or higher.
16
+
17
+ ## 0.3.0
18
+
19
+ ### Changed
20
+
21
+ - Renamed the `completion_path` argument to `path` on both `PatientLLM.ask` and provider configuration. The `completion_path` name is still accepted as a deprecated alias and now emits a deprecation warning.
22
+ - The path is now joined with the base URL using `URI.join` to ensure proper handling of relative paths in the `path` argument. If the path starts with a slash, it will be treated as an absolute path and will replace the base URL's path. If it does not start with a slash, it will be treated as a relative path and will be appended to the base URL's path.
23
+
7
24
  ## 0.2.0
8
25
 
9
26
  ### Added
data/README.md CHANGED
@@ -52,6 +52,38 @@ end
52
52
  > [!NOTE]
53
53
  > You can also set up encryption for your job payloads to ensure the entire serialized payload is always encrypted in the job queue. See the documentation for [patient_http-sidekiq](https://github.com/bdurand/patient_http-sidekiq#sensitive-data-handling) or [patient_http-solid_queue](https://github.com/bdurand/patient_http-solid_queue#sensitive-data-handling) for details.
54
54
 
55
+ ### Request signing (preprocessors)
56
+
57
+ Some providers require request signing rather than a static authentication header — for example, AWS Bedrock with SigV4, where a signature is computed over the final outgoing request. For these, register a [request preprocessor](https://github.com/bdurand/patient_http#request-preprocessors) on the PatientHttp configuration and reference it by name from the provider. Like secrets, only the preprocessor name is serialized into the job queue; the signing logic and credentials stay on the processor side.
58
+
59
+ ```ruby
60
+ PatientLLM.configure do |config|
61
+ config.provider :bedrock,
62
+ url: "https://bedrock-runtime.us-east-1.amazonaws.com",
63
+ serializer: :converse,
64
+ preprocessors: :aws_sigv4
65
+ end
66
+
67
+ PatientHttp::Sidekiq.configure do |config|
68
+ config.register_preprocessor(:aws_sigv4) do |request|
69
+ signer = Aws::Sigv4::Signer.new(
70
+ service: "bedrock",
71
+ region: "us-east-1",
72
+ credentials_provider: Aws::CredentialProviderChain.new.resolve
73
+ )
74
+ signature = signer.sign_request(
75
+ http_method: request.http_method.to_s.upcase,
76
+ url: request.url,
77
+ headers: request.headers.to_h,
78
+ body: request.body.to_s
79
+ )
80
+ signature.headers.each { |name, value| request.headers[name] = value }
81
+ end
82
+ end
83
+ ```
84
+
85
+ Multiple preprocessors can be given as an array; they run in order at dispatch time.
86
+
55
87
  ### Creating a Callback Class
56
88
 
57
89
  Create a callback class with `on_complete` and `on_error` methods. Callbacks receive
@@ -185,34 +217,37 @@ session.max_output_tokens = 1000
185
217
  PatientLLM.ask(session,
186
218
  provider: :openai,
187
219
  callback: LLMCallback,
188
- url: "http://localhost:1234", # Override the provider's base URL
220
+ url: "http://localhost:1234", # Override the provider's base URL
189
221
  serializer: :messages, # Override the API format
190
- completion_path: "/chat/completions", # Override the endpoint path
222
+ path: "/chat/completions", # Override the endpoint path
191
223
  headers: {"X-Custom" => "value"}, # Additional HTTP headers
192
- params: {max_completion_tokens: 1000} # Additional request parameters
224
+ params: {max_completion_tokens: 1000}, # Additional request parameters
225
+ preprocessors: :aws_sigv4 # Replace the provider's request preprocessors
193
226
  )
194
227
  ```
195
228
 
229
+ Note that `headers` and `params` are merged on top of the provider's configured values, while `preprocessors` (like `url`, `serializer`, and `path`) replaces the provider default. Pass `preprocessors: []` to clear a provider-level preprocessor for a single request.
230
+
196
231
  ### URL composition
197
232
 
198
- The full request URL is built by concatenating the base URL (from the provider registry or the `url:` option) with the `completion_path`. When you don't set `completion_path`, it defaults to the path for the active serializer (`/v1/chat/completions` for `:chat_completion`, `/v1/responses` for `:open_responses`, `/v1/messages` for `:messages`, `/converse` for `:converse`, `/v1beta/models/{model}:generateContent` for `:gemini`). A `{model}` placeholder in the path is replaced with the session's model at dispatch time, which is how the Gemini default targets Google's `/v1beta/models/{model}:generateContent` endpoint. Trailing slashes on the base and leading slashes on the path are normalized, so:
233
+ The full request URL is built by concatenating the base URL (from the provider registry or the `url:` option) with the `path`. When you don't set `path`, it defaults to the path for the active serializer (`/v1/chat/completions` for `:chat_completion`, `/v1/responses` for `:open_responses`, `/v1/messages` for `:messages`, `/converse` for `:converse`, `/v1beta/models/{model}:generateContent` for `:gemini`). A `{model}` placeholder in the path is replaced with the session's model at dispatch time, which is how the Gemini default targets Google's `/v1beta/models/{model}:generateContent` endpoint. Trailing slashes on the base and leading slashes on the path are normalized, so:
199
234
 
200
235
  ```
201
- url = "https://api.openai.com" completion_path = "/v1/chat/completions"
236
+ url = "https://api.openai.com" path = "/v1/chat/completions"
202
237
  -> https://api.openai.com/v1/chat/completions
203
238
 
204
- url = "http://localhost:1234" completion_path = "/v1/chat/completions"
239
+ url = "http://localhost:1234" path = "/v1/chat/completions"
205
240
  -> http://localhost:1234/v1/chat/completions
206
241
  ```
207
242
 
208
- If your base URL already includes a `/v1` prefix, override the completion path to avoid duplication:
243
+ If your base URL already includes a `/v1` prefix, override the path to avoid duplication:
209
244
 
210
245
  ```ruby
211
246
  PatientLLM.ask(session,
212
247
  provider: :openai,
213
248
  callback: LLMCallback,
214
249
  url: "https://my-gateway.internal/openai/v1",
215
- completion_path: "/chat/completions"
250
+ path: "chat/completions"
216
251
  )
217
252
  ```
218
253
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.0
1
+ 0.4.0
@@ -213,9 +213,10 @@ module PatientLLM
213
213
  # Restore per-request overrides
214
214
  ask_kwargs[:url] = request_options["url"] if request_options["url"]
215
215
  ask_kwargs[:serializer] = request_options["serializer"].to_sym if request_options["serializer"]
216
- ask_kwargs[:completion_path] = request_options["completion_path"] if request_options["completion_path"]
216
+ ask_kwargs[:path] = request_options["path"] if request_options["path"]
217
217
  ask_kwargs[:headers] = request_options["headers"] if request_options["headers"]
218
218
  ask_kwargs[:params] = request_options["params"] if request_options["params"]
219
+ ask_kwargs[:preprocessors] = request_options["preprocessors"] if request_options["preprocessors"]
219
220
 
220
221
  PatientLLM.ask(session, **ask_kwargs)
221
222
  end
@@ -26,10 +26,19 @@ module PatientLLM
26
26
  # @param url [String] Base URL for the provider API
27
27
  # @param headers [Hash] Default headers for requests
28
28
  # @param serializer [Symbol] API format (:chat_completion, :open_responses, :messages, :converse, :gemini)
29
- # @param completion_path [String, nil] Override the default endpoint path
29
+ # @param path [String, nil] Override the default endpoint path
30
+ # @param completion_path [String, nil] Deprecated alias for +path+
30
31
  # @param params [Hash] Additional parameters to merge into every request payload
32
+ # @param preprocessors [String, Symbol, Array<String, Symbol>, nil] Names of request
33
+ # preprocessors to apply to every request (e.g. for request signing). Names must be
34
+ # registered on the PatientHttp configuration with `register_preprocessor`.
31
35
  # @return [void]
32
- def provider(name, url:, headers: {}, serializer: :chat_completion, completion_path: nil, params: {})
36
+ def provider(name, url:, headers: {}, serializer: :chat_completion, path: nil, completion_path: nil, params: {}, preprocessors: nil)
37
+ if completion_path
38
+ warn "PatientLLM::Configuration#provider: the `completion_path:` argument is deprecated; use `path:` instead", uplevel: 1
39
+ path ||= completion_path
40
+ end
41
+
33
42
  sym = serializer.to_sym
34
43
  unless PatientLLM::VALID_SERIALIZERS.include?(sym)
35
44
  raise ArgumentError, "Unknown serializer: #{sym.inspect}. Valid options: #{PatientLLM::VALID_SERIALIZERS.map(&:inspect).join(", ")}"
@@ -41,8 +50,9 @@ module PatientLLM
41
50
  url: url,
42
51
  headers: headers,
43
52
  serializer: sym,
44
- completion_path: completion_path,
45
- params: params
53
+ path: path,
54
+ params: params,
55
+ preprocessors: preprocessors
46
56
  }
47
57
  end
48
58
 
data/lib/patient_llm.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "patient_http"
4
4
  require "prompt_builder"
5
+ require "uri"
5
6
 
6
7
  module PatientLLM
7
8
  VERSION = File.read(File.join(__dir__, "../VERSION")).strip
@@ -16,11 +17,11 @@ module PatientLLM
16
17
  # dispatch time, matching Google's `/v1beta/models/{model}:generateContent`
17
18
  # endpoint.
18
19
  SERIALIZER_PATHS = {
19
- chat_completion: "/v1/chat/completions",
20
- open_responses: "/v1/responses",
21
- messages: "/v1/messages",
22
- converse: "/converse",
23
- gemini: "/v1beta/models/{model}:generateContent"
20
+ chat_completion: "v1/chat/completions",
21
+ open_responses: "v1/responses",
22
+ messages: "v1/messages",
23
+ converse: "converse",
24
+ gemini: "v1beta/models/{model}:generateContent"
24
25
  }.freeze
25
26
 
26
27
  # Required version header for the Anthropic Messages API.
@@ -62,11 +63,20 @@ module PatientLLM
62
63
  # @param callback_args [Hash] Custom arguments passed through to the callback
63
64
  # @param url [String, nil] Override the provider's base URL for this request
64
65
  # @param serializer [Symbol, nil] Override the provider's serializer for this request
65
- # @param completion_path [String, nil] Override the endpoint path for this request
66
+ # @param path [String, nil] Override the endpoint path for this request
67
+ # @param completion_path [String, nil] Deprecated alias for +path+
66
68
  # @param headers [Hash, nil] Additional headers merged on top of provider headers
67
69
  # @param params [Hash, nil] Additional params merged into the request payload
70
+ # @param preprocessors [String, Symbol, Array<String, Symbol>, nil] Names of request
71
+ # preprocessors to apply to this request. Replaces the provider's configured
72
+ # preprocessors; pass an empty array to clear them for this request.
68
73
  # @return [Object] Handler-specific identifier for the enqueued request
69
- def ask(session, provider:, callback:, callback_args: {}, url: nil, serializer: nil, completion_path: nil, headers: nil, params: nil, tool_iteration: 0, original_request_id: nil) # :nodoc: tool_iteration and original_request_id are internal
74
+ def ask(session, provider:, callback:, callback_args: {}, url: nil, serializer: nil, path: nil, completion_path: nil, headers: nil, params: nil, preprocessors: nil, tool_iteration: 0, original_request_id: nil) # :nodoc: tool_iteration and original_request_id are internal
75
+ if completion_path
76
+ warn "PatientLLM.ask: the `completion_path:` argument is deprecated; use `path:` instead", uplevel: 1
77
+ path ||= completion_path
78
+ end
79
+
70
80
  provider_config = self.provider(provider) || {}
71
81
  provider_name = provider.to_s
72
82
 
@@ -79,32 +89,35 @@ module PatientLLM
79
89
 
80
90
  resolved_serializer = (serializer || provider_config[:serializer] || :chat_completion).to_sym
81
91
  validate_serializer!(resolved_serializer)
82
- resolved_completion_path = completion_path || provider_config[:completion_path] || SERIALIZER_PATHS[resolved_serializer] || "/v1/chat/completions"
83
- if resolved_completion_path.include?("{model}")
84
- resolved_completion_path = resolved_completion_path.gsub("{model}", session.model.to_s)
92
+ resolved_path = path || provider_config[:path] || SERIALIZER_PATHS[resolved_serializer] || "/v1/chat/completions"
93
+ if resolved_path.include?("{model}")
94
+ resolved_path = resolved_path.gsub("{model}", session.model.to_s)
85
95
  end
86
96
  resolved_headers = (provider_config[:headers] || {}).merge(headers || {})
87
97
  if resolved_serializer == :messages && !resolved_headers.key?("anthropic-version")
88
98
  resolved_headers = {"anthropic-version" => ANTHROPIC_VERSION}.merge(resolved_headers)
89
99
  end
90
100
  resolved_params = (provider_config[:params] || {}).merge(params || {})
101
+ resolved_preprocessors = preprocessors || provider_config[:preprocessors]
91
102
 
92
103
  payload = session.request_payload(resolved_serializer)
93
104
  payload = deep_merge(payload, deep_stringify_keys(resolved_params)) unless resolved_params.empty?
94
105
 
95
- request_url = join_url(resolved_url, resolved_completion_path)
106
+ request_url = join_url(resolved_url, resolved_path)
96
107
 
97
108
  request_options = {}
98
109
  request_options["url"] = url if url
99
110
  request_options["serializer"] = serializer.to_s if serializer
100
- request_options["completion_path"] = completion_path if completion_path
111
+ request_options["path"] = path if path
101
112
  request_options["headers"] = headers if headers && !headers.empty?
102
113
  request_options["params"] = params if params && !params.empty?
114
+ request_options["preprocessors"] = preprocessors if preprocessors
103
115
 
104
116
  PatientHttp.post(
105
117
  request_url,
106
118
  json: payload,
107
119
  headers: resolved_headers,
120
+ preprocessors: resolved_preprocessors,
108
121
  raise_error_responses: true,
109
122
  callback: PatientLLM::Callback,
110
123
  callback_args: {
@@ -128,7 +141,9 @@ module PatientLLM
128
141
  end
129
142
 
130
143
  def join_url(base, path)
131
- "#{base.sub(%r{/\z}, "")}/#{path.to_s.sub(%r{\A/}, "")}"
144
+ base_uri = URI.parse(base)
145
+ base_uri.path = "#{base_uri.path}/" unless base_uri.path&.end_with?("/")
146
+ URI.join(base_uri, path).to_s
132
147
  end
133
148
 
134
149
  def deep_merge(hash1, hash2)
data/patient_llm.gemspec CHANGED
@@ -37,7 +37,7 @@ Gem::Specification.new do |spec|
37
37
 
38
38
  spec.required_ruby_version = ">= 3.0"
39
39
 
40
- spec.add_dependency "patient_http", "~> 1.1"
40
+ spec.add_dependency "patient_http", "~> 1.2"
41
41
  spec.add_dependency "prompt_builder"
42
42
 
43
43
  spec.add_development_dependency "bundler"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: patient_llm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brian Durand
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: '1.1'
18
+ version: '1.2'
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
- version: '1.1'
25
+ version: '1.2'
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: prompt_builder
28
28
  requirement: !ruby/object:Gem::Requirement