From e16ba1f8d5a342fab4efe3acecc9d9fcd99c20e6 Mon Sep 17 00:00:00 2001 From: admin Date: Tue, 4 Mar 2025 18:51:13 +0800 Subject: [PATCH 1/6] feature for support cancel chat --- cozepy/__init__.py | 4 +++ cozepy/websockets/chat/__init__.py | 36 ++++++++++++++++++++++++ cozepy/websockets/ws.py | 2 ++ examples/websockets_chat.py | 7 +++++ examples/websockets_chat_realtime_gui.py | 9 ++++++ 5 files changed, 58 insertions(+) diff --git a/cozepy/__init__.py b/cozepy/__init__.py index c8e3908..5a16950 100644 --- a/cozepy/__init__.py +++ b/cozepy/__init__.py @@ -125,9 +125,11 @@ ConversationAudioDeltaEvent, ConversationAudioTranscriptCompletedEvent, ConversationChatCompletedEvent, + ConversationChatCanceledEvent, ConversationChatCreatedEvent, ConversationChatRequiresActionEvent, ConversationChatSubmitToolOutputsEvent, + ConversationChatCancelEvent, ConversationMessageDeltaEvent, WebsocketsChatClient, WebsocketsChatEventHandler, @@ -257,12 +259,14 @@ # websockets.chat "ChatUpdateEvent", "ConversationChatSubmitToolOutputsEvent", + "ConversationChatCancelEvent", "ConversationChatCreatedEvent", "ConversationMessageDeltaEvent", "ConversationAudioTranscriptCompletedEvent", "ConversationChatRequiresActionEvent", "ConversationAudioDeltaEvent", "ConversationChatCompletedEvent", + "ConversationChatCanceledEvent", "WebsocketsChatEventHandler", "WebsocketsChatClient", "AsyncWebsocketsChatEventHandler", diff --git a/cozepy/websockets/chat/__init__.py b/cozepy/websockets/chat/__init__.py index 4e0448b..18d0172 100644 --- a/cozepy/websockets/chat/__init__.py +++ b/cozepy/websockets/chat/__init__.py @@ -54,6 +54,10 @@ class Data(BaseModel): data: Data +class ConversationChatCancelEvent(WebsocketsEvent): + event_type: WebsocketsEventType = WebsocketsEventType.CONVERSATION_CHAT_CANCEL + + # resp class ChatCreatedEvent(WebsocketsEvent): event_type: WebsocketsEventType = WebsocketsEventType.CHAT_CREATED @@ -119,6 +123,10 @@ class ConversationChatCompletedEvent(WebsocketsEvent): data: Chat +class ConversationChatCanceledEvent(WebsocketsEvent): + event_type: WebsocketsEventType = WebsocketsEventType.CONVERSATION_CHAT_CANCELED + + class WebsocketsChatEventHandler(WebsocketsBaseEventHandler): def on_chat_created(self, cli: "WebsocketsChatClient", event: ChatCreatedEvent): pass @@ -160,6 +168,9 @@ def on_conversation_audio_completed(self, cli: "WebsocketsChatClient", event: Co def on_conversation_chat_completed(self, cli: "WebsocketsChatClient", event: ConversationChatCompletedEvent): pass + def on_conversation_chat_canceled(self, cli: "WebsocketsChatClient", event: ConversationChatCanceledEvent): + pass + class WebsocketsChatClient(WebsocketsBaseClient): def __init__( @@ -211,6 +222,9 @@ def chat_update(self, data: ChatUpdateEvent.Data) -> None: def conversation_chat_submit_tool_outputs(self, data: ConversationChatSubmitToolOutputsEvent.Data): self._input_queue.put(ConversationChatSubmitToolOutputsEvent.model_validate({"data": data})) + def conversation_chat_cancel(self): + self._input_queue.put(ConversationChatCancelEvent.model_validate({})) + def input_audio_buffer_append(self, data: InputAudioBufferAppendEvent.Data) -> None: self._input_queue.put(InputAudioBufferAppendEvent.model_validate({"data": data})) @@ -313,6 +327,13 @@ def _load_event(self, message: Dict) -> Optional[WebsocketsEvent]: "data": Chat.model_validate(data), } ) + elif event_type == WebsocketsEventType.CONVERSATION_CHAT_CREATED.value: + return ConversationChatCanceledEvent.model_validate( + { + "id": event_id, + "detail": detail, + } + ) else: log_warning("[%s] unknown event, type=%s, logid=%s", self._path, event_type, detail.logid) return None @@ -396,6 +417,11 @@ async def on_conversation_chat_completed( ): pass + async def on_conversation_chat_canceled( + self, cli: "AsyncWebsocketsChatClient", event: ConversationChatCanceledEvent + ): + pass + class AsyncWebsocketsChatClient(AsyncWebsocketsBaseClient): def __init__( @@ -447,6 +473,9 @@ async def chat_update(self, data: ChatUpdateEvent.Data) -> None: async def conversation_chat_submit_tool_outputs(self, data: ConversationChatSubmitToolOutputsEvent.Data) -> None: await self._input_queue.put(ConversationChatSubmitToolOutputsEvent.model_validate({"data": data})) + async def conversation_chat_cancel(self) -> None: + await self._input_queue.put(ConversationChatCancelEvent.model_validate({})) + async def input_audio_buffer_append(self, data: InputAudioBufferAppendEvent.Data) -> None: await self._input_queue.put(InputAudioBufferAppendEvent.model_validate({"data": data})) @@ -549,6 +578,13 @@ def _load_event(self, message: Dict) -> Optional[WebsocketsEvent]: "data": Chat.model_validate(data), } ) + elif event_type == WebsocketsEventType.CONVERSATION_CHAT_CANCELED.value: + return ConversationChatCanceledEvent.model_validate( + { + "id": event_id, + "detail": detail, + } + ) else: log_warning("[%s] unknown event, type=%s, logid=%s", self._path, event_type, detail.logid) return None diff --git a/cozepy/websockets/ws.py b/cozepy/websockets/ws.py index 70e6c28..51e9c1f 100644 --- a/cozepy/websockets/ws.py +++ b/cozepy/websockets/ws.py @@ -91,6 +91,7 @@ class WebsocketsEventType(str, Enum): # INPUT_AUDIO_BUFFER_COMPLETE = "input_audio_buffer.complete" # no audio send, start chat CHAT_UPDATE = "chat.update" # send chat config to server CONVERSATION_CHAT_SUBMIT_TOOL_OUTPUTS = "conversation.chat.submit_tool_outputs" # send tool outputs to server + CONVERSATION_CHAT_CANCEL = "conversation.chat.cancel" # send cancel chat to server # resp CHAT_CREATED = "chat.created" CHAT_UPDATED = "chat.updated" @@ -104,6 +105,7 @@ class WebsocketsEventType(str, Enum): CONVERSATION_AUDIO_DELTA = "conversation.audio.delta" # get agent audio message update CONVERSATION_AUDIO_COMPLETED = "conversation.audio.completed" CONVERSATION_CHAT_COMPLETED = "conversation.chat.completed" # all message received, can close connection + CONVERSATION_CHAT_CANCELED = "conversation.chat.canceled" # chat canceled class WebsocketsEvent(CozeModel, ABC): diff --git a/examples/websockets_chat.py b/examples/websockets_chat.py index d216773..3ffcc92 100644 --- a/examples/websockets_chat.py +++ b/examples/websockets_chat.py @@ -12,9 +12,11 @@ AudioFormat, ConversationAudioDeltaEvent, ConversationChatCompletedEvent, + ConversationChatCanceledEvent, ConversationChatCreatedEvent, ConversationChatRequiresActionEvent, ConversationChatSubmitToolOutputsEvent, + ConversationChatCancelEvent, ConversationMessageDeltaEvent, DeviceOAuthApp, InputAudioBufferAppendEvent, @@ -113,6 +115,11 @@ async def on_conversation_chat_completed( log_info("[examples] Saving audio data to output.wav") write_pcm_to_wav_file(b"".join(self.delta), "output.wav") + async def on_conversation_chat_canceled( + self, cli: "AsyncWebsocketsChatClient", event: ConversationChatCanceledEvent + ): + log_info("[examples] chat canceled") + def wrap_coze_speech_to_iterator(coze: AsyncCoze, text: str): async def iterator(): diff --git a/examples/websockets_chat_realtime_gui.py b/examples/websockets_chat_realtime_gui.py index 0d858d6..1ca5336 100644 --- a/examples/websockets_chat_realtime_gui.py +++ b/examples/websockets_chat_realtime_gui.py @@ -19,6 +19,7 @@ ChatUpdateEvent, ConversationAudioDeltaEvent, ConversationChatCompletedEvent, + ConversationChatCanceledEvent, InputAudio, InputAudioBufferAppendEvent, TokenAuth, @@ -295,6 +296,14 @@ async def on_conversation_chat_completed( except Exception as e: print(f"完成对话错误: {e}") + async def on_conversation_chat_canceled( + self, cli: AsyncWebsocketsChatClient, event: ConversationChatCanceledEvent + ): + try: + print("打断") + except Exception as e: + print(f"对话打断错误: {e}") + kwargs = json.loads(os.getenv("COZE_KWARGS") or "{}") self.chat_client = self.coze.websockets.chat.create( bot_id=os.getenv("COZE_BOT_ID"), From 94a6603fde22dc21cfa1530acce0838db7ab075b Mon Sep 17 00:00:00 2001 From: admin Date: Tue, 4 Mar 2025 18:51:13 +0800 Subject: [PATCH 2/6] feature for support cancel chat --- cozepy/__init__.py | 4 +++ cozepy/websockets/chat/__init__.py | 36 ++++++++++++++++++++++++ cozepy/websockets/ws.py | 2 ++ examples/websockets_chat.py | 7 +++++ examples/websockets_chat_realtime_gui.py | 9 ++++++ 5 files changed, 58 insertions(+) diff --git a/cozepy/__init__.py b/cozepy/__init__.py index c8e3908..096a197 100644 --- a/cozepy/__init__.py +++ b/cozepy/__init__.py @@ -124,6 +124,8 @@ ChatUpdateEvent, ConversationAudioDeltaEvent, ConversationAudioTranscriptCompletedEvent, + ConversationChatCanceledEvent, + ConversationChatCancelEvent, ConversationChatCompletedEvent, ConversationChatCreatedEvent, ConversationChatRequiresActionEvent, @@ -257,12 +259,14 @@ # websockets.chat "ChatUpdateEvent", "ConversationChatSubmitToolOutputsEvent", + "ConversationChatCancelEvent", "ConversationChatCreatedEvent", "ConversationMessageDeltaEvent", "ConversationAudioTranscriptCompletedEvent", "ConversationChatRequiresActionEvent", "ConversationAudioDeltaEvent", "ConversationChatCompletedEvent", + "ConversationChatCanceledEvent", "WebsocketsChatEventHandler", "WebsocketsChatClient", "AsyncWebsocketsChatEventHandler", diff --git a/cozepy/websockets/chat/__init__.py b/cozepy/websockets/chat/__init__.py index 4e0448b..18d0172 100644 --- a/cozepy/websockets/chat/__init__.py +++ b/cozepy/websockets/chat/__init__.py @@ -54,6 +54,10 @@ class Data(BaseModel): data: Data +class ConversationChatCancelEvent(WebsocketsEvent): + event_type: WebsocketsEventType = WebsocketsEventType.CONVERSATION_CHAT_CANCEL + + # resp class ChatCreatedEvent(WebsocketsEvent): event_type: WebsocketsEventType = WebsocketsEventType.CHAT_CREATED @@ -119,6 +123,10 @@ class ConversationChatCompletedEvent(WebsocketsEvent): data: Chat +class ConversationChatCanceledEvent(WebsocketsEvent): + event_type: WebsocketsEventType = WebsocketsEventType.CONVERSATION_CHAT_CANCELED + + class WebsocketsChatEventHandler(WebsocketsBaseEventHandler): def on_chat_created(self, cli: "WebsocketsChatClient", event: ChatCreatedEvent): pass @@ -160,6 +168,9 @@ def on_conversation_audio_completed(self, cli: "WebsocketsChatClient", event: Co def on_conversation_chat_completed(self, cli: "WebsocketsChatClient", event: ConversationChatCompletedEvent): pass + def on_conversation_chat_canceled(self, cli: "WebsocketsChatClient", event: ConversationChatCanceledEvent): + pass + class WebsocketsChatClient(WebsocketsBaseClient): def __init__( @@ -211,6 +222,9 @@ def chat_update(self, data: ChatUpdateEvent.Data) -> None: def conversation_chat_submit_tool_outputs(self, data: ConversationChatSubmitToolOutputsEvent.Data): self._input_queue.put(ConversationChatSubmitToolOutputsEvent.model_validate({"data": data})) + def conversation_chat_cancel(self): + self._input_queue.put(ConversationChatCancelEvent.model_validate({})) + def input_audio_buffer_append(self, data: InputAudioBufferAppendEvent.Data) -> None: self._input_queue.put(InputAudioBufferAppendEvent.model_validate({"data": data})) @@ -313,6 +327,13 @@ def _load_event(self, message: Dict) -> Optional[WebsocketsEvent]: "data": Chat.model_validate(data), } ) + elif event_type == WebsocketsEventType.CONVERSATION_CHAT_CREATED.value: + return ConversationChatCanceledEvent.model_validate( + { + "id": event_id, + "detail": detail, + } + ) else: log_warning("[%s] unknown event, type=%s, logid=%s", self._path, event_type, detail.logid) return None @@ -396,6 +417,11 @@ async def on_conversation_chat_completed( ): pass + async def on_conversation_chat_canceled( + self, cli: "AsyncWebsocketsChatClient", event: ConversationChatCanceledEvent + ): + pass + class AsyncWebsocketsChatClient(AsyncWebsocketsBaseClient): def __init__( @@ -447,6 +473,9 @@ async def chat_update(self, data: ChatUpdateEvent.Data) -> None: async def conversation_chat_submit_tool_outputs(self, data: ConversationChatSubmitToolOutputsEvent.Data) -> None: await self._input_queue.put(ConversationChatSubmitToolOutputsEvent.model_validate({"data": data})) + async def conversation_chat_cancel(self) -> None: + await self._input_queue.put(ConversationChatCancelEvent.model_validate({})) + async def input_audio_buffer_append(self, data: InputAudioBufferAppendEvent.Data) -> None: await self._input_queue.put(InputAudioBufferAppendEvent.model_validate({"data": data})) @@ -549,6 +578,13 @@ def _load_event(self, message: Dict) -> Optional[WebsocketsEvent]: "data": Chat.model_validate(data), } ) + elif event_type == WebsocketsEventType.CONVERSATION_CHAT_CANCELED.value: + return ConversationChatCanceledEvent.model_validate( + { + "id": event_id, + "detail": detail, + } + ) else: log_warning("[%s] unknown event, type=%s, logid=%s", self._path, event_type, detail.logid) return None diff --git a/cozepy/websockets/ws.py b/cozepy/websockets/ws.py index 70e6c28..51e9c1f 100644 --- a/cozepy/websockets/ws.py +++ b/cozepy/websockets/ws.py @@ -91,6 +91,7 @@ class WebsocketsEventType(str, Enum): # INPUT_AUDIO_BUFFER_COMPLETE = "input_audio_buffer.complete" # no audio send, start chat CHAT_UPDATE = "chat.update" # send chat config to server CONVERSATION_CHAT_SUBMIT_TOOL_OUTPUTS = "conversation.chat.submit_tool_outputs" # send tool outputs to server + CONVERSATION_CHAT_CANCEL = "conversation.chat.cancel" # send cancel chat to server # resp CHAT_CREATED = "chat.created" CHAT_UPDATED = "chat.updated" @@ -104,6 +105,7 @@ class WebsocketsEventType(str, Enum): CONVERSATION_AUDIO_DELTA = "conversation.audio.delta" # get agent audio message update CONVERSATION_AUDIO_COMPLETED = "conversation.audio.completed" CONVERSATION_CHAT_COMPLETED = "conversation.chat.completed" # all message received, can close connection + CONVERSATION_CHAT_CANCELED = "conversation.chat.canceled" # chat canceled class WebsocketsEvent(CozeModel, ABC): diff --git a/examples/websockets_chat.py b/examples/websockets_chat.py index d216773..3ffcc92 100644 --- a/examples/websockets_chat.py +++ b/examples/websockets_chat.py @@ -12,9 +12,11 @@ AudioFormat, ConversationAudioDeltaEvent, ConversationChatCompletedEvent, + ConversationChatCanceledEvent, ConversationChatCreatedEvent, ConversationChatRequiresActionEvent, ConversationChatSubmitToolOutputsEvent, + ConversationChatCancelEvent, ConversationMessageDeltaEvent, DeviceOAuthApp, InputAudioBufferAppendEvent, @@ -113,6 +115,11 @@ async def on_conversation_chat_completed( log_info("[examples] Saving audio data to output.wav") write_pcm_to_wav_file(b"".join(self.delta), "output.wav") + async def on_conversation_chat_canceled( + self, cli: "AsyncWebsocketsChatClient", event: ConversationChatCanceledEvent + ): + log_info("[examples] chat canceled") + def wrap_coze_speech_to_iterator(coze: AsyncCoze, text: str): async def iterator(): diff --git a/examples/websockets_chat_realtime_gui.py b/examples/websockets_chat_realtime_gui.py index 0d858d6..1ca5336 100644 --- a/examples/websockets_chat_realtime_gui.py +++ b/examples/websockets_chat_realtime_gui.py @@ -19,6 +19,7 @@ ChatUpdateEvent, ConversationAudioDeltaEvent, ConversationChatCompletedEvent, + ConversationChatCanceledEvent, InputAudio, InputAudioBufferAppendEvent, TokenAuth, @@ -295,6 +296,14 @@ async def on_conversation_chat_completed( except Exception as e: print(f"完成对话错误: {e}") + async def on_conversation_chat_canceled( + self, cli: AsyncWebsocketsChatClient, event: ConversationChatCanceledEvent + ): + try: + print("打断") + except Exception as e: + print(f"对话打断错误: {e}") + kwargs = json.loads(os.getenv("COZE_KWARGS") or "{}") self.chat_client = self.coze.websockets.chat.create( bot_id=os.getenv("COZE_BOT_ID"), From 5737e38bfd4a953e309b3b6173c599ea7161dd19 Mon Sep 17 00:00:00 2001 From: admin Date: Tue, 4 Mar 2025 20:41:57 +0800 Subject: [PATCH 3/6] feature for support cancel chat with git hook again --- cozepy/__init__.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/cozepy/__init__.py b/cozepy/__init__.py index 87dc5a0..096a197 100644 --- a/cozepy/__init__.py +++ b/cozepy/__init__.py @@ -127,11 +127,9 @@ ConversationChatCanceledEvent, ConversationChatCancelEvent, ConversationChatCompletedEvent, - ConversationChatCanceledEvent, ConversationChatCreatedEvent, ConversationChatRequiresActionEvent, ConversationChatSubmitToolOutputsEvent, - ConversationChatCancelEvent, ConversationMessageDeltaEvent, WebsocketsChatClient, WebsocketsChatEventHandler, From 152ff4517869851b786df2a72faf7c7840ddcc91 Mon Sep 17 00:00:00 2001 From: chyroc Date: Tue, 4 Mar 2025 21:19:57 +0800 Subject: [PATCH 4/6] Update cozepy/websockets/chat/__init__.py --- cozepy/websockets/chat/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/cozepy/websockets/chat/__init__.py b/cozepy/websockets/chat/__init__.py index 18d0172..e1db04a 100644 --- a/cozepy/websockets/chat/__init__.py +++ b/cozepy/websockets/chat/__init__.py @@ -54,6 +54,7 @@ class Data(BaseModel): data: Data +# req class ConversationChatCancelEvent(WebsocketsEvent): event_type: WebsocketsEventType = WebsocketsEventType.CONVERSATION_CHAT_CANCEL From 531bf6e148c8f95fe1e93afdc80f7286d14ea2f0 Mon Sep 17 00:00:00 2001 From: chyroc Date: Tue, 4 Mar 2025 21:20:17 +0800 Subject: [PATCH 5/6] Update cozepy/websockets/chat/__init__.py --- cozepy/websockets/chat/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/cozepy/websockets/chat/__init__.py b/cozepy/websockets/chat/__init__.py index e1db04a..31bb420 100644 --- a/cozepy/websockets/chat/__init__.py +++ b/cozepy/websockets/chat/__init__.py @@ -124,6 +124,7 @@ class ConversationChatCompletedEvent(WebsocketsEvent): data: Chat +# resp class ConversationChatCanceledEvent(WebsocketsEvent): event_type: WebsocketsEventType = WebsocketsEventType.CONVERSATION_CHAT_CANCELED From 0e337c39a09326b6583ae097ba9560ad9bb20dda Mon Sep 17 00:00:00 2001 From: bing Date: Tue, 4 Mar 2025 23:29:19 +0800 Subject: [PATCH 6/6] fix mapping bug --- cozepy/websockets/chat/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cozepy/websockets/chat/__init__.py b/cozepy/websockets/chat/__init__.py index 31bb420..a76508a 100644 --- a/cozepy/websockets/chat/__init__.py +++ b/cozepy/websockets/chat/__init__.py @@ -200,6 +200,7 @@ def __init__( WebsocketsEventType.CONVERSATION_AUDIO_DELTA: on_event.on_conversation_audio_delta, WebsocketsEventType.CONVERSATION_AUDIO_COMPLETED: on_event.on_conversation_audio_completed, WebsocketsEventType.CONVERSATION_CHAT_COMPLETED: on_event.on_conversation_chat_completed, + WebsocketsEventType.CONVERSATION_CHAT_CANCELED: on_event.on_conversation_chat_canceled, } ) super().__init__( @@ -329,7 +330,7 @@ def _load_event(self, message: Dict) -> Optional[WebsocketsEvent]: "data": Chat.model_validate(data), } ) - elif event_type == WebsocketsEventType.CONVERSATION_CHAT_CREATED.value: + elif event_type == WebsocketsEventType.CONVERSATION_CHAT_CANCELED.value: return ConversationChatCanceledEvent.model_validate( { "id": event_id, @@ -451,6 +452,7 @@ def __init__( WebsocketsEventType.CONVERSATION_AUDIO_DELTA: on_event.on_conversation_audio_delta, WebsocketsEventType.CONVERSATION_AUDIO_COMPLETED: on_event.on_conversation_audio_completed, WebsocketsEventType.CONVERSATION_CHAT_COMPLETED: on_event.on_conversation_chat_completed, + WebsocketsEventType.CONVERSATION_CHAT_CANCELED: on_event.on_conversation_chat_canceled, } ) super().__init__(