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

# カスタム アクティビティから ABBYY Phoenix Plus を呼び出す

> Process skill 内のカスタム アクティビティ スクリプトから、ABBYY が管理する LLM 接続を利用します。チャット セッションの作成、OCR データとページ画像のアタッチ、メッセージの送信、構造化されたレスポンスの読み取りを行います。

カスタム アクティビティ スクリプトからは、HTTP や認証、プロバイダー固有のコードを記述することなく、ABBYY が提供・運用する LLM エンドポイント **ABBYY Phoenix Plus** を呼び出せます。このアクティビティは Process skill の Step として実行されるため、モデルは現在のトランザクションを読み取ることができ、その結果を後続の Step で利用できます。

<Note>
  ABBYY Phoenix Plus は ABBYY Vantage Cloud で利用でき、契約に基づくエンタイトルメントが必要です。お使いのテナントで有効化するには、ABBYY のアカウント チームにお問い合わせください。概要については、[ABBYY Vantage における LLM](/ja/vantage/documentation/llms/llms) を参照してください。
</Note>

<h2 id="before-you-begin">
  開始する前に
</h2>

* テナントで **Phoenix Plus entitlement** が有効になっており、**ADMIN → Configuration → Connections** に **ABBYY Phoenix Model** の接続が表示されていること。
* **カスタム アクティビティ**を含む Process skill があること。手順については、[カスタム アクティビティ](/ja/vantage/documentation/skill-designer/process/custom-activity/custom-activity)を参照してください。
* アクティビティの **Available Files** タブで、スクリプトに必要なエクスポート形式を選択します。ほとんどのスクリプトでは **OcrJson** が必要です。ページ画像を送信する場合は **JPEG** のエクスポートも必要となり、アクティビティの実行前に生成しておく必要があります。

<h2 id="create-a-chat-session">
  チャットセッションの作成
</h2>

引数なしで `Context.CreateLlmChatSession()` を呼び出すと、tenant の ABBYY 管理接続に対してセッションが開かれます。接続名を指定した場合は、代わりに tenant 自身の接続のいずれかに対してセッションが開かれます。

```javascript theme={null}
var session = Context.CreateLlmChatSession();              // ABBYY が管理する接続
var byo     = Context.CreateLlmChatSession('My OpenAI');   // 自分のテナントの接続を名前で指定
```

このページの以降の内容では、マネージド接続について説明します。ご自身の接続に対して開いたセッションも動作は同じですが、課金はご利用のプロバイダー経由となり、利用権の対象外となります。

セッションで使用されるモデルは、ABBYY が選定し保守します。セッションはモデルの変更をいっさい受け付けないため、マネージド接続を通じて特定のモデルやバージョンを指定することはできません。

<h3 id="session-properties">
  セッションのプロパティ
</h3>

