📚

ローカルのDifyとSlackを使った社内用語回答Botの開発

2024/10/18に公開

はじめに

こんにちは、AxelspaceのDX基盤本部 DevOps&AIソリューションユニットに所属する橋本(@r-hashi01)です。
社内のDXにつながるツール開発などを担当しています。
今回はSlack Boltローカル環境におけるDify(v0.8.3)を使って、Slackから質問できる社内用語回答Botを作ってリリースしたので、その開発について話したいと思います。

今回の仕組み

今回は「Slackで質問したら回答する」という仕組みです。

社内用語回答Botの要件

  • 社内用語を答えられるようにする
  • 回答はユーザーのSlackの言語設定に合わせた言語で回答する
  • 用語の情報を自動で更新できるようにする
  • 質問ごとに費用があまりかからないようにする
  • レスポンスを1分以内くらいにする

社内用語回答Botの動作

  1. Slackに質問を投げると、Slack Boltで受ける
  2. Slack BoltからDifyにクエリを送る
  3. LLMサーバー上で動いているDifyでLLMを動かして質問に対する回答を作成する
  4. ローカルDifyが用意した回答をSlack Boltが受ける
  5. 回答をSlackで質問に対するスレッドとしてチャットする

社内用語回答Botのアーキテクチャ

LLMサーバーが自社のVPN内に閉じている関係で、Vultrのcompute内のdockerコンテナでSlack Boltは動いています。

alt text

Difyを使ったAIアプリについて

DifyはAI利用のワークフローをノーコードで開発できるツールです。
LLMには、ChatGPT、Claude、Gemini、Amazon Bedrockを始めとして、ollamaを使ったOSSのLLM利用もできます。

  • Difyの画面
    Difyの画面

  • LLM一覧
    Difyから使えるLLM一覧

ローカル環境でのDify

Difyにはローカルで動かせるコミュニティ版とクラウドサービスの2種類あります。
コミュニティ版は、dockerを使ってローカルマシンで動作させることが無料でできます。
また、クラウドサービスにおいても無料含めて4つのプランがあります。

コミュニティ版では以下の制約があります。(2024年9月末時点)

  • アプリの編集や実行履歴などの閲覧等の権限設定ができない
  • ログインはメール/パスワード方式しか対応していない

今回はコミュニティ版を使ってアプリを開発していきました。

DifyのGitHubのリポジトリからcloneしてきてdocker-composeで動かします。

LLMの選定

今回はOllamaを使ってローカルで動作するLLMを選びました。
回答難易度が比較的容易な社内用語であったことから、ChatGPTやClaudeにやらせるのは重すぎることと、自社サーバーで動かした場合に実運用で耐えれるのかどうか等見ておきたかったためです。

