Module: PatientLLM
- Defined in:
- lib/patient_llm.rb,
lib/patient_llm/agent.rb,
lib/patient_llm/schema.rb,
lib/patient_llm/presets.rb,
lib/patient_llm/callback.rb,
lib/patient_llm/halt_error.rb,
lib/patient_llm/agent/failure.rb,
lib/patient_llm/configuration.rb,
lib/patient_llm/agent/response.rb,
lib/patient_llm/request_preview.rb,
lib/patient_llm/aws_request_signer.rb,
lib/patient_llm/structured_output_error.rb,
lib/patient_llm/max_tool_iterations_error.rb
Defined Under Namespace
Modules: Presets Classes: Agent, AwsRequestSigner, Callback, Configuration, HaltError, MaxToolIterationsError, RequestPreview, Schema, StructuredOutputError
Constant Summary collapse
- VERSION =
File.read(File.join(__dir__, "../VERSION")).strip
- SERIALIZER_PATHS =
Default API paths per serializer format. The Bedrock Converse and Gemini paths embed a
{model}placeholder that is replaced with the session's model (percent-encoded as a single path segment) at dispatch time. { chat_completion: "v1/chat/completions", open_responses: "v1/responses", messages: "v1/messages", converse: "model/{model}/converse", gemini: "v1beta/models/{model}:generateContent" }.freeze
- ANTHROPIC_VERSION =
Required version header for the Anthropic Messages API.
"2023-06-01"- VALID_SERIALIZERS =
Valid serializer format names.
SERIALIZER_PATHS.keys.freeze
- SESSION_REF_KEY =
Key used in callback args to reference a session stored in a payload store.
"$session_ref"- AUTHENTICATION_HEADERS =
Headers that must be setup to use the secrets manager. If any of these headers are included in the provider configuration, an error will be raised unless their values are set up as secrets using
PatientHttp.secret. ["authorization", "x-api-key", "x-goog-api-key", "api-key"].freeze
Class Attribute Summary collapse
-
.configuration ⇒ Configuration?
readonly
The current configuration.
Class Method Summary collapse
-
.ask(session, provider:, callback:, callback_args: {}, url: nil, serializer: nil, path: nil, headers: nil, params: nil, preprocessors: nil, timeout: nil, max_tool_iterations: nil) ⇒ Object
Send an LLM request asynchronously using the given session and provider.
-
.configure {|Configuration| ... } ⇒ void
Configure providers for LLM requests.
-
.dispatch(session, provider:, callback:, callback_args:, request_options:, tool_iteration: 0, original_request_id: nil) ⇒ Object
private
Internal dispatch used by PatientLLM.ask and by Callback to re-issue requests during the automatic tool loop.
-
.inline { ... } ⇒ Object
Execute requests inline (synchronously, in-process) for the duration of the block instead of dispatching through the registered PatientHttp handler.
-
.inline? ⇒ Boolean
Check if requests are currently executing inline via PatientLLM.inline.
-
.preview_request(session, provider:, url: nil, serializer: nil, path: nil, headers: nil, params: nil, preprocessors: nil, timeout: nil, max_tool_iterations: nil) ⇒ RequestPreview
Build the request that PatientLLM.ask would send without sending it.
-
.provider(name) ⇒ Hash?
Look up a registered provider by name.
-
.reset! ⇒ void
Reset configuration.
-
.verify_configuration! ⇒ true
Verify that the configuration is fully wired: a PatientHttp request handler is registered, every secret referenced by a provider is registered, and every preprocessor referenced by a provider is registered (when a default PatientHttp configuration is available to check against).
Class Attribute Details
.configuration ⇒ Configuration? (readonly)
The current configuration.
56 57 58 |
# File 'lib/patient_llm.rb', line 56 def configuration @configuration end |
Class Method Details
.ask(session, provider:, callback:, callback_args: {}, url: nil, serializer: nil, path: nil, headers: nil, params: nil, preprocessors: nil, timeout: nil, max_tool_iterations: nil) ⇒ Object
Send an LLM request asynchronously using the given session and provider.
160 161 162 163 164 165 166 167 168 169 170 171 172 173 |
# File 'lib/patient_llm.rb', line 160 def ask(session, provider:, callback:, callback_args: {}, url: nil, serializer: nil, path: nil, headers: nil, params: nil, preprocessors: nil, timeout: nil, max_tool_iterations: nil) = ( url: url, serializer: serializer, path: path, headers: headers, params: params, preprocessors: preprocessors, timeout: timeout, max_tool_iterations: max_tool_iterations ) dispatch(session, provider: provider, callback: callback, callback_args: callback_args, request_options: ) end |
.configure {|Configuration| ... } ⇒ void
This method returns an undefined value.
Configure providers for LLM requests.
48 49 50 51 |
# File 'lib/patient_llm.rb', line 48 def configure @configuration ||= Configuration.new yield @configuration end |
.dispatch(session, provider:, callback:, callback_args:, request_options:, tool_iteration: 0, original_request_id: nil) ⇒ Object
224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 |
# File 'lib/patient_llm.rb', line 224 def dispatch(session, provider:, callback:, callback_args:, request_options:, tool_iteration: 0, original_request_id: nil) provider_config = self.provider(provider) || {} provider_name = provider.to_s if tool_iteration.zero? PatientLLM::Callback.validate_callback_class!(PatientHttp::ClassHelper.resolve_class_name(callback.to_s)) end resolved = resolve_request(session, provider_config, ) dispatch_callback_args = { session: session_payload(session), provider: provider_name, serializer: resolved.serializer.to_s, callback: callback.to_s, custom: PromptBuilder.jsonify(callback_args || {}), request_options: , max_tool_iterations: resolved.max_tool_iterations, tool_iteration: tool_iteration, original_request_id: original_request_id } if inline? request = PatientHttp::Request.new( :post, resolved.url, json: resolved.payload, headers: resolved.headers, preprocessors: resolved.preprocessors, timeout: resolved.timeout ) PatientHttp.execute_inline( request: request, callback: PatientLLM::Callback, callback_args: dispatch_callback_args, raise_error_responses: true ) else PatientHttp.post( resolved.url, json: resolved.payload, headers: resolved.headers, preprocessors: resolved.preprocessors, timeout: resolved.timeout, raise_error_responses: true, callback: PatientLLM::Callback, callback_args: dispatch_callback_args ) end end |
.inline { ... } ⇒ Object
Execute requests inline (synchronously, in-process) for the duration of the block instead of dispatching through the registered PatientHttp handler. Useful in consoles and tests; the automatic tool loop also runs inline since it re-enters on the same thread.
123 124 125 126 127 128 129 130 131 |
# File 'lib/patient_llm.rb', line 123 def inline previous = Thread.current.thread_variable_get(:patient_llm_inline) Thread.current.thread_variable_set(:patient_llm_inline, true) begin yield ensure Thread.current.thread_variable_set(:patient_llm_inline, previous) end end |
.inline? ⇒ Boolean
Check if requests are currently executing inline via inline.
136 137 138 |
# File 'lib/patient_llm.rb', line 136 def inline? !!Thread.current.thread_variable_get(:patient_llm_inline) end |
.preview_request(session, provider:, url: nil, serializer: nil, path: nil, headers: nil, params: nil, preprocessors: nil, timeout: nil, max_tool_iterations: nil) ⇒ RequestPreview
Build the request that ask would send without sending it. The same resolution logic as ask is applied: per-request overrides are merged over the provider configuration, the session is serialized with the resolved serializer, and provider params are merged into the payload.
Nothing is enqueued or executed and no callback is required. Request preprocessors (e.g. AWS SigV4 signing) run at send time in the request processor, so their changes are not reflected in the preview. Header values that reference registered secrets are replaced with placeholders and never resolved.
199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 |
# File 'lib/patient_llm.rb', line 199 def preview_request(session, provider:, url: nil, serializer: nil, path: nil, headers: nil, params: nil, preprocessors: nil, timeout: nil, max_tool_iterations: nil) = ( url: url, serializer: serializer, path: path, headers: headers, params: params, preprocessors: preprocessors, timeout: timeout, max_tool_iterations: max_tool_iterations ) resolved = resolve_request(session, self.provider(provider) || {}, ) RequestPreview.new( url: resolved.url, headers: redact_secret_headers(resolved.headers), payload: resolved.payload ) end |
.provider(name) ⇒ Hash?
Look up a registered provider by name.
69 70 71 |
# File 'lib/patient_llm.rb', line 69 def provider(name) @configuration&.lookup(name) end |
.reset! ⇒ void
This method returns an undefined value.
Reset configuration. Primarily useful in tests.
61 62 63 |
# File 'lib/patient_llm.rb', line 61 def reset! @configuration = nil end |
.verify_configuration! ⇒ true
Verify that the configuration is fully wired: a PatientHttp request handler is registered, every secret referenced by a provider is registered, and every preprocessor referenced by a provider is registered (when a default PatientHttp configuration is available to check against).
Call this at the end of your application initializer to surface wiring mistakes at boot time instead of at dispatch time inside a job.
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 |
# File 'lib/patient_llm.rb', line 83 def verify_configuration! errors = [] unless PatientHttp.handler_registered? errors << "No PatientHttp request handler is registered. Add a job-system integration (patient_http-sidekiq or patient_http-solid_queue) or call PatientHttp.inline! for synchronous execution." end @configuration&.provider_names&.each do |name| provider_config = @configuration.lookup(name) provider_config[:headers].each do |header_name, value| next unless value.is_a?(PatientHttp::SecretReference) unless PatientHttp.secret_registered?(value.name) errors << "Provider #{name.inspect} header #{header_name.inspect} references secret #{value.name.inspect} but it is not registered with PatientHttp." end end patient_http_config = PatientHttp.default_configuration if patient_http_config Array(provider_config[:preprocessors]).each do |preprocessor_name| unless patient_http_config.preprocessor(preprocessor_name) errors << "Provider #{name.inspect} references preprocessor #{preprocessor_name.inspect} but it is not registered on the PatientHttp configuration." end end end end raise errors.join("\n") unless errors.empty? true end |