aws-samples / generative-ai-use-cases-jp

すぐに業務活用できるビジネスユースケース集付きの安全な生成AIアプリ実装
MIT No Attribution
546 stars 96 forks source link

RAGチャットからkendraのFAQSを利用する方法について #173

Open tetuya-iyell opened 8 months ago

tetuya-iyell commented 8 months ago

kendraのFAQSを利用して、RAGチャットの回答を生成できるようにしていただけると助かります 個人的に色々と試していのですが、今のところ解決ができてないので、出来れば機能として実装いただけると助かります

よろしくお願いします

wadabee commented 8 months ago

お問い合わせありがとうございます! 現在、RAGチャット時のKendra検索は、Retrieve APIを利用しているためFAQが検索できない仕様となっております。

https://docs.aws.amazon.com/ja_jp/kendra/latest/APIReference/API_Retrieve.html

This doesn't include question-answer or FAQ type responses from your index.

Query APIだとFAQを検索できますので、以下のように実装いただければと思います。

https://github.com/aws-samples/generative-ai-use-cases-jp/blob/7d206325587016b4022e9a40ceb375eadf7cd199/packages/web/src/hooks/useRag.ts#L46

      const faq = await (
        await query(searchQuery)
      ).data.ResultItems?.filter((item) => item.Type === 'QUESTION_ANSWER'); // <= これを追加して、referenceItemsに渡す

https://github.com/aws-samples/generative-ai-use-cases-jp/blob/7d206325587016b4022e9a40ceb375eadf7cd199/packages/web/src/prompts/index.ts#L166-L170

export type RagParams = {
  promptType: 'RETRIEVE' | 'SYSTEM_CONTEXT';
  retrieveQueries?: string[];
  referenceItems?: (RetrieveResultItem | QueryResultItem)[];
};

https://github.com/aws-samples/generative-ai-use-cases-jp/blob/7d206325587016b4022e9a40ceb375eadf7cd199/packages/web/src/prompts/index.ts#L214-L224

// 以下のように修正
${params
  .referenceItems!.map((item) => {
    return `${JSON.stringify({
      DocumentId: item.DocumentId,
      DocumentTitle: item.DocumentTitle,
      DocumentURI: item.DocumentURI,
      Content: (item as RetrieveResultItem).Content
        ? (item as RetrieveResultItem).Content
        : (item as QueryResultItem).AdditionalAttributes?.find(
            (a) => a.Key === 'AnswerText'
          ),
    })}`;
  })
  .join(',\n')}

FAQの正式対応については、検討させていただきます!

tetuya-iyell commented 8 months ago

お世話になっております ご教授ありがとうございます

いただきました、アドバイスについて一点、ご教授をお願いできますと幸いです https://github.com/aws-samples/generative-ai-use-cases-jp/blob/7d206325587016b4022e9a40ceb375eadf7cd199/packages/web/src/hooks/useRag.ts#L46 上記のコードの部分を以下の通りに変更をするという理解でよろしかったでしょうか

      const faq = await (
        await query(searchQuery)
      ).data.ResultItems?.filter((item) => item.Type === 'QUESTION_ANSWER'); // <= これを追加して、referenceItemsに渡す

上記のコードを46行目に差し込みますと、VS Studio Codeで添付の画像のような形でErrorが発生するのですがこれはどの様に対応すればよいかご教授いただけますと幸いです スクリーンショット 2023-11-20 18 42 33

どうぞよろしくお願い申し上げます

wadabee commented 8 months ago

以下のように修正いただくとどうでしょうか?

      // Kendra から 参考ドキュメントを Retrieve してシステムコンテキストとして設定する
      const retrievedItems = await retrieve(searchQuery);
      const faq = await (
        await query(searchQuery)
      ).data.ResultItems?.filter((item) => item.Type === 'QUESTION_ANSWER');

      updateSystemContext(
        ragPrompt({
          promptType: 'SYSTEM_CONTEXT',
          referenceItems: [
            ...(retrievedItems.data.ResultItems ?? []),
            ...(faq ?? []),
          ],
        })
      );
tetuya-iyell commented 8 months ago

早速のご教授、ありがとうございます! いただきましたコードを既存のコードと差し替えますと、添付の様な状況になります ご確認とご教授いただけますと幸いです よろしくお願い申し上げます

スクリーンショット 2023-11-21 9 41 40

wadabee commented 8 months ago

小出しにしてすみません! 以下が全文です。こちらでいかがでしょうか?

