TECH PLAY

MNTSQ

MNTSQ の技術ブログ

86

こんにちは、MNTSQでエンジニアをやっている平田です。 MNTSQでは 自然言語処理 を使って契約書を解析したり検索したりする機能を開発しています。 契約書解析には、次のようなタスクがあります。 秘密保持契約等の契約類型に分類 契約締結日や契約当事者等の基本情報を抽出 条項(第1条, 第2条, ...)単位で分解 本稿では、これらの契約書解析タスクをGPT-4oに解かせてどんな結果になるか見てみます。 ざっくりやり方 GPT-4oの API を呼び出すところ ここではAzure OpenAIのGPT-4oを使います。 Microsoft のサンプルコードほぼそのままですが、一応貼り付けておきます。 from openai import AzureOpenAI client = AzureOpenAI( api_version= "2023-05-15" , azure_endpoint=os.getenv( "AZURE_OPENAI_ENDPOINT" ), api_key=os.getenv( "AZURE_OPENAI_API_KEY" ), ) response = client.chat.completions.create( model= "gpt-4o" , messages=[{ "role" : "user" , "content" : "ここにプロンプトを入れる" }], temperature= 0 , ) completion = response.choices[ 0 ].message.content 解析結果を JSON 形式で出力させるプロンプトテンプレート 解析結果をソフトウェアで扱いやすくするために、 JSON 形式で出力させます。 JSON 形式で出力させるテクニックはいくつかありますが、ここではプロンプトで JSON スキーマ を渡します。 プロンプトのテンプレートは次のようなイメージです。 prompt_template = """ \ {content} Transform the above text into a JSON according to the following JSON schema. Output in a code block, and do not output anything else. ```json {json_schema} ``` """ content : 契約書本文 json_schema : 解析結果の JSON スキーマ Pydanticで JSON スキーマ を生成するところ Pydanticは JSON スキーマ の生成とバリデーションができるので、今回やりたいことにぴったりです。 docs.pydantic.dev Pydanticを使わずに JSON スキーマ を直接書いてもよいのですが、 JSON スキーマ は複雑すぎて少なくとも私は読みたくないですし、 JSON スキーマ 自体のバリデーションが必要になるので Python で書いてmypyに静的解析させるほうが楽だと思います。 Pydanticで次のようなデータモデルを定義します。 from pydantic import BaseModel, Field class ClauseResponse (BaseModel): number: str | None = Field(default= None , description= "条番号" ) heading: str | None = Field(default= None , description= "条見出し" ) class ContractResponse (BaseModel): document_name: str | None = Field(default= None , description= "ドキュメントのタイトル" ) is_contract: bool = Field(description= "契約書のテキストかどうか" ) execution_date: str | None = Field(default= None , description= "契約を締結した日" ) effective_date: str | None = Field(default= None , description= "契約の効力が発生した日" ) renewal_term: str | None = Field(default= None , description= "契約の自動更新期間" ) notice_to_terminate_renewal: str | None = Field(default= None , description= "契約の自動更新を終了するための条件" ) governing_law: str | None = Field(default= None , description= "契約の準拠法" ) parties: list [ str ] = Field(default_factory= list , description= "契約の当事者名リスト" ) clauses: list [ClauseResponse] = Field(default_factory= list , description= "契約の条項リスト" ) JSON スキーマ は ContractResponse.model_json_schema() で生成できます。 { "$defs": { "ClauseResponse": { "properties": { "number": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "条番号", "title": "Number" }, "heading": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "条見出し", "title": "Heading" } }, "title": "ClauseResponse", "type": "object" } }, "properties": { "document_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "ドキュメントのタイトル", "title": "Document Name" }, "is_contract": { "description": "契約書のテキストかどうか", "title": "Is Contract", "type": "boolean" }, "execution_date": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "契約を締結した日", "title": "Execution Date" }, "effective_date": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "契約の効力が発生した日", "title": "Effective Date" }, "renewal_term": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "契約の自動更新期間", "title": "Renewal Term" }, "notice_to_terminate_renewal": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "契約の自動更新を終了するための条件", "title": "Notice To Terminate Renewal" }, "governing_law": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "契約の準拠法", "title": "Governing Law" }, "parties": { "description": "契約の当事者名リスト", "items": { "type": "string" }, "title": "Parties", "type": "array" }, "clauses": { "description": "契約の条項リスト", "items": { "$ref": "#/$defs/ClauseResponse" }, "title": "Clauses", "type": "array" } }, "required": [ "is_contract" ], "title": "ContractResponse", "type": "object" } GPT-4oが出力した JSON 文字列を抽出する プロンプトで Output in a code block と指示しているので、GPT-4oはコードブロックで囲われた JSON を出力します。 ここでは 正規表現 を使ってコードブロックから JSON 文字列を抽出します。 import re json_pattern = re.compile( r"```json\n(.+?)\n```" , re.DOTALL) if m := json_pattern.search(completion): json_text = m.group( 1 ) Pydanticで JSON 文字列をバリデーションするところ Pydanticを使うと JSON 文字列が JSON スキーマ に従っているかバリデーションできます。 json_object = ContractResponse.model_validate_json(json_text) もしバリデーションでエラーになった場合は、次のようなテンプレートでエラーメッセージをプロンプトに追加するとうまく修正してくれることがあります。(まあGPT-4oだとほぼ完璧な JSON を返してくれるので使う機会のないテクニックですが...) """ \ The following errors occurred, fix it. ``` {errors} ``` """ 実際にやってみた MNTSQにたくさんある契約書サンプルの1つを使って実験してみます。 契約書サンプル(先頭と末尾のページ) 上記契約書から OCR で抽出したテキストを入力すると、GPT-4oから次のような出力が返ってきます。 ```json { "document_name": "金銭消費貸借契約書", "is_contract": true, "execution_date": "2021-04-20", "effective_date": "2021-04-30", "renewal_term": null, "notice_to_terminate_renewal": null, "governing_law": null, "parties": [ "株式会社フォイエルバッハ商事", "MNTSQ株式会社", "板谷隆平" ], "clauses": [ { "number": "1", "heading": "消費貸借" }, { "number": "2", "heading": "借入条件" }, { "number": "3", "heading": "連帯保証" }, { "number": "4", "heading": "期限の利益の喪失" }, { "number": "5", "heading": "届出義務" }, { "number": "6", "heading": "反社会的勢力の排除" }, { "number": "7", "heading": "公正証書の作成" }, { "number": "8", "heading": "費用負担" }, { "number": "9", "heading": "管轄" } ] } ``` GPT-4oの出力から JSON 文字列を抽出し、Pydanticのデータモデルでバリデーションしたところ、エラーなく読み込めました。 解析結果の値も完璧です 🚀 実は execution_date (締結日)と effective_date (発効日)って別物なのですが、これらをちゃんと区別できていますね。すごい! 感想 ちょっと前まで、契約書解析は次のような工程で開発していて、1機能リリースするのに数ヶ月かかることが通常でした。 アノテーション モデリング テスト リリース ChatGPTを使った開発では、 アノテーション と モデリング の代わりにプロンプトエンジニアリングを行います。 これにより次のような嬉しさ(開発のスケールアウト)があります。 数ヶ月かかっていた アノテーション と モデリング が数日になる MLエンジニアがいないとできなかった モデリング が非エンジニアでもできるようになる GPT-4でも同様の開発はできるのですが、 API の料金が高く選択肢に入りにくい状況でした。 一方、GPT-4oはGPT-4に比べて破壊的に安く、選択肢に入るビジネスモデルが大幅に増えたのではないかと思います。 Azure OpenAI Serviceの価格表 (執筆時点) こうやってできることが増えていくとワクワクしますね 🙌 残る課題 これでAIが仕事を奪ってくれて明日から遊べるぞ!!!!とはならないのが残念なのですが、実際に契約書解析のプロダクトでGPT-4oを使う上でどんな課題があるか、いくつか挙げてみます。主に契約書というデータが長過ぎることに起因するものです。 GPT-4oの トーク ン制限数 GPT-4oの入力 トーク ン数は最大128kです。やばいですね。一方、契約書の トーク ン数はだいたい1ページ500 トーク ンです。MNTSQには数百ページの契約書が入ってきたりするのですが、仮に300ページとすると150k トーク ン必要なのでそのままプロンプトに入力するにはちょっと足りないのです。 また、GPT-4oの出力 トーク ン数は最大4kです。条項数が100を超える契約書もあるので、本稿で紹介した解析結果を得るには心許ない数字です。 Lost in the Middle こちらの論文 にもあるように、プロンプトの中間にある情報は忘れられがちです。契約書は長いのでこの問題が結構クリティカルです。 GPT-4oの料金 いくら安くなったとはいえ、128k トーク ン全部使い切るようなプロンプトを入力すると1回のリク エス トで100円くらいかかるので、サービスのプライシングロジックに配慮した戦略が必要です。 GPT-4oのターンアラウンドタイム 爆速!と言われたGPT-4oですが、60ページくらいの契約書データを使ってプロンプト入力から JSON 出力の時間を測ると1分くらいでした。サービスで提供したい体験によっては許容できない可能性があります。 他にもありますが、MNTSQではこのような課題と向き合いながらLLMを活用したプロダクト開発を行っています。 もしこの記事に興味をお持ちいただいて、「他のデータだとどうなの?」とか「課題は解決したの?」とか聞いてみたい方がいらっしゃいましたらお気軽にDM *1 等でお問い合わせください。 この記事を書いた人 Takumi Hirata MNTSQの アルゴリズム エンジニア。流離の なんでも屋 。 *1 : https://x.com/_hrappuccino
アバター
こんにちは。 すべての合意をフェアにしたいMNTSQ(もんてすきゅー)のJessie ( @Jessica_nao_ ) です。 仕事では、 Ruby on Rails を"利用"させていただき、法務業務に利用される SaaS Webアプリの開発を行っています。 RubyKaigi2024が間も無く開催されますね! 土日から沖縄入りされている方の投稿が Twitter に流れてきており、既に私の気持ちもかなり高まってきています✈️ この記事では、「今回RubyKaigi に初めて参加するぞ!」という方に向けて、2023年に初めてRubyKaigiに参加した私が、知っておくと嬉しいと思うTipsやRubyKaigiのあれこれをいくつか紹介していきます📝 なお、公式アナウンスは非常に重要です。オンサイト参加の方で、 Onsite Information - RubyKaigi 2024 をまだご覧になっていない方は、必ずご確認を! いくつかは私のチョイスでここにも記載しておきます📝 プレチェックインが、Day 0(5/14) 16:00 ~ 19:00 にある Day1 の混雑緩和のため、ぜひ使ってほしそう Discord → https://rubykaigi.org/go/discord イベントページ → Events - RubyKaigi 2024 day1 の公式 Party は別チケット 写真に写ってOKか、否かの意思表示できるストラップがある 「仕事では何をしていますか?」「 Rails を書いています」 Webエンジニアの私がするコミュニケーションでは、「 Rails を利用してWebアプリを開発している」という意味で、「 Rails を書いている」と言ってしまうこともあると思います。しかし、RubyKaigiではこの言い方は誤解されてしまう恐れがあります⚠️ RubyKaigiには、 Ruby や Rails のコミッターの方もたくさんいらしています。そんな中で、「 Ruby / Rails を書いています」と言ってしまうと、相手は文字通りに受け取り「あ、コミッターだ!!」となってしまいますね。笑 私は昨年、初めましての方に対して「 Rails を書いています」と伝えてしまったせいで、ちょっと気まずい思いをしました😇 自己紹介では「 SaaS の業務アプリケーションを開発しています」のように始めるべきでしたね 1 。 Ruby を"使う"人には高度なことも多いセッション 昨年の私は、「 スケジュール (※ 2024版のリンクは こちら ) を見て、ピンと来ないものも多いがとりあえずこれこれ聞くぞ」と直感でピックアップしたセッションを聞こうと挑みました。正直予習も全然できておらず、 "YJIT" や " RBS " 2 など、当時の私には言葉自体馴染みのないものもあり、セッションを聞いてもチンプンカンプンなこともあり面食らってしまいました。 これを読んでくださっているRubyKaigi初参戦の方の中にも、心構えを間違えて、同じように絶望してしまう人が出てくることを懸念しています。 この点に関して、ちょうど、 STORES CTO藤村さんが語っていらっしゃる良いBlog を見つけたので、一部をここで共有いたします。 RubyKaigiって Ruby を開発している人のカンファレンスで、 Ruby についてのカンファレンスなんですよ。 Ruby を使ってどうとかいうのよりも、 Ruby 本体をどう作るかにフォーカスがあるので、 プログラミング言語 の最新機能とか最新の実装って作ってる人しかわからない人が多いんですよ。(中略) わからなくていいのだろうかっていう引け目を感じることは一切必要なくて、こんなにわからないことがわかったみたいな、わからないジャンルを知ること自体がもはや成果だと思います。 弊社EMの飯田さんも同様に、昨年RubyKaigiについて、次のようなことを語ってくれました。 Ruby の最先端であるから、分からないことがあるのは当然 Ruby を開発している人たちが、こんなことに今取り組んでいるのかと知れることは成果 普段の業務ではあまり触れられることのないところに接点を持って、興味の幅を広げられると最高 RubyKaigiは、例えば Kaigi on Rails と比べて、 Ruby / Rails を利用して仕事をしている人には"実用的" 3 ではないと感じる話題も多いです。 それでも分かりたいセッション とは言え、せっかく参加できて得た貴重な機会ですからスピーカーの方の熱量を少しでも身近に触れて、自分の幅を広げられる機会にしていきたいところですね🚀 今回は、CRubyについてのセッションが多いとのことなので、 Masaki Hara (@qnighy) さんが #rubykaigi2024_koryaku にて発表されたスライド「 CRuby超入門 」に目を通してみるのはとても良さそうです! 本スライドでは、読み進めるのに「難しい部分」の補足をメインにしてくださっていますが、ここでは、「CRubyなんて初めて知ったよ」という方向けに、CRubyに触れる一歩目のヒントを書いてみたいと思います。 例えば、 ruby/array.c#L1388 を見ると、 Array#pop はここで実装されているのが分かります。これだけでも、このレポジトリが”完全なる未知のもの”ではなくなり、「 Ruby の実装見たことある!!」が真になりますね😆 もう一歩進んで、無駄なメモリを解放するために resize している様子も見られます 4 。 Ruby を使っている際には、Arrayの長さについては無関心でいられますが、このような実装のおかげなのですね。 parser や Garbage Collection などについても、あとほんの少しでも踏み込んでいけるよう、とりあえずレポジトリをcloneして、移動中でも眺めていきましょうか✈️ Be Nice Yukihiro Matz (@yukihiro_matz) さんの「Be Nice」という言葉が印象的でした。 この大きなカンファレンスを良いものにするために、Organizerやスタッフの皆さんの努力と、コミッターやスピーカーの方々の貢献、最後に、これらに加えて、RubyKaigiの参加者全員の楽しむ姿勢と気配りが欠かせないと感じました。 私の昨年の体験をお話しします。 Day1のお昼のことでした。 上述の飯田さんが、前職のエンジニアの方も誘って、MNTSQメンバー6人とその方の計7名でランチに行きました。 最初、何が起きているのか良く分かりませんでした。私の日常の常識では、「これって通常だったらやや気まずい状況じゃないっけ?」という気持ちになっていました。笑 RubyKaigiはみんなが交流を楽しむ場でもあり、これは至って自然な、推奨されるべき事象でした。初めましての人とであっても、きっかけさえあれば自然とご飯に行けます。目の前で手持ち無沙汰にしている方がいれば、例え初めてお会いした方でも、積極的に話しかけて大丈夫です。きっと、目の前の方もあなたとの会話を望んでいるでしょう。 去年の私は当初、(お酒を飲んでいない時には)「セッションにも全くついていけない自分が、初めましての Rubyist の方と会話を成立させられるのだろうか?」という不安もありましたが、そんなことを心配する必要は実は全くありません。 Ruby の話はもちろん、普段の仕事のことを尋ねたり、自分の話を聞いてもらっても良いですね。せっかく社外の人と多く話せる機会ですから、自分のチームで今抱えている・取り組んでいる問題について、他のチームではどうしているのか教わるのも良いです(私は今回、カンバン事情や Rspec のお作法などを聞いていきたい気持ちがあります)。 Twitter で利用できるタグ達 RubyKaigi会期中、あるいはその前後でぜひ使っていきたい Twitter のタグを紹介します。 #rubykaigi RubyKaigiに関することを、どんどんつぶやいて盛り上げていきましょう🚀 #rubykaigi2024 も一定使われるだろうと思いますが、多数派は #rubykaigi という印象ですね👀 #rubykaigiA , #rubykaigiB , #rubykaigiC Schedule - RubyKaigi 2024 に記載のタグを、見つけることはできましたか?👀 セッションが行われる会場ごとにタグが用意されていて、セッションの内容や感想、あるいは期待について、みなさん多く投稿されています。 スピーカーの皆さんに感想を届けるのも非常に良いことなので、活用していきましょうっ! #rubyfriends 2023の会期中の様子は こんな 感じです! セルフィーを投稿する際には、ぜひ利用していきましょう🙌 ちなみに、 Onsite Information - RubyKaigi 2024 によると、今回は自分が写真に映ることに関する意思表示用のLanyard(ストラップ)が配布されるようですね📸 素敵な取り組みです。 #MNTSQ 弊社MNTSQのタグです!! MNTSQは、RubyKaigi 2024 にGold Sponsorとして協賛いたします🙌 弊社の参加メンバーとの写真や、 ノベルティー について Twitter 上に載せる際には、一緒にタグ付けしていただけると嬉しいです! 無理をせず楽しむ! 最後になりますが、終わっても最高だったと言えるRubyKaigiを過ごすために、以下の点には気をつけましょう。 RubyKaigiのメインコンテンツはセッションですね。初参加で真面目な方は、「セッションは全部出るもの」と思うかもしれませんが、体力的にも、内容の高度さ的にも、それは厳しいです。 セッション以外にも、他の Rubyist と食事に行ったり立ち話をしたり、企業ブースを回ったり、飲み会などの企画に(ref: Events - RubyKaigi 2024 )に参加したりと、楽しむべきことは多くあります。 去年の私は連日エンジョイして、睡眠時間もそこそこに会場に足を運んでいたら、RubyKaigi後、1週間程度倦怠感が続きました。他のメンバーにも帰ってきてから具合が悪くなる人もいました。 お祭りごとですし、本当に楽しいRubyKaigiがあなたを待っていると思います。最高の思い出として記憶に残しておくためにも、無理はせずに身体は労わりながら楽しみましょう🙌 私は今回、火曜から土曜の朝まで 那覇 にて参戦します! みなさんにRubyKaigiでお会いできることを、とても楽しみにしています。 仮にコミッターでない場合に「 Ruby を書いています」と言っても、それはそれで笑ってくれるので安心してください📝 ↩ 今は、MNTSQでも導入されています ↩ 今すぐ活用することは難しいという意味で、何が自分のためになるかは、今全て分かることではありませんね! ↩ rb_ary_pop -> ary_resize_capa -> ary_heap_free_ptr -> ruby_sized_xfree と辿ると、メモリ解放まで行っているのだなぁという雰囲気が掴めます ↩
アバター
おはようございます!こんにちは!場合によってはこんばんわ! SREチームマネージャーの藤原です。 今年の3月からSREチームの中で、勉強会を定期的に実施するようになりました。 本エントリでは勉強会を開催するにあたって考えたことと、実際の進め方についての事例解説です。 勉強会を通じて実現したいことの説明から、テーマ設定、効果最大化のための開催形態と頻度、現時点(2024/4・5時点)の結果について解説しています。 勉強会を通じて実現したいこと チーム内勉強会を通じて実現したいこととして、以下を設定していました。 チーム・個人としてのレベルアップ 積極的な議論への参加 相互理解 チームとしての目線合わせ 勉強会を開催するからには何かしらのスキルや考え方を身に付けたいはずです。 また、学習効果を最大化するためにも、積極的な議論への参加を実現したいと考えました。 さらには、勉強会の中での議論を通じてチームメンバーが常日頃考えていることをメンバー間で相互に理解し、その上で今後のチームとしての取り組みについてメンバー間で目線のすり合わせをしたいと考えました。 基本的にはこれらの項目を実現できるような材料(テーマ)設定や開催形態を考えました。 勉強会の材料(テーマ)設定 勉強会としては、一種の読書会形式としました。題材としては、 The DevOps ハンドブック 理論・原則・実践のすべて を選びました。 The DevOps ハンドブック 理論・原則・実践のすべて 作者: ジーン・キム , ジェズ・ハンブル , パトリック・ボア , ジョン・ウィリス 日経BP Amazon 少し古い書籍では有りますが、名著として評価がある程度定まっている書籍であるため、適切に勉強会を運営さえできれば組織・チームとしてのレベルアップに確実に繋がるであろうということを考えて選定しました。もっと直接的に業務に役立つような書籍(たとえば、詳解Terraformなど)も材料として検討しました。 詳解 Terraform 第3版 ―Infrastructure as Codeを実現する 作者: Yevgeniy Brikman オーム社 Amazon この書籍自体はTerraformについて学ぶという意味合いではまちがいなく良書です。今回の題材が一通り終わったのちに具体的な技術を掘り下げる際には第一候補となり得ます。 一方で今後の組織として取り組みを進めていく上でメンバーの目線を揃えるという意味では、特定の技術について深ぼるよりは、その前提となる考え方、取り組み姿勢の観点を学べる書籍がよかろうという結論に至りました。 勉強会の効果を最大化するために考えたこと 私自身の経験の観点でもなんとなく本を読んだりしだだけよりも、他の人と議論をしたり、教えたりした場合の方が効率的ですし、 ラーニングピラミッド のような観点からも、議論したり教えあったりということが学習効果の最大化には良いようです。 career-ed-lab.mynavi.jp 一方的に情報を受け取るだけでなく、相互にコミュニケーションをすることで学習効果を最大化することを意図して、開催形態や頻度を考えました。 開催頻度・時間 頻度が低すぎると前回の内容を忘れてしまうこと、頻度が高すぎると日常業務に支障が出ることからから、業務時間中に週1回の開催としました。 1回あたりの時間が短すぎると本論としての議論が中途半端になってしまうこと、長すぎると議論がだらけてしまうことから、適度な長さとして45分と仮決めしました。 結果、毎週水曜日に45分間実施しています。 開催形態 勉強会の開催形態としてはさまざまな形があると思います。 講義形態もあればハンズオン形態もあります。それぞれの形態にも長所・短所があります。 現在実施している勉強会では、以下の3つを意識しています。 ひと通り対象書籍は読み通すこと 勉強会の準備が負担となりすぎないこと 積極的な発言、議論につながること 具体的な勉強会の開催形態としては、次のように進めました。 開催前までにやることと、開催時にやることの2つがあります。 開催前は対象となる章(1回あたり2章を想定)を読みます。 読んだ内容のうち気になる文言をひたすら Google Docs に引き写します。 引き写した文章にたいして、自身の感想やエピソード、そのほか追加情報(たとえば似たような内容の記述のある他書籍の紹介など)があれば記述します。 勉強会当日は、参加者ごとにメモした部分について軽く説明してもらいます。他の参加者はそれらにコメントや、感想を投げかけたり、場合によっては反論したりします。また事前に準備した Google docs に、当日議論した内容をログとして残したり、 Google docs のコメント機能でコメントを追加したりもします。 そのような形で、勉強会の開催前に考えたこと、勉強会の最中に他の人が考えたことを受け止めつつ、自身が他の人が考えたことをどう捉えたかをフィードバックするといった形で勉強会を進めています。 結果としてどうなのか(現時点 2024/4時点での感触) 結果としては、チームの学びの場として機能していると思います。 積極的な議論といった観点ではかなりうまくいっています。1回の開催でA4サイズでおおよそ3ページ、 Google Docs のコメントで30コメント強程度の分量になります。 45分では少し足りないかもしれないくらいの感覚は少しありますが、次回もっと議論したいという飢餓感に似た感覚を持ち続けるにはちょうど良い開催時間になっています。 勉強会のログ(具体的な中身については非公開です) 勉強会の準備についても、書籍を読み進めた上で気になった部分を抜き出すことが準備としてはミニマムなので、さほど準備の負担にもなっていないようです。 具体的な学びについては、勉強会の中で、”対象の捉え方としてそのような捉え方をしたことがなかった”といった新しい視点を身につけられたり、”よく言われるプ ラク ティスがどのような経緯で出来上がったものなのか理解できた”といったフィードバックが得られています。参加者からも勉強会を有意義なものだと感じてもらえているようです。 勉強会についての参加者フィードバックの例 書籍的には特定のツールやテク ノロ ジー にたいしてのスキルを向上させるものではないので、すぐには効果は出ないと思います。 考え方や取り組み姿勢に関わる部分なため、中長期的にさまざまな局面で効果が出るであろうことを期待しています。 今後は、学びをチームとしての成果にいかに繋げていくことができるか、勉強会参加者のモチベーションをどう高く維持していくかが試されそうです。
アバター
こんにちは。 GitHub Copilotを先日初めて触って、感銘を受けたMNTSQ代表の 板谷 です。MNTSQの代表をしておりますが、現役の弁護士でもあります。 なぜ私が、 GitHub Copilotに感銘を受けたかというと、「プログラミングの LLM による進化」は、契約という言語をコーディングするためにもドンピシャで使えそうだと感じたからです。 例えば、 GitHub Copilot では、自分の過去のコードを参照して、最適なコードをサジェストしてくれます。 これは、 契約に関わるすべての ビジネスパーソン が求めていたもの です!契約の 99.9%が過去のコードの使い回しであるにもかかわらず、毎回ゼロからコーディングするのが本当に苦痛だからです。ちなみに、前回契約と理由なく diff があると取引先に怒られます。笑 しかし、 GitHub Copilot 的なものが プログラミング言語 だけでなく契約言語でも求められるのは、よく考えると当然です。 以前にも記事にした のですが、 プログラミング言語 と契約言語はとても似ているためです。 どちらも「一定のインプットを入れると、必ず一定のアウトプットが返ってくるように設計された独自の言語体系」だからです。 契約言語とは「一定の紛争⇒一定の判決が出るように構造化されたコード」です。処理するのがコンピューターではなく裁判官であるというのが違いでしょう。 もし「リーダブルコード」を弁護士が読んだら? - MNTSQ Techブログ もし、契約言語の世界に GitHub Copilot が産まれたら、すべてのビジネスが圧倒的に加速し、無駄に難解な契約というものが変革されるのではないでしょうか…? この記事では、このすべての ビジネスパーソン の夢を追及してみます。 プログラミングと LLM  コード / コードコメント / コミットメッセージ ソフトウェア開発においては、まずコードを書き、コードの変更箇所の説明のため コミットメッセージ や プルリク エス トの説明 を書きますよね。 GitHub Copilot は、 自然言語 での指示によって、これらの作成を助けてくれます! 1 GitHub Copilotによる、コードやコミットメッセージの生成 GitHub Copilotによる、プルリク エス トの説明の生成 2 これらのアウトプットの流れは、契約業務でも実はまったく変わりません! ・まず 契約本文 (=コード)を書き、 ・次にその意図が伝わるように Microsoft Word にコメント (=コミットメッセージ)をして、 ・差分の発生理由を メールに記載 して関係者に通知するのが(=プルリク エス トの説明)、契約業務の流れです。 すべて、心をこめて手作りしています。もしも、これらの契約業務のアウトプットを GitHub Copilot のように助けてくれるなら、本当に夢のようです。確信があります、 私なら泣きます 。  Copilot?いやまずは VS Code だ GitHub Copilot の素晴らしいところは、LLM によるサジェストが、例えば VS Code などエディタのなかで完結するところです。コミットメッセージは、 GitHub に一元的に記録されます。 ここで契約に関わる ビジネスパーソン は、そもそものプログラミング環境の違いに絶望します…… VS Code ??…それって、Word のこと? GitHub ??…それって、メールのこと? そう、 GitHub Copilot 以前に、そもそも契約には プログラマー を思った開発環境がないのです。Word とメールと紙だけで、歯を食いしばって生きてきました。 しかし、この 1 年に私たち MNTSQ が解決してきた課題はまさにそこでした。いかに Word を契約の プログラマー にとって開発しやすい環境にできるか、 GitHub のようなチームでの開発基盤を提供できるかが、ちょうど今クリアされたところです! RAG の設計  なぜ RAG なのか? GitHub Copilot が手に馴染む理由は、利用者の過去のコードを参照して、コードの記述の仕方を合わせてくれるところです。 その裏側にあるのは RAG という構造に近く、現在作業中の内容を踏まえて、過去のデータのうち参考になるものを特定し、それを変形して適用する(generation)という流れを辿っています。 契約に LLM を活用するため、RAG は非常に重要です! なぜなら、会社ごとに法務戦略は異なっているためです。 法務戦略は、それ自体が 言語化 されることは少なく、過去の類似シチュエーションにおける交渉データとしてのみ表現されています。これを踏まえて新たな契約を生成できれば、 契約の世界では本当に革命が起こせるはず です。  そもそも DB がない…! しかし、契約業界ではまたもここで、そもそも論にぶつかります…… 「過去データの DB?そんなものなくね?」 契約データは、オンプレのファイルサーバーに格納されていればまだマシで、メールの添付ファイルとして探せば残っているかも…という状況です。 実は、これこそ MNTSQ が創業以来解決してきた問題です。MNTSQ の登場以前には、一流企業ですら「自分たちがどのような契約を締結しているかわからない」という状況でした。 しかし、私たちが契約をデータベース化して、 NLP によって自動的にタグ付け・整理をすることで、契約というデータはようやく活用可能な状態になりました。 「データベースを提供している= RAG で使えるベストな参考情報を持っている」 ということです。ここを レバレッジ して、最高品質の契約を誰もが作れるようにしていきたい。  推薦 AI DB があれば、そのうち参考になるデータを特定できるか(retrieval)が次のポイントです。 これこそ、 ドメイン 特化 NLP の腕の見せ所です!ユーザーの意図に沿った過去データを特定できるように、 ドメイン 特化の メタデータ を自動付与し、絞り込みをかけていきます。 その選りすぐりされたデータを、いかに Word を VS Code 化し、最高の UX でサジェストするかをデザインしていきます。まさに契約のプログラミング基盤を作っているようなワクワク感があります。  ベストプ ラク ティスの提供 なお、皆さま日本最高の法律事務所をご存じでしょうか?その一つが「 長島・大野・常松法律事務所 」という、 形態素解析 が難しすぎる事務所です。 MNTSQ では、この長島・大野・常松法律事務所のトップ弁護士によるコード(=契約文言)を、ベストプ ラク ティスとしてコンテンツ化し、オープンに提供しています。 このベストプ ラク ティスであるコードも RAG においてサジェストし、ユーザーの契約に自然と取り込ませていくことによって、社会全体からアンフェアな契約を駆逐していきたいと考えています。 終わりに プログラミング言語 と比べると技術的進歩の余地がある契約言語ですが、これから アルゴリズム によって変革されるスピードは プログラミング言語 よりも圧倒的に速いはずだと考えています。まもなく契約は、 アルゴリズム が書き、交渉するものになるでしょう。 なぜなら、契約言語はプログラミングより圧倒的にパターン性が高いためです。実は、100-200 くらいのライブラリ(=契約条項)を組み合わせるだけで、90%以上のビジネスシーンを網羅できるのではないかと思います。 これが実現したときの社会的意義も大きいです。契約が不要であるビジネスはありません。また 「契約言語は、全国民が完全理解できることがなぜか前提になっている」 なかで、アンフェアな契約に騙されてしまう人を減らせるかもしれません。 「契約をつくる アルゴリズム 」が誕生するとすれば、これまで最前線の弁護士が書いてきたような最高品質のものにしたいと私は考えています。そのための、長島・大野・常松法律事務所というトップ事務所との提携です! 誰もが一瞬で、フェアな合意ができるようなプラットフォームを作れれば、職業人として一つ社会貢献ができるかな…という想いです。今まさに社会が大きく変化しようとしているところですので、もし興味を持ったエンジニアの皆さまがいれば、是非私とカジュアル面談をさせてください! hrmos.co この記事を書いた人 板谷  隆平 MNTSQ( モンテスキュー )社のCEO/ファウンダー。長島・大野・常松法律事務所所属の現役弁護士です。 /* 動画のためのCSS */ video { width: 100%; height: auto; } サンプル動画は GitHub 社様の ブログ から引用させていただきました。 ↩ プルリク エス トの説明を生成する機能は、 GitHub Copilot Enterprise でのみ提供されています(本記事投稿日現在)。 ↩
アバター
こんにちは。MNTSQの下村です。 コーポレートエンジニアとしてMNTSQ従業員の生産系向上施策等を実施していたりします。 最近では情報セキュリティ責任者としてもセキュリティ活動を推進しています。 Twitterもやっている のでフォローしてもらえると嬉しいです! こんなアイコンです 今回はMNTSQが 従業員同士の相互理解を深める ための施策として実施している Weekly Sync という取り組みについて紹介致します。 この記事で紹介したいこと Weekly Syncという制度の紹介について。 Weekly Syncの仕組み。 Weekly Syncを実施したことによる効果。 Weekly Syncについての概要&取り組み後の声 まずはざっくりとWeekly Syncの概要と実施したことによる従業員の声を紹介します。 概要 Weekly Syncとはすごくざっくりまとめると 自部署の取り組みのアップデート内容を他部署のメンバーに要約して伝えること を毎週実施することです。 他部署の取り組み紹介を知ることで相互理解や、自部署の取り組みについての気づきを発生させる取り組みとなっています。 Weekly Syncに寄せられた声 下記のような形でWeekly Syncの取り組み自体にはポジティブな効果が出ています。 新入社員からはMNTSQの事業内容のキャッチアップに役立ったという声 PdMとSalesが相互感謝する例 アポ獲得のトリガーになったり Weekly Syncとは? さて、ここからはWeekly Syncの内容について詳細を記載させていただきます。 まずMNTSQにはクリスタルクリアという Value の観点で、マネージメントミーティングログという経営会議の内容が完全公開されています。 (そもそも完全公開自体がすごいことなのですが、ここでは詳細を割愛し CEO板谷の記事のリンク だけ貼らせてもらいます。) Weekly Syncではこのマネージメントミーティングログに記載された各部署のアップデートを4-5人程度の小グループを bot がランダム作成、メンバー選出し 選出されたメンバーが5分程度に要約 して他の部署のメンバーに説明する取り組みです。 ミソなのが、その内容を 「自分なりの理解で説明すること」 にあります。 書いてある内容の正確性を最重視するなら、部署の長が説明すればよいのでは?と思う方もいらっしゃるかもしれません。ですがここで重視しているのは選出者が「自分なりの理解で説明すること」です。この取り組みにより 選出者の当事者意識が高まり、自部署への気づき・発見 が生まれることにつながります 。 (もちろん正確性も重視しており、読むだけで正確な理解が得られるよう情報は整理されています!) また、他部署のメンバーからすると、他部署のメンバーの補足説明等により、マネージメントミーティングログの内容だけでは把握しきれなかった 「現場の生の声」 が聞けることにより 生きた情報をキャッチする ことにも繋がります。 以上のことにより、短時間で 最新の事業状況を把握 でき、説明内容についての質疑応答などにより 相互の理解・発見を深める場 になっています。 なお、この取り組みは以前に Daily-sync と呼ばれて運用されていたものを100人組織に合わせてアップデートしたものになります。 Weekly Syncの実装内容について ここからはWeekly Syncの実装内容について記載させていただきます。 実は設計方針についてはCEO 板谷 の要望を元に、 アルゴリズムエンジニアの平田 が設計してくれました。 ただ、平田はMNTSQの中心人物であり 常に多忙です 。 「となると、これはコーポレートエンジニアの仕事でしょう!」と実装は私が引き受けることにしました。 余談 その日、平田が「NE KO NO TE MO KARITAI」「KASANAI...」と書いてあったTシャツを着ていたことからも想像を絶する多忙だったと予想します。(嘘) Tシャツ【ネコ ノ テモ カリタイ】黒字にピンクプリントの「ネコの手も借りたい」Tシャツ。忙しい時にさりげなく着たいデザインです。 #グラニフ https://t.co/EvKgONikDk pic.twitter.com/xEPMFatciM — グ ラニ フ (@ graniph _updates) 2020年8月10日 要件整理 まず、前身の Daily-sync についての流れを汲む必要がありました。 (成功している施策であったので、無為にしたくなかったのです。) Daily-syncについては組織規模が30人程度であれば、そこまで時間をかけずに相互理解が可能になっていてポジティブに効果していたのですが従業員数が増えてきたことにより、時間対効果が悪くなってきた側面がありました。 そこでDaily-syncの最大のメリットであった「 異職種間コラボレーションの質を高めること 」「 限られたリソースの中で、みなが最も優先度の高い仕事に取り掛かっている状況を実現すること 」をWeekly Syncで時間対効果を高めつつ実現するにはどうしたら良いかということをまず、CEO 板谷 と平田とディスカッションしました。 そこで、「 日次で行っていたDaily-syncを週次で30分という凝縮しつつ、相互理解を深めるにはどうしたら? 」という問いに対して前述した 「マネージメントミーティングログをお互いで説明しあう」ことが時間対効果が高いのでは? という結論に達しました。 また、CEO 板谷 からは下記のような要望もありました。 入社間もないオンボーディングメンバーは視聴のみ(喋らない)にしたい。 各部署のメンバーが数人程度でランダムに アサイ ンされるようにしたい。 そして、これに加えて更に... 毎週のことなので運用を極力、楽にする必要があります。 (事務/総務アシスタントさんが、手動で参加者を募り、ランダム アサイ ンするとかは絶対に避けないといけません) そして、参加者の方も 1. 参加/不参加の意思表示を容易にする必要がある。 2. 新入社員がWeekly Syncの取り組みを即座に理解するために運用方法を工夫する必要がある。 といった形で 正直めんどくさい しっかり実装方法を考える必要があります。 こんなことは思っていない こんな感じになりました1 - 通知処理 通知処理 「共有カレンダーに「Weekly Sync」という件名の予定が作成されているか否かをAM9時頃にチェックします。 「Weekly Syncが開催される日」みたいなものを別途管理するのは嫌なので、カレンダー登録状況をチェックすることで回避します。 予定があれば通知先チャンネルに「Weekly Sync参加有無」を ヒアリ ングする内容を通知します。 この時、従業員は参加の有無を回答しやすいように Slackリアクションで反応すれば良いだけ にします。 更に更に、リアクションしやすいように、 reactions.add method で押すべきリアクションをリアクションしておきます。 こうすることで、従業員はリアクション絵文字を探してリアクションするのではなく、ポチッとリアクションをすれば良いという1アクションで済みます。 また、参加表明リアクションには「リモート参加」か「物理参加」かを選べるようにしています。 こうすることで、リモート参加組はMeetのみで完結するように、物理参加組は対面で会話できるように会議室を bot が自動取得できるようにしておきます。 リアクションを付け忘れてしまう従業員にそなえて、 conversions.members method で通知先チャンネルに所属している人一覧を取得し、 reactions.get method でリアクションした人を 除いた 一覧にリマインド通知します。 こうすることで、リアクション済みの人には余計な通知をせず、リアクションがまだの方にはしつこくメンションされるのでリアクション付け忘れを最小化できます。 こんな感じになりました2 - ランダム小グループを作成 ランダム小グループ作成処理 マネージメントミーティングログが格納されている Google Drive フォルダを DriveApp でチェックし、 スクリプト 実行日のファイルを見つけ、そのファイルのURLを取得します。 このURLをこの後作成する小グループ宛にお知らせします。こうすることで従業員は「今日のファイルどれだっけ...?」と探す時間を無くせます。 reactions.get method で参加表明した方の一覧を抽出します。 社員マスタを スプレッドシート で管理しているので参加表明した方々の「所属部署」と「入社日」を抽出します。 入社日を抽出しているのは「入社3ヶ月以内」のメンバーはまだ不慣れな状況なので傍聴者として アサイ ンされるようにするためです。 あとは上記で取得したメンバーを「所属部署ごとにランダムでPick」し、「4-5人程度」の小グループ化したメンバー一覧を作成します。 その小グループごとにSlackスレッドを作成し、説明する人を bot が指名します。 新入社員が戸惑わないように、作成したスレッドにWeekly Sync開催の趣旨を説明した内容も通知しておきます。 こんな感じの通知をします。 通知内容 従業員側の対応 以上のことにより、従業員は下記のようなオペレーションでWeekly Syncに参加可能になります。 bot から参加可否の質問が到着。 従業員はリアクションするだけで参加要否の連絡が完了。 物理参加かリモート参加なのかも考慮しています。 開催時間になったら、参加者向けにWeekly Syncの内容を通知。 これによりWeekly Syncを知らない新入社員も運用可能。 通知内容 やってみた後の声 下記のような形で良いフィードバックを頂けたり... 嬉しい声 従業員に楽をしてもらうための工夫に気づいて頂いてお褒めの言葉頂いたりと... 嬉しい声2 Weekly Syncは誰の負荷をかけることもなく、ポジティブに運用され続けています(嬉しい) 最後に 本記事ではMNTSQで実施されている相互理解の仕組みとして「Weekly Sync」とその運用の裏側を紹介させていただきました。 相互理解の仕組みを検討中の方の参考になると幸いです。 MNTSQでは すべての合意をフェアにする というミッションを掲げています。 sg.wantedly.com そんなミッションに共感してくれる方、ぜひMNTSQで一緒に働きましょう!
アバター
こんにちは。MNTSQの下村です。 コーポレートエンジニアとして、MNTSQ従業員の生産系向上施策等を実施していたりします。 Twitterもやっている のでフォローしてもらえると嬉しいです! こんなアイコンです 本日は社員からの問い合わせ業務 いわゆる ヘルプデスク業務について効率化するためのツールを自作した 話を書いてみます。 この記事の要約 一人目コーポレートエンジニアとして参画したがヘルプデスク業務が非効率だったので効率化した。 質問に対して特定のemojiを押すと GitHub ProjectsのItemを作成するようにした。 Slackスレッドのコメントと GitHub ProjectsのItemを双方向同期するようにした。 Azure OpenAIも利用して効率化した。 きっかけ 2023年5月からMNTSQの一人目コーポレートエンジニアとして参画しています。 情報システムを色々と整備している真っ最中なのですが、 ヘルプデスク業務のフローが未整備である というものがありました。 (あるあるですね。) 放置するとヘルプデスク業務の 工数 が増加する恐れがあったので対策を打つことにしました。 どんな問題があったか? 様々なSlackチャンネルで質問があり、どこでどんなステータスになっているかわからない。 「あの回答したっけ、、」とかとなり、Slackの過去書き込み等を検索して状況整理していた → ムダ ナレッジがたまらない。 Slackでのみ回答をしているので類似事例等を検索できない → 属人化 ヘルプデスクにどの程度の時間を費やしているか等の傾向分析ができない。 よくある解決方法として ヘルプデスク用チャンネルを作ってSlackワークフロー等で質問を受け付ける というのがセオリーかと思います。 私自身もそうしようとしたのですが、下記のような悩みを抱えることになるなと思い何か別の方法を考えることにしました。 社員の生産性向上を行うのがコーポレートエンジニアなのに社員にルールを増やすことになる。 ワークフローで質問してねーというルールを社員に周知徹底する必要がある。 ワークフローを知らない方や、急ぎの質問などはワークフローが使われないケース等がありそう。 ワークフローを知らない方にはワークフローのことを説明して、急ぎの質問だったりすると場合によってはコーポレートエンジニアが代筆するとかありそう。 ワークフローで質問を受け付けるのは良いが、解決済か否かのステータス管理は結局難しいまま。 別の方法を探ってみる まず、どんな機能が欲しいのかを整理してみました。 初手として下記機能を満たす SaaS を探してみることにしました。 問い合わせ内容をチケット管理したい。 MNTSQは GitHub を全従業員が利用しているので GitHub Issue or GitHub Projectsと連携できると良し。 チケット化することでナレッジ化もしたい。 従業員側はSlackにコメントするだけにしたい。 従業員にヘルプデスク用 SaaS にログインさせて、質問をしてーみたいな面倒なことはさせたくない。 どのSlackチャンネルからでもヘルプデスクメンバー側に負担なくチケット化したい。 Slackの投稿内容がチケットに同期して欲しい。 ヘルプデスク業務をアウトソースしたいので、アウトソースしやすいシンプルな仕組みが欲しい。 質問傾向等や対応時間などを解析できるようにしたい。 せっかくなのでAIを活用して業務を簡略化したい。 できるだけコストも抑えたい。 そんなわけで SaaS を探してみる。 探してみたのですがズバピタなものは見つかりませんでした。。。。 Halp がイメージに近い。 ただ、MNTSQではJiraを使ってないので断念。 THENA も良さそうだが、 GitHub 連携は無い。 issit が良さそう!と思いつつチケット化するだけなので、同期とかは難しそう。 じゃあどうする? 無いなら作ろう! ということで1週間ほどかけて突貫で作ることにしました。 こんな感じ のを作りました。 構成図 こういうフローになりました。 質問者 質問者は任意のパブリックチャンネルで質問をして、そのスレッドで状況の説明をする(だけ) ヘルプデスクメンバー 作成された GitHub ProjectsのItemに回答をコメントするだけ。(Slackスレッドに自動同期されるのでSlackを開く必要は無し) ヘルプデスク宛の質問を GitHub ProjectsのItemのチケット化するのはできるだけ、AIに任せて自動化する。 それぞれの機能についての解説1: ヘルプデスク宛の質問にemojiをつける 10分おきに下記処理を行うCloud Runを用意してヘルプデスク宛の質問だった場合は helpdesk といったemojiを該当メッセージに付けるようにしました。 (なぜemojiを付けるのかは後述します) 質問者が記載した質問内容を search.messages API を利用して検索。 検索条件はヘルプデスクメンバーが含まれるグループメンション or ヘルプデスクメンバーのメンション。 検索した内容をAzure OpenAIの API を利用してヘルプデスク宛の質問か否かをAIに判断させる。 下記のようなプロンプトで判断させてます。 投稿された内容はITヘルプデスクチームの問い合わせだと思いますか?なお、回答については「Yes」もしくは「No」という回答のみで答えてください。 このようにすることで後続の判定処理に利用しやすくしています。 AIがヘルプデスク宛だと判断した場合、 reactions.add API を利用して helpdesk といったemojiを該当メッセージに付ける。 次回実行時用に、Firestoreに「どこまで検索済か」を記録するようにして終了。(同じメッセージに対して二重でemojiを付けないようようにしています) ヘルプデスク宛の質問にemojiをつける それぞれの機能についての解説2: GitHub ProjectsのItemを作成 先述した、emojiを利用して下記のような処理を行います。 Slack公式ツールである リアク字チャンネラー を使い、ヘルプデスク用Slackチャンネルにメッセージを転送する。 emojiがついたことをトリガーにヘルプデスク用Slackチャンネルにチケット化したい内容が通知されるので Event Subscriptions を利用して、Cloud Runに GitHub Projectsの API を利用してItemを作成する処理を命令する。 このようにリアク字チャンネラーの機能を利用することで下記のようなメリットがあります。 各SlackチャンネルにEvent Subscriptions用の bot を招待しなくても良い。 ヘルプデスク用チャンネルに転送するといった機能を自前で実装しなくて良くなる。 GitHub API を利用して、該当メッセージに関する GitHub ProjectsのItemを作成する。 この際、下記のようなことを行う。 Azure OpenAIの API を利用して、質問内容を要約した内容でitemの件名を設定する。(itemを見やすくするため) プロンプトは ITヘルプデスクのチケットとして起票したいです。テキストを50文字以内で要約してください。 といった感じにしています。 Slackの質問内容をスレッドの内容も含めて、 GitHub ProjectsのItemに記録する。 後述の同期処理用にFirestoreにSlackスレッドの情報(タイムスタンプなど)と GitHub ProjectsのItem ID等を記録しておく。 GitHub ProjectsのItemを作成 こんな感じにチケットを作成します それぞれの機能についての解説3: Slackと GitHub ProjectsのItemを同期 10分おきに下記処理を行うCloud Runを用意して、Slackと GitHub ProjectsのItemを同期します。 こうすることによりヘルプデスクメンバーは GitHub Projectsだけを見る or コメントすれば良いことになります。 GitHub API 及び Slack API を利用して、ItemやSlackスレッドの内容に新しい投稿が無いかをチェックする。 新しい投稿があれば、その内容を同期して投稿する。 例: Slackスレッドに投稿があれば、 GitHub ProjectsのItemにスレッドの内容を自動記載。 例: ヘルプデスクメンバーが GitHub ProjectsのItemにスレッドの内容を記載した場合Slackスレッドに自動投稿。 次回実行時用に、Firestoreに「どこまで同期済か」を記録するようにして終了。(同じメッセージに対して二重で投稿しないようにしています) Slackと GitHub ProjectsのItemを同期 それぞれの機能についての解説4: 解析用のデータを蓄積 後ほど、ヘルプデスク対応について解析するために上記Cloud Runが実行されるたびに、下記内容を Google Sheets API を利用して スプレッドシート に記録、 スプレッドシート の内容をもとにLooker Studioでレポート化しています。 質問者メールアドレス 質問者所属部署 対応開始、終了日時 全体所要時間 解析用のデータを蓄積 レポート例 その他、工夫したポイント 対応開始と終了を bot から通知させるようにしてます。 ちょっとしたことなのですが人対人のやりとりだと、「対応します!」→「お願いします!」みたいなやり取りが発生しがちなのでその時間も節約。 対応開始メッセージ ヘルプデスクは即対応を求められがち、ヘルプデスクメンバーも即対応をしがち。ただ、優先度に応じた対応をしたほうがいいのでその旨を毎回周知する。 対応開始メッセージ 対応終了メッセージ ヘルプデスク側は対応終了したつもりだったが、当人は「そうじゃない(まだ終わってない)」みたいなケースもあるので、その旨も告知。 対応終了メッセージ リリース後の結果 リリース後は下記のように問題が解決しました。 様々なSlackチャンネルで質問があり、どこでどんなステータスになっているかわからない。 → ストレスなくチケット化できる環境が整ったので、 GitHub Projectsだけを見ればステータス管理できるようになった。 ナレッジがたまらない。 → すべて GitHub ProjectsのItemに蓄積されているので過去ナレッジが集約された。 今後は溜まったナレッジをAIに学習させて自動返答なども検討したいと考えているが、それの礎になるものができた。 ヘルプデスクにどの程度の時間を費やしているか等の傾向分析ができない。 → Looker Studioにより 見える化 できるようになったので傾向分析が可能に。 今後、どの部署に対してどのように手厚くすべきかなどが検討可能になりました。 また、運用が簡略化されたことで 対応 工数 が削減 できたのもポイントです。 「あれ?あの回答したっけ??」みたいにSlackを検索する時間も無くなりました。 また、未回答の総量等も見れるようになったので、「ある程度連続して時間を確保して一気に片付けよう」 みたいな計画も立てられるようになりました。 その他嬉しいこと 開発者から褒められた。 本業の方から褒められるのは純粋に嬉しいですね☺️ ほめられた 横展開を希望された。 横展開できるように想定していたのでこう言われるのも嬉しいですね☺️ 横展開 低コストでできた。 月額3,000円程度の維持費。 Halpだと最低でも一人あたり月額$150- なのでだいぶ低コストで出来た☺️ 自作ツールなので汎用性がある。 今後、仮にNotionやasanaにタスク管理を移行する!となっても同じ仕組みで流用できそう。 後悔することも 自作なので 自分の理想形 になるのは良いのですが、自作なので自社メンテが必要になるため、負債化してしまったなと思ってます。 今後もヘルプデスク管理ツールの動向を見守りながら、良さそうなツールがあれば即リプレイスしたいなとは思っています。 wrangle.io は今は気になり(このシステム構築後にこのツイートを気づいた) 2024年1月追記 GitHub Issueにチケットを貯めるのではなく、Notionでヘルプデスク用のDatabaseを作成してそちらに貯めるように変更しました。 チケット作成ごとにNotion Pageを作成して、Slackスレッドの内容はNotion Page上のコメントに同期するようにしました。 この形の方が便利でした。 (問い合わせ者への返信はNotion Pageのコメントに投稿、そうじゃない内部向けのメモはPage上に記載するようにすることで情報整理できる) ニーズがありそうなら、どこかのタイミングで新たに投稿したいなと思っています。 最後に 本記事ではヘルプデスク業務を自作ツールで効率化した話を記載しました。 同じようにお困りの方の参考となれば嬉しいです。 MNTSQでは すべての合意をフェアにする というミッションを掲げています。 sg.wantedly.com そんなミッションに共感してくれる方、ぜひMNTSQで一緒に働きましょう!
アバター
はじめに  こんにちは、MNTSQ( モンテスキュー )の アルゴリズム エンジニアの清水です。本記事では事前学習済み 言語モデル の一つであるLUKEを用いた固有表現抽出の実装方法について紹介します。 LUKEとは  LUKEは、 LUKE: Deep Contextualized Entity Representations with Entity-aware Self-attention において提案された 言語モデル です。LUKEは、単語とエンティティの文脈付きベクトルを出力する知識拡張型(knowledge-enhanced)の 言語モデル であることが大きな特徴です。単語とエンティティの双方を独立した トーク ンとして扱い、(他のBERT系モデルと同様に)マスクされた単語を予測する訓練を行うと同時に、マスクされたエンティティを予測する訓練を行うことで、エンティティを考慮した文脈表現を獲得できます。また、固有表現抽出などのエンティティの知識を使うことが重要なタスクにおいて、当時のSoTAを達成しています。  詳しくは論文著者の スライド で分かりやすく解説されているので、ぜひご覧ください。 TransformersのLukeForEntitySpanClassificationによる固有表現抽出  LUKEは Transformers から簡単に利用することができます。LUKEではエンティティ表現を使って各種 NLP タスクを解くことが可能であり、Transformersでは LukeForEntitySpanClassification を用いることで固有表現抽出を行うことができます。ただし、少し独特な実装が必要であり、公式のドキュメントにも多くの記載がないため、本記事で実装方法を紹介します。また、 GitHub に 実装コード を公開しているので、そちらも合わせてご参照ください。 モデルへ入力  (詳しくは論文著者のスライドをご覧いただければと思いますが)LUKEの事前学習タスクは以下の通り、テキストとは別に入力されるエンティティを復元するタスクになっています。  (本記事では図中の”Words”を「単語」、”Entities”を「エンティティ」と呼称します。)  TransformersのドキュメントのLUKEのページを参照すると、通常のBERTに入力する各単語の特徴量( input_ids 、 attention_mask など)と同じように、エンティティの特徴量を入力する必要があることがわかります。 LukeForEntitySpanClassificationの入力(Transformersより)  これらの特徴量は、 entity_spans という特徴量(後述)を LukeTokenizer (もしくはその多言語版である MLukeTokenizer )に入力することによって自動で作成されるため、実装上は特に意識する必要がありません。ただ entity_position_ids の計算方法を把握しておくと、モデル理解の助けになるでしょう。 entity_position_ids はエンティティがどの単語に対応するかを示すことで、テキスト中のエンティティの位置を表現します。エンティティが複数の単語に対応する場合、下記スライドのようにポジションエンベディングからの出力を平均し、モデル本体に入力します。 入力の作成方法  以下のようにTransformersの LukeTokenizer (もしくは MLukeTokenizer )に entity_spans を与えることで、前述したモデルに必要な入力を自動で作成することができます。引数の task には "entity_classification" 、 "entity_pair_classification" 、 "entity_span_classification" のいずれかを指定する必要があり、本記事では固有表現抽出を行うことができる"entity_span_classification"を指定します。 tokenizer = MLukeTokenizer.from_pretrained( "studio-ousia/luke-japanese-base-lite" , task= "entity_spans_classification" ) text: str = "MNTSQ株式会社は全ての合意をフェアにします。" # entity_spansの具体的な作成方法については後述 entity_spans: list [ tuple [ int , int ]] = [( 0 , 5 ), ( 0 , 9 ), ( 0 , 10 ), ( 0 , 12 )...] encoding = tokenizer(text, entity_spans=entity_spans, return_tensors= "pt" )  この entity_spans が、通常の固有表現抽出から連想される形式と異なるため注意が必要です。テキストを任意の方法( MeCab ・Sudachiなどによる 分かち書き )で分割し、連続する トーク ンの組み合わせを entity_spans として入力します。より正確には、テキスト中のエンティティの始点位置・終点位置を示す二つの整数のタプルのリストを渡します。入力イメージとしては以下の通りです。 text = "MNTSQ株式会社は全ての合意をフェアにします。" # 任意の方法で分割し、連続するトークンの組み合わせを`entity_spans`として与える。 # MNTSQ/株式会社/は/全て/の/合意/を/フェア/に/し/ます/。(Sudachiによる分かち書き) entity_spans = [ ( 0 , 5 ), # "MNTSQ", ( 0 , 9 ), # "MNTSQ株式会社", ( 0 , 10 ), # "MNTSQ株式会社は", ( 0 , 12 ), # "MNTSQ株式会社は全て", ( 5 , 9 ), # "株式会社", ( 5 , 10 ), # "株式会社は", # 省略 ( 20 , 21 ), # "し", ( 20 , 23 ), # "します", ( 20 , 24 ), # "します。", ( 21 , 23 ), # "ます", ( 21 , 24 ), # "ます。", ( 23 , 24 ), # "。" ]  この entity_spans を使用して、 entity_position_ids などのモデルに必要な特徴量が LukeTokenizer によって作成されます。また、正解ラベルはこの entity_spans 内のスパン一つ一つに対して付与されます。上記の例の場合は上から2つ目の"MNTSQ株式会社"のスパンに対してのみ"ORG"などのラベルが付与されることが期待されます *1 。また、( LukeTokenizer によって作成される) entity_ids には一律して"[MASK]"という特殊 トーク ンのIDが割り振られます。 *2 *3 。 その他実装上の注意  LUKEと他のBERT系のモデル間で最も異なる点は entity_spans を作成して トーク ナイザに入力する必要がある点です。具体的な実装は 実装コード をご覧いただければと思いますが、本記事でも実装上注意する必要がある点をご紹介しておきます。 一つのentity_spansにおける最大長  全ての連続する トーク ンの組み合わせを entity_spans として作成すると、スパンの数が膨大になりますし、また明らかにエンティティではないスパン(例えばテキストの最初から最後までのスパン)も作成されることになります。そのため、スパンの最大長を決めて、一つ一つのスパンがその最大長を超えないように entity_spans を作成する必要があります。また、この最大長は トーク ナイザによって分割される単語(上記論文著者のスライドの18ページの”Words”に当たる)の数を基準に設定する必要があります。つまり、一つのエンティティに定めた個数以上の単語が含まれないように制限します。また、その最大長を LukeTokenizer に max_mention_length という引数として与える必要があります。詳細は実装コードの こちらの部分 を参照ください。 各データに対するentity_spans内のスパンの個数  また、メ モリー エラーを防ぐために、一つのデータ(一つのテキスト)に対する entity_spans 内のスパンの個数も制限する必要があります。溢れてしまったスパンは、テキストを複製し別のデータとしてモデルに入力します。この一つのデータに対するスパンの最大個数は LukeTokenizer に max_entity_length という引数として与える必要があります。詳細は実装コードの こちらの部分 を参照ください。  また、このように別データとして入力された同一テキストに対する entity_spans は、推論時に再び集約する必要があります。詳細は実装コードの こちらの部分 を参照ください。 推論時の処理  推論時にも独特の処理が必要です。具体的には、予測確率の高い順に、テキストの各文字に対して予測結果を反映していく処理が必要になります。詳細は実装コードの こちらの部分 を参照ください。 精度評価  ストックマーク株式会社が公開している 日本語の固有表現抽出データセット で評価 *4 したところ、Accuracyが0.96、F1スコアが0.89となりました。詳しい結果は WandBのログ をご覧ください。 終わりに  LUKEによる固有表現抽出は通常の 言語モデル による固有表現抽出とかなり性質が異なります。私は通常の固有表現抽出の先入観に引っ張られ、当初は誤った実装をしてしまいました。LUKEにご興味があり、実際に使ってみようと考えている方の参考になれば幸いです。 MNTSQ, Ltd.では一緒に働く仲間を募集しています  MNTSQでは 自然言語処理 やLLMにご興味のあるエンジニアを募集しています。是非、下記リンクからカジュアル面談をお申し込みください! この記事を書いた人 清水健 吾 自然言語処理 エンジニア TransformersのRelease Notesを眺めるのが趣味 *1 : 一般的な固有表現抽出の"B-ORG"、"I-ORG"のようなBIO形式でないことに注意してください。一方で、推論時には seqeval を使ってスコアを計算するためにBIOタグに変換しています。 *2 : 事前訓練時に使った"[MASK]" トーク ンを入力することで、入力テキスト中からエンティティに関する情報を集約した表現が得られます。詳しくは こちら 。 *3 : entity_idsは全て"[MASK]" トーク ンであるため、エンティティのエンベディング層が不要です。そのためエンティティのエンべディング層を持たない liteモデル を使用します。 *4 : 全データのうち20%をテストデータとして使用しました。
アバター
こんにちは。「リーダブルコード」を先月読破して、感銘を受けた弁護士の人です。 なにに感銘を受けたかというと、「エンジニアが 高級言語 を効率的にコーディングするための工夫」は、契約という言語をコーディングするために援用できることがとても多いということです。 例えば、リーダブルコードは 「関数には空虚な名前(tmpとかretvalとか)でなく、エンティティの実体に即した名前をつけよう!」 と提案しています。 これめっちゃわかります!!!なぜなら、契約言語では当事者というクラスの表現のために「甲」 「乙」 という定義を未だに使います。そして、甲と乙を逆に書いてしまったままReviewを通過することが実際によくあります。 オライリー さんには激怒されるでしょう。 しかし、よく考えると 高級言語 と契約言語が似ているのは当然だと思うようになりました。それは、どちらも 「一定のインプットを入れると、必ず一定のアウトプットが返ってくるように設計された独自の言語体系」 だからです。 契約言語とは 「一定の紛争⇒一定の判決が出るように構造化されたコード」 です。処理するのがコンピューターではなく裁判官であるというのが違いでしょう。 調べれば調べるほど、プログラミングの世界でのこれまでの工夫は、契約言語の世界の未来の姿だと思うようになりました。 ソフトウェア開発のプロセスになぞらえて、少し書いてみます。  ビジネス モデリング   モデリング の重要性 ソフトウェア開発においては、まず実際の業務を モデリング しますよね。まず業務要件を適切に分解することが重要で、クラスを定義したりER図を作ったりするのはその後です。ここを間違うと、プログラムとして内的整合性が保たれていても、実際には意味のない / 役に立たないものになってしまいます。 契約でも、最もセンスが問われるのがここです!実際の取引を、契約という独自の言語体系に モデリング していきます。いくら契約のフォーマットが美しくても、実際の取引を反映していなければ、裁判で役に立たずにむしろ有害です。  ノーコード/ ローコードの流れ そのため、プログラミングでも契約でも「コードを求めているユーザー」と「コーディングできる職人」とのコミュニケーションがとても重要です。 しかし、これが超難しい。契約の世界では、法務部と事業部とのやりとりにめちゃめちゃ時間がかかるのです。日本全体で契約のために消費している時間は、10億時間とも言われます。 そうすると「全部を職人さんがコーディングするのではなく、コードを求めている人でもコーディングできるようにしよう!」という流れが産まれます。いわゆる、ノーコード/ ローコードですね。 「契約のノーコード」は、LegalTechでもアツい分野の一つです。ChatGPTに契約を書かせてみたらどうなるのか…という tweet を当社創業者がしましたが、もし誰もがフェアな契約を瞬時にドラフトできれば、世界中のビジネスはものすごく加速するはずです。 ただ、「CopilotをProduction Codeに使うのはまだ怖い」というくらいの感覚で、 NLP のなかでも生成モデルの活用は道半ばという感じです。契約の世界でも、生成モデルよりも検索や推薦 アルゴリズム といったもう少し地に足のついた技術活用が進んでいます(後述)。  コーディング   DRY原則 プログラミングでは、汎用化できる部分はライブラリ化され、同じコードは何度も書かないようにすべきという考え方が当然のように受け入れられています( DRY原則 )。 さらに、社会全体でも共 通化 できるレイヤーは、 デファクト・スタンダード となるようなプラットフォームから API が提供されています。当社も、 SaaS プロダクトを提供するにあたって AWS を利用したり、随所でライブラリを利用することで、エンジニアがプロダクトのために必要な部分にフォーカスできるという便益を享受できています。 弁護士から見ると、これって本当に羨ましいことです……! なぜなら、 契約はその99.9%が過去のものの繰り返しであるにもかかわらず、毎回ゼロからコーディングしているからです 。 契約言語には、未だに デファクト・スタンダード が存在しません!年間5億件も契約されてるにもかかわらずです!  クリエイティブコモンズ やJ-KISSなど標準化が進んだ領域は、契約全体の0.1%未満です。 そのため、契約をしたい世のビジネスマンは、ライブラリ等を一切使わない フルスクラッチ 開発を常に強いられて疲弊しています。ちなみに IDE のサポートもないので デバッグ 作業も重いです。 これは非効率だというのもありますが、めちゃめちゃつまらないことです。法務の人たちはとってもツラいのです。   デファクト・スタンダード と 自然言語処理 技術 プログラミングの世界では、長い歴史のなかで デファクト・スタンダード を積み上げてきました。 契約言語にも、今ようやくその機運が訪れています。その契機となったのが、実は 自然言語処理 技術の登場です。契約言語は比較的明確な構造を持っているため、 自然言語処理 技術との相性がとてもよかったのです。 私たちのようなLegalTechが広く使われていくと、社会全体で「どのような契約条件が標準なのか」がついに可視化されます。これまで弁護士の職人芸頼りであったものが、スタンダードを 定量 分析できるようになるかもしれません。 また、日本最高の職人=トップ弁護士によるコードを オープンソース 化する取り組みも始まっています。LegalTechは、フェアな契約言語への API を提供するという社会的役割を果たそうとしています。  Review ソフトウェア開発では、 GitHub をはじめとした バージョン管理システム を中心としたプラットフォームが当然に使われています。これがあまりに便利なので、当社では経営上の意思決定すら GitHub でPull RequestをApproveすることで実施しています。 かたや契約言語は、コードがベタ打ちされたWordファイルをメールに添付してやりとりすることで更新しています。交渉過程で、複数のビジネスマンが異なる修正をしたにもかかわらず、conflictに気づかぬままハンコを押してしまった…ということも起こります。 そもそも裁判では、取引先と最後に合意された契約コードだけでなく、そこに至るまでの更新経緯も参照されます。しかし、誰がなぜどのようにコードを更新したのかという情報は、たいていメールが離散してわからなくなっています。 これを解決するために、昨今のLegalTechは「契約のバージョン管理」に乗り出しています。一つのプラットフォームで契約言語のReview履歴が見れるように、これからようやくなっていくかもしれません。  管理・更新 プログラムは「動いてからが本番」であり、リリース後に改善活動が アジャイル に繰り返されます。ここが SaaS の面白さでもあります。 契約言語であっても、本来は同じです。そもそも契約言語は、裁判官というリソースが超貴重でテストが不可能であるため、ぶっちゃけ間違いが多いです。 また、「契約期間」というEOLもあるのでメンテナンスも必要になります。 他方で、契約言語は「現在有効なコードがなにか」ということすら管理することができていません(!)。これはすごい話で、なぜかというと、最終版の ソースコード が紙で管理されているからです。 そのため、LegalTechベンダーは、 OCR を提供するだけで喜ばれることもあります。さらに、 自然言語処理 技術によって契約期間を抽出し、メンテナンスが必要なタイミングでアラートを出すことができるだけでも社会にWOWを与えられます。  終わりに こうして見ていくだけでも、契約言語はプログラミングの歴史から大いに学ぶべきであると痛感します。 さらに悪いことに、 高級言語 と契約言語との大きな違いがあります。それは 「契約言語は、全国民が完全理解できることがなぜか前提になっている」 ことです。ビジネスの世界では、契約言語を通じてしか、他者と関わることができません。 例えば、外部サービスを利用するときには、 利用規約 に同意する必要があります。あれって、皆さんは本当に読めていますでしょうか? ぶっちゃけ、現状のものは弁護士である私ですら読む気にならないです。そのくせ「私たちは一切の損害賠償責任を負いません」だとか、明らかにアンフェアな条項がこっそり含まれていたりします。 LegalTechとは、契約言語をモダナイズし、誰でも他社とフェアな協力関係を築けるようなツールに進化させるものだと考えています。契約言語のノーコードと、充実したライブラリ推薦アルゴと、 IDE と、 GitHub を私はこの世界に送り出したい!(これが言いたかった) 今まさに社会が大きく変化しようとしているところですので、もし興味を持ったエンジニアの皆さまがいれば、是非私とカジュアル面談をさせてください! カジュアル面談申込フォーム | MNTSQ株式会社 ※右上にも採用ページへのリンクがあります この記事を書いた人 板谷  隆平 MNTSQ( モンテスキュー )社のCEO/ファウンダー。長島・大野・常松法律事務所所属の現役弁護士です。
アバター
開発しているシステムの.NET バージョンを4.5にしたらメール件名が文字化けしたので、 エンコードを2回するコード を書いた全世界100万のソフトウェアエンジニアの皆さん、こんにちは。 MNTSQ株式会社でバックエンドエンジニアをしている沼井です。 弊社が提供している SaaS である「MNTSQ CLM」は、多数の エンタープライズ 企業へ導入されています。その結果、契約書はもちろん、それに付随する多くのドキュメントをデータとして扱う必要があります。 そのドキュメントにはメールも含まれており、メールは企業の(契約をはじめとする)業務ナレッジを含む重要な資産となります。 そのため、MNTSQではこれから送られるメールはもちろん、過去のメールについてもナレッジとして取り込み、活用できる機能を提供しています。 崩れたメールを語る ここまで読んで鋭い方はお気づきと思いますが、過去から長年蓄積されてきたメールをきちんと取り扱えるようにするには、様々な配慮が必要です。 標準的なメールファイル(emlファイル)だけでなく、 Microsoft Outlook からエクスポートされたmsg形式等のメールも取り扱う 多種多様な 文字コード が使用されたメールを取り扱う RFC に沿ってないフォーマットのメールを取り扱う とくに「 RFC に沿ってないメール」については、 メーラー ( MUA )と送信サーバ(MTA)の RFC 非準拠の実装、不具合その他によって引き起こされているのが実情かと思います。 もちろん完全に崩れて読み取れないもの(それはメールといえるのか?)を救出することはできないのですが、ヘッダーのごく一部だけが RFC 非準拠のフォーマットである、かつそれが頻発しているようなケースについては、 可能な範囲で救う必要があります。 ところで私はこうしたデータに向き合うとき、最新の RFC や最新のメール処理ライブラリの挙動だけをもとに、過去のメールファイルの扱いづらさを安易に断じるような姿勢をとるべきではないと考えています。長年の歴史のなかで複数のソフトウェアが介在して作成・転送・蓄積されたデータというのは、本質的にこうした問題をはらみがちで、それはソフトウェアの利用者の責任に帰すべきではないと思っています (一方で、未来においてこうした問題が起きにくい プロトコル やデータフォーマットを検討・提案していくことは、ITエンジニアの重要な責務としてあると思っています)。 閑話休題 で、以下では、 RFC 準拠のメールパーサーライブラリを使っているだけではエラーがおきたり、テキストを正しく表示できないようなメールを「崩れたメール」と呼びつつ、私達がどのようなメールに直面し、対処してきた(あるいは対処できなかった)かというナレッジを、一部ですが共有したいと思います。 文字化け メールデータで問題が起きるというと、やはり文字化けのことが思い浮かぶ方が多いと思います。 文字化けといっても実情は多様です。 件名、本文、添付ファイル名のそれぞれについて個別に文字化けが起き得るし、また、原因としても以下のような種類があります: Content-Typeヘッダーのcharset名指定(どの文字 エンコーディング 方式を使ったか) が、実際に使われた エンコーディング と異なる 例1: 実際はcp1250 ( windows -1250)で エンコーディング されているが、charset としてはiso-1250 という名前が指定されている 例2: 実際はcp932で エンコーディング されているが、charsetとしては shift_jis という名前が指定されている (ようするに、 機種依存文字 がまざっちゃってる、ということですね) 対処としては、 エンコード エラーがおきたときに、正しい(かもしれない)charset名指定でリトライするという方法があります 使用している プログラミング言語 処理系/ライブラリでそもそも対応していない文字 エンコーディング 方式が使われている 例: ISO-2022-KRが Ruby で取り扱えない メールも含めた 文字コード および文字化けの理解のためには、「 プログラマ のための 文字コード 技術入門」が非常に有用なため、一読をおすすめします。 Referencesヘッダーが壊れている これはかなり見かけるケースです。 メールの送信・返信を繰り返すたびにデータ(メッセージID)が追記されていくため、「複数の メーラー が介在し、どこかの メーラー が1つでも正しくないフォーマットで追記するとエラーになる」「ヘッダー行がどんどん長くなっていった結果、一定長をすぎたときに問題が発生してしまう」という少なくとも2つの要因がありそうだと考えています。 以下、例です: References: <ABCDEF@aaa.example.com> <BCDEFG@bbb.example.com> (中略。1000文字くらい) <CDEFGH@c cc.example> ヘッダー行が1000文字を超えたあたりで、余計なスペースが入り込んでいます これは、メールの1行998文字制限を超えたことでおきてしまう問題と思われます (RFC5322 2.1.1) 対処としては、メッセージIDのなかには通常スペースが入りえないので、それが見つかったらカットする、という方法があります 参考: https://sendgrid.kke.co.jp/docs/API_Reference/SMTP_API/building_an_smtp_email.html ヘッダは1行あたり最大1,000文字が上限となります。この制限に従わないと、中間のMTAがヘッダをスペース以外で分割してしまう可能性があります。これにより、最終的なメールにスペースが挿入されてしまう可能性があります。メールがSendGridに到達する前に別のMTAを通過する場合、最大ヘッダ長の制限がさらに厳しくなり、ヘッダが切り捨てられる可能性があります。 References: <ABCDEF@aaa.example.com> <BCDEFG@bbb.example.com>,<CDEFGH@ccc.example> メッセージIDの区切りの一部にカンマが混入しています 実際はカンマ区切りでなく、空白で区切られるのが正しいフォーマットです 対処としては、メッセージID外に見つかったカンマをカットする、という方法があります 実際にはこれ以外の壊れ方もいくつか見られるのですが、今回の記事はそれを書くには余白が狭すぎるため、省略します。 メールアドレスのフォーマットが RFC 準拠でない <hoge@example.co.> や <hoge..@example.co.jp> のような、 RFC 準拠でないメールアドレスが From や To ヘッダーなどに一定含まれるケースがあります (頻出ネタですね)。 対処としては、メールアドレス部分はフォーマットの検証をしないというのがあります。(このメールアドレスを使って後段でメール送信するのでなければ、文字列データとして扱えればよく、不正フォーマットであってもよい) 当たり前にあると思ったヘッダーが存在しない From, Date などの、最低限存在すべき ( RFC 5322 3.6) と定められたヘッダーが存在しないケースも存在します。 体験したことがある例としては、 MIME のマルチパートメッセージだけが残っていて、その他のヘッダーが存在しないというものです。 これについては、一般の MUA やMTAの不具合によって起きたものか? というと怪しい部分はあると思っていますが、原因追求は本題ではないので深堀りしないでおきます。 対処としては、 RFC 的に必須のヘッダーだとしても、無いことはありえると想定した処理をするという方法があります。 改行コード メールメッセージの行はCRLFで区切られるはずなのですが、CRCRLFで区切られているケースが存在します。 一部のMTAを経由したときにこのような事象が起き得る、という情報を見たことはありますが、やはり今回の記事の本題ではないので深堀りしないでおきます。 対処としては、 CRCRLF を CRLF へ変換する方法があります。 最後に 本記事では、崩れたメールについて見てきました。 読んでいただいた方の参考となれば幸いです。 今回は標準的なメールファイル(emlファイル)についてのみ語りましたが、実際には Outlook のmsgファイルについても語り尽くせないほどの話題があります。 ですが、今回はここまで(腹八分目)にとどめておき、今後の宿題としたいと思います。 MNTSQでは、メールを含む歴史的経緯のあるドキュメントをリスペクトし、それに向き合うことに価値を感じられるエンジニアを募集しています。 参考資料 メールの構文についての RFC : https://www.rfc-editor.org/rfc/rfc5322 (メールも含む) 文字コード や文字化けについて: 矢野啓介「[改訂新版] プログラマ のための 文字コード 技術入門」( 技術評論社 , 2018) https://gihyo.jp/book/2019/978-4-297-10291-3 この記事を書いた人 沼井 裕二 MNTSQ( モンテスキュー )社のソフトウェアエンジニア。今は主に Rails での開発をしています。はじめて触った メーラー は Mew です。
アバター
はじめに Python を対象とした静的型チェックツールとして mypy はよく知られています。静的型チェックを通じて プログラマー はより安全にコードを記述でき、安全にコードが記述できることで最終的にはソフトウェア開発の効率をより高めることができます。もちろん初期的な導入 工数 とCIパイプライン等での追加の時間的コストは発生するものの、それ以上の 複利 的な効果が期待できます。 静的型チェックよりも更に込み入った構文上の改良点や、未使用変数などの抽出を行ってくれるツールとして Linter があります。 pylint 、 flake8 もLinterの一種としてカテゴライズされます。 mypyは静的解析の過程でAST( Abstract syntax tree : 抽象 構文木 )を吐きますが、このASTを利用したlintを行うツールとして今回はRefurbを取り上げます。 github.com Refurb本体の説明としては Lots of static analysis tools already exist, but none of them seem to be focused on making code more elegant, more readable, or more modern. That is where Refurb comes in. とあり、既存のLinterよりもよりCode-readabilityや、モダンな書き方にフォーカスしたツールのようです。 Refurbの動作環境はPython3.10以降を前提にしていますが、解析対象はPython3.6以上のコードになります。 動作サンプル pip経由でのインストールが可能です $ pip3 install refurb 早速ですが動作を確かめるため次のサンプルコードに対してrefurbを回してみます。 # main.py for filename in [ "file1.txt" , "file2.txt" ]: with open (filename) as f: contents = f.read() lines = contents.splitlines() for line in lines: if not line or line.startswith( "# " ) or line.startswith( "// " ): continue for word in line.split(): print (f "[{word}]" , end= "" ) print ( "" ) $ refurb main.py main.py:6:17 [FURB109]: Replace `in [x, y, z]` with `in (x, y, z)` main.py:7:5 [FURB101]: Use `y = Path(x).read_text()` instead of `with open(x) as f: y = f.read()` main.py:13:40 [FURB102]: Replace `x.startswith(y) or x.startswith(z)` with `x.startswith((y, z))` main.py:19:9 [FURB105]: Use `print() instead of `print("")` Run `refurb --explain ERR` to further explain an error. Use `--quiet` to silence this message 各行にエラーの内容が出ています。 [FURB101]: Use `y = Path(x).read_text()` instead of `with open(x) as f: y = f.read()` などはファ イルハン ドリングの詳細まで踏み込んだメッセージで、pylint, flake8には見られないものです。 エラーの詳細について --explain フラグを使ってエラーコードを指定することで、エラーの詳細が表示されます。 $ refurb –explain FURB101 When you just want to save the contents of a file to a variable, using a `with` block is a bit overkill. A simpler alternative is to use pathlib's `read_text()` function: Bad: """ with open(filename) as f: contents = f.read() """ Good: """ contents = Path(filename).read_text() """ エラーの一覧 エラー一覧を確認できるコマンドオプションは提供されておらず、エラーの一覧を確認したい場合は テストデータのディレクトリ を通じて調査できます。比較的新しいライブラリなのでこのあたりは後の改善に期待したいところです。 カスタム プラグイン の作成 カスタムで自作エラーも定義できます。ただし静的解析結果をいじるため、 mypyのASTシンボル をある程度漁って既存モジュールの動作を確認しつつ開発をする必要があります。 github.com 簡易エラー検出器の作成 カスタム プラグイン を作成するほどのニーズではないが、一時的なエラー検出器を利用したいニーズに対しては refurb gen コマンドがあります。 これは CUI のプロンプトのガイドに沿ってASTのノード種別と検出器のファイルパス(.pyファイル)を指定すると、指定箇所にノード種別に対するエラー検出器サンプルを作成してくれるというものです。 作成した検出器をロードしてrefurbを利用する際には refurb file.py --load 検出器モジュールパス で利用できます。 その他機能 他にも追加の機能として ローカルでgitのcommit前にチェックするツール pre-commit との連携 設定ファイルによる一部エラー、対象解析モジュールの無効化 ができるようです。 この記事を書いた人 yad ビリヤニ 食べたい
アバター
このブログ投稿は、 Ruby on Rails でNTLM認証を実装する必要が出たので、その対応と追加調査の記録である。 NTLMにはv1とv2が存在するが、このブログで扱うのは主にv1である。 プログラマ も歩けばNTLMにあたるとはよく言ったもので、この記事を見ているあなたもおそらくうっかりNTLM対応をすることになったITエンジニアの一人だろう。そんなあなたの一助になれば幸いである。 NTLMとは NTLM(NT LAN Manager)とは、チャレンジ/レスポンス方式を使う認証方式の1つである。前世代にLM(LAN Manager)認証という認証 プロトコル があり、その後継にあたる。SMBを介して認証を行うNTLM over SMBや、HTTPを介して認証を行うNTLM over HTTPなど、各種 プロトコル を介した認証も提供されている。 DESやMD4など現在では安全でない技術を含むなどいくつかの理由で、 利用は推奨されていない 。ただ実態としては、社内ネットワーク 複合機 など一部では現役のようだ。 幸い、 詳しい仕様はMicrosoftのサイトに掲載されている ので、仕様の理解で困ることは無い。 チャレンジレスポンス認証とは チャレンジレスポンス認証とは、以下のような流れで生パスワードをネットワークに流さずに認証する方式である。 以下のような流れである: チャレンジレスポンス認証 クライアントからサーバーに認証を要求 サーバーからランダムな値を生成してクライアントに送付(チャレンジ) クライアントはパスワードとチャレンジを利用して アルゴリズム に沿って値を生成してサーバー側に送付(レスポンス) サーバー側でレスポンスを検証し、問題なければ認証成功の結果を返す NTLM v1 本題のNTLMのv1はどのような暗号化方式をとっているのか確認してみよう。 認証のフローは、チャレンジレスポンス認証と同じである。 引用: [MS-NLMP]: NTLM Connection-Oriented Call Flow | Microsoft Learn チャレンジとして、サーバーからは8バイトのランダムな乱数を1つクライアントに送信する。 受け取ったクライアントは、以下の流れでレスポンス(「応答鍵」とも呼ばれている)を生成する: NTハッシュを生成する パスワードを UNICODE として評価する MD4で暗号化する LMハッシュを生成する パスワードをすべて大文字に変換する 14文字に満たない部分をゼロフィルする それらを前半、後半で分離する それぞれを KGS !@#$%[b] という文字列を用いてDESで暗号化する 分離したバイト列を結合する NTハッシュとLMハッシュからそれぞれレスポンスを生成する それらを3分割する 3つそれぞれをキーとしてチャレンジをDESで暗号化する 生成されたバイト列を順番通り結合する 上記の処理でできた2つのハッシュを 所定のフォーマット に充当する 上記ロジックの都合でパスワードは14文字以内に制限されている。また、上記ではクライアントチャレンジを利用する拡張セッションセキュリティについては触れていない。 参考: [MS-NLMP]: NTLM v1 Authentication | Microsoft Learn 参考2: DESLなど各関数についてはappendixに記述 がある DESとは DES(Data Encryption Standard)とは、 共通鍵暗号 方式の1つである。 現在は鍵長が短く安全でないと言われており、AESという別の暗号化方式に取って代わられた。 MD4とは MD4(Message Digest 4)とは、一方向の(復元不可能な)ハッシュを生成する関数の1つである。 脆弱であることが判明したため、後継の MD5 が開発された。ただ、 MD5 も現在は脆弱であるとされており、更に後継とされている SHA-1 も非推奨となり、記事執筆現在ではSHA-2が比較的広範に利用されている。 メッセージの種類 前段の図で示された3種類のメッセージがある。細かなメッセージの仕様については以下の通りである。 [MS-NLMP]: NEGOTIATE_MESSAGE | Microsoft Learn クライアントからサーバーに対して認証を開始する際に送られるメッセージ [MS-NLMP]: CHALLENGE_MESSAGE | Microsoft Learn チャレンジレスポンス認証のチャレンジに相当するメッセージ [MS-NLMP]: AUTHENTICATE_MESSAGE | Microsoft Learn チャレンジレスポンス認証のレスポンスに相当するメッセージ バージョンはどこで見分けるか NTLMにはv1、v2と2つのバージョンが存在するが、どこで見分ければ良いだろうか? バージョンを判別する方法は 無さそうだ 。v1とv2ではレスポンスの長さや生成方法が異なるため、長さで判別するなどの方法はとれそうだ。バージョンが異なる場合、レスポンスが異なるため、認証失敗として終了する。 紛らわしいのだが、 NegotiateFlags という構造体のNTLMSSP_NEGOTIATE_VERSIONフラグは デバッグ 用途で OSのバージョン情報 の提供を要求するもので、これはNTLMのバージョンとは無関係である。 同様に、 NegotiateFlags に含まれているNTLMSSP_NEGOTIATE_NTLMフラグはNTLM v1の拡張ではないセッションセキュリティを利用せよというフラグであり、v1を指定するものではない。 NTLM over HTTP HTTPを利用したNTLM認証の話に移ろう。 HTTPを経由して認証する場合、以下のような流れになる: NTLM over HTTP 実装は比較的簡単で、最初のNEGOTIATE_MESSAGEは Base64 エンコード してAuthorization HTTPヘッダーに「NTLM ( Base64 エンコード したNEGOTIATE_MESSAGE)」の形式で指定する。 サーバーは ステータスコード 401と共にWWW-Authorization HTTPヘッダーにチャレンジを返却してくるので、それに対してまたAuthorization HTTPヘッダーに「NTLM ( Base64 エンコード したレスポンス)」を指定して応答する。 するとサーバー側から成功した場合は2xx系のステータスを、失敗した場合は4xx系のステータスを返却する。 これで認証は完了である。 Ruby のgem NTLM自体はかなり古いが(1990年代)、おそらく今なお一部で利用されていることもあり、ありがたいことにNTLMのgemはそれなりに存在する。 GitHub - macks/ruby-ntlm: NTLM authentication client for Ruby. GitHub - WinRb/rubyntlm GitHub - pyu10055/ntlm-http: ntlm authentication for http GitHub - at-point/net-http-ntlm: Adds NTLM authentication to HTTP requests using the rubyntlm gem. A drop-in replacement for ruby-ntlm with NTLMv2 support. 内部実装を見てみよう それぞれのgemの内部実装を眺めてみよう。 ruby -ntlm ruby-ntlm はNTLM v1 over HTTP/ IMAP / SMTP に対応したgemである。 各Net::HTTPなどのNet系モジュールを拡張するかたちとなっている。拡張部分は至ってシンプルに実装されている。NTLM実装部分はNTLMモジュールの中にまとまっており、各メッセージはMessageクラスの下に収まっている。 各所に2.2.2.1など仕様書のナンバリングが見受けられ、また、メソッドの分割単位やフラグの名称も仕様書に倣った内容となっており、仕様書をよく読み下していることが伺える。 サンプルのプログラムも同梱されており、好感が持てる。 rubyntlm rubyntlm はNTLM v1/v2 over HTTP/ IMAP / SMTP に対応したgemである。 インデントが揃っていない、Type1-3はそうなのだがわかりやすくはない、1文字や2文字の短い変数名が頻出する、メソッドがスネークケースで揃っていないなどあまりお行儀は良くない内容だ。 html-http ntlm-http はNTLM v1/v2 over HTTP/ IMAP / SMTP に対応したgemである。 こちらもタブインデントとスペースインデントが混在していたり、前段のrubyntlmと同じものを踏襲したものと推測される。 ほとんどのメソッドが800行程度の1ファイルに入れられており、見通しも良くない。 net-html-http net-http-ntlm は ruby -ntlm gemにNTLMv2サポートした代替gemである。サポートするNTLMv2 over HTTPをサポートする。 実装は確かに ruby -ntlmと同様の実装となっている。 ruby -ntlmをラッピングし、NTLMv2のサポートをする部分だけの薄いgemである。 実装してみよう この中でどれを利用するかというと ruby-ntlm かなと判断した。 これで実装してみよう。と言いたいところだが、実装は至って簡単で、以下が全容である。 http = Net :: HTTP .new( @host_name ) request = Net :: HTTP :: Get .new( @path ) request.ntlm_auth( @user , @domain , @password ) http.request(request) We are hiring! いかがだっただろうか。 弊社の相手は エンタープライズ 故に、クライアントが持つ古い技術と弊社が持つ新しい技術を ブレンド して解決を試みるようなこともままある。 こういった課題はネット上に情報がそもそも無いことも多く、自身で調査して実装する面白さがある。 弊社はとことん試行錯誤しながら課題を解決していくエンジニアを募集中である。 右上の「採用ページへ」のリンクから職種を探してみて欲しい。 参考文献 NTLM認証とは - 意味をわかりやすく - e-words NTLM 認証と LDAP 認証の違いは何ですか。 - Cisco NTLM認証 - 通信用語の基礎知識 NTLMとは?複合機のエラー発生時の対処法を紹介!|複合機リースの格安NO1|株式会社じむや チャレンジ/レスポンス認証とは - 意味をわかりやすく - e-words @IT:@ITハイブックス連携企画 > Windows サーバー セキュリティ徹底解説 NT LAN Manager - Wikipedia Microsoft NTLM - Win32 apps | Microsoft Learn LMハッシュ - Wikipedia NTLM Overview | Microsoft Learn パスワードWindowsローカル SAM データベースに LAN Manager (LM) ハッシュを格納AD防止する - Windows Server | Microsoft Learn NTLM ユーザー認証 - Windows Server | Microsoft Learn チャレンジレスポンス認証とは - 「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典 [MS-NLMP]: NT LAN Manager (NTLM) Authentication Protocol | Microsoft Learn 【図解】わかりやすいNTLM 認証の仕組みとシーケンス, pass-the-hash について | SEの道標 Windows NTLM認証とマン・イン・ザ・ミドル攻撃 - Cyber Security Management 2004年5月号 Data Encryption Standard - Wikipedia MD4 - Wikipedia [MS-NTHT]: NTLM Over HTTP Protocol | Microsoft Learn The NTLM Authentication Protocol and Security Support Provider この記事を書いた人 Yuki Nishimura 雑食系エンジニア
アバター
組織の情報爆発 突然ですが組織で働くすべての皆さん、所属組織のルールや方針、意思決定やその背後の理由についてどの程度把握されているでしょうか?突然理由も知らされず組織ルールや方針が更新されたり、日々の仕事において必要な承認や確認の取得に時間がかかっていないでしょうか。 MNTSQでは、 (センシティブな個人情報を除いて)組織の職位やロールによらず誰もがアクセスできるようにする そのためにドキュメント駆動で意思決定や相談記録を残し、誰もが非同期でそれらにキャッチアップしながら賛否や新案を提案できる GitHub のIssue機能を介して、ゴミの分別ルールからビジョンレベルの意思決定まで誰もが発議できる などの文化を大切にしています。これらの効用については様々な発信があるので、他の記事に譲りますが、同時に次のような悩ましい トレードオフ 問題に直面しています。 組織の拡大に伴って発信される情報が 非線形 に増える(活発なことは大変良いが) それに伴っていまのタスクや会議に必要な情報の アクセシビリティ が低下する(検索・優先付与の難度が上がる) 新しく入ってきた人ほど不明な概念が増え、キャッチアップ難度が上がっていく( SaaS という1枚岩プロダクトをみんなで連綿と作っているので、咀嚼しないといけない情報分量が大きいという弊社特性もある) ※COVID-19新規陽性者数と見紛うexponential 社内の発信メディアとしては、日々のslackやdocsや GitHub やらが枚挙できますが、入社して数ヶ月の私としては、 アサイ ンタスク遂行を丁寧に完遂することを主目標に据えつつも、 GitHub に挙がっている組織Issueのキャッチアップが蝸牛の歩のごとく進まないことを課題に感じておりました。 というのも入社時点でMNTSQの GitHub レポジトリにはIssue総数が2kほども転がっており、技術的なものはそれぞれが非常に込み入って見えました。(ちなみに rails レポジトリのイシュー数は執筆時点で0.3~0.4kぐらい。流石に vscode は5k超えとかだけど...) また GitHub のUI上、1本釣りやリストアップ型の体験は用意されているけども、サンプリングした上で全体の関係性や位置付け、優先順位を把握するのがどうも難しい印象を受けていました。 ネット上の共感者を探してみるとこんなのを見つけました。 Visualizing GitHub Issues Issues also show whenever they are mentioned by other issues or by commit messages. It is apparent that all these concepts and mechanisms are deeply interconnected. However, GitHub shows all this information in textual form and shows the interconnections through hyperlinks, which hinders a comprehensive understanding of the evolution of an issue. この文言が完璧に私のもやもや感を明言化していました。 GitHub ではUI上イシューの想起関係などが見えにくく、問題解決において重用なサブタスクに分割することなんかにも心理障壁があったりします。特にテキスト情報処理に優れた人と、映像や概念で情報処理する傾向にある人だと前者寄りのUIに感じていました。 この問題に対して、試しにIssue間のリンク構造を可視化してみることにしました。 そうすることで全体における位置付けや分類が多少なりとも進むのではないかという気がしたのです。 GitHub イシューの関係性をグラフ構造で可視化する Step1. GraphQLによるリソースの取得 ググってみると、同じア イデア を持った人がすぐに見つかりましたので、こちらを元に味付けしてみることにしました。 GitHub Issues Graph with Netlify and GraphQL 基本的なア イデア はIssueのメンション関係を取得し、グラフ化することです。同時に以下のような情報も含められるとよいと感じました。 誰の起案か=Ownerは誰か コメント 誰が参与しているかもフィルタしたかったので取得(例えば代表や社内のマネージャが関わっているイシューは優先度や解像度が高いかもしれないといったこと) ラベル(MNTSQでは各イシューに☆1~3でざっくりと優先順位をつけているため、これは最低限取得したい。) 以上、簡単な初期仮説を元に、一旦はopenなイシューに絞って社内Issueを管理しているレポジトリから必要な情報を GitHub のGraphQL API を介して取得します。 最終的な取得 スクリプト (TypeScript)は以下のような形で、この結果を一旦適当な場所に保存して、それをパースしていきます。 const { Octokit } = require( "@octokit/rest" ) const fs = require( 'fs' ) // Create a personal access token at https://github.com/settings/tokens/new?scopes=repo const octokit = new Octokit( { auth: "Your access token here." , //ここでは簡単のためベタ書き } ) const targetOwner = "hoge" const targetRepository = "huga" const fetchIssues = (endCursor: any): Promise<any> => new Promise((resolve) => { setTimeout(() => { let fetchRes = octokit.graphql( `{ repository(owner: ${targetOwner} , name: ${targetRepository} ) { issues( first: 100, ${endCursor ? `after: " ${endCursor} "` : '' } , states: OPEN) { totalCount pageInfo { startCursor hasNextPage endCursor } edges { node { number url title author { login avatarUrl } body labels(first: 10) { nodes { id name } } comments(first: 50) { edges { node { author { login avatarUrl } } } } timelineItems(first: 100, itemTypes: CROSS_REFERENCED_EVENT) { totalCount pageInfo { startCursor hasNextPage endCursor } nodes { ... on CrossReferencedEvent { source { ... on Issue { number } } } } } } } } } }` ) resolve(fetchRes) } , 500) } ) const getAllIssues = async () => { let curCallNum = 0 let endCursor = '' const maxCallNum = 20 let edges = [] while (curCallNum < maxCallNum) { // eslint-disable-next-line no-await-in-loop let res = await fetchIssues(endCursor) // res = JSON.stringify(res) endCursor = res.repository.issues.pageInfo.endCursor let hasNext = res.repository.issues.pageInfo.hasNextPage edges.push(...res.repository.issues.edges) console.log(endCursor, hasNext) if (!hasNext) { console.log( 'All issues has been fetched.' ) break } curCallNum = curCallNum + 1 // console.log(`fetched res: ${edges}`) } return edges } getAllIssues() .then((res: any) => { let resJSON = JSON.stringify(res) fs.writeFileSync( './data/issueData.json' , resJSON) } ) Step2. 取得した JSON オブジェクトをd3.jsでグラフ構造化/可視化 次に上記 スクリプト で取得した JSON データを元に実際にグラフ構造を描画していきます。 またグラフ描画ライブラリとしてd3.jsを採用しました。今回は ForceGraph を応用します。これを使うとこんな感じでグラフのnodeとedgeにいい感じに引斥力を与え、それを物理的にシミュレーションすることでいい感じに適当に集まったり離れたりしながら各nodeの位置を決めてくれます。 ここではコードの詳細は省略しますが、初期仕様としては上記情報爆発課題にかんがみて、以下のような機能を実装しました。 ノードが個別のIssue、エッジが参照関係となるグラフの描画 ただし孤立したIssueまで書き込むと情報爆発そのものを眺める羽目になるため、各ノードの次数(Incoming Edges+Outgoing Edges)に応じて描画するノードをフィルタできるようにする ノードの半径は上記の次数に応じて大きくする(ハブとなるイシューが一瞥できるようにする) 3段階の優先度ラベルに応じて金銀銅の外円を描画 ノードの中にownerアイコンを描画する ノードをクリックすると詳細を右ペインで把握できるようにする(ここは GitHub の検索時UIを模す) 俯瞰図 さて、この可視化の結果どのような示唆や効果があったでしょうか。以下 ドッグフーディング の結果をレポートします。 Issueグラフ作成の結果と示唆 このアプリケーションの使用感として、以下のような発見がありました。気づいた順に列挙してみます イシューをポチポチしていったときに、すべての情報がロード済みなのでダウンタイムなく内容確認できるスピード感はよい(俯瞰できる感は本家よりスムーズ) 上図でいうと、金/銀/銅色の円で一覧描画されるのでざっくり優先度をつかめる これはCOOを始めとして弊社各員の地道なイシュー回覧のおかげ GitHub でもラベルによるフィルタできるので比較した差分は小さいけど、金塊や銀塊あたりを発見するにはよい感じ 母艦イシューや関連性を中心に会社のメインイシューを把握できる 例として右下の クラスタ (金銀銅の混合山)はビジネスメンバが多く、関連するイシューは主に導入効率化に係るものが多い 弊社ではありがたいことに引き合い多く、一方で大企業法務のお客様は既に複雑かつ巨大な業務システムを抱えてらっしゃるケースが多く、このような事情で導入や整理が大変であるという関連課題がまとまっている 左下の銀山は社内事務の効率化にかかるもので、個人的に肌感もてている感じではないものの、複数の当事者の間で問題意識が共有されているあたり からし て無視できないのだろう 人の切り口で見る 左上の金銀山はsalesメンバーの巻山さんがownerで、見てみると新規顧客獲得に係るア イデア や議論のようだ。一人で関連イシューを出しており、強い気持ちを感じる! 福井さんは母艦イシューのownerであることが多く、コンサル出身で社内の情報整理に長けているので、納得できる マネージャーの稲村さんのイシューは海外製品の調査などに係るもので、私も似たイシューを立てたことがあった。完全な セレンディピティ だが、これを起点に似たモチベーションをもった人がちらほらみつかった 左中段の クラスタ には人事の細野さんがおり、採用や人事メンバに関連した課題群がみつかる といった具合にグラフ構造はメンバー間の想起の歴史を反映している点は意義深く、俯瞰性と偶発的発見に特化した可視化手段として一定有用だなと感じました。 改善案や課題 以上、良い体験や発見をベースに列挙していきましたが、改善点も同時に多くみつかりました。こちら列挙すると イシュー間のmentionが不十分 むしろ孤立したイシューのほうが多く、それらの中に重用イシューが散在している場合もある 検索によるフィルタリングが同時にできるとよい duplicatedなイシュー、似たイシューをツール内で見つけられるようにできるとUXとしてかなり改善しそう(ただしそれによる GitHub との差分は少ない) 既にイシューの概念構築がすすんでいれば、欲しい情報の一本釣りは GitHub で十分とも感じる また情報爆発の対処法は誰かが整理したもの(或いはこのように自動的に整理されたもの)を受動的に享受するだけだと片手落ちだと感じました。 イシューからはじめよ に記載ある通り、人間の「理解した」の本質は、概念間の関係を見出したり、逆に無相関であることが明らかになることによって訓練される想起ネットワークだと考えています。情報記述する側の各員による配慮やこういった自動的可視化の支援は最低限度必要であるものの、それらにかまけず自分からゼロベースで概念を整理する、配線するといった方向性を助ける方が、寧ろ本質的解決につながるかもしれないとも思い至りました。 とはいえグラフ構造はチャットツールのような1次元リスト構造や一通り関連情報がまとまっている クラスタ (docsに近い)、 wiki や マインドマップ のようなツリーに比べるとリソースフルかつ整理コストが低く(想起したらリンクすればよい!)、また上記の通り人間の概念把握 トポロジー に近いと思われるため、整理と抽出のバランスがよい点が改めて良いなと感じました。 参考: 階層整理型WiKiはスケールしない ー私は階層型整理は書き手側の読み手への配慮手段の一つだという考えですが(そのため書き手側に回るとつらさが出てくる)、ネットワーク型の情報整理の利点については scrapbox の考えが非常によくまとまっています。 この記事を書いた人 白井佑治 MNTSQ社で 機械学習 ・ 自然言語処理 に関わるもろもろをやっています。好きな食べ物は担々麺です。
アバター
新しい仲間 前回はHot Docsという bot を紹介した。Hot Docsは組織拡大に伴う情報爆発において、情報にattentionをつける仕組みだ。 tech.mntsq.co.jp 今回紹介するのは、Peer-to-Peerフィードバックを促す bot だ。 Peer-to-Peerフィードバックとは Peer-to-Peerフィードバックは、個人成長やチーム開発を目的として個人から個人へ行うフィードバックである。Peer-to-Peerフィードバックの効果や方法についてはGitLabの360 Feedbackがより参考になる。 about.gitlab.com 行為や責任の所在を明確にするためチームではなく個人に対してフィードバックすることが重要であるが、個人攻撃とならない/捉えられないために次のような点に注意する。 フィードバックを送る側: リスペクトを欠く言動をしない 個人そのものではなく個人の言動や成果等、具体的な事象に対してフィードバックする フィードバックを受ける側: 個人攻撃ではないことを理解する フィードバックによって何を良くしようとしているか理解する なぜ作ったか MNTSQでは全メンバーから見える形でフィードバックが行われることを推奨しているが、内容がソフトスキルに関する等でセンシティブである場合はプライベートで行われることを許容している。 プライベートなフィードバックは機会が少ないので、 bot により促進する仕組みを作った。 Peer-to-Peerフィードバック bot の仕組み この仕組みを作るにあたり、Ubieの人を介さない自動フィードバックのすすめを大いに参考にした。 note.com フィードバックのフロー bot によるPeer-to-Peerフィードバックのフローは次のとおり。事前/事後に1on1を行うこともある。 bot が受信者を日次で アサイ ンする 受信者が Google Formで送信者3人を選ぶ bot が送信者3人にフィードバックをリク エス トする 送信者が Google Formでフィードバックを送る bot が受信者にフィードバックを送る 受信者と送信者への通知はすべてSlackで行われる。 受信者の アサイ ンでは次のような通知が届く。メッセージやアイコンはランダムで変わる。これは社長の例。 受信者がフィードバックをリク エス トするとき、フィードバックのテーマを指定できる。 送信者がフィードバックするとき、内容は伊藤羊一氏のGoodとMottoで「今よりもっと良くなる」ことを意識して書く。 www.hito-link.jp フィードバック履歴の可視化 フィードバックの履歴はSpreadsheetに記録し、月1で集計結果をSlackに通知する。 グラフを見るとリク エス トに偏りがあることがわかるので、今後の改善に活かしたい。フィードバックの内容はセンシティブなので記録していない。 技術的な話 この仕組みを作る上でいくつか技術的なポイントがあるので挙げておく。 アーキテクチャ bot のプログラムはすべてGAS( Google App Script)で動かしている。 受信者の アサイ ンと可視化の通知は時間ベースのトリガーで、それ以外は Google Formの投稿をトリガーにしている。休日にSlackでメンションが来るのは治安が良くないので休日はスキップするようにしている。 Google Formにパラメータを渡す 送信者がフィードバックを送るときに受信者をデフォルトで埋めておきたい。 これは Google FormのURL https://docs.google.com/forms/d/e/hoge/viewform に ?entry.12345=itaya のようなクエリ文字列を繋げることで実現できる。 12345 は質問ごとに割り当てられるIDで、「 google form entry id」等で ググる と調べ方が出てくる。 SpreadsheetのグラフをSlackに投稿する SpreadsheetでプロットしたグラフのBLOBをSlackに投稿するには files.upload API を使う。しかし、これだけではやりたいことが実現できない。なぜなら、 chat.postMessage と異なり icon_emoji と username が変更できないからである。アプリの表示設定で変更できないことはないが、これだとちょっと都合が悪い。 そこで(美しくないが) files.upload で個人のチャンネルにアップロードしてから画像URLを取得して、 chat.postMessage の attachments に埋め込む形で投稿するようにしている。 api.slack.com api.slack.com 運用してみて 運用を始めて9ヶ月が経った。少なくとも運用を始めたことによって会社全体のフィードバックは増えていると感じる。 フィードバックをもらった感想 結構緊張する。筆者自身は顧客対面にもっと出ろというフィードバックをもらってミーティングに出るようになったり、声が小さいと言われて腹から声を出すことを意識するようになったりした。 他の方から感想を募集したところ、次のようなコメントをいただいた。 どきどきしますね。でもしっかりGoodでさんざんホメてくださった後に、すこしだけ丁寧なMottoがつづいていて、やさしさを感じた。Mottoの内容は家族からもたびたび指摘されていることだったので、マジで気をつけます FB者から見て自分がどう見えているのか、全然意識していなかった部分も知ることができてよかった 入社してすぐのタイミングでも、業務面、キャ ラク ター面の両方からフィードバックをもらえた。周囲が新メンバーに気を配ってくれていることがわかり、安心した。 フィードバックを送った感想 結構緊張する。1件あたり最大2時間かけて返すのでめちゃくちゃ大変だが、相手にとって僅かでも価値のあるフィードバックであることを願う。 こちらも他の方から次のようなコメントをいただいた。 結構大変。口頭でお礼言われたりもするので、やってよかったなと思う。 おしまい あなたの会社でもぜひ試してほしい。
アバター
MNTSQ( モンテスキュー )という契約書管理の SaaS 製品を開発する会社で、 アルゴリズム エンジニアをしている坂本です。 契約書に書かれた情報を自動で抽出する仕組みを作っています。 概要 正規表現とはなにか 正規表現を書きにくい、中ボス的なテキストが存在する regexモジュールのFuzzy match機能を使って、楽に中ボスを倒す Fuzzy matchの使用例 裏話 実は、採用ブログも兼ねています この記事を書いた人 概要 私も非エンジニア出身であるため、Techブログではあるものの、 幅広い方に読んでいただきたいと思いました。 このブログの内容をざっくり図解すると、こんな内容を扱います。 正規表現 とはなにか テキスト(=文字列)に対して、マッチさせるパターンのことです。 特定の文字を含むテキストを探すときに使います。 例えば、  テキスト1:「庭には2羽ニワトリがいる」  パターン1:「ニワトリ」 だった時、このパターン1はテキスト1にマッチします。 ほかにも、  テキ スト2 :「庭には2羽鶏がいる」  パターン2:「鶏」 のように書けば、パターン2はテキ スト2 にマッチします。 テキスト1とテキ スト2 のどちらにもマッチするパターンを書きたいときには、  パターン3:「(ニワトリ|鶏)」 のように「( )」と「|」という記号をつかって、「ニワトリ or 鶏」というパターンを書くことができます。 1つのパターンでテキスト1にも、テキ スト2 にも、マッチします。 このように、裏技的な記号を使えば、 ある程度のバリエーションを持つテキストに対するパターンが書けます。 正規表現 を書きにくい、中ボス的なテキストが存在する ところが、どうにも 正規表現 が書きにくいテキストが存在します。 それは、 ランダムにノイズが入る タイプのテキストです。 弊社の場合だと、紙の契約書を OCR ( 光学文字認識 )したテキストが該当します。 紙で締結した契約書を解析したい場合、 最初に OCR をして 画像(紙をスキャンしたPDFファイル)からテキストを取り出した後、 取り出したテキストを対象に、いろんな方法で、 契約当事者や、契約締結日を抽出したり、契約類型を推論したりしています。 OCR は、見た目に忠実に認識します。 印字のレイアウトの影響を受けますので、 たとえば、1文字1文字の間にスペースが挟まっていたり、 ほぼ完璧に認識できているのに1文字だけ間違っている、ということがよくあります。 単純な 正規表現 のパターンを書くと、 このようなエラーを含むテキストにはマッチしません。 そこで、記号を使ってある程度のバリエーションをカバーできるようにしたいのですが、 どこにエラーが出るかがまったくランダムなので、とても難しいのです。 regex モジュールのFuzzy match機能を使って、楽に中ボスを倒す 中ボスを楽に倒すということで、 課金アイテm 無料ですが、 別途インストールが必要な regexモジュール を使ってみます。 Python をインストールすると標準でついてくるモジュールの中にも、 re という 正規表現 を扱うためのモジュールがありますが、 regex にはreにはない便利な機能があります。 具体的には、Fuzzy matchと呼ばれる機能をつかって、 ある程度の揺れ幅を許容してパターンとテキストをマッチングさせることができます。 OCR 結果のように、多少のノイズが想定されるテキストを扱う際に重宝します。 Fuzzy matchの使用例 regex のFuzzy matchは、通常の 正規表現 を丸カッコでくくった後、 その直後につけた中括弧の中に細かい指示を書いて使います。 例えば、下記のtext1とtext2にマッチさせるパターンを作るには、 patternAのように書きます '(?: 業務委託契約 書){i:\s}'の後半にある {i:\s} がポイントで、 「\s(空白文字)」の[i(挿入)]を許容しています。 import regex text1 = '業務委託契約書' text2 = '業 務 委 託 契 約 書' patternA = '(?:業務委託契約書){i:\s}' regex.match(patternA, text1) <regex.Match object ; span=( 0 , 7 ), match= '業務委託契約書' > regex.match(patternA, text2) <regex.Match object ; span=( 0 , 13 ), match= '業 務 委 託 契 約 書' , fuzzy_counts=( 0 , 6 , 0 )> また、下記のtext1とtext3にマッチさせるパターンを作るには、 patternBのように書きます。 こんどは、'(?: 業務委託契約 書){s}'の後半にある {s} がポイントで、 文字の入れ替わり(substitution)を許容しています。 import regex text1 = '業務委託契約書' <br> text3 = '業務季託契約書' <br> patternB = '(?:業務委託契約書){s}' <br> regex.match(patternB, text3)<br> <regex.Match object ; span=( 0 , 7 ), match= '業務季託契約書' , fuzzy_counts=( 1 , 0 , 0 )> 実際には、データの傾向を見ながら、上記のパターンを調整します。 機械学習 で解決しても良さそうですが、 データが少ない場合や、試作を作りたいときに、 学習用データを作らなくても動作とデータの相性をざっくり掴むことができます。 裏話 実は、採用ブログも兼ねています 正規表現 、 自然言語処理 、 機械学習 、MLOps、検索、Elasticsearch といった分野に関係のある仕事をお探しの方で、 経営層と近い距離で、 裁量を持って領域横断的に働くことにご興味を持っていただけましたら、 ぜひ右上の採用ページからお問い合わせください。 また、法律分野、もしくは 言語学 の知識を生かして、 学習用データを作ったり実験サイクルを重ねて製品を作ることにご興味のある方も、 どうぞご連絡ください。 (私も、もともとは 言語学 でした。) この記事を書いた人 坂本明子 MNTSQ, Ltd.で アルゴリズム エンジニアをしています。 入社エントリはこちら www.wantedly.com
アバター
MNTSQ( モンテスキュー )株式会社で検索エンジニアをやっている溝口です。 MNTSQはMNTSQ CLM(ちょっと前までMNTSQ for Enterprise)という数十万件を超えるような契約書を管理するプロダクトを提供しており、以下のような検索機能を提供しています。 https://speakerdeck.com/mntsq/mntsq-careersdeck?slide=12 (公開時点) 今年の4月で入社してから2年が経ち、入社してからやってきたことの振り返りも兼ねて今回は検索処理周辺の コンポーネント の変遷をまとめてみました。 最初期(2020/08-2021/07) Index sizeの増加に伴う変更(2021/08-2022/04) インデクシングの高速化と複雑なクエリへの対応(現在) (今後)マルチテナントへ まとめ この記事を書いた人 最初期(2020/08-2021/07) 検索エンジン とインデクシング処理をコンテナとして独立させ、間にELBを挟んでリク エス トを分散していました。 オンプレミスの環境では、インデクシング処理はコンテナとして独立させたものの、サーバ台数の都合からシング ルノー ドで運用していました。 (MNTSQでは AWS 上だけでなく顧客の要件に応じてオンプレミスでもサービスを提供しているため、双方でよしなに動くようにインフラを設計する必要があります。) 1 , 2 Index sizeの増加に伴う変更(2021/08-2022/04) 契約の検索のみから、契約の管理へとプロダクトラインが広がるとともに 検索エンジン が提供する機能と扱うデータ量も増加していきました。 また、契約書は契約書間で似たような条項が存在したり(MNTSQは条単位での検索も可能です)、微妙に言い回しの違う締結前のドラフトなど、場合によりますが検索結果としてはグルーピングして表示してほしいものなどがあります。そういった機能を検索処理側で行っているため、徐々に検索パフォーマンスが落ちていくという問題に直面しました。 特に AWS 環境において従来の構成ではディスクI/Oによる検索速度の劣化が目立ってきたため、SREと相談してECS上での稼働をやめて自由度の高いEC2上に移動させ、ディスクをEBSからInstance storeに変更して速度劣化を解消しました。 ほぼ同時期にEC2の インスタンス を ARMアーキテクチャ に変更し、コストを維持したままCPUコア数を増やし、性能を 最大80%程度 向上させることに成功しました。 また、上記に加えてお客様が増えるにつれて当初の想定以上のデータをお持ちのお客様からの受注も頂いており、今後もデータ量の増加が予測できたことから、柔軟にシャード分割数を変更できるようにしました。 インデクシングの高速化と複雑なクエリへの対応(現在) 検索エンジン の役割が増えていくにつれて、変更された内容が登録 & 反映されるまでのリードタイムの影響が大きくなってきました。そのため、 ソフトウェアエンジニアの沼井の力を借りて インデクシング処理を Ruby on Rails に移管し、最大40%程度のリードタイムの短縮を実施しました。 また、当初は制約こそ非常に強いものの、それなりの結果を出せるようなクエリの仕組みを提供していましたが、より自由なクエリを提供する必要に迫られました。そのため、 クエリパーサーを実装して クエリの自由度を上げるとともに、その状態でもより良い検索結果が提供できるように アルゴリズム エンジニアの知見を検索処理に応用できるように、検索処理を Ruby on Rails から Python ベースのFastAPIに移行しています。 3 , 4 , 5 (今後)マルチテナントへ 現在、MNTSQでは 契約案件の管理プロダクトを開発中 であり、そこでも 検索エンジン を利用する機能を提供する予定です。 また、多くのお客様から受注をいただく中で、運用コストが非常に大きくなってきており、そこを減らすことが将来的に課題になると考えています。 そのため、マルチテナントをデフォルトの構成とし、インフラの スケールメリット の追求と運用コストの低減を実現したいと思っています。 まとめ MNTSQのこれまでの検索周りのインフラ構成を振り返りました。 紹介したのは大きめの変更をしたときのものですが、もっと細かい変化を含めるともっと活発に変化し続けています。 MNTSQの検索処理ではプロダクトの成長/プロダクトラインの増加、導入先の増加とともに進歩し続けています。その中で今回触れたような構成変更には当然SREやソフトウェアエンジニアとの協働が必要になってきます。また、当然ながら検索に利用する メタデータ の抽出は アルゴリズム エンジニアが担当している場合が多いため、 自然言語処理 分野では アルゴリズム エンジニアと協働することもあります。 上記のように検索処理はインフラ・ソフトウェア・ アルゴリズム ( 自然言語処理 )にまんべんなく携われるという魅力があるとおもいます。 検索処理以外でも、もしご興味を持っていただけたなら、右上の「採用ページへ」からご応募いただければ幸いです。 この記事を書いた人 溝口泰史 MNTSQ社で検索エンジニアをしています。 https://guides.rubyonrails.org ↩ https://www.elastic.co/elasticsearch ↩ https://guides.rubyonrails.org ↩ https://fastapi.tiangolo.com/ja ↩ https://www.elastic.co/elasticsearch ↩
アバター
こんにちは、MNTSQ( モンテスキュー )でSREをやっている中原です。 前回(?)、 突撃!隣のPCデスク!! MNTSQ キーボード・マウス編 というタイトルでブログを書かかせてもらいました。社内外から(特にエンターキーについて)大変反響をいただきうれしいなぁという気持ちでいっぱいです。 開発者がこだわるものというのは、そのこだわりの強さ故に争いを生むことがあります。時に「 宗教戦争 」とも称されるその争いのテーマとしては、前回取り上げたキーボードの他、ブラウザやOSなどもあげられようかと思います。 今回は、そんな「 宗教戦争 」になりがちなテーマの一つである「 きのこたけのk *1 エディタとターミナル エミュレータ 」を取り上げ、MNTSQ社内のエンジニアがどういった開発環境で仕事をしているのかを紹介していきます! 基本的に調査対象としては、MNTSQ社内でエンジニアリングまたは運用上コードを修正したり、サーバに入っての作業がある人となっています。また、複数回答可としています。 エディタ編 エディタについては、17名から25件の回答をもらうことができました。早速グラフを見ていきましょう。 ちなみに、第一候補だけだとこんな感じに。 いわゆる テキストエディタ と IDE とが、それぞれ入り混ざる結果となりました。その中でも Mac 、 Windows ユーザともに圧倒的な Visual Studio Code の強さが見て取れます。続いて Vim というところも、私の中での世の中一般の感覚とそれほど大きくはありません。ただ、PyCharm、RubyMine、WebStormといった、JetBrains製品を合計すると、 Vim を上回るという絶妙な数量というところはありそうです。 回答を見ていくと、コーディングでは IDE や重めに拡張を入れた Visual Studio Code 、軽い編集などの際に Vim などを使うという例が多そうです。実際私も、重めの編集では Visual Studio Code を、そうじゃない場合には Vim を使うことが多いです 軽い処理に使うエディタ: Vim 、CotEditor、 EmEditor Sublime Textの利用者の理由として「達人 プログラマ のなかで、一つのエディタを使っていったほうがいいという記述があり、それに則って他を使わないようにしている」というものがありました。おそらく対象はなんでもいいと思うのですが、自分の使う道具はしっかり使いこなせた方がいいというのはありそうですね Vim 一本という社員は「 Vim を使えない者、使わない者はエンジニアに非ず」と言っていました。5年前までは私もそうだったのですけど… neovimを使ってる人がいないのは意外かもしれません。移行しようとして結局できていないのがどうも私です なお、MNTSQでは必要に応じて、好きなソフトウェアの購入も可能です。もちろん、 Sublime TextやJetBrains製品も可能ですよ! ターミナル エミュレータ 編 こちらについては、複数のソフトを使い分ける人が多くないという性質上、割と平和な結果になっているように思います。グラフをどうぞ。 開発者には Mac を支給することが多いため、 Mac 標準のターミナルとiTerm2が2強となりました Windows をメインマシンとする社員はそれぞれ、WSL2とFluent Terminalを利用。人数が少ないのはありますが、 Windows Terminalがいなかったのは意外でした Fluent Terminalは初めて聞いたのですが、iTerm2のカラー スキーマ がそのまま使えるとのこと。 Mac でiTerm2を使ってる人の移行先としては候補になるかも知れませんね Ubuntu 利用の社員はHyperでした また、専用ソフトを使わずに Visual Studio Code のコンソールを利用するという人も。私も時々使いますが、ついうっかり、 vim コマンドを叩いてしまうことがあるんですよね… 総じてエディタに比べると戦争感は少ない印象ですね。 Windows のユーザがもう少し多ければ、 Windows Terminalや PuTTY 、 TeraTerm などもあったのかなと思います。 まとめ 以上、MNTSQの開発にあたって、エンジニアの開発環境をまとめてみました。個人的な感想としては、「意外とバラツキすくなかったかな?」という印象を持ちました。もっと「こういう環境がおすすめです」「なんで Emacs の人がいないんですか?」「 Poderosa 派です」という方がいらっしゃったら是非カジュアル面談へお越しください! この記事を書いた人 中原大介 MNTSQ社でSREをやってます。愛車を車検に出したら30万と言われましたが、乗り潰す覚悟で頑張ります。 *1 : なお私は きのこ派 です
アバター
書き出し MNTSQ( モンテスキュー )株式会社でプロダクトマネージャーをしています、野中と申します。 この記事を読んでいる貴方様も、きっと同じIT業界の住人なのではないでしょうか。 さて、IT系の、しかも社員エンジニアの方々(いちおう私含む)が背負う宿命といえばーーそう、テックブログの執筆、ですね。 昨今、優秀なエンジニアを採用したい企業が芋洗い的にごった返しており、「なんとかウチの良さをアピールしなければ!情報を発信するんだよ!当番制でテックブログ書こうよ!」というムーヴもすっかり業界に定着したように思えます。 もちろん弊社もごった返す芋のひとつですので、この辺り手を抜くわけにはいきません。私も、当番が回ってきたからには立派な記事を書き、弊社社員の優秀さをアピールしたい、と考えておりました。 ーーところで、現実って残酷ですよね。なんやかんややっているうちに、いつの間にか「テックブログ:下書き社内公開予定日」まで来てしまいました。そして今、私はこの「書き出し」を書いています。もちろん、テックブログのテーマはまだ決まっていません。 私が心の支えにしている言葉に「大事なのは、ここからどうするか、だ。まずそれに集中しろ」というものがあります。若き頃の先輩の教えです。そう、この状況から何ができるか、残りの時間でどんな落とし所に着地させるか。仕事に携わる人は常に、それを真剣に考えるべきなのです。 ということで、活路を見出しました。 このブログのテーマ テックブログをカッコよく一回分先延ばしにさせてもらう方法を開発する(検証つき) 課題 そう、私がいま直面している課題、そして解決すべき課題は、これなのです。そして、きっと同じ課題にぶつかる同業者の方も、多くいらっしゃることでしょう。私が今、ベストとはいわずともベターな一手を実践し結果を共有することが、業界への貢献にもつながるのではないでしょうか。そう思うと、実践する勇気が湧いてきます。 方策 ここで、現在の状況を整理します。 今日中に、記事の下書きを終わらせたい。 この後、社内公開&レビューがある。なので、それなりの品質にはしておきたい。 最終的には「読者に刺さる、弊社に興味を持ってもらえる面白い記事」にする。 なのでネタは尖りすぎず、広い層に気軽に読んでもらえるものにする 「夏休みの工作」的なハンズオンものにしたい 前から興味を持っていたガジェット(デジタル文具)がある これでなにか作ってみよう ということで、改めて以下の方針でトライしてみようと思います。 テックブログは 「前編/後編で書くつもり」 とする 今回は前編とし、風呂敷を広げて終わりにする 実際に 苦労する モノを作りレポートするのは、後編とする また、この方法なら以下の副産物的メリットも期待できます。 「すごいヤル気がある!」ことが伝えられる 万が一、前編で反響が芳しくない場合は、そこで打ち切ることができる(決定の先送り) 次回当番(=2ヶ月後)のネタも決まる 転んでもタダでは起きない。そういった精神が、現場では大事だと思います。 実践 方針が決まったので、次は実践です。 ここから先は、「最初から書くハズだったハンズオン系テックブログの前編記事」としてお読みください。 書き出し MNTSQ( モンテスキュー )株式会社でプロダクトマネージャーをしています、野中と申します。 この記事を読んでいる貴方様も、きっと同じIT業界の住人なのではないでしょうか。 この業界の方々は、やはりガジェットが好きな傾向があると思います。かくいう私も、以前から「これでなにか面白いことができそうだな」と目をつけていたモノがあります。今回はそのガジェットを実際に手に取り「日常が便利になるハックシステム」を作ってみようと思います(前編/後編の構成になります)。 本来のテーマ テプラの QRコード を使って身の回りの情報を管理しよう 話の起点 「テプラ」という有名な ステーショナリー があります。ラベルに印字してくれる、言わば機能特化型のミニプリンターです。 このテプラ(製品種別名はラベルライター)、高級モデル or 最近のモデル だと、 QRコード を印刷することができます。 QRコード は、いわずもがな、URL等をカメラ経由でモバイルデ バイス に転送するのに適した画像情報です。もっとも単純に使用する場合、数十文字程度のURLをそのまま QRコード に落とし込み、印刷し、配布します。印刷物を受け取った方は、自身の スマホ のカメラでそれを写し、ブラウザでURLにアクセスします。 この使い方を 「固定URL式」 とこの記事では呼ぶことにします。 やってみたいこと 家電ショップに並んでいるテプラを見て、ふと思いました。 「 QRコード の形で印刷されたURLを、プロキシサーバ宛てにしておいて、実際に利用するURLを後から書き換え可能にできたら、どうだろう?」 例えば、既に QRコード を印刷済みのラベルを先に持っておき、必要なときに(例:紙のノートに Wikipedia の参照を貼りたい)その「まだ紐付けされていない QRコード のラベル(ブランク QRコード )」を貼る。続けてその場で スマホ で QRコード を開くと、プロキシサーバ上の「URL登録フォーム」がブラウザで開き、そこで Wikipedia のURLを貼り付け登録する。それ以降は、その(同じ) QRコード を スマホ で開くと、直で Wikipedia がブラウザで開く。 この使い方を 「浮動URL式」 とこの記事では呼ぶことにします。 上記の例はとてもシンプルで、正直あまり驚きがないかも知れませんが、 QRコード およびプロキシサーバの以下の特徴を組み合わせることで、いろいろな ユースケース が発掘できそうです。 QRコード は、複製が可能である。 同じ QRコード を、複数の場所に貼っておける 不特定多数に配布できる QRコード は、だれでもアクセスが可能である またアクセスが容易である プロキシサーバでは、後から紐付けURLを更新することができる と、ここまで考えて「いや、こういうの、もうどこかにあるんじゃないか?」と思い、軽く探してみました。 市場、類似サービス 「 QRコード 市場」で調べると、以下の情報が出てきました。 4年後の QRコード 決済市場、12兆3,976億円に‐ 矢野経済研究所 が予測 https://news.mynavi.jp/techplus/article/20211021-2165641/ おお、そうか。 QRコード といえば電子決済。でもこれは「電子決済の規模」なので、あまり参考にならないですね。 株式会社 デンソー ウェーブ( https://www.denso-wave.com/ja/ ) QRコード を開発された会社です。 社員数:1,200名超、資本金 495M、という規模。 テプラ(ラベルライター)の市場規模……を確認したかったのですが、ぴったりの情報が出てきませんでした。 ちなみに、最大手商品の「テプラ」が、2018年で1,000万台突破(発売から30年)、とのこと またテプラの キングジム だけでなく、カシオ、ブラザー等が参入している また類似サービスでは、以下のものがヒットしました。 Mamoru Biz https://mamoru-secure.com/pay/?source=pay ラベル印刷した QRコード で物品管理ができる、とのこと 仕組み、システム的には、上記の「浮動URL式」の模様 オフィス向け。個人用ではない。 可変 QR https://qr.quel.jp/flex.php 「後から移動先URLを変更できる QR 」を提供 無償版は広告があるとのこと クルクルManager https://m.qrqrq.com/ 「可変 QRコード 」という呼称で、リダイレクト先を変更できる QRコード を提供 なるほど。 「個人用途の文具として、自由に使える浮動式 QR 」のサービス、製品は、まだ無いようです。 これは、夏休みの工作のお題としては、良いのではないでしょうか。 作ってみよう_1: ユースケース 実際に作るにあたり、ミニマムな ユースケース を書き出してみようと思います。 ちなみに、私は iPhone12 mini, MacBook Air を使っています。今回は、プロキシサーバ役をローカルネットワーク上の MacBook Air にさせることにします。 ミニマム ユースケース : 1:浮動式の QRコード を印刷したラベルをあらかじめ用意する 2:紙のノートにメモを書く 3:メモの横に、ラベルを貼る 4:ラベルを iPhone でスキャン 5: iPhone でブラウザが起動し、(プロキシサーバ上の)入力フォームが開く 6: iPhone 上で「メモと紐付けたい Wikipedia のページのURL」をフォームに入力し、保存 7:もういちど、ラベルを iPhone でスキャン 8: iPhone でブラウザが起動し、紐付けした Wikipedia のページが開く ここまでの一連ができれば、今回のハンズオンは成功!とします。 作ってみよう_2:システム構成 では次に、上記のミニマム ユースケース を実現するためのシステム構成、および構成要素の状態を洗い出します。 システム構成: A: QRコード 自体の作成 Mac のコンソール上で QRコード 画像を作成できる、という都合の良いgemがあったので、それを使う。 rqrcode https://github.com/whomwah/rqrcode B:ラベルライター( QRコード 印刷可能) まだ無し。10,000円以内で購入できる。 C: QRコード スキャナ 自前の iPhone を使用。 D:プロキシサーバ MacBookAir 上で、何かしらのサーバを立ち上げる Docker × Rails で良いか E:ブラウザ 自前の iPhone を使用。 こうみると、物品としては「ラベルライター」さえ調達すれば、あとは有りものでなんとかなりそうです。 作ってみよう_3:ラベルライターの調達 善は急げ、ということで、ラベルライターの調達まで進めましょう。 いろいろと考えた結果、せっかくならイイものを、ということで、以下を注文しました。 brother P-TOUCH CUBE PT-P910BT https://www.brother.co.jp/product/labelwriter/ptp910bt/index.aspx とても残念なことに、こちらが届くのが今度の週末になってしまうとのことです。 ですので、この続き「作ってみよう、使ってみよう」は、また次回の後編でまとめられればと思います。 終わりに いかがだったでしょうか? 特に、最後の「必要な機材の到着を待たなくてはならないので」という外的要因が提示されることで、無理なく自然な先送りが可能になるのです。 なお、ここまでを書くのに、中断を挟みながら4時間ほどかかりました。 実際、「最後までやりたいこと/書きたいこと」を完遂するには、追加でざっと2日ほど欲しいところです。 もちろん、完璧なものを最初から用意できればそれに越したことはないのですが、そうはいかないこともままあります。「漠然としたア イデア はあるんだが、カタチにする時間がなぁ」という状況に身を置かれるテックブログ同士におかれましては、こうい うしの ぎ方もありますよ、というメッセージをお届けできたなら幸いです。(最後に勝っていれば良いのです!) それでは、次回の後半をお楽しみに。 (モノは本当に作ります!) この記事を書いた人 mtq-nonaka MNTSQ株式会社でPdMをしています。
アバター
はじめに MNTSQ( モンテスキュー )株式会社 フロントエンド担当の安積です。 入社して4ヶ月とちょっと。 コードに取り組もうと入社して、まさに日々格闘しております。 私の後ろの席にはこんな バズ記事 書く人や、こんな イカつい記事 書く人が座ってまして、そんな プレッシャー 期待の中からお送りいたします。 tech.mntsq.co.jp tech.mntsq.co.jp 昨日はこんな記事も公開されています。 tech.mntsq.co.jp はじめに 現在のステータス またはMNTSQ考古学 リファクタリングやるぜっっ! 仕様書大事だよね 差分指向テストとは テスト環境の概要 テストデータ ブラウザ操作自動化 スクリーンショット比較 Playwriteの操作 ちょっとコードのサンプル 最後に この記事を書いた人 現在のステータス またはMNTSQ考古学 コードベースから見たMNTSQのフロントエンドは、0->1 のフェーズにおいて「 アーキテクチャ の精査」とか「クリーンなコードを書く」というよりかは様々な要素をとにかく形にして使ってみて、という繰り返しだった事が伺えるものになっていて、 Github の中で考古学的考察が必要な場面がかなりあります。 コードの行間から感じる事、色々あって解ってきた事かなりあるのですが、 MNTSQのコードは最初からきちんと管理されていて履歴を全て追える状態である、という事もあり考古学的見地から背景を読み解くのもなかなか面白みがある、って書いたら不謹慎でしょうか。 山積している課題も今までフロントエンド専任の方が居なかった事もあり、今までの経験で新たなPJに参画した時にはよく感じる事で、あとは程度問題、みたいに捉えています。 で、今進行している新機能の開発と平行して、既存のコードに対しての改善計画のロードマップを策定中です。 その一環として リファクタリング を行おうとしています。 リファクタリング やるぜっっ! 方針を幾つか書くと、 全体スコープでのリプレース、書き直しはしない MNTSQ入社以前に、リプレースの案件もそこそこ経験しているのですがリプレースが成功するには幾つかの条件があり、かなりハードルが高いです。 ネット上で探せば成功例が出てきますが、そういった成功例に価値があるのは失敗例が多いという事の裏返しでもあるのは皆さんご存知だと思います。 (この辺の話もいつか書きたいのですがここでは割愛) コーディングは命令的なスタイルから宣言的なスタイルに 命令的なスタイルは、その結果までが当該コードの関心事となる事から肥大化しがちです。 宣言的に書くことでイベントの連鎖からはある程度は解放されて、リアクティブなフロントエンド フレームワーク の恩恵を最大限受ける事ができるようになります。 膨大な スタイルシート に立ち向かう 上記の歴史からコード上では局所戦の跡がそこかしこに存在し、局所的に解決しようとすることでVueComponentではグロー バルス コープの CSS への上書き、重複したスタイル定義等が多数あり、コードの肥大の原因となっています。 ここが自分にとっても主戦場の一つになると想定しています。 スタイルシート って実はとても難しく、プロフェッショナルの戦場なのです。 進化が速いのに デバッグ が面倒、おまけに全てグロー バルス コープで定義されるものなので。 一言でいうと部分的に置き換えを進め、「 テセウス の船」みたいな事をやろうと思っています。 テセウス の船( テセウス のふね、英: Ship of Theseus)は パラドックス の一つであり、 テセウス の パラドックス とも呼ばれる。ある物体において、それを構成するパーツが全て置き換えられたとき、過去のそれと現在のそれは「同じそれ」だと言えるのか否か、という問題(同一性の問題)をさす。 テセウスの船 - Wikipedia その位、 リファクタリング の結果としての見た目は変えたくないと考えています。 本線は開発がどんどん進む中、「動く標的を撃つ」ような側面もあり、課題感あるのですがその話は別途。 ここで問題になるのが、 デグレ ーションをどうやって防ぐかという点です。 仕様書大事だよね 例えばTDD( テスト駆動開発 、Test Driven Development)においては、 まずテストを書く テストが通るような、固定値を返すコードを書く [Red] ロジック実装、テスト実行すると全ては通らない状態 [Green] テストが通るところまで実装 [Refactor] テストが通る状態をキープしながら、ブラッシュアップする という繰り返しで、コードを書く作業と並行してテストコードが蓄積されるように開発を進める手法が知られています。 テストを書くには当然ながら、テストが書けるようにケースが出せる状態まで仕様が落とせている事が必要となります。 そしてテストの関心事はこの「仕様が満たせているか」という所になります。 MNTSQの社内には"SSoT"という概念が浸透していて、仕様についてもSSoT化されてメンテナンスされています。 信頼できる唯一の情報源 (Single Source of Truth; SSOT) とは、情報システムの設計と理論においては、すべてのデータが1か所でのみ作成、あるいは編集されるように、情報モデルと関連するデータ スキーマ とを構造化する方法である。 信頼できる唯一の情報源 - Wikipedia ところが、MNTSQは「破壊的 PDCA 」を回すことを旨としており、やりたいことがコロコロ変わるということを前提とする必要があってですね。 Bizサイドからのリク エス トを都度仕様に落とすとしても比較的荒い解像度のものとなり、個別のケースについての仕様を全て落とし切るというよりかは、どんどん作る事を可能にしたい訳です。 一方、MNTSQは エンタープライズ SaaS で、扱っているデータの重要度の高さは言うまでもなく、要求される信頼性も並大抵のものではない訳です。 どうする、俺。 そこで、一旦現在の動作を正として、今後の改修のステップ毎に発生する「差分」に注目しようと考えました。 差分指向テストとは エンジニア観点では書いたコードが仕様を満たしているか担保したいのは当然なのですが(バックエンドチームはちゃんとやっているし、私自身も ユニットテスト の粒度でTDDするとフロー状態になってキモチイイのも知ってますが)、刻々と変わるフロントエンドについては、テスト項目をすべてコード化してメンテナンスし続けるよりは 改修と新機能追加の結果、変わった所はどこなのか 意図しない部分が変わっていないか(どちらかといえばこちらが重要) という辺りにフォーカスしようと考えました。 そして本当にクリティカルで動作を担保したい所だけテスト項目としてGreen/Redをチェック なおかつ、エンジニアがPullRequestを上げる前に手元で実行できるようになっていれば尚可、という方針としました。 つまり、差分指向テスト とは 開発作業の前後の出力の差分を比較することで、ケースまで落ちない粗い粒度の仕様からの実装であっても開発作業の結果を判りやすく、かつ不要な影響が出ていない事を確認するテスト というイメージです。 えっ、そんな、と思った方は詳しくお話聞かせて下さい! カジュアルに面談でお話しましょう! 今回は画面 スクリーンショット を例に取りますが、別にDOMでも良いし JSON でもdiffは取れます。 私は行動解析の経験もあるので、そういった辺りも差分は発生するので追って対象にしたいと考えています。 とにかく開発作業の結果、「変わった点」と「変わっていない点」にフォーカスします。 ちなみに呼び名は私が勝手につけたものです。(ここ重要) テスト環境の概要 テストデータ コードの出力の差分にフォーカスするので、それ以外の特に入力データは毎回同じものを利用する必要があります。 また、内容としても実際に使われるデータに近いものでないと意味は半減します。 (「ああああ」なんて文字列入れてテストしても気持ちが持てない、と思いませんか?) 幸い、MNTSQでは個々の開発者のローカル環境用にステージング環境のデータを取り込む機構が整備されており、これを利用します。 ブラウザ操作自動化 自動でブラウザを操作してログイン、シナリオに沿って自動で操作して目的の画面で スクリーンショット を次々に撮る形とします。 こういった用途には Selenium やPuppeteerが有名ですが、今回はPlaywrightを使います。 github.com MNTSQは対象ブラウザを Chrome と Chromium 版Edgeに限定しており、本記事でも Chrome を操作するのですがこのツールは Microsoft 製です。 世の中変わったよなぁ・・・と思います。 ブラウザの操作と状態取得は全てPromiseベースで、なおかつ画面を開いているブラウザに外から JavaScript を挿入し実行させて何かするという事も比較的簡単に出来ます。 ChromeDevToolにアクセスすることも出来ます。 スクリーンショット の取得もPlaywriteのコマンドで行います。 ページ全体に限らず、DOMの中の或るHtmlElementだけ指定して部分的に撮るという事も出来ます。 新しい機能の開発についてはStorybookを利用しているので、そちらの方で差分を確認する方法もあります。 ですが諸般の事情にて現時点では自前で書いている関係で、この部分的に スクリーンショット を撮れる機能、なかなか便利です。 大筋として管理側と利用者側、独立した2つのコンテキストでそれぞれページを開いて管理側での操作が利用者画面にどのように影響するかという観点でもテストを行います。 シナリオのプログラミング環境としてはTypeScript, JavaScript , Python , .NET , Java が利用可能で、今回はTypeScriptで書くことにしました。 (ここも様々な手段があるようですがフロントエンド担当ですし、細かな操作があることもあってこうしています。) スクリーンショット 比較 今回は img-diff-js を利用します。 シンプルな API で高速な動作が身の上のようです。 www.npmjs.com この他、レポート保存には API 経由でGoogleSpreadSheetに保存しようかと。 ここはまだ後回しです。 取りたいのはエラーの有無、画像のリスト (w/サイズとHTTPレスポンス ステータスコード )、リンクのリスト(w/有効or無効)、 スタイルシート のリストと カバレッジ 等です。 結構データ量があるので後処理も 楽しそう 大変そうなので スプレッドシート が向いているかと。 実はまだ開発中で実証コードが動いた段階なのですが、「なんとなく」書いた処理シーケンス貼っておきます。 処理シーケンスドラフト 何だよこれ、と思った方は是非お話聞かせて下さい! カジュアル面談でお待ちしております! Playwriteの操作 Playwriteにおけるブラウザの API は大きく分けて以下の3つとなります。 Browser 文字とおりブラウザ。 Chromium 、 WebKit とか Firefox とか。 BrowserContext ここちょっと解りづらいかも知れませんが、Contextを別ける事で複数のセッション(ログインセッションとか)を同時に扱うことが出来るようになります。 BrowserContexts provide a way to operate multiple independent browser sessions. BrowserContext | Playwright Page これがブラウザの一つのウインドウです。複数ページを同時に開く場合BrowserContextからPageの インスタンス を複数生成する形となります。 ちょっとコードのサンプル Browser生成 import { Browser , BrowserContext , Page , chromium } from 'playwright' ; const browser = await chromium.launch ( { channel: 'chrome' , headless: false , // ここをfalseにすることで実行中ブラウザ画面が表示されます。デバッグ用途 args: [ `--window-position= ${ windowPositionX.toString() } , ${ windowPositionY.toString() } ` , ] , //PC画面上でブラウザが開く場所を指定できます。複数開いてデバッグするのに便利です。 } ); BrowserContext生成 const browserContext = await browser.newContext ( { // ここで指定しておくことで、後のページ遷移はpathで指定できるようになります baseURL: 'http://localhost:8080' //.ここでviewportのサイズも指定できます。 viewport: { width: 1280 , height: 800 , } , } ); Page生成 const page = await browserContext.newPage (); Pageでページをpath指定して開く await page. goto( path ); ページ内のあるテーブルの2列目のセルから文字列(foobar)を検索して何行目にあるかを返す const searchNeedle: SearchNeedle = { needleText: 'foobar' , } ; /** * スクリプトをPage内で評価、実行して結果を返します。 */ const rollIdx: number = await page.evaluate (( param: SearchNeedle ) => { // この中がブラウザ側で実行されます。 const { needleText } = param ; // 検索結果 let resultIdx = 0 ; Array . prototype .forEach.call ( document .querySelectorAll ( '#target-table tr' ), ( elm , idx ) => { const cellText = elm.querySelector ( 'td:nth-of-type(2)' ) .textContent if ( cellText === needleText ) { resultIdx = idx ; } } , ) // ここでブラウザ上での処理結果を返り値とするとPage.evaluate関数の返却値として取得できます。 return resultIdx ; } , searchNeedle ); ページ上のリンクをクリック、画面遷移を待つ await page.click ( 'a.target' ); // 複数hitした場合、最初の要素がclickされます await page.waitForLoadState ( 'load' ); // 次のページのloadイベントまで待ちます スクリーンショット を取る(全体) import path from 'node:path' ; await page.screenshot ( { path: path.join ( SCREENSHOTS_IMG_DIR , fileName ), fullPage: true , } ); スクリーンショット を取る(一部エレメント) const part = await page.locator ( 'div.target' ); if (await part.count ()) { await part.screenshot ( { path: path.join ( SCREENSHOTS_IMG_DIR , fileName ), } ); } ; 画像のdiffを取る import { imgDiff } from 'img-diff-js' ; const result = await imgDiff ( { actualFilename: sourceFilePath , expectedFilename: destFilePath ), diffFilename: diffFilePath , // <- このpathに差分を強調した画像ファイルが出力されます。 } ); 最後に 駆け足でしたが、いかがだったでしょうか。 今は一人で取り組んでいるのですが、一緒に考えて進める仲間を探しています。 詳しくはページヘッダの採用ページへのリンクから! この記事を書いた人 安積洋 MNTSQ( モンテスキュー )社のソフトウェアエンジニア。実はギタリスト。 LAMP エンジニアとしてバックエンド担当が長かったがその後 O2O アプリのフロントエンド、アプリPM、行動解析、と渡り歩いてMNTSQではフロントエンド担当。 入社エントリはこちら! note.com
アバター
はじめに 次の文章をまずは眺めてみてください。 “Chief Executive Tim Cook has jetted into China for talks with government officials as he seeks to clear up a pile of problems in the firm's biggest growth market. Cook is on his first trip to the country since taking over from late co- founder Steve Jobs " Apple CEO in China mission to clear up problems | ロイター 文中で、ティム・クックを表す表現にはどのようなものがあったでしょうか? 文中の別々の表現が同じ実体を指しており、それらの関係を洗い出す要素技術は共参照解析(coreference resolution)と呼ばれ、検索や固有表現抽出との関連で重要なものになっています。 上記の文の中でティム・クックについて言及(mention)した部分にハイライトをつけたい場合は、以下のようなデータ構造がとれると良さそうです。 {Chief Executive Tim Cook: [Chief Executive Tim Cook, he, Cook, his]} このようなニーズは人名・物体名の中でもさらに限られたカテゴリの実体をマーキングするという意味では固有表現抽出の意味を狭めた応用になっています。 本記事では共参照解析を行うためのライブラリとしてneuralcorefを触ってみて、その入出力の中身や意味について見ていきます。 neuralcorefについて neuralcoref は分散表現に基づく共参照解析ライブラリです。 *1 ライブラリの周囲には参照関係をビジュアライズするクライアント NeuralCoref-Viz もあり、このようなグラフを出力できます。 モデルは標準で英語のみの対応になっていますが、訓練することで他言語のモデルにも 展開可能 です インストール方法 neuralcorefは spaCy プラットフォーム上で動くライブラリになっており、動作にはneuralcorefの他にspaCyのインストールも必要です。 筆者の環境 ( python ==3.8.10) 上では以下のコマンドでインストールし、ライブラリの動作を確認しています。 pip install spacy==2.1.0 pip install neuralcoref==4.0 --no-binary neuralcoref python3 -m spacy download en 動作確認 まずspacyのパイプラインに共参照解析を行うneuralcorefの プラグイン を追加します。 In import neuralcoref import spacy nlp = spacy.load(‘en’) neuralcoref.add_to_pipe(nlp) nlp.pipeline Out [('tagger', <spacy.pipeline.pipes.Tagger at 0x7f315b0d5e20>), ('parser', <spacy.pipeline.pipes.DependencyParser at 0x7f315b0bffa0>), ('ner', <spacy.pipeline.pipes.EntityRecognizer at 0x7f315b0e3520>), ('neuralcoref', <neuralcoref.neuralcoref.NeuralCoref at 0x7f315aec8970>)] これだけで共参照解析を行う準備が整いました。 先程の文章をパイプラインに入力し、どのような出力を得るか確認してみます。 In doc = nlp( "Chief Executive Tim Cook has jetted into China for talks with government officials as he seeks to clear up a pile of problems in the firm's biggest growth market. Cook is on his first trip to the country since taking over from late co-founder Steve Jobs." ) doc._.coref_clusters 冒頭に紹介したような出力を得ます。 Out [Chief Executive Tim Cook: [Chief Executive Tim Cook, he, Cook, his], China: [China, the country]] この ._.coref_clusters でアクセスできるプロパティは共参照解析の結果を表したものです。 共参照解析では実体ごとに対応した文中の表現の関係を クラスタ (Cluster)と呼び、このプロパティでは共参照解析の結果としての クラスタ のリストにアクセスできます。 クラスタ 経由で以下のattributeにもアクセスすることが可能で、共参照の実体と 参照元 のリストが取得可能です。 main: 共参照の実体と思われるテキストのSpan mentions: 共参照の 参照元 となっているテキストのSpan In doc._.coref_clusters[ 0 ].main Out Chief Executive Tim Cook In doc._.coref_clusters[ 0 ].mentions Out [Chief Executive Tim Cook, he, Cook, his] なお、 ._. はspacyのパイプライン特有の インターフェイス であるExtension attributeを利用するときに用いる表現で、nuralcorefでは ._. を通じて各種結果にアクセスできます。 解析時のオプションについて neuralcoref.add_to_pipe(nlp, greedyness=0.75) のようにパラメータを指定することで クラスタ の作られやすさを調整したり、頻度が少ない単語への個別対応を行えます。以下主なパラメーターです。 greedyness: 0~1の値を指定して共参照の クラスタ の作られやすさを調整できます(デフォルト0.5) max_dist: 共参照を生成する時にどれくらい手前の先行詞まで考慮するかを調整できます(デフォルト50) max_dist_match: 先行する実体へのメンションが名詞等だった時に、max_distを超えてどのくらい手前までさかのぼって参照を生成するかを調整できます(デフォルト500) blacklist: 共参照解析をI,meなどの代名詞を含めて行うかどうかを決められます(デフォルトTrue) conv_dict: 人名などの頻度の少ない単語の分散表現を頻度の多い単語の分散表現に置き換えて共参照解析します 共参照解析と照応解析との関係 共参照以外にも文中の言語表現が別の表現を参照する事象として「照応」という概念がありますが、これらは微妙に異なる概念です。共参照はテキスト中の表現から実体への参照を表すのに対し、照応は同格、代名詞などで表される照応詞が文中の表現を参照する方向にフォーカスします。この2つの概念の整理については コロナ社 の照応解析について書かれた書籍が詳しいです。 文脈解析- 述語項構造・照応・談話構造の解析 - (自然言語処理シリーズ) | 笹野 遼平, 飯田 龍, 奥村 学 |本 | 通販 | Amazon この記事を書いた人 yad ビリヤニ 食べたい *1 : Kevin Clark and Christopher D. Manning. 2016. Deep Reinforcement Learning for Mention-Ranking Coreference Models - ACL Anthology
アバター
認証認可とワンセットで語られることが多い印象だが、今回話すのは「認可(Authorization)」の話だ。「認証(Authentication)」の話は含まない。 (システムで言う)認可とは、大雑把に言うと「誰が」「何を」「どうすることが」「できる/できない」の要素に従って判定することだ。 どちらも略すと「Auth」になってしまってクラス名が衝突したりするので困ることがある。区別するために認証はAuthN、認可はAuthZと略されることがある。「WebAuthn」などは一例と言えるだろう。 弊社内ではまず話題になってこなかったため、実装の話が流れたとき、非エンジニアからは「認可?権限と何が違うの?おいしいの?」といった声が聞かれたり聞かれなかったりした。 認可制御の種類 MNTSQで採用した認可制御 認可のrailsのgemの紹介 pundit cancancan MNTSQの認可制御の実装 RBACは抽象的な仕様 RBACをどのように実装したか 実装してみてどうだったか 今後起こり得る事象 参考文献一覧 We are hiring! 認可制御の種類 認可制御にはいくつか種類があるので大雑把に紹介してみよう。 ACL 単純な「誰が」「何に」「どの操作を」することを許可するかのリスト DAC よくあるOwner、Group、Everyoneに対してRead、Write、eXecuteを設定する形式 所有者が権限を決定する MAC DAC の所有者ではなく管理者が権限を決定するバージョン RBAC 特定の「ロール(役割)」を想定したアクセス制御であり、アクセス権の束を「ロール」として設定し、そこにユーザーを割り当てる ABAC 属性ベースのアクセス制御。例えば「30代」「男性」「独身」などの属性でアクセス制御をする IBAC 認証後の認可は ACL その他、調べれば「XXAC」系の認可制御はたくさん出てくる。 MNTSQで採用した認可制御 ではMNTSQ( モンテスキュー )で採用した認可処理は何かというと、「RBAC」である。弊社のクライアントは主に大企業であり、基本的に部署や階級があるため、それに基づいたロールが設定する可能性が高かったことが選定理由となっている。おまけの話だが、SlackもRBACを参考に実装しているようだ。 slack.engineering ただ、RBACをベースにどのように実装するのか、そもそもRBACとは何であるか、どのような制御をするものかなどの資料が圧倒的に乏しいのが現状である。実際に書いてみて分かることだが、機密性の高い仕組みを公開することになってしまうので、公開されづらい傾向にあると思われる。 幸いなことに、RBACについては ANSIのPDF が公開されており、また 日本語の論文 も存在している。これらを読むことでRBACの仕様について理解を進めることが可能だ。 認可の rails のgemの紹介 認可について、主に2つのgemが有名である。いずれも簡易な ACL と言えそうだ。 pundit github.com モデル側から認可制御をする方式をとっているgemである。 対象のモデルがPostであればPostPolicyという名前のクラスを作成し、そこでどのような条件のユーザーにreadやwriteを許可するかを指定することが可能となっている。つまり、テーブルの数に比例して同名のPolicyクラスが増えていくことになる。 実装は以下のようにし、 class PostPolicy attr_reader :user , :post def initialize (user, post) @user = user @post = post end def update? user.admin? || !post.published? end end 実際の認可処理では以下のように「何を」「どうするか」を指定して実行する。 def publish @post = Post .find(params[ :id ]) authorize @post , :update? @post .publish! redirect_to @post end cancancan github.com 一見ふざけた名前だが、認可のgemとしては有名どころである。こちらは「誰が」側から認可を実装するかたちだ。 認可用のクラスを作成し、ユーザーをラップするかたちでインスタンタイズし、必要箇所で権限の有無の判定をすることができる。 実装は以下のようなかたちでクラスを実装し、 class Ability include CanCan :: Ability def initialize (user) can :read , Post , public : true return unless user.present? # additional permissions for logged in users (they can read their own posts) can :read , Post , user : user return unless user.admin? # additional permissions for administrators can :read , Post end end 以下のようにController、Viewなど各所で認可処理をすることができる。punditと同じく「何を」「どうするか」を指定する。 <% if can? :read, @post %> <%= link_to "View", @post %> <% end %> def show @post = Post .find(params[ :id ]) authorize! :read , @post end MNTSQの認可制御の実装 MNTSQでは上記のgemでは複雑性を回避しづらいため、これらのgemは利用していない。自前で実装をすることとなった。実際のコードについては公開を控えさせていただくので、その点ご了承いただきたい。 MNTSQで認可処理を実装する際のポイント MNTSQの認可制御の複雑さは、1つの書類に対して契約データベース、契約管理、案件管理という3つの方向から認可制御をする必要があることだ。 衝突が発生した場合には、それぞれのORをとることで解決することとした。なぜなら、ある書類を案件管理から閲覧可能であれば、契約データベースからでも閲覧可能であって良いという整理が可能だったからだ。逆に、例えば契約管理で閲覧不可と設定されていた場合、契約データベース、契約管理、案件管理どの角度から閲覧しようとしても不可にするという制御とした。 また、関連するテーブルの数が30以上に上り、個別に管理するとメンテナンス性が損なわれるため、それらを中央集権的に管理する必要性に迫られていた。 これらについては、テーブル名と閲覧権限のペアを定義し、それをベースにフィルタリングする仕組みを導入した。 RBACは抽象的な仕様 RBACを実際に適用するに際して苦労した点としては、仕様としてはどのようなシステムにも適用可能なように抽象度が高く書かれているため、RBACを実際のシステムに適用するための間を自前で埋める必要があることだった。 それらを AWS など他所のシステムの認可制御を参考にしながら、「MNTSQ用語」として落とし込んでいる。開発者や営業など非開発者の間での認識がずれにくいように、認可制御について記載したマークダウンを準備したり、認可の実装にあたって打ち合わせを何度か行い、認識合わせに注力した。ちなみに主に参考にしたのは、 AWS のIAMの仕組みだ。 RBACをどのように実装したか 弊社ではgrapeというgemを利用して API を実装しているが、これには Rails で言うApplicationController相当のroot.rbというファイルが存在しているので、そこで API 全体に対する認可制御を包括的に行うことにした。 github.com 認可制御についてはフロントエンドでも「書類の削除ができない場合は削除ボタンを出さない」などの認可制御が必要なため、対象のリソースに対する認可情報を問い合わせる API を実装した。 検索にはElasticsearchを利用しており、ここの認可処理については Rails の効力が及んでいないため、自前の認可制御の仕組みのデータを作り、独自の プラグイン を開発して認可を実装している。 構造上、速度に大きく影響する可能性が高かったため、キャッシュをする仕組みを導入することとした。キャッシュの設計をどうするかなどは当然RBACの仕様には書かれていないため、「誰」「何」「どのアクション」「ロール」のどのレベルでキャッシュを効かせるのが良いのかも悩みポイントとなった。ここはシステムによって最適なポイントが変わってくると推測されるが、MNTSQではユーザー側(「誰」)に寄ったキャッシュの持ち方をすることとした。 実装してみてどうだったか 弊社では rspec でテストを書いているため、一定のバグを防ぐところには至っているが、いかんせん仕組みが複雑なため、カバーし切れていない部分の修正を続けているところだ。 また、システムに合わない部分について、ドラスティックに仕組みの変更を進めている途上である。 今後起こり得る事象 今後に起こり得る事象について、まだ運用が始まったばかりであり、残念ながら申し述べることができない段階にあるのだが、面白い資料をご紹介しよう。 エンタープライズ ロール管理解説書 (第3版) この資料では企業において人事異動、組織改変、プロジェクト型や組織型など想定される ロールモデル 、今後どのような問題が起こり得るか、そのために事前にどのようなことを決めておくべきかといったことが書かれた資料となっている。ぜひとも参考にしていただきたい。 参考文献一覧 Access Control Acronyms: ACL, RBAC, ABAC, PBAC, RAdAC, and a Dash of CBAC - DZone Security Role Management at Slack American National Standard エンタープライズ ロール管理解説書 (第3版) アクセス制御の種類---DAC,MAC,RBAC | 日経クロステック(xTECH) アクセス制御 - Wikipedia What is Identity-Based IBAC Access Control? – Digital Masta Authorization in Rails controllers: Pundit versus CanCan We are hiring! 上記の通り、MNTSQはまだまだ進化の途上である。複雑な課題にチャレンジしてみたい人も、そうでない人もぜひ右上の採用ページから応募してみて欲しい。
アバター