Skip to content

Part 08

LLMOps and Observability

Tracing with Langfuse / LangSmith / Helicone / Arize Phoenix / Braintrust; per-request token, cost, and latency tracking; prompt and agent versioning; A/B testing; replay and debugging; drift detection when model upgrades silently change behaviour; feedback loops that grow your eval dataset. Each section ships in two voices: the engineer's version (technical, with code & diagrams) and a layman's version (plain English with analogies). প্রতিটি টপিকের শেষে বাংলা ব্যাখ্যাও আছে।

1 section · 30 sources · three voices

01

LLMOps & observability

Traditional DevOps answers "did the request return 200?" LLM apps need richer questions: did the model return the right answer, at the right cost, in the right tone, with no leakage? The discipline of answering those is LLMOps, and it has its own toolchain.

Tracing

An LLM trace is a tree of spans: the user request, the system prompt assembly, the LLM call, every tool invocation, the result. The four leading tracing tools in 2025:

ToolStrength
LangfuseOpen-source, OpenTelemetry-native, self-hostable
LangSmithPolished UI, deep LangChain integration
HeliconeDrop-in proxy that captures everything with one env-var change
Arize PhoenixEvaluation-first, embeddings-aware drift detection
BraintrustEval + trace in one workspace
trace_id: 7f2c… | agent_run_id: 91 | user: u_8421 POST /agent/run (1.8 s) user: "plan my Tokyo trip" llm.plan (0.6 s, 412 tok) plan: [search, weather, fx] tool.weather (0.2 s) cached ✓ tool.fx_rates (0.4 s) usd_jpy: 154.2 llm.synthesize (0.9 s) 487 tok out · cached prefix 80% tool.email.send (0.3 s) user approved ✓ cost: $0.0042 · input 812 tok · output 487 tok · latency p50 1.6 s · p99 4.1 s evals: faithfulness 0.91 · task_success 1.00 · no_pii_leak ✓
A single agent run as a traced tree: parent (POST), LLM calls (in violet), tool calls (in green/yellow), approval gate, plus a cost and latency footer.
একটি এজেন্ট রান traced tree হিসেবে: parent POST, LLM calls (বেগুনি), tool calls (সবুজ/হলুদ), approval gate, এবং খরচ ও latency ফুটার।

Token, cost, and latency tracking

