> ## Documentation Index
> Fetch the complete documentation index at: https://docs.abbyy.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Call ABBYY Phoenix Plus from a Custom activity

> Use the ABBYY-managed LLM connection from a Custom activity script in a Process skill: create a chat session, attach OCR data and page images, send a message, and read a structured response.

A Custom activity script can call **ABBYY Phoenix Plus**, the LLM endpoint ABBYY supplies and operates, without writing any HTTP, authentication, or provider-specific code. The activity runs as a step in a Process skill, so the model can read the current transaction and its result can be used by later steps.

<Note>
  ABBYY Phoenix Plus is available on ABBYY Vantage Cloud and requires a contracted entitlement. To enable it for your tenant, contact your ABBYY account team. For an overview, see [LLMs in ABBYY Vantage](/vantage/documentation/llms/llms).
</Note>

## Before you begin

* The **Phoenix Plus entitlement** is enabled for your tenant, and the **ABBYY Phoenix Model** connection appears under **ADMIN → Configuration → Connections**.
* You have a Process skill with a **Custom** activity. For the steps, see [Custom activity](/vantage/documentation/skill-designer/process/custom-activity/custom-activity).
* On the activity's **Available Files** tab, select the export formats your script needs. Most scripts need **OcrJson**. To send page images, you also need a **JPEG** export, which must be produced before the activity runs.

## Create a chat session

Call `Context.CreateLlmChatSession()` with no arguments to open a session against your tenant's ABBYY-managed connection. Passing a connection name opens a session against one of your own tenant connections instead.

```javascript theme={null}
var session = Context.CreateLlmChatSession();              // ABBYY-managed connection
var byo     = Context.CreateLlmChatSession('My OpenAI');   // your own tenant connection, by name
```

The rest of this page describes the managed connection. A session opened against your own connection behaves the same way, but bills through your provider and is not subject to the entitlement.

The model behind the session is selected and maintained by ABBYY. The session refuses any attempt to change it, so there is no way to request a specific model or version through the managed connection.

### Session properties

