airia 0.1.20__tar.gz → 0.1.22__tar.gz
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.
- {airia-0.1.20 → airia-0.1.22}/PKG-INFO +1 -1
- {airia-0.1.20 → airia-0.1.22}/airia/client/_request_handler/base_request_handler.py +0 -4
- {airia-0.1.20 → airia-0.1.22}/airia/client/async_client.py +2 -0
- airia-0.1.22/airia/client/attachments/__init__.py +4 -0
- airia-0.1.22/airia/client/attachments/async_attachments.py +52 -0
- airia-0.1.22/airia/client/attachments/base_attachments.py +54 -0
- airia-0.1.22/airia/client/attachments/sync_attachments.py +52 -0
- {airia-0.1.20 → airia-0.1.22}/airia/client/pipeline_execution/async_pipeline_execution.py +60 -4
- {airia-0.1.20 → airia-0.1.22}/airia/client/pipeline_execution/base_pipeline_execution.py +18 -3
- {airia-0.1.20 → airia-0.1.22}/airia/client/pipeline_execution/sync_pipeline_execution.py +60 -4
- {airia-0.1.20 → airia-0.1.22}/airia/client/pipelines_config/sync_pipelines_config.py +1 -1
- {airia-0.1.20 → airia-0.1.22}/airia/client/sync_client.py +2 -0
- airia-0.1.22/airia/types/api/__init__.py +1 -0
- airia-0.1.22/airia/types/api/attachments/__init__.py +3 -0
- airia-0.1.22/airia/types/api/attachments/upload_file.py +17 -0
- {airia-0.1.20 → airia-0.1.22}/airia/types/api/pipeline_execution/_pipeline_execution.py +2 -2
- {airia-0.1.20 → airia-0.1.22}/airia/types/api/pipelines_config/export_pipeline_definition.py +28 -3
- {airia-0.1.20 → airia-0.1.22}/airia.egg-info/PKG-INFO +1 -1
- {airia-0.1.20 → airia-0.1.22}/airia.egg-info/SOURCES.txt +6 -0
- {airia-0.1.20 → airia-0.1.22}/pyproject.toml +1 -1
- airia-0.1.20/airia/types/api/__init__.py +0 -0
- {airia-0.1.20 → airia-0.1.22}/LICENSE +0 -0
- {airia-0.1.20 → airia-0.1.22}/README.md +0 -0
- {airia-0.1.20 → airia-0.1.22}/airia/__init__.py +0 -0
- {airia-0.1.20 → airia-0.1.22}/airia/client/__init__.py +0 -0
- {airia-0.1.20 → airia-0.1.22}/airia/client/_request_handler/__init__.py +0 -0
- {airia-0.1.20 → airia-0.1.22}/airia/client/_request_handler/async_request_handler.py +0 -0
- {airia-0.1.20 → airia-0.1.22}/airia/client/_request_handler/sync_request_handler.py +0 -0
- {airia-0.1.20 → airia-0.1.22}/airia/client/base_client.py +0 -0
- {airia-0.1.20 → airia-0.1.22}/airia/client/conversations/__init__.py +0 -0
- {airia-0.1.20 → airia-0.1.22}/airia/client/conversations/async_conversations.py +0 -0
- {airia-0.1.20 → airia-0.1.22}/airia/client/conversations/base_conversations.py +0 -0
- {airia-0.1.20 → airia-0.1.22}/airia/client/conversations/sync_conversations.py +0 -0
- {airia-0.1.20 → airia-0.1.22}/airia/client/data_vector_search/__init__.py +0 -0
- {airia-0.1.20 → airia-0.1.22}/airia/client/data_vector_search/async_data_vector_search.py +0 -0
- {airia-0.1.20 → airia-0.1.22}/airia/client/data_vector_search/base_data_vector_search.py +0 -0
- {airia-0.1.20 → airia-0.1.22}/airia/client/data_vector_search/sync_data_vector_search.py +0 -0
- {airia-0.1.20 → airia-0.1.22}/airia/client/deployments/__init__.py +0 -0
- {airia-0.1.20 → airia-0.1.22}/airia/client/deployments/async_deployments.py +0 -0
- {airia-0.1.20 → airia-0.1.22}/airia/client/deployments/base_deployments.py +0 -0
- {airia-0.1.20 → airia-0.1.22}/airia/client/deployments/sync_deployments.py +0 -0
- {airia-0.1.20 → airia-0.1.22}/airia/client/pipeline_execution/__init__.py +0 -0
- {airia-0.1.20 → airia-0.1.22}/airia/client/pipelines_config/__init__.py +0 -0
- {airia-0.1.20 → airia-0.1.22}/airia/client/pipelines_config/async_pipelines_config.py +0 -0
- {airia-0.1.20 → airia-0.1.22}/airia/client/pipelines_config/base_pipelines_config.py +0 -0
- {airia-0.1.20 → airia-0.1.22}/airia/client/project/__init__.py +0 -0
- {airia-0.1.20 → airia-0.1.22}/airia/client/project/async_project.py +0 -0
- {airia-0.1.20 → airia-0.1.22}/airia/client/project/base_project.py +0 -0
- {airia-0.1.20 → airia-0.1.22}/airia/client/project/sync_project.py +0 -0
- {airia-0.1.20 → airia-0.1.22}/airia/client/store/__init__.py +0 -0
- {airia-0.1.20 → airia-0.1.22}/airia/client/store/async_store.py +0 -0
- {airia-0.1.20 → airia-0.1.22}/airia/client/store/base_store.py +0 -0
- {airia-0.1.20 → airia-0.1.22}/airia/client/store/sync_store.py +0 -0
- {airia-0.1.20 → airia-0.1.22}/airia/constants.py +0 -0
- {airia-0.1.20 → airia-0.1.22}/airia/exceptions.py +0 -0
- {airia-0.1.20 → airia-0.1.22}/airia/logs.py +0 -0
- {airia-0.1.20 → airia-0.1.22}/airia/types/__init__.py +0 -0
- {airia-0.1.20 → airia-0.1.22}/airia/types/_api_version.py +0 -0
- {airia-0.1.20 → airia-0.1.22}/airia/types/_request_data.py +0 -0
- {airia-0.1.20 → airia-0.1.22}/airia/types/api/conversations/__init__.py +0 -0
- {airia-0.1.20 → airia-0.1.22}/airia/types/api/conversations/_conversations.py +0 -0
- {airia-0.1.20 → airia-0.1.22}/airia/types/api/data_vector_search/__init__.py +0 -0
- {airia-0.1.20 → airia-0.1.22}/airia/types/api/data_vector_search/get_file_chunks.py +0 -0
- {airia-0.1.20 → airia-0.1.22}/airia/types/api/deployments/__init__.py +0 -0
- {airia-0.1.20 → airia-0.1.22}/airia/types/api/deployments/get_deployment.py +0 -0
- {airia-0.1.20 → airia-0.1.22}/airia/types/api/deployments/get_deployments.py +0 -0
- {airia-0.1.20 → airia-0.1.22}/airia/types/api/pipeline_execution/__init__.py +0 -0
- {airia-0.1.20 → airia-0.1.22}/airia/types/api/pipelines_config/__init__.py +0 -0
- {airia-0.1.20 → airia-0.1.22}/airia/types/api/pipelines_config/get_pipeline_config.py +0 -0
- {airia-0.1.20 → airia-0.1.22}/airia/types/api/pipelines_config/get_pipelines_config.py +0 -0
- {airia-0.1.20 → airia-0.1.22}/airia/types/api/project/__init__.py +0 -0
- {airia-0.1.20 → airia-0.1.22}/airia/types/api/project/get_projects.py +0 -0
- {airia-0.1.20 → airia-0.1.22}/airia/types/api/store/__init__.py +0 -0
- {airia-0.1.20 → airia-0.1.22}/airia/types/api/store/get_file.py +0 -0
- {airia-0.1.20 → airia-0.1.22}/airia/types/api/store/get_files.py +0 -0
- {airia-0.1.20 → airia-0.1.22}/airia/types/sse/__init__.py +0 -0
- {airia-0.1.20 → airia-0.1.22}/airia/types/sse/sse_messages.py +0 -0
- {airia-0.1.20 → airia-0.1.22}/airia/utils/sse_parser.py +0 -0
- {airia-0.1.20 → airia-0.1.22}/airia.egg-info/dependency_links.txt +0 -0
- {airia-0.1.20 → airia-0.1.22}/airia.egg-info/requires.txt +0 -0
- {airia-0.1.20 → airia-0.1.22}/airia.egg-info/top_level.txt +0 -0
- {airia-0.1.20 → airia-0.1.22}/setup.cfg +0 -0
|
@@ -82,10 +82,6 @@ class BaseRequestHandler:
|
|
|
82
82
|
|
|
83
83
|
# Process payload for logging
|
|
84
84
|
log_payload = payload.copy() if payload is not None else {}
|
|
85
|
-
if "images" in log_payload and log_payload["images"] is not None:
|
|
86
|
-
log_payload["images"] = f"{len(log_payload['images'])} images"
|
|
87
|
-
if "files" in log_payload and log_payload["files"] is not None:
|
|
88
|
-
log_payload["files"] = f"{len(log_payload['files'])} files"
|
|
89
85
|
log_payload = json.dumps(log_payload)
|
|
90
86
|
|
|
91
87
|
self.logger.info(
|
|
@@ -9,6 +9,7 @@ from ..constants import (
|
|
|
9
9
|
DEFAULT_TIMEOUT,
|
|
10
10
|
)
|
|
11
11
|
from ._request_handler import AsyncRequestHandler
|
|
12
|
+
from .attachments import AsyncAttachments
|
|
12
13
|
from .base_client import AiriaBaseClient
|
|
13
14
|
from .conversations import AsyncConversations
|
|
14
15
|
from .data_vector_search import AsyncDataVectorSearch
|
|
@@ -59,6 +60,7 @@ class AiriaAsyncClient(AiriaBaseClient):
|
|
|
59
60
|
bearer_token=self.bearer_token,
|
|
60
61
|
log_requests=self.log_requests,
|
|
61
62
|
)
|
|
63
|
+
self.attachments = AsyncAttachments(self._request_handler)
|
|
62
64
|
self.pipeline_execution = AsyncPipelineExecution(self._request_handler)
|
|
63
65
|
self.pipelines_config = AsyncPipelinesConfig(self._request_handler)
|
|
64
66
|
self.project = AsyncProject(self._request_handler)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
from ...types._api_version import ApiVersion
|
|
4
|
+
from ...types.api.attachments import AttachmentResponse
|
|
5
|
+
from .._request_handler import AsyncRequestHandler
|
|
6
|
+
from .base_attachments import BaseAttachments
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class AsyncAttachments(BaseAttachments):
|
|
10
|
+
def __init__(self, request_handler: AsyncRequestHandler):
|
|
11
|
+
super().__init__(request_handler)
|
|
12
|
+
|
|
13
|
+
async def upload_file(
|
|
14
|
+
self,
|
|
15
|
+
file_path: str,
|
|
16
|
+
correlation_id: Optional[str] = None,
|
|
17
|
+
) -> AttachmentResponse:
|
|
18
|
+
"""
|
|
19
|
+
Upload a file and get attachment information.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
file_path: Path to the file on disk
|
|
23
|
+
correlation_id: Optional correlation ID for request tracing. If not provided,
|
|
24
|
+
one will be generated automatically.
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
AttachmentResponse: Response containing the attachment ID and URL.
|
|
28
|
+
|
|
29
|
+
Raises:
|
|
30
|
+
AiriaAPIError: If the API request fails with details about the error.
|
|
31
|
+
aiohttp.ClientError: For other request-related errors.
|
|
32
|
+
|
|
33
|
+
Example:
|
|
34
|
+
```python
|
|
35
|
+
async_client = AiriaAsyncClient(api_key="your_api_key")
|
|
36
|
+
|
|
37
|
+
# Upload a file
|
|
38
|
+
response = await async_client.attachments.upload_file(
|
|
39
|
+
file_path="example.jpg"
|
|
40
|
+
)
|
|
41
|
+
print(f"Uploaded attachment ID: {response.id}")
|
|
42
|
+
print(f"Attachment URL: {response.image_url}")
|
|
43
|
+
```
|
|
44
|
+
"""
|
|
45
|
+
request_data = self._pre_upload_file(
|
|
46
|
+
file_path=file_path,
|
|
47
|
+
correlation_id=correlation_id,
|
|
48
|
+
api_version=ApiVersion.V1.value,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
resp = await self._request_handler.make_request_multipart("POST", request_data)
|
|
52
|
+
return AttachmentResponse(**resp)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from mimetypes import guess_type
|
|
3
|
+
from typing import Optional, Union
|
|
4
|
+
from urllib.parse import urljoin
|
|
5
|
+
|
|
6
|
+
from ...types._api_version import ApiVersion
|
|
7
|
+
from .._request_handler import AsyncRequestHandler, RequestHandler
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class BaseAttachments:
|
|
11
|
+
def __init__(self, request_handler: Union[RequestHandler, AsyncRequestHandler]):
|
|
12
|
+
self._request_handler = request_handler
|
|
13
|
+
|
|
14
|
+
def _pre_upload_file(
|
|
15
|
+
self,
|
|
16
|
+
file_path: str,
|
|
17
|
+
correlation_id: Optional[str] = None,
|
|
18
|
+
api_version: str = ApiVersion.V1.value,
|
|
19
|
+
):
|
|
20
|
+
"""
|
|
21
|
+
Prepare request data for file upload endpoint.
|
|
22
|
+
|
|
23
|
+
This internal method constructs the URL and files for file upload
|
|
24
|
+
requests, validating the API version and preparing all request components.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
file_path: Path to the file on disk
|
|
28
|
+
correlation_id: Optional correlation ID for tracing
|
|
29
|
+
api_version: API version to use for the request
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
RequestData: Prepared request data for the file upload endpoint
|
|
33
|
+
|
|
34
|
+
Raises:
|
|
35
|
+
ValueError: If an invalid API version is provided
|
|
36
|
+
"""
|
|
37
|
+
if api_version not in ApiVersion.as_list():
|
|
38
|
+
raise ValueError(
|
|
39
|
+
f"Invalid API version: {api_version}. Valid versions are: {', '.join(ApiVersion.as_list())}"
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
url = urljoin(
|
|
43
|
+
self._request_handler.base_url,
|
|
44
|
+
f"{api_version}/upload",
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
filename = os.path.basename(file_path)
|
|
48
|
+
files = {"file": (filename, open(file_path, "rb"), guess_type(file_path)[0])}
|
|
49
|
+
|
|
50
|
+
request_data = self._request_handler.prepare_request(
|
|
51
|
+
url=url, payload={}, files=files, correlation_id=correlation_id
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
return request_data
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
from ...types._api_version import ApiVersion
|
|
4
|
+
from ...types.api.attachments import AttachmentResponse
|
|
5
|
+
from .._request_handler import RequestHandler
|
|
6
|
+
from .base_attachments import BaseAttachments
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Attachments(BaseAttachments):
|
|
10
|
+
def __init__(self, request_handler: RequestHandler):
|
|
11
|
+
super().__init__(request_handler)
|
|
12
|
+
|
|
13
|
+
def upload_file(
|
|
14
|
+
self,
|
|
15
|
+
file_path: str,
|
|
16
|
+
correlation_id: Optional[str] = None,
|
|
17
|
+
) -> AttachmentResponse:
|
|
18
|
+
"""
|
|
19
|
+
Upload a file and get attachment information.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
file_path: Path to the file on disk
|
|
23
|
+
correlation_id: Optional correlation ID for request tracing. If not provided,
|
|
24
|
+
one will be generated automatically.
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
AttachmentResponse: Response containing the attachment ID and URL.
|
|
28
|
+
|
|
29
|
+
Raises:
|
|
30
|
+
AiriaAPIError: If the API request fails with details about the error.
|
|
31
|
+
requests.RequestException: For other request-related errors.
|
|
32
|
+
|
|
33
|
+
Example:
|
|
34
|
+
```python
|
|
35
|
+
client = AiriaClient(api_key="your_api_key")
|
|
36
|
+
|
|
37
|
+
# Upload a file
|
|
38
|
+
response = client.attachments.upload_file(
|
|
39
|
+
file_path="example.jpg"
|
|
40
|
+
)
|
|
41
|
+
print(f"Uploaded attachment ID: {response.id}")
|
|
42
|
+
print(f"Attachment URL: {response.image_url}")
|
|
43
|
+
```
|
|
44
|
+
"""
|
|
45
|
+
request_data = self._pre_upload_file(
|
|
46
|
+
file_path=file_path,
|
|
47
|
+
correlation_id=correlation_id,
|
|
48
|
+
api_version=ApiVersion.V1.value,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
resp = self._request_handler.make_request_multipart("POST", request_data)
|
|
52
|
+
return AttachmentResponse(**resp)
|
|
@@ -14,6 +14,51 @@ class AsyncPipelineExecution(BasePipelineExecution):
|
|
|
14
14
|
def __init__(self, request_handler: AsyncRequestHandler):
|
|
15
15
|
super().__init__(request_handler)
|
|
16
16
|
|
|
17
|
+
async def _upload_files(
|
|
18
|
+
self, files: List[str], images: List[str]
|
|
19
|
+
) -> tuple[List[str], List[str]]:
|
|
20
|
+
"""
|
|
21
|
+
Upload files and images synchronously and return their URLs.
|
|
22
|
+
URLs are passed through directly, local paths are uploaded first.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
files: List of file paths or URLs
|
|
26
|
+
images: List of image file paths or URLs
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
Tuple of (file_urls, image_urls)
|
|
30
|
+
"""
|
|
31
|
+
from ..attachments.async_attachments import AsyncAttachments
|
|
32
|
+
|
|
33
|
+
attachments_client = AsyncAttachments(self._request_handler)
|
|
34
|
+
file_urls = None
|
|
35
|
+
image_urls = None
|
|
36
|
+
|
|
37
|
+
if files:
|
|
38
|
+
file_urls = []
|
|
39
|
+
for file_path in files:
|
|
40
|
+
if self._is_local_path(file_path):
|
|
41
|
+
# Local file - upload it
|
|
42
|
+
response = await attachments_client.upload_file(file_path)
|
|
43
|
+
file_urls.append(response.image_url)
|
|
44
|
+
else:
|
|
45
|
+
# URL - use directly
|
|
46
|
+
file_urls.append(file_path)
|
|
47
|
+
|
|
48
|
+
if images:
|
|
49
|
+
image_urls = []
|
|
50
|
+
for image_path in images:
|
|
51
|
+
if self._is_local_path(image_path):
|
|
52
|
+
# Local file - upload it
|
|
53
|
+
response = await attachments_client.upload_file(image_path)
|
|
54
|
+
if response.image_url:
|
|
55
|
+
image_urls.append(response.image_url)
|
|
56
|
+
else:
|
|
57
|
+
# URL - use directly
|
|
58
|
+
image_urls.append(image_path)
|
|
59
|
+
|
|
60
|
+
return file_urls, image_urls
|
|
61
|
+
|
|
17
62
|
@overload
|
|
18
63
|
async def execute_pipeline(
|
|
19
64
|
self,
|
|
@@ -119,8 +164,8 @@ class AsyncPipelineExecution(BasePipelineExecution):
|
|
|
119
164
|
conversation_id: Optional conversation ID (guid).
|
|
120
165
|
async_output: Whether to stream the response. Default is False.
|
|
121
166
|
include_tools_response: Whether to return the initial LLM tool result. Default is False.
|
|
122
|
-
images: Optional list of
|
|
123
|
-
files: Optional list of
|
|
167
|
+
images: Optional list of image file paths or URLs.
|
|
168
|
+
files: Optional list of file paths or URLs.
|
|
124
169
|
data_source_folders: Optional data source folders information.
|
|
125
170
|
data_source_files: Optional data source files information.
|
|
126
171
|
in_memory_messages: Optional list of in-memory messages, each with a role and message.
|
|
@@ -149,6 +194,17 @@ class AsyncPipelineExecution(BasePipelineExecution):
|
|
|
149
194
|
print(response.result)
|
|
150
195
|
```
|
|
151
196
|
"""
|
|
197
|
+
# Validate user_input parameter
|
|
198
|
+
if not user_input:
|
|
199
|
+
raise ValueError("user_input cannot be empty")
|
|
200
|
+
|
|
201
|
+
# Handle file and image uploads (local files are uploaded, URLs are passed through)
|
|
202
|
+
image_urls = None
|
|
203
|
+
file_urls = None
|
|
204
|
+
|
|
205
|
+
if images or files:
|
|
206
|
+
file_urls, image_urls = await self._upload_files(files or [], images or [])
|
|
207
|
+
|
|
152
208
|
request_data = self._pre_execute_pipeline(
|
|
153
209
|
pipeline_id=pipeline_id,
|
|
154
210
|
user_input=user_input,
|
|
@@ -157,8 +213,8 @@ class AsyncPipelineExecution(BasePipelineExecution):
|
|
|
157
213
|
conversation_id=conversation_id,
|
|
158
214
|
async_output=async_output,
|
|
159
215
|
include_tools_response=include_tools_response,
|
|
160
|
-
images=
|
|
161
|
-
files=
|
|
216
|
+
images=image_urls,
|
|
217
|
+
files=file_urls,
|
|
162
218
|
data_source_folders=data_source_folders,
|
|
163
219
|
data_source_files=data_source_files,
|
|
164
220
|
in_memory_messages=in_memory_messages,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from typing import Any, Dict, List, Optional, Union
|
|
2
|
-
from urllib.parse import urljoin
|
|
2
|
+
from urllib.parse import urljoin, urlparse
|
|
3
3
|
|
|
4
4
|
from ...types._api_version import ApiVersion
|
|
5
5
|
from .._request_handler import AsyncRequestHandler, RequestHandler
|
|
@@ -9,6 +9,20 @@ class BasePipelineExecution:
|
|
|
9
9
|
def __init__(self, request_handler: Union[RequestHandler, AsyncRequestHandler]):
|
|
10
10
|
self._request_handler = request_handler
|
|
11
11
|
|
|
12
|
+
def _is_local_path(self, path: str) -> bool:
|
|
13
|
+
"""
|
|
14
|
+
Check if a given path is a local file path or a URL.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
path: The path to check
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
True if it's a local file path, False if it's a URL
|
|
21
|
+
"""
|
|
22
|
+
parsed = urlparse(path)
|
|
23
|
+
# If it has a scheme (http, https, ftp, etc.) and a netloc, it's a URL
|
|
24
|
+
return not (parsed.scheme and parsed.netloc)
|
|
25
|
+
|
|
12
26
|
def _pre_execute_pipeline(
|
|
13
27
|
self,
|
|
14
28
|
pipeline_id: str,
|
|
@@ -45,8 +59,8 @@ class BasePipelineExecution:
|
|
|
45
59
|
conversation_id: Optional conversation identifier
|
|
46
60
|
async_output: Whether to enable streaming output
|
|
47
61
|
include_tools_response: Whether to include tool responses
|
|
48
|
-
images: Optional list of
|
|
49
|
-
files: Optional list of
|
|
62
|
+
images: Optional list of image URLs
|
|
63
|
+
files: Optional list of file URLs
|
|
50
64
|
data_source_folders: Optional data source folder configuration
|
|
51
65
|
data_source_files: Optional data source files configuration
|
|
52
66
|
in_memory_messages: Optional list of in-memory messages
|
|
@@ -68,6 +82,7 @@ class BasePipelineExecution:
|
|
|
68
82
|
raise ValueError(
|
|
69
83
|
f"Invalid API version: {api_version}. Valid versions are: {', '.join(ApiVersion.as_list())}"
|
|
70
84
|
)
|
|
85
|
+
|
|
71
86
|
url = urljoin(
|
|
72
87
|
self._request_handler.base_url,
|
|
73
88
|
f"{api_version}/PipelineExecution/{pipeline_id}",
|
|
@@ -13,6 +13,51 @@ from .base_pipeline_execution import BasePipelineExecution
|
|
|
13
13
|
class PipelineExecution(BasePipelineExecution):
|
|
14
14
|
def __init__(self, request_handler: RequestHandler):
|
|
15
15
|
super().__init__(request_handler)
|
|
16
|
+
|
|
17
|
+
def _upload_files(
|
|
18
|
+
self, files: List[str], images: List[str]
|
|
19
|
+
) -> tuple[List[str], List[str]]:
|
|
20
|
+
"""
|
|
21
|
+
Upload files and images synchronously and return their URLs.
|
|
22
|
+
URLs are passed through directly, local paths are uploaded first.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
files: List of file paths or URLs
|
|
26
|
+
images: List of image file paths or URLs
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
Tuple of (file_urls, image_urls)
|
|
30
|
+
"""
|
|
31
|
+
from ..attachments.sync_attachments import Attachments
|
|
32
|
+
|
|
33
|
+
attachments_client = Attachments(self._request_handler)
|
|
34
|
+
file_urls = None
|
|
35
|
+
image_urls = None
|
|
36
|
+
|
|
37
|
+
if files:
|
|
38
|
+
file_urls = []
|
|
39
|
+
for file_path in files:
|
|
40
|
+
if self._is_local_path(file_path):
|
|
41
|
+
# Local file - upload it
|
|
42
|
+
response = attachments_client.upload_file(file_path)
|
|
43
|
+
file_urls.append(response.image_url)
|
|
44
|
+
else:
|
|
45
|
+
# URL - use directly
|
|
46
|
+
file_urls.append(file_path)
|
|
47
|
+
|
|
48
|
+
if images:
|
|
49
|
+
image_urls = []
|
|
50
|
+
for image_path in images:
|
|
51
|
+
if self._is_local_path(image_path):
|
|
52
|
+
# Local file - upload it
|
|
53
|
+
response = attachments_client.upload_file(image_path)
|
|
54
|
+
if response.image_url:
|
|
55
|
+
image_urls.append(response.image_url)
|
|
56
|
+
else:
|
|
57
|
+
# URL - use directly
|
|
58
|
+
image_urls.append(image_path)
|
|
59
|
+
|
|
60
|
+
return file_urls, image_urls
|
|
16
61
|
|
|
17
62
|
@overload
|
|
18
63
|
def execute_pipeline(
|
|
@@ -119,8 +164,8 @@ class PipelineExecution(BasePipelineExecution):
|
|
|
119
164
|
conversation_id: Optional conversation ID (guid).
|
|
120
165
|
async_output: Whether to stream the response. Default is False.
|
|
121
166
|
include_tools_response: Whether to return the initial LLM tool result. Default is False.
|
|
122
|
-
images: Optional list of
|
|
123
|
-
files: Optional list of
|
|
167
|
+
images: Optional list of image file paths or URLs.
|
|
168
|
+
files: Optional list of file paths or URLs.
|
|
124
169
|
data_source_folders: Optional data source folders information.
|
|
125
170
|
data_source_files: Optional data source files information.
|
|
126
171
|
in_memory_messages: Optional list of in-memory messages, each with a role and message.
|
|
@@ -149,6 +194,17 @@ class PipelineExecution(BasePipelineExecution):
|
|
|
149
194
|
print(response.result)
|
|
150
195
|
```
|
|
151
196
|
"""
|
|
197
|
+
# Validate user_input parameter
|
|
198
|
+
if not user_input:
|
|
199
|
+
raise ValueError("user_input cannot be empty")
|
|
200
|
+
|
|
201
|
+
# Handle file and image uploads (local files are uploaded, URLs are passed through)
|
|
202
|
+
image_urls = None
|
|
203
|
+
file_urls = None
|
|
204
|
+
|
|
205
|
+
if images or files:
|
|
206
|
+
file_urls, image_urls = self._upload_files(files or [], images or [])
|
|
207
|
+
|
|
152
208
|
request_data = self._pre_execute_pipeline(
|
|
153
209
|
pipeline_id=pipeline_id,
|
|
154
210
|
user_input=user_input,
|
|
@@ -157,8 +213,8 @@ class PipelineExecution(BasePipelineExecution):
|
|
|
157
213
|
conversation_id=conversation_id,
|
|
158
214
|
async_output=async_output,
|
|
159
215
|
include_tools_response=include_tools_response,
|
|
160
|
-
images=
|
|
161
|
-
files=
|
|
216
|
+
images=image_urls,
|
|
217
|
+
files=file_urls,
|
|
162
218
|
data_source_folders=data_source_folders,
|
|
163
219
|
data_source_files=data_source_files,
|
|
164
220
|
in_memory_messages=in_memory_messages,
|
|
@@ -133,7 +133,7 @@ class PipelinesConfig(BasePipelinesConfig):
|
|
|
133
133
|
|
|
134
134
|
This method fetches a list of pipeline configurations including their
|
|
135
135
|
deployment details, execution statistics, version information, and metadata.
|
|
136
|
-
The results can be filtered by project ID to retrieve only pipelines
|
|
136
|
+
The results can be filtered by project ID to retrieve only pipelines
|
|
137
137
|
belonging to a specific project.
|
|
138
138
|
|
|
139
139
|
Args:
|
|
@@ -9,6 +9,7 @@ from ..constants import (
|
|
|
9
9
|
DEFAULT_TIMEOUT,
|
|
10
10
|
)
|
|
11
11
|
from ._request_handler import RequestHandler
|
|
12
|
+
from .attachments import Attachments
|
|
12
13
|
from .base_client import AiriaBaseClient
|
|
13
14
|
from .conversations import Conversations
|
|
14
15
|
from .data_vector_search import DataVectorSearch
|
|
@@ -59,6 +60,7 @@ class AiriaClient(AiriaBaseClient):
|
|
|
59
60
|
bearer_token=self.bearer_token,
|
|
60
61
|
log_requests=self.log_requests,
|
|
61
62
|
)
|
|
63
|
+
self.attachments = Attachments(self._request_handler)
|
|
62
64
|
self.pipeline_execution = PipelineExecution(self._request_handler)
|
|
63
65
|
self.pipelines_config = PipelinesConfig(self._request_handler)
|
|
64
66
|
self.project = Project(self._request_handler)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from . import attachments as attachments
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, Field
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class AttachmentResponse(BaseModel):
|
|
7
|
+
"""Response model for uploading an attachment file.
|
|
8
|
+
|
|
9
|
+
This class conveys the unique identifier and URL of the uploaded attachment.
|
|
10
|
+
|
|
11
|
+
Attributes:
|
|
12
|
+
id: The unique identifier of the attachment
|
|
13
|
+
image_url: The URL of the attachment
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
id: Optional[str] = None
|
|
17
|
+
image_url: Optional[str] = Field(None, alias="imageUrl")
|
|
@@ -5,7 +5,7 @@ This module defines the response models returned by pipeline execution endpoints
|
|
|
5
5
|
including both synchronous and streaming response types.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
from typing import Any, AsyncIterator, Dict, Iterator
|
|
8
|
+
from typing import Any, AsyncIterator, Dict, Iterator, Optional
|
|
9
9
|
|
|
10
10
|
from pydantic import BaseModel, ConfigDict, Field
|
|
11
11
|
|
|
@@ -41,7 +41,7 @@ class PipelineExecutionDebugResponse(BaseModel):
|
|
|
41
41
|
is_backup_pipeline: Whether a backup pipeline was used for execution
|
|
42
42
|
"""
|
|
43
43
|
|
|
44
|
-
result: str
|
|
44
|
+
result: Optional[str]
|
|
45
45
|
report: Dict[str, Any]
|
|
46
46
|
is_backup_pipeline: bool = Field(alias="isBackupPipeline")
|
|
47
47
|
|
{airia-0.1.20 → airia-0.1.22}/airia/types/api/pipelines_config/export_pipeline_definition.py
RENAMED
|
@@ -20,6 +20,7 @@ class ExportCredentialDataList(BaseModel):
|
|
|
20
20
|
key: The key name for the credential data
|
|
21
21
|
value: The value associated with the key
|
|
22
22
|
"""
|
|
23
|
+
|
|
23
24
|
key: str = Field(..., description="Gets or sets the key.")
|
|
24
25
|
value: str = Field(..., description="Gets or sets the value.")
|
|
25
26
|
|
|
@@ -37,6 +38,7 @@ class ExportCredentials(BaseModel):
|
|
|
37
38
|
credential_data_list: List of key-value pairs containing credential data
|
|
38
39
|
id: The unique identifier for the credential set
|
|
39
40
|
"""
|
|
41
|
+
|
|
40
42
|
name: str = Field(..., description="Gets or sets the name.")
|
|
41
43
|
credential_type: str = Field(
|
|
42
44
|
..., description="Gets or sets the type.", alias="credentialType"
|
|
@@ -62,6 +64,7 @@ class ExportPosition(BaseModel):
|
|
|
62
64
|
x: The X-coordinate position
|
|
63
65
|
y: The Y-coordinate position
|
|
64
66
|
"""
|
|
67
|
+
|
|
65
68
|
x: str = Field(..., description="Gets or sets the X.")
|
|
66
69
|
y: str = Field(..., description="Gets or sets the Y.")
|
|
67
70
|
|
|
@@ -80,6 +83,7 @@ class ExportHandle(BaseModel):
|
|
|
80
83
|
x: The X-coordinate position of the handle
|
|
81
84
|
y: The Y-coordinate position of the handle
|
|
82
85
|
"""
|
|
86
|
+
|
|
83
87
|
uuid: str = Field(..., description="Gets or sets the UUID of the handle.")
|
|
84
88
|
type: str = Field(
|
|
85
89
|
...,
|
|
@@ -113,6 +117,7 @@ class ExportDependency(BaseModel):
|
|
|
113
117
|
parent_handle_id: The UUID of the parent's output handle
|
|
114
118
|
handle_id: The UUID of the child's input handle
|
|
115
119
|
"""
|
|
120
|
+
|
|
116
121
|
parent_id: str = Field(
|
|
117
122
|
...,
|
|
118
123
|
description="Gets or sets the UUID of the parent pipeline step.",
|
|
@@ -160,6 +165,7 @@ class ExportPipelineStep(BaseModel):
|
|
|
160
165
|
content: Optional content or configuration data
|
|
161
166
|
step_title: The human-readable title of the step
|
|
162
167
|
"""
|
|
168
|
+
|
|
163
169
|
id: str = Field(..., description="Gets or sets the ID.")
|
|
164
170
|
step_type: str = Field(
|
|
165
171
|
..., description="Gets or sets the step type.", alias="stepType"
|
|
@@ -240,6 +246,7 @@ class AgentDetailItemDefinition(BaseModel):
|
|
|
240
246
|
value: The current value of the parameter
|
|
241
247
|
options: Optional list of available options for select/multiselect types
|
|
242
248
|
"""
|
|
249
|
+
|
|
243
250
|
item_type: str = Field(
|
|
244
251
|
...,
|
|
245
252
|
description="Gets or sets the entries for the agent details item input type.",
|
|
@@ -277,6 +284,7 @@ class ExportPipeline(BaseModel):
|
|
|
277
284
|
agent_icon: Optional base64-encoded icon image
|
|
278
285
|
steps: List of pipeline steps that make up the workflow
|
|
279
286
|
"""
|
|
287
|
+
|
|
280
288
|
name: str = Field(..., description="Gets or sets the name.")
|
|
281
289
|
execution_name: Optional[str] = Field(
|
|
282
290
|
None, description="Gets or sets the execution name.", alias="executionName"
|
|
@@ -312,6 +320,7 @@ class ExportDataSourceFile(BaseModel):
|
|
|
312
320
|
file_path: Optional path or location of the file
|
|
313
321
|
input_token: Optional access token for the file
|
|
314
322
|
"""
|
|
323
|
+
|
|
315
324
|
data_source_id: str = Field(
|
|
316
325
|
...,
|
|
317
326
|
description="Gets or sets the ID of the associated DataSource.",
|
|
@@ -341,6 +350,7 @@ class ExportChunkingConfig(BaseModel):
|
|
|
341
350
|
chunk_overlap: The number of characters/tokens that overlap between chunks
|
|
342
351
|
strategy_type: The chunking strategy used (sentence, paragraph, etc.)
|
|
343
352
|
"""
|
|
353
|
+
|
|
344
354
|
id: str = Field(
|
|
345
355
|
..., description="Gets or sets the chunking configuration identifier."
|
|
346
356
|
)
|
|
@@ -376,6 +386,7 @@ class ExportDataSource(BaseModel):
|
|
|
376
386
|
credentials: Optional credential information for access
|
|
377
387
|
is_image_processing_enabled: Whether image processing is enabled
|
|
378
388
|
"""
|
|
389
|
+
|
|
379
390
|
id: str = Field(..., description="Gets the id.")
|
|
380
391
|
name: Optional[str] = Field(None, description="Gets the Name.")
|
|
381
392
|
execution_name: Optional[str] = Field(
|
|
@@ -432,6 +443,7 @@ class ExportPromptMessageList(BaseModel):
|
|
|
432
443
|
text: The content of the message
|
|
433
444
|
order: The order of this message in the prompt sequence
|
|
434
445
|
"""
|
|
446
|
+
|
|
435
447
|
text: str = Field(..., description="Gets or sets the text.")
|
|
436
448
|
order: int = Field(..., description="Gets or sets the order.")
|
|
437
449
|
|
|
@@ -448,6 +460,7 @@ class ExportPrompt(BaseModel):
|
|
|
448
460
|
prompt_message_list: List of messages that make up the prompt
|
|
449
461
|
id: The unique identifier of the prompt
|
|
450
462
|
"""
|
|
463
|
+
|
|
451
464
|
name: str = Field(..., description="Gets or sets the name.")
|
|
452
465
|
version_change_description: str = Field(
|
|
453
466
|
...,
|
|
@@ -472,6 +485,7 @@ class ExportToolHeaders(BaseModel):
|
|
|
472
485
|
key: The header name
|
|
473
486
|
value: The header value
|
|
474
487
|
"""
|
|
488
|
+
|
|
475
489
|
key: str = Field(..., description="Gets or sets the key of the header.")
|
|
476
490
|
value: str = Field(..., description="Gets or sets the value of the header.")
|
|
477
491
|
|
|
@@ -490,6 +504,7 @@ class ExportToolParameters(BaseModel):
|
|
|
490
504
|
valid_options: Optional list of valid values for the parameter
|
|
491
505
|
id: The unique identifier of the parameter
|
|
492
506
|
"""
|
|
507
|
+
|
|
493
508
|
name: str = Field(..., description="Gets or sets the name.")
|
|
494
509
|
parameter_type: str = Field(
|
|
495
510
|
..., description="Gets or sets the type.", alias="parameterType"
|
|
@@ -503,7 +518,7 @@ class ExportToolParameters(BaseModel):
|
|
|
503
518
|
description="Gets or sets the list of valid options.",
|
|
504
519
|
alias="validOptions",
|
|
505
520
|
)
|
|
506
|
-
id: str = Field(
|
|
521
|
+
id: Optional[str] = Field(None, description="Gets or sets the ID.")
|
|
507
522
|
|
|
508
523
|
|
|
509
524
|
class ExportTool(BaseModel):
|
|
@@ -529,6 +544,7 @@ class ExportTool(BaseModel):
|
|
|
529
544
|
use_user_credentials_type: The type of user credentials to use
|
|
530
545
|
id: The unique identifier of the tool
|
|
531
546
|
"""
|
|
547
|
+
|
|
532
548
|
tool_type: str = Field(
|
|
533
549
|
...,
|
|
534
550
|
description="Gets or sets a value indicating whether flag that indicates if the tool is native.",
|
|
@@ -570,8 +586,8 @@ class ExportTool(BaseModel):
|
|
|
570
586
|
description="Gets or sets a value indicating whether the tool should use user based credentials.",
|
|
571
587
|
alias="useUserCredentials",
|
|
572
588
|
)
|
|
573
|
-
use_user_credentials_type: str = Field(
|
|
574
|
-
|
|
589
|
+
use_user_credentials_type: Optional[str] = Field(
|
|
590
|
+
None,
|
|
575
591
|
description="Gets or sets a value indicating what the credential type is when the tool use user based credentials.",
|
|
576
592
|
alias="useUserCredentialsType",
|
|
577
593
|
)
|
|
@@ -612,6 +628,7 @@ class ExportModel(BaseModel):
|
|
|
612
628
|
author: Optional author information
|
|
613
629
|
price_type: The pricing model type
|
|
614
630
|
"""
|
|
631
|
+
|
|
615
632
|
id: str = Field(..., description="Gets or sets the ID.")
|
|
616
633
|
display_name: str = Field(
|
|
617
634
|
..., description="Gets or sets the display name.", alias="displayName"
|
|
@@ -707,6 +724,7 @@ class ExportMemory(BaseModel):
|
|
|
707
724
|
name: The name of the memory
|
|
708
725
|
is_user_specific: Whether the memory is specific to individual users
|
|
709
726
|
"""
|
|
727
|
+
|
|
710
728
|
id: str = Field(..., description="Gets or sets the memory id.")
|
|
711
729
|
name: str = Field(..., description="Gets or sets the memory name.")
|
|
712
730
|
is_user_specific: bool = Field(
|
|
@@ -726,6 +744,7 @@ class ExportPythonCodeBlock(BaseModel):
|
|
|
726
744
|
id: The unique identifier of the code block
|
|
727
745
|
code: The Python code content
|
|
728
746
|
"""
|
|
747
|
+
|
|
729
748
|
id: str = Field(..., description="Gets or sets the memory id.")
|
|
730
749
|
code: str = Field(..., description="Gets or sets the code.")
|
|
731
750
|
|
|
@@ -741,6 +760,7 @@ class ExportRouterConfig(BaseModel):
|
|
|
741
760
|
prompt: The prompt used for routing decisions
|
|
742
761
|
is_default: Whether this is the default routing option
|
|
743
762
|
"""
|
|
763
|
+
|
|
744
764
|
id: str = Field(..., description="Gets or sets the Id.")
|
|
745
765
|
prompt: str = Field(..., description="Gets or sets the Prompt.")
|
|
746
766
|
is_default: Optional[bool] = Field(
|
|
@@ -762,6 +782,7 @@ class ExportRouter(BaseModel):
|
|
|
762
782
|
model: Optional AI model definition for routing
|
|
763
783
|
router_config: Dictionary of routing configurations
|
|
764
784
|
"""
|
|
785
|
+
|
|
765
786
|
id: str = Field(..., description="Gets or sets the Router identifier.")
|
|
766
787
|
model_id: Optional[str] = Field(
|
|
767
788
|
None, description="Gets or sets the Model identifier.", alias="modelId"
|
|
@@ -783,6 +804,7 @@ class ExportUserPrompt(BaseModel):
|
|
|
783
804
|
message: The prompt message content
|
|
784
805
|
prompt_description: Description of the prompt's purpose
|
|
785
806
|
"""
|
|
807
|
+
|
|
786
808
|
name: str = Field(..., description="Gets or sets the name of the UserPrompt.")
|
|
787
809
|
message: str = Field(..., description="Gets or sets the UserPrompt Message.")
|
|
788
810
|
prompt_description: str = Field(
|
|
@@ -810,6 +832,7 @@ class ExportDeployment(BaseModel):
|
|
|
810
832
|
conversation_type: The type of conversation interface
|
|
811
833
|
about_json: Optional JSON metadata about the deployment
|
|
812
834
|
"""
|
|
835
|
+
|
|
813
836
|
name: str = Field(..., description="Gets the Deployment Name.")
|
|
814
837
|
deployment_icon: Optional[str] = Field(
|
|
815
838
|
None, description="Gets the Deployment Icon.", alias="deploymentIcon"
|
|
@@ -864,6 +887,7 @@ class ExportMetadata(BaseModel):
|
|
|
864
887
|
version_information: Information about the pipeline version
|
|
865
888
|
state: The current state of the agent
|
|
866
889
|
"""
|
|
890
|
+
|
|
867
891
|
id: str = Field(..., description="Gets or sets the id.")
|
|
868
892
|
export_version: Optional[str] = Field(
|
|
869
893
|
None,
|
|
@@ -912,6 +936,7 @@ class ExportPipelineDefinitionResponse(BaseModel):
|
|
|
912
936
|
routers: Optional list of router configurations used by the pipeline
|
|
913
937
|
deployment: Optional deployment configuration for the pipeline
|
|
914
938
|
"""
|
|
939
|
+
|
|
915
940
|
metadata: ExportMetadata = Field(..., description="Gets or sets the Metadata.")
|
|
916
941
|
agent: ExportPipeline = Field(..., description="Gets or sets the pipeline.")
|
|
917
942
|
data_sources: Optional[List[ExportDataSource]] = Field(
|
|
@@ -18,6 +18,10 @@ airia/client/_request_handler/__init__.py
|
|
|
18
18
|
airia/client/_request_handler/async_request_handler.py
|
|
19
19
|
airia/client/_request_handler/base_request_handler.py
|
|
20
20
|
airia/client/_request_handler/sync_request_handler.py
|
|
21
|
+
airia/client/attachments/__init__.py
|
|
22
|
+
airia/client/attachments/async_attachments.py
|
|
23
|
+
airia/client/attachments/base_attachments.py
|
|
24
|
+
airia/client/attachments/sync_attachments.py
|
|
21
25
|
airia/client/conversations/__init__.py
|
|
22
26
|
airia/client/conversations/async_conversations.py
|
|
23
27
|
airia/client/conversations/base_conversations.py
|
|
@@ -50,6 +54,8 @@ airia/types/__init__.py
|
|
|
50
54
|
airia/types/_api_version.py
|
|
51
55
|
airia/types/_request_data.py
|
|
52
56
|
airia/types/api/__init__.py
|
|
57
|
+
airia/types/api/attachments/__init__.py
|
|
58
|
+
airia/types/api/attachments/upload_file.py
|
|
53
59
|
airia/types/api/conversations/__init__.py
|
|
54
60
|
airia/types/api/conversations/_conversations.py
|
|
55
61
|
airia/types/api/data_vector_search/__init__.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|