LangSmithExporter
On this page
Overview
Sends Fennec spans to LangSmith as runs.
The langsmith package is an optional dependency — it's only
imported the first time a LangSmithExporter is constructed, so nothing
else in zerda.observability requires it to be installed.
Not re-exported from observability/__init__.py's top level; use
enable_langsmith() for the
zero-import-hassle path, or import it explicitly from
observability.exporters (or observability.exporters.langsmith) if you
need the class itself (e.g. to isinstance-check it or subclass it).
Field mapping
From the module docstring, reproduced here since it documents intended behavior directly:
Constructor
def __init__(
self,
api_key: Optional[str] = None,
project_name: str = "default",
**client_kwargs: Any,
) -> None
Raises: ImportError if the langsmith package is not installed,
with the message:
LangSmithExporter requires the optional 'langsmith' package. Install it with: pip install langsmith
Behavior detail: the source constructs the client as
Client(api_key=api_key, **client_kwargs) only if api_key is
truthy, and as Client(**client_kwargs) (no api_key kwarg at all)
otherwise — so when api_key is None, it is not passed to Client(...)
as api_key=None; it's omitted entirely, which is what allows the
client's own env-var fallback to take effect.
export
def export(self, span: Span) -> None
Everything in this method is wrapped in a try/except Exception, logged
via logger.exception(...) on failure and otherwise swallowed — the
docstring/comment in the source is explicit that this is intentional
("never break the app"). This is in addition to, not instead of, the
isolation Tracer._drain_loop already provides — belt and suspenders.
Steps performed:
- Look up
run_typevia_RUN_TYPE_MAP, defaulting to"chain"for anyspan.typenot in the map. - Build
extrastarting from a copy ofspan.metadata, then addextra["model"]/extra["provider"](only if set on the span), andextra["token_usage"](viaTokenUsage.to_dict(), only ifspan.token_usageis set). - Call
self._client.create_run(...)with:inputs:{"input": span.input}ifspan.inputisn't already a dict, elsespan.inputitself (passed through unchanged).outputs:{"output": span.output}ifspan.outputis notNoneand not already a dict;span.outputitself if it's a dict; omitted asNoneifspan.output is None(the ternary'selsebranch also fires whenoutputisNone, producingoutputs=None).start_time/end_time: converted from epoch seconds to timezone-aware UTCdatetimeobjects via the private_to_datetime()helper (end_timeonly ifspan.end_timeis set).error:span.errorpassed through as-is (a string orNone) — notespan.stack_traceis not separately forwarded despite being listed in the field-mapping table above; onlyerroris passed tocreate_run.extra:{"metadata": extra}— i.e. the whole built-upextradict from step 2 is nested one level under a"metadata"key.trace_id,parent_run_id(fromspan.parent_id),project_name(from the constructor argument), andid(fromspan.id).
shutdown
def shutdown(self) -> None
If the underlying langsmith.Client exposes a callable flush
attribute, calls it — wrapped in its own try/except, logged on failure.
If the client has no flush method, this is a no-op.
_RUN_TYPE_MAP (internal)
Module-level constant mapping every built-in SpanType
value to one of the run types LangSmith's create_run accepts:
{
"chain": "chain", "llm": "llm", "tool": "tool",
"tool_call": "tool", "tool_result": "tool",
"retriever": "retriever", "embedding": "embedding",
"vector_search": "retriever", "context": "chain",
"rerank": "chain", "agent": "chain", "planning": "chain",
"router": "chain", "response": "chain", "generic": "chain",
}
Any span.type string not present in this dict (e.g. a custom type you
invented) is mapped to "chain" by the _RUN_TYPE_MAP.get(span.type, "chain") call in export().
_to_datetime (internal)
def _to_datetime(epoch_seconds: float): ...
Converts epoch seconds to a timezone-aware UTC datetime via
datetime.fromtimestamp(epoch_seconds, tz=timezone.utc). Imports
datetime locally inside the function rather than at module scope.
Example
from zerda.observability import enable_langsmith, get_tracer
enable_langsmith(api_key="ls__...", project_name="Customer Support")
with get_tracer().span("Retriever", type="retriever") as span:
span.set_input("what's the refund policy?")
docs = retrieve(...)
span.set_output(docs)
If langsmith is not installed:
>>> enable_langsmith()
ImportError: LangSmithExporter requires the optional 'langsmith' package. Install it with: pip install langsmith
Related: BaseExporter · Span · enable_langsmith · Installation → Optional dependency