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.
- {ojin_client-0.1.7.dev3 → ojin_client-0.1.7.dev5}/PKG-INFO +1 -1
- {ojin_client-0.1.7.dev3 → ojin_client-0.1.7.dev5}/ojin/ojin_persona_client.py +103 -26
- {ojin_client-0.1.7.dev3 → ojin_client-0.1.7.dev5}/ojin/ojin_persona_messages.py +1 -21
- {ojin_client-0.1.7.dev3 → ojin_client-0.1.7.dev5}/ojin_client.egg-info/PKG-INFO +1 -1
- {ojin_client-0.1.7.dev3 → ojin_client-0.1.7.dev5}/pyproject.toml +1 -1
- {ojin_client-0.1.7.dev3 → ojin_client-0.1.7.dev5}/README.md +0 -0
- {ojin_client-0.1.7.dev3 → ojin_client-0.1.7.dev5}/ojin/__init__.py +0 -0
- {ojin_client-0.1.7.dev3 → ojin_client-0.1.7.dev5}/ojin/cacert.pem +0 -0
- {ojin_client-0.1.7.dev3 → ojin_client-0.1.7.dev5}/ojin/entities/interaction_messages.py +0 -0
- {ojin_client-0.1.7.dev3 → ojin_client-0.1.7.dev5}/ojin/entities/session_messages.py +0 -0
- {ojin_client-0.1.7.dev3 → ojin_client-0.1.7.dev5}/ojin_client.egg-info/SOURCES.txt +0 -0
- {ojin_client-0.1.7.dev3 → ojin_client-0.1.7.dev5}/ojin_client.egg-info/dependency_links.txt +0 -0
- {ojin_client-0.1.7.dev3 → ojin_client-0.1.7.dev5}/ojin_client.egg-info/requires.txt +0 -0
- {ojin_client-0.1.7.dev3 → ojin_client-0.1.7.dev5}/ojin_client.egg-info/top_level.txt +0 -0
- {ojin_client-0.1.7.dev3 → ojin_client-0.1.7.dev5}/setup.cfg +0 -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
|
-
|
|
197
|
-
|
|
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=
|
|
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
|
-
|
|
301
|
-
for i in range(0, len(
|
|
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:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|