A string that LangSmith serializes as [LANGSMITH SECRET].
Wrap a credential once, where it is read, and LangSmith masks it wherever
it appears in a trace, at any nesting depth::
@traceable
def call_vendor(api_key: str, prompt: str) -> str: ...
call_vendor(api_key=LangSmithSecret(key), prompt="hi")
# traced inputs: {"api_key": "[LANGSMITH SECRET]", "prompt": "hi"}
It is a real str everywhere else: json.dumps, logging and
third-party clients all see the true value. Operations that derive a new
string return a LangSmithSecret again, so the marker is not lost by
.strip() or slicing.
Known limits:
f"Bearer {secret}", "".join([secret]) and
"Bearer {}".format(secret) yield a plain str: those build the
result in C, where the operand gets no say in its type. Wrap the finished
value instead -- LangSmithSecret(f"Bearer {key}"). Concatenation and
% are covered, in either operand order.
secret.encode() returns the real bytes, by design.
- A secret used as a dict key is masked, but two of them in one dict
collapse to a single entry. A key held inside a dataclass is not masked:
orjson serializes those itself.
- An object whose own
__str__ interpolates a secret leaks it wherever
LangSmith stringifies that object. __repr__ is masked; __str__
belongs to the object.
- Nothing inside a pydantic model is masked. Pydantic serializes its
own fields and LangSmith deliberately does not override that: a hook
there would also replace the credential with the mask when a vendor
client dumps the model to build a request.