| Name                 | Type    | Access     | Description                                                                                                                                          |
| :------------------- | :------ | :--------- | :--------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Model**            | string  | Read-write | The model to request. Defaults to the connection's own model. The ABBYY-managed connection accepts only its own, so setting this has no effect there |
| **SystemPrompt**     | string  | Read-write | Instructions sent as the first message of every request in the session                                                                               |
| **Temperature**      | number  | Read-write | Sampling temperature. Omitted from the request when not set                                                                                          |
| **TopP**             | number  | Read-write | Nucleus sampling. Omitted from the request when not set                                                                                              |
| **MaxTokens**        | integer | Read-write | Upper bound on tokens generated in a response. See [Set MaxTokens deliberately](#set-maxtokens-deliberately)                                         |
| **History**          | list    | Read-only  | The messages exchanged so far, as `{ Role, Content }`. Roles are `user` and `assistant`; images appear as `[image]`                                  |
| **Timeout**          | integer | Read-write | Request timeout **in minutes**, clamped so it can never exceed the script execution timeout                                                          |
| **LastUsage**        | object  | Read-only  | Token usage for the most recent call                                                                                                                 |
| **TotalUsage**       | object  | Read-only  | Token usage accumulated across the session                                                                                                           |
| **LastFinishReason** | string  | Read-only  | Why the model stopped generating, for example `stop` or `length`                                                                                     |

`LastUsage` and `TotalUsage` are objects carrying `PromptTokens`, `CompletionTokens`, and `TotalTokens`.

```javascript theme={null}
var usage = session.TotalUsage;
Context.LogMessage("Tokens: prompt " + usage.PromptTokens
  + ", completion " + usage.CompletionTokens
  + ", total " + usage.TotalTokens);
```

### Reset a session

`Reset()` clears the conversation history and any pending attachments, so the next message starts fresh. Settings such as `SystemPrompt` and `Temperature` are kept, and so is accumulated usage.

## Attach content to a message

Attach the transaction data you want the model to see, then send.

| Method                                 | Description                                                                                                                                                                                               |
| :------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **AttachJson(label, json)**            | Attaches JSON as a labelled block. Use this for OCR JSON and extracted field data.                                                                                                                        |
| **AttachText(label, text)**            | Attaches plain text as a labelled block.                                                                                                                                                                  |
| **AttachDocument(document)**           | Attaches a document's extracted data as labelled JSON.                                                                                                                                                    |
| **AttachExtractedData(extractedData)** | Attaches extracted data as labelled JSON.                                                                                                                                                                 |
| **AttachPageImage(page)**              | Attaches a page as an image, for vision models.                                                                                                                                                           |
| **AttachImage(export)**                | Attaches a single page image from a [DocumentExportResult](/vantage/documentation/skill-designer/process/custom-activity/document-export-result), with no label and no media type. Call it once per page. |

Attachments queue against the **next** user message rather than being sent immediately.

You can also build conversation history without sending anything, which is useful for few-shot priming:

| Method                           | Description                                           |
| :------------------------------- | :---------------------------------------------------- |
| **AddUserMessage(message)**      | Appends a user message to the history without sending |
| **AddAssistantMessage(message)** | Appends an assistant message to the history           |

<Note>
  `AttachFile`, `AttachBinary`, and a generic `Attach` do not exist. Use the methods above.
</Note>

### Attach page images in page order

JPEG exports are ordered by `Properties["PageIndex"]`, so attaching them in the order they appear keeps attachment order aligned with the page numbers the model reports.

```javascript theme={null}
var exports = Context.Transaction.Documents[0].Exports;
exports
  .filter(result => result.ExportFormat === ExportFormat.Jpeg)
  .forEach(result => {
    session.AttachImage(result);   // one call per page
  });
```

## Send the message and read the response

| Method               | Returns                                                                                                                         |
| :------------------- | :------------------------------------------------------------------------------------------------------------------------------ |
| **SendJson(prompt)** | A parsed object. Sets JSON response mode, so nested arrays and numbers are directly usable, for example `result.items[1].total` |
| **Send(prompt)**     | The assistant's reply as text                                                                                                   |
| **Send()**           | Sends the pending history and attachments as they stand                                                                         |

```javascript theme={null}
var result = session.SendJson(prompt);
```

`SendJson` returns an object. If it returns a string, the model did not produce parseable JSON, and the prompt needs tightening rather than the call retrying.

### Check how the response finished

Read `LastFinishReason` before trusting a response. A value of `"length"` means the response was cut off at the token limit. That is not an error and nothing else signals it, so a script that ignores it will parse a partial result as though it were complete. The fix is to raise `MaxTokens` or reduce the field set.

### Validate the shape before writing values

A response can arrive complete and still be structurally wrong: the right scalar fields but none of the repeating content a skill defines. Check that the reply carries the tables and repeating fields you asked for, and re-ask if it does not, rather than writing the reply into the document unchecked. Reset the session between attempts, and set `SystemPrompt` again afterwards.

## What the managed connection requires

**Document context.** The managed connection is refused for an execution that has no document pages. This keeps the shared platform credential from being used as a general-purpose LLM gateway. A Custom activity running over a transaction that contains documents satisfies this; a script that opens a session outside that context does not.

**Metering.** Calls through the managed connection are metered against your ABBYY entitlement. Calls through a connection you configured yourself bill through your own provider instead. Consider the volume before pointing a bulk reprocessing job at the managed connection.

## Message limits

Both limits are enforced, and a message that exceeds either is refused outright. Size the message in the script before sending rather than letting the call fail.

| Limit                      | Behavior                                                                                                                                                           |
| :------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Prompt budget**          | A maximum number of characters per message, counting **everything**: system prompt, user message, attached JSON, and base64 image content                          |
| **Attachments**            | 10 per message. The eleventh is refused                                                                                                                            |
| **Requests per execution** | A capped number of LLM calls per script execution, scaled by the transaction's page count. See [Errors your script cannot catch](#errors-your-script-cannot-catch) |

The prompt ceiling is not a single fixed number. It is configured per environment and **scales with the number of pages being processed**, up to a maximum. On ABBYY Vantage Cloud today, each page allows 500,000 characters, counted over at most three pages, to a ceiling of 1,500,000:

| Pages in the transaction | Characters allowed per message |
| :----------------------- | :----------------------------- |
| 1                        | 500,000                        |
| 2                        | 1,000,000                      |
| 3 or more                | 1,500,000                      |

Because the ceiling is environment-configured and can change, treat these figures as informational rather than as a contract. Size the message at runtime and degrade when it does not fit, rather than assuming a fixed budget.

Exceeding it produces a message of the form:

```text theme={null}
The LLM prompt exceeds the maximum allowed size of N characters
```

### Errors that retrying cannot fix

A send error mentioning `exceeds the maximum allowed size`, `maximum number of attachments`, `context length`, or `too large` is terminal. The message is too big or carries too much, and the same call will fail again. The remedies are to lower the JPEG export resolution, send fewer pages, or drop the images and run on the OCR JSON alone.

### Errors your script cannot catch

Most failures are catchable in the script with `try`/`catch`: connection problems, a failed request, a reply that is not valid JSON, and a prompt that exceeds the size limit. Handle those and carry on.

**Exceeding the request cap is different.** There is a limit on how many LLM calls one script execution may make, scaled by the transaction's page count. Exceeding it stops the script with a constraint error that a `try`/`catch` cannot swallow, in the same way the existing HTTP request cap behaves.

This matters if you retry. A validation-and-re-ask loop consumes a call each time it runs, and a loop with no upper bound of its own will eventually hit a limit it cannot handle. Bound your retries.

## Sending page images

Page images work, within a practical ceiling set by the prompt budget.

Two measured examples, against the ceilings above:

* A single 1584x1000 page image costs roughly **311,776 base64 characters** and about 2,015 prompt tokens. On a one-page transaction that fits inside the 500,000 character allowance, leaving room for the OCR JSON and the prompt.
* A two-page A4 form scanned at 300 dpi produces roughly **1,326,136 characters** of image. A two-page transaction allows 1,000,000, so it is refused. Sending it as images would need each page at about a quarter of its 300 dpi size.

The arithmetic changes with the page count and with any change to the environment ceiling, which is why the message has to be measured before it is sent rather than assumed to fit.

Design for the budget rather than against it:

* Send the OCR JSON as the primary payload, and add page images only for what the text layer cannot carry, such as stamps, signatures, and photographs.
* Measure the message before sending. If the images do not fit, drop them and rebuild the prompt to match, so the document still processes on the OCR JSON alone.
* Hold back room for the OCR JSON while sizing images, so a large image cannot crowd out the payload your coordinates are copied from.
* Reduce image resolution before export where the model only needs to see layout or a stamp rather than fine detail.

## Locations and bounding boxes

<Warning>
  Do not ask the model for coordinates. The model behind the managed connection cannot ground a bounding box on a page image, and a response that looks like coordinates will not be a measurement.
</Warning>

Asked to return coordinates from a page image, the model returns values on a ten-unit lattice: every number a multiple of ten, uniform heights, and two different fields sharing an identical rectangle. That is a composed layout rather than a measured one, and no coordinate convention setting corrects it.

Take geometry from the Vantage OCR layer instead. The OCR JSON export carries measured positions for both text and non-text content, including `layout.pages[].pictures[]` and `barcodes[]`, so a photograph, logo, or barcode can be located just as reliably as a word.

A robust script:

* **Copies coordinates, never estimates them.** Every rectangle comes from a position value in the OCR JSON, validated against the OCR page size and scaled to the Vantage page image when the two differ.
* **Enforces provenance.** Each region the model returns is measured against the OCR geometry before it is accepted. A region that cannot be traced back to the OCR layer is refused, while the extracted value itself is kept.
* **Requires page attribution.** On a multi-page document, a region that arrives without a page number is refused rather than defaulted to page 1.

Use the model for what it is good at, which is reading and classifying. Let ABBYY OCR supply the geometry.

## Set MaxTokens deliberately

Leaving `MaxTokens` at the provider default risks silent truncation on dense documents, visible only through `LastFinishReason`. Set it explicitly so the cap is yours and legible.

For scale, extracting a nine-column table cell by cell across three pages measures roughly 28,000 completion tokens.

## Allow enough time

Latency scales with the **output** tokens generated, not with the size of the input. Phoenix Plus generates roughly 100 completion tokens per second, so a response of 28,000 tokens needs several minutes.

`Timeout` is set in minutes and is capped at the script execution timeout. A two-minute timeout on a document needing 28,000 output tokens will fail at about 121 seconds, having generated only a fraction of the response.

## Reduce the OCR payload before sending

A raw OCR JSON export is dominated by the character layer, typically 97 to 98 per cent of its size. Pruning it leaves the text and word positions the model actually needs, at a fraction of the prompt budget.

In one measured case a 43,437 character export pruned to 4,842 characters with word positions retained. In another, a 317,296 character export pruned to 32,712 characters.

When estimating cost, OCR JSON runs at roughly **2 characters per token**. JSON punctuation tokenises badly, so ratios derived from prose do not apply.

## Known limitations

<Warning>
  To attach a page as an image, use **`AttachPageImage(page)`**. Passing `Page.Image` to `AttachImage` throws `Value cannot be null. (Parameter 'fileLink')`, on split and unsplit documents alike, because `AttachImage` expects an exported file rather than a page's image property. A JPEG export from `Document.Exports` works with `AttachImage`.
</Warning>

## Example

This script sends pruned OCR JSON and asks for structured field values, taking all geometry from the OCR layer.

```javascript theme={null}
var document = Context.Transaction.Documents[0];

// Read the OCR JSON export configured on the activity's Available Files tab.
var ocrExport = document.Exports.GetByFormat(ExportFormat.OcrJson);
var prunedOcr = pruneOcr(ocrExport.ToJson());   // your own pruning function

var session = Context.CreateLlmChatSession();
session.SystemPrompt =
  "You read documents. Return only the requested fields as JSON. " +
  "Never return coordinates. Quote values exactly as they appear in the OCR text.";
session.MaxTokens = 32000;
session.Timeout = 8;            // minutes
session.Temperature = 0;

session.AttachJson("OCR JSON", prunedOcr);

var prompt = "Return vendor name, invoice number, invoice date and total as JSON.";
var result = session.SendJson(prompt);

if (session.LastFinishReason === "length") {
  Context.LogMessage("Response truncated. Raise MaxTokens or reduce the field set.");
}

var usage = session.TotalUsage;
Context.LogMessage("Tokens: prompt " + usage.PromptTokens
  + ", completion " + usage.CompletionTokens
  + ", total " + usage.TotalTokens);
```

## Related topics

* [LLMs in ABBYY Vantage](/vantage/documentation/llms/llms)
* [Custom activity](/vantage/documentation/skill-designer/process/custom-activity/custom-activity)
* [Context](/vantage/documentation/skill-designer/process/custom-activity/context)
* [DocumentExportResult](/vantage/documentation/skill-designer/process/custom-activity/document-export-result)
* [OCR JSON schema](/vantage/documentation/skill-designer/process/custom-activity/ocr-skill-schema-json)
