llm_logs 0.4.1 → 0.6.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 +4 -4
- data/README.md +35 -16
- data/app/jobs/llm_logs/batch/flush_job.rb +1 -0
- data/app/jobs/llm_logs/batch/poll_job.rb +1 -0
- data/app/models/llm_logs/batch/reconciler.rb +1 -0
- data/app/models/llm_logs/batch/submitter.rb +9 -7
- data/app/models/llm_logs/batch/trace_recorder.rb +19 -7
- data/app/models/llm_logs/batch.rb +15 -28
- data/app/models/llm_logs/prompt.rb +11 -2
- data/app/models/llm_logs/span.rb +15 -3
- data/app/views/llm_logs/spans/show.html.erb +3 -3
- data/app/views/llm_logs/traces/index.html.erb +3 -3
- data/app/views/llm_logs/traces/show.html.erb +5 -3
- data/db/migrate/001_create_llm_logs_traces.rb +1 -1
- data/db/migrate/002_create_llm_logs_spans.rb +3 -3
- data/db/migrate/004_create_llm_logs_prompt_versions.rb +3 -3
- data/db/migrate/006_add_tags_to_prompts.rb +6 -2
- data/db/migrate/007_create_llm_logs_batches.rb +1 -1
- data/db/migrate/008_create_llm_logs_batch_requests.rb +2 -2
- data/db/migrate/009_add_provider_columns_to_llm_logs_batches.rb +1 -1
- data/lib/llm_logs/configuration.rb +1 -2
- data/lib/llm_logs/engine.rb +10 -1
- data/lib/llm_logs/instrumentation/ruby_llm_chat.rb +190 -82
- data/lib/llm_logs/ruby_llm_patches/bedrock_sigv4.rb +75 -0
- data/lib/llm_logs/ruby_llm_patches/converse_claude_adaptive_thinking.rb +70 -0
- data/lib/llm_logs/ruby_llm_patches/converse_foreign_reasoning.rb +80 -0
- data/lib/llm_logs/ruby_llm_patches/converse_reasoning_config.rb +78 -0
- data/lib/llm_logs/ruby_llm_patches/retry_ssl_error.rb +25 -0
- data/lib/llm_logs/ruby_llm_patches.rb +54 -0
- data/lib/llm_logs/version.rb +1 -1
- data/lib/llm_logs.rb +1 -5
- metadata +23 -13
- data/app/models/llm_logs/batch/adapters/openai_responses.rb +0 -63
- data/app/models/llm_logs/batch/schema_format.rb +0 -25
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
require "llm_logs/ruby_llm_patches/bedrock_sigv4"
|
|
2
|
+
require "llm_logs/ruby_llm_patches/converse_reasoning_config"
|
|
3
|
+
require "llm_logs/ruby_llm_patches/converse_foreign_reasoning"
|
|
4
|
+
require "llm_logs/ruby_llm_patches/converse_claude_adaptive_thinking"
|
|
5
|
+
require "llm_logs/ruby_llm_patches/retry_ssl_error"
|
|
6
|
+
|
|
7
|
+
module LlmLogs
|
|
8
|
+
# Bedrock fixes that upstream ruby_llm 2.0.0 lacks, applied with Module#prepend. Each
|
|
9
|
+
# backports an open upstream PR (crmne/ruby_llm#1024, #1025, #1026; see the README), so it
|
|
10
|
+
# can be dropped once a ruby_llm release includes that PR.
|
|
11
|
+
# Each was verified against 2.0.x only: on 1.x they are skipped (different internals),
|
|
12
|
+
# on a newer ruby_llm they still install but log a warning so we re-check whether
|
|
13
|
+
# upstream fixed the bug (then drop the patch) or moved the method (then the patch
|
|
14
|
+
# skips itself and logs why).
|
|
15
|
+
module RubyLLMPatches
|
|
16
|
+
PATCHES = [
|
|
17
|
+
BedrockSigV4, ConverseReasoningConfig, ConverseForeignReasoning, ConverseClaudeAdaptiveThinking, RetrySSLError
|
|
18
|
+
].freeze
|
|
19
|
+
MINIMUM = Gem::Version.new("2.0.0")
|
|
20
|
+
VERIFIED = Gem::Requirement.new(">= 2.0.0", "< 2.1")
|
|
21
|
+
|
|
22
|
+
module_function
|
|
23
|
+
|
|
24
|
+
def install!
|
|
25
|
+
version = Gem::Version.new(RubyLLM::VERSION)
|
|
26
|
+
return [] if version < MINIMUM
|
|
27
|
+
|
|
28
|
+
unless VERIFIED.satisfied_by?(version)
|
|
29
|
+
warn_log("verified against ruby_llm #{VERIFIED}, running #{version}: check whether upstream " \
|
|
30
|
+
"now fixes #{PATCHES.map(&:name).join(', ')} and drop the patches it does")
|
|
31
|
+
end
|
|
32
|
+
PATCHES.select(&:install!)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def warn_log(message)
|
|
36
|
+
logger = defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger
|
|
37
|
+
logger ? logger.warn("[llm_logs] #{message}") : Kernel.warn("[llm_logs] #{message}")
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Prepends +patch+ to +target+ when +target+ still defines every method in +methods+.
|
|
41
|
+
def prepend_checked(target, patch, methods)
|
|
42
|
+
return true if target <= patch
|
|
43
|
+
|
|
44
|
+
missing = methods.reject { |name| target.method_defined?(name) || target.private_method_defined?(name) }
|
|
45
|
+
if missing.any?
|
|
46
|
+
warn_log("#{patch.name} not installed: #{target} no longer defines #{missing.join(', ')}")
|
|
47
|
+
return false
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
target.prepend(patch)
|
|
51
|
+
true
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
data/lib/llm_logs/version.rb
CHANGED
data/lib/llm_logs.rb
CHANGED
|
@@ -54,10 +54,6 @@ module LlmLogs
|
|
|
54
54
|
configuration.batch_enabled
|
|
55
55
|
end
|
|
56
56
|
|
|
57
|
-
def self.batch_provider
|
|
58
|
-
configuration.batch_provider
|
|
59
|
-
end
|
|
60
|
-
|
|
61
57
|
def self.bedrock_batch
|
|
62
58
|
configuration.bedrock_batch
|
|
63
59
|
end
|
|
@@ -71,7 +67,7 @@ module LlmLogs
|
|
|
71
67
|
end
|
|
72
68
|
|
|
73
69
|
def self.batch_adapters
|
|
74
|
-
@batch_adapters ||= {
|
|
70
|
+
@batch_adapters ||= {}
|
|
75
71
|
end
|
|
76
72
|
|
|
77
73
|
def self.register_batch_adapter(provider, adapter)
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: llm_logs
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.6.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Anton
|
|
@@ -97,30 +97,36 @@ dependencies:
|
|
|
97
97
|
name: ruby_llm
|
|
98
98
|
requirement: !ruby/object:Gem::Requirement
|
|
99
99
|
requirements:
|
|
100
|
-
- - "
|
|
100
|
+
- - ">="
|
|
101
101
|
- !ruby/object:Gem::Version
|
|
102
|
-
version: '
|
|
103
|
-
|
|
102
|
+
version: '2.0'
|
|
103
|
+
- - "<"
|
|
104
|
+
- !ruby/object:Gem::Version
|
|
105
|
+
version: '3'
|
|
106
|
+
type: :runtime
|
|
104
107
|
prerelease: false
|
|
105
108
|
version_requirements: !ruby/object:Gem::Requirement
|
|
106
109
|
requirements:
|
|
107
|
-
- - "
|
|
110
|
+
- - ">="
|
|
111
|
+
- !ruby/object:Gem::Version
|
|
112
|
+
version: '2.0'
|
|
113
|
+
- - "<"
|
|
108
114
|
- !ruby/object:Gem::Version
|
|
109
|
-
version: '
|
|
115
|
+
version: '3'
|
|
110
116
|
- !ruby/object:Gem::Dependency
|
|
111
|
-
name:
|
|
117
|
+
name: aws-eventstream
|
|
112
118
|
requirement: !ruby/object:Gem::Requirement
|
|
113
119
|
requirements:
|
|
114
|
-
- - "
|
|
120
|
+
- - ">="
|
|
115
121
|
- !ruby/object:Gem::Version
|
|
116
|
-
version: '0
|
|
122
|
+
version: '0'
|
|
117
123
|
type: :development
|
|
118
124
|
prerelease: false
|
|
119
125
|
version_requirements: !ruby/object:Gem::Requirement
|
|
120
126
|
requirements:
|
|
121
|
-
- - "
|
|
127
|
+
- - ">="
|
|
122
128
|
- !ruby/object:Gem::Version
|
|
123
|
-
version: '0
|
|
129
|
+
version: '0'
|
|
124
130
|
- !ruby/object:Gem::Dependency
|
|
125
131
|
name: webmock
|
|
126
132
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -186,10 +192,8 @@ files:
|
|
|
186
192
|
- app/models/llm_logs/application_record.rb
|
|
187
193
|
- app/models/llm_logs/batch.rb
|
|
188
194
|
- app/models/llm_logs/batch/adapters/bedrock.rb
|
|
189
|
-
- app/models/llm_logs/batch/adapters/openai_responses.rb
|
|
190
195
|
- app/models/llm_logs/batch/handler_registry.rb
|
|
191
196
|
- app/models/llm_logs/batch/reconciler.rb
|
|
192
|
-
- app/models/llm_logs/batch/schema_format.rb
|
|
193
197
|
- app/models/llm_logs/batch/submitter.rb
|
|
194
198
|
- app/models/llm_logs/batch/trace_recorder.rb
|
|
195
199
|
- app/models/llm_logs/batch_request.rb
|
|
@@ -237,6 +241,12 @@ files:
|
|
|
237
241
|
- lib/llm_logs/engine.rb
|
|
238
242
|
- lib/llm_logs/instrumentation/ruby_llm_chat.rb
|
|
239
243
|
- lib/llm_logs/prompt_renderer.rb
|
|
244
|
+
- lib/llm_logs/ruby_llm_patches.rb
|
|
245
|
+
- lib/llm_logs/ruby_llm_patches/bedrock_sigv4.rb
|
|
246
|
+
- lib/llm_logs/ruby_llm_patches/converse_claude_adaptive_thinking.rb
|
|
247
|
+
- lib/llm_logs/ruby_llm_patches/converse_foreign_reasoning.rb
|
|
248
|
+
- lib/llm_logs/ruby_llm_patches/converse_reasoning_config.rb
|
|
249
|
+
- lib/llm_logs/ruby_llm_patches/retry_ssl_error.rb
|
|
240
250
|
- lib/llm_logs/tracer.rb
|
|
241
251
|
- lib/llm_logs/version.rb
|
|
242
252
|
- lib/tasks/llm_logs.rake
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
module LlmLogs
|
|
2
|
-
class Batch
|
|
3
|
-
module Adapters
|
|
4
|
-
# Wraps ruby_llm-responses_api's OpenAI Batch API. Contains the remote-API logic
|
|
5
|
-
# previously inline in Submitter/Reconciler; the DB/claim lifecycle stays in those.
|
|
6
|
-
class OpenaiResponses
|
|
7
|
-
PROVIDER = :openai_responses
|
|
8
|
-
|
|
9
|
-
def submit(_batch, requests)
|
|
10
|
-
rubyllm_batch = RubyLLM.batch(model: requests.first.model, provider: PROVIDER)
|
|
11
|
-
requests.each do |request|
|
|
12
|
-
payload = request.payload
|
|
13
|
-
rubyllm_batch.add(
|
|
14
|
-
payload["input"],
|
|
15
|
-
id: request.custom_id,
|
|
16
|
-
instructions: payload["instructions"],
|
|
17
|
-
temperature: payload["temperature"],
|
|
18
|
-
**reasoning_extra(payload["reasoning_effort"]),
|
|
19
|
-
**schema_extra(payload["schema"])
|
|
20
|
-
)
|
|
21
|
-
end
|
|
22
|
-
rubyllm_batch.create!
|
|
23
|
-
{provider_batch_id: rubyllm_batch.id, openai_batch_id: rubyllm_batch.id, provider_metadata: {}}
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def terminal_status(batch)
|
|
27
|
-
resume(batch).status
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def results(batch)
|
|
31
|
-
resume(batch).results
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
def error_ids(batch)
|
|
35
|
-
resume(batch).errors.filter_map { |e| e["custom_id"] }
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
private
|
|
39
|
-
|
|
40
|
-
# The Responses API nests effort under `reasoning:`; `reasoning_effort` is the
|
|
41
|
-
# Chat Completions spelling and is silently ignored here.
|
|
42
|
-
def reasoning_extra(effort)
|
|
43
|
-
return {} if effort.blank?
|
|
44
|
-
|
|
45
|
-
{reasoning: {effort: effort}}
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
# No memoization: LlmLogs.batch_adapters holds one shared instance of this adapter
|
|
49
|
-
# for the life of the process, so caching the resumed handle here would go stale
|
|
50
|
-
# for a long-running PollJob worker (in-progress batches would never re-fetch a
|
|
51
|
-
# later terminal status) and would grow unbounded. Build a fresh handle every call.
|
|
52
|
-
def resume(batch)
|
|
53
|
-
RubyLLM.batch(id: batch.provider_batch_id, provider: PROVIDER)
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
def schema_extra(schema)
|
|
57
|
-
format = SchemaFormat.call(schema)
|
|
58
|
-
format ? {text: format} : {}
|
|
59
|
-
end
|
|
60
|
-
end
|
|
61
|
-
end
|
|
62
|
-
end
|
|
63
|
-
end
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
module LlmLogs
|
|
2
|
-
class Batch
|
|
3
|
-
# Translates a {name:, schema:, strict:} schema (the shape RubyLLM::Chat#with_schema
|
|
4
|
-
# produces) into the OpenAI Responses API `text.format` block. The batch path builds
|
|
5
|
-
# request bodies directly via RubyLLM.batch#add(**extra), bypassing with_schema, so
|
|
6
|
-
# we must hand the json_schema block in ourselves.
|
|
7
|
-
module SchemaFormat
|
|
8
|
-
module_function
|
|
9
|
-
|
|
10
|
-
def call(schema)
|
|
11
|
-
return nil if schema.nil?
|
|
12
|
-
|
|
13
|
-
schema = schema.symbolize_keys if schema.respond_to?(:symbolize_keys)
|
|
14
|
-
{
|
|
15
|
-
format: {
|
|
16
|
-
type: "json_schema",
|
|
17
|
-
name: schema[:name] || "response",
|
|
18
|
-
schema: schema[:schema] || schema,
|
|
19
|
-
strict: schema.key?(:strict) ? schema[:strict] : true
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
end
|