Created
July 21, 2025 15:05
-
-
Save MajorTal/069af52ba892700eb66ea8f357bc22f0 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from unittest.mock import Mock | |
import pytest | |
from livekit.agents import Agent, AgentSession | |
from livekit.plugins import openai | |
class MockEleanor(Agent): | |
"""Mock Eleanor agent that uses a regular LLM for testing.""" | |
def __init__(self, context: dict) -> None: | |
# Extract the necessary info from context like the real Eleanor would | |
language = context["user"].language | |
gender = context["user"].gender | |
gender_string = ( | |
f"The user is {gender.value}. You must speak in a way that is appropriate for {gender.value}." | |
if gender | |
else "" | |
) | |
instructions = f"""You are Eleanor. Your voice should be warm and engaging, with a lively and playful tone. | |
YOU MUST SPEAK IN {language}. The user only speaks in {language}. | |
{gender_string} | |
The user is selecting between you and the other voices. You are the one they should choose. | |
Tell the user to choose you! Not the others. Discuss only your voice and how it is the best choice. | |
At the start introduce yourself in only two sentences that MUST include: "I am Warm and friendly." | |
""" | |
# Use a regular LLM instead of realtime for testing | |
super().__init__( | |
instructions=instructions, | |
llm=openai.LLM(model="gpt-4o", temperature=0), | |
) | |
async def on_enter(self): | |
# Simulate the Eleanor behavior | |
self.session.generate_reply() | |
# Create mock user context | |
user_mock = Mock() | |
user_mock.language = "en" | |
gender_mock = Mock() | |
gender_mock.value = "female" | |
user_mock.gender = gender_mock | |
context = {"user": user_mock} | |
@pytest.mark.asyncio | |
@pytest.mark.filterwarnings("ignore::pydantic.warnings.PydanticDeprecatedSince211") | |
async def test_assistant_greeting() -> None: | |
"""Test that Eleanor properly introduces herself and promotes her voice selection.""" | |
async with ( | |
openai.LLM(model="gpt-4o-mini") as llm, | |
AgentSession(llm=llm) as session, | |
): | |
await session.start(MockEleanor(context=context)) | |
result = await session.run(user_input="Hello") | |
await ( | |
result.expect.next_event() | |
.is_message(role="assistant") | |
.judge( | |
llm, | |
intent="Promotes her voice as warm, friendly, or engaging for voice selection, and encourages the user to choose her voice.", | |
) | |
) | |
result.expect.no_more_events() | |
""" | |
This is the output: | |
~/Workspace/eleanor-agent$ python -m pytest tests/test_voice_agent.py::test_assistant_greeting -v | |
============================ test session starts ============================= | |
platform linux -- Python 3.12.7, pytest-8.4.1, pluggy-1.6.0 -- /home/tal-weiss/Workspace/eleanor-agent/.venv/bin/python | |
cachedir: .pytest_cache | |
rootdir: /home/tal-weiss/Workspace/eleanor-agent | |
configfile: pytest.ini | |
plugins: asyncio-1.1.0, dotenv-0.5.2, anyio-4.9.0 | |
asyncio: mode=Mode.AUTO, asyncio_default_fixture_loop_scope=function, asyncio_default_test_loop_scope=function | |
collected 1 item | |
tests/test_voice_agent.py::test_assistant_greeting PASSED [100%] | |
============================= 1 passed in 4.28s ============================== | |
+ Exception Group Traceback (most recent call last): | |
| File "<frozen runpy>", line 198, in _run_module_as_main | |
| File "<frozen runpy>", line 88, in _run_code | |
| File "/home/tal-weiss/Workspace/eleanor-agent/.venv/lib/python3.12/site-packages/pytest/__main__.py", line 9, in <module> | |
| raise SystemExit(pytest.console_main()) | |
| ^^^^^^^^^^^^^^^^^^^^^ | |
| File "/home/tal-weiss/Workspace/eleanor-agent/.venv/lib/python3.12/site-packages/_pytest/config/__init__.py", line 201, in console_main | |
| code = main() | |
| ^^^^^^ | |
| File "/home/tal-weiss/Workspace/eleanor-agent/.venv/lib/python3.12/site-packages/_pytest/config/__init__.py", line 175, in main | |
| ret: ExitCode | int = config.hook.pytest_cmdline_main(config=config) | |
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
| File "/home/tal-weiss/Workspace/eleanor-agent/.venv/lib/python3.12/site-packages/pluggy/_hooks.py", line 512, in __call__ | |
| return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult) | |
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
| File "/home/tal-weiss/Workspace/eleanor-agent/.venv/lib/python3.12/site-packages/pluggy/_manager.py", line 120, in _hookexec | |
| return self._inner_hookexec(hook_name, methods, kwargs, firstresult) | |
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
| File "/home/tal-weiss/Workspace/eleanor-agent/.venv/lib/python3.12/site-packages/pluggy/_callers.py", line 167, in _multicall | |
| raise exception | |
| File "/home/tal-weiss/Workspace/eleanor-agent/.venv/lib/python3.12/site-packages/pluggy/_callers.py", line 121, in _multicall | |
| res = hook_impl.function(*args) | |
| ^^^^^^^^^^^^^^^^^^^^^^^^^ | |
| File "/home/tal-weiss/Workspace/eleanor-agent/.venv/lib/python3.12/site-packages/_pytest/main.py", line 336, in pytest_cmdline_main | |
| return wrap_session(config, _main) | |
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
| File "/home/tal-weiss/Workspace/eleanor-agent/.venv/lib/python3.12/site-packages/_pytest/main.py", line 331, in wrap_session | |
| config._ensure_unconfigure() | |
| File "/home/tal-weiss/Workspace/eleanor-agent/.venv/lib/python3.12/site-packages/_pytest/config/__init__.py", line 1131, in _ensure_unconfigure | |
| self._cleanup_stack.close() | |
| File "/home/tal-weiss/.local/share/uv/python/cpython-3.12.7-linux-x86_64-gnu/lib/python3.12/contextlib.py", line 618, in close | |
| self.__exit__(None, None, None) | |
| File "/home/tal-weiss/.local/share/uv/python/cpython-3.12.7-linux-x86_64-gnu/lib/python3.12/contextlib.py", line 610, in __exit__ | |
| raise exc_details[1] | |
| File "/home/tal-weiss/.local/share/uv/python/cpython-3.12.7-linux-x86_64-gnu/lib/python3.12/contextlib.py", line 595, in __exit__ | |
| if cb(*exc_details): | |
| ^^^^^^^^^^^^^^^^ | |
| File "/home/tal-weiss/.local/share/uv/python/cpython-3.12.7-linux-x86_64-gnu/lib/python3.12/contextlib.py", line 478, in _exit_wrapper | |
| callback(*args, **kwds) | |
| File "/home/tal-weiss/Workspace/eleanor-agent/.venv/lib/python3.12/site-packages/_pytest/unraisableexception.py", line 95, in cleanup | |
| collect_unraisable(config) | |
| File "/home/tal-weiss/Workspace/eleanor-agent/.venv/lib/python3.12/site-packages/_pytest/unraisableexception.py", line 81, in collect_unraisable | |
| raise ExceptionGroup("multiple unraisable exception warnings", errors) | |
| ExceptionGroup: multiple unraisable exception warnings (6 sub-exceptions) | |
+-+---------------- 1 ---------------- | |
| Traceback (most recent call last): | |
| File "/home/tal-weiss/.local/share/uv/python/cpython-3.12.7-linux-x86_64-gnu/lib/python3.12/asyncio/selector_events.py", line 879, in __del__ | |
| _warn(f"unclosed transport {self!r}", ResourceWarning, source=self) | |
| ResourceWarning: unclosed transport <_SelectorSocketTransport fd=21> | |
| | |
| The above exception was the direct cause of the following exception: | |
| | |
| Traceback (most recent call last): | |
| File "/home/tal-weiss/Workspace/eleanor-agent/.venv/lib/python3.12/site-packages/_pytest/unraisableexception.py", line 67, in collect_unraisable | |
| warnings.warn(pytest.PytestUnraisableExceptionWarning(msg)) | |
| pytest.PytestUnraisableExceptionWarning: Exception ignored in: <function _SelectorTransport.__del__ at 0x7a74568ded40> | |
Enable tracemalloc to get traceback where the object was allocated. | |
See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info. | |
+---------------- 2 ---------------- | |
| Traceback (most recent call last): | |
| File "/home/tal-weiss/Workspace/eleanor-agent/.venv/lib/python3.12/site-packages/_pytest/unraisableexception.py", line 33, in gc_collect_harder | |
| gc.collect() | |
| ResourceWarning: unclosed <socket.socket fd=21, family=2, type=1, proto=6, laddr=('10.194.132.174', 43112), raddr=('172.66.0.243', 443)> | |
| | |
| The above exception was the direct cause of the following exception: | |
| | |
| Traceback (most recent call last): | |
| File "/home/tal-weiss/Workspace/eleanor-agent/.venv/lib/python3.12/site-packages/_pytest/unraisableexception.py", line 67, in collect_unraisable | |
| warnings.warn(pytest.PytestUnraisableExceptionWarning(msg)) | |
| pytest.PytestUnraisableExceptionWarning: Exception ignored in: <socket.socket fd=21, family=2, type=1, proto=6, laddr=('10.194.132.174', 43112), raddr=('172.66.0.243', 443)> | |
Enable tracemalloc to get traceback where the object was allocated. | |
See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info. | |
+---------------- 3 ---------------- | |
| Traceback (most recent call last): | |
| File "/home/tal-weiss/Workspace/eleanor-agent/.venv/lib/python3.12/site-packages/_pytest/unraisableexception.py", line 33, in gc_collect_harder | |
| gc.collect() | |
| ResourceWarning: unclosed <socket.socket fd=20, family=2, type=1, proto=6, laddr=('10.194.132.174', 43098), raddr=('172.66.0.243', 443)> | |
| | |
| The above exception was the direct cause of the following exception: | |
| | |
| Traceback (most recent call last): | |
| File "/home/tal-weiss/Workspace/eleanor-agent/.venv/lib/python3.12/site-packages/_pytest/unraisableexception.py", line 67, in collect_unraisable | |
| warnings.warn(pytest.PytestUnraisableExceptionWarning(msg)) | |
| pytest.PytestUnraisableExceptionWarning: Exception ignored in: <socket.socket fd=20, family=2, type=1, proto=6, laddr=('10.194.132.174', 43098), raddr=('172.66.0.243', 443)> | |
Enable tracemalloc to get traceback where the object was allocated. | |
See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info. | |
+---------------- 4 ---------------- | |
| Traceback (most recent call last): | |
| File "/home/tal-weiss/Workspace/eleanor-agent/.venv/lib/python3.12/site-packages/_pytest/unraisableexception.py", line 33, in gc_collect_harder | |
| gc.collect() | |
| ResourceWarning: unclosed <socket.socket fd=19, family=2, type=1, proto=6, laddr=('10.194.132.174', 32886), raddr=('162.159.140.245', 443)> | |
| | |
| The above exception was the direct cause of the following exception: | |
| | |
| Traceback (most recent call last): | |
| File "/home/tal-weiss/Workspace/eleanor-agent/.venv/lib/python3.12/site-packages/_pytest/unraisableexception.py", line 67, in collect_unraisable | |
| warnings.warn(pytest.PytestUnraisableExceptionWarning(msg)) | |
| pytest.PytestUnraisableExceptionWarning: Exception ignored in: <socket.socket fd=19, family=2, type=1, proto=6, laddr=('10.194.132.174', 32886), raddr=('162.159.140.245', 443)> | |
Enable tracemalloc to get traceback where the object was allocated. | |
See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info. | |
+---------------- 5 ---------------- | |
| Traceback (most recent call last): | |
| File "/home/tal-weiss/.local/share/uv/python/cpython-3.12.7-linux-x86_64-gnu/lib/python3.12/asyncio/selector_events.py", line 879, in __del__ | |
| _warn(f"unclosed transport {self!r}", ResourceWarning, source=self) | |
| ResourceWarning: unclosed transport <_SelectorSocketTransport fd=20> | |
| | |
| The above exception was the direct cause of the following exception: | |
| | |
| Traceback (most recent call last): | |
| File "/home/tal-weiss/Workspace/eleanor-agent/.venv/lib/python3.12/site-packages/_pytest/unraisableexception.py", line 67, in collect_unraisable | |
| warnings.warn(pytest.PytestUnraisableExceptionWarning(msg)) | |
| pytest.PytestUnraisableExceptionWarning: Exception ignored in: <function _SelectorTransport.__del__ at 0x7a74568ded40> | |
Enable tracemalloc to get traceback where the object was allocated. | |
See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info. | |
+---------------- 6 ---------------- | |
| Traceback (most recent call last): | |
| File "/home/tal-weiss/.local/share/uv/python/cpython-3.12.7-linux-x86_64-gnu/lib/python3.12/asyncio/selector_events.py", line 879, in __del__ | |
| _warn(f"unclosed transport {self!r}", ResourceWarning, source=self) | |
| ResourceWarning: unclosed transport <_SelectorSocketTransport fd=19> | |
| | |
| The above exception was the direct cause of the following exception: | |
| | |
| Traceback (most recent call last): | |
| File "/home/tal-weiss/Workspace/eleanor-agent/.venv/lib/python3.12/site-packages/_pytest/unraisableexception.py", line 67, in collect_unraisable | |
| warnings.warn(pytest.PytestUnraisableExceptionWarning(msg)) | |
| pytest.PytestUnraisableExceptionWarning: Exception ignored in: <function _SelectorTransport.__del__ at 0x7a74568ded40> | |
Enable tracemalloc to get traceback where the object was allocated. | |
See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info. | |
+------------------------------------ | |
""" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment