From 295378c0d4971079f9e06faab37fd6bff122e752 Mon Sep 17 00:00:00 2001 From: Domenic Denicola Date: Thu, 24 Oct 2024 15:49:38 +0900 Subject: [PATCH] Initial specification work, focused on creation and downloading --- index.bs | 377 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 370 insertions(+), 7 deletions(-) diff --git a/index.bs b/index.bs index d4f1672..8c79db7 100644 --- a/index.bs +++ b/index.bs @@ -1,21 +1,384 @@ -
+
 
-Introduction {#intro}
-=====================
+
+
+
+
+

Introduction

For now, see the [explainer]([REPOSITORYURL]). + +

Shared AI APIs and infrastructure

+ + +partial interface WindowOrWorkerGlobalScope { + [Replaceable, SecureContext] readonly attribute AI ai; +}; + +[Exposed=(Window,Worker), SecureContext] +interface AI {}; + +[Exposed=(Window,Worker), SecureContext] +interface AICreateMonitor : EventTarget { + attribute EventHandler ondownloadprogress; +}; + +callback AICreateMonitorCallback = undefined (AICreateMonitor monitor); + +enum AICapabilityAvailability { "readily", "after-download", "no" }; + + +Each {{WindowOrWorkerGlobalScope}} has an AI namespace, an {{AI}} object. Upon creation of the {{WindowOrWorkerGlobalScope}} object, its [=WindowOrWorkerGlobalScope/AI namespace=] must be set to a [=new=] {{AI}} object created in the {{WindowOrWorkerGlobalScope}} object's [=relevant realm=]. + +The ai getter steps are to return [=this=]'s [=WindowOrWorkerGlobalScope/AI namespace=]. + +
+ +[=Tasks=] queued by this specification use the AI task source. + +
+ +The following are the [=event handlers=] (and their corresponding [=event handler event types=]) that must be supported, as [=event handler IDL attributes=], by all {{AICreateMonitor}} objects: + + + + + + +
[=Event handler=] + [=Event handler event type=] +
ondownloadprogress + downloadprogress +
+ +

The summarizer API

+ + +partial interface AI { + readonly attribute AISummarizerFactory summarizer; +}; + +[Exposed=(Window,Worker), SecureContext] +interface AISummarizerFactory { + Promise<AISummarizer> create(optional AISummarizerCreateOptions options = {}); + Promise<AISummarizerCapabilities> capabilities(); +}; + +[Exposed=(Window,Worker), SecureContext] +interface AISummarizer { + Promise<DOMString> summarize(DOMString input, optional AISummarizerSummarizeOptions options = {}); + ReadableStream summarizeStreaming(DOMString input, optional AISummarizerSummarizeOptions options = {}); + + readonly attribute DOMString sharedContext; + readonly attribute AISummarizerType type; + readonly attribute AISummarizerFormat format; + readonly attribute AISummarizerLength length; + + undefined destroy(); +}; + +[Exposed=(Window,Worker), SecureContext] +interface AISummarizerCapabilities { + readonly attribute AICapabilityAvailability available; + + AICapabilityAvailability createOptionsAvailable(optional AISummarizerCreateCoreOptions options = {}); + AICapabilityAvailability languageAvailable(DOMString languageTag); +}; + +dictionary AISummarizerCreateCoreOptions { + AISummarizerType type = "key-points"; + AISummarizerFormat format = "markdown"; + AISummarizerLength length = "short"; +}; + +dictionary AISummarizerCreateOptions : AISummarizerCreateCoreOptions { + AbortSignal signal; + AICreateMonitorCallback monitor; + + DOMString sharedContext; +}; + +dictionary AISummarizerSummarizeOptions { + AbortSignal signal; + DOMString context; +}; + +enum AISummarizerType { "tl;dr", "key-points", "teaser", "headline" }; +enum AISummarizerFormat { "plain-text", "markdown" }; +enum AISummarizerLength { "short", "medium", "long" }; + + +Each {{AI}} has an summarizer factory, an {{AISummarizerFactory}} object. Upon creation of the {{AI}} object, its [=AI/summarizer factory=] must be set to a [=new=] {{AISummarizerFactory}} object created in the {{AI}} object's [=relevant realm=]. + +The summarizer getter steps are to return [=this=]'s [=AI/summarizer factory=]. + +

Creation

+ +
+ The create(|options|) method steps are: + + 1. If [=this=]'s [=relevant global object=] is a {{Window}} whose [=associated Document=] is not [=Document/fully active=], then return [=a promise rejected with=] an "{{InvalidStateError}}" {{DOMException}}. + + 1. If |options|["{{AISummarizerCreateOptions/signal}}"] [=map/exists=] and is [=AbortSignal/aborted=], then return [=a promise rejected with=] |options|["{{AISummarizerCreateOptions/signal}}"]'s [=AbortSignal/abort reason=]. + + 1. Let |fireProgressEvent| be an algorithm taking two arguments that does nothing. + + 1. If |options|["{{AISummarizerCreateOptions/monitor}}"] [=map/exists=], then: + + 1. Let |monitor| be a [=new=] {{AICreateMonitor}} created in [=this=]'s [=relevant realm=]. + + 1. [=Invoke=] |options|["{{AISummarizerCreateOptions/monitor}}"] with « |monitor| » and "`rethrow`". + + If an exception |e| is thrown, return [=a promise rejected with=] |e|. + + 1. Set |fireProgressEvent| to an algorithm taking arguments |loaded| and |total|, which performs the following steps: + + 1. [=Assert=]: this algorithm is running [=in parallel=]. + + 1. [=Queue a global task=] on the [=AI task source=] given [=this=]'s [=relevant global object=] to perform the following steps: + + 1. [=Fire an event=] named {{AICreateMonitor/downloadprogress}} at |monitor|, using {{ProgressEvent}}, with the {{ProgressEvent/loaded}} attribute initialized to |loaded|, the {{ProgressEvent/total}} attribute initialized to |total|, and the {{ProgressEvent/lengthComputable}} attribute initialized to true. + + 1. Let |abortedDuringDownload| be false. + +

This variable will be written to from the [=event loop=], but read from [=in parallel=]. + + 1. If |options|["{{AISummarizerCreateOptions/signal}}"] [=map/exists=], then [=AbortSignal/add|add the following abort steps=] to |options|["{{AISummarizerCreateOptions/signal}}"]: + + 1. Set |abortedDuringDownload| to true. + + 1. Let |promise| be [=a new promise=] created in [=this=]'s [=relevant realm=]. + + 1. [=In parallel=]: + + 1. Let |availability| be the [=current summarizer capability value=] given |options|["{{AISummarizerCreateCoreOptions/type}}"], |options|["{{AISummarizerCreateCoreOptions/format}}"], and |options|["{{AISummarizerCreateCoreOptions/length}}"]. + + 1. Switch on |availability|: + +

+ : "{{AICapabilityAvailability/no}}" + :: + 1. [=Reject=] |promise| with a "{{NotSupportedError}}" {{DOMException}}. + + 1. Abort these steps. + + : "{{AICapabilityAvailability/readily}}" + :: + 1. If [=initializing the summarization model=] given |promise| and |options| returns false, then abort these steps. + + 1. Let |totalBytes| be the total size of the previously-downloaded summarization capabilities, in bytes. + + 1. [=Assert=]: |totalBytes| is greater than 0. + + 1. Perform |fireProgressEvent| given 0 and |totalBytes|. + + 1. Perform |fireProgressEvent| given |totalBytes| and |totalBytes|. + + 1. [=Finalize summarizer creation=] given |promise| and |options|. + + : "{{AICapabilityAvailability/after-download}}" + :: + 1. Initiate the download process for everything the user agent needs to summarize text according to |options|["{{AISummarizerCreateCoreOptions/type}}"], |options|["{{AISummarizerCreateCoreOptions/format}}"], or |options|["{{AISummarizerCreateCoreOptions/length}}"]. + + 1. Run the following steps, by [=abort when=] |abortedDuringDownload| becomes true: + + 1. Wait for the total number of bytes to be downloaded to become determined, and let that number be |totalBytes|. + + 1. Let |lastProgressTime| be the [=monotonic clock=]'s [=monotonic clock/unsafe current time=]. + + 1. Perform |fireProgressEvent| given 0 and |totalBytes|. + + 1. While true: + + 1. If one or more bytes have been downloaded, then: + + 1. If the [=monotonic clock=]'s [=monotonic clock/unsafe current time=] minus |lastProgressTime| is greater than 50 ms, then: + + 1. Let |bytesSoFar| be the number of bytes downloaded so far. + + 1. [=Assert=]: |bytesSoFar| is greater than 0 and less than or equal to |totalBytes|. + + 1. Perform |fireProgressEvent| given |bytesSoFar| and |totalBytes|. + + 1. If |bytesSoFar| equals |totalBytes|, then [=iteration/break=]. + + 1. Set |lastProgressTime| to the [=monotonic clock=]'s [=monotonic clock/unsafe current time=]. + + 1. Otherwise, if downloading has failed and cannot continue, then: + + 1. [=Queue a global task=] on the [=AI task source=] given [=this=]'s [=relevant global object=] to [=reject=] |promise| with a "{{NetworkError}}" {{DOMException}}. + + 1. Abort these steps. + + 1. [=If aborted=], then: + 1. [=Queue a global task=] on the [=AI task source=] given [=this=]'s [=relevant global object=] to perform the following steps: + + 1. [=Assert=]: |options|["{{AISummarizerCreateOptions/signal}}"]'s is [=AbortSignal/aborted=]. + + 1. [=Reject=] |promise| with |options|["{{AISummarizerCreateOptions/signal}}"]'s [=AbortSignal/abort reason=]. + + 1. Abort these steps. + + 1. If [=initializing the summarization model=] given |promise| and |options| returns false, then abort these steps. + + 1. [=Finalize summarizer creation=] given |promise| and |options|. +
+ + 1. Return |promise|. +
+ +
+ To initialize the summarization model, given a {{Promise}} |promise| and an {{AISummarizerCreateOptions}} |options|: + + 1. [=Assert=]: these steps are running [=in parallel=]. + + 1. Perform any necessarily initialization operations for the AI model backing the [=user agent=]'s summarization capabilities. + + This could include loading the model into memory, loading |options|["{{AISummarizerCreateOptions/sharedContext}}"] into the model's context window, or loading any fine-tunings necessary to support |options|["{{AISummarizerCreateCoreOptions/type}}"], |options|["{{AISummarizerCreateCoreOptions/format}}"], or |options|["{{AISummarizerCreateCoreOptions/length}}"]. + + 1. If initialization failed for any reason, then: + + 1. [=Queue a global task=] on the [=AI task source=] given |promise|'s [=relevant global object=] to [=reject=] |promise| with an "{{OperationError}}" {{DOMException}}. + + 1. Return false. + + 1. Return true. +
+ +
+ To finalize summarizer creation, given a {{Promise}} |promise| and an {{AISummarizerCreateOptions}} |options|: + + 1. [=Assert=]: these steps are running [=in parallel=]. + + 1. [=Assert=]: the [=current summarizer capability value=] for |options|["{{AISummarizerCreateCoreOptions/type}}"], |options|["{{AISummarizerCreateCoreOptions/format}}"], and |options|["{{AISummarizerCreateCoreOptions/length}}"] is "{{AICapabilityAvailability/readily}}". + + 1. [=Queue a global task=] on the [=AI task source=] given |promise|'s [=relevant global object=] to perform the following steps: + + 1. If |options|["{{AISummarizerCreateOptions/signal}}"] [=map/exists=] and is [=AbortSignal/aborted=], then: + + 1. [=Reject=] |promise| with |options|["{{AISummarizerCreateOptions/signal}}"]'s [=AbortSignal/abort reason=]. + + 1. Abort these steps. + +

This check is necessary in case any code running on the [=agent/event loop=] caused the {{AbortSignal}} to become [=AbortSignal/aborted=] before this [=task=] ran. + + 1. Let |summarizer| be a new {{AISummarizer}} object, created in |promise|'s [=relevant realm=], with + +

+ : [=AISummarizer/shared context=] + :: |options|["{{AISummarizerCreateOptions/sharedContext}}"] + + : [=AISummarizer/summary type=] + :: |options|["{{AISummarizerCreateCoreOptions/type}}"] + + : [=AISummarizer/summary format=] + :: |options|["{{AISummarizerCreateCoreOptions/format}}"] + + : [=AISummarizer/summary length=] + :: |options|["{{AISummarizerCreateCoreOptions/length}}"] +
+ + 1. If |options|["{{AISummarizerCreateOptions/signal}}"] [=map/exists=], then [=AbortSignal/add|add the following abort steps=] to |options|["{{AISummarizerCreateOptions/signal}}"]: + + 1. [=AISummarizer/Destroy=] |summarizer| with |options|["{{AISummarizerCreateOptions/signal}}"]'s [=AbortSignal/abort reason=]. + + 1. [=Resolve=] |promise| with |summarizer|. +
+ +

Capabilities

+ +TODO algorithm for the creation of {{AISummarizerCapabilities}} objects and how they get their snapshot. + +
+ +Every {{AISummarizerCapabilities}} has an available create options, a [=map=] from [=tuples=] of ({{AISummarizerType}}, {{AISummarizerFormat}}, {{AISummarizerLength}}) values to {{AICapabilityAvailability}} values, set during creation. The [=map/values=] will never be "{{AICapabilityAvailability/no}}". + +Every {{AISummarizerCapabilities}} has an available languages, a [=map=] of strings representing BCP 47 language tags to {{AICapabilityAvailability}} values, set during creation. The [=map/values=] will never be "{{AICapabilityAvailability/no}}". + +
+ The available getter steps are: + + 1. If either [=this=]'s [=AISummarizerCapabilities/available create options=] or [=this=]'s [=AISummarizerCapabilities/available languages=] [=map/is empty|are empty=], then return "{{AICapabilityAvailability/no}}". + + 1. If all of [=this=]'s [=AISummarizerCapabilities/available create options=]'s [=map/values=] or all of [=this=]'s [=AISummarizerCapabilities/available languages=]'s [=map/values=] are "{{AICapabilityAvailability/after-download}}", then return "{{AICapabilityAvailability/after-download}}". + + 1. Return "{{AICapabilityAvailability/readily}}". +
+ +
+ The createOptionsAvailable(|options|) method steps are: + + 1. Return [=this=]'s [=AISummarizerCapabilities/available create options=][(|options|["{{AISummarizerCreateCoreOptions/type}}"], |options|["{{AISummarizerCreateCoreOptions/format}}"], |options|["{{AISummarizerCreateCoreOptions/length}}"])]. +
+ +
+ The languageAvailable(|languageTag|) method steps are: + + 1. Return [=this=]'s [=AISummarizerCapabilities/available languages=][|languageTag|]. + +

Per WICG/translation-api#11 it seems we're supposed to do something more complex than just straight string comparison here, but it's not clear what.

+
+ +
+ +
+ The current summarizer capability value, given a {{AISummarizerType}} |type|, {{AISummarizerFormat}} |format|, and an {{AISummarizerLength}} |length|, is the return value of the following steps: + + 1. [=Assert=]: this algorithm is running [=in parallel=]. + + 1. If the user agent supports summarizing text into the type of summary described by |type|, in the format described by |format|, and with the length guidance given by |length| without performing any downloading operations, then return "{{AICapabilityAvailability/readily}}". + + 1. If the user agent believes it can summarize text according to |type|, |format|, and |length|, but only after performing a download (e.g., of an AI model or fine-tuning), then return "{{AICapabilityAvailability/after-download}}". + + 1. Otherwise, return "{{AICapabilityAvailability/no}}". +
+ +

Summarization

+ +Every {{AISummarizer}} has a shared context, a [=string=], set during creation. + +Every {{AISummarizer}} has a summary type, an {{AISummarizerType}}, set during creation. + +Every {{AISummarizer}} has a summary format, an {{AISummarizerFormat}}, set during creation. + +Every {{AISummarizer}} has a summary length, an {{AISummarizerLength}}, set during creation. + +The sharedContext getter steps are to return [=this=]'s [=AISummarizer/shared context=]. + +The type getter steps are to return [=this=]'s [=AISummarizer/summary type=]. + +The format getter steps are to return [=this=]'s [=AISummarizer/summary format=]. + +The length getter steps are to return [=this=]'s [=AISummarizer/summary length=]. + +The destroy() method steps are to [=AISummarizer/destroy=] [=this=] given a new "{{AbortError}}" {{DOMException}}. + +
+ To destroy an {{AISummarizer}} |summarizer|, given a JavaScript value |exception|: + + 1. TODO use |summarizer| and |exception|. +