patient_llm 0.2.0 → 0.3.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: c5a3fc235def2976b4a57107239ab72e51a50a2b3abdfed579c0c0d4ea6f681d
4
+ data.tar.gz: 77b63683fc706598ab29e2fa710d9d6650c568712301e1ff4d87bd8a50668228
5
5
  SHA512:
6
- metadata.gz: edfb6fc58609f0b7b7733b9e5729877318f2bbe7bdec15a7aeddff05f0a1d75f1c8c47683a3431154a5267e8f5099c1a0271b4520cc3b56fec4df9e1b3228048
7
- data.tar.gz: 5075e4c627595ee0d99795726a0071378e6529ca2db9ac11228740781e8e0764d44e44fa624156c30ce4675aa85978f6bdaf93d4693e8068e5305791413cadbf
6
+ metadata.gz: e43ffc0679e0ab348e8462884042ba538d734101acfb5b7ecb5e4058167dc3fdd7aa50ad953a091695faae5ad1ce4c25ff1b13614c2ac553587b3568115e8f9a
7
+ data.tar.gz: 034554bf031c5696ddb2bf877463f9cbcf5e57e32cdc39563975de22f0ea6cde189be72cfe053ea1338144b320254196ed51589ded11ba753161e4af544de3e4
data/CHANGELOG.md CHANGED
@@ -4,6 +4,13 @@ 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.3.0
8
+
9
+ ### Changed
10
+
11
+ - 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.
12
+ - 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.
13
+
7
14
  ## 0.2.0
8
15
 
9
16
  ### Added
data/README.md CHANGED
@@ -185,9 +185,9 @@ session.max_output_tokens = 1000
185
185
  PatientLLM.ask(session,
186
186
  provider: :openai,
187
187
  callback: LLMCallback,
188
- url: "http://localhost:1234", # Override the provider's base URL
188
+ url: "http://localhost:1234", # Override the provider's base URL
189
189
  serializer: :messages, # Override the API format
190
- completion_path: "/chat/completions", # Override the endpoint path
190
+ path: "/chat/completions", # Override the endpoint path
191
191
  headers: {"X-Custom" => "value"}, # Additional HTTP headers
192
192
  params: {max_completion_tokens: 1000} # Additional request parameters
193
193
  )
@@ -195,24 +195,24 @@ PatientLLM.ask(session,
195
195
 
196
196
  ### URL composition
197
197
 
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:
198
+ 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
199
 
200
200
  ```
201
- url = "https://api.openai.com" completion_path = "/v1/chat/completions"
201
+ url = "https://api.openai.com" path = "/v1/chat/completions"
202
202
  -> https://api.openai.com/v1/chat/completions
203
203
 
204
- url = "http://localhost:1234" completion_path = "/v1/chat/completions"
204
+ url = "http://localhost:1234" path = "/v1/chat/completions"
205
205
  -> http://localhost:1234/v1/chat/completions
206
206
  ```
207
207
 
208
- If your base URL already includes a `/v1` prefix, override the completion path to avoid duplication:
208
+ If your base URL already includes a `/v1` prefix, override the path to avoid duplication:
209
209
 
210
210
  ```ruby
211
211
  PatientLLM.ask(session,
212
212
  provider: :openai,
213
213
  callback: LLMCallback,
214
214
  url: "https://my-gateway.internal/openai/v1",
215
- completion_path: "/chat/completions"
215
+ path: "chat/completions"
216
216
  )
217
217
  ```
218
218
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.0
1
+ 0.3.0
@@ -213,7 +213,7 @@ 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
219
 
@@ -26,10 +26,16 @@ 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
31
32
  # @return [void]
32
- def provider(name, url:, headers: {}, serializer: :chat_completion, completion_path: nil, params: {})
33
+ def provider(name, url:, headers: {}, serializer: :chat_completion, path: nil, completion_path: nil, params: {})
34
+ if completion_path
35
+ warn "PatientLLM::Configuration#provider: the `completion_path:` argument is deprecated; use `path:` instead", uplevel: 1
36
+ path ||= completion_path
37
+ end
38
+
33
39
  sym = serializer.to_sym
34
40
  unless PatientLLM::VALID_SERIALIZERS.include?(sym)
35
41
  raise ArgumentError, "Unknown serializer: #{sym.inspect}. Valid options: #{PatientLLM::VALID_SERIALIZERS.map(&:inspect).join(", ")}"
@@ -41,7 +47,7 @@ module PatientLLM
41
47
  url: url,
42
48
  headers: headers,
43
49
  serializer: sym,
44
- completion_path: completion_path,
50
+ path: path,
45
51
  params: params
46
52
  }
47
53
  end
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,17 @@ 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
68
70
  # @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
71
+ def ask(session, provider:, callback:, callback_args: {}, url: nil, serializer: nil, path: nil, completion_path: nil, headers: nil, params: nil, tool_iteration: 0, original_request_id: nil) # :nodoc: tool_iteration and original_request_id are internal
72
+ if completion_path
73
+ warn "PatientLLM.ask: the `completion_path:` argument is deprecated; use `path:` instead", uplevel: 1
74
+ path ||= completion_path
75
+ end
76
+
70
77
  provider_config = self.provider(provider) || {}
71
78
  provider_name = provider.to_s
72
79
 
@@ -79,9 +86,9 @@ module PatientLLM
79
86
 
80
87
  resolved_serializer = (serializer || provider_config[:serializer] || :chat_completion).to_sym
81
88
  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)
89
+ resolved_path = path || provider_config[:path] || SERIALIZER_PATHS[resolved_serializer] || "/v1/chat/completions"
90
+ if resolved_path.include?("{model}")
91
+ resolved_path = resolved_path.gsub("{model}", session.model.to_s)
85
92
  end
86
93
  resolved_headers = (provider_config[:headers] || {}).merge(headers || {})
87
94
  if resolved_serializer == :messages && !resolved_headers.key?("anthropic-version")
@@ -92,12 +99,12 @@ module PatientLLM
92
99
  payload = session.request_payload(resolved_serializer)
93
100
  payload = deep_merge(payload, deep_stringify_keys(resolved_params)) unless resolved_params.empty?
94
101
 
95
- request_url = join_url(resolved_url, resolved_completion_path)
102
+ request_url = join_url(resolved_url, resolved_path)
96
103
 
97
104
  request_options = {}
98
105
  request_options["url"] = url if url
99
106
  request_options["serializer"] = serializer.to_s if serializer
100
- request_options["completion_path"] = completion_path if completion_path
107
+ request_options["path"] = path if path
101
108
  request_options["headers"] = headers if headers && !headers.empty?
102
109
  request_options["params"] = params if params && !params.empty?
103
110
 
@@ -128,7 +135,9 @@ module PatientLLM
128
135
  end
129
136
 
130
137
  def join_url(base, path)
131
- "#{base.sub(%r{/\z}, "")}/#{path.to_s.sub(%r{\A/}, "")}"
138
+ base_uri = URI.parse(base)
139
+ base_uri.path = "#{base_uri.path}/" unless base_uri.path&.end_with?("/")
140
+ URI.join(base_uri, path).to_s
132
141
  end
133
142
 
134
143
  def deep_merge(hash1, hash2)
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.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brian Durand