AIチャットボットの迅速な統合

# **シナリオ概要**

ChatGPTの世界的な人気により、人工知能（AI）は今日の開発者の焦点となっており、中国の主要ベンダーは独自の大規模モデル（BM）アプリケーションと製品を発表しています。多くのベンダーがアプリケーションとAIを組み合わせて新しい機会を見出しています。次世代の大規模言語モデル（LLM）の強力な会話コミュニケーション能力は、あらゆる種類のインスタントメッセージングシナリオと自然に適合し、ChatとAIの組み合わせに広大な想像の余地をもたらします。

このチュートリアルでは、ChatのWebhook機能を通じてAIサービス機能をChatに統合し、ユーザー向けのAIチャットボットを構築して、インテリジェントカスタマーサービス、クリエイティブアシスタンス、ワークアシスタントなどの機能を実装する方法について説明します。（このドキュメントの手順ではMiniMax LLMを例として使用していますが、同じ方法で他のChatGPTのようなサービスを統合することができます。）

# **基本的な統合ガイドライン**

## **Chatアカウントの作成**

[コンソール](https://console.trtc.io/)にアクセスし、アプリケーションを作成して、アプリケーションのSDKAppIDとキー（Chatキー）を取得します。

# **対応するAIサービスプロバイダーのアカウント登録**

統合するAIサービスのプロバイダーにアカウントを登録し、ログインしてAPIキー（`AI_SECRET_KEY`）を取得します。

# **Chatチャットボットアカウントの作成**

RESTful APIを通じてChatチャットボットアカウントを作成します。Chatチャットボットは、ユーザーIDが`@RBT#`で始まる特別なユーザーです。

```go
curl -d '{"UserID":"@RBT#001","Nick":"MyRobot"}' "https://console.tim.qq.com/v4/openim_robot_http_svc/create_robot?sdkappid= {}&identifier=administrator&usersig={}&random=123456789&contenttype=json"
```

上記のコマンドの`sdkappid={}`と`usersig={}`を、あなたのSDKAppIDとChatキーに基づいて生成されたUserSigに置き換えてください。詳細については、[UserSigの生成](https://trtc.io/document/34385?product=chat&menulabel=serverapis)を参照してください。Linuxでコマンドを実行すると、サーバーは以下の情報を返します：

```go
{"ActionStatus": "OK", "ErrorCode": 0, "ErrorInfo": ""}
```

上記の情報は、ニックネーム`MyRobot`のチャットボット`@RBT#001`が正常に作成されたことを示しています。

# **Chat webhookの設定**

Chat webhookは、イベントの前後にChatバックエンドから対応するアプリケーションのバックエンドサーバーに送信されるリクエストです。アプリケーションバックエンドは、必要なデータ同期を実行したり、イベントのその後の処理に介入したりすることができます。ユーザーがチャットボットに送信したメッセージや、グループチャットでの@RBT#イベントをリッスンして反応するために、「ロボットイベントWebhook」を使用します。Chatコンソールで「ロボットイベントWebhook」を見つけてクリックし、機能を有効にして設定を保存する必要があります。

# **シナリオ固有の実装**

一対一のチャットを例にとると、全体的な作業プロセスは以下の通りです：

1. `user1`ユーザーが「hello」メッセージをチャットボット`@RBT#001`に送信します。

2. ChatバックエンドがWebhookを送信して、アプリケーションバックエンドにイベントを通知します。

3. アプリケーションバックエンドがイベント通知を受信し、メッセージ送信者`user1`、メッセージ受信者`@RBT#001`、メッセージ内容`hello`などの情報が含まれています。

4. アプリケーションバックエンドがAIサービスAPI（MiniMax API）を呼び出し、「nice to meet you」などの返信メッセージを含む応答を受信します。

5. アプリケーションバックエンドがChatのRESTful API（一対一チャットの場合は`sendmsg` API、グループチャットの場合は`send_group_msg` API）を呼び出して、`@RBT#001`として`user1`に返信メッセージを送信します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3957687/8588c310-8f5c-54fe-063f-3ea5cda96cee.png)


Go言語を例にとると、アプリケーションバックエンドの主要なコードは以下の通りです。

**注意：**

以下のコードはデモンストレーション用であり、多くの例外処理コードを省略しています。本番環境で直接使用することはできません。

# **Webhookコマンドの配布と処理**

ポート80でリッスンするHTTPサービスを作成し、`/im` URLにハンドラーを登録して、`http://im`に送信されるすべてのリクエストを処理します。Chatから送信されるすべてのWebhookリクエストには`CallbackCommand`パラメータが含まれており、異なる値が異なるWebhookコマンドを表します。ハンドラーは、Chatによって設定された`CallbackCommand`パラメータに従って処理を実行します。

```java
func handler(w http.ResponseWriter, r *http.Request) {
   command := r.URL.Query().Get("CallbackCommand")
   reqbody, _ := io.ReadAll(r.Body)
   var rspbody []byte
   switch command {
   case "Bot.OnC2CMessage":// 一対一メッセージのチャットボットのWebhookコマンドワード
       dealC2c(context.Background(), reqbody)
       rspbody = []byte("{\"ActionStatus\": \"OK\", \"ErrorCode\": 0, \"ErrorInfo\": \"\"}")
   default:
       rspbody = []byte("invalid CallbackCommand.")
   }
   w.Write(rspbody)
}

func main() {// アプリケーションバックエンドに送信されたWebhookコマンドを処理するハンドラーを登録
    http.HandleFunc("/im", handler)
    http.ListenAndServe(":80", nil)
}
```

# **チャットボットが受信した一対一メッセージの処理**

一対一メッセージを処理する際、まず送信者がチャットボットでないことを確認します（一般的にチャットボットは別のチャットボットにメッセージを送信しません）。これは無限のWebhookループを防ぐためです。次に、メッセージの本文を解析して、ユーザーがチャットボットに送信したメッセージの内容テキストを取得し、送信者のUserIDをコンテキストに保存して、後続のRESTful API呼び出しで返信するのを容易にします。最後に、`askAI`を呼び出してAIサービスにリクエストを送信します。

```go
func dealC2c(ctx context.Context, reqbody []byte) error {
  root, _ := simplejson.NewJson(reqbody)
  jFromAccount := root.Get("From_Account")
  fromAccount, _ = jFromAccount.String()
// 送信者のIDをチェックして、あるチャットボットから別のチャットボットへの要求を処理することを避け、無限ループを防ぎます。
if strings.HasPrefix(fromAccount, "@RBT#") {
    return nil
  }
  jToAccount := root.Get("To_Account")
  toAccount, _ := jToAccount.String()
  msgBodyList, _ := root.Get("MsgBody").Array()
  for _, m := range msgBodyList {
    msgBody, _ := m.(map[string]interface{})
    msgType, _ := msgBody["MsgType"].(string)
    if msgType != "TIMTextElem" {
      continue
    }
    msgContent, _ := msgBody["MsgContent"].(map[string]interface{})
    text, _ := msgContent["Text"].(string)
    ctx = context.WithValue(ctx, "from", fromAccount)
    ctx = context.WithValue(ctx, "to", toAccount)
    go askAI(ctx, text)
  }
  return nil
```

# **AIサービスAPIの呼び出し**

このステップでは、サードパーティのAIサービスであるMiniMax LLMを使用してインテリジェントチャットを実装します。MiniMax LLMサービスの代わりに他のAIサービスを使用することもできます。ここでは、会話のコンテキストを含まない単純な`completion` APIをデモンストレーションしていることに注意してください。必要に応じて他のAPIの詳細についてはMiniMaxのドキュメントを参照してください。

```go
type MiniMaxRsp struct {
  Reply string `json:"reply"`
}

// MiniMaxにリクエストを送信し、返信を取得する
func askAI(ctx context.Context, prompt string) {
  url := "https://api.minimax.chat/v1/text/completion"
  var reqData = []byte(`{
    "model": "abab5-completion",
    "prompt": prompt
  }`)
  request, _ := http.NewRequest("POST", url, bytes.NewBuffer(reqData))
  request.Header.Set("Content-Type", "application/json; charset=UTF-8
  request.Header.Set("Authorization", API_SECRET_KEY)
  client := &http.Client{}
  response, _ := client.Do(request)
  defer response.Body.Close()
  body, _ := ioutil.ReadAll(response.Body)
  rsp := &MiniMaxRsp{}
  json.Unmarshal(body, rsp)
  reply(ctx, rsp.Reply) // AIサービスが返信した内容をユーザーに送信
}
```

# **AIサービスが返信した結果をユーザーに返す**

AIサービスからの返信を受信した後、ChatのRESTful API `sendmsg`を呼び出すだけで、チャットボットがユーザーに返信をシミュレートできます。メッセージの送信者を`@RBT#001`、受信者を`user1`と指定します。

```go
// RESTful APIリクエストを送信する
func doRestAPI(host string, sdkappid int, admin, usersig, command, body string) {
  url := fmt.Sprintf("https://%s/v4/%s?sdkappid=%d&identifier=%s&usersig=%s&random=%d&contenttype=json",
    host, command, sdkappid, admin, usersig, rand.Uint32())
  req, _ := http.NewRequest("POST", url, bytes.NewBufferString(body))
  req.Header.Set("Content-Type", "application/json")
  cli := &http.Client{}
  rsp, err := cli.Do(req)
  if err != nil {
    log.Printf("REST API failed. %s", err.Error())
    return
  }
  defer rsp.Body.Close()
  rsptext, _ := io.ReadAll(rsp.Body)
  log.Printf("rsp:%s", rsptext)
}

// ChatのRESTful APIを呼び出してユーザーに返信する
func reply(ctx context.Context, text string) {
  rsp := make(map[string]interface{})
  msgbody := []map[string]interface{}{{
    "MsgType":    "TIMTextElem",
    "MsgContent": map[string]interface{}{"Text": text},
  }}
// `GenUserSig`の実装については、ドキュメントを参照してください。
  usersig, _ := GenUserSig(IM_SDKAPPID, IM_KEY, "administrator", 60)
  rsp["From_Account"] = ctx.Value("to").(string)//"@RBT#001"
  rsp["To_Account"] = ctx.Value("from").(string)
  rsp["SyncOtherMachine"] = 2
  rsp["MsgLifeTime"] = 60 * 60 * 24 * 7
  rsp["MsgSeq"] = rand.Uint32()
  rsp["MsgRandom"] = rand.Uint32()
  rsp["MsgBody"] = msgbody
  rspbody, _ := json.Marshal(rsp)
  doRestAPI("console.tim.qq.com", IM_SDKAPPID, "administrator", usersig, "openim/sendmsg", string(rspbody))
}
```

上記の手順により、ChatサーバーサイドとMiniMaxAIオープンプラットフォーム間の一対一チャット接続を実装しました。上記の手順に従って他のAIサービスプロバイダーからのAIサービスを統合することも可能です。単に`askAI`関数をそのAIサービスプロバイダーの対応するAPI呼び出しに置き換えるだけです。グループチャットチャットボットの場合、`Bot.OnGroupMessage` webhookコマンド処理の実装を補完するだけで済みます。