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

Quelle  head.js

  Sprache: JAVA
 

/* Any copyright is dedicated to the Public Domain.
   http://creativecommons.org/publicdomain/zero/1.0/ */


/// <reference path="../../../../../toolkit/components/translations/tests/browser/shared-head.js" />

// Load the shared-head file first.
Services.scriptloader.loadSubScript(
  "chrome://mochitests/content/browser/toolkit/components/ml/tests/browser/shared-head.js",
  this
);

/**
 * @type {import("../../actors/MLEngineParent.sys.mjs")}
 */

const { MLEngineParent, MLEngine } = ChromeUtils.importESModule(
  "resource://gre/actors/MLEngineParent.sys.mjs"
);

const { ModelHub, TestIndexedDBCache } = ChromeUtils.importESModule(
  "chrome://global/content/ml/ModelHub.sys.mjs"
);

const { getInferenceProcessInfo } = ChromeUtils.importESModule(
  "chrome://global/content/ml/Utils.sys.mjs"
);

const { HttpServer } = ChromeUtils.importESModule(
  "resource://testing-common/httpd.sys.mjs"
);

const MS_PER_SEC = 1000;
const IndexedDBCache = TestIndexedDBCache;

/**
 * @type {import("../../../ml/content/EngineProcess.sys.mjs")}
 */

const {
  createEngine,
  PipelineOptions,
  QuantizationLevel,
  ExecutionPriority,
  InferenceDevice,
  LogLevel,
} = ChromeUtils.importESModule(
  "chrome://global/content/ml/EngineProcess.sys.mjs"
);

// This test suite shares some utility functions with translations as they work in a very
// similar fashion. Eventually, the plan is to unify these two components.
Services.scriptloader.loadSubScript(
  "chrome://mochitests/content/browser/toolkit/components/translations/tests/browser/shared-head.js",
  this
);

/**
 * Mock out remote settings and set some default preferences for the testing environment.
 */

async function setup({
  disabled = false,
  prefs = [],
  records = null,
  backend,
} = {}) {
  const { removeMocks, remoteClients } = await createAndMockMLRemoteSettings({
    autoDownloadFromRemoteSettings: false,
    records,
    backend,
  });

  Services.fog.testResetFOG();

  await SpecialPowers.pushPrefEnv({
    set: [
      // Enabled by default.
      ["browser.ml.enable", !disabled],
      ["browser.ml.logLevel""All"],
      ["browser.ml.modelCacheTimeout"1000],
      ["browser.ml.checkForMemory"false],
      ["browser.ml.queueWaitTimeout"2],
      ["javascript.options.wasm_lazy_tiering"true],
      ...prefs,
    ],
  });

  return {
    remoteClients,
    async cleanup() {
      await removeMocks();
      await waitForCondition(
        () => EngineProcess.areAllEnginesTerminated(),
        "Waiting for all of the engines to be terminated.",
        100,
        200
      );
      await SpecialPowers.popPrefEnv();
    },
  };
}

function getDefaultWasmRecords(backend) {
  return [
    {
      name: MLEngineParent.WASM_FILENAME[
        backend || MLEngineParent.DEFAULT_BACKEND
      ],
      version:
        MLEngineParent.WASM_MAJOR_VERSION[
          backend || MLEngineParent.DEFAULT_BACKEND
        ] + ".0",
    },
  ];
}

async function createAndMockMLRemoteSettings({
  autoDownloadFromRemoteSettings = false,
  records = null,
  backend,
} = {}) {
  const wasmRecords = getDefaultWasmRecords(backend).map(
    ({ name, version }) => ({
      id: crypto.randomUUID(),
      name,
      version,
      last_modified: Date.now(),
      schema: Date.now(),
    })
  );
  const runtime = await createRemoteClient({
    collectionName: "test-translation-wasm",
    records: wasmRecords,
    attachmentMock: true,
    autoDownloadFromRemoteSettings,
  });

  const options = await createRemoteClient({
    records: records || [
      {
        taskName: "moz-echo",
        modelId: "mozilla/distilvit",
        processorId: "mozilla/distilvit",
        tokenizerId: "mozilla/distilvit",
        modelRevision: "main",
        processorRevision: "main",
        tokenizerRevision: "main",
        dtype: "q8",
        id: "74a71cfd-1734-44e6-85c0-69cf3e874138",
      },
    ],
    collectionName: "test-ml-inference-options",
  });

  const allowDeny = await createRemoteClient({
    records: [
      {
        filter: "ALLOW",
        urlPrefix: "https://",
        id: "74a71cfd-1734-44e6-85c0-69cf3e874138",
      },
    ],
    collectionName: "test-ml-allow-deny-list",
  });

  const remoteClients = {
    "ml-onnx-runtime": runtime,
    "ml-inference-options": options,
    "ml-model-allow-deny-list": allowDeny,
  };

  MLEngineParent.mockRemoteSettings({
    "ml-onnx-runtime": runtime.client,
    "ml-inference-options": options,
    "ml-model-allow-deny-list": allowDeny,
  });

  return {
    async removeMocks() {
      await runtime.client.attachments.deleteAll();
      await runtime.client.db.clear();
      await options.db.clear();
      await allowDeny.db.clear();
      MLEngineParent.removeMocks();
    },
    remoteClients,
  };
}

/**
 * Creates a local RemoteSettingsClient for use within tests.
 *
 * @returns {RemoteSettings|AttachmentMock}
 */

async function createRemoteClient({
  records,
  collectionName,
  attachmentMock = false,
  autoDownloadFromRemoteSettings = false,
}) {
  const { RemoteSettings } = ChromeUtils.importESModule(
    "resource://services-settings/remote-settings.sys.mjs"
  );
  const client = RemoteSettings(`${collectionName}-${_remoteSettingsMockId++}`);
  await client.db.clear();
  await client.db.importChanges({}, Date.now(), records);

  if (attachmentMock) {
    return createAttachmentMock(
      client,
      collectionName,
      autoDownloadFromRemoteSettings
    );
  }
  return client;
}

/*
 * Perftest related
 */

const ONE_MIB = 1024 * 1024;
const INIT_START = "initializationStart";
const INIT_END = "initializationEnd";
const RUN_START = "runStart";
const RUN_END = "runEnd";
const PIPELINE_READY_START = "ensurePipelineIsReadyStart";
const PIPELINE_READY_END = "ensurePipelineIsReadyEnd";
const PIPELINE_READY_LATENCY = "pipeline-ready-latency";
const INITIALIZATION_LATENCY = "initialization-latency";
const MODEL_RUN_LATENCY = "model-run-latency";
const TOTAL_MEMORY_USAGE = "total-memory-usage";
const COLD_START_PREFIX = "cold-start-";
const PEAK_MEMORY_USAGE = "peak-memory-usage";
const ITERATIONS = 4;
const WHEN = "when";
const MEMORY = "memory";
const E2E_INIT_LATENCY = "e2e-init-latency";
const E2E_RUN_LATENCY = "e2e-run-latency";
const FIRST_TOKEN_LATENCY = "1st-token-latency";
const DECODING_LATENCY = "decoding-latency";
// Token speeds are apppropriate for comparing the speed of the same model.
const DECODING_TOKEN_SPEED = "decoding-tokenSpeed";
const PROMPT_TOKEN_SPEED = "prompt-tokenSpeed";
// Characters speed is appropriate for comparing the speed of two different models.
const DECODING_CHARACTERS_SPEED = "decoding-charactersSpeed";
const PROMPT_CHARACTERS_SPEED = "prompt-charactersSpeed";

const formatNumber = new Intl.NumberFormat("en-US", {
  maximumSignificantDigits: 4,
}).format;

function median(arr) {
  arr = [...arr].sort((a, b) => a - b);
  const mid = Math.floor(arr.length / 2);

  if (arr.length % 2) {
    return arr[mid];
  }

  return (arr[mid - 1] + arr[mid]) / 2;
}

function stringify(arr) {
  function pad(str) {
    str = str.padStart(7" ");
    if (str[0] != " ") {
      str = " " + str;
    }
    return str;
  }

  return arr.reduce((acc, elem) => acc + pad(formatNumber(elem)), "");
}

function reportMetrics(journal) {
  let text = "\nResults (ms)\n";
  const names = Object.keys(journal);
  const prefixLen = 1 + Math.max(...names.map(str => str.length));
  for (const name in journal) {
    const med = median(journal[name]);
    text += (name + ":").padEnd(prefixLen, " ") + stringify(journal[name]);
    text += "   median " + formatNumber(med) + "\n";
  }
  const reportedMetrics = [];
  for (const [name, values] of Object.entries(journal)) {
    reportedMetrics.push({
      name,
      values,
      value: median(values),
    });
  }
  dump(text);
  info(`perfMetrics | ${JSON.stringify(reportedMetrics)}`);
}

/**
 * Fetches the latest metric entry with the specified name and retrieves its value for the given key.
 * If multiple metrics share the same name, the function returns the key from the most recent one.
 *
 * @param {Array<object>} metrics - The array of metric objects to search through.
 * @param {string} name - The name of the metric to find.
 * @param {string} key - The key within the metric object whose value should be returned.
 * @returns {*} - The value of the specified key in the latest metric with the given name, or undefined if no matching metric is found.
 */

