agent_search_gateway.providers.contracts
Provider-facing contracts and candidate value objects.
1"""Provider-facing contracts and candidate value objects.""" 2 3from collections.abc import Mapping, Sequence 4from dataclasses import dataclass 5from datetime import date 6from typing import Protocol, runtime_checkable 7 8from ..models import LLMInvocation, OAResolution 9from ..url_normalization import NormalizedURL 10 11ChatMessage = Mapping[str, str] 12 13 14@dataclass(frozen=True, slots=True) 15class KeywordSearchHit: 16 url: str 17 title: str = "" 18 snippet: str = "" 19 raw_content: str = "" 20 content: str = "" 21 22 23@dataclass(frozen=True, slots=True) 24class URLFetchCandidate: 25 raw_content: str 26 content: str = "" 27 28 29@dataclass(frozen=True, slots=True) 30class PaperSearchHit: 31 source: str 32 source_id: str 33 title: str 34 authors: tuple[str, ...] = () 35 abstract: str = "" 36 doi: str = "" 37 arxiv_id: str = "" 38 published_date: date | None = None 39 updated_date: date | None = None 40 url: str = "" 41 pdf_url: str = "" 42 venue: str = "" 43 topics: tuple[str, ...] = () 44 citation_count: int | None = None 45 is_open_access: bool | None = None 46 oa_status: str = "" 47 license: str = "" 48 49 50@dataclass(frozen=True, slots=True) 51class ProviderCapabilities: 52 search: bool 53 fetch: bool 54 55 56@runtime_checkable 57class KeywordSearchProvider(Protocol): 58 name: str 59 60 async def search(self, query: str) -> list[KeywordSearchHit]: ... 61 62 63@runtime_checkable 64class URLFetchProvider(Protocol): 65 name: str 66 67 async def fetch(self, url: NormalizedURL) -> URLFetchCandidate: ... 68 69 70@runtime_checkable 71class AcademicSearchProvider(Protocol): 72 name: str 73 74 async def search(self, query: str) -> list[PaperSearchHit]: ... 75 76 77@runtime_checkable 78class OAResolver(Protocol): 79 name: str 80 81 async def resolve(self, doi: str) -> OAResolution | None: ... 82 83 84@runtime_checkable 85class LLMClient(Protocol): 86 name: str 87 88 async def complete_text( 89 self, 90 invocation: LLMInvocation, 91 messages: Sequence[ChatMessage], 92 ) -> str: ... 93 94 async def complete_json( 95 self, 96 invocation: LLMInvocation, 97 messages: Sequence[ChatMessage], 98 ) -> Mapping[str, object]: ... 99 100 async def aclose(self) -> None: ...
15@dataclass(frozen=True, slots=True) 16class KeywordSearchHit: 17 url: str 18 title: str = "" 19 snippet: str = "" 20 raw_content: str = "" 21 content: str = ""
30@dataclass(frozen=True, slots=True) 31class PaperSearchHit: 32 source: str 33 source_id: str 34 title: str 35 authors: tuple[str, ...] = () 36 abstract: str = "" 37 doi: str = "" 38 arxiv_id: str = "" 39 published_date: date | None = None 40 updated_date: date | None = None 41 url: str = "" 42 pdf_url: str = "" 43 venue: str = "" 44 topics: tuple[str, ...] = () 45 citation_count: int | None = None 46 is_open_access: bool | None = None 47 oa_status: str = "" 48 license: str = ""
57@runtime_checkable 58class KeywordSearchProvider(Protocol): 59 name: str 60 61 async def search(self, query: str) -> list[KeywordSearchHit]: ...
Base class for protocol classes.
Protocol classes are defined as::
class Proto(Protocol):
def meth(self) -> int:
...
Such classes are primarily used with static type checkers that recognize structural subtyping (static duck-typing).
For example::
class C:
def meth(self) -> int:
return 0
def func(x: Proto) -> int:
return x.meth()
func(C()) # Passes static type check
See PEP 544 for details. Protocol classes decorated with @typing.runtime_checkable act as simple-minded runtime protocols that check only the presence of given attributes, ignoring their type signatures. Protocol classes can be generic, they are defined as::
class GenProto[T](Protocol):
def meth(self) -> T:
...
1739def _no_init_or_replace_init(self, *args, **kwargs): 1740 cls = type(self) 1741 1742 if cls._is_protocol: 1743 raise TypeError('Protocols cannot be instantiated') 1744 1745 # Already using a custom `__init__`. No need to calculate correct 1746 # `__init__` to call. This can lead to RecursionError. See bpo-45121. 1747 if cls.__init__ is not _no_init_or_replace_init: 1748 return 1749 1750 # Initially, `__init__` of a protocol subclass is set to `_no_init_or_replace_init`. 1751 # The first instantiation of the subclass will call `_no_init_or_replace_init` which 1752 # searches for a proper new `__init__` in the MRO. The new `__init__` 1753 # replaces the subclass' old `__init__` (ie `_no_init_or_replace_init`). Subsequent 1754 # instantiation of the protocol subclass will thus use the new 1755 # `__init__` and no longer call `_no_init_or_replace_init`. 1756 for base in cls.__mro__: 1757 init = base.__dict__.get('__init__', _no_init_or_replace_init) 1758 if init is not _no_init_or_replace_init: 1759 cls.__init__ = init 1760 break 1761 else: 1762 # should not happen 1763 cls.__init__ = object.__init__ 1764 1765 cls.__init__(self, *args, **kwargs)
61 async def search(self, query: str) -> list[KeywordSearchHit]: ...
64@runtime_checkable 65class URLFetchProvider(Protocol): 66 name: str 67 68 async def fetch(self, url: NormalizedURL) -> URLFetchCandidate: ...
Base class for protocol classes.
Protocol classes are defined as::
class Proto(Protocol):
def meth(self) -> int:
...
Such classes are primarily used with static type checkers that recognize structural subtyping (static duck-typing).
For example::
class C:
def meth(self) -> int:
return 0
def func(x: Proto) -> int:
return x.meth()
func(C()) # Passes static type check
See PEP 544 for details. Protocol classes decorated with @typing.runtime_checkable act as simple-minded runtime protocols that check only the presence of given attributes, ignoring their type signatures. Protocol classes can be generic, they are defined as::
class GenProto[T](Protocol):
def meth(self) -> T:
...
1739def _no_init_or_replace_init(self, *args, **kwargs): 1740 cls = type(self) 1741 1742 if cls._is_protocol: 1743 raise TypeError('Protocols cannot be instantiated') 1744 1745 # Already using a custom `__init__`. No need to calculate correct 1746 # `__init__` to call. This can lead to RecursionError. See bpo-45121. 1747 if cls.__init__ is not _no_init_or_replace_init: 1748 return 1749 1750 # Initially, `__init__` of a protocol subclass is set to `_no_init_or_replace_init`. 1751 # The first instantiation of the subclass will call `_no_init_or_replace_init` which 1752 # searches for a proper new `__init__` in the MRO. The new `__init__` 1753 # replaces the subclass' old `__init__` (ie `_no_init_or_replace_init`). Subsequent 1754 # instantiation of the protocol subclass will thus use the new 1755 # `__init__` and no longer call `_no_init_or_replace_init`. 1756 for base in cls.__mro__: 1757 init = base.__dict__.get('__init__', _no_init_or_replace_init) 1758 if init is not _no_init_or_replace_init: 1759 cls.__init__ = init 1760 break 1761 else: 1762 # should not happen 1763 cls.__init__ = object.__init__ 1764 1765 cls.__init__(self, *args, **kwargs)
68 async def fetch(self, url: NormalizedURL) -> URLFetchCandidate: ...
71@runtime_checkable 72class AcademicSearchProvider(Protocol): 73 name: str 74 75 async def search(self, query: str) -> list[PaperSearchHit]: ...
Base class for protocol classes.
Protocol classes are defined as::
class Proto(Protocol):
def meth(self) -> int:
...
Such classes are primarily used with static type checkers that recognize structural subtyping (static duck-typing).
For example::
class C:
def meth(self) -> int:
return 0
def func(x: Proto) -> int:
return x.meth()
func(C()) # Passes static type check
See PEP 544 for details. Protocol classes decorated with @typing.runtime_checkable act as simple-minded runtime protocols that check only the presence of given attributes, ignoring their type signatures. Protocol classes can be generic, they are defined as::
class GenProto[T](Protocol):
def meth(self) -> T:
...
1739def _no_init_or_replace_init(self, *args, **kwargs): 1740 cls = type(self) 1741 1742 if cls._is_protocol: 1743 raise TypeError('Protocols cannot be instantiated') 1744 1745 # Already using a custom `__init__`. No need to calculate correct 1746 # `__init__` to call. This can lead to RecursionError. See bpo-45121. 1747 if cls.__init__ is not _no_init_or_replace_init: 1748 return 1749 1750 # Initially, `__init__` of a protocol subclass is set to `_no_init_or_replace_init`. 1751 # The first instantiation of the subclass will call `_no_init_or_replace_init` which 1752 # searches for a proper new `__init__` in the MRO. The new `__init__` 1753 # replaces the subclass' old `__init__` (ie `_no_init_or_replace_init`). Subsequent 1754 # instantiation of the protocol subclass will thus use the new 1755 # `__init__` and no longer call `_no_init_or_replace_init`. 1756 for base in cls.__mro__: 1757 init = base.__dict__.get('__init__', _no_init_or_replace_init) 1758 if init is not _no_init_or_replace_init: 1759 cls.__init__ = init 1760 break 1761 else: 1762 # should not happen 1763 cls.__init__ = object.__init__ 1764 1765 cls.__init__(self, *args, **kwargs)
75 async def search(self, query: str) -> list[PaperSearchHit]: ...
78@runtime_checkable 79class OAResolver(Protocol): 80 name: str 81 82 async def resolve(self, doi: str) -> OAResolution | None: ...
Base class for protocol classes.
Protocol classes are defined as::
class Proto(Protocol):
def meth(self) -> int:
...
Such classes are primarily used with static type checkers that recognize structural subtyping (static duck-typing).
For example::
class C:
def meth(self) -> int:
return 0
def func(x: Proto) -> int:
return x.meth()
func(C()) # Passes static type check
See PEP 544 for details. Protocol classes decorated with @typing.runtime_checkable act as simple-minded runtime protocols that check only the presence of given attributes, ignoring their type signatures. Protocol classes can be generic, they are defined as::
class GenProto[T](Protocol):
def meth(self) -> T:
...
1739def _no_init_or_replace_init(self, *args, **kwargs): 1740 cls = type(self) 1741 1742 if cls._is_protocol: 1743 raise TypeError('Protocols cannot be instantiated') 1744 1745 # Already using a custom `__init__`. No need to calculate correct 1746 # `__init__` to call. This can lead to RecursionError. See bpo-45121. 1747 if cls.__init__ is not _no_init_or_replace_init: 1748 return 1749 1750 # Initially, `__init__` of a protocol subclass is set to `_no_init_or_replace_init`. 1751 # The first instantiation of the subclass will call `_no_init_or_replace_init` which 1752 # searches for a proper new `__init__` in the MRO. The new `__init__` 1753 # replaces the subclass' old `__init__` (ie `_no_init_or_replace_init`). Subsequent 1754 # instantiation of the protocol subclass will thus use the new 1755 # `__init__` and no longer call `_no_init_or_replace_init`. 1756 for base in cls.__mro__: 1757 init = base.__dict__.get('__init__', _no_init_or_replace_init) 1758 if init is not _no_init_or_replace_init: 1759 cls.__init__ = init 1760 break 1761 else: 1762 # should not happen 1763 cls.__init__ = object.__init__ 1764 1765 cls.__init__(self, *args, **kwargs)
82 async def resolve(self, doi: str) -> OAResolution | None: ...
85@runtime_checkable 86class LLMClient(Protocol): 87 name: str 88 89 async def complete_text( 90 self, 91 invocation: LLMInvocation, 92 messages: Sequence[ChatMessage], 93 ) -> str: ... 94 95 async def complete_json( 96 self, 97 invocation: LLMInvocation, 98 messages: Sequence[ChatMessage], 99 ) -> Mapping[str, object]: ... 100 101 async def aclose(self) -> None: ...
Base class for protocol classes.
Protocol classes are defined as::
class Proto(Protocol):
def meth(self) -> int:
...
Such classes are primarily used with static type checkers that recognize structural subtyping (static duck-typing).
For example::
class C:
def meth(self) -> int:
return 0
def func(x: Proto) -> int:
return x.meth()
func(C()) # Passes static type check
See PEP 544 for details. Protocol classes decorated with @typing.runtime_checkable act as simple-minded runtime protocols that check only the presence of given attributes, ignoring their type signatures. Protocol classes can be generic, they are defined as::
class GenProto[T](Protocol):
def meth(self) -> T:
...
1739def _no_init_or_replace_init(self, *args, **kwargs): 1740 cls = type(self) 1741 1742 if cls._is_protocol: 1743 raise TypeError('Protocols cannot be instantiated') 1744 1745 # Already using a custom `__init__`. No need to calculate correct 1746 # `__init__` to call. This can lead to RecursionError. See bpo-45121. 1747 if cls.__init__ is not _no_init_or_replace_init: 1748 return 1749 1750 # Initially, `__init__` of a protocol subclass is set to `_no_init_or_replace_init`. 1751 # The first instantiation of the subclass will call `_no_init_or_replace_init` which 1752 # searches for a proper new `__init__` in the MRO. The new `__init__` 1753 # replaces the subclass' old `__init__` (ie `_no_init_or_replace_init`). Subsequent 1754 # instantiation of the protocol subclass will thus use the new 1755 # `__init__` and no longer call `_no_init_or_replace_init`. 1756 for base in cls.__mro__: 1757 init = base.__dict__.get('__init__', _no_init_or_replace_init) 1758 if init is not _no_init_or_replace_init: 1759 cls.__init__ = init 1760 break 1761 else: 1762 # should not happen 1763 cls.__init__ = object.__init__ 1764 1765 cls.__init__(self, *args, **kwargs)