今回の剪定基準ですが、社内用語回答Botであることと、社内は多国籍メンバーであることから

  1. 多言語に対応できる(特に英語と日本語
  2. 回答が早い
  3. RAGの内容をできるだけ正しく答えられる

ことを重視して選びました。

特に、日本語は漢字やひらがななどに対応しなければならないので性能だけでは測りきれないポイントとして重視しました。(私自身英語がそんなに得意ではないので日本語回答だと嬉しかったのもあります笑、、、英語勉強しなきゃ、、、)

また、対象にしたLLMですが、Ollamaで動かせるものから以下のものをピックアップしてみました。

  • gemma2:7b
  • gemma2:27b
  • llama3
  • phi3
  • command-r

それぞれ使ってみてどうだったかを表にしてみました。

model性能の簡単な比較表

model名 パラメータ数 速度 日本語能力 選定
gemma2 7b 早い 高いが語彙が少ないため回答データが長い場合に正しい回答ができない
gemma2 27b 少し遅い 高く、語彙が豊富なため回答データが長い場合も正しい回答を出せる
llama3 70b 少し遅い 低く、文字化けっぽい文字を出力し回答としては読みづらく使いづらさあり ×
phi3.5 3b 早い 低く、文字化けっぽい文字を出力し回答としては読みづらく使いづらさあり ×
command-r 35b 遅い 文字化けはしないが、存在しない日本語を出してきたり、そもそも訳さず出力してくるため低い ×

回答内容の例

  • 質問: Constellation?
  • RAGに登録した内容:
  1. 全天に見える星を二次元的に結びつけて絵を描いたもの。
    現在では88個の星座が存在する。
    古代ギリシャでプトレマイオス(トレミー)が制定した48星座が特に有名で、47星座が現在も残っている(アルゴ船座だけ分解された)。
    現存する88星座はほぼ全て西洋文化で制定されたもの(大航海時代に発見された南半球の星座を含む)だが、他の文化圏でも星座は通常作られている。
    中国の二十八宿や、日本では北斗七星(七剣星)などが有名。
  2. 1の意味を素にして、複数の衛星で一つのミッションを行う人工衛星群のこと。
    伝統的に衛星は大型で高性能なものにより単一でミッションを行うのが主だった。
    しかし、近年小型の衛星の開発が進むにつれて、単一では大型のものに比べて機能は劣るが、互いに連携することで機能を補完する試みがされるようになってきた。
    これがコンステレーション衛星と呼ばれる。
    具体的な補完方法としては撮影頻度を向上させる、ネットワークを構築するなどがある。
    Space X社のStar linkやAxelspaceのGRUSなどが具体的なコンステレーション衛星の例としてはある。
model名 回答 コメント
gemma2:7b 星座は、空に見える星を二次元的に結びつけて絵を描いたものです。現在では88個の星座が存在します。古代ギリシャでプトレマイオスが制定した48星座が特に有名で、47星座が現在も残っています(アルゴ船座だけ分解されました)。現存する88星座はほぼ全て西洋文化で制定されたものですが、他の文化圏でも星座は通常作られています。中国の二十八宿や、日本では北斗七星(七剣星)などが有名です。また、複数の衛星が一つのミッションを遂行するために協力して動くグループのことを指すこともあります。 ほぼ星座に関しての説明ですね笑
gemma2:27b 人工衛星のグループで、複数の衛星が共同で一つのミッションを実行するものです。 \n\n従来は、衛星は大型で高性能であり、単一の衛星でミッションが行われていました。しかし、近年では小型衛星の開発が進み、個々の衛星の機能は大型のものに劣りますが、互いに補完し合うことで協調して動作する試みがなされています。これをコンステレーション衛星と呼びます。具体的な補完方法としては、観測頻度を増やすことやネットワークを構築することが挙げられます。\n\nコンステレーション衛星の具体的な例として、SpaceXのStarlinkやAxelspaceのGRUSがあります。 衛星に関してきちんと回答していますね、GRUSにも触れています、完璧です。(RAGにConstellationの例としてGRUSを入れていたため)
llama3 1. 星座は、全天に見える星を二次元的に結びつけて絵を描いたものです。現在では88個の星座が存在する。
2. 星座とは、複数の衛星が一つのミッションを実行するために協力して機能するグループのことを指します。従来は、大型で高性能の衛星が使われていたが、小型衛星の開発が進み、各衛星が互いに補完することで機能を強化する試みが行われている。このような衛星群を星座衛星と呼びます。具体的には、Space XのStarlinkやAxelspaceのGRUSなどが挙げられます。
間違ってませんが、惜しいですよね。Constellationという単語自体を全部星座と翻訳してるせいで読みづらいですね
phi3.5 星座のグループであるものです。古代ギリシャの人、プトレマイオスによって制定された48個の星座が特に有名で、現在までそのうち47個が残っています(アルゴ船座だけ分解された)。これらは西洋文化で制定されたものがほとんどであり、他の文化においても同様です(中国の二十八宿や日本の北斗七星など)。また、技術的進歩を背景にした現代では、小型衛星が連合体として機能し、例えばSpaceXのStarlinkやAxelspaceのGRUSがそれぞれ独自の目的を持つものとされます。 日本語が少し怪しいのかな?と感じる回答ですね、ただ精度としては非常に高いですね。
command-r 星座絵。星々を結ぶことで空全体に絵を描くもので、現在は88個 existence constellates があり、うち47個は古代ギリシャのプトレマイオスが制定したものである。また、人工衛星の群れを指すこともある。従来は大型高性能な衛星が1機で任務に当たっていたが、小型衛星の発達が進むにつれ、機能を補い合うために複数の衛星が協力して任務に当たるようになったものだ。具体例として、Space X の Starlink や Axelspace の GRUS がある。 日本語は怪しげですね、existence constellationを日本語に訳せてないですし、、、

上記より今回はgemma2:27bを使うことにしました。

RAG(Retrieval-Augmented Generation)とは

ChatGPTさんに聞きました。

「RAG(Retrieval-Augmented Generation)は、生成型AI(例えばLLM: 大規模言語モデル)の性能を強化するための技術です。このアプローチでは、AIモデルが質問に回答する際、外部の知識ベースやドキュメントから情報を取得して応答を生成します。

具体的には、通常の生成型AIは学習データに基づいて回答を作成しますが、RAGではリアルタイムに関連する情報を検索し、その情報を基にしてより正確で具体的な回答を提供します。このプロセスは以下のように進みます

1. クエリの入力: ユーザーからの質問や要求を入力。
2. 情報の検索: AIモデルがクエリに基づいて、外部のデータベースやドキュメント(PDF、ウェブページ、その他のデータソース)から関連情報を検索。
3. 回答の生成: 検索で得た情報をもとに、AIが回答を生成。

これにより、静的な情報に依存せず、動的に最新の情報やユーザーの指定したデータから回答が得られるため、応答の精度や信頼性が向上します。

この技術は、企業の内部ドキュメントや技術データベースなど、特定の知識領域を持つAIの構築に有効です。

さすがですね!
一言で言えば「AIモデルが学習していない外部から与えられるデータを使って回答できるようにする技術」と捉えています。
今回でいえば、社内用語が外部データで、「社内用語をLLMが読み取り回答する仕組み」がRAGですね。

実装について

1. ローカルDifyの構築

1.1 GitHubのDifyのリポジトリをclone

git clone https://github.com/langgenius/dify.git

1.2 ollamaを使う場合

dify/dockerディレクトリへ移動後、docker-compose.yamlを編集する

(変数省略)
services:
  # API service
  api:
    image: langgenius/dify-api:0.7.3
    restart: always
    environment:
      # Use the shared environment variables.
      <<: *shared-api-worker-env
      # Startup mode, 'api' starts the API server.
      MODE: api
    depends_on:
      - db
      - redis
    volumes:
      # Mount the storage directory to the container, for storing user files.
      - ./volumes/app/storage:/app/api/storage
    networks:
      - ssrf_proxy_network
      - default
      - langfuse_network

  # worker service
  # The Celery worker for processing the queue.
  worker:
    image: langgenius/dify-api:0.7.3
    restart: always
    environment:
      # Use the shared environment variables.
      <<: *shared-api-worker-env
      # Startup mode, 'worker' starts the Celery worker for processing the queue.
      MODE: worker
    depends_on:
      - db
      - redis
    volumes:
      # Mount the storage directory to the container, for storing user files.
      - ./volumes/app/storage:/app/api/storage
    networks:
      - ssrf_proxy_network
      - default
      - langfuse_network

  # Frontend web application.
  web:
    image: langgenius/dify-web:0.7.3
    restart: always
    environment:
      CONSOLE_API_URL: ${CONSOLE_API_URL:-}
      APP_API_URL: ${APP_API_URL:-}
      SENTRY_DSN: ${WEB_SENTRY_DSN:-}
      NEXT_TELEMETRY_DISABLED: ${NEXT_TELEMETRY_DISABLED:-0}

  # unstructured .
  # (if used, you need to set ETL_TYPE to Unstructured in the api & worker service.)
  unstructured:
    image: downloads.unstructured.io/unstructured-io/unstructured-api:latest
    profiles:
      - unstructured
    restart: always
    volumes:
      - ./volumes/unstructured:/app/data

  # このollamaの箇所を追記する
  ollama:
    restart: always
    image: ollama/ollama:latest
    container_name: ollama
    ports:
      - "11434:11434"
    volumes:
      - ollama:/root/.ollama

networks:
  # create a network between sandbox, api and ssrf_proxy, and can not access outside.
  ssrf_proxy_network:
    driver: bridge
    internal: true
  milvus:
    driver: bridge
  opensearch-net:
    driver: bridge
    internal: true
  langfuse_network:
    name: langfuse_network
    external: true

volumes:
  oradata:
  dify_es01_data:
  # ollamaのvolumeを指定
  ollama:

1.3 dockerでDifyを起動

dify/dockerディレクトリで、以下のコマンドで起動します。

docker-compose up -d

起動すると、http://localhostでアクセスできるようになり、http://localhost/installで初期化のプロセスを実行できます。

2. モデルの設定

モデル一覧の画面(個人の設定 -> モデルプロバイダーで選択)から、モデルを選んでセットアップを選んで、API Keyを入力
モデル一覧

セットアップの画面
alt text

3. ナレッジの設定

3.1 Difyの画面上部にあるナレッジを選択

  • Dify上部のタブ
    alt text

3.2 ナレッジを作成でナレッジを作成

  • ナレッジ作成
    alt text

今回は、アクセルスペース用語集として作成されたAxelGlossary.xlsxを登録しました。

  • 登録画面
    alt text

  • 登録時の前処理画面
    alt text
    処理するとこんな感じでチャンクが作成されますというプレビューが出ます

エクセルやcsvなどは、基本的にはQuestion-Answer形式にした方がいいとのことでした。(例: https://it.impress.co.jp/articles/-/25842

4. ワークフローの設定

今回はチャットボットを作成します。

4.1 Difyの上部のタブのスタジオを選択

alt

4.2 一番左にある最初から作成をクリック

alt text

4.3 チャットボットをクリック

alt text

4.4 適当に名前をつけて、Chatflowを選んだら、作成するボタンをクリック

4.5 以下の画面が出てきたらOKです。(モデルはシステムモデルに選択しているものが表示されます、何も選択されていなければgpt-3.5-turboが選ばれています。)

alt text

4.6 登録したナレッジを使いたいので、開始LLMの間の+ボタンを押して、知識習得のノードを追加します。

alt text

4.7 ナレッジの右端にある+ボタンをクリックして、AxelGlossary.xlsxを選び、追加ボタンをクリック

alt text

4.8 LLMノードを選んで設定する

  • モデル
    モデルプロバイダで設定したモデルが使用できる

  • コンテキスト
    先ほど設定した知識習得ノードのresultを選ぶ
    alt text

  • SYSTEM(システムプロンプト)
    alt text
    この時、/から始めると、sys.query(チャットの開始の質問)などを選択できます。(以下のプロンプトの例の中の{{#sys.query#}}と書かれている箇所)

    # 役割
      あなたはAxelspace社内のことを何でも知っている従業員です。事前知識ではなく、常に以下のコンテキスト情報に基づいて回答をしてください。
    
      # 従うべきルール
      1. まずは英語でセマンティック検索を用いて、コンテキストのみに基づいた最も関連性の高い情報を抽出してください。
      2. コンテキストに存在しない単語を推測することは禁止します。
      3. 回答内で指定されたコンテキストを直接参照しないでください。
      4.「コンテキストに基づいて、...」や「コンテキスト情報は...」、またはそれに類するような記述は避けてください。
      4. コンテキストに存在しない質問は、「知識が足らず分かりません」と回答してください。
      5. 最終的な回答は{{#Start/lang#}}の言語に翻訳してください。
      6. 質問の単語が略称の場合は正式名称を回答に含めてください。
      7.  回答にURLが含まれている場合は、URLも含めてください。
      8.  複数の単語が指定されたときは、知っているもののみ箇条書きで回答し、それ以外の単語は羅列した上でわからないことを伝えてください。
      9.  クエリが英語の場合は、小文字大文字混ぜて検索してください。
    
      クエリ: {{#sys.query#}}
      コンテキスト: {{#context#}}
    

4.9 このワークフローの左側のメニューのAPIアクセスをクリックすると、右上にAPIkキーという項目があるのでそれをクリック

alt text

4.10 新しいシークレットキーを作成でAPIキーが表示されるのでこれを保管する

5. Slack Appの作成

5.1 Slack Appへアクセス

5.2 右上のCreate New Appからアプリを新しく作成する

alt text

5.3 左側のメニューのSocket Modeを開いて、真ん中のEnable Socket ModeをOnにする

alt text

5.4 Onにすると、以下のようにtokenについて情報を入力する画面が出てくるので、Token Nameと、Scopeを設定する(今回のscopeはconnections:writeのみで問題ない)

alt text

5.5 左側のメニューからOAuth & Permissionを選ぶ

alt text

5.6 下に行くとBotのScopesを選択する箇所が出てくるので、Scopeを選択する

  • 必要なスコープは以下の通り
    • app_mentions:read
    • channel:history
    • chat:write
  • 今回は誰が質問をしたのかをトレースするためにuser情報を取得している
    • users.profile:read
    • users:read
      alt text

5.7 6まで完了したら左のメニューのInstall AppからSlackのワークスペースにInstallする

6. Slack Appの実装

今回は、TypeScript(Node.js)で実装しました。

Slack Bolt

  • 必要なパッケージ

    • @slack/bolt
    • 何かしらのビルドツール(viterollupなど)or tsのまま動かせるパッケージ(ts-nodetsxなど)
  • お好み

    • dotenv
      • 環境変数を取得するために.envファイルから取得するために今回は使用
  • 使用するトークン

    • Slackのwebソケットのトークン(作成)
    • SlackのBotトークン(作成
    • DifyのAPIキー(作成
6.1 TypeScriptのプロジェクトを作る
npx tsc --init

tsconfig.jsonの中身ですが、今回はviteを使ってビルドするので以下の内容です。

{
  "compilerOptions": {
    // Enable latest features
    "lib": ["ESNext", "DOM"],
    "target": "ESNext",
    "module": "ESNext",
    "moduleDetection": "force",
    "jsx": "react-jsx",
    "allowJs": true,

    // Bundler mode
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "verbatimModuleSyntax": true,
    "noEmit": true,

    // Best practices
    "strict": true,
    "skipLibCheck": true,
    "noFallthroughCasesInSwitch": true,

    // Some stricter flags (disabled by default)
    "noUnusedLocals": false,
    "noUnusedParameters": false,
    "noPropertyAccessFromIndexSignature": false
  }
}

6.2 npmで@slack/boltのインストールをする

npm i -D @slack/bolt

6.3 ビルドに関して

先ほどビルドツールはviteを使うと話しましたが、cite.config.tsの内容は以下の通りです。

// vite.config.ts
import { defineConfig } from "vite";
import { resolve } from 'path';

export default defineConfig({
  build: {
    lib: {
      entry: resolve(__dirname, 'src/index.ts'),
      name: 'CloudFunctionBundle',
      fileName: 'main',
      formats: ['cjs'],
    },
    rollupOptions: {
      external: ['window'],
    },
    target: 'node20',
    ssr: true,
    sourcemap: false,
    commonjsOptions: {
      transformMixedEsModules: true,
    },
  },
  resolve: {
    alias: {
      '@': resolve(__dirname, 'src'),
    },
  },
  optimizeDeps: {
    include: ['@slack/bolt'],
  },
});

6.4 Slack/boltの実装

今回はメンションを使ってメッセージを受け取りたいため、app_mentionを使います。

以下の内容で実装すると、質問内容をそのまま返します。

// index.ts
import { App, LogLevel } from "@slack/bolt";
import dotenv from 'dotenv';

dotenv.config();

const token = process.env.SLACK_BOT_TOKEN;
const signingSecret = process.env.SLACK_SIGNING_SECRET;
const appToken = process.env.SLACK_APP_TOKEN;

const numConnections = 3;

const apps = [];

if (!token || !signingSecret || !appToken) {
  throw new Error('SLACK_BOT_TOKEN and SLACK_SIGNING_SECRET and SLACK_APP_TOKEN must be set in environment variables');
}

for (let i = 0; i < numConnections; i++) {
  apps.push(new App({
    token: token,
    signingSecret: signingSecret,
    socketMode: true,
    appToken: appToken,
    logger: logger,
  }));
}

apps.forEach(async (app) => {
  app.event('app_mention', async ({ event, client, say }) => {
    try {
      const message = event.text.replace(/<@[^>]+>\s*/, '').trim();
      const userId = event.user || "";
      const userInfo = await client.users.info({
        user: userId,
        include_locale: true
      });
      const userName = userInfo.user?.name || 'Unknown';
      let locale = userInfo.user?.locale?.substring(0, 2) || 'en';

      let question = message;
      if (message.includes('lang:')) {
        const langMatch = message.match(/lang:([a-z]{2})/);
        if (langMatch) {
          locale = langMatch[1];
        }
        question = message.replace(/lang:[a-z]{2}/, '').trim();
      }

      await say({
        text: question,
        thread_ts: event.ts
      });
    } catch (error) {
      console.error('Error processing Slack mention:', error);
      let errorMessage = 'エラーが発生しました。しばらくしてからもう一度お試しください。';
      if (error instanceof Error) {
        errorMessage += ` (${error.message})`;
      }
      await say({
        text: errorMessage,
        thread_ts: event.ts
      });
    }
  });

  app.error(async (error) => {
    console.error('An error occurred:', error);
    
    // 特定のエラーに対して再接続を試みるロジック
    if (error.message.includes('server explicit disconnect')) {
      console.log('Attempting to reconnect...');
      // 再接続ロジック(適宜実装)
      await app.start();
    }
  });
});

(async () => {
  for (let i = 0; i < numConnections; i++) {
    try {
      await apps[i].start();
      console.log('⚡️ Bolt app is running!');
    } catch (error) {
      console.error('Error starting Bolt app:', error);
    }
  }
})();

6.5 difyとのやり取りの実装

DifyはAPIが整っており、質問して答えるchat-messageなどのAPIが用意されています。
今回はchat-messageを使っていきたいと思います。

chatの場合のAPIはhttps://xxx/v1/chat-messageshttps://xxxはDifyのホスト名)

import axios from "axios";
import dotenv from "dotenv";

dotenv.config();

const difyHost = process.env.DIFY_HOST || "";
if (!difyHost) {
  throw new Error('DIFY_HOST must be set in environment variables');
}

export async function sendMessageToDify(
  apiKey: string,
  message: string, 
  userName: string,
  lang: string,
  apiEndpoint: string = `http://${difyHost}/v1`,
): Promise<any> {
  try {
    const response = await axios.post(
      `${apiEndpoint}/chat-messages`,
      {
        inputs: { lang: lang },
        query: message,
        response_mode: 'blocking',
        conversation_id: null, // 新しい会話を開始
        user: userName
      },
      {
        headers: {
          'Authorization': `Bearer ${apiKey}`,
          'Content-Type': 'application/json',
        }, 
        timeout: 1800000,
      }
    );

    return response.data;
  } catch (error) {
    if (axios.isAxiosError(error)) {
      if (error.code === 'ETIMEDOUT') {
        console.error('Request to Dify API timed out');
        throw new Error('Dify API request timed out. Please try again later.');
      } else if (error.response) {
        console.error('Dify API responded with an error:', error.response.status, error.response.data);
        throw new Error(`Dify API error: ${error.response.status}`);
      } else if (error.request) {
        console.error('No response received from Dify API');
        throw new Error('No response received from Dify API. Please check your network connection.');
      }
    }
    console.error('Error sending message to Dify:', error);
    throw error;
  }
}

実際の動作

SlackでConstellation?と質問してみました。

  • 日本語の場合(自分はSlackの言語設定を日本語にしているので日本語で回答してくれます)
    alt text

  • 英語の場合
    alt text

課題

Slack BoltのWebSocket通信が勝手に切断されて異常終了する

接続が1つだと切れて、dockerコンテナ自体が落ちるので、接続を3つ用意することでどれか一つが落ちても他の接続で対応するようにしました

  • 実際の接続が切れたログ

    [INFO]   Going to establish a new connection to Slack ...
    [INFO]   Now connected to Slack
    ⚡️ Bolt app is running!
    /app/node_modules/finity/lib/core/StateMachine.js:76
          throw new Error('Unhandled event \'' + event + '\' in state \'' + this.currentState + '\'.');
                ^
    
    Error: Unhandled event 'server explicit disconnect' in state 'connecting'.
        at StateMachine.handleUnhandledEvent (/app/node_modules/finity/lib/core/StateMachine.js:76:13)
        at /app/node_modules/finity/lib/core/HierarchicalStateMachine.js:79:33
        at TaskScheduler.execute (/app/node_modules/finity/lib/core/TaskScheduler.js:29:7)
        at TaskScheduler.enqueue (/app/node_modules/finity/lib/core/TaskScheduler.js:19:12)
        at HierarchicalStateMachine.handle (/app/node_modules/finity/lib/core/HierarchicalStateMachine.js:72:24)
        at SocketModeClient.onWebSocketMessage (/app/node_modules/@slack/socket-mode/dist/SocketModeClient.js:608:31)
        at WebSocket.onMessage (/app/node_modules/ws/lib/event-target.js:132:16)
        at WebSocket.emit (node:events:520:28)
        at Receiver.receiverOnMessage (/app/node_modules/ws/lib/websocket.js:1070:20)
        at Receiver.emit (node:events:520:28)
    
    Node.js v22.8.0
    

データセットにない質問を適当に回答してしまう

これはLLMあるあるですね。
今回は、社内用語にないものは回答してほしくないので、Temperatureを0にしています。
また、システムコマンドには、コンテキストにないものは「知らない」と回答するよう指定しました。

  • 例:
    • 質問: APD?
    • 正しい回答: 知識が足らず分かりません。
    • 実際の回答: 高感度デジタルピンホイール - 光を利用して信号強度の変化を検出するセンサ。

英語だと少し短めの文章になる(未解決)

現状、英語だと短めの文章で回答されます。
日本語だと割とちゃんと回答されているので、要約されないような形にしたいのですが、まだここはチューニング中になります。

Difyの注釈機能で回答を生成する場合に翻訳してくれない(未解決)

Difyの注釈機能を使うと、同様の質問が今後来た際に注釈の内容を回答としてくれます。なのでLLMを使わないため回答速度が速いです。
ただ、注釈の内容をそのまま答えるだけなので、今回みたいに翻訳した結果が欲しい場合は翻訳せず答えるので現状はすぐには使えないですね。

  • 例: 「Constellation?」と質問した場合

    • 元々の回答
      alt text

    • 「Constellation?」という質問に「Constellation」と回答するよう注釈をつけた場合
      alt text

番外編: 注釈の使い方

Difyのスタジオの画面のログ&アナウンスから注釈をつけることができます。

  • 注釈の追加画面
    alt text

  • 注釈の有効化は、右端にある注釈の返信をOnにして、embeddingモデルを設定すればOK
    alt text

さいごに

今回は初回お試し版みたいな感じで構築していきましたが、今後はより発展できるよう以下のことに取り組んでいきたいと思います。

  1. RAGパイプラインの構築
  2. LLMの回答速度の向上
  3. 社内ルールなどに対応できるAIアプリの構築
Axelspace

Discussion