| Name                 | Type    | アクセス   | 説明                                                                                           |
| :------------------- | :------ | :----- | :------------------------------------------------------------------------------------------- |
| **Model**            | string  | 読み書き   | リクエスト先のモデル。既定では接続自身のモデルが使用されます。ABBYY 管理の接続は自身のモデルのみを受け付けるため、この設定は無効です                        |
| **SystemPrompt**     | string  | 読み書き   | セッション内の各リクエストの先頭メッセージとして送信される指示                                                              |
| **Temperature**      | number  | 読み書き   | サンプリング温度。未設定の場合はリクエストから省略されます                                                                |
| **TopP**             | number  | 読み書き   | Nucleus サンプリング。未設定の場合はリクエストから省略されます                                                          |
| **MaxTokens**        | integer | 読み書き   | レスポンスで生成されるトークン数の上限。[MaxTokens は意図的に設定する](#set-maxtokens-deliberately)を参照してください              |
| **History**          | list    | 読み取り専用 | これまでにやり取りしたメッセージ (`{ Role, Content }` 形式) 。ロールは `user` と `assistant` で、画像は `[image]` と表示されます |
| **Timeout**          | integer | 読み書き   | リクエストのタイムアウト (**分単位**) 。スクリプト実行タイムアウトを超えない値に制限されます                                           |
| **LastUsage**        | object  | 読み取り専用 | 直近の呼び出しのトークン使用量                                                                              |
| **TotalUsage**       | object  | 読み取り専用 | セッション全体で累積したトークン使用量                                                                          |
| **LastFinishReason** | string  | 読み取り専用 | モデルが生成を停止した理由 (例: `stop`、`length`)                                                           |

`LastUsage` と `TotalUsage` は、`PromptTokens`、`CompletionTokens`、`TotalTokens` を保持するオブジェクトです。

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

<h3 id="reset-a-session">
  セッションのリセット
</h3>

`Reset()` は会話履歴と保留中の添付ファイルをすべてクリアするため、次のメッセージは新しい状態から開始されます。`SystemPrompt` や `Temperature` などの設定は保持され、累積された使用量もそのまま維持されます。

<h2 id="attach-content-to-a-message">
  メッセージにコンテンツをアタッチする
</h2>

モデルに参照させたいトランザクションデータをアタッチしてから送信します。

| メソッド                                   | 説明                                                                                                                                                                     |
| :------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **AttachJson(label, json)**            | JSON をラベル付きブロックとしてアタッチします。Optical Character Recognition (OCR) の JSON や抽出された field データには、このメソッドを使用します。                                                                  |
| **AttachText(label, text)**            | プレーンテキストをラベル付きブロックとしてアタッチします。                                                                                                                                          |
| **AttachDocument(document)**           | Document の抽出データをラベル付き JSON としてアタッチします。                                                                                                                                 |
| **AttachExtractedData(extractedData)** | 抽出データをラベル付き JSON としてアタッチします。                                                                                                                                           |
| **AttachPageImage(page)**              | ビジョンモデル向けに、ページを画像としてアタッチします。                                                                                                                                           |
| **AttachImage(export)**                | [DocumentExportResult](/ja/vantage/documentation/skill-designer/process/custom-activity/document-export-result) から単一のページ画像を、ラベルとメディアタイプなしでアタッチします。ページごとに 1 回呼び出してください。 |

アタッチしたコンテンツはすぐに送信されるのではなく、**次の**ユーザーメッセージに合わせてキューに入れられます。

何も送信せずに会話履歴を構築することもできます。これは few-shot プライミングに役立ちます。

| メソッド                             | 説明                      |
| :------------------------------- | :---------------------- |
| **AddUserMessage(message)**      | 送信せずにユーザーメッセージを履歴に追加します |
| **AddAssistantMessage(message)** | アシスタントメッセージを履歴に追加します    |

<Note>
  `AttachFile`、`AttachBinary`、および汎用の `Attach` は存在しません。上記のメソッドを使用してください。
</Note>

<h3 id="attach-page-images-in-page-order">
  ページ順序でページ画像をアタッチする
</h3>

JPEG のエクスポートは `Properties["PageIndex"]` の順に並んでいるため、出現順にアタッチすれば、添付ファイルの順序を Model が返すページ番号と一致させたまま維持できます。

```javascript theme={null}
var exports = Context.Transaction.Documents[0].Exports;
exports
  .filter(result => result.ExportFormat === ExportFormat.Jpeg)
  .forEach(result => {
    session.AttachImage(result);   // 1ページにつき1回呼び出す
  });
```

<h2 id="send-the-message-and-read-the-response">
  メッセージの送信とレスポンスの読み取り
</h2>

| メソッド                 | 戻り値                                                                                   |
| :------------------- | :------------------------------------------------------------------------------------ |
| **SendJson(prompt)** | 解析済みのオブジェクト。JSON レスポンスモードに設定されるため、ネストされた配列や数値をそのまま利用できます (例: `result.items[1].total`) |
| **Send(prompt)**     | アシスタントの返信 (テキスト形式)                                                                    |
| **Send()**           | 保留中の履歴と添付ファイルをそのままの状態で送信します                                                           |

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

`SendJson` はオブジェクトを返します。string が返された場合は、Model が解析可能な JSON を生成できなかったことを意味するため、呼び出しを再試行するのではなく prompt をより厳密にする必要があります。

<h3 id="check-how-the-response-finished">
  レスポンスがどのように終了したかを確認する
</h3>

レスポンスを信頼する前に、必ず `LastFinishReason` を確認してください。値が `"length"` の場合、レスポンスはトークン上限で打ち切られています。これはエラーではなく、他に知らせる手段もないため、この値を無視する script は、不完全な結果を完全なものとして解析してしまいます。対処方法は、`MaxTokens` を引き上げるか、field セットを絞り込むことです。

<h3 id="validate-the-shape-before-writing-values">
  値を書き込む前に構造を検証する
</h3>

レスポンスが完全な形で返ってきていても、構造が正しいとは限りません。スカラーの field は正しいのに、Skill が定義する繰り返し部分がまったく含まれていない、といったケースです。要求したテーブルや repeating fields がレスポンスに含まれているかを確認し、含まれていない場合は未確認のまま Document に書き込まず、再度問い合わせてください。試行の合間にはセッションをリセットし、その後 `SystemPrompt` を改めて設定します。

<h2 id="what-the-managed-connection-requires">
  マネージド接続に必要な条件
</h2>

**文書コンテキスト。** 文書ページを持たない実行では、マネージド接続は拒否されます。これは、共有のプラットフォーム資格情報が汎用の LLM ゲートウェイとして使われるのを防ぐためです。文書を含むトランザクション上で動作するカスタム アクティビティはこの条件を満たしますが、そのコンテキストの外でセッションを開くスクリプトは満たしません。

**課金計測。** マネージド接続経由の呼び出しは、お客様の ABBYY 利用権に対して計測されます。お客様ご自身で構成した接続経由の呼び出しは、代わりにお客様ご自身のプロバイダーから課金されます。大量の再処理ジョブをマネージド接続に向ける前に、その処理量を十分に検討してください。

<h2 id="message-limits">
  メッセージの制限
</h2>

いずれの制限も強制され、どちらか一方でも超過したメッセージはその時点で拒否されます。呼び出しが失敗するのを待つのではなく、送信前にスクリプト内でメッセージのサイズを確認してください。

| 制限               | 動作                                                                                                             |
| :--------------- | :------------------------------------------------------------------------------------------------------------- |
| **プロンプトの上限**     | 1メッセージあたりの最大文字数。システムプロンプト、ユーザーメッセージ、添付されたJSON、base64画像コンテンツなど、**すべて**が対象に含まれます                                 |
| **添付ファイル**       | 1メッセージあたり10個。11個目は拒否されます                                                                                       |
| **実行あたりのリクエスト数** | スクリプトの実行1回あたりのLLM呼び出し回数の上限。トランザクションのページ数に応じて変動します。[スクリプトで捕捉できないエラー](#errors-your-script-cannot-catch)を参照してください |

プロンプトの上限は単一の固定値ではありません。環境ごとに設定され、**処理対象のページ数に応じて増加**しますが、最大値が定められています。現在のABBYY Vantage Cloudでは、1ページあたり500,000文字が許容され、カウント対象は最大3ページまで、上限は1,500,000文字です。

| トランザクション内のページ数 | 1メッセージあたりの許容文字数 |
| :------------- | :-------------- |
| 1              | 500,000         |
| 2              | 1,000,000       |
| 3以上            | 1,500,000       |

上限は環境ごとに設定され、変更される可能性があるため、これらの数値は保証された仕様ではなく参考情報として扱ってください。固定の上限を前提とせず、ランタイムでメッセージのサイズを確認し、収まらない場合は処理を縮退させてください。

上限を超えると、次の形式のメッセージが出力されます。

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

<h3 id="errors-that-retrying-cannot-fix">
  再試行では解決できないエラー
</h3>

`exceeds the maximum allowed size`、`maximum number of attachments`、`context length`、`too large` といった文言を含む送信エラーは、再試行しても回復できません。メッセージのサイズが大きすぎるか、含まれる内容が多すぎるため、同じ呼び出しを繰り返しても再び失敗します。対処方法は、JPEG エクスポートの解像度を下げる、送信するページ数を減らす、または画像を省いて OCR の JSON のみで実行する、のいずれかです。

<h3 id="errors-your-script-cannot-catch">
  スクリプトで捕捉できないエラー
</h3>

ほとんどの失敗は、スクリプト内の `try`/`catch` で捕捉できます。接続の問題、リクエストの失敗、有効な JSON ではない応答、サイズ制限を超える prompt などです。これらは適切に処理して処理を続行してください。

**ただし、リクエスト上限の超過は例外です。** 1 回のスクリプトの実行で行える LLM 呼び出しの回数には制限があり、その上限はトランザクションのページ数に応じて変動します。これを超えるとスクリプトは制約エラーで停止し、既存の HTTP request 上限と同じように、`try`/`catch` では捕捉できません。

この点は、リトライを行う場合に特に重要です。検証して再度問い合わせるループは実行ごとに 1 回の呼び出しを消費するため、それ自体に上限を持たないループは、いずれ対処できない制限に達してしまいます。リトライ回数には必ず上限を設けてください。

<h2 id="sending-page-images">
  ページ画像の送信
</h2>

ページ画像も利用できますが、プロンプト予算で決まる実用上の上限があります。

上記の上限に照らした実測例を2つ挙げます。

* 1584x1000のページ画像1枚は、およそ **311,776 base64文字** とおよそ2,015プロンプトトークンを消費します。1ページのトランザクションであれば500,000文字の上限に収まり、OCR JSONとプロンプトの分の余裕も残ります。
* 300 dpiでスキャンした2ページのA4帳票は、画像だけでおよそ **1,326,136文字** になります。2ページのトランザクションの上限は1,000,000文字なので、これは拒否されます。画像として送信するには、各ページを300 dpi時のサイズの約4分の1まで縮小する必要があります。

この計算はページ数や環境側の上限の変更によって変わります。だからこそ、収まるはずと決めつけず、送信前にメッセージのサイズを実測しなければなりません。

予算に逆らうのではなく、予算に合わせて設計してください。

* OCR JSONを主たるペイロードとして送信し、スタンプ、署名、写真など、テキストレイヤーでは表現できないものに限ってページ画像を追加します。
* 送信前にメッセージを実測します。画像が収まらない場合は画像を外し、それに合わせてプロンプトを組み直して、OCR JSONだけでもDocumentが処理されるようにします。
* 画像のサイズを決める際はOCR JSONの分の余裕を確保し、大きな画像が座標の取得元となるペイロードを圧迫しないようにします。
* モデルがレイアウトやスタンプを確認するだけでよく、細部が不要な場合は、エクスポート前に画像解像度を下げます。

<h2 id="locations-and-bounding-boxes">
  位置とバウンディングボックス
</h2>

<Warning>
  モデルに座標を要求しないでください。マネージド接続の背後にあるモデルは、ページ画像上でバウンディングボックスを位置づけることができず、座標のように見えるレスポンスが返っても、それは実測値ではありません。
</Warning>

ページ画像から座標を返すよう求めると、モデルは10単位刻みの格子上の値を返します。すべての数値が10の倍数、高さは一律、異なる2つのfieldが同一のRectangleを共有する、といった具合です。これは実測されたlayoutではなく作り出されたlayoutであり、座標系の設定を変更しても解消されません。

形状は代わりにVantageのOCR layerから取得してください。OCR JSONエクスポートには、`layout.pages[].pictures[]` や `barcodes[]` を含め、テキストと非テキストの両方のコンテンツについて実測位置が含まれています。そのため、写真、ロゴ、barcodeも単語と同じ信頼性で位置を特定できます。

堅牢なscriptは次のような作りになります。

* **座標は推定せず、コピーする。** すべてのRectangleはOCR JSON内の位置の値から取得し、OCRのpage sizeと照合して検証し、両者が異なる場合はVantageのページ画像に合わせてスケーリングします。
* **出自を厳格に確認する。** モデルが返す各Regionは、受け入れる前にOCRの形状と照合して検証します。OCR layerまで遡れないRegionは拒否し、抽出された値そのものは保持します。
* **ページの帰属を必須とする。** 複数ページ文書では、ページ番号のないRegionは、既定でページ1とみなさず拒否します。

モデルは、その得意分野である読み取りと分類に使用してください。形状はABBYY OCRに任せましょう。

<h2 id="set-maxtokens-deliberately">
  MaxTokens は意識的に設定する
</h2>

`MaxTokens` をプロバイダー の Default のままにしておくと、情報量の多い Document で気づかないうちに切り詰めが発生し、`LastFinishReason` を見て初めて分かるという事態になりかねません。明示的に設定し、上限を自分で管理・把握できる状態にしてください。

規模の目安として、9 column のテーブルを 3 ページにわたってセル単位で Extract する場合、コンプリーショントークンはおよそ 28,000 になります。

<h2 id="allow-enough-time">
  十分な時間を確保する
</h2>

レイテンシは入力のサイズではなく、生成される **出力** トークン数に応じて増加します。Phoenix Plus が生成するコンプリーショントークンは 1 秒あたり約 100 個のため、28,000 トークンのレスポンスには数分を要します。

`Timeout` は分単位で設定し、スクリプト実行タイムアウトが上限となります。28,000 個の出力トークンを必要とするドキュメントに 2 分のタイムアウトを設定した場合、約 121 秒の時点で、レスポンスの一部しか生成されないまま失敗します。

<h2 id="reduce-the-ocr-payload-before-sending">
  送信前にOCRペイロードを削減する
</h2>

未加工のOCR JSONエクスポートは文字レイヤーが大半を占め、通常はサイズの97～98パーセントに達します。これを削り落とせば、モデルが実際に必要とするテキストと単語の位置だけが残り、promptの予算もごくわずかで済みます。

ある計測事例では、43,437文字のエクスポートが、単語の位置を保持したまま4,842文字まで削減されました。別の事例では、317,296文字のエクスポートが32,712文字になりました。

コストを見積もる際は、OCR JSONはおよそ**1トークンあたり2文字**として換算します。JSONの記号類はトークン化の効率が悪いため、通常の文章から導き出した比率は当てはまりません。

<h2 id="known-limitations">
  Known limitations
</h2>

<Warning>
  ページを画像としてアタッチするには、**`AttachPageImage(page)`** を使用してください。`Page.Image` を `AttachImage` に渡すと、分割済み・未分割のいずれの Document でも `Value cannot be null. (Parameter 'fileLink')` がスローされます。これは、`AttachImage` がページの画像プロパティではなく、エクスポート済みのファイルを想定しているためです。`Document.Exports` から取得した JPEG のエクスポートであれば、`AttachImage` で正常に動作します。
</Warning>

<h2 id="example">
  例
</h2>

この script は、絞り込んだ OCR JSON を送信し、形状情報をすべて OCR layer から取得して structured な field 値を要求します。

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

// Activity の Available Files タブで設定された OCR JSON エクスポートを読み取ります。
var ocrExport = document.Exports.GetByFormat(ExportFormat.OcrJson);
var prunedOcr = pruneOcr(ocrExport.ToJson());   // 独自の絞り込み関数

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;            // 分
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);
```

<h2 id="related-topics">
  関連トピック
</h2>

* [ABBYY Vantage の LLM](/ja/vantage/documentation/llms/llms)
* [カスタム アクティビティ](/ja/vantage/documentation/skill-designer/process/custom-activity/custom-activity)
* [Context](/ja/vantage/documentation/skill-designer/process/custom-activity/context)
* [DocumentExportResult](/ja/vantage/documentation/skill-designer/process/custom-activity/document-export-result)
* [OCR JSON スキーマ](/ja/vantage/documentation/skill-designer/process/custom-activity/ocr-skill-schema-json)
