ojin-client 0.1.7.dev4__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.dev4 → ojin_client-0.1.7.dev5}/PKG-INFO +1 -1
- {ojin_client-0.1.7.dev4 → ojin_client-0.1.7.dev5}/ojin/ojin_persona_client.py +96 -25
- {ojin_client-0.1.7.dev4 → ojin_client-0.1.7.dev5}/ojin/ojin_persona_messages.py +1 -21
- {ojin_client-0.1.7.dev4 → ojin_client-0.1.7.dev5}/ojin_client.egg-info/PKG-INFO +1 -1
- {ojin_client-0.1.7.dev4 → ojin_client-0.1.7.dev5}/pyproject.toml +1 -1
- {ojin_client-0.1.7.dev4 → ojin_client-0.1.7.dev5}/README.md +0 -0
- {ojin_client-0.1.7.dev4 → ojin_client-0.1.7.dev5}/ojin/__init__.py +0 -0
- {ojin_client-0.1.7.dev4 → ojin_client-0.1.7.dev5}/ojin/cacert.pem +0 -0
- {ojin_client-0.1.7.dev4 → ojin_client-0.1.7.dev5}/ojin/entities/interaction_messages.py +0 -0
- {ojin_client-0.1.7.dev4 → ojin_client-0.1.7.dev5}/ojin/entities/session_messages.py +0 -0
- {ojin_client-0.1.7.dev4 → ojin_client-0.1.7.dev5}/ojin_client.egg-info/SOURCES.txt +0 -0
- {ojin_client-0.1.7.dev4 → ojin_client-0.1.7.dev5}/ojin_client.egg-info/dependency_links.txt +0 -0
- {ojin_client-0.1.7.dev4 → ojin_client-0.1.7.dev5}/ojin_client.egg-info/requires.txt +0 -0
- {ojin_client-0.1.7.dev4 → ojin_client-0.1.7.dev5}/ojin_client.egg-info/top_level.txt +0 -0
- {ojin_client-0.1.7.dev4 → ojin_client-0.1.7.dev5}/setup.cfg +0 -0
|
@@ -88,6 +88,9 @@ class OjinPersonaClient(IOjinPersonaClient):
|
|
|
88
88
|
self._inference_server_ready: bool = False
|
|
89
89
|
self._cancelled: bool = False
|
|
90
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()
|
|
91
94
|
|
|
92
95
|
async def connect(self) -> None:
|
|
93
96
|
"""Establish WebSocket connection and authenticate with the service."""
|
|
@@ -109,6 +112,7 @@ class OjinPersonaClient(IOjinPersonaClient):
|
|
|
109
112
|
)
|
|
110
113
|
self._running = True
|
|
111
114
|
self._receive_task = asyncio.create_task(self._receive_messages())
|
|
115
|
+
self._split_audio_task = asyncio.create_task(self._split_audio())
|
|
112
116
|
logger.info("Successfully connected to OJIN Persona service")
|
|
113
117
|
return
|
|
114
118
|
except WebSocketException as e:
|
|
@@ -132,12 +136,7 @@ class OjinPersonaClient(IOjinPersonaClient):
|
|
|
132
136
|
pass
|
|
133
137
|
|
|
134
138
|
self._running = False
|
|
135
|
-
|
|
136
|
-
if self._receive_task:
|
|
137
|
-
self._receive_task.cancel()
|
|
138
|
-
with contextlib.suppress(asyncio.CancelledError):
|
|
139
|
-
await self._receive_task
|
|
140
|
-
self._receive_task = None
|
|
139
|
+
self.active_interaction_id = None
|
|
141
140
|
|
|
142
141
|
if self._ws:
|
|
143
142
|
try:
|
|
@@ -145,6 +144,19 @@ class OjinPersonaClient(IOjinPersonaClient):
|
|
|
145
144
|
except Exception as e:
|
|
146
145
|
logger.error("Error closing WebSocket connection: %s", e)
|
|
147
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
|
+
|
|
148
160
|
|
|
149
161
|
logger.info("Disconnected from OJIN Persona service")
|
|
150
162
|
|
|
@@ -198,10 +210,19 @@ class OjinPersonaClient(IOjinPersonaClient):
|
|
|
198
210
|
logger.error(e)
|
|
199
211
|
raise
|
|
200
212
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
213
|
+
# NOTE: str type
|
|
214
|
+
# TODO: clean when the proxy add structured logs for this error
|
|
204
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
|
+
)
|
|
205
226
|
raise Exception(message)
|
|
206
227
|
|
|
207
228
|
data = json.loads(message)
|
|
@@ -232,10 +253,11 @@ class OjinPersonaClient(IOjinPersonaClient):
|
|
|
232
253
|
if isinstance(msg, OjinPersonaSessionReadyMessage):
|
|
233
254
|
self._inference_server_ready = True
|
|
234
255
|
|
|
256
|
+
await self._message_queue.put(msg)
|
|
257
|
+
|
|
235
258
|
if isinstance(msg, ErrorResponseMessage):
|
|
236
259
|
raise RuntimeError(f"Error in Inference Server received: {msg}")
|
|
237
260
|
|
|
238
|
-
await self._message_queue.put(msg)
|
|
239
261
|
logger.info("Received message: %s", msg)
|
|
240
262
|
else:
|
|
241
263
|
logger.warning("Unknown message type: %s", msg_type)
|
|
@@ -263,6 +285,7 @@ class OjinPersonaClient(IOjinPersonaClient):
|
|
|
263
285
|
if isinstance(message, OjinPersonaCancelInteractionMessage):
|
|
264
286
|
logger.info("Interrupt")
|
|
265
287
|
|
|
288
|
+
self._cancelled = True
|
|
266
289
|
cancel_input = CancelInteractionMessage(
|
|
267
290
|
payload=message.to_proxy_message()
|
|
268
291
|
)
|
|
@@ -284,10 +307,11 @@ class OjinPersonaClient(IOjinPersonaClient):
|
|
|
284
307
|
return
|
|
285
308
|
|
|
286
309
|
if isinstance(message, StartInteractionMessage):
|
|
287
|
-
interaction_id = uuid.uuid4()
|
|
310
|
+
interaction_id = str(uuid.uuid4())
|
|
311
|
+
self.active_interaction_id = interaction_id
|
|
288
312
|
logger.info("Generate UUID %s", interaction_id)
|
|
289
313
|
interaction_response = StartInteractionResponseMessage(
|
|
290
|
-
interaction_id=
|
|
314
|
+
interaction_id=interaction_id
|
|
291
315
|
)
|
|
292
316
|
while not self._message_queue.empty():
|
|
293
317
|
await self._message_queue.get()
|
|
@@ -297,14 +321,71 @@ class OjinPersonaClient(IOjinPersonaClient):
|
|
|
297
321
|
if isinstance(message, OjinPersonaInteractionInputMessage):
|
|
298
322
|
logger.info("InteractionMessage")
|
|
299
323
|
logger.info(f"Message sent {message.interaction_id}")
|
|
324
|
+
if message.interaction_id != self.active_interaction_id:
|
|
325
|
+
return
|
|
326
|
+
|
|
300
327
|
if not message.audio_int16_bytes:
|
|
301
328
|
raise ValueError("Audio cannot be empty")
|
|
302
329
|
|
|
330
|
+
|
|
331
|
+
await self._audio_queue.put(message.audio_int16_bytes)
|
|
303
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
|
+
|
|
304
385
|
max_chunk_size = 3200 * 2
|
|
305
386
|
audio_chunks = [
|
|
306
|
-
|
|
307
|
-
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)
|
|
308
389
|
]
|
|
309
390
|
logger.info(
|
|
310
391
|
"Split audio into %d chunks of max %d bytes",
|
|
@@ -323,19 +404,9 @@ class OjinPersonaClient(IOjinPersonaClient):
|
|
|
323
404
|
params=message.params if i == 0 else None,
|
|
324
405
|
)
|
|
325
406
|
proxy_message = InteractionInputMessage(payload=interaction_input)
|
|
407
|
+
|
|
326
408
|
await self._ws.send(proxy_message.to_bytes())
|
|
327
|
-
return
|
|
328
409
|
|
|
329
|
-
logger.error("The message %s is Unknown", message)
|
|
330
|
-
await self.close()
|
|
331
|
-
error = ErrorResponseMessage(
|
|
332
|
-
payload=ErrorResponse(
|
|
333
|
-
code="UNKNOWN",
|
|
334
|
-
message="The message is Unknown",
|
|
335
|
-
timestamp=int(time.monotonic() * 1000),
|
|
336
|
-
)
|
|
337
|
-
)
|
|
338
|
-
raise Exception(error)
|
|
339
410
|
|
|
340
411
|
async def receive_message(self) -> BaseModel | None:
|
|
341
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
|