opperai 0.12.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. opperai/__init__.py +52 -0
  2. opperai/__version__.py +3 -0
  3. opperai/_client.py +85 -0
  4. opperai/core/_http_clients.py +112 -0
  5. opperai/core/datasets/__init__.py +0 -0
  6. opperai/core/datasets/_async_datasets.py +68 -0
  7. opperai/core/datasets/_datasets.py +183 -0
  8. opperai/core/functions/__init__.py +0 -0
  9. opperai/core/functions/_async_functions.py +346 -0
  10. opperai/core/functions/_functions.py +374 -0
  11. opperai/core/indexes/__init__.py +0 -0
  12. opperai/core/indexes/_async_indexes.py +309 -0
  13. opperai/core/indexes/_indexes.py +329 -0
  14. opperai/core/spans/__init__.py +7 -0
  15. opperai/core/spans/_async_spans.py +236 -0
  16. opperai/core/spans/_decorator.py +165 -0
  17. opperai/core/spans/_spans.py +226 -0
  18. opperai/core/utils/__init__.py +36 -0
  19. opperai/datasets/async_datasets.py +97 -0
  20. opperai/datasets/datasets.py +93 -0
  21. opperai/functions/async_functions.py +275 -0
  22. opperai/functions/decorator/__init__.py +0 -0
  23. opperai/functions/decorator/_decorator.py +232 -0
  24. opperai/functions/decorator/_schemas.py +104 -0
  25. opperai/functions/functions.py +311 -0
  26. opperai/indexes/async_indexes.py +93 -0
  27. opperai/indexes/indexes.py +93 -0
  28. opperai/spans/async_spans.py +145 -0
  29. opperai/spans/spans.py +139 -0
  30. opperai/types/__init__.py +153 -0
  31. opperai/types/datasets.py +27 -0
  32. opperai/types/exceptions.py +58 -0
  33. opperai/types/indexes.py +33 -0
  34. opperai/types/spans.py +44 -0
  35. opperai/types/validators.py +21 -0
  36. opperai-0.12.3.dist-info/METADATA +141 -0
  37. opperai-0.12.3.dist-info/RECORD +39 -0
  38. opperai-0.12.3.dist-info/WHEEL +5 -0
  39. opperai-0.12.3.dist-info/licenses/LICENSE +201 -0
