Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/toolkit/components/ml/eval/   (Firefox Browser Version 153.0.1©)  Datei vom 27.6.2026 mit Größe 11 kB image not shown  

Quelle  evals.py

  Sprache: Python
 

# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

import json
import os
from abc import ABC, abstractmethod
from typing import Any, Callable


class _Evaluation(ABC):
    """
    The abstract base class for an evaluation that is run by mozperftest.
    See python/mozperftest/mozperftest/metrics/eval.py
    """

    # Include a list of requirements that will be pip installed via the test harness.
    # Requirements should not require a build, and should have the appropriate .whl
    # files for reproducibility.
    requirements: list[str] = []

    def __init__(self, log: Callable[[str], None], config: dict[str, Any]) -> None:
        self.log = log
        self.config = config

    @abstractmethod
    def run(self, payloads: list[dict[str, Any]]) -> dict:
        """Run the evaluation and return a perftest metric result."""
        ...


class _LlmJudge(_Evaluation):
    """
    Use the Mozilla LLM Proxy Auth (MLPA) endpoint to run an LLM as a judge.
    """

    requirements = [
        "openai>=2.7.1",
    ]

    def __init__(self, log: Callable[[str], None], config: dict[str, Any]) -> None:
        super().__init__(log, config)
        self.mlpa_url = "https://mlpa-nonprod-stage-mozilla.global.ssl.fastly.net"
        self.model = config.get("model""vertex_ai/mistral-small-2503")
        self.fxa_token = os.environ.get("MOZ_FXA_BEARER_TOKEN")
        self.mlpa_token = os.environ.get("MOZ_MLPA_AUTHORIZATION_TOKEN")

    def query_llm(
        self,
        model: str,
        messages: list[dict[str, str]],
        response_format: dict[str, Any],
    ):
        from openai import OpenAI

        # check for tokens
        if not self.fxa_token:
            raise RuntimeError("Missing MOZ_FXA_BEARER_TOKEN for LLM evaluation.")
        if not self.mlpa_token:
            raise RuntimeError(
                "Missing MOZ_MLPA_AUTHORIZATION_TOKEN for LLM evaluation."
            )

        # generate client
        client = OpenAI(
            api_key="unused",
            base_url=f"{self.mlpa_url}/v1",
            default_headers={
                "Authorization": f"Bearer {self.fxa_token}",
                "X-Dev-Authorization": self.mlpa_token,
                "Service-Type""mochi-dev",
            },
        )

        # construct appropriate payload
        payload = {
            "model": model.lower(),
            "messages": messages,
            "stream"False,
            "response_format": response_format,
        }

        # make the request
        try:
            result = client.chat.completions.create(**payload)

        except Exception as e:
            raise RuntimeError(f"LLM query failed: {e}")
        finally:
            # close
            client.close()

        # parse the response
        try:
            content = result.choices[0].message.content
            return json.loads(content)
        except json.JSONDecodeError as exc:
            raise ValueError(
                f"Failed to parse LLM response as JSON: {content}"
            ) from exc


class _TranslationsSacreBleu(_Evaluation):
    """
    Compute the bleu or chrF (character level f-score) for a translation.
    https://en.wikipedia.org/wiki/BLEU
    https://en.wikipedia.org/wiki/F-score

    Use TranslationsBleu and TranslationsChrf for the respective scores.
    """

    requirements = [
        "sacrebleu==2.4.2",
    ]

    name = ""

    def compute_score(self, trg: str, ref: str) -> float:
        raise NotImplementedError()

    def run(self, payloads: list[dict[str, Any]]) -> dict:
        results: list[float] = []
        for payload in payloads:
            if "trg" not in payload or "ref" not in payload:
                raise ValueError(f"Missing required translation fields in {payload}")
            trg = payload["trg"]
            ref = payload["ref"]

            results.append(self.compute_score(trg, ref))

        if not results:
            raise ValueError(
                "No evaluation results were produced for translation data."
            )

        return {
            "name": self.name,
            "values": results,
            "lowerIsBetter"True,
        }


class TranslationsBleu(_TranslationsSacreBleu):
    """See _TranslationsSacreBleu for documentation."""

    name = "bleu"

    def compute_score(self, trg: str, ref: str) -> float:
        import sacrebleu

        self.log("Computing the bleu score")
        return sacrebleu.corpus_bleu([trg], [[ref]]).score


class TranslationsChrf(_TranslationsSacreBleu):
    """See _TranslationsSacreBleu for documentation."""

    name = "chrF"

    def compute_score(self, trg: str, ref: str) -> float:
        import sacrebleu

        self.log("Computing the chrF score")
        return sacrebleu.corpus_chrf([trg], [[ref]]).score


