詳細設計書を「効果的に」活用するためのパターン

発端はこのツイート

スナップショットはこちら(ロードするたびに割合がかわってしまうので)

前提条件

いろいろ議論が発散してしまうので、前提条件を確認しておきます。

「詳細設計」というのは、PMBOKの「Detail Design」にあたる、とします。

・要件定義
・概要設計、外部設計、基本設計、画面設計
・詳細設計、内部設計
・実装
・各種のテスト

PMBOKで云うと概要設計と基本設計が同じになります。最近は「基本設計」の方が言われることが多かった(多分、新聞などで使われることになったからだと思う)のですが、いわゆる顧客からの要件定義を受けて、システムを構築するための設計のことです。

実は、大手の会社でもここの分類が分かれるところで「自社だと違う」というパターンが多いでしょう。実際に画面設計(ユーザーインターフェース)やネットワーク設計などの仕様書が必要になることが多く、PMBOKが規定する分類にあてはまるとは限りません。

この分類は、漠然としたものではなく「誰が誰のために作るのか?」という視点で分けるとわかりやすいです。

「要件定義」は、顧客の要望を文書化したものです。顧客自身が書いてもいいし、請け負った会社が書いても構いません。

概要設計(基本設計や画面設計やネットワーク設計など)は、要望をどのように実現するのか?を記述していきます。これは、要件定義と対になるもので、いわゆるプロジェクト予算を作るときに利用します。ただし、実際のITプロジェクトの契約では、要件定義段階で契約することが多く、概要設計を作らずに(あるいは概要の概要みたいなものは作る)契約してしまうことも多いのですが、これは別の問題なのでここでは議論しません。

詳細設計は、概要に従ったものを具体的にクラスやフレームワーク、関数や、シーケンスなどを考慮して記述していきます。近年「詳細設計」を飛ばしてしまうことが多いのは、概要設計から実装(WEBの各種フレームワークやプロトタイプ、ノンコーディングできる実装環境など)が繋ぎやすいところにあります。いわゆる、スクラッチビルドが試せる環境が整っていたことがあります。逆に言えば、プロトタイプビルディングができない環境である場合は、詳細設計が必須です。

実装は、実際にコーディングをして動かす部分です。これに対しても以前はモジュール設計、クラス設計などを記述して分離させていた時期もあるのですが、最近では詳細設計に入れてしまう場合が多いです。これは会社のプロジェクトによって異なるところです。

UMLは、かつては概要設計と詳細設計の両方で使われる可能性があったのですが、現在はあまりUMLで書いてある仕様書を見かけません。これは有償のケースツールの衰退もあるのと、UML自体が細分化されてしまって、忌諱されてしまったためでしょう。

UMLを記述する場合は、最低限

・クラス図
・シーケンス図
・オブジェクト図(これはなくてもよい)
・状態遷移図
・ユースケース記述

だけ覚えればOKでしょう。これは時間と具象/抽象の4象限でわけたものです。

ただし、これ自体はここでは議論しません。

詳細設計は誰のためにつくるのか?

さて、詳細設計は誰のために作るのか?をパターン分けしておきましょう。先のアンケートを分類すると、

  1. 詳細設計書と実装が別の人
  2. 詳細設計書と実装が同じ人
  3. 詳細設計を書かず、実装のみ

のパターンに分けられています。それぞれ、意味があるので何がよいという訳ではありません。ITプロジェクトの状態に沿った形で、詳細設計を書くかあるいは省略するかすればよいのです。

詳細設計と実装が別の会社の場合

契約上、詳細設計と実装が異なる会社の場合があります。かつての中国オフショアなどがそれで、日本で詳細設計書を書いて中国のプログラマに発注していた時期があります。オフショアに限らず、子会社は外注に出すパターンはこの組み合わせが多いです。ただし、WEBサイト開発のような場合は、概要設計や画面設計から実装へ進む場合も多いので、これは情報システムや銀行系の場合に多いですね。

それぞれの会社が別なので「瑕疵」という契約が発生します。どのような場合に「瑕疵」となるのか、不具合をどのように直すのか?が問われるところなので、契約のために「詳細設計書」が必要になります。

別の会社であっても、派遣社員や準派遣の場合は、発注会社が責任を持つことが多いので、詳細設計書を省略しても契約上問題ありません。

詳細設計と実装が同じ会社(人)の場合

同じ会社であり同じ人であるならば詳細設計を作らずに、画面設計からいきなり実装に移ることも多いでしょう。保守運用的に詳細設計を残す或いは残さないの判断もあるのですが、実装する人の経験が浅かったり、ロジックが複雑怪奇であったりする場合には、詳細設計書を書くのがベターです。

詳細設計といっても、コードと逐一同じものを書くのではなく、UMLなどを使いながら簡略化していきます。

私の新人教育では、

・箇条書きで詳細設計を行う
・シーケンス図を記述する

ことにしています。シーケンス図はプログラムコードが入り組んでいた時、タイミングが微妙なときを考察するのに便利です。基本的に詳細設計は使い捨てです。そのままコードのドキュメントとして残すか(文芸的プログラミングのように)、単なるメモ書きとして記録しておくだけでよいでしょう。

コードレベルの詳細設計は書かない

先のアンケートの結果を見ればわかる通り、私は「コードレベルの詳細設計」は書きません。しかし、場合によってはシーケンス図のような詳細設計を書きます。

このアンケートで答えた人がどの位の「詳細設計書」を考えているかわかりませんが、分布としては形式的な詳細設計書はなくなっていく傾向にあります。

ここで注意しておきたいのは、

・アジャイル開発だからといって、詳細設計が存在しない訳ではない
・ウォーターフォール開発だからといって、詳細設計が必須な理由はない

ということです。ITプロジェクトのスタイル(アジャイル開発、計画駆動、イテレーション開発などなど)に問われるものではありません。アジャイル開発においても「設計書」は情報共有のために必要となるので、そこに囚われる必要はないでしょう。

設計書からコードを出力するツールはあるのか?

あると言えばあるんですよ。

記事を見ると解る通り、みずほ銀行の件は失敗しています。失敗していますが、詳細設計書からコードを出力するツールはあるんです。あるんですが、失敗しています、ということを覚えておくことが重要です。

個人的には MDA の復活になると思うのですが、それがいつ頃になるかわかりません。Scratch とか Node-RED のツールとかがそれに近いです。各種のノンコードツールもそれに近いものがあります。ノンコードツールの延長線上にあるとは思えないのですが、一種ではあると思われます。

参考先

図解即戦力 PMBOK第6版の知識と手法がこれ1冊でしっかりわかる教科書

図解即戦力 アジャイル開発の基礎知識と導入方法がこれ1冊でしっかりわかる教科書 Kindle版

図解入門 よくわかる最新 システム開発者のための仕様書の基本と仕組み

カテゴリー: 開発 | 詳細設計書を「効果的に」活用するためのパターン はコメントを受け付けていません

MS-MVP の再受賞(14回目)と学習効果の話

