Logging Events
Seer accepts one event per retrieval with a task (query) and a context array. You can log events two ways:
- Direct client: Call
client.log(...)where you construct the payload. - 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:
contextmust be eitherlist[str]orlist[dict]. If using dicts, includetext. Full shape + examples: Context & Event Schema.metadatais free-form — include anything you want to filter by later.sample_ratecontrols 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
| Parameter | Type | Description |
|---|---|---|
task_arg | str | Name of argument containing the query (default: "query") |
context_from_return | bool | If True, use the return value as context |
context_arg | str | Name of argument containing context (alternative to context_from_return) |
metadata | dict | Static metadata to attach to every log |
use_otel_trace | bool | Auto-detect OTEL trace context (default: True) |
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 Case | Recommended |
|---|---|
| Full control at each call site | client.log() |
| Cleanly encapsulated retrieval function | @seer_trace |
| Multiple retrieval functions | @seer_trace on each |
| Custom logging pipeline | client.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=[...])
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 flagrecall < 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
contextitems).
Next Steps
- SDK Reference: /seer/sdk-python
- Metrics Definitions: /seer/metrics
- Change Testing (A/B): /seer/change-testing
- Production Monitoring: /seer/production-monitoring