# Langfuse: The @observe Decorator — LLM Observability (Langfuse / LangSmith)

Source: https://www.skillbyai.com/en/llm-observability/lo-lf-observe

> Trace an app with the @observe decorator and the drop-in OpenAI integration with minimal code changes.

## Decorate functions, get a tree

The quickest way to instrument Python code with Langfuse is the `@observe()` decorator. Put it on any function and each call becomes an observation: the function's arguments are captured as input, its return value as output, and its start and end times as duration. Because the SDK tracks the current observation with context variables, **nested decorated calls automatically become children**: if `answer()` calls `retrieve()` and both are decorated, the trace shows `answer` with `retrieve` inside it. The outermost decorated call creates the trace. It works for sync and async functions. For model calls, the **drop-in OpenAI integration** (`from langfuse.openai import openai`) records each completion as a generation with model, messages, usage and cost, and it nests under whatever decorated function is running. You can turn off input or output capture for a function (for example when it handles large files or secrets) with decorator options such as `capture_input=False`.

## A traced RAG answer in a few lines

Requires the `langfuse` and `openai` packages, Langfuse keys and an OpenAI key. Not run here because it calls external services.

```python
from langfuse import observe, get_client
from langfuse.openai import openai  # drop-in: each call becomes a generation

@observe()
def retrieve(question: str) -> list[str]:
    # a real app would query a vector store here
    return ["Refunds are allowed within 7 days of purchase."]

@observe()
def answer(question: str) -> str:
    docs = retrieve(question)          # child observation
    resp = openai.chat.completions.create(  # child generation
        model="gpt-4o-mini",
        messages=[
            {"role": "system", "content": "Answer only from: " + " ".join(docs)},
            {"role": "user", "content": question},
        ],
    )
    return resp.choices[0].message.content

print(answer("Can I get a refund after 10 days?"))
get_client().flush()  # short scripts: send buffered events before exit
```

## CCTV at each counter

Decorating functions is like fitting a camera above every counter in a bank. You do not ask staff to write diaries; the cameras record who came in, what was handed over and how long it took, and the footage lines up by time automatically.

## Decorate the right layer

Decorate functions that represent meaningful steps (retrieve, rerank, generate, call tool), not every tiny helper. Too many spans make traces noisy and add overhead; too few hide where time goes.

**Quiz:** Function `answer()` and function `retrieve()` both have `@observe()`, and `answer()` calls `retrieve()`. What does the trace look like?

- [x] One trace with `answer` as the parent and `retrieve` nested inside it
- [ ] Two separate traces, one per function
- [ ] One flat list with no parent-child relationship
- [ ] Only `answer` is recorded because nested decorators are ignored

*Answer:* One trace with `answer` as the parent and `retrieve` nested inside it. The SDK tracks the current observation in context, so the inner decorated call becomes a child of the outer one, and the outermost call defines the trace.
