テキスト生成 API

スクリプトから任意の用途に使うテキストを生成できます。基本は api.v1.generate を使います。現時点では、生成は OpenAI 互換の completions エンドポイントを使うモデルでのみサポートされています。

テキスト生成

基本的な生成

api.v1.generate に、プロンプトとなるメッセージ配列を渡して生成します。各メッセージは role(“user” / “assistant” / “system”)と content(本文)を持つオブジェクトです。

第 2 引数のパラメータオブジェクトには、modeltemperaturemax_tokens などの生成設定を含められます。model は必須で、有効なモデル ID を指定する必要があります。

例:

let response = await api.v1.generate(
  [
    { role: 'system', content: 'You are a not-very-helpful assistant.' },
    { role: 'user', content: 'Where am I?' }
  ],
  {
    model: 'glm-4-6',
  }
);
api.v1.log('Generated text: ' + response.choices[0].text);

生成パラメータ

利用できる生成パラメータの例:

  • model(string、必須)
  • temperature(number)
  • max_tokens(number)
  • top_p(number)
  • top_k(number)
  • frequency_penalty(number)
  • presence_penalty(number)
  • stop(string の配列)

ストリーミング生成

api.v1.generate はデフォルトでストリーミングします。ストリーミング中の値は、第 3 引数にコールバック関数を渡すことで受け取れます。このコールバックは、生成エンドポイントから新しいデータが届くたびに複数回呼ばれます。

例:

await api.v1.generate(
  [
    { role: 'user', content: 'aaaa, nslx, bjal, abcd, or izti?' }
  ],
  {
    model: 'glm-4-6',
  },
  (choices, final) => {
    for (let choice of choices) {
      api.v1.log('Received text chunk: ' + choice.text);
    }
    if (final) {
      api.v1.log('Generation complete.');
    }
  }
);

ブロッキング生成とバックグラウンド生成

デフォルトでは、api.v1.generate は「ブロッキング」と扱われ、生成完了までエディター操作とユーザー起因の生成をブロックします。生成中もユーザー操作を許可したい場合は、第 4 引数 behaviour'background' に設定します。

生成の制限

スクリプトには、生成頻度や生成量に上限があります。これらの制限値は、UI のスクリプト診断(diagnostics)タブに表示され、デバッグに利用できます。

出力トークン上限

スクリプトは一定時間内に生成できる出力トークン数に上限があります。現在の上限は「4 分あたり 2048 出力トークン」(スクリプトごと)です。生成で使った出力トークン数は残量から差し引かれ、「バケット」に移されます。バケット内のトークンは、次の 2 条件を満たすと残量へ戻ります。

  1. その生成から 4 分が経過している
  2. ユーザーが、スクリプトが作った UI を操作するか、エディターで手動生成を行っている

入力トークン上限

入力トークンにも上限があります。現在の上限は「モデルとサブスクリプションティアにおける最大コンテキストサイズの 2 倍」を、6 分あたりで使用できる、というものです。入力トークンは api.v1.generate に渡すメッセージからカウントされます。

ただし、過去の生成で使った入力トークンと重複していない「新規入力トークン」のみが上限に計上されます。そのため、キャッシュが効くようにコンテキストを組むと上限内に収まりやすくなります。

入力トークンも出力トークンと同様にバケットへ入り、6 分経過+ユーザー操作で戻ります。

キャッシュは、過去の生成のプロンプト先頭と一致するトークンが対象になります。同じ先頭トークン列で始まるプロンプトを送ると、その部分は入力上限にカウントされません。キャッシュ判定は 64 トークン単位の解像度で行われます。

一般的な対策として「ロールオーバー」ロジックがあります。これはコンテキストを意図的に上限より少し低めに保ち、上限に近づいたら古いメッセージを捨てて新しいメッセージを入れる方式です。api.v1.createRolloverHelper で補助オブジェクトを作れます(用途により実装は変わります)。

/**
 * Simple Chat Window - Rollover Helper Example
 * This script creates a simple chat window that uses a rollover helper to manage context size.
 */

const MODEL = "glm-4-6";
const MAX_TOKENS = 2000;
const ROLLOVER_TOKENS = 500;

const SYSTEM_MESSAGE: Message = {
    role: "system",
    content: "You are a helpful assistant. Keep responses brief."
};

type ChatMessage = {
    role: "user" | "assistant";
    content: string;
};

const rollover = api.v1.createRolloverHelper<ChatMessage>({
    maxTokens: MAX_TOKENS,
    rolloverTokens: ROLLOVER_TOKENS,
    model: MODEL
});

async function sendMessage(userInput: string) {
    if (!userInput.trim()) return;
    
    await rollover.add({ role: "user", content: userInput });
    updateDisplay();
    
    const messages: Message[] = [SYSTEM_MESSAGE, ...rollover.read()];
    
    const response = await api.v1.generate(messages, {
        model: MODEL,
        max_tokens: 200,
        temperature: 0.7
    });
    
    const assistantContent = response.choices[0]?.text ?? "(no response)";
    await rollover.add({ role: "assistant", content: assistantContent });
    updateDisplay();
}

function updateDisplay() {
    const history = rollover.read();
    const chatText = history
        .map(m => `**${m.role}:** ${m.content}`)
        .join("\n\n");
    
    win.update({
        content: [
            { type: "text", id: "chat", markdown: true, text: chatText || "*No messages yet*" },
            {
                type: "textInput",
                id: "input",
                placeholder: "Type a message...",
                onSubmit: sendMessage
            },
            {
                type: "text",
                id: "status",
                text: `${rollover.count()} messages, ~${rollover.totalTokens()} tokens`,
                style: { opacity: 0.6, fontSize: "0.85em" }
            }
        ]
    });
}

const win = await api.v1.ui.window.open({
    title: "Chat (Rollover Demo)",
    defaultWidth: 400,
    defaultHeight: 300,
    resizable: true,
    content: []
});

updateDisplay();

ユーティリティ関数

api.v1.script.getAllowedOutputapi.v1.script.getAllowedInput で、現在利用可能なトークン量を確認できます。

api.v1.script.getTimeUntilAllowedOutputapi.v1.script.getTimeUntilAllowedInput で、再びトークンが使えるまでの時間を確認できます。

api.v1.script.waitForAllowedOutputapi.v1.script.waitForAllowedInput は、トークンが利用可能になるまで待つ Promise を返します。

countUncachedInputTokens は、指定プロンプトが入力上限にどれだけ計上されるか(キャッシュを考慮して)見積もれます。

エディターへの生成(Editor Generate)

api.v1.editor.generate を使うと、ユーザーがエディターの「送信」ボタンを押したのと同様に、現在のドキュメント内容を元に生成し、結果をドキュメントへ追加できます。

ユーザー操作を挟まずに行えるエディター生成は 3 回までです。3 回を超えると、ユーザーが手動で生成するか、スクリプトが作った UI を操作するまで、以降の api.v1.editor.generate は拒否されます。

参考