日報のフィードバックをChat GPTにやらせてみる

## はじめに
近年、AI技術の進化により、様々な業務プロセスが効率化されています。
特に、自然言語処理（NLP）技術を活用することで、文章の解析や生成が可能となっており、これらの技術を業務に取り入れることで、生産性の向上が期待されています。

この記事では、私たちの会社で実際に取り組んでいる、日報のフィードバックプロセスにChat GPTを導入する試みについて紹介します。
日報のフィードバックは、従業員の成長やコミュニケーションの向上に大きく寄与するものですが、手作業で行うと時間と労力がかかります。
そこで、Chat GPTを活用して、日報のフィードバックプロセスを効率化し、従業員と管理職の負担を軽減することを目指します。

具体的には、日報に対するフィードバックを自動生成し、適切な指導やアドバイスを提供する仕組みを開発します。
この取り組みによって、従業員がより効果的なフィードバックを受け取ることができるだけでなく、管理職もフィードバックの時間を短縮し、
他の重要な業務に集中することができます。

この記事では、Chat GPTを導入する目的や背景、具体的な実装方法、そして実際の効果について詳しく解説していきます。
ぜひ、これからの日報フィードバックのあり方を考えるきっかけにしていただければ幸いです。




という感じでGPT-4で、「はじめに」を作ってもらいました（笑）
インプットはタイトル名だけでしたが、ここまで伝えたいことを綺麗に再現してくれるとは驚きです。

今回の記事では上記で書いてもらった効率化について、弊社の週末ハッカソンで開発したことについて書いていきます。

折角なのでChat GPTの概要についても自分で話してもらいましょう(笑)
以下説明は、GPT-4で生成した文章です。

### ChatGPTとは

Chat GPTは、OpenAIが開発したGPT-4アーキテクチャをベースにした大規模な言語モデルで、自然言語処理（NLP）タスクにおいて高い性能を発揮します。
これにより、会話型のアプリケーションや、テキスト生成、質問応答などのタスクを効果的に実行することができます。

Chat GPTの主な特徴は以下の通りです。

- 高い文章生成能力：様々なトピックに対応し、自然な文章を生成することができます。
- 質問応答能力：質問に対して適切な回答を提供することができます。
- 柔軟性：多言語に対応し、幅広い業務や分野で利用可能です。

Chat GPTは、以下のような応用例があります。

- 顧客サポート：FAQやチャットボットでの顧客対応を効率化します。
- 文書作成：文章の生成や校正を助け、効率的な文書作成をサポートします。
- ソーシャルメディア管理：投稿内容の生成や解析を行い、効果的なコンテンツマーケティングを支援します。

Chat GPTは、自然言語処理技術を活用して様々な業務を効率化することができるため、今後もその活用範囲が広がることが期待されます。


## 自動フィードバックのメリット
上記でChatGPTの素晴らしい点を説明してきましたが、精度面での課題が存在することは否定できません。
それでも、私は以下のような大きなメリットがあると考えています。

### リソースの最大化
自動フィードバックを導入することで、レビュアーは簡単な誤りや表現の修正、報告内容の不足など、比較的単純なフィードバックをシステムに任せることができます。これにより、レビュアーは本質的な課題や、より深い洞察が必要な問題に集中して対応することが可能になります。
その結果、全体として質の高いフィードバックが提供され、業務の効率化が図られると考えました。

### 機械的なフィードバックによる受け手の心理的負担の軽減
自動フィードバックシステムを使用すると、フィードバックが機械的で客観的なものになります。
そのため、フィードバックを受ける側は、人間のレビュアーからのフィードバックに比べ、感情や主観が介入しないため気が楽になります。

これにより、受け手はフィードバックをより受け入れやすくなり、改善に対する抵抗感が減少することが期待されます。


## 実装

### DynamoDB
前回提出した日報を引き出したり、Botが自分のしたフィードバックを参照できるようにDynamoDBを使って情報を保存しておきます。

テーブル名 : chat_histories
パーティションキー : id (String)
ソートキー : created_unixtime (Number)

### Lambda
APIの実装はLambdaとAPI GAtewayで行っていきます。今回は、APIキーでアクセスするようにしています。
プロトタイプとして作ったので、コードの汚さは許してください...

```Python
import json
import requests
import boto3
from decimal import Decimal
import os
import openai
from boto3.dynamodb.conditions import Key
import datetime

dynamodb = boto3.resource('dynamodb')
openai.api_key = os.environ['API_Key']
API_ENDPOINT = 'https://api.openai.com/v1/engine/davinci-codex/completions'

def decimal_to_int(obj):
    if isinstance(obj, Decimal):
        return int(obj)

def lambda_handler(event, context):
    params = json.loads(event['body'])

    text = params['text']
    id = params['id']
    name = params['name']
    
    # 日報以外は通常通り質問に答える
    if "【日報】" not in text and "【日報提出】" not in text:
        response = openai.ChatCompletion.create(
            model="gpt-3.5-turbo",
            messages=[
                {"role": "user", "content": text}, 
            ]
        )
    
        print("Received response:" + json.dumps(response,default=decimal_to_int, ensure_ascii=False))
        print(response["choices"][0]["message"]["content"])
        
        response_text = response["choices"][0]["message"]["content"]
        dict = {
            'result': response_text
        }
        return {
            'statusCode': 200,
            'body': json.dumps(dict, ensure_ascii=False)
        }
    
    

    table = dynamodb.Table('chat_histories')
    response = table.query(
          KeyConditionExpression = Key("id").eq(id),
          Limit = 1,
          ScanIndexForward = False,
      )
    
    before_text = ""
    if len(response['Items']) != 0:
        print(response['Items'][0]['text'])
        before_text = response['Items'][0]['text']
    
    now = datetime.datetime.now()
    now_unixtime = int(now.timestamp())
    print(now_unixtime)
    
    
    table.put_item(
    	Item={
    		"id": id,
    		"name" : name,
    		"created_unixtime": now_unixtime,
    		"text": text
    	}
    )
    
    # 日報提出の場合はDynamoDBへの記録のみ
    if "【日報提出】" in text:
        dict = {
            'result': "日報の提出が完了しました。\n本日の業務お疲れ様でした。"
        }
        return {
            'statusCode': 200,
            'body': json.dumps(dict, ensure_ascii=False)
        }
    
    if before_text == "":
        before_text = "この日は業務がありませんでした。"
        
    response2 = table.query(
          KeyConditionExpression = Key("id").eq(id + "_bot"),
          Limit = 1,
          ScanIndexForward = False,
      )
    
    bot_text = ""
    if len(response2['Items']) != 0:
        print(response2['Items'][0]['text'])
        bot_text = response2['Items'][0]['text']
    
    if bot_text == "":
        bot_text = "フィードバックなし"
    
    q_a = "以下に示す【前回の日報】と、【今回の日報】を見比べて、新人のOJTをする上司のように成長を促す質問を3つしてください。\n"
    q_a += "尚、【前回の日報】は、【今回の日報】のより以前に書かれており、文中の曜日については気にしないでください。\n"
    q_a += "また、アポ打診に関してはメッセージで行われています。業務がない日は気にしないでください。\n"
    q_a += "そして、前回あなたは【前回のフィードバック】の内容をフィードバックしています。フィードバックなしの場合は気にしないでください。\n"
    q_a += "【前回の日報】\n"
    q_a += before_text
    q_a += "【今回の日報】\n"
    q_a += text
    q_a += "【前回のフィードバック】\n"
    q_a += bot_text
    
    

    response = openai.ChatCompletion.create(
        model="gpt-3.5-turbo",
        messages=[
            {"role": "user", "content": q_a}, 
        ]
    )

    print("Received response:" + json.dumps(response,default=decimal_to_int, ensure_ascii=False))
    print(response["choices"][0]["message"]["content"])
    
    response_text = response["choices"][0]["message"]["content"]
    dict = {
        'result': response_text
    }
    
    # Botのフィードバック内容もDynamoDBに保存
    table.put_item(
    	Item={
    		"id": id+"_bot",
    		"name" : name + "_bot",
    		"created_unixtime": now_unixtime,
    		"text": response_text
    	}
    )

    return {
        'statusCode': 200,
        'body': json.dumps(dict, ensure_ascii=False)
    }

```