今年も無事、Microsoft MVP を受賞することができました。ぱちぱちぱち。

ひとまず、Microsoft系の書籍を書いている限りは、申請を出すつもりなので、引き続きよろしくお願い致します。

「Azure OpenAI Service 入門」のほうも合わせて。

さて学習効果の話

ツイートを失念してしまったのですが、学習効果が複利的に逓増するか?というのが再び流行りました。もともとの図自体はかなり前に流行ったものなので、目新しいことではないのですが、賛同だけではなく、疑問符な方たちが結構いたようなので、学習効果についてちょっと記述しておきます。

誰が云ったか忘れてしまったのですが、「1.01の法則と0.99の法則」というものです。

1.01を365日続けると37倍になり、0.99で努力が減ると0.03になるという話で、最初でてきた当時も「1年間続けたとしても、さすがに37倍にはならんやろ」という結論がでています。まあ、1.01の努力ってのもたとえ話なので、実際に37倍になるかどうかは不明です。

さて、これを数式で表すと、次のように複利的にΠで計算するか、加算ということでΣで表すかという違いになります。

果たして、微々たる努力(1.00のところを1%だけ上げるので、勤務時間8時間であれば、8x60x0.01 = 4.8分/日ということになる)で、じゃあ、1年後に37倍になるか?という話になり、まあそうなんらんやろという結論で、Bの加算程度じゃないだろうか?というツイートが今回散見しました。努力が加算されるという意味ですね。

なにかの作業的なものであれば、たかだか1日のうち5分間というのは大した効果ではありません。単純に自給換算にで言えば、8時間勤務が8時間5分勤務になったというだけなのです。

が、Aのような複利効果を考えるとき、実はベースとなる実力そのものが増大するため、その効果はその時々において確かに複利的になる(常に元金が増えるような状態なので)ものです。たとえ話のように、356日連続で向上するとなるとなにやらうさん臭くなってしまいますが、これが「1か月単位で、ある程度実力が向上する」となると少し話が違ってきます。

たとえば、プログラムのコードを書く作業を生産性とみなし(詳細設計や単体試験まどを含めた、バグのないコードをどのくらいのペースで書けるか?ということを考えてみましょう)たときに、1か月のうちで、1.00の時間ところを0.99の時間で書けるようになったとしましょう。たかだか1か月のうちで1%しかスピードアップはしませんが、これが12か月になると、0.99^12 = 0.886 となり、おおよそ 90%程度になります。つまり、1割位の短縮ができるわけです。

プログラムを書くスピードはそのプロログラム言語の習得率や設計のうまさ、テスト環境の整え方など各人の経験で向上できるところの多い分野です。この「経験」は、Σのように加算ではなく、Πのように累積されることは、いわゆるプログラマ力の個人差が大きいところを見ると明らかでしょう。スタート時点での経験の差(あるいは習得率の差)は、その差のまま続くのではなく、1年間のうちに「経験」として向上するものです。さらに、1か月ごとの累積で考えるならば、1か月単位のスタートで「経験」に対して累積していくと言えるでしょう。スタートとなる土台は、次のスタートの時には土台が上がっていると考えられます。

このあたりは町工場などの職人も同じですね。学習前の土台を常々上げることによって、次のステップが楽になります(段差が小さくなる状態にしておきます)。

ここでは仮に1か月につき1%の向上にしましたが、5%と仮定したときは、当初1.0だった見積もりが0.54程度に住むことになります(0.95^12 = 0.54)。つまり1年たてば、最初の見積もり期間よりも半分の工数でコードができあがる、という予定になります。

実際、プログラミング言語の習得だけではこの向上は見込めませんが、

  • フレームワークの使い方の習得
  • サンプルコードやテストコードを作ることで、落とし穴を回避する
  • あるいは、あらかじめ落とし穴に落ちておく(踏み抜いておく)
  • テストコードや不具合対処の勘がさえてくる(経験上)
  • プログラミング言語自体の習熟度が上がっている

などの要因もあり、単純な加算ではなく複利的に実力が向上することが見込まれます。

実は、似たところはプロジェクトのスケジューリングにも言えるのです。プロジェクトの当初では未知の部分も多く、プロジェクトで利用するライブラリや環境にも不慣れな状態からスタートします。しかし、プロジェクトが終盤になれば、プロジェクトのメンバーはライブラリや環境にも習熟して、数々の不具合を素早く対処できるようになってきています。

終盤で下手に炎上しているプロジェクトではなければ(実は炎上しているプロジェクトであったとしても、プロジェクトメンバー自身は成長しているのですが)、プロジェクトの最初の力量よりもプロジェクト終了時の力量のほうが上であると考えられます。

つまり、この習熟度の高い状態を疑似的に作り出せば、プロジェクトはより安全に安定期(予測可能という意味で)に入ることができます。

  • プロジェクトで使うフレームワークを事前にサンプル等を作って試しておく
  • 運用直前でトラブルになりそうなリスク要件をあらかじめ確認しておく
  • もともと、習熟度の高いメンバーを入れる(可能であれば)
  • プロジェクト途中で「学習」を重視する

という様々な方法が考えられます。

そんな訳で、私的には学習による複利的効果(基礎力をアップするという意味で)を期待して、基礎力のアップに努めたほうがプロジェクトも安定しやすいだろう、ということです。

加筆

これは正しいwww

カテゴリー: 開発 | MS-MVP の再受賞(14回目)と学習効果の話 はコメントを受け付けていません

2024年 都知事選を予測&検証する

都知事選は結果を見る通り、小池氏が全得票の半数弱を取る結果となった。

いろいろな後付けの感想がツイッター(X)に流れている訳だが、実はひとつの実験として、行動経済学の視点から実地の数値から都知事選を予測することをやっていた。

結果の資料

東京都知事選挙2024 立候補者紹介・選挙速報(7月7日投票) https://www3.nhk.or.jp/senkyo2/shutoken/20336/skh54664.html

あわせて開票所別も

東京都議会議員補欠選挙2024 立候補者紹介・選挙速報(7月7日投票) https://www3.nhk.or.jp/senkyo2/shutoken/20337/

東京都知事選挙 NHK出口調査結果|NHK 首都圏のニュース https://www3.nhk.or.jp/shutoken-news/20240707/1000106259.html

支持層割合、とくに無党派層の割合を調べる

都知事選 【結果】2024 現職の小池百合子氏が3回目の当選 石丸伸二氏 蓮舫氏らを抑える | NHK | 選挙 https://www3.nhk.or.jp/news/html/20240707/k10014502181000.html

年代別用

私の予想

投票日の1週間前位に投票数を予想している。

私としては、

  • 組織長(首長)の三選は禁止したい派なので、非小池知事を希望
  • 板橋区というのもあり地元当選の蓮舫氏を支持

