From 792b9bbecadd09b45e22c91e953ac996e6f91bdc Mon Sep 17 00:00:00 2001 From: plutoless Date: Wed, 17 Jun 2026 10:59:26 -0700 Subject: [PATCH] fix(ci): bump minimum Python to 3.9 and fix type errors from #37 PR #37 broke all three CI jobs: - test / compat-build: cn.py uses PEP 585 builtins (`list[str]`) which fail at Pydantic model-build time on Python 3.8. Raise the minimum supported version to 3.9 (pyproject + compat pyproject + CI matrix, poetry.lock refreshed) so the existing annotations are valid. - compile (mypy): fix the type errors introduced alongside the new regional/CN feature: * Remove the `@overload` stubs on Agent/Agora `__new__`/`__init__` (they could not be paired with their implementations by mypy). Runtime dispatch is unchanged; only static return-type precision is dropped. * __init__.py: import AgentClient/AsyncAgentClient from .pool_client (where they are defined) instead of .agentkit. * Agent.turn_detection / _resolve_asr_config: accept dict as well as the model, matching runtime behaviour. * regional_agent.py: type: ignore[override] for the intentional regional-vendor parameter narrowing. * test_regional_vendors.py: narrow Optional before indexing. Verified: mypy clean (303 files), 193 passed / 1 skipped, lock consistent. Co-Authored-By: Claude Opus 4.8 --- .github/workflows/ci.yml | 8 +- compat/agora-agent-server-sdk/pyproject.toml | 3 +- poetry.lock | 9 +- pyproject.toml | 3 +- src/agora_agent/__init__.py | 4 +- src/agora_agent/agentkit/agent.py | 57 +---- src/agora_agent/agentkit/regional_agent.py | 16 +- src/agora_agent/pool_client.py | 208 ------------------- tests/custom/test_regional_vendors.py | 8 +- 9 files changed, 30 insertions(+), 286 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e3cc233..da5cd48 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,7 +9,7 @@ jobs: - name: Set up python uses: actions/setup-python@v4 with: - python-version: 3.8 + python-version: 3.9 - name: Bootstrap poetry run: | curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1 @@ -27,7 +27,7 @@ jobs: - name: Set up python uses: actions/setup-python@v4 with: - python-version: 3.8 + python-version: 3.9 - name: Bootstrap poetry run: | curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1 @@ -45,7 +45,7 @@ jobs: - name: Set up python uses: actions/setup-python@v4 with: - python-version: 3.8 + python-version: 3.9 - name: Bootstrap poetry run: | curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1 @@ -77,7 +77,7 @@ jobs: - name: Set up python uses: actions/setup-python@v4 with: - python-version: 3.8 + python-version: 3.9 - name: Bootstrap poetry run: | curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1 diff --git a/compat/agora-agent-server-sdk/pyproject.toml b/compat/agora-agent-server-sdk/pyproject.toml index 078ac75..e686d2e 100644 --- a/compat/agora-agent-server-sdk/pyproject.toml +++ b/compat/agora-agent-server-sdk/pyproject.toml @@ -13,7 +13,6 @@ classifiers = [ "Intended Audience :: Developers", "Programming Language :: Python", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", @@ -34,7 +33,7 @@ packages = [ Repository = 'https://github.com/AgoraIO-Conversational-AI/agent-server-sdk-python' [tool.poetry.dependencies] -python = "^3.8" +python = "^3.9" agora-agents = ">=2.2.0,<3.0.0" [build-system] diff --git a/poetry.lock b/poetry.lock index 46f7b7b..42a4123 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. [[package]] name = "annotated-types" @@ -11,9 +11,6 @@ files = [ {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, ] -[package.dependencies] -typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.9\""} - [[package]] name = "anyio" version = "4.5.2" @@ -561,5 +558,5 @@ files = [ [metadata] lock-version = "2.0" -python-versions = "^3.8" -content-hash = "8551b871abee465e23fb0966d51f2c155fd257b55bdcb0c02d095de19f92f358" +python-versions = "^3.9" +content-hash = "43fd91a195ce370dfed48f6baebe3ad977efb9d6741bf43dcd458eba28d9cf1f" diff --git a/pyproject.toml b/pyproject.toml index 327306a..1c33f76 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,6 @@ classifiers = [ "Intended Audience :: Developers", "Programming Language :: Python", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", @@ -34,7 +33,7 @@ packages = [ Repository = 'https://github.com/AgoraIO-Conversational-AI/agent-server-sdk-python' [tool.poetry.dependencies] -python = "^3.8" +python = "^3.9" httpx = ">=0.21.2" pydantic = ">= 1.9.2" pydantic-core = ">=2.18.2" diff --git a/src/agora_agent/__init__.py b/src/agora_agent/__init__.py index b2a41ac..56b91e7 100644 --- a/src/agora_agent/__init__.py +++ b/src/agora_agent/__init__.py @@ -12,14 +12,12 @@ if typing.TYPE_CHECKING: from . import agents, agentkit, core, phone_numbers, telephony from .core.domain import Area, Pool, create_pool - from .pool_client import Agora, AsyncAgora + from .pool_client import Agora, AsyncAgora, AgentClient, AsyncAgentClient from .version import __version__ from .agentkit import ( Agent, AgentSession, AgentSessionOptions, - AgentClient, - AsyncAgentClient, CNAgent, GlobalAgent, GenericAvatar, diff --git a/src/agora_agent/agentkit/agent.py b/src/agora_agent/agentkit/agent.py index 2590561..a671f6a 100644 --- a/src/agora_agent/agentkit/agent.py +++ b/src/agora_agent/agentkit/agent.py @@ -350,54 +350,6 @@ class Agent: ... ) """ - if typing.TYPE_CHECKING: - _GlobalArea = typing_extensions.Literal[Area.US, Area.EU, Area.AP] - - @typing.overload - def __new__( - cls, - client: "Agora[typing_extensions.Literal[Area.CN]]", - *args: typing.Any, - **kwargs: typing.Any, - ) -> "CNAgent": - ... - - @typing.overload - def __new__( - cls, - client: "Agora[_GlobalArea]", - *args: typing.Any, - **kwargs: typing.Any, - ) -> "GlobalAgent": - ... - - @typing.overload - def __new__( - cls, - client: "AsyncAgora[typing_extensions.Literal[Area.CN]]", - *args: typing.Any, - **kwargs: typing.Any, - ) -> "CNAgent": - ... - - @typing.overload - def __new__( - cls, - client: "AsyncAgora[_GlobalArea]", - *args: typing.Any, - **kwargs: typing.Any, - ) -> "GlobalAgent": - ... - - @typing.overload - def __new__( - cls, - client: typing.Any, - *args: typing.Any, - **kwargs: typing.Any, - ) -> "Agent": - ... - def __new__( cls, client: typing.Any = None, @@ -422,7 +374,7 @@ def __init__( self, client: typing.Any, instructions: typing.Optional[str] = None, - turn_detection: typing.Optional[TurnDetectionConfig] = None, + turn_detection: typing.Optional[typing.Union[TurnDetectionConfig, typing.Dict[str, typing.Any]]] = None, interruption: typing.Optional[InterruptionConfig] = None, sal: typing.Optional[SalConfig] = None, advanced_features: typing.Optional[AdvancedFeatures] = None, @@ -733,7 +685,7 @@ def mllm(self) -> typing.Optional[typing.Dict[str, typing.Any]]: return self._mllm @property - def turn_detection(self) -> typing.Optional[TurnDetectionConfig]: + def turn_detection(self) -> typing.Optional[typing.Union[TurnDetectionConfig, typing.Dict[str, typing.Any]]]: return self._turn_detection @property @@ -1072,7 +1024,10 @@ def _resolve_llm_config(self) -> typing.Dict[str, typing.Any]: llm_config["max_history"] = self._max_history return llm_config - def _resolve_asr_config(self, turn_detection_config: TurnDetectionConfig) -> typing.Dict[str, typing.Any]: + def _resolve_asr_config( + self, + turn_detection_config: typing.Union[TurnDetectionConfig, typing.Dict[str, typing.Any]], + ) -> typing.Dict[str, typing.Any]: asr_config = dict(self._stt or {}) if not asr_config: asr_config["vendor"] = "ares" diff --git a/src/agora_agent/agentkit/regional_agent.py b/src/agora_agent/agentkit/regional_agent.py index c8742b3..6a5f985 100644 --- a/src/agora_agent/agentkit/regional_agent.py +++ b/src/agora_agent/agentkit/regional_agent.py @@ -108,30 +108,30 @@ class CNAgent(Agent): - def with_stt(self, vendor: CNSTT) -> "CNAgent": + def with_stt(self, vendor: CNSTT) -> "CNAgent": # type: ignore[override] return typing.cast("CNAgent", super().with_stt(typing.cast(BaseSTT, vendor))) - def with_llm(self, vendor: CNLLM) -> "CNAgent": + def with_llm(self, vendor: CNLLM) -> "CNAgent": # type: ignore[override] return typing.cast("CNAgent", super().with_llm(typing.cast(BaseLLM, vendor))) - def with_tts(self, vendor: CNTTS) -> "CNAgent": + def with_tts(self, vendor: CNTTS) -> "CNAgent": # type: ignore[override] return typing.cast("CNAgent", super().with_tts(typing.cast(BaseTTS, vendor))) - def with_avatar(self, vendor: CNAvatar) -> "CNAgent": + def with_avatar(self, vendor: CNAvatar) -> "CNAgent": # type: ignore[override] return typing.cast("CNAgent", super().with_avatar(typing.cast(BaseAvatar, vendor))) class GlobalAgent(Agent): - def with_stt(self, vendor: GlobalSTT) -> "GlobalAgent": + def with_stt(self, vendor: GlobalSTT) -> "GlobalAgent": # type: ignore[override] return typing.cast("GlobalAgent", super().with_stt(typing.cast(BaseSTT, vendor))) - def with_llm(self, vendor: GlobalLLM) -> "GlobalAgent": + def with_llm(self, vendor: GlobalLLM) -> "GlobalAgent": # type: ignore[override] return typing.cast("GlobalAgent", super().with_llm(typing.cast(BaseLLM, vendor))) - def with_tts(self, vendor: GlobalTTS) -> "GlobalAgent": + def with_tts(self, vendor: GlobalTTS) -> "GlobalAgent": # type: ignore[override] return typing.cast("GlobalAgent", super().with_tts(typing.cast(BaseTTS, vendor))) - def with_avatar(self, vendor: GlobalAvatar) -> "GlobalAgent": + def with_avatar(self, vendor: GlobalAvatar) -> "GlobalAgent": # type: ignore[override] return typing.cast("GlobalAgent", super().with_avatar(typing.cast(BaseAvatar, vendor))) diff --git a/src/agora_agent/pool_client.py b/src/agora_agent/pool_client.py index 440af10..9599603 100644 --- a/src/agora_agent/pool_client.py +++ b/src/agora_agent/pool_client.py @@ -209,110 +209,6 @@ class Agora(BaseAgora, typing.Generic[_AreaT]): ) """ - if typing.TYPE_CHECKING: - - @typing.overload - def __new__( - cls, - *, - area: typing_extensions.Literal[Area.CN], - app_id: str, - app_certificate: str, - customer_id: typing.Optional[str] = None, - customer_secret: typing.Optional[str] = None, - auth_token: typing.Optional[str] = None, - headers: typing.Optional[typing.Dict[str, str]] = None, - timeout: typing.Optional[float] = None, - follow_redirects: typing.Optional[bool] = True, - httpx_client: typing.Optional[httpx.Client] = None, - debug: bool = False, - ) -> "CNAgora": ... - - @typing.overload - def __new__( - cls, - *, - area: _GlobalArea, - app_id: str, - app_certificate: str, - customer_id: typing.Optional[str] = None, - customer_secret: typing.Optional[str] = None, - auth_token: typing.Optional[str] = None, - headers: typing.Optional[typing.Dict[str, str]] = None, - timeout: typing.Optional[float] = None, - follow_redirects: typing.Optional[bool] = True, - httpx_client: typing.Optional[httpx.Client] = None, - debug: bool = False, - ) -> "GlobalAgoraClient": ... - - @typing.overload - def __new__( - cls, - *, - area: _AreaT, - app_id: str, - app_certificate: str, - customer_id: typing.Optional[str] = None, - customer_secret: typing.Optional[str] = None, - auth_token: typing.Optional[str] = None, - headers: typing.Optional[typing.Dict[str, str]] = None, - timeout: typing.Optional[float] = None, - follow_redirects: typing.Optional[bool] = True, - httpx_client: typing.Optional[httpx.Client] = None, - debug: bool = False, - ) -> "Agora[_AreaT]": ... - - @typing.overload - def __init__( - self: "Agora[typing_extensions.Literal[Area.CN]]", - *, - area: typing_extensions.Literal[Area.CN], - app_id: str, - app_certificate: str, - customer_id: typing.Optional[str] = None, - customer_secret: typing.Optional[str] = None, - auth_token: typing.Optional[str] = None, - headers: typing.Optional[typing.Dict[str, str]] = None, - timeout: typing.Optional[float] = None, - follow_redirects: typing.Optional[bool] = True, - httpx_client: typing.Optional[httpx.Client] = None, - debug: bool = False, - ) -> None: ... - - @typing.overload - def __init__( - self: "Agora[_GlobalArea]", - *, - area: _GlobalArea, - app_id: str, - app_certificate: str, - customer_id: typing.Optional[str] = None, - customer_secret: typing.Optional[str] = None, - auth_token: typing.Optional[str] = None, - headers: typing.Optional[typing.Dict[str, str]] = None, - timeout: typing.Optional[float] = None, - follow_redirects: typing.Optional[bool] = True, - httpx_client: typing.Optional[httpx.Client] = None, - debug: bool = False, - ) -> None: ... - - @typing.overload - def __init__( - self, - *, - area: _AreaT, - app_id: str, - app_certificate: str, - customer_id: typing.Optional[str] = None, - customer_secret: typing.Optional[str] = None, - auth_token: typing.Optional[str] = None, - headers: typing.Optional[typing.Dict[str, str]] = None, - timeout: typing.Optional[float] = None, - follow_redirects: typing.Optional[bool] = True, - httpx_client: typing.Optional[httpx.Client] = None, - debug: bool = False, - ) -> None: ... - def __new__(cls, **kwargs: typing.Any) -> typing.Any: if cls is Agora: area = kwargs.get("area") @@ -547,110 +443,6 @@ class AsyncAgora(BaseAsyncAgora, typing.Generic[_AreaT]): ) """ - if typing.TYPE_CHECKING: - - @typing.overload - def __new__( - cls, - *, - area: typing_extensions.Literal[Area.CN], - app_id: str, - app_certificate: str, - customer_id: typing.Optional[str] = None, - customer_secret: typing.Optional[str] = None, - auth_token: typing.Optional[str] = None, - headers: typing.Optional[typing.Dict[str, str]] = None, - timeout: typing.Optional[float] = None, - follow_redirects: typing.Optional[bool] = True, - httpx_client: typing.Optional[httpx.AsyncClient] = None, - debug: bool = False, - ) -> "CNAsyncAgora": ... - - @typing.overload - def __new__( - cls, - *, - area: _GlobalArea, - app_id: str, - app_certificate: str, - customer_id: typing.Optional[str] = None, - customer_secret: typing.Optional[str] = None, - auth_token: typing.Optional[str] = None, - headers: typing.Optional[typing.Dict[str, str]] = None, - timeout: typing.Optional[float] = None, - follow_redirects: typing.Optional[bool] = True, - httpx_client: typing.Optional[httpx.AsyncClient] = None, - debug: bool = False, - ) -> "GlobalAsyncAgoraClient": ... - - @typing.overload - def __new__( - cls, - *, - area: _AreaT, - app_id: str, - app_certificate: str, - customer_id: typing.Optional[str] = None, - customer_secret: typing.Optional[str] = None, - auth_token: typing.Optional[str] = None, - headers: typing.Optional[typing.Dict[str, str]] = None, - timeout: typing.Optional[float] = None, - follow_redirects: typing.Optional[bool] = True, - httpx_client: typing.Optional[httpx.AsyncClient] = None, - debug: bool = False, - ) -> "AsyncAgora[_AreaT]": ... - - @typing.overload - def __init__( - self: "AsyncAgora[typing_extensions.Literal[Area.CN]]", - *, - area: typing_extensions.Literal[Area.CN], - app_id: str, - app_certificate: str, - customer_id: typing.Optional[str] = None, - customer_secret: typing.Optional[str] = None, - auth_token: typing.Optional[str] = None, - headers: typing.Optional[typing.Dict[str, str]] = None, - timeout: typing.Optional[float] = None, - follow_redirects: typing.Optional[bool] = True, - httpx_client: typing.Optional[httpx.AsyncClient] = None, - debug: bool = False, - ) -> None: ... - - @typing.overload - def __init__( - self: "AsyncAgora[_GlobalArea]", - *, - area: _GlobalArea, - app_id: str, - app_certificate: str, - customer_id: typing.Optional[str] = None, - customer_secret: typing.Optional[str] = None, - auth_token: typing.Optional[str] = None, - headers: typing.Optional[typing.Dict[str, str]] = None, - timeout: typing.Optional[float] = None, - follow_redirects: typing.Optional[bool] = True, - httpx_client: typing.Optional[httpx.AsyncClient] = None, - debug: bool = False, - ) -> None: ... - - @typing.overload - def __init__( - self, - *, - area: _AreaT, - app_id: str, - app_certificate: str, - customer_id: typing.Optional[str] = None, - customer_secret: typing.Optional[str] = None, - auth_token: typing.Optional[str] = None, - headers: typing.Optional[typing.Dict[str, str]] = None, - timeout: typing.Optional[float] = None, - follow_redirects: typing.Optional[bool] = True, - httpx_client: typing.Optional[httpx.AsyncClient] = None, - debug: bool = False, - ) -> None: ... - def __new__(cls, **kwargs: typing.Any) -> typing.Any: if cls is AsyncAgora: area = kwargs.get("area") diff --git a/tests/custom/test_regional_vendors.py b/tests/custom/test_regional_vendors.py index a1c42b8..29dcefe 100644 --- a/tests/custom/test_regional_vendors.py +++ b/tests/custom/test_regional_vendors.py @@ -59,7 +59,9 @@ def test_cn_client_allows_global_only_vendor() -> None: DeepgramSTT(api_key="dg-key", model="nova-2", language="en-US") ) assert agent.__class__.__name__ == "CNAgent" - assert agent.stt["vendor"] == "deepgram" + stt = agent.stt + assert stt is not None + assert stt["vendor"] == "deepgram" def test_global_client_allows_cn_only_vendor() -> None: @@ -69,7 +71,9 @@ def test_global_client_allows_cn_only_vendor() -> None: ) agent = Agent(client=client).with_stt(tencent_stt) assert agent.__class__.__name__ == "GlobalAgent" - assert agent.stt["vendor"] == "tencent" + stt = agent.stt + assert stt is not None + assert stt["vendor"] == "tencent" def test_direct_import_vendors_work_with_bound_global_client() -> None: