From dc666c7b216da96e4c3efc38b9087ccdb72d0752 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Mon, 8 Jul 2024 07:41:12 +0000 Subject: [PATCH 1/3] test: Split integration test cases of RAG --- test/integration/slack_events/rag_test.rb | 324 ++++++++++++++++++++++ test/integration/slack_events_test.rb | 318 --------------------- 2 files changed, 324 insertions(+), 318 deletions(-) create mode 100644 test/integration/slack_events/rag_test.rb diff --git a/test/integration/slack_events/rag_test.rb b/test/integration/slack_events/rag_test.rb new file mode 100644 index 0000000..964eaea --- /dev/null +++ b/test/integration/slack_events/rag_test.rb @@ -0,0 +1,324 @@ +require "test_helper" +require "utils" + +class SlackEventsRagTest < ActionDispatch::IntegrationTest + include ActiveJob::TestHelper + include SlackTestHelper + + test "app_mention event from a channel for Magellan RAG" do + slack_ts = Time.now.to_f.to_s + channel = conversations(:magellan_rag) + user = users(:one) + query_body = "ZZZ" + query = "<@TEST_BOT_ID> #{query_body}" + + any_instance_of(SlackBot::Application) do |klass| + stub(klass).magellan_rag_channel? do |ch| + ch.slack_id == channel.slack_id + end + end + + stub_slack_api(:post, "chat.postMessage").to_return(body: { "ok" => true, "ts" => Time.now.to_f.to_s }.to_json) + + # Check the case when missing reactions:write scope + stub_slack_api(:post, "reactions.add").to_return { raise Slack::Web::Api::Errors::MissingScope, "missing_scope" } + stub_slack_api(:post, "reactions.remove").to_return { raise Slack::Web::Api::Errors::MissingScope, "missing_scope" } + + assert Message.find_by(conversation: channel, user: user, slack_ts: slack_ts).blank? + + assert_enqueued_with(job: MagellanRagQueryJob) do + params = { + type: "event_callback", + event: { + type: "app_mention", + text: query, + channel: channel.slack_id, + user: user.slack_id, + ts: slack_ts + } + } + request_body = ActionDispatch::RequestEncoder.encoder(:json).encode_params(params) + timestamp = slack_ts.to_i + headers = { + "X-Slack-Request-Timestamp": timestamp, + "X-Slack-Signature": compute_request_signature(timestamp, request_body) + } + + post "/slack/events", params:, headers:, as: :json + end + + message = Message.find_by!(conversation: channel, user: user, slack_ts: slack_ts) + assert_equal([ + query_body, + slack_ts, + ], + [ + message.text, + message.slack_thread_ts, + ]) + + mock(Utils::MagellanRAG).endpoint do + "http://report-rag-api.test" + end + + rag_answer = <<~END_ANSWER + 花王株式会社_エッセンシャルの事例では、TVCMの残存週が他社(11週)と比べて13週と長いと報告されています。 + 詳細は以下のファイルで確認できます。 + - ファイル名: 【花王様】エッセンシャル_初回レポート報告_20221125.pdf + - ファイルのURL: [こちら](https://drive.google.com/file/d/1MB_IerrxHZ_Dn3ziT7vPixaOF_ng84D3/preview?authuser=0) + - 該当部分: 「15秒・30秒・60秒ともに他施策と比べて効率は良好。残存週は他社(11週)と比べて、13週と長い。」 + + 花王株式会社_ブランド横断の事例では、TVCMの残存週が業界傾向値(11週)と比べて13週と長いと報告されています。 + 詳細は以下のファイルで確認できます。 + - ファイル名: 【花王様】横断レポート_20230116.pdf + - ファイルのURL: [こちら](https://drive.google.com/file/d/1_t9ldOf-KcHxtC2fUS3sqCEJm8HwxMmU/preview?authuser=0) + - 該当部分: 「残存週は業界傾向値(11週)と比べて、13週と長い。」 + END_ANSWER + rag_response = {"answer": rag_answer} + + stub_request( + :get, "http://report-rag-api.test/generate_answer" + ).with( + query: {"query" => "ZZZ"} + ).to_return_json( + status: 200, + body: rag_response + ) + + expected_answer = rag_answer + expected_response = "<@#{user.slack_id}> 次の2件の文書が質問に該当する可能性があります。\n\n#{expected_answer}" + expected_answer_blocks = [ + { + "type" => "section", + "text" => { + "type" => "mrkdwn", + "text" => "<@#{user.slack_id}> 次の2件の文書が質問に該当する可能性があります。" + } + }, + { + "type" => "rich_text", + "elements" => [ + { + "type" => "rich_text_section", + "elements" => [ + "type" => "text", + "text" => "花王株式会社_エッセンシャルの事例では、TVCMの残存週が他社(11週)と比べて13週と長いと報告されています。\n詳細は以下のファイルで確認できます。" + ] + }, + { + "type" => "rich_text_list", + "style" => "bullet", + "indent" => 0, + "elements" => [ + { + "type" => "rich_text_section", + "elements" => [ + {"type" => "text", "text" => "ファイル: "}, + { + "type" => "link", + "url" => "https://drive.google.com/file/d/1MB_IerrxHZ_Dn3ziT7vPixaOF_ng84D3/preview?authuser=0", + "text" => "【花王様】エッセンシャル_初回レポート報告_20221125.pdf" + } + ] + }, + { + "type" => "rich_text_section", + "elements" => [ + { + "type" => "text", + "text" => "該当部分: 「15秒・30秒・60秒ともに他施策と比べて効率は良好。残存週は他社(11週)と比べて、13週と長い。」" + } + ] + } + ] + } + ] + }, + {"type" => "divider"}, + { + "type" => "rich_text", + "elements" => [ + { + "type" => "rich_text_section", + "elements" => [ + "type" => "text", + "text" => "花王株式会社_ブランド横断の事例では、TVCMの残存週が業界傾向値(11週)と比べて13週と長いと報告されています。\n詳細は以下のファイルで確認できます。" + ] + }, + { + "type" => "rich_text_list", + "style" => "bullet", + "indent" => 0, + "elements" => [ + { + "type" => "rich_text_section", + "elements" => [ + {"type" => "text", "text" => "ファイル: "}, + { + "type" => "link", + "url" => "https://drive.google.com/file/d/1_t9ldOf-KcHxtC2fUS3sqCEJm8HwxMmU/preview?authuser=0", + "text" => "【花王様】横断レポート_20230116.pdf" + } + ] + }, + { + "type" => "rich_text_section", + "elements" => [ + { + "type" => "text", + "text" => "該当部分: 「残存週は業界傾向値(11週)と比べて、13週と長い。」" + } + ] + } + ] + } + ] + }, + {"type" => "divider"}, + ] + + perform_enqueued_jobs + + assert_response :success + + actual_body = nil + assert_slack_api_called(:post, "chat.postMessage") do |request| + actual_body = decode_slack_client_request_body(request.body) + end + + query = Query.find_by!(message: message) + assert_kind_of Hash, query.body + response = Response.find_by!(query: query) + assert_kind_of Hash, response.body + + assert_equal( + { + "blocks" => [ + *expected_answer_blocks, + feedback_action_block + ], + "channel" => channel.slack_id, + "text" => expected_response, + "thread_ts" => slack_ts, + }, + actual_body + ) + end + + + test "app_mention event from a channel for Magellan RAG with --retrieval option" do + skip "TODO" + slack_ts = Time.now.to_f.to_s + channel = conversations(:magellan_rag) + user = users(:one) + query_body = "ZZZ" + query = "<@TEST_BOT_ID> --retrieval #{query_body}" + + any_instance_of(SlackBot::Application) do |klass| + stub(klass).magellan_rag_channel? do |ch| + ch.slack_id == channel.slack_id + end + end + + stub_slack_api(:post, "chat.postMessage").to_return(body: { "ok" => true, "ts" => Time.now.to_f.to_s }.to_json) + + # Check the case when missing reactions:write scope + stub_slack_api(:post, "reactions.add").to_return { raise Slack::Web::Api::Errors::MissingScope, "missing_scope" } + stub_slack_api(:post, "reactions.remove").to_return { raise Slack::Web::Api::Errors::MissingScope, "missing_scope" } + + assert Message.find_by(conversation: channel, user: user, slack_ts: slack_ts).blank? + + assert_enqueued_with(job: MagellanRagQueryJob) do + params = { + type: "event_callback", + event: { + type: "app_mention", + text: query, + channel: channel.slack_id, + user: user.slack_id, + ts: slack_ts + } + } + request_body = ActionDispatch::RequestEncoder.encoder(:json).encode_params(params) + timestamp = slack_ts.to_i + headers = { + "X-Slack-Request-Timestamp": timestamp, + "X-Slack-Signature": compute_request_signature(timestamp, request_body) + } + + post "/slack/events", params:, headers:, as: :json + end + + message = Message.find_by!(conversation: channel, user: user, slack_ts: slack_ts) + assert_equal([ + query_body, + slack_ts, + ], + [ + message.text, + message.slack_thread_ts, + ]) + + mock(Utils::MagellanRAG).endpoint do + "http://report-rag-api.test" + end + + stub_request( + :get, "http://report-rag-api.test/retrieve_documents" + ).with( + query: {"query" => "ZZZ"} + ).to_return_json( + status: 200, + body: [ + { + "metadata" => { + "company_name" => "Xica", + "file_name" => "xica_report.pdf", + "file_url" => "https://example.com/xyzzy/xica_report.pdf", + }, + "content" => "ABC" + } + ] + ) + + expected_response = <<~END_BODY + <@#{user.slack_id}> # Doc-0: Xica + * file_name = xica_report.pdf + * file_url = https://example.com/xyzzy/xica_report.pdf + ABC + END_BODY + + perform_enqueued_jobs + + assert_response :success + + actual_body = nil + assert_slack_api_called(:post, "chat.postMessage") do |request| + actual_body = decode_slack_client_request_body(request.body) + end + + query = Query.find_by!(message: message) + assert_kind_of Hash, query.body + response = Response.find_by!(query: query) + assert_kind_of Hash, response.body + + assert_equal( + { + "channel" => channel.slack_id, + "text" => expected_response, + "thread_ts" => slack_ts, + "blocks" => [ + { + "type" => "section", + "text" => { + "type" => "mrkdwn", + "text" => expected_response, + } + }, + feedback_action_block + ] + }, + actual_body + ) + end +end diff --git a/test/integration/slack_events_test.rb b/test/integration/slack_events_test.rb index 2dee6cc..9d31330 100644 --- a/test/integration/slack_events_test.rb +++ b/test/integration/slack_events_test.rb @@ -440,322 +440,4 @@ class SlackEventsTest < ActionDispatch::IntegrationTest assert Message.find_by(conversation: channel, user: user, slack_ts: slack_ts).blank? end - - - test "app_mention event from a channel for Magellan RAG" do - slack_ts = Time.now.to_f.to_s - channel = conversations(:magellan_rag) - user = users(:one) - query_body = "ZZZ" - query = "<@TEST_BOT_ID> #{query_body}" - - any_instance_of(SlackBot::Application) do |klass| - stub(klass).magellan_rag_channel? do |ch| - ch.slack_id == channel.slack_id - end - end - - stub_slack_api(:post, "chat.postMessage").to_return(body: { "ok" => true, "ts" => Time.now.to_f.to_s }.to_json) - - # Check the case when missing reactions:write scope - stub_slack_api(:post, "reactions.add").to_return { raise Slack::Web::Api::Errors::MissingScope, "missing_scope" } - stub_slack_api(:post, "reactions.remove").to_return { raise Slack::Web::Api::Errors::MissingScope, "missing_scope" } - - assert Message.find_by(conversation: channel, user: user, slack_ts: slack_ts).blank? - - assert_enqueued_with(job: MagellanRagQueryJob) do - params = { - type: "event_callback", - event: { - type: "app_mention", - text: query, - channel: channel.slack_id, - user: user.slack_id, - ts: slack_ts - } - } - request_body = ActionDispatch::RequestEncoder.encoder(:json).encode_params(params) - timestamp = slack_ts.to_i - headers = { - "X-Slack-Request-Timestamp": timestamp, - "X-Slack-Signature": compute_request_signature(timestamp, request_body) - } - - post "/slack/events", params:, headers:, as: :json - end - - message = Message.find_by!(conversation: channel, user: user, slack_ts: slack_ts) - assert_equal([ - query_body, - slack_ts, - ], - [ - message.text, - message.slack_thread_ts, - ]) - - mock(Utils::MagellanRAG).endpoint do - "http://report-rag-api.test" - end - - rag_answer = <<~END_ANSWER - 花王株式会社_エッセンシャルの事例では、TVCMの残存週が他社(11週)と比べて13週と長いと報告されています。 - 詳細は以下のファイルで確認できます。 - - ファイル名: 【花王様】エッセンシャル_初回レポート報告_20221125.pdf - - ファイルのURL: [こちら](https://drive.google.com/file/d/1MB_IerrxHZ_Dn3ziT7vPixaOF_ng84D3/preview?authuser=0) - - 該当部分: 「15秒・30秒・60秒ともに他施策と比べて効率は良好。残存週は他社(11週)と比べて、13週と長い。」 - - 花王株式会社_ブランド横断の事例では、TVCMの残存週が業界傾向値(11週)と比べて13週と長いと報告されています。 - 詳細は以下のファイルで確認できます。 - - ファイル名: 【花王様】横断レポート_20230116.pdf - - ファイルのURL: [こちら](https://drive.google.com/file/d/1_t9ldOf-KcHxtC2fUS3sqCEJm8HwxMmU/preview?authuser=0) - - 該当部分: 「残存週は業界傾向値(11週)と比べて、13週と長い。」 - END_ANSWER - rag_response = {"answer": rag_answer} - - stub_request( - :get, "http://report-rag-api.test/generate_answer" - ).with( - query: {"query" => "ZZZ"} - ).to_return_json( - status: 200, - body: rag_response - ) - - expected_answer = rag_answer - expected_response = "<@#{user.slack_id}> 次の2件の文書が質問に該当する可能性があります。\n\n#{expected_answer}" - expected_answer_blocks = [ - { - "type" => "section", - "text" => { - "type" => "mrkdwn", - "text" => "<@#{user.slack_id}> 次の2件の文書が質問に該当する可能性があります。" - } - }, - { - "type" => "rich_text", - "elements" => [ - { - "type" => "rich_text_section", - "elements" => [ - "type" => "text", - "text" => "花王株式会社_エッセンシャルの事例では、TVCMの残存週が他社(11週)と比べて13週と長いと報告されています。\n詳細は以下のファイルで確認できます。" - ] - }, - { - "type" => "rich_text_list", - "style" => "bullet", - "indent" => 0, - "elements" => [ - { - "type" => "rich_text_section", - "elements" => [ - {"type" => "text", "text" => "ファイル: "}, - { - "type" => "link", - "url" => "https://drive.google.com/file/d/1MB_IerrxHZ_Dn3ziT7vPixaOF_ng84D3/preview?authuser=0", - "text" => "【花王様】エッセンシャル_初回レポート報告_20221125.pdf" - } - ] - }, - { - "type" => "rich_text_section", - "elements" => [ - { - "type" => "text", - "text" => "該当部分: 「15秒・30秒・60秒ともに他施策と比べて効率は良好。残存週は他社(11週)と比べて、13週と長い。」" - } - ] - } - ] - } - ] - }, - {"type" => "divider"}, - { - "type" => "rich_text", - "elements" => [ - { - "type" => "rich_text_section", - "elements" => [ - "type" => "text", - "text" => "花王株式会社_ブランド横断の事例では、TVCMの残存週が業界傾向値(11週)と比べて13週と長いと報告されています。\n詳細は以下のファイルで確認できます。" - ] - }, - { - "type" => "rich_text_list", - "style" => "bullet", - "indent" => 0, - "elements" => [ - { - "type" => "rich_text_section", - "elements" => [ - {"type" => "text", "text" => "ファイル: "}, - { - "type" => "link", - "url" => "https://drive.google.com/file/d/1_t9ldOf-KcHxtC2fUS3sqCEJm8HwxMmU/preview?authuser=0", - "text" => "【花王様】横断レポート_20230116.pdf" - } - ] - }, - { - "type" => "rich_text_section", - "elements" => [ - { - "type" => "text", - "text" => "該当部分: 「残存週は業界傾向値(11週)と比べて、13週と長い。」" - } - ] - } - ] - } - ] - }, - {"type" => "divider"}, - ] - - perform_enqueued_jobs - - assert_response :success - - actual_body = nil - assert_slack_api_called(:post, "chat.postMessage") do |request| - actual_body = decode_slack_client_request_body(request.body) - end - - query = Query.find_by!(message: message) - assert_kind_of Hash, query.body - response = Response.find_by!(query: query) - assert_kind_of Hash, response.body - - assert_equal( - { - "blocks" => [ - *expected_answer_blocks, - feedback_action_block - ], - "channel" => channel.slack_id, - "text" => expected_response, - "thread_ts" => slack_ts, - }, - actual_body - ) - end - - - test "app_mention event from a channel for Magellan RAG with --retrieval option" do - skip "TODO" - slack_ts = Time.now.to_f.to_s - channel = conversations(:magellan_rag) - user = users(:one) - query_body = "ZZZ" - query = "<@TEST_BOT_ID> --retrieval #{query_body}" - - any_instance_of(SlackBot::Application) do |klass| - stub(klass).magellan_rag_channel? do |ch| - ch.slack_id == channel.slack_id - end - end - - stub_slack_api(:post, "chat.postMessage").to_return(body: { "ok" => true, "ts" => Time.now.to_f.to_s }.to_json) - - # Check the case when missing reactions:write scope - stub_slack_api(:post, "reactions.add").to_return { raise Slack::Web::Api::Errors::MissingScope, "missing_scope" } - stub_slack_api(:post, "reactions.remove").to_return { raise Slack::Web::Api::Errors::MissingScope, "missing_scope" } - - assert Message.find_by(conversation: channel, user: user, slack_ts: slack_ts).blank? - - assert_enqueued_with(job: MagellanRagQueryJob) do - params = { - type: "event_callback", - event: { - type: "app_mention", - text: query, - channel: channel.slack_id, - user: user.slack_id, - ts: slack_ts - } - } - request_body = ActionDispatch::RequestEncoder.encoder(:json).encode_params(params) - timestamp = slack_ts.to_i - headers = { - "X-Slack-Request-Timestamp": timestamp, - "X-Slack-Signature": compute_request_signature(timestamp, request_body) - } - - post "/slack/events", params:, headers:, as: :json - end - - message = Message.find_by!(conversation: channel, user: user, slack_ts: slack_ts) - assert_equal([ - query_body, - slack_ts, - ], - [ - message.text, - message.slack_thread_ts, - ]) - - mock(Utils::MagellanRAG).endpoint do - "http://report-rag-api.test" - end - - stub_request( - :get, "http://report-rag-api.test/retrieve_documents" - ).with( - query: {"query" => "ZZZ"} - ).to_return_json( - status: 200, - body: [ - { - "metadata" => { - "company_name" => "Xica", - "file_name" => "xica_report.pdf", - "file_url" => "https://example.com/xyzzy/xica_report.pdf", - }, - "content" => "ABC" - } - ] - ) - - expected_response = <<~END_BODY - <@#{user.slack_id}> # Doc-0: Xica - * file_name = xica_report.pdf - * file_url = https://example.com/xyzzy/xica_report.pdf - ABC - END_BODY - - perform_enqueued_jobs - - assert_response :success - - actual_body = nil - assert_slack_api_called(:post, "chat.postMessage") do |request| - actual_body = decode_slack_client_request_body(request.body) - end - - query = Query.find_by!(message: message) - assert_kind_of Hash, query.body - response = Response.find_by!(query: query) - assert_kind_of Hash, response.body - - assert_equal( - { - "channel" => channel.slack_id, - "text" => expected_response, - "thread_ts" => slack_ts, - "blocks" => [ - { - "type" => "section", - "text" => { - "type" => "mrkdwn", - "text" => expected_response, - } - }, - feedback_action_block - ] - }, - actual_body - ) - end end From bfe7162abd2b14add0da8cc95aa101f3287e489b Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Mon, 8 Jul 2024 08:13:20 +0000 Subject: [PATCH 2/3] test: Add fixture files --- test/fixtures/files/rag_answer-002.json | 1 + test/fixtures/files/rag_answer-003.json | 1 + 2 files changed, 2 insertions(+) create mode 100644 test/fixtures/files/rag_answer-002.json create mode 100644 test/fixtures/files/rag_answer-003.json diff --git a/test/fixtures/files/rag_answer-002.json b/test/fixtures/files/rag_answer-002.json new file mode 100644 index 0000000..b7fdf75 --- /dev/null +++ b/test/fixtures/files/rag_answer-002.json @@ -0,0 +1 @@ +{"answer":"オークラヤ住宅株式会社_オークラヤ住宅の事例では、予算シミュレーションに関して、シミュレーション対象成果を売却・購入全体に変更し、反響数比率を設定したパターンと設定しないパターンの2パターンのシミュレーションを算出した結果が報告されています。\n詳細は以下のファイルで確認できます。\n- ファイル名: ※予算配分更新※【オークラヤ住宅様】初回レポート報告_20220303.pdf\n- ファイルのURL: [こちら](https://drive.google.com/file/d/1Yw22hVaQvzZOhyXlp7PNC444k8KZQrTK/preview?authuser=0)\n- 該当部分: 予算シミュレーションに関して、以下の変更を実施。シミュレーション対象成果を売却・購入全体に変更。反響数比率を設定したパターンと設定しないパターンの2パターンのシミュレーションを算出。\n\n株式会社ボルテックス_全サービスの事例では、2019年度および2020年度の予算を基準とした最適予算プランニングを行い、予算最適化により同予算で成果増が見込まれるシミュレーションが報告されています。\n詳細は以下のファイルで確認できます。\n- ファイル名: 【ボルテックス様】初回レポート報告_20210121.pdf\n- ファイルのURL: [こちら](https://drive.google.com/file/d/1-xx2lkcuETc52jRm8RHXh74pK6GnTRB5/preview?authuser=0)\n- 該当部分: 2019年度および2020年度の予算を基準とした最適予算プランニング。予算最適化により、同予算で成果増が見込まれるシミュレーション。\n\n株式会社ヴィエリス_キレイモの事例では、予算指定(3億、4億の上限内で最適化)および成果指定(昨対同水準維持、昨対から+20%の成果獲得案)のシミュレーションが報告されています。\n詳細は以下のファイルで確認できます。\n- ファイル名: 【ヴィエリス様】初回レポート報告_20190808_MTG内容反映.pdf\n- ファイルのURL: [こちら](https://drive.google.com/file/d/11R8MpN4d9F_zVRmGN0o1E9-HTiyuRRcs/preview?authuser=0)\n- 該当部分: 予算指定(3億、4億の上限内で最適化)および成果指定(昨対同水準維持、昨対から+20%の成果獲得案)のシミュレーション。\n\n株式会社クラシアン_水のトラブル水回りリフォームの事例では、2022年1月1日~2022年12月31日における予算を最適化した場合のシミュレーションが報告されています。\n詳細は以下のファイルで確認できます。\n- ファイル名: 【クラシアン様】初回レポート報告_20220202.pdf\n- ファイルのURL: [こちら](https://drive.google.com/file/d/1XP42i9px_U58ylPXuGPDQoSfJdxZwwmZ/preview?authuser=0)\n- 該当部分: 2022年1月1日~2022年12月31日における予算を最適化した場合のシミュレーション。予算配分最適化により同予算で成果増が図れる可能性がある。\n\n株式会社三井住友銀行_カードローンの事例では、2020年7月度予算をプランニングし、成果「申込数」および「承認数」の最大化シミュレーションが報告されています。\n詳細は以下のファイルで確認できます。\n- ファイル名: 【三井住友銀行様】最新モデルご報告_20200619.pdf\n- ファイルのURL: [こちら](https://drive.google.com/file/d/1yE6lM6nFFmS-o6nOcnA6K4Bj6tN3v3g2/preview?authuser=0)\n- 該当部分: 2020年7月度予算をプランニングし、成果「申込数」および「承認数」の最大化シミュレーション。\n\n株式会社秀英予備校_秀英予備校の事例では、各エリア×季節間における目標達成に向けたシミュレーションが報告されています。\n詳細は以下のファイルで確認できます。\n- ファイル名: 【秀英予備校様】第1回レポート報告_20230130.pdf\n- ファイルのURL: [こちら](https://drive.google.com/file/d/1jt64Ml81vcaxnGF99I6epE0IN-KQcm6G/preview?authuser=0)\n- 該当部分: 各エリア×季節間における目標達成に向けたシミュレーション。計画予算と頂いた制約条件では、静岡、愛知が目標未達の見込み。\n\nライフネット生命保険株式会社_ライフネット生命の事例では、21年4月~22年3月の予算を申込件数最大化のために最適化した場合のシミュレーションが報告されています。\n詳細は以下のファイルで確認できます。\n- ファイル名: 【ライフネット生命様】21年度プロモーション振り帰り_20220530.pdf\n- ファイルのURL: [こちら](https://drive.google.com/file/d/157PNvlQU95758frg0rCOuYvg7gSqvYAC/preview?authuser=0)\n- 該当部分: 21年4月~22年3月の予算を申込件数最大化のために最適化した場合のシミュレーション。予算配分最適化により同予算で+5,034件(4.3%)の成果増が見込める。\n\nホーユー株式会社_シエロの事例では、2021年3月~2021年4月および2020年10月~2020年11月のプロモーション予算を最適化した場合のシミュレーションが報告されています。\n詳細は以下のファイルで確認できます。\n- ファイル名: 【ホーユー様】分析結果ご報告(3回目)_20210629.pdf\n- ファイルのURL: [こちら](https://drive.google.com/file/d/1URt6bXOjQ6RjKhy2r-wlH8kHTa6-deBJ/preview?authuser=0)\n- 該当部分: 2021年3月~2021年4月のプロモーション予算を最適化した場合のシミュレーション。予算配分最適化により同予算で成果増が図れた可能性がある。\n\n- ファイル名: 【ホーユー様】分析結果ご報告(2回目)_20210219.pdf\n- ファイルのURL: [こちら](https://drive.google.com/file/d/1m7bjBIEThC0p2WWt5J0iK-h7DfbxcUot/preview?authuser=0)\n- 該当部分: 2020年10月~2020年11月のプロモーション予算を最適化した場合のシミュレーション。予算配分最適化により同予算で成果増が図れた可能性がある。\n\n株式会社マンダム_ギャッツビーの事例では、2021年1月~5月の予算を最適化した場合のシミュレーションが報告されています。\n詳細は以下のファイルで確認できます。\n- ファイル名: 【マンダム様】第一弾分析結果ご報告_20210727.pdf\n- ファイルのURL: [こちら](https://drive.google.com/file/d/1yoZx0o0uCDsV3i5EYhmvDLhEONeGmaFd/preview?authuser=0)\n- 該当部分: 2021年1月~5月の予算を最適化した場合のシミュレーション。予算配分最適化により同予算で成果増が図れる可能性がある。"} diff --git a/test/fixtures/files/rag_answer-003.json b/test/fixtures/files/rag_answer-003.json new file mode 100644 index 0000000..f31258f --- /dev/null +++ b/test/fixtures/files/rag_answer-003.json @@ -0,0 +1 @@ +{"answer":"はるやまの事例に関する情報は見つかりませんでした。他の企業の事例についての情報が必要であれば、お知らせください。"} \ No newline at end of file From 59b614cd60000ddb5ad26db797820ca2b7a4f09a Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Mon, 8 Jul 2024 08:19:08 +0000 Subject: [PATCH 3/3] Implement fallback on invalid blocks error --- app/jobs/magellan_rag_query_job.rb | 39 +++++- test/integration/slack_events/rag_test.rb | 138 ++++++++++++++++++++++ test/jobs/magellan_rag_query_job_test.rb | 10 ++ 3 files changed, 182 insertions(+), 5 deletions(-) diff --git a/app/jobs/magellan_rag_query_job.rb b/app/jobs/magellan_rag_query_job.rb index 5e81179..031e5df 100644 --- a/app/jobs/magellan_rag_query_job.rb +++ b/app/jobs/magellan_rag_query_job.rb @@ -129,11 +129,26 @@ def perform(params) user: message.user ) - posted_message = Utils.post_message( - channel: message.conversation.slack_id, - thread_ts: message.slack_thread_ts, - **post_params - ) + begin + posted_message = Utils.post_message( + channel: message.conversation.slack_id, + thread_ts: message.slack_thread_ts, + **post_params + ) + rescue Exception => err + logger.warn "Error occurred on post_message: #{err.full_message(highlight: false)}" + + post_params = format_simple_rag_response( + answer, + user: message.user + ) + + posted_message = Utils.post_message( + channel: message.conversation.slack_id, + thread_ts: message.slack_thread_ts, + **post_params + ) + end logger.info posted_message.inspect unless posted_message.ok @@ -172,6 +187,20 @@ def perform(params) {"answer" => s} end + private def format_simple_rag_response(answer, user:) + answer = rewrite_markdown_link(answer) + text = "<@#{user.slack_id}> 回答は次のとおりです。\n\n#{answer}" + response = SlackBot.format_chat_gpt_response(text) + feedback_block = response[:blocks].pop + response[:blocks] << { "type": "divider" } + response[:blocks] << feedback_block + response + end + + private def rewrite_markdown_link(s) + s.gsub(/\[(.+?)\]\((.+?)\)/) { "<#{$2}|#{$1}>" } + end + private def format_rag_response(answer, user:) answer_blocks = format_answer_blocks(answer, user) text = "#{answer_blocks[0][:text][:text]}\n\n#{answer}" diff --git a/test/integration/slack_events/rag_test.rb b/test/integration/slack_events/rag_test.rb index 964eaea..d3f3343 100644 --- a/test/integration/slack_events/rag_test.rb +++ b/test/integration/slack_events/rag_test.rb @@ -206,6 +206,144 @@ class SlackEventsRagTest < ActionDispatch::IntegrationTest end + test "the case of invalid blocks error" do + slack_ts = Time.now.to_f.to_s + channel = conversations(:magellan_rag) + user = users(:one) + query_body = "ZZZ" + query = "<@TEST_BOT_ID> #{query_body}" + + any_instance_of(SlackBot::Application) do |klass| + stub(klass).magellan_rag_channel? do |ch| + ch.slack_id == channel.slack_id + end + end + + stub_slack_api(:post, "chat.postMessage").to_return( + body: { "ok" => false, "error" => "invalid_blocks" }.to_json + ).then.to_return( + body: { "ok" => true, "ts" => Time.now.to_f.to_s }.to_json + ) + + # Check the case when missing reactions:write scope + stub_slack_api(:post, "reactions.add").to_return { raise Slack::Web::Api::Errors::MissingScope, "missing_scope" } + stub_slack_api(:post, "reactions.remove").to_return { raise Slack::Web::Api::Errors::MissingScope, "missing_scope" } + + assert Message.find_by(conversation: channel, user: user, slack_ts: slack_ts).blank? + + assert_enqueued_with(job: MagellanRagQueryJob) do + params = { + type: "event_callback", + event: { + type: "app_mention", + text: query, + channel: channel.slack_id, + user: user.slack_id, + ts: slack_ts + } + } + request_body = ActionDispatch::RequestEncoder.encoder(:json).encode_params(params) + timestamp = slack_ts.to_i + headers = { + "X-Slack-Request-Timestamp": timestamp, + "X-Slack-Signature": compute_request_signature(timestamp, request_body) + } + + post "/slack/events", params:, headers:, as: :json + end + + message = Message.find_by!(conversation: channel, user: user, slack_ts: slack_ts) + assert_equal([ + query_body, + slack_ts, + ], + [ + message.text, + message.slack_thread_ts, + ]) + + mock(Utils::MagellanRAG).endpoint do + "http://report-rag-api.test" + end + + rag_answer = <<~END_ANSWER + 花王株式会社_エッセンシャルの事例では、TVCMの残存週が他社(11週)と比べて13週と長いと報告されています。 + 詳細は以下のファイルで確認できます。 + - ファイル名: 【花王様】エッセンシャル_初回レポート報告_20221125.pdf + - ファイルのURL: [こちら](https://drive.google.com/file/d/1MB_IerrxHZ_Dn3ziT7vPixaOF_ng84D3/preview?authuser=0) + - 該当部分: 「15秒・30秒・60秒ともに他施策と比べて効率は良好。残存週は他社(11週)と比べて、13週と長い。」 + + 花王株式会社_ブランド横断の事例では、TVCMの残存週が業界傾向値(11週)と比べて13週と長いと報告されています。 + 詳細は以下のファイルで確認できます。 + - ファイル名: 【花王様】横断レポート_20230116.pdf + - ファイルのURL: [こちら](https://drive.google.com/file/d/1_t9ldOf-KcHxtC2fUS3sqCEJm8HwxMmU/preview?authuser=0) + - 該当部分: 「残存週は業界傾向値(11週)と比べて、13週と長い。」 + END_ANSWER + rag_response = {"answer": rag_answer} + + stub_request( + :get, "http://report-rag-api.test/generate_answer" + ).with( + query: {"query" => "ZZZ"} + ).to_return_json( + status: 200, + body: rag_response + ) + + expected_answer = <<~END_ANSWER + 花王株式会社_エッセンシャルの事例では、TVCMの残存週が他社(11週)と比べて13週と長いと報告されています。 + 詳細は以下のファイルで確認できます。 + - ファイル名: 【花王様】エッセンシャル_初回レポート報告_20221125.pdf + - ファイルのURL: + - 該当部分: 「15秒・30秒・60秒ともに他施策と比べて効率は良好。残存週は他社(11週)と比べて、13週と長い。」 + + 花王株式会社_ブランド横断の事例では、TVCMの残存週が業界傾向値(11週)と比べて13週と長いと報告されています。 + 詳細は以下のファイルで確認できます。 + - ファイル名: 【花王様】横断レポート_20230116.pdf + - ファイルのURL: + - 該当部分: 「残存週は業界傾向値(11週)と比べて、13週と長い。」 + END_ANSWER + expected_response = "<@#{user.slack_id}> 回答は次のとおりです。\n\n#{expected_answer}" + expected_answer_blocks = [ + { + "type" => "section", + "text" => { + "type" => "mrkdwn", + "text" => "<@#{user.slack_id}> 回答は次のとおりです。\n\n#{expected_answer}" + } + }, + {"type" => "divider"}, + ] + + perform_enqueued_jobs + + assert_response :success + + actual_body = nil + assert_slack_api_called(:post, "chat.postMessage", times: 2) do |request| + actual_body = decode_slack_client_request_body(request.body) + end + + query = Query.find_by!(message: message) + assert_kind_of Hash, query.body + response = Response.find_by!(query: query) + assert_kind_of Hash, response.body + + assert_equal( + { + "blocks" => [ + *expected_answer_blocks, + feedback_action_block + ], + "channel" => channel.slack_id, + "text" => expected_response, + "thread_ts" => slack_ts, + }, + actual_body + ) + end + + test "app_mention event from a channel for Magellan RAG with --retrieval option" do skip "TODO" slack_ts = Time.now.to_f.to_s diff --git a/test/jobs/magellan_rag_query_job_test.rb b/test/jobs/magellan_rag_query_job_test.rb index b56bf93..7245f08 100644 --- a/test/jobs/magellan_rag_query_job_test.rb +++ b/test/jobs/magellan_rag_query_job_test.rb @@ -118,4 +118,14 @@ class MagellanRagQueryJobTest < ActiveSupport::TestCase actual_body ) end + + test "query = 成果最大化シミュレーションを実施した事例を教えてください" do + skip "TODO" + message = messages(:report_query_one) + channel = message.conversation + user = message.user + + rag_answer = JSON.load(fixture_file_path("rag_answer-002.json").read)["answer"] + # p rag_answer + end end