From fb78a6a213104a053844a7b1933aa17236275438 Mon Sep 17 00:00:00 2001 From: Domenic Denicola Date: Wed, 30 Oct 2024 16:29:02 +0900 Subject: [PATCH] Initial specification work This is focused on creating, downloading, and most of the capabilities, although the language tag matching is still fuzzy. --- index.bs | 457 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 450 insertions(+), 7 deletions(-) diff --git a/index.bs b/index.bs index d4f1672..5b86992 100644 --- a/index.bs +++ b/index.bs @@ -1,21 +1,464 @@ -
+
 
-Introduction {#intro}
-=====================
+TODO add back Die On: warning
+
+
+
+
+
+

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 create options availability=] given |options|["{{AISummarizerCreateCoreOptions/type}}"], |options|["{{AISummarizerCreateCoreOptions/format}}"], and |options|["{{AISummarizerCreateCoreOptions/length}}"]. + + 1. Switch on |availability|: + +

+ : null + :: + 1. [=Reject=] |promise| with an "{{UnknownError}}" {{DOMException}}. + + 1. Abort these steps. + + : "{{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 necessary 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 create options availability=] 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

+ +
+ The capabilities() 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. Let |promise| be [=a new promise=] created in [=this=]'s [=relevant realm=]. + + 1. [=In parallel=]: + + 1. Let |availableCreateOptions| be a new [=map=] from [=tuples=] of ({{AISummarizerType}}, {{AISummarizerFormat}}, {{AISummarizerLength}}) values to {{AICapabilityAvailability}} values, initially empty. + + 1. [=list/For each=] |type| of {{AISummarizerType}}'s [=enumeration values=]: + + 1. [=list/For each=] |format| of {{AISummarizerFormat}}'s [=enumeration values=]: + + 1. [=list/For each=] |length| of {{AISummarizerLength}}'s [=enumeration values=]: + + 1. Set |availableCreateOptions|[(|type|, |format|, |length|)] to the [=current summarizer create options availability=] given |type|, |format|, and |length|. + + 1. Let |availableLanguages| be the [=current summarizer language availability map=]. + + 1. If |availableLanguages| is null, or |availableCreateOptions|'s [=map/values=] [=list/contains=] null, then [=queue a global task=] on the [=AI task source=] given [=this=] to perform the following steps: + + 1. [=Reject=] |promise| with an "{{UnknownError}}" {{DOMException}}. + + 1. Otherwise, [=queue a global task=] on the [=AI task source=] given [=this=] to perform the following steps: + + 1. Let |capabilitiesObject| be a new {{AISummarizerCapabilities}} object, created in [=this=]'s [=relevant realm=], with + +
+ : [=AISummarizerCapabilities/available create options=] + :: |availableCreateOptions| + : [=AISummarizerCapabilities/available languages=] + :: |availableLanguages| +
+ + 1. [=Resolve=] |promise| with |capabilitiesObject|. +
+ +
+ +Every {{AISummarizerCapabilities}} has an available create options, a [=map=] from [=tuples=] of ({{AISummarizerType}}, {{AISummarizerFormat}}, {{AISummarizerLength}}) values to {{AICapabilityAvailability}} values, set during creation. + +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 [=this=]'s [=AISummarizerCapabilities/available languages=] [=map/is empty|are empty=], then return "{{AICapabilityAvailability/no}}". + + 1. If [=this=]'s all of [=this=]'s [=AISummarizerCapabilities/available create options=] [=map/values=] are "{{AICapabilityAvailability/no}}", 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|], or "{{AICapabilityAvailability/no}}" if no such [=map/entry=] [=map/exists=]. + +

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

+
+ +
+ +
+ The current summarizer create options availability, given a {{AISummarizerType}} |type|, {{AISummarizerFormat}} |format|, and an {{AISummarizerLength}} |length|, is given by the following steps. They return an {{AICapabilityAvailability}} value or null. + + 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. If there is some error attempting to determine whether the user agent supports summarizing text, which the user agent believes to be transient (such that re-querying the [=current summarizer create options availability=] could stop producing such an error), then return null. + + 1. Otherwise, return "{{AICapabilityAvailability/no}}". +
+ +
+ The current summarizer language availability map is given by the following steps. They return a [=map=] from strings representing BCP 47 language tags to {{AICapabilityAvailability}} values, or null. [[!RFC5646]] + + 1. [=Assert=]: this algorithm is running [=in parallel=]. + + 1. If there is some error attempting to determine whether the user agent supports summarizing text, which the user agent believes to be transient (such that re-querying the [=current summarizer create options availability=] could stop producing such an error), then return null. + + 1. Let |availableLanguages| be an empty [=map=]. + + 1. [=list/For each=] human language for which the user agent supports summarizing text written in that language, without performing any downloading operations: + + 1. Let |languageTag| be that language, represented as a BCP 47 language tag string. Describe how to handle subtags. + + 1. Set |availableLanguages|[|languageTag|] to "{{AICapabilityAvailability/readily}}". + + 1. [=list/For each=] human language for which the user agent believes it can summarize text written in that language, but only after performing a download (e.g., of an AI model or fine-tuning): + + 1. Let |languageTag| be that language, represented as a BCP 47 language tag string. Describe how to handle subtags. + + 1. Set |availableLanguages|[|languageTag|] to "{{AICapabilityAvailability/after-download}}". + + 1. Return |availableLanguages|. +
+ +

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|. +