Observability · Production Agents

AI Agent Observability: traces, κόστος και alerts πριν σε τρελάνει

Ένας agent που «δούλευε στο laptop» γίνεται μαύρο κουτί μόλις πατήσει production: κάνει loops, καλεί δεκάδια tools, ξοδεύει tokens που κανείς δεν παρακολουθεί. Ο πρακτικός οδηγός για OpenTelemetry GenAI + Langfuse: setup, traces, κόστος ανά trace, loop detection, alerts και evals.

~22 λεπτά ανάγνωση hands-on · κώδικας OTel GenAI · OTLP · Langfuse Οκτώβριος 2026
Ενότητα 01

Γιατί τα agents σπάνε διαφορετικά

Σε ένα κλασικό API, η αποτυχία φαίνεται: status 500, timeout, exception. Σε έναν agent όχι — γιατί ο agent μπορεί να «πετύχει» τεχνικά και να αποτύχει πραγματικά.

Η αποτυχία κρύβεται στη μέση.
Ένας agent διάλεξε λάθος tool, ή άρπαξε λάθος έγγραφο, ή έκανε loop σε ένα βήμα — και παρόλα αυτά παρήγαγε μια αξιοπιστία φαινομενική απάντηση. Το τελικό output δείχνει σωστό.
Αποφασίζει μόνο του τις δαπάνες.
Πόσες κλήσεις μοντέλου θα κάνει, πόσα tools θα τρέξει, πόσα retries — το αποφασίζει ο agent, όχι εσύ. Άρα το κόστος δεν είναι γραμμικό και πρέπει να μετριέται ανά trace.
Τα traces είναι μεγάλα.
Plan-act-observe loops και subagents παράγουν traces με εκατοντάδες ή χιλιάδες observations. Το scroll σε span tree δεν είναι debugging strategy.

Συνεπώς, το observability για agents δεν είναι «να βλέπω logs». Είναι να μπορώ να απαντήσω: γιατί αυτό το trace κόστισε $1,40 όταν το μέσο είναι $0,05; και να το απαντήσω σε δευτερόλεπτα, όχι σε ώρες.

Ενότητα 02

Monitoring, observability, evals — τι είναι τι

Τα μπερδεύουν όλοι. Η διαφορά:

ΕπίπεδοΤι απαντάΠαράδειγμα
MetricsΕίναι καλά σε συνολικά;p95 latency, κόστος/ώρα, error rate
ObservabilityΓιατί αυτό το trace απέτυχε;Span tree με κάθε tool call και token
EvalsΕίναι η απάντηση καλή;LLM-as-a-judge σε groundedness, human annotation

Ο σωστός συνδυασμός: metrics για alerts, traces για debugging, evals για ποιότητα. Και οι τρεις τροφοδοτούνται από την ίδια πηγή — τα OpenTelemetry GenAI conventions.

Ενότητα 03

Setup A — τοπικός έλεγχος σε 2 λεπτά (Aspire Dashboard)

Πριν αγοράσεις οτιδήποτε, έλεγξε ότι ο κώδικάς σου εκπέμπει σωστά. Το Aspire Dashboard είναι δωρεάν, ανοιχτό, δέχεται OTLP απευθείας και έχει ενσωματωμένο trace viewer και GenAI visualizer.

terminal
# δωρεάν OTLP backend με trace viewer + GenAI visualization
docker run --rm -p 18888:18888 -p 4317:18889 -p 4318:18890 -d --name aspire-dashboard \
  -e ASPIRE_DASHBOARD_UNSECURED_ALLOW_ANONYMOUS=true \
  mcr.microsoft.com/dotnet/aspire-dashboard:latest

Στέλνεις εκεί OTLP στο http://localhost:4318 και ανοίγεις http://localhost:18888 για να δεις traces, metrics και logs. Ιδανικό για local ανάπτυξη και debugging — δεν θέλεις cloud λογαριασμό για να δεις αν δουλεύει.

