Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Resolve WebSocket issues and add real-time call example #160

Merged
merged 1 commit into from
Jan 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@ dist/
scripts/
.cache/
output.wav
response.wav
temp_response.pcm
2 changes: 1 addition & 1 deletion cozepy/chat/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@
def get_audio(self) -> Optional[bytes]:
if self.content_type == MessageContentType.AUDIO:
return base64.b64decode(self.content)
return None
return b""

Check warning on line 194 in cozepy/chat/__init__.py

View check run for this annotation

Codecov / codecov/patch

cozepy/chat/__init__.py#L194

Added line #L194 was not covered by tests


class ChatStatus(str, Enum):
Expand Down
4 changes: 2 additions & 2 deletions cozepy/websockets/audio/speech/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@
self._input_queue.put(event)

def _load_event(self, message: Dict) -> Optional[WebsocketsEvent]:
event_id = message.get("event_id") or ""
event_id = message.get("id") or ""

Check warning on line 124 in cozepy/websockets/audio/speech/__init__.py

View check run for this annotation

Codecov / codecov/patch

cozepy/websockets/audio/speech/__init__.py#L124

Added line #L124 was not covered by tests
detail = WebsocketsEvent.Detail.model_validate(message.get("detail") or {})
event_type = message.get("event_type") or ""
data = message.get("data") or {}
Expand Down Expand Up @@ -235,7 +235,7 @@
await self._input_queue.put(SpeechUpdateEvent.model_validate({"data": data}))

def _load_event(self, message: Dict) -> Optional[WebsocketsEvent]:
event_id = message.get("event_id") or ""
event_id = message.get("id") or ""

Check warning on line 238 in cozepy/websockets/audio/speech/__init__.py

View check run for this annotation

Codecov / codecov/patch

cozepy/websockets/audio/speech/__init__.py#L238

Added line #L238 was not covered by tests
detail = WebsocketsEvent.Detail.model_validate(message.get("detail") or {})
event_type = message.get("event_type") or ""
data = message.get("data") or {}
Expand Down
14 changes: 12 additions & 2 deletions cozepy/websockets/audio/transcriptions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,16 @@
event_type: WebsocketsEventType = WebsocketsEventType.INPUT_AUDIO_BUFFER_APPEND
data: Data

def _dump_without_delta(self):
return {

Check warning on line 34 in cozepy/websockets/audio/transcriptions/__init__.py

View check run for this annotation

Codecov / codecov/patch

cozepy/websockets/audio/transcriptions/__init__.py#L34

Added line #L34 was not covered by tests
"id": self.id,
"type": self.event_type.value,
"detail": self.detail,
"data": {
"delta_length": len(self.data.delta) if self.data and self.data.delta else 0,
},
}


# req
class InputAudioBufferCompleteEvent(WebsocketsEvent):
Expand Down Expand Up @@ -127,7 +137,7 @@
self._input_queue.put(InputAudioBufferCompleteEvent.model_validate({}))

def _load_event(self, message: Dict) -> Optional[WebsocketsEvent]:
event_id = message.get("event_id") or ""
event_id = message.get("id") or ""

Check warning on line 140 in cozepy/websockets/audio/transcriptions/__init__.py

View check run for this annotation

Codecov / codecov/patch

cozepy/websockets/audio/transcriptions/__init__.py#L140

Added line #L140 was not covered by tests
event_type = message.get("event_type") or ""
detail = WebsocketsEvent.Detail.model_validate(message.get("detail") or {})
data = message.get("data") or {}
Expand Down Expand Up @@ -250,7 +260,7 @@
await self._input_queue.put(InputAudioBufferCompleteEvent.model_validate({}))

def _load_event(self, message: Dict) -> Optional[WebsocketsEvent]:
event_id = message.get("event_id") or ""
event_id = message.get("id") or ""

Check warning on line 263 in cozepy/websockets/audio/transcriptions/__init__.py

View check run for this annotation

Codecov / codecov/patch

cozepy/websockets/audio/transcriptions/__init__.py#L263

Added line #L263 was not covered by tests
event_type = message.get("event_type") or ""
detail = WebsocketsEvent.Detail.model_validate(message.get("detail") or {})
data = message.get("data") or {}
Expand Down
34 changes: 32 additions & 2 deletions cozepy/websockets/chat/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@
event_type: WebsocketsEventType = WebsocketsEventType.CHAT_CREATED


# resp
class ChatUpdatedEvent(WebsocketsEvent):
event_type: WebsocketsEventType = WebsocketsEventType.CHAT_UPDATED
data: ChatUpdateEvent.Data


# resp
class ConversationChatCreatedEvent(WebsocketsEvent):
event_type: WebsocketsEventType = WebsocketsEventType.CONVERSATION_CHAT_CREATED
Expand Down Expand Up @@ -107,6 +113,9 @@
def on_chat_created(self, cli: "WebsocketsChatClient", event: ChatCreatedEvent):
pass

