社内ドキュメントの内容をAIに食べさせたいじゃないですか
こんにちは。Feature2 Unitのうなすけです。一番よく使っているAIサービスはClaudeです。
Reproでは社内ドキュメントにesaを使用しています。他にも、Kibela、DocBase、Notion、Confluenceなど、様々な社内ドキュメント用サービスがありますよね。その中でもNotionにはNotion AIが、ConfluenceにはAtlassian Intelligenceがあり、AIと連携した便利な機能がある……と聞いています。僕はどちらも使ったことはありませんが。
僕の周りでは特にNotion AIを使ったことのある人が多いのですが、Notion上にある情報をAIで探してもらうという使い方をしている人が多いようです。むしろ自分では見つけられない情報がAIに聞くと見つかることも多いと聞きます。
社内ドキュメントは得てして必要な情報を見つけにくくなっていく傾向にあると思います。とくに長年蓄積された大量の記事がある場合には、検索でのノイズも増えてくることでしょう。そんなときに便利なNotion AIのようなものが、私たちのesaにもあれば便利だな、と思いました。
なければ作ればいい
さてAIブームな昨今ですが、AWSでも生成AIを使用できるサービスが提供されており、そのなかには独自のデータソースから回答を生成できるAmazon Bedrock Knowledge Baseというものがあります。
これを使い、esaの記事をKnowledge Baseとして学習させたものを用意すれば様々なことができそうです。というわけでやってみました。以下はその経緯です。
全体の構成を考える

まず全体の構成を考えてみます。社内での説明に使った図をそのまま持ってきたので雑ですし、文字も読めない部分があるとは思いますが、概要は掴めるはず……
esaのアーカイブをS3に置き、それをdata sourceとしてKnowledge Baseを構築します。SlackからのメンションをLambdaで受けとり、Knowledge Baseに対して問い合わせた結果をSlackで返信として返します。回答の生成には時間がかかるので間にSQSを挟みます。S3に置いてあるesaのアーカイブは、esaからのWebhookなどを使い定期的に更新し、Knowledge Baseをアップデートします。
esaの記事をS3に置き、更新し続ける
esaの記事を全てS3に置きたいとなった場合、投稿データのエクスポートがまず思い浮かびます。
これはesaのドキュメントにも記載のあるように、Middleman Blog としてそのまま使えるフォーマットになっています。例えば「Foo/Bar/Baz/記事」というタイトルの記事がある場合、それはそのまま Foo/Bar/Baz/記事.md というパスで格納されています。
ここで問題となってくるのが、Knowledge Baseによる返答の生成です。AIによって生成された返答は誤っていることもあるでしょうし、その返答を生成する元となったソースを一緒にSlackに投稿したいと考えていました。しかしその場合、エクスポートされたままのディレクトリ構造でS3に置いてしまうと、記事のIDがわからず、つまりesaのURLがわからないので、結果の検証ができません。
そのため、エクスポートされたMarkdown内のFrontmatterを読み、先程の記事のIDが1234だった場合は Foo/Bar/Baz/記事/1234.md というディレクトリ構造になるよう変換するスクリプトを組みました。
require "front_matter_parser" require "fileutils" SOURCE_DIR = "esa-archive-dir-foobarbaz" Dir.glob("**/*.md", base: SOURCE_DIR).each do |entry| front_matter = FrontMatterParser::Parser.parse_file("#{SOURCE_DIR}/#{entry}") FileUtils.mkdir_p("./output/#{entry.delete_suffix('.md')}") FileUtils.cp("#{SOURCE_DIR}/#{entry}", "./output/#{entry.delete_suffix('.md')}/#{front_matter.front_matter['number']}.md") end
ある時点でのスナップショットをエクスポートで取得できたら、次は記事の更新をしていくことを考えます。esaにはWebhookがあるので、記事の更新時にPOSTされるWebhookを受け取るLambdaを作成し、S3上のMarkdownを更新し続けるようにしました。
これにより、まずはesaの全記事の内容が入ったS3 bucketと、それを更新し続ける仕組みを構築できました。あとはこれをData sourceとしたKnowledge Baseを構築すればよさそうです。
Knowledge Baseの構築
S3へのesaの投稿が用意できてさえいれば、Knowledge Baseの準備はそう苦労せずに済みます。と、言いたいところですが、なんといってもAI関係はお金がかかるので撤退とその後の再構築の可能性がありました。そのため、やりなおしがしやすいように全部Terraformで管理するようにした1のですが……それが中々に難しかったです。
Terraformで構築するにあたり、Terraform AWS Providerのdocumentはもちろん、以下の記事にも大変助けられました。この場を借りてお礼申し上げます。ありがとうございます。
registry.terraform.io dev.classmethod.jp blog.kinto-technologies.com
Data sourceとしているS3 bucketの内容は前述の通りWebhookで更新されていくので、Amazon EventBridge Schedulerで数時間おきにSync (StartIngestionJob)してやれば、Knowledge Baseを最新の状態に保つことができます。
Slackと繋ぎこむ
ここまでできたら、あとはSlack botを作成してKnowledge Baseからの回答を返信するだけです!Knowledge Baseでの回答の生成は時間がかかるので間にSQSを挟むなどの一工夫は必要でしたが、ここはやるだけなので、やるだけですね。

回答の内容には、回答を生成するために参照されたesaのURLを返すようにしました。これはKnowledge Baseからの回答の生成に使用している RetrieveAndGenerate APIのresponseに含まれる citations の内容から生成しています。この内容に含まれるのはS3のkeyになるので、esaのエクスポート結果を変換して末尾の数字だけを取り出せばいいようにしたことが効いてくるわけです。
これにより、Botが嘘を言ってないか、間違った記事を参考にしていないかを確認できるのはもちろん、esaの記事にアクセスして関連する情報を辿りやすくなります。
あと、プロンプトをちょっと変更して、砕けた口調で返事をするようにしました。これは個人的な好みです。
future work
この機能はまだまだ荒削りです。例えばみんなが使ってみて返答の質がどうだったのか、その結果に対して回答を生成するモデルを変えるとどうなるのか、esaの記事に対する重み付けをどうするか、など課題は山積みです。
そして、社内に展開して広く使ってもらうことで分かったことは、思ったより情報が古くなってしまっているesaが多いということです。そもそも知識の元となるデータが古かったり間違っていたりすると、回答の質も悪くなってしまうのは避けられません。botの返してくるesaが古かったり間違っている場合、積極的にesa側を更新していけるような動きができると理想的ですね。
引き続き改善を続け、AIによって仕事が楽になるような仕組みを育てていければと考えています。
WE ARE HIRING!
このテックブログの他の記事をお読みになった方はご存じかと思いますが、Reproでは他にもDevinやGemini、ClaudeなどのAIを活用して開発業務の効率化に取り組んでいます。AIのいる開発風景に興味のある方はお気軽にお声掛けください!
- Aurora上のテーブルの作成については手でやってしまいましたが。https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/AuroraPostgreSQL.VectorDB.html↩
