Zendesk アクションフロー × Amazon Bedrock (Claude) × Contentful による AI  カスタマーサポートの構築

Zendesk アクションフロー × Amazon Bedrock (Claude) × Contentful による AI カスタマーサポートの構築

Zendesk アクションフローと Amazon Bedrock (Claude) を組み合わせ、Contentful のナレッジベースを参照して AI が自動回答するカスタマーサポートシステムを構築しました。顧客からの問い合わせに対して、関連記事を検索し Claude が適切な回答を生成してサポート担当者に提示する仕組みを実装例とともに紹介します。

はじめに

近年、カスタマーサポートの効率化と品質向上を目的とした AI 活用が注目されています。本記事では、Zendesk アクションフローと Amazon Bedrock (Claude) を組み合わせ、「Contentful ナレッジベースを参照して AI が自動回答するシステム」を構築してみた例を紹介します。顧客からの問い合わせが Zendesk に届くと自動的に記事を検索し、Claude が適切な回答を生成してサポート担当者に提示します。

動作例

技術スタック

  • Zendesk アクションフロー: ワークフロー自動化
  • Amazon Bedrock (Claude): LLM
  • Contentful: ヘッドレス CMS (ナレッジベース)
  • AWS Lambda: 統合処理

対象読者

  • AI を活用したサポート業務の効率化を検討しているカスタマーサポート部門の責任者
  • Zendesk や AWS サービスの連携に興味があるエンジニア

アーキテクチャ概要

本システムは以下のような構成です。

[Zendesk Trigger] (チケット作成)
    ↓
[Zendesk アクション]
    ↓
[AWS Lambda]
    ├─→ [Contentful API] (ナレッジ検索)
    └─→ [Amazon Bedrock (Claude)] (回答生成)
    ↓
[Zendesk] (回答提案)

構築手順

1. Contentful でナレッジベース準備

本件は、すでに Contentful にナレッジが蓄積されているユースケースを想定しています。今回は検証のため、まず AI が参照するナレッジベースを Contentful で作成します。

Content Type の作成

Contentful の管理画面で新しい Content Type Article を作成します。

  • Title (Short text): 記事タイトル
  • Category (Short text): カテゴリ分類
  • Tags (Short text, list): 検索用タグ
  • Summary (Long text): 記事要約
  • Content (Rich text): 記事本文

Article type

サンプル記事の作成

LLM の検索対象となるように、以下のようなサンプル記事をいくつか作成します。

記事例: API 利用方法

Title: API 利用方法
Category: 開発者向け
Summary: 当社 API の基本的な使い方と認証方法について説明します
Content: 
## 認証設定
1. API トークンの取得
2. リクエストヘッダーの設定
...

Sample articles

2. Bedrock の準備

今回使用するモデルは「Claude Sonnet 4」とします。Bedrock コンソール の Model catalog から Claude Sonnet 4 のページを開き、EULA に同意して使用承認を取得します。

Claude Sonnet 4

3. AWS Lambda 関数作成

次に、Contentful と Bedrock を連携する Lambda 関数を作成します。

関数の作成

AWS Lambda コンソールで新しい関数を作成します。

  • 関数名: zendesk-contentful-integration
  • ランタイム: Node.js 22.x
  • 実行ロール: 新しいロールを作成

環境変数の設定

Lambda 関数の設定で以下の環境変数を追加します:

CONTENTFUL_SPACE_ID = your_space_id
CONTENTFUL_ACCESS_TOKEN = your_access_token

IAM 権限の設定

Lambda 実行ロールに対し、 Bedrock へのアクセスを許可する IAM ポリシーを追加します。Claude Sonnet 4 の ID は anthropic.claude-sonnet-4-20250514-v1:0 です。(2025.07 現在)

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "bedrock:InvokeModel",
                "bedrock:InvokeModelWithResponseStream"
            ],
            "Resource": [
                "arn:aws:bedrock:*:*:inference-profile/apac.anthropic.claude-sonnet-4-20250514-v1:0",
                "arn:aws:bedrock:*::foundation-model/anthropic.*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "bedrock:ListFoundationModels",
                "bedrock:ListInferenceProfiles",
                "bedrock:GetInferenceProfile"
            ],
            "Resource": "*"
        }
    ]
}

Lambda 関数の作成

ローカルに環境を作成します。

mkdir zendesk-contentful-lambda
cd zendesk-contentful-lambda
npm init -y
npm install @aws-sdk/client-bedrock-runtime axios

下記の index.js を作成します。

const { BedrockRuntimeClient, InvokeModelCommand } = require('@aws-sdk/client-bedrock-runtime');
const axios = require('axios');

exports.handler = async (event) => {
    try {
        // Zendesk からのリクエストボディを解析
        let body;
        if (event.body) {
            body = typeof event.body === 'string' ? JSON.parse(event.body) : event.body;
        } else {
            body = event;
        }

        const question = body.question || body.message || '';

        if (!question) {
            return {
                statusCode: 400,
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ error: 'Question is required' })
            };
        }

        // 1. Contentful からナレッジを検索
        const knowledge = await searchContentful(question);

        // 2. Claude で回答生成
        const answer = await generateAnswer(knowledge, question);

        // 3. Zendesk に結果を返却
        return {
            statusCode: 200,
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
                question: question,
                answer: answer,
                knowledgeUsed: knowledge.length,
                knowledgeArticles: knowledge.map(k => ({ 
                    title: k.title, 
                    category: k.category 
                })),
                timestamp: new Date().toISOString()
            })
        };

    } catch (error) {
        return {
            statusCode: 500,
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
                error: 'Internal server error',
                message: error.message
            })
        };
    }
};

