Skip to content

Commit 52314ec

Browse files
chore(closes OPEN-10363): clean up trace configuration
1 parent 4569206 commit 52314ec

9 files changed

Lines changed: 550 additions & 421 deletions

File tree

Lines changed: 62 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,143 +1,141 @@
11
"""
2-
Example: Programmatic Configuration for Openlayer Tracing
2+
Example: Configuring the Openlayer Tracer
33
4-
This example demonstrates how to configure Openlayer tracing programmatically
5-
using the configure() function, instead of relying on environment variables.
4+
Demonstrates the three ways to configure the tracer and how they compose:
5+
1. Environment variables (canonical for deployments)
6+
2. init() — programmatic, idempotent, merges on repeated calls
7+
3. Per-decorator override via @trace(inference_pipeline_id=...)
8+
9+
Precedence (highest first):
10+
decorator argument > init() > environment variable > default
11+
12+
Also shows the deprecated configure() alias, kept for backward compatibility.
613
"""
714

815
import os
916
import openai
10-
from openlayer.lib import configure, trace, trace_openai
17+
from openlayer.lib import init, configure, get_tracer_config, trace, trace_openai
1118

1219

1320
def example_environment_variables():
14-
"""Traditional approach using environment variables."""
21+
"""Canonical deployment path — env vars only, no code changes needed."""
1522
print("=== Environment Variables Approach ===")
1623

17-
# Set environment variables (traditional approach)
1824
os.environ["OPENLAYER_API_KEY"] = "your_openlayer_api_key_here"
1925
os.environ["OPENLAYER_INFERENCE_PIPELINE_ID"] = "your_pipeline_id_here"
2026
os.environ["OPENAI_API_KEY"] = "your_openai_api_key_here"
2127

22-
# Use the @trace decorator
2328
@trace()
2429
def generate_response(query: str) -> str:
25-
"""Generate a response using OpenAI."""
26-
# Configure OpenAI client and trace it
2730
client = trace_openai(openai.OpenAI())
28-
2931
response = client.chat.completions.create(
3032
model="gpt-3.5-turbo",
3133
messages=[{"role": "user", "content": query}],
3234
max_tokens=100,
3335
)
3436
return response.choices[0].message.content
3537

36-
# Test the function
37-
result = generate_response("What is machine learning?")
38-
print(f"Response: {result}")
38+
print(f"Response: {generate_response('What is machine learning?')}")
3939

4040

41-
def example_programmatic_configuration():
42-
"""New approach using programmatic configuration."""
43-
print("\n=== Programmatic Configuration Approach ===")
41+
def example_programmatic_init():
42+
"""Programmatic configuration via init() — preferred for notebooks/apps."""
43+
print("\n=== Programmatic init() ===")
4444