import useChat from './useChat';
import useChatApi from './useChatApi';
import useRagApi from './useRagApi';
import { ragPrompt } from '../prompts';

const useRag = (id: string) => {
  const {
    messages,
    postChat,
    clear,
    loading,
    setLoading,
    updateSystemContext,
    popMessage,
    pushMessage,
    isEmpty,
  } = useChat(id);

  const { retrieve, query } = useRagApi();
  const { predict } = useChatApi();

  return {
    isEmpty,
    clear,
    loading,
    messages,
    postMessage: async (content: string) => {
      // Kendra から Retrieve する際に、ローディング表示する
      setLoading(true);
      pushMessage('user', content);
      pushMessage('assistant', '[Kendra から参照ドキュメントを取得中...]');

      const searchQuery = await predict({
        messages: [
          {
            role: 'user',
            content: ragPrompt({
              promptType: 'RETRIEVE',
              retrieveQueries: [content],
            }),
          },
        ],
      });

      // Kendra から 参考ドキュメントを Retrieve してシステムコンテキストとして設定する
      const retrievedItems = await retrieve(searchQuery);
      const faq = await (
        await query(searchQuery)
      ).data.ResultItems?.filter((item) => item.Type === 'QUESTION_ANSWER');

      updateSystemContext(
        ragPrompt({
          promptType: 'SYSTEM_CONTEXT',
          referenceItems: [
            ...(retrievedItems.data.ResultItems ?? []),
            ...(faq ?? []),
          ],
        })
      );

      // ローディング表示を消してから通常のチャットの POST 処理を実行する
      popMessage();
      popMessage();
      postChat(content);
    },
  };
};

export default useRag;
tetuya-iyell commented 8 months ago

ありがとうございます! itemの型が無いので、Errorになっているようなのですが、以下の様に変更しても良いでしょうか

スクリーンショット 2023-11-21 10 01 59

上記の様に変更をすると、Errorは全て解消できます ご教授をよろしくお願い申し上げます

wadabee commented 8 months ago

私の貼ったコードはあくまで実装例ですので、ご自由に改廃いただいて問題ございません!

tetuya-iyell commented 8 months ago

ありがとうございます! いただきましたコードでdeployをして、後ほど、結果をご報告させて頂きます ご教授ありがとうございます

tetuya-iyell commented 8 months ago

お世話になっております いただきました、コードを改変してdeployをした所、RAGチャットでFAQsの内容を確認することができました

const retrievedItems = await retrieve(searchQuery);
      const faq = await (
        await query(searchQuery)
      ).data.ResultItems?.filter((item: {Type: string}) => item.Type === 'QUESTION_ANSWER'); //この部分
  ${params
    .referenceItems!.map((item) => {
      return `${JSON.stringify({
        DocumentId: item.DocumentId,
        DocumentTitle: item.DocumentTitle,
        DocumentURI: item.DocumentURI,
        Content: (item as RetrieveResultItem).Content
          ? (item as RetrieveResultItem).Content
          : (item as QueryResultItem).AdditionalAttributes?.find(
              (a: { Key: string }) => a.Key === 'AnswerText' //この部分
            ),
      })}`;
    })
    .join(',\n')}

上記の「この部分」で型宣言をすると、CDKがErrorになったので、型宣言を削除するとcdk deployも問題なく行えました ぜひ、本機能の実装についても前向きなご検討をいただけますと幸いです

この度はご指導、誠にありがとうございました 引き続きよろしくお願い申し上げます

tetuya-iyell commented 6 months ago

@wadabee お世話になっております 本件について、改めて質問をさせてください

最新のgenerative-ai-use-cases-jpのリポジトリでkendraのfaqsを利用できるように、ご教授いただきましたコードの改変をおこなったのですが、index.tsで添付の通りのErrorとなってしまいます スクリーンショット 2024-01-16 19 52 35

スクリーンショット 2024-01-16 19 52 49

一旦、この状態で、cloud9から以下のコマンドでcdkをbuildしたのですが、いかの様なErrorになってしまいます 誠にお忙しいところ、恐縮ですがこれはどのようにすれば解決ができますでしょうか

ご指導をいただけますと助かります どうぞよろしくお願いもうしあげます

[cloudwatchに記録されたError] スクリーンショット 2024-01-16 19 56 31

Nasubikun commented 5 months ago

上で提示されている実装とは異なる部分がいくつかありますが、以下の実装でFAQを含むRAGが行えますので参考にしていただければと思います。aws-samples/generative-ai-use-cases-jp/packages/web/src/hooks/useRag.tsを以下に置き換えてみてください。