function fetchMLMetric(metrics, name, key) {
  const matchingMetrics = metrics.filter(metric => metric.name === name);
  if (matchingMetrics.length === 0) {
    return undefined;
  } // Return undefined if no match found
  const latestMetric = matchingMetrics[matchingMetrics.length - 1];
  return latestMetric[key];
}

function fetchLatencyMetrics(metrics, isFirstRun) {
  let timestamps = [];
  if (Array.isArray(metrics?.runTimestamps)) {
    timestamps = metrics.runTimestamps;
  } else if (Array.isArray(metrics)) {
    timestamps = metrics;
  }
  const pipelineLatency =
    fetchMLMetric(timestamps, PIPELINE_READY_END, WHEN) -
    fetchMLMetric(timestamps, PIPELINE_READY_START, WHEN);
  const initLatency =
    fetchMLMetric(timestamps, INIT_END, WHEN) -
    fetchMLMetric(timestamps, INIT_START, WHEN);
  const runLatency =
    fetchMLMetric(timestamps, RUN_END, WHEN) -
    fetchMLMetric(timestamps, RUN_START, WHEN);
  return {
    [`${isFirstRun ? COLD_START_PREFIX : ""}${PIPELINE_READY_LATENCY}`]:
      pipelineLatency,
    [`${isFirstRun ? COLD_START_PREFIX : ""}${INITIALIZATION_LATENCY}`]:
      initLatency,
    [`${isFirstRun ? COLD_START_PREFIX : ""}${MODEL_RUN_LATENCY}`]: runLatency,
  };
}

function fetchMetrics(metrics, isFirstRun) {
  return {
    ...fetchLatencyMetrics(metrics, isFirstRun),
  };
}

async function initializeEngine(pipelineOptions, prefs = null) {
  const modelDirectory = normalizePathForOS(
    `${Services.env.get("MOZ_FETCHES_DIR")}/onnx-models`
  );
  info(`Model Directory: ${modelDirectory}`);

  const modelHubRootUrl = Services.env.get("MOZ_MODELS_HUB");
  if (!modelHubRootUrl) {
    throw new Error(
      "MOZ_MODELS_HUB is not set, you need to run with --hooks toolkit/components/ml/tests/tools/hooks_local_hub.py"
    );
  }

  info(`ModelHubRootUrl: ${modelHubRootUrl}`);
  var browserPrefs = [["browser.ml.modelHubRootUrl", modelHubRootUrl]];
  if (prefs) {
    browserPrefs = browserPrefs.concat(prefs);
  }

  const { cleanup } = await perfSetup({
    prefs: browserPrefs,
    backend: pipelineOptions.backend,
  });
  info("Get the engine process");
  const startTime = performance.now();
  const engine = await createEngine(new PipelineOptions(pipelineOptions));
  const e2eInitTime = performance.now() - startTime;

  info("Get Pipeline Options");
  info("Run the inference");
  return {
    cleanup,
    engine,
    e2eInitTime,
  };
}

