# Use negative weights to minimize cost and duration
objective = MultiMetricObjective(
    metrics=[answer_relevance, cost_in_cents, duration_seconds],
    weights=[1.0, -5, -0.3],  # Maximize quality, minimize cost (manually adjusted bigger weight since the cost values are small), minimize duration
    name="quality_cost_speed",
)
```

<Note>
  Span-based metrics like `TotalSpanCost` and `SpanDuration` automatically receive the `task_span` parameter during evaluation, which contains execution information about the agent's run. Use negative weights to minimize metrics (cost, duration) rather than maximize them.
</Note>

<Note>
  LLM task total cost and duration are not normalized values, we recommend adjusting their weights based on your baseline metrics and the importance you want them to have.
</Note>

## Recommended presets

| Scenario                | Metric                                                              | Notes                                                                    |
| ----------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------------ |
| Factual QA              | `LevenshteinRatio` or `ExactMatch`                                  | Works with text-only datasets; deterministic and low cost.               |
| Retrieval / grounding   | `AnswerRelevance`                                                   | Pass reference context via `context=[item["answer"]]` or retrieved docs. |
| Safety                  | `Moderation` or custom LLM-as-a-judge                               | Combine with `MultiMetricObjective` to gate unsafe answers.              |
| Multi-turn trajectories | [Agent trajectory evaluator](/evaluation/evaluate_agent_trajectory) | Scores complete conversations, not just final outputs.                   |

Reuse these heuristics before writing custom metrics—most are already imported in `opik.evaluation.metrics`.

## Checklist for great metrics

* **Return explanations** – populate `reason` so reflective optimizers can group failure modes.
* **Avoid randomness** – deterministic metrics keep optimizers from chasing noise.
* **Bound runtime** – use cached references or lightweight models where possible; heavy metrics slow down trials.
* **Log metadata** – include `details` in the `ScoreResult` if you want to visualize per-sample attributes later.

When you outgrow presets, move to [Custom metrics](/agent_optimization/advanced/custom_metrics) for LLM-as-a-judge flows or domain-specific scoring.

## Testing metrics

1. Dry-run against a handful of dataset rows before launching an optimization.
2. Use `optimizer.task_evaluator.evaluate_prompt` to evaluate a single prompt with your metric.
3. Inspect the per-sample reasons in the Opik dashboard to ensure they match expectations.

## Related resources

* Deep dive: [Multi-metric optimization guide](/agent_optimization/best_practices/multi_metric_optimization)
* API reference: [`ScoreResult`](https://www.comet.com/docs/opik/python-sdk-reference/Objects/ScoreResult.html)
* Advanced topic: [Custom metrics](/agent_optimization/advanced/custom_metrics)