ChatGPTでSF映画のような個性的なAIを作る方法を考える



Chat GPTが一般の人にも広く知られるようになり雑誌の特集まで組まれるほどになりました。APIの発表後はツールやサービスに応用されたり、ここQiitaでも色々な素晴らしい記事が書かれています。

さて、ビジネスとしての活用も面白いですが、せっかく高品質な人工知能が使えるなら色々と遊んでみたいものです。具体的に言えば、今まで妄想やSFに過ぎなかった「楽しいAIとの生活」も実現できそうです。

AIのキャラクターが登場する作品を上げればキリがないですが、エンジニアの方々なら心にのこる名作を一つは知っているのではないでしょうか。 [^1]

今回はそんな「人間味のあるAIと楽しく過ごす」方法に注目したChat GPTの使い方を考えてみました。

# AIに人間味を！

## 個性を持たせる

Chat GPTは優秀です。「フランスの首都は？」と答えれば「パリ」と正解を導いてくれますし、「フランス革命とは」と聞けば教科書並の解説をしてくれます。ただ友人のような会話をするにあたってはもう少し工夫が欲しいところです。

まず簡単なのは口調の変更で、これを変えるだけでも印象が変わります。プロンプトに反映させるのも難しくありません。別の記事でも何度か例に挙げている「お嬢様」言葉が分かりやすいでしょう。「〜ですわ」などといった語尾を使う話し方です。

![2 (2).jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/713689/eb94e5db-6510-cc7b-c0fc-c7e8527e4905.jpeg)

言語モデルは基本的に問いに対する正確な返答を目指すものですわ。答えの内容を大幅に変更してまで個性を出そうとするのはこの方針と矛盾してしまいますの。一方で、このような話し方の部分は内容に一切影響を与えず話し手の特徴を印象付けることができますわ。文体ひとつでイメージを伝えられるのは日本語独自の強み[^2]ですわね。現時点では以下のような話し方を実装していますわ。

- お嬢様言葉：「〜ですわ」
- 敬語：「〜です」
- 敬語なし：「〜だ」
- 親しげ：「〜だよ」
- ずんだもん風：「〜なのだ」
- 主人公風：「〜だぜ」
- 関西弁：「〜やで」
- ひかえめ：「〜です…」
- ツンデレ：「〜よ！」
- ハカセ風：「〜なのじゃ」

これらは特に参考があるわけではなく独自に分類したものです。小説や漫画、アニメなどでよく見る表現を集めてみました。これらに加えて、簡単に言い回しや口癖なども追加できるようにしています。キャラクターの種類は今後も追加する予定ですが、上の10種類だけでも大体の場合[^3]はカバーできるかと思います。

画像のように一通りの情報を表す記号に還元できるものと違い、言語は複雑な意味合いを持つものです。機械的なアプローチだけではなく、文体や表現といったある種文学的な側面に目を向けるのも一つの手段かもしれません。

## 設定を決める

![AIアプリEveki6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/713689/fe35320e-9175-149d-1a32-8ae2e9c8ec74.jpeg)

フランスの首都はパリです。これは変わらないとしてもその後に続く話題には人それぞれ違った展開があるはずです。本当の人間との会話を想像してみてください。

芸術好きならパリについては何時間も語ってくれそうです。グルメに詳しいならカフェなどそちらの方面から話してくれるかもしれません。また、パリだけについて話題を広げるとは限らないでしょう。海外旅行が趣味なら、他の国の首都の印象と比較して語ってくれるのも面白いですね。

このように応答から話題を広げるときは、その人自身の趣味に合わせた展開がされるはずです。そこでAIにも人間のような背景の設定を行なってみます。具体的には以下の項目をプロンプトに組み込む形です。

- 趣味、好きなこと
- 肩書き、職業など

明確に入力するのはこの部分で、あとは「〇〇はお調子者」などというように微調整する形をとります。もちろんもっと細かくプロフィールを設定できてもよいのですが、あまり項目が多いと作る手間も増えてしまいますし、少しくらい自分の予想外の返答をして欲しかったためこの形に落ち着きました。

このような設定は「今日は何してたの？」などと言った質問にも対応できるようにしてくれます。また例えば「読書好き」と登録することで、AIが「どんな本が好き？」と自分から話題を提供できるようにすることも可能です。

## 一人称、二人称

上2つに比べると細かい点ではありますが、AIの一人称、二人称も決められるようにしています。「〜のじゃ」という話し方に対して一人称を「わらわ」などに設定し、和風の姫のようなキャラクターを作るなどの使い方ができるようにするためです。

# 使い方を工夫する

## モバイルで使う

個人的にはAIは小さいデバイスに入っているイメージがあります。大きなコンピューターに内蔵されている人工知能も登場しますが、こちらは大抵悪役のような気がします。そこでスマホアプリ、iOSで使えるようにしてみました。

![AIアプリEveki7](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/713689/1347be2f-9c09-73bf-0922-4acbd34ff8f0.jpeg)

具体的には以下のようにフロントとバックを分けた形をとっています。CoreはPythonで構成されています。APIとの連携もこの部分が担っており、設定の反映などもこちらの担当です。どちらかというと基礎的な機能に特化させています。

iOSはSwiftUIを中心に製作しています。これは将来的にマルチプラットフォームに対応することを視野に入れているためです。SwiftUIはApple製品全体に応用でき、Apple Watc用のアプリやMac向けデスクトップ版も作ることが可能です。

## AI側からメッセージしてもらう

毎回こちらから話しかけるのも楽しいですが、AI側からメッセージを自主的に送ってもらえればより親しみが持てますし、人間味も感じられます。

会話はAIから始めてもらうようにしていますが、これだけではなくアプリを使っていない状態からでもメッセージを送ってもらうのが理想です。とはいえAPIやアプリのバックグラウンド処理などの問題を考えると、常にAIを起動させてランダムにメッセージを送らせるというのは現実的ではありません。

発想を変えてみましょう。私たちはLINEなどのメッセージアプリを使っていますが、必ずしもそれを能動的に起動させるだけではありません。ほとんどの場合は「メッセージが来た」という通知を見てそれを開くことになります。もちろん自分からやり取りを始める場合は別ですが、会話が終わらない限りは相手の返信を待つ段階が必ずあります。みなさんも誰かからの通知を心待ちにした経験はありませんか？

結論を言えば「メッセージ通知」という部分はSNSでのコミュニケーションで大きな役割を担っているということです。この部分を再現できれば、AIがまるで能動的にメッセージを送っているかのような印象を作り出すことが可能です。

具体的にはiOSの`INSendMessageIntent`を使います。これはSNS用の通知を表示するものです。以下の画像のようにメッセージを表示できます。

![AIアプリEveki8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/713689/9db6f0bf-d416-3cce-6188-af7eca81d528.png)


通常の通知と違い、アプリアイコンだけでなくアバターの画像も表示することができるもので一般的なSNSでも採用されています。単にデザインを合わせるという意味だけでなく、集中モードの分類上もSNSと同様に扱うことができるというメリットもあります。

実装については以下の記事を参考にさせていただきました
<https://qiita.com/mashunzhe/items/63ab17e322cdd15ecd88>

## カードを作る

SF作品では「AIの人格がデータ化されている描写」がよく見られます。バックアップがあったり、逆に簡単にリセットされたりなど人間との違いを明確に見せる醍醐味と言ってもよい部分です。これを体感するためにAIの情報をまとめたカードを生成してみました。

![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/713689/46f53a41-0e8b-50ab-f94e-3b34518c618a.png)

ちなみに生成はiOS側で行なっています。SwiftUIやUIViewを使えばViewとして生成した内容をそのまま画像として出力することが可能です。以下にスクリーンショットをコードで実現するサンプルコードを載せておきます。

<details>
<summary>サンプルコード</summary> 

