Back to Blog

AI Agent Observability: Beyond Basic Monitoring

Ultrion TeamJuly 18, 202613 min read

AI Agent Observability: Beyond Basic Monitoring

How to build deep observability into your AI agents for debugging, optimization, and improvement.


Observability for AI agents goes beyond knowing if your service is up. You need to understand why an agent made a specific decision, where quality drops occur, and how to systematically improve performance. This is AI observability.


What AI Observability Means

The Three Pillars

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚          AI Observability                β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                         β”‚
β”‚  1. Traces β€” Full request lifecycle     β”‚
β”‚     User message β†’ Agent thinking β†’     β”‚
β”‚     Tool calls β†’ Response generation    β”‚
β”‚                                         β”‚
β”‚  2. Metrics β€” Quantitative measures     β”‚
β”‚     Latency, cost, tokens, error rate,  β”‚
β”‚     quality scores, satisfaction        β”‚
β”‚                                         β”‚
β”‚  3. Logs β€” Qualitative context          β”‚
β”‚     Decision reasoning, tool inputs,    β”‚
β”‚     model outputs, user feedback        β”‚
β”‚                                         β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Implementing Traces

Full Request Trace

import opentelemetry as otel

class AgentTracer:
    def __init__(self):
        self.tracer = otel.trace.get_tracer("ai-agent")

    async def trace_request(self, user_message, agent_process_fn):
        with self.tracer.start_as_current_span("agent_request") as root:
            root.set_attribute("user.id", user.id)
            root.set_attribute("message.length", len(user_message))
            root.set_attribute("message.preview", user_message[:100])

            # Trace intent classification
            with self.tracer.start_as_current_span("intent_classification"):
                intent = await classify_intent(user_message)
                span.set_attribute("intent", intent.name)
                span.set_attribute("confidence", intent.confidence)

            # Trace tool selection and execution
            with self.tracer.start_as_current_span("tool_selection"):
                tools = await select_tools(intent)
                span.set_attribute("tools.selected", [t.name for t in tools])

            for tool in tools:
                with self.tracer.start_as_current_span(f"tool_{tool.name}"):
                    span.set_attribute("tool.input", str(tool.input))
                    result = await tool.execute()
                    span.set_attribute("tool.output_size", len(str(result)))
                    span.set_attribute("tool.success", result.success)
                    span.set_attribute("tool.duration_ms", result.duration)

            # Trace response generation
            with self.tracer.start_as_current_span("response_generation"):
                response = await generate_response(context)
                span.set_attribute("response.tokens", response.token_count)
                span.set_attribute("response.cost", response.cost)

            return response

Trace Visualization

[agent_request] ──────────────────────────── 3.2s €0.03
β”œβ”€β”€ [intent_classification] ──────── 0.3s
β”‚   intent: "refund_request" confidence: 0.92
β”œβ”€β”€ [tool_selection] ──────────────── 0.1s
β”‚   tools: ["check_order", "process_refund"]
β”œβ”€β”€ [tool_check_order] ────────────── 0.8s
β”‚   input: {order_id: "12345"}
β”‚   output: {status: "delivered", total: 49.99}
β”‚   success: true
β”œβ”€β”€ [tool_process_refund] ─────────── 1.2s
β”‚   input: {order_id: "12345", reason: "damaged"}
β”‚   output: {refund_id: "ref_789", amount: 49.99}
β”‚   success: true
β”œβ”€β”€ [response_generation] ─────────── 0.8s
β”‚   tokens: {input: 450, output: 120}
β”‚   cost: $0.0014
└── [quality_check] ───────────────── 0.2s
    quality_score: 0.91
    passed: true

Quality Metrics

Automated Quality Scoring

class QualityScorer:
    async def score(self, conversation):
        return {
            "relevance": await self.score_relevance(conversation),
            "accuracy": await self.score_accuracy(conversation),
            "completeness": await self.score_completeness(conversation),
            "tone": await self.score_tone(conversation),
            "safety": await self.score_safety(conversation),
        }

    async def score_relevance(self, conversation):
        """Is the response relevant to the question?"""
        prompt = f"""
        Rate the relevance of this response to the question (0-1).

        Question: {conversation.user_message}
        Response: {conversation.agent_response}

        Score:
        """
        score = await evaluator_llm.complete(prompt)
        return float(score)

    async def score_accuracy(self, conversation):
        """Is the response factually correct?"""
        if conversation.tool_results:
            # Verify against tool data
            return self.check_against_tool_data(
                conversation.agent_response,
                conversation.tool_results,
            )
        return 0.7  # Default when no ground truth available

User Feedback Collection

