Skip to content

RTClient.generate_response and RTClient.events race for the "response.created" message.Β #111

@kingychiu

Description

@kingychiu

This issue is for a: (mark with an x)

- [X] bug report -> please search issues before submitting
- [ ] feature request
- [ ] documentation issue or request
- [ ] regression (a behavior that used to work and stopped in a new release)

Minimal steps to reproduce

I discovered this bug in the middle-tier demo.

In the Python Fast-API middle-tier demo, it uses await self.client.generate_response() for text user input and self.client.events() for audio stuff.

Try to send text user input more than once, and you will see the second one is blocked by the first one. because await self.client.generate_response() never resolves.

In cause you also cannot run the middle-tier demo, here is my PR to fix the demo (not fixing this issue): #110

Any log messages given by the failure

Expected/desired behavior

We can send text messages and audio messages as many as we want in any order.

OS and Version?

macOS

Versions

Python rtclient-0.5.3

Mention any other details that might be useful

RTClient.generate_response wait for response.created event:

    async def generate_response(self) -> RTResponse:
        await self._client.send(ResponseCreateMessage())
        message = await self._message_queue.receive(lambda m: m.type == "response.created")
        ...

RTClient.events wait for response.created event as well:

    async def events(self) -> AsyncGenerator[RTInputAudioItem | RTResponse]:
        # TODO: Add the updated quota message as a control type of event.
        while True:
            message = await self._message_queue.receive(
                lambda m: m.type == "input_audio_buffer.speech_started" or m.type == "response.created"
            )
...

The below code from message_queue.py shows why only one of the functions above can get the response.created event, but not both:

    def _find_and_remove(self, predicate: Callable[[T], bool]) -> Optional[T]:
        for i, message in enumerate(self._stored_messages):
            if predicate(message):
                return self._stored_messages.pop(i)
        return None

    async def receive(self, predicate: Callable[[T], bool]) -> Optional[T]:
        # The first one who takes the message will remove it from the queue as well.
        found_message = self._find_and_remove(predicate)
....

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions