feat(types): add is_given TypeGuard helpers for NotGiven sentinels
Pyright can't narrow identity checks against module-level NotGiven sentinels (they aren't typed as singletons), which leaves many NotGiven-bearing unions stuck as unnarrowed types throughout the codebase. Introduce is_given TypeGuard helpers so narrowing works via isinstance under the hood. Each helper is co-located with the NotGiven flavor it guards: - services/settings.py: upgrade the existing is_given to a TypeGuard. - processors/aggregators/llm_context.py: add an is_given for LLMContext's NotGiven. Treat LLMContext's re-exported types (LLMStandardMessage, LLMContextToolChoice, NOT_GIVEN, NotGiven) as LLMContext's own — independent definitions that happen to coincide with OpenAI's as an implementation detail. - adapters/services/anthropic_adapter.py: add is_given for anthropic's NotGiven. - adapters/services/open_ai_adapter.py: add is_given for openai's NotGiven.
This commit is contained in:
@@ -9,7 +9,7 @@
|
||||
import copy
|
||||
import json
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, TypedDict
|
||||
from typing import Any, TypedDict, TypeGuard, TypeVar
|
||||
|
||||
from anthropic import NOT_GIVEN, NotGiven
|
||||
from anthropic.types.message_param import MessageParam
|
||||
@@ -26,6 +26,29 @@ from pipecat.processors.aggregators.llm_context import (
|
||||
LLMStandardMessage,
|
||||
)
|
||||
|
||||
_T = TypeVar("_T")
|
||||
|
||||
|
||||
def is_given(value: _T | NotGiven) -> TypeGuard[_T]:
|
||||
"""Check whether a value was explicitly provided.
|
||||
|
||||
Typically used when checking whether a parameter or field typed with
|
||||
Anthropic's ``NotGiven`` was set::
|
||||
|
||||
if is_given(system):
|
||||
...
|
||||
|
||||
Also acts as a type guard: inside a true branch, the value is narrowed
|
||||
to exclude ``NotGiven`` (e.g. ``str | NotGiven`` becomes ``str``).
|
||||
|
||||
Args:
|
||||
value: The value to check.
|
||||
|
||||
Returns:
|
||||
``True`` if *value* is anything other than ``NOT_GIVEN``.
|
||||
"""
|
||||
return not isinstance(value, NotGiven)
|
||||
|
||||
|
||||
class AnthropicLLMInvocationParams(TypedDict):
|
||||
"""Context-based parameters for invoking Anthropic's LLM API."""
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
"""OpenAI LLM adapter for Pipecat."""
|
||||
|
||||
from typing import Any, TypedDict
|
||||
from typing import Any, TypedDict, TypeGuard, TypeVar
|
||||
|
||||
from openai._types import NotGiven as OpenAINotGiven
|
||||
from openai.types.chat import (
|
||||
@@ -25,6 +25,31 @@ from pipecat.processors.aggregators.llm_context import (
|
||||
NotGiven,
|
||||
)
|
||||
|
||||
_T = TypeVar("_T")
|
||||
|
||||
|
||||
def is_given(value: _T | OpenAINotGiven) -> TypeGuard[_T]:
|
||||
"""Check whether a value was explicitly provided.
|
||||
|
||||
Typically used when checking whether a parameter or field typed with
|
||||
OpenAI's ``NotGiven`` was set::
|
||||
|
||||
if is_given(tool_choice):
|
||||
...
|
||||
|
||||
Also acts as a type guard: inside a true branch, the value is narrowed
|
||||
to exclude ``OpenAINotGiven`` (e.g.
|
||||
``ChatCompletionToolChoiceOptionParam | OpenAINotGiven`` becomes
|
||||
``ChatCompletionToolChoiceOptionParam``).
|
||||
|
||||
Args:
|
||||
value: The value to check.
|
||||
|
||||
Returns:
|
||||
``True`` if *value* is anything other than ``NOT_GIVEN``.
|
||||
"""
|
||||
return not isinstance(value, OpenAINotGiven)
|
||||
|
||||
|
||||
class OpenAILLMInvocationParams(TypedDict):
|
||||
"""Context-based parameters for invoking OpenAI ChatCompletion API."""
|
||||
|
||||
@@ -21,7 +21,7 @@ import io
|
||||
import wave
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, TypeAlias
|
||||
from typing import Any, TypeAlias, TypeGuard, TypeVar
|
||||
|
||||
from loguru import logger
|
||||
from openai._types import NOT_GIVEN as OPEN_AI_NOT_GIVEN
|
||||
@@ -36,16 +36,45 @@ from pipecat.adapters.schemas.tools_schema import ToolsSchema
|
||||
from pipecat.frames.frames import AudioRawFrame
|
||||
|
||||
# "Re-export" types from OpenAI that we're using as universal context types.
|
||||
# NOTE: if universal message types need to someday diverge from OpenAI's, we
|
||||
# should consider managing our own definitions. But we should do so carefully,
|
||||
# as the OpenAI messages are somewhat of a standard and we want to continue
|
||||
# supporting them.
|
||||
# NOTE: these are aliased to OpenAI's today, but callers should treat them as
|
||||
# LLMContext's own types — independent definitions that happen to coincide
|
||||
# with OpenAI's as an implementation detail. If universal context types need
|
||||
# to someday diverge from OpenAI's, we should consider managing our own
|
||||
# definitions (but with care, since OpenAI's types are somewhat of a standard
|
||||
# and we want to continue supporting them). In the meantime, code at the
|
||||
# LLMContext/OpenAI boundary should use explicit casts rather than rely on
|
||||
# the aliasing.
|
||||
LLMStandardMessage = ChatCompletionMessageParam
|
||||
LLMContextToolChoice = ChatCompletionToolChoiceOptionParam
|
||||
NOT_GIVEN = OPEN_AI_NOT_GIVEN
|
||||
NotGiven = OpenAINotGiven
|
||||
|
||||
|
||||
_T = TypeVar("_T")
|
||||
|
||||
|
||||
def is_given(value: _T | NotGiven) -> TypeGuard[_T]:
|
||||
"""Check whether a value was explicitly provided.
|
||||
|
||||
Typically used when checking whether a ``NotGiven``-valued field or
|
||||
parameter was set::
|
||||
|
||||
if is_given(context.tools):
|
||||
...
|
||||
|
||||
Also acts as a type guard: inside a true branch, the value is narrowed
|
||||
to exclude ``NotGiven`` (e.g. ``ToolsSchema | NotGiven`` becomes
|
||||
``ToolsSchema``).
|
||||
|
||||
Args:
|
||||
value: The value to check.
|
||||
|
||||
Returns:
|
||||
``True`` if *value* is anything other than ``NOT_GIVEN``.
|
||||
"""
|
||||
return not isinstance(value, NotGiven)
|
||||
|
||||
|
||||
@dataclass
|
||||
class LLMSpecificMessage:
|
||||
"""A container for a context message that is specific to a particular LLM service.
|
||||
|
||||
@@ -39,7 +39,7 @@ from __future__ import annotations
|
||||
import copy
|
||||
from collections.abc import Mapping
|
||||
from dataclasses import dataclass, field, fields
|
||||
from typing import TYPE_CHECKING, Any, ClassVar, TypeVar
|
||||
from typing import TYPE_CHECKING, Any, ClassVar, TypeGuard, TypeVar
|
||||
|
||||
from loguru import logger
|
||||
|
||||
@@ -88,7 +88,10 @@ Valid only in delta-mode settings objects. Must never appear in a service's
|
||||
"""
|
||||
|
||||
|
||||
def is_given(value: Any) -> bool:
|
||||
_T = TypeVar("_T")
|
||||
|
||||
|
||||
def is_given(value: _T | _NotGiven) -> TypeGuard[_T]:
|
||||
"""Check whether a delta field was explicitly provided.
|
||||
|
||||
Typically used when processing a delta to decide whether a field
|
||||
@@ -98,6 +101,10 @@ def is_given(value: Any) -> bool:
|
||||
# caller wants to change the voice
|
||||
...
|
||||
|
||||
Also acts as a type guard: inside a true branch, the value is narrowed
|
||||
to exclude ``_NotGiven`` (e.g. ``str | None | _NotGiven`` becomes
|
||||
``str | None``).
|
||||
|
||||
For store-mode objects this always returns ``True`` (since
|
||||
``validate_complete`` ensures no ``NOT_GIVEN`` fields remain).
|
||||
|
||||
|
||||
Reference in New Issue
Block a user