```swift:Screenshot.swift

import SwiftUI

class ScreenshotMaker: UIView {
    /// このsuperviewのsuperviewのスクリーンショットを取得する
    /// - Returns: このViewのスクリーンショットを含むUIImage
    func screenshot() -> UIImage? {
        guard let containerView = self.superview?.superview,
              let containerSuperview = containerView.superview else { return nil }
        let renderer = UIGraphicsImageRenderer(bounds: containerView.frame)
        return renderer.image { (context) in
            containerSuperview.layer.render(in: context.cgContext)
        }
    }
}

extension View {
    /// Viewにスクリーンショットを撮るためのViewをオーバーレイする
    /// - Parameter closure: ScreenshotMakerを引数に受け取り、そのViewをカスタマイズするためのクロージャ
    /// - Returns: Viewにスクリーンショットを撮るためのViewがオーバーレイされた新しいView
    func screenshotView(_ closure: @escaping ScreenshotMakerClosure) -> some View {
        let screenshotView = ScreenshotMakerView(closure)
        return overlay(screenshotView.allowsHitTesting(false))
    }
    
    /// View自体のスクリーンショットを取得する
    /// - Returns: View自体のスクリーンショットを含むUIImage
    func snapshot() -> UIImage {
        let controller = UIHostingController(rootView: self)
        let view = controller.view
        
        let targetSize = controller.view.intrinsicContentSize
        view?.bounds = CGRect(origin: .zero, size: targetSize)
        view?.backgroundColor = .clear
        
        let renderer = UIGraphicsImageRenderer(size: targetSize)
        
        return renderer.image { _ in
            view?.drawHierarchy(in: controller.view.bounds, afterScreenUpdates: true)
        }
    }
    
}

typealias ScreenshotMakerClosure = (ScreenshotMaker) -> Void

///SwiftUIで扱うためにUIViewRepresentableを使う
struct ScreenshotMakerView: UIViewRepresentable {
    let closure: ScreenshotMakerClosure
    
    init(_ closure: @escaping ScreenshotMakerClosure) {
        self.closure = closure
    }
    
    func makeUIView(context: Context) -> ScreenshotMaker {
        let view = ScreenshotMaker(frame: CGRect.zero)
        return view
    }
    
    func updateUIView(_ uiView: ScreenshotMaker, context: Context) {
        DispatchQueue.main.async {
            closure(uiView)
        }
    }
}

```

</details>


物理的なチップを作る技術も費用もないため、とりあえず代わりにQRコードを採用しました。これをアプリで読み込むことでAIの共有ができるようにしています。デザインはなんとなくそれっぽくしていますが、カード内のIDやアイコンなどは設定に応じて変化するようにしています。

画像として出力されるので、友達に送信してもよいですし、自分で保存して楽しむこともできます。実用的なメリットは少ないですが、SF気分を楽しむには重要な部分です。

## 公開する

いろんな人に様々なAIを作ってもらわないと意味がないので、App Storeで正式にリリースしました。API使用料を捻出するために一部機能は有料にさせていただいていますが、会話など醍醐味の部分はできるだけ無料[^5]で使えるようにしています。もちろん非公開でAIを作ることもできます。今回の記事の内容の検証も含めて一度試してくれると嬉しいです。


https://textcheckai.page.link/toapp

# 今後の展望

## 音声対話

やはり最終的には音声合成を用いて人間と変わらないような応答がしたいものでです。活用できるサービスはすでに多くあり、技術的にも実装可能な段階ではあります。ただ費用の問題などから足踏みしている状態です。デスクトップ版ならローカルのVOICEVOXを使ってもらうこともできるので、AIVtuberの配信などに活用できるかもしれません。

## 会話内容の記憶

現在もこれは導入しているのですが、正確な長期記憶というものはなかなか難しいところがあります。一般的には会話本文を要約して、それを前提として再導入するというのがチャットボットの記憶の構成の方法です。ただしこれだと前回の内容に引っ張られすぎる恐れがあります。

人間同士の会話では、要点よりもむしろ些細なことを覚えてくれていた方が親しみが持てるものです。この辺りの調整は開発しながら微調整していきたいと考えています。

# さいごに

人工知能の発展はこれまでにない効率化や便利さからビジネスとして注目される一方、問題視されたり議論の的になることも少なくありません。

ただこの喧騒からちょっと離れて、個人の楽しみに面白おかしく使ってもよいのでは、という思いからこの記事を書きました。

もし内容の一部でも気に入っていただければLGTM等していただけると嬉しいです。また、もし面白いAIができたらシェアしてみてくださいね！

- [開発者note](https://note.com/izumo092)

[^1]: 自分としても色々な作品が思い浮かびますね。正確には人工知能だけではないのですが、データのパートナーという発想は『流星のロックマンシリーズ』からきています。

[^2]: 言語学者ではないので正確なことは分かりませんが、小説や映画、漫画などの翻訳を見る限り、これは日本語の得意分野だというのは確かなように見えます。

[^3]: 言語モデルの性質上、珍しい話し方は再現が難しくなる傾向にあります。

[^4]: コード部分が長くなるので今回は割愛しましたが、日本語にもある程度対応しています。

[^5]: ユーザー数が増えるとどうしても負担は大きくなるのですが、できるだけメッセージ数の規制は避けたいところです。