opperai/__init__.py ADDED
@@ -0,0 +1,52 @@
1
+ # ruff: noqa: F401
2
+ from opperai.core.spans._decorator import start_span, trace
3
+ from opperai.functions.async_functions import AsyncFunctions
4
+ from opperai.functions.decorator._decorator import fn, get_last_span_id
5
+ from opperai.functions.functions import Functions
6
+ from opperai.indexes.async_indexes import AsyncIndexes
7
+ from opperai.indexes.indexes import Index, Indexes
8
+ from opperai.spans.async_spans import AsyncSpans
9
+ from opperai.spans.spans import Spans
10
+
11
+ from .__version__ import __version__
12
+ from ._client import AsyncClient, Client
13
+
14
+
15
+ class Opper:
16
+ client: Client = None
17
+
18
+ def __init__(self, client: Client = None, api_key: str = None):
19
+ if client is not None:
20
+ if not isinstance(client, Client):
21
+ raise ValueError("Client must be an instance of Client")
22
+ if api_key is not None:
23
+ client = Client(api_key=api_key)
24
+ if client is None:
25
+ client = Client()
26
+
27
+ self.client: Client = client
28
+ self.functions: Functions = Functions(client)
29
+ self.indexes: Indexes = Indexes(client)
30
+ self.spans: Spans = Spans(client) # deprecated
31
+ self.traces: Spans = self.spans
32
+ self.call = self.functions.call
33
+
34
+
35
+ class AsyncOpper(Opper):
36
+ client: AsyncClient = None
37
+
38
+ def __init__(self, client: AsyncClient = None, api_key: str = None):
39
+ if client is not None:
40
+ if not isinstance(client, AsyncClient):
41
+ raise ValueError("Client must be an instance of AsyncClient")
42
+ if api_key is not None:
43
+ client = AsyncClient(api_key=api_key)
44
+ if client is None:
45
+ client = AsyncClient()
46
+
47
+ self.client: AsyncClient = client
48
+ self.functions: AsyncFunctions = AsyncFunctions(client)
49
+ self.indexes: AsyncIndexes = AsyncIndexes(client)
50
+ self.spans: AsyncSpans = AsyncSpans(client) # deprecated
51
+ self.traces: AsyncSpans = self.spans
52
+ self.call = self.functions.call
opperai/__version__.py ADDED
@@ -0,0 +1,3 @@
1
+ from importlib.metadata import version
2
+
3
+ __version__ = version("opperai")
opperai/_client.py ADDED
@@ -0,0 +1,85 @@
1
+ import os
2
+
3
+ from opperai.core.datasets._async_datasets import AsyncDatasets
4
+ from opperai.core.datasets._datasets import Datasets
5
+ from opperai.core.functions._async_functions import AsyncFunctions
6
+ from opperai.core.functions._functions import Functions
7
+ from opperai.core.indexes._async_indexes import AsyncIndexes
8
+ from opperai.core.indexes._indexes import Indexes
9
+ from opperai.core.spans._async_spans import AsyncSpans
10
+ from opperai.core.spans._spans import Spans
11
+
12
+ from .core._http_clients import _async_http_client, _http_client
13
+
14
+ DEFAULT_API_URL = "https://api.opper.ai"
15
+ DEFAULT_TIMEOUT = 120
16
+
17
+
18
+ class AsyncClient:
19
+ functions: AsyncFunctions = None
20
+ indexes: AsyncIndexes = None
21
+ spans: AsyncSpans = None
22
+ datasets: AsyncDatasets = None
23
+
24
+ def __init__(
25
+ self,
26
+ api_key: str = None,
27
+ api_url: str = None,
28
+ default_model: str = None,
29
+ timeout: float = DEFAULT_TIMEOUT,
30
+ ):
31
+ if api_key is None:
32
+ api_key = os.getenv("OPPER_API_KEY")
33
+ if api_key is None:
34
+ raise Exception(
35
+ "API key is not provided and OPPER_API_KEY environment variable is not set."
36
+ )
37
+ if api_url is None:
38
+ api_url = os.getenv("OPPER_API_URL", DEFAULT_API_URL)
39
+ if default_model is None:
40
+ default_model = os.getenv("OPPER_DEFAULT_MODEL")
41
+
42
+ self.api_key = api_key
43
+ self.api_url = api_url
44
+ self.default_model = default_model
45
+
46
+ self.http_client = _async_http_client(api_key, api_url, timeout)
47
+ self.functions = AsyncFunctions(self.http_client, default_model=default_model)
48
+ self.indexes = AsyncIndexes(self.http_client)
49
+ self.spans = AsyncSpans(self.http_client)
50
+ self.datasets = AsyncDatasets(self.http_client)
51
+
52
+
53
+ class Client:
54
+ functions: Functions
55
+ indexes: Indexes
56
+ spans: Spans
57
+ datasets: Datasets
58
+
59
+ def __init__(
60
+ self,
61
+ api_key: str = None,
62
+ api_url: str = None,
63
+ default_model: str = None,
64
+ timeout: float = DEFAULT_TIMEOUT,
65
+ ):
66
+ if api_key is None:
67
+ api_key = os.getenv("OPPER_API_KEY")
68
+ if api_key is None:
69
+ raise Exception(
70
+ "API key is not provided and OPPER_API_KEY environment variable is not set."
71
+ )
72
+ if api_url is None:
73
+ api_url = os.getenv("OPPER_API_URL", DEFAULT_API_URL)
74
+ if default_model is None:
75
+ default_model = os.getenv("OPPER_DEFAULT_MODEL")
76
+
77
+ self.api_key = api_key
78
+ self.api_url = api_url
79
+ self.default_model = default_model
80
+
81
+ self.http_client = _http_client(api_key, api_url, timeout)
82
+ self.functions = Functions(self.http_client, default_model=default_model)
83
+ self.indexes = Indexes(self.http_client)
84
+ self.spans = Spans(self.http_client)
85
+ self.datasets = Datasets(self.http_client)
@@ -0,0 +1,112 @@
1
+ import json
2
+ from http import HTTPStatus
3
+
4
+ import httpx
5
+ from httpx_sse import aconnect_sse, connect_sse
6
+ from opperai import __version__
7
+ from opperai.types import Errors
8
+ from opperai.types.exceptions import (
9
+ ContentPolicyViolationError,
10
+ OpperTimeoutError,
11
+ RateLimitError,
12
+ RequestValidationError,
13
+ StructuredGenerationError,
14
+ )
15
+
16
+
17
+ def _prepare_error(response):
18
+ error = Errors.model_validate(response.json())
19
+ error = error.errors[0]
20
+ status_code = response.status_code
21
+
22
+ if status_code == HTTPStatus.BAD_REQUEST:
23
+ if error.type == "StructuredGenerationError":
24
+ raise StructuredGenerationError(error.message, error.detail)
25
+ if error.type == "ContentPolicyViolationError":
26
+ raise ContentPolicyViolationError(error.message, error.detail)
27
+ if status_code == HTTPStatus.UNPROCESSABLE_ENTITY:
28
+ if error.type == "RequestValidationError":
29
+ raise RequestValidationError(error.message, error.detail)
30
+ if status_code == HTTPStatus.TOO_MANY_REQUESTS:
31
+ if error.type == "RateLimitError":
32
+ raise RateLimitError(error.message, error.detail)
33
+
34
+
35
+ class _async_http_client:
36
+ def __init__(self, api_key: str, api_url: str, timeout: float):
37
+ self.session = httpx.AsyncClient(
38
+ base_url=api_url,
39
+ headers={
40
+ "X-OPPER-API-KEY": f"{api_key}",
41
+ "User-Agent": f"opper-python/{__version__}",
42
+ },
43
+ timeout=httpx.Timeout(timeout),
44
+ )
45
+
46
+ async def do_request(self, method: str, path: str, **kwargs):
47
+ try:
48
+ response = await self.session.request(method, path, **kwargs)
49
+ if response.status_code >= 400:
50
+ _prepare_error(response)
51
+ return response
52
+ except httpx.TimeoutException as e:
53
+ raise OpperTimeoutError(
54
+ "request timed out",
55
+ "The request to the opper api timed out.",
56
+ ) from e
57
+
58
+ async def stream(self, method: str, path: str, **kwargs):
59
+ try:
60
+ async with aconnect_sse(
61
+ self.session,
62
+ method,
63
+ path,
64
+ **kwargs,
65
+ ) as event_source:
66
+ async for sse in event_source.aiter_sse():
67
+ yield json.loads(sse.data)
68
+ except httpx.TimeoutException as e:
69
+ raise OpperTimeoutError(
70
+ "request timed out",
71
+ "The request to the opper api timed out.",
72
+ ) from e
73
+
74
+
75
+ class _http_client:
76
+ def __init__(self, api_key: str, api_url: str, timeout):
77
+ self.session = httpx.Client(
78
+ base_url=api_url,
79
+ headers={
80
+ "X-OPPER-API-KEY": f"{api_key}",
81
+ "User-Agent": f"opper-python/{__version__}",
82
+ },
83
+ timeout=httpx.Timeout(timeout),
84
+ )
85
+
86
+ def do_request(self, method: str, path: str, **kwargs):
87
+ try:
88
+ response = self.session.request(method, path, **kwargs)
89
+ if response.status_code >= 400:
90
+ _prepare_error(response)
91
+ return response
92
+ except httpx.TimeoutException as e:
93
+ raise OpperTimeoutError(
94
+ "request timed out",
95
+ "The request to the opper api timed out.",
96
+ ) from e
97
+
98
+ def stream(self, method: str, path: str, **kwargs):
99
+ try:
100
+ with connect_sse(
101
+ self.session,
102
+ method,
103
+ path,
104
+ **kwargs,
105
+ ) as event_source:
106
+ for sse in event_source.iter_sse():
107
+ yield json.loads(sse.data)
108
+ except httpx.TimeoutException as e:
109
+ raise OpperTimeoutError(
110
+ "request timed out",
111
+ "The request to the opper api timed out.",
112
+ ) from e
File without changes
@@ -0,0 +1,68 @@
1
+ from typing import List
2
+
3
+ from opperai.core._http_clients import _async_http_client
4
+ from opperai.types.datasets import (
5
+ DatasetEntry,
6
+ DatasetEntryResponse,
7
+ DatasetEntryUpdate,
8
+ )
9
+ from opperai.types.exceptions import APIError
10
+
11
+
12
+ class AsyncDatasets:
13
+ def __init__(self, http_client: _async_http_client):
14
+ self.http_client = http_client
15
+
16
+ async def add(self, dataset_uuid: str, entry: DatasetEntry) -> str:
17
+ response = await self.http_client.do_request(
18
+ "POST",
19
+ f"/v1/datasets/{dataset_uuid}",
20
+ json=entry.model_dump(),
21
+ )
22
+ if response.status_code != 200:
23
+ raise APIError(f"Failed to add entry with status {response.status_code}")
24
+ return response.json()
25
+
26
+ async def get_entries(
27
+ self, dataset_uuid: str, offset: int = 0, limit: int = 100
28
+ ) -> List[DatasetEntryResponse]:
29
+ response = await self.http_client.do_request(
30
+ "GET",
31
+ f"/v1/datasets/{dataset_uuid}/entries",
32
+ params={"offset": offset, "limit": limit},
33
+ )
34
+ if response.status_code != 200:
35
+ raise APIError(f"Failed to get entries with status {response.status_code}")
36
+ return [DatasetEntryResponse.model_validate(item) for item in response.json()]
37
+
38
+ async def get_entry(
39
+ self, dataset_uuid: str, entry_uuid: str
40
+ ) -> DatasetEntryResponse:
41
+ response = await self.http_client.do_request(
42
+ "GET",
43
+ f"/v1/datasets/{dataset_uuid}/entries/{entry_uuid}",
44
+ )
45
+ if response.status_code != 200:
46
+ raise APIError(f"Failed to get entry with status {response.status_code}")
47
+ return DatasetEntryResponse.model_validate(response.json())
48
+
49
+ async def update_entry(
50
+ self, dataset_uuid: str, entry_uuid: str, entry: DatasetEntryUpdate
51
+ ) -> DatasetEntryResponse:
52
+ response = await self.http_client.do_request(
53
+ "PUT",
54
+ f"/v1/datasets/{dataset_uuid}/entries/{entry_uuid}",
55
+ json=entry.model_dump(),
56
+ )
57
+ if response.status_code != 200:
58
+ raise APIError(f"Failed to update entry with status {response.status_code}")
59
+ return DatasetEntryResponse.model_validate(response.json())
60
+
61
+ async def delete_entry(self, dataset_uuid: str, entry_uuid: str) -> bool:
62
+ response = await self.http_client.do_request(
63
+ "DELETE",
64
+ f"/v1/datasets/{dataset_uuid}/entries/{entry_uuid}",
65
+ )
66
+ if response.status_code != 200:
67
+ raise APIError(f"Failed to delete entry with status {response.status_code}")
68
+ return response.json()
@@ -0,0 +1,183 @@
1
+ from typing import List
2
+
3
+ from opperai.core._http_clients import _http_client
4
+ from opperai.types.datasets import (
5
+ DatasetEntry,
6
+ DatasetEntryResponse,
7
+ DatasetEntryUpdate,
8
+ )
9
+ from opperai.types.exceptions import APIError
10
+
11
+
12
+ class Datasets:
13
+ def __init__(self, http_client: _http_client):
14
+ self.http_client = http_client
15
+
16
+ def add(self, dataset_uuid: str, entry: DatasetEntry) -> str:
17
+ """Add an entry to a dataset
18
+
19
+ This method adds an entry to a specified dataset by its unique identifier. The entry is added to the dataset, making it part of the dataset's collection.
20
+
21
+ Args:
22
+ dataset_uuid (str): The unique identifier of the dataset.
23
+ entry (DatasetEntrySchema): The entry to be added to the dataset.
24
+
25
+ Returns:
26
+ str: The unique identifier of the added entry.
27
+
28
+ Raises:
29
+ APIError: If the entry addition fails due to an API error.
30
+
31
+ Examples:
32
+ >>> from opperai import Client
33
+ >>> from opperai.types.datasets import DatasetEntrySchema
34
+ >>> client = Client(api_key="your_api_key_here")
35
+ >>> entry = DatasetEntrySchema(input="Example input", output="Example output")
36
+ >>> entry_uuid = client.datasets.add(dataset_uuid="123", entry=entry)
37
+ >>> print(entry_uuid)
38
+ "456"
39
+
40
+ """
41
+ response = self.http_client.do_request(
42
+ "POST",
43
+ f"/v1/datasets/{dataset_uuid}",
44
+ json=entry.model_dump(),
45
+ )
46
+ if response.status_code != 200:
47
+ raise APIError(f"Failed to add entry with status {response.status_code}")
48
+ return response.json()
49
+
50
+ def get_entries(
51
+ self, dataset_uuid: str, offset: int = 0, limit: int = 100
52
+ ) -> List[DatasetEntryResponse]:
53
+ """Get entries from a dataset
54
+
55
+ This method retrieves entries from a specified dataset by its unique identifier. The entries are returned based on the specified offset and limit.
56
+
57
+ Args:
58
+ dataset_uuid (str): The unique identifier of the dataset.
59
+ offset (int, optional): The offset for pagination. Defaults to 0.
60
+ limit (int, optional): The maximum number of entries to retrieve. Defaults to 100.
61
+
62
+ Returns:
63
+ List[DatasetEntryResponseSchema]: A list of DatasetEntryResponseSchema instances representing the entries in the dataset.
64
+
65
+ Raises:
66
+ APIError: If the retrieval fails due to an API error.
67
+
68
+ Examples:
69
+ >>> from opperai import Client
70
+ >>> client = Client(api_key="your_api_key_here")
71
+ >>> entries = client.datasets.get_entries(dataset_uuid="123", offset=0, limit=10)
72
+ >>> for entry in entries:
73
+ ... print(entry)
74
+ DatasetEntryResponseSchema(uuid='456', input='Example input', output='Example output', ...)
75
+
76
+ """
77
+ response = self.http_client.do_request(
78
+ "GET",
79
+ f"/v1/datasets/{dataset_uuid}/entries",
80
+ params={"offset": offset, "limit": limit},
81
+ )
82
+ if response.status_code != 200:
83
+ raise APIError(f"Failed to get entries with status {response.status_code}")
84
+ return [DatasetEntryResponse.model_validate(item) for item in response.json()]
85
+
86
+ def get_entry(self, dataset_uuid: str, entry_uuid: str) -> DatasetEntryResponse:
87
+ """Get a specific entry from a dataset
88
+
89
+ This method retrieves a specific entry from a specified dataset by its unique identifier.
90
+
91
+ Args:
92
+ dataset_uuid (str): The unique identifier of the dataset.
93
+ entry_uuid (str): The unique identifier of the entry.
94
+
95
+ Returns:
96
+ DatasetEntryResponseSchema: An instance of DatasetEntryResponseSchema representing the entry.
97
+
98
+ Raises:
99
+ APIError: If the retrieval fails due to an API error.
100
+
101
+ Examples:
102
+ >>> from opperai import Client
103
+ >>> client = Client(api_key="your_api_key_here")
104
+ >>> entry = client.datasets.get_entry(dataset_uuid="123", entry_uuid="456")
105
+ >>> print(entry)
106
+ DatasetEntryResponseSchema(uuid='456', input='Example input', output='Example output', ...)
107
+
108
+ """
109
+ response = self.http_client.do_request(
110
+ "GET",
111
+ f"/v1/datasets/{dataset_uuid}/entries/{entry_uuid}",
112
+ )
113
+ if response.status_code != 200:
114
+ raise APIError(f"Failed to get entry with status {response.status_code}")
115
+ return DatasetEntryResponse.model_validate(response.json())
116
+
117
+ def update_entry(
118
+ self, dataset_uuid: str, entry_uuid: str, entry: DatasetEntryUpdate
119
+ ) -> DatasetEntryResponse:
120
+ """Update a specific entry in a dataset
121
+
122
+ This method updates a specific entry in a specified dataset by its unique identifier.
123
+
124
+ Args:
125
+ dataset_uuid (str): The unique identifier of the dataset.
126
+ entry_uuid (str): The unique identifier of the entry.
127
+ entry (DatasetEntryUpdateSchema): The updated entry data.
128
+
129
+ Returns:
130
+ DatasetEntryResponseSchema: An instance of DatasetEntryResponseSchema representing the updated entry.
131
+
132
+ Raises:
133
+ APIError: If the update fails due to an API error.
134
+
135
+ Examples:
136
+ >>> from opperai import Client
137
+ >>> from opperai.types.datasets import DatasetEntryUpdateSchema
138
+ >>> client = Client(api_key="your_api_key_here")
139
+ >>> updated_entry = DatasetEntryUpdateSchema(input="Updated input", output="Updated output")
140
+ >>> entry = client.datasets.update_entry(dataset_uuid="123", entry_uuid="456", entry=updated_entry)
141
+ >>> print(entry)
142
+ DatasetEntryResponseSchema(uuid='456', input='Updated input', output='Updated output', ...)
143
+
144
+ """
145
+ response = self.http_client.do_request(
146
+ "PUT",
147
+ f"/v1/datasets/{dataset_uuid}/entries/{entry_uuid}",
148
+ json=entry.model_dump(),
149
+ )
150
+ if response.status_code != 200:
151
+ raise APIError(f"Failed to update entry with status {response.status_code}")
152
+ return DatasetEntryResponse.model_validate(response.json())
153
+
154
+ def delete_entry(self, dataset_uuid: str, entry_uuid: str) -> bool:
155
+ """Delete a specific entry from a dataset
156
+
157
+ This method deletes a specific entry from a specified dataset by its unique identifier.
158
+
159
+ Args:
160
+ dataset_uuid (str): The unique identifier of the dataset.
161
+ entry_uuid (str): The unique identifier of the entry.
162
+
163
+ Returns:
164
+ bool: True if the entry was successfully deleted, False otherwise.
165
+
166
+ Raises:
167
+ APIError: If the deletion fails due to an API error.
168
+
169
+ Examples:
170
+ >>> from opperai import Client
171
+ >>> client = Client(api_key="your_api_key_here")
172
+ >>> success = client.datasets.delete_entry(dataset_uuid="123", entry_uuid="456")
173
+ >>> print(success)
174
+ True
175
+
176
+ """
177
+ response = self.http_client.do_request(
178
+ "DELETE",
179
+ f"/v1/datasets/{dataset_uuid}/entries/{entry_uuid}",
180
+ )
181
+ if response.status_code != 200:
182
+ raise APIError(f"Failed to delete entry with status {response.status_code}")
183
+ return response.json()
File without changes