opentelemetry-instrumentation-ruby_llm 0.1.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/.github/workflows/dynamic-readme.yml +19 -0
- data/.github/workflows/dynamic-security.yml +19 -0
- data/.github/workflows/main.yml +30 -0
- data/.gitignore +1 -0
- data/CODEOWNERS +15 -0
- data/CODE_OF_CONDUCT.md +6 -0
- data/Gemfile +15 -0
- data/Gemfile.lock +105 -0
- data/LICENSE +21 -0
- data/README.md +69 -0
- data/Rakefile +8 -0
- data/SECURITY.md +20 -0
- data/example/trace_demonstration.rb +29 -0
- data/example/trace_demonstration_with_langfuse.rb +42 -0
- data/example/trace_demonstration_with_langfuse_and_tools.rb +52 -0
- data/example/trace_demonstration_with_tools.rb +39 -0
- data/lib/opentelemetry/instrumentation/ruby_llm/instrumentation.rb +21 -0
- data/lib/opentelemetry/instrumentation/ruby_llm/patches/chat.rb +71 -0
- data/lib/opentelemetry/instrumentation/ruby_llm/version.rb +9 -0
- data/lib/opentelemetry-instrumentation-ruby_llm.rb +5 -0
- data/opentelemetry-instrumentation-ruby_llm.gemspec +23 -0
- data/test/instrumentation_test.rb +231 -0
- data/test/test_helper.rb +15 -0
- metadata +92 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 9e85590961ea3d4ac30828dbc035d692040a2a66758ba6dab9decab2114af3f3
|
|
4
|
+
data.tar.gz: a8a701fa288ec93109bfb6a4d3fdcc5962c40abc98d9a9269d12a9e430848f78
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 60672653bad15a0e24a97a60ea94433f82df8300370987f656f135a5eb1339b731afbf3644dddbe4a867b3f9970d2b757e06ca7862a4b7d0d36798dff3f9aa58
|
|
7
|
+
data.tar.gz: d4536fbcba4b6439df8c8a4a30198072eea8cc61265ee1572a5c2b0dbd6f775260381d727d06ebc13604d755dc98f44f24f7ee552d218bb50b2695aec6ad3873
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
name: update-templates
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
paths:
|
|
8
|
+
- README.md
|
|
9
|
+
workflow_dispatch:
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
update-templates:
|
|
13
|
+
permissions:
|
|
14
|
+
contents: write
|
|
15
|
+
pull-requests: write
|
|
16
|
+
pages: write
|
|
17
|
+
uses: thoughtbot/templates/.github/workflows/dynamic-readme.yaml@main
|
|
18
|
+
secrets:
|
|
19
|
+
token: ${{ secrets.GITHUB_TOKEN }}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
name: update-security
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
paths:
|
|
6
|
+
- SECURITY.md
|
|
7
|
+
branches:
|
|
8
|
+
- main
|
|
9
|
+
workflow_dispatch:
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
update-security:
|
|
13
|
+
permissions:
|
|
14
|
+
contents: write
|
|
15
|
+
pull-requests: write
|
|
16
|
+
pages: write
|
|
17
|
+
uses: thoughtbot/templates/.github/workflows/dynamic-security.yaml@main
|
|
18
|
+
secrets:
|
|
19
|
+
token: ${{ secrets.GITHUB_TOKEN }}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
name: Ruby
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
pull_request:
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
build:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
name: Ruby ${{ matrix.ruby }}
|
|
13
|
+
strategy:
|
|
14
|
+
matrix:
|
|
15
|
+
ruby:
|
|
16
|
+
- '3.4'
|
|
17
|
+
- '3.3'
|
|
18
|
+
- '3.2'
|
|
19
|
+
|
|
20
|
+
steps:
|
|
21
|
+
- uses: actions/checkout@v4
|
|
22
|
+
with:
|
|
23
|
+
persist-credentials: false
|
|
24
|
+
- name: Set up Ruby
|
|
25
|
+
uses: ruby/setup-ruby@v1
|
|
26
|
+
with:
|
|
27
|
+
ruby-version: ${{ matrix.ruby }}
|
|
28
|
+
bundler-cache: true
|
|
29
|
+
- name: Run tests
|
|
30
|
+
run: bundle exec rake
|
data/.gitignore
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
*.gem
|
data/CODEOWNERS
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Lines starting with '#' are comments.
|
|
2
|
+
# Each line is a file pattern followed by one or more owners.
|
|
3
|
+
|
|
4
|
+
# More details are here: https://help.github.com/articles/about-codeowners/
|
|
5
|
+
|
|
6
|
+
# The '*' pattern is global owners.
|
|
7
|
+
|
|
8
|
+
# Order is important. The last matching pattern has the most precedence.
|
|
9
|
+
# The folders are ordered as follows:
|
|
10
|
+
|
|
11
|
+
# In each subsection folders are ordered first by depth, then alphabetically.
|
|
12
|
+
# This should make it easy to add new rules without breaking existing ones.
|
|
13
|
+
|
|
14
|
+
# Global rule:
|
|
15
|
+
* @clarissalimab
|
data/CODE_OF_CONDUCT.md
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
opentelemetry-instrumentation-ruby_llm (0.1.0)
|
|
5
|
+
opentelemetry-api (~> 1.0)
|
|
6
|
+
opentelemetry-instrumentation-base (~> 0.23)
|
|
7
|
+
|
|
8
|
+
GEM
|
|
9
|
+
remote: https://rubygems.org/
|
|
10
|
+
specs:
|
|
11
|
+
addressable (2.8.8)
|
|
12
|
+
public_suffix (>= 2.0.2, < 8.0)
|
|
13
|
+
base64 (0.3.0)
|
|
14
|
+
bigdecimal (4.0.1)
|
|
15
|
+
crack (1.0.1)
|
|
16
|
+
bigdecimal
|
|
17
|
+
rexml
|
|
18
|
+
event_stream_parser (1.0.0)
|
|
19
|
+
faraday (2.14.0)
|
|
20
|
+
faraday-net_http (>= 2.0, < 3.5)
|
|
21
|
+
json
|
|
22
|
+
logger
|
|
23
|
+
faraday-multipart (1.2.0)
|
|
24
|
+
multipart-post (~> 2.0)
|
|
25
|
+
faraday-net_http (3.4.2)
|
|
26
|
+
net-http (~> 0.5)
|
|
27
|
+
faraday-retry (2.4.0)
|
|
28
|
+
faraday (~> 2.0)
|
|
29
|
+
google-protobuf (4.33.4)
|
|
30
|
+
bigdecimal
|
|
31
|
+
rake (>= 13)
|
|
32
|
+
google-protobuf (4.33.4-arm64-darwin)
|
|
33
|
+
bigdecimal
|
|
34
|
+
rake (>= 13)
|
|
35
|
+
googleapis-common-protos-types (1.22.0)
|
|
36
|
+
google-protobuf (~> 4.26)
|
|
37
|
+
hashdiff (1.2.1)
|
|
38
|
+
json (2.18.0)
|
|
39
|
+
logger (1.7.0)
|
|
40
|
+
marcel (1.1.0)
|
|
41
|
+
minitest (6.0.1)
|
|
42
|
+
prism (~> 1.5)
|
|
43
|
+
multipart-post (2.4.1)
|
|
44
|
+
net-http (0.9.1)
|
|
45
|
+
uri (>= 0.11.1)
|
|
46
|
+
opentelemetry-api (1.7.0)
|
|
47
|
+
opentelemetry-common (0.23.0)
|
|
48
|
+
opentelemetry-api (~> 1.0)
|
|
49
|
+
opentelemetry-exporter-otlp (0.31.1)
|
|
50
|
+
google-protobuf (>= 3.18)
|
|
51
|
+
googleapis-common-protos-types (~> 1.3)
|
|
52
|
+
opentelemetry-api (~> 1.1)
|
|
53
|
+
opentelemetry-common (~> 0.20)
|
|
54
|
+
opentelemetry-sdk (~> 1.10)
|
|
55
|
+
opentelemetry-semantic_conventions
|
|
56
|
+
opentelemetry-instrumentation-base (0.25.0)
|
|
57
|
+
opentelemetry-api (~> 1.7)
|
|
58
|
+
opentelemetry-common (~> 0.21)
|
|
59
|
+
opentelemetry-registry (~> 0.1)
|
|
60
|
+
opentelemetry-registry (0.4.0)
|
|
61
|
+
opentelemetry-api (~> 1.1)
|
|
62
|
+
opentelemetry-sdk (1.10.0)
|
|
63
|
+
opentelemetry-api (~> 1.1)
|
|
64
|
+
opentelemetry-common (~> 0.20)
|
|
65
|
+
opentelemetry-registry (~> 0.2)
|
|
66
|
+
opentelemetry-semantic_conventions
|
|
67
|
+
opentelemetry-semantic_conventions (1.36.0)
|
|
68
|
+
opentelemetry-api (~> 1.0)
|
|
69
|
+
prism (1.9.0)
|
|
70
|
+
public_suffix (7.0.2)
|
|
71
|
+
rake (13.3.1)
|
|
72
|
+
rexml (3.4.4)
|
|
73
|
+
ruby_llm (1.11.0)
|
|
74
|
+
base64
|
|
75
|
+
event_stream_parser (~> 1)
|
|
76
|
+
faraday (>= 1.10.0)
|
|
77
|
+
faraday-multipart (>= 1)
|
|
78
|
+
faraday-net_http (>= 1)
|
|
79
|
+
faraday-retry (>= 1)
|
|
80
|
+
marcel (~> 1.0)
|
|
81
|
+
ruby_llm-schema (~> 0.2.1)
|
|
82
|
+
zeitwerk (~> 2)
|
|
83
|
+
ruby_llm-schema (0.2.5)
|
|
84
|
+
uri (1.1.1)
|
|
85
|
+
webmock (3.26.1)
|
|
86
|
+
addressable (>= 2.8.0)
|
|
87
|
+
crack (>= 0.3.2)
|
|
88
|
+
hashdiff (>= 0.4.0, < 2.0.0)
|
|
89
|
+
zeitwerk (2.7.4)
|
|
90
|
+
|
|
91
|
+
PLATFORMS
|
|
92
|
+
arm64-darwin-24
|
|
93
|
+
ruby
|
|
94
|
+
|
|
95
|
+
DEPENDENCIES
|
|
96
|
+
minitest
|
|
97
|
+
opentelemetry-exporter-otlp
|
|
98
|
+
opentelemetry-instrumentation-ruby_llm!
|
|
99
|
+
opentelemetry-sdk
|
|
100
|
+
rake
|
|
101
|
+
ruby_llm
|
|
102
|
+
webmock
|
|
103
|
+
|
|
104
|
+
BUNDLED WITH
|
|
105
|
+
2.7.1
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) Clarissa Borges and thoughtbot, inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# OpenTelemetry RubyLLM Instrumentation
|
|
2
|
+
|
|
3
|
+
OpenTelemetry instrumentation for [RubyLLM](https://rubyllm.com).
|
|
4
|
+
|
|
5
|
+
## How do I get started?
|
|
6
|
+
|
|
7
|
+
Install the gem using:
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
gem opentelemetry-instrumentation-ruby_llm
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Or, if you use [bundler](https://bundler.io/), include `opentelemetry-instrumentation-ruby_llm` in your `Gemfile`.
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
To use the instrumentation, call `use` with the name of the instrumentation:
|
|
18
|
+
|
|
19
|
+
```ruby
|
|
20
|
+
OpenTelemetry::SDK.configure do |c|
|
|
21
|
+
c.use 'OpenTelemetry::Instrumentation::RubyLLM'
|
|
22
|
+
end
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Alternatively, you can also call `use_all` to install all the available instrumentation.
|
|
26
|
+
|
|
27
|
+
```ruby
|
|
28
|
+
OpenTelemetry::SDK.configure do |c|
|
|
29
|
+
c.use_all
|
|
30
|
+
end
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## What's traced?
|
|
34
|
+
|
|
35
|
+
| Feature | Status |
|
|
36
|
+
|---------|--------|
|
|
37
|
+
| Chat completions | Supported |
|
|
38
|
+
| Tool calls | Supported |
|
|
39
|
+
| Error handling | Supported |
|
|
40
|
+
| Conversation tracking (`gen_ai.conversation.id`) | Planned |
|
|
41
|
+
| Opt-in input/output content capture | Planned |
|
|
42
|
+
| System instructions capture | Planned |
|
|
43
|
+
| Embeddings | Planned |
|
|
44
|
+
| Streaming | Planned |
|
|
45
|
+
|
|
46
|
+
This gem follows the [OpenTelemetry GenAI Semantic Conventions](https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-spans/).
|
|
47
|
+
|
|
48
|
+
## License
|
|
49
|
+
|
|
50
|
+
Copyright (c) Clarissa Borges and thoughtbot, inc.
|
|
51
|
+
|
|
52
|
+
This gem is free software and may be redistributed under the terms specified in the [LICENSE](LICENSE) file.
|
|
53
|
+
|
|
54
|
+
<!-- START /templates/footer.md -->
|
|
55
|
+
## About thoughtbot
|
|
56
|
+
|
|
57
|
+

|
|
58
|
+
|
|
59
|
+
This repo is maintained and funded by thoughtbot, inc.
|
|
60
|
+
The names and logos for thoughtbot are trademarks of thoughtbot, inc.
|
|
61
|
+
|
|
62
|
+
We love open source software!
|
|
63
|
+
See [our other projects][community].
|
|
64
|
+
We are [available for hire][hire].
|
|
65
|
+
|
|
66
|
+
[community]: https://thoughtbot.com/community?utm_source=github
|
|
67
|
+
[hire]: https://thoughtbot.com/hire-us?utm_source=github
|
|
68
|
+
|
|
69
|
+
<!-- END /templates/footer.md -->
|
data/Rakefile
ADDED
data/SECURITY.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<!-- START /templates/security.md -->
|
|
2
|
+
# Security Policy
|
|
3
|
+
|
|
4
|
+
## Supported Versions
|
|
5
|
+
|
|
6
|
+
Only the the latest version of this project is supported at a given time. If
|
|
7
|
+
you find a security issue with an older version, please try updating to the
|
|
8
|
+
latest version first.
|
|
9
|
+
|
|
10
|
+
If for some reason you can't update to the latest version, please let us know
|
|
11
|
+
your reasons so that we can have a better understanding of your situation.
|
|
12
|
+
|
|
13
|
+
## Reporting a Vulnerability
|
|
14
|
+
|
|
15
|
+
For security inquiries or vulnerability reports, visit
|
|
16
|
+
<https://thoughtbot.com/security>.
|
|
17
|
+
|
|
18
|
+
If you have any suggestions to improve this policy, visit <https://thoughtbot.com/security>.
|
|
19
|
+
|
|
20
|
+
<!-- END /templates/security.md -->
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bundler/inline"
|
|
4
|
+
|
|
5
|
+
gemfile(true) do
|
|
6
|
+
source "https://rubygems.org"
|
|
7
|
+
gem "ruby_llm"
|
|
8
|
+
gem "opentelemetry-api"
|
|
9
|
+
gem "opentelemetry-sdk"
|
|
10
|
+
gem "opentelemetry-instrumentation-ruby_llm", path: "../"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
ENV["OTEL_TRACES_EXPORTER"] ||= "console"
|
|
14
|
+
|
|
15
|
+
OpenTelemetry::SDK.configure do |c|
|
|
16
|
+
c.use "OpenTelemetry::Instrumentation::RubyLLM"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
RubyLLM.configure do |c|
|
|
20
|
+
c.openai_api_key = ENV["OPENAI_API_KEY"]
|
|
21
|
+
c.default_model = "gpt-5-nano"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
chat = RubyLLM.chat
|
|
25
|
+
response = chat.ask("What is the meaning of life?")
|
|
26
|
+
puts "\nResponse: #{response.content}"
|
|
27
|
+
|
|
28
|
+
# This line is only necessary in short-lived scripts. In a long-running application, spans will be flushed automatically.
|
|
29
|
+
OpenTelemetry.tracer_provider.force_flush
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bundler/inline"
|
|
4
|
+
|
|
5
|
+
gemfile(true) do
|
|
6
|
+
source "https://rubygems.org"
|
|
7
|
+
gem "ruby_llm"
|
|
8
|
+
gem "opentelemetry-api"
|
|
9
|
+
gem "opentelemetry-sdk"
|
|
10
|
+
gem "opentelemetry-exporter-otlp"
|
|
11
|
+
gem "opentelemetry-instrumentation-ruby_llm", path: "../"
|
|
12
|
+
gem "base64"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
require "base64"
|
|
16
|
+
|
|
17
|
+
credentials = Base64.strict_encode64("#{ENV['LANGFUSE_PUBLIC_KEY']}:#{ENV['LANGFUSE_SECRET_KEY']}")
|
|
18
|
+
|
|
19
|
+
OpenTelemetry::SDK.configure do |c|
|
|
20
|
+
c.service_name = "ruby_llm-demo"
|
|
21
|
+
c.add_span_processor(
|
|
22
|
+
OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new(
|
|
23
|
+
OpenTelemetry::Exporter::OTLP::Exporter.new(
|
|
24
|
+
endpoint: "https://us.cloud.langfuse.com/api/public/otel/v1/traces",
|
|
25
|
+
headers: { "Authorization" => "Basic #{credentials}" }
|
|
26
|
+
)
|
|
27
|
+
)
|
|
28
|
+
)
|
|
29
|
+
c.use "OpenTelemetry::Instrumentation::RubyLLM"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
RubyLLM.configure do |c|
|
|
33
|
+
c.openai_api_key = ENV["OPENAI_API_KEY"]
|
|
34
|
+
c.default_model = "gpt-5-nano"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
chat = RubyLLM.chat
|
|
38
|
+
response = chat.ask("What is the meaning of life?")
|
|
39
|
+
puts "\nResponse: #{response.content}"
|
|
40
|
+
|
|
41
|
+
# This line is only necessary in short-lived scripts. In a long-running application, spans will be flushed automatically.
|
|
42
|
+
OpenTelemetry.tracer_provider.force_flush
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bundler/inline"
|
|
4
|
+
|
|
5
|
+
gemfile(true) do
|
|
6
|
+
source "https://rubygems.org"
|
|
7
|
+
gem "ruby_llm"
|
|
8
|
+
gem "opentelemetry-api"
|
|
9
|
+
gem "opentelemetry-sdk"
|
|
10
|
+
gem "opentelemetry-exporter-otlp"
|
|
11
|
+
gem "opentelemetry-instrumentation-ruby_llm", path: "../"
|
|
12
|
+
gem "base64"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
require "base64"
|
|
16
|
+
|
|
17
|
+
credentials = Base64.strict_encode64("#{ENV['LANGFUSE_PUBLIC_KEY']}:#{ENV['LANGFUSE_SECRET_KEY']}")
|
|
18
|
+
|
|
19
|
+
OpenTelemetry::SDK.configure do |c|
|
|
20
|
+
c.service_name = "ruby_llm-demo"
|
|
21
|
+
c.add_span_processor(
|
|
22
|
+
OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new(
|
|
23
|
+
OpenTelemetry::Exporter::OTLP::Exporter.new(
|
|
24
|
+
endpoint: "https://us.cloud.langfuse.com/api/public/otel/v1/traces",
|
|
25
|
+
headers: { "Authorization" => "Basic #{credentials}" }
|
|
26
|
+
)
|
|
27
|
+
)
|
|
28
|
+
)
|
|
29
|
+
c.use "OpenTelemetry::Instrumentation::RubyLLM"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
RubyLLM.configure do |c|
|
|
33
|
+
c.openai_api_key = ENV["OPENAI_API_KEY"]
|
|
34
|
+
c.default_model = "gpt-5-nano"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
class Calculator < RubyLLM::Tool
|
|
38
|
+
description "Performs basic math calculations"
|
|
39
|
+
param :expression, type: "string", desc: "Math expression to evaluate"
|
|
40
|
+
|
|
41
|
+
def execute(expression:)
|
|
42
|
+
eval(expression).to_s
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
chat = RubyLLM.chat
|
|
47
|
+
chat.with_tool(Calculator)
|
|
48
|
+
response = chat.ask("Use the calculator tool to compute 123 * 456")
|
|
49
|
+
puts "\nResponse: #{response.content}"
|
|
50
|
+
|
|
51
|
+
# This line is only necessary in short-lived scripts. In a long-running application, spans will be flushed automatically.
|
|
52
|
+
OpenTelemetry.tracer_provider.force_flush
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bundler/inline"
|
|
4
|
+
|
|
5
|
+
gemfile(true) do
|
|
6
|
+
source "https://rubygems.org"
|
|
7
|
+
gem "ruby_llm"
|
|
8
|
+
gem "opentelemetry-api"
|
|
9
|
+
gem "opentelemetry-sdk"
|
|
10
|
+
gem "opentelemetry-instrumentation-ruby_llm", path: "../"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
ENV["OTEL_TRACES_EXPORTER"] ||= "console"
|
|
14
|
+
|
|
15
|
+
OpenTelemetry::SDK.configure do |c|
|
|
16
|
+
c.use "OpenTelemetry::Instrumentation::RubyLLM"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
RubyLLM.configure do |c|
|
|
20
|
+
c.openai_api_key = ENV["OPENAI_API_KEY"]
|
|
21
|
+
c.default_model = "gpt-5-nano"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
class Calculator < RubyLLM::Tool
|
|
25
|
+
description "Performs basic math calculations"
|
|
26
|
+
param :expression, type: "string", desc: "Math expression to evaluate"
|
|
27
|
+
|
|
28
|
+
def execute(expression:)
|
|
29
|
+
eval(expression).to_s
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
chat = RubyLLM.chat
|
|
34
|
+
chat.with_tool(Calculator)
|
|
35
|
+
response = chat.ask("What is 123 * 456?")
|
|
36
|
+
puts "\nResponse: #{response.content}"
|
|
37
|
+
|
|
38
|
+
# This line is only necessary in short-lived scripts. In a long-running application, spans will be flushed automatically.
|
|
39
|
+
OpenTelemetry.tracer_provider.force_flush
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OpenTelemetry
|
|
4
|
+
module Instrumentation
|
|
5
|
+
module RubyLLM
|
|
6
|
+
class Instrumentation < OpenTelemetry::Instrumentation::Base
|
|
7
|
+
instrumentation_name "OpenTelemetry::Instrumentation::RubyLLM"
|
|
8
|
+
instrumentation_version VERSION
|
|
9
|
+
|
|
10
|
+
present do
|
|
11
|
+
defined?(::RubyLLM)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
install do |_config|
|
|
15
|
+
require_relative "patches/chat"
|
|
16
|
+
::RubyLLM::Chat.prepend(Patches::Chat)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OpenTelemetry
|
|
4
|
+
module Instrumentation
|
|
5
|
+
module RubyLLM
|
|
6
|
+
module Patches
|
|
7
|
+
module Chat
|
|
8
|
+
def ask(message, &block)
|
|
9
|
+
provider = @model&.provider || "unknown"
|
|
10
|
+
model_id = @model&.id || "unknown"
|
|
11
|
+
|
|
12
|
+
attributes = {
|
|
13
|
+
"gen_ai.operation.name" => "chat",
|
|
14
|
+
"gen_ai.provider.name" => provider,
|
|
15
|
+
"gen_ai.request.model" => model_id,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
tracer.in_span("chat #{model_id}", attributes: attributes, kind: OpenTelemetry::Trace::SpanKind::CLIENT) do |span|
|
|
19
|
+
begin
|
|
20
|
+
result = super
|
|
21
|
+
|
|
22
|
+
if @messages.last
|
|
23
|
+
response = @messages.last
|
|
24
|
+
span.set_attribute("gen_ai.response.model", response.model_id) if response.model_id
|
|
25
|
+
span.set_attribute("gen_ai.usage.input_tokens", response.input_tokens) if response.input_tokens
|
|
26
|
+
span.set_attribute("gen_ai.usage.output_tokens", response.output_tokens) if response.output_tokens
|
|
27
|
+
span.set_attribute("gen_ai.request.temperature", @temperature) if @temperature
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
result
|
|
31
|
+
rescue => e
|
|
32
|
+
span.record_exception(e)
|
|
33
|
+
span.status = OpenTelemetry::Trace::Status.error(e.message)
|
|
34
|
+
span.set_attribute("error.type", e.class.name)
|
|
35
|
+
raise
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
rescue StandardError => e
|
|
39
|
+
OpenTelemetry.handle_error(exception: e)
|
|
40
|
+
super
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def execute_tool(tool_call)
|
|
44
|
+
attributes = {
|
|
45
|
+
"gen_ai.tool.name" => tool_call.name,
|
|
46
|
+
"gen_ai.tool.call.id" => tool_call.id,
|
|
47
|
+
"gen_ai.tool.call.arguments" => tool_call.arguments.to_json,
|
|
48
|
+
"gen_ai.tool.type" => "function"
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
tracer.in_span("execute_tool #{tool_call.name}", attributes: attributes, kind: OpenTelemetry::Trace::SpanKind::INTERNAL) do |span|
|
|
52
|
+
result = super
|
|
53
|
+
result_str = result.is_a?(::RubyLLM::Tool::Halt) ? result.content.to_s : result.to_s
|
|
54
|
+
span.set_attribute("gen_ai.tool.call.result", result_str[0..500])
|
|
55
|
+
result
|
|
56
|
+
end
|
|
57
|
+
rescue StandardError => e
|
|
58
|
+
OpenTelemetry.handle_error(exception: e)
|
|
59
|
+
super
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
|
|
64
|
+
def tracer
|
|
65
|
+
RubyLLM::Instrumentation.instance.tracer
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
require_relative "lib/opentelemetry/instrumentation/ruby_llm/version"
|
|
2
|
+
|
|
3
|
+
Gem::Specification.new do |spec|
|
|
4
|
+
spec.name = "opentelemetry-instrumentation-ruby_llm"
|
|
5
|
+
spec.version = OpenTelemetry::Instrumentation::RubyLLM::VERSION
|
|
6
|
+
spec.authors = ["Clarissa Borges"]
|
|
7
|
+
spec.email = ["cborges@thoughtbot.com"]
|
|
8
|
+
spec.license = "MIT"
|
|
9
|
+
|
|
10
|
+
spec.summary = "OpenTelemetry instrumentation for RubyLLM"
|
|
11
|
+
spec.description = "Adds OpenTelemetry tracing to RubyLLM chat operations"
|
|
12
|
+
spec.homepage = "https://github.com/thoughtbot/opentelemetry-instrumentation-ruby_llm"
|
|
13
|
+
|
|
14
|
+
spec.required_ruby_version = ">= 3.2.0"
|
|
15
|
+
|
|
16
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
|
17
|
+
|
|
18
|
+
spec.files = `git ls-files`.split("\n")
|
|
19
|
+
spec.require_paths = ["lib"]
|
|
20
|
+
|
|
21
|
+
spec.add_dependency "opentelemetry-api", "~> 1.0"
|
|
22
|
+
spec.add_dependency "opentelemetry-instrumentation-base", "~> 0.23"
|
|
23
|
+
end
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
require "test_helper"
|
|
2
|
+
|
|
3
|
+
class InstrumentationTest < Minitest::Test
|
|
4
|
+
def setup
|
|
5
|
+
EXPORTER.reset
|
|
6
|
+
|
|
7
|
+
RubyLLM.configure do |c|
|
|
8
|
+
c.openai_api_key = "fake-key-for-testing"
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def test_creates_span_with_attributes
|
|
13
|
+
stub_request(:post, "https://api.openai.com/v1/chat/completions")
|
|
14
|
+
.to_return(
|
|
15
|
+
status: 200,
|
|
16
|
+
headers: { "Content-Type" => "application/json" },
|
|
17
|
+
body: {
|
|
18
|
+
id: "chatcmpl-123",
|
|
19
|
+
object: "chat.completion",
|
|
20
|
+
model: "gpt-4o-mini",
|
|
21
|
+
choices: [
|
|
22
|
+
{
|
|
23
|
+
index: 0,
|
|
24
|
+
message: { role: "assistant", content: "Hello, world!" },
|
|
25
|
+
finish_reason: "stop"
|
|
26
|
+
}
|
|
27
|
+
],
|
|
28
|
+
usage: {
|
|
29
|
+
prompt_tokens: 10,
|
|
30
|
+
completion_tokens: 5,
|
|
31
|
+
total_tokens: 15
|
|
32
|
+
}
|
|
33
|
+
}.to_json
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
chat = RubyLLM.chat(model: "gpt-4o-mini")
|
|
37
|
+
chat.ask("Hi")
|
|
38
|
+
|
|
39
|
+
spans = EXPORTER.finished_spans
|
|
40
|
+
assert_equal 1, spans.length
|
|
41
|
+
|
|
42
|
+
span = spans.first
|
|
43
|
+
assert_equal OpenTelemetry::Trace::SpanKind::CLIENT, span.kind
|
|
44
|
+
assert_equal "chat gpt-4o-mini", span.name
|
|
45
|
+
assert_equal "openai", span.attributes["gen_ai.provider.name"]
|
|
46
|
+
assert_equal "gpt-4o-mini", span.attributes["gen_ai.request.model"]
|
|
47
|
+
assert_equal "chat", span.attributes["gen_ai.operation.name"]
|
|
48
|
+
assert_equal 10, span.attributes["gen_ai.usage.input_tokens"]
|
|
49
|
+
assert_equal 5, span.attributes["gen_ai.usage.output_tokens"]
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def test_records_error_on_api_failure
|
|
53
|
+
stub_request(:post, "https://api.openai.com/v1/chat/completions")
|
|
54
|
+
.to_return(status: 500, body: "Internal Server Error")
|
|
55
|
+
|
|
56
|
+
chat = RubyLLM.chat(model: "gpt-4o-mini")
|
|
57
|
+
|
|
58
|
+
assert_raises do
|
|
59
|
+
chat.ask("Hi")
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
spans = EXPORTER.finished_spans
|
|
63
|
+
span = spans.last
|
|
64
|
+
|
|
65
|
+
assert_equal "chat gpt-4o-mini", span.name
|
|
66
|
+
assert span.attributes["error.type"]
|
|
67
|
+
assert_equal OpenTelemetry::Trace::Status::ERROR, span.status.code
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def test_ask_still_works_when_instrumentation_fails
|
|
71
|
+
stub_request(:post, "https://api.openai.com/v1/chat/completions")
|
|
72
|
+
.to_return(
|
|
73
|
+
status: 200,
|
|
74
|
+
headers: { "Content-Type" => "application/json" },
|
|
75
|
+
body: {
|
|
76
|
+
id: "chatcmpl-123",
|
|
77
|
+
object: "chat.completion",
|
|
78
|
+
model: "gpt-4o-mini",
|
|
79
|
+
choices: [{
|
|
80
|
+
index: 0,
|
|
81
|
+
message: { role: "assistant", content: "Hello!" },
|
|
82
|
+
finish_reason: "stop"
|
|
83
|
+
}],
|
|
84
|
+
usage: { prompt_tokens: 10, completion_tokens: 5, total_tokens: 15 }
|
|
85
|
+
}.to_json
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
chat = RubyLLM.chat(model: "gpt-4o-mini")
|
|
89
|
+
chat.define_singleton_method(:tracer) { raise StandardError, "instrumentation bug" }
|
|
90
|
+
|
|
91
|
+
response = chat.ask("Hi")
|
|
92
|
+
assert_equal "Hello!", response.content
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def test_creates_span_for_tool_call
|
|
96
|
+
calculator = Class.new(RubyLLM::Tool) do
|
|
97
|
+
def self.name = "calculator"
|
|
98
|
+
description "Performs math"
|
|
99
|
+
param :expression, type: "string", desc: "Math expression"
|
|
100
|
+
|
|
101
|
+
def execute(expression:)
|
|
102
|
+
eval(expression).to_s
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
stub_request(:post, "https://api.openai.com/v1/chat/completions")
|
|
107
|
+
.to_return(
|
|
108
|
+
{
|
|
109
|
+
status: 200,
|
|
110
|
+
headers: { "Content-Type" => "application/json" },
|
|
111
|
+
body: {
|
|
112
|
+
id: "chatcmpl-123",
|
|
113
|
+
object: "chat.completion",
|
|
114
|
+
model: "gpt-4o-mini",
|
|
115
|
+
choices: [{
|
|
116
|
+
index: 0,
|
|
117
|
+
message: {
|
|
118
|
+
role: "assistant",
|
|
119
|
+
content: nil,
|
|
120
|
+
tool_calls: [{
|
|
121
|
+
id: "call_abc123",
|
|
122
|
+
type: "function",
|
|
123
|
+
function: { name: "calculator", arguments: '{"expression":"2+2"}' }
|
|
124
|
+
}]
|
|
125
|
+
},
|
|
126
|
+
finish_reason: "tool_calls"
|
|
127
|
+
}],
|
|
128
|
+
usage: { prompt_tokens: 10, completion_tokens: 5, total_tokens: 15 }
|
|
129
|
+
}.to_json
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
status: 200,
|
|
133
|
+
headers: { "Content-Type" => "application/json" },
|
|
134
|
+
body: {
|
|
135
|
+
id: "chatcmpl-456",
|
|
136
|
+
object: "chat.completion",
|
|
137
|
+
model: "gpt-4o-mini",
|
|
138
|
+
choices: [{
|
|
139
|
+
index: 0,
|
|
140
|
+
message: { role: "assistant", content: "The answer is 4" },
|
|
141
|
+
finish_reason: "stop"
|
|
142
|
+
}],
|
|
143
|
+
usage: { prompt_tokens: 20, completion_tokens: 5, total_tokens: 25 }
|
|
144
|
+
}.to_json
|
|
145
|
+
}
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
chat = RubyLLM.chat(model: "gpt-4o-mini")
|
|
149
|
+
chat.with_tool(calculator)
|
|
150
|
+
chat.ask("What is 2+2?")
|
|
151
|
+
|
|
152
|
+
spans = EXPORTER.finished_spans
|
|
153
|
+
|
|
154
|
+
tool_spans = spans.select { |s| s.name.start_with?("execute_tool ") }
|
|
155
|
+
chat_spans = spans.select { |s| s.name.include?("chat ") }
|
|
156
|
+
|
|
157
|
+
assert_equal 1, tool_spans.length
|
|
158
|
+
assert_equal 1, chat_spans.length
|
|
159
|
+
|
|
160
|
+
tool_span = tool_spans.first
|
|
161
|
+
assert_equal OpenTelemetry::Trace::SpanKind::INTERNAL, tool_span.kind
|
|
162
|
+
assert_equal "execute_tool calculator", tool_span.name
|
|
163
|
+
assert_equal "calculator", tool_span.attributes["gen_ai.tool.name"]
|
|
164
|
+
assert_equal '{"expression":"2+2"}', tool_span.attributes["gen_ai.tool.call.arguments"]
|
|
165
|
+
assert_equal "4", tool_span.attributes["gen_ai.tool.call.result"]
|
|
166
|
+
assert_equal "call_abc123", tool_span.attributes["gen_ai.tool.call.id"]
|
|
167
|
+
assert_equal "function", tool_span.attributes["gen_ai.tool.type"]
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def test_execute_tool_still_works_when_instrumentation_fails
|
|
171
|
+
calculator = Class.new(RubyLLM::Tool) do
|
|
172
|
+
def self.name = "calculator"
|
|
173
|
+
description "Performs math"
|
|
174
|
+
param :expression, type: "string", desc: "Math expression"
|
|
175
|
+
|
|
176
|
+
def execute(expression:)
|
|
177
|
+
eval(expression).to_s
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
stub_request(:post, "https://api.openai.com/v1/chat/completions")
|
|
182
|
+
.to_return(
|
|
183
|
+
{
|
|
184
|
+
status: 200,
|
|
185
|
+
headers: { "Content-Type" => "application/json" },
|
|
186
|
+
body: {
|
|
187
|
+
id: "chatcmpl-123",
|
|
188
|
+
object: "chat.completion",
|
|
189
|
+
model: "gpt-4o-mini",
|
|
190
|
+
choices: [{
|
|
191
|
+
index: 0,
|
|
192
|
+
message: {
|
|
193
|
+
role: "assistant",
|
|
194
|
+
content: nil,
|
|
195
|
+
tool_calls: [{
|
|
196
|
+
id: "call_abc123",
|
|
197
|
+
type: "function",
|
|
198
|
+
function: { name: "calculator", arguments: '{"expression":"2+2"}' }
|
|
199
|
+
}]
|
|
200
|
+
},
|
|
201
|
+
finish_reason: "tool_calls"
|
|
202
|
+
}],
|
|
203
|
+
usage: { prompt_tokens: 10, completion_tokens: 5, total_tokens: 15 }
|
|
204
|
+
}.to_json
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
status: 200,
|
|
208
|
+
headers: { "Content-Type" => "application/json" },
|
|
209
|
+
body: {
|
|
210
|
+
id: "chatcmpl-456",
|
|
211
|
+
object: "chat.completion",
|
|
212
|
+
model: "gpt-4o-mini",
|
|
213
|
+
choices: [{
|
|
214
|
+
index: 0,
|
|
215
|
+
message: { role: "assistant", content: "The answer is 4" },
|
|
216
|
+
finish_reason: "stop"
|
|
217
|
+
}],
|
|
218
|
+
usage: { prompt_tokens: 20, completion_tokens: 5, total_tokens: 25 }
|
|
219
|
+
}.to_json
|
|
220
|
+
}
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
chat = RubyLLM.chat(model: "gpt-4o-mini")
|
|
224
|
+
chat.with_tool(calculator)
|
|
225
|
+
|
|
226
|
+
chat.define_singleton_method(:tracer) { raise StandardError, "instrumentation bug" }
|
|
227
|
+
|
|
228
|
+
response = chat.ask("What is 2+2?")
|
|
229
|
+
assert_equal "The answer is 4", response.content
|
|
230
|
+
end
|
|
231
|
+
end
|
data/test/test_helper.rb
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __dir__)
|
|
2
|
+
|
|
3
|
+
require "minitest/autorun"
|
|
4
|
+
require "webmock/minitest"
|
|
5
|
+
require "ruby_llm"
|
|
6
|
+
require "opentelemetry/sdk"
|
|
7
|
+
require "opentelemetry-instrumentation-ruby_llm"
|
|
8
|
+
|
|
9
|
+
EXPORTER = OpenTelemetry::SDK::Trace::Export::InMemorySpanExporter.new
|
|
10
|
+
span_processor = OpenTelemetry::SDK::Trace::Export::SimpleSpanProcessor.new(EXPORTER)
|
|
11
|
+
|
|
12
|
+
OpenTelemetry::SDK.configure do |c|
|
|
13
|
+
c.add_span_processor(span_processor)
|
|
14
|
+
c.use "OpenTelemetry::Instrumentation::RubyLLM"
|
|
15
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: opentelemetry-instrumentation-ruby_llm
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Clarissa Borges
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: opentelemetry-api
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '1.0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '1.0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: opentelemetry-instrumentation-base
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0.23'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0.23'
|
|
40
|
+
description: Adds OpenTelemetry tracing to RubyLLM chat operations
|
|
41
|
+
email:
|
|
42
|
+
- cborges@thoughtbot.com
|
|
43
|
+
executables: []
|
|
44
|
+
extensions: []
|
|
45
|
+
extra_rdoc_files: []
|
|
46
|
+
files:
|
|
47
|
+
- ".github/workflows/dynamic-readme.yml"
|
|
48
|
+
- ".github/workflows/dynamic-security.yml"
|
|
49
|
+
- ".github/workflows/main.yml"
|
|
50
|
+
- ".gitignore"
|
|
51
|
+
- CODEOWNERS
|
|
52
|
+
- CODE_OF_CONDUCT.md
|
|
53
|
+
- Gemfile
|
|
54
|
+
- Gemfile.lock
|
|
55
|
+
- LICENSE
|
|
56
|
+
- README.md
|
|
57
|
+
- Rakefile
|
|
58
|
+
- SECURITY.md
|
|
59
|
+
- example/trace_demonstration.rb
|
|
60
|
+
- example/trace_demonstration_with_langfuse.rb
|
|
61
|
+
- example/trace_demonstration_with_langfuse_and_tools.rb
|
|
62
|
+
- example/trace_demonstration_with_tools.rb
|
|
63
|
+
- lib/opentelemetry-instrumentation-ruby_llm.rb
|
|
64
|
+
- lib/opentelemetry/instrumentation/ruby_llm/instrumentation.rb
|
|
65
|
+
- lib/opentelemetry/instrumentation/ruby_llm/patches/chat.rb
|
|
66
|
+
- lib/opentelemetry/instrumentation/ruby_llm/version.rb
|
|
67
|
+
- opentelemetry-instrumentation-ruby_llm.gemspec
|
|
68
|
+
- test/instrumentation_test.rb
|
|
69
|
+
- test/test_helper.rb
|
|
70
|
+
homepage: https://github.com/thoughtbot/opentelemetry-instrumentation-ruby_llm
|
|
71
|
+
licenses:
|
|
72
|
+
- MIT
|
|
73
|
+
metadata:
|
|
74
|
+
homepage_uri: https://github.com/thoughtbot/opentelemetry-instrumentation-ruby_llm
|
|
75
|
+
rdoc_options: []
|
|
76
|
+
require_paths:
|
|
77
|
+
- lib
|
|
78
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - ">="
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: 3.2.0
|
|
83
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
84
|
+
requirements:
|
|
85
|
+
- - ">="
|
|
86
|
+
- !ruby/object:Gem::Version
|
|
87
|
+
version: '0'
|
|
88
|
+
requirements: []
|
|
89
|
+
rubygems_version: 3.7.1
|
|
90
|
+
specification_version: 4
|
|
91
|
+
summary: OpenTelemetry instrumentation for RubyLLM
|
|
92
|
+
test_files: []
|