> For the complete documentation index, see [llms.txt](https://docs.agilecase.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.agilecase.com/developer-documentation/case-scripting/on-save-scripts/examples/running-an-ai-action-on-a-document.md).

# Running an AI Action on a Document

A file upload field triggers a script in the same way any other field does, which makes uploading a document a natural point to review it. `Service.AIAction` runs a configured AI template and hands the result straight back to the script, so unlike sending an email you can act on the answer before the script finishes.

This example runs two reviews over one upload: a narrative for somebody to read, and a structured result the script can pick apart.

## The scenario

A record of processing activity is uploaded to a GDPR case. It needs a written gap review stored against the case, plus a machine-readable version whose summary is pulled out into its own field.

| Setting       | Value                                                                                   |
| ------------- | --------------------------------------------------------------------------------------- |
| Trigger field | `5610` Processing record (file upload)                                                  |
| Written       | `5611` GDPR review (narrative), `5612` GDPR review (JSON), `5613` GDPR review (summary) |
| AI templates  | `GDPRReview-Narrative`, `GDPRReview-JSON`                                               |

## The script

```javascript
[Changed.CustomField(5610).Changed]

function after_save() {
    var documentId = Service.CustomField("5610").ValueAsNumber;

    if (documentId === 0) {
        return true;
    }

    var narrative = Service.AIAction("GDPRReview-Narrative", "", documentId);
    var jsonResult = Service.AIAction("GDPRReview-JSON", "", documentId);

    if (narrative) {
        Service.UpdateCustomField("5611", narrative);
    } else {
        Service.UpdateCustomField("5611", "The GDPR review could not be completed for this document.");
    }

    if (jsonResult) {
        Service.UpdateCustomField("5612", jsonResult);

        try {
            var parsed = JSON.parse(jsonResult);
            Service.UpdateCustomField("5613", parsed.summary || "No summary was returned.");
        } catch (e) {
            Service.UpdateCustomField("5613", "The result could not be read as JSON: " + e.message);
        }
    }

    Service.AddMessage(
        "The GDPR review has finished. Refresh this page to see the results.",
        "success",
        "Review complete");

    return true;
}
```

## Pointing the template at the document

```javascript
var documentId = Service.CustomField("5610").ValueAsNumber;
Service.AIAction("GDPRReview-Narrative", "", documentId);
```

A file upload field holds the numeric ID of the document uploaded to it, and `ValueAsNumber` is how you read it. That ID is what tells the AI template which file to work on.

The arguments are `templateName`, then `input`, then `documentId`. The middle argument is text for the template to work on instead of a document, so it is passed as an empty string here. Getting these the wrong way round is the usual reason an AI action returns nothing useful.

`ValueAsNumber` returns `0` when there is nothing to read, which is also what the field reads as if the upload is cleared. Checking for zero first avoids calling the model with no document.

## Acting on the result immediately

`AIAction` is one of the few methods that does not queue. It calls the model, waits, and returns the result as a string, which is why the script can test it, parse it, and decide what to write.

That also means the save waits for the model. Two calls means two waits, so avoid making more of them than the work needs. If the same document needs several things asked of it, one template returning a structured answer is faster than several templates returning parts of one.

## Handling what comes back

Both results are checked before use. `if (narrative)` covers an empty string as well as nothing being returned, which is what you get when a document is too large or the model fails, and writing a plain sentence into the field in that case means the case shows the review did not complete rather than looking as though it was never attempted.

The JSON result is wrapped in `try` / `catch` because a model can return something that is nearly JSON. Without the `catch`, a stray character would throw, stop the script and leave nothing written. With it, the raw text is still stored in `5612` for somebody to look at, and `5613` explains what went wrong.

The `||` on the summary covers the other failure: valid JSON that simply has no `summary` in it.

{% hint style="info" %}
Storing the raw result in `5612` as well as the parsed summary is worth the field. When a result cannot be parsed, that field is the only record of what the model actually said.
{% endhint %}

## Telling the user to refresh

The script writes to fields after the page has been rendered, so the user is looking at values that are now out of date. The dialog asks them to refresh, with a title so it reads as a step to take rather than a passing confirmation.

{% hint style="warning" %}
There is no message type that reloads the page for the user. A type such as `success-reload` is read as the style `success` with an unrecognised variant, which produces an unstyled dialog and no reload. Ask the user to refresh in the wording of the message instead.
{% endhint %}

## Prompts belong in the template

The script names templates and never contains a prompt. Keeping the wording in the AI template means it can be adjusted without editing scripts, the same review can be reused across case types, and the script stays short enough to see what it does.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.agilecase.com/developer-documentation/case-scripting/on-save-scripts/examples/running-an-ai-action-on-a-document.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
