🤖
LangGraphでステートフルなチャットAIを実装した件
はじめに
web、discord、twitter、youtubeなどで動作するチャットAI「シャノン」を作りました。
このチャットAI「シャノン」はtoolの使用が可能で、必要に応じて事前に作成しておいたbingSearchツールやwolframAlphaツールを使用することができます。
また、内部状態として感情・行動計画を持ち、感情的な出力や現在のタスク進行状況によって行動計画を動的に修正することが可能です。
今回は基幹部分のみ紹介していきます。
動作例
Web上に構築したシャノン監視用UI
- 左側のサイドバーでは、ログの検索、tool一覧の表示、定時タスクの表示・実行、各種bot(discord botなど)の状態表示・起動・停止が可能です。
- 中央のステータス画面では、シャノンの行動計画と感情をリアルタイムで確認できます。
- 右側のチャット欄では、シャノンとテキストと音声で会話できます。
discord上での動作
検索
画像認識
画像生成
表作成
画像認識&表作成
新規スキル獲得
- ここは以前紹介したもの↓と同じです
https://zenn.dev/articles/25025f789ab792/edit
twitter上での動作
12星座占い
今日は何の日?
明日の天気予報
- シャノンのアカウント
https://x.com/I_am_Sh4nnon
Youtube上での動作
- コメントの内容・ユーザー名、動画のタイトル・概要欄を元に生成
使用技術
バックエンド
- LangGraph
- LangChain
- OpenAI API
- TypeScript
- Discord API
など
フロントエンド
- React
- TypeScript
- chatscope
- chart.js
など
大まかな構成
LangGraph
- 状態としてタスクID、行動計画、感情パラメータなどを保持
行動計画の形式
export type TaskStatus = "pending" | "in_progress" | "completed" | "error";
export interface TaskTreeState {
goal: string;
strategy: string;
status: TaskStatus;
error?: string | null;
subTasks?:
| {
subTaskGoal: string;
subTaskStrategy: string;
subTaskStatus: TaskStatus;
}[]
| null;
}
感情パラメータの形式
export interface EmotionType {
emotion: string;
parameters: {
joy: number;
trust: number;
fear: number;
surprise: number;
sadness: number;
disgust: number;
anger: number;
anticipation: number;
};
}
- 開始ノード=>感情ノード=>プランニングノード=>ツール使用ノード=>感情ノードというようにループするように設計。プランニングノードで終了するかどうかを判定。
各Agentの連携
- 各AgentはeventBusをハブとして双方向にデータをやり取りできるように設計
eventBusのコード
export class EventBus {
private listeners: Map<EventType, Array<(event: Event) => void>> = new Map();
/**
* イベントタイプに対応するコールバック関数を追加する
* @param eventType イベントタイプ
* @param callback コールバック関数
*/
subscribe(
eventType: EventType,
callback: (event: Event) => void
): () => void {
if (!this.listeners.has(eventType)) {
this.listeners.set(eventType, []);
}
this.listeners.get(eventType)?.push(callback);
// unsubscribe関数を返す
return () => {
const callbacks = this.listeners.get(eventType);
if (callbacks) {
this.listeners.set(
eventType,
callbacks.filter((cb) => cb !== callback)
);
}
};
}
/**
* イベントを送信する
* @param event イベント
*/
publish(event: Event) {
this.listeners.get(event.type)?.forEach((callback) => {
// targetMemoryZonesが指定されている場合、対象メモリゾーンのみに配信
if (
!event.targetMemoryZones ||
event.targetMemoryZones.includes(event.memoryZone)
) {
callback(event);
}
});
}
}
- バックエンドとフロントエンドはWebSocketでリアルタイムに双方向通信できるように連携
GitHub
Discussion