import { RetrieveResultItem } from '@aws-sdk/client-kendra';
import { Model, ShownMessage } from 'generative-ai-use-cases-jp';
import { ragPrompt } from '../prompts';
import useChat from './useChat';
import useChatApi from './useChatApi';
import useRagApi from './useRagApi';

const useRag = (id: string) => {
  const {
    messages,
    postChat,
    clear,
    loading,
    setLoading,
    updateSystemContext,
    popMessage,
    pushMessage,
    isEmpty,
  } = useChat(id);

  const { retrieve, query } = useRagApi();
  const { predict } = useChatApi();

  return {
    isEmpty,
    clear,
    loading,
    messages,
    postMessage: async (content: string, model: Model) => {
      // Kendra から Retrieve する際に、ローディング表示する
      setLoading(true);
      pushMessage('user', content);
      pushMessage('assistant', 'Kendra から参照ドキュメントを取得中...');

      const queryContent = await predict({
        model: model,
        messages: [
          {
            role: 'user',
            content: ragPrompt.generatePrompt({
              promptType: 'RETRIEVE',
              retrieveQueries: [content],
            }),
          },
        ],
      });

      // Kendra から 参考ドキュメントを Retrieve してシステムコンテキストとして設定する
      const retrieveItemsp = retrieve(queryContent);
      const queryItemsp = query(queryContent)
      const [retrieveItems, queryItems] = await Promise.all([retrieveItemsp, queryItemsp])
      console.log({
        retrieveResult: retrieveItems
      })
      console.log({
        queryResult: queryItems
      })
      const faqs: RetrieveResultItem[] = queryItems.data.ResultItems?.filter((item) => item.Type === 'QUESTION_ANSWER').map((item)=>{
        const res = {
          Content: item.DocumentExcerpt?.Text || "",
          Id: item.Id,
          DocumentId: item.DocumentId,
          DocumentTitle: item.DocumentTitle?.Text || "",
          DocumentURI: item.DocumentURI,
          DocumentAttributes: item.DocumentAttributes,
          ScoreAttributes: item.ScoreAttributes
        }
        console.log({
          faq:res
        })
        return res
      })||[];

      if ((retrieveItems.data.ResultItems ?? []).length === 0) {
        popMessage();
        pushMessage(
          'assistant',
          `参考ドキュメントが見つかりませんでした。次の対応を検討してください。
- Amazon Kendra の data source に対象のドキュメントが追加されているか確認する
- Amazon Kendra の data source が sync されているか確認する
- 入力の表現を変更する`
        );
        setLoading(false);
        return;
      }

      updateSystemContext(
        ragPrompt.generatePrompt({
          promptType: 'SYSTEM_CONTEXT',
          referenceItems: [...retrieveItems.data.ResultItems!, ...faqs!] ?? [],
        })
      );

      // ローディング表示を消してから通常のチャットの POST 処理を実行する
      popMessage();
      popMessage();
      postChat(
        content,
        false,
        model,
        (messages: ShownMessage[]) => {
          // 前処理:Few-shot で参考にされてしまうため、過去ログから footnote を削除
          return messages.map((message) => ({
            ...message,
            content: message.content.replace(/\[\^(\d+)\]:.*/g, ''),
          }));
        },
        (message: string) => {
          // 後処理:Footnote の付与
          const footnote = retrieveItems.data.ResultItems?.map((item, idx) => {
            // 参考にしたページ番号がある場合は、アンカーリンクとして設定する
            const _excerpt_page_number = item.DocumentAttributes?.find(
              (attr) => attr.Key === '_excerpt_page_number'
            )?.Value?.LongValue;
            return message.includes(`[^${idx}]`)
              ? `[^${idx}]: [${item.DocumentTitle}${
                  _excerpt_page_number ? `(${_excerpt_page_number} ページ)` : ''
                }](${item.DocumentURI}${
                  _excerpt_page_number ? `#page=${_excerpt_page_number}` : ''
                })`
              : '';
          })
            .filter((x) => x)
            .join('\n');
          return message + '\n' + footnote;
        }
      );
    },
  };
};

export default useRag;

動作確認用のconsole.log()を三箇所あえて残しておりますので、適宜削除いただければと思います。また、FAQにおけるQuestion側をコンテキストに含めるかどうかなど、上記実装に関してまだまだ考慮点はあるかと思いますので適宜修正ください。

以上、参考になれば幸いです。