Skip to content

Commit 4487ff8

Browse files
committed
Gate LangSmith-specific attributes behind tracing_langsmith_compat
Add tracing_langsmith_compat config option (default: false) that: - Adds langsmith.span.kind attribute to spans - Adds input.value/output.value for LangSmith panels - Auto-sets tracing_metadata_prefix to 'langsmith.metadata' Standard gen_ai.* attributes are always emitted regardless of this setting. This allows users of other OTel backends to avoid the LangSmith-specific noise while LangSmith users get full integration. Also replaces the made-up gen_ai.prompt/gen_ai.completion fallback attributes with proper gen_ai.tool.input/gen_ai.tool.output for tool spans.
1 parent 2884c5c commit 4487ff8

File tree

5 files changed

+189
-36
lines changed

5 files changed

+189
-36
lines changed

docs/_advanced/observability.md

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,13 @@ RubyLLM.configure do |config|
7777
config.tracing_enabled = true
7878

7979
# Log prompt/completion content (default: false)
80-
# Enable for full LangSmith functionality
8180
config.tracing_log_content = true
8281

8382
# Max content length before truncation (default: 10000)
8483
config.tracing_max_content_length = 5000
84+
85+
# Enable LangSmith-specific span attributes (default: false)
86+
config.tracing_langsmith_compat = true
8587
end
8688
```
8789

@@ -106,11 +108,13 @@ chat = RubyLLM.chat
106108
chat.ask("Hello!")
107109
```
108110

109-
Metadata appears as `metadata.*` attributes by default. For LangSmith's metadata panel, set:
111+
Metadata appears as `metadata.*` attributes by default. When `tracing_langsmith_compat` is enabled, metadata uses the `langsmith.metadata.*` prefix for proper LangSmith panel integration.
112+
113+
You can also set a custom prefix:
110114

111115
```ruby
112116
RubyLLM.configure do |config|
113-
config.tracing_metadata_prefix = 'langsmith.metadata'
117+
config.tracing_metadata_prefix = 'app.metadata'
114118
end
115119
```
116120

