Tracing and Monitoring¶
LLMOps questions reveal whether you've shipped LLM systems or just built demos. The difference between the two is mostly observability.
Q1: What should you log for every LLM request in production?¶
Show answer
Minimum viable logging per request:
import time, uuid, os
from openai import OpenAI
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
def call_with_logging(messages: list, model: str = "gpt-4o") -> str:
request_id = str(uuid.uuid4())
start = time.perf_counter()
try:
response = client.chat.completions.create(model=model, messages=messages)
latency_ms = (time.perf_counter() - start) * 1000
usage = response.usage
log_event({
"request_id": request_id,
"model": model,
"latency_ms": round(latency_ms, 1),
"input_tokens": usage.prompt_tokens,
"output_tokens": usage.completion_tokens,
"cost_usd": estimate_cost(model, usage.prompt_tokens, usage.completion_tokens),
"status": "success",
})
return response.choices[0].message.content
except Exception as e:
latency_ms = (time.perf_counter() - start) * 1000
log_event({"request_id": request_id, "latency_ms": latency_ms, "status": "error", "error": str(e)})
raise
Optional but valuable: input/output content (with PII redaction), user ID, session ID, prompt version, model temperature.
Privacy
Don't log raw user input without considering your data retention policy and any PII obligations. At minimum, strip or hash personal identifiers before logging.
Q2: How does LangSmith tracing work and what does @traceable capture?¶
Show answer
LangSmith is Anthropic and LangChain's observability platform. It captures the full execution tree of LLM calls, including intermediate steps in chains and agents.
The @traceable decorator wraps a function and records its inputs, outputs, and any nested LLM calls as a "run" in LangSmith.
import os
from langsmith import traceable
from openai import OpenAI
os.environ["LANGCHAIN_TRACING_V2"] = "true"
os.environ["LANGCHAIN_API_KEY"] = os.getenv("LANGSMITH_API_KEY")
os.environ["LANGCHAIN_PROJECT"] = "my-rag-app"
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
@traceable(name="retrieve_context")
def retrieve(query: str) -> list[str]:
# ChromaDB call — appears as a child run in LangSmith
return collection.query(...)["documents"][0]
@traceable(name="rag_answer")
def answer(query: str) -> str:
context = retrieve(query) # nested — shows as child trace
response = client.chat.completions.create(...)
return response.choices[0].message.content
In LangSmith you can see: each function's inputs/outputs, latency per step, total token counts, and the full prompt sent to the model. This makes debugging RAG pipelines dramatically faster than reading logs.
Q3: How do you detect prompt regressions in production?¶
Show answer
A prompt regression is when a change to your system prompt, model version, or retrieval logic degrades output quality — often silently.
Detection approach: maintain a golden test set — a fixed set of input/output pairs that represent correct behaviour. Run the evaluation on every deployment.
import os
from langsmith import Client
from langsmith.evaluation import evaluate
ls_client = Client(api_key=os.getenv("LANGSMITH_API_KEY"))
def evaluator(run, example) -> dict:
# LLM-as-judge: score the output against the expected output
score = llm_judge(
question=example.inputs["query"],
answer=run.outputs["answer"],
expected=example.outputs["expected_answer"],
)
return {"key": "quality", "score": score["score"]}
results = evaluate(
lambda inputs: {"answer": rag_answer(inputs["query"])},
data="golden-test-set",
evaluators=[evaluator],
experiment_prefix="v2.1-deployment",
)
print(results.to_pandas()[["quality"]].mean())
Alert if the mean quality score drops by more than 5% compared to the baseline experiment. Fail the deployment if it drops by more than 15%.
Q4: What metrics should you monitor in production for an LLM API?¶
Show answer
| Metric | Target | Alert when |
|---|---|---|
| p50 latency | < 1s for cached, < 3s for streaming | p50 > 5s |
| p95 latency | < 5s | p95 > 10s |
| Error rate | < 1% | > 5% in 5-minute window |
| Rate limit rate | < 0.1% | > 1% |
| Cost per request | Baseline ± 20% | Sudden spike (prompt injection or runaway loops) |
| Faithfulness score | > 0.80 (RAG) | < 0.70 over 100 requests |
| Cache hit rate | > 20% for stable workloads | Sudden drop (cache invalidation bug) |
Track these with your existing observability stack (Datadog, Prometheus, CloudWatch). LangSmith handles LLM-specific metrics; your infra stack handles infrastructure metrics.
Q5: How do you trace an agent's execution across multiple LLM calls?¶
Show answer
Agents make multiple LLM calls with branching and looping. Tracing requires correlating all calls in a session under a single parent run.
With LangSmith, nesting @traceable functions creates a parent-child trace tree automatically.
For custom tracing, use a run_id that flows through all calls:
import uuid, time
class AgentTracer:
def __init__(self):
self.session_id = str(uuid.uuid4())
self.steps: list[dict] = []
self.start_time = time.perf_counter()
def log_step(self, step_type: str, input_data: dict, output_data: dict, tokens: int):
self.steps.append({
"session_id": self.session_id,
"step": len(self.steps) + 1,
"type": step_type, # "llm_call", "tool_call", "routing"
"input": input_data,
"output": output_data,
"tokens": tokens,
"elapsed_ms": (time.perf_counter() - self.start_time) * 1000,
})
def summary(self) -> dict:
return {
"session_id": self.session_id,
"total_steps": len(self.steps),
"total_tokens": sum(s["tokens"] for s in self.steps),
"total_ms": (time.perf_counter() - self.start_time) * 1000,
}
Previous: Fine-Tuning Evaluation | Next: Deployment