ビジネスで生成AIを活用するためのRAGテスト

2024-07-07
noah

概要

‍

第4次産業革命は2016年頃から本格的に注目され始めました。2024年現在、第4次産業はさまざまな分野で急速に広がっていますが、多くの一般の方々はいまだにその技術動向に疑問を持っています。しかし、2022年11月にOpenAIが生成AIサービスを公開して以降、AI市場は急激に変化しており、それは産業全般にとどまらず私たちの日常にも深く浸透しつつあります。第4次産業革命の中でも、すべての人の生活に最も速く溶け込む技術として期待されています。

Selleaseは技術基盤型の企業として成長するため、こうした変化に敏感に対応し、新しい技術が私たちのビジネスにどのように適用できるかを研究するとともに、既存のビジネスと調和させながら統合するための努力を続けています。

‍

目的と目標

‍

私たちはOpenAIのサービスを積極的に活用して生成AIをビジネスに取り入れ、ユーザーが必要なデータを簡単に問い合わせられるようにすることを目標としています。今後はマルチモーダルに対応したモデルを通じて、あらゆる形式のメディアで問い合わせ結果を受け取れるようになると期待しています。

そのためには、RAGシステムを理解することが不可欠です。

‍

RAGとは何か、なぜ必要なのか

‍

生成AIは急速に発展していますが、依然としてさまざまな問題を抱えています。その中で最も大きな問題はハルシネーション(hallucination)で、AIが存在しない情報や誤った情報を生成する現象を指します。ハルシネーションは、LLMモデルの特性、学習データの不足、文脈理解の不足など、さまざまな理由で発生します。このため初期の生成AIは信頼性が低く、信頼性が重要な企業プロセスへの導入は困難でした。

RAG(Retrieval-Augmented Generation)は、生成AIがより正確な回答を生成できるよう、外部情報を検索して補完する手法で、次のようなメリットが期待できます。

  1. さまざまなデータソースからリアルタイムに情報を検索し、正確な回答を生成
  2. 継続的なデータ更新により最新情報を提供

今回のPoCの目標は、RAGによって生成AIの回答の信頼性を高め、より多くの人工知能技術をビジネスに適用できる可能性を検証することです。

‍

技術スタック

‍

  • Webアプリケーション: Java/SpringBoot
  • 生成AI: OpenAI API (Assistant API beta v2)
  • OpenAIプラットフォーム: gpt-4oモデルを使用

‍

RAGの実装方法

‍

  • Vector Storeによる情報検索
  • Function callingによる社内サービスへのアクセス
  • LLMモデル連携API

‍

システムアーキテクチャ

‍

以下のシステムアーキテクチャはベストプラクティスとして作成したもので、実際のテストでは簡易的に実装しました。

モジュール

  • Platform Sellease IO: 現在運用中のSelleaseのバックエンドAPIサービス
    • 役割: 認証およびユーザーセッションの処理、会社・統計データの更新
  • Persist Data: MySQL DBMS、ユーザーごとのOpenAIのContext情報を保存
  • Sellease Assistant: OpenAIと通信する主体で、OpenAIのAssistant API beta2を利用した生成AIサービス

‍

‍

RAG

‍

実装

‍

  • RAG: ベクターストアにデータをアップロードし、検索機能を有効化
// アップロード
String resourceName = "sample-data/company-info.md";

ClassLoader classLoader = getClass().getClassLoader();
File file = new File(Objects.requireNonNull(classLoader.getResource(resourceName)).getFile());
var uploadedFile = openAI.files()
                          .create(FileRequest.builder()
                                            .file(file.toPath())
                                            .purpose(PurposeType.ASSISTANTS)
                                            .build())
                          .join();
String fileId = uploadedFile.getId();
System.out.println("File was created with id: " + fileId); // file-OIfrC1nJLgfjz4GyS3P3j3tJ
System.out.println();

var vectorStore = openAI.vectorStores()
                        .createAndPoll(VectorStoreRequest.builder()
                                                          .fileId("file-OIfrC1nJLgfjz4GyS3P3j3tJ")
                                                          .name("company-info")
                                                          .build());

String vectorStoreId = vectorStore.getId();
System.out.println("Vector Store was created with id: " + vectorStoreId); // vs_3fkPydWq3HnDkhxAKDqkopVF

‍

結果

‍

‍

  • Function Calling: 特定の関数の定義と呼び出しによるサービス提供
// 関数の定義と登録
var functionExecutor = new FunctionExecutor();
functionExecutor.enrollFunction(
    FunctionDef.builder()
                .name("get_stats_of_app_usage")
                .description("Return the usage statistics for the requested department and app.")
                .functionalClass(StatsOfAppUsage.class)
                .build());

// 関数の実装
public static class StatsOfAppUsageByCategory implements Functional {

  @JsonPropertyDescription("Search start date")
  @JsonProperty(required = true)
  public String since;

  @JsonPropertyDescription("Search end date")
  @JsonProperty(required = true)
  public String until;

  @JsonPropertyDescription("search category names, for example: Document, Productivity, CRM ...etc (Optional)")
  public Set<String> categories;

  @Override
  public Object execute() {
    log.info("since: {}, until: {}, categories: {}", since, until, ArrayUtils.toString(categories));
    try {
      HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
      Me me = (Me) request.getAttribute(ME);
      if (me == null) {
        return "ログインが必要です。";
      }
      List<AppUsageByCategoryItem> insight = INSIGHT_SERVICE.getAppUsageByCategories(
          1L, // me.getCompany().getId()
          DateRange.builder().since(LocalDate.parse(since)).until(LocalDate.parse(until)).build());
      return Map.of("data", insight, "dashboardUrl",
                    "https://dev.sellease.io/insight/usages/apps-by-categories?since=%s&until=%s".formatted(since, until));
    } catch (Exception e) {
      return "処理中にエラーが発生しました。";
    }
  }
}

// 関数の呼び出し
var messages = new ArrayList<ChatMessage>();
messages.add(UserMessage.of("直近1週間の文書カテゴリの利用統計を教えて"));
var chatRequest = ChatRequest.builder()
                              .model("gpt-4o")
                              .messages(messages)
                              .tools(functionExecutor.getToolFunctions())
                              .build();
var futureChat = openAI.chatCompletions().create(chatRequest);
var chatResponse = futureChat.join();
var chatMessage = chatResponse.firstMessage();
var chatToolCall = chatMessage.getToolCalls().get(0);
var result = functionExecutor.execute(chatToolCall.getFunction());
messages.add(chatMessage);
messages.add(ToolMessage.of(result.toString(), chatToolCall.getId()));
chatRequest = ChatRequest.builder()
                          .model("gpt-4o")
                          .messages(messages)
                          .tools(functionExecutor.getToolFunctions())
                          .build();
futureChat = openAI.chatCompletions().create(chatRequest);
chatResponse = futureChat.join();
System.out.println(chatResponse.firstContent());

結果

‍

‍

結論とまとめ

‍

PoCの主な発見事項:

  • RAGシステムにより、生成AIの回答の信頼性を高められました。
  • チャットによるデータアクセスは非常に効果的なインターフェースであることを確認しました。

‍

今後の計画:

  • データ上の課題があるため、OpenAIの代わりにローカルLLMを用いた追加調査が必要
  • OpenAI Assistant APIのコスト問題の解決策を模索

Selleaseは今後も継続的な研究開発を通じて、人工知能技術をビジネスに着実に取り入れてまいります。

‍

サービスについて知りたいですか?