Every request must record: input tokens, output tokens, model, cost (computed from vendor's pricing), latency (TTFT, total), and cache hit. Tag with user_id, feature, experiment. This data is the substrate for every other LLMOps question — "is feature X losing money?", "is the new model slower?"

Prompt and agent versioning

Prompts are code. Treat them like code: store in git, version them, review them via PR, deploy via CI. Each prompt version gets a stable ID; every trace carries the prompt ID. When something breaks, you can answer "what prompt version were users on?" instantly.

A/B testing prompts and models in production

Route traffic between two prompt versions (or two models) by user_id hash. Measure: cost, latency, evals run on the output, user feedback. Stop the experiment when one variant wins on the metric you chose (and you didn't peak on it after one bad afternoon).

Replay and debugging

When a user complains, you should be able to pull the trace, see every prompt and every model reply, identify the failure, and replay the exact request against a different model or prompt to verify the fix. Langfuse, Helicone, and LangSmith all give you a replay button.

Drift detection (silent behaviour changes)

When OpenAI swaps GPT-4 for GPT-4-0613 or Anthropic bumps the weights of a "same-named" model, your outputs drift in ways your offline evals won't catch. The fix: run a small fraction of production traffic (1–5%) through both the old and new model, compare evals, alert on delta. Arize Phoenix and Braintrust automate this.

Feedback loops

Capture thumbs-up / thumbs-down (or richer signals) and feed the failures into your eval dataset. After three months you'll have a thousand real failures that's worth more than ten thousand synthetic ones.

Practice · 4 tasks

Open any free AI chat: ChatGPT · Claude · Gemini

How practice works
  • A new chat is a fresh conversation with no history. In ChatGPT, Claude, or Gemini, click New chat. It is usually at the top left.
  • Same chat means: keep typing in the chat that is already open. The AI remembers what you sent before.
  • To use a prompt: click Copy, click in the message box of the chat, paste it (Ctrl+V, or press and hold on a phone), and press Enter.
  • AI answers are a little different every time. Your answer does not need to match the example word for word. Look for the same idea.
  • Use made-up data only. Do not paste private information into a chat.

Task 1

Turn a flat log into a trace

Why this matters

When a user says "your chatbot gave a bad answer", a flat log of lines is hard to read. A trace shows each step and its time, so you find the broken step fast.

  1. Step 1
    New chat

    Look at the log in this prompt. Guess which step took the longest. Then copy the prompt, paste it, and press Enter.

    Prompt
    Here is the flat log of one agent run (invented data). A span is one step of the run, with a start and an end.
    
    10:02:11.000 REQUEST_START POST /chat user=u_204 prompt_id=support-v7
    10:02:11.020 SPAN_START build_system_prompt
    10:02:11.040 SPAN_END   build_system_prompt
    10:02:11.040 SPAN_START llm_call_1 model=model-x
    10:02:12.440 SPAN_END   llm_call_1 in_tokens=1200 out_tokens=60 decision="call tool search_orders"
    10:02:12.440 SPAN_START tool_search_orders args={"customer":"u_204"}
    10:02:16.940 SPAN_END   tool_search_orders status=timeout
    10:02:16.940 SPAN_START llm_call_2 model=model-x
    10:02:18.140 SPAN_END   llm_call_2 in_tokens=1350 out_tokens=80
    10:02:18.150 REQUEST_END status=200 answer="Sorry, I could not check your order right now."
    
    Tasks:
    1. Draw this run as a trace: a tree of spans, with indentation. Show the duration of each span in seconds.
    2. Name the slowest span and say what share of the total time it used.
    3. Name the span where the real problem started.

    You should see: A tree with the request on top and four steps below it. The slowest span is tool_search_orders, about 4.5 s, which is about 63% of the 7.15 s total. The problem started there (timeout).

    Example answer
    REQUEST POST /chat (7.15 s)
      build_system_prompt (0.02 s)
      llm_call_1 (1.4 s)
      tool_search_orders (4.5 s) status=timeout
      llm_call_2 (1.2 s)
    Slowest span: tool_search_orders, 4.5 s, about 63% of 7.15 s.
    The real problem started in tool_search_orders, which timed out.

    Your answer will be worded differently.

  2. Step 2
    Same chat

    Check one duration by hand: subtract the start time of the tool span (12.440) from its end time (16.940).

    You should see: 4.5 seconds. It matches the model's tree.

  3. Step 3
    Same chat

    Copy this prompt, paste it in the same chat, and press Enter. Read what the model says about the 200 status.

    Prompt
    The request returned status 200. A classic DevOps dashboard shows green for it. Explain in 3 short sentences what a plain 200 check misses for this run. Then list 4 fields that the trace should carry so that I can find this failure later.

    You should see: The model says 200 only means the server replied. The answer was still useless for the user. It lists fields like prompt version, model, token counts, cost, latency, and the tool status.

    Example answer
    A 200 only says the server replied. It does not say the answer was good. Here the tool timed out, so the user got "Sorry, I could not check your order". The dashboard stays green while the user fails.
    Fields: prompt_id, model name, token counts and cost, tool status and latency.

    Your answer will be worded differently.

What just happened

A trace is a tree of spans. A span is one step, such as an LLM call or a tool call, with its time. The tree shows that the tool call took 63% of the time and failed. The server still returned 200, so a plain status check saw nothing wrong. LLM apps need traces to see where the answer broke.

You are done when You have a span tree with 4 steps, you know the slowest one (4.5 s), and you have a list of 4 fields to log.

Check yourselfWhy is status 200 not enough to call this request healthy?Show answer

The server answered, but the answer was wrong for the user, because the tool timed out. LLMOps asks whether the answer was right. A trace of spans shows where it failed.

Try a harder versionChange the log so that a second tool call starts at the same time as the first one. Ask the model to redraw the tree. How does the total time change?

Task 2

Find which feature loses money

Why this matters

When your app has several AI features, the monthly bill is one big number. You must know which feature costs more than it earns, or you cannot fix it.

  1. Step 1
    New chat

    Look at the 6 log rows in this prompt. Guess which feature loses money. Then copy the prompt, paste it, and press Enter.

    Prompt
    Here are logged LLM requests (invented data). Prices per 1 million tokens: small model = 0.20 USD input and 0.80 USD output. Large model = 3.00 USD input and 15.00 USD output.
    
    id | feature       | user | model | in_tokens | out_tokens | latency_s
    1  | autocomplete  | u1   | small | 300       | 20         | 0.4
    2  | autocomplete  | u2   | small | 280       | 25         | 0.5
    3  | autocomplete  | u1   | small | 310       | 18         | 0.4
    4  | report_writer | u3   | large | 12000     | 1500       | 14.2
    5  | report_writer | u4   | large | 9000      | 1200       | 11.8
    6  | report_writer | u3   | large | 15000     | 2000       | 18.5
    
    Revenue: each autocomplete request earns 0.001 USD. Each report_writer request earns 0.04 USD.
    
    Tasks:
    1. Compute the cost of each request in USD. Show the formula once, then give a table.
    2. Sum cost and revenue per feature.
    3. Say which feature loses money, and by how much per request on average.

    You should see: Autocomplete costs a tiny fraction of a cent and earns a profit. report_writer loses money: its average cost is about 0.0595 USD per request, but it earns 0.04 USD. The average loss is about 0.0195 USD.

    Example answer
    Cost = in_tokens × in_price ÷ 1,000,000 + out_tokens × out_price ÷ 1,000,000
    1: 0.000076  2: 0.000076  3: 0.0000764
    4: 0.0585  5: 0.045  6: 0.075
    autocomplete: cost about 0.00023 USD, revenue 0.003 USD. Profit.
    report_writer: cost 0.1785 USD, revenue 0.12 USD. Loss 0.0585 USD, about 0.0195 USD per request.
    report_writer loses money.

    Your answer will be worded differently.

  2. Step 2
    Same chat

    Check request 4 by hand with a calculator: 12,000 × 3 ÷ 1,000,000, plus 1,500 × 15 ÷ 1,000,000.

    You should see: 0.036 + 0.0225 = 0.0585 USD. It matches the table, and it is more than the 0.04 USD this request earns.

  3. Step 3
    Same chat

    Copy this prompt, paste it in the same chat, and press Enter. Read the new cost and the warning.

    Prompt
    Now suppose we move report_writer to the small model. Use the same token counts for requests 4, 5, and 6. What is the new average cost per request? What must we measure before we make this change in production? Answer in under 100 words.

    You should see: A new average cost of about 0.0037 USD per request. A good answer also says to check the answer quality first, for example with evals (tests that score answers).

    Example answer
    On the small model, the average cost is about 0.0037 USD per request (0.0036, 0.0028, and 0.0046 for requests 4, 5, 6). That is below the 0.04 USD revenue.
    Before the change, measure answer quality with evals on real report requests. A cheaper model may write worse reports, so also watch user feedback and latency.

    Your answer will be worded differently.

What just happened

Every request has input tokens and output tokens. Multiply them by the model's price and you get the cost of that request. Add the tags (labels) in the log, such as feature and model, and you can group the cost by feature. Without the feature tag, you could not answer "which feature loses money?". A cheaper model lowers cost, but you must check quality too.

You are done when You have a cost for each of the 6 requests, you know which feature loses money, and you checked request 4 by hand.

Check yourselfWhich logged fields let you answer "is this feature losing money?"Show answer

Input tokens, output tokens, and the model give the cost, using the vendor's prices. The feature tag groups the requests. Without these fields the data cannot answer the question.

Try a harder versionAdd a rule: half of the large-model input tokens are cached, and cached tokens cost 10% of the normal price. Ask the model to recompute. Does report_writer still lose money?

Task 3

Decide when to stop an A/B test

Why this matters

When you test a new prompt in production, an early good result is tempting. If you ship too soon, you may pay more for a prompt that is not better.

  1. Step 1
    New chat

    Read the numbers in this prompt. Decide: ship v8 after day 1, or wait for day 7? Write your reason. Then copy the prompt, paste it, and press Enter.

    Prompt
    We test two versions of our support-bot prompt in production (invented data). Traffic is split by a hash of user_id. Version support-v7 is the current prompt. Version support-v8 is a new, longer prompt.
    
    After day 1 (afternoon):
    - v7: 40 thumbs-up out of 60 rated answers
    - v8: 45 thumbs-up out of 60 rated answers
    
    After day 7:
    - v7: 290 thumbs-up out of 420 rated answers, average cost 0.8 cents per answer, average latency 2.1 s
    - v8: 292 thumbs-up out of 420 rated answers, average cost 1.1 cents per answer, average latency 3.3 s
    
    Tasks:
    1. Compute the thumbs-up rate for each version at day 1 and at day 7.
    2. Should we stop and ship v8 after day 1? Why or why not?
    3. What should we do at day 7?
    4. Which metrics should we have chosen before the test started?
    Be short and direct.

    You should see: Day 1: v7 is 66.7%, v8 is 75.0%. Day 7: v7 is 69.0%, v8 is 69.5%. The early gap almost disappears. At day 7 v8 is about equal in quality but costs more and is slower. Most models say keep v7.

    Example answer
    1. Day 1: v7 40/60 = 66.7%, v8 45/60 = 75.0%. Day 7: v7 290/420 = 69.0%, v8 292/420 = 69.5%.
    2. No. One afternoon with 60 ratings per version is too little data. Chance can make v8 look better.
    3. Keep v7. v8 is about the same in quality, but it costs 1.1 cents instead of 0.8 and is slower (3.3 s instead of 2.1 s).
    4. Choose the main metric, a cost limit, a latency limit, and the test length before the test starts.

    Your answer will be worded differently.

  2. Step 2
    Same chat

    Copy this prompt, paste it in the same chat, and press Enter. Read why the split uses a hash of the user_id.

    Prompt
    We split traffic by a hash of user_id (a fixed rule that sends each user to the same version every time). Why do we do that, and not pick at random for every message? Also, why should every trace carry the prompt version ID, such as support-v8? Answer in 4 short sentences.

    You should see: Four short sentences. The hash keeps each user on one version, so a user does not see two different bots. The version ID in every trace tells you which prompt made each answer.

    Example answer
    A hash sends each user to the same version every time. A random pick per message could show one user two different bots in one chat. The version ID in every trace shows which prompt made each answer. Then you can say "users on support-v8 got more timeouts" and find the bad version fast.

    Your answer will be worded differently.

What just happened

With few ratings, chance can make one version look better. After 60 ratings v8 led by 8 points. After 420 ratings the lead was 0.5 points. You choose the metric (and the cost and latency limits) before the test, and you stop only when one version clearly wins on that metric. A prompt can win on feedback and still be a bad trade if it is slower and costs more.

You are done when You have the thumbs-up rates at day 1 (66.7% and 75.0%) and at day 7 (69.0% and 69.5%), and a decision for each day.

Check yourselfWhy is it wrong to stop the test after one good afternoon?Show answer

With few ratings, chance can make one version look better. You choose the metric first and stop only when one version clearly wins on it. Cost and latency also count.

Try a harder versionChange the day 7 numbers so that v8 wins on thumbs-up by 8 points but costs 40% more. Ask the model to help you decide. What extra information do you want?

Task 4

Catch silent model drift

Why this matters

A vendor can update a model without changing its name. Your code stays the same, but your JSON can break. You need a way to catch this before your users do.

  1. Step 1
    New chat

    Read the 5 cases in this prompt. Mark which ones changed. Then copy the prompt, paste it, and press Enter. Compare the model's list with yours.

    Prompt
    Our app asks a model to extract JSON like {"name": ..., "amount_bdt": ...} from a message. The vendor updated the model behind the same model name. We ran 5 production inputs through the old and new versions (invented data).
    
    1. Input: "Rahim paid 1,200 taka"
       OLD: {"name":"Rahim","amount_bdt":1200}
       NEW: {"name":"Rahim","amount_bdt":1200}
    2. Input: "Sumi sent 5k BDT"
       OLD: {"name":"Sumi","amount_bdt":5000}
       NEW: {"name":"Sumi","amount_bdt":"5k"}
    3. Input: "Tanvir paid 300 taka and Mina paid 450 taka"
       OLD: [{"name":"Tanvir","amount_bdt":300},{"name":"Mina","amount_bdt":450}]
       NEW: {"name":"Tanvir","amount_bdt":300}
    4. Input: "Lina paid 75 taka"
       OLD: {"name":"Lina","amount_bdt":75}
       NEW: Sure! Here is the JSON: {"name":"Lina","amount_bdt":75}
    5. Input: "Arif paid 2,000 taka"
       OLD: {"name":"Arif","amount_bdt":2000}
       NEW: {"name":"Arif","amount_bdt":2000}
    
    Tasks:
    1. For each case say SAME or CHANGED, and name the type of change.
    2. For each change, write one automatic check (a rule a program can test).
    3. Say what share of live traffic we should send through both models, and what we should alert on.

    You should see: Cases 1 and 5 are SAME. Cases 2, 3, and 4 are CHANGED: a wrong type (text, not a number), a lost second person, and extra text around the JSON.

    Example answer
    1 SAME
    2 CHANGED: amount_bdt is the text "5k", not a number. Check: amount_bdt must be a number.
    3 CHANGED: the list became one object, Mina is lost. Check: the number of people in the output equals the number in the input.
    4 CHANGED: extra text around the JSON. Check: the whole reply must parse as JSON.
    5 SAME
    Send 1-5% of live traffic through both models. Alert when the check pass rates differ.

    Your answer will be worded differently.

  2. Step 2
    Same chat

    Copy this prompt, paste it in the same chat, and press Enter. Check that the expected output makes sense.

    Prompt
    A user clicked thumbs-down on this answer.
    Input: "Karim paid 1.5 lakh taka" (1 lakh = 100,000)
    Output: {"name":"Karim","amount_bdt":1.5}
    
    Turn this failure into one test case for our eval dataset. Give the input, the expected output, and the rule that checks it. Then say in 2 sentences why a thousand real failures like this are more useful than synthetic tests.

    You should see: One test case. The input is the Karim sentence, the expected output has "amount_bdt": 150000, and the rule says the amount must equal 150000. Then 2 sentences about real failures.

    Example answer
    Input: "Karim paid 1.5 lakh taka"
    Expected output: {"name":"Karim","amount_bdt":150000}
    Rule: the JSON parses, and amount_bdt equals 150000 (a number).
    Real failures come from real users, so they show the cases that break your app. Synthetic tests only cover what you already thought of.

    Your answer will be worded differently.

What just happened

Drift means the model changes its behaviour while its name stays the same. Cases 1 and 5 look fine, so a test with only simple inputs would find nothing. The harder cases broke. To catch drift, send a small part of live traffic (1-5%) through the old and new model, compare the check results, and alert on the difference. Real failures from users become new test cases.

You are done when You have a list of 3 changed cases with one check for each, and one new test case for Karim.

Check yourselfWhy can offline evals miss this kind of change?Show answer

The model name stayed the same, so nothing in your code changed. A fixed test set may not contain the cases that broke. Running a small share of real traffic through both models finds the difference.

Try a harder versionAdd a 6th case where the new model does better than the old one. Ask the model to separate regressions (got worse) from improvements. Why is an alert for one direction only not enough?

Watch · 7 videos

References

Sources

All citations resolve to primary papers (arXiv), vendor docs (OpenAI, Anthropic, Google), or official team blogs. IDs were verified by fetching each page and extracting the citation metadata.

  1. [1]Yao et al., "ReAct: Synergizing Reasoning and Acting in Language Models" (ICLR 2023)
  2. [2]Wei et al., "Plan-and-Execute Agents" / "Chain-of-Thought Prompting Elicits Reasoning in Large Language Models" — NeurIPS 2022
  3. [3]Shinn et al., "Reflexion: Language Agents with Verbal Reinforcement Learning" — NeurIPS 2023
  4. [4]Xu et al., "ReWOO: Decoupling Reasoning from Observations for Efficient Augmented Language Models"
  5. [5]Anthropic Engineering blog — "How we built our multi-agent research system" (2025)
  6. [6]LangGraph — official documentation (LangChain)
  7. [7]Temporal — "Durable Execution for LLM Agents"
  8. [8]Park et al., "Generative Agents: Interactive Simulacra of Human Behavior" — 2023
  9. [9]Anthropic — "Introducing computer use, a new Claude 3.5 Sonnet capability" (Oct 2024)
  10. [10]Anthropic — Tool use / function calling documentation
  11. [11]OpenAI — Function calling guide
  12. [12]Anthropic — Model Context Protocol (MCP) specification
  13. [13]Kwon et al., "Efficient Memory Management for Large Language Model Serving with PagedAttention" — vLLM paper, SOSP 2023
  14. [14]Zheng et al., "SGLang: Efficient Execution of Structured Language Model Programs" — 2023
  15. [15]llama.cpp — open-source GGUF-native inference (ggerganov)
  16. [16]Lin et al., "AWQ: Activation-aware Weight Quantization for LLM Compression and Acceleration" — MLSys 2024
  17. [17]Anthropic — Prompt caching documentation
  18. [18]Zheng et al., "Judging LLM-as-a-Judge with MT-Bench and Chatbot Arena" — NeurIPS 2023
  19. [19]Es et al., "Ragas: Automated Evaluation of Information Retrieval Augmented Language Models" — 2023
  20. [20]Yue et al., "Large Language Model Cascades with Mixture of Thought Representations" — 2024
  21. [21]GPTCache — open-source semantic cache
  22. [22]Guo et al., "DeepSeek-R1: Incentivizing Reasoning Capability in LLMs via RL" — 2025
  23. [23]Jiang et al., "LLMLingua: Compressing Prompts for Accelerated Inference of Large Language Models" — EMNLP 2023
  24. [24]LiteLLM — unified interface for 100+ LLMs
  25. [25]OWASP — "Top 10 for LLM Applications" (LLM01 Prompt Injection, 2025)
  26. [26]Bai et al., "Constitutional AI: Harmlessness from AI Feedback" — Anthropic, 2022
  27. [27]NVIDIA NeMo Guardrails — open-source toolkit
  28. [28]Qwen2-VL Team, "Qwen2-VL: Enhancing Vision-Language Model's Perception of the World at Any Resolution" — 2024
  29. [29]OpenAI — Realtime API documentation
  30. [30]OpenAI — Production best practices guide