Skip to main content

Logging Events

Seer accepts one event per retrieval with a task (query) and a context array. You can log events two ways:

  1. Direct client: Call client.log(...) where you construct the payload.
  2. Decorator: Wrap your retrieval function to auto-capture inputs/outputs.

New to the context format? See Context & Event Schema for the exact shapes.


Install & Initialize

pip install seer-sdk
export SEER_API_KEY="seer_live_your_key_here"
from seer import SeerClient

client = SeerClient() # reads SEER_API_KEY from env

Option A — Direct Logging (client.log)

Use this when you already have the query + context assembled.

query = "Who directed Inception and what is their nationality?"
context = [
{"text": "Christopher Nolan directed Inception.", "id": "p-001", "score": 0.95},
{"text": "Nolan is British-American.", "id": "p-002", "score": 0.89},
]

client.log(
task=query, # the user query
context=context, # list of passage dicts or strings
metadata={
"env": "prod", # environment tag
"feature_flag": "retrieval-v1", # for A/B testing
},
sample_rate=0.1, # 10% sampling for monitoring
)
# Events are sent automatically in the background

Notes:

  • context must be either list[str] or list[dict]. If using dicts, include text. Full shape + examples: Context & Event Schema.
  • metadata is free-form — include anything you want to filter by later.
  • sample_rate controls what % of events get evaluated (for cost management).

Option B — Decorator (@seer_trace)

The decorator eliminates boilerplate by mapping your function's arguments/return to Seer's event fields.

Pattern 1: Context from Return Value

For retrieval functions where the return value is the context:

from seer import seer_trace

@seer_trace(
task_arg="query", # which argument is the query
context_from_return=True, # use return value as context
)
def retrieve(query: str) -> list[dict]:
# Your retriever returns passage dicts
return [
{"text": "Christopher Nolan directed Inception.", "score": 0.95},
{"text": "Nolan is British-American.", "score": 0.89}
]

# Decorator logs: task=query, context=return_value
results = retrieve("Who directed Inception?")

The return value can be list[dict] (must have text key) or list[str] (auto-converted).

Pattern 2: Context from Input Argument

For processing functions where context is an input argument (not the return):

@seer_trace(
task_arg="query", # which argument is the query
context_arg="passages", # which argument is the context
)
def generate_answer(query: str, passages: list[dict]) -> str:
# Function receives context as input, returns something else
return "Christopher Nolan, a British-American filmmaker"

# Decorator logs: task=query, context=passages
answer = generate_answer("Who directed Inception?", retrieved_passages)

Decorator Options

ParameterTypeDescription
task_argstrName of argument containing the query (default: "query")
context_from_returnboolIf True, use the return value as context
context_argstrName of argument containing context (alternative to context_from_return)
metadatadictStatic metadata to attach to every log
use_otel_traceboolAuto-detect OTEL trace context (default: True)
Choose One

Use either context_from_return=True or context_arg="..." — they are mutually exclusive ways to specify where the context comes from.

Adding Dynamic Metadata

from seer import seer_trace

@seer_trace(
task_arg="query",
context_from_return=True,
metadata={"service": "help-bot"}, # static metadata
)
def retrieve(query: str) -> list[dict]:
return [{"text": "...", "score": 0.9}]

Choosing Between Client vs Decorator

Use CaseRecommended
Full control at each call siteclient.log()
Cleanly encapsulated retrieval function@seer_trace
Multiple retrieval functions@seer_trace on each
Custom logging pipelineclient.log()

Fire-and-Forget vs Synchronous

By default, the SDK uses fire-and-forget mode — events are queued and sent asynchronously in the background.

# Fire-and-forget (default) — log() returns immediately
client = SeerClient() # fire_and_forget=True by default
client.log(task="...", context=[...]) # returns None, queued async
# Events auto-flush on process exit

# Synchronous — log() blocks and returns record_id
client = SeerClient(fire_and_forget=False)
record_id = client.log(task="...", context=[...]) # returns record_id
print(f"Created record: {record_id}")

OpenTelemetry Integration

The SDK automatically captures OTEL trace context when available:

from opentelemetry import trace

tracer = trace.get_tracer(__name__)

with tracer.start_as_current_span("retrieval"):
# trace_id, span_id, parent_span_id, span_name captured automatically
client.log(task="...", context=[...])
Enable OTEL Auto-Detection

Install with pip install seer-sdk[otel] to enable auto-detection.

If you already have opentelemetry-api in your environment, it works automatically.

To disable auto-detection or provide manual IDs:

# Disable OTEL auto-detection
client.log(task="...", context=[...], use_otel_trace=False)

# Provide manual trace IDs
client.log(
task="...",
context=[...],
trace_id="0af7651916cd43dd8448eb211c80319c",
span_id="b7ad6b7169203331",
)

How This Powers Seer

Once logs arrive, Seer computes:

  • Recall: Our model enumerates minimal disjoint requirements (K) needed to answer the query and checks which are covered by your context. We flag recall < 1.0.
  • Precision: Fraction of unique documents judged "supporting" over the number in context (signal for context bloat).
  • F1, nDCG: Derived from recall + precision (and optional ranks/scores if present in your context items).

Next Steps