// Contentful からナレッジ記事を検索
async function searchContentful(query) {
    try {
        const spaceId = process.env.CONTENTFUL_SPACE_ID;
        const accessToken = process.env.CONTENTFUL_ACCESS_TOKEN;

        if (!spaceId || !accessToken) {
            return [];
        }

        const response = await axios.get(`https://cdn.contentful.com/spaces/${spaceId}/entries`, {
            params: {
                access_token: accessToken,
                content_type: 'article',
                limit: 10
            }
        });

        const articles = response.data.items.map(item => ({
            title: item.fields.title || 'No title',
            summary: item.fields.summary || 'No summary',
            content: item.fields.content || 'No content',
            category: item.fields.category || 'No category'
        }));

        // キーワードマッチングで関連記事を検索
        const relevantArticles = articles.filter(article => {
            const searchText = `${article.title} ${article.summary} ${article.content}`.toLowerCase();
            const keywords = query.toLowerCase().split(' ');
            return keywords.some(keyword => searchText.includes(keyword));
        });

        return relevantArticles.length > 0 ? relevantArticles.slice(0, 3) : articles.slice(0, 2);

    } catch (error) {
        console.error('Contentful search error:', error.message);
        return [];
    }
}

// Claude で回答を生成
async function generateAnswer(knowledge, question) {
    const client = new BedrockRuntimeClient({
        region: 'ap-northeast-1'
    });

    // ナレッジベースの有無でプロンプトを切り替え
    let prompt;
    if (knowledge.length === 0) {
        prompt = `あなたは企業のカスタマーサポート担当者です。以下の質問に対して、親切で丁寧な回答をしてください。

ユーザーの質問: ${question}

回答:`;
    } else {
        const knowledgeText = knowledge.map(article =>
            `## ${article.title}\nカテゴリ: ${article.category}\n要約: ${article.summary}\n内容: ${article.content}`
        ).join('\n\n');

        prompt = `あなたは企業のカスタマーサポート担当者です。以下のナレッジベースを参考に、ユーザーの質問に正確で親切な回答をしてください。

ナレッジベース:
${knowledgeText}

ユーザーの質問: ${question}

回答の条件:
- ナレッジベースの情報を基に、具体的で実用的な回答をしてください
- 手順がある場合は、番号付きリストで分かりやすく説明してください
- 敬語を使用してください

回答:`;
    }

    const command = new InvokeModelCommand({
        modelId: 'apac.anthropic.claude-sonnet-4-20250514-v1:0',
        body: JSON.stringify({
            messages: [{ role: "user", content: prompt }],
            max_tokens: 500,
            anthropic_version: "bedrock-2023-05-31"
        })
    });

    const response = await client.send(command);
    const result = JSON.parse(new TextDecoder().decode(response.body));

    return result.content[0].text;
}

zip ファイルに圧縮してアップロードします。

zip -r function.zip .

タイムアウト設定

LLM アクセスにかかる時間を考慮し、タイムアウトを 30 秒に設定します。

API Gateway

Lambda にアクセスするためのエンドポイントを API Gateway の REST API で用意します。 CORS 設定を行い、ブラウザからの API アクセスを許可します。認証は今回は検証目的なので「オープン」としますが、本番環境では適切な認証設定を行ってください。

4. Zendesk アクション設定

まず、Lambda 関数を呼び出すためのアクションを作成します。Zendesk 管理センターの アプリおよびインテグレーション > アクション でアクションを作成します。

  • 入力:
    • 名前: comment
    • 説明: チケットコメント
    • タイプ: String
      アクション設定画面 - 入力
  • API設定:
    • リクエスト方法: POST
    • エンドポイントURL: API Gateway のエンドポイント URL
    • 認証: 認証なし(本番環境では適切に設定してください)
    • 本文:
      {
        "question": "{{comment}}"
      }
      
    アクション設定画面 - API設定
  • 出力:
    • answer: 回答
    • question: 質問
    • timestamp: タイムスタンプ
    • knowledgeuserd: 参照数
    • knowledgearticles: 参照記事
      アクション設定画面 - 出力

5. Zendesk アクションフロー設定

次に、Zendesk 管理センターの アプリおよびインテグレーション > アクションフロー でアクションフローを作成します。

  • トリガー: Zendesk > Tickets > Lifecycle > Ticket created
  • チケット内容取得: Zendesk steps > Look up ticket
    • Ticket ID: Ticket created > id
      アクションフロー - Look up ticket
  • 回答生成: 先に作成したアクション
    • comment: Look up ticket > description
      アクションフロー - Action
  • 回答提案:
    • Ticket ID: Ticket created > id
    • Comment: 先に作成したアクション > answer
    • Comment visibility: Internal note
      アクションフロー - Update ticket

フローの有効化

作成したフローのメニューから Activate を選択して有効化します。

Activate

動作確認

テスト実行

Zendesk でテストチケットを作成し、「API の使い方について教えて」とコメントを追加します。

テストチケット

結果確認

アクションフローが実行され、Claude が生成した回答を確認できます。

提案された回答

まとめ

本記事では、Zendesk アクションフロー、Amazon Bedrock (Claude)、Contentful を組み合わせた AI カスタマーサポートシステムを構築しました。 顧客の質問に関連する記事を既存のナレッジ(今回は Contentful を想定)から自動で検索し、自然な文章で回答を提案します。今後の展開としては、複数のシステムに渡るナレッジの横断的な検索や、学習機能なども検討可能です。

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.