feat: 对话流采用策略设计模式重构

master
xjs 2025-05-29 17:50:05 +08:00
parent 97d2ccc154
commit 86918a667e
2 changed files with 178 additions and 104 deletions

View File

@ -14,6 +14,7 @@ import {
useState, useState,
} from "react"; } from "react";
import { useToast } from "@/hooks/use-toast"; import { useToast } from "@/hooks/use-toast";
import { MessageHandlerStrategy } from "@/hooks/useRealtimeClient";
type RoomInfo = { type RoomInfo = {
appId: string; appId: string;
@ -94,6 +95,8 @@ export const RealtimeClientProvider = ({
const { toast } = useToast(); const { toast } = useToast();
const messageHandlerStrategy = useRef(new MessageHandlerStrategy());
/** 初始化客户端并设置监听 */ /** 初始化客户端并设置监听 */
const initClient = async ({ const initClient = async ({
initMessage, initMessage,
@ -244,101 +247,17 @@ export const RealtimeClientProvider = ({
client.on(EventNames.ALL, (_eventName, event: any) => { client.on(EventNames.ALL, (_eventName, event: any) => {
// 交给状态机处理解析流程 // 交给状态机处理解析流程
// 普通消息流处理
if ( if (
event.event_type !== ChatEventType.CONVERSATION_MESSAGE_DELTA && event.event_type !== ChatEventType.CONVERSATION_MESSAGE_DELTA &&
event.event_type !== ChatEventType.CONVERSATION_MESSAGE_COMPLETED event.event_type !== ChatEventType.CONVERSATION_MESSAGE_COMPLETED &&
) { event.event_type !== "conversation.created"
// 处理conversation.created事件
if (
event.event_type === "conversation.created" &&
!opts.initMessage &&
!opts.fileInfo
) {
setMessageList((prev) => [
...prev,
{
content: event.data.prologue,
role: RoleType.Assistant,
event,
},
]);
}
return;
}
// 如果是assistant的completed消息或verbose类型直接返回
if (
(event.data.role === "assistant" &&
event.event_type === ChatEventType.CONVERSATION_MESSAGE_COMPLETED &&
event.data.type === "verbose") ||
(event.data.type === "answer" &&
event.event_type === ChatEventType.CONVERSATION_MESSAGE_COMPLETED)
) { ) {
return; return;
} }
const content = event.data.content; setMessageList(prev =>
setMessageList((prev) => { messageHandlerStrategy.current.process(event, prev, opts)
// 如果是工具调用相关的消息,不添加到消息列表 );
if (
event.data.type === "function_call" ||
event.data.type === "tool_response"
) {
const jsonContent = JSON.parse(event.data.content);
const lastMessage = prev[prev.length - 1];
if (jsonContent.name === "doc_reader-PDF_reader") {
return [
...prev,
{
content: "",
role: RoleType.Assistant,
fileInfo: opts.fileInfo,
fileParseStatus: 1,
event,
},
];
}
else if (
lastMessage.event.type === "function_call" &&
event.data.type === "tool_response"
) {
return [
...prev.slice(0, prev.length - 2),
{
content: "",
role: RoleType.Assistant,
fileInfo: opts.fileInfo,
fileParseStatus: 2,
event,
},
];
}else{
return [...prev]
}
}
if (
prev.length > 0 &&
prev[prev.length - 1].event?.event_type ===
ChatEventType.CONVERSATION_MESSAGE_DELTA &&
event.event_type === ChatEventType.CONVERSATION_MESSAGE_DELTA &&
prev[prev.length - 1].event.data.answer_id === event.data.answer_id
) {
return [
...prev.slice(0, -1),
{
content: prev[prev.length - 1].content + content,
role: prev[prev.length - 1].role,
event,
},
];
} else {
// 新消息追加
return [...prev, { content, role: event.data.role, event }];
}
});
}); });
}; };

View File

@ -1,20 +1,175 @@
class MessageHandler { import { ChatEventType, RoleType } from "@coze/api";
canHandle(_eventType: string, _dataType: string) {
throw new Error("Subclass must implement canHandle method");
}
handle(_event: string) { interface MessageHandler {
throw new Error("Subclass must implement handle method"); canHandle(event: any, opts?: any, prevMessageList?: any[]): boolean;
handle(event: any, prevMessageList: any[], opts?: any): any[];
}
abstract class BaseMessageHandler implements MessageHandler {
abstract canHandle(event: any, opts: any, prevMessageList?: any[]): boolean;
abstract handle(event: any, prevMessageList: any[], opts: any): any[];
protected createMessage(content: string, role: RoleType, event: any, opts?: any) {
const fileParseStatus = opts && opts.fileInfo ? event.data.type === "function_call" ? 1 :
event.data.type === "tool_response" ? 2 : undefined:undefined
return {
content,
role,
fileInfo: (opts && opts.fileInfo) ? opts.fileInfo : undefined,
fileParseStatus: fileParseStatus,
event
};
}
} }
setNextHandle(){}
}
class ToolResponseHandler extends MessageHandler{ // 普通消息处理 添加欢迎语
canHandle(eventType: string, dataType: string): void { class ConversationHelloHandler extends BaseMessageHandler {
canHandle(event: any, opts: any, _prevMessageList?: any[]): boolean {
return event.event_type === "conversation.created" &&
!opts.initMessage &&
!opts.fileInfo;
} }
handle(event: string): void {
handle(event: any, prevMessageList: any[], _opts: any): any[] {
return [
...prevMessageList,
this.createMessage(event.data.prologue, RoleType.Assistant, event,)
];
} }
} }
// 处理忽略的事件类型
class IgnoredEventHandler extends BaseMessageHandler {
canHandle(event: any, _opts: any, _prevMessageList?: any[]): boolean {
return (
(event.data.role === "assistant" &&
event.event_type === ChatEventType.CONVERSATION_MESSAGE_COMPLETED &&
event.data.type === "verbose") ||
(event.data.type === "answer" &&
event.event_type === ChatEventType.CONVERSATION_MESSAGE_COMPLETED)
);
}
handle(_event: any, prevMessageList: any[], _opts: any): any[] {
// 直接返回原消息列表,不做任何更改
return prevMessageList;
}
}
class UserMessageEventHandle extends BaseMessageHandler{
canHandle(event: any, _opts: any, _prevMessageList?: any[]): boolean {
return event.event_type === ChatEventType.CONVERSATION_MESSAGE_COMPLETED && event.data.role === RoleType.User
}
handle(event: any, prevMessageList: any[], _opts: any): any[] {
return [...prevMessageList,this.createMessage(event.data.content,event.data.role,event)]
}
}
class NormalMessageEventHandle extends BaseMessageHandler{
canHandle(event: any, _opts: any, prevMessageList?: any[]): boolean {
if(typeof prevMessageList === 'undefined'){
return false
}
let prevMessage = prevMessageList[prevMessageList.length - 1];
// 当前信息为增量,并且上一条信息为完成
if(prevMessage?.event.event_type === ChatEventType.CONVERSATION_MESSAGE_COMPLETED && event.event_type === ChatEventType.CONVERSATION_MESSAGE_DELTA){
return true
}else{
return false;
}
}
handle(event: any, prevMessageList: any[], _opts: any): any[] {
return [...prevMessageList,this.createMessage(event.data.content,event.data.role,event)]
}
}
class AppendMessageEventHandle extends BaseMessageHandler{
canHandle(event: any, _opts: any, prevMessageList?: any[]): boolean {
if(typeof prevMessageList === 'undefined' || prevMessageList.length === 0){
return false;
}
let prevMessage = prevMessageList[prevMessageList.length - 1];
if(prevMessage.event?.event_type === ChatEventType.CONVERSATION_MESSAGE_DELTA && event.event_type === ChatEventType.CONVERSATION_MESSAGE_DELTA){
return true
}else{
return false;
}
}
handle(event: any, prevMessageList: any[], _opts: any): any[] {
let prevMessage = prevMessageList[prevMessageList.length - 1];
return [...prevMessageList.slice(0,prevMessageList.length-1),{...prevMessage,content:prevMessage.content + event.data.content}]
}
}
class BottomMessageEventHandle extends BaseMessageHandler{
canHandle(_event: any, _opts: any, _prevMessageList?: any[]): boolean {
return true
}
handle(_event: any, _prevMessageList: any[], _opts: any): any[] {
return _prevMessageList
}
}
class FileFunctionCallEventHandle extends BaseMessageHandler{
canHandle(event: any, _opts: any, _prevMessageList?: any[]): boolean {
if(event.data.type === "function_call"){
let jsonData = JSON.parse(event.data.content)
return jsonData.name==='doc_reader-PDF_reader' && event.event_type === "conversation.message.completed"
}else{
return false
}
}
handle(event: any, prevMessageList: any[], opts: any): any[] {
return [...prevMessageList,this.createMessage('',event.data.role,event,opts)]
}
}
class FileFunctionResponseEventHandle extends BaseMessageHandler{
canHandle(event: any, _opts: any, prevMessageList?: any[]): boolean {
if(typeof prevMessageList === 'undefined'){
return false
}
let prevMessage = prevMessageList[prevMessageList.length - 1];
if( prevMessage?.event.data.type === "function_call" && event.data.type ==="tool_response"){
return true
}else {
return false
}
}
handle(event: any, prevMessageList: any[], opts: any): any[] {
return [...prevMessageList.slice(0,prevMessageList.length-1),this.createMessage('',event.data.role,event,opts)]
}
}
export class MessageHandlerStrategy {
private handlers: MessageHandler[];
constructor() {
this.handlers = [
new ConversationHelloHandler(),
new IgnoredEventHandler(),
new UserMessageEventHandle(),
new NormalMessageEventHandle(),
new AppendMessageEventHandle(),
new FileFunctionCallEventHandle(),
new FileFunctionResponseEventHandle(),
new BottomMessageEventHandle()
];
}
process(event: any, prevMessageList: any[], opts: any): any[] {
for (const handler of this.handlers) {
if (handler.canHandle(event, opts, prevMessageList)) {
return handler.handle(event, prevMessageList, opts);
}
}
return prevMessageList; // 理论上不会执行到这里
}
}