LangfuseLangfuse Python SDK 4.15.4 · Python 3.10+
0%
1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
38 small wins to finish your pathNext lesson

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.

Exampledesk.py
"""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.

Exampledesk.py, continued
@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 status

The tool with its warning for a missing order, from lesson 8.

Exampledesk.py, continued
@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 text

The model call as a decorated generation, from lesson 6.

Exampledesk.py, continued
@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

Examplewho.py
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 answer

The 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.

Examplewho.py, continued
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?")
Examplewho.py, continued
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')}")
Example
python who.py

One 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.

Examplewho.py, fixed
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?")
Example
python who.py

Set 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).

Try it yourself
  • Pass a metadata value of 250 characters and read the warning.
  • Add version="2" to propagate_attributes and find langfuse.version in the attributes.
  • Put propagate_attributes inside answer in desk.py instead. Which observations get the user now?

This is what real progress feels like.