class LlmJudge(_LlmJudge):
    """
    A generic LLM judge that can be used for various evaluation tasks.
    See TranslationsLlmJudge for an example of a specific implementation.
    """

    def run(self, payloads: list[dict[str, Any]]) -> dict:
        # This base implementation just returns the raw LLM response content for each payload.
        # Subclasses can implement specific prompting and parsing logic as needed.
        results = {}
        eval_config = {}
        for payload in payloads:
            # Per-payload evalConfig takes precedence over the top-level eval_config.
            eval_config = payload.get("eval_config", {})
            messages = payload.get("messages")
            if not messages:
                raise ValueError(f"Missing 'messages' field in payload: {payload}")
            response_format = payload.get("response_format")
            if not response_format:
                raise ValueError(
                    f"Missing 'response_format' field in payload: {payload}"
                )
            model = payload.get("model", self.model)
            response = self.query_llm(
                model=model,
                messages=messages,
                response_format=response_format,
            )
            errors = []
            for key, value in response.items():
                key_config = eval_config.get(key, {})
                threshold_min = key_config.get("thresholdMin"None)
                if (
                    threshold_min is not None
                    and isinstance(value, (int, float))
                    and value < threshold_min
                ):
                    errors.append(
                        f"Value for '{key}' is below the minimum threshold of {threshold_min}: {value}"
                    )
                results.setdefault(key, []).append(value)
            if errors:
                raise AssertionError(
                    "LLM judge evaluation failed:\n"
                    + "\n".join(errors)
                    + f"\nJudge Model: {model}\nMessages: {messages}\n\nFull LLM response: {response}"
                )

        if not results:
            raise ValueError("No evaluation results were produced for LLM judge data.")

        # verify that numeric results meet any specified thresholds and determine if any alerts should be raised
        for key, values in results.items():
            key_config = eval_config.get(key, {})
            threshold_min = key_config.get("thresholdMin"None)
            if threshold_min is not None:
                for value in values:
                    if not isinstance(value, (int, float)):
                        raise ValueError(
                            f"LLM judge result for key '{key}' is not numeric: {value}"
                        )
                    if value < threshold_min:
                        raise AssertionError(
                            f"LLM judge result for '{key}' is below the minimum threshold of {threshold_min}: {value}. "
                        )

        # construct return values for perfherder
        return_results = []
        for key, values in results.items():
            if isinstance(values[0], (int, float)):
                return_results.append({
                    "name": key,
                    "values": values,
                    "lowerIsBetter"False,
                    "shouldAlert": eval_config.get(key, {}).get("shouldAlert"False),
                    "alertThreshold": eval_config.get(key, {}).get(
                        "alertThreshold"None
                    ),
                })
            else:
                self.log(
                    f"LLM judge result for key '{key}' is not numeric and will not be included in perfherder metrics: {values}"
                )

        return return_results


class TranslationsLlmJudge(_LlmJudge):
    """
    Judge a translation based on an LLM's judgement.

    Returns:
    {
        "score": int,
        "verdict": str,
        "explanation": str,
        "model": str,
    }

    perfherder_metrics: [
        {
          name: "bleu",
          unit: "bleu",
          lowerIsBetter: false,
          shouldAlert: false,
        },
        {
          name: "chrF",
          unit: "chrF",
          lowerIsBetter: false,
          shouldAlert: false,
        },
    ]
    """

    def run(self, payloads: list[dict[str, Any]]) -> dict:
        results: list[dict[str, Any]] = []
        for payload in payloads:
            missing = [key for key in ("src""trg""ref"if key not in payload]
            if missing:
                raise ValueError(
                    f"Missing required translation fields {missing} in {payload}"
                )
            src = payload["src"]
            trg = payload["trg"]
            ref = payload["ref"]

            user_prompt = (
                f"Source: {src}Reference: {ref}\nHypothesis: {trg}\n"
                'Return JSON with fields: score (0-100), verdict ("good"|"ok"|"bad"), explanation (short).'
            )

            response_format = {
                "type""json_schema",
                "json_schema": {
                    "name""basic_quality_eval",
                    "strict"True,
                    "schema": {
                        "type""object",
                        "properties": {
                            "score": {"type""integer"},
                            "verdict": {"type""string"},
                            "explanation": {"type""string"},
                        },
                        "required": ["score""verdict""explanation"],
                        "additionalProperties"False,
                    },
                },
            }

            model = payload.get("model", self.model)
            response = self.query_llm(
                model=model,
                messages=[
                    {
                        "role""system",
                        "content""You are a translation quality judge. Rate adequacy/fluency.",
                    },
                    {"role""user""content": user_prompt},
                ],
                response_format=response_format,
            )

            score = response.get("score")
            if score is None:
                raise ValueError(f"Missing score in LLM judge response: {response}")
            if isinstance(score, str):
                try:
                    score = float(score)
                except ValueError as exc:
                    raise ValueError(
                        f"Invalid score value in LLM judge response: {response}"
                    ) from exc

            results.append({
                "score": score,
                "verdict": response.get("verdict"),
                "explanation": response.get("explanation"),
                "model": model,
            })

        if not results:
            raise ValueError("No evaluation results were produced for LLM judge data.")

        scores = [result.get("score"0for result in results]
        return {
            "name""llm-judge",
            "values": scores,
            "lowerIsBetter"False,
        }

Messung V0.5 in Prozent
C=92 H=97 G=94

¤ Dauer der Verarbeitung: 0.23 Sekunden  (vorverarbeitet am  2026-08-25) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.