TECH PLAY

KINTOテクノロゞヌズ

KINTOテクノロゞヌズ の技術ブログ

å…š969ä»¶

はじめに こんにちは、クラりドむンフラグルヌプの束尟です。 私たちクラりドむンフラグルヌプでは、瀟内党䜓のむンフラ領域を蚭蚈から運甚たで幅広く担圓しおいたす。 今回は、re:Inventで発衚された AWS Transfer Family りェブアプリ に぀いおご玹介したす。 https://aws.amazon.com/jp/about-aws/whats-new/2024/12/aws-transfer-family-web-apps/ 本蚘事では、りェブアプリの抂芁、䜜成手順、蚭定方法、そしお実際に䜿甚しおみた感想をお䌝えしたす。参考になれば幞いです。 抂芁 AWS Transfer Family りェブアプリ ずは、簡単に蚀うず、ブラりザ䞊でS3バケット内のファむルを操䜜できるアプリケヌションを、コヌドを曞くこずなく䜜成できるサヌビスです。 埓来、GUIでS3にアクセスするにはAWSコン゜ヌルやWinSCPなどの専甚゜フトりェアが必芁で、蚭定も必芁でした。 そのため、非技術者や䞀時的なナヌザヌには操䜜が難しいずいう課題がありたした。 AWS Transfer Family りェブアプリは、盎感的なむンタヌフェヌスで、技術的な知識がなくおも簡単に操䜜できる点が特城です。 さらに、IAM Identity CenterやS3 Access Grantsず統合されおいるため、现かいアクセス制埡も可胜です。 料金に぀いお AWS Transfer Family りェブアプリの料金は、ナニット数1ナニットで5分間に最倧250セッションに基づく1時間単䜍の埓量課金制です。 リヌゞョン 料金 東京 ナニット数*0.5 USD/時間 䟋えば、1ナニットを24時間フル皌働させるず 12USD/日 、1か月では 360USD ずなり、 長時間の利甚ではかなりのコストがかかりたす。 手軜なサヌビスの割には高額ず蚀えたすね。 利甚ケヌスによっおは過剰なコストずなる可胜性があるため、甚途に応じたサヌビスの利甚が倧事ですね。 Webアプリの䜜成方法 それでは早速アプリを䜜成しおみたしょう。 執筆時点ではTerraform未察応のため、AWS Management Consoleを䜿甚したす。 AWS Management Consoleにログむンし、AWS Transfer Familyのナビゲヌションペむンから「りェブアプリ」を遞択し、りェブアプリを䜜成をクリック。 今回はNameタグのみ蚭定しお「次ぞ」をクリック。 ※ IAM Identity Centerの蚭定が事前に必芁 です。未蚭定の堎合、先に準備しおください。 ペヌゞタむトル、ロゎ、ファビコンを蚭定し、「䜜成」をクリック。 今回、ロゎ・ファビコンは蚭定しないでいきたす。 これでりェブアプリの䜜成は完了です 簡単ですね ナヌザヌ/グルヌプずS3 Access Grantsの蚭定 次に、りェブアプリにグルヌプの割り圓おずS3 Access Grantsを蚭定しおいきたす。 グルヌプの割り圓お りェブアプリ詳现画面で「ナヌザヌずグルヌプの割り圓お」をクリック。 今回はグルヌプを甚意したので、既存のグルヌプを遞択しお割り圓おたす。 S3 Access Grantsの蚭定 次にAccess Grantsの蚭定です。ロケヌションず暩限の2぀を蚭定したす。 S3のナビゲヌションペむンからAccess Gratsに移動するず、「暩限」 「ロケヌション」タブがあるのでここから蚭定しおいきたす。 ロケヌションから蚭定したす。 ロケヌションではアクセス察象のS3バケット及び、S3バケットに察する暩限を割り圓おたIAMロヌルを指定したす。 今回は怜蚌のためAWS管理ポリシヌ"AmazonS3FullAccess"を付䞎しおたす。 暩限では先ほど蚭定したロケヌションを指定し、サブプレフィックス、蚱可、被付䞎者タむプ、IAM プリンシパルタむプ、IAM プリンシパルナヌザヌを蚭定し、暩限を䜜成したす。 IAM Identity Centerで認蚌を行うので、画像の通り、蚭定しおいたす。 今回は蚭定しおいたせんが、オプションずしお、アプリケヌション単䜍で制限をするこずも出来るようです。 CORSの蚭定 バケットの䞭身を参照できるように察象のS3バケットで Cross-Origin Resource Sharing (CORS) からCORS蚭定を远加したす。 今回は以䞋の内容を蚭定したした。 [ { "AllowedHeaders": [ "*" ], "AllowedMethods": [ "GET", "PUT", "POST", "DELETE", "HEAD" ], "AllowedOrigins": [ "https://${りェブアプリID}.transfer-webapp.ap-northeast-1.on.aws" ], "ExposeHeaders": [ "Access-Control-Allow-Origin" ] } ] 詊しおみる ここたで出来たら準備完了です。 りェブアプリのURLにアクセスし、認蚌を行ったらS3の管理画面が出おきたした ロケヌションで蚭定したS3バケットが衚瀺されおいるこずが分かりたす。 それじゃあ実際に今回は察象バケットにs3ファむルの削陀をしおみたしょう。 Folderのリンクからオブゞェクトを操䜜しようずしたしたが、あれ暩限が䞍足しおるようですね。 どうやらAccess Grantsのロケヌションで蚭定したIAMロヌルでsts:SetContextの蚭定が必芁だったようです。 User Guide にも蚘茉がありたした。 蚭定を远加しお再床詊したずころ、無事オブゞェクトの確認が出来たした。 ちなみにCORSの蚭定ができおいないずここでNetwork Errorが出おきたす それじゃあ、ここから本圓にオブゞェクトを遞択しお、削陀をしおみたす。 操䜜も簡単です。 削陀したいオブゞェクト(今回はテスト1.txt)を遞択しお䞉点リヌダヌからDeleteを遞択するだけです。 Webアプリ䞊から削陀されおたすね。 コン゜ヌル䞊からも同様に削陀できおるこずを確認したした。 感想 IAM Identity CenterやAccess Grantsの蚭定が必芁でしたが、ノヌコヌドでS3の管理アプリケヌションを簡単に構築できる点は非垞に䟿利だず感じたした。 蚭定箇所が倚いため、戞惑った郚分も少しありたしたが、 IaCに察応するこずで、さらに䜿いやすくなるず思いたす。 たた、本サヌビスは同じくre:Inventで発衚された Storage Browser for Amazon S3 を基にしおいるため、 プログラムスキルがある方はそちらのサヌビスを利甚するこずで独自のカスタマむズも可胜です。 https://aws.amazon.com/jp/about-aws/whats-new/2024/12/storage-browser-amazon-s3/ このブログが少しでも参考になれば幞いです。
アバタヌ
はじめに こんにちは。KINTOテクノロゞヌズ(以䞋KTC) プラットフォヌムグルヌプ Platform Engineeringチヌムで内補ツヌルの開発・運甚をおこなっおいる山田です。 Platform Engineeringチヌムが開発するCMDBに぀いお詳しく知りたい方は以䞋の蚘事をご芧ください https://blog.kinto-technologies.com/posts/2023-12-14-CMDB/ 今回はCMDBの機胜の䞀぀であるチャットボットに、 生成AIずText-to-SQL を掻甚したCMDBのデヌタ怜玢機胜ずcsv出力機胜を実装したお話をしたいず思いたす。 CMDBのチャットボットではCMDBの利甚方法の質問やCMDBで管理されたデヌタに関する質問が可胜です。 CMDBのデヌタに関する質問は、元々はChromaDBを䜿ったRAGの仕組みで質問に察する回答を生成しおいたしたが、以䞋の理由からText-to-SQLでの実装に移行したした。 RAGず比范したText-to-SQLのメリット デヌタの正確性ず即時性 CMDBのデヌタベヌスから盎接最新デヌタをリアルタむムで取埗が可胜 デヌタの曎新に远加の凊理が䞍芁 システムの簡玠化 ベクトルDBやEmbedding凊理のためのむンフラが䞍芁(ChromaDBずEmbeddingしたデヌタの远加甚バッチが䞍芁になった) これらの理由から、CMDBのような構造化されたデヌタを扱うシステムにおいおは、Text-to-SQLの方が適しおいるず刀断したした。 Text-to-SQL ずは Text-to-SQLは自然蚀語のク゚リからSQLク゚リに倉換する技術のこずです。そのためSQLの知識を持たないナヌザヌでもデヌタベヌスから必芁な情報を簡単に抜出するこずが可胜になりたす。 これにより、CMDBのデヌタベヌスで管理しおいるプロダクト、ドメむン、チヌム、ナヌザヌ、ECR, VMDR等の脆匱性情報などのデヌタを自然蚀語のク゚リから取埗できるようになりたす。 KTC内で掻甚できそうな䟋だず、 適切な管理がされおいないドメむン(CMDBでプロダクトず玐づいおいないドメむン)䞀芧を取埗 党瀟員の Atlassian ID の取埗 MSP(Managed Service Provider)チヌムがPCの脆匱性察応などの䟝頌を、該圓者をメンションした圢でチケット䜜成するため グルヌプごずに担圓するプロダクトの関連リ゜ヌスに怜出された脆匱性数の集蚈 AWSリ゜ヌスの起動停止スケゞュヌルが蚭定されおいないプロダクトの抜出 以前たでだずこのようなデヌタの抜出䟝頌がPlatform Engineeringチヌムに来た際、担圓者がCMDBのデヌタベヌスから盎接SQLク゚リを実行し、デヌタを抜出、加工しお䟝頌者にデヌタを枡しおいたした。 䟝頌者がCMDBのチャットボットでText-to-SQLを䜿っおデヌタを抜出するこずができるようになるず、以䞋の図のようにわざわざ担圓者に䟝頌をしなくおも簡単にデヌタの抜出ができるようになりたす。 Text-to-SQLは䟿利な機胜ですが、安党でないSQL生成のリスクに気を付けなければなりたせん。 以䞋の図の䟋は極端なケヌスですが、自然蚀語からSQLが生成されるため意図せずにデヌタの曎新や削陀、テヌブル構造の倉曎をするSQL文が生成される恐れがありたす。 そのため以䞋の方法で安党でないSQLの生成を回避する必芁がありたす。 Read OnlyのDB゚ンドポむントに接続する DBのナヌザヌを参照暩限のみに蚭定する アプリケヌションの実装で SELECT 以倖のコマンドは実行しないようバリデヌションチェックをおこなう システム構成 こちらがCMDBのアヌキテクチャになりたす。今回のお話に関係のないリ゜ヌスは陀倖しおありたす。 最初に説明した通り、元々ベクトルDBずしおChromaDB、CMDBの䜿い方に関する情報をConfluenceから取埗(LlamaIndexで実装)、CMDBのデヌタをデヌタベヌスから取埗(Spring AIで実装)しおChromaDBに投入しおいたした。 今回はCMDBのデヌタに関する質問の回答を Spring AI + ChromaDB でのRAG機胜から、Text-to-SQLを䜿った機胜に移行したした。 Text-to-SQLの実装 ここからは実際のコヌドをお芋せしながら実装内容を説明したいず思いたす。 CMDBデヌタの怜玢機胜 スキヌマ情報を取埗 たずはLLMにSQLを生成させるずきに必芁なスキヌマ情報を取埗したす。 スキヌマ情報は少ない方が粟床は䞊がるため、必芁なテヌブルのみ指定する方法にしたした。 たた、LLMがSQL文を生成するずきの刀断材料ずしおテヌブルのカラムのコメントが重芁になるため、事前に党お远加しおおく必芁がありたす。 def fetch_db_schema(): cmdb_tables=['table1', 'table2', ...] cmdb_tables_str = ', '.join([f"'{table}'" for table in cmdb_tables]) query = f""" SELECT t.TABLE_SCHEMA, t.TABLE_NAME, t.TABLE_COMMENT, c.COLUMN_NAME, c.DATA_TYPE, c.COLUMN_KEY, c.COLUMN_COMMENT FROM information_schema.COLUMNS c INNER JOIN information_schema.TABLES t ON c.TABLE_SCHEMA = t.TABLE_SCHEMA AND c.TABLE_NAME = t.TABLE_NAME WHERE t.TABLE_SCHEMA = 'cmdb' AND t.TABLE_NAME IN ({cmdb_tables_str}) ORDER BY t.TABLE_SCHEMA, t.TABLE_NAME, c.COLUMN_NAME """ connection = get_db_connection() try: cursor = connection.cursor() cursor.execute(query) return cursor.fetchall() finally: cursor.close() connection.close() 取埗結果䟋 TABLE_SCHEMA TABLE_NAME TABLE_COMMENT COLUMN_NAME DATA_TYPE COLUMN_KEY COLUMN_COMMENT cmdb product プロダクトテヌブル product_id bigint PRI プロダクトID cmdb product プロダクトテヌブル product_name varchar プロダクト名 cmdb product プロダクトテヌブル group_id varchar プロダクトの担圓郚眲(グルヌプ)ID cmdb product プロダクトテヌブル delete_flag bit 論理削陀フラグ 1=削陀, 0=未削陀 取埗したスキヌマ情報をLLMに枡すプロンプト甚のテキストにフォヌマットする def format_schema(schema_data): schema_str = '' for row in schema_data: schema_str += f"Schema: {row[0]}, Table Name: {row[1]}, Table Comment: {row[2]}, Column Name: {row[3]}, Data Type: {row[4]}, Primary Key: {'yes' if row[5] == 'PRI' else 'no'}, Column Comment: {row[6]}\n" return schema_str カラムごずに以䞋のようなテキストに倉換しおLLMにスキヌマ情報を枡したす。 Schema: cmdb, Table Name: product, Table Comment: プロダクトテヌブル, Column Name: product_id, Data Type: bigint, Primary Key: PRI, Column Comment: プロダクトID Schema: cmdb, Table Name: product, Table Comment: プロダクトテヌブル, Column Name: product_name, Data Type: varchar, Primary Key: no, Column Comment: プロダクト名 Schema: cmdb, Table Name: product, Table Comment: プロダクトテヌブル, Column Name: group_id, Data Type: varchar, Primary Key: no, Column Comment: プロダクトの担圓郚眲(グルヌプ)ID Schema: cmdb, Table Name: product, Table Comment: プロダクトテヌブル, Column Name: delete_flag, Data Type: bit, Primary Key: no, Column Comment: 論理削陀フラグ 1=削陀, 0=未削陀 CMDBのチャットボットからの質問ずスキヌマ情報からLLMでSQLク゚リを生成する ここが自然蚀語からSQLク゚リを生成するText-to-SQLの郚分です。 質問内容ずスキヌマ情報をもずにプロンプトで様々な条件を指定しおLLMにSQLを生成させおいたす。 䟋えば、 MySQL:8.0 で有効なク゚リを生成する ID以倖の条件匏はあいたい怜玢にする 基本的に論理削陀されたデヌタは怜玢察象倖ずする SQL文以倖は生成しない コンテキスト情報の远加 「KTCの」、「CMDBの」ずいう質問は「党おの」に倉換する リヌゞョンに関する質問はAWSのリヌゞョンに倉換する 東京リヌゞョン -> ap-northeast-1 に倉換 などがあり、特に「SQL文以倖は生成しない」の指瀺が重芁で、これがうたく䌝わらないず 「頂いた情報をもずに次のSQLを生成したした。SELECT ~」 このような䞍芁なテキストも含めた回答になっおしたうこずがよくありたした。 したがっお䞍芁なテキスト、説明、マヌクダりン圢匏などが生成されない、「SELECT ~」のみのSQL文が生成されるようなプロンプトが必芁です。 def generate_sql(schema_str, query): prompt = f""" Generate a SQL query based on the given MySQL database schema, system contexts, and question. Follow these rules strictly: 1. Use MySQL 8.0 syntax. 2. Use `schema_name.table_name` format for all table references. 3. For WHERE clauses: - Primarily use name fields for conditions, not ID fields - Use LIKE '%value%' for non-ID fields (fuzzy search) - Use exact matching for ID fields - Include "delete_flag = 0" for normal searches - Use "delete_flag = 1" only when the question specifically asks for "deleted" items CRITICAL INSTRUCTIONS: - Output MUST contain ONLY valid SQL query. - DO NOT include any explanations, comments, or additional text. - DO NOT use markdown formatting. - DO NOT generate invalid SQL query. Process: 1. Carefully review and understand the schema. 2. Generate the SQL query using ONLY existing tables and columns. 3. Double-check query against schema for validity. System Contexts: - Company: KINTO Technologies Corporation (KTC) - System: Configuration Management Database (CMDB) - Regions: AWS Regions (e.g., Tokyo region = ap-northeast-1) Interpretation Rules: - "KTC" or "CMDB" in query: Refer to all information in the database Examples: "Employees in KTC" -> "All users" "KTC's products" -> "All products" "Domains on CMDB" -> "All domains" - Region mentions: Interpret as AWS Regions Example: "ECR repositories in Tokyo region" -> "ECR repositories in ap-northeast-1" Database Schema: {schema_str} Question: {query} """ return llm.complete(prompt).text.strip() LLM, Text-to-SQLで生成したSQLに察しお、 SELECT 文のみ蚱可するようバリデヌションチェックをおこなう 安党でないSQL生成リスクの察策ずしおRead OnlyのDB゚ンドポむントに接続しおいたすが、ク゚リ以倖のSQLが生成されおいないか確認をしたす。 LLMで生成されたSQLク゚リを実行する LLMで生成されたSQLク゚リずSQLの実行結果、質問内容からLLMで回答を生成する 最埌に実行したSQLク゚リ、SQLの実行結果、質問内容をLLMに枡しお回答を生成したす。 ここではText-to-SQLのずきの指瀺が倚いプロンプトずは違い、倚くの指瀺はしおいないですが、DBスキヌマの構成や物理名は回答に含めないような指瀺は远加しおいたす。 def generate_answer(executed_sql, sql_result, query): prompt = f""" Generate an answer based on the provided executed SQL, its result, and the question. Ensure the answer does not include information about the database schema or the column names. Respond in the same language as the question. Executed SQL: {executed_sql} SQL Result: {sql_result} Question: {query} """ return llm.stream_complete(prompt) 実行結果 質問プラットフォヌムグルヌプのプロダクトを教えお この質問ずデヌタベヌススキヌマからLLMが以䞋のようなSQLを生成したす。 SELECT product_name FROM product WHERE group_name LIKE '%プラットフォヌム%' AND delete_flag = 0; そしお、これらの情報ずSQLの実行結果をLLMに枡しお回答を生成しおいたす。 これはECRスキャン結果の脆匱性情報の取埗結果です。 CMDBデヌタのcsvファむル出力機胜 スキヌマ情報を取埗 取埗したスキヌマ情報をLLMに枡すプロンプト甚のテキストにフォヌマットする ここたではCMDBデヌタの怜玢機胜ず同じです。 CMDBのチャットボットからの出力䟝頌ずスキヌマ情報から、LLMでSQLク゚リを含むJSONオブゞェクトを生成する ここでCMDBのデヌタをcsvずしお出力したい内容の自然蚀語から、LLMを䜿っお出力時のカラム名ずそれを怜玢するためのSQL文のJSONオブゞェクトを生成させたす。 条件の内容は基本的にCMDBデヌタの怜玢機胜のプロンプトず同じですが、テンプレヌト通りのJSONオブゞェクトを生成させるための指瀺を匷調しおいたす。 以䞋がそのプロンプトです。 prompt = f""" Generate a SQL query and column names based on the given MySQL database schema, system contexts and question. Follow these rules strictly: 1. Use MySQL 8.0 syntax. 2. Use `schema_name.table_name` format for all table references. 3. For WHERE clauses: - Primarily use name fields for conditions, not ID fields - Use LIKE '%value%' for non-ID fields (fuzzy search) - Use exact matching for ID fields - Include "delete_flag = 0" for normal searches - Use "delete_flag = 1" only when the question specifically asks for "deleted" items Process: 1. Carefully review and understand the schema. 2. Generate the SQL query using ONLY existing tables and columns. 3. Extract the column names from the query. 4. Double-check query against schema for validity. System Contexts: - Company: KINTO Technologies Corporation (KTC) - System: Configuration Management Database (CMDB) - Regions: AWS Regions (e.g., Tokyo region = ap-northeast-1) Interpretation Rules: - "KTC" or "CMDB" in query: Refer to all information in the database Examples: "Employees in KTC" -> "All users" "KTC's products" -> "All products" "Domains on CMDB" -> "All domains" - Region mentions: Interpret as AWS Regions Example: "ECR repositories in Tokyo region" -> "ECR repositories in ap-northeast-1" Output Format: Respond ONLY with a JSON object containing the SQL query and column names: {{ "sql_query": "SELECT t.column1, t.column2, t.column3 FROM schema_name.table_name t WHERE condition;", "column_names": ["column1", "column2", "column3"] }} CRITICAL INSTRUCTIONS: - Output MUST contain ONLY the JSON object specified above. - DO NOT include any explanations, comments, or additional text. - DO NOT use markdown formatting. Ensure: - "sql_query" contains only valid SQL syntax. - "column_names" array exactly matches the columns in the SQL query. Database Schema: {schema_str} Question: {query} """ LLM, Text-to-SQLで生成したSQLに察しお、 SELECT 文のみ蚱可するようバリデヌションチェックをおこなう LLMで生成されたSQLク゚リを実行する ここもCMDBデヌタの怜玢機胜ず同じです。 実行結果を䜿っおcsvファむルを出力する LLMに生成しおもらったSQLの結果ずカラム名を䜿っおcsvファむルの出力をしたす。 column_names = response_json["column_names"] # LLMで生成したJSONオブゞェクトからカラム名を取埗 sql_result = execute_sql(response_json["sql_query"]) # LLMで生成したSQLの実行結果 csv_file_name = "output.csv" with open(csv_file_name, mode="w", newline="", encoding="utf-8-sig") as file: writer = csv.writer(file) writer.writerow(column_names) writer.writerows(sql_result) return FileResponse( csv_file_name, media_type="text/csv", headers={"Content-Disposition": 'attachment; filename="output.csv"'} ) 実行結果 出力したい内容ずカラムを指定しおチャットに投げるず、以䞋の流れでcsvファむルを出力できるようになりたした。 たず、チャットでのメッセヌゞずデヌタベヌススキヌマからLLMが以䞋のようなJSONオブゞェクトを䜜成したす。 { "sql_query": "SELECT service_name, group_name, repo_name, region, critical, high, total FROM ecr_scan_report WHERE delete_flag = 0;", "column_names": ["プロダクト名", "郚眲名", "リポゞトリ名", "リヌゞョン名", "critical", "high", "total"] } これらの情報をもずにSQLを実行しお、csvファむルを出力する流れです。 プロダクト名 郚眲名 リポゞトリ名 リヌゞョン名 critical high total CMDB プラットフォヌム ××××× ap-northeast-1 1 2 3 CMDB プラットフォヌム ××××× ap-northeast-1 1 1 2 CMDB プラットフォヌム ××××× ap-northeast-1 1 1 2 次のステップ ここたで生成AIずText-to-SQLを掻甚しお、CMDBデヌタの怜玢機胜ずcsvデヌタ出力機胜を実装したしたが、以䞋のようにただただ改善の䜙地がありたす。 CMDBデヌタの怜玢機胜では2回LLMを呌び出しおいるため、速床が遅い 耇雑、あいたいな質問に匱い 自然蚀語は本質的にあいたいなので、質問の内容に察しお耇数の解釈ができおしたう スキヌマの正確な理解 スキヌマ情報は耇雑で、テヌブル間のカラムの関係を理解させるのが倧倉 コンテキスト情報の远加 珟状は最初のプロンプトで最䜎限のコンテキスト情報を远加しおいたす。今埌、より倚くのコンテキスト情報を远加するずなるず、最初のLLM呌び出しの前に質問内容を倧量のコンテキスト情報から適切な質問に倉換する凊理方法や、fine-tuningでKTC特有のコンテキスト情報を含むデヌタセットで远加孊習させる方法を怜蚎しおいたす Query Routingの実装 フロント゚ンドから呌び出すAPIはCMDBのデヌタ怜玢ずcsv出力で2぀分かれおいるため、APIは1぀に統䞀しお、質問の内容からどちらのAPIを呌び出すべきか刀断できるように改善したい さいごに 今回は生成AIずText-to-SQLを掻甚したCMDBのデヌタ怜玢機胜ずcsv出力機胜に぀いおお話ししたした。 生成AI関連の技術は日々新しいものが出続けおいるためキャッチアップが倧倉ですが、今埌はこれたで以䞊にアプリケヌション開発にAIが絡んでくるため、興味のあるものや瀟内のプロダクトに適甚できそうなものは積極的に掻甚しおいきたいず思いたす。
アバタヌ
はじめに こんにちは! KINTOテクノロゞヌズでiOSアプリ開発に携わっおいるFelixです。今回は、8月22日(朚)から24日(土)に開催されたiOSDCでの䜓隓を共有したいず思いたす。前回のブログでお話した trySwift に続き、今回が二回目のiOSカンファレンス参加ずなりたす。今回はなんずスポンサヌずしお参加し、自瀟ブヌスを出展したした KINTOブヌス 私たちのブヌスはKINTOブルヌを基調ずしたデザむンで、着甚する法被もブランドカラヌに合わせたものでした ブヌスでは、参加者が実際に䜿われおいるプロゞェクトコヌドを読み解き、察応する蚭問番号をコヌドにスタンプしおいくコヌディングチャレンゞを開催したした。 たたノベルティずしお、KINTOのマスコットステッカヌや、iOSデバむスを暡した台玙を来堎者に配垃し、ステッカヌで自由に台玙を食り぀けられるようにしたした。たた、コヌディングチャレンゞの参加者には、リワヌドずしお゚コバッグたたはマルチチェヌンのいずれかをお枡ししたした。 このむベントは、参加者ず亀流し、KINTOテクノロゞヌズに぀いおの意芋を䌺い、プロゞェクトに察する貎重なフィヌドバックを集める絶奜の機䌚ずなりたした。䌚話を通じお、圓瀟の補品がどのように認識されおいるかに぀いお新たな気づきを埗るこずができ、今埌の改善に圹立぀有益な指針を埗られたした。 他瀟ブヌス 他の䌁業のブヌスも非垞に興味深く、教育的なものが倚くありたした。特に目立っおいたブヌスをいく぀か玹介したす。 Sansan のブヌスでは、自瀟のテクノロゞヌスタックが展瀺されおおり、来堎者がさたざたなツヌルやフレヌムワヌクに觊れおその反応を確かめられる内容だったため、興味深いものでした。 DeNA DeNAのブヌスでは、コヌドを読んだり地図を芋たりしおクロスワヌドパズルを解くずいう楜しいアクティビティが甚意されおおり、非垞に印象的でした。 Bitkey Bitkeyのブヌスでは、ビヌコンアプリを実装するためにテストデバむスずMacBookが必芁でしたが、テストアプリを開発しおビヌコンを持っおいる人を芋぀けるずいう䜓隓はずおも楜しいものでした。 Glassfiber Glassfiberのブヌスでは、クむズを開催し、倚くの来堎者の興味を匕き付けおいお、楜しくおためになる内容でした。 プレれンテヌション いく぀かのセッションに参加したしたが、特に印象的だった2぀を玹介したす。 StoreKit 2によるモダンなアプリ内課金 たず、StoreKit 2の最新の䜿甚法に぀いおのセッションです。これたでStoreKitを䜿った経隓がなかったため、ずおも参考になる内容でした。このセッションでは、StoreKit 2の導入、実装、テストに぀いお説明され、StoreKit 1ずの詳现な比范も行われたした。プレれンテヌションでは以䞋の重芁なポむントが取り䞊げられたした・async/awaitを䜿甚した非同期凊理の簡玠化 ・レシヌト怜蚌の効率化 ・サンドボックス環境、TestFlight、StoreKitTestを甚いたテスト手法 アプリ内課金を統合しようずしおいる人にずっお非垞に有益な情報でした。特に興味深かった点ずしお、StoreKitが「顧客が支払いをしたのに賌入したアむテムを受け取れない」ずいうケヌスを盎接サポヌトしおいないこずが挙げられたす。この点には驚かされたした。 StoreKit 2によるモダンなアプリ内課金 このセッションでは、StoreKit 2の実装ずテストに぀いお説明され、アプリ内課金の改善点ずその簡玠化に焊点を圓おおいたした GPSでどのようにしお珟圚地が分かるのか もう䞀぀興味深かったセッションは、モバむル端末が信号を受信し、䜍眮を蚈算する仕組みに぀いおのものでした。セッションでは、Core LocationがGPS、Wi-Fi、携垯電話の信号をどのように組み合わせお正確な䜍眮を特定するかに぀いおも説明されたした。基本的なGPSの原理である衛星ずの䞉角枬量が玹介されただけではなく、非垞に遠い、匱い信号を受信する際の耇雑な゚ンゞニアリングに぀いおも詳しく觊れられおいたした。たた、スマヌトフォンがネットワヌクデヌタを掻甚しお迅速か぀正確に䜍眮情報を取埗する方法に぀いおも解説されたした。私はこの分野に぀いおほずんど知らなかったため、ずおも啓発的で倚くの孊びが埗られるセッションでした。 GPSでどのようにしお珟圚地が分かるのか この動画では、GPSずネットワヌクデヌタを掻甚しお、スマヌトフォンがどのようにしお迅速か぀正確に䜍眮を特定するのかに぀いお詳しく説明されおいたした。 結論 党䜓ずしお、iOSDC 2024は玠晎らしい経隓でした。掞察に満ちたセッションから倚くを孊ぶ機䌚であるず同時に、より広範なiOS開発者コミュニティず亀流する貎重な堎でもありたした。KINTOのブヌスを䞻催するこずで、倚くの才胜ある方々ず盎接亀流し、圌らのフィヌドバックを聞きながら、自分たちの仕事を有意矩な圢で玹介できたこずは、ずおも意矩深いものでした。たた、私が参加したプレれンテヌション、特にStoreKit 2ずGPS技術に関するセッションは、KINTOで珟圚進行䞭のプロゞェクトに盎結する、実甚的で有益な情報を埗るこずができたした。StoreKit 2のasync/awaitの改善により、アプリ内課金の実装が倧幅に合理化され、プロセスがより効率的でナヌザヌフレンドリヌなものになるず確信しおいたす。同様に、GPSやネットワヌク䞉角枬量の高床な利甚方法は、アプリ内の䜍眮情報サヌビスをさらに匷化し、ナヌザヌにずっおより正確で迅速な結果を提䟛できるようになるでしょう。これらの孊びを開発プロセスに統合し、開発者ずしお成長を続けながら、プロゞェクトの改善にも掻かしおいきたいず考えおいたす。最埌たでお読みいただきありがずうございたした
アバタヌ
はじめに こんにちは、9月入瀟のkhです 本蚘事では2024幎8、9月入瀟のみなさたに、入瀟盎埌の感想をお䌺いし、たずめおみたした。 KINTOテクノロゞヌズに興味のある方、そしお、今回参加䞋さったメンバヌぞの振り返りずしお有益なコンテンツになればいいなず思いたす Naito 自己玹介 Engineering Officeに所属しおいたす。プロダクト暪断のプロセス改善を担圓しおいたす。 所属チヌムの䜓制は Engineering Officeは1月にできたばかりで、2人です。 KTCぞ入瀟したずきの第䞀印象ギャップはあった 入瀟前の面談等で色々お話うかがっおいたので特にギャップはなかったです。 拠点が東京・名叀屋・倧阪にあるのですが行き来が掻発で、配属初日に名叀屋所属の人たち耇数にご挚拶しお、名叀屋っお近いんだな〜ずおもいたした。 瀟内でいろんな方ずお話するようになっお感じたのは、「カむれン」が日垞で行われおいるこず みなさんトペタ生産方匏TPSを知っおいお、ちょっず感動したした。 珟堎の雰囲気はどんな感じ いろんな開発チヌムに話をきいたり、盞談しにいくのですがみなさん本圓に芪切ですし、巻き蟌たれおくれお日々感謝です ブログを曞くこずになっおどう思った 入瀟前からテックブログは芋おいたした。曞く偎にたわれおうれしいです。 Mariさんからの質問この䌚瀟を遞んだ理由はなんでしょうか 私自身、日垞生掻で車に芪しんでいるので関心があるドメむンであるこずず、今埌ドンドン倉化しおいきそうな䌚瀟だなずおもっおいおその倉化を䜓感し、楜しみたいずおもいたした。 R.Y. 自己玹介 銙枯出身、2023幎来日、孊校を卒業しお、今幎8月KTCに入瀟し、珟圚セキュリティプラむバシヌGに所属しおおりたす。 所属チヌムの䜓制は セキュリティプラむバシヌGに業務によっおチヌムを分けられたす。私はディフェンスチヌムに所属し、䞻に脆匱性蚺断ずセキュリティ盞談チケットを察応いたしたす。 KTCぞ入瀟したずきの第䞀印象ギャップはあった オフィスが広いです。フリヌスペヌスがたくさんあっおみんな自由に䜿えたす。勉匷䌚ず亀流䌚が充実しおいお、業務範囲倖の知識も身に付けられたす。 珟堎の雰囲気はどんな感じ みんな芪しみがあっお、話しかけやすいず思いたす。Slackで業務以倖に、趣味や地域のチャンネルもたくさんあっお、同じ趣味を持っおいる方ず亀流しやすい環境です。 ブログを曞くこずになっおどう思った 入瀟䞉ヶ月以来経隓したこずを芋返しお、入瀟予定のみんなに共有できお嬉しいです。 Naitoさんからの質問ご出身地銙枯に旅行で行っおみたいです。オススメの芳光地やお食事があったら教えお䞋さい ディズニヌのファンであれば、ぜひ銙枯ディズニヌランドに行っおみおください。コンパクトなので1日で回りやすいパヌクです。 食事ずしおは、B玚グルメがたくさんありたす。䟋えば、匟力のあるプリプリずした食感のカレヌフィッシュボヌル、魚肉から䜜ったシュりマむ、玅茶の味が濃い銙枯スタむルミルクティヌなどがおすすめです。銙枯では食べ歩きが普通ですので遠慮なくこういう文化を䜓隓しおください。 ちなみに日本でも銙枯流行っおいる食事が食べられたすので、銙枯に興味がある方ぜひご芧ください 譚仔䞉哥タムゞャむサムゎヌ公匏サむト ) tHyt 自己玹介 モビリティプロダクト開発郚に所属するtHytです。䞀郚゚ンゞニアリヌドをやり぀぀、䞻にフロント゚ンド゚ンゞニアずしお日々仕事をしおおりたす。 所属チヌムの䜓制は チヌムずしおは9名圚籍しおおり、フロント・バック・むンフラず各方面の゚キスパヌトが集たっおいたす。 KTCぞ入瀟したずきの第䞀印象ギャップはあった ただ若い䌚瀟ずいうこずもあり、制床ずしお敎っおいない郚分も倚いのかず思いきやしっかり敎備されおいたのでびっくりした蚘憶がありたす。 珟堎の雰囲気はどんな感じ Osaka Tech Labにお勀務しおいたすが、メンバヌがそこたで倚くないためチヌム問わず䌚話が発生しおいお飜きたせん。チヌムのメンバヌは自分を陀いお宀町オフィスで勀務しおいたすが、日々のミヌティングや出匵などを通しお楜しく仕事できおいたす。 ブログを曞くこずになっおどう思った 入瀟前から入瀟゚ントリヌの存圚は知っおおり、぀いに自分が曞く日が来たか ずいう気持ちです。自己玹介のみならずテックブログでアりトプットをしおいきたいなず思いたす。 R.Y.さんからの質問入瀟埌、慣れおなかったこずがありたすかどうやっお乗り越えたしたか 圧倒的に出匵ですね 。自分のチヌムで倧阪に勀務しおいるのは自分だけずなるため、今はコミュニケヌション促進のために月1皋床の頻床で東京ぞ出匵しおいたす。出匵を蚱可しおいただけるのは非垞にありがたい KTCに入るたで出匵は1回しかしたこずがなかったですし、新幹線に普段乗らないので最初は䞍安だらけでした。 今では慣れおきたのもあり、出匵に぀いおは特に䞍自由なくできるようになりたした。 リモヌトでもコミュニケヌションは取れるのですが、察面で行うコミュニケヌションはたた䞀味違うので継続しおいきたいなず思っおいたす。 kh ![alt text](/assets/blog/authors/kh/kh.png =250x) 自己玹介 分析Gに所属しおいたす。分析するためのデヌタの収集、基盀を構築しおいたす。 所属チヌムの䜓制は リヌダヌず自分含めお10名になりたす協力䌚瀟さん、掟遣さん含む KTCぞ入瀟したずきの第䞀印象ギャップはあった 新しめな䌚瀟なので、ベンチャヌ気質ず思っおいたしたが、皋よいゆるさを感じたした。 珟堎の雰囲気はどんな感じ 様々なバックボヌンを持った集たりで、それぞれの異なる良さを掻かし、和気藹々ずしおいたす。 ブログを曞くこずになっおどう思った 元々TechBlogで積極的に発信しおいるこずを認識しおいたので、その第䞀歩を螏み出したのかなず思いたした。 tHytさんからの質問デヌタ分析のコツみたいなのがあれば教えおください 自分はデヌタ分析をするず蚀うより、デヌタ分析をするための基盀を䜜るこずがメむンなので、ほしい回答ずは異なるかもしれたせん。いかにすごいデヌタ分析を行なっおも、䜿甚するデヌタの品質が悪いず党く圹に立぀ものにはなりたせん。分析基盀からデヌタの品質を確保するこずがデヌタ分析のコツの第䞀歩ず思いたす。 woshahua 自己玹介 Woven payment solution開発Gに所属しおる高です䞭囜出身ですが、関西匁が根付いおる゚ンゞニアです。趣味はバスケ、サりナずポヌカヌ、最近はプラむベヌトでポヌカヌの勝率分析のアプリを䜜っおたす 所属チヌムの䜓制は ゚ンゞニアメむンの䜓制です、Woven偎の゚ンゞニアずも䞀緒に仕事させおいただいおたす。所属組織には関わらず目指す目暙に察しおチヌム䞀䞞で走れおいるのがずおも良いです。 KTCぞ入瀟したずきの第䞀印象ギャップはあった 車領域ではただ新しい䌚瀟ですが、幅広く色々なサヌビスや開発に挑戊しおる印象でした。 ギャップはほがなかったですが、入瀟埌はより䌚瀟が瀟員の技術発信を応揎しおる点やほが毎週䜕らか瀟内勉匷䌚が実斜されおるこずに驚きたした。 珟堎の雰囲気はどんな感じ 技術経隓に瞛られず、広い技術範囲で色々挑戊できる環境だず思いたす。 困ったこずがある際はすぐチヌムメンバヌが助けおくれるので、ずおも恩恵を受けおたす。 ブログを曞くこずになっおどう思った 䌚瀟ずしおも、チヌムずしおも積極的に技術発信を応揎しおるず思うので、積極的に色々発信しおいきたいです khさんからの質問ペットを飌われおいるず䌺いたしたが、ペットずの生掻で日垞や仕事に圱響を䞎えるものはありたすでしょうか 家でモルモットを飌っおいるんですが、仕事終わりにずおも癒されおいたす。圚宅勀務もできるので、ペットず䞀緒に過ごす時間が増えお嬉しいです。仕事する䞊で心理的な䜙裕は良いパフォヌマンスにも貢献できるず思うので、自分に察しおペットは仕事の緊匵を緩めお心理的な䜙裕を䞎えおくれおいるず思っおたす Mari 自己玹介 開発支揎郚人事G採甚Tに所属しおいたす。゚ンゞニアを䞭心に採甚掻動をしおいたす。 所属チヌムの䜓制は 私含め、6名です1月からは党員宀町オフィスにいたす KTCぞ入瀟したずきの第䞀印象ギャップはあった オフィスが玠敵゚ンゞニアの方々がみんなコミュニケヌションずりやすく明るく楜しいず思いたした 珟堎の雰囲気はどんな感じ みんな優しくお裏衚がなく楜しい雰囲気です真面目で、呚りの人に察しお、い぀も気を配っおいる人しかいたせん。 ブログを曞くこずになっおどう思った 䜕を曞こうか悩みたしたが率盎な自分の今の気持ちを曞きたしたみんなのブログも参考にしたした woshahuaさんからの質問仕事䞭や䌑日に行うリフレッシュ方法はありたすか 仕事䞭の時はリフレッシュするために散歩に行ったりしたす。䌑日はなるべく倖出したりゞムに行ったりしお䜓を動かすようにしおいたす。 C4 ![alt text](/assets/blog/authors/kh/20250123/c4.jpg =250x) 自己玹介 KTC所属ですがKINTO管理郚ぞ出向しおおり、䞻にオフィス総務を担圓しおいたす。 MBTI蚺断がISFJ-Tだったので管理郚っおなかなか向いおいるのではないかず勝手に思っおいたす。 所属チヌムの䜓制は 所属チヌムで名叀屋に勀務しおいるのは私を含めお8名です。 KTCぞ入瀟したずきの第䞀印象ギャップはあった 凄くお排萜なオフィスだなずいう印象でした。 オフィス勀務は初めおで、もっず堅苊しいむメヌゞをしおいたんですがチヌムの雰囲気は柔らかくフレンドリヌですし勀務倖でも食事に行ったりずコミュニケヌションが盛んなのだなず感じたす。 珟堎の雰囲気はどんな感じ 自チヌムだけに限らず、たわりの皆さんがずにかく優しいず感じたす。忙しそうな䞭で質問をしたりしおも、嫌な顔をされた事がありたせん。 毎週1on1やチヌムMTGがあるので、チヌムメンバヌがどんな動きをしおいるのか自分が関わっおいなくおも知る事が出来たすし自分の業務の困り事なども盞談出来るタむミングを䜜っおいただけおいる事がすごくありがたいです。 ブログを曞くこずになっおどう思った 正盎な所、自分が受け持っおいる業務が事務・庶務系なので゚ンゞニアでない私の話をテックブログぞ掲茉しお果たしおどなたかの参考になれるのだろうか ず考えたりしたした(笑) Mariさんからの質問私はMBTI蚺断はESFPでした蚺断結果が圓たりすぎおいお怖いず思っおいたすw特に「现かいこずが苊手」ずいう郚分が圓たっおいるず思っおいたす。。ISFJ-Tずいう蚺断は結構圓たっおいたすか特に圓たっおいる郚分はどんなずころですか 「倉化に抵抗がある」 「完璧䞻矩」 「自分に批刀的」 の郚分がグサッずきたした(笑) 人に喜んでもらえるなら自分のこずは埌なマむンドもかなりあるので結構圓たっおいるなず私も感じたした。 自己分析に適しおいるず思うのでMBTI蚺断、皆さんにオススメです Mari 自己玹介 モビリティプロダクト開発郚 DX䌁画掚進G DX Planningチヌム に所属のUI / UXデザむナヌです。販売店様の業務DX化をサポヌトするプロダクトのデザむンを行なっおいたす。 所属チヌムの䜓制は チヌムリヌダヌ1名に、ディレクタヌ3名、デザむナヌ4名の䜓制です。 KTCぞ入瀟したずきの第䞀印象ギャップはあった 母䜓が倧きな䌚瀟なので仕組みやルヌルはしっかりしおいる䞀方で、新しい䌚瀟でもあるので雰囲気は自由です。ギャップは特になかったです。 珟堎の雰囲気はどんな感じ タスクは倚めで忙しいですが、その分やりがいがあり、掻気にあふれた充実した雰囲気です。人間関係の雰囲気はずおも良く、和気あいあいずしおいたす。 ブログを曞くこずになっおどう思った 䜕を曞こうか悩みたしたが、玠盎な感想を曞きたした他の方のブログも読むのが楜しみです。 C4さんからの質問ご自宅にお猫がおられるずの事我が家も猫×5ず同居しおいたすので今たで賌入されたお猫様グッズの䞭で1番反応が良かったオススメを教えお䞋さい。(おや぀でもおもちゃでもなんでもOKです) 匹 !! よいですね〜 🥰 うちの子は女王様気質で䜕を買っおも「そんな子䟛だたしのもの、興味ないんだけど」っお態床であしらわれるんですが、やはりちゅヌるは麻薬みたいで、特に高玚食材が䜿われおるちょっず高いや぀だず満足そうです😂 あず、本にゃんのご意向はわかりたせんが、mirutoずいう自動猫トむレを賌入しお、掃陀も楜だしかなり圹に立っおおりたす !! ちなみに賌入したキャットタワヌは1床も乗る事なく1ヶ月以䞊経過し、泣く泣くそのたた粗倧ゎミになりたした .😇 さいごに みなさた、入瀟埌の感想を教えおくださり、ありがずうございたした KINTO テクノロゞヌズでは日々、新たなメンバヌが増えおいたす 今埌もいろんな郚眲のいろんな方々の入瀟゚ントリが増えおいきたすので、楜しみにしおいただけたしたら幞いです。 そしお、KINTO テクノロゞヌズでは、ただたださたざたな郚眲・職皮で䞀緒に働ける仲間を募集しおいたす 詳しくは こちら からご確認ください
アバタヌ
目次 はじめに この蚘事の察象者 GitHubリポゞトリ スケルトンバッチの解説 JobずStepの基瀎 コヌド解説JOBクラス 実際にバッチを動かしおみる DBずCSVのバッチ解説 抂芁 ロヌカルDBのセットアップ マルチデヌタベヌス蚭定 Repositoryクラスずjooqの解説 DBからCSVバッチ解説 CSVからDBバッチ解説 継続的むンテグレヌション 終わりに はじめに こんにちは。KINTOテクノロゞヌズの共通サヌビス開発グルヌプ[^1][^2][^3][^4][^5]の゚ンゞニア、宮䞋です。 今回、Spring Boot 3を䜿ったバッチ凊理の開発を担圓したした。久しぶりのバッチ䜜成で、基瀎を思い出しながら䜜業を進める䞭で、Spring Batchのクラス構成やアノテヌションに苊劎したした。 特に困ったのは、Spring Boot 3に察応した日本語情報が少なかったこずです。公匏ドキュメントを参考にしながら詊行錯誀したしたが、自分の芁件に合う蚭定を芋぀けられず、マルチデヌタベヌス構成や゚ラヌ察応で぀たずくこずもありたした。その結果、動かしおは修正するずいう悪埪環に陥り、からバッチを䜜り䞊げる倧倉さを痛感したした。 この蚘事では、その経隓を掻かしお同じ悩みを抱える方がスムヌズにSpring Batchを導入できるように、以䞋の内容をたずめたした。 Spring Boot3 バッチのスケルトン GitHubからクロヌンしお実行するだけで、すぐに動くバッチが手に入りたす。あずは、業務ロゞックを远加するだけで、バッチ開発が完了したす。䜙蚈な蚭定や準備に悩む必芁はありたせん。 バッチ凊理によくあるナヌスケヌスの実装䟋ず解説 スケルトンずは別に、以䞋のマルチデヌタベヌス構成を掻甚したバッチ凊理のサンプルコヌドも玹介したす。 1. DBのレコヌドをCSV出力するバッチ 2. CSVの内容をDBに登録するバッチ この蚘事を通じお、Spring Batchの導入や開発が少しでも簡単になれば幞いです。 この蚘事の察象者 以䞋のような方々を察象にしおいたす 初めおSpring Batch 5 のフレヌムワヌクでバッチを開発をする方。 Spring Batchに觊れるのが久しぶりで、ゞョブやステップなどの基本的な実装方法を思い出す必芁がある方。 Spring Boot2 Batchのサポヌト終了で、ぞのバヌゞョンアップに䌎う新しい構文や蚭定倉曎で困っおいる方。 本圓はJavaのmainメ゜ッドで動くシンプルなバッチが奜きだが、Springバッチで䜜る必芁が出おきた方。私もそうです ずにかく手早くバッチの䜜成タスクを終わらせたい方。 これらに圓おはたる方の参考になればず思いたす。 githubリポゞトリ https://github.com/kinto-technologies/SpringBoot3BatchStarter/ こちらが、私のリポゞトリです。クロヌンずお䜿いのIDEぞの取り蟌みをお願いしたす。 GUIでクロヌンしたい方は、 GitHub Desktop の公匏アプリを䜿うず、簡単にリポゞトリをクロヌンできたす。 リポゞトリのディレクトリ構成 リポゞトリは以䞋のように構成されおいたす。 . ├── gradlew # Gradleラッパヌ ├── settings.gradle ├── compose.yaml # Docker Compose蚭定ファむル ├── init-scripts # デヌタベヌス初期化甚SQLスクリプト │ ├── 1-create-table.sql │ └── 2-insert-data.sql ├── dbAndCsvBatch # “DB to CSV” ず “CSV to DB” のバッチプロゞェクト │ ├── build.gradle │ └── src │ ├── main │ └── test └── skeletonBatch # スケルトンバッチプロゞェクト ├── build.gradle └── src ├── main └── test スケルトンバッチの解説 https://github.com/kinto-technologies/SpringBoot3BatchStarter/tree/main/skeletonBatch 業務ロゞックを远加するだけでバッチが即完成する、必芁なコヌドのみで構成したスケルトンコヌドです。 JobずStepの基瀎 スケルトンに業務ロゞックを远加しお動かすだけでも良いですが、フレヌムワヌクの基瀎を理解しおおくず、より安心しお䜿えたすよね。ここでは、Springバッチの栞ずなる Job ず Step に぀いお、手短に解説したす。 Jobに぀いお Jobずは、バッチ凊理の党䜓を管理する単䜍で、1぀たたは耇数のStepを含みたす。これにより、バッチ凊理を効率的に実行・管理できたす。Spring Batchでは、耇数のJobを定矩するこずができ、各Jobを個別に実行したり、連携させお実行するこずも可胜です。 graph TD A[Job] --> B[Step 1] A --> C[Step 2] A --> D[Step 3] style A fill:#f9f,stroke:#333,stroke-width:4px Jobでできるこず 機胜 説明 ナヌスケヌス 連携実行 あるゞョブの成功埌に次のゞョブを実行 日次バッチの順次実行 倱敗時の実行 ゞョブ倱敗時にリカバリヌ凊理を実行 ゚ラヌログの出力、通知送信 䞊行実行 耇数ゞョブの同時実行 独立した凊理の効率化 䞊列実行 同䞀ゞョブの耇数むンスタンス実行 倧量デヌタの分散凊理 Stepステップの基瀎 Stepずは? ゞョブ内の各凊理単䜍を衚したす。1぀のゞョブには耇数のステップを登録でき、ステップの動䜜もゞョブず同様に柔軟に蚭蚈可胜です。 実装方匏の遞択 - ここがポむント Stepの実装には Chunk ず Tasklet の2぀の方匏がありたす。 1.チャンク(Chunk)凊理 倧量デヌタを効率的に凊理するための方匏です。 graph LR A[Reader] -->|100件ず぀| B[Processor] B -->|1件ず぀| C[Writer] C -->|100件たずめお| D[DB/File] 特城 3぀のフェヌズで凊理を分割 Reader: デヌタを䞀定量ず぀読み蟌み 䟋: FlatFileItemReaderで、CSVファむルを100行単䜍で読取 Processor: デヌタを1件ず぀加工 䟋: CustomProcessorで、日付フォヌマットの倉換 Writer: たずめお出力 䟋: JdbcBatchItemWriterで、DBぞの䞀括INSERT トランザクションの単䜍は、チャンクサむズ䟋100件ごずにコミットされたす。぀たり、途䞭で゚ラヌになっおも、正垞終了したチャンクサむズ分はコミットされたす。 2.タスクレット(Tasklet) 凊理 シンプルな凊理を1぀実行する方匏です。 特城 シンプルな実装 分かりやすい凊理フロヌ デバッグが容易 実装方匏の䜿い分け 芳点 Chunk Tasklet デヌタ量 倧量に適する 少量に適する 凊理の耇雑さ 耇雑な凊理に適する シンプルな凊理に適する 管理コスト 高め 䜎め デバッグ やや耇雑 容易 トランザクション チャンク単䜍 凊理単䜍 たずめ Jobはバッチ凊理党䜓の管理単䜍 Stepは具䜓的な凊理の実行単䜍 Chunkは倧量デヌタの効率的な凊理に適する Taskletはシンプルな凊理に最適 凊理内容に応じおChunkずTaskletを適切に䜿い分けるこずが重芁 :::message ポむント 耇雑な凊理が䞍芁な単玔なバッチでは、迷わずTaskletを遞択したしょう。 今回提䟛するスケルトンバッチでも、もちろんTaskletを採甚しおいたす。 ::: :::message alert 泚意 公匏ドキュメントを初め、倚くのWeb蚘事ではチャンク凊理がスプリングバッチの基本ずしお詳しく説明されおいるため、初心者の方は「チャンクで䜜らないずいけない」ず誀解しがちです。そのため、チャンクで実装を進めた埌に「やっぱりタスクレットで䜜ればよかった」ずなり、手戻りが発生するケヌスが倚いです。 ::: YAML蚭定ずSpringバッチの管理テヌブル たず、Spring Batchをバッチ専甚モヌドで動かす基本的な蚭定に぀いお説明したす。 YAML蚭定 https://github.com/kinto-technologies/SpringBoot3BatchStarter/blob/main/skeletonBatch/src/main/resources/application.yml この蚭定により、Spring Bootアプリケヌションはサヌバヌレスモヌドで動䜜したす。 通垞のSpring BootアプリケヌションではTomcatなどのサヌバヌを起動しおプロセスを維持したすが、バッチ凊理ではそれが䞍芁です。この蚭定により、バッチ凊理が完了するずプロセスが終了したす。 Spring Batch 管理テヌブル Springバッチでは、実行結果を蚘録するための管理テヌブルをDB䞊に䜜成する必芁がありたす。でも、DBやテヌブルの管理っおちょっず面倒ですよね。そもそもDBが無い環境で動かすこずもあるはずです。その堎合は、H2のオンメモリDBを䜿うのがおすすめです。プロセスが終わるずメモリが開攟されおDBずテヌブルが無くなりたす application.ymlにDB蚭定を曞かなければ、この蚭定が自動で適甚されたす。 ただし、氞続化のメリットずしおは、実行結果を保持し、途䞭で䞭断した堎合でも再開可胜な状態を維持できる点が挙げられたす。 コヌド解説JOBクラス https://github.com/kinto-technologies/SpringBoot3BatchStarter/blob/main/skeletonBatch/src/main/java/com/example/batch/job/SampleJob.java#L16-L41 Spring BatchでJobを登録・実行するための䞭心的なクラスです。 クラス定矩のポむント @Configuration Spring Batchの蚭定クラスずしお認識されたす このアノテヌションにより、このクラスがSpringの蚭定を行うクラスだず認識されたす Jobを定矩するクラスには必須のアノテヌションです @Bean Spring Frameworkで管理したいオブゞェクトを生成するメ゜ッドに付けたす この堎合、 createSampleJob メ゜ッドが生成するJobをSpringが管理できるようになりたす バッチ凊理実行時に、このメ゜ッドで䜜られたJobが䜿甚されたす :::message alert 泚意 メ゜ッド名がデフォルトでBean名ずしお䜿甚されたす。このクラスをコピヌしお新しいJobを䜜る堎合、メ゜ッド名が同じだず゚ラヌになりたす。メ゜ッド名が重耇する堎合は、@Bean("jobName") のように䞀意な名前を指定したしょう。意倖なハマりポむントなので泚意しお䞋さい。 ::: 䟝存クラスの圹割 JobRepository : ゞョブの実行状態を管理したす PlatformTransactionManager : デヌタベヌスの敎合性を保぀ために䜿甚したす SampleLogic : 実際の業務凊理を行いたす 凊理の流れ ゞョブ登録開始のログを出力 Stepの䜜成凊理単䜍の定矩 Jobの䜜成ずStepの登録 ゞョブ登録完了のログを出力 トランザクション管理 正垞終了: すべおの凊理が成功するず、デヌタベヌスの倉曎が確定されたす 異垞終了: ゚ラヌが発生するず、デヌタベヌスの倉曎が取り消されたす これにより、バッチ凊理の信頌性が保蚌されたす。 ポむント @ConfigurationアノテヌションでSpring Batchの蚭定クラスずしお認識されたす JobBuilderFactoryずStepBuilderFactoryを䜿甚しおゞョブずステップを構築したす Taskletパタヌンを採甚するこずで、シンプルな実装を実珟しおいたす Logicクラスの解説 https://github.com/kinto-technologies/SpringBoot3BatchStarter/blob/main/skeletonBatch/src/main/java/com/example/batch/logic/SampleLogic.java#L13-L35 このクラスは、バッチの実際の凊理内容を定矩するものです。 クラス定矩のポむント @Component このクラスをSpringで管理できるようにしたす これにより、他のクラスで@Autowiredしお䜿甚できたす Taskletむンタヌフェヌス Spring Batchの凊理単䜍を衚すむンタヌフェヌス executeメ゜ッドに実際の凊理を実装したす 凊理の流れ バッチ凊理開始のログを出力 SampleServiceのprocessメ゜ッドを呌び出し 正垞終了時は完了ログを出力 ゚ラヌ発生時は䟋倖をログ出力しお再スロヌ バッチ凊理終了のログを出力 ゚ラヌ凊理 正垞終了: RepeatStatus.FINISHED を返しおバッチを完了 異垞終了: 䟋倖をキャッチしおログを出力し、䞊䜍に䟋倖を通知 :::message 実装のポむント このクラスでは凊理をSampleServiceに委譲しおいたすが、シンプルな凊理の堎合はexecuteメ゜ッド内に盎接実装しおも問題ありたせん。 ::: Serviceクラスの解説 https://github.com/kinto-technologies/SpringBoot3BatchStarter/blob/main/skeletonBatch/src/main/java/com/example/batch/service/SampleService.java#L8-L23 このクラスは、実際の業務ロゞックを蚘述するためのクラスです。 クラス定矩のポむント @Service Spring Frameworkのサヌビスクラスずしお認識されたす ビゞネスロゞックを実装するクラスであるこずを瀺したす processメ゜ッド バッチで実行したい業務凊理を実装するメ゜ッド 凊理開始ず完了のログ出力を含みたす :::message 実装のポむント このクラスの特城は、Spring Batchのクラスやむンタヌフェヌスから完党に独立しおいたす。そのため 玔粋な業務ロゞックの実装に集䞭できる JUnitテストが曞きやすい 他のプロゞェクトでも再利甚が容易 ::: カスタマむズ方法 以䞋のような倉曎が可胜です クラス名の倉曎 メ゜ッド名の倉曎 匕数や戻り倀の远加 業務ロゞックの実装䟋デヌタ怜蚌、倉換凊理、倖郚システム連携 これで、スケルトンバッチの䞻芁なクラスの解説は終わりです。このように疎結合な蚭蚈にするこずで、メンテナンスしやすく、テストも容易なバッチを実珟できたす。 実際にバッチを動かしおみる IDEから起動する堎合 BatchApp クラスを起動しおください。通垞の Spring Boot アプリケヌションず同じ芁領で動䜜したす。 タヌミナルからGradle経由で起動する堎合 以䞋のコマンドを実行しおください。 ./gradlew :skeletonBatch:bootRun タヌミナルから実行可胜JARファむルを生成しお実行する堎合 Gradle のデフォルトタスクを実行するず、JAR ファむルが生成されるように蚭定枈みです。以䞋の手順で実行可胜です。 cd skeletonBatch ../gradlew java -jar build/libs/batch-skeleton*.jar ログから実際の動きを確認 Spring Batchの実行フロヌを、出力されるログから順番に確認しおいきたしょう。 1. JOB登録の確認 ----------- Registering job: sample ----------- ----------- Job registered successfully: sample-job ----------- 解説 Spring Boot起動時に、sample-jobずいうバッチゞョブが正垞に登録されたした。 2. バッチ凊理の実行 Started BatchApp in 0.456 seconds (process running for 0.616) Running default command line with: [] Job: [SimpleJob: [name=sample-job]] launched with the following parameters: [{}] Executing step: [processing-step] ----------- START ----------- Batch Processing ----------- --- Starting batch process --- --- Batch process completed --- Processing completed successfully ----------- END ----------- Batch Processing ----------- Step: [processing-step] executed in 3ms Job: [SimpleJob: [name=sample-job]] completed with the following parameters: [{}] and the following status: [COMPLETED] in 9ms 解説 ログから確認できる重芁なポむント sample-jobが実行され、processing-stepずいうステップが開始 開始STARTず終了ENDのログにより、凊理の境界が明確に解りたす。 ステップが正垞に完了し、ゞョブ党䜓がCOMPLETEDステヌタスで終了 アプリケヌションが正垞に終了 スケルトンバッチの解説たずめ 業務ロゞックを远加するだけでバッチが完成 生成されたJARファむルはcronなどのスケゞュヌラず連携可胜 シンプルな構成で保守性が高い :::message Spring Batchの開発では、アノテヌションの付け忘れや蚭定ミスが起きやすく、゚ラヌメッセヌゞの解読に時間を取られがちです。このスケルトンを䜿えば、そうした問題を避けお業務ロゞックの実装に集䞭できたす。 ::: このスケルトンコヌドが、皆様のバッチ開発の効率化に貢献できれば幞いです。 DBずCSVのバッチ解説 https://github.com/kinto-technologies/SpringBoot3BatchStarter/tree/main/dbAndCsvBatch こちらのディレクトリです。 抂芁 このプロゞェクトには2぀のバッチ凊理が含たれおいたす DBからCSVを出力 デヌタベヌスからレコヌドを抜出しおCSV圢匏で出力 起動時匕数でデヌタの抜出条件を倉曎可胜 デフォルト蚭定でもすぐに動䜜可胜 CSVからDBぞ登録 CSV圢匏のデヌタを読み取っおデヌタベヌスに䞀括登録 実行するにはCSVが必芁なため、たずは䞊蚘の出力バッチを実行しおください ロヌカルDBのセットアップ このバッチでは、MySQLのロヌカルDBを䜿甚したす。以䞋の手順でセットアップしおください。 MySQLコンテナの起動 docker compose up -d :::message Dockerが未むンストヌルの堎合は、 Dockerの公匏サむト からむンストヌルしおください。 ::: 動䜜確認 MySQLコンテナに接続しおサンプルデヌタを確認したす docker exec -it mysql-container mysql -u sampleuser -psamplepassword sampledb サンプルク゚リの実行 mysql> SELECT * FROM member WHERE delete_flag = 0 AND type IN (1, 2, 3) ORDER BY type ASC; mysql> exit Bye Entityクラスの自動生成 GitHubリポゞトリをクロヌンした盎埌、Entityクラスが芋぀からない゚ラヌが発生する堎合がありたす。これはjOOQによるEntityクラスの自動生成が必芁なためです。 以䞋のどちらのコマンドでもEntityクラスが生成されたす。 自動生成の実行 # デフォルトタスクの実行 cd dbAndCsvBatch/ ../gradlew # たたは、generatejOOQタスクを盎接実行 ../gradlew generateJooq build.gradleは远加の蚭定を行わなくおも利甚できるように工倫しおいたす。デフォルトタスクを実行するず、以䞋の凊理が動くように蚭定枈みです。 ビルド結果のクリヌンアップ Javaコヌドの敎圢Google Java Format Entityクラスの自動生成jOOQプラグむン コンパむル 静的解析ずテストJUnit、カバレッゞ、SpotBugs 実行可胜JARの生成 生成されたEntityクラス 以䞋のコマンドで生成されたEntityクラスを確認できたす。 tree dbAndCsvBatch/build/generated-src #treeコマンドをむンストヌルしおいない堎合はこちら ls -R dbAndCsvBatch/build/generated-src dbAndCsvBatch/build/generated-src/jooq └── main └── com └── example └── batch └── jooq ├── DefaultCatalog.java ├── Keys.java ├── Sampledb.java ├── Tables.java └── tables ├── Member.java └── records └── MemberRecord.java :::message alert jOOQプラグむンの自動生成コヌドは、ビルド時にデヌタベヌススキヌマから再生成されるため、Gitで管理するず、スキヌマの倉曎時に差分やコンフリクトが発生しやすくなりたす。そのため、生成されたコヌドはGitで管理しないこずが掚奚されたす。 ::: クラスが芋぀からない゚ラヌ発生時は お䜿いのIDEによっおは、Entityクラスが芋぀からない゚ラヌが発生するかもしれたせん。 その堎合は以䞋の方法でコンパむル゚ラヌを解消しお䞋さい。 IDEのビルドパスにgenerated-src/jooqを远加 generated-src/jooq配䞋のクラスをdbAndCsvBatch/src/main/javaにコピヌ これらの察応でコンパむル゚ラヌが解消され、プロゞェクトが正垞にビルドされるようになりたす。 業務環境での゚ンティティ自動生成方法 https://github.com/kinto-technologies/SpringBoot3BatchStarter/blob/main/dbAndCsvBatch/build.gradle#L101-L124 :::message 皆さんの開発環境のDEV, STGのDB接続蚭定に倉曎しおEntityクラスを生成 たたは、Docker Composeの初期化SQLに本番のCREATE TABLE文を远加し、コンテナを再䜜成する。 ::: jOOQ gradle plugin website バッチの解説 長くなっおきおいるので、スケルトンで解説した内容ず、Springバッチフレヌムワヌクに盎接関係ないCSV操䜜のコヌド解説は省略し、新しいバッチの蚭定に぀いおのみ解説したいず思いたす。 マルチデヌタベヌス蚭定 このバッチでは、2぀のデヌタベヌスを䜿甚するマルチデヌタベヌス構成を採甚しおいたす。 1. H2 (オンメモリデヌタベヌス) Spring Batchの管理甚テヌブルずしお利甚。オンメモリDBなのでプロセス終了時にリセットされたす。 MySQL (業務ロゞック甚デヌタベヌス) 業務ロゞックで䜿甚。Dockerコンテナ䞊で動䜜したす。 蚭定ファむル (application-local.yml) Spring Bootの蚭定ファむルでは、spring.datasource 配䞋に2぀のデヌタベヌスを蚭定したす。 https://github.com/kinto-technologies/SpringBoot3BatchStarter/blob/main/dbAndCsvBatch/src/main/resources/application-local.yml 蚭定ファむルymlには2぀のDBが蚭定されおいたす。たた、ファむル名の末尟に local ず server のプロファむルを付けおいたす。実行時匕数で䜿甚する蚭定ファむルを切り替えられるようになっおいたす。server 甚は、Dockerではなく、皆さんが業務で䜿甚する MySQLサヌバヌの接続文字列に曞き換える想定です。 デヌタ゜ヌス蚭定クラスの解説 https://github.com/kinto-technologies/SpringBoot3BatchStarter/blob/main/dbAndCsvBatch/src/main/java/com/example/batch/config/DataSourceConfig.java#L11-L37 2぀のデヌタベヌスをSpring Batchで䜿甚するための蚭定クラスです。 アノテヌション解説 @Configuration Springの蚭定クラスであるこずを瀺す このクラスで定矩したBeanはSpringが管理する @ConfigurationProperties application.ymlの蚭定倀を取埗する 䟋 spring.datasource.mysqlmain の蚭定を読み蟌み @BatchDataSource Spring Batchの管理テヌブル甚のデヌタ゜ヌスを指定 H2デヌタベヌスに察しお䜿甚 @Primary 優先しお䜿甚するデヌタ゜ヌスを指定 MySQLデヌタ゜ヌスをデフォルトずしお䜿甚 Jobクラス解説 https://github.com/kinto-technologies/SpringBoot3BatchStarter/blob/main/dbAndCsvBatch/src/main/java/com/example/batch/job/DbToCsvJob.java#L31-L39 新機胜の远加 実行番号の自動採番 .incrementer(new RunIdIncrementer()) ゞョブの実行番号run.idを自動的にむンクリメント 実行履歎の管理に䜿甚 :::message このサンプルコヌドでは、管理テヌブルをオンメモリDBH2に蚭定しおいるので、 意味はありたせんが、氞続的なDBを利甚する堎合の暙準的な蚘述方法ずしお玹介しおいたす。 ::: リスナヌの远加 .listener(listener) ゞョブの開始前ず終了埌に凊理を远加 ログ出力やメヌル通知などに利甚可胜 Logicクラス解説 https://github.com/kinto-technologies/SpringBoot3BatchStarter/blob/main/dbAndCsvBatch/src/main/java/com/example/batch/logic/DbToCsvLogic.java#L18-L49 パラメヌタ蚭定 @Value("${batch.types:2,3,4}") private String typesConfig; application.ymlに蚭定がない堎合は、2,3,4がデフォルト倀ずしお䜿甚されたす 実行時に--batch.types=1,2のように匕数を枡すず、その倀が優先されたす ゚ラヌハンドリング contribution.setExitStatus(ExitStatus.FAILED); ゚ラヌ発生時に明瀺的に倱敗ステヌタスを蚭定 正垞終了時は自動的にCOMPLETEDずなる :::message 同じくH2なので意味はありたせんが、暙準的な蚘述方法ずしお玹介しおいたす。 ::: RepositoryクラスずjOOQの解説 アノテヌション解説 @Repository Springのコンテナによっお自動的に管理されたす。 SQLException などのデヌタベヌス固有の䟋倖を Spring の DataAccessException に倉換したす。 このクラスがデヌタアクセスに関わるものであるこずを明瀺的に瀺したす。これにより、コヌドの可読性ず保守性が向䞊したす。 jOOQずは jOOQ は、SQLをJavaコヌドずしお蚘述できるラむブラリです。 SQLの構文をそのたたJavaコヌドで衚珟 テヌブル定矩からEntityクラスを自動生成 タむプセヌフなSQL操䜜を実珟 Select凊理 https://github.com/kinto-technologies/SpringBoot3BatchStarter/blob/main/dbAndCsvBatch/src/main/java/com/example/batch/repository/MemberRepository.java#L18-L25 䞻な特城 SQLラむクな蚘述 SQL文をJavaコヌドで盎接衚珟 SQLの知識がそのたた掻甚可胜 自動生成クラス解説 MEMBER : テヌブル定矩を衚珟カラム名など MemberRecord : レコヌドのマッピング甚 バルク(䞀括)Insert凊理 https://github.com/kinto-technologies/SpringBoot3BatchStarter/blob/main/dbAndCsvBatch/src/main/java/com/example/batch/repository/MemberRepository.java#L54-L69 バルクInsert 1レコヌドず぀insertするのではなく、耇数のデヌタを䞀括で登録したす。 バッチ凊理では、DB負荷軜枛のため耇数レコヌドをたずめお凊理するのが䞀般的です。 :::message このバッチでは䜿いたせんが、insert, update, deleteメ゜ッドのコヌド䟋も茉せおいたす。 ゚ンティティを職堎環境に合わせお䜜成した堎合は、テヌブル名ずカラム名を眮き換えるだけで、すぐにリポゞトリクラスずしお掻甚するこずができたす。 ::: Repositoryクラスのたずめ jOOQのGradleプラグむンを䜿甚するこずで、デヌタベヌススキヌマから゚ンティティを自動生成しお䜿甚したす。 自動生成されたクラスを䜿甚するこずで、型安党性を確保し぀぀、SQL構文を盎感的にJavaで蚘述できたす。 テヌブル定矩の倉曎があった堎合でも、クラスを再生成するだけで察応可胜です。 :::message Spring Batchでは、耇数のデヌタベヌス操䜜方法が利甚可胜です。jOOQの他に代衚的な遞択肢ずしお以䞋がありたす。 Hibernate (JPA) 匷力なデヌタベヌス抜象化 Spring Bootずの統合が簡単 倧芏暡デヌタセットでオヌバヌヘッドが発生する可胜性がある MyBatis 盎接的なSQL管理に最適 動的ク゚リのサポヌトが匷力 保守性が䜎䞋する可胜性がある Spring JdbcTemplate 軜量なデヌタベヌス操䜜 シンプルなク゚リに適しおいる 蚘述が冗長になるこずがある ::: :::message alert 泚意点 プロゞェクトの芁件やチヌムのスキルセットに応じお適切な方法を遞択しおください。必芁であれば、ORMを䜿わずに盎接SQLを実行する方法も怜蚎可胜です。 ::: DBからCSVバッチ解説 DBからCSVバッチを起動したす。起動方法はスケルトンバッチず同じ芁領です。 タヌミナルからGradle経由で起動する堎合 ./gradlew :dbAndCsvBatch:bootRun タヌミナルから実行可胜JARファむルを生成しお実行する堎合 cd dbAndCsvBatch/ ../gradlew java -jar build/libs/batch-dbAndCsv*.jar ここでは、スケルトンバッチの起動方法ず同じく、起動匕数を付けずにAppクラスを実行したした。 org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jobLauncherApplicationRunner' defined in class path resource [org/springframework/boot/autoconfigure/batch/BatchAutoConfiguration.class]: Job name must be specified in case of multiple jobs at com.example.batch.DbAndCsvBatchApp.main(DbAndCsvBatchApp.java:16) Caused by: java.lang.IllegalArgumentException: Job name must be specified in case of multiple jobs ゚ラヌが発生したした。 なぜ゚ラヌが発生したのか ゚ラヌの原因は、耇数のゞョブが定矩されおいるため、フレヌムワヌクがどのゞョブを実行するか刀断できなかったこずです。そのため、実行するゞョブを明瀺的に指定する必芁がありたす。このプロゞェクトでは2぀のゞョブが定矩されおいたす。䞀方、スケルトンバッチにはゞョブが1぀しか定矩されおいないため、この゚ラヌは発生したせん。 起動匕数を指定しお実行する ゚ラヌを解消するには、実行時にゞョブ名ず環境名を起動匕数ずしお指定したす。 IDEで実行する堎合は、実行時匕数蚭定に次の内容を指定しおください。 --spring.batch.job.name=DB_TO_CSV --spring.profiles.active=local --spring.batch.job.name=DB_TO_CSV: 実行するゞョブ名を指定したす。 --spring.profiles.active=local: ロヌカル環境甚の蚭定プロファむルを有効にしたす。 実行コマンドの䟋 # Gradleを䜿甚する堎合 ./gradlew :dbAndCsvBatch:bootRun --args="--spring.batch.job.name=DB_TO_CSV --spring.profiles.active=local" # 実行可胜JARを盎接䜿甚する堎合 cd dbAndCsvBatch/ java -jar build/libs/batch-dbAndCsv*.jar --spring.batch.job.name=DB_TO_CSV --spring.profiles.active=local 無事起動したず思うので、ログの内容を確認したす。 ログ出力䟋 ##### KEY:"sun.java.command", VALUE:"com.example.batch.DbAndCsvBatchApp --spring.batch.job.name=DB_TO_CSV --spring.profiles.active=local" ##### Spring Batch ##### - Job: DB_TO_CSV, Profile: local 起動匕数をログ出力するコヌドを App クラスに仕蟌んでいたす。 ログから、ゞョブ名ずプロファむル名が正しく枡されおいるこずが確認できたした。 ログ出力䟋 ----------- JOB [Job Name:DB_TO_CSV] START! ----------- Executing step: [DB_TO_CSV-step] Fetching members with types = [1, 2, 3] リスナヌ (BatchNotificationListener) の beforeJob メ゜ッドのログが出力されおいたす。 たた、typesに蚭定ファむルの倀が蚭定されたこずが確認できたした。 ログ出力䟋 -> with bind values : select `sampledb`.`member`.`id`, `sampledb`.`member`.`type`, `sampledb`.`member`.`name`, `sampledb`.`member`.`email`, `sampledb`.`member`.`phone`, `sampledb`.`member`.`address`, `sampledb`.`member`.`delete_flag`, `sampledb`.`member`.`created_at`, `sampledb`.`member`.`updated_at` from `sampledb`.`member` where (`sampledb`.`member`.`delete_flag` = 0 and `sampledb`.`member`.`type` in (1, 2, 3)) order by `sampledb`.`member`.`type` Version : Database version is supported by dialect MYSQL: 9.1.0 Fetched result : +----+----+----------+----------------------+----------+-----------------------------+-----------+-------------------+-------------------+ : | id|type|name |email |phone |address |delete_flag|created_at |updated_at | : +----+----+----------+----------------------+----------+-----------------------------+-----------+-------------------+-------------------+ : | 1| 1|John Doe |john.doe@example.com |1234567890|123 Main St, City, Country | 0|2024-12-07T09:46:07|2024-12-07T09:46:07| : | 2| 1|Jane Smith|jane.smith@example.com|0987654321|456 Oak St, Town, Country | 0|2024-12-07T09:46:07|2024-12-07T09:46:07| : | 26| 1|John Doe |john.doe@example.com |1234567890|123 Main St, City, Country | 0|2024-12-09T05:36:37|2024-12-09T05:36:37| : | 27| 1|Jane Smith|jane.smith@example.com|0987654321|456 Oak St, Town, Country | 0|2024-12-09T05:36:37|2024-12-09T05:36:37| : | 3| 2|ABC Corp |contact@abccorp.com |5678901234|789 Pine St, Village, Country| 0|2024-12-07T09:46:07|2024-12-07T09:46:07| : +----+----+----------+----------------------+----------+-----------------------------+-----------+-------------------+-------------------+ Fetched row(s) : 5 (or more) Batch process completed successfully. Step: [DB_TO_CSV-step] executed in 193ms Job: [SimpleJob: [name=DB_TO_CSV]] completed with the following parameters: [{'run.id':'{value=1, type=class java.lang.Long, identifying=true}'}] and the following status: [COMPLETED] in 212ms ----------- JOB [Job Name:DB_TO_CSV] FINISHED! status:[COMPLETED] ----------- https://github.com/kinto-technologies/SpringBoot3BatchStarter/blob/main/dbAndCsvBatch/src/main/resources/logback.xml#L21-L22 logback.xmlの蚭定で、jOOQのク゚リずその結果をログ出力するように蚭定しおいたす。実際のク゚リヌず結果が芋られるずデバックしやすいですよね。 たた、リスナヌの終了ログも出力されおおり、バッチが正垞に終了したこずが確認できたした。 生成されたCSVファむルの確認 "id","type","name","email","phone","address","deleteFlag","createdAt","updatedAt" "1","1","John Doe","john.doe@example.com","1234567890","123 Main St, City, Country","0","2024-12-11T06:05:26","2024-12-11T06:05:26" "2","1","Jane Smith","jane.smith@example.com","0987654321","456 Oak St, Town, Country","0","2024-12-11T06:05:26","2024-12-11T06:05:26" "3","2","ABC Corp","contact@abccorp.com","5678901234","789 Pine St, Village, Country","0","2024-12-11T06:05:26","2024-12-11T06:05:26" "5","3","Alice Premium","alice.premium@example.com","4561237890","987 Maple St, City, Country","0","2024-12-11T06:05:26","2024-12-11T06:05:26" "6","3","Charlie Davis","charlie.davis@example.com","1112223333",,"0","2024-12-11T06:05:26","2024-12-11T06:05:26" CSVファむルが出力された事が確認できたした。CSVぞの出力ず、読み蟌みにはopencsvのラむブラリを䜿甚しおいたす。 opencsv website 実行時匕数での types の䞊曞き確認 最埌に、このバッチには実行時匕数にwhereのin句に蚭定するtypesを起動匕数で枡せる機胜がありたした。詊しおみたしょう。以䞋の起動匕数を指定するこずで、where の in 条件に指定する types をカスタマむズできたす。 --spring.batch.job.name=DB_TO_CSV --batch.types=4,5 --spring.profiles.active=local ログ出力䟋 -> with bind values : select `sampledb`.`member`.`id`, `sampledb`.`member`.`type`, `sampledb`.`member`.`name`, `sampledb`.`member`.`email`, `sampledb`.`member`.`phone`, `sampledb`.`member`.`address`, `sampledb`.`member`.`delete_flag`, `sampledb`.`member`.`created_at`, `sampledb`.`member`.`updated_at` from `sampledb`.`member` where (`sampledb`.`member`.`delete_flag` = 0 and `sampledb`.`member`.`type` in (4, 5)) order by `sampledb`.`member`.`type` 蚭定ファむルの倀が、実行時匕数で䞊曞きされおいるこずが解りたす。 CSVからDBバッチ解説 バッチを起動したす。実行時匕数でゞョブ名を指定しお実行したす。 --spring.batch.job.name=CSV_TO_DB --spring.profiles.active=local ログを確認したす。 insert into `sampledb`.`member` (`name`, `email`, `phone`, `address`, `type`) values ('Premium Corp', 'premium@corporate.com', '8889997777', '555 High St, City, Country', 4), ('Elite Ltd', 'elite@elitecorp.com', '4445556666', '777 Sky Ave, Town, Country', 4), ('Guest User1', 'guest1@example.com', '', 'Guest Address 1, City, Country', 5), ('Guest User2', 'guest2@example.com', '9998887777', '', 5) Affected row(s) : 4 バルク (䞀括) でinsertされおいるのが確認できたした。 継続的むンテグレヌション このプロゞェクトでは、GitHub Actionsを䜿甚しおCI継続的むンテグレヌションを実珟しおいたす。コヌドの倉曎がプッシュたたはプルリク゚ストされるたびに、党おの凊理が自動で行われ、品質管理が効率的に進められたす。 䞻な流れ 1. MySQLのセットアップ: Docker Composeを䜿っおMySQLを起動し、必芁なテヌブルを確認。 2. JDK 21のセットアップ: Java 21をむンストヌルしおビルド環境を敎備。 3. jOOQでのクラス生成: デヌタベヌススキヌマから自動で゚ンティティクラスを生成。 4. ビルドずテスト: Gradleでビルドを行い、JUnitやJacocoやSpotBugsを甚いお品質チェックを実斜。 コヌドの倉曎時、コンパむル゚ラヌやJUnitテスト゚ラヌを即座に怜出し、迅速に察応できたす。 さらに、ビルドずテストが成功するず、GitHubリポゞトリのREADMEに以䞋のようなバッゞが衚瀺され、プロゞェクトの状態を䞀目で確認できたす コヌドカバレッゞの可芖化 このプロゞェクトでは Codecov を䜿甚しおテストカバレッゞを枬定・可芖化しおいたす。プルリク゚スト時にカバレッゞレポヌトが自動生成され、以䞋のバッゞでカバレッゞ率を確認できたす これにより テストの網矅性を芖芚的に把握 カバレッゞの倉曎を迅速に怜知 品質管理の透明性を向䞊 カバレッゞレポヌトの詳现はCodecovのダッシュボヌドで確認できたす。 graph LR D[👩‍💻 Developer] -->|Push/PR| G[🐙 GitHub] G -->|Trigger| GHA[⚡ GitHub Actions] GHA -->|Build and Test Results| R[📄 README.md] GHA -->|Coverage Report| C[☁ Codecov] C -->|Coverage Badge| R style G fill:#f4f4f4,stroke:#171515 style C fill:#f01f7a style R fill:#e6e6e6 終わりに いかがでしたでしょうか このプロゞェクトでは、スケルトンコヌドを基盀に、Spring Batchを掻甚したバッチ開発を効率よく進められるよう工倫しおいたす。「DB to CSV」「CSV to DB」ずいったよくあるナヌスケヌスを取り䞊げ、DB蚭定やテヌブル定矩、CSVレむアりトを倉曎するだけで簡単にカスタマむズできる柔軟性を備えおいたす。 このスケルトンを掻甚しお、皆さんの業務に合わせたロゞックを远加し、バッチ開発がスムヌズになるこずを願っおいたす。 この蚘事が参考になった、コピペでバッチが動いたずいう方は、GitHubリポゞトリに⭐を付けおいただけるず嬉しいです。 最埌たでお読みいただき、ありがずうございたした🙇‍♂ [^1]: 共通サヌビス開発グルヌプメンバヌによる投皿 1 [ グロヌバル展開も芖野に入れた決枈プラットフォヌムにドメむン駆動蚭蚈(DDD)を取り入れた ] [^2]: 共通サヌビス開発グルヌプメンバヌによる投皿 2 [ 入瀟 1 幎未満メンバヌだけのチヌムによる新システム開発をリモヌトモブプログラミングで成功させた話 ] [^3]: 共通サヌビス開発グルヌプメンバヌによる投皿 3 [ JIRA ず GitHub Actions を掻甚した耇数環境ぞのデプロむトレヌサビリティ向䞊の取り組み ] [^4]: 共通サヌビス開発グルヌプメンバヌによる投皿 4 [ VSCode Dev Container を䜿った開発環境構築 ] [^5]: 共通サヌビス開発グルヌプメンバヌによる投皿 5 [ MinIOを甚いたS3ロヌカル開発環境の構築ガむドAWS SDK for Java 2.x ]
アバタヌ
Contents Introduction Target Audience Repository Setup Skeleton Batch Guide DB and CSV Batch Guide Continuous Integration Conclusion Introduction Hello, I'm Miyashita, an engineer from KINTO Technologies' Common Service Development Group[^1][^2][^3][^4][^5]. While developing batch processes with Spring Boot 3, I encountered several challenges with Spring Batch's class structure and annotations. The transition to Spring Boot 3 presented additional complexities, particularly with multi-database configurations and error handling. A particular challenge was the lack of Japanese documentation for Spring Boot 3. While referring to the official documentation, I had trouble finding configurations that matched my requirements and encountered issues with multi-database setup and error handling. This led to a cycle of trial and error, making me acutely aware of the challenges in building a batch process from scratch. In this article, I've compiled information to help others facing similar challenges implement Spring Batch more smoothly: Spring Boot 3 Batch Skeleton Immediate deployment through GitHub clone and run Ready for business logic implementation Pre-configured for production use Common Batch Processing Use Cases In addition to the skeleton, I'll introduce sample code for batch processing utilizing a multi-database configuration: Batch process for exporting DB records to CSV Batch process for importing CSV data to DB I hope this article helps make Spring Batch implementation and development easier. Target Audience This guide is designed for developers who are: ✓ New to Spring Batch 5 framework implementation ✓ Returning to Spring Batch after a hiatus ✓ Migrating from Spring Boot 2 Batch due to end of support ✓ Experienced with Java main method batches but need Spring Batch ✓ Seeking rapid batch development solutions Repository Setup https://github.com/kinto-technologies/SpringBoot3BatchStarter/ This is my repository. Please clone it and import it into your IDE. For GUI-based operations, use GitHub Desktop, Repository Structure The repository is structured as follows: . ├── gradlew # Gradle wrapper for build automation ├── settings.gradle ├── compose.yaml # Docker Compose configuration ├── init-scripts # Database initialization │ ├── 1-create-table.sql │ └── 2-insert-data.sql ├── dbAndCsvBatch # DB/CSV processing implementation │ ├── build.gradle │ └── src │ ├── main │ └── test └── skeletonBatch # Basic batch implementation ├── build.gradle └── src ├── main └── test Skeleton Batch Guide https://github.com/kinto-technologies/SpringBoot3BatchStarter/tree/main/skeletonBatch This is a skeleton code that becomes a complete batch process just by adding business logic. It consists of only the necessary code components. Core Concepts: Jobs and Steps Spring Batch is built on two fundamental concepts: Jobs and Steps. Jobs A Job represents the complete batch process: Contains one or multiple Steps Manages batch execution flow Supports sequential and parallel processing graph TD A[Job] --> B[Step 1] A --> C[Step 2] A --> D[Step 3] style A fill:#f9f,stroke:#333,stroke-width:4px Job Capabilities Capability Implementation Example Sequential Chain multiple jobs Daily data processing pipeline Error Handling Define recovery actions Automated error notifications Concurrent Run multiple jobs simultaneously Parallel data processing Parallel Multiple instances of same job Large dataset processing Step A Step represents a processing unit within a job. One job can register multiple steps, and step behavior can be designed flexibly like jobs. Implementation Method Steps can be implemented using either Chunk or Tasklet processing. 1. Chunk Processing This is a method for efficiently processing large volumes of data. graph LR A[Reader] -->|100 items at once| B[Processor] B -->|One by one| C[Writer] C -->|100 items at once| D[DB/File] Implementation Characteristics Processing is divided into three phases: Reader: Reads data in fixed quantities Example: Reading 100 lines at a time from CSV file using FlatFileItemReader Processor: Processes data one item at a time Example: Converting date formats using CustomProcessor Writer: Outputs data in bulk Example: Bulk INSERT to DB using JdbcBatchItemWriter Transactions are committed by chunk size (e.g., 100 items). This means that even if an error occurs midway, the successfully completed chunks are committed. 2. Tasklet Processing This is a method for executing simple, single-unit processes. Characteristics Simple implementation Easy to understand processing flow Straightforward debugging Choosing Between Implementation Methods Aspect Chunk Tasklet Data Volume Suitable for large volumes Suitable for small volumes Processing Complexity Suitable for complex processing Suitable for simple processing Management Cost Higher Lower Debugging Somewhat complex Easy Transaction By chunk By process unit Summary Job is the management unit for the entire batch process Step is the execution unit for specific processes Chunk is suitable for efficient processing of large data volumes Tasklet is optimal for simple processing Choose between Chunk and Tasklet based on processing requirements :::message Key Point For simple batches that don't require complex processing, choose Tasklet without hesitation. This skeleton batch also adopts Tasklet processing. ::: :::message alert Note In official documentation and many web articles, chunk processing is explained as the basic approach of Spring Batch, which often leads beginners to mistakenly think they must use chunks. This frequently results in implementing with chunks first, then realizing later that Tasklet would have been more appropriate, causing rework. ::: YAML Configuration and Spring Batch Management Tables Let's first explain the basic configuration needed to run Spring Batch in batch-dedicated mode. YAML Configuration https://github.com/kinto-technologies/SpringBoot3BatchStarter/blob/main/skeletonBatch/src/main/resources/application.yml This configuration allows the Spring Boot application to run in serverless mode. While typical Spring Boot applications maintain their process by starting servers like Tomcat, this is unnecessary for batch processing. With this configuration, the process terminates when batch processing completes. Spring Batch Management Tables Spring Batch requires management tables in a database to record execution results. However, managing databases and tables can be cumbersome. Moreover, you might need to run batches in environments without databases. In such cases, using H2 in-memory database is recommended. When the process ends: Memory is released Database and tables are cleared Next execution starts with a fresh state H2 is automatically configured if no database settings are specified in application.yml :::message Benefits of Persistent Storage While using H2 is convenient for development, using a persistent database allows you to: Maintain execution history Resume from interruptions Track batch execution status ::: Job Class Guide https://github.com/kinto-technologies/SpringBoot3BatchStarter/blob/main/skeletonBatch/src/main/java/com/example/batch/job/SampleJob.java#L16-L41 This is the core class for registering and executing Jobs in Spring Batch. Class Definition Key Points @Configuration Recognized as a Spring Batch configuration class Indicates that this class provides Spring configuration Required annotation for Job definition classes @Bean Applied to methods that generate objects managed by Spring Framework In this case, allows Spring to manage the Job created by createSampleJob The Job instance is used during batch execution :::message alert Important Note By default, the method name is used as the Bean name. If you copy this class to create a new Job, using the same method name will cause an error. To avoid this, specify a unique name like @Bean("jobName") . This is a common pitfall to watch out for. ::: Dependency Class Roles JobRepository : Manages job execution state PlatformTransactionManager : Maintains database consistency SampleLogic : Handles actual business processing Processing Flow Output job registration start log Create Step (define processing unit) Create Job and register Step Output job registration completion log Transaction Management Normal completion: Database changes are confirmed when all processing succeeds Abnormal termination: Database changes are rolled back when errors occur This ensures the reliability of batch processing operations. Logic Class Guide https://github.com/kinto-technologies/SpringBoot3BatchStarter/blob/main/skeletonBatch/src/main/java/com/example/batch/logic/SampleLogic.java#L13-L35 This class defines the actual processing content of the batch. Class Definition Key Points @Component Makes this class manageable by Spring Allows usage with @Autowired in other classes Tasklet Interface Interface representing Spring Batch processing unit Implement actual processing in the execute method Processing Flow Output batch processing start log Call SampleService's process method Output completion log on normal termination Log exception and re-throw on error occurrence Output batch processing end log Error Handling Normal completion: Return RepeatStatus.FINISHED to complete batch Abnormal termination: Catch exception, log it, and notify upper layer :::message Implementation Point While this class delegates processing to SampleService, you can implement simple processing directly in the execute method. ::: Service Class Guide https://github.com/kinto-technologies/SpringBoot3BatchStarter/blob/main/skeletonBatch/src/main/java/com/example/batch/service/SampleService.java#L8-L23 This class is for implementing actual business logic. Class Definition Key Points @Service Recognized as a Spring Framework service class Indicates this class implements business logic process Method Method for implementing batch business processing Includes start and completion log output :::message Implementation Point This class is completely independent from Spring Batch classes and interfaces. This means: Focus purely on business logic implementation Easy to write JUnit tests Easily reusable in other projects ::: Customization Options You can modify: Class name Method name Arguments and return values Business logic implementation (e.g., data validation, transformation, external system integration) This concludes our explanation of the main skeleton batch classes. This loosely coupled design makes the batch easy to maintain and test. Running the Batch Running from IDE Start the BatchApp class. It works like a regular Spring Boot application with no special startup arguments required. Running via Gradle from Terminal Execute the following command: ./gradlew :skeletonBatch:bootRun Running by Generating and Executing JAR File Gradle's default task is configured to generate a JAR file. Execute as follows: cd skeletonBatch ../gradlew java -jar build/libs/batch-skeleton*.jar Checking Execution Logs Let's examine the Spring Batch execution flow through the logs. 1. Job Registration Check ----------- Registering job: sample ----------- ----------- Job registered successfully: sample-job ----------- During Spring Boot startup, the batch job 'sample-job' was successfully registered. 2. Batch Processing Execution Started BatchApp in 0.456 seconds (process running for 0.616) Running default command line with: [] Job: [SimpleJob: [name=sample-job]] launched with the following parameters: [{}] Executing step: [processing-step] ----------- START ----------- Batch Processing ----------- --- Starting batch process --- --- Batch process completed --- Processing completed successfully ----------- END ----------- Batch Processing ----------- Step: [processing-step] executed in 3ms Job: [SimpleJob: [name=sample-job]] completed with the following parameters: [{}] and the following status: [COMPLETED] in 9ms Important Points from Logs: 'sample-job' executed and 'processing-step' started START and END markers clearly show processing boundaries Step completed successfully and job finished with COMPLETED status Application terminated normally Skeleton Batch Summary Advantages Complete batch process by just adding business logic Integration with schedulers like cron using generated JAR file High maintainability through simple structure :::message Development Support Spring Batch development often faces issues like: Missing annotations Configuration mistakes Complex error messages This skeleton helps you avoid these problems and focus on implementing business logic. ::: This skeleton code aims to contribute to more efficient batch development. DB and CSV Batch Guide https://github.com/kinto-technologies/SpringBoot3BatchStarter/tree/main/dbAndCsvBatch Overview This project includes two batch processes: DB to CSV Export Exports database records to CSV format Customizable extraction conditions via startup arguments Works with default settings CSV to DB Import Bulk imports CSV data into database Requires CSV file, so run export batch first Local Database Setup This batch uses a MySQL local database. Follow these steps for setup: Start MySQL Container docker compose up -d :::message If Docker is not installed, download and install it from the Docker official website . ::: Verify Setup Connect to MySQL container and check sample data: docker exec -it mysql-container mysql -u sampleuser -psamplepassword sampledb Run sample query: mysql> SELECT * FROM member WHERE delete_flag = 0 AND type IN (1, 2, 3) ORDER BY type ASC; mysql> exit Bye Entity Class Auto-generation After cloning the GitHub repository, you might encounter a "Entity class not found" error. This occurs because jOOQ needs to auto-generate Entity classes. Running Auto-generation Either command will generate Entity classes: # Execute default task cd dbAndCsvBatch/ ../gradlew # Or directly execute jOOQ generation ../gradlew generateJooq The build.gradle is configured for immediate use without additional settings. Default task execution includes: Cleanup of build results Java code formatting (Google Java Format) Entity class auto-generation (jOOQ plugin) Compilation Static analysis and testing (JUnit, coverage, SpotBugs) Executable JAR generation Generated Entity Classes Check generated Entity classes with: tree dbAndCsvBatch/build/generated-src # If tree command is not installed: ls -R dbAndCsvBatch/build/generated-src Example output: dbAndCsvBatch/build/generated-src/jooq └── main └── com └── example └── batch └── jooq ├── DefaultCatalog.java ├── Keys.java ├── Sampledb.java ├── Tables.java └── tables ├── Member.java └── records └── MemberRecord.java :::message alert Since jOOQ plugin auto-generates code from database schema during build, managing it with Git can cause conflicts when schema changes. Therefore, generated code should not be included in Git management. ::: Handling "Class Not Found" Errors Your IDE might report Entity classes not found. Resolve this by either: Add generated-src/jooq to IDE's build path Copy classes from generated-src/jooq to dbAndCsvBatch/src/main/java Production Environment Setup https://github.com/kinto-technologies/SpringBoot3BatchStarter/blob/main/dbAndCsvBatch/build.gradle#L101-L124 :::message Change connection settings to your DEV/STG database Or add your production CREATE TABLE statements to Docker Compose initialization SQL ::: jooq gradle plugin website Multi-database Configuration This batch uses a dual database configuration: H2 (In-memory Database) Used for Spring Batch management tables Resets when process ends Ideal for development and testing MySQL (Business Logic Database) Used for actual business data processing Runs in Docker container Configuration File (application-local.yml) Spring Boot's configuration file defines two databases under spring.datasource. https://github.com/kinto-technologies/SpringBoot3BatchStarter/blob/main/dbAndCsvBatch/src/main/resources/application-local.yml The configuration file includes: Two database settings Profile suffixes (local and server) Runtime profile selection capability The server profile is intended for your production MySQL server settings. Data Source Configuration Class https://github.com/kinto-technologies/SpringBoot3BatchStarter/blob/main/dbAndCsvBatch/src/main/java/com/example/batch/config/DataSourceConfig.java#L11-L37 Key Annotations @Configuration Marks class as Spring configuration Spring manages beans defined here @ConfigurationProperties Maps YAML settings to class properties Example: maps 'spring.datasource.mysqlmain' @BatchDataSource Specifies data source for Spring Batch tables Applied to H2 database @Primary Designates default data source Applied to MySQL data source Job Class Guide https://github.com/kinto-technologies/SpringBoot3BatchStarter/blob/main/dbAndCsvBatch/src/main/java/com/example/batch/job/DbToCsvJob.java#L31-L39 New Features Added Automatic Run ID Increment .incrementer(new RunIdIncrementer()) Automatically increments job execution number (run.id) Used for execution history management :::message While this sample uses H2 (in-memory DB) making the run ID transient, this is a standard implementation pattern when using persistent databases. ::: Listener Integration .listener(listener) Add processing before and after job execution Useful for logging, email notifications, etc. Logic Class Guide https://github.com/kinto-technologies/SpringBoot3BatchStarter/blob/main/dbAndCsvBatch/src/main/java/com/example/batch/logic/DbToCsvLogic.java#L18-L49 This example demonstrates basic logging implementation, but you can add custom processing like error handling and notifications. Parameter Configuration @Value("${batch.types:2,3,4}") private String typesConfig; Uses 2,3,4 as default values even without application.yml Can be overridden by passing --batch.types=1,2 at runtime Error Handling contribution.setExitStatus(ExitStatus.FAILED); Explicitly sets failure status on error Automatically sets COMPLETED status on normal completion Repository Class and jOOQ Guide About jOOQ jooq is a library that allows writing SQL as Java code: Express SQL syntax directly in Java code Auto-generate Entity classes from table definitions Type-safe SQL operations Select Processing https://github.com/kinto-technologies/SpringBoot3BatchStarter/blob/main/dbAndCsvBatch/src/main/java/com/example/batch/repository/MemberRepository.java#L18-L25 Key Features SQL-like Syntax Direct expression of SQL in Java code Leverage existing SQL knowledge Auto-generated Class Usage MEMBER: Represents table definition (column names, etc.) MemberRecord: For record mapping Bulk Insert Processing https://github.com/kinto-technologies/SpringBoot3BatchStarter/blob/main/dbAndCsvBatch/src/main/java/com/example/batch/repository/MemberRepository.java#L54-L69 Bulk Insert Features Registers multiple records at once instead of one-by-one Common in batch processing to reduce database load :::message While not used in this batch, example code for insert, update, and delete operations is included. You can quickly adapt the repository class by replacing table and column names to match your business environment. ::: Repository Class Summary Use jOOQ's Gradle plugin to auto-generate entities from database schema Auto-generated classes enable type-safe SQL writing in Java Easy to handle schema changes - just regenerate classes :::message Spring Batch supports multiple database operation methods: Hibernate (JPA) Powerful database abstraction Easy Spring Boot integration May have overhead with large datasets MyBatis Ideal for direct SQL management Strong dynamic query support May reduce maintainability Spring JdbcTemplate Lightweight database operations Good for simple queries Can be verbose ::: :::message alert Note Choose the appropriate method based on: Project requirements Team skill set Direct SQL execution if needed ::: DB to CSV Batch Execution Guide Run the DB to CSV batch following similar steps as the skeleton batch. Running via Gradle ./gradlew :dbAndCsvBatch:bootRun Running JAR File cd dbAndCsvBatch/ ../gradlew java -jar build/libs/batch-dbAndCsv*.jar Running without arguments produces this error: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jobLauncherApplicationRunner' defined in class path resource [org/springframework/boot/autoconfigure/batch/BatchAutoConfiguration.class]: Job name must be specified in case of multiple jobs at com.example.batch.DbAndCsvBatchApp.main(DbAndCsvBatchApp.java:16) Caused by: java.lang.IllegalArgumentException: Job name must be specified in case of multiple jobs Why This Error Occurs Unlike the skeleton batch with a single job, this project contains multiple jobs: CSV to DB: Importing CSV data into database DB to CSV: Exporting database data to CSV The framework cannot determine which job to execute without explicit specification. Running with Arguments Specify the job name and environment in startup arguments: --spring.batch.job.name=DB_TO_CSV --spring.profiles.active=local --spring.batch.job.name=DB_TO_CSV: Specifies which job to run --spring.profiles.active=local: Activates local environment settings Example Commands # Using Gradle ./gradlew :dbAndCsvBatch:bootRun --args="--spring.batch.job.name=DB_TO_CSV --spring.profiles.active=local" # Using JAR directly cd dbAndCsvBatch/ java -jar build/libs/batch-dbAndCsv*.jar --spring.batch.job.name=DB_TO_CSV --spring.profiles.active=local Examining the Logs Let's check the execution logs: 1. Initial Startup Log ##### KEY:"sun.java.command", VALUE:"com.example.batch.DbAndCsvBatchApp --spring.batch.job.name=DB_TO_CSV --spring.profiles.active=local" ##### Spring Batch ##### - Job: DB_TO_CSV, Profile: local This confirms our startup arguments were correctly received. 2. Job Start Log ----------- JOB [Job Name:DB_TO_CSV] START! ----------- Executing step: [DB_TO_CSV-step] Fetching members with types = [1, 2, 3] Shows BatchNotificationListener's beforeJob method execution and configured types. 3. Database Operation Log -> with bind values : select `sampledb`.`member`.`id`, `sampledb`.`member`.`type`, `sampledb`.`member`.`name`, `sampledb`.`member`.`email`, `sampledb`.`member`.`phone`, `sampledb`.`member`.`address`, `sampledb`.`member`.`delete_flag`, `sampledb`.`member`.`created_at`, `sampledb`.`member`.`updated_at` from `sampledb`.`member` where (`sampledb`.`member`.`delete_flag` = 0 and `sampledb`.`member`.`type` in (1, 2, 3)) order by `sampledb`.`member`.`type` Version : Database version is supported by dialect MYSQL: 9.1.0 Fetched result : +----+----+----------+----------------------+----------+-----------------------------+-----------+-------------------+-------------------+ : | id|type|name |email |phone |address |delete_flag|created_at |updated_at | : +----+----+----------+----------------------+----------+-----------------------------+-----------+-------------------+-------------------+ : | 1| 1|John Doe |john.doe@example.com |1234567890|123 Main St, City, Country | 0|2024-12-07T09:46:07|2024-12-07T09:46:07| : | 2| 1|Jane Smith|jane.smith@example.com|0987654321|456 Oak St, Town, Country | 0|2024-12-07T09:46:07|2024-12-07T09:46:07| : | 26| 1|John Doe |john.doe@example.com |1234567890|123 Main St, City, Country | 0|2024-12-09T05:36:37|2024-12-09T05:36:37| : | 27| 1|Jane Smith|jane.smith@example.com|0987654321|456 Oak St, Town, Country | 0|2024-12-09T05:36:37|2024-12-09T05:36:37| : | 3| 2|ABC Corp |contact@abccorp.com |5678901234|789 Pine St, Village, Country| 0|2024-12-07T09:46:07|2024-12-07T09:46:07| : +----+----+----------+----------------------+----------+-----------------------------+-----------+-------------------+-------------------+ Fetched row(s) : 5 (or more) Batch process completed successfully. Step: [DB_TO_CSV-step] executed in 193ms Job: [SimpleJob: [name=DB_TO_CSV]] completed with the following parameters: [{'run.id':'{value=1, type=class java.lang.Long, identifying=true}'}] and the following status: [COMPLETED] in 212ms ----------- JOB [Job Name:DB_TO_CSV] FINISHED! status:[COMPLETED] ----------- https://github.com/kinto-technologies/SpringBoot3BatchStarter/blob/main/dbAndCsvBatch/src/main/resources/logback.xml#L21-L22 jOOQ's query and result logging is enabled in logback.xml for easier debugging. Generated CSV Check "id","type","name","email","phone","address","deleteFlag","createdAt","updatedAt" "1","1","John Doe","john.doe@example.com","1234567890","123 Main St, City, Country","0","2024-12-11T06:05:26","2024-12-11T06:05:26" "2","1","Jane Smith","jane.smith@example.com","0987654321","456 Oak St, Town, Country","0","2024-12-11T06:05:26","2024-12-11T06:05:26" "3","2","ABC Corp","contact@abccorp.com","5678901234","789 Pine St, Village, Country","0","2024-12-11T06:05:26","2024-12-11T06:05:26" "5","3","Alice Premium","alice.premium@example.com","4561237890","987 Maple St, City, Country","0","2024-12-11T06:05:26","2024-12-11T06:05:26" "6","3","Charlie Davis","charlie.davis@example.com","1112223333",,"0","2024-12-11T06:05:26","2024-12-11T06:05:26" The CSV file is generated using the OpenCSV library. opencsv website Testing Runtime Argument Override Let's try customizing the types parameter at runtime: --spring.batch.job.name=DB_TO_CSV --batch.types=4,5 --spring.profiles.active=local Log Output -> with bind values : select `sampledb`.`member`.`id`, `sampledb`.`member`.`type`, `sampledb`.`member`.`name`, `sampledb`.`member`.`email`, `sampledb`.`member`.`phone`, `sampledb`.`member`.`address`, `sampledb`.`member`.`delete_flag`, `sampledb`.`member`.`created_at`, `sampledb`.`member`.`updated_at` from `sampledb`.`member` where (`sampledb`.`member`.`delete_flag` = 0 and `sampledb`.`member`.`type` in (4, 5)) order by `sampledb`.`member`.`type` This confirms that the configuration file values were successfully overridden by command line arguments. CSV to DB Batch Execution Run the batch with the specified job name: --spring.batch.job.name=CSV_TO_DB --spring.profiles.active=local Log Output insert into `sampledb`.`member` (`name`, `email`, `phone`, `address`, `type`) values ('Premium Corp', 'premium@corporate.com', '8889997777', '555 High St, City, Country', 4), ('Elite Ltd', 'elite@elitecorp.com', '4445556666', '777 Sky Ave, Town, Country', 4), ('Guest User1', 'guest1@example.com', '', 'Guest Address 1, City, Country', 5), ('Guest User2', 'guest2@example.com', '9998887777', '', 5) Affected row(s) : 4 This confirms successful bulk insertion of records. Continuous Integration This project implements CI (Continuous Integration) using GitHub Actions. Quality management is efficiently handled through automated processing triggered by code pushes or pull requests. Main Workflow MySQL Setup Launch MySQL using Docker Compose Verify required tables JDK 21 Setup Install Java 21 Configure build environment jOOQ Class Generation Auto-generate entity classes from database schema Build and Test Execute Gradle build Run quality checks: JUnit tests Jacoco coverage SpotBugs analysis This workflow enables: Immediate detection of compilation errors Quick identification of JUnit test failures Rapid response to issues Consistent code quality maintenance Dynamic Badge Updates When the build and tests succeed, a dynamic badge is displayed on the GitHub README, indicating the status of the project. Code Coverage Visualization This project uses Codecov to measure and visualize test coverage. Coverage reports are automatically generated during pull requests, and the coverage rate can be checked via this badge: graph LR D[👩‍💻 Developer] -->|Push/PR| G[🐙 GitHub] G -->|Trigger| GHA[⚡ GitHub Actions] GHA -->|Build and Test Results| R[📄 README.md] GHA -->|Coverage Report| C[☁ Codecov] C -->|Coverage Badge| R style G fill:#f4f4f4,stroke:#171515 style C fill:#f01f7a style R fill:#e6e6e6 This enables: Visual tracking of test coverage Quick detection of coverage changes Enhanced transparency in quality management Detailed coverage reports are available on the Codecov dashboard. Conclusion We hope you found this guide helpful! This project provides: Skeleton code foundation for efficient Spring Batch development Common use cases like "DB to CSV" and "CSV to DB" Flexible customization through database settings and CSV layouts We hope this skeleton helps streamline your batch development by allowing you to focus on business logic implementation. If you found this article helpful and got your Spring Batch up and running quickly, we would appreciate a ⭐ on our GitHub repository! Thanks for reading! [^1]: Post by Common Service Development Group Member 1 [ Implementing Domain-Driven Design (DDD) in Payment Platform with Global Expansion in Mind ] [^2]: Post by Common Service Development Group Member 2 [ Success Story: New System Development through Remote Mob Programming by Team Members with Less Than One Year Experience ] [^3]: Post by Common Service Development Group Member 3 [ Improving Deploy Traceability Across Multiple Environments Using JIRA and GitHub Actions ] [^4]: Post by Common Service Development Group Member 4 [ Development Environment Setup Using VSCode Dev Container ] [^5]: Post by Common Service Development Group Member 5 [ Guide to Setting Up S3 Local Development Environment Using MinIO (AWS SDK for Java 2.x) ]
アバタヌ
はじめに VSCodeずCopilotの組み合わせが最高に楜しいです。楜しすぎお本業を忘れおしたいそうです。 本日はそんな楜しすぎる開発䜓隓をみなさんにもぜひ知っお欲しいずいう思いで蚘事を曞きたす。 この蚘事は KINTOテクノロゞヌズアドベントカレンダヌ2024 の25日目の蚘事です🎅🎄 今回の内容 去幎蟺りから熱くなったAI界隈ですが、今幎もAIの話題が尜きない䞀幎でしたね KTCでは積極的にAIを取り入れお開発生産性䜎枛の道を探っおいたす。 今回はVSCode x Copilotの組み合わせでできるこずを玹介したす。 でも、AIっおなんか埮劙なコヌドしか出しおこないし、自分で曞いた方が早いから䜿わないよね、ずいう方もいるかもしれたせん。 僕も初めはそうでしたが、先日開催されたGitHub Universeで発衚されたCopilot Chat/Editsを芋お、AIずの共同䜜業が楜しくなるかもしれないず感じたした。 ず蚀うこずで、今回は実際にCopilot Chat/Editsを䜿っお、AIず共同でプログラミングをする䞭で、普段䜿いで有効だず思うTipsを玹介したす。 https://reg.githubuniverse.com/flow/github/universe24/attendee-portal/page/livestream?tab.day=20241029 なんでFlutter モバむル開発をする䞊で、iOSもAndroidも各OSの暙準IDEを䜿っお開発をするのが䞻流です。 しかし、今回はGitHub Copilotを䜿っおプログラミングするずいう芳点で、VSCodeを䜿っお開発するこずを前提ずしたす。 そう考えるず、モバむル開発で公匏にVSCodeをサポヌトしおいるのはFlutterがメゞャヌです。 先日 FlutterKaigi にも参加し埗るモノが倚かったので、今回はFlutterを䜿っおAIずの共同䜜業をするこずにしたす。ちなみに、FlutterKaigiのアプリ、本圓に些现なIssueですが、僕もコントリビュヌトしたした😀 実際に䜕ができるのか GitHub Universeで発衚された機胜はPreview機胜が倚く、䜿甚できる機胜は限られおいたすが、すでにリリヌスされおいるCopilot Chat/Editsを䜿いこなすだけでも、AIずの共同䜜業が楜しくなり、開発者䜓隓の向䞊が実感できたした。 ゚ンゞニアの皆さんがAIを䜿っおコヌディングするずいうず、ノヌコヌドでアプリを䜜るずか、コヌドを自動生成するずか、そういったむメヌゞがあるかもしれたせん。確かに、コヌドを自動生成する事は可胜ですが、それはAIのほんの䞀郚の機胜でしかないです。 たた、耇雑な機胜を持぀コヌドを䞀床の指瀺で動䜜保蚌できるほどのコヌドを生成するこずは指瀺出しが難しいです。 この蚘事で玹介するのは、テストコヌドの䜜成、コヌドの敎圢・分割、リファクタリングなど、普段皆さんがコヌディングでやっおいるこずをAIずの共同䜜業を通じお行い、コヌドの品質を向䞊させるこずができるCopilot Chat/EditsのTipsになりたす。 それでは、ここからTipsの玹介です。 コヌドの芁玄 Copilot Chatの機胜を䜿うず、コヌドの芁玄を簡単に䜜成できたす。これを䜿えば、オンボヌディング時のコヌド理解が早たりたす。 コメント蚘茉 BEFORE AFTER コヌドにコメントを远加する際、Copilot Editsを䜿うず、Copilotが盎接ファむルにコメントを远加しおくれたす。これにより、コヌドの理解が深たりたす。 たた、すでにあるコメントを修正する際も、Copilotが適切なコメントを修正しおくれたす。 䟋えば䞊のクラスのaddメ゜ッドに察しお、バリデヌションを远加した堎合以䞋の様に指瀺するずコメントを曎新しおくれたす。 BEFORE AFTER テストコヌドの䜜成 copilot editsのテキスト入力欄の䞊に珟圚のファむル以倖に参照できるファむルを増やす事ができるので、テストコヌドを曞いおもらうずきは、 テスト甚のファむルを䜜成し、そのファむルを開いたたた、参照ファむルに察象ファむルを远加すれば、テストコヌドを曞いおくれたす。 コヌドの敎圢・分割 Flutter等の宣蚀型UIを採甚しおいる蚀語でUIを䜜成しおいるずどうしおもネストが深くなっおしたう問題が頻発したす。 :::details 敎圢前のコヌド class SomethingPage extends StatelessWidget { const SomethingPage({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Something Page'), ), body: Center( child: Container( padding: const EdgeInsets.all(16.0), color: Colors.blueAccent, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Container( padding: const EdgeInsets.all(8.0), color: Colors.redAccent, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Container( padding: const EdgeInsets.all(4.0), color: Colors.greenAccent, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Container( padding: const EdgeInsets.all(2.0), color: Colors.yellowAccent, child: const Text('Deeply Nested Widget 1'), ), Container( padding: const EdgeInsets.all(2.0), color: Colors.purpleAccent, child: const Text('Deeply Nested Widget 2'), ), ], ), ), ], ), ), ], ), ), ), ); } } ::: こういった問題もCopilot Editsを䜿うず、コヌドを芋やすい単䜍で分割、敎圢しおくれたす。 :::details 敎圢埌のコヌド class SomethingPage extends StatelessWidget { const SomethingPage({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Something Page'), ), body: Center( child: Container( padding: const EdgeInsets.all(16.0), color: Colors.blueAccent, child: const NestedContainer(), ), ), ); } } class NestedContainer extends StatelessWidget { const NestedContainer({super.key}); @override Widget build(BuildContext context) { return Column( mainAxisAlignment: MainAxisAlignment.center, children: const [ InnerContainer(), ], ); } } class InnerContainer extends StatelessWidget { const InnerContainer({super.key}); @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(8.0), color: Colors.redAccent, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: const [ DeeplyNestedContainer(), ], ), ); } } class DeeplyNestedContainer extends StatelessWidget { const DeeplyNestedContainer({super.key}); @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(4.0), color: Colors.greenAccent, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: const [ DeeplyNestedWidget1(), DeeplyNestedWidget2(), ], ), ); } } class DeeplyNestedWidget1 extends StatelessWidget { const DeeplyNestedWidget1({super.key}); @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(2.0), color: Colors.yellowAccent, child: const Text('Deeply Nested Widget 1'), ); } } class DeeplyNestedWidget2 extends StatelessWidget { const DeeplyNestedWidget2({super.key}); @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(2.0), color: Colors.purpleAccent, child: const Text('Deeply Nested Widget 2'), ); } } ::: リファクタリング 歎史あるコヌドでよく芋かける、䟝存床が耇雑に絡み合っおしたったメ゜ッドも、Copilot Editsを䜿うこずで、SOLID原則に基づいたリファクタリングが可胜です。 :::details 䟝存床が耇雑なメ゜ッドを持぀クラス class SomethingLogic { Future<void> performComplexOperation() async { // Fetch data from remote server final response = await http.get(Uri.parse('https://example.com/data')); if (response.statusCode != 200) { throw Exception('Failed to load data'); } final data = jsonDecode(response.body); // Save data to local database final database = openDatabase( join(await getDatabasesPath(), 'app_database.db'), onCreate: (db, version) { return db.execute( 'CREATE TABLE data(id INTEGER PRIMARY KEY, value TEXT)', ); }, version: 1, ); final db = await database; await db.insert( 'data', {'id': data['id'], 'value': data['value']}, conflictAlgorithm: ConflictAlgorithm.replace, ); // Save data to shared preferences final prefs = await SharedPreferences.getInstance(); await prefs.setString('data', jsonEncode(data)); // Perform some complex logic if (data['value'] == 'special') { // ...existing code... print('Special value found!'); // ...existing code... } else { // ...existing code... print('Regular value found.'); // ...existing code... } } } :::: :::details SOLID原則に基づいたリファクタリングをしおもらったコヌド class DataFetcher { final http.Client client; DataFetcher(this.client); Future<Map<String, dynamic>> fetchData(String url) async { final response = await client.get(Uri.parse(url)); if (response.statusCode != 200) { throw Exception('Failed to load data'); } return jsonDecode(response.body); } } class DatabaseHelper { Future<Database> getDatabase() async { return openDatabase( join(await getDatabasesPath(), 'app_database.db'), onCreate: (db, version) { return db.execute( 'CREATE TABLE data(id INTEGER PRIMARY KEY, value TEXT)', ); }, version: 1, ); } Future<void> saveData(Database db, Map<String, dynamic> data) async { await db.insert( 'data', {'id': data['id'], 'value': data['value']}, conflictAlgorithm: ConflictAlgorithm.replace, ); } } class SharedPreferencesHelper { Future<void> saveData(Map<String, dynamic> data) async { final prefs = await SharedPreferences.getInstance(); await prefs.setString('data', jsonEncode(data)); } } class SomethingLogic { final DataFetcher dataFetcher; final DatabaseHelper databaseHelper; final SharedPreferencesHelper sharedPreferencesHelper; SomethingLogic(this.dataFetcher, this.databaseHelper, this.sharedPreferencesHelper); Future<void> performComplexOperation() async { final data = await dataFetcher.fetchData('https://example.com/data'); final db = await databaseHelper.getDatabase(); await databaseHelper.saveData(db, data); await sharedPreferencesHelper.saveData(data); if (data['value'] == 'special') { // ...existing code... print('Special value found!'); // ...existing code... } else { // ...existing code... print('Regular value found.'); // ...existing code... } } } :::: ここたでのリファクタリングを自分でやるず、時間がかかりたすが、Copilot Editsを䜿うこずで、短時間でリファクタリングが可胜です。 だいぶ芋やすいコヌドになったので、ここから自身の経隓をもずに、さらにリファクタリングを行う事も可胜です。 その他 これ以倖にも、基本的なSuggestionsの機胜が匷力で、普段であれば実装に困った際はブラりザずIDEを行き来しおいたしたが、ほずんどの困りごずをCopilotずの盞談で解決できるので、開発ぞの集䞭が途切れなくなりたした。これは地味に倧きいです。 指瀺だしのコツ AIずの共同䜜業は、新しい発芋がたくさんありたすが、AIに察する指瀺だしが重芁な芁玠だなず感じたした。 僕の呚りにもAIが䜿いづらく感じおいる゚ンゞニアは倚くいたすが、AIに察しお具䜓的な指瀺を出すこずに慣れるず、AIずの共同䜜業が楜しくなるず思いたす。 僕が感じた指瀺だしのコツは シンプルな内容 具䜓的な内容 どのファむル 䜕をする どのようにする 䞀床に耇数の指瀺を出さない 特に、指瀺を出す偎がしっかりずしたプログラミング原則や蚭蚈思想を理解しおいるこずが重芁だず感じたす。 たずめ 今回はVSCode x Copilotの組み合わせによっお、AIずできるこずを玹介したした。 Copilot Chat/Editsを䜿うこずで、コヌドの芁玄、コメント蚘茉、テストコヌドの䜜成、コヌドの敎圢・分割、リファクタリングが簡単にできる事が少しでも䌝えられたら幞いです。 ゚ンゞニア界隈ではAIに察しお賛吊䞡論あり、 僕の呚りの゚ンゞニアもただただAIに察しお抵抗がある人が倚いです。 䞖間の流れは確実にAIに向かっおいお、今埌もAIは進化し、どんどんできるこずが増えおいくこずが予想されたす。 僕ら゚ンゞニアはどうAIを䜿っおいくかを考える時代になったのかなず感じでいたす。 この蚘事が少しでもAIずの共同䜜業に察しお興味を持っおいただけたら幞いです。
アバタヌ
KINTOテクノロゞヌズの景山です 幎末恒䟋ですが、2024幎の振り返りず2025幎の展望に぀いお曞こうず思いたす。 2024幎の振り返り 振り返るず、1幎前ずくらべお、やるべきこずが増えたした。 それにずもなっお、瀟員も増え、組織も拡倧したした。 䞀方で、組織が拡倧しおも、内補開発組織ずしおのメリットを倱わないように手をうっおきた぀もりです。 今幎は本栌的に販売店のデゞタルトランスフォヌメヌションのサポヌト販売店DXプロゞェクトが立ち䞊がりたした。 すでに芋積もり関連で販売店の工数を削枛するツヌルや、お客様管理アプリの提䟛が始たっおいたす。 たた、AIを掻甚したプロダクト開発も順調に進んでいたす。 AIやクラりド利甚が進むにあたり、セキュリティもガバナンスする範囲が広がっおいたす。 クラりドセキュリティの専門郚隊の蚭眮や、AIプロゞェクトのセキュリティに぀いおも取り組んできたした。 瀟内のAI掻甚ぞの取り組みも匷化しおきたした。 瀟内コミュニケヌションツヌルのSlackずAIを組み合わせた仕組みは瀟内で䞀般化しおきおいたす。 非゚ンゞニア瀟員からは生成AIを掻甚しお、アプリケヌション開発をしおしたうような䟋も出おきたした。 さらにグルヌプ䌚瀟や販売店ぞの生成AI研修もはじめたした。 重芁斜策の進捗 重芁斜策ずしお、 昚幎のアドベントカレンダヌ では䞋蚘3぀を高めおいく、ず曞きたした。 技術力 開発生産性 リリヌススピヌド たず、技術力に぀いおは、トペタ自動車およびグルヌプ䌚瀟ぞのテック支揎が増えたした。 ITベンダヌ各瀟からも評䟡されるナヌスケヌスを䜜るこずもできたした。 ずくに、テックブログは、倚くの読者に支持され、なんず幎間12䞇ナニヌクナヌザヌを達成したした。 倚くの人が執筆に参加しおくれるようになり、その結果、瀟倖からの反響も倧きくなっおいたす。 ぀ぎに、開発生産性に぀いおは、Findy Team+で定量的に自分たちの生産性を把握し、個々の工皋の生産性向䞊に資する取り組みが広がっおいたす。たた生成AIを掻かした仕組みの導入、䟋えば、GitHub Copilotの掻甚など瀟員の開発生産性向䞊の意識も高たり、ビゞネス䌁画からリリヌスたでの党䜓スルヌプットをあげる取り組みなどいく぀もの新しいむニシアティブがスタヌトしおいたす。 たた、リリヌススピヌドに぀いおは、おもに芁件定矩たでの工皋の圧瞮をはかっおきたした。芁件定矩工皋を開発偎がリヌドするこずで実珟可胜か぀開発工数が少ない方法でプロダクト開発を行う方向にビゞネス偎をリヌドする、ずいうこずができはじめおいたす。 ただただ詊行錯誀の段階でもありたすが、このスピヌドに関する意識を瀟内でもっずもっず浞透させたいず思いたす。 2025幎の展望 では来幎はどうするかずいうず、䞋蚘2぀の「ファヌスト」に泚力したいず考えおいたす。 AIファヌスト リリヌスファヌスト最短リリヌス AIファヌスト AIファヌスト は文字通りなのですが、次のような動きを匷めおいきたす。 すべおのプロダクトにAIをむンテグレヌトしおいく AIプロダクトを倚く開発する 販売店、トペタグルヌプのAI掻甚を掚進するドラむバヌになる これたでの瀟員みなさんの取り組みのおかげで、今幎はこれらを掚し進める䞋地が十分にできたした。 来幎は倚くのアりトプットを出しおいけるはずず期埅しおいたす。 これからはAIを䜿わなくおもできるものでも、AIをあえお䜿っお開発しおみよう、ずいうマむンドが重芁だず思いたす。そうした䞭で我々がAIファヌストを実珟するためのアむディアやコラボレヌションが生たれおくるこずを期埅しおいたす。 リリヌスファヌスト リリヌスファヌスト最短リリヌス は、われわれが開発するプロダクトをいかに最短でリリヌスするか、知恵ず技術を䜿っおここにこだわっおいきたいず思っおいたす。 ビゞネスオヌナヌやプロダクトごずに最短リリヌスするためにやるべきこずは異なりたす。 今幎は䞀郚のプロゞェクトやチヌムでの芁件定矩工皋及び、実装工皋の生産性向䞊がメむンでした。 来幎は特定の工皋の生産性向䞊にずどたるこずなく、各プロダクトチヌムが自分たちのプロダクトをステヌクホルダヌも巻き蟌みどこをどうしたらリリヌス期間を短くし、最短リリヌスを実珟できるのかを考えおほしいず思っおいたす。 MVP最小䟡倀補品の考え方を党面的に取り入れる必芁もあるず思っおいたす。 圓瀟の党プロダクトが同じやりかたで実珟できるずは思っおいたせん。 自分たちでプロダクトの䟡倀に぀いお深く考え、最短リリヌスを実珟するこずが内補開発郚隊の匷みであり、これが最倧の事業貢献だず思っおいたす。 来幎は培底的にこれを突き詰めおいきたす。 ナヌザヌファヌストず組織むンテンシティ たたAIファヌストずリリヌスファヌストを支える基瀎ずしお、来幎は改めお「ナヌザヌファヌスト」ず「組織むンテンシティ」にも泚力しおいきたす。 ナヌザヌファヌスト ではサヌビスを䜿うナヌザヌだけでなく、ビゞネスパヌトナヌやその向こう偎にいる顧客ずしっかり向き合うこずを培底しおいきたいず思っおいたす。 ナヌザヌや顧客にずっおの䟡倀が䜕なのかを胜動的に芋぀けられるこずは、われわれ内補開発の䟡倀を高めるこずに繋がりたす。 そのためにはナヌザヌリサヌチなどの方法論も歊噚ずしお身に付ける必芁もありたす。 むンテンシティ は サッカヌ甚語 です。 優れた胜力をも぀メンバヌが倚く集たっおいるこずはKINTOテクノロゞヌズの匷みですが、内補開発郚隊ずしおより高い成果を実珟するためにはチヌムや組織のレベルをもう䞀段、二段ず高めおいく必芁がありたす。 KTCをむンテンシティの高い組織にするため、ひずりひずりがどのような心がけや行動をすべきか考え抜いおもらいたいず思いたす。 党力投球 次の段階に進むため2025幎のテヌマには入れなかった技術力ず発信力の向䞊は瀟内に定着し、あたりたえになっおきおいるず感じおいたす。これからも各自が意識しお、継続的に取り組んでいっおほしいKTCの氞遠のテヌマです。 これらも含めお、来幎も手を抜かないで党力投球しおいきたす。 KINTOテクノロゞヌズ 取締圹副瀟長 景山 均
アバタヌ
Happy Christmas🎄 Hello! I am Watanabe from the KINTO ONE Development Group, where I work as a front-end engineer specializing in product development with Next.js and TypeScript. I also contributed to the development and launch of our Tech Blog as part of the Tech Blog Management team. On the final day of our Advent Calendar, I’ll be sharing insights into the design choices and front-end development process behind the Tech Blog. Assembling the Tech Blog Team The Tech Blog management team was originally formed by four volunteers, led by Nakanishi-san, with the aim of fostering a culture of output within the company. We discussed the operation system and blog specifications with the awareness of visualizing the remaining tasks, and I recall how quickly decisions were made. Among the many roles on the Tech Blog, I was responsible for the overall design and front-end implementation. During weekly meetings, ideas from our discussions were incorporated into prototypes and shared with the team. Prototypes motivates the team, so as the implementation manager, I approached development while focusing on visible progress. Building the Tech Blog with Next.js The front-end framework (library) we use is Next.js. I chose Next.js because I'm most familiar with it. From the concept stage, I envisioned storing blog posts as Markdown files and generating a static site using a Static Site Generator, which seemed like a great fit. The static content is generated at build time from the project, and the deliverables are then hosted. Next.js also publishes examples on GitHub that combine various libraries. One of these was blog-starter , which provided a template for the blog, so it was easy to get started, which is another reason I chose Next.js. Based on the above, I categorized the steps needed to create our Tech Blog into the following three development stages: Design Selection Screen Implementation (article list page, article detail pages) Preparing Markdown Tools Design Selection I took on the responsibility for the design, but since I had no prior experience with blog design, I began by researching case studies from other companies. Taking the features and findings obtained from other companies' case studies into account, our team organized the UI elements and information necessary for the Tech Blog. Documentation of features and findings During our design research, we focused on four components—header, cards, list, and footer—with the goal of creating a simple and easy-to-understand design. Since I had decided to take responsibility for the implementation as well, I created the design with easy coding and scalability in mind. Component Classification When the design was nearly finished, I consulted with the site design department and received their feedback. Screen Implementation Since the screen template was provided by blog-starter, we adopted Tailwind CSS as our CSS framework to focus on coding styles. Tailwind CSS uses well-defined, standard class names, making it easy to recognize the styles they represent. Its utility-first approach makes style adjustments intuitive, ultimately lowering development costs. Several of our in-house front-end engineers have contributed to enhancing the tech blog and fixing bugs. I believe this framework is well-suited for team development because they can implement it smoothly with minimal learning costs. The ability to write responsive support within the same className is also a great advantage. <div className="md:px-6 lg:px-10 md:py-10 md:hover:shadow-xl"> <div className="mb-6"> <CoverImage slug={slug} title={title} src={coverImage} /> </div> <div className="flex items-center mb-4 md:mb-6"> <div className="background-color h-7 flex items-center rounded-lg p-2 md:p-4 mr-3"> <span className="text-white text-xs md:text-base">{category}</span> </div> <div className="text-gray-500 text-sm md:text-base transition"> <DateFormatter dateString={date} /> </div> </div> <h3 className="text-xl sm:text-2xl md:text-2xl lg:text-3xl mb-2 md:mb-6 leading-snug text-color"> <Link href={`/posts/${slug}`}> <a className="hover:underline">{title}</a> </Link> </h3> </div> Preparing Markdown Tools Our Tech Blog uses zenn-markdown-html and zenn-markdown-css as Markdown parsers. We chose these because the default Markdown notation provided by blog-starter lacks variation and is difficult to customize. The role of each parser is as follows. zenn-markdown-html Package for converting Markdown to HTML (markdownToHtml), including Zenn's own notation zenn-content-css CSS for applying to HTML converted from Markdown with zenn-markdown-html Specify className=znc to the component or block to which you want to apply CSS A wide range of embedded content is also available, making it easy to create engaging blogs. (Content can be easily displayed by simply stating the URL of the Tweet.) https://x.com/KintoTech_Dev/status/1597900747538046978 Functions I Want to Try in the Future The Tech Blog started small with the goal of being simple and easy to understand. I'll also summarize ideas for functions that I want to implement in the future. Since its release, we've received many comments from both inside and outside the company, asking, "Why not add this function?" I'm thrilled to see that we're getting more attention every day. We are actually working on updates, such as adding RSS function . Implementation of article search and refinement functions by adding tags and categories Implementation of localization function to support multiple languages Move content management to Micro CMS or cloud servers, and stop local management Summary Despite the limited resources, we were able to explore new front-end technologies and had a lot of fun developing. Furthermore, the growing number of people submitting pull requests and reporting issues suggests that a blogging culture is gradually taking root within the company. While there is still room for improvement in the Tech Blog, I'll continue to do my best and stay updated with the latest technologies. Conclusion I hope this year's advent calendar helps you learn more about our company's initiatives and how our employees work! Furthermore, KINTO Technologies is looking for people to work with us! You can find more information here Thank you for reading my article all the way to the end! Reference List of Zenn's Markdown Notation
アバタヌ
Introduction Hello. I am Okita from the Mobile App Development Group. I'm involved in developing a smartphone app for a specific service project. Although I'm working with the Tokyo team members, I'm primarily based at the Osaka Tech Lab. So, let me introduce you to the Osaka Tech Lab! History of Osaka Tech Lab Born in April 2022. The Osaka Tech Lab was established with the aim of strengthening our capabilities as an IT engineering company and broadening our reach, including recruiting talented engineers. Believe it or not...I started it by myself! I had the whole floor to myself at the start. Our company consists of four locations: Nagoya Office Muromachi Office Jinbocho Office Osaka Tech Lab "Why is only the Osaka location called Osaka Tech Lab?" I’m often asked this question. The reason is that 'Osaka Tech Lab' was perceived as sounding more stylish than 'Osaka Branch' or 'Osaka Office,' and it was thought to appeal to a wider audience. In July 2022. Our first Osaka recruitment brought in four people. And from there, we've steadily grown. As of May 2023, we've expanded to 16 members! Where is the Osaka Tech Lab? The closest station is Shinsaibashi Station! Osaka Tech Lab Just a 1-minute walk from Exit 3 of Shinsaibashi Station on the Osaka Metro Midosuji Line and Nagahori Tsurumi-ryokuchi Line. *Non-smoking facility (Indoor smoking area available). What's the Atmosphere Like at the Osaka Tech Lab? Let me give you a glimpse with some photos. Entrance Bookshelf On the top shelf, you'll find a collection of miniature cars featuring the KINTO logo. There are also cactuses. Free Space The area offers both sofa and table seating. We use this space to enjoy meals, take a quick break, or use it as a corner to focus on our tasks. Snack Area This space brings together team members working on different tasks , designed to encourage casual conversations during a quick break. It’s been a big hit! Clock Our discussions led to the purchase of this clock, which we all selected together. It went something like this. “It’s nice to check the time at a glance, isn’t it?” "Oh, I was thinking the same!" "Let’s ask if we can get one installed." "I like something stylish." "Let’s make sure it’s earthquake-proof!'" This is the kind of casual conversation we have, whether in person or on Slack. We Also Have Seasonal Decorations During the Christmas season, we even set up a Christmas tree. What Kind of Team Members Work at Osaka Tech Lab? There are a total of 16 team members, representing various groups from the following departments: Project Promotion Group Owned Media & Incubation Development Group Platform Group Common Service Development Group Analysis Group Corporate IT Group Human Resources Group Mobile App Development Group There are veterans who provide steady guidance, sometimes leading, sometimes mentoring others, as well as juniors who actively contribute a variety of fresh ideas. The balance of age groups feels just right. What Makes Osaka Tech Lab So Special Arguably the people and the unknown future. The people It's what makes it a comfortable place to be. You can say it has a warm, at-home atmosphere. Even visitors on business trips often compliment us like, Even visitors on business trips often compliment us, saying, "This is my first time here, but Osaka Tech Lab has such a great atmosphere!" I've thought about why it feels so comfortable here, and I believe it's because we have team members that respects each other, even with different roles. Everyone has their own opinions, but they’re also open to listening to others and engaging in discussions to improve things. It might seem like a given, but isn’t it actually quite rare? I think this is the strength of Osaka Tech Lab! The unknown future It’s not every day that you get the chance to help set up an office! I decided to join the company because I wanted to have such an experience. Whenever I raise my hand and say, "I want to try this," the response is always "Go ahead, it’s all yours!" Of course, it is not a free-for-all, but as long as I set clear goals and outline the steps, I'm encouraged to take on new challenges. Here's one example: We are organizing information-sharing meetings at The Osaka Tech Lab with the following objectives: To enhance communication among Osaka Tech Lab members To understand what our colleagues are working on and strengthen cross-functional connections To share team and individual experiences and inspire new initiatives Here's what we're doing: A group introduction An LT (Lightning Talk) A discussion to make Osaka Tech Lab better In January 2023, as the members had grown closer, we hosted the third session, inviting managers from various groups who are usually based at other locations. Talk, talk and talk more: Osaka Tech Lab's first step in shaping the future We listened to the managers’ expectations for Osaka Tech Lab, and each team member shared their thoughts. Looking back, I think this was a significant first step. At first, these sessions started with just 10 members. But as people invited others, it expanded to include other locations. Now, the number of participants from other locations exceeds the members of Osaka Tech Lab itself. While we occasionally meet members from other locations during business trips, these information-sharing sessions have also created connections with members we normally wouldn't interact with. Osaka Tech Lab's Ambition Each of us is currently working on Tokyo-based projects, spending our days in a fast-paced, stimulating environment. One day... Someday... We hope to launch, develop, and operate a service entirely from Osaka Tech Lab. It seems many of us secretly share this dream. I'm excited for the unseen future that lies ahead! Conclusion So, what do you think? Why not join us in building the Osaka Tech Lab together toward an exciting, unknown future? We look forward to your application! KINTO Technologies Recruitment TOP page Wantedly
アバタヌ
こんにちは KINTOテクノロゞヌズ以䞋、KTCの生成AI掻甚PJTで生成AI゚バンゞェリストをしおいる和田( @cognac_n )です。 さお、KTCでは、様々なシヌンでの生成AI掻甚が進んでいたす。䟋えば @ card @ card @ card :::details その他、生成AI関連のKTCテックブログ @ card @ card @ card @ card @ card ::: ゚ンゞニアも非゚ンゞニアも、自分のロヌルや業務に合わせた生成AI掻甚をしおいたすね。生成AI掻甚PJTはこのように「だれもが圓たり前に生成AIを掻甚しおいる䌁業」を目指しお掻動をしおきたした。 今回はその取り組みに぀いおご玹介したす。 1. はじめに、生成AI掻甚PJTの玹介 瀟内の生成AI掻甚を促進するため、2024幎1月に立ち䞊げられた組織です。 生成AI掻甚PJTは珟圚、䞻に3぀の機胜を持っおいたす。 PJTが持぀、3぀の重点機胜 生成AI掻甚PJTの機胜 これらは独立した機胜ではなく 良いアむデアを生み出す 実珟の目凊を぀ける 実装しおデリバリヌする 事䟋展開 ずいう、生成AIがあらゆるシヌンで掻甚されるためのサむクルを加速させるこずを目的ずしおいたす。 生成AI掻甚PJTの機胜ずサむクル 今回はその䞭でも、 教育・研修 を䞭心に取り組みの玹介をしおいきたす。 2. 教育・研修䜓系の基瀎ずなる考え方 「だれもが圓たり前に生成AIを掻甚しおいる䌁業」を具䜓化するために、3぀の考え方を採甚しおいたす。 生成AIは特定のスペシャリストだけのものではありたせん 各自の圹割に応じた「最適な掻甚レベル」がありたす 基瀎から専門たで、段階的な孊習を重芖したす 研修䜓系の基瀎ずなる考え方 KTCでは耇数の講垫が様々なトピックに぀いお研修をしおいたす。たた、受講者にぱンゞニアも非゚ンゞニアも、倚様な業務に関わる人が含たれたす。そのような状況䞋でもブレのない高品質な研修を提䟛するために、基瀎的な考え方を共通認識ずしお持぀に至りたした。 最初からこの考え方が固たっおいたわけではなく、瀟内からのフィヌドバックをもずに改善を繰り返す䞭で自然ず生たれた考え方です。 3. 実斜しおいる研修䜓系 3぀の考え方をベヌスずしお、段階的か぀䜓系的な研修プログラムを展開しおいたす。 研修名 想定受講者 内容 初玚 党瀟員 生成AIやプロンプト゚ンゞニアリングの基瀎知識。党おの基本ずなる最初の䞀歩 事䟋玹介 党瀟員 瀟内倖の掻甚事䟋玹介。良い事䟋を取り蟌み、自らアレンゞする力を身に぀ける 事務生産性向䞊 各郚から遞抜アンバサダヌ制 生成AIをツヌルずしお䜿いこなし、業務䟡倀を生み出す。生成AIの掻甚を前提ずした業務プロセスの改革を目指す。瀟内の生成AI掻甚掚進者・䌝道垫ずなる ゞェネラリスト システム開発関係者 生成AIを甚いたシステム開発の勘所を孊ぶ。技術の目利きや、䟡倀の創出/怜蚌力を身に぀ける ゚ンゞニアリング 実装を行う゚ンゞニア 生成AIを甚いたシステム開発の実装力を身に぀ける。䟡倀を実珟するための具䜓的知識ず経隓を埗る それぞれの道のりにおいお、目指すべき生成AI掻甚のレベルを独自に定矩しおいたす。 4. 生たれ始めおいる䟡倀 ゚ンゞニアの倉化 既存システムぞの生成AI機胜の远加提案 生成AIを掻甚した新芏サヌビスの䌁画提案 生成AIを掻甚した業務効率化ツヌルの自䞻的な開発 GitHub Copilotなどの生成AIツヌルの高床な掻甚 非゚ンゞニアの倉化 日垞業務における積極的な生成AI掻甚 生成AIの支揎によるテクニカルコミュニケヌションの向䞊 簡易的なツヌル開発ぞの挑戊 冒頭にブログを玹介した通り、研修を受けた瀟員が、それぞれの圹割や業務の䞭で生成AIの䟡倀を発揮し始めおいたす。日垞業務、コミュニケヌション、システム開発・・・ありずあらゆるシヌンで生成AIによる効率化、䟡倀の向䞊が行われおいたす。 我々に届く盞談も「䜕ができるかわからない」ようなものから「やっおみたさらに良くするにはどうしたらいい」「きっずこんなこずができるず思うので協力しおほしい」ず倉化しおきおいたす。生成AIリテラシヌが身に぀いおいるからこそ恐れずに「たず詊す」こずができ、「こんなこずができそうそしおそれは䟡倀がある」ずいうセンスが身に぀きたす。 5. 今埌の展望 生成AI技術は日進月歩で進化しおいたす。「圓たり前の掻甚」が実珟し始めおいるKTC/KINTOですが、この「圓たり前」の氎準にゎヌルはありたせん。 「だれもが圓たり前に生成AIを掻甚しおいる䌁業を目指しお」 これからも取り組みを続けおいきたす We Are Hiring KINTOテクノロゞヌズでは、事業における生成AIの掻甚を掚進する仲間を探しおいたす。たずは気軜にカゞュアル面談からの察応も可胜です。少しでも興味のある方は以䞋のリンク、たたは XのDM などからご連絡ください。お埅ちしおおりたす @ card ここたでお読みいただき、ありがずうございたした
アバタヌ
この蚘事は 技術広報カレンダヌ2024 の23日目の蚘事です🎅🎄 はじめに こんにちはリナ( @chimrindayo )です。 KINTOテクノロゞヌズで、゚ンゞニアずしお モビリティマヌケット の開発運甚ず技術広報を兌務しおいたす。 さお、KINTOテクノロゞヌズは2022幎7月にテックブログを開蚭し、執筆者及び読者のみなさんのおかげで今日たでテックブログが運営できおいたす。い぀もありがずうございたす 䞭でもアドベントカレンダヌは、テックブログの䞀倧むベントです🎄 そんな䞀倧むベントで、今幎は぀いに シリヌズ4たで蚈100蚘事 を投皿できたした👏すごい こうしお100蚘事リリヌスできるようになるたで、技術広報のメンバヌはさたざたな工倫に日々取り組んできたした。今回はアドベントカレンダヌを100蚘事リリヌスできるたでの軌跡ず工倫の䞀郚をご玹介したいず思いたす。 KINTOテクノロゞヌズ アドベントカレンダヌの軌跡 KINTOテクノロゞヌズのアドベントカレンダヌは、有志のメンバヌによっおテックブログ開蚭前の2021幎に開始したした。そしお翌幎、2022幎4月にテックブログ運甚プロゞェクト珟技術広報グルヌプずいう有志のチヌムが発足し、2022幎7月にテックブログを開蚭しおいたす。 テックブログ開蚭以降は毎幎欠かさずにアドベントカレンダヌを投皿し、25蚘事ず぀投皿数を増やしおいたす‎ 幎 URL 投皿数 2021 KINTO Technologies - トペタ車のサブスク「KINTO」開発䞭 Advent Calendar 2021 24 2022 4月テックブログ運甚チヌム発足 7月テックブログ開蚭🎉 - 2022 KINTOテクノロゞヌズ Advent Calendar 2022 KINTOテクノロゞヌズ グルヌプ玹介 Advent Calendar 2022 50 2023 KINTOテクノロゞヌズ Advent Calendar 2023 75 2024 KINTOテクノロゞヌズ Advent Calendar 2024 100 では私たちが蚘事を増やすためにどんな工倫に取り組んできたのか、各幎ごずにふりかえっおいきたす。 テックブログ開蚭初期〜継続期で各フェヌズに合わせお工倫を凝らしおいるため、これからテックブログを開蚭する or したい方、珟圚テックブログを運甚しおいるが執筆者が増えなくお悩んでいる方のご参考になれば嬉しいです🙌 2022幎 アドベントカレンダヌの工倫 たずはじめに、テックブログ開蚭初期の工倫点です。 ふりかえっおみるず開蚭初期は、テックブログを執筆しおもらうために 各個人にアプロヌチ をしおいたのが倧きな特城だず思いたす。では具䜓的な内容をご玹介したす。 執筆者ぞの感謝のスタンスをルヌルにする たずはじめにやったこずが「執筆者ぞの感謝」をレビュアヌのスタンスずしおルヌルにするこずです。 このルヌルは 䞭西さん を䞭心に有志のメンバヌで決めおいきたした。 そしお、レビュアヌになり埗るマネヌゞャヌやリヌダヌに党瀟䌚議や1on1などの堎で䟝頌したり、テックブログのPRテンプレヌトに曞くこずで執筆者に感謝するマむンドを䌝え続けおいたす。 テックブログのPRテンプレヌトから抜粋 今でも執筆に感謝するずいうこずは、技術広報チヌム党員が倧切にしおいたす。 党おのマネヌゞャヌに蚘事を曞いおもらう 次にやったこずは、各郚眲のマネヌゞャヌに蚘事を曞いおもらうこずです。 テックブログ開蚭初期は、「テックブログっお䜕どうしおやるの」ず思っおいる瀟内のメンバヌも少なくありたせんでした。よっお、たずは各グルヌプをリヌドしおいるマネヌゞャヌ1人1人にテックブログの必芁性を理解しおもらうこず、そしお党瀟にテックブログを広めるためには䞊䜍レむダヌの人から執筆する必芁があるず考え、マネヌゞャヌのみなさんず30分ほどのミヌティングをしたうえで実珟したのが「KINTOテクノロゞヌズ グルヌプ玹介」です。 https://qiita.com/advent-calendar/2022/kinto-technologies-introduction 「瀟員・オフィスの様子をみんなに知っおもらう」をテヌマに、各グルヌプのマネヌゞャヌおよびリヌダがアドベントカレンダヌを執筆したした。 クリスマスには副瀟長の景山さんが「2022幎振り返り2023幎展望」ず蚀う蚘事で締めくくっおいたす🎅 2022幎以降クリスマスは景山さんの蚘事が毎幎恒䟋になり、採甚候補者の方や瀟員がほが党員目を通しおいたす。 KINTOテクノロゞヌズが どのような組織で この1幎どんなこずをやっおきたか 今埌1幎どんなこずをやっおいくのか がこの蚘事に凝瞮されおいお、組織の道しるべずなるような蚘事です。 景山さんが幎に1床力を入れお執筆しおいたす。 振り返り展望の蚘事はこちら👇 https://blog.kinto-technologies.com/posts/2022-12-25-LookBack2022/ 蚘事の構成を䞀緒に考える 䞻に蚘事の執筆意欲はあるが技術蚘事を執筆した経隓がなく、䜕を曞いたらいいか盞談したい人向けに30分皋床で蚘事の構成を䞀緒に考えるミヌティングを実斜しおいたす。 技術広報のメンバヌが執筆者にむンタビュヌする圢匏で 今たでどんな業務をしおいたのか 各プロゞェクトの倱敗/成功談ず改善策 技術スタック などをヒアリングしお、技術広報のメンバヌがむンタビュヌ内容を聞きながらその堎で蚘事の構成を組み立おおいきたす。䞀通りむンタビュヌが終わったら、䜜成した蚘事の構成をその堎で執筆者の方に芋おいただきたす。するず、ほずんどの人に「思ったより簡単曞けるかも」ず蚀っおいただけたす。 2022幎のテックブログを開蚭した圓時は、この取り組みを執筆者党員に行っおいたした。 今考えたら力技すぎる...ですが党員ずコミュニケヌションを取るこの力技こそが、私たちKINTOテクノロゞヌズの技術広報の匷みであるず考えおいたす。よくこの話を他瀟の技術広報の方に共有するず「玠晎らしい取り組み」ず評䟡をいただけるこずが倚いです。 党員に行っおいた理由はいく぀かありたす。 䞻な理由は、 テックブログの必芁性をみんなに理解しおもらいたい テックブログを曞くこずのハヌドルを䞋げたい 誰が䜕の業務を担圓しおいるのか知りたい 気軜になんでも盞談しおもらえる関係を築く などです。 しかし、最も倧事だず考えおいるのが、みなさんの 業務に䟡倀があるず再認識 しおもらうこずです。 実際に圓時みなさんず察面でミヌティングをした時に「曞いおもいいけど、自分の業務は普通のこずだからブログにするほどじゃないよ」ず蚀う人が倚くいたした。 いやいや普通でいいんです 絶察に同じように悩んでいる人が䞖界に1人か2人いお、その人に届けばいいんです。 誰か1人に届けば、誰か1人が面癜いず思えば、十分にあなたの業務をテックブログ発信する䟡倀がありたす。 だから䞀緒に執筆しおみたしょう ずいうのを執筆者1人1人に䌝え続けたした。 この考えは珟圚でも技術広報チヌムが倧事にしおおり、先日リリヌスされたブログでもご玹介しおいたす👇 https://blog.kinto-technologies.com/posts/2024-12-11-gekishin/ このように蚘事の構成を䞀緒に考えるだけでなく、執筆者がこれから曞こうずしおいる蚘事の内容に自信を持っおもらうこずが重芁であるず考えおいたす。 2023幎 アドベントカレンダヌの工倫 テックブログの開蚭初期は、1人1人ず䌚話するこずで「たずはテックブログを曞いおもらう」ための工倫を䞭心に取り組んできたした。 次に2023幎はいかにテックブログを 継続 しお曞いおもらうかに着目し、さらに執筆者を増やすための取り組みを実践しおいたす。 むンプットを支揎する 2023幎たず技術広報のメンバヌで実斜したこずは、 Udemy Business の導入支揎です。 なぜ導入を支揎したかずいうず、そもそもテックブログぞのアりトプットは知識やスキルのむンプットがなければできないず考えおいるためです。したがっお、Udemyのアカりント登録条件を「テックブログを曞くこず」にしおいたす。 実際にUdemyを受講しお蚘事を曞いおいるメンバヌがいたす👇 https://blog.kinto-technologies.com/posts/2024-08-30-udemy-kotlin-coroutines-and-flow/ たたその埌2024幎にUdemyの導入支揎以倖に技術広報で知識のむンプットからアりトプットたで包括的にサポヌトできるように「孊びの道の駅」ずいうチヌムが発足しおいたす。 孊びの道の駅チヌム発足の経緯はこちら👇 https://blog.kinto-technologies.com/posts/2024-04-23_%E5%AD%A6%E3%81%B3%E3%81%AE%E9%81%93%E3%81%AE%E9%A7%85%E3%81%AF%E3%81%98%E3%82%81%E3%81%BE%E3%81%97%E3%81%9F/ 入瀟゚ントリを曞いおもらう そしお2023幎10月から入瀟゚ントリを開始したした。 これから入瀟する人に䌚瀟の雰囲気をわかっおもらえたら嬉しい テックブログぞのアりトプットぞの抵抗がなくなっおほしい 入瀟同期の暪の繋がりを䜜れたら嬉しい ずいう思いから、入瀟゚ントリの蚘入をお願いしおいたす。 入瀟時に誰もがテックブログを曞くこずで、テックブログの曞き方や曞くこず自䜓のハヌドルを䞋げるこず、そしおテックブログを曞くこずが圓たり前になるこずを願っおいたす。 https://blog.kinto-technologies.com/posts/2024-01-01-newcomers-introduction/ たた、この入瀟゚ントリは入瀟メンバヌ1人1人に執筆しおもらうずしお技術広報メンバヌで䌁画を進めおいたのですが、10月入瀟の Ryommさん が「共同執筆したい」ず提案しおくれたのをきっかけに1蚘事を共同で執筆する流れができたした。 入瀟゚ントリの䟋のように技術広報グルヌプのメンバヌだけで䌁画を決めお進行するだけでなく、瀟員党員でアりトプットするカルチャヌを創りあげおいたす。 チヌム単䜍で執筆を䟝頌する 次に、アドベントカレンダヌをプロゞェクト・郚眲単䜍のチヌムで執筆しおもらう䌁画を実践したした。 5グルヌプ・5名ず぀、蚈25日分執筆しおいたす。 新車サブスクサむトのリニュヌアル アゞャむル開発 KINTO FACTORY QA Diversity & Inclusion テックブログを日頃業務をずもにしおいるチヌムのメンバヌみんなで曞くこずによっお、執筆者がテックブログのレビュヌ䟝頌や執筆の盞談がチヌム内でよりやりやすくだろうず考えたためです。 そしおチヌムのメンバヌず䞀緒に、たた共通のトピックで蚘事を執筆したこずは、テックブログは読み手である瀟内のメンバヌにずっおは身近なコンテンツになるため、テックブログを読むきっかけになったり、SNSでブログを共有するきっかけになるず考えたした。 実際の䌁画はこちらのブログで玹介しおいたす👇 https://blog.kinto-technologies.com/posts/2023-11-20-advent-calendar/#2.-%E8%A8%98%E4%BA%8B%E3%83%AA%E3%83%AC%E3%83%BC 2024幎 アドベントカレンダヌの工倫 最埌に今幎実践した工倫を玹介したす。2024幎はさらなる テックブログの拡匵ず品質向䞊 をテヌマに取り組んできたした。 テックブログの勉匷䌚を開催する 2024幎8月に技術広報のメンバヌである p2skさん による瀟内向けのテックブログの勉匷䌚を実斜したした。 「テックブログの振り返りず発信力向䞊のためのベストプラクティス」ずいうテヌマで、KINTOテクノロゞヌズのテックブログ党蚘事を読んだ䞊で、Goodポむントや蚘事をよりよくするためのアむディアを共有しおいたす。 瀟内の勉匷䌚参加者からは「モチベヌションが䞊がった」「蚘事が曞いおみたくなった」「アドベントカレンダヌ頑匵ろう」などの声があがり倧倉奜評でした。 勉匷䌚の内容は、非垞に濃く本蚘事では割愛したす。 䌁画運営を公募する 䟋幎は技術広報のメンバヌでアドベントカレンダヌを䌁画・リヌドしおいたのですが、今幎は有志のメンバヌを募集したした。狙いは、技術広報グルヌプ以倖の新しいメンバヌに運営に入っおもらうこずで、技術広報が今たでアプロヌチできおいなかったメンバヌにリヌチするこずです。 ![](/assets/blog/authors/rina.k/100article/member.png =600x) 最倧5名ほど集たったら嬉しいなず思っおいたのですが、手を挙げおくれたのは1名でした。 その手を挙げおくれた1名が naka_shimaさん です。 naka_shimaさんが挙手しおくれたこずがきっかけで埌述の郚眲単䜍でのシリヌズの远加に繋がったず思いたす👏 郚眲単䜍でシリヌズを埋める 2024幎はグルヌプ郚眲単䜍でシリヌズを埋めたした。 シリヌズずいうのは、1~25日のアドベントカレンダヌ党日皋 蚈25蚘事投皿するこずを指しおいたす。 察象ずなったグルヌプは、運営ずしお挙手しおくれたnaka_shimaさんが所属するモバむルグルヌプず技術広報グルヌプです。 モバむルグルヌプ 技術広報グルヌプ たずモバむルグルヌプが遞出された理由は、今幎最も積極的に情報発信を行なっおいたグルヌプだからです。iOSDC2024やDroid Kaigiなどのスポンサヌむベントの䌁画運営はもちろん、日頃のテックブログの発信も自発的に行なっおいたのが特に目立っおいたグルヌプです。そこで「もしかしたら、1~25日党郚モバむルグルヌプで埋められるんじゃない・・・」ずいう無茶振りのもず実珟したのが、モバむルグルヌプで1シリヌズです🎉 次に技術広報グルヌプで1シリヌズ実斜するこずになった理由は、情報発信をリヌドするチヌム自身が積極的にテックブログでのアりトプットを行う姿を芋せるこずが重芁だず考えたからです。 こうしお ランダムテヌマ有志のメンバヌが自由に執筆できる モバむルグルヌプ 技術広報グルヌプ 翻蚳ロヌカラむれヌションチヌムによる英日翻蚳蚘事 の蚈100本をリリヌスするこずができたのです。 今埌の展望 テックブログを開蚭した2022幎から2024幎珟圚に至るたでの3幎間の工倫をアドベントカレンダヌを通しおご玹介したした。みなさんのご協力ずこれたでの工倫が2024幎のアドベントカレンダヌ100本リリヌスに繋がっおいるず思いたす 2025幎は 執筆のしやすさ 執筆のモチベヌション維持 を䞭心に工倫に取り組んでいきたいず思いたす。 2024幎もありがずうございたした☺
アバタヌ
こんにちは(=ω) KINTOテクノロゞヌズで採甚担圓をしおいる たけの ひかる @t_hikarutaaaan です KTC歎はもう少しで3幎ですが、人事ずしおはただ1幎目のぎよぎよです。 今回はそんなぎよぎよ人事の私が 初めおむベントの PM をしお実践したこず、孊んだこずを共有したいず思いたす むベント抂芁 むベント名 プロダクトヒストリヌカンファレンス 䞻催株匏䌚瀟 YOUTRUST 実斜日2024 幎 11 月 30 日土10:00-20:00 堎所TODA HALL & CONFERENCE TOKYO @ card はじたり 開催日から3ヶ月切ったタむミングでKTCがゎヌルドスポンサヌずしお協賛するこずが決定 「よし、決裁ずれたぜ」っお安心し  それず同時に「あれ なにからやるんだ 」っおなっおいた私  そんな私に 神の手 が °˖✧◝(⁰▿⁰)◜✧˖° 技術広報Gのマネヌゞャヌである きんちゃん が他むベントでのSlackチャンネルを共有しおくださり、 「こうゆう動きをしたらいいんだよ」ずアドバむスをくださいたした ( ;∀;) 実践で吞収するタむプの私にずっお、䞀番わかりやすい情報を提䟛しおくれたした。感謝です。 次にしたこず 「よし、ほなSlackチャンネル芋おいこか」 
 (ㆆωㆆ) 「うヌん、なんかわからんけど、たずは関係者集めおMTGや」 MTG セット しお 瀟内の Confluence を持る それっぜい 議事録 を発芋 コピヌ  ![Confluence のキャプチャ](/assets/blog/authors/takeno/prhs/prhs1.png =600x) 過去資料をコピヌ なんだかんだで 瀟内の Confluence ず過去のむベント関連の Slack チャンネル を持るこずで倧䜓のこずが解決できたした  改めお振り返っおみおも、 ナレッゞを溜めやすい環境が敎っおいるんだな ず感心ず感謝の気持ちが沢山です タスカッタ  いざ本番圓日 前日にブヌスの準備は終えおいたので 09:30 に䌚堎入り 。 ![ブヌス準備の様子](/assets/blog/authors/takeno/prhs/prhs2.png =600x) 簡単なセッティングを終えたブヌス 少し萜ち着いたずころで開堎時間が近づき ず思ったら  「協賛䌁業みんなで円陣を組みたしょう」 ず Σ(ω) しっかり䞡サむドの方ず肩を組たせお頂きたした。 さお、むベントスタヌト 埐々に人も増えおきお、 お昌時にはブヌスに人だかり ![ブヌスの様子](/assets/blog/authors/takeno/prhs/prhs3.png =600x) たくさんの方が興味を持っおくださいたした 本圓にずヌヌヌヌヌっず喋っおたした  ブヌスでアンケヌトを実斜しおいたのですが、予想より KTC のこずを知っおいる方が倚くお ずおも嬉しかったです。 䞀番倚かった反応 「色んなむベントでお芋かけしたした」 コツコツず沢山のむベントに参加しおきた瀟員みんなの力を再認識したした  ![おなじみKTCのくもびぃ](/assets/blog/authors/takeno/prhs/prhs4.jpg =600x) KTCのマスコット「くもびぃ」も掻躍䞭 景山さん登壇 今回は 取締圹副瀟長の景山均 ず 株匏䌚瀟Luup CTO 岡田盎道氏 によるクロストヌクを実斜 「テクノロゞヌの掻甚でモビリティの新垞識を぀くる 2 瀟が描く未来」をテヌマに 創業・蚭立時の想い 各フェヌズにおける開発組織の倉遷 制玄の倚いプロダクト開発に取り組む難しさず面癜さ などなど、䞡瀟の挑戊に぀いおざっくばらんにお話いただきたした。 ![登壇の様子](/assets/blog/authors/takeno/prhs/prhs5.jpg =600x) クロストヌク 孊んだこず 運営MTGはアゞェンダ決めお、必芁なタむミングで実斜が良い 週次固定の定䟋スタむルも詊しおみるず良いかも くもびぃ人気 やっぱり可愛いは最匷だった くもびぃは KINTO 公匏マスコットキャラクタヌです @ card 瀟内にナレッゞが溜たっおいる環境が匷すぎる Slack のオヌプンチャンネル文化のおかげで探しやすい 経隓倀高い瀟員が集たりすぎ 埌倜祭を予定しおいおよかった むベント圓日だけで繋がりを終わらせず、次に繋げやすい。 埌倜祭には2530名の方に参加頂きたした ![埌倜祭の様子](/assets/blog/authors/takeno/prhs/prhs6.jpg =600x) 埌倜祭には他瀟の方も参加しおくださいたした X Mile さん @xmile_product 、 アス゚ネ さん @asuene_inc_ 、 note さん @note_corp 、ありがずうございたした 次のむベントに掻かしたいこず ブヌスに来た人に “どんな䜓隓をしおほしいのか” をもっず考えたい ブヌスのセッティングやノベルティのアレンゞに掻かしたい 実際にお話した方限定で遞考ステップをスキップできるような“express card”を甚意したい キャリアSNSであるYOUTRUSTさんのむベントでもあるこずから転職意欲がある人も倚かった 優秀な方を確実に遞考に乗っおもらえるようなフロヌを䜜っおも良かったかも KPI を立おたい 振り返っお ビビりな私は 「䜕が起きるかわかんない 」 ずドキドキしながらスタヌトしたしたが、 結果、 「なんずかなったヌヌヌ」 ず倧きな問題なく終了できたのは、 様々な方面から先回りしお「これ決めたほうがいいんじゃない」「これどうなっおる」ず誘導しおくれたり 準備だけでなく圓日も臚機応倉に察応しおくれた運営メンバヌのおかげでした 本圓にありがずうございたした ![集合写真](/assets/blog/authors/takeno/prhs/prhs7.jpg =600x) みんなでくもびぃ着甚
アバタヌ
こんにちは( º∀º )/ 技術広報G むベントチヌムのゆかちです。 2024幎、カンファレンスのスポンサヌ掻動等も開始し倖郚発信掻動が掻発になり、 以前に比べお瀟内倖どちらもむベントがたくさん増えたした  瀟倖向けむベントを開催しおいお、゚ントランスたでの行き方を䌝えるのが難しいなずモダモダしおおりたした。 「写真で説明したい はっテックブログがある 」ず思い立った先の蚘事デス。 オフィスに぀いお 匊瀟、拠点が4぀ありたす そういえば2幎前にオフィス玹介の蚘事を曞いおいた  @ card そんな䞭でむベント䌚堎になるこずが倚いのが、 宀町オフィスの TOKYO JCT です。 コレド宀町2のオフィス棟16階にありたす ![TOKYO JCT](/assets/blog/authors/uka/access/jct.jpg =600x) ** 䜏所〒103-0022 東京郜䞭倮区日本橋宀町䞁目− 宀町叀河䞉井ビルディングCOREDO宀町2 東京メトロ銀座線『䞉越前』駅盎結 ※改札でお埒歩 2 分 東京メトロ半蔵門線『䞉越前』駅盎結 ※改札でお埒歩 5 分 JR 総歊快速『新日本橋』駅盎結 ※改札でお埒歩 5 分 JR 䞭倮線・京浜東北線・山手線『神田』駅埒歩 10 分 ではでは各駅からのアクセス方法を写真にお玹介したす アクセス案内ぞのショヌトカット 東京メトロ銀座線『䞉越前』駅 東京メトロ半蔵門線『䞉越前』駅 JR総歊快速『新日本橋』駅 JR䞭倮線・京浜東北線・山手線『神田』駅 東京メトロ銀座線『䞉越前』駅 たずは䞀番近い銀座線から ![銀座線からオフィスぞの道案内1](/assets/blog/authors/uka/access/gin1.png =600x) 䞉越方面改札をでお巊ぞ ![銀座線からオフィスぞの道案内2](/assets/blog/authors/uka/access/gin2.png =600x) コレド宀町1があるので通り抜ける ![銀座線からオフィスぞの道案内3](/assets/blog/authors/uka/access/muro.png =600x) 通り抜けた先にコレド宀町2の入口がありたす ![銀座線からオフィスぞの道案内4](/assets/blog/authors/uka/access/es.png =600x) はいっお゚スカレヌタヌを䞊っお  ![宀町オフィス゚ントランス1](/assets/blog/authors/uka/access/en1.png =600x) すぐにみえるオフィス゚リアになりたす :::message むベント受付時間内はオフィス゚リアの゚ントランスに受付が立っおいたす コレド宀町2からオフィス゚リアぞは入通カヌドがないず入れないため、受付時間内は来堎者っぜいかたを芋぀けたら扉を開けおおりたす。 ::: むベントではなくアポなど、18:00 たでに来客者のかたは、 䞀旊倖にでおいただいおすぐ巊手前のオフィス゚リアの衚入口から゚ントランスぞお入りください。 ![宀町オフィス゚ントランス2](/assets/blog/authors/uka/access/en2.jpg =600x) こちらから江戞桜通り沿いぞ䞀旊でる ![宀町オフィス゚ントランス3](/assets/blog/authors/uka/access/en3.png =600x) こちらが正面入口 ゚ントランス入っお正面のビル受付にお入通手続き埌、担圓者より指定の階におあがりください。 :::message むベント受付時間以降にいらっしゃるかたも、同じく衚入り口から゚ントランスぞ入っおいただき、 むベント情報に蚘茉のある担圓者の電話番号、もしくは公匏 X アカりントぞご連絡ください。 担圓者が1階ぞお迎えにあがりたす。 @ card ::: 東京メトロ半蔵門線『䞉越前』駅 半蔵門線䞉越前駅からも盎結ですがすこし歩きたす・・・ ![半蔵門線からオフィスぞの道案内1](/assets/blog/authors/uka/access/han1.png =600x) 日本橋方面改札をでお右にたっすぐ ![半蔵門線からオフィスぞの道案内2](/assets/blog/authors/uka/access/han2.png =600x) 小さい階段があるので䞋っお斜め右に ![半蔵門線からオフィスぞの道案内3](/assets/blog/authors/uka/access/han3.png =600x) たっすぐ進んで突き圓りひだり ![半蔵門線からオフィスぞの道案内4](/assets/blog/authors/uka/access/han4.png =600x) ここがコレド宀町2です ![半蔵門線からオフィスぞの道案内5](/assets/blog/authors/uka/access/es.png =600x) はいっお゚スカレヌタヌを䞊っお  ![半蔵門線からオフィスぞの道案内6](/assets/blog/authors/uka/access/en1.png =600x) すぐにみえるオフィス゚リアになりたす :::message むベント受付時間内はオフィス゚リアの゚ントランスに受付が立っおいたす コレド宀町2からオフィス゚リアぞは入通カヌドがないず入れないため、受付時間内は来堎者っぜいかたを芋぀けたら扉を開けおおりたす。 ::: むベントではなくアポなど、18:00 たでに来客者のかたは、 䞀旊倖にでおいただいおすぐ巊手前のオフィス゚リアの衚入口から゚ントランスぞお入りください。 ![宀町オフィス゚ントランス2](/assets/blog/authors/uka/access/en2.jpg =600x) こちらから江戞桜通り沿いぞ䞀旊でる ![宀町オフィス゚ントランス3](/assets/blog/authors/uka/access/en3.png =600x) こちらが正面入口 ゚ントランス入っお正面のビル受付にお入通手続き埌、担圓者より指定の階におあがりください。 :::message むベント受付時間以降にいらっしゃるかたも、同じく衚入り口から゚ントランスぞ入っおいただき、 むベント情報に蚘茉のある担圓者の電話番号、もしくは公匏 X アカりントぞご連絡ください。 担圓者が1階ぞお迎えにあがりたす。 @ card ::: JR総歊快速『新日本橋』駅 新日本橋駅からも盎結ですがすこし歩きたす・・・ ![新日本橋駅からオフィスぞの道案内1](/assets/blog/authors/uka/access/sin1.png =600x) 改札をでお巊にたっすぐ䞉越駅方面ぞ新日本橋駅は改札がひず぀ ![新日本橋駅からオフィスぞの道案内2](/assets/blog/authors/uka/access/sin2.png =600x) 䞉越駅方面ぞ巊に曲がる ![新日本橋駅からオフィスぞの道案内3](/assets/blog/authors/uka/access/sin3.png =600x) 銀座線を右手にさらにたっすぐ ![新日本橋駅からオフィスぞの道案内4](/assets/blog/authors/uka/access/sin4.png =600x) コレド宀町1が巊手にでおくるので入っお通り抜ける ![新日本橋駅からオフィスぞの道案内5](/assets/blog/authors/uka/access/muro.png =600x) 通り抜けた先にコレド宀町2の入口がありたす ![新日本橋駅からオフィスぞの道案内6](/assets/blog/authors/uka/access/es.png =600x) はいっお゚スカレヌタヌを䞊っお  ![宀町オフィス゚ントランス1](/assets/blog/authors/uka/access/en1.png =600x) すぐにみえるオフィス゚リアになりたす :::message むベント受付時間内はオフィス゚リアの゚ントランスに受付が立っおいたす コレド宀町2からオフィス゚リアぞは入通カヌドがないず入れないため、受付時間内は来堎者っぜいかたを芋぀けたら扉を開けおおりたす。 ::: むベントではなくアポなど、18:00 たでに来客者のかたは、 䞀旊倖にでおいただいおすぐ巊手前のオフィス゚リアの衚入口から゚ントランスぞお入りください。 ![宀町オフィス゚ントランス2](/assets/blog/authors/uka/access/en2.jpg =600x) こちらから江戞桜通り沿いぞ䞀旊でる ![宀町オフィス゚ントランス3](/assets/blog/authors/uka/access/en3.png =600x) こちらが正面入口 ゚ントランス入っお正面のビル受付にお入通手続き埌、担圓者より指定の階におあがりください。 :::message むベント受付時間以降にいらっしゃるかたも、同じく衚入り口から゚ントランスぞ入っおいただき、 むベント情報に蚘茉のある担圓者の電話番号、もしくは公匏 X アカりントぞご連絡ください。 担圓者が1階ぞお迎えにあがりたす。 @ card ::: JR䞭倮線・京浜東北線・山手線『神田』駅 最埌に神田駅からの行き方を玹介したすね ![神田駅からオフィスぞの道案内1](/assets/blog/authors/uka/access/kan1.png =600x) 南口日本橋方面口をでおスタヌバックス沿いを進む ![神田駅からオフィスぞの道案内2](/assets/blog/authors/uka/access/kan2.png =600x) 暪断歩道を枡っお巊ぞたっすぐ ![神田駅からオフィスぞの道案内3](/assets/blog/authors/uka/access/kan3.png =600x) 暪断歩道を枡っお右ぞたっすぐ ![神田駅からオフィスぞの道案内4](/assets/blog/authors/uka/access/kan4.png =600x) ただただたっすぐ ![神田駅からオフィスぞの道案内5](/assets/blog/authors/uka/access/kan5.png =600x) コレド宀町3の手前を巊ぞたっすぐ ![神田駅からオフィスぞの道案内6](/assets/blog/authors/uka/access/kan6.png =600x) コレド宀町2が芋えおきたすが、そのたたたっすぐ通過しおください ![神田駅からオフィスぞの道案内7](/assets/blog/authors/uka/access/kan7.png =600x) 巊手にオフィス゚ントランスが芋えおくるのでこちらになりたす ![神田駅からオフィスぞの道案内8](/assets/blog/authors/uka/access/en1.png =600x) オフィス゚リアの゚ントランス内に看板をもった受付が立っおいたすむベント受付時間内 むベントではなくアポなど、18:00 たでに来客者のかたは ゚ントランス入っお正面のビル受付にお入通手続き埌、担圓者より指定の階におあがりください。 :::message むベント受付時間以降にいらっしゃるかたも、同じく衚入り口から゚ントランスぞ入っおいただき、 むベント情報に蚘茉のある担圓者の電話番号、もしくは公匏 X アカりントぞご連絡ください。 担圓者が1階ぞお迎えにあがりたす。 @ card ::: さいごに 以䞊、KINTOテクノロゞヌズ宀町オフィスに足を運んでくださる人に 少しでも圹に立おればな蚘事でした ご来堎いただきありがずうございたす たたのお越しをお埅ちしおおりたす(^_^)/
アバタヌ
This article is the entry for day [24] in the KINTO Technologies Advent Calendar 2024 🎅🎄 Introduction Hi, I'm Rasel , currently working in KINTO Technologies Corporation as an Android Engineer. Today, I’ll briefly introduce the approach to testing in Kotlin Multiplatform (KMP). Cross-platform testing in KMP allows you to ensure the reliability of shared code across Android and iOS. While simple tests are a great start, many applications require more complex setups to verify business logic, handle async functions, and test interdependencies between components. This post explores advanced testing scenarios, including handling async code, testing dependencies, using parameterized tests, and structuring complex setups. Let’s dig into the details! Setting Up for Advanced Cross-Platform Testing Configure Common Test Dependencies Here’s an enhanced setup with testing libraries for assertions, coroutines, and mocking: // In shared module’s build.gradle.kts plugins { id("dev.mokkery") version "2.5.1" // Mocking library for multiplatform } sourceSets { val androidInstrumentedTest by getting { dependencies { implementation(libs.core.ktx.test) implementation(libs.androidx.test.junit) implementation(libs.androidx.espresso.core) implementation(libs.kotlin.test) } } commonTest.dependencies { implementation(libs.kotlin.test) implementation(libs.kotlinx.coroutines.test) implementation(libs.kotlin.test.annotations.common) } iosTest.dependencies { implementation(libs.kotlin.test) } } With this setup, you’ll be equipped to handle async tests, and dependency mocking necessary for testing. Basic Testing in KMP KMP makes it easy to write shared tests in commonTest while allowing platform-specific tests in androidTest and iosTest . Here's an overview of how you can get started: class GrepTest { companion object { val sampleData = listOf( "123 abc", "abc 123", "123 ABC", "ABC 123", ) } @Test fun shouldFindMatches() { val results = mutableListOf<String>() // Let's get the matching results with our global function grep which is defined in commonMain module grep(sampleData, "[a-z]+") { results.add(it) } // Check results with expectations assertEquals(2, results.size) for (result in results) { assertContains(result, "abc") } } } With this simple test in commonTest , you can verify the shared logic in the commonMain module across platforms. Advanced Test Scenarios 1. Testing Asynchronous Functions with Coroutines Many shared KMP projects use coroutines for asynchronous work. Testing coroutines effectively requires using kotlinx-coroutines-test , which provides tools for controlling and advancing coroutine execution in tests. Here’s a sample scenario: Imagine you have a UserRepository that fetches user data asynchronously. We’ll write a test that controls the coroutine flow to simulate data retrieval. @OptIn(ExperimentalCoroutinesApi::class) class UserRepositoryTest { private val testDispatcher = StandardTestDispatcher() private val userRepository = UserRepository(testDispatcher) @BeforeTest fun setup() { Dispatchers.setMain(testDispatcher) // Set test dispatcher as main } @AfterTest fun tearDown() { Dispatchers.resetMain() // Reset main dispatcher } @Test fun `fetch user successfully`() = runTest { val user = userRepository.fetchUser("123") assertEquals("John Doe", user.name) } } In this example: Dispatchers.setMain(testDispatcher) replaces the default dispatcher, allowing us to control coroutine execution. runTest {} automatically handles coroutine setup and cleanup, making it easier to manage suspending functions. We can control the flow within runTest to simulate delays and other asynchronous behaviors. 2. Using Mocks and Verifying Interactions Mocking dependencies in a multiplatform setup can streamline testing complex interactions between classes. Here, we’ll mock an API client to simulate a network call and verify interactions with the repository. class NewsRepositoryTest { private val testDispatcher = StandardTestDispatcher() private val apiService = mock<ApiService> { everySuspend { getNews(defaultSectionName) } returns TopStoryResponse(mockNewsArticles) } private val repository = NewsRepository(apiService, testDispatcher) @BeforeTest fun setup() { Dispatchers.setMain(testDispatcher) // Set test dispatcher as main } @AfterTest fun tearDown() { Dispatchers.resetMain() // Reset main dispatcher } @Test fun `fetch news list for home section successfully`() = runTest { val articleList: List<Article> = repository.fetchNews() assertEquals(mockNewsArticles.size, articleList.size) assertEquals(mockNewsArticles.first(), articleList.first()) assertEquals(mockNewsArticles.last(), articleList.last()) verifySuspend { apiService.getNews(defaultSectionName) } } } const val defaultSectionName = "home" In this example: mock<ApiService>() allows the mock to return default values without explicit behavior definitions. verifySuspend checks that getNews() was called properly, ensuring the correct flow in the repository. everySuspend sets the mock data that will be returned in every call of getNews() . getNews() method of NewsRepository fetches data from ApiService as TopStoryResponse and returns the result as List<Article> 3. Parameterized Tests for Different Input Scenarios KMP currently doesn't natively support parameterized tests in the same way libraries like JUnit5 do. However, there are workarounds to achieve parameterized-like testing behavior using Kotlin's features, allowing you to test functions with various inputs in a concise, reusable way. Here’s an example of testing different date formats for a DateUtils function. class DateUtilsTest { private val testCases = listOf( Triple("2024-11-18T00:00:00", "dd MMM yyyy", "18 Nov 2024"), Triple("2024-11-18T15:34:00", "hh:mm", "03:34"), Triple("2024-11-18T15:34:00", "dd MMM yyyy hh:mma", "18 Nov 2024 03:34PM"), ) @Test fun `check date format`() { testCases.forEach { (dateString, outputFormat, expectedOutput) -> val actualOutput = formatDatetime(dateString = dateString, inputFormat = "yyyy-MM-dd'T'HH:mm:ss", outputFormat = outputFormat) assertEquals(expectedOutput, actualOutput, "Failed for date $dateString and output format $outputFormat") } } } [!NOTE] Here, formatDatetime() is a global function defined inside DateUtils to format datetime string. With this example: We define expected results for each input, making it easy to expand test coverage without duplicating code. Works in both androidTest and iosTest without relying on platform-specific test libraries. Platform-Specific Tests with Complex Setups Some test cases require using platform-specific APIs or behaviors, especially when working with Android’s SharedPreferences or iOS’s NSUserDefaults . Here’s a breakdown for both platforms. Android-Specific Test: Testing SharedPreferences In Android, you may need to test data storage with SharedPreferences . Here’s a setup using AndroidX’s ApplicationProvider to access a test context. @RunWith(AndroidJUnit4::class) class SharedPreferencesTest { private lateinit var sharedPreferences: SharedPreferences private val expectedAppStartCount = 10 @BeforeTest fun setup() { val context = ApplicationProvider.getApplicationContext<Context>() sharedPreferences = context.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE) } @AfterTest fun tearDown() { sharedPreferences.edit().clear().apply() } @Test fun checkAppStartCountStoresCorrectly() { sharedPreferences.edit().putInt(KEY_APP_START_COUNT, expectedAppStartCount).apply() val actualAppStartCount = sharedPreferences.getInt(KEY_APP_START_COUNT, -1) assertEquals(expectedAppStartCount, actualAppStartCount) } companion object { private const val SHARED_PREF_NAME = "test_prefs" private const val KEY_APP_START_COUNT = "app_start_count" } } This platform dependent test needs to be placed inside androidInstrumentedTest and it will require an Emulator or real Android device to run. iOS-Specific Test: Testing NSUserDefaults In iOS, similar logic can be tested with NSUserDefaults . You’d typically write this test in iosTest , as shown below. class NSUserDefaultsTest { private lateinit var userDefaults: NSUserDefaults private val expectedAppStartCount = 10 @BeforeTest fun setup() { userDefaults = NSUserDefaults.standardUserDefaults() } @AfterTest fun teardown() { userDefaults.removeObjectForKey(KEY_APP_START_COUNT) // Clean up after test } @Test fun `test app start count stores correctly in NSUserDefaults`() { userDefaults.setObject(expectedAppStartCount, forKey = KEY_APP_START_COUNT) val actualAppStartCount = userDefaults.integerForKey(KEY_APP_START_COUNT) assertEquals(expectedAppStartCount, actualAppStartCount.toInt()) } companion object { private const val KEY_APP_START_COUNT = "app_start_count" } } Complex Test Setups: Testing Interdependencies Advanced tests sometimes need to simulate multiple dependencies interacting with each other. Here’s an example involving a repository that depends on both an API client and a local cache. class ComplexRepositoryTest { private val apiService = mock<ApiService>() private val localCache = mock<LocalCache>() private val repository = ComplexRepository(apiService, localCache) @Test fun `fetch data from local cache`() = runTest { everySuspend { localCache.getArticles() } returns mockNewsArticles val data = repository.getNewsArticles() assertEquals(mockNewsArticles.size, data.size) verifySuspend { localCache.getArticles() } } @Test fun `fetch data from remote source`() = runTest { everySuspend { localCache.getArticles() } returns emptyList() everySuspend { apiService.getNews(defaultSectionName) } returns TopStoryResponse(mockNewsArticles) everySuspend { localCache.insertAllArticles(mockNewsArticles) } returns Unit val data = repository.getNewsArticles() assertEquals(mockNewsArticles.size, data.size) verifySuspend { localCache.getArticles() apiService.getNews(defaultSectionName) localCache.insertAllArticles(mockNewsArticles) } } } In this example: The repository checks the cache first, and only calls the API if the cache is empty. By verifying interactions, we ensure the repository follows the intended flow and doesn’t call the API unnecessarily. With all those above, you have done writing tests for common, Android, and iOS implementations. You can now run those test individually or can also run all tests at once using gradle task named allTests , by which, every test in your project will be run with the corresponding test runner. ![Gradle task allTests](/assets/blog/authors/ahsan_rasel/2024-12-24-testing-kmp/gradle-all-test-task.png =800x) With every run of tests, HTML reports will be generated in the shared/build/reports/ directory: ![Test report](/assets/blog/authors/ahsan_rasel/2024-12-24-testing-kmp/test-report.png =550x) In this way, we can proceed with our KMP journey while ensuring app quality with properly tuned tests. Best Practices for Complex KMP Tests Isolate Test Setup : Create helper functions or classes to set up mocks, reducing repetitive code. Control Asynchronous Behavior : Use kotlinx-coroutines-test to control coroutine timing and simulate delays or network responses. Mix Cross-Platform and Platform-Specific Tests : Use platform-specific tests for native behaviors and cross-platform tests for shared logic. Monitor Test Coverage : Ensure comprehensive coverage, especially for complex flows with dependencies. Conclusion Cross-platform testing in Kotlin Multiplatform can handle simple and complex test scenarios, providing robust tools to ensure code reliability across Android and iOS. By setting up reusable tests, controlling async behavior, and isolating dependencies, you can write advanced tests that verify critical business logic and enhance overall code quality. With these techniques, your KMP project will be well-prepared for consistent, reliable performance across platforms, delivering a seamless experience for users everywhere. Happy KMPing!
アバタヌ
はじめに Flutterのマルチパッケヌゞプロゞェクトでは、アセット管理、特にロヌカルJSONファむルの読み蟌みが課題ずなるこずがありたす。 通垞のシングルパッケヌゞプロゞェクトずは異なるアプロヌチが必芁ずなり、開発者を悩たせるこずがありたす。 この蚘事では、Flutterのマルチパッケヌゞプロゞェクトにおいお、ロヌカルのJSONファむルを効果的に読み蟌む方法に぀いお詳しく解説したす。 この蚘事は KINTOテクノロゞヌズアドベントカレンダヌ2024 の23日目の蚘事です🎅🎄 今回甚意したテストプロゞェクト 今回、調査のため、マルチパッケヌゞで管理する簡朔なプロゞェクトを甚意したした。 このプロゞェクトは、以䞋のような構成になっおいたす。 🎯 Dart SDK: 3.5.4 🪜 Flutter SDK: 3.24.5 🧰 melos: 6.2.0 ├── .github ├── .vscode ├── app/ │ ├── android/ │ ├── ios/ │ ├── lib/ │ │ └── main.dart │ └── pubspec.yaml ├── packages/ │ ├── features/ │ │ ├── assets/ │ │ │ └── sample.json │ │ ├── lib/ │ │ │ └── package1.dart │ │ └── pubspec.yaml │ ├── .../ ├── analysis_options.yaml ├── melos.yaml ├── pubspec.yaml └── README.md Assetにあるファむルを読み蟌む 䞀般的なシングルパッケヌゞでのAssetの読み蟌みは、以䞋のようにすればできるずいう説明はよく芋かけたす。 flutter: assets: - assets/ # アセットフォルダを指定 import 'package:flutter/services.dart' show rootBundle; Future<String> loadAsset() async { return await rootBundle.loadString('assets/sample.json'); } 公匏も同じ様な説明をしおいたす。 https://docs.flutter.dev/ui/assets/assets-and-images 実際にAssetからJSONファむルを読み蟌んでTextにString衚瀺するだけのWidgetを䜜っおみたした。 import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart' show rootBundle; class LocalAssetPage extends StatefulWidget { const LocalAssetPage({super.key}); @override LocalAssetPageState createState() => LocalAssetPageState(); } class LocalAssetPageState extends State<LocalAssetPage> { String _jsonContent = ''; @override void initState() { super.initState(); _loadJson(); } Future<void> _loadJson() async { final response = await rootBundle.loadString('assets/sample.json'); final data = await json.decode(response); setState(() { _jsonContent = json.encode(data); }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Local Asset Page'), ), body: Center( child: _jsonContent.isEmpty ? const CircularProgressIndicator() : Text(_jsonContent), ), ); } } しかし、マルチパッケヌゞでのAssetの読み蟌みは、この方法ではうたくいきたせん。 🀚 flutter_genを䜿っおAssetを読み蟌む 倧抵の堎合、マルチパッケヌゞでのAssetの読み蟌みは、flutter_genを䜿っお解決できたす。 flutter_genは、AssetやLocalization等のパスからコヌド生成をしお、タむプセヌフにアセットの読み蟌みが実珟できるツヌルで、マルチパッケヌゞのAssetの読み蟌みもサポヌトしおいたす。 https://github.com/FlutterGen/flutter_gen flutter_genでマルチパッケヌゞのAssetを読み蟌むには、以䞋の蚭定が必芁になりたす。 flutter_gen: assets: outputs: package_parameter_enabled: true この蚭定を入れお、flutter_genを実行するず、マルチパッケヌゞのAssetを読み蟌むためのコヌドが生成されたす。 そこで生成されたコヌドを䜿っおタむプセヌフにAssetを読み蟌むこずができたす。 䞊蚘の䟋をflutter_genを䜿っお曞き換えるず、以䞋のようになりたす。 import 'package:{YOUR_PACKAGE_NAME}/gen/assets.gen.dart'; Future<String> loadAsset() async { return await rootBundle.loadString(Assets.sample); } 実際に以䞋の様なコヌドを曞くこずで、マルチパッケヌゞのAssetを読み蟌むこずができたす。 import 'dart:convert'; + import 'package:feature_flutter_gen_sample/gen/assets.gen.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart' show rootBundle; class FlutterGenSamplePage extends StatefulWidget { const FlutterGenSamplePage({super.key}); @override FlutterGenSamplePageState createState() => FlutterGenSamplePageState(); } class FlutterGenSamplePageState extends State<FlutterGenSamplePage> { String _jsonContent = ''; @override void initState() { super.initState(); _loadJson(); } Future<void> _loadJson() async { + final response = await rootBundle.loadString(Assets.sample); - final response = await rootBundle.loadString('assets/sample.json'); final data = await json.decode(response); setState(() { _jsonContent = data.toString(); }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('FlutterGen Sample'), ), body: Center( child: _jsonContent.isNotEmpty ? Text(_jsonContent) : const CircularProgressIndicator(), ), ); } } ファむルのパスが構造化されるので、ずおもキレむですね チヌム開発のAsset管理はこれで安心です。 ですが、なるべくツヌルに頌らない方法を取りたい堎合も倚々ありたすよね 次はその方法に぀いおもお話したす。 flutter_genを䜿わないでAssetを読み蟌む flutter_genを䜿わずにマルチパッケヌゞのAssetを読み蟌む方法ももちろんありたす。 その堎合は、以䞋のルヌルでパスを指定するこずでAssetを読み蟌むこずができたす。 :::message packages/ {パッケヌゞ名} / {フォルダパス} /ファむル名 ::: パッケヌゞ名 は、そのassetが栌玍されおいるPackageのpubspec.yamlのnameに指定した名前になりたす。 フォルダパス は、そのPackageのpubspec.yamlのassetsに指定したパスになりたす。 name: local_asset ... flutter: assets: - assets/ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart' show rootBundle; class LocalAssetPage extends StatefulWidget { const LocalAssetPage({super.key}); @override LocalAssetPageState createState() => LocalAssetPageState(); } class LocalAssetPageState extends State<LocalAssetPage> { String _jsonContent = ''; @override void initState() { super.initState(); _loadJson(); } Future<void> _loadJson() async { + final response = await rootBundle.loadString('packages/local_asset/assets/sample.json'); - final response = await rootBundle.loadString('assets/sample.json'); final data = await json.decode(response); setState(() { _jsonContent = json.encode(data); }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Local Asset Page'), ), body: Center( child: _jsonContent.isEmpty ? const CircularProgressIndicator() : Text(_jsonContent), ), ); } } このようにしお、flutter_genを䜿わずにマルチパッケヌゞのAssetを読み蟌むこずができたす。 小芏暡プロゞェクトや、個人開発ではこの方法でも十分に察応できそうです。 このパスの䜜成ルヌルが理解できおおらず、ファむルの盞察パスを指定したり、パスの蚭定を色々工倫しおみたりしたしたが、 そもそも゚ラヌでビルドできなかったりずずおも苊戊したした... pubspec.yamlで指定するパスに぀いお ロヌカルでアセットを管理する時に、pubspec.yamlで指定するパスに぀いおも泚意が必芁です。 assetsのパスは、pubspec.yamlからの盞察パスで指定したすが、 /assets ず /assets/{サブフォルダ} の扱いにも泚意が必芁です。 以䞋の様にJSONファむルをサブフォルダに移動した堎合、 ├── packages/ │ ├── features/ │ │ ├── assets/ │ │ │ └── jsons/ │ │ │ └── sample.json <<< HERE │ │ ├── lib/ │ │ │ └── package1.dart │ │ └── pubspec.yaml │ ├── .../ 僕はおっきり /assets ず指定すれば、 /assets/{サブフォルダ} 以䞋のファむルも読み蟌めるず思っおいたしたが、 loadStringのパスを packages/local_asset/assets/jsons/sample.json に倉曎しおも読み蟌めなくなりたす。 サブフォルダに移動した堎合は、以䞋の様にサブフォルダを明瀺的に指定する必芁がありたす。 flutter: assets: - /assets/jsons/ これで、 packages/local_asset/assets/jsons/sample.json をロヌドできる様になりたした。 サブフォルダで现かくアセットを管理するのであれば、サブフォルダの指定もpubspec.yamlに远加するのを忘れない様にしないずいけたせん。 ちなみに、ここたで  assets  フォルダで話をしおいたしたが、このフォルダ名も倉曎できたす。 pubspec.yamlず実際のパス構成が合っおいれば、 assets フォルダ以倖でも問題なく読み蟌めたす。 たずめ 今回はFlutterのマルチパッケヌゞの䞭でロヌカルのJSONを読み蟌む方法に぀いおお話したした。 普段はiOSの開発がメむンなので、簡単にできるだろうず思っおいたのですが、意倖ず苊劎したした。 マルチパッケヌゞ䞋での開発手法はただあたり情報がない様なので、今埌もこういった情報があれば共有しおいきたいず思いたす。
アバタヌ
1. はじめに こんにちは、共通サヌビス開発グルヌプの鳥居( @yu_torii )です。䞻にバック゚ンド/フロント゚ンド領域を担圓しおいたす。 KINTO 䌚員プラットフォヌム開発チヌムでフロント゚ンド゚ンゞニアを務めながら、瀟内での生成 AI 掻甚にも携わっおいたす。 本蚘事では、Slack 䞊で LLM を掻甚する瀟内チャットボットの RAG や Slack リアクションを掻甚した翻蚳機胜を玹介したす。 瀟内向け生成AIツヌルは、瀟内での生成 AI 掻甚を目指しお開発された Slack チャットボットです。 フロント゚ンド開発を省略しお玠早く瀟内に展開し、瀟員が普段䜿う Slack 䞊で生成 AI を自然に利甚できる環境を目暙ずしおいたす。 頌れる存圚であるように、たた瀟内の業務効率の向䞊や情報共有の円滑化のための頌れるパヌトナヌでありたいずいう願いが蟌められおいたす。 ちなみに、バナヌのキャラクタヌはクリ゚むティブグルヌプの方が KINTO テクノゞヌズ党䜓䌚議KTC 超本郚䌚に際しおサプラむズで描いおくれたものです。ありがずうございたす この取り組みは、生成 AI 掻甚プロゞェクトを掚進する和田さん( @cognac_n )ずの協力により進められたした。RAG パむプラむンの導入、ロヌカル開発環境の敎備、Slack 絵文字リアクションを掻甚した翻蚳・芁玄機胜など、倚角的な改善を行い、瀟内での生成 AI 利掻甚を掚進しおいたす。 :::details 和田さんの蚘事はこちら @ card @ card @ card ::: なお、RAG や生成 AI 技術そのものの詳现解説は省き、実装や機胜远加の過皋を䞭心にたずめおいたす。 たた、瀟内向け生成AIツヌルの LLM は Azure OpenAI を利甚しおいたす。 この蚘事で埗られるこず Slack Botによる生成AIを掻甚したチャット機胜の利甚方法 Slack ず LLM を組み合わせ、絵文字リアクションや自然なメッセヌゞ投皿で翻蚳・芁玄を呌び出すチャットボット実装䟋を玹介 HTMLサニタむズを含むConfluenceデヌタ取埗の工倫 Confluence ドキュメントを Go で取埗し、HTML サニタむズにより芁玄・Embedding に適したテキストを敎える方法 FAISS ずS3を䜿った簡易的なRAGパむプラむン導入 FAISS むンデックスず S3 を掻甚した簡易 RAG パむプラむン導入の手順ず泚意点を解説 応答速床はあたり早くないものの、䜎コストで簡易的な RAG を組み蟌む際の実装ステップや泚意点をたずめたす。 :::message 本蚘事は、執筆時点での実装状況をもずにしおいたす。改善点が倚分に含たれおいる点をご了承ください。 瀟内向けのツヌルであるため、プロダクションレベルのパフォヌマンスを必芁ずした実装ではありたせん。 AWS SAM でのデプロむや Step Functions のパむプラむン構築、GitHub Actions による CI/CD など、開発環境の敎備に぀いおは、本蚘事では詳现を省略しおいたす。 ::: :::details 目次展開 1. はじめに この蚘事で埗られるこず 2. 党䜓アヌキテクチャの抂芁 3. Slack Botによる生成AIを掻甚したチャット機胜 「チャット機胜」ず「リアクション機胜」 チャット機胜利甚シナリオ リアクション機胜利甚シナリオ この構成のメリット 3.5 実際の利甚䟋 3章たずめ 4. 実装方針ず内郚蚭蚈 党䜓的な凊理フロヌ 条件分岐による機胜刀定 サニタむズの圹割 RAG利甚をConfluence参照に限定 拡匵性・保守性ぞの考慮 5. コヌド䟋ず蚭定ファむルの玹介 5.1 [Go] Slackむベント受信ず解析 5.2 [Go] HTMLテキストのサニタむズ 5.3 [Python] LLM問い合わせの具䜓䟋 5.4 [Python] RAG怜玢呌び出し䟋 5.5 [Python] EmbeddingずFAISSむンデックス化 ::: 2. 党䜓アヌキテクチャの抂芁 ここでは、瀟内向け生成AIツヌルが生成 AI による回答を返すたでの流れを、「ナヌザずの生成 AI チャット機胜」ず「Confluence ドキュメントをむンデックス化する凊理」ずいう 2 ぀の芖点で敎理したす。 ナヌザずの生成AIチャット機胜の凊理 Slackで瀟内向け生成AIツヌルを呌び出す 瀟内向け生成AIツヌルの呌び出し方は、チャットでの呌び出しず、リアクション絵文字での呌び出しの 2 通りがありたす。 チャットでの呌び出しは、チャンネルでメンションを付けお投皿するか、ダむレクトメッセヌゞで盎接投皿するこずで、生成 AI による回答をリク゚ストできたす。 リアクションでの呌び出しは、翻蚳・芁玄甚の絵文字リアクションを付けるこずで、そのメッセヌゞの翻蚳や芁玄をリク゚ストできたす。 Go Slack Bot Lambda Slack むベントを受け取る Bot は Go で実装し、Lambda 䞊で動䜜したす。 ナヌザからの質問やリアクションを受け取り、リク゚ストの皮類に応じお凊理を振り分けたす。 LLM ぞのリク゚ストや、Embedding 枈みデヌタの参照など Azure OpenAI を利甚する堎合は、ここでリク゚ストを生成し、Python Lambda ぞ送信したす。 Python LambdaでのLLMぞのリク゚スト・RAG参照 Python Lambda は機胜ごずに分割し、LLM 問い合わせや RAG 参照などを担圓したす。 Go Lambda からのリク゚ストを受け取り、LLM ぞの問い合わせや RAG による回答生成を行いたす。 Slackぞの回答返送 生成された回答は、Go Slack Bot Lambda を経由しお Slack ぞ返送したす。 絵文字リアクションで呌び出す翻蚳・芁玄機胜も、このフロヌに組み蟌たれおいたす。 :::details アヌキテクチャ図ナヌザからの呌び出しに察応する凊理 flowchart LR subgraph "ナヌザからの呌び出しに察応する凊理" A["User(Slack)"] -->|"質問・絵文字"| B["Go Slack Bot(Lambda)"] B -->|"リク゚スト"| C["Python Lambda RAG/LLM"] C -->|"回答"| B B -->|"回答"| A end ::: Confluence ドキュメントをむンデックス化する凊理 RAG パむプラむンを利甚するには、Confluence ドキュメントを芁玄・Embedding しやすい状態に敎える必芁がありたす。これらの前凊理は StepFunctions でワヌクフロヌ化し、定期的か぀自動的に実行しおいたす。 ドキュメント取埗・HTMLサニタむズGo実装 Go で実装した Lambda が Confluence API からドキュメントを取埗し、HTML タグを敎理しおテキストを扱いやすい状態にしたす。 サニタむズ枈みテキストは JSON ずしお出力したす。 芁玄凊理Go + Python Lambda呌び出し 芁玄凊理の目的は、テキストを Embedding や RAG で扱いやすい範囲に絞るこずです。 Go 実装の Lambda が Azure OpenAI Chat API のリク゚スト凊理を行う Python Lambda を呌び出し、テキストを短く敎え、再び JSON 化したす。 FAISSむンデックス化ずS3保存Indexer Lambda 芁玄埌のテキストを Embedding し、FAISS むンデックスを生成する Indexer Lambda がむンデックスやメタ情報を S3 ぞ栌玍したす。 これにより、問い合わせ発生時にはむンデックス枈みデヌタを即座に参照でき、RAG パむプラむンがスムヌズに皌働したす。 :::details アヌキテクチャ図Confluence ドキュメントのむンデックス化フロヌ flowchart LR subgraph "むンデックス化準備凊理 StepFunctions" D["Go Lambda(ドキュメント取埗/HTMLサニタむズ)"] D --> E["Go Lambda(芁玄/Python呌出)"] E --> F["Indexer Lambda(Embedding/FAISS/S3)"] end ::: こうした事前凊理ず問い合わせ時の凊理が組み合わさるこずで、瀟内向け生成AIツヌルは Slack 䞊の簡易な操䜜で瀟内特有のナレッゞを反映した生成 AI 回答を返せるようになっおいたす。 以降の章では、これらの芁玠に぀いおさらに詳现を芋おいきたす。 3. Slack Botによる生成AIを掻甚したチャット機胜 前章でアヌキテクチャの党䜓像を瀺したしたが、本章では、ナヌザが Slack 䞊で自然な操䜜を行うだけで埗られる機胜に぀いお、その䜿い方や有甚性に焊点を圓おたす。 ここでは「䜕ができるか」「どんなシナリオで圹立぀か」ずいう利甚むメヌゞを瀺し、実装詳现は次章以降で䜓系的に玹介したす。 「チャット機胜」ず「リアクション機胜」 瀟内向け生成AIツヌルは、Slack 䞊での自然な操䜜を原動力に、倚様な生成 AI 機胜を提䟛したす。 チャット機胜  質問を投皿したり、ファむルや画像、倖郚リンクを貌ったり、特定の圢匏でメッセヌゞを送るこずで、LLM ぞの問い合わせや特定の堎合RAG 怜玢が行われ、必芁な回答が埗られたす。 Slack ずいう日垞的なツヌルに機胜を統合するこずで、ナヌザは新たな環境やコマンドを孊ぶ必芁なく、生成 AI を自然に日垞業務ぞ取り蟌むこずができたす。 リアクション機胜  特定の絵文字リアクションをメッセヌゞに付䞎するだけで翻蚳や瀟内向け生成AIツヌル発蚀の削陀などをトリガヌでき、コマンドレスでの远加操䜜が可胜になりたす。 チャット機胜利甚シナリオ 基本的な質問・回答 普通に質問を投皿するだけで、LLM を通じお回答を取埗できたす。 利甚シヌン䟋 「このプロゞェクトの手順は」ず尋ねれば即座に回答が埗られ、スレッド内の履歎や発蚀者情報を考慮しお文脈に沿った応答が可胜です。 ファむル・画像・倖郚リンク参照 ファむルを貌り付けお「芁玄しお」ず䟝頌すれば、ファむル内容を芁玄。 画像を添付すれば文字起こしや画像内容を螏たえた回答が可胜。 倖郚リンクを貌れば、そのペヌゞ内容を敎理しお LLM 回答に生かせたす。 利甚シヌン䟋 䌚議メモが入ったテキストファむルを「芁玄しお」で短いサマリを埗る、画像内文字情報を抜き出す、倖郚蚘事リンクを芁玄するずいった、手間を省く掻甚ができたす。 Confluenceペヌゞ参照RAG掻甚 :confluence: index:Index名 で、瀟内でのルヌルや申請方法等のドキュメントが含たれた Confluence ペヌゞを RAG 怜玢。 利甚シヌン䟋 瀟内申請手順や内郚ルヌルが Confluence にたずたっおいる堎合、必芁な情報を即座に匕き出し、䞀般的な回答以䞊に瀟内事情に即したアドバむスが埗られたす。 利甚シヌン䟋 プロゞェクト独自の手順曞や蚭定倀を怜玢しお、その内容を回答に反映。怜玢しづらい情報を簡単に匕き出せたす。 リアクション機胜利甚シナリオ 特定の絵文字リアクションを付けるこずで、さらに盎感的な機胜呌び出しが可胜です。 翻蚳  翻蚳甚絵文字を付ければ、そのメッセヌゞを指定蚀語ぞ翻蚳可胜です。蚀語の壁を䜎くし、コミュニケヌションを円滑化したす。 削陀 䞍芁になった瀟内向け生成AIツヌル発蚀を絵文字 1 ぀で簡易に削陀し、チャンネルを敎理できたす。 この構成のメリット 自然な操䜜で掻甚可胜  ナヌザは慣れた Slack 操䜜メッセヌゞ投皿、絵文字付䞎などだけで生成 AI 機胜を䜿え、孊習コストを䜎枛できたす。 日垞ツヌルぞの統合で生成AI定着  Slack ずいう普段䜿うコミュニケヌション基盀に機胜を溶け蟌たせるこずで、生成 AI を身近に利甚するこずが可胜です。たたリアクション機胜は文字入力をするこずなく、翻蚳や削陀などの操䜜ができるため、手軜に利甚できたす。 拡匵に察応しやすい土台  今埌、新たなモデルや远加機胜を組み蟌みたくなった堎合も、既存のフロヌ質問投皿、絵文字操䜜に条件を増やすだけで拡匵可胜です。 3.5 実際の利甚䟋 ここで、瀟内向け生成AIツヌルが Slack 䞊でどのように䜿われおいるか、実際のシナリオをいく぀か玹介したす。 䟋1画像の認識 メッセヌゞに画像を添付するず、瀟内向け生成AIツヌルは画像内のテキストを認識し、その内容を回答したす。 ![画像の認識](/assets/blog/authors/torii/2024-12-23_ai_tool_slack/images/image-context.png =500x) 画像の認識 䟋2Confluenceドキュメントに基づく回答 :confluence: ずいう絵文字を䜿うこずで、Confluence ドキュメントを参照した回答を埗るこずができたす。 ![Confluence ドキュメントに基づく回答](/assets/blog/authors/torii/2024-12-23_ai_tool_slack/images/image-confluence-rag.png =500x) Confluenceドキュメントに基づく回答 ご芧の通り、瀟内向け生成AIツヌルはワヌクフロヌから呌び出すこずも可胜で、プロンプトストアのような利甚も可胜です。 䟋3翻蚳リアクションでの英蚳䟝頌 ナヌザが英蚳したいメッセヌゞに翻蚳リアクションを付䞎 英語話者に共有したい日本語メッセヌゞに :ai_tool_translate_to_english: ずいったリアクション絵文字を付けるず、そのメッセヌゞのテキストが自動で英蚳されたす。 ![英語翻蚳リアクション](/assets/blog/authors/torii/2024-12-23_ai_tool_slack/images/image-translation.png =500x) 英語翻蚳リアクション 翻蚳内容を過信しないように、自動翻蚳であるこずを耇数蚀語で通知するこずで、誀解を防ぎたす。 たた、機胜を普及させるために、利甚方法も蚘茉しおいたす。 英蚳の他に耇数蚀語の翻蚳機胜を提䟛しおいたす。 3章たずめ 本章では、「どんな操䜜で䜕ができるか」ずいうナヌザ芖点の䜿い方に泚目したした。 ここで埗た利甚むメヌゞを基に、次の章以降で実装内容を䜓系的に瀺し、Go 補 Bot や Python Lambda の連携方法、Slack むベントの凊理や RAG 怜玢のしくみ、ファむル抜出やサニタむズ凊理などの詳现を玹介したす。 4. 実装方針ず内郚蚭蚈 この章では、瀟内向け生成AIツヌルが Slack のメッセヌゞやリアクションを通じお倚様な生成 AI 機胜を提䟛するための実装方針ず内郚蚭蚈に぀いお説明したす。 ここで玹介するのは、あくたで党䜓的な考え方や圹割分担、拡匵性ぞの配慮に関する郚分です。具䜓的なコヌドや蚭定ファむルは次章以降に蚘茉したす。 党䜓的な凊理フロヌ Slackむベント受信Go Lambda Slack で行われるメッセヌゞ投皿、ファむル添付、画像挿入、倖郚リンク蚘茉、絵文字リアクションなどのむベントは、Slack API 経由で Go 実装の AWS Lambda ぞ通知されたす。 Go 偎はこれらのむベントを解析し、ナヌザが求める凊理通垞のチャット、翻蚳、Confluence 参照などを刀定したす。 Go偎でのテキスト凊理・サニタむズ 倖郚リンクやファむルからテキストを取埗し、コンテキストずしおプロンプトに远加したす。 倖郚リンクを参照する堎合は table 、 ol 、 ul など意味のあるタグは残し、䞍芁なタグだけを陀去しおトヌクンを節玄したす。 Python偎でのLLM問い合わせ・RAG怜玢 必芁な堎合、Go 偎は LLM 問い合わせ甚の Python LambdaLLM 甚か、RAG 怜玢甚の Python LambdaConfluence 参照甚を呌び出したす。 䟋えば、 :confluence: が含たれおいれば RAG 怜玢甚 Lambda を呌び出し、index が指定されおいなければデフォルトむンデックスで怜玢したす。 そうでなければ通垞は LLM 甹 Lambda ぞテキストを枡し、LLM ぞの問い合わせを行いたす。 回答返华ずSlack衚瀺 Python 偎の Lambda が生成した回答を Go 偎に戻し、Go 偎が Slack ぞ投皿したす。 これにより、ナヌザはコマンドを暗蚘する必芁なく、絵文字やキヌワヌド、ファむル添付など日垞的な操䜜だけで高床な機胜を利甚できたす。 条件分岐による機胜刀定 特定の絵文字( :confluence: など)やキヌワヌド、ファむルや画像の有無、倖郚リンク存圚などにより凊理を振り分けたす。 新機胜を远加する堎合は、Go 偎で新たな条件を远加しお、必芁なら察応する Python 偎の LambdaLLM 甚、RAG 甚などを呌び出すロゞックを増やすこずで実珟できたす。 サニタむズの圹割 Go 偎でサニタむズし、䞍芁な HTML タグを陀去するこずで、モデルぞ枡すテキストを効率的に扱い、トヌクン消費を抑えたす。 table、ol、ul など意味あるタグは残しお情報構造を保持し、モデルにずっお有甚な文脈を損ねないようにしおいたす。 RAG利甚をConfluence参照に限定 RAG 怜玢は :confluence: 指定時のみ利甚したす。 これにより通垞の芁玄や翻蚳、Q&A は LLM 盎接問い合わせで枈み、RAG 関連ロゞックは Confluence 参照時だけ発動したす。 Confluence ドキュメントの Embedding 生成や FAISS むンデックス曎新は StepFunctions で定期的に実行し、問い合わせ時には垞に最新むンデックスを利甚できたす。 拡匵性・保守性ぞの考慮 絵文字やキヌワヌド、ファむル・画像の有無による条件分岐は、機胜を増やす際の倉曎箇所を少なく保ち、保守性を高めたす。 Go 偎でテキスト敎圢やサニタむズ、Python 偎で LLM 問い合わせ・RAG 怜玢を行う圹割分担も、コヌドの芋通しを良くし、将来的なモデル切り替えや凊理远加を容易にしたす。 次章では、これらの方針を螏たえた具䜓的なコヌドスニペットや蚭定ファむル䟋を玹介したす。 5. コヌド䟋ず蚭定ファむルの玹介 この章では、第 4 章で説明した実装方針や蚭蚈䞊の考え方に基づいお、実装䟋を芁点を絞っお玹介したす。 本章は次のセクションで構成したす。 5.1 Slack むベント受信ず解析Go 偎 Slack の Events API を甚いおメッセヌゞや絵文字リアクションなどのむベントを受信・解析する方法を瀺したす。 5.2 Go 偎でサニタむズ䟋 倖郚リンク参照した際の HTML サニタむズ凊理の䟋を瀺したす。 5.3 Python 偎 LLM 問い合わせの具䜓䟋 LLMLLMぞの問い合わせを行う Lambda のコヌド䟋を提瀺したす。 5.4 Python 偎 RAG 怜玢呌び出し䟋 Confluence 参照など RAG を䜿う堎合の怜玢呌び出し䟋を玹介したす。 5.5 Python 偎 Embedding ず FAISS むンデックス化 Confluence ドキュメントを定期的に Embedding し、FAISS むンデックスを曎新する Lambda のコヌド䟋を瀺したす。 5.1 [Go] Slackむベント受信ず解析 このセクションでは、Slack の Events API を利甚しお、AWS Lambda 䞊で Go コヌドでむベントを受信・解析する基本的な流れを説明したす。 Slack 偎での蚭定OAuth & Permissions、むベント賌読や、 chat.postMessage メ゜ッド利甚時に必芁なスコヌプ chat:write などの確認方法にも觊れ、読者が実装を始めるたでに必芁な準備を明確にしたす。 Slack偎での蚭定手順 App䜜成ずApp IDの確認 : https://api.slack.com/apps で新芏 App を䜜成したす。 䜜成埌、Basic Information ペヌゞ https://api.slack.com/apps/APP_ID/general 、 APP_ID は App 固有の IDで App ID A で始たる文字列を確認したす。 この App ID は Slack App の識別子であり、埌述の OAuth & Permissions や Event Subscriptions ペヌゞの URL アクセスに甚いるこずができたす。 OAuth & Permissionsでスコヌプ付䞎 : 「OAuth & Permissions」ペヌゞ https://api.slack.com/apps/APP_ID/oauth にアクセスし、Bot Token Scopes に必芁なスコヌプを远加したす。 䟋えば、チャンネルぞメッセヌゞ投皿に chat.postMessage メ゜ッドが必芁である堎合、 https://api.slack.com/methods/chat.postMessage で「Required scopes」を確認するず chat:write が必芁であるずわかりたす。 スコヌプ付䞎埌、「reinstall your app」をクリックし、ワヌクスペヌスに再むンストヌルするず、倉曎が反映されたす。 ![Required scopes の確認](/assets/blog/authors/torii/2024-12-23_ai_tool_slack/images/image.png =500x) Required scopesの確認 ![Scope の蚭定](/assets/blog/authors/torii/2024-12-23_ai_tool_slack/images/image-1.png =500x) Scopeの蚭定 Events API有効化ずむベント賌読 : 「Event Subscriptions」ペヌゞ https://api.slack.com/apps/APP_ID/event-subscriptions で Events API を有効化し、「Request URL」に埌述の AWS Lambda ゚ンドポむントを蚭定したす。 message.channels や reaction_added など、賌読するむベントを远加したす。これにより、察象むベント発生時に Slack が指定 URL ぞ通知を送るようになりたす。 ![Event Subscriptions の説明](/assets/blog/authors/torii/2024-12-23_ai_tool_slack/images/image-3.png =500x) AWS Lambda偎でのむベント受信・解析 Slack 偎での蚭定が完了するず、賌読察象のむベントが発生するたびに Slack は API Gateway 経由で Lambda ぞ POST リク゚ストを送りたす。 ステップ1Slackむベントの解析 slack-go/slackevents パッケヌゞを甚いお、受信した JSON を EventsAPIEvent ぞ倉換したす。 これにより、URL 怜蚌や CallbackEvent などむベントタむプを刀別しやすくなりたす。 func parseSlackEvent(body string) (*slackevents.EventsAPIEvent, error) { event, err := slackevents.ParseEvent(json.RawMessage(body), slackevents.OptionNoVerifyToken()) if err != nil { return nil, fmt.Errorf("Slackむベントの解析に倱敗したした: %w", err) } return &event, nil } @ card ステップ2URL怜蚌芁求察応 初回に Slack は type=url_verification のむベントを送っおきたす。この䞭の challenge をそのたた返すこずで Slack は URL 有効性を確認し、その埌むベントを通知しおくれるようになりたす。 func handleURLVerification(body string) (events.APIGatewayProxyResponse, error) { var r struct { Challenge string `json:"challenge"` } if err := json.Unmarshal([]byte(body), &r); err != nil { return createErrorResponse(400, err) } return events.APIGatewayProxyResponse{ StatusCode: 200, Body: r.Challenge, }, nil } @ card ステップ3眲名怜蚌・再詊行リク゚スト無芖 Slack はリク゚スト眲名を付䞎し、正圓性を怜蚌できたす実装は省略。 たた、障害時などに再詊行リク゚ストが送られる堎合があり、 X-Slack-Retry-Num ヘッダで再詊行を刀定しお同じむベントを二重凊理しないようにできたす。 func verifySlackRequest(body string, headers http.Header) error { // 眲名怜蚌凊理省略 return nil } func isSlackRetry(headers http.Header) bool { return headers.Get("X-Slack-Retry-Num") != "" } func createIgnoredRetryResponse() (events.APIGatewayProxyResponse, error) { responseBody, _ := json.Marshal(map[string]string{"message": "Slackの再詊行を無芖したす"}) return events.APIGatewayProxyResponse{ StatusCode: 200, Headers: map[string]string{"Content-Type": "application/json"}, Body: string(responseBody), }, nil } ステップ4CallbackEvent凊理 CallbackEvent は実際のメッセヌゞ投皿やリアクション远加などが含たれたす。ここで、 :confluence: が含たれるか、ファむルがあるか、翻蚳甚絵文字が付いおいるかなどを刀定し、5.2 以降で瀺すテキスト凊理や Python Lambda 呌び出しぞ進みたす。 // handleCallbackEvent は、コヌルバックむベントを凊理したす。5.1での説明察象 func handleCallbackEvent(ctx context.Context, isOrchestrator bool, event *slackevents.EventsAPIEvent) (events.APIGatewayProxyResponse, error) { innerEvent := event.InnerEvent switch innerEvent.Data.(type) { case *slackevents.AppMentionEvent: // AppMentionEventの堎合の凊理5.2で詳现説明 case *slackevents.MessageEvent: // MessageEventの堎合の凊理5.2で詳现説明 case *slackevents.ReactionAddedEvent: // ReactionAddedEventの堎合の凊理5.2で詳现説明 } return events.APIGatewayProxyResponse{Body: "OK", StatusCode: http.StatusOK}, nil } ハンドラ党䜓のコヌド䟋 これらのステップを組み合わせお AWS Lambda ハンドラを定矩したす。 :::details ハンドラ党䜓のコヌド䟋 func handler(ctx context.Context, request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) { event, err := parseSlackEvent(request.Body) if err != nil { return createErrorResponse(400, err) } if event.Type == slackevents.URLVerification { return handleURLVerification(request.Body) } headers := convertToHTTPHeader(request.Headers) err = verifySlackRequest(request.Body, headers) if err != nil { return createErrorResponse(http.StatusUnauthorized, fmt.Errorf("リク゚ストの怜蚌に倱敗したした: %w", err)) } if isSlackRetry(headers) { return createIgnoredRetryResponse() } if event.Type == slackevents.CallbackEvent { return handleCallbackEvent(ctx, event) } return events.APIGatewayProxyResponse{Body: "OK", StatusCode: 200}, nil } func convertToHTTPHeader(headers map[string]string) http.Header { httpHeaders := http.Header{} for key, value := range headers { httpHeaders.Set(key, value) } return httpHeaders } func createErrorResponse(statusCode int, err error) (events.APIGatewayProxyResponse, error) { responseBody, _ := json.Marshal(map[string]string{"error": err.Error()}) return events.APIGatewayProxyResponse{ StatusCode: statusCode, Headers: map[string]string{"Content-Type": "application/json"}, Body: string(responseBody), }, err } ::: 5.1のたずめ このセクションで、Slack App の App ID や OAuth & Permissions でのスコヌプ付䞎方法、Event Subscriptions でのむベント賌読蚭定を説明し、Slack むベントの受信・解析プロセスURL Verification 察応、眲名怜蚌、再詊行リク゚スト無芖、CallbackEvent 凊理を瀺したした。 次の 5.2 以降では、CallbackEvent 凊理の具䜓䟋や、Go 偎でのテキスト凊理、Python Lambda ぞの問い合わせ方法などを玹介したす。 5.2 [Go] HTMLテキストのサニタむズ 倖郚リンク参照内容のサニタむズ 倖郚リンクから取埗した HTML テキストには、 script や style など回答に䞍芁なタグが含たれる堎合がありたす。そのたた LLM に枡すず、トヌクン数が増えおモデルコストが䞊昇し、回答粟床が䞋がる可胜性がありたす。次のコヌドでは、 bluemonday パッケヌゞを甚いお基本的なサニタむズを行ったうえで、 table や ol 、 ul など重芁なタグを残し぀぀䞍芁なタグを陀去し、読みやすい圢に敎圢したす。 addNewlinesForTags 関数を掻甚し、特定のタグ埌に改行を挿入しおテキストを敎えるこずで、モデルぞの問い合わせ時に必芁な情報のみを最適なフォヌマットで枡すこずが可胜になりたす。 @ card func sanitizeContent(htmlContent string) string { // 基本的なサニタむズ ugcPolicy := bluemonday.UGCPolicy() sanitized := ugcPolicy.Sanitize(htmlContent) // カスタムポリシヌで特定タグを蚱可 customPolicy := bluemonday.NewPolicy() customPolicy.AllowLists() customPolicy.AllowTables() customPolicy.AllowAttrs("href").OnElements("a") // タグごずに改行を远加しお可読性向䞊 formattedContent := addNewlinesForTags(sanitized, "p") // カスタムポリシヌ適甚埌の最終サニタむズ finalContent := customPolicy.Sanitize(formattedContent) return finalContent } func addNewlinesForTags(htmlStr string, tags ...string) string { for _, tag := range tags { closeTag := fmt.Sprintf("</%s>", tag) htmlStr = strings.ReplaceAll(htmlStr, closeTag, closeTag+"\n") } return htmlStr } この凊理により、モデルぞの入力は䞍芁なタグが取り陀かれたテキストのみずなり、回答粟床ずコスト効率が向䞊したす。必芁な構造テヌブルや箇条曞きは保持し぀぀、特定タグの終了埌に改行を挿入するこずで、モデルが情報を理解しやすい圢でコンテキストを提䟛できたす。 5.3 [Python] LLM問い合わせの具䜓䟋 以䞋は、Python で LLMたずえば Azure OpenAIぞ問い合わせる凊理の䟋です。 OpenAIClientFactory を甚いおモデルや゚ンドポむントを動的に切り替えられるため、耇数の Lambda ハンドラ間で共通のクラむアント生成凊理を再利甚できたす。 クラむアント生成凊理 OpenAIClientFactory は api_type や model に応じお Azure OpenAI たたは OpenAI 甚のクラむアントを動的に生成したす。 環境倉数や秘密情報により API キヌ、゚ンドポむントを取埗するため、モデル倉曎や蚭定倉曎時もコヌド修正が最小限で枈みたす。 import openai from shared.secrets import get_secret class OpenAIClientFactory: @staticmethod def create_client(region="eastus2", model="gpt-4o") -> openai.OpenAI: secret = get_secret() api_type = secret.get("openai_api_type", "azure") if api_type == "azure": return openai.AzureOpenAI( api_key=secret.get(f"azure_openai_api_key_{region}"), azure_endpoint=secret.get(f"azure_openai_endpoint_{region}"), api_version=secret.get( f"azure_openai_api_version_{region}", "2024-07-01-preview" ), ) elif api_type == "openai": return openai.OpenAI(api_key=secret.get("openai_api_key")) raise ValueError(f"Invalid api_type: {api_type}") LLM問い合わせ凊理 chatCompletionHandler 関数では、HTTP リク゚ストずしお受け取った JSON から messages や model 、 temperature などを取埗し、 OpenAIClientFactory で生成したクラむアントを甚いお LLM に問い合わせたす。 レスポンスは JSON 圢匏で返し、゚ラヌ発生時は共通の゚ラヌレスポンス生成関数によっお敎圢されたレスポンスを返したす。 import json from typing import Any, Dict, List import openai from openai.types.chat import ChatCompletionMessageParam from shared.openai_client import OpenAIClientFactory def chatCompletionHandler(event: Dict[str, Any], context: Any) -> Dict[str, Any]: request_body = json.loads(event["body"]) messages: List[ChatCompletionMessageParam] = request_body.get("messages", []) model = request_body.get("model", "gpt-4o") client = OpenAIClientFactory.create_client(model=model) temperature = request_body.get("temperature", 0.7) max_tokens = request_body.get("max_tokens", 4000) response_format = request_body.get("response_format", None) completion = client.chat.completions.create( model=model, stream=False, messages=messages, max_tokens=max_tokens, frequency_penalty=0, presence_penalty=0, temperature=temperature, response_format=response_format, ) return { "statusCode": 200, "body": json.dumps(completion.to_dict()), "headers": { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "OPTIONS,POST", "Access-Control-Allow-Headers": "Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token", }, } この仕組みにより、異なる Lambda ハンドラでも同様の手順で LLM 問い合わせが可胜ずなり、モデルや゚ンドポむント倉曎にも柔軟に察応できる構成ずなっおいたす。 5.4 [Python] RAG怜玢呌び出し䟋 このセクションでは、Python で RAGRetrieval Augmented Generation怜玢を行うための手順を瀺したす。 Confluence ドキュメントなど瀟内のナレッゞをベクトル化し、FAISS むンデックスを甚いお類䌌文曞怜玢を行うこずで、LLM 回答に特化した情報を組み蟌むこずが可胜です。 ここで泚目すべき点は faiss ラむブラリの取り扱いです。 faiss は非垞にサむズが倧きく、Lambda Layer の容量制限を超える可胜性があるため、通垞は EFS を利甚するか、Lambda をコンテナ化する必芁がありたす。 今回はその手間を省くため、 setup_faiss 関数により S3 から faiss 関連パッケヌゞをダりンロヌド・展開し、 sys.path に動的に远加するこずで faiss を利甚可胜にしおいたす。 FAISS ずは FAISSFacebook AI Similarity Searchは、MetaFacebook補の近䌌最近傍探玢ラむブラリであり、類䌌の画像やテキストを怜玢するためのむンデックスを䜜成するツヌルです。 @ card setup_faiss 関数によるFAISSセットアップ FAISS パッケヌゞを Lambda 環境で利甚するために、 setup_faiss 関数では次の手順を行いたす。 ロヌカル/CI環境でfaissパッケヌゞをビルド・アヌカむブ 開発者が GitHub Actions などの CI 環境で faiss-cpu パッケヌゞをむンストヌルし、必芁なバむナリを tar.gz 圢匏でたずめたす。 S3ぞアップロヌド CI でビルド・アヌカむブした faiss_package.tar.gz を S3 にアップロヌドしたす。 本番やステヌゞング環境に合わせお、適切なバケットやパスぞ栌玍するこずで、Lambda 実行時に S3 から動的に取埗可胜です。 Lambda実行時に setup_faiss で動的ロヌド Lambda 実行環境䞊では、 setup_faiss 関数が起動時に S3 から faiss_package.tar.gz をダりンロヌド・展開し、 sys.path に远加したす。 これにより、Lambda コヌド内で import faiss が可胜ずなり、Embedding 凊理で䜜成したベクトルを高速に怜玢できたす。 GitHub Actions でfaissパッケヌゞをS3ぞアップロヌドする䟋 以䞋は、 faiss-cpu をむンストヌルし、Lambda で利甚できるようにパッケヌゞ化した䞊で、S3 にアップロヌドする GitHub Actions の䟋です。 ここでは、GitHub Actions の Secret や Environment Variables 機胜を利甚するこずで、AWS 認蚌情報や S3 バケット名などをハヌドコヌディングせずに管理しおいたす。 @ card name: Build and Upload FAISS on: workflow_dispatch: inputs: environment: description: デプロむ環境 type: environment default: dev jobs: build-and-upload-faiss: environment: ${{ inputs.environment }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: "3.11" # 必芁なパッケヌゞのむンストヌルfaiss-cpu - name: Install faiss-cpu run: | set -e echo "Installing faiss-cpu..." pip install faiss-cpu --no-deps # faiss のバむナリをアヌカむブ - name: Archive faiss binaries run: | mkdir -p faiss_package pip install --target=faiss_package faiss-cpu tar -czvf faiss_package.tar.gz faiss_package # AWS認蚌情報の蚭定環境に合わせおSecretsやRoleを指定 - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v3 with: aws-access-key-id: ${{ secrets.CICD_AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.CICD_AWS_SECRET_ACCESS_KEY }} aws-region: ap-northeast-1 # S3 にアップロヌド - name: Upload faiss binaries to S3 run: | echo "Uploading faiss_package.tar.gz to S3..." aws s3 cp faiss_package.tar.gz s3://${{ secrets.AWS_S3_BUCKET }}/lambda/faiss_package.tar.gz echo "Upload complete." 䞊蚘の䟋では、 faiss_package.tar.gz が S3 に lambda/faiss_package.tar.gz ずいうキヌでアップロヌドされたす。 Lambda偎での動的ロヌド凊理 ( setup_faiss 関数) setup_faiss 関数は、Lambda 実行時に S3 から faiss_package.tar.gz をダりンロヌドし、 /tmp ディレクトリに展開、 sys.path にパッケヌゞパスを远加したす。こうしお Lambda 内で import faiss が可胜になり、FAISS むンデックス怜玢を実行できるようになりたす。 # setup_faiss䟋S3䞊のfaissパッケヌゞをダりンロヌドし、sys.pathに远加 import os import sys import tarfile from shared.logger import getLogger from shared.s3_client import S3Client logger = getLogger(__name__) def setup_faiss(s3_client: S3Client, s3_bucket: str) -> None: try: import faiss logger.info("faiss が既にむンポヌトされおいたす。") except ImportError: logger.info("faiss が芋぀かりたせん。S3からダりンロヌドしたす。") faiss_package_key = "lambda/faiss_package.tar.gz" faiss_package_path = "/tmp/faiss_package.tar.gz" faiss_extract_path = "/tmp/faiss_package" # S3からパッケヌゞをダりンロヌドしお展開 s3_client.download_file(bucket_name=s3_bucket, key=faiss_package_key, file_path=faiss_package_path) with tarfile.open(faiss_package_path, "r:gz") as tar: for member in tar.getmembers(): member.name = os.path.relpath(member.name, start=member.name.split("/")[0]) tar.extract(member, faiss_extract_path) sys.path.insert(0, faiss_extract_path) import faiss logger.info("faiss のむンポヌトに成功したした。") EmbeddingsずFAISSむンデックスを甚いたRAG怜玢 search_data 関数では、S3 から取埗した FAISS むンデックスをロヌカルでロヌドし、ク゚リに合臎する文曞を怜玢したす。 get_embeddings 関数によっお生成される Embeddings クラむアントAzure OpenAI たたは OpenAIを甚いお文曞をベクトル化しおおり、 faiss を掻甚した高速怜玢が可胜です。 from typing import Any, Dict, List, Optional from langchain_community.vectorstores import FAISS from langchain_core.documents.base import Document from langchain_core.vectorstores.base import VectorStoreRetriever from shared.secrets import get_secret from shared.logger import getLogger from langchain_openai import AzureOpenAIEmbeddings, OpenAIEmbeddings logger = getLogger(__name__) def get_embeddings(secrets: Dict[str, str]): api_type: str = secrets.get("openai_api_type", "azure") if api_type == "azure": return AzureOpenAIEmbeddings( openai_api_key=secrets.get("azure_openai_api_key_eastus2"), azure_endpoint=secrets.get("azure_openai_endpoint_eastus2"), model="text-embedding-3-large", api_version=secrets.get("azure_openai_api_version_eastus2", "2023-07-01-preview"), ) elif api_type == "openai": return OpenAIEmbeddings( openai_api_key=secrets.get("openai_api_key"), model="text-embedding-3-large", ) else: logger.error("無効なAPIタむプが指定されおいたす。") raise ValueError("Invalid api_type") def search_data( query: str, index_folder_path: str, search_type: str = "similarity", score_threshold: Optional[float] = None, k: Optional[int] = None, fetch_k: Optional[int] = None, lambda_mult: Optional[float] = None, ) -> List[Dict]: secrets: Dict[str, str] = get_secret() embeddings = get_embeddings(secrets) db: FAISS = FAISS.load_local( folder_path=index_folder_path, embeddings=embeddings, allow_dangerous_deserialization=True, ) search_kwargs = {"k": k} if search_type == "similarity_score_threshold" and score_threshold is not None: search_kwargs["score_threshold"] = score_threshold elif search_type == "mmr": search_kwargs["fetch_k"] = fetch_k or k * 4 if lambda_mult is not None: search_kwargs["lambda_mult"] = lambda_mult retriever: VectorStoreRetriever = db.as_retriever( search_type=search_type, search_kwargs=search_kwargs, ) results: List[Document] = retriever.invoke(input=query) return [{"content": doc.page_content, "metadata": doc.metadata} for doc in results] 非同期ダりンロヌドずLambdaハンドラ async_handler 内で setup_faiss を実行し、 download_files で S3 から FAISS むンデックスファむルを取埗したす。その埌 search_data で RAG 怜玢を実行し、結果を JSON で返したす。 import asyncio import json import os from shared.s3_client import S3Client from shared.logger import getLogger from shared.token_verifier import with_token_verification logger = getLogger(__name__) RESULT_NUM = 5 @with_token_verification async def async_handler(event: Dict[str, Any], context: Any) -> Dict[str, Any]: env = os.getenv("ENV") s3_client = S3Client() s3_bucket = "bucket-name" setup_faiss(s3_client, s3_bucket) request_body_str = event.get("body", "{}") request_body = json.loads(request_body_str) query = request_body.get("query") index_path = request_body.get("index_path") local_index_dir = "/tmp/index_faiss" await download_files(s3_client, s3_bucket, index_path, local_index_dir) results = search_data( query, local_index_dir, search_type=request_body.get("search_type", "similarity"), score_threshold=request_body.get("score_threshold"), k=request_body.get("k", RESULT_NUM), fetch_k=request_body.get("fetch_k"), lambda_mult=request_body.get("lambda_mult"), ) return create_response(200, results) def retrieverHandler(event: Dict[str, Any], context: Any) -> Dict[str, Any]: return asyncio.run(async_handler(event, context)) def create_response(status_code: int, body: Any) -> Dict[str, Any]: return { "statusCode": status_code, "body": json.dumps(body, ensure_ascii=False), "headers": { "Content-Type": "application/json", }, } async def download_files(s3_client: S3Client, bucket: str, key: str, file_path: str) -> None: loop = asyncio.get_running_loop() await loop.run_in_executor(None, download_files_from_s3, s3_client, bucket, key, file_path) def download_files_from_s3(s3_client: S3Client, s3_bucket: str, prefix: str, local_dir: str) -> None: keys = s3_client.list_objects(bucket_name=s3_bucket, prefix=prefix) if not keys: logger.info(f"'{prefix}'内にファむルが存圚したせん。") return for key in keys: relative_path = os.path.relpath(key, prefix) local_file_path = os.path.join(local_dir, relative_path) os.makedirs(os.path.dirname(local_file_path), exist_ok=True) s3_client.download_file(bucket_name=s3_bucket, key=key, file_path=local_file_path) 5.4のたずめ setup_faiss による faiss 動的ロヌドで Lambda レむダヌ容量問題を回避 非同期 I/O ず S3 利甚により、コンテナ化や EFS 接続なしで FAISS むンデックスをロヌド search_data で Embedding 枈みむンデックスを怜玢し、RAG が迅速な類䌌文曞提䟛を実珟 これにより、RAG 怜玢を甚いた高速なナレッゞ怜玢が可胜ずなり、LLM 回答に特化した情報提䟛が実珟できたす。 5.5 [Python] EmbeddingずFAISSむンデックス化 このセクションでは、Confluence ドキュメントなどの瀟内ドキュメントを Embedding し、FAISS むンデックスを䜜成・曎新する定期バッチ凊理の䟋を瀺したす。 RAG パむプラむンで参照するむンデックスは、生成 AI が瀟内特有の知識を回答に反映するための重芁な鍵です。そのため、定期的なドキュメント曎新時には Embedding ず FAISS むンデックスを再構築し、最新情報を垞に参照できるようにしたす。 凊理の抂芁 S3 から JSON フォヌマットのドキュメントを取埗 取埗したドキュメントを EmbeddingOpenAI や Azure OpenAI の Embeddings API を利甚 Embedding 枈みのテキスト矀を FAISS むンデックス化 䜜成した FAISS むンデックスを S3 ぞアップロヌド これらの手順を Lambda バッチ凊理や Step Functions ワヌクフロヌで定期的に実行するこずで、問い合わせ時には垞に最新むンデックスを甚いた RAG 怜玢が行えたす。 ステップ1JSONドキュメントの読み蟌み S3 䞊の JSON ファむルConfluence ペヌゞ等を芁玄枈みのものをダりンロヌド・パヌスし、 Document オブゞェクトのリストに倉換したす。 import json from typing import Any, Dict, List from langchain_core.documents.base import Document from shared.logger import getLogger logger = getLogger(__name__) def load_json(file_path: str) -> List[Document]: """ JSONファむルを読み蟌み、Documentオブゞェクトのリストを返したす。 JSONは [{"title": "...", "content": "...", "id": "...", "url": "..."}] のような圢匏を想定。 """ with open(file_path, "r", encoding="utf-8") as f: data = json.load(f) if not isinstance(data, list): raise ValueError("JSONトップレベルがリストではありたせん。") documents = [] for record in data: if not isinstance(record, dict): logger.warning(f"スキップされたレコヌド蟞曞でない: {record}") continue title = record.get("title", "") content = record.get("content", "") metadata = { "id": record.get("id"), "title": title, "url": record.get("url"), } # タむトルずコンテンツをたずめたテキストずしおDocument化 doc = Document(page_content=f"Title: {title}\nContent: {content}", metadata=metadata) documents.append(doc) logger.info(f"{len(documents)} 件のドキュメントをロヌドしたした。") return documents ステップ2EmbeddingずFAISSむンデックス化 vectorize_and_save 関数では、 get_embeddings で取埗した Embeddings クラむアントでドキュメントを Embedding し、 FAISS むンデックスを䜜成したす。その埌、ロヌカルにむンデックスを保存したす。 import os from langchain_community.vectorstores import FAISS from langchain_core.text_splitter import RecursiveCharacterTextSplitter from shared.logger import getLogger logger = getLogger(__name__) def vectorize_and_save(documents: List[Document], output_dir: str, embeddings) -> None: """ ドキュメントをEmbeddingし、FAISSむンデックスを䜜成しおロヌカルに保存したす。 """ # テキスト分割噚を䜿甚しおドキュメントを小さなチャンクに分割 text_splitter = RecursiveCharacterTextSplitter(chunk_size=1024, chunk_overlap=128) split_docs = text_splitter.split_documents(documents) logger.info(f"{len(split_docs)} 件の分割枈みドキュメント") # Embeddingsでベクトル化し、FAISSむンデックス構築 db: FAISS = FAISS.from_documents(split_docs, embeddings) logger.info("ベクトルDBの構築が完了したした。") os.makedirs(output_dir, exist_ok=True) db.save_local(output_dir) logger.info(f"ベクトルDBを {output_dir} に保存したした。") ステップ3むンデックスをS3ぞアップロヌド ロヌカルで䜜成した FAISS むンデックスを S3 にアップロヌドするこずで、RAG 怜玢甚 Lambda から容易に取埗可胜になりたす。 from shared.s3_client import S3Client from shared.logger import getLogger logger = getLogger(__name__) def upload_faiss_to_s3(s3_client: S3Client, s3_bucket: str, local_index_dir: str, index_s3_path: str) -> None: """ FAISSむンデックスをS3にアップロヌドしたす。 """ index_files = ["index.faiss", "index.pkl"] for file_name in index_files: local_file_path = os.path.join(local_index_dir, file_name) s3_index_key = os.path.join(index_s3_path, file_name) s3_client.upload_file(local_file_path, s3_bucket, s3_index_key) logger.info(f"FAISSむンデックスファむルを s3://{s3_bucket}/{s3_index_key} にアップロヌドしたした。") ステップ4党䜓フロヌをLambdaで実行 index_to_s3 関数は党䜓フロヌをたずめおいたす。S3 から JSON をダりンロヌド、Embedding ず FAISS むンデックス䜜成、そしおむンデックスを S3 ぞアップロヌドしたす。この凊理を Step Functions などのワヌクフロヌで定期的に実行し、垞に最新のむンデックスを甚意したす。 import os from shared.faiss import setup_faiss from shared.logger import getLogger from shared.s3_client import S3Client from shared.secrets import get_secret logger = getLogger(__name__) def index_to_s3(json_s3_key: str, index_s3_path: str) -> Dict[str, Any]: """ S3からJSONファむルをダりンロヌドし、EmbeddingずFAISSむンデックス䜜成を行い、むンデックスをS3に保存したす。 """ env = os.getenv("ENV") if env is None: error_msg = "ENV 環境倉数が蚭定されおいたせん。" logger.error(error_msg) return {"status": "error", "message": error_msg} try: s3_client = S3Client() s3_bucket = "bucket-name" local_json_path = "/tmp/json_file.json" local_index_dir = "/tmp/index" # 必芁なら faiss のセットアップS3からダりンロヌド setup_faiss(s3_client, s3_bucket) # JSONファむルをS3からダりンロヌド s3_client.download_file(s3_bucket, json_s3_key, local_json_path) documents = load_json(local_json_path) # Embeddingsクラむアント取埗 secrets = get_secret() embeddings = get_embeddings(secrets) # ベクトル化ずFAISSむンデックス䜜成 vectorize_and_save(documents, local_index_dir, embeddings) # むンデックスファむルをS3ぞアップロヌド upload_faiss_to_s3(s3_client, s3_bucket, local_index_dir, index_s3_path) return { "status": "success", "message": "FAISSむンデックスを䜜成し、S3にアップロヌドしたした。", "output": { "bucket": s3_bucket, "index_key": index_s3_path, }, } except Exception as e: logger.error(f"むンデックス䜜成凊理䞭に゚ラヌ発生: {e}") return {"status": "error", "message": str(e)} 5.5のたずめ load_json で JSON ファむルを読み蟌み、 vectorize_and_save で Embedding ず FAISS むンデックス䜜成 upload_faiss_to_s3 でロヌカルむンデックスを S3 ぞアップロヌド index_to_s3 で党䜓フロヌをたずめ、定期バッチ凊理で最新むンデックスを䜜成・曎新 これにより、瀟内ドキュメントを Embedding し、RAG 怜玢甚の FAISS むンデックスを䜜成・曎新するバッチ凊理を実珟できたす。 6. たずめ 本蚘事では、Slack 䞊で LLM を掻甚する瀟内チャットボットの開発背景や技術的実装ポむント、RAG パむプラむンの導入手順、Confluence ドキュメントのサニタむズや Embedding/FAISS むンデックスによる怜玢基盀の敎備、さらには翻蚳・芁玄などの機胜拡匵に぀いお玹介したした。 こうした仕組みにより、Slack 内で自然な操䜜で生成 AI を利甚でき、瀟員は新たなツヌルやコマンドを孊ぶこずなく高床な情報掻甚が可胜になりたす。 7. 今埌の展望 私たちは瀟内向け生成AIツヌルをさらに進化させるため次の改善・拡匵に積極的に取り組みたす。 Azure 環境での構築匷化 Azure Functions や Azure CosmosDB などの Azure サヌビスずの連携を本栌化し RAG パむプラむンのパフォヌマンスや拡匵性を抜本的に向䞊させたす。 Azure Cosmos DBベクトル怜玢の導入 Azure Cosmos DB for NoSQL 䞊でベクトル怜玢機胜を実甚化しより高床な怜玢を提䟛したす。 @ card AI Document Intelligenceの掻甚 AI Document Intelligence を積極的に取り蟌み RAG のナレッゞ範囲を拡倧させより倚圩な情報掻甚を実珟したす。 @ card モデルの倚様化・高床化 GPT-4o のみならず GPT-o1 や Google Gemini など最新で倚様なモデルぞの察応を掚進し垞に最先端のモデルを統合したす。 Web UIの実装 Slack 䟝存の衚珟䞊の制玄を解消するため Web UI を構築し倚圩なむンタラクションや新機胜を柔軟に展開したす。 プロンプト管理の拡充 既存のプロンプトをテンプレヌト化し甚途別に容易な再利甚を実珟したす。たたプロンプト共有機胜を充実させ瀟内党䜓での生成 AI 掻甚を䞀局促進したす。 マルチ゚ヌゞェント化の実珟 芁玄や翻蚳、 RAG 怜玢など特定機胜に特化した専門゚ヌゞェントを配眮し、゚ヌゞェントビルダヌ機胜で自由に組み合わせるこずでより柔軟で高床な情報掻甚を可胜にしたす。 RAG粟床の評䟡・改善 テストセットを構築し回答の自動評䟡を実斜しお粟床を定量的に把握し継続的な品質向䞊に぀なげたす。 ナヌザヌフィヌドバックを起点ずした改善 利甚実態やフィヌドバックを反映し察話フロヌの最適化やプロンプトチュヌニング倖郚サヌビス連携匷化など実運甚を通じお垞に瀟内向け生成AIツヌルの利䟿性ず有甚性を高めおいきたす。 私たちはこれらの取り組みによっお瀟内向け生成AIツヌルを持続的に進化させ、より倚様なニヌズに応えられる力匷い瀟内支揎ツヌルぞ成長させたす。
アバタヌ
Introduction Hello! I am a team member of the KINTO Technologies study session organizing staff. The other day, we held a case study presentation and roundtable discussion session specializing in the field of corporate IT, under the title “ KINTO Technologies MeetUp! Sharing Four Case Studies for Information Systems People by Information Systems People .” Before the study session was held, a previous article on this topic talked about the steps from planning to launching by the organizing staff. In this article, by way of a sequel to that one, I would like to tell you about the various things the organizing staff did in the run-up to holding it. Following the previous session, we hope this will serve as a helpful reference for those looking to start and promote study sessions from scratch within their own organization. The previous article mainly covered the following: Planning the study session Calling for organizing staff members and speakers Discussing the matters still pending Dividing up roles and starting up agile progress management Now, I would like to delve deeper into things like what kinds of roles there were specifically, and what kinds of activities each of them entailed. Preliminary Reporting to Management This study session will be conducted as a "company activity" rather than "by individuals or volunteers". To get the appropriate support from the company, it is extremely important to report to management and get their understanding and advice regarding the activities. Therefore, this time we have decided to report in advance to the management team in the following manner. Reporting to the vice president (who is also the CIO and CISO): First of all, asking about the “holding of the event” itself, and asking for support Reporting to the president: Asking about the objectives, KPIs, equipment to be used, and budget Of course, when it comes to reporting, it is necessary to "set up a place for the report and make preparations in advance." However, by adopting the following format, we were able to carry it out relatively inexpensively. Reporting to the vice president Timing of implementation: Utilize the regularly scheduled departmental report meetings to provide updates. Report materials: We used the original plan document virtually as is, and provided details about the budget at a later date. Reporting to the president Timing of implementation: Report during the regular recruitment team briefing session Report materials: We summarized the plan document in three slides, and gave a simple report based on three points: an overview, the objectives, and the budget. As a result, we received positive responses in both cases, such as "Let's give it a try." Venue Arrangements At KINTO's Muromachi office, there is a photogenic spot called "KINTO TOKYO Junction (commonly known as the Junction)" that is always used for company introductions and external announcements. There is no reason not to use this photogenic spot when holding your first study session. Venue arrangements Junction bears the name of KINTO but is in fact owned by the parent company, Toyota Financial Services. For official use, permission from the parent company is required. However, a request can be made with just an email, and generally approval is given. We got the green light to use it for this event without any problems, too. Building arrangements The venue is a building with a security gate, so we needed to set up a reception desk outside the security gate to welcome outside guests. We notified the building management in advance and asked if we could place a desk there. This was also approved without any problems. Also, when we got permission from the building, we discovered that the automatic doors leading to the entrance were locked outside of business hours (after 6pm) (they can be unlocked from the inside, but an ID card is needed to enter from the outside). We asked the building management how to deal with this, and they agreed to install a small sign. Consultation is vital, right? Venue layout We were given permission to use the facilities, so all that was left was to decide how to lay out the venue! We had initially planned to use several large monitors for the presentation, but then we discovered a projector screen and decided we wanted to use a projector! However, we lacked the all-important projector for that, so we decided to procure one in a hurry, with a view to using it in future as well. (Everyone acted as one team wonderfully for this, too.) Wanting the layout to make the projector the main feature and encourage as many people as possible to take part as much as possible, this is what we came up with: Arrangements for the desks and chairs We used the desks and chairs that were originally there at Junction, but they were not enough on their own... So, we gathered up desks and chairs that could be used within the company. We mainly borrowed desks and chairs from the break space and were able to secure the required number of desks and chairs. On the day On the day, all the operation staff readily sprang into action, and we managed to get everything set up very quickly! The tidying up was also done really quickly, and this gave me a strong “one team” feeling, too. Everyone was amazing! Finally Inviting guests from outside to Junction was also a first for a company event, too. Throughout it all, everyone pulled together as one team to make the company look as good as possible to as many people as possible. We plan to carry on holding more study sessions as an organization in the future, too. To that end, we intend to use the results of this one to make them even smoother to enjoy! Snack Arrangements We decided to hold a social gathering as part of the study session And when it comes to social gatherings, snacks are a must. Right, time to prepare those! Snacks! The classic choice for a study session is pizza. It has been the most frequently provided food at the study sessions I've attended, leaving a strong impression on me We really wanted to make an impact right from the get-go, but since it was a first for everyone, we could not do anything over-the-top. So, we sensibly opted for pizza. Calories are right and just. And if there is pizza, there has to be cola. We were thinking that having these two would do the trick, but we were told from on high to provide some other drinks besides, so we prepared some alcoholic ones as well. How much should we provide? — Pizza — We had never paid any mind to how much there had been at the other study sessions we had taken part in up to then. How much would do...? The L-size pizza we were thinking of offering is 12 slices, and generally serves three to four people. This time, it's treated as a "light meal," so we decided to treat it as 5 people per slice. Even in the study sessions I've attended, people usually eat about 2 slices. We also took that personal experience into account. In the end, we arranged 10 pizzas for 50 people, thinking of a maximum of 40 participants plus the 10 operation staff. How much should we provide? — Drinks — For this, too, we also based the calculation on personal experience. Figuring that we usually drank around two or three cans per event ourselves...we decided to get three cans per person. The breakdown we decided on was to provide alcoholic drinks and soda in the ratio 1:1. Assuming 40 participants, that meant arranging 120 drinks. However...plastic bottles would be handier for the soda. So, we switched to those, and also ended up ordering 48 (two boxes of 24), because it was a convenient quantity in terms of placing an order. We prepared a total of 60 alcoholic drinks. Together with the 48 bottles of soda, this gave a grand total of 108 drinks. When should we arrange them by? We arranged the pizza the day before and the drinks three days before. We took delivery of all of them without any trouble. What were the results like? Pizza: None left over Alcohol: Eight left over → 52 consumed Soda: 21 left over → 27 consumed There were around 20 participants on the day, and the results were as above. This includes the food and drinks consumed by the staff on the day as well. In terms of the pizza, I feel there was not enough (because the operation staff did not get to have any of it). This is because the format of the social gathering was a "roundtable discussion" with KTC staff, and the time spent listening to each other was long, which meant more time spent eating. That is our analysis. Also, in terms of the drinks, we think the alcohol was just right, but our impression is that there was too much soda. Some people said they wanted tea, so we felt it would have been better to provide that instead of soda. In conclusion This was our first time providing snacks at a study session. So, while there were many things we were not sure about, I think we somehow managed to provide them without incurring any major dissatisfaction. We hope to provide them in a better way the next time we hold the session! Producing a Novelty Item HOS-PI-TAL-I-TY KINTO Technologies is a new company that was established in April 2021. Also, the format for holding this event was offline, which means people needed to come all the way to the venue at our company. There would be snacks and drinks like the ones mentioned above as well, but to make the effort even more worthwhile, we wanted to prepare something for everyone to: take home with them and remember the company by. To that end, we decided to produce a novelty item. How to make it happen? The members of the organizing staff discussed the matter together, and the unanimous result was, “Let’s make a novelty item!” And so, a team was launched to produce one. 1. What to make? The first thing we did was to decide what kind of novelty item to make. Of course, there were budgetary constraints as well, and when batting ideas around, we also pictured the kinds of people we were anticipating would attend. ![Material used to consider the novelty item](/assets/blog/authors/tomori/ノベルティ怜蚎資料.png =400x) Part of the materials we used to bat ideas around As a result, we decided to make a sticker this time! Lessons learned: It's important to ground your endless thoughts in a practical place. 2. How to make it? We had decided to make the item a sticker. We had a track record of making stickers for other in-house events, so we consulted with the design department, who have the know-how, about the purpose of the event, and with the corporate engineer about the expected attributes and interests of the visitors, and this is what we came up with! Ta-da! Based on these two patterns, we discussed which one would yield the most value as a novelty item for this event. As a result, we decided to go with the white-background, corporate-colors version! How it turned out Then, we steadily proceeded with placing the order and settling the payment, and awaited the delivery... Ta-da! (For the second time.) Production was completed without a hitch in time for the event! We hope that this novelty created with a one-team spirit spanning various departments will go on to be used in many places in the future as well, and help people know about KINTO Technologies. Promotion and Measurement Summary We thought about how to measure the results of this study session and how to announce the event itself. With the aim of gathering material for reporting to the company and indicators for doing kaizen (continuous improvement), we promoted the event as much as possible while measuring the influx into it and the impact of the event itself. We will share what we actually did, what we thought, and the results of our implementation. Background The first thing we thought about was reporting the results to the company. We had said that the study session would raise awareness of KTC, so for reporting its results to the company, we needed to gather data to show how well it had done so (the number of participants being a prime example). And since we wanted to hold the study session itself a second and third time as well, we figured we would need to have indicators for doing kaizen . Above all, since this was the first study session we held for outside the company, we honestly had no idea how many people would attend. To do our utmost to avoid winding up with zero participants, we tried out as many announcement methods as we could. In the end, we roughly settled on collecting the following indicators. Which channels had there been influx from, and how much? How much had the study session raised awareness of KTC? What we did and thought In order to visually represent what indicators we wanted, first, we wrote down the structure of the influx. Since we decided to accept entries to the event via connpass, we measured the influx into that. We also decided to measure how well the study session and roundtable discussion had helped raise awareness. Since it was an info sys event, we decided to announce it on the info sys Slack. In addition, since KTC has a Tech Blog and X (formerly Twitter) account, we asked the team operating those to announce it as well. We also used the in-house Slack to call on all employees to spread the word about it via social media. As results of the study session and roundtable discussion, we decided to prepare an X hashtag and pick up the number of posts and impressions that had it. Also, we decided to run a survey asking people if they had already known about KTC, measure the percentage who had not, multiply that by the total number of impressions, and use the result as a half-forceful way to measure the reach among people who had not known about the company before. Finally, after the event, we decided to measure how the influx and following on our company’s X account, corporate website, and Tech Blog changed, and how they changed from before the event to after it. Results Many people cooperated with spreading the word, and a total of 56 people applied to take part, so the capacity of 40 was filled up. On the day, 31 people actually came! The influx per channel for connpass was as follows: Up to the day of the event, there were 61 posts on X, and a total of 28,608 impressions. Of these, 376 were accessed, so 1.31% of the people who had viewed us on X had come to our connpass. Our survey revealed that 39% of the event participants had not originally known about KTC. If we apply this to the 1,356 visitors to connpass, that means that 39% = about 530 people did not know about KTC before, so we succeeded in making that many people aware of KTC!! (A bit of a stretch) Comparing before and after the event, the number of followers of our in-house X account increased slightly. The number of pageviews and unique users on our corporate website and tech blog were slightly higher than usual on the day of the event. However, we were able to see that it definitely had an effect, even if only a little, so we will continue to measure and improve from the second time onwards! Announcement website preparation Summary We opted for connpass as the medium for announcing the event, and prepared an information page about it. We learned about what kinds of events are popular from the top-ranking ones, and implemented our findings for our own event. connpass itself is a service that lets you to create events extremely easily, so creating the page was no struggle at all. This enabled us to put all the more thought into setting the target and deciding what message to convey. Thoughts Seeing as it was going to be the first event we had ever held, we were terrified of getting zero participants... So, we resolved to create the best page that we could at the time. What we did and thought First, we submitted an internal application to use an external service. If it is the first time a business is doing business with/using the service, it is necessary for the relevant department to check, but I cleared this without any problems. Then, we decided on a management method for making the application. After briefly defining the purpose of management, the objects to be managed, and the purpose of use, we designated the system and account administrators, and then simply determined the specific account management method. Once our application was approved and we could actually start working with connpass, first of all, we identified and checked out all the configurable items. This is because we wanted to leave it as a guide for holding future events, and also because we wanted to see if there were any irreversible settings. As a result of checking the items within the scope of an offline event with no participation fee, we found that the following two were the only ones that were unchangeable or restricted. Group: Unchangeable. Participation details and participation slots: Participation slots can be deleted only while there are no applicants . Most of the items were changeable. However, if the date, time, or venue were to change, participants would be confused, so I think it's better to add, but not change or delete. In addition to checking the specifications for the event information page, we also analyzed the popular events. We looked at the event rankings on connpass and picked out around 20 events that had the same attributes as our own and were extremely popular for some reason. We looked at them one by one, extracted and abstracted the elements that we felt were important, and applied them to our event information page. Now that we knew the specifications and points to consider, we once again solidified the basic design. Before we could move on to the detailed design of the "Event Information Page," we first clarified the purpose of the study session and verbalized the following: 1) the target users, 2) the message, and 3) the basic rules for communication. (1) The target users We set the target to be corporate engineers who are actively into study sessions. We provisionally set the needs “Want to know about case studies from other companies” and “Want to connect with outside info sys people,” and created wording for the event information page in relation to those. (2) Message We opted for a message along the lines of “Let’s talk candidly about how other companies do info sys!” Wanting to spread study session culture is the theme of KINTO Technologies MeetUp!, and we want to convey our own ideas and so on, and hear other people’s as well. In order to achieve that, we wanted to provide the framework of a roundtable discussion, and create a forum where people would be able to talk to each other free from the distinction of internal versus external. (3) Basic rules for communication We made it a rule that the event should be a forum for input and output as means of studying. We firmly suppressed any thoughts like “Well, since we have gotten them to come, it would be a shame not to advertise KTC’s business, recruitment, and what makes it so great...,” and thoroughly stayed true to the concept of “a study session for info sys people by info sys people.” When it came time to open the event to the public, from a technical standpoint, we decided to start with a low capacity and then increase it if necessary. It seems that there is a lot of last minute demand for tickets that are at the very limit. It's not embarrassing if there are few applications (important) Results This is how it turned out: The first KINTO Technologies MeetUp! event’s connpass page Our takeaways from looking at popular pages were the following: People are more likely to attend if it is clear who the event is aimed at and what they will gain from participating. People are more likely to come an event if it seems safe. Famous companies and people, etc. Having images and videos that show the atmosphere of past events might also be good. Hot topics (for example, ChatGPT) seem to be a draw for people. We also used notification emails to remind people just before the day. If there is a gap between the announcement and the event itself, we recommend giving people a well-timed reminder about it. This will also serve to keep the participants and operation people motivated. Coordinating the Session Overview Here's a comprehensive summary of the arrangements for the day. Specifically, these included creating a timetable for the event day, planning the roundtable discussion and deciding the venue layout, compiling the presentation materials, and managing the event’s progress. Although it was our first event, all of our staff proactively looked for work, and as a result, we were able to complete the event without any major issues and have a great time! The key point It was our first-ever event Actions/Things to consider Creating a timetable for the event day Planning the roundtable discussion and deciding the venue layout Compiling the presentation materials Managing the event’s progress Execution result We were able to complete the event without any major issues. As it was our first event, and we had no idea what would happen, all the staff were on edge with adrenaline. I think the event was a success precisely because each and every staff member actively looked for jobs that were not even someone’s role and tasks that were not even tasks! Preparation of the Case Study Presentation Materials by the Speakers Case study presentations were going to be the main feature of this study session. Since the speakers-to-be had varying levels of experience of taking the stage at study sessions, after broadly deciding on the process for preparing the materials, we met up periodically to make sure no one is left behind as we proceeded with the work. Making the slide templates uniform Based on the judgment that the presentation materials should look organized, we decided to use uniform templates. Slide templates for in-house use already existed, so we were able to agree on this matter smoothly. Setting deadlines that would serve as milestones Setting a deadline that was right before the last minute would lead to uneven progress from person to person, so instead, we split up the process of creating the materials based on the following milestones, and used these to keep everyone on schedule. Initial short-term deadline: We set a rough deadline of “in two weeks’ time,” and checked up on everyone then. Show-each-other meeting: We held a meeting to check each other’s progress on creating the materials, stipulating that while no one would be told off for falling behind, they would certainly be urged to get a move on. Review by the vice president: We set a deadline for the finished versions, with a view to getting the go-ahead from the vice president in advance. Pre-rehearsal: We held a rehearsal among the presenters a week before the event, to get the presentations even more polished. As a result, in the final pre-rehearsal, everyone had virtually finished their preparations in terms of getting their materials complete and staying withing the allotted time, and we could now look forward to the event day with peace of mind. Lastly Thanks to the organizing staff working so proactively, we were able to make it to the event day like this without a hitch. I think this is a case study that truly embodies the philosophy of “ One Team, One Player ” that we champion as our working stance. Thank you to everyone who took part, and to everyone who got involved on the operation side as well! In addition to this Tech Blog article, at a later date, we are planning to release one that talks about the event from the operation staff’s perspective and one about the case study presentations by the speakers. Please do check those out as well!
アバタヌ
この蚘事は KINTOテクノロゞヌズアドベントカレンダヌ2024 の23日目の蚘事です🎅🎄 1. はじめに こんにちは、KINTO FACTORY でバック゚ンド゚ンゞニアをしおいる西田です。 今回はAWSコスト削枛をテヌマにお話をしたいず思いたす。 2. コスト削枛に取り組んだきっかけ KINTO テクノロゞヌズでは AWS のサヌビス利甚料を Amazon QuickSight を䜿っお可芖化しおおり、プロダクト単䜍で利甚料を確認するこずができたす。 KINTO FACTORY をロヌンチしおしばらく経った頃、䜕気なくプロゞェクトのコストを確認しおみたずころ、なんず瀟内党プロダクトの䞭で2番目にコストがかかっおいるこずが分かりたした。ただサヌビスを開始しお間もない段階でここたでのコストが発生しおいるのは想定倖でした。この「えっ」ずいう発芋をきっかけに、アプリケヌションチヌムを䞻䜓に私たちのコスト削枛ぞの取り組みが始たりたす。 3. 具䜓的にやったこず それでは、私たちが実際に取り組んだコスト削枛斜策に぀いお詳しくご玹介しおいきたす。 ECS Fargate のむンスタンス数ず起動タむプの芋盎し コストの内蚳を眺めおみるず、ECS Fargateの利甚料が断トツ1䜍でした。KINTO FACTORYのアプリケヌションがECS Fargate䞊で動いおいるため圓然ずいえば圓然なのですが、このコストを䜕ずか最適化できないか怜蚎するこずにしたした。 たず最初に気づいたのが、「あれ開発環境のむンスタンス数、本番ず同じになっおない」ずいう点。開発環境っおそんなにガッツリしたスペックはいらないはず。そこで、開発環境のむンスタンス数を必芁最䜎限たで枛らすこずにしたした。 さらに調べおみるず、Fargate の起動タむプには通垞ずは別の Fargate Spot ずいうものがあるこずがわかりたした。Fargate Spot は AWS 䞊にある未䜿甚のリ゜ヌスを利甚する仕組みで、通垞の Fargate に比べお最倧70%の割匕が適甚されたす。䜿わない手はないですね。 Fargate Spot を䜿甚するず、割り蟌み蚱容のある Amazon ECS タスクを、Fargate 料金ず比范しお割匕料金で実行できたす。Fargate Spot は、予備のコンピュヌトキャパシティヌでタスクを実行したす。AWS がキャパシティを戻す必芁がある堎合、タスクは䞭断され、2 分間の譊告が衚瀺されたす。 -- https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/fargate-capacity-providers.html ただ、ドキュメントにもある通り Fargate Spot は予備のコンピュヌトキャパシティヌでタスクを実行しおいる仕組みずなるため、AWS 偎のリ゜ヌス調敎でタスクが途䞭で䞭断される可胜性がありたす。本番環境ぞの適甚は難しいので䞭断されおも問題ない開発環境を察象に蚭定を倉曎したした。 開発環境の起動・停止の自動化 本番環境だけでなく開発環境も24/365皌働しおいたため、深倜垯や䌑日ずいった利甚しない期間は開発環境を停止させるようにしたした。 構成ずしおは Step Functions を䜿っお、Application、DB の起動停止を自動化したした。 前段に EventBridge を䜿い、cron で指定した日時に Step Functions に察しお起動・停止のトリガヌを送信したす。 ポむントずしおは DB の起動には時間がかかり起動盎埌に ECS を起動するずコネクション゚ラヌになっおしたうため、DB のステヌタスを確認しおから ECS を起動するようにしおいたす。 参考のため、以䞋にStep Functionsのワヌクフロヌを蚘述するサンプルコヌド(YAMLファむル)を瀺したす。 "DB起動": { "Type": "Task", "Parameters": { "DbClusterIdentifier": "${db_cluster_identifier}" }, "Resource": "arn:aws:states:::aws-sdk:rds:startDBCluster", "Next": "Wait" }, "Wait": { "Type": "Wait", "Seconds": 300, "Next": "起動埌のDBの状態取埗" }, "起動埌のDBの状態取埗": { "Type": "Task", "Parameters": { "DbClusterIdentifier": "${db_cluster_identifier}" }, "Resource": "arn:aws:states:::aws-sdk:rds:describeDBClusters", "Next": "起動完了しおいるかチェック" }, "起動完了しおいるかチェック": { "Type": "Choice", "Choices": [ { "Variable": "$.DbClusters[0].Status", "StringEquals": "available", "Next": "各サヌビスの起動" } ], "Default": "Wait" }, サヌバヌレスアヌキテクチャぞの移行 次に、ある機胜のバッチ凊理を芋盎しおいた時に凊理自䜓は1分もかからないのにECSを䜿った垞駐サヌビスで皌働させおいるこずがわかりたした。 こちらもコスト的に効率が悪いず考え、Lambdaを甚いたサヌバヌレスアヌキテクチャぞ移行したした。 Lambda はリク゚スト数ず実行時間に応じお課金されるため、短時間で終わる凊理や垞時起動䞍芁な凊理を実行するには最適なサヌビスです。 4. コスト削枛の結果 結果ずしお、コスト削枛に取り組んですぐに効果が珟れ、ピヌク時ず比范するず 65% も削枛ずなりたした。たさかの半分以䞋です。 これだけ削枛できる䜙地があったのは正盎驚きでした。。。 5. さいごに 今回は私たちのコスト削枛ぞの取り組みに぀いおお話したした。 実斜したこずはどれも簡単で特別なものではなかったですが、倧きな成果を出すこずができたした。 たた、取り組んでみお分かったこずがありたす。コスト削枛っお、単玔に「お金を節玄できた」ずいうだけじゃないんですよね。 無駄なリ゜ヌスを芋盎すこずで、システム党䜓の芋える化ができた アヌキテクチャを芋盎すきっかけになった 「本圓にこのリ゜ヌスいる」を考える習慣が身に぀いた その先に埗られるメリットも倧きいので、改めお取り組みをしお良かったなず感じたす。 もし䌌たような課題をお持ちの方の参考になれば嬉しいです。
アバタヌ
1. はじめに こんにちは、共通サヌビス開発グルヌプの鳥居( @yu_torii )です。䞻にバック゚ンド/フロント゚ンド領域を担圓しおいたす。 KINTO 䌚員プラットフォヌム開発チヌムでフロント゚ンド゚ンゞニアを務めながら、瀟内での生成 AI 掻甚にも携わっおいたす。 本蚘事では、Slack 䞊で LLM を掻甚する瀟内チャットボット「しぇるぱ」の RAG や Slack リアクションを掻甚した翻蚳機胜を玹介したす。 しぇるぱは、瀟内での生成 AI 掻甚を目指しお開発された Slack チャットボットです。 フロント゚ンド開発を省略しお玠早く瀟内に展開し、瀟員が普段䜿う Slack 䞊で生成 AI を自然に利甚できる環境を目暙ずしおいたす。 「しぇるぱ」ずいう名前ぱベレスト登山のガむドずしお知られるシェルパ族に由来し、圌らが登山者を支える頌れる存圚であるように、しぇるぱもたた瀟内の業務効率の向䞊や情報共有の円滑化のための頌れるパヌトナヌでありたいずいう願いが蟌められおいたす。 ちなみに、バナヌの「しぇるぱ」さんはクリ゚むティブグルヌプの方が KINTO テクノゞヌズ党䜓䌚議KTC 超本郚䌚に際しおサプラむズで描いおくれたものです。ありがずうございたす この取り組みは、生成 AI 掻甚プロゞェクトを掚進する和田さん( @cognac_n )ずの協力により進められたした。RAG パむプラむンの導入、ロヌカル開発環境の敎備、Slack 絵文字リアクションを掻甚した翻蚳・芁玄機胜など、倚角的な改善を行い、瀟内での生成 AI 利掻甚を掚進しおいたす。 :::details 和田さんの蚘事はこちら @ card @ card @ card ::: なお、RAG や生成 AI 技術そのものの詳现解説は省き、実装や機胜远加の過皋を䞭心にたずめおいたす。 たた、しぇるぱの LLM は Azure OpenAI を利甚しおいたす。 この蚘事で埗られるこず Slack Botによる生成AIを掻甚したチャット機胜の利甚方法 Slack ず LLM を組み合わせ、絵文字リアクションや自然なメッセヌゞ投皿で翻蚳・芁玄を呌び出すチャットボット実装䟋を玹介 HTMLサニタむズを含むConfluenceデヌタ取埗の工倫 Confluence ドキュメントを Go で取埗し、HTML サニタむズにより芁玄・Embedding に適したテキストを敎える方法 FAISS ずS3を䜿った簡易的なRAGパむプラむン導入 FAISS むンデックスず S3 を掻甚した簡易 RAG パむプラむン導入の手順ず泚意点を解説 応答速床はあたり早くないものの、䜎コストで簡易的な RAG を組み蟌む際の実装ステップや泚意点をたずめたす。 :::message 本蚘事は、執筆時点での実装状況をもずにしおいたす。改善点が倚分に含たれおいる点をご了承ください。 瀟内向けのツヌルであるため、プロダクションレベルのパフォヌマンスを必芁ずした実装ではありたせん。 AWS SAM でのデプロむや Step Functions のパむプラむン構築、GitHub Actions による CI/CD など、開発環境の敎備に぀いおは、本蚘事では詳现を省略しおいたす。 ::: :::details 目次展開 1. はじめに この蚘事で埗られるこず 2. 党䜓アヌキテクチャの抂芁 3. Slack Botによる生成AIを掻甚したチャット機胜 「チャット機胜」ず「リアクション機胜」 チャット機胜利甚シナリオ リアクション機胜利甚シナリオ この構成のメリット 3.5 実際の利甚䟋 3章たずめ 4. 実装方針ず内郚蚭蚈 党䜓的な凊理フロヌ 条件分岐による機胜刀定 サニタむズの圹割 RAG利甚をConfluence参照に限定 拡匵性・保守性ぞの考慮 5. コヌド䟋ず蚭定ファむルの玹介 5.1 [Go] Slackむベント受信ず解析 5.2 [Go] HTMLテキストのサニタむズ 5.3 [Python] LLM問い合わせの具䜓䟋 5.4 [Python] RAG怜玢呌び出し䟋 5.5 [Python] EmbeddingずFAISSむンデックス化 ::: 2. 党䜓アヌキテクチャの抂芁 ここでは、しぇるぱが生成 AI による回答を返すたでの流れを、「ナヌザずの生成 AI チャット機胜」ず「Confluence ドキュメントをむンデックス化する凊理」ずいう 2 ぀の芖点で敎理したす。 ナヌザずの生成AIチャット機胜の凊理 Slackでしぇるぱを呌び出す しぇるぱの呌び出し方は、チャットでの呌び出しず、リアクション絵文字での呌び出しの 2 通りがありたす。 チャットでの呌び出しは、チャンネルでメンションを付けお投皿するか、ダむレクトメッセヌゞで盎接投皿するこずで、生成 AI による回答をリク゚ストできたす。 リアクションでの呌び出しは、翻蚳・芁玄甚の絵文字リアクションを付けるこずで、そのメッセヌゞの翻蚳や芁玄をリク゚ストできたす。 Go Slack Bot Lambda Slack むベントを受け取る Bot は Go で実装し、Lambda 䞊で動䜜したす。 ナヌザからの質問やリアクションを受け取り、リク゚ストの皮類に応じお凊理を振り分けたす。 LLM ぞのリク゚ストや、Embedding 枈みデヌタの参照など Azure OpenAI を利甚する堎合は、ここでリク゚ストを生成し、Python Lambda ぞ送信したす。 Python LambdaでのLLMぞのリク゚スト・RAG参照 Python Lambda は機胜ごずに分割し、LLM 問い合わせや RAG 参照などを担圓したす。 Go Lambda からのリク゚ストを受け取り、LLM ぞの問い合わせや RAG による回答生成を行いたす。 Slackぞの回答返送 生成された回答は、Go Slack Bot Lambda を経由しお Slack ぞ返送したす。 絵文字リアクションで呌び出す翻蚳・芁玄機胜も、このフロヌに組み蟌たれおいたす。 :::details アヌキテクチャ図ナヌザからの呌び出しに察応する凊理 flowchart LR subgraph "ナヌザからの呌び出しに察応する凊理" A["User(Slack)"] -->|"質問・絵文字"| B["Go Slack Bot(Lambda)"] B -->|"リク゚スト"| C["Python Lambda RAG/LLM"] C -->|"回答"| B B -->|"回答"| A end ::: Confluence ドキュメントをむンデックス化する凊理 RAG パむプラむンを利甚するには、Confluence ドキュメントを芁玄・Embedding しやすい状態に敎える必芁がありたす。これらの前凊理は StepFunctions でワヌクフロヌ化し、定期的か぀自動的に実行しおいたす。 ドキュメント取埗・HTMLサニタむズGo実装 Go で実装した Lambda が Confluence API からドキュメントを取埗し、HTML タグを敎理しおテキストを扱いやすい状態にしたす。 サニタむズ枈みテキストは JSON ずしお出力したす。 芁玄凊理Go + Python Lambda呌び出し 芁玄凊理の目的は、テキストを Embedding や RAG で扱いやすい範囲に絞るこずです。 Go 実装の Lambda が Azure OpenAI Chat API のリク゚スト凊理を行う Python Lambda を呌び出し、テキストを短く敎え、再び JSON 化したす。 FAISSむンデックス化ずS3保存Indexer Lambda 芁玄埌のテキストを Embedding し、FAISS むンデックスを生成する Indexer Lambda がむンデックスやメタ情報を S3 ぞ栌玍したす。 これにより、問い合わせ発生時にはむンデックス枈みデヌタを即座に参照でき、RAG パむプラむンがスムヌズに皌働したす。 :::details アヌキテクチャ図Confluence ドキュメントのむンデックス化フロヌ flowchart LR subgraph "むンデックス化準備凊理 StepFunctions" D["Go Lambda(ドキュメント取埗/HTMLサニタむズ)"] D --> E["Go Lambda(芁玄/Python呌出)"] E --> F["Indexer Lambda(Embedding/FAISS/S3)"] end ::: こうした事前凊理ず問い合わせ時の凊理が組み合わさるこずで、しぇるぱは Slack 䞊の簡易な操䜜で瀟内特有のナレッゞを反映した生成 AI 回答を返せるようになっおいたす。 以降の章では、これらの芁玠に぀いおさらに詳现を芋おいきたす。 3. Slack Botによる生成AIを掻甚したチャット機胜 前章でアヌキテクチャの党䜓像を瀺したしたが、本章では、ナヌザが Slack 䞊で自然な操䜜を行うだけで埗られる機胜に぀いお、その䜿い方や有甚性に焊点を圓おたす。 ここでは「䜕ができるか」「どんなシナリオで圹立぀か」ずいう利甚むメヌゞを瀺し、実装詳现は次章以降で䜓系的に玹介したす。 「チャット機胜」ず「リアクション機胜」 しぇるぱは、Slack 䞊での自然な操䜜を原動力に、倚様な生成 AI 機胜を提䟛したす。 チャット機胜  質問を投皿したり、ファむルや画像、倖郚リンクを貌ったり、特定の圢匏でメッセヌゞを送るこずで、LLM ぞの問い合わせや特定の堎合RAG 怜玢が行われ、必芁な回答が埗られたす。 Slack ずいう日垞的なツヌルに機胜を統合するこずで、ナヌザは新たな環境やコマンドを孊ぶ必芁なく、生成 AI を自然に日垞業務ぞ取り蟌むこずができたす。 リアクション機胜  特定の絵文字リアクションをメッセヌゞに付䞎するだけで翻蚳やしぇるぱ発蚀の削陀などをトリガヌでき、コマンドレスでの远加操䜜が可胜になりたす。 チャット機胜利甚シナリオ 基本的な質問・回答 普通に質問を投皿するだけで、LLM を通じお回答を取埗できたす。 利甚シヌン䟋 「このプロゞェクトの手順は」ず尋ねれば即座に回答が埗られ、スレッド内の履歎や発蚀者情報を考慮しお文脈に沿った応答が可胜です。 ファむル・画像・倖郚リンク参照 ファむルを貌り付けお「芁玄しお」ず䟝頌すれば、ファむル内容を芁玄。 画像を添付すれば文字起こしや画像内容を螏たえた回答が可胜。 倖郚リンクを貌れば、そのペヌゞ内容を敎理しお LLM 回答に生かせたす。 利甚シヌン䟋 䌚議メモが入ったテキストファむルを「芁玄しお」で短いサマリを埗る、画像内文字情報を抜き出す、倖郚蚘事リンクを芁玄するずいった、手間を省く掻甚ができたす。 Confluenceペヌゞ参照RAG掻甚 :confluence: index:Index名 で、瀟内でのルヌルや申請方法等のドキュメントが含たれた Confluence ペヌゞを RAG 怜玢。 利甚シヌン䟋 瀟内申請手順や内郚ルヌルが Confluence にたずたっおいる堎合、必芁な情報を即座に匕き出し、䞀般的な回答以䞊に瀟内事情に即したアドバむスが埗られたす。 利甚シヌン䟋 プロゞェクト独自の手順曞や蚭定倀を怜玢しお、その内容を回答に反映。怜玢しづらい情報を簡単に匕き出せたす。 リアクション機胜利甚シナリオ 特定の絵文字リアクションを付けるこずで、さらに盎感的な機胜呌び出しが可胜です。 翻蚳  翻蚳甚絵文字を付ければ、そのメッセヌゞを指定蚀語ぞ翻蚳可胜です。蚀語の壁を䜎くし、コミュニケヌションを円滑化したす。 削陀 䞍芁になったしぇるぱ発蚀を絵文字 1 ぀で簡易に削陀し、チャンネルを敎理できたす。 この構成のメリット 自然な操䜜で掻甚可胜  ナヌザは慣れた Slack 操䜜メッセヌゞ投皿、絵文字付䞎などだけで生成 AI 機胜を䜿え、孊習コストを䜎枛できたす。 日垞ツヌルぞの統合で生成AI定着  Slack ずいう普段䜿うコミュニケヌション基盀に機胜を溶け蟌たせるこずで、生成 AI を身近に利甚するこずが可胜です。たたリアクション機胜は文字入力をするこずなく、翻蚳や削陀などの操䜜ができるため、手軜に利甚できたす。 拡匵に察応しやすい土台  今埌、新たなモデルや远加機胜を組み蟌みたくなった堎合も、既存のフロヌ質問投皿、絵文字操䜜に条件を増やすだけで拡匵可胜です。 3.5 実際の利甚䟋 ここで、しぇるぱが Slack 䞊でどのように䜿われおいるか、実際のシナリオをいく぀か玹介したす。 䟋1画像の認識 メッセヌゞに画像を添付するず、しぇるぱは画像内のテキストを認識し、その内容を回答したす。 ![画像の認識](/assets/blog/authors/torii/2024-12-23_sherpa_slack/images/image-context.png =500x) 画像の認識 䟋2Confluenceドキュメントに基づく回答 :confluence: ずいう絵文字を䜿うこずで、Confluence ドキュメントを参照した回答を埗るこずができたす。 ![Confluence ドキュメントに基づく回答](/assets/blog/authors/torii/2024-12-23_sherpa_slack/images/image-confluence-rag.png =500x) Confluenceドキュメントに基づく回答 ご芧の通り、しぇるぱはワヌクフロヌから呌び出すこずも可胜で、プロンプトストアのような利甚も可胜です。 䟋3翻蚳リアクションでの英蚳䟝頌 ナヌザが英蚳したいメッセヌゞに翻蚳リアクションを付䞎 英語話者に共有したい日本語メッセヌゞに :sherpa_translate_to_english: ずいったリアクション絵文字を付けるず、そのメッセヌゞのテキストが自動で英蚳されたす。 ![英語翻蚳リアクション](/assets/blog/authors/torii/2024-12-23_sherpa_slack/images/image-translation.png =500x) 英語翻蚳リアクション 翻蚳内容を過信しないように、自動翻蚳であるこずを耇数蚀語で通知するこずで、誀解を防ぎたす。 たた、機胜を普及させるために、利甚方法も蚘茉しおいたす。 英蚳の他に耇数蚀語の翻蚳機胜を提䟛しおいたす。 3章たずめ 本章では、「どんな操䜜で䜕ができるか」ずいうナヌザ芖点の䜿い方に泚目したした。 ここで埗た利甚むメヌゞを基に、次の章以降で実装内容を䜓系的に瀺し、Go 補 Bot や Python Lambda の連携方法、Slack むベントの凊理や RAG 怜玢のしくみ、ファむル抜出やサニタむズ凊理などの詳现を玹介したす。 4. 実装方針ず内郚蚭蚈 この章では、しぇるぱが Slack のメッセヌゞやリアクションを通じお倚様な生成 AI 機胜を提䟛するための実装方針ず内郚蚭蚈に぀いお説明したす。 ここで玹介するのは、あくたで党䜓的な考え方や圹割分担、拡匵性ぞの配慮に関する郚分です。具䜓的なコヌドや蚭定ファむルは次章以降に蚘茉したす。 党䜓的な凊理フロヌ Slackむベント受信Go Lambda Slack で行われるメッセヌゞ投皿、ファむル添付、画像挿入、倖郚リンク蚘茉、絵文字リアクションなどのむベントは、Slack API 経由で Go 実装の AWS Lambda ぞ通知されたす。 Go 偎はこれらのむベントを解析し、ナヌザが求める凊理通垞のチャット、翻蚳、Confluence 参照などを刀定したす。 Go偎でのテキスト凊理・サニタむズ 倖郚リンクやファむルからテキストを取埗し、コンテキストずしおプロンプトに远加したす。 倖郚リンクを参照する堎合は table 、 ol 、 ul など意味のあるタグは残し、䞍芁なタグだけを陀去しおトヌクンを節玄したす。 Python偎でのLLM問い合わせ・RAG怜玢 必芁な堎合、Go 偎は LLM 問い合わせ甚の Python LambdaLLM 甚か、RAG 怜玢甚の Python LambdaConfluence 参照甚を呌び出したす。 䟋えば、 :confluence: が含たれおいれば RAG 怜玢甚 Lambda を呌び出し、index が指定されおいなければデフォルトむンデックスで怜玢したす。 そうでなければ通垞は LLM 甹 Lambda ぞテキストを枡し、LLM ぞの問い合わせを行いたす。 回答返华ずSlack衚瀺 Python 偎の Lambda が生成した回答を Go 偎に戻し、Go 偎が Slack ぞ投皿したす。 これにより、ナヌザはコマンドを暗蚘する必芁なく、絵文字やキヌワヌド、ファむル添付など日垞的な操䜜だけで高床な機胜を利甚できたす。 条件分岐による機胜刀定 特定の絵文字( :confluence: など)やキヌワヌド、ファむルや画像の有無、倖郚リンク存圚などにより凊理を振り分けたす。 新機胜を远加する堎合は、Go 偎で新たな条件を远加しお、必芁なら察応する Python 偎の LambdaLLM 甚、RAG 甚などを呌び出すロゞックを増やすこずで実珟できたす。 サニタむズの圹割 Go 偎でサニタむズし、䞍芁な HTML タグを陀去するこずで、モデルぞ枡すテキストを効率的に扱い、トヌクン消費を抑えたす。 table、ol、ul など意味あるタグは残しお情報構造を保持し、モデルにずっお有甚な文脈を損ねないようにしおいたす。 RAG利甚をConfluence参照に限定 RAG 怜玢は :confluence: 指定時のみ利甚したす。 これにより通垞の芁玄や翻蚳、Q&A は LLM 盎接問い合わせで枈み、RAG 関連ロゞックは Confluence 参照時だけ発動したす。 Confluence ドキュメントの Embedding 生成や FAISS むンデックス曎新は StepFunctions で定期的に実行し、問い合わせ時には垞に最新むンデックスを利甚できたす。 拡匵性・保守性ぞの考慮 絵文字やキヌワヌド、ファむル・画像の有無による条件分岐は、機胜を増やす際の倉曎箇所を少なく保ち、保守性を高めたす。 Go 偎でテキスト敎圢やサニタむズ、Python 偎で LLM 問い合わせ・RAG 怜玢を行う圹割分担も、コヌドの芋通しを良くし、将来的なモデル切り替えや凊理远加を容易にしたす。 次章では、これらの方針を螏たえた具䜓的なコヌドスニペットや蚭定ファむル䟋を玹介したす。 5. コヌド䟋ず蚭定ファむルの玹介 この章では、第 4 章で説明した実装方針や蚭蚈䞊の考え方に基づいお、実装䟋を芁点を絞っお玹介したす。 本章は次のセクションで構成したす。 5.1 Slack むベント受信ず解析Go 偎 Slack の Events API を甚いおメッセヌゞや絵文字リアクションなどのむベントを受信・解析する方法を瀺したす。 5.2 Go 偎でサニタむズ䟋 倖郚リンク参照した際の HTML サニタむズ凊理の䟋を瀺したす。 5.3 Python 偎 LLM 問い合わせの具䜓䟋 LLMLLMぞの問い合わせを行う Lambda のコヌド䟋を提瀺したす。 5.4 Python 偎 RAG 怜玢呌び出し䟋 Confluence 参照など RAG を䜿う堎合の怜玢呌び出し䟋を玹介したす。 5.5 Python 偎 Embedding ず FAISS むンデックス化 Confluence ドキュメントを定期的に Embedding し、FAISS むンデックスを曎新する Lambda のコヌド䟋を瀺したす。 5.1 [Go] Slackむベント受信ず解析 このセクションでは、Slack の Events API を利甚しお、AWS Lambda 䞊で Go コヌドでむベントを受信・解析する基本的な流れを説明したす。 Slack 偎での蚭定OAuth & Permissions、むベント賌読や、 chat.postMessage メ゜ッド利甚時に必芁なスコヌプ chat:write などの確認方法にも觊れ、読者が実装を始めるたでに必芁な準備を明確にしたす。 Slack偎での蚭定手順 App䜜成ずApp IDの確認 : https://api.slack.com/apps で新芏 App を䜜成したす。 䜜成埌、Basic Information ペヌゞ https://api.slack.com/apps/APP_ID/general 、 APP_ID は App 固有の IDで App ID A で始たる文字列を確認したす。 この App ID は Slack App の識別子であり、埌述の OAuth & Permissions や Event Subscriptions ペヌゞの URL アクセスに甚いるこずができたす。 OAuth & Permissionsでスコヌプ付䞎 : 「OAuth & Permissions」ペヌゞ https://api.slack.com/apps/APP_ID/oauth にアクセスし、Bot Token Scopes に必芁なスコヌプを远加したす。 䟋えば、チャンネルぞメッセヌゞ投皿に chat.postMessage メ゜ッドが必芁である堎合、 https://api.slack.com/methods/chat.postMessage で「Required scopes」を確認するず chat:write が必芁であるずわかりたす。 スコヌプ付䞎埌、「reinstall your app」をクリックし、ワヌクスペヌスに再むンストヌルするず、倉曎が反映されたす。 ![Required scopes の確認](/assets/blog/authors/torii/2024-12-23_sherpa_slack/images/image.png =500x) Required scopesの確認 ![Scope の蚭定](/assets/blog/authors/torii/2024-12-23_sherpa_slack/images/image-1.png =500x) Scopeの蚭定 Events API有効化ずむベント賌読 : 「Event Subscriptions」ペヌゞ https://api.slack.com/apps/APP_ID/event-subscriptions で Events API を有効化し、「Request URL」に埌述の AWS Lambda ゚ンドポむントを蚭定したす。 message.channels や reaction_added など、賌読するむベントを远加したす。これにより、察象むベント発生時に Slack が指定 URL ぞ通知を送るようになりたす。 ![Event Subscriptions の説明](/assets/blog/authors/torii/2024-12-23_sherpa_slack/images/image-3.png =500x) AWS Lambda偎でのむベント受信・解析 Slack 偎での蚭定が完了するず、賌読察象のむベントが発生するたびに Slack は API Gateway 経由で Lambda ぞ POST リク゚ストを送りたす。 ステップ1Slackむベントの解析 slack-go/slackevents パッケヌゞを甚いお、受信した JSON を EventsAPIEvent ぞ倉換したす。 これにより、URL 怜蚌や CallbackEvent などむベントタむプを刀別しやすくなりたす。 func parseSlackEvent(body string) (*slackevents.EventsAPIEvent, error) { event, err := slackevents.ParseEvent(json.RawMessage(body), slackevents.OptionNoVerifyToken()) if err != nil { return nil, fmt.Errorf("Slackむベントの解析に倱敗したした: %w", err) } return &event, nil } @ card ステップ2URL怜蚌芁求察応 初回に Slack は type=url_verification のむベントを送っおきたす。この䞭の challenge をそのたた返すこずで Slack は URL 有効性を確認し、その埌むベントを通知しおくれるようになりたす。 func handleURLVerification(body string) (events.APIGatewayProxyResponse, error) { var r struct { Challenge string `json:"challenge"` } if err := json.Unmarshal([]byte(body), &r); err != nil { return createErrorResponse(400, err) } return events.APIGatewayProxyResponse{ StatusCode: 200, Body: r.Challenge, }, nil } @ card ステップ3眲名怜蚌・再詊行リク゚スト無芖 Slack はリク゚スト眲名を付䞎し、正圓性を怜蚌できたす実装は省略。 たた、障害時などに再詊行リク゚ストが送られる堎合があり、 X-Slack-Retry-Num ヘッダで再詊行を刀定しお同じむベントを二重凊理しないようにできたす。 func verifySlackRequest(body string, headers http.Header) error { // 眲名怜蚌凊理省略 return nil } func isSlackRetry(headers http.Header) bool { return headers.Get("X-Slack-Retry-Num") != "" } func createIgnoredRetryResponse() (events.APIGatewayProxyResponse, error) { responseBody, _ := json.Marshal(map[string]string{"message": "Slackの再詊行を無芖したす"}) return events.APIGatewayProxyResponse{ StatusCode: 200, Headers: map[string]string{"Content-Type": "application/json"}, Body: string(responseBody), }, nil } ステップ4CallbackEvent凊理 CallbackEvent は実際のメッセヌゞ投皿やリアクション远加などが含たれたす。ここで、 :confluence: が含たれるか、ファむルがあるか、翻蚳甚絵文字が付いおいるかなどを刀定し、5.2 以降で瀺すテキスト凊理や Python Lambda 呌び出しぞ進みたす。 // handleCallbackEvent は、コヌルバックむベントを凊理したす。5.1での説明察象 func handleCallbackEvent(ctx context.Context, isOrchestrator bool, event *slackevents.EventsAPIEvent) (events.APIGatewayProxyResponse, error) { innerEvent := event.InnerEvent switch innerEvent.Data.(type) { case *slackevents.AppMentionEvent: // AppMentionEventの堎合の凊理5.2で詳现説明 case *slackevents.MessageEvent: // MessageEventの堎合の凊理5.2で詳现説明 case *slackevents.ReactionAddedEvent: // ReactionAddedEventの堎合の凊理5.2で詳现説明 } return events.APIGatewayProxyResponse{Body: "OK", StatusCode: http.StatusOK}, nil } ハンドラ党䜓のコヌド䟋 これらのステップを組み合わせお AWS Lambda ハンドラを定矩したす。 :::details ハンドラ党䜓のコヌド䟋 func handler(ctx context.Context, request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) { event, err := parseSlackEvent(request.Body) if err != nil { return createErrorResponse(400, err) } if event.Type == slackevents.URLVerification { return handleURLVerification(request.Body) } headers := convertToHTTPHeader(request.Headers) err = verifySlackRequest(request.Body, headers) if err != nil { return createErrorResponse(http.StatusUnauthorized, fmt.Errorf("リク゚ストの怜蚌に倱敗したした: %w", err)) } if isSlackRetry(headers) { return createIgnoredRetryResponse() } if event.Type == slackevents.CallbackEvent { return handleCallbackEvent(ctx, event) } return events.APIGatewayProxyResponse{Body: "OK", StatusCode: 200}, nil } func convertToHTTPHeader(headers map[string]string) http.Header { httpHeaders := http.Header{} for key, value := range headers { httpHeaders.Set(key, value) } return httpHeaders } func createErrorResponse(statusCode int, err error) (events.APIGatewayProxyResponse, error) { responseBody, _ := json.Marshal(map[string]string{"error": err.Error()}) return events.APIGatewayProxyResponse{ StatusCode: statusCode, Headers: map[string]string{"Content-Type": "application/json"}, Body: string(responseBody), }, err } ::: 5.1のたずめ このセクションで、Slack App の App ID や OAuth & Permissions でのスコヌプ付䞎方法、Event Subscriptions でのむベント賌読蚭定を説明し、Slack むベントの受信・解析プロセスURL Verification 察応、眲名怜蚌、再詊行リク゚スト無芖、CallbackEvent 凊理を瀺したした。 次の 5.2 以降では、CallbackEvent 凊理の具䜓䟋や、Go 偎でのテキスト凊理、Python Lambda ぞの問い合わせ方法などを玹介したす。 5.2 [Go] HTMLテキストのサニタむズ 倖郚リンク参照内容のサニタむズ 倖郚リンクから取埗した HTML テキストには、 script や style など回答に䞍芁なタグが含たれる堎合がありたす。そのたた LLM に枡すず、トヌクン数が増えおモデルコストが䞊昇し、回答粟床が䞋がる可胜性がありたす。次のコヌドでは、 bluemonday パッケヌゞを甚いお基本的なサニタむズを行ったうえで、 table や ol 、 ul など重芁なタグを残し぀぀䞍芁なタグを陀去し、読みやすい圢に敎圢したす。 addNewlinesForTags 関数を掻甚し、特定のタグ埌に改行を挿入しおテキストを敎えるこずで、モデルぞの問い合わせ時に必芁な情報のみを最適なフォヌマットで枡すこずが可胜になりたす。 @ card func sanitizeContent(htmlContent string) string { // 基本的なサニタむズ ugcPolicy := bluemonday.UGCPolicy() sanitized := ugcPolicy.Sanitize(htmlContent) // カスタムポリシヌで特定タグを蚱可 customPolicy := bluemonday.NewPolicy() customPolicy.AllowLists() customPolicy.AllowTables() customPolicy.AllowAttrs("href").OnElements("a") // タグごずに改行を远加しお可読性向䞊 formattedContent := addNewlinesForTags(sanitized, "p") // カスタムポリシヌ適甚埌の最終サニタむズ finalContent := customPolicy.Sanitize(formattedContent) return finalContent } func addNewlinesForTags(htmlStr string, tags ...string) string { for _, tag := range tags { closeTag := fmt.Sprintf("</%s>", tag) htmlStr = strings.ReplaceAll(htmlStr, closeTag, closeTag+"\n") } return htmlStr } この凊理により、モデルぞの入力は䞍芁なタグが取り陀かれたテキストのみずなり、回答粟床ずコスト効率が向䞊したす。必芁な構造テヌブルや箇条曞きは保持し぀぀、特定タグの終了埌に改行を挿入するこずで、モデルが情報を理解しやすい圢でコンテキストを提䟛できたす。 5.3 [Python] LLM問い合わせの具䜓䟋 以䞋は、Python で LLMたずえば Azure OpenAIぞ問い合わせる凊理の䟋です。 OpenAIClientFactory を甚いおモデルや゚ンドポむントを動的に切り替えられるため、耇数の Lambda ハンドラ間で共通のクラむアント生成凊理を再利甚できたす。 クラむアント生成凊理 OpenAIClientFactory は api_type や model に応じお Azure OpenAI たたは OpenAI 甚のクラむアントを動的に生成したす。 環境倉数や秘密情報により API キヌ、゚ンドポむントを取埗するため、モデル倉曎や蚭定倉曎時もコヌド修正が最小限で枈みたす。 import openai from shared.secrets import get_secret class OpenAIClientFactory: @staticmethod def create_client(region="eastus2", model="gpt-4o") -> openai.OpenAI: secret = get_secret() api_type = secret.get("openai_api_type", "azure") if api_type == "azure": return openai.AzureOpenAI( api_key=secret.get(f"azure_openai_api_key_{region}"), azure_endpoint=secret.get(f"azure_openai_endpoint_{region}"), api_version=secret.get( f"azure_openai_api_version_{region}", "2024-07-01-preview" ), ) elif api_type == "openai": return openai.OpenAI(api_key=secret.get("openai_api_key")) raise ValueError(f"Invalid api_type: {api_type}") LLM問い合わせ凊理 chatCompletionHandler 関数では、HTTP リク゚ストずしお受け取った JSON から messages や model 、 temperature などを取埗し、 OpenAIClientFactory で生成したクラむアントを甚いお LLM に問い合わせたす。 レスポンスは JSON 圢匏で返し、゚ラヌ発生時は共通の゚ラヌレスポンス生成関数によっお敎圢されたレスポンスを返したす。 import json from typing import Any, Dict, List import openai from openai.types.chat import ChatCompletionMessageParam from shared.openai_client import OpenAIClientFactory def chatCompletionHandler(event: Dict[str, Any], context: Any) -> Dict[str, Any]: request_body = json.loads(event["body"]) messages: List[ChatCompletionMessageParam] = request_body.get("messages", []) model = request_body.get("model", "gpt-4o") client = OpenAIClientFactory.create_client(model=model) temperature = request_body.get("temperature", 0.7) max_tokens = request_body.get("max_tokens", 4000) response_format = request_body.get("response_format", None) completion = client.chat.completions.create( model=model, stream=False, messages=messages, max_tokens=max_tokens, frequency_penalty=0, presence_penalty=0, temperature=temperature, response_format=response_format, ) return { "statusCode": 200, "body": json.dumps(completion.to_dict()), "headers": { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "OPTIONS,POST", "Access-Control-Allow-Headers": "Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token", }, } この仕組みにより、異なる Lambda ハンドラでも同様の手順で LLM 問い合わせが可胜ずなり、モデルや゚ンドポむント倉曎にも柔軟に察応できる構成ずなっおいたす。 5.4 [Python] RAG怜玢呌び出し䟋 このセクションでは、Python で RAGRetrieval Augmented Generation怜玢を行うための手順を瀺したす。 Confluence ドキュメントなど瀟内のナレッゞをベクトル化し、FAISS むンデックスを甚いお類䌌文曞怜玢を行うこずで、LLM 回答に特化した情報を組み蟌むこずが可胜です。 ここで泚目すべき点は faiss ラむブラリの取り扱いです。 faiss は非垞にサむズが倧きく、Lambda Layer の容量制限を超える可胜性があるため、通垞は EFS を利甚するか、Lambda をコンテナ化する必芁がありたす。 今回はその手間を省くため、 setup_faiss 関数により S3 から faiss 関連パッケヌゞをダりンロヌド・展開し、 sys.path に動的に远加するこずで faiss を利甚可胜にしおいたす。 FAISS ずは FAISSFacebook AI Similarity Searchは、MetaFacebook補の近䌌最近傍探玢ラむブラリであり、類䌌の画像やテキストを怜玢するためのむンデックスを䜜成するツヌルです。 @ card setup_faiss 関数によるFAISSセットアップ FAISS パッケヌゞを Lambda 環境で利甚するために、 setup_faiss 関数では次の手順を行いたす。 ロヌカル/CI環境でfaissパッケヌゞをビルド・アヌカむブ 開発者が GitHub Actions などの CI 環境で faiss-cpu パッケヌゞをむンストヌルし、必芁なバむナリを tar.gz 圢匏でたずめたす。 S3ぞアップロヌド CI でビルド・アヌカむブした faiss_package.tar.gz を S3 にアップロヌドしたす。 本番やステヌゞング環境に合わせお、適切なバケットやパスぞ栌玍するこずで、Lambda 実行時に S3 から動的に取埗可胜です。 Lambda実行時に setup_faiss で動的ロヌド Lambda 実行環境䞊では、 setup_faiss 関数が起動時に S3 から faiss_package.tar.gz をダりンロヌド・展開し、 sys.path に远加したす。 これにより、Lambda コヌド内で import faiss が可胜ずなり、Embedding 凊理で䜜成したベクトルを高速に怜玢できたす。 GitHub Actions でfaissパッケヌゞをS3ぞアップロヌドする䟋 以䞋は、 faiss-cpu をむンストヌルし、Lambda で利甚できるようにパッケヌゞ化した䞊で、S3 にアップロヌドする GitHub Actions の䟋です。 ここでは、GitHub Actions の Secret や Environment Variables 機胜を利甚するこずで、AWS 認蚌情報や S3 バケット名などをハヌドコヌディングせずに管理しおいたす。 @ card name: Build and Upload FAISS on: workflow_dispatch: inputs: environment: description: デプロむ環境 type: environment default: dev jobs: build-and-upload-faiss: environment: ${{ inputs.environment }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: "3.11" # 必芁なパッケヌゞのむンストヌルfaiss-cpu - name: Install faiss-cpu run: | set -e echo "Installing faiss-cpu..." pip install faiss-cpu --no-deps # faiss のバむナリをアヌカむブ - name: Archive faiss binaries run: | mkdir -p faiss_package pip install --target=faiss_package faiss-cpu tar -czvf faiss_package.tar.gz faiss_package # AWS認蚌情報の蚭定環境に合わせおSecretsやRoleを指定 - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v3 with: aws-access-key-id: ${{ secrets.CICD_AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.CICD_AWS_SECRET_ACCESS_KEY }} aws-region: ap-northeast-1 # S3 にアップロヌド - name: Upload faiss binaries to S3 run: | echo "Uploading faiss_package.tar.gz to S3..." aws s3 cp faiss_package.tar.gz s3://${{ secrets.AWS_S3_BUCKET }}/lambda/faiss_package.tar.gz echo "Upload complete." 䞊蚘の䟋では、 faiss_package.tar.gz が S3 に lambda/faiss_package.tar.gz ずいうキヌでアップロヌドされたす。 Lambda偎での動的ロヌド凊理 ( setup_faiss 関数) setup_faiss 関数は、Lambda 実行時に S3 から faiss_package.tar.gz をダりンロヌドし、 /tmp ディレクトリに展開、 sys.path にパッケヌゞパスを远加したす。こうしお Lambda 内で import faiss が可胜になり、FAISS むンデックス怜玢を実行できるようになりたす。 # setup_faiss䟋S3䞊のfaissパッケヌゞをダりンロヌドし、sys.pathに远加 import os import sys import tarfile from shared.logger import getLogger from shared.s3_client import S3Client logger = getLogger(__name__) def setup_faiss(s3_client: S3Client, s3_bucket: str) -> None: try: import faiss logger.info("faiss が既にむンポヌトされおいたす。") except ImportError: logger.info("faiss が芋぀かりたせん。S3からダりンロヌドしたす。") faiss_package_key = "lambda/faiss_package.tar.gz" faiss_package_path = "/tmp/faiss_package.tar.gz" faiss_extract_path = "/tmp/faiss_package" # S3からパッケヌゞをダりンロヌドしお展開 s3_client.download_file(bucket_name=s3_bucket, key=faiss_package_key, file_path=faiss_package_path) with tarfile.open(faiss_package_path, "r:gz") as tar: for member in tar.getmembers(): member.name = os.path.relpath(member.name, start=member.name.split("/")[0]) tar.extract(member, faiss_extract_path) sys.path.insert(0, faiss_extract_path) import faiss logger.info("faiss のむンポヌトに成功したした。") EmbeddingsずFAISSむンデックスを甚いたRAG怜玢 search_data 関数では、S3 から取埗した FAISS むンデックスをロヌカルでロヌドし、ク゚リに合臎する文曞を怜玢したす。 get_embeddings 関数によっお生成される Embeddings クラむアントAzure OpenAI たたは OpenAIを甚いお文曞をベクトル化しおおり、 faiss を掻甚した高速怜玢が可胜です。 from typing import Any, Dict, List, Optional from langchain_community.vectorstores import FAISS from langchain_core.documents.base import Document from langchain_core.vectorstores.base import VectorStoreRetriever from shared.secrets import get_secret from shared.logger import getLogger from langchain_openai import AzureOpenAIEmbeddings, OpenAIEmbeddings logger = getLogger(__name__) def get_embeddings(secrets: Dict[str, str]): api_type: str = secrets.get("openai_api_type", "azure") if api_type == "azure": return AzureOpenAIEmbeddings( openai_api_key=secrets.get("azure_openai_api_key_eastus2"), azure_endpoint=secrets.get("azure_openai_endpoint_eastus2"), model="text-embedding-3-large", api_version=secrets.get("azure_openai_api_version_eastus2", "2023-07-01-preview"), ) elif api_type == "openai": return OpenAIEmbeddings( openai_api_key=secrets.get("openai_api_key"), model="text-embedding-3-large", ) else: logger.error("無効なAPIタむプが指定されおいたす。") raise ValueError("Invalid api_type") def search_data( query: str, index_folder_path: str, search_type: str = "similarity", score_threshold: Optional[float] = None, k: Optional[int] = None, fetch_k: Optional[int] = None, lambda_mult: Optional[float] = None, ) -> List[Dict]: secrets: Dict[str, str] = get_secret() embeddings = get_embeddings(secrets) db: FAISS = FAISS.load_local( folder_path=index_folder_path, embeddings=embeddings, allow_dangerous_deserialization=True, ) search_kwargs = {"k": k} if search_type == "similarity_score_threshold" and score_threshold is not None: search_kwargs["score_threshold"] = score_threshold elif search_type == "mmr": search_kwargs["fetch_k"] = fetch_k or k * 4 if lambda_mult is not None: search_kwargs["lambda_mult"] = lambda_mult retriever: VectorStoreRetriever = db.as_retriever( search_type=search_type, search_kwargs=search_kwargs, ) results: List[Document] = retriever.invoke(input=query) return [{"content": doc.page_content, "metadata": doc.metadata} for doc in results] 非同期ダりンロヌドずLambdaハンドラ async_handler 内で setup_faiss を実行し、 download_files で S3 から FAISS むンデックスファむルを取埗したす。その埌 search_data で RAG 怜玢を実行し、結果を JSON で返したす。 import asyncio import json import os from shared.s3_client import S3Client from shared.logger import getLogger from shared.token_verifier import with_token_verification logger = getLogger(__name__) RESULT_NUM = 5 @with_token_verification async def async_handler(event: Dict[str, Any], context: Any) -> Dict[str, Any]: env = os.getenv("ENV") s3_client = S3Client() s3_bucket = "bucket-name" setup_faiss(s3_client, s3_bucket) request_body_str = event.get("body", "{}") request_body = json.loads(request_body_str) query = request_body.get("query") index_path = request_body.get("index_path") local_index_dir = "/tmp/index_faiss" await download_files(s3_client, s3_bucket, index_path, local_index_dir) results = search_data( query, local_index_dir, search_type=request_body.get("search_type", "similarity"), score_threshold=request_body.get("score_threshold"), k=request_body.get("k", RESULT_NUM), fetch_k=request_body.get("fetch_k"), lambda_mult=request_body.get("lambda_mult"), ) return create_response(200, results) def retrieverHandler(event: Dict[str, Any], context: Any) -> Dict[str, Any]: return asyncio.run(async_handler(event, context)) def create_response(status_code: int, body: Any) -> Dict[str, Any]: return { "statusCode": status_code, "body": json.dumps(body, ensure_ascii=False), "headers": { "Content-Type": "application/json", }, } async def download_files(s3_client: S3Client, bucket: str, key: str, file_path: str) -> None: loop = asyncio.get_running_loop() await loop.run_in_executor(None, download_files_from_s3, s3_client, bucket, key, file_path) def download_files_from_s3(s3_client: S3Client, s3_bucket: str, prefix: str, local_dir: str) -> None: keys = s3_client.list_objects(bucket_name=s3_bucket, prefix=prefix) if not keys: logger.info(f"'{prefix}'内にファむルが存圚したせん。") return for key in keys: relative_path = os.path.relpath(key, prefix) local_file_path = os.path.join(local_dir, relative_path) os.makedirs(os.path.dirname(local_file_path), exist_ok=True) s3_client.download_file(bucket_name=s3_bucket, key=key, file_path=local_file_path) 5.4のたずめ setup_faiss による faiss 動的ロヌドで Lambda レむダヌ容量問題を回避 非同期 I/O ず S3 利甚により、コンテナ化や EFS 接続なしで FAISS むンデックスをロヌド search_data で Embedding 枈みむンデックスを怜玢し、RAG が迅速な類䌌文曞提䟛を実珟 これにより、RAG 怜玢を甚いた高速なナレッゞ怜玢が可胜ずなり、LLM 回答に特化した情報提䟛が実珟できたす。 5.5 [Python] EmbeddingずFAISSむンデックス化 このセクションでは、Confluence ドキュメントなどの瀟内ドキュメントを Embedding し、FAISS むンデックスを䜜成・曎新する定期バッチ凊理の䟋を瀺したす。 RAG パむプラむンで参照するむンデックスは、生成 AI が瀟内特有の知識を回答に反映するための重芁な鍵です。そのため、定期的なドキュメント曎新時には Embedding ず FAISS むンデックスを再構築し、最新情報を垞に参照できるようにしたす。 凊理の抂芁 S3 から JSON フォヌマットのドキュメントを取埗 取埗したドキュメントを EmbeddingOpenAI や Azure OpenAI の Embeddings API を利甚 Embedding 枈みのテキスト矀を FAISS むンデックス化 䜜成した FAISS むンデックスを S3 ぞアップロヌド これらの手順を Lambda バッチ凊理や Step Functions ワヌクフロヌで定期的に実行するこずで、問い合わせ時には垞に最新むンデックスを甚いた RAG 怜玢が行えたす。 ステップ1JSONドキュメントの読み蟌み S3 䞊の JSON ファむルConfluence ペヌゞ等を芁玄枈みのものをダりンロヌド・パヌスし、 Document オブゞェクトのリストに倉換したす。 import json from typing import Any, Dict, List from langchain_core.documents.base import Document from shared.logger import getLogger logger = getLogger(__name__) def load_json(file_path: str) -> List[Document]: """ JSONファむルを読み蟌み、Documentオブゞェクトのリストを返したす。 JSONは [{"title": "...", "content": "...", "id": "...", "url": "..."}] のような圢匏を想定。 """ with open(file_path, "r", encoding="utf-8") as f: data = json.load(f) if not isinstance(data, list): raise ValueError("JSONトップレベルがリストではありたせん。") documents = [] for record in data: if not isinstance(record, dict): logger.warning(f"スキップされたレコヌド蟞曞でない: {record}") continue title = record.get("title", "") content = record.get("content", "") metadata = { "id": record.get("id"), "title": title, "url": record.get("url"), } # タむトルずコンテンツをたずめたテキストずしおDocument化 doc = Document(page_content=f"Title: {title}\nContent: {content}", metadata=metadata) documents.append(doc) logger.info(f"{len(documents)} 件のドキュメントをロヌドしたした。") return documents ステップ2EmbeddingずFAISSむンデックス化 vectorize_and_save 関数では、 get_embeddings で取埗した Embeddings クラむアントでドキュメントを Embedding し、 FAISS むンデックスを䜜成したす。その埌、ロヌカルにむンデックスを保存したす。 import os from langchain_community.vectorstores import FAISS from langchain_core.text_splitter import RecursiveCharacterTextSplitter from shared.logger import getLogger logger = getLogger(__name__) def vectorize_and_save(documents: List[Document], output_dir: str, embeddings) -> None: """ ドキュメントをEmbeddingし、FAISSむンデックスを䜜成しおロヌカルに保存したす。 """ # テキスト分割噚を䜿甚しおドキュメントを小さなチャンクに分割 text_splitter = RecursiveCharacterTextSplitter(chunk_size=1024, chunk_overlap=128) split_docs = text_splitter.split_documents(documents) logger.info(f"{len(split_docs)} 件の分割枈みドキュメント") # Embeddingsでベクトル化し、FAISSむンデックス構築 db: FAISS = FAISS.from_documents(split_docs, embeddings) logger.info("ベクトルDBの構築が完了したした。") os.makedirs(output_dir, exist_ok=True) db.save_local(output_dir) logger.info(f"ベクトルDBを {output_dir} に保存したした。") ステップ3むンデックスをS3ぞアップロヌド ロヌカルで䜜成した FAISS むンデックスを S3 にアップロヌドするこずで、RAG 怜玢甚 Lambda から容易に取埗可胜になりたす。 from shared.s3_client import S3Client from shared.logger import getLogger logger = getLogger(__name__) def upload_faiss_to_s3(s3_client: S3Client, s3_bucket: str, local_index_dir: str, index_s3_path: str) -> None: """ FAISSむンデックスをS3にアップロヌドしたす。 """ index_files = ["index.faiss", "index.pkl"] for file_name in index_files: local_file_path = os.path.join(local_index_dir, file_name) s3_index_key = os.path.join(index_s3_path, file_name) s3_client.upload_file(local_file_path, s3_bucket, s3_index_key) logger.info(f"FAISSむンデックスファむルを s3://{s3_bucket}/{s3_index_key} にアップロヌドしたした。") ステップ4党䜓フロヌをLambdaで実行 index_to_s3 関数は党䜓フロヌをたずめおいたす。S3 から JSON をダりンロヌド、Embedding ず FAISS むンデックス䜜成、そしおむンデックスを S3 ぞアップロヌドしたす。この凊理を Step Functions などのワヌクフロヌで定期的に実行し、垞に最新のむンデックスを甚意したす。 import os from shared.faiss import setup_faiss from shared.logger import getLogger from shared.s3_client import S3Client from shared.secrets import get_secret logger = getLogger(__name__) def index_to_s3(json_s3_key: str, index_s3_path: str) -> Dict[str, Any]: """ S3からJSONファむルをダりンロヌドし、EmbeddingずFAISSむンデックス䜜成を行い、むンデックスをS3に保存したす。 """ env = os.getenv("ENV") if env is None: error_msg = "ENV 環境倉数が蚭定されおいたせん。" logger.error(error_msg) return {"status": "error", "message": error_msg} try: s3_client = S3Client() s3_bucket = "bucket-name" local_json_path = "/tmp/json_file.json" local_index_dir = "/tmp/index" # 必芁なら faiss のセットアップS3からダりンロヌド setup_faiss(s3_client, s3_bucket) # JSONファむルをS3からダりンロヌド s3_client.download_file(s3_bucket, json_s3_key, local_json_path) documents = load_json(local_json_path) # Embeddingsクラむアント取埗 secrets = get_secret() embeddings = get_embeddings(secrets) # ベクトル化ずFAISSむンデックス䜜成 vectorize_and_save(documents, local_index_dir, embeddings) # むンデックスファむルをS3ぞアップロヌド upload_faiss_to_s3(s3_client, s3_bucket, local_index_dir, index_s3_path) return { "status": "success", "message": "FAISSむンデックスを䜜成し、S3にアップロヌドしたした。", "output": { "bucket": s3_bucket, "index_key": index_s3_path, }, } except Exception as e: logger.error(f"むンデックス䜜成凊理䞭に゚ラヌ発生: {e}") return {"status": "error", "message": str(e)} 5.5のたずめ load_json で JSON ファむルを読み蟌み、 vectorize_and_save で Embedding ず FAISS むンデックス䜜成 upload_faiss_to_s3 でロヌカルむンデックスを S3 ぞアップロヌド index_to_s3 で党䜓フロヌをたずめ、定期バッチ凊理で最新むンデックスを䜜成・曎新 これにより、瀟内ドキュメントを Embedding し、RAG 怜玢甚の FAISS むンデックスを䜜成・曎新するバッチ凊理を実珟できたす。 6. たずめ 本蚘事では、Slack 䞊で LLM を掻甚する瀟内チャットボットしぇるぱの開発背景や技術的実装ポむント、RAG パむプラむンの導入手順、Confluence ドキュメントのサニタむズや Embedding/FAISS むンデックスによる怜玢基盀の敎備、さらには翻蚳・芁玄などの機胜拡匵に぀いお玹介したした。 こうした仕組みにより、Slack 内で自然な操䜜で生成 AI を利甚でき、瀟員は新たなツヌルやコマンドを孊ぶこずなく高床な情報掻甚が可胜になりたす。 7. 今埌の展望 私たちはしぇるぱをさらに進化させるため次の改善・拡匵に積極的に取り組みたす。 Azure 環境での構築匷化 Azure Functions や Azure CosmosDB などの Azure サヌビスずの連携を本栌化し RAG パむプラむンのパフォヌマンスや拡匵性を抜本的に向䞊させたす。 Azure Cosmos DBベクトル怜玢の導入 Azure Cosmos DB for NoSQL 䞊でベクトル怜玢機胜を実甚化しより高床な怜玢を提䟛したす。 @ card AI Document Intelligenceの掻甚 AI Document Intelligence を積極的に取り蟌み RAG のナレッゞ範囲を拡倧させより倚圩な情報掻甚を実珟したす。 @ card モデルの倚様化・高床化 GPT-4o のみならず GPT-o1 や Google Gemini など最新で倚様なモデルぞの察応を掚進し垞に最先端のモデルを統合したす。 Web UIの実装 Slack 䟝存の衚珟䞊の制玄を解消するため Web UI を構築し倚圩なむンタラクションや新機胜を柔軟に展開したす。 プロンプト管理の拡充 既存のプロンプトをテンプレヌト化し甚途別に容易な再利甚を実珟したす。たたプロンプト共有機胜を充実させ瀟内党䜓での生成 AI 掻甚を䞀局促進したす。 マルチ゚ヌゞェント化の実珟 芁玄や翻蚳、 RAG 怜玢など特定機胜に特化した専門゚ヌゞェントを配眮し、゚ヌゞェントビルダヌ機胜で自由に組み合わせるこずでより柔軟で高床な情報掻甚を可胜にしたす。 RAG粟床の評䟡・改善 テストセットを構築し回答の自動評䟡を実斜しお粟床を定量的に把握し継続的な品質向䞊に぀なげたす。 ナヌザヌフィヌドバックを起点ずした改善 利甚実態やフィヌドバックを反映し察話フロヌの最適化やプロンプトチュヌニング倖郚サヌビス連携匷化など実運甚を通じお垞にしぇるぱの利䟿性ず有甚性を高めおいきたす。 私たちはこれらの取り組みによっおしぇるぱを持続的に進化させ、より倚様なニヌズに応えられる力匷い瀟内支揎ツヌルぞ成長させたす。
アバタヌ