ということで、蓮舫陣営寄りの観察になりがちなので、これを補正するためにきちんと事前のデータだけを利用することにした。感情的にはツイッター(X)での様相に左右されがちなのだけど、これは安野氏や暇空氏支持でも同じことだろう。どうしてもエコーチェンバーになりがちになってします。このために、行動経済学における「見たものからしかデータを得られない」というバイアスがかかってしまう。また、どうしても期待を込めてしまうために、ちょっとでも希望が持てそうな事実を重く取り上げてしまう。いわゆる、損失バイアスをかけた状態になってしまうのだ。

投票率は 55% 程度、全体で600万票と予想する。各党の支持率は、東京都の比例代表からとってきている(ここは、前回の出口調査から類するするべきであった。無支持層が少なすぎる)。

上段のものが、比例代表などから想定したもの。各党のまとめ方は、組織票(公明と共産)が集まりやすいものと、分散しやすい層(自民、立憲)とわかれている。実は、都内なので都民ファーストの率が高いのであるが、国政に都民ファーストが入っていないので票が読めなかった。また、2020年の選挙でのれいわの票の行先が不明であった。

得票予想としてはとしては、

  • 小池氏 250万票 41% 程度
  • 蓮舫氏 125万票 20%程度

となっている。

実際のところは、

  • 小池氏 290万票 43%程度
  • 蓮舫氏 128万票 19%程度

となるので、ほぼこれで合ってる。石丸氏が得票数を伸ばした(1/4の支持を得ている)ことは予想できなかったし、田母神氏がこれほど低い得票率であったこと、泡沫候補がトータルでも5%未満であることは予想し得なかったので、ちょっと偶然の具合も強いのだが、最初の予想はあっている。

予測時点で、蓮舫氏を当選させるシミュレーションとしては、

  • 自民票が、半分以下にとどまること
  • 立憲、共産が票を強くまとめること
  • 蓮舫氏が無党派層の割合をもう少しあげること

であったが、それでも160万票ぐらいにしか届かない。このためボーダーラインとしては、150万票から200万票ぐらいと考えていたわけだが、黄色のセルをどう工夫しても蓮舫氏の勝ち目は薄かったところである。

それでも期待バイアス(損失バイアスの反対)を掛けて、蓮舫氏の当選を工夫しようとするのだが…結果的には、当初の予測値に近い形になっている。

無慈悲な統計のほうがあたり率が高いというのは、やっぱり行動経済学ですね、という具合になってしまう。

支持政党別を補正して予測し直す

先の予測では、比例代表から支持政党の割合を決めたのだが、無支持層の割合が低すぎる(9.4%)になっている。今回の出口調査を見ると 48% である。

これを使って予測し直してみよう。

得票率は61%となるので、前回より得票率が高い。有効投票数は690万票と100万票ぐらい増えている。

これをベースにして計算し直すと、

  • 小池氏 290万票 42.1%
  • 蓮舫氏 106万票 15.4%

ということで、これは概ね合っている。維新、れいわは資料がないので按分し、都民ファーストはほとんどを小池氏にいれる計算になる。

実際の割合はこれになるが、統計上2桁位で計算すればよいので、1%,2%である、れいわは誤差として無視してよいだろう(前回の山本太郎候補の66万票 10% 程度、が、れいわ新選組以外からの票が多かったことを示している)。

行動経済学の損失/期待バイアスはシステム1を無視すれば、バイアスを避けられる

この実験は、予測値をきちんとした実測値(実測値自体の精度もあるのだが)を使えば、心理学的にシステム1(損失バイアス)を回避して、うまくシステム2の予測ができるだろうか?という実証実験である。

結論から言えば、実測値を忠実に扱えば、システム1に引っ張られることを避けることができる。さらに言えば、実測値をシステム2の思考で忠実に扱えば、かなり近い形で未来の予測が可能であろう、ということになる。まあ、N=1の実験結果でしかないので、都知事選については過去のデータに遡って予測を繰り返してみるとよいだろう。

応用としては、ITプロジェクトの進捗予測や不具合予測に利用可能である。

プロジェクトマネジメントにおける直感はシステム1に引っ張られる可能性もあるが、同時に専門職による経験と勘は内部的にはシステム2の思考を利用している可能性も高く重要である。傍目からみると(自分でさえ)その思考手順がシステム1なのかシステム2なのかを知ることはできない。このため、バイアスが存在するかどうかが判断し辛いのだが、直感と実測からの予測を見比べることで、ずれが生じていれば、それは「システム1に引っ張られているかもしれない」という懸念を持つことが重要だろう。こうすると、経験と勘を捨てずに、精度の高い予測を行うことが可能と考えられる。

ただし、この予測値においては石丸氏の170万票(24%)は予測できていない。無党派層の2割(日経新聞より)からだけでは、24%という高い得票率は想定し得ない。私の予想では、石丸氏、田母神氏がギリギリ供託金の60万票をまぬがれるかどうか、と思っていたのだが、実際のところでは田母神氏は保守票をまとめきれず 27万票しか得られていない。

個人的には、安野氏や暇空氏に注目するところ(賛同ではないけれど)ではあったのだが、2%前後という低い割合になったのは意外なところだった。2%というと、学校の学年で2,3人ぐらいの支持なわけで、クラスで徒党を組むことも難しい。その位のマイノリティということになる。マイノリティにはマイノリティによる戦い方があるので、ここではゲリラ戦ですよね :)

参考資料

都知事選2024予想(Excel)

https://1drv.ms/x/s!AmXmBbuizQkXg4Ag0jTmpCT4KbShow?e=4TavxZ

カテゴリー: 開発 | 2024年 都知事選を予測&検証する はコメントを受け付けていません

組み込みシステムとWEBアプリのグローバル変数の扱いについて

元ネタは、以下からなんですが、もともと新人が C++ で書いたプロトコルがばしばしグローバル変数を叩いていてなんともならん!が発端らしいので、ちょっと筋が違うかもしれないけど、ネタ的に興味深いところがあるので、ちょっと書き連ねておきます。

結論から先に言えば、組み込みシステムの割り込みで使うグローバル変数と、WEBアプリ(ブラウザで作る方)の割り込みはかなり違うので比較ができません。加えて、サーバーサイドのWEBアプリというかWEBシステムも「割り込み」や「非同期」の扱いが、組み込みの非同期とはかなり違います。

組み込みの非同期

先のツイートについては、組み込みの非同期については釈迦に説法のような気もするのですが、このブログの対象としてはあまり知らない分野だと思うので、いちおう。

組み込みにしてもOSあり/なしがあるわけですが、OSを使うにせよ使わないにせよ、「割り込み処理」(インターラプト)内の処理は気を付ける必要があります。いわゆる、ハードウェアの割り込み処理(I/Oポートからのインターラプト)とOSのシグナル、ソフトウェアからの割り込み処理に分けられるわけで、この割り込み処理の中では、

  • スタックが異なる
  • ランタイム(C言語ランタイムなど)の初期化有無がある
  • スレッド(メモリ空間)が異なる場合がある
  • CPUレベルでスイッチングしている

という面がある。