class FeedbackCollector:
    async def collect(self, conversation_id):
        return {
            "explicit": await self.get_explicit_feedback(conversation_id),
            "implicit": await self.get_implicit_signals(conversation_id),
        }

    async def get_explicit_feedback(self, conversation_id):
        """Thumbs up/down, ratings, comments."""
        return await db.query("feedback",
            conversation_id=conversation_id,
            type="explicit",
        )

    async def get_implicit_signals(self, conversation_id):
        """Behavioral signals that indicate quality."""
        conversation = await db.get_conversation(conversation_id)

        return {
            "response_time_to_read": self.calc_read_time(conversation),
            "follow_up_questions": self.count_followups(conversation),
            "rephrased_question": self.check_rephrase(conversation),
            "session_ended": self.check_ended(conversation),
            "escalation_requested": self.check_escalation(conversation),
        }

Distributed Tracing for Multi-Agent Systems

class MultiAgentTracer:
    async def trace_orchestration(self, task, orchestrator):
        with self.tracer.start_as_current_span("multi_agent_task") as root:
            root.set_attribute("task.description", task.description)
            root.set_attribute("agents.involved", [])

            for step in orchestrator.steps:
                with self.tracer.start_as_current_span(
                    f"agent_{step.agent_name}"
                ) as agent_span:
                    agent_span.set_attribute("agent.name", step.agent_name)
                    agent_span.set_attribute("agent.task", step.task)

                    result = await step.agent.process(step.task)

                    agent_span.set_attribute("agent.duration", result.duration)
                    agent_span.set_attribute("agent.cost", result.cost)
                    agent_span.set_attribute("agent.quality", result.quality_score)
                    agent_span.set_attribute("agent.tools_used", result.tools_used)

            # Final synthesis
            with self.tracer.start_as_current_span("synthesis"):
                final = await orchestrator.synthesize(all_results)
                root.set_attribute("final.quality", final.quality_score)

Debugging with Observability

Finding Quality Issues

class QualityDebugger:
    async def find_low_quality_conversations(self, threshold=0.7):
        """Find conversations where quality dropped."""
        return await db.query("""
            SELECT * FROM conversations
            WHERE quality_score < %s
            ORDER BY quality_score ASC
            LIMIT 100
        """, threshold)

    async def analyze_pattern(self, conversations):
        """Find common patterns in low-quality conversations."""
        patterns = {
            "common_topics": Counter(),
            "failed_tools": Counter(),
            "model_versions": Counter(),
            "common_times": Counter(),
            "input_lengths": [],
        }

        for conv in conversations:
            patterns["common_topics"][conv.topic] += 1
            for tool in conv.failed_tools:
                patterns["failed_tools"][tool] += 1
            patterns["model_versions"][conv.model] += 1
            patterns["common_times"][conv.timestamp.hour] += 1
            patterns["input_lengths"].append(len(conv.user_message))

        return patterns

Regression Detection

class RegressionDetector:
    async def check_for_regressions(self):
        """Compare recent quality metrics to baseline."""
        current = await self.get_recent_metrics(days=7)
        baseline = await self.get_baseline_metrics(days=30)

        regressions = []

        for metric in ["quality", "latency", "cost", "satisfaction"]:
            current_val = getattr(current, metric)
            baseline_val = getattr(baseline, metric)

            if self.is_regression(metric, current_val, baseline_val):
                regressions.append({
                    "metric": metric,
                    "current": current_val,
                    "baseline": baseline_val,
                    "change": (current_val - baseline_val) / baseline_val,
                })

        if regressions:
            await self.alert_team(regressions)

        return regressions

Tools for AI Observability

Tool Best For Key Feature Price
LangSmith LangChain agents Deep tracing Free/€39+
Langfuse Any LLM app Open source Free/€50+
Phoenix ML teams Embedding analysis Free
Helicone OpenAI apps Proxy-based Free/€29+
Arize Enterprise Full-stack AI obs Custom
Datadog AI Existing users Integrates with infra €15+/host

Building an Observability Stack

Recommended Stack

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚         Observability Stack              β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                         β”‚
β”‚  Tracing: Langfuse (open source)        β”‚
β”‚  Metrics: Grafana + Prometheus          β”‚
β”‚  Logs: ELK (Elasticsearch) or Loki      β”‚
β”‚  Alerts: PagerDuty + Slack              β”‚
β”‚  Dashboards: Grafana                    β”‚
β”‚  Quality: Custom LLM-based evaluation   β”‚
β”‚                                         β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Conclusion

AI observability is essential for maintaining and improving AI agents in production. By implementing comprehensive traces, quality metrics, and regression detection, you can identify issues proactively and systematically improve your agents.


Learn More

Explore observability tools on SkillExchange.

Newsletter

Enjoying this article?

Get weekly insights on building and selling AI skills, MCP tools, and creator economics. Join 2,000+ AI builders and creators.

No spam. Unsubscribe anytime.

Related Articles

Ready to try AI skills?

Browse the marketplace and discover skills for your AI agents.

Browse Skills