ojin-client 0.1.7.dev4__tar.gz → 0.1.7.dev6__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.dev6}/PKG-INFO +1 -1
- {ojin_client-0.1.7.dev4 → ojin_client-0.1.7.dev6}/ojin/ojin_persona_client.py +99 -31
- {ojin_client-0.1.7.dev4 → ojin_client-0.1.7.dev6}/ojin/ojin_persona_messages.py +1 -21
- {ojin_client-0.1.7.dev4 → ojin_client-0.1.7.dev6}/ojin_client.egg-info/PKG-INFO +1 -1
- {ojin_client-0.1.7.dev4 → ojin_client-0.1.7.dev6}/pyproject.toml +1 -1
- {ojin_client-0.1.7.dev4 → ojin_client-0.1.7.dev6}/README.md +0 -0
- {ojin_client-0.1.7.dev4 → ojin_client-0.1.7.dev6}/ojin/__init__.py +0 -0
- {ojin_client-0.1.7.dev4 → ojin_client-0.1.7.dev6}/ojin/cacert.pem +0 -0
- {ojin_client-0.1.7.dev4 → ojin_client-0.1.7.dev6}/ojin/entities/interaction_messages.py +0 -0
- {ojin_client-0.1.7.dev4 → ojin_client-0.1.7.dev6}/ojin/entities/session_messages.py +0 -0
- {ojin_client-0.1.7.dev4 → ojin_client-0.1.7.dev6}/ojin_client.egg-info/SOURCES.txt +0 -0
- {ojin_client-0.1.7.dev4 → ojin_client-0.1.7.dev6}/ojin_client.egg-info/dependency_links.txt +0 -0
- {ojin_client-0.1.7.dev4 → ojin_client-0.1.7.dev6}/ojin_client.egg-info/requires.txt +0 -0
- {ojin_client-0.1.7.dev4 → ojin_client-0.1.7.dev6}/ojin_client.egg-info/top_level.txt +0 -0
- {ojin_client-0.1.7.dev4 → ojin_client-0.1.7.dev6}/setup.cfg +0 -0
|
@@ -87,7 +87,9 @@ class OjinPersonaClient(IOjinPersonaClient):
|
|
|
87
87
|
self._receive_task: Optional[asyncio.Task] = None
|
|
88
88
|
self._inference_server_ready: bool = False
|
|
89
89
|
self._cancelled: bool = False
|
|
90
|
-
self.
|
|
90
|
+
self.active_interaction_id: str | None = None
|
|
91
|
+
self._split_audio_task: Optional[asyncio.Task] = None
|
|
92
|
+
self._audio_queue: asyncio.Queue[OjinPersonaInteractionInputMessage] = asyncio.Queue()
|
|
91
93
|
|
|
92
94
|
async def connect(self) -> None:
|
|
93
95
|
"""Establish WebSocket connection and authenticate with the service."""
|
|
@@ -109,6 +111,7 @@ class OjinPersonaClient(IOjinPersonaClient):
|
|
|
109
111
|
)
|
|
110
112
|
self._running = True
|
|
111
113
|
self._receive_task = asyncio.create_task(self._receive_messages())
|
|
114
|
+
self._split_audio_task = asyncio.create_task(self._split_audio())
|
|
112
115
|
logger.info("Successfully connected to OJIN Persona service")
|
|
113
116
|
return
|
|
114
117
|
except WebSocketException as e:
|
|
@@ -132,12 +135,7 @@ class OjinPersonaClient(IOjinPersonaClient):
|
|
|
132
135
|
pass
|
|
133
136
|
|
|
134
137
|
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
|
|
138
|
+
self.active_interaction_id = None
|
|
141
139
|
|
|
142
140
|
if self._ws:
|
|
143
141
|
try:
|
|
@@ -145,6 +143,19 @@ class OjinPersonaClient(IOjinPersonaClient):
|
|
|
145
143
|
except Exception as e:
|
|
146
144
|
logger.error("Error closing WebSocket connection: %s", e)
|
|
147
145
|
self._ws = None
|
|
146
|
+
|
|
147
|
+
if self._split_audio_task:
|
|
148
|
+
self._split_audio_task.cancel()
|
|
149
|
+
with contextlib.suppress(asyncio.CancelledError):
|
|
150
|
+
await self._split_audio_task
|
|
151
|
+
self._split_audio_task = None
|
|
152
|
+
|
|
153
|
+
if self._receive_task:
|
|
154
|
+
self._receive_task.cancel()
|
|
155
|
+
with contextlib.suppress(asyncio.CancelledError):
|
|
156
|
+
await self._receive_task
|
|
157
|
+
self._receive_task = None
|
|
158
|
+
|
|
148
159
|
|
|
149
160
|
logger.info("Disconnected from OJIN Persona service")
|
|
150
161
|
|
|
@@ -198,10 +209,19 @@ class OjinPersonaClient(IOjinPersonaClient):
|
|
|
198
209
|
logger.error(e)
|
|
199
210
|
raise
|
|
200
211
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
212
|
+
# NOTE: str type
|
|
213
|
+
# TODO: clean when the proxy add structured logs for this error
|
|
204
214
|
if message == "No backend servers available. Please try again later.":
|
|
215
|
+
await self._message_queue.put(
|
|
216
|
+
ErrorResponseMessage(
|
|
217
|
+
payload=ErrorResponse(
|
|
218
|
+
interaction_id=None,
|
|
219
|
+
code="NO_BACKEND_SERVER_AVAILABLE",
|
|
220
|
+
message=message,
|
|
221
|
+
timestamp=int(time.monotonic() * 1000),
|
|
222
|
+
)
|
|
223
|
+
)
|
|
224
|
+
)
|
|
205
225
|
raise Exception(message)
|
|
206
226
|
|
|
207
227
|
data = json.loads(message)
|
|
@@ -232,10 +252,11 @@ class OjinPersonaClient(IOjinPersonaClient):
|
|
|
232
252
|
if isinstance(msg, OjinPersonaSessionReadyMessage):
|
|
233
253
|
self._inference_server_ready = True
|
|
234
254
|
|
|
255
|
+
await self._message_queue.put(msg)
|
|
256
|
+
|
|
235
257
|
if isinstance(msg, ErrorResponseMessage):
|
|
236
258
|
raise RuntimeError(f"Error in Inference Server received: {msg}")
|
|
237
259
|
|
|
238
|
-
await self._message_queue.put(msg)
|
|
239
260
|
logger.info("Received message: %s", msg)
|
|
240
261
|
else:
|
|
241
262
|
logger.warning("Unknown message type: %s", msg_type)
|
|
@@ -263,6 +284,7 @@ class OjinPersonaClient(IOjinPersonaClient):
|
|
|
263
284
|
if isinstance(message, OjinPersonaCancelInteractionMessage):
|
|
264
285
|
logger.info("Interrupt")
|
|
265
286
|
|
|
287
|
+
self._cancelled = True
|
|
266
288
|
cancel_input = CancelInteractionMessage(
|
|
267
289
|
payload=message.to_proxy_message()
|
|
268
290
|
)
|
|
@@ -277,17 +299,16 @@ class OjinPersonaClient(IOjinPersonaClient):
|
|
|
277
299
|
except asyncio.QueueEmpty:
|
|
278
300
|
break
|
|
279
301
|
|
|
280
|
-
|
|
281
302
|
self._cancelled = False
|
|
282
|
-
self._cancelled_interaction_id = message.interaction_id
|
|
283
303
|
|
|
284
304
|
return
|
|
285
305
|
|
|
286
306
|
if isinstance(message, StartInteractionMessage):
|
|
287
|
-
interaction_id = uuid.uuid4()
|
|
307
|
+
interaction_id = str(uuid.uuid4())
|
|
308
|
+
self.active_interaction_id = interaction_id
|
|
288
309
|
logger.info("Generate UUID %s", interaction_id)
|
|
289
310
|
interaction_response = StartInteractionResponseMessage(
|
|
290
|
-
interaction_id=
|
|
311
|
+
interaction_id=interaction_id
|
|
291
312
|
)
|
|
292
313
|
while not self._message_queue.empty():
|
|
293
314
|
await self._message_queue.get()
|
|
@@ -297,14 +318,71 @@ class OjinPersonaClient(IOjinPersonaClient):
|
|
|
297
318
|
if isinstance(message, OjinPersonaInteractionInputMessage):
|
|
298
319
|
logger.info("InteractionMessage")
|
|
299
320
|
logger.info(f"Message sent {message.interaction_id}")
|
|
321
|
+
if message.interaction_id != self.active_interaction_id:
|
|
322
|
+
return
|
|
323
|
+
|
|
300
324
|
if not message.audio_int16_bytes:
|
|
301
325
|
raise ValueError("Audio cannot be empty")
|
|
302
326
|
|
|
327
|
+
|
|
328
|
+
await self._audio_queue.put(message)
|
|
303
329
|
# Split audio bytes into chunks of max 3200 samples
|
|
330
|
+
# max_chunk_size = 3200 * 2
|
|
331
|
+
# audio_chunks = [
|
|
332
|
+
# message.audio_int16_bytes[i : i + max_chunk_size]
|
|
333
|
+
# for i in range(0, len(message.audio_int16_bytes), max_chunk_size)
|
|
334
|
+
# ]
|
|
335
|
+
# logger.info(
|
|
336
|
+
# "Split audio into %d chunks of max %d bytes",
|
|
337
|
+
# len(audio_chunks), max_chunk_size
|
|
338
|
+
# )
|
|
339
|
+
|
|
340
|
+
# for i, chunk in enumerate(audio_chunks):
|
|
341
|
+
# is_last = i == len(audio_chunks) - 1 and message.is_last_input
|
|
342
|
+
#
|
|
343
|
+
# interaction_input = InteractionInput(
|
|
344
|
+
# interaction_id=message.interaction_id,
|
|
345
|
+
# is_final_input=is_last,
|
|
346
|
+
# payload_type="audio",
|
|
347
|
+
# payload=chunk,
|
|
348
|
+
# timestamp=int(time.monotonic() * 1000),
|
|
349
|
+
# params=message.params if i == 0 else None,
|
|
350
|
+
# )
|
|
351
|
+
# proxy_message = InteractionInputMessage(payload=interaction_input)
|
|
352
|
+
# await self._ws.send(proxy_message.to_bytes())
|
|
353
|
+
return
|
|
354
|
+
|
|
355
|
+
logger.error("The message %s is Unknown", message)
|
|
356
|
+
# TODO: should we close the connection here?
|
|
357
|
+
await self.close()
|
|
358
|
+
error = ErrorResponseMessage(
|
|
359
|
+
payload=ErrorResponse(
|
|
360
|
+
interaction_id=message.interaction_id,
|
|
361
|
+
code="UNKNOWN",
|
|
362
|
+
message="The message is Unknown",
|
|
363
|
+
timestamp=int(time.monotonic() * 1000),
|
|
364
|
+
)
|
|
365
|
+
)
|
|
366
|
+
raise Exception(error)
|
|
367
|
+
|
|
368
|
+
async def _split_audio(self) -> None:
|
|
369
|
+
while True:
|
|
370
|
+
message_audio: OjinPersonaInteractionInputMessage| None = None
|
|
371
|
+
if self._cancelled:
|
|
372
|
+
pass
|
|
373
|
+
|
|
374
|
+
try:
|
|
375
|
+
message_audio = self._audio_queue.get_nowait()
|
|
376
|
+
except asyncio.QueueEmpty:
|
|
377
|
+
pass
|
|
378
|
+
|
|
379
|
+
if message_audio is None:
|
|
380
|
+
pass
|
|
381
|
+
|
|
304
382
|
max_chunk_size = 3200 * 2
|
|
305
383
|
audio_chunks = [
|
|
306
|
-
|
|
307
|
-
for i in range(0, len(
|
|
384
|
+
message_audio.audio_int16_bytes[i : i + max_chunk_size]
|
|
385
|
+
for i in range(0, len(message_audio.audio_int16_bytes), max_chunk_size)
|
|
308
386
|
]
|
|
309
387
|
logger.info(
|
|
310
388
|
"Split audio into %d chunks of max %d bytes",
|
|
@@ -312,30 +390,20 @@ class OjinPersonaClient(IOjinPersonaClient):
|
|
|
312
390
|
)
|
|
313
391
|
|
|
314
392
|
for i, chunk in enumerate(audio_chunks):
|
|
315
|
-
is_last = i == len(audio_chunks) - 1 and
|
|
393
|
+
is_last = i == len(audio_chunks) - 1 and message_audio.is_last_input
|
|
316
394
|
|
|
317
395
|
interaction_input = InteractionInput(
|
|
318
|
-
interaction_id=
|
|
396
|
+
interaction_id=message_audio.interaction_id,
|
|
319
397
|
is_final_input=is_last,
|
|
320
398
|
payload_type="audio",
|
|
321
399
|
payload=chunk,
|
|
322
400
|
timestamp=int(time.monotonic() * 1000),
|
|
323
|
-
params=
|
|
401
|
+
params=message_audio.params if i == 0 else None,
|
|
324
402
|
)
|
|
325
403
|
proxy_message = InteractionInputMessage(payload=interaction_input)
|
|
404
|
+
|
|
326
405
|
await self._ws.send(proxy_message.to_bytes())
|
|
327
|
-
return
|
|
328
406
|
|
|
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
407
|
|
|
340
408
|
async def receive_message(self) -> BaseModel | None:
|
|
341
409
|
"""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
|