ojin-client 0.1.7.dev3__tar.gz → 0.1.7.dev5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ojin-client
3
- Version: 0.1.7.dev3
3
+ Version: 0.1.7.dev5
4
4
  Summary: Ojin platform services
5
5
  Author: Journee
6
6
  License: Apache-2.0
@@ -86,7 +86,11 @@ class OjinPersonaClient(IOjinPersonaClient):
86
86
  self._running = False
87
87
  self._receive_task: Optional[asyncio.Task] = None
88
88
  self._inference_server_ready: bool = False
89
- self._cancelled = False
89
+ self._cancelled: bool = False
90
+ self._cancelled_interaction_id: str | None = None
91
+ self.active_interaction_id: str | None = None
92
+ self._split_audio_task: Optional[asyncio.Task] = None
93
+ self._audio_queue: asyncio.Queue[bytes] = asyncio.Queue()
90
94
 
91
95
  async def connect(self) -> None:
92
96
  """Establish WebSocket connection and authenticate with the service."""
@@ -108,6 +112,7 @@ class OjinPersonaClient(IOjinPersonaClient):
108
112
  )
109
113
  self._running = True
110
114
  self._receive_task = asyncio.create_task(self._receive_messages())
115
+ self._split_audio_task = asyncio.create_task(self._split_audio())
111
116
  logger.info("Successfully connected to OJIN Persona service")
112
117
  return
113
118
  except WebSocketException as e:
@@ -131,12 +136,7 @@ class OjinPersonaClient(IOjinPersonaClient):
131
136
  pass
132
137
 
133
138
  self._running = False
134
-
135
- if self._receive_task:
136
- self._receive_task.cancel()
137
- with contextlib.suppress(asyncio.CancelledError):
138
- await self._receive_task
139
- self._receive_task = None
139
+ self.active_interaction_id = None
140
140
 
141
141
  if self._ws:
142
142
  try:
@@ -144,6 +144,19 @@ class OjinPersonaClient(IOjinPersonaClient):
144
144
  except Exception as e:
145
145
  logger.error("Error closing WebSocket connection: %s", e)
146
146
  self._ws = None
147
+
148
+ if self._split_audio_task:
149
+ self._split_audio_task.cancel()
150
+ with contextlib.suppress(asyncio.CancelledError):
151
+ await self._split_audio_task
152
+ self._split_audio_task = None
153
+
154
+ if self._receive_task:
155
+ self._receive_task.cancel()
156
+ with contextlib.suppress(asyncio.CancelledError):
157
+ await self._receive_task
158
+ self._receive_task = None
159
+
147
160
 
148
161
  logger.info("Disconnected from OJIN Persona service")
149
162
 
@@ -187,16 +200,29 @@ class OjinPersonaClient(IOjinPersonaClient):
187
200
  is_final_response=interaction_server_response.payload.is_final_response,
188
201
  )
189
202
  logger.debug("Received InteractionResponse for id %s", interaction_response.interaction_id)
203
+
204
+ if interaction_response.interaction_id == self._cancelled_interaction_id:
205
+ logger.warning("Message From old interaction")
206
+ return
190
207
  await self._message_queue.put(interaction_response)
191
208
  return
192
209
  except Exception as e:
193
210
  logger.error(e)
194
211
  raise
195
212
 
196
- if not isinstance(message, str):
197
- raise Exception("not a know Format")
198
-
213
+ # NOTE: str type
214
+ # TODO: clean when the proxy add structured logs for this error
199
215
  if message == "No backend servers available. Please try again later.":
216
+ await self._message_queue.put(
217
+ ErrorResponseMessage(
218
+ payload=ErrorResponse(
219
+ interaction_id=None,
220
+ code="NO_BACKEND_SERVER_AVAILABLE",
221
+ message=message,
222
+ timestamp=int(time.monotonic() * 1000),
223
+ )
224
+ )
225
+ )
200
226
  raise Exception(message)
201
227
 
202
228
  data = json.loads(message)
@@ -227,10 +253,11 @@ class OjinPersonaClient(IOjinPersonaClient):
227
253
  if isinstance(msg, OjinPersonaSessionReadyMessage):
228
254
  self._inference_server_ready = True
229
255
 
256
+ await self._message_queue.put(msg)
257
+
230
258
  if isinstance(msg, ErrorResponseMessage):
231
259
  raise RuntimeError(f"Error in Inference Server received: {msg}")
232
260
 
233
- await self._message_queue.put(msg)
234
261
  logger.info("Received message: %s", msg)
235
262
  else:
236
263
  logger.warning("Unknown message type: %s", msg_type)
@@ -258,6 +285,7 @@ class OjinPersonaClient(IOjinPersonaClient):
258
285
  if isinstance(message, OjinPersonaCancelInteractionMessage):
259
286
  logger.info("Interrupt")
260
287
 
288
+ self._cancelled = True
261
289
  cancel_input = CancelInteractionMessage(
262
290
  payload=message.to_proxy_message()
263
291
  )
@@ -274,14 +302,16 @@ class OjinPersonaClient(IOjinPersonaClient):
274
302
 
275
303
 
276
304
  self._cancelled = False
305
+ self._cancelled_interaction_id = message.interaction_id
277
306
 
278
307
  return
279
308
 
280
309
  if isinstance(message, StartInteractionMessage):
281
- interaction_id = uuid.uuid4()
310
+ interaction_id = str(uuid.uuid4())
311
+ self.active_interaction_id = interaction_id
282
312
  logger.info("Generate UUID %s", interaction_id)