組み込みのC言語プログラミングの場合、C言語ランタイムが初期化されていない場合が多いので通常の関数(printfとかputsとか)が使えないときがある。スタックの切り替えやメモリ空間の切り替えが起こっている可能性もある(ハードウェアのインターラプトの場合はそう)のだけど、これは組み込み用のCPUの場合は、仮想メモリを使わない場合が多いので、プロセスごとに気を付ける必要があるという面もある。

そんなわけで、IntelのようにCPUレベルで仮想化されているわけではないので、プロセス(iTronプロセスみたいに)ごとに相手のプロセス空間を汚さないように注意する必要がある。と同時に、グローバル変数にデータを置くと、自動的に共有メモリとして扱えるので、インターラプト関係では結構便利なので、ついつい使ってしまう、という弱点がある。

先のツイートの元ネタのプロトコル通信でグローバル変数を使っていてあかん、というのはそれで、通信自体はハードウェア的に非同期で走っていてかつ、受信側も非同期で動いているとグローバル変数のメモリ空間は取り合いになってしまうという面がある。そのあたりは、iTron型のリアルタイムOSなのか、OSなしなのかが不明なのでなんとも言えないけど、

  • 全体のメモリが少ないので、プロセス間通信はグローバル変数で受け渡ししてしまうことが多い。
  • ただし、ハード割り込みの関係で、ポーリング形式になりがちな解析部分での非同期化が必要になり、単純な1個のグローバル変数では難しく、リングバッファにするかー、とか考えないといけない。場合によるが。

そんなわけで、組み込みプログラミングに置いて、グローバル変数と割り込みは絶妙な感じにしないといけない。あと、メモリ周りの制限がきついので C++ で大量に new/delete するのは困る。別途 allocater を作るわけだが…という話が待っている。

WEBアプリ(クライアントサイド)の非同期

React.js や Vue.js のように一見、async/await や Promise クラスを使って非同期処理をしているように見えるが、ベースは JavaScript なのでシングルスレッドで動いている。このあたりは見かけ上、非同期処理(特に GUI部分と)をしているように見える、あるいはプログラミングできるというだけになる。

特にシングルページアプリケーション(SPA)の場合は、非同期処理とはいえWeb APIを呼び出している間「待たない」という処理だけなので、組み込みプログラミングでいうところのハードウェア割り込みというものが存在しない。ボタン操作やキーボード操作などは、イベント処理として扱われているので、低レベルな話で言えば OS のポーリング処理に属している。

なので、非同期処理内(ラムダ式とか)であっても、他のクラスを使うこともできるし、グローバル変数(のようなもの)に対しての処理もプログラム言語での仕様でしかなく、ハードウェア上の制限ではない。

特にブラウザアプリケーションの場合は、グローバル変数というものは存在しない。コード内に var 変数として書くこともできるが、これはソースコードを跨げない。ちょうど、C言語のファイル内のスコープと同じになる。唯一、ブラウザ自身を示す window オブジェクトというのがあって、これを媒介してグローバル変数として扱うこともできる。乱暴だが window.a とすれば、a というグローバル変数が作れて別のソースコードから参照可能になる。

まあ、何が言いたいかというと、react.js や vue.js に flux, vuex というストアがあってですね。実質グローバル変数化されるわけですが、これ、ルールを決めて window.* にアクセスすればそれで充分では?と思うのです。実際 vuex が廃れてしまって mvvm パターンの pinia 標準化されつつあるわけで、「グローバル変数」を嫌うあまりに、プロセス/アプリケーションが共通でもつべきメモリ空間、の置き所に混乱が生じてしまっているような気がするのです。まあ、結局のところグローバル変数に落ち着きそうですが。

で、WEBアプリの場合は非同期でラムダ式を使っていたとしても、実質的にはシングルスレッドで動いているのでグローバル変数(windows.* へのアクセス)は競合しません。将来的にネイティブな typescript が wasm 上で実装さえたりすると競合するようになるかもしれませんが、いまのところ大丈夫です。

余談ですが、react, vue ともにビルドをするとひとつの javascript に圧縮されるので、中身で書いた var 変数はグローバル変数化します。ですが、ビルドする前に別のソースコードの変数を参照する手段がない(export/importすれば別だけど)ので、実質グローバル変数がないのです。これは、そういう文法を作ればクリアできる問題かもしれません。

そんなわけで、組み込みプログラミングとWEBアプリでの「グローバル変数」の立ち位置が異なるのと、それにかかわる「非同期処理」のレベルが異なるので、一概にどっちがどうという訳ではありませんね。という話です。

カテゴリー: 開発 | 組み込みシステムとWEBアプリのグローバル変数の扱いについて はコメントを受け付けていません

Microsoft Store で配布されているアプリがネイティブアプリかストアアプリ(UWP)かを見分ける方法

ストア形式といえば、忘れ去れてている感がある Microsoft Store ですが、現状ではサン土木すで制限された UWP アプリの他にネイティブ Windows アプリを登録できます。

UWP アプリは、「UWP」は「ユニバーサル Windows プラットフォーム」の略で、iPhone の App Store や Android の Google Play で配布されているアプリのように、安全にダウンロード&インストールしたのち、安全にプログラムが実行される(ユーザーがアプリの権限を不許可にしたりして制限ができるなど)アプリのことです。Windows 8 からの Microsoft 肝いり企画だったわけですが、Mobile Windows の廃止により、その価値は激減しています。

実行ファイルを配布するときに、どれを選ぶのかがややこしいのですが、

  • ストアでUWPアプリ
    一番制限がきついパターンだが、一番安全に配布できる
  • ストアでWindows アプリ
    一応セキュリティ審査/署名があるが、実行はネイティブなので、UWP アプリより少し制限が緩い
  • インストーラーを作って配布
    従来の一般的な配布。ただし、ストアよりも目に惹かれないので、できればストア配布を推奨…とされる
  • EXEファイルを直接配布
    社内での配布の場合はこれで十分なことが多い。 

久しく制限のきつい開発環境≒大手IT会社のハコヅメ環境をやっていないので、今の実情はわからないのですが、社内でツールの制限をかけるときに「何処からダウンロードするのか?」「どのツールを使うのか?」が求めらえるところでしょう。

例えば Line Desktop は Windows アプリ

Microsoft Store で「Line」を検索すると「LINE Desktop」がでてきます。このアプリは従来ならば UWP アプリで作るところですが、このアプリはネイティブの Windows アプリです。

判別が難しいのが難点なのですが、追加情報のところの「このアプリでは次のことができます」の中で「すべてのシステムリソースを使用する」となっていると、ネイティブな Windows アプリです。

一見すると、このアプリが Windows の秘密機能をあれこれやってキーロガーとか個人情報とかを抜きとるように見えますが(実際、できるんだけど)、実はそうではありません。ネイティブの Windows アプリなので「特に制限されてない」だけです。制限はされていませんが、一般的な Windows アプリと同様に、Administor 権限とか System 権限が必要なものは、それが使えるユーザー(管理ユーザー)でしか動きません。

一方で電卓アプリは UWP アプリ

