GASでClaude APIの
システムプロンプトで役割と回答ルールを固定する方法
AIの答えが毎回ぶれる、口調が揃わない——その多くは、指示を毎回のuserメッセージに混ぜているのが原因です。役割・口調・ルールはsystem(システムプロンプト)に固定します。GASからの書き方を動くコードで解説します。
Table of Contents
システムプロンプトとは(systemとuserの違い)
Claude APIのリクエストには、system(システムプロンプト)とmessagesという2つの入り口があります。systemはAIの「役割・前提・守るべきルール」を伝える場所で、会話全体の土台になります。messagesの中のuserは「今回してほしい具体的な依頼」です。
system(システムプロンプト)
変わらない指示。役割・口調・禁止事項・出力ルール。毎回の会話の前提になる。
user(ユーザーメッセージ)
その都度変わる依頼。質問文や処理したいデータそのものを入れる。
役割やルールを毎回のuserに混ぜると、質問文の言い回しに引きずられて答えがぶれます。変わらない指示はsystemに、変わるデータはuserに、と分けるのが基本です。
GASでsystemを付けた基本リクエスト
GASからはUrlFetchApp.fetchでClaude APIを呼びます。payloadのトップ階層にsystemを1行足すだけで、AIの役割を固定できます。APIキーはコードに直書きせず、PropertiesService(設定値を安全に保管する仕組み)から読み込みます。
function askClaude() {
const apiKey = PropertiesService.getScriptProperties().getProperty('CLAUDE_API_KEY');
const payload = {
model: 'claude-haiku-4-5',
max_tokens: 1024,
// system = AIの役割・前提。毎回の会話の土台になる
system: 'あなたは自社ECサイトの丁寧なサポート担当です。常に敬体(です・ます)で答えます。',
// messages = 今回の具体的な依頼
messages: [
{ role: 'user', content: 'パスワードを忘れました。どうすればいいですか?' }
]
};
const res = UrlFetchApp.fetch('https://api.anthropic.com/v1/messages', {
method: 'post',
contentType: 'application/json',
headers: {
'x-api-key': apiKey,
'anthropic-version': '2023-06-01'
},
payload: JSON.stringify(payload),
muteHttpExceptions: true
});
const json = JSON.parse(res.getContentText());
Logger.log(json.content[0].text); // AIの回答テキスト
}ヘッダーのanthropic-versionはAPIのバージョン指定で、必ず付けます。muteHttpExceptions: trueを付けておくと、エラー時もレスポンス本文を読んで原因を確認できます。回答テキストはcontent[0].textに入っています。
役割・口調・ルールを構造化して書く
システムプロンプトは、だらだらと1文で書くより、見出しと箇条書きで整理したほうがAIがルールを取りこぼしません。 「役割」「口調」「守ること」のように区切ると、指示が増えても管理しやすくなります。
// 役割・口調・守ることを見出しと箇条書きで整理する
const SYSTEM_PROMPT = [
'# 役割',
'あなたは自社ECサイトのカスタマーサポート担当です。',
'',
'# 口調',
'- 常に敬体(です・ます)で答える',
'- 専門用語は避け、中学生にも分かる言葉にする',
'',
'# 守ること',
'- 分からないことは推測せず「担当者に確認します」と答える',
'- 返金や社内規定に関わる約束はしない',
'- 回答は3文以内にまとめる'
].join('\n');このように組み立てた文字列を、systemに渡します。処理ごとにuserだけ差し替えられるよう、呼び出し部分を関数にまとめておくと使い回せます。
// systemとuserを分けて渡せる呼び出し関数
function callClaude(systemPrompt, userText) {
const apiKey = PropertiesService.getScriptProperties().getProperty('CLAUDE_API_KEY');
const res = UrlFetchApp.fetch('https://api.anthropic.com/v1/messages', {
method: 'post',
contentType: 'application/json',
headers: { 'x-api-key': apiKey, 'anthropic-version': '2023-06-01' },
payload: JSON.stringify({
model: 'claude-haiku-4-5',
max_tokens: 1024,
system: systemPrompt, // 変わらないルール
messages: [{ role: 'user', content: userText }] // 都度の質問
}),
muteHttpExceptions: true
});
return JSON.parse(res.getContentText()).content[0].text;
}
function run() {
const answer = callClaude(SYSTEM_PROMPT, '注文をキャンセルしたいです');
Logger.log(answer);
}ルールを書くときは、〜しないより〜するという肯定文のほうが従いやすい傾向があります。「敬語を崩さない」より「常に敬体で答える」と書くイメージです。
例を見せて出力を安定させる(few-shot)
出力の形式(1語で返す、JSONで返すなど)は、言葉で説明するより「手本」を見せるほうが確実です。messagesにuserとassistantの正解ペアを数組入れる手法を、few-shot(数例提示)と呼びます。
// 手本(正解例)をmessagesに入れて出力の形を安定させる
function classifyInquiry(text) {
const apiKey = PropertiesService.getScriptProperties().getProperty('CLAUDE_API_KEY');
const system = '問い合わせを「配送」「返品」「その他」のいずれか1語で分類する。説明や記号は付けない。';
const messages = [
// ↓ ここが few-shot(手本)。2往復入れると形が固まる
{ role: 'user', content: '荷物がまだ届きません' },
{ role: 'assistant', content: '配送' },
{ role: 'user', content: 'サイズが合わないので交換したい' },
{ role: 'assistant', content: '返品' },
// ↓ 実際に分類したい文
{ role: 'user', content: text }
];
const res = UrlFetchApp.fetch('https://api.anthropic.com/v1/messages', {
method: 'post',
contentType: 'application/json',
headers: { 'x-api-key': apiKey, 'anthropic-version': '2023-06-01' },
payload: JSON.stringify({
model: 'claude-haiku-4-5',
max_tokens: 20,
system: system,
messages: messages
}),
muteHttpExceptions: true
});
return JSON.parse(res.getContentText()).content[0].text.trim();
}役割そのものはsystemに置き、「どんな形で返すか」の手本をmessagesに入れる、という分担にすると効果的です。分類・抽出のように答えが決まっている処理ほど、手本が効きます。
長いsystemはプロンプトキャッシュで使い回す
APIは前回の会話を覚えていない(ステートレスな)ため、systemは毎回のリクエストに含める必要があります。用語集やFAQを詰めてsystemが長くなると、その分の入力トークンを毎回支払うことになります。 そこで役立つのがプロンプトキャッシュです。
// 長いsystemは配列にして cache_control を付けると使い回せる
const payload = {
model: 'claude-haiku-4-5',
max_tokens: 512,
system: [
{
type: 'text',
text: LONG_SYSTEM_PROMPT, // 共通ルール・用語集・FAQなど長い前提
cache_control: { type: 'ephemeral' } // ここまでをキャッシュ対象にする
}
],
messages: [{ role: 'user', content: userText }]
};
// 2回目以降、同じsystemを送るとキャッシュヒットで入力料金が下がるsystemを配列にしてcache_controlを付けると、2回目以降は同じ部分が割引価格で再利用されます。スプレッドシートの数百行に同じ長いsystemを回すときほど効果が大きくなります。 詳しくはプロンプトキャッシュの記事で解説しています。
スプレッドシートの一括処理に組み込む
システムプロンプトの真価は、同じルールを大量のデータに適用するときに出ます。 スプレッドシートに並んだ問い合わせを1行ずつ読み、共通のsystemで返信下書きを作る例です。
// スプレッドシートの問い合わせに、同じルールで一括返信下書き
function replyDraftForRows() {
const sheet = SpreadsheetApp.getActiveSheet();
const last = sheet.getLastRow();
if (last < 2) return;
const values = sheet.getRange(2, 1, last - 1, 2).getValues(); // A:本文 B:状態
const system = [
'あなたは丁寧な日本語のサポート担当です。',
'敬体で、3文以内、謝意→対応→次の案内の順で返信文の下書きを書く。',
'断定できない社内情報には触れない。'
].join('\n');
values.forEach((row, i) => {
const inquiry = row[0]; // A列: 問い合わせ本文
const done = row[1]; // B列: 生成済みフラグ
if (!inquiry || done) return; // 空行・処理済みはスキップ
const draft = callClaude(system, inquiry); // 前掲のcallClaudeを再利用
sheet.getRange(i + 2, 3).setValue(draft); // C列に下書きを書き込み
sheet.getRange(i + 2, 2).setValue('done'); // 二重生成を防ぐ
Utilities.sleep(500); // レート制限対策に軽く間隔を空ける
});
}全行に同じルールが効くため、返信のトーンが揃います。状態列(B列)で処理済みを記録すると、途中で止まっても続きから再開でき、二重生成も防げます。 件数が多く6分の実行時間制限に触れそうなときは、時間主導トリガーで小分けに回します。
指示に従わないときのコツと注意点
肯定文で、ルールは数を絞る
禁止事項を並べすぎると、かえって守られにくくなります。本当に外せないルールに絞り、〜するという肯定文で書きます。
形式は言葉より手本で示す
出力形式が崩れるときは、systemの説明を増やすより、前掲のfew-shotで正解例を1〜2組見せるほうが早く安定します。 それでも崩れると困る処理では、Structured Outputsでスキーマを固定します。
systemに機密や外部由来の文章を混ぜない
systemはあくまで自社が決めた固定ルールを置く場所です。ユーザーが入力した文章や外部から取り込んだテキストはuser側に入れ、systemと混ぜないようにします。指示とデータを分けておくと、想定外の入力に振り回されにくくなります。
systemだけで安全性は保証されない
「返金の約束はしない」と書いても、100%守られる保証はありません。返信は下書きとして人が確認する、金額に関わる回答は自動送信しない、といった運用面の歯止めも併せて設計します。
まとめ
AIの答えを揃える鍵は、指示とデータを分けることです。役割・口調・ルールはsystemに固定し、その都度変わる質問やデータはuserに入れます。systemは見出しと箇条書きで整理し、形式はfew-shotの手本で見せると安定します。長くなったらプロンプトキャッシュ、形式を厳密にしたいならStructured Outputsと組み合わせれば、スプレッドシートの大量処理でもぶれない出力が得られます。
よくある質問
systemは「AIの前提や役割・守るべきルール」を伝える場所で、毎回の会話の土台になります。userメッセージは「今回してほしい具体的な依頼」です。役割や口調、禁止事項などの変わらない指示はsystemに、その都度変わる質問文や対象データはuserに入れる、と役割を分けると、指示がぶれにくくなります。
問題ありません。日本語で役割・口調・ルールを書けば、日本語で自然に従います。むしろ日本語の敬体(です・ます)で答えさせたい業務では、systemも日本語で「常に敬体で答える」と明記したほうが安定します。箇条書きや見出しで整理して書くと、AIがルールを取りこぼしにくくなります。
3つ試してください。1つ目は「〜しない」より「〜する」と肯定文で書く。2つ目は守ってほしいルールを箇条書きにして数を絞る。3つ目はmessagesに正解例(手本)を1〜2往復入れる(few-shot)ことです。特に出力形式の指定は、言葉で説明するより実例を見せるほうが効きます。
APIはステートレス(前回の会話を覚えていない)なので、毎回のリクエストにsystemを含める必要があります。ただしsystemが長い場合は、プロンプトキャッシュを使うと2回目以降は同じ部分を割引価格で使い回せます。systemを配列にしてcache_control: { type: 'ephemeral' } を付けるだけです。
systemで「JSONだけを返す」と指示し、messagesに正解例を入れる方法でもかなり安定します。より確実にしたい場合は、Structured Outputs(構造化出力)を使うとスキーマどおりのJSONが保証されます。分類やデータ抽出のように形式が崩れると困る処理では、後者が安全です。
AI×GASの業務自動化を
相談する。
問い合わせ対応・分類・要約など、Claude APIとGASを組み合わせた自動化をご相談いただけます。プロンプト設計から運用まで対応します。