### GAS
以下のコードをデプロイすることでSlack Botを実装できます。
GASのコード、Slack側の設定に関しては、以下の記事を参考にしている為、詳細はこちらをご覧ください。
[ChatGPTなモデルで応答してくれるSlackのチャットボットを作る（ChatGPT API対応済）](https://qiita.com/Cliffford/items/162011abcc8c1e60f002)

```JavaScript

function doPost(e)
{

  // ユーザIDの文字列の長さ
  let userIdLength = 11;

  // 疎通を確認
  let params = JSON.parse(e.postData.getDataAsString());
  if('challenge' in params)
  {
    return ContentService.createTextOutput(params.challenge);
  }

  // 再送を無視する
  const eventId = params.event_id;
  const cache = CacheService.getScriptCache();
  const cached = cache.get(eventId);
  if (cached)
  {
    Logger.log(`Already processed. Skip. （eventId: ${eventId})`);
    return;
  }
  cache.put(eventId, true, 60 * 10) ;

  // Botによるメンションは無視する
  if('subtype' in params.event) {
    return;
  }

  userName = params.event.user;
  console.log(params.event);
  let contents = '';

  // textがない場合はデフォルトの回答をする
  if('text' in params.event) 
  {
    let textTmp = params.event.text.substr(userIdLength + 3, params.event.text.length);
    let resultStr = getChatGptMessage(textTmp);

    if(typeof resultStr === 'undefined')
    {
      return;
    }

    contents = `<@${userName}> ${resultStr}`;
  }
  else
  {
    let resultStr = getChatGptMessage('ChatGPTについて教えてください。') ;
    if(typeof resultStr === 'undefined')
    {
      return;
    }

    contents = `<@${userName}> ${resultStr}`;
  }

  let options =
  {
    "method" : "post",
    "contentType" : "application/json",
    "payload" : JSON.stringify(
      {
        "text" : contents,
        link_names: 1
      }
    )
  };

  UrlFetchApp.fetch("発行したSlack WebHookのURL", options);
}

// ChatGPTのAPIを呼び出し応答を取得する
function getChatGptMessage(message) {
  const paramsJson = JSON.stringify({"text": message, "name":userName, "id":userName});
  console.log(paramsJson)
  let url = 'API Gatewayで設定したURL';

  let response = UrlFetchApp.fetch(url, {
    "method": "POST",
    "headers": {
      "Content-Type": "application/json",
      "x-api-key": "API Gatewayで設定したAPIキー"
    },
    "payload": paramsJson,
    "muteHttpExceptions": true,
  });
  console.log(response.getResponseCode())

  if(response.getResponseCode() != 200) {
    return 'エラーです';
  }

  let result = JSON.parse(response.getContentText());
  console.log(result['result']);
  return result['result'];
}

```

デプロイ管理からバージョンを更新していくことで、同じURLで修正版をリリースすることができます。

## 結果
前回の日報も含めてレビューしてもらいたいので、1日目に提出のみを行い、2日目にレビューをしてもらいます。

### 1日目
![スクリーンショット 2023-04-02 14.59.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308887/1232f12e-0fd5-b0af-a383-97b8f0f6743e.png)
1日目は提出のみ。

### 2日目
![スクリーンショット 2023-04-02 15.04.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308887/8695b99c-70c8-dfbb-5aaa-931cd8f8bf57.png)
かなり良い感じだと思います！
自分でテストしていた際、デフォルトの口調ではわかりづらい文章が生成され、微妙な印象でしたが、
「新人のOJTをする上司のように」という指示を出したところ、非常にわかりやすい文章になりましたので、おすすめです！


## まとめ
今回は試験段階であるため、まだ荒いフィードバックが目立ちましたが、業務内で積極的に使用し、
良い社内プロダクトに成長させていくことを目指しています。

今後はフィードバック内容の改善、記憶領域の最適化、ストック情報の活用方法などを検討していきたいと考えています。


## Peer　Lodgeの週末ハッカソンについて
弊社には複数人の副業エンジニアの方が在籍しており、週末に稼働されている方が多いです。
その為、平日稼働のエンジニアとの接点が少ないため、社内コミュニケーションの一環として今回の取り組みを実施しました。

今後社外向けにも、もくもく会という形で実施できればと思っておりますので、興味がある方いらっしゃいましたら、以下のアカウントでの告知をチェックしていただけると嬉しいです。

[@ikeponsu2](https://twitter.com/ikeponsu2)

また、弊社では、営業効率化や営業支援といった部分を、テクノロジーの力で一緒に解決してくれる仲間を募集しています。
Go言語、Pythonでのバックエンド開発がメインになります。詳細は募集、または上記アカウントへお問合せください。

https://en-gage.net/peer-lodge/

https://peer-lodge.com/