45-
# Configure Openlayer programmatically
46-
configure(
45+
init(
4746
api_key="your_openlayer_api_key_here",
4847
inference_pipeline_id="your_pipeline_id_here",
49-
# base_url="https://api.openlayer.com/v1" # Optional: custom base URL
48+
# base_url="https://onprem.example.com", # Optional, for on-prem deployments
5049
)
5150

52-
# Set OpenAI API key
5351
os.environ["OPENAI_API_KEY"] = "your_openai_api_key_here"
5452

55-
# Use the @trace decorator (no environment variables needed for Openlayer)
5653
@trace()
57-
def generate_response_programmatic(query: str) -> str:
58-
"""Generate a response using OpenAI with programmatic configuration."""
59-
# Configure OpenAI client and trace it
54+
def generate_response(query: str) -> str:
6055
client = trace_openai(openai.OpenAI())
61-
6256
response = client.chat.completions.create(
6357
model="gpt-3.5-turbo",
6458
messages=[{"role": "user", "content": query}],
6559
max_tokens=100,
6660
)
6761
return response.choices[0].message.content
6862

69-
# Test the function
70-
result = generate_response_programmatic("What is deep learning?")
71-
print(f"Response: {result}")
63+
print(f"Response: {generate_response('What is deep learning?')}")
64+
65+
66+
def example_init_merges_on_repeat():
67+
"""init() is idempotent and merges — safe to call multiple times."""
68+
print("\n=== init() Merge Semantics ===")
69+
70+
init(api_key="key-A", inference_pipeline_id="pipeline-A")
71+
# Later in the program, override just one knob — the rest is preserved.
72+
init(inference_pipeline_id="pipeline-B")
73+
74+
cfg = get_tracer_config() # API key is redacted in the returned dict
75+
print(f"Resolved config: api_key={cfg['api_key']}, pipeline_id={cfg['inference_pipeline_id']}")
76+
# api_key=***, pipeline_id=pipeline-B
7277

7378

7479
def example_per_decorator_override():
75-
"""Example showing how to override pipeline ID per decorator."""
76-
print("\n=== Per-Decorator Pipeline ID Override ===")
80+
"""The @trace decorator can override the configured pipeline per-call."""
81+
print("\n=== Per-Decorator Override ===")
7782

78-
# Configure default settings
79-
configure(
80-
api_key="your_openlayer_api_key_here",
81-
inference_pipeline_id="default_pipeline_id",
82-
)
83+
init(api_key="your_openlayer_api_key_here", inference_pipeline_id="default_pipeline_id")
8384

84-
# Function using default pipeline ID
8585
@trace()
8686
def default_pipeline_function(query: str) -> str:
8787
return f"Response to: {query}"
8888

89-
# Function using specific pipeline ID (overrides default)
9089
@trace(inference_pipeline_id="specific_pipeline_id")
9190
def specific_pipeline_function(query: str) -> str:
9291
return f"Specific response to: {query}"
9392

94-
# Test both functions
95-
default_pipeline_function("Question 1") # Uses default_pipeline_id
96-
specific_pipeline_function("Question 2") # Uses specific_pipeline_id
93+
default_pipeline_function("Question 1") # default_pipeline_id
94+
specific_pipeline_function("Question 2") # specific_pipeline_id
9795

98-
print("Both functions executed with different pipeline IDs")
9996

97+
def example_mixed_env_and_init():
98+
"""Env var for API key, init() for pipeline — both honored via resolver."""
99+
print("\n=== Mixed (Env Var + init()) ===")
100100

101-
def example_mixed_configuration():
102-
"""Example showing mixed environment and programmatic configuration."""
103-
print("\n=== Mixed Configuration Approach ===")
104-
105-
# Set API key via environment variable
106101
os.environ["OPENLAYER_API_KEY"] = "your_openlayer_api_key_here"
107-
108-
# Set pipeline ID programmatically
109-
configure(inference_pipeline_id="programmatic_pipeline_id")
102+
init(inference_pipeline_id="programmatic_pipeline_id")
110103

111104
@trace()
112105
def mixed_config_function(query: str) -> str:
113-
"""Function using mixed configuration."""
114106
return f"Mixed config response to: {query}"
115107

116-
# Test the function
117-
result = mixed_config_function("What is the best approach?")
118-
print(f"Response: {result}")
108+
print(f"Response: {mixed_config_function('What is the best approach?')}")
109+
110+
111+
def example_deprecated_configure():
112+
"""configure() is kept for backward compatibility but is deprecated.
113+
114+
It now merges (rather than replacing) state and emits a DeprecationWarning
115+
on each call. New code should use init() instead.
116+
"""
117+
print("\n=== Deprecated configure() (still works) ===")
118+
119+
configure(
120+
api_key="your_openlayer_api_key_here",
121+
inference_pipeline_id="your_pipeline_id_here",
122+
)
119123

120124

121125
if __name__ == "__main__":
122126
print("Openlayer Tracing Configuration Examples")
123127
print("=" * 50)
124-
125-
# Note: Replace the placeholder API keys and IDs with real values
126-
print(
127-
"Note: Replace placeholder API keys and pipeline IDs with real values before running."
128-
)
129-
print()
128+
print("Note: Replace placeholder API keys and pipeline IDs with real values.\n")
130129

131130
try:
132-
# Run examples (these will fail without real API keys)
133131
example_environment_variables()
134-
example_programmatic_configuration()
132+
example_programmatic_init()
133+
example_init_merges_on_repeat()
135134
example_per_decorator_override()
136-
example_mixed_configuration()
137-
135+
example_mixed_env_and_init()
136+
example_deprecated_configure()
138137
except Exception as e:
139138
print(f"Example failed (expected with placeholder keys): {e}")
140139
print("\nTo run this example successfully:")
141140
print("1. Replace placeholder API keys with real values")
142141
print("2. Replace pipeline IDs with real Openlayer pipeline IDs")
143-
print("3. Ensure you have valid OpenAI and Openlayer accounts")

src/openlayer/lib/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
"""Openlayer lib."""
22

33
__all__ = [
4+
"init",
45
"configure",
6+
"get_tracer_config",
57
"trace",
68
"trace_anthropic",
79
"trace_openai",
@@ -43,7 +45,9 @@
4345
clear_user_session_context,
4446
)
4547

48+
init = tracer.init
4649
configure = tracer.configure
50+
get_tracer_config = tracer.get_tracer_config
4751
trace = tracer.trace
4852
trace_async = tracer.trace_async
4953
update_current_trace = tracer.update_current_trace

src/openlayer/lib/integrations/langchain_callback.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ def _end_step(
203203
and tracer.get_current_step() is None
204204
):
205205
trace = self._traces_by_root.pop(run_id)
206-
if tracer._configured_background_publish_enabled:
206+
if tracer._resolve("background_publish_enabled"):
207207
ctx = contextvars.copy_context()
208208
tracer._get_background_executor().submit(
209209
ctx.run, self._process_and_upload_trace, trace
@@ -262,9 +262,7 @@ def _process_and_upload_trace(self, trace: traces.Trace) -> None:
262262
serialized_config = utils.json_serialize(config)
263263

264264
client.inference_pipelines.data.stream(
265-
inference_pipeline_id=utils.get_env_variable(
266-
"OPENLAYER_INFERENCE_PIPELINE_ID"
267-
),
265+
inference_pipeline_id=tracer.resolve_pipeline_id(),
268266
rows=[serialized_trace_data],
269267
config=serialized_config,
270268
)
@@ -1313,7 +1311,7 @@ def _end_step(
13131311
# Only upload if: root step + has standalone trace + not part of external trace
13141312
if is_root_step and has_standalone_trace and not self._has_external_trace:
13151313
trace = self._traces_by_root.pop(run_id)
1316-
if tracer._configured_background_publish_enabled:
1314+
if tracer._resolve("background_publish_enabled"):
13171315
ctx = contextvars.copy_context()
13181316
tracer._get_background_executor().submit(
13191317
ctx.run, self._process_and_upload_async_trace, trace
@@ -1372,9 +1370,7 @@ def _process_and_upload_async_trace(self, trace: traces.Trace) -> None:
13721370
serialized_config = utils.json_serialize(config)
13731371

13741372
client.inference_pipelines.data.stream(
1375-
inference_pipeline_id=utils.get_env_variable(
1376-
"OPENLAYER_INFERENCE_PIPELINE_ID"
1377-
),
1373+
inference_pipeline_id=tracer.resolve_pipeline_id(),
13781374
rows=[serialized_trace_data],
13791375
config=serialized_config,
13801376
)

src/openlayer/lib/tracing/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44
from .tracer import (
55
configure,
66
create_step,
7+
get_tracer_config,
78
get_current_step,
89
get_current_trace,
10+
init,
911
log_attachment,
1012
log_context,
1113
log_output,
@@ -25,7 +27,9 @@
2527
"log_context",
2628
"log_output",
2729
"log_question",
30+
"init",
2831
"configure",
32+
"get_tracer_config",
2933
"get_current_trace",
3034
"get_current_step",
3135
"create_step",

src/openlayer/lib/tracing/attachment_uploader.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -297,15 +297,15 @@ def get_uploader() -> Optional[AttachmentUploader]:
297297
global _uploader
298298
from . import tracer
299299

300-
if not tracer._configured_attachment_upload_enabled:
300+
if not tracer._resolve("attachment_upload_enabled"):
301301
return None
302302

303303
if _uploader is None:
304304
client = tracer._get_client()
305305
if client:
306306
_uploader = AttachmentUploader(
307307
client,
308-
url_upload_enabled=tracer._configured_url_upload_enabled,
308+
url_upload_enabled=bool(tracer._resolve("url_upload_enabled")),
309309
)
310310

311311
return _uploader

0 commit comments

Comments
 (0)