💬

自社サービスにAIチャットボット機能を導入した話

に公開

はじめに

顧客管理/マーケティングを目的とする、LINE拡張ツールを開発しております、@mitsuyashiです。

2025年11月、LINEにAIチャットボット機能がリリースされました。
顧客応答をAIがチャットで行ってくれるというものです。
https://www.lycbiz.com/jp/column/line-official-account/service-information/ai-chatbot/

この発表より先んじて開発を行っていましたが、リリースの先を越される形になりました。
12月中にリリースの目処が立ったため(記事公開時にはリリース済みの予定)、技術的な工夫や苦労について語れたらと思います。

LINE拡張ツールとは

LINE公式アカウントを運用する方法は大きく3つあります。

  1. OAM(Official Account Manager):LINE社提供の無料管理画面
  2. Messaging API:開発者向けAPI(自社開発が必要)
  3. LINE拡張ツール:SaaSサービス

本記事のサービスは「LINE拡張ツール」に分類されます。OAMの標準機能では足りない企業が、開発コストをかけずに高度な顧客管理・マーケティングを実現するためのツールです。
OAMとの大きな違いとして、複数店舗管理、セグメント機能を強みとしています。

本題

LINEチャットボットとの会話画面。ユーザーが営業時間と駐車場について質問し、ボットが「営業時間は9:00-18:00」「駐車場は10台停められる」と自動応答している様子
実際の応答イメージ

AIサービス選定

実装にあたり使用するAIサービスとして、OpenAI APIを採用しました。

理由としては大きく2つです。
一つは社内で導入実績があったこと。
もう一つは、シンプルなチャットボットなので、他の候補と比べた際に代表的なOpenAIで申し分ないと判断したためです。

OpenAI APIの機能の中でも、実装にはAssistants APIを使用しました。
通常のチャットと違い、カスタムエージェントの作成に特化しており、

  • ユーザーのコンテキストスレッドをOpenAIで保持してくれる
  • 毎回プロンプトを投げる必要がない

などの特徴があります。

参考記事
https://zenn.dev/umi_mori/books/chatbot-chatgpt/viewer/chatgpt_assistants_api

設計

サービスのアーキテクチャとして、共通で使用する部分は単独のライブラリとして管理しています。
その共通領域の抽象化に力を入れて実装しました。

OpenAIの機能が、AssistantsからResponsesへの過渡期だったのと、これから先、他のサービスを使う可能性があると考えたためです。実際にAssistantsは非推奨となったため、今後、Responses APIへの移行を予定しています

コードサンプル

export class OpenAPIProvider implements AIProvider {
  public readonly provider = 'openai' as const;
  public readonly name = 'OpenAI';
  private client: OpenAI;
  private config: AIProviderConfig;

  constructor(config: AIProviderConfig) {
    this.config = config;
    this.client = new OpenAI({
      apiKey: config.apiKey,
      baseURL: config.baseUrl,
      timeout: config.timeout || 30000,
    });
  }

  public async createAssistant(request: AssistantCreateParams): Promise<Assistant> {
    try {
      if (!this.isConfigured()) {
        throw new AIError('OpenAI API key is not configured', 'MISSING_API_KEY', this.name);
      }

      const assistant = await this.client.beta.assistants.create(request);
      return assistant;
    } catch (error) {
      if (error instanceof AIError) {
        throw error;
      }

      const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
      throw new AIError(
        `CreateAssistant API error: ${errorMessage}`,
        'API_ERROR',
        this.name,
        error instanceof Error ? error : undefined
      );
    }
  }

  // 省略 ...
  public async updateAssistant(assistantId: string, request: AssistantUpdateParams){}
  public async deleteAssistant(assistantId: string): Promise<{ id: string; object: string; deleted: boolean }> {}
  public async createThread(): Promise<Thread> {}
  // ......
}

工夫した点

Assistantの取得をサービス内で行わないようにしました。
一つのAPIKeyを使っているため、万が一AssistantsのIDが流出した場合に、他のアカウントの情報が取得できる可能性があるためです。

具体的な実装としては、OpenAIの情報はAssistantsのIDのみを保存し、プロンプトはサービス側で保持し、上書きのみできるようにしています。

この設計により、万が一AssistantsのIDが流出しても、プロンプト情報自体は守られるため、被害を最小限に抑えられます。

また、弊サービスでプロンプトを保持するメリットとして

  • API Keyが変更となった際や、AIサービスが変わった場合の移行がスムーズ
  • OpenAIと弊サービスで同期が取れないことが起こらない

などがあります。

図解イメージ

苦労した点

インフラ面で既存システムの知識が足りておらず、キャッチアップに時間がかかりました。

特に時間がかかった点が、LINEへのメッセージ送信の処理部分です。
複数人でLINEにチャットを送り、何人かに応答されない事象が発生しました。

これは弊サービス特有のインフラの事情が関係していました。

既存の機能として、ユーザーからチャットが送られた際に返信をする「応答メッセージ」機能があります。

「営業時間」が含まれていたら -> 営業時間を返す
「駐車場」が含まれていたら -> 駐車場の案内を返す 

この処理はバッチ処理とキューシステムの組み合わせで実行されていました。
バッチ処理は10人ずつ処理するシステムで、タイムアウトが30秒でした。
AI応答の場合、そのタイムアウトに引っ掛かり、タイムアウト以降の人は応答されない現象が起きました。

解決策としては別のLambdaにAI処理を切り出し、非同期で実行することにより、タイムアウトを機にする必要がなくなりました。

おわりに

AI機能は敷居が高いと思われがちですが、コンテキストの管理を含め、公式で用意されているものだけで色々できます。皆さんも試してみてください。

株式会社ソニックムーブ

Discussion