一方で電卓アプリは、UWP アプリとして登録されています。

同じように機能の制限を見ると、「インターネット接続にアクセスする」ことだけが許可されています。

この文言は非常に解り辛いのですが、

  • 「すべてのシステムリソースを使用する」の表示があれば、Windows アプリ
  • それ以外の制限が書いてあれば、UWP アプリ

という違いがあります。前述した通り、UWP アプリとして作られている電卓アプリのほうが安全です。

プロセスを比較する

タスクマネージャで両者のプロセスを見ていきましょう。

LINE Desktop の場合は、各種のプロエスが動いています。

プロセスのプロパティを見ると、「C:\Users\masuda\AppData\Local\LINE\bin\current」のような場所で動いています。

エクスプローラーでフォルダーを見ると、Qt フレームワークを使って書かれていることがわかります。他にも DLL があって、一般的な Windows アプリと同じです。

同じように「電卓」をみてみましょう。

プロパティを見てみると、「C:\Program Files\WindowsApps\Microsoft.WindowsCalculator_11.2405.2.0_x64__8wekyb3d8bbwe」のように、C:\Program Files\WindowsApps フォルダーの配下にアプリケーションが配置されています。

この WindowsApps フォルダーがサンドボックスの役割をしています。このフォルダーは閲覧が制限されていて Powershell などを管理者モードにしないと見ることができません。

ls コマンドなどで、インストールされているアプリの正式名を知ることができます。このそれぞれのフォルダーに UWP アプリの実体が含まれています。

ためしに Temas のフォルダーを見ていきましょう。

この中で使われている DLL や JS コードなどを見ることで、ああ JavaScript で作られているな、とか判断したりするわけです。

カテゴリー: 開発 | Microsoft Store で配布されているアプリがネイティブアプリかストアアプリ(UWP)かを見分ける方法 はコメントを受け付けていません

Semantic Kernel を Windows フォームアプリで使う

「Azure OpenAI Service 入門」のサンプルコードを Semantic Kernel に書き換えるシリーズの第3弾は、Windows フォームを使ってみる、の巻です。

こんな感じで、必要項目を埋めるといい感じのブログ記事を書いてくれるツールを作ります。いわゆるブログ記事の量産ツールではあるのですが、自社製品である架空の「製品ABC」の紹介をブログ記事の中に織り込みます。書籍では第5章にあります。

第8章で、いろいろなツール(スマホ、ブラウザ、JSなど)から使える解説をするのですが、最初に WinForms を持ってきたのは諸々の技術を使うよりも初心者には手っ取り早いからです。私的には WPF で書いたほうが手っ取り早いのですが、まあ、一定の読者層を見込んでというところです。

SemanticKernel パッケージを使う

	<ItemGroup>
		<PackageReference Include="Microsoft.SemanticKernel" Version="1.14.1" />
	</ItemGroup>

画面をデザインする

デザイナで作って、コントロールに適当に名前を付けておきます。

実装するのは「生成ボタン」だけです。

生成ボタンをクリックしたときの処理

/// <summary>
/// 生成ボタンをクリックしたときの処理
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void button1_Click(object sender, EventArgs e)
{

    string blogTheme = textBox1.Text;   // テーマ
    string blogTerms = textBox2.Text;   // 専門用語
    DateTime blogDate = dateTimePicker1.Value; // 作成日
    string authorName = textBox3.Text;  // 作成者
    string attention = textBox4.Text;   // 注意事項
    string additionalInfo = textBox5.Text; // 追加情報

    string prompt = $"""
        テーマ:{blogTheme}
        専門用語:{blogTerms}
        作成日:{blogDate.ToString("yyyy年MM月dd日")}
        作成者:{authorName}
        注意事項:{attention}

        このテーマと専門用語を用いて、詳細かつ情報豊富なブログ記事を作成してください。

        追加情報:
        #追加情報のはじまり
        {additionalInfo}
        #追加情報のおわり

        出力フォーマット:
        #出力フォーマットはじまり

        ■タイトル
        [タイトルをここに記入]

        ■ブログ記事
        [記事の詳細な内容をここに記入]

        作成日:[作成日をここに記入] 
        作成者:[作成者をここに記入]
        ※注意事項 
        [注意事項をここに記入]

        #出力フォーマットおわり

        ブログ記事:
        """;

    var builder = Kernel.CreateBuilder();
    builder.AddAzureOpenAIChatCompletion("test-x", endpoint, apiKey);
    var kernel = builder.Build();

    var result = await kernel.InvokePromptAsync(prompt);
    string generatedText = result.GetValue<string>() ?? "";
    string blogPost = generatedText.Trim().Replace("\n","\r\n");
    textBox6.Text = blogPost;
}

それぞれのテキストボックスから値を取ってきて、プロンプトを作成します。semantic kernel にはプロンプトテンプレートという機能があるのですが、C#の場合はそのまま変数の埋め込みができるので、先にプロンプト自体を作ってしまったほうがわかりやすいでしょう。

プロンプト自体は、半年前に少し工夫したものですが、ポイントとしては

  • 箇条書きにする
  • 見出しに「■」や「#」のマークを付ける
  • AI に入れて欲しいときは「[作成日をここに記入]」のように括弧で括ると適用しやすい

ことです。

書籍にも書きましたが、下手にプロンプトエンジニアリングの本を読むよりも、最近では「○○を出力するためのプロンプトを作ってください」と AI 自身に頼んだほうが、AI が解釈しやすいプロンプトを吐き出してくれます。

区切りとなるコロン「:」も、全角と半角が混在しても大丈夫です。何度か試してみて、補助的な指示を工夫してみてください。

このプログラムを実行すると、次のようなブログ記事が得られます。

カテゴリー: 開発 | Semantic Kernel を Windows フォームアプリで使う はコメントを受け付けていません

カドカワシステムへのサーバー攻撃について(想像を含む)

参考先

ニコニコインフォ https://blog.nicovideo.jp/niconews/225099.html

当社サービスへのサイバー攻撃に関するFAQ | 株式会社ドワンゴ https://dwango.co.jp/news/5088891233107968/

現在のニコニコ動画には課金もしていないし、あまり見ることもなくなって久しいのですが、ニコニコ動画が始まった当初(ハルヒや、文字ぴったんや、初音ミクや、アイマスの「とかちつくちて」のあたりとか)の頃は結構見ていたので、それのお礼も兼ねて考察しておきます。

というか、半田病院のランサムウェアの件とかIT屋としてはなんとか対処したい案件ではあります。遭遇したくはないが、遭遇してしまったときに被害を最小におさえときたい。

現在、ツイッター(新X)で憶測が走り回っていて、いろなものがリツーイト(新ポスト)されてくるのですが、合っているような合ってないような、合ってないようなものが多いので、ちょっとイライラします。イライラを個人にぶつけるのはアレなので(新型コロナ以来、この手の代理戦争はしない、と決めています)、掃き出し口として自分のブログに残しておきます。この手の方法は、心理学的にも行動経済学的にも「システム1を垂れ流しにしない」こととして有効なので、何か別のものに書きつけておくのは良い方法です。意外と、それだけで気持ちがおさまるので。

