This API is offered in beta, and is not subject to any SLA or deprecation policy. Changes may be made to this API that break backward compatibility.

![](https://developers.google.com/static/ml-kit/images/genai/card-proofreading.svg)

With ML Kit's GenAI Proofreading API, you can help users check their grammar and
spelling on short pieces of text.

## Key capabilities

- Proofread text inputted through keyboard or voice
- Requests will return at least one suggestion. If multiple suggestions are returned, results will be sorted by descending confidence.

## Example results

|---|---|---|
| **Input** | **Input Type** | **Output** |
| this is a short msg | Keyboard | This is a short message |
| The project is nearly compleete butt needs too be reviewed | Keyboard | The project is nearly complete but needs to be reviewed |
| Please meat me at the bear, | Voice | Please meet me at the bar. |

## Getting Started

> [!NOTE]
> **Note:** This API requires Android API level 26 or higher.

To get started with the GenAI Proofreading API, add this dependency to your
project's build file.

```kotlin
implementation("com.google.mlkit:genai-proofreading:1.0.0-beta1")
```

Then, configure and obtain a [`Proofreader`](https://developers.google.com/android/reference/com/google/mlkit/genai/proofreading/Proofreader.md.txt) client with specific language
and input type settings. Check and ensure the necessary on-device model features
are available (triggering a download if needed). Submit the text you want to
analyze in a [`ProofreadingRequest`](https://developers.google.com/android/reference/com/google/mlkit/genai/proofreading/ProofreadingRequest.md.txt), execute the proofreading inference, and
finally process the returned suggestions for correcting the text.

### Kotlin

```kotlin
val textToProofread = "The praject is compleet but needs too be reviewd"

// Define task with required input and output format
val options = ProofreaderOptions.builder(context)
    // InputType can be KEYBOARD or VOICE. VOICE indicates that
    // the user generated text based on audio input.
    .setInputType(ProofreaderOptions.InputType.KEYBOARD)
    // Refer to ProofreaderOptions.Language for available
    // languages
    .setLanguage(ProofreaderOptions.Language.ENGLISH)
    .build()
val proofreader = Proofreading.getClient(options)

suspend fun prepareAndStartProofread() {
    // Check feature availability, status will be one of the
    // following: UNAVAILABLE, DOWNLOADABLE, DOWNLOADING, AVAILABLE
    val featureStatus = proofreader.checkFeatureStatus().await()

    if (featureStatus == FeatureStatus.DOWNLOADABLE) {
        // Download feature if necessary.
        // If downloadFeature is not called, the first inference
        // request will also trigger the feature to be downloaded
        // if it's not already downloaded.
        proofreader.downloadFeature(object : DownloadCallback {
            override fun onDownloadStarted(bytesToDownload: Long) { }

            override fun onDownloadFailed(e: GenAiException) { }

            override fun onDownloadProgress(
                totalBytesDownloaded: Long
            ) {}

            override fun onDownloadCompleted() {
                startProofreadingRequest(textToProofread, proofreader)
            }
        })
    } else if (featureStatus == FeatureStatus.DOWNLOADING) {
        // Inference request will automatically run once feature is
        // downloaded.
        // If Gemini Nano is already downloaded on the device, the
        // feature-specific LoRA adapter model will be downloaded
        // very quickly. However, if Gemini Nano is not already
        // downloaded, the download process may take longer.
        startProofreadingRequest(textToProofread, proofreader)
    } else if (featureStatus == FeatureStatus.AVAILABLE) {
        startProofreadingRequest(textToProofread, proofreader)
    }
}

suspend fun startProofreadingRequest(
    text: String, proofreader: Proofreader
) {
    // Create task request
    val proofreadingRequest =
        ProofreadingRequest.builder(text).build()

    // Start proofreading request with non-streaming response
    // More than 1 result may be returned. If multiple suggestions
    // are returned, results will be sorted by descending confidence.
    val proofreadingResults =
        proofreader.runInference(proofreadingRequest).await().results

    // You can also start a streaming request
    // proofreader.runInference(proofreadingRequest) { newText ->
    //     // show new text in UI
    // }
}

// Be sure to release the resource when no longer needed
// For example, on viewModel.onCleared() or activity.onDestroy()
proofreader.close()
```

### Java

```java
String textToProofread = "The praject is compleet but needs too be reviewd";

// Define task with required input and output format
ProofreaderOptions proofreaderOptions = 
    ProofreaderOptions
        .builder(context)
        // InputType can be KEYBOARD or VOICE. VOICE indicates that the
        // user generated text based on audio input.
        .setInputType(ProofreaderOptions.InputType.KEYBOARD)
        // Refer to ProofreaderOptions.Language for available languages
        .setLanguage(ProofreaderOptions.Language.ENGLISH)
        .build();
Proofreader proofreader = Proofreading.getClient(proofreaderOptions);

void prepareAndStartProofread(Context context) throws ExecutionException,
        InterruptedException {
    // Check feature availability, status will be one of the following:
    // UNAVAILABLE, DOWNLOADABLE, DOWNLOADING, AVAILABLE
    try {
        int featureStatus = proofreader.checkFeatureStatus().get();
        if (featureStatus == FeatureStatus.DOWNLOADABLE) {
            // Download feature if necessary.
            // If downloadFeature is not called, the first inference request
            // will also trigger the feature to be downloaded if it's not
            // already downloaded.
            proofreader.downloadFeature(new DownloadCallback() {
                @Override
                public void onDownloadCompleted() {
                    startProofreadingRequest(textToProofread, proofreader);
                }

                @Override
                public void onDownloadFailed(GenAiException e) {
                }

                @Override
                public void onDownloadProgress(long totalBytesDownloaded) {
                }

                @Override
                public void onDownloadStarted(long bytesDownloaded) {
                }
            });
        } else if (featureStatus == FeatureStatus.DOWNLOADING) {
            // Inference request will automatically run once feature is
            // downloaded.
            // If Gemini Nano is already downloaded on the device, the
            // feature-specific LoRA adapter model will be downloaded
            // very quickly. However, if Gemini Nano is not already
            // downloaded, the download process may take longer.
            startProofreadingRequest(textToProofread, proofreader);
        } else if (featureStatus == FeatureStatus.AVAILABLE) {
            startProofreadingRequest(textToProofread, proofreader);
        }
    } catch (ExecutionException | InterruptedException e) {
        e.printStackTrace();
    }
}

void startProofreadingRequest(String text, Proofreader proofreader) {
    // Create task request
    ProofreadingRequest proofreadingRequest = ProofreadingRequest
            .builder(text).build();

    try {
        // Start proofreading request with non-streaming response
        // More than 1 result may be returned. If multiple suggestions are
        // returned, results will be sorted by descending confidence.
        proofreader.runInference(proofreadingRequest).get().getResults();

        // You can also start a streaming request
        // proofreader.runInference(proofreadingRequest, newText -> {
        //     // Show new text in UI
        // });
    } catch (ExecutionException | InterruptedException e) {
        e.printStackTrace();
    }
}

// Be sure to release the resource when no longer needed
// For example, on viewModel.onCleared() or activity.onDestroy()
proofreader.close();
```

## How the model handles different input types

When the model has more information about how the user inputted text (keyboard
or voice), it can better predict the type of errors that may be present.
Keyboard-inputted text is more prone to misspelling with nearby keys, while
voice-inputted text is more prone to misspelling of words with the same
pronunciation.

## Supported features and limitations

Proofreading is supported for the following languages: English,
Japanese, French, German, Italian, Spanish, and Korean, and they are defined in
[`ProofreaderOptions.Language`](https://developers.google.com/android/reference/com/google/mlkit/genai/proofreading/ProofreaderOptions.Language.md.txt). Input should be less than 256 tokens.

Availability of the specific feature configuration (specified by
`ProofreaderOptions`) may vary depending on the particular device's
configuration and the models that have been downloaded to the device.

The most reliable way for developers to ensure that the intended API feature is
supported on a device with the requested `ProofreaderOptions` is to call the
[`checkFeatureStatus()`](https://developers.google.com/android/reference/com/google/mlkit/genai/proofreading/Proofreader.md.txt#getBaseModelName()) method. This method provides the definitive status
of feature availability on the device at runtime.

> [!NOTE]
> **Note:** Variations in hardware across different device types may lead to differences in Gemini Nano base model versions and, consequently, in ML Kit GenAI API outputs. LoRA adapters are applied to mitigate the differences.

## Common setup issues

> [!NOTE]
> **Note:** This API is not supported on devices with an [unlocked bootloader](https://source.android.com/docs/core/architecture/bootloader/locking_unlocking).

ML Kit GenAI APIs rely on the Android AICore app to access Gemini Nano. When a device is just setup (including reset), or the AICore app is just reset (e.g. clear data, uninstalled then reinstalled), the AICore app may not have enough time to finish initialization (including downloading latest configurations from server). As a result, the ML Kit GenAI APIs may not function as expected. Here are the common setup error messages you may see and how to handle them:

|---|---|
| **Example error message** | **How to handle** |
| AICore failed with error type 4-CONNECTION_ERROR and error code 601-BINDING_FAILURE: AICore service failed to bind. | This could happen when you install the app using ML Kit GenAI APIs immediately after device setup or when AICore is uninstalled after your app is installed. Updating AICore app then reinstalling your app should fix it. |
| AICore failed with error type 3-PREPARATION_ERROR and error code 606-FEATURE_NOT_FOUND: Feature ... is not available. | This could happen when AICore hasn't finished downloading the latest configurations. When the device is connected to the internet, it usually takes a few minutes to a few hours to update. Restarting the device can speed up the update. <br /> Note that if the device's bootloader is unlocked, you'll also see this error---this API does not support devices with unlocked bootloaders. |
| AICore failed with error type 1-DOWNLOAD_ERROR and error code 0-UNKNOWN: Feature ... failed with failure status 0 and error esz: UNAVAILABLE: Unable to resolve host ... | Keep network connection, wait for a few minutes and retry. |

> [!NOTE]
> **Note:** While various resetting is common to developers, it is much less frequent to users. Also, all these setup failure cases can be caught by the `checkFeatureStatus()` or `checkStatus()` APIs. Make sure to call `checkFeatureStatus()` or `checkStatus()` first before showing any related UI, so your app users won't see these errors in any cases.

<br />

## Sample code

- [ML Kit GenAI Samples](https://github.com/googlesamples/mlkit/tree/master/android/genai)