function normalizePathForOS(path) {
  if (Services.appinfo.OS === "WINNT") {
    // On Windows, replace forward slashes with backslashes
    return path.replace(/\//g, "\\");
  }

  // On Unix-like systems, replace backslashes with forward slashes
  return path.replace(/\\/g, "/");
}

async function perfSetup({ disabled = false, prefs = [], backend } = {}) {
  const { removeMocks, remoteClients } = await createAndMockMLRemoteSettings({
    autoDownloadFromRemoteSettings: false,
    backend,
  });

  var finalPrefs = [
    // Enabled by default.
    ["browser.ml.enable", !disabled],
    ["browser.ml.logLevel""Error"],
    ["browser.ml.modelCacheTimeout"1000],
    ["browser.ml.checkForMemory"false],
    ["javascript.options.wasm_lazy_tiering"true],
    ...prefs,
  ];

  await SpecialPowers.pushPrefEnv({
    set: finalPrefs,
  });

  let artifactDirectory = normalizePathForOS(
    `${Services.env.get("MOZ_FETCHES_DIR")}`
  );

  async function pathExists(path) {
    try {
      return await IOUtils.exists(path);
    } catch (e) {
      return false;
    }
  }

  // Stop immediately if this fails.
  if (!artifactDirectory) {
    artifactDirectory = normalizePathForOS(
      `${Services.env.get("MOZ_ML_LOCAL_DIR")}`
    );
  }

  if (!artifactDirectory) {
    throw new Error(
      `The wasm artifact directory is not set. This usually happens when running locally. " +
      "Please download all the files from taskcluster/kinds/fetch/onnxruntime-web-fetch.yml. " +
      "Place them in a directory and rerun the test with the environment variable 'MOZ_ML_LOCAL_DIR' " +
      "set such that all the files are directly inside 'MOZ_ML_LOCAL_DIR'`
    );
  }

  if (!PathUtils.isAbsolute(artifactDirectory)) {
    throw new Error(
      "Please provide an absolute path for 'MOZ_ML_LOCAL_DIR and not a relative path"
    );
  }

  async function download(record) {
    info(`Downloading record: ${record.name}`);
    const recordPath = normalizePathForOS(
      `${artifactDirectory}/${record.name}`
    );

    // Stop immediately if this fails.
    if (!(await pathExists(recordPath))) {
      throw new Error(`The wasm file <${recordPath}> does not exist. This usually happens when running locally. " +
        "Please download all the files from taskcluster/kinds/fetch/onnxruntime-web-fetch.yml. " +
        "Place them in the directory <${artifactDirectory}> " +
        "such that <${recordPath}> exists.`);
    }

    return {
      buffer: (await IOUtils.read(recordPath)).buffer,
    };
  }

  remoteClients["ml-onnx-runtime"].client.attachments.download = download;

  let downloadRSAttachmentStub;

  // Lot of unit tests even the one outside our components declare sinon
  // So we are not making it module level to allow including this file without issue
  const { sinon } = ChromeUtils.importESModule(
    "resource://testing-common/Sinon.sys.mjs"
  );

  if (typeof MLEngineParent.downloadRSAttachment.restore !== "function") {
    downloadRSAttachmentStub = sinon
      .stub(MLEngineParent, "downloadRSAttachment")
      .callsFake(async ({ wasmRecord }) => {
        return (await download(wasmRecord)).buffer;
      });
  }

  return {
    remoteClients,
    async cleanup() {
      await removeMocks();
      await waitForCondition(
        () => EngineProcess.areAllEnginesTerminated(),
        "Waiting for all of the engines to be terminated.",
        100,
        200
      );
      await SpecialPowers.popPrefEnv();
      if (downloadRSAttachmentStub) {
        downloadRSAttachmentStub.restore();
      }
    },
  };
}

/**
 * Returns the current total physical memory usage in MiB for the inference process
 */

async function getTotalMemoryUsage() {
  const procInfo = await getInferenceProcessInfo();
  return Math.round(procInfo.memory / ONE_MIB);
}

/**
 * Runs an inference given the options and arguments
 *
 */

async function runInference({
  pipelineOptions,
  request,
  isFirstRun = false,
  browserPrefs = null,
}) {
  info(
    `runInference is request null | ${request === null || request === undefined}`
  );
  const { cleanup, engine, e2eInitTime } = await initializeEngine(
    pipelineOptions,
    browserPrefs
  );

  const streamerOptions = {
    perTokens: true,
    skipPrompt: pipelineOptions.taskName !== "text-generation",
    returnTokens: true,
    ...(request.streamerOptions || {}),
  };
  request = { ...request, streamerOptions };

  let metrics = {};
  let timeToFirstToken;
  let startTime;
  let runStartTime;
  let runEndTime;
  let numGeneratedCharacters = 0;
  let numGeneratedTokens = 0;
  let numPromptCharacters = 0;
  if (streamerOptions.skipPrompt && Array.isArray(request?.args)) {
    numPromptCharacters += request.args
      .flat()
      .reduce((sum, item) => sum + (item?.length || 0), 0);
  }
  let numPromptTokens = 0;
  const run = async () => {
    let isFirstTokenReceived = false;
    let result;
    let currentTokenLen = 0;
    let currentCharLen = 0;
    startTime = performance.now();
    runStartTime = startTime;
    const generator = engine.runWithGenerator(request);

    do {
      result = await generator.next();

      currentTokenLen = result.value?.tokens?.flat()?.length || 0;
      currentCharLen = result.value?.text?.length || 0;

      if (result.value?.isPrompt) {
        numPromptCharacters += currentCharLen;
        numPromptTokens += currentTokenLen;
      } else {
        numGeneratedCharacters += currentCharLen;
        numGeneratedTokens += currentTokenLen;
        if (!isFirstTokenReceived) {
          timeToFirstToken = performance.now() - startTime;
          isFirstTokenReceived = true;
          startTime = performance.now();
        }
      }
    } while (!result.done);

    return result.value;
  };

  try {
    const res = await run();
    runEndTime = performance.now();
    const decodingTime = runEndTime - startTime;
    metrics = fetchMetrics(res.metrics?.runTimestamps || [], isFirstRun);
    metrics[`${isFirstRun ? COLD_START_PREFIX : ""}${TOTAL_MEMORY_USAGE}`] =
      await getTotalMemoryUsage();

    metrics[`${isFirstRun ? COLD_START_PREFIX : ""}${E2E_INIT_LATENCY}`] =
      e2eInitTime;
    metrics[`${isFirstRun ? COLD_START_PREFIX : ""}${FIRST_TOKEN_LATENCY}`] =
      timeToFirstToken;
    metrics[`${isFirstRun ? COLD_START_PREFIX : ""}${DECODING_LATENCY}`] =
      decodingTime;
    metrics[`${isFirstRun ? COLD_START_PREFIX : ""}${E2E_RUN_LATENCY}`] =
      runEndTime - runStartTime;
    metrics[
      `${isFirstRun ? COLD_START_PREFIX : ""}${DECODING_CHARACTERS_SPEED}`
    ] = numGeneratedCharacters / (decodingTime / MS_PER_SEC);
    metrics[`${isFirstRun ? COLD_START_PREFIX : ""}${DECODING_TOKEN_SPEED}`] =
      numGeneratedTokens / (decodingTime / MS_PER_SEC);
    metrics[
      `${isFirstRun ? COLD_START_PREFIX : ""}${PROMPT_CHARACTERS_SPEED}`
    ] = numPromptCharacters / (timeToFirstToken / MS_PER_SEC);
    metrics[`${isFirstRun ? COLD_START_PREFIX : ""}${PROMPT_TOKEN_SPEED}`] =
      numPromptTokens / (timeToFirstToken / MS_PER_SEC);
  } finally {
    await engine.terminate();
    await EngineProcess.destroyMLEngine();
    await cleanup();
  }
  return metrics;
}

/**
 * Can be used to track peak memory
 *
 */

class PeakMemoryTracker {
  constructor(interval = 500) {
    this._memory = 0;
    this._intervalId = null;
    this._interval = interval;
  }

  async collectPeakMemory() {
    const procInfo = await getInferenceProcessInfo();
    if (procInfo.memory && procInfo.memory > this._memory) {
      this._memory = procInfo.memory;
    }
  }

  start() {
    if (this._intervalId !== null) {
      return;
    } // Prevent multiple intervals
    this._intervalId = setInterval(() => {
      this.collectPeakMemory().catch(console.error);
    }, this._interval);
  }

  stop() {
    if (this._intervalId !== null) {
      clearInterval(this._intervalId);
      this._intervalId = null;
    }

    try {
      return Math.round(this._memory / ONE_MIB);
    } finally {
      this._memory = 0;
    }
  }
}
/**
 * Runs a performance test for the given name, options, and arguments and
 * reports the results for perfherder.
 */

async function perfTest({
  name,
  options,
  request,
  iterations = ITERATIONS,
  addColdStart = false,
  trackPeakMemory = false,
  peakMemoryInterval = 500,
  browserPrefs = null,
}) {
  info(`is request null | ${request === null || request === undefined}`);
  name = name.toUpperCase();

  let METRICS;

  // When tracking peak memory we only do this because we're
  // stressing the system with 500ms callbacks so other netrics are impacted
  if (trackPeakMemory) {
    METRICS = [`${name}-${PEAK_MEMORY_USAGE}`];
  } else {
    METRICS = [
      `${name}-${PIPELINE_READY_LATENCY}`,
      `${name}-${INITIALIZATION_LATENCY}`,
      `${name}-${MODEL_RUN_LATENCY}`,
      `${name}-${TOTAL_MEMORY_USAGE}`,
      `${name}-${E2E_RUN_LATENCY}`,
      `${name}-${E2E_INIT_LATENCY}`,
      `${name}-${FIRST_TOKEN_LATENCY}`,
      `${name}-${DECODING_LATENCY}`,
      `${name}-${DECODING_CHARACTERS_SPEED}`,
      `${name}-${DECODING_TOKEN_SPEED}`,
      `${name}-${PROMPT_CHARACTERS_SPEED}`,
      `${name}-${PROMPT_TOKEN_SPEED}`,
      ...(addColdStart
        ? [
            `${name}-${COLD_START_PREFIX}${PIPELINE_READY_LATENCY}`,
            `${name}-${COLD_START_PREFIX}${INITIALIZATION_LATENCY}`,
            `${name}-${COLD_START_PREFIX}${MODEL_RUN_LATENCY}`,
            `${name}-${COLD_START_PREFIX}${TOTAL_MEMORY_USAGE}`,
          ]
        : []),
    ];
  }

  const journal = {};
  for (let metric of METRICS) {
    journal[metric] = [];
  }

  const pipelineOptions = new PipelineOptions(options);
  var tracker;

  let nIterations = addColdStart ? iterations + 1 : iterations;
  for (let i = 0; i < nIterations; i++) {
    if (trackPeakMemory) {
      tracker = new PeakMemoryTracker(peakMemoryInterval);
      tracker.start();
    }
    const shouldAddColdStart = addColdStart && i === 0;
    let metrics = await runInference({
      pipelineOptions,
      request,
      isFirstRun: shouldAddColdStart,
      browserPrefs,
    });
    if (trackPeakMemory) {
      journal[`${name}-${PEAK_MEMORY_USAGE}`].push(tracker.stop());
    } else {
      for (let [metricName, metricVal] of Object.entries(metrics)) {
        if (!Number.isFinite(metricVal) || metricVal < 0) {
          metricVal = 0;
        }
        // Add the metric if it wasn't there
        if (journal[`${name}-${metricName}`] === undefined) {
          journal[`${name}-${metricName}`] = [];
        }
        journal[`${name}-${metricName}`].push(metricVal);
      }
    }
  }
  Assert.ok(true);
  reportMetrics(journal);
}

/**
 * Measures floating point value within epsilon tolerance
 *
 * @param {number[]} a
 * @param {number[]} b
 * @param {number} [epsilon]
 * @returns {boolean}
 */

function isEqualWithTolerance(a, b, epsilon = 0.000001) {
  return Math.abs(Math.abs(a) - Math.abs(b)) < epsilon;
}

/**
 * Asserts whether two float arrays are equal within epsilon tolerance.
 *
 * @param {number[] | ArrayBufferLike} a
 * @param {number[] | ArrayBufferLike} b
 * @param {string} message
 * @param {number} [epsilon]
 */

function assertFloatArraysMatch(a, b, message, epsilon) {
  const raise = () => {
    // When logging errors, spread into a new array so that the logging is nice for
    // ArrayBufferLike values. This makes it easy to see how arrays differ
    console.log("a:", [...a]);
    console.log("b:", [...b]);
    throw new Error(message);
  };
  if (a.length !== b.length) {
    raise();
  }
  for (let i = 0; i < a.length; i++) {
    if (!isEqualWithTolerance(a[i], b[i], epsilon)) {
      raise();
    }
  }
  ok(true, message);
}

// Mock OpenAI Chat Completions server for mochitests
// Serves: http://localhost:11434/v1/chat/completions

function readRequestBody(request) {
  // Read the POST body as UTF-8 text
  const stream = request.bodyInputStream;
  const available = stream.available();
  return NetUtil.readInputStreamToString(stream, available, {
    charset: "UTF-8",
  });
}

function startMockOpenAI({
  echo = "This gets echoed.",
  onRequest = null,
} = {}) {
  const server = new HttpServer();

  server.registerPathHandler("/v1/chat/completions", (request, response) => {
    info("[openai] GET /v1/chat/completions");

    // Call the onRequest callback if provided to allow test inspection
    if (onRequest) {
      onRequest(request);
    }

    let bodyText = "";
    if (request.method === "POST") {
      try {
        bodyText = readRequestBody(request);
      } catch (_) {}
    }
    info("[openai] bodyText: " + bodyText);

    let body;
    try {
      body = JSON.parse(bodyText || "{}");
    } catch (_) {
      body = {};
    }

    const wantsStream = !!body.stream;
    const tools = Array.isArray(body.tools) ? body.tools : [];
    const askedForTools = tools.length;
    const messages = Array.isArray(body.messages) ? body.messages : [];
    const hasToolResult = messages.some(m => m && m.role === "tool");

    // ---- SSE helpers (for streaming mode) ----
    function startSSE() {
      response.setStatusLine(request.httpVersion, 200"OK");
      response.setHeader(
        "Content-Type",
        "text/event-stream; charset=utf-8",
        false
      );
      response.setHeader("Cache-Control""no-cache"false);
      response.setHeader("Access-Control-Allow-Origin""*"false);
      response.processAsync();
    }
    function sendSSE(obj) {
      const line = `data: ${JSON.stringify(obj)}\n\n`;
      response.write(line);
    }
    function endSSE() {
      response.write("data: [DONE]\n\n");
      response.finish();
    }

    // ===========================
    // STREAMING BRANCHES (SSE)
    // ===========================
    if (wantsStream && askedForTools && !hasToolResult) {
      info("[openai] Streaming tool call, without tool results");
      // First turn: stream partial tool_calls, then finish with "tool_calls"
      startSSE();

      // Partial 1: name/args prefix
      sendSSE({
        id: "chatcmpl-mock-tools-stream-1",
        object: "chat.completion.chunk",
        created: Math.floor(Date.now() / 1000),
        model: "qwen3:0.6b",
        choices: [
          {
            index: 0,
            delta: {
              content: "",
              tool_calls: [
                {
                  index: 0,
                  id: "call_1",
                  type: "function",
                  function: { name: "search_", arguments: '{ "type": "ne' },
                },
              ],
            },
            finish_reason: null,
          },
        ],
      });

      // Partial 2: complete name/args
      sendSSE({
        id: "chatcmpl-mock-tools-stream-2",
        object: "chat.completion.chunk",
        created: Math.floor(Date.now() / 1000),
        model: "qwen3:0.6b",
        choices: [
          {
            index: 0,
            delta: {
              content: "",
              tool_calls: [
                {
                  index: 0,
                  id: "call_1",
                  type: "function",
                  function: { name: "open_tabs", arguments: 'ws" }' },
                },
              ],
            },
            finish_reason: null,
          },
        ],
      });

      // Signal the turn ends with tool calls
      sendSSE({
        id: "chatcmpl-mock-tools-stream-3",
        object: "chat.completion.chunk",
        created: Math.floor(Date.now() / 1000),
        model: "qwen3:0.6b",
        choices: [{ index: 0, delta: {}, finish_reason: "tool_calls" }],
      });

      endSSE();
      return;
    }

    if (wantsStream && askedForTools && hasToolResult) {
      // Second turn (after tool result): stream normal assistant text
      info("[openai] Streaming tool call, with results");

      startSSE();

      sendSSE({
        id: "chatcmpl-mock-tools-stream-4",
        object: "chat.completion.chunk",
        created: Math.floor(Date.now() / 1000),
        model: "qwen3:0.6b",
        choices: [
          {
            index: 0,
            delta: { content: "Here are the tabs " },
            finish_reason: null,
          },
        ],
      });

      sendSSE({
        id: "chatcmpl-mock-tools-stream-5",
        object: "chat.completion.chunk",
        created: Math.floor(Date.now() / 1000),
        model: "qwen3:0.6b",
        choices: [
          {
            index: 0,
            delta: { content: "I found for you." },
            finish_reason: "stop",
          },
        ],
      });

      endSSE();
      return;
    }

    // Simple streaming (no tools)
    if (wantsStream && !askedForTools) {
      info("[openai] Simple streaming (no tools)");
      startSSE();

      // Send at least 3 chunks
      sendSSE({
        id: "chatcmpl-mock-stream-1",
        object: "chat.completion.chunk",
        created: Math.floor(Date.now() / 1000),
        model: "qwen3:0.6b",
        choices: [
          {
            index: 0,
            delta: { content: "Hello, " },
            finish_reason: null,
          },
        ],
      });

      sendSSE({
        id: "chatcmpl-mock-stream-2",
        object: "chat.completion.chunk",
        created: Math.floor(Date.now() / 1000),
        model: "qwen3:0.6b",
        choices: [
          {
            index: 0,
            delta: { content: "this is " },
            finish_reason: null,
          },
        ],
      });

      sendSSE({
        id: "chatcmpl-mock-stream-3",
        object: "chat.completion.chunk",
        created: Math.floor(Date.now() / 1000),
        model: "qwen3:0.6b",
        choices: [
          {
            index: 0,
            delta: { content: echo },
            finish_reason: null,
          },
        ],
      });

      // Final chunk with finish_reason
      sendSSE({
        id: "chatcmpl-mock-stream-4",
        object: "chat.completion.chunk",
        created: Math.floor(Date.now() / 1000),
        model: "qwen3:0.6b",
        choices: [
          {
            index: 0,
            delta: { content: "" },
            finish_reason: "stop",
          },
        ],
      });

      endSSE();
      return;
    }

    // ===========================
    // NON-STREAMING BRANCHES
    // ===========================
    if (wantsStream) {
      throw new Error("Unhandled streaming case.");
    }

    // First turn w/ tools: return tool_calls message (finish_reason: tool_calls)
    if (askedForTools && !hasToolResult) {
      info("[openai] Non-streaming tool call without results");
      const payload = {
        id: "chatcmpl-mock-tools-1",
        object: "chat.completion",
        created: Math.floor(Date.now() / 1000),
        model: "qwen3:0.6b",
        choices: [
          {
            index: 0,
            message: {
              role: "assistant",
              content: "",
              tool_calls: [
                {
                  id: "call_1",
                  type: "function",
                  function: {
                    name: "search_open_tabs",
                    arguments: JSON.stringify({ type: "news" }),
                  },
                },
              ],
            },
            finish_reason: "tool_calls",
          },
        ],
        usage: { prompt_tokens: 10, completion_tokens: 0, total_tokens: 10 },
        echo,
      };

      response.setStatusLine(request.httpVersion, 200"OK");
      response.setHeader(
        "Content-Type",
        "application/json; charset=utf-8",
        false
      );
      response.setHeader("Access-Control-Allow-Origin""*"false);
      response.write(JSON.stringify(payload));
      info("[openai] Sending back payload: " + JSON.stringify(payload));
      return;
    }

    // Second turn w/ tools (after tool result): normal assistant message
    if (askedForTools && hasToolResult) {
      info("[openai] Non-streaming tool call with results");
      const payload = {
        id: "chatcmpl-mock-tools-2",
        object: "chat.completion",
        created: Math.floor(Date.now() / 1000),
        model: "qwen3:0.6b",
        choices: [
          {
            index: 0,
            message: {
              role: "assistant",
              content: "Here are the tabs I found for you.",
            },
            finish_reason: "stop",
          },
        ],
        usage: { prompt_tokens: 10, completion_tokens: 5, total_tokens: 15 },
        echo,
      };

      response.setStatusLine(request.httpVersion, 200"OK");
      response.setHeader(
        "Content-Type",
        "application/json; charset=utf-8",
        false
      );
      response.setHeader("Access-Control-Allow-Origin""*"false);
      response.write(JSON.stringify(payload));
      info("[openai] Sending back payload: " + JSON.stringify(payload));
      return;
    }

    info("[openai] Non-streaming call");

    const payload = {
      id: "chatcmpl-mock-1",
      object: "chat.completion",
      created: Math.floor(Date.now() / 1000),
      model: "qwen3:0.6b",
      choices: [
        {
          index: 0,
          message: {
            role: "assistant",
            content: "This is a mock summary for testing end-to-end flow.",
          },
          finish_reason: "stop",
        },
      ],
      usage: { prompt_tokens: 10, completion_tokens: 5, total_tokens: 15 },
      echo,
    };

    info("[openai] Sending back payload: " + JSON.stringify(payload));
    response.setStatusLine(request.httpVersion, 200"OK");
    response.setHeader(
      "Content-Type",
      "application/json; charset=utf-8",
      false
    );
    response.setHeader("Access-Control-Allow-Origin""*"false);
    response.write(JSON.stringify(payload));
  });

  // -1 tells it to pick an ephemeral port
  server.start(-1);
  const port = server.identity.primaryPort;
  return { server, port };
}

function stopMockOpenAI(server) {
  return new Promise(resolve => server.stop(resolve));
}

/**
 * Generates a numpy encoded float16 array to be used for generating static embeddings
 * test data.
 *
 * @param {number} vocabSize
 * @param {number} dimensions
 * @returns {{ numbers: Float16Array, encoding: Uint8Array }}
 */

function generateFloat16Numpy(vocabSize, dimensions) {
  const numbers = new Float16Array(vocabSize * dimensions);
  // Build the data:
  // [0.1, 0.2, 0.3, ..., 0.1 * vocabSize * dimensions]
  for (let i = 0; i < vocabSize; i++) {
    for (let j = 0; j < dimensions; j++) {
      const index = i * dimensions + j;
      numbers[index] = index / 10;
    }
  }
  const magic = new Uint8Array([0x93, 7885778089]); // \x93NUMPY
  const version = new Uint8Array([10]); // Version 1.0
  let header = `{'descr''<f2''fortran_order'False'shape': (${vocabSize},${dimensions}), }`;

  // Pad header to 16-byte alignment
  const preLength = magic.length + version.length + 2// +2 for header length field
  let padding = 16 - ((preLength + header.length + 1) % 16);
  if (padding === 16) {
    padding = 0;
  }
  header += " ".repeat(padding) + "\n";

  const headerBytes = new TextEncoder().encode(header);

  const headerLen = new Uint8Array(2);
  new DataView(headerLen.buffer).setUint16(0, headerBytes.length, true);

  const encoding = new Uint8Array(
    preLength + headerBytes.length + numbers.byteLength
  );

  // Write everything out.
  let offset = 0;
  encoding.set(magic, offset);
  offset += magic.length;
  encoding.set(version, offset);
  offset += version.length;
  encoding.set(headerLen, offset);
  offset += 2;
  encoding.set(headerBytes, offset);
  offset += headerBytes.length;
  encoding.set(new Uint8Array(numbers.buffer), offset);

  return { numbers, encoding };
}

/**
 * Checks that a process exists.
 *
 * @param {string} remoteType
 */

async function checkForRemoteType(remoteType) {
  let procinfo3 = await ChromeUtils.requestProcInfo();
  for (const child of procinfo3.children) {
    if (child.type === remoteType) {
      return true;
    }
  }
  return false;
}

Messung V0.5 in Prozent
C=89 H=97 G=93

¤ Dauer der Verarbeitung: 0.13 Sekunden  ¤

*© 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.