Who and what: user_id, tags and metadata
propagate_attributes puts a user id, tags and metadata on the running observation and every observation started inside it. Set them first, or early steps miss them.
Part 2 gave the desk a model. Its traces say what happened, but not for whom or through which channel. Langfuse groups and filters traces by a few trace attributes: the user id of the customer, tags such as the feature involved, and metadata. Before using them, the desk moves into a module of its own, so lesson files stay short.
"""The shop's support desk, traced: the pieces from lessons 4 to 8 in one module."""
import re
from langfuse import get_client, observe
from shop_model import reply
ORDERS = {"A17": "shipped on 3 March"}
SYSTEM = "You answer customers of a small online shop in one short sentence."get_client() returns the Langfuse client your program already created, so the module needs no setup of its own. The orders and the instruction to the model are the ones from part 2.
@observe(name="lookup-order", as_type="tool")
def lookup_order(order_id):
status = ORDERS.get(order_id, "not found")
if status == "not found":
get_client().update_current_span(level="WARNING", status_message=f"no order {order_id}")
return statusThe tool with its warning for a missing order, from lesson 8.
@observe(name="write-reply", as_type="generation")
def ask_model(messages):
text, usage = reply(messages)
get_client().update_current_generation(model="shop-model", usage_details=usage)
return textThe model call as a decorated generation, from lesson 6.
@observe(name="answer-ticket", as_type="agent")
def answer(ticket, system=SYSTEM):
order = re.search(r"[A-Z]\d{2}", ticket)
facts = f"\nLookup: {order.group()} {lookup_order(order.group())}" if order else ""
return ask_model([{"role": "system", "content": system}, {"role": "user", "content": ticket + facts}])The agent, with one argument added: system lets a caller try another instruction for the model, which part 6 uses to compare prompts.
Adding a customer and tags
from langfuse import Langfuse, propagate_attributes
import local_langfuse
url = local_langfuse.start()
langfuse = Langfuse(public_key="pk-lf-local", secret_key="sk-lf-local", base_url=url)
from desk import answerThe setup lines import propagate_attributes this time, and the desk comes from its module. Lessons from here start the same way, with whichever names they need.
with langfuse.start_as_current_observation(name="email-ticket", input="Where is my order A17?"):
answer("Where is my order A17?")
with propagate_attributes(user_id="cust-42", tags=["orders", "email"], metadata={"channel": "email"}):
answer("And is B22 on its way?")langfuse.flush()
for span in local_langfuse.SPANS:
attributes = span["attributes"]
print(f"{span['name']:14} user={attributes.get('user.id')} tags={attributes.get('langfuse.trace.tags')}")python who.pyOne email with two questions, one trace. Observations are printed in the order they ended. The first answer ran before propagate_attributes, so its three observations have no user and no tags. The second answer's observations have them, and so does email-ticket, which was still running when the attributes were set. The documentation's advice is to propagate early in the trace, and this is why.
with langfuse.start_as_current_observation(name="email-ticket", input="Where is my order A17?"):
with propagate_attributes(user_id="cust-42", tags=["orders", "email"], metadata={"channel": "email"}):
answer("Where is my order A17?")
answer("And is B22 on its way?")python who.pySet first, every observation in the trace carries them. Langfuse stores a copy of these trace-level attributes on each observation, which keeps its filters and queries fast.
Limits
The user id, each tag and each metadata value must be US-ASCII strings of at most 200 characters; a longer value is dropped with a warning. Tags cannot be changed after the observation is created. Metadata keys should be letters and digits only. A value that a person would search for later, such as an order id, fits metadata; a value decided after the answer, such as whether it was good, belongs in a score (lesson 23).
- Pass a metadata value of 250 characters and read the warning.
- Add
version="2"topropagate_attributesand findlangfuse.versionin the attributes. - Put
propagate_attributesinsideanswerindesk.pyinstead. Which observations get the user now?
This is what real progress feels like.