283
313
  interaction_response = StartInteractionResponseMessage(
284
- interaction_id=str(interaction_id)
314
+ interaction_id=interaction_id
285
315
  )
286
316
  while not self._message_queue.empty():
287
317
  await self._message_queue.get()
@@ -291,14 +321,71 @@ class OjinPersonaClient(IOjinPersonaClient):
291
321
  if isinstance(message, OjinPersonaInteractionInputMessage):
292
322
  logger.info("InteractionMessage")
293
323
  logger.info(f"Message sent {message.interaction_id}")
324
+ if message.interaction_id != self.active_interaction_id:
325
+ return
326
+
294
327
  if not message.audio_int16_bytes:
295
328
  raise ValueError("Audio cannot be empty")
296
329
 
330
+
331
+ await self._audio_queue.put(message.audio_int16_bytes)
297
332
  # Split audio bytes into chunks of max 3200 samples
333
+ # max_chunk_size = 3200 * 2
334
+ # audio_chunks = [
335
+ # message.audio_int16_bytes[i : i + max_chunk_size]
336
+ # for i in range(0, len(message.audio_int16_bytes), max_chunk_size)
337
+ # ]
338
+ # logger.info(
339
+ # "Split audio into %d chunks of max %d bytes",
340
+ # len(audio_chunks), max_chunk_size
341
+ # )
342
+
343
+ # for i, chunk in enumerate(audio_chunks):
344
+ # is_last = i == len(audio_chunks) - 1 and message.is_last_input
345
+ #
346
+ # interaction_input = InteractionInput(
347
+ # interaction_id=message.interaction_id,
348
+ # is_final_input=is_last,
349
+ # payload_type="audio",
350
+ # payload=chunk,
351
+ # timestamp=int(time.monotonic() * 1000),
352
+ # params=message.params if i == 0 else None,
353
+ # )
354
+ # proxy_message = InteractionInputMessage(payload=interaction_input)
355
+ # await self._ws.send(proxy_message.to_bytes())
356
+ return
357
+
358
+ logger.error("The message %s is Unknown", message)
359
+ # TODO: should we close the connection here?
360
+ await self.close()
361
+ error = ErrorResponseMessage(
362
+ payload=ErrorResponse(
363
+ interaction_id=message.interaction_id,
364
+ code="UNKNOWN",
365
+ message="The message is Unknown",
366
+ timestamp=int(time.monotonic() * 1000),
367
+ )
368
+ )
369
+ raise Exception(error)
370
+
371
+ async def _split_audio(self) -> None:
372
+ while True:
373
+ message_audio: bytes | None = None
374
+ if self._cancelled:
375
+ pass
376
+
377
+ try:
378
+ message_audio = self._audio_queue.get_nowait()
379
+ except asyncio.QueueEmpty:
380
+ pass
381
+
382
+ if message_audio is None:
383
+ pass
384
+
298
385
  max_chunk_size = 3200 * 2
299
386
  audio_chunks = [
300
- message.audio_int16_bytes[i : i + max_chunk_size]
301
- for i in range(0, len(message.audio_int16_bytes), max_chunk_size)
387
+ message_audio[i : i + max_chunk_size]
388
+ for i in range(0, len(message_audio), max_chunk_size)
302
389
  ]
303
390
  logger.info(
304
391
  "Split audio into %d chunks of max %d bytes",
@@ -317,19 +404,9 @@ class OjinPersonaClient(IOjinPersonaClient):
317
404
  params=message.params if i == 0 else None,
318
405
  )
319
406
  proxy_message = InteractionInputMessage(payload=interaction_input)
407
+
320
408
  await self._ws.send(proxy_message.to_bytes())
321
- return
322
409
 
323
- logger.error("The message %s is Unknown", message)
324
- await self.close()
325
- error = ErrorResponseMessage(
326
- payload=ErrorResponse(
327
- code="UNKNOWN",
328
- message="The message is Unknown",
329
- timestamp=int(time.monotonic() * 1000),
330
- )
331
- )
332
- raise Exception(error)
333
410
 
334
411
  async def receive_message(self) -> BaseModel | None:
335
412
  """Receive the next message from the OJIN Persona service.
@@ -168,26 +168,6 @@ class OjinPersonaInteractionInputMessage(OjinPersonaMessage):
168
168
  )
169
169
 
170
170
 
171
- class ErrorResponsePayload(BaseModel):
172
- """Response message informing the client there was an error.
173
-
174
- contains details about the error
175
- """
176
-
177
- error: str
178
- code: Optional[str] = None
179
- timestamp: Optional[int] = None
180
-
181
-
182
- class ErrorResponseMessage(BaseModel):
183
- """Response message informing the client there was an error.
184
-
185
- contains details about the error
186
- """
187
-
188
- type: str
189
- payload: ErrorResponsePayload
190
-
191
171
 
192
172
  class IOjinPersonaClient(ABC):
193
173
  """Interface for Ojin Persona client communication.
@@ -210,7 +190,7 @@ class IOjinPersonaClient(ABC):
210
190
  """
211
191
 
212
192
  @abstractmethod
213
- async def receive_message(self) -> BaseModel:
193
+ async def receive_message(self) -> BaseModel|None:
214
194
  """Receive a message from the server.
215
195
 
216
196
  Returns:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ojin-client
3
- Version: 0.1.7.dev3
3
+ Version: 0.1.7.dev5
4
4
  Summary: Ojin platform services
5
5
  Author: Journee
6
6
  License: Apache-2.0
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ojin-client"
3
- version = "0.1.7dev3"
3
+ version = "0.1.7dev5"
4
4
  description = "Ojin platform services"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"