@@ -127,7 +131,7 @@ LangSmith is LangChain's observability platform with specialized LLM debugging f
127131
RubyLLM.configure do |config|
128132
config.tracing_enabled = true
129133
config.tracing_log_content = true
130-
config.tracing_metadata_prefix = 'langsmith.metadata'
134+
config.tracing_langsmith_compat = true # Adds LangSmith-specific span attributes
131135
end
132136
```
133137

@@ -153,6 +157,11 @@ end
153157

154158
LangSmith uses the `Langsmith-Project` header (not `service_name`) to organize traces.
155159

160+
When `tracing_langsmith_compat = true`, RubyLLM adds these additional attributes for LangSmith integration:
161+
- `langsmith.span.kind` - Identifies span type (LLM, TOOL)
162+
- `input.value` / `output.value` - Populates LangSmith's Input/Output panels
163+
- `langsmith.metadata.*` - Custom metadata appears in LangSmith's metadata panel
164+
156165
### Other Backends
157166

158167
RubyLLM works with any OpenTelemetry-compatible backend. Configure the `opentelemetry-exporter-otlp` gem to send traces to your platform of choice.

lib/ruby_llm/chat.rb

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,8 @@ def complete_without_instrumentation(&)
159159
end
160160

161161
def complete_with_span(span, &)
162+
langsmith = @config.tracing_langsmith_compat
163+
162164
# Set request attributes
163165
if span.recording?
164166
span.add_attributes(
@@ -167,7 +169,8 @@ def complete_with_span(span, &)
167169
provider: @provider.slug,
168170
session_id: @session_id,
169171
temperature: @temperature,
170-
metadata: @metadata
172+
metadata: @metadata,
173+
langsmith_compat: langsmith
171174
)
172175
)
173176

@@ -176,7 +179,8 @@ def complete_with_span(span, &)
176179
span.add_attributes(
177180
Instrumentation::SpanBuilder.build_message_attributes(
178181
messages,
179-
max_length: @config.tracing_max_content_length
182+
max_length: @config.tracing_max_content_length,
183+
langsmith_compat: langsmith
180184
)
181185
)
182186
end
@@ -200,7 +204,8 @@ def complete_with_span(span, &)
200204
span.add_attributes(
201205
Instrumentation::SpanBuilder.build_completion_attributes(
202206
response,
203-
max_length: @config.tracing_max_content_length
207+
max_length: @config.tracing_max_content_length,
208+
langsmith_compat: langsmith
204209
)
205210
)
206211
end
@@ -304,20 +309,23 @@ def execute_tool(tool_call)
304309
def execute_tool_with_span(tool_call, span)
305310
tool = tools[tool_call.name.to_sym]
306311
args = tool_call.arguments
312+
langsmith = @config.tracing_langsmith_compat
307313

308314
if span.recording?
309315
span.add_attributes(
310316
Instrumentation::SpanBuilder.build_tool_attributes(
311317
tool_call: tool_call,
312-
session_id: @session_id
318+
session_id: @session_id,
319+
langsmith_compat: langsmith
313320
)
314321
)
315322

316323
if @config.tracing_log_content
317324
span.add_attributes(
318325
Instrumentation::SpanBuilder.build_tool_input_attributes(
319326
tool_call: tool_call,
320-
max_length: @config.tracing_max_content_length
327+
max_length: @config.tracing_max_content_length,
328+
langsmith_compat: langsmith
321329
)
322330
)
323331
end
@@ -329,7 +337,8 @@ def execute_tool_with_span(tool_call, span)
329337
span.add_attributes(
330338
Instrumentation::SpanBuilder.build_tool_output_attributes(
331339
result: result,
332-
max_length: @config.tracing_max_content_length
340+
max_length: @config.tracing_max_content_length,
341+
langsmith_compat: langsmith
333342
)
334343
)
335344
end

lib/ruby_llm/configuration.rb

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ class Configuration
5151
:tracing_enabled,
5252
:tracing_log_content,
5353
:tracing_max_content_length,
54-
:tracing_metadata_prefix
54+
:tracing_metadata_prefix,
55+
:tracing_langsmith_compat
5556

5657
def initialize
5758
@request_timeout = 300
@@ -79,6 +80,13 @@ def initialize
7980
@tracing_log_content = false
8081
@tracing_max_content_length = 10_000
8182
@tracing_metadata_prefix = 'metadata'
83+
@tracing_langsmith_compat = false
84+
end
85+
86+
def tracing_langsmith_compat=(value)
87+
@tracing_langsmith_compat = value
88+
# Auto-set metadata prefix for LangSmith when enabling compat mode
89+
@tracing_metadata_prefix = 'langsmith.metadata' if value
8290
end
8391

8492
def instance_variables

lib/ruby_llm/instrumentation.rb

Lines changed: 25 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -121,41 +121,44 @@ def describe_attachments(attachments)
121121
"[#{descriptions.join(', ')}]"
122122
end
123123

124-
def build_message_attributes(messages, max_length:)
124+
def build_message_attributes(messages, max_length:, langsmith_compat: false)
125125
attrs = {}
126126
messages.each_with_index do |msg, idx|
127127
attrs["gen_ai.prompt.#{idx}.role"] = msg.role.to_s
128128
content = extract_content_text(msg.content)
129129
attrs["gen_ai.prompt.#{idx}.content"] = truncate_content(content, max_length)
130130
end
131131
# Set input.value for LangSmith Input panel (last user message)
132-
last_user_msg = messages.reverse.find { |m| m.role.to_s == 'user' }
133-
if last_user_msg
134-
content = extract_content_text(last_user_msg.content)
135-
attrs['input.value'] = truncate_content(content, max_length)
132+
if langsmith_compat
133+
last_user_msg = messages.reverse.find { |m| m.role.to_s == 'user' }
134+
if last_user_msg
135+
content = extract_content_text(last_user_msg.content)
136+
attrs['input.value'] = truncate_content(content, max_length)
137+
end
136138
end
137139
attrs
138140
end
139141

140-
def build_completion_attributes(message, max_length:)
142+
def build_completion_attributes(message, max_length:, langsmith_compat: false)
141143
attrs = {}
142144
attrs['gen_ai.completion.0.role'] = message.role.to_s
143145
content = extract_content_text(message.content)
144146
truncated = truncate_content(content, max_length)
145147
attrs['gen_ai.completion.0.content'] = truncated
146148
# Set output.value for LangSmith Output panel
147-
attrs['output.value'] = truncated
149+
attrs['output.value'] = truncated if langsmith_compat
148150
attrs
149151
end
150152

151-
def build_request_attributes(model:, provider:, session_id:, temperature: nil, metadata: nil)
153+
def build_request_attributes(model:, provider:, session_id:, temperature: nil, metadata: nil,
154+
langsmith_compat: false)
152155
attrs = {
153-
'langsmith.span.kind' => 'LLM',
154156
'gen_ai.system' => provider.to_s,
155157
'gen_ai.operation.name' => 'chat',
156158
'gen_ai.request.model' => model.id,
157159
'gen_ai.conversation.id' => session_id
158160
}
161+
attrs['langsmith.span.kind'] = 'LLM' if langsmith_compat
159162
attrs['gen_ai.request.temperature'] = temperature if temperature
160163
build_metadata_attributes(attrs, metadata) if metadata
161164
attrs
@@ -188,33 +191,32 @@ def build_response_attributes(response)
188191
attrs
189192
end
190193

191-
def build_tool_attributes(tool_call:, session_id:)
192-
{
193-
'langsmith.span.kind' => 'TOOL',
194+
def build_tool_attributes(tool_call:, session_id:, langsmith_compat: false)
195+
attrs = {
194196
'gen_ai.operation.name' => 'tool',
195197
'gen_ai.tool.name' => tool_call.name.to_s,
196198
'gen_ai.tool.call.id' => tool_call.id,
197199
'gen_ai.conversation.id' => session_id
198200
}
201+
attrs['langsmith.span.kind'] = 'TOOL' if langsmith_compat
202+
attrs
199203
end
200204

201-
def build_tool_input_attributes(tool_call:, max_length:)
205+
def build_tool_input_attributes(tool_call:, max_length:, langsmith_compat: false)
202206
args = tool_call.arguments
203-
input = args.is_a?(String) ? args : args.to_json
207+
input = args.is_a?(String) ? args : JSON.generate(args)
204208
truncated = truncate_content(input, max_length)
205-
{
206-
'input.value' => truncated, # LangSmith Input panel
207-
'gen_ai.prompt' => truncated # GenAI convention fallback
208-
}
209+
attrs = { 'gen_ai.tool.input' => truncated }
210+
attrs['input.value'] = truncated if langsmith_compat
211+
attrs
209212
end
210213

211-
def build_tool_output_attributes(result:, max_length:)
214+
def build_tool_output_attributes(result:, max_length:, langsmith_compat: false)
212215
output = result.is_a?(String) ? result : result.to_s
213216
truncated = truncate_content(output, max_length)
214-
{
215-
'output.value' => truncated, # LangSmith Output panel
216-
'gen_ai.completion' => truncated # GenAI convention fallback
217-
}
217+
attrs = { 'gen_ai.tool.output' => truncated }
218+
attrs['output.value'] = truncated if langsmith_compat
219+
attrs
218220
end
219221
end
220222
end

0 commit comments

Comments
 (0)