Instrumentation
Details on code instrumentation and best practices for tracing with Laminar
Automatic instrumentation
By default, Laminar.initialize()
will automatically instrument majority of common LLM and VectorDB libraries for tracing.
This includes OpenAI, Anthropic, Langchain, Pinecone, and many more.
Instrument all available libraries
from lmnr import Laminar as L
import os
from openai import OpenAI
# ...
L.initialize(project_api_key=os.environ["LMNR_PROJECT_API_KEY"])
See all available auto-instrumentable modules here.
Disable automatic instrumentation
initialize()
also accepts
an optional instruments
parameter. If you explicitly pass an empty set,
no automatic instrumentations will be applied.
from lmnr import Laminar as L
import os
from openai import OpenAI
L.initialize(
project_api_key=os.environ["LMNR_PROJECT_API_KEY"],
instruments=set()
)
# When you call OpenAI, it will NOT be instrumented
Instrument specific modules only
You can also enable instrumentation for specific modules only.
This is useful if you either want more control over what is being instrumented. Also, use this if you are using NextJS and having issues with automatic instrumentation – learn more.
Let’s say, for example, we call OpenAI and Anthropic models to perform the same task, and we only want to instrument the Anthropic calls, but not OpenAI.
initialize()
accepts
an optional instruments
parameter. Pass a set of instruments you want to enable.
In this case we only want to pass Instruments.ANTHROPIC
.
See available instruments in the next subsection.
import os
from anthropic import Anthropic
from openai import OpenAI
from lmnr import observe, Laminar as L, Instruments
L.initialize(
project_api_key=os.environ["LMNR_PROJECT_API_KEY"],
instruments={ Instruments.ANTHROPIC } # only enable anthropic, not openai
)
openai_client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])
anthropic_client = Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])
def poem_writer(topic="turbulence"):
prompt = f"write a poem about {topic}"
messages = [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": prompt},
]
# OpenAI calls are NOT instrumented
openai_response = openai_client.chat.completions.create(
model="gpt-4o",
messages=messages,
)
# Anthropic calls are instrumented
anthropic_response = anthropic_client.messages.create(
max_tokens=1024,
messages=messages,
model="claude-3-5-sonnet-20240620",
)
openai_poem = openai_response.choices[0].message.content
anthropic_poem = anthropic_response.content[0].text
return {"o": openai_poem, "a": anthropic_poem}
if __name__ == "__main__":
print(poem_writer(topic="laminar flow"))
Available instruments
See available instruments by importing Instruments
from lmnr
or view source.
These exact modules are auto-instrumented, if you do not pass instruments
to L.initialize()
.
Manual instrumentation
Use observe
to group separate LLM calls in one trace
Automatic instrumentation creates spans for LLM calls within the current trace context.
Unless you start a new trace before calling an LLM, each LLM call will create a new trace.
If you want to group several auto-instrumented calls in one trace,
simply observe
the top-level function that makes these calls.
Example
In this example, the request_handler
makes a call to OpenAI to determine the user
intent. If the intent matches the expected one, the handler makes another call to
OpenAI (possibly with additional RAG) to generate a response.
request_handler
is observed, so all calls to OpenAI inside it are grouped in one trace.
from lmnr import Laminar as L, observe
from openai import OpenAI
import os
L.initialize(project_api_key=os.environ['LMNR_PROJECT_API_KEY'])
openai_client = OpenAI(api_key=os.environ['OPENAI_API_KEY'])
@observe()
def request_handler(user_message: str):
router_prompt = """Your goal is to determine if the user is asking for \
help with onboarding to the service. Answer 'yes' or 'no' without any \
explanation.""" + f"User message: {user_message}"
user_intent = openai_client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{
"role": "user",
"content": router_prompt
}
]
)
if user_intent.choices[0].message.content == "yes":
# likely some RAG here to enrich the context
# ...
model_response = openai_client.chat.completions.create(
model="gpt-4o",
messages=[
{
"role": "user",
"content": user_message
}
]
)
return model_response.choices[0].message.content
return "the user is not asking for help with onboarding to the service"
As a result, you will get a nested trace with the request_handler
span as the top level span,
and the OpenAI calls as child spans.
Observe specific code chunks
Also, in Python, you can use start_as_current_span
if you want to record a chunk of your code using with
statement.
Example
from lmnr import Laminar as L
from openai import OpenAI
L.initialize(project_api_key=os.environ['LMNR_PROJECT_API_KEY'])
openai_client = OpenAI(api_key=os.environ['OPENAI_API_KEY'])
def request_handler(user_message: str):
with L.start_as_current_span(name="handler", input=user_message) as span:
response = openai_client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{
"role": "user",
"content": user_message
}
]
)
result = response.choices[0].message.content
L.set_span_output(result) # this will set the output of the current span
return result