Three tickets, and the card number
One program decides where traces and model calls go, and what is stripped before anything leaves. It runs three tickets, one with a card number in it.
This is the only file that knows about servers and keys. The desk module knows none of it.
from langfuse import Langfuse
from langfuse.openai import OpenAI
import local_langfuse
import support_desk
from privacy import mask_cards
from shop_model import reply
url = local_langfuse.start(model=reply)
langfuse = Langfuse(public_key="pk-lf-local", secret_key="sk-lf-local", base_url=url,
environment="production", release="2026.03.1", mask_otel_spans=mask_cards)
support_desk.model = OpenAI(base_url=f"{url}/v1", api_key="not-a-real-key")
langfuse.create_prompt(name="desk-system", prompt=support_desk.FALLBACK, labels=["production"])The client carries the environment and the version of the deployment, so traces can be filtered by either, and the masking function from part 4, which runs on everything exported. The desk's model client is set here too, which is how the tests point it somewhere else.
tickets = [("ticket-1042", "Where is my order A17?"),
("ticket-1043", "Is B22 on its way? Card 4111 1111 1111 1111 was charged."),
("ticket-1044", "I want a refund for A17")]
for number, text in tickets:
print(number, "->", support_desk.handle(number, text, customer="cust-42", chat="chat-0042"))
support_desk.record_feedback("ticket-1043", helpful=False, comment="B22 is on my receipt")
langfuse.flush()
local_langfuse.tree()
print([(s["name"], s["attributes"]["langfuse.observation.status_message"]) for s in local_langfuse.SPANS
if s["attributes"].get("langfuse.observation.level") == "WARNING"])
print("card exported:", "4111" in str([s["attributes"] for s in local_langfuse.SPANS]))python app.pyThree traces, each rooted in support-ticket with the model call and any lookup under it. The B22 ticket carries the warning. The card number reached the desk and was replaced before export, so the last line prints False.
Pick one to watch it run, step by step.
Click through the three tickets in the drawing: the answer, the warning and the masked card each take a different path through the same desk.
- Add a fourth ticket with an email address and mask that too.
- Set
environment="staging"and find it on the observations. - Remove
mask_otel_spansand watch the last line turn to True.
Slow is fine. Stopping is the only problem.