lagChainを活用したお手軽モダンなチャットアプリ（Vue3）

# 概要
langChain(js)先生のおかげて実装がメタクソシンプルになったので共有
世の中にはpython/Node.jsの例はいっぱいあるが、クライアントアプリの事例がすくないので。。
## 構成
- Node.js
- Vue.js (Vue3 + TypeScript + Composition API）
- Azure OpenAI or OpenAI or OpenAI互換なAPI
- "langchain": "^0.1.11"
- "@langchain/core": "^0.1.20"
- "@langchain/openai": "^0.0.13"

langChainのバージョンは2024年1月末のものです。
メジャーバージョンアップが入らなければ動くと思います。
（こいつら、3日おきとかにリリースしよるので、、、）

## コード
### API
``` typescript chatapi.ts
import { appSetting } from "@/config";
import { ChatOpenAI } from "@langchain/openai";
import { ChatMessage, HumanMessage } from "@langchain/core/messages";
import { useAppStore } from "@/store/app";

import type { ChatCompletionMessageChunk, ImagePoolingResponse } from "@/types";

const app_store = useAppStore();

function getChatModel(
  temperature: number,
  token: string | undefined
): ChatOpenAI {
  const openAIConfig = app_store.openAIConfig!;
  // Azure
  if (appSetting.login_required) {
    return new ChatOpenAI({
      temperature: temperature,
      // APIキー
      azureOpenAIApiKey: openAIConfig.apim_subscription_key,
      azureOpenAIBasePath: openAIConfig.endpoint + "/deployments",
      // gpt3.5-turboとか
      azureOpenAIApiDeploymentName: openAIConfig.deployment,
      modelName: openAIConfig.model,
      // 2024-02-15-preview とか
      azureOpenAIApiVersion: openAIConfig.api_version,
      // APIManagement関係の追加ヘッダ
      configuration: {
        baseOptions: {
          headers: {
            "Ocp-Apim-Subscription-Key": openAIConfig.apim_subscription_key,
            Authorization: "Bearer " + token,
          },
        },
      },
    });
  } else {
    // local
    return new ChatOpenAI({
      temperature: temperature,
      openAIApiKey: openAIConfig.apim_subscription_key,
      configuration: {
        baseURL: openAIConfig.endpoint,
      },
    });
  }
}

export async function* streamChatCompletion_langChain(
  message: ChatMessage[],
  temperature: number,
  token: string | undefined
) {
  const chatModel = getChatModel(temperature, token);
  try {
    const completion = await chatModel.stream(message);
    for await (const chunk of completion) {
      yield chunk.content;
    }
  } catch (error) {
    console.error(error);
    throw error;
  }
}

```
### 画面
``` typescript view.ts
import { HumanMessage } from "@langchain/core/messages";
import {
  streamChatCompletion_langChain,
} from "@/chatapi";

// チャット送信
const sendMessage = async () => {
    //API呼び出し
    const temp = [0.9, 0.5, 0.1][input.temperature]; //・創造的に・バランス・厳格に
    const message = new HumanMessage({
        content: "Hello OpenAI!!!",
    });
    const generator = streamChatCompletion_langChain(
      [message] as ChatMessage[],
      temp,
      appSetting.login_required ? app_store.authToken : undefined
    );
    // 順次生成の回答を表示
    loading_message.loading = true;
    for await (let chunk of generator) {
      loading_message.content = loading_message.content + chunk;
    }
    loading_message.loading = false;
};
```
``` html view.vue
<v-card v-show="loading_message.loading" flat class="px-4">
      <div class="d-flex flex-no-wrap">
        <v-icon icon="mdi-loading mdi-spin" size="large"></v-icon>
        <v-card-text :class="loading_message.color">{{
          loading_message.content
        }}</v-card-text>
      </div>
</v-card>
```

わーいシンプル！

と言いつつ、ちょっとだけ（意図的に）めんどくさくしてるとこがあり
以下のコードでChatOpenAIクラスをログインあり・なし切り替えてます。
[vllm](https://github.com/vllm-project/vllm) とかでローカルにLLM APIを立てている場合、ログインとかモデルの設定とかが要らないので、そこを分岐してます。

``` typescript
function getChatModel(
  temperature: number,
  token: string | undefined
): ChatOpenAI {
  const openAIConfig = app_store.openAIConfig!;
  if (appSetting.login_required) {
    // Azure APIManagement対応
    return new ChatOpenAI({
      temperature: temperature,
      azureOpenAIApiKey: openAIConfig.apim_subscription_key,
      azureOpenAIBasePath: openAIConfig.endpoint + "/deployments",
      azureOpenAIApiDeploymentName: openAIConfig.deployment,
      modelName: openAIConfig.model,
      azureOpenAIApiVersion: openAIConfig.api_version,
      configuration: {
        baseOptions: {
          headers: {
            "Ocp-Apim-Subscription-Key": openAIConfig.apim_subscription_key,
            Authorization: "Bearer " + token,
          },
        },
      },
    });
  } else {
    //ローカルモデル対応
    return new ChatOpenAI({
      temperature: temperature,
      openAIApiKey: openAIConfig.apim_subscription_key,
      configuration: {
        baseURL: openAIConfig.endpoint,
      },
    });
  }
}
```

逆に、いや、OpenAIしか呼ばないし。
ログイン管理とか要らんですし、という場合
以下でOKです。

``` typescript
export async function* streamChatCompletion_langChain(
  message: ChatMessage[]
) {
  const chatModel = new ChatOpenAI({
      openAIApiKey: OPEN_AI_API_KEY,
   });
  try {
    const completion = await chatModel.stream(message);
    for await (const chunk of completion) {
      yield chunk.content;
    }
  } catch (error) {
    console.error(error);
    throw error;
  }
}
```

## 関連情報

AzureADと連携したログインについては、以前に書いてるのでそちら参照。
[Azure AD連携を使ってVue\.js（Vue3）のAzure OpenAIチャットアプリのアクセス権を制御する \#TypeScript \- Qiita](https://qiita.com/natsu_san/items/f7cfe798ddb54a0a9c40)


なんか最近はAzureのOpenAISDKも充実してきたようで、そちらへの乗り換えも検討中。
でもRetriever連携とか考えるとlangChainで繋いでおきたいんだよなぁ。。

というわけで、よきlangChainライフを！