# Comprehensive evaluation approach
scoring_metrics = [
    # Output quality metrics
    Equals(),
    Hallucination(),

    # Execution analysis metrics
    TaskExecutionQualityMetric(),
    PerformanceMetric(),

    # Cost optimization metrics
    CostEfficiencyMetric(),
]
```

### 4. Security Considerations

Be mindful of sensitive data in span information:

```python
def score(self, task_span: SpanModel, **ignored_kwargs: Any) -> score_result.ScoreResult:
    # Avoid logging sensitive input data
    input_size = len(str(task_span.input)) if task_span.input else 0

    # Use aggregated information instead of raw data
    return score_result.ScoreResult(
        value=1.0 if input_size < 1000 else 0.5,
        name=self.name,
        reason=f"Input size: {input_size} characters"
    )
```

## Complete Example: Agent Trajectory Analysis metric

Here's a comprehensive example that analyzes agent decision-making:

```python
class AgentTrajectoryMetric(BaseMetric):
    def __init__(self, max_steps: int = 10, name: str = "agent_trajectory_quality"):
        super().__init__(name=name)
        self.max_steps = max_steps

    def _analyze_trajectory_recursively(self, span: SpanModel, trajectory_stats: Dict[str, Any] = None) -> Dict[str, Any]:
        """Recursively analyze agent trajectory across the span tree."""
        if trajectory_stats is None:
            trajectory_stats = {
                'total_steps': 0,
                'tool_uses': 0,
                'llm_reasoning': 0,
                'other_steps': 0,
                'tool_spans': [],
                'llm_spans': [],
                'step_names': [],
                'max_depth': 0,
                'current_depth': 0
            }

        # Count current span as a step
        trajectory_stats['total_steps'] += 1
        trajectory_stats['step_names'].append(span.name)
        trajectory_stats['max_depth'] = max(trajectory_stats['max_depth'], trajectory_stats['current_depth'])

        # Categorize span types for agent decision analysis
        if span.type == "tool":
            trajectory_stats['tool_uses'] += 1
            trajectory_stats['tool_spans'].append(span.name)
        elif span.type == "llm":
            trajectory_stats['llm_reasoning'] += 1
            trajectory_stats['llm_spans'].append(span.name)
        else:
            trajectory_stats['other_steps'] += 1

        # Recursively analyze nested spans with depth tracking
        for nested_span in span.spans:
            trajectory_stats['current_depth'] += 1
            self._analyze_trajectory_recursively(nested_span, trajectory_stats)
            trajectory_stats['current_depth'] -= 1

        return trajectory_stats

    def score(self, task_span: SpanModel, **ignored_kwargs: Any) -> score_result.ScoreResult:
        # Analyze agent trajectory across an entire span tree
        trajectory_stats = self._analyze_trajectory_recursively(task_span)

        total_steps = trajectory_stats['total_steps']
        tool_uses = trajectory_stats['tool_uses']
        llm_reasoning = trajectory_stats['llm_reasoning']
        max_depth = trajectory_stats['max_depth']

        # Check for an efficient path
        if total_steps == 0:
            return score_result.ScoreResult(
                value=0.0, name=self.name,
                reason="No decision steps found"
            )

        # Analyze trajectory quality with enhanced metrics.
        # Only for illustrative purposes.
        # Please adjust for your specific use case!
        if tool_uses == 0 and llm_reasoning == 0:
            score = 0.1
            reason = f"Poor trajectory: {total_steps} steps with no tools or reasoning"
        elif tool_uses == 0:
            score = 0.3
            reason = f"Agent used {llm_reasoning} reasoning steps but no tools across {total_steps} operations"
        elif llm_reasoning == 0:
            score = 0.4
            reason = f"Agent used {tool_uses} tools but no reasoning across {total_steps} operations"
        elif total_steps > self.max_steps:
            # Penalize excessive steps but consider tool/reasoning balance
            efficiency_penalty = max(0.1, 1.0 - (total_steps - self.max_steps) * 0.05)
            balance_ratio = min(tool_uses, llm_reasoning) / max(tool_uses, llm_reasoning)
            score = min(0.6, efficiency_penalty * balance_ratio)
            reason = f"Excessive steps: {total_steps} > {self.max_steps} (depth: {max_depth}, tools: {tool_uses}, reasoning: {llm_reasoning})"
        else:
            # Calculate a comprehensive score based on multiple factors.
            # Only for illustrative purposes.
            # Please adjust for your specific use case!
            #
            # 1. Step efficiency (fewer steps = better)
            # 1. Step efficiency (fewer steps = better)
            step_efficiency = min(1.0, self.max_steps / total_steps)

            # 2. Tool-reasoning balance (closer to 1:1 ratio = better)
            balance_ratio = min(tool_uses, llm_reasoning) / max(tool_uses, llm_reasoning) if max(tool_uses, llm_reasoning) > 0 else 0
            balance_ratio = min(tool_uses, llm_reasoning) / max(tool_uses, llm_reasoning) if max(tool_uses, llm_reasoning) > 0 else 0

            # 3. Depth complexity (moderate depth suggests good decomposition)
            depth_score = 1.0 if max_depth <= 3 else max(0.7, 1.0 - (max_depth - 3) * 0.1)

            # 4. Decision density (good ratio of reasoning to total steps)
            decision_density = llm_reasoning / total_steps if total_steps > 0 else 0
            density_score = 1.0 if decision_density >= 0.3 else decision_density / 0.3

            # Combine all factors
            score = (step_efficiency * 0.3 + balance_ratio * 0.3 + depth_score * 0.2 + density_score * 0.2)

            if score >= 0.8:
                reason = f"Excellent trajectory: {total_steps} steps (depth: {max_depth}), {tool_uses} tools, {llm_reasoning} reasoning - well balanced"
            elif score >= 0.6:
                reason = f"Good trajectory: {total_steps} steps (depth: {max_depth}), {tool_uses} tools, {llm_reasoning} reasoning"
            else:
                reason = f"Acceptable trajectory: {total_steps} steps (depth: {max_depth}), {tool_uses} tools, {llm_reasoning} reasoning - could be optimized"

        return score_result.ScoreResult(
            value=score,
            name=self.name,
            reason=reason
        )
```

## Integration with LLM Evaluation

For a complete guide on using task span metrics in LLM evaluation workflows, see the [Using task span evaluation metrics](/evaluation/evaluate_your_llm#using-task-span-evaluation-metrics) section in the LLM evaluation guide.

## Related Documentation

* [Custom Metrics](/evaluation/metrics/custom_metric) - Creating traditional input/output evaluation metrics
* [SpanModel API Reference](https://www.comet.com/docs/opik/python-sdk-reference/message_processing_emulation/SpanModel.html) - Complete SpanModel documentation
* [Evaluation Overview](/evaluation/metrics/overview) - Understanding Opik's evaluation system