犯人像を考える

犯人像を考えるのは捜査のプロファイル手法としての常套手段です。というのが「羊たちの沈黙」という小説にあってですね、当時は流行ったのです。実際にFBIで取り入られている手法なのですが(今はよくわかりません)、犯人像を決めてその動機や知識から実際の犯行の手段を想像し、現実の犯行とあわせてみるという考え方です。こうすると、犯人がかなり絞り込めます。

同じようにサーバー攻撃を考えるときに、犯人が何を求めているのか?を考えます。

一般的にサーバー攻撃は、愉快犯、敵対攻撃、ランサムウェアによる身代金、機密情報の奪取(顧客情報など)に分けられます。愉快犯や敵対攻撃の場合、サーバー攻撃は非常に簡単です。システムダウンが目的なので、DDoS攻撃のような単純なものが使われます。相手のホームページに犯行声明を上げたりするパターンもあります。当初、ニコニコ動画が動かなくなっていたときには、DDoS攻撃かと言われいたものですが、犯行声明などがないので「ランサムウェア」ではないか、と思っていました。

ところが、周知の通りランサムウェアの場合は、相手のファイルを暗号化させて身代金を取って復号化する、という手段が必要になります。目的は「身代金=ビットコインなど」なのですから、相手のシステムをダウンさせてはいけません。まして、今回のように復旧に至ってしまっては、サイバー攻撃をした意味がないのです。

角川社内の顧客情報やクレジットカードの情報を抜き出すということも考えられるのですが、その場合でもシステム全体をアウトにする必要はありません。むしろ損です。

社内の機密情報を抜き取りたい(顧客情報を売る、クレジットカード情報をとる、インサイダー取引情報を抜き出す)場合には、できるだけ角川のシステムを生かしておかないといけません。さらに、継続的に情報を抜き出すためには、もっと目につかない形でこっそりとやるのが常套手段です。

情報抜き取りのほうは、これからの調査で、実は5年前ぐらいからやられていた、というパターンも考えられます。で、だいたい抜き取ってしまったので、犯人が遊びで角川のシステムをダウンさせた、とも考えられます。これは、もう少し経ったら解る情報でしょう。

現在のところは、そういう情報がないので、「犯人は、ランサムウェアで身代金をとろうとした」と考えるのが妥当です。

そうなると、システム全体がアウトになった原因はどこにあるのか?ということになります。犯人としてはシステム全体をダウンさせることは望んでいません。むしろ、一部だけを暗号化したかったわけです。そうしないと身代金をとれないですからね。

となると、システム全体に被害が広まってしまったのではカドカワシステムの自責ではないか?と思われるのです。

ネットワーク図を考える

私はカドカワ相手に仕事をしたことがないので、このような想像でネットワーク図を書けるのですが、仕事で関わっている人は書けないですよね。なので、あくまで想像です。先の「ニコニコインフォ」から想像したものです。

「グループ企業が提供するデータセンター内サーバー間の~」の部分が気になりました。どうも提供している会社は一社(Kadokawa connected)で、その会社が一括してさまざまなカドカワのシステムを担っているような雰囲気です。これは、グループ会社全体での作業の効率化にはなるし、情報のやりとりが素早くできるという大きな利点があるのですが、今回のようにサーバーのひとつがやられると隣接するサーバーもやられてしまう、このとき隣接するサーバーが多いと被害も大きくなってしまいます。おそらく、全社のサーバーがひとつに集まっていた(ひとつのネットワークを共有していた)のが被害が大きくなった要因と考えられます。

侵入経路は内部犯行、外部からのアクセス、社員PCのアクセス等が考えられるのですが、戸田病院の例などを考えるとVPN経由で侵入されるパターンと思われます。実際にこの手のサーバー攻撃でランサムウェアが使われる場合はVPN経由が多いのが常です(どこかで円グラフがあるのですが、40%ぐらいはそれです)。

VPNは別の物理ネットワークを仮想的にひとつのネットワークとしてつなげられるという便利さはあるのですが、ある意味でいったんVPNの仮想ネットワークに侵入されてしまうと同じネットワーク内ということなってしまうので、社内ネットワークが乗っ取られやすい状態になってしまいます。

特に、今回の場合は社内の社員PCよりもサーバーにランサムウェアを仕込まれていることから、社外からアクセスする社員SE(あるいはPC)経由でVPNを奪取されたと考えられます。

あと、VPN経由などでサーバーを起動できるのは結構普通にできるので、別に不思議なものではありません。ポートを叩く方式とソフトウェアを使う方式があります。どちらかの犯人側がスクリプトで動かせばよい(社内の別のサーバーで定期的に)ので、その方式かなと思われます。

犯人側からは想定外がおきた

繰り返しになりますが、私見では「犯人側から想定外だった」と考えています。ある意味で、ニコニコ動画をダウンさせる(最初の6/8に発覚したのがニコニコ動画なので、ニコニコ動画のサーバー狙いじゃないかな)つもりだったのが、カドカワの社内業務システムに広がってしまったという現象でしょう。あるいは、その逆かもしれません。

ターゲットとしては、ニコニコ動画のような結構技術的に復旧可能そうな陣営に手を出すよりも、社内業務のサーバーを暗号化してしまって、ちょっとした身代金で済むと思わせたほうが犯人としては儲かりますからね。

被害拡大を防ぐには

カドカワ本体としてもN高などの緊急性の高いもの(一般層からのパッシングが高いもの)は早めに復旧したいところです。幸いにもN高のシステムは復旧が早く(バックアップあるいは再構築手順がうまくなされていた?)現状では支障がなさそうです。

ただし、できることならば、社内業務がやられてしまっても、N高やニコニコ動画には影響がないようにネットワークを組んでおくのがベストと思われます。

一般的には古い対策ではありますが、物理的にネットワークを分ける手段が考えれます。もともと、カドカワの各社グループは別のネットワークだったわけですから、相互をあまり使わない形でネットワークを組むことも考えられます。が、確かカドカワのシステムをAWSに乗せ換えるなどの処置が進めていたため( AWSコスト最適化ガイドブック https://www.hanmoto.com/bd/isbn/9784046053558)このあたりが、社内の仮想化の部分とAWSの仮想化の部分で密接になってしまったのかもしれません。社内の仮想システムの攻撃(おそらくランサムウェアによる社内仮想化ファイルの暗号化)は、犯人にとっては想定外と思われます。メンテナンスシステムの利便性を考えて、ネットワークを共通化する(あるいは管理者IDの共通化?)したことによる弊害ではないかと考えられます。

官庁絡みではありますが、この手のバックアップを守る手段としては、

  • 物理的にHDDを抜いてしまってバックアップを守る
  • 定期的に磁気テープに出力する
  • 相互に関係ない部署(特に財務系、社内業務、B2Cシステム)ではネットワークを切り離す

という手段が取られます。

SE(システムエンジニア)的にもプログラマ的にも今回のサイバー攻撃は、ちゃんと考察したおいてほうがいいかな、と思って記録に残しておきます。もちろん、ニコニコ動画の復旧を応援しながらでもあり、ここまでは私の想像でしかないのですが。

補足ですが、以下のようにプライベートクラウド(社内の仮想システム)の場合はアウトで、パブリッククラウド(おそらくAWSのこと)の運用には影響がなかったということなので、「AWSのほうが安全だった」という結果になっていますね。たまに、ツイッターで逆の意見が多い(プライベートクラウドのほうが安全)という話がでているので、注意してください。

追記 2024/06/23

NewsPicks のスクープが載った模様。ほぼ、これが正解でしょう。既に身代金を払っていて、更に追加を要求されて広がっている、という最悪な状態。これでは KADOKAWA 側を擁護できない…

追記 2024/07/04

追加で身代金を払ったのか定かではないが、流出サイトからは削除された模様。

このツイートの参考先も真偽は明らかではないが、記録のため。

カテゴリー: 開発 | カドカワシステムへのサーバー攻撃について(想像を含む) はコメントを受け付けていません

Semantic Kernel のチャットでスケジュール管理ができるツールを作る

「Azure OpenAI Service 入門」を Semantic Kernel で書き換えるシリーズの第4弾です。

書籍の第8章では、いろいろな実行環境で OpenAI を使ってみようということで、デスクトップやスマホアプリ、ブラウザアプリ、シングルページアプリケーションなど作成しています。色々コードが散ってしまうのが読者…というか筆者が大変だったので「スケジュール管理ツール」1本に絞っています。

一般的なスケジュール管理では、項目を追加・削除するボタンで操作をしますが、このスケジュール管理ツールでは自然言語を使って項目を操作します。「4/1 は休日にして」とか、「4/10 の項目を消して」という具合ですね。あらかじめ、スケジュールのデータを AI 側に保持させておいて、それに対して指示を出すので、正確に変更されるとは限らない!のが最大の欠点でありますが、まあ、チャットツールを作るときの良い練習にはなると思います。

書籍のほうでは、常に成功しているように見えますが、執筆時には成功するように指示文を作るところに苦労しています。

画面はシンプル

画面はシンプルで「送信」と「保存」ボタンしかありません。指示をテキストボックスにいれて「送信」するだけです。

MVVMパターンを使う

MVVMパターンのために CommunityToolkit.Mvvm パッケージをいれておきます。

  <ItemGroup>
	  <PackageReference Include="Microsoft.SemanticKernel" Version="1.14.1" />
	  <PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" />
  </ItemGroup>

ViewModel クラス

コンストラクタはこんな感じ。

    public class PromptViewModel : ObservableObject
    {
        private const string _model = "test-x";
        private string _apikey = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY") ?? "";
        private const string _url = "https://sample-moonmile-openai.openai.azure.com/";

        private Kernel _kernel;
        private IChatCompletionService _service;

        /// <summary>
        /// コンストラクタ
        /// </summary>
        public PromptViewModel()
        {
            var builder = Kernel.CreateBuilder();
            builder.AddAzureOpenAIChatCompletion(
                _model,
                _url,
                _apikey);
            _kernel = builder.Build();
            _service = _kernel.GetRequiredService<IChatCompletionService>();

            this.SendCommand = new RelayCommand(this.Send);
            this.SaveCommand = new RelayCommand(this.Save);
        }

最初にあらかじめスケジュールを送信しておくのがミソです。本来ならば、ここの予定表はファイルから読み込めばいいのですが、端折っています。

        /// <summary>
        /// 最初のプロンプトを送信する
        /// </summary>
        public async void SendInit()
        {
            _history.AddSystemMessage(
                """
                箇条書きで予定表を作ってください。

                予定表のフォーマット:
                - [日付] [内容]

                現在の予定は以下の通りです。
                [予定表はここから]
                - 4/1 入社式
                - 4/2 新人歓迎会
                - 4/3 プログラム研修1
                - 4/4 プログラム研修2
                [予定表はここまで]

                予定表を表示してください。

                """);

            var response = await _service.GetChatMessageContentAsync(
                                           _history,
                                           kernel: _kernel);
            // 応答を取得
            string combinedResponse = response.Items.OfType<TextContent>().FirstOrDefault()?.Text ?? "";
            this.Output = combinedResponse;
            // AIの応答を履歴に追加
            _history.AddAssistantMessage(combinedResponse);
        }

送信ボタンを押した時は、ヒストリも含めて送信します。

        /// <summary>
        /// プロンプトを送信する
        /// </summary>
        public async void Send()
        {
            _history.AddUserMessage(Input);
            var response = await _service.GetChatMessageContentAsync(
                                           _history,
                                           kernel: _kernel);
            // 応答を取得
            string combinedResponse = response.Items.OfType<TextContent>().FirstOrDefault()?.Text ?? "";
            this.Output = combinedResponse;
            // AIの応答を履歴に追加
            _history.AddAssistantMessage(this.Output);
        }

ファイルに保存されるのは、最後の応答だけで構いません。最後の応答に最新のスケジュールが含まれるからです。

        /// <summary>
        /// 最後の回答をストレージに保存
        /// </summary>
        public void Save()
        {
            // ここでは簡便のためメッセージとして表示させる
            var msg = _history.Last()?.Content;
            // 保存ダイアログを開く
            var dlg = new Microsoft.Win32.SaveFileDialog();
            dlg.FileName = "schedule"; 
            dlg.DefaultExt = ".txt"; 
            dlg.Filter = "Text documents (.txt)|*.txt"; 
            if ( dlg.ShowDialog() == true )
            {
                var filename = dlg.FileName;
                System.IO.File.WriteAllText(filename, msg);
                MessageBox.Show("保存しました。", "AIスケジューラー");
            }
        }

実行してみる

こんな感じに誕生日を追加することができます。執筆時は GPT-3.5で動きが悪かったのですが、GPT-4oに切り替えると結構スムースに指示が反映されます。

カテゴリー: 開発 | Semantic Kernel のチャットでスケジュール管理ができるツールを作る はコメントを受け付けていません

Azure OpenAI Service 本のサンプルを Semantic Kernel で書き直す(チャット編)

Completion(入力候補)の API を使って1回だけ質問する場合はAIの答えを表示するだけで終了するのですが、チャットのようにAIとの会話を進める場合には、チャットの内容を保存しなければなりません。何故、保存しないとダメか?ってのは書籍に書いたので、ここでは、ChatHistory を使えば、AIとの会話を保存できます、とだけ言っておきます。いや、やっていることは、List に追加しているだけなんですが。

Microsoft.SemanticKernel パッケージを追加する

  <ItemGroup>
    <PackageReference Include="Microsoft.SemanticKernel" Version="1.14.1" />
  </ItemGroup>

kernel を作る

using Azure.AI.OpenAI;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.OpenAI;

var builder = Kernel.CreateBuilder();
builder.AddAzureOpenAIChatCompletion(
    "test-x",
    "https://sample-moonmile-openai.openai.azure.com/",
    Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY") ?? "");
var kernel = builder.Build();

ChatHistory を作る

Console.WriteLine("チャットの例");
// チャットの履歴をためておく
var history = new ChatHistory();
var chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();

GetRequiredService メソッドを使って ChatCompletionService のインスタンスを持ってこないといけないのが微妙なところですが、まあこれで動きます。

You(自分)のメッセージ入力はコンソールで、GetChatMessageContentAsync を使い API を呼び出します。会話履歴は hisotry にため込んでいるので、これも一緒に渡します。

while ( true ) {
    Console.Write("You: ");
    var prompt = Console.In.ReadLine();
    if ( string.IsNullOrEmpty(prompt) )
    {
        break;
    }
    // ユーザーの入力を履歴に追加
    history.AddUserMessage(prompt);
    var response = await chatCompletionService.GetChatMessageContentAsync(
                                   history,
                                   kernel: kernel);
    // 応答を取得
    string combinedResponse = response.Items.OfType<TextContent>().FirstOrDefault()?.Text ?? "";
    Console.WriteLine("AI: " + combinedResponse);
    // AIの応答を履歴に追加
    history.AddAssistantMessage(combinedResponse);
}

AI の応答がちょっとややこしいのですが、レスポンスの先頭の要素だけ持ってきて(パラメータによって、複数の回答を得ることができるので)、これを画面に表示します。

AIの回答も履歴に追加して、次の入力を待ちます。

実行してみる

中央林間から東京駅までの道のりを尋ねたものです。

最初にシステムメッセージを履歴に加えておけば、口調や応答の長さなどの調節ができます。

AI の回答を見ながら、結局タクシーで行くという結論を出すわけですが、この試行錯誤は入力候補のような1回だけのプロンプトでは到達できません。このようにチャットを使い複数回 AI とやりとりすることが重要になります。

カテゴリー: 開発 | Azure OpenAI Service 本のサンプルを Semantic Kernel で書き直す(チャット編) はコメントを受け付けていません

Azure OpenAI Service 本のサンプルを Semantic Kernel で書き直す

発売時期的に Azure OpenAI Service の API を直接使っている「Azure OpenAI Service入門」ではありますが、同じことは Semantic Kernelを使って書き換えることが可能です。

もともと、Semantic Kernel は Python の LangChain の .NET版(主にC#ではありますが)なところがあって、生成AIのモデルを問わない形になっています。出版時には、OpenAI社の GPTシリーズが主流であったためのこれに揃えて Azure OpenAI Serviceを活用しているのですが、現在では Google の Gemini など他の生成AIのモデルもあり、同じプログラムでもモデルを切り替えられるのは便利…とは思うんですがどうなんでしょう?

ひとまず、APIとしてはどのAIモデルもAPIをJSON形式で呼び出すようになっているので、そのフォーマットを切り替えるだけで概ねいけそうです。違いとしては、Calling Functionのように他システムと組み合わせたいときのプレ処理・ポスト処理に係る部分でしょう。属にいうRAG(検索拡張生成)と呼ばれる部分です。「RAG」というとちょっと難しい感じもしますが、要するの画像処理のプレ処理やポスト処理にあたります。画像処理を行うときに、プレ処理として2値化やグレー処理、エンボス処理などを行いますが、それと似たような形でテキスト解釈をGPTのAPIに任せて関数をコールバックさせることができます。私の場合、GPTシリーズでしかやったことがないのですが、Gemini などの他の生成AIでも同じことができるでしょう(未確認)。

まずは簡単なところから

簡単なところで、第7章のコードを書き直してみましょう。

Microsoft.SemanticKernel パッケージを追加する

Semantic Kernel はパッケージ「Microsoft.SemanticKernel」を使うので、*.csproj を以下に書き換えます。

  <ItemGroup>
    <PackageReference Include="Microsoft.SemanticKernel" Version="1.14.1" />
  </ItemGroup>

現在のところ最新版は ver.1.14.1 になります。

実は、Microsoft.SemanticKernelパッケージは、Azure.AI.OpenAI パッケージに依存しています。なので書籍で書いたAzure.AI.OpenAIパッケージのもそのままで動くし、Semantic Kernel で書き直したものも動きます。ちなみに、Azure.AI.OpenAI パッケージは beta 版のまま ver.2 に移行しているので、ver.1 はリリース版はない…と見るんでしょうね。やっぱり(苦笑)

従来の Completion(入力候補)を呼び出す

おそらく従来型のCompletion(プロンプトから1回だけ呼び出すもの)の方法を先に示します。

using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Connectors.OpenAI;

var builder = Kernel.CreateBuilder();
builder.AddAzureOpenAIChatCompletion(
    "test-x",
    "https://sample-moonmile-openai.openai.azure.com/",
    Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY") ?? "");
var kernel = builder.Build();

最初の Kernel 部分(従来の OpenAIClientに当たるところ)を Builder パターンで作成します。

  1. CreateBuilder でビルダーを作成
  2. AddAzureOpenAIChatCompletion でチャット/入力候補用の設定を追加
  3. Build メソッドで Kernel を作成

という手順です。ここでの「test-x」は、Azure OpenAI Studio で作成したデプロイ名です。

var kernel_args = new KernelArguments(
    new OpenAIPromptExecutionSettings()
    {
        Temperature = (float)0.5,
        MaxTokens = 800,
        TopP = (float)0.95,
        FrequencyPenalty = 0,
        PresencePenalty = 0,
    });

温度や最大トークンなどは、OpenAIPromptExecutionSettings で作成します。先に作成した Kernel に渡すために、KernelArguments でくるんでおきます。このあたり、KernelArguments 内で色々と切り替える(プロンプトテンプレートとか)のですが、ひとまず OpenAI のチャットか入力候補を試したいときはこれで十分です。

Console.WriteLine("通常のプロンプト") ;
var result = await kernel.InvokePromptAsync(
    """
    Azure OpenAIについて詳しく説明してください。
    """,
    kernel_args
    );

Console.WriteLine(result.GetValue<string>());

InvokePromptAsync でプロンプトを呼び出します。

各種パラメータがデフォルト値のままでいい場合は、kernel_args は追加しなくて構いません。応答は、GetValue で string 型にキャストしています。

結果

stream で出しているわけではないので応答が長いのですが、こんな感じで AI から応答が返ってきます。

文章が途中で途切れてしまっているのは、トークン数を800に制限したためで、チャット型式にして「続き」のように要求するか、最大トークン数をもう少し大きめにしておきます。

このあたりの詳しいところは書籍の第7章に書いてあります。

サンプルコードはこちら。ブランチを SemanticKernel にして取得してください。

https://github.com/moonmile/openai-samples/tree/SemanticKernel

カテゴリー: 開発 | Azure OpenAI Service 本のサンプルを Semantic Kernel で書き直す はコメントを受け付けていません