def on_chat_updated(self, cli: "WebsocketsChatClient", event: ChatUpdatedEvent):
pass

Check warning on line 117 in cozepy/websockets/chat/__init__.py

View check run for this annotation

Codecov / codecov/patch

cozepy/websockets/chat/__init__.py#L117

Added line #L117 was not covered by tests

def on_input_audio_buffer_completed(self, cli: "WebsocketsChatClient", event: InputAudioBufferCompletedEvent):
pass

Expand Down Expand Up @@ -151,6 +160,7 @@
on_event = on_event.to_dict(
{
WebsocketsEventType.CHAT_CREATED: on_event.on_chat_created,
WebsocketsEventType.CHAT_UPDATED: on_event.on_chat_updated,
WebsocketsEventType.INPUT_AUDIO_BUFFER_COMPLETED: on_event.on_input_audio_buffer_completed,
WebsocketsEventType.CONVERSATION_CHAT_CREATED: on_event.on_conversation_chat_created,
WebsocketsEventType.CONVERSATION_CHAT_IN_PROGRESS: on_event.on_conversation_chat_in_progress,
Expand Down Expand Up @@ -188,7 +198,7 @@
self._input_queue.put(InputAudioBufferCompleteEvent.model_validate({}))

def _load_event(self, message: Dict) -> Optional[WebsocketsEvent]:
event_id = message.get("event_id") or ""
event_id = message.get("id") or ""

Check warning on line 201 in cozepy/websockets/chat/__init__.py

View check run for this annotation

Codecov / codecov/patch

cozepy/websockets/chat/__init__.py#L201

Added line #L201 was not covered by tests
detail = WebsocketsEvent.Detail.model_validate(message.get("detail") or {})
event_type = message.get("event_type") or ""
data = message.get("data") or {}
Expand All @@ -199,6 +209,14 @@
"detail": detail,
}
)
elif event_type == WebsocketsEventType.CHAT_UPDATED.value:
return ChatUpdatedEvent.model_validate(

Check warning on line 213 in cozepy/websockets/chat/__init__.py

View check run for this annotation

Codecov / codecov/patch

cozepy/websockets/chat/__init__.py#L212-L213

Added lines #L212 - L213 were not covered by tests
{
"id": event_id,
"detail": detail,
"data": ChatUpdateEvent.Data.model_validate(data),
}
)
elif event_type == WebsocketsEventType.INPUT_AUDIO_BUFFER_COMPLETED.value:
return InputAudioBufferCompletedEvent.model_validate(
{
Expand Down Expand Up @@ -299,6 +317,9 @@
async def on_chat_created(self, cli: "AsyncWebsocketsChatClient", event: ChatCreatedEvent):
pass

async def on_chat_updated(self, cli: "AsyncWebsocketsChatClient", event: ChatUpdatedEvent):
pass

Check warning on line 321 in cozepy/websockets/chat/__init__.py

View check run for this annotation

Codecov / codecov/patch

cozepy/websockets/chat/__init__.py#L321

Added line #L321 was not covered by tests

async def on_input_audio_buffer_completed(
self, cli: "AsyncWebsocketsChatClient", event: InputAudioBufferCompletedEvent
):
Expand Down Expand Up @@ -355,6 +376,7 @@
on_event = on_event.to_dict(
{
WebsocketsEventType.CHAT_CREATED: on_event.on_chat_created,
WebsocketsEventType.CHAT_UPDATED: on_event.on_chat_updated,
WebsocketsEventType.INPUT_AUDIO_BUFFER_COMPLETED: on_event.on_input_audio_buffer_completed,
WebsocketsEventType.CONVERSATION_CHAT_CREATED: on_event.on_conversation_chat_created,
WebsocketsEventType.CONVERSATION_CHAT_IN_PROGRESS: on_event.on_conversation_chat_in_progress,
Expand Down Expand Up @@ -392,7 +414,7 @@
await self._input_queue.put(InputAudioBufferCompleteEvent.model_validate({}))

def _load_event(self, message: Dict) -> Optional[WebsocketsEvent]:
event_id = message.get("event_id") or ""
event_id = message.get("id") or ""

Check warning on line 417 in cozepy/websockets/chat/__init__.py

View check run for this annotation

Codecov / codecov/patch

cozepy/websockets/chat/__init__.py#L417

Added line #L417 was not covered by tests
detail = WebsocketsEvent.Detail.model_validate(message.get("detail") or {})
event_type = message.get("event_type") or ""
data = message.get("data") or {}
Expand All @@ -403,6 +425,14 @@
"detail": detail,
}
)
elif event_type == WebsocketsEventType.CHAT_UPDATED.value:
return ChatUpdatedEvent.model_validate(

Check warning on line 429 in cozepy/websockets/chat/__init__.py

View check run for this annotation

Codecov / codecov/patch

cozepy/websockets/chat/__init__.py#L428-L429

Added lines #L428 - L429 were not covered by tests
{
"id": event_id,
"detail": detail,
"data": ChatUpdateEvent.Data.model_validate(data),
}
)
elif event_type == WebsocketsEventType.INPUT_AUDIO_BUFFER_COMPLETED.value:
return InputAudioBufferCompletedEvent.model_validate(
{
Expand Down
19 changes: 14 additions & 5 deletions cozepy/websockets/ws.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
CONVERSATION_CHAT_SUBMIT_TOOL_OUTPUTS = "conversation.chat.submit_tool_outputs" # send tool outputs to server
# resp
CHAT_CREATED = "chat.created"
CHAT_UPDATED = "chat.updated"
# INPUT_AUDIO_BUFFER_COMPLETED = "input_audio_buffer.completed" # received `input_audio_buffer.complete` event
CONVERSATION_CHAT_CREATED = "conversation.chat.created" # audio ast completed, chat started
CONVERSATION_CHAT_IN_PROGRESS = "conversation.chat.in_progress"
Expand All @@ -109,7 +110,7 @@
logid: Optional[str] = None

event_type: WebsocketsEventType
event_id: Optional[str] = None
id: Optional[str] = None
detail: Optional[Detail] = None


Expand All @@ -118,7 +119,7 @@
data: CozeAPIError


class InputAudio(CozeModel):
class InputAudio(BaseModel):
format: Optional[str]
codec: Optional[str]
sample_rate: Optional[int]
Expand Down Expand Up @@ -266,7 +267,7 @@
self._handle_error(e)

def _load_all_event(self, message: Dict) -> Optional[WebsocketsEvent]:
event_id = message.get("event_id") or ""
event_id = message.get("id") or ""

Check warning on line 270 in cozepy/websockets/ws.py

View check run for this annotation

Codecov / codecov/patch

cozepy/websockets/ws.py#L270

Added line #L270 was not covered by tests
event_type = message.get("event_type") or ""
detail = WebsocketsEvent.Detail.model_validate(message.get("detail") or {})
data = message.get("data") or {}
Expand Down Expand Up @@ -466,7 +467,7 @@
await self._handle_error(e)

def _load_all_event(self, message: Dict) -> Optional[WebsocketsEvent]:
event_id = message.get("event_id") or ""
event_id = message.get("id") or ""

Check warning on line 470 in cozepy/websockets/ws.py

View check run for this annotation

Codecov / codecov/patch

cozepy/websockets/ws.py#L470

Added line #L470 was not covered by tests
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Add test coverage for both sync and async _load_all_event methods

The websocket event parsing methods are not covered by tests. These methods handle critical functionality:

  • Core message field parsing with fallbacks
  • Event detail validation
  • Error event handling
🔗 Analysis chain

Add test coverage for async _load_all_event method.

The async version of the method is not covered by tests according to code coverage report.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check existing test coverage for async _load_all_event method.
# Test: Search for test files and test cases related to async _load_all_event.

echo "Searching for async test files..."
fd -e py test_.*async.*\.py

echo "Searching for async _load_all_event test cases..."
rg -A 5 'async.*_load_all_event' --type python

Length of output: 296


Script:

#!/bin/bash
# Search for test files and _load_all_event references
echo "=== Test files ==="
fd -e py test_

echo -e "\n=== _load_all_event references in tests ==="
rg "_load_all_event" -t py tests/

echo -e "\n=== Method definition ==="
rg -B2 -A5 "def.*_load_all_event" -t py

Length of output: 2108

🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 470-470: cozepy/websockets/ws.py#L470
Added line #L470 was not covered by tests

event_type = message.get("event_type") or ""
detail = WebsocketsEvent.Detail.model_validate(message.get("detail") or {})
data = message.get("data") or {}
Expand Down Expand Up @@ -553,7 +554,15 @@
async def _send_event(self, event: Optional[WebsocketsEvent] = None) -> None:
if not event or not self._ws:
return
log_debug("[%s] send event, type=%s", self._path, event.event_type.value)
if event.event_type == WebsocketsEventType.INPUT_AUDIO_BUFFER_APPEND:
log_debug(

Check warning on line 558 in cozepy/websockets/ws.py

View check run for this annotation

Codecov / codecov/patch

cozepy/websockets/ws.py#L557-L558

Added lines #L557 - L558 were not covered by tests
"[%s] send event, type=%s, event=%s",
self._path,
event.event_type.value,
json.dumps(event._dump_without_delta()), # type: ignore
)
else:
log_debug("[%s] send event, type=%s, event=%s", self._path, event.event_type.value, event.model_dump_json())

Check warning on line 565 in cozepy/websockets/ws.py

View check run for this annotation

Codecov / codecov/patch

cozepy/websockets/ws.py#L565

Added line #L565 was not covered by tests
await self._ws.send(event.model_dump_json())


Expand Down
Loading
Loading