Τι περιμένεις να δεις: ένα top-level invoke_agent span με παιδιά chat (ανά κλήση μοντέλου) και execute_tool (ανά tool call). Αν βλέπεις μόνο ένα απλό span, δεν έχεις instrumentation.
Ενότητα 04

Setup B — Langfuse με OTLP (cloud)

Το Langfuse δέχεται traces ως απλό OpenTelemetry backend στο endpoint /api/public/otel. Δεν χρειάζεται έτοιμο integration — αρκεί να στέλνεις OTLP.

.env
# EU data region (υπάρχουν και US, Japan, HIPAA)
OTEL_EXPORTER_OTLP_ENDPOINT="https://cloud.langfuse.com/api/public/otel"

# Basic Auth: base64 των "pk-lf-...:sk-lf-..."
OTEL_EXPORTER_OTLP_HEADERS="Authorization=Basic ${AUTH_STRING},x-langfuse-ingestion-version=4"
terminal
# φτιάξε το AUTH_STRING από τα API keys σου
echo -n "pk-lf-1234567890:sk-lf-1234567890" | base64

Τρεις λεπτομέρειες που κολλάνε τους περισσότερους

Self-hosted: το ίδιο setup δουλεύει με τοπικό Langfuse (http://localhost:3000/api/public/otel) — το endpoint εμφανίστηκε στην έκδοση v3.22.0, οπότε αν παίρνεις 4xx κάνε upgrade.
Ενότητα 05

Setup C — OpenTelemetry Collector

Όταν οι εφαρμογές σου είναι παραπάνω από μία, βάζεις Collector στη μέση: δέχεται OTLP από όλες και προωθεί με ξεχωριστό auth.

otelcol-config.yaml
receivers:
  otlp:
    protocols:
      grpc:  { endpoint: 0.0.0.0:4317 }
      http:  { endpoint: 0.0.0.0:4318 }

processors:
  memory_limiter:
    limit_mib: 1500        # 80% του μέγιστου, έως 2G
    spike_limit_mib: 512
    check_interval: 5s
  batch:

exporters:
  otlphttp/langfuse:
    endpoint: "https://cloud.langfuse.com/api/public/otel"
    headers:
      Authorization: "Basic ${AUTH_STRING}"
      x-langfuse-ingestion-version: "4"

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [memory_limiter, batch]
      exporters: [otlphttp/langfuse]
Αν φιλτράρεις, φιλτράρει προσεκτικά. Με το filterprocessor μπορείς να στείλεις μόνο spans με gen_ai.system=\"openai\" — αλλά το Langfuse χρειάζεται το root span για να δημιουργήσει σωστά το trace. Πάντα να περνάει το root.
Ενότητα 06

Τα attributes που μετράς (gen_ai.*)

Τα OpenTelemetry GenAI Semantic Conventions ορίζουν τα gen_ai.* attributes σε spans, metrics και events — για LLM clients και MCP. Αυτά είναι τα ίδια conventions που καταναλώνουν Langfuse, Arize, Datadog και οποιοδήποτε OTLP backend, άρα δεν κλειδώνεστε σε εργαλείο.

AttributeΤι σημαίνει
gen_ai.operation.namechat, invoke_agent, execute_tool, invoke_workflow, plan
gen_ai.provider.nameopenai, anthropic, gcp.vertex_ai, aws.bedrock
gen_ai.request.modelτο μοντέλο που κλήθηκε
gen_ai.usage.input_tokens / output_tokensη βάση για όλο τον υπολογισμό κόστους
gen_ai.response.finish_reasonsstop, tool_calls, length — γιατί κόπηκε
gen_ai.agent.name / id / versionποιος agent έτρεξε (σε multi-agent)
gen_ai.tool.name / call.idποιο tool κλήθηκε και με ποιο id
gen_ai.conversation.idομαδοποίηση πολλαπλών γύρων και cross-service
gen_ai.input.messages / output.messagescontent — opt-in, προσοχή με PII
Πρακτικό: αν βλέπεις finish_reasons=length συχνά σε κάποιο prompt, κόβεις απαντήσεις — φθηνότερο fix απ’ όσο νομίζεις. Και αν ο provider είναι κενός, το instrumentation δεν μαζεύει σωστά τα tokens.
Ενότητα 07

Trace attributes σε όλα τα spans (Baggage)

Το Langfuse φιλτράρει και ομαδοποιεί όλο και περισσότερο σε επίπεδο observation, όχι μόνο trace. Αν θες να φιλτράρεις με userId, sessionId ή tags, πρέπει να υπάρχουν σε κάθε span του trace — όχι μόνο στο root. Το πρότυπο είναι το OpenTelemetry Baggage με BaggageSpanProcessor.

  1. Βάζεις τα attributes ως baggage entries στην αρχή του trace
  2. Τα βάζεις και στο ενεργό span
  3. Ο BaggageSpanProcessor τα αντιγράφει αυτόματα σε όλα τα παιδιά
Ασφάλεια: το baggage διαδίδεται μεταξύ services και σε API τρίτων. Ποτέ passwords, API keys ή προσωπικά δεδομένα μέσα σε baggage.

Στο Langfuse SDK υπάρχουν οι συναρτήσεις propagate_attributes() (Python) / propagateAttributes() (TS) που το κάνουν αυτό χωρίς χειροκίνητη δουλειά. Αν χρησιμοποιείς απλό OTel SDK, βάλε χειροκίνητα attributes με prefix langfuse.trace.metadata.* για να είναι filterable.

Ενότητα 08

Το dashboard που χρειάζεσαι (5 charts, όχι 50)

1. p95 latency ανά operation
gen_ai.client.operation.duration (histogram), χωρισμένο per model. Εδώ βλέπεις αν μια αλλαγή μοντέλου «ζάλισε» τους χρήστες.
2. Κόστος ανά trace
Tokens (input × output) × τιμή, αθροισμένα ανά trace. Η πιο χρήσιμη μετρική για budget control.
3. task_success_rate
% traces που ολοκληρώθηκαν επιτυχώς vs failed/canceled. Η κύρια μετρική ποιότητας ροής.
4. tool_errors
Counter αποτυχημένων tool calls. Με το gen_ai.tool.name βλέπεις αμέσως ποιο tool είναι το προβληματικό.
5. loop indicator
Μετρητής επαναλαμβανόμενων tool calls ανά observation + counter loop_detected. Το συχνότερο αδιέξοδο σε production.
Κανόνας: αν ένα chart δεν παίρνει κάποια απόφαση, βγάλ' το. Δέκα μέτρησεις που χαζεύεις είναι χειρότερες από μία που ελέγχεις κάθε μέρα.
Ενότητα 09

Debugging σε πράξη: 3 περιπτώσεις

Περίπτωση 1 — Loop 30 tool calls

Σύμπτωμα: κόστος ×8, χρόνος απόκρισης 90s. Τι ψάχνεις: στο trace, μετράς πόσα execute_tool spans έχει το ίδιο όνομα και ίδια arguments. Αν βρεις 30 φορές το web.search με το ίδιο query, το πρόβλημα είναι στο tool result — προφανώς επιστρέφει κάτι που δεν ικανοποιεί τον agent, οπότε προσπαθεί ξανά. Η λύση είναι καλύτερο tool result ή σαφέστερη οδηγία, όχι περισσότερα iterations.

filter
# στο observations table: βρες looping agents
observation.type = "tool"  AND  count(tool.name) >= 30

Περίπτωση 2 — Κόστος $1,40 ανά trace

Ταξινομείς τα generations ανά gen_ai.usage.* και βλέπεις το χτυπημένο: συνήθως ένα τεράστιο input_tokens σε ένα retrieval observation. Δηλαδή το RAG γύρισε 40 αποσπάσματα αντί για 4, ή το prompt ξαναστέλνει όλη τη συνομιλία. Είναι μίας γραμμής η διόρθωση — αλλά μόνο αν το είδες.

Περίπτωση 3 — Agent «κολλάει» σε input-required

Το task μένει input-required και κανείς δεν τον ξαναπαίρνει. Με rate metric (

Ενότητα 10

Distributed: supervisor → specialist → tool

Σε multi-agent, τα spans έρχονται από διαφορετικά services. Γίνονται ένα ενιαίο trace μόνο αν διαδίδεις το trace ID μεταξύ τους (standard OTel context propagation). Τότε παίρνεις ένα span tree, ένα agent graph, ένα σημείο για scores.

trace: req-xyz
  └─ invoke_agent  supervisor-agent
     ├─ invoke_agent  research-agent   (A2A delegation)
     │  ├─ chat  gpt-4.1
     │  └─ execute_tool  web.search     (MCP)
     ├─ invoke_agent  analyst-agent
     │  └─ chat  claude-sonnet-4.5
     └─ execute_tool  report.save

Το aggregated agent graph δείχνει ένα node ανά βήμα (με counters για επαναλήψεις) και σχεδιάζει τους κύκλους — ιδανικό για να πιάνεις loops και άχρηστες κλήσεις με μια ματιά. Το expanded view ακολουθεί μια συγκεκριμένη εκτέλεση βήμα βήμα, που είναι ό,τι θες για debugging.

MCP tip: client και server traces ξεκινούν ξεχωριστά. Για να ενωθούν, περνάς το trace context μέσα στο MCP _meta field.
Ενότητα 11

Alerts που έχουν νόημα

AlertΌριο-παράδειγμαΓιατί
Κόστος ανά trace p95> $0,50 όταν μέσο = $0,08προλαμβάνει λογαριασμό-έκπληξη
loop_detected> 0 σε 5 λεπτάο agent καίει χρήμα και χρόνο χωρίς να προχωρά
tool_errors rate> 5% ανά toolσυνήθως σπάσει ένα εξωτερικό API, όχι ο agent
task_success_rate< 90% για 15 λεπτάη μοναδική μετρική που νοιάζονται οι χρήστες
p95 latency> 25sο χρήστης εγκαταλείπει

Στέλνε τα σε Slack/Discord/webhook. Κανένα alert δεν πρέπει να σε ξυπνάει αν δεν υπάρχει συγκεκριμένη ενέργεια που πρέπει να κάνεις.

Ενότητα 12

Από traces σε evals — και μετά σε CI gate

Το observability δεν έχει αξία αν σταματάει εκεί. Ο κύκλος είναι: βλέπω αποτυχία στη production → την κάνω dataset item → τρέχω experiment στο CI → μπλοκάρω release αν πέσει το score. Έτσι το ίδιο bug δεν επιστρέφει.

Code evaluators
Για ντετερμινιστικά checks: καλέστηκε το σωστό tool, σεβάστηκε το budget βημάτων, το output περνάει το schema. Μια γραμμή κώδικα, μηδέν αστάθεια.
LLM-as-a-judge
Για σημασιολογικά: ολοκληρώθηκε το task, είναι grounded η απάντηση. Χρειάζεται βαθμονόμηση με human labels για να έχει αξία.
Prefer binary
Προτίμα δυαδικό pass/fail αντί για κλίμακα 1–5. Σε αναγκάζει να ορίσεις τι σημαίνει «αποδεκτό» — και τα scores γίνονται συγκρίσιμα.

Δυο μέρη τρέχουν παράλληλα: offline (datasets + experiments πάνω σε πραγματικά traces πριν το deploy) και online (evaluators και user feedback πάνω στη ζωντανή κίνηση). Όταν η production βγάλει νέο failure mode, μπαίνει στο dataset και ο κύκλος κλείνει.

Ενότητα 13

PII: τι δεν πρέπει να στέλνεις

Από προεπιλογή, το GenAI telemetry περιέχει metadata — ονόματα μοντέλων, token counts, durations. Το content capture (prompts, completions, tool arguments) είναι ξεχωριστή, ρητή επιλογή, ακριβώς επειδή μπορεί να περιέχει ευαίσθητα δεδομένα.

  • Ξεκίνα με content capture κλειστό και άνοιξέ το μόνο όταν χρειάζεται για debugging.
  • Αν το ανοίξεις: redaction πριν την εξαγωγή (emails, κάρτες, tokens), όχι μετά.
  • Μην στέλνεις gen_ai.input.messages σε backends που δεν ελέγχεις.
  • Remember: τα attributes μεγαλώνουν γρήγορα — πολλά backends τα αποθηκεύουν ως raw JSON και δεν τα διαβάζει κανείς.
Ενότητα 14

Production checklist

Instrumentation
OTel GenAI σε κάθε agent. Framework-native instrumentation όπου υπάρχει (Vercel AI SDK, LangGraph, Pydantic AI, Claude Agent SDK).
Propagation
Trace context σε κάθε service boundary, και μέσα στο MCP _meta για client/server traces.
Backend
OTLP endpoint + x-langfuse-ingestion-version: 4 (αν χρησιμοποιείς Langfuse). Έλεγξε ότι φτάνουν spans πριν κοιμηθείς.
Dashboards
p95 latency, cost per trace, success rate, tool errors, loops. Πέντε charts, όχι πενήντα.
Alerts
Cost p95, loop_detected, tool error rate, success rate — με σαφή ενέργεια παρακάτω.
Evals
Datasets από παραγωγικά failures, experiments στο CI, binary scores.
PII
Content capture opt-in, redaction πριν την εξαγωγή, κανένα ευαίσθητο σε baggage.
Budgets
Όρια κόστους ανά κλειδί και max iterations ανά task. Το observability δείχνει, όρια αποτρέπουν.
Ενότητα 15

Συχνές ερωτήσεις

Χρειάζομαι Langfuse ή μπορώ με ανοιχτό stack;

Μπορείς και με ανοιχτό: OpenTelemetry Collector + Grafana Tempo/Mimir. Το Langfuse προσθέτει έτοιμο UI, agent graph, evals και κόστος ανά trace. Το ίδιο OTLP τροφοδοτεί και τα δύο, άρα η επιλογή δεν είναι μόνιμη.

Πρέπει να στέλνω prompts και completions στα traces;

Όχι από προεπιλογή. Τα αρχικά telemetry περιέχουν metadata (model, tokens, durations) — αρκετά για το 90% του monitoring. Το content capture είναι opt-in και θέλει redaction για PII.

Πώς εντοπίζω agent που μπαίνει σε loop;

Με counter σε tool calls ανά observation και alert όταν ξεπερνάει όριο, συν μετρική σε επαναλαμβανόμενα ονόματα tools. Το aggregated agent graph δείχνει τους κύκλους οπτικά.

Λειτουργεί με Vercel AI SDK;

Ναι. Το AI SDK έχει ενσωματωμένο telemetry που εξάγει OTel spans για generations και tool calls, και συνδέεται απευθείας με OTLP backends όπως το Langfuse. Δες το tutorial για το SDK 7.

Είναι τα GenAI conventions σταθερά;

Όχι ακόμα — βρίσκονται υπό ενεργή ανάπτυξη και ορισμένα attributes αλλάζουν. Γι' αυτό διατηρούμε τα δεδομένα σε OTLP backend: αλλάζεις consumer χωρίς να ξαναγράφεις instrumentation. Αν θες τις νεότερες συμβάσεις, συχνά χρειάζεται OTEL_SEMCONV_STABILITY_OPT_IN=gen_ai_latest_experimental.

Ενότητα 16

Πηγές

Διαφάνεια: τα endpoints, headers και ονόματα attributes επαληθεύτηκαν στα επίσημα docs τον Οκτώβριο 2026. Τα GenAI conventions εξελίσσονται — έλεγξε την έκδοση πριν το κλειδώσεις σε production.