haystack-enterprise-sdk 0.1.2__tar.gz → 0.2.0__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.
- {haystack_enterprise_sdk-0.1.2 → haystack_enterprise_sdk-0.2.0}/PKG-INFO +4 -4
- {haystack_enterprise_sdk-0.1.2 → haystack_enterprise_sdk-0.2.0}/README.md +3 -3
- {haystack_enterprise_sdk-0.1.2 → haystack_enterprise_sdk-0.2.0}/haystack_enterprise_sdk/README.md +1 -1
- {haystack_enterprise_sdk-0.1.2 → haystack_enterprise_sdk-0.2.0}/haystack_enterprise_sdk/_api/deployments.py +59 -1
- {haystack_enterprise_sdk-0.1.2 → haystack_enterprise_sdk-0.2.0}/haystack_enterprise_sdk/_api/haystack_enterprise_api.py +16 -4
- {haystack_enterprise_sdk-0.1.2 → haystack_enterprise_sdk-0.2.0}/haystack_enterprise_sdk/_service/deployment_service.py +55 -1
- {haystack_enterprise_sdk-0.1.2 → haystack_enterprise_sdk-0.2.0}/haystack_enterprise_sdk/cli.py +76 -5
- {haystack_enterprise_sdk-0.1.2 → haystack_enterprise_sdk-0.2.0}/haystack_enterprise_sdk/workflows/async_client/deployment_client.py +26 -4
- {haystack_enterprise_sdk-0.1.2 → haystack_enterprise_sdk-0.2.0}/haystack_enterprise_sdk/workflows/sync_client/deployment_client.py +13 -1
- {haystack_enterprise_sdk-0.1.2 → haystack_enterprise_sdk-0.2.0}/pyproject.toml +1 -1
- {haystack_enterprise_sdk-0.1.2 → haystack_enterprise_sdk-0.2.0}/pyproject.toml.orig +1 -1
- {haystack_enterprise_sdk-0.1.2 → haystack_enterprise_sdk-0.2.0}/LICENSE +0 -0
- {haystack_enterprise_sdk-0.1.2 → haystack_enterprise_sdk-0.2.0}/haystack_enterprise_sdk/__init__.py +0 -0
- {haystack_enterprise_sdk-0.1.2 → haystack_enterprise_sdk-0.2.0}/haystack_enterprise_sdk/_api/config.py +0 -0
- {haystack_enterprise_sdk-0.1.2 → haystack_enterprise_sdk-0.2.0}/haystack_enterprise_sdk/_api/files.py +0 -0
- {haystack_enterprise_sdk-0.1.2 → haystack_enterprise_sdk-0.2.0}/haystack_enterprise_sdk/_api/pipeline_run.py +0 -0
- {haystack_enterprise_sdk-0.1.2 → haystack_enterprise_sdk-0.2.0}/haystack_enterprise_sdk/_api/shared_prototypes.py +0 -0
- {haystack_enterprise_sdk-0.1.2 → haystack_enterprise_sdk-0.2.0}/haystack_enterprise_sdk/_api/upload_sessions.py +0 -0
- {haystack_enterprise_sdk-0.1.2 → haystack_enterprise_sdk-0.2.0}/haystack_enterprise_sdk/_console.py +0 -0
- {haystack_enterprise_sdk-0.1.2 → haystack_enterprise_sdk-0.2.0}/haystack_enterprise_sdk/_s3/__init__.py +0 -0
- {haystack_enterprise_sdk-0.1.2 → haystack_enterprise_sdk-0.2.0}/haystack_enterprise_sdk/_s3/upload.py +0 -0
- {haystack_enterprise_sdk-0.1.2 → haystack_enterprise_sdk-0.2.0}/haystack_enterprise_sdk/_service/files_service.py +0 -0
- {haystack_enterprise_sdk-0.1.2 → haystack_enterprise_sdk-0.2.0}/haystack_enterprise_sdk/_service/io_spec.py +0 -0
- {haystack_enterprise_sdk-0.1.2 → haystack_enterprise_sdk-0.2.0}/haystack_enterprise_sdk/_service/pipeline_extract.py +0 -0
- {haystack_enterprise_sdk-0.1.2 → haystack_enterprise_sdk-0.2.0}/haystack_enterprise_sdk/_service/pipeline_service.py +0 -0
- {haystack_enterprise_sdk-0.1.2 → haystack_enterprise_sdk-0.2.0}/haystack_enterprise_sdk/_service/pipeline_transform.py +0 -0
- {haystack_enterprise_sdk-0.1.2 → haystack_enterprise_sdk-0.2.0}/haystack_enterprise_sdk/_utils/__init__.py +0 -0
- {haystack_enterprise_sdk-0.1.2 → haystack_enterprise_sdk-0.2.0}/haystack_enterprise_sdk/_utils/datetime.py +0 -0
- {haystack_enterprise_sdk-0.1.2 → haystack_enterprise_sdk-0.2.0}/haystack_enterprise_sdk/models.py +0 -0
- {haystack_enterprise_sdk-0.1.2 → haystack_enterprise_sdk-0.2.0}/haystack_enterprise_sdk/py.typed +0 -0
- {haystack_enterprise_sdk-0.1.2 → haystack_enterprise_sdk-0.2.0}/haystack_enterprise_sdk/workflows/__init__.py +0 -0
- {haystack_enterprise_sdk-0.1.2 → haystack_enterprise_sdk-0.2.0}/haystack_enterprise_sdk/workflows/async_client/__init__.py +0 -0
- {haystack_enterprise_sdk-0.1.2 → haystack_enterprise_sdk-0.2.0}/haystack_enterprise_sdk/workflows/async_client/async_pipeline_client.py +0 -0
- {haystack_enterprise_sdk-0.1.2 → haystack_enterprise_sdk-0.2.0}/haystack_enterprise_sdk/workflows/async_client/files.py +0 -0
- {haystack_enterprise_sdk-0.1.2 → haystack_enterprise_sdk-0.2.0}/haystack_enterprise_sdk/workflows/sync_client/__init__.py +0 -0
- {haystack_enterprise_sdk-0.1.2 → haystack_enterprise_sdk-0.2.0}/haystack_enterprise_sdk/workflows/sync_client/files.py +0 -0
- {haystack_enterprise_sdk-0.1.2 → haystack_enterprise_sdk-0.2.0}/haystack_enterprise_sdk/workflows/sync_client/pipeline_client.py +0 -0
- {haystack_enterprise_sdk-0.1.2 → haystack_enterprise_sdk-0.2.0}/haystack_enterprise_sdk/workflows/sync_client/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: haystack-enterprise-sdk
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Python SDK and CLI for the Haystack Enterprise Platform
|
|
5
5
|
Keywords: agents,deepset,haystack,llm,pipelines,rag,sdk
|
|
6
6
|
Author: deepset.ai
|
|
@@ -145,14 +145,14 @@ A deployed service is served over an OpenAI-compatible chat-completions endpoint
|
|
|
145
145
|
once the service is running:
|
|
146
146
|
|
|
147
147
|
```bash
|
|
148
|
-
curl -N https://api.cloud.deepset.ai/api/v1/workspaces/<workspace>/deployments
|
|
148
|
+
curl -N https://api.cloud.deepset.ai/api/v1/workspaces/<workspace>/deployments/v1/chat/completions \
|
|
149
149
|
-H "Authorization: Bearer $API_KEY" \
|
|
150
150
|
-H "Content-Type: application/json" \
|
|
151
|
-
-d '{"model": "<workspace>/<
|
|
151
|
+
-d '{"model": "<workspace>/<deployment-id>", "messages": [{"role": "user", "content": "Hello"}]}'
|
|
152
152
|
```
|
|
153
153
|
|
|
154
154
|
The response is a server-sent-event stream of `chat.completion.chunk` objects. Any OpenAI client works —
|
|
155
|
-
point its `base_url` at everything up to and including `/deployments
|
|
155
|
+
point its `base_url` at everything up to and including `/deployments/v1`.
|
|
156
156
|
|
|
157
157
|
Pass `--verbose` to any command for INFO/DEBUG logs, and `<command> --help` for its arguments.
|
|
158
158
|
|
|
@@ -100,14 +100,14 @@ A deployed service is served over an OpenAI-compatible chat-completions endpoint
|
|
|
100
100
|
once the service is running:
|
|
101
101
|
|
|
102
102
|
```bash
|
|
103
|
-
curl -N https://api.cloud.deepset.ai/api/v1/workspaces/<workspace>/deployments
|
|
103
|
+
curl -N https://api.cloud.deepset.ai/api/v1/workspaces/<workspace>/deployments/v1/chat/completions \
|
|
104
104
|
-H "Authorization: Bearer $API_KEY" \
|
|
105
105
|
-H "Content-Type: application/json" \
|
|
106
|
-
-d '{"model": "<workspace>/<
|
|
106
|
+
-d '{"model": "<workspace>/<deployment-id>", "messages": [{"role": "user", "content": "Hello"}]}'
|
|
107
107
|
```
|
|
108
108
|
|
|
109
109
|
The response is a server-sent-event stream of `chat.completion.chunk` objects. Any OpenAI client works —
|
|
110
|
-
point its `base_url` at everything up to and including `/deployments
|
|
110
|
+
point its `base_url` at everything up to and including `/deployments/v1`.
|
|
111
111
|
|
|
112
112
|
Pass `--verbose` to any command for INFO/DEBUG logs, and `<command> --help` for its arguments.
|
|
113
113
|
|
|
@@ -10,7 +10,7 @@ list endpoint and matching client-side.
|
|
|
10
10
|
|
|
11
11
|
import asyncio
|
|
12
12
|
import enum
|
|
13
|
-
from dataclasses import dataclass
|
|
13
|
+
from dataclasses import dataclass, field
|
|
14
14
|
from typing import Any, Dict, List, Optional, Type, TypeVar
|
|
15
15
|
from uuid import UUID
|
|
16
16
|
|
|
@@ -97,6 +97,10 @@ class Deployment:
|
|
|
97
97
|
# revision, so it stays None until a revision is activated. This is the platform's own answer to
|
|
98
98
|
# "is this a chat pipeline?" -- the CLI reads it rather than guessing from components or sockets.
|
|
99
99
|
output_type: Optional[PipelineOutputType] = None
|
|
100
|
+
# Free-form labels, capped server-side at MAX_SERVICE_TAGS (3). Not settable at creation --
|
|
101
|
+
# the platform has no `tags` field on its create-deployment request, only the dedicated
|
|
102
|
+
# add/remove-tag endpoints below, so this always reflects a create followed by N tag calls.
|
|
103
|
+
tags: List[str] = field(default_factory=list)
|
|
100
104
|
|
|
101
105
|
@classmethod
|
|
102
106
|
def from_response(cls, body: Dict[str, Any]) -> "Deployment":
|
|
@@ -120,6 +124,7 @@ class Deployment:
|
|
|
120
124
|
# The platform enum has values this SDK does not model (e.g. "unknown"), and the field is
|
|
121
125
|
# absent until a revision is active, so anything unrecognized degrades to None.
|
|
122
126
|
output_type=_enum_or_none(PipelineOutputType, body.get("output_type")),
|
|
127
|
+
tags=list(body.get("tags") or []),
|
|
123
128
|
)
|
|
124
129
|
|
|
125
130
|
|
|
@@ -244,6 +249,15 @@ class FailedToActivateRevisionError(Exception):
|
|
|
244
249
|
"""Raised when a deployment revision could not be activated."""
|
|
245
250
|
|
|
246
251
|
|
|
252
|
+
class FailedToTagDeploymentError(Exception):
|
|
253
|
+
"""Raised when a tag could not be added to, or removed from, a deployment.
|
|
254
|
+
|
|
255
|
+
Covers the platform's own rejections too: a name over 50 chars or outside
|
|
256
|
+
``[A-Za-z0-9 _-]``, a duplicate (case-insensitive), or a fourth tag past MAX_SERVICE_TAGS (3)
|
|
257
|
+
all come back as a 4xx from the tag endpoints, not just a network/auth failure.
|
|
258
|
+
"""
|
|
259
|
+
|
|
260
|
+
|
|
247
261
|
class FailedToValidatePipelineError(Exception):
|
|
248
262
|
"""Raised when the validation request itself failed (e.g. auth/5xx), not the pipeline config."""
|
|
249
263
|
|
|
@@ -453,6 +467,50 @@ class DeploymentsAPI:
|
|
|
453
467
|
)
|
|
454
468
|
return Deployment.from_response(response.json())
|
|
455
469
|
|
|
470
|
+
async def add_tag(self, workspace_name: str, deployment_id: UUID, tag_name: str) -> List[str]:
|
|
471
|
+
"""Add a tag to a deployment.
|
|
472
|
+
|
|
473
|
+
:param workspace_name: Name of the workspace.
|
|
474
|
+
:param deployment_id: Deployment id.
|
|
475
|
+
:param tag_name: Tag name (1-50 chars; letters, digits, spaces, underscores, hyphens).
|
|
476
|
+
Rejected as a duplicate case-insensitively, and past the third tag on one deployment.
|
|
477
|
+
:raises FailedToTagDeploymentError: If the tag could not be added.
|
|
478
|
+
:return: The deployment's full tag list after the add.
|
|
479
|
+
"""
|
|
480
|
+
response = await self._haystack_enterprise_api.post(
|
|
481
|
+
workspace_name=workspace_name,
|
|
482
|
+
endpoint=f"{self._ENDPOINT}/{deployment_id}/tags",
|
|
483
|
+
json={"name": tag_name},
|
|
484
|
+
)
|
|
485
|
+
raise_for_unexpected_status(
|
|
486
|
+
response,
|
|
487
|
+
(codes.OK,),
|
|
488
|
+
FailedToTagDeploymentError,
|
|
489
|
+
f"Failed to add tag {tag_name!r} to deployment '{deployment_id}'.",
|
|
490
|
+
)
|
|
491
|
+
return list(response.json())
|
|
492
|
+
|
|
493
|
+
async def remove_tag(self, workspace_name: str, deployment_id: UUID, tag_name: str) -> List[str]:
|
|
494
|
+
"""Remove a tag from a deployment. Matching is case-insensitive.
|
|
495
|
+
|
|
496
|
+
:param workspace_name: Name of the workspace.
|
|
497
|
+
:param deployment_id: Deployment id.
|
|
498
|
+
:param tag_name: Tag name to remove.
|
|
499
|
+
:raises FailedToTagDeploymentError: If the tag could not be removed (including "not found").
|
|
500
|
+
:return: The deployment's full tag list after the removal.
|
|
501
|
+
"""
|
|
502
|
+
response = await self._haystack_enterprise_api.delete(
|
|
503
|
+
workspace_name=workspace_name,
|
|
504
|
+
endpoint=f"{self._ENDPOINT}/{deployment_id}/tags/{tag_name}",
|
|
505
|
+
)
|
|
506
|
+
raise_for_unexpected_status(
|
|
507
|
+
response,
|
|
508
|
+
(codes.OK,),
|
|
509
|
+
FailedToTagDeploymentError,
|
|
510
|
+
f"Failed to remove tag {tag_name!r} from deployment '{deployment_id}'.",
|
|
511
|
+
)
|
|
512
|
+
return list(response.json())
|
|
513
|
+
|
|
456
514
|
async def validate_pipeline(
|
|
457
515
|
self,
|
|
458
516
|
workspace_name: str,
|
|
@@ -148,11 +148,9 @@ def raise_for_unexpected_status(
|
|
|
148
148
|
|
|
149
149
|
|
|
150
150
|
def deployment_base_url(api_url: str, workspace_name: str, deployment_id: Any) -> str:
|
|
151
|
-
"""Build the base URL
|
|
151
|
+
"""Build the per-deployment base URL (used for per-deployment routes like ``/chat`` and ``/chat-stream``).
|
|
152
152
|
|
|
153
|
-
|
|
154
|
-
keyed on the deployment id, not the service name. Returned without the ``/chat/completions`` suffix
|
|
155
|
-
because that is exactly the ``base_url`` an OpenAI client expects (it appends the path itself).
|
|
153
|
+
Returned without a trailing path segment because callers append their own route (e.g. ``/chat``).
|
|
156
154
|
|
|
157
155
|
:param api_url: Base API URL, already normalized (see :func:`~haystack_enterprise_sdk._api.config.normalize_base_url`).
|
|
158
156
|
:param workspace_name: Name of the workspace the deployment lives in.
|
|
@@ -162,6 +160,20 @@ def deployment_base_url(api_url: str, workspace_name: str, deployment_id: Any) -
|
|
|
162
160
|
return f"{api_url}/{API_VERSION_PATH}/workspaces/{workspace_name}/deployments/{deployment_id}"
|
|
163
161
|
|
|
164
162
|
|
|
163
|
+
def chat_completions_base_url(api_url: str, workspace_name: str) -> str:
|
|
164
|
+
"""Build the workspace-scoped OpenAI-compatible chat-completions base URL.
|
|
165
|
+
|
|
166
|
+
The platform's OpenAI-compatible gateway lives at ``POST <this>/chat/completions``.
|
|
167
|
+
The ``model`` field must be ``{workspace_name}/{deployment_id}`` (UUID, not service name).
|
|
168
|
+
Returned without the ``/chat/completions`` suffix so an OpenAI client can use it as ``base_url``.
|
|
169
|
+
|
|
170
|
+
:param api_url: Base API URL, already normalized (see :func:`~haystack_enterprise_sdk._api.config.normalize_base_url`).
|
|
171
|
+
:param workspace_name: Name of the workspace.
|
|
172
|
+
:return: e.g. ``https://api.cloud.deepset.ai/api/v1/workspaces/my-ws/deployments/v1``.
|
|
173
|
+
"""
|
|
174
|
+
return f"{api_url}/{API_VERSION_PATH}/workspaces/{workspace_name}/deployments/v1"
|
|
175
|
+
|
|
176
|
+
|
|
165
177
|
class HaystackEnterpriseAPI:
|
|
166
178
|
"""Haystack Enterprise Platform API client.
|
|
167
179
|
|
|
@@ -22,6 +22,7 @@ from haystack_enterprise_sdk._api.deployments import (
|
|
|
22
22
|
DeploymentsAPI,
|
|
23
23
|
DeploymentServiceLevel,
|
|
24
24
|
DeploymentStatus,
|
|
25
|
+
FailedToTagDeploymentError,
|
|
25
26
|
PipelineValidationError,
|
|
26
27
|
PipelineValidationResult,
|
|
27
28
|
)
|
|
@@ -147,6 +148,9 @@ class CreateOptions:
|
|
|
147
148
|
cpu_limit: Optional[str] = None
|
|
148
149
|
memory_limit: Optional[str] = None
|
|
149
150
|
gpu_limit_gigabyte: Optional[int] = None
|
|
151
|
+
# Applied with one add_tag call each, right after creation -- the create-deployment endpoint
|
|
152
|
+
# itself has no `tags` field. Not a sizing field: it applies to serverless services too.
|
|
153
|
+
tags: Tuple[str, ...] = ()
|
|
150
154
|
|
|
151
155
|
_SIZING_FIELDS = (
|
|
152
156
|
"service_level",
|
|
@@ -493,6 +497,38 @@ class DeploymentService:
|
|
|
493
497
|
)
|
|
494
498
|
return await self._deployments.get_deployment(self._workspace_name, deployment.deployment_id)
|
|
495
499
|
|
|
500
|
+
async def add_tag(self, service_name: str, tag_name: str) -> List[str]:
|
|
501
|
+
"""Add a tag to a service by name, independent of deploy/create.
|
|
502
|
+
|
|
503
|
+
:param service_name: Name of the service deployment.
|
|
504
|
+
:param tag_name: Tag name (1-50 chars; letters, digits, spaces, underscores, hyphens).
|
|
505
|
+
:raises ServiceNotFoundError: If no service with that name exists.
|
|
506
|
+
:raises FailedToTagDeploymentError: If the platform rejected it (limit, duplicate, bad name).
|
|
507
|
+
:return: The service's full tag list after the add.
|
|
508
|
+
"""
|
|
509
|
+
deployment = await self._deployments.find_by_name(self._workspace_name, service_name)
|
|
510
|
+
if deployment is None:
|
|
511
|
+
raise ServiceNotFoundError(
|
|
512
|
+
f"No service deployment named '{service_name}' in workspace '{self._workspace_name}'."
|
|
513
|
+
)
|
|
514
|
+
return await self._deployments.add_tag(self._workspace_name, deployment.deployment_id, tag_name)
|
|
515
|
+
|
|
516
|
+
async def remove_tag(self, service_name: str, tag_name: str) -> List[str]:
|
|
517
|
+
"""Remove a tag from a service by name. Matching is case-insensitive.
|
|
518
|
+
|
|
519
|
+
:param service_name: Name of the service deployment.
|
|
520
|
+
:param tag_name: Tag name to remove.
|
|
521
|
+
:raises ServiceNotFoundError: If no service with that name exists.
|
|
522
|
+
:raises FailedToTagDeploymentError: If the tag could not be removed (including "not found").
|
|
523
|
+
:return: The service's full tag list after the removal.
|
|
524
|
+
"""
|
|
525
|
+
deployment = await self._deployments.find_by_name(self._workspace_name, service_name)
|
|
526
|
+
if deployment is None:
|
|
527
|
+
raise ServiceNotFoundError(
|
|
528
|
+
f"No service deployment named '{service_name}' in workspace '{self._workspace_name}'."
|
|
529
|
+
)
|
|
530
|
+
return await self._deployments.remove_tag(self._workspace_name, deployment.deployment_id, tag_name)
|
|
531
|
+
|
|
496
532
|
async def create_shared_prototype(
|
|
497
533
|
self, service_name: str, options: Optional[ShareOptions] = None
|
|
498
534
|
) -> SharedPrototype:
|
|
@@ -538,7 +574,7 @@ class DeploymentService:
|
|
|
538
574
|
)
|
|
539
575
|
options = create_options or CreateOptions()
|
|
540
576
|
logger.info("Creating service deployment.", service=service_name, mode=options.deployment_mode.value)
|
|
541
|
-
|
|
577
|
+
deployment = await self._deployments.create_deployment(
|
|
542
578
|
self._workspace_name,
|
|
543
579
|
name=service_name,
|
|
544
580
|
deployment_mode=options.deployment_mode,
|
|
@@ -550,6 +586,24 @@ class DeploymentService:
|
|
|
550
586
|
memory_limit=options.memory_limit,
|
|
551
587
|
gpu_limit_gigabyte=options.gpu_limit_gigabyte,
|
|
552
588
|
)
|
|
589
|
+
for tag_name in options.tags:
|
|
590
|
+
# Sequential, not gathered: each call returns the full tag list so far, and the
|
|
591
|
+
# platform's own duplicate/limit checks are per-request -- gathering could race two
|
|
592
|
+
# adds past the 3-tag cap. There are at most 3 of these, so latency is not a concern.
|
|
593
|
+
#
|
|
594
|
+
# Caught here rather than left to propagate: the service was already created, so raising
|
|
595
|
+
# would abort a deploy that otherwise fully succeeded. `tag-add` can retry independently.
|
|
596
|
+
try:
|
|
597
|
+
deployment.tags = await self._deployments.add_tag(
|
|
598
|
+
self._workspace_name, deployment.deployment_id, tag_name
|
|
599
|
+
)
|
|
600
|
+
except FailedToTagDeploymentError:
|
|
601
|
+
logger.warning(
|
|
602
|
+
"Failed to add tag to newly created service; retry with 'tag-add'.",
|
|
603
|
+
service=service_name,
|
|
604
|
+
tag=tag_name,
|
|
605
|
+
)
|
|
606
|
+
return deployment
|
|
553
607
|
|
|
554
608
|
async def _poll_until_settled(
|
|
555
609
|
self,
|
{haystack_enterprise_sdk-0.1.2 → haystack_enterprise_sdk-0.2.0}/haystack_enterprise_sdk/cli.py
RENAMED
|
@@ -27,6 +27,7 @@ from haystack_enterprise_sdk._api.config import (
|
|
|
27
27
|
from haystack_enterprise_sdk._api.deployments import (
|
|
28
28
|
DeploymentMode,
|
|
29
29
|
DeploymentServiceLevel,
|
|
30
|
+
FailedToTagDeploymentError,
|
|
30
31
|
PipelineValidationError,
|
|
31
32
|
)
|
|
32
33
|
from haystack_enterprise_sdk._api.haystack_enterprise_api import HaystackEnterpriseAPIError
|
|
@@ -449,6 +450,9 @@ def deploy( # pylint: disable=too-many-arguments,too-many-locals
|
|
|
449
450
|
memory: Optional[str] = None,
|
|
450
451
|
gpu: Optional[int] = None,
|
|
451
452
|
idle_timeout: Optional[int] = None,
|
|
453
|
+
tag: Optional[List[str]] = typer.Option(
|
|
454
|
+
None, "--tag", help="Tag to add when creating the service (repeatable; up to 3)."
|
|
455
|
+
),
|
|
452
456
|
python: Optional[str] = None,
|
|
453
457
|
dry_run: bool = False,
|
|
454
458
|
output: Optional[Path] = None,
|
|
@@ -492,6 +496,11 @@ def deploy( # pylint: disable=too-many-arguments,too-many-locals
|
|
|
492
496
|
:param memory: Memory limit, e.g. '2Gi' (with --managed).
|
|
493
497
|
:param gpu: GPU memory limit in gigabytes (with --managed).
|
|
494
498
|
:param idle_timeout: Idle timeout in seconds before scale-down (with --managed).
|
|
499
|
+
:param tag: Tag to add to the service when it is created (repeatable; the platform allows at
|
|
500
|
+
most 3 per service). Only applies when the service is created -- pass --create, or omit
|
|
501
|
+
--create against a service name that does not exist yet. Rejected if the service already
|
|
502
|
+
exists, same as --managed and the sizing flags -- use `tag-add`/`tag-remove` to change
|
|
503
|
+
tags on a service after creation.
|
|
495
504
|
:param python: Path to the Python interpreter used to load your pipeline (defaults to an
|
|
496
505
|
auto-detected virtualenv near the target file, else the current interpreter).
|
|
497
506
|
:param dry_run: Transform the pipeline and print/write the resulting YAML without deploying. No
|
|
@@ -524,6 +533,9 @@ def deploy( # pylint: disable=too-many-arguments,too-many-locals
|
|
|
524
533
|
Create a managed service with explicit sizing (activates and waits for the rollout):
|
|
525
534
|
`haystack-enterprise deploy pipeline.py my-service --managed --service-level PRODUCTION --cpu 2`
|
|
526
535
|
|
|
536
|
+
Create a service tagged for a team and a hackathon, up to 3 tags:
|
|
537
|
+
`haystack-enterprise deploy pipeline.py my-service --create --tag team-success --tag hackathon`
|
|
538
|
+
|
|
527
539
|
Describe the revision instead of using the auto-generated comment:
|
|
528
540
|
`haystack-enterprise deploy pipeline.py my-service -m "Bump embedder model to bge-large"`
|
|
529
541
|
|
|
@@ -575,8 +587,8 @@ def deploy( # pylint: disable=too-many-arguments,too-many-locals
|
|
|
575
587
|
if create is False and existing is None:
|
|
576
588
|
typer.echo(f"No service named '{service_name}' in workspace '{workspace_name}'. Drop --no-create to create it.")
|
|
577
589
|
raise typer.Exit(1)
|
|
578
|
-
if existing is not None and (managed or given_sizing):
|
|
579
|
-
creation_flags = (["--managed"] if managed else []) + given_sizing
|
|
590
|
+
if existing is not None and (managed or given_sizing or tag):
|
|
591
|
+
creation_flags = (["--managed"] if managed else []) + given_sizing + (["--tag"] if tag else [])
|
|
580
592
|
applies = "only applies" if len(creation_flags) == 1 else "only apply"
|
|
581
593
|
typer.echo(
|
|
582
594
|
f"Service '{service_name}' already exists; {', '.join(creation_flags)} {applies} when a "
|
|
@@ -596,9 +608,10 @@ def deploy( # pylint: disable=too-many-arguments,too-many-locals
|
|
|
596
608
|
cpu_limit=cpu,
|
|
597
609
|
memory_limit=memory,
|
|
598
610
|
gpu_limit_gigabyte=gpu,
|
|
611
|
+
tags=tuple(tag or ()),
|
|
599
612
|
)
|
|
600
613
|
else:
|
|
601
|
-
create_options = CreateOptions(deployment_mode=DeploymentMode.SERVERLESS)
|
|
614
|
+
create_options = CreateOptions(deployment_mode=DeploymentMode.SERVERLESS, tags=tuple(tag or ()))
|
|
602
615
|
|
|
603
616
|
activate = not skip_activation
|
|
604
617
|
|
|
@@ -1152,8 +1165,8 @@ def _echo_endpoint(client: DeploymentClient, service_name: str, result: DeployRe
|
|
|
1152
1165
|
if not result.is_deployed:
|
|
1153
1166
|
return
|
|
1154
1167
|
|
|
1155
|
-
base_url = client.
|
|
1156
|
-
model = f"{client.workspace_name}/{
|
|
1168
|
+
base_url = client.chat_completions_base_url()
|
|
1169
|
+
model = f"{client.workspace_name}/{result.deployment.deployment_id}"
|
|
1157
1170
|
typer.echo(f"\nChat completions endpoint (OpenAI-compatible):\n POST {base_url}/chat/completions")
|
|
1158
1171
|
typer.echo(
|
|
1159
1172
|
f"\n curl -N {base_url}/chat/completions \\\n"
|
|
@@ -1649,12 +1662,70 @@ def service_status(
|
|
|
1649
1662
|
"pending_revision_id": (
|
|
1650
1663
|
str(deployment.pending_revision_id) if deployment.pending_revision_id else None
|
|
1651
1664
|
),
|
|
1665
|
+
"tags": deployment.tags,
|
|
1652
1666
|
},
|
|
1653
1667
|
indent=4,
|
|
1654
1668
|
)
|
|
1655
1669
|
)
|
|
1656
1670
|
|
|
1657
1671
|
|
|
1672
|
+
@cli_app.command()
|
|
1673
|
+
def tag_add(
|
|
1674
|
+
service_name: str,
|
|
1675
|
+
tag_name: str,
|
|
1676
|
+
api_key: Optional[str] = None,
|
|
1677
|
+
api_url: Optional[str] = None,
|
|
1678
|
+
workspace_name: str = DEFAULT_WORKSPACE_NAME,
|
|
1679
|
+
) -> None:
|
|
1680
|
+
"""Add a tag to a service deployment, independent of deploy/create.
|
|
1681
|
+
|
|
1682
|
+
:param service_name: Name of the service deployment.
|
|
1683
|
+
:param tag_name: Tag name (1-50 chars; letters, digits, spaces, underscores, hyphens).
|
|
1684
|
+
Rejected as a duplicate case-insensitively, and past the third tag on one service.
|
|
1685
|
+
:param api_key: deepset API key to use for authentication.
|
|
1686
|
+
:param api_url: API URL to use for authentication.
|
|
1687
|
+
:param workspace_name: Workspace of the service. Uses the workspace from the .ENV file by default.
|
|
1688
|
+
|
|
1689
|
+
Example:
|
|
1690
|
+
`haystack-enterprise tag-add my-service hackathon`
|
|
1691
|
+
"""
|
|
1692
|
+
client = DeploymentClient(api_key=api_key, api_url=api_url, workspace_name=workspace_name)
|
|
1693
|
+
try:
|
|
1694
|
+
tags = client.add_tag(service_name, tag_name)
|
|
1695
|
+
except (ServiceNotFoundError, FailedToTagDeploymentError) as err:
|
|
1696
|
+
typer.echo(str(err))
|
|
1697
|
+
raise typer.Exit(1) # noqa: B904
|
|
1698
|
+
typer.echo(f"Tags on '{service_name}': {', '.join(tags) or '(none)'}")
|
|
1699
|
+
|
|
1700
|
+
|
|
1701
|
+
@cli_app.command()
|
|
1702
|
+
def tag_remove(
|
|
1703
|
+
service_name: str,
|
|
1704
|
+
tag_name: str,
|
|
1705
|
+
api_key: Optional[str] = None,
|
|
1706
|
+
api_url: Optional[str] = None,
|
|
1707
|
+
workspace_name: str = DEFAULT_WORKSPACE_NAME,
|
|
1708
|
+
) -> None:
|
|
1709
|
+
"""Remove a tag from a service deployment. Matching is case-insensitive.
|
|
1710
|
+
|
|
1711
|
+
:param service_name: Name of the service deployment.
|
|
1712
|
+
:param tag_name: Tag name to remove.
|
|
1713
|
+
:param api_key: deepset API key to use for authentication.
|
|
1714
|
+
:param api_url: API URL to use for authentication.
|
|
1715
|
+
:param workspace_name: Workspace of the service. Uses the workspace from the .ENV file by default.
|
|
1716
|
+
|
|
1717
|
+
Example:
|
|
1718
|
+
`haystack-enterprise tag-remove my-service hackathon`
|
|
1719
|
+
"""
|
|
1720
|
+
client = DeploymentClient(api_key=api_key, api_url=api_url, workspace_name=workspace_name)
|
|
1721
|
+
try:
|
|
1722
|
+
tags = client.remove_tag(service_name, tag_name)
|
|
1723
|
+
except (ServiceNotFoundError, FailedToTagDeploymentError) as err:
|
|
1724
|
+
typer.echo(str(err))
|
|
1725
|
+
raise typer.Exit(1) # noqa: B904
|
|
1726
|
+
typer.echo(f"Tags on '{service_name}': {', '.join(tags) or '(none)'}")
|
|
1727
|
+
|
|
1728
|
+
|
|
1658
1729
|
def version_callback(value: bool) -> None:
|
|
1659
1730
|
"""Show the SDK version and exit.
|
|
1660
1731
|
|
|
@@ -19,6 +19,7 @@ from haystack_enterprise_sdk._api.deployments import (
|
|
|
19
19
|
)
|
|
20
20
|
from haystack_enterprise_sdk._api.haystack_enterprise_api import (
|
|
21
21
|
HaystackEnterpriseAPI,
|
|
22
|
+
chat_completions_base_url,
|
|
22
23
|
deployment_base_url,
|
|
23
24
|
)
|
|
24
25
|
from haystack_enterprise_sdk._api.pipeline_run import DEFAULT_RUN_RETRIES, OnRetry
|
|
@@ -76,16 +77,23 @@ class AsyncDeploymentClient:
|
|
|
76
77
|
return self._workspace_name
|
|
77
78
|
|
|
78
79
|
def deployment_base_url(self, deployment_id: Any) -> str:
|
|
79
|
-
"""The
|
|
80
|
-
|
|
81
|
-
Append ``/chat/completions`` to call it directly, or hand it to an OpenAI client as ``base_url``.
|
|
82
|
-
Only usable once the deployment has an active revision.
|
|
80
|
+
"""The per-deployment base URL (for per-deployment routes like ``/chat`` and ``/chat-stream``).
|
|
83
81
|
|
|
84
82
|
:param deployment_id: Id of the deployment (e.g. ``DeployResult.deployment.deployment_id``).
|
|
85
83
|
:return: The deployment's base URL.
|
|
86
84
|
"""
|
|
87
85
|
return deployment_base_url(self._api_config.api_url, self._workspace_name, deployment_id)
|
|
88
86
|
|
|
87
|
+
def chat_completions_base_url(self) -> str:
|
|
88
|
+
"""The workspace-scoped OpenAI-compatible base URL for chat completions.
|
|
89
|
+
|
|
90
|
+
Use ``POST <this>/chat/completions`` with ``model`` set to ``{workspace}/{deployment_id}`` (UUID).
|
|
91
|
+
Pass this as ``base_url`` to an OpenAI client.
|
|
92
|
+
|
|
93
|
+
:return: e.g. ``https://api.cloud.deepset.ai/api/v1/workspaces/my-ws/deployments/v1``.
|
|
94
|
+
"""
|
|
95
|
+
return chat_completions_base_url(self._api_config.api_url, self._workspace_name)
|
|
96
|
+
|
|
89
97
|
@asynccontextmanager
|
|
90
98
|
async def _service(self) -> AsyncIterator[DeploymentService]:
|
|
91
99
|
"""Yield a :class:`DeploymentService` backed by a managed API client."""
|
|
@@ -210,6 +218,20 @@ class AsyncDeploymentClient:
|
|
|
210
218
|
async with self._service() as service:
|
|
211
219
|
return await service.get_service_status(service_name)
|
|
212
220
|
|
|
221
|
+
async def add_tag(self, service_name: str, tag_name: str) -> List[str]:
|
|
222
|
+
"""Add a tag to a service by name, independent of deploy/create. See
|
|
223
|
+
:meth:`haystack_enterprise_sdk._service.deployment_service.DeploymentService.add_tag`.
|
|
224
|
+
"""
|
|
225
|
+
async with self._service() as service:
|
|
226
|
+
return await service.add_tag(service_name, tag_name)
|
|
227
|
+
|
|
228
|
+
async def remove_tag(self, service_name: str, tag_name: str) -> List[str]:
|
|
229
|
+
"""Remove a tag from a service by name. See
|
|
230
|
+
:meth:`haystack_enterprise_sdk._service.deployment_service.DeploymentService.remove_tag`.
|
|
231
|
+
"""
|
|
232
|
+
async with self._service() as service:
|
|
233
|
+
return await service.remove_tag(service_name, tag_name)
|
|
234
|
+
|
|
213
235
|
async def create_shared_prototype(
|
|
214
236
|
self, service_name: str, options: Optional[ShareOptions] = None
|
|
215
237
|
) -> SharedPrototype:
|
|
@@ -183,6 +183,14 @@ class DeploymentClient: # pylint: disable=too-few-public-methods
|
|
|
183
183
|
"""Return the current deployment (with live runtime status) for ``service_name``."""
|
|
184
184
|
return _run(self._async_client.get_service_status(service_name))
|
|
185
185
|
|
|
186
|
+
def add_tag(self, service_name: str, tag_name: str) -> List[str]:
|
|
187
|
+
"""Add a tag to a service by name, independent of deploy/create, synchronously."""
|
|
188
|
+
return _run(self._async_client.add_tag(service_name, tag_name))
|
|
189
|
+
|
|
190
|
+
def remove_tag(self, service_name: str, tag_name: str) -> List[str]:
|
|
191
|
+
"""Remove a tag from a service by name synchronously."""
|
|
192
|
+
return _run(self._async_client.remove_tag(service_name, tag_name))
|
|
193
|
+
|
|
186
194
|
def create_shared_prototype(self, service_name: str, options: Optional[ShareOptions] = None) -> SharedPrototype:
|
|
187
195
|
"""Create a shared prototype (a shareable chat UI link) for a deployed service synchronously."""
|
|
188
196
|
return _run(self._async_client.create_shared_prototype(service_name, options))
|
|
@@ -193,5 +201,9 @@ class DeploymentClient: # pylint: disable=too-few-public-methods
|
|
|
193
201
|
return self._async_client.workspace_name
|
|
194
202
|
|
|
195
203
|
def deployment_base_url(self, deployment_id: Any) -> str:
|
|
196
|
-
"""The
|
|
204
|
+
"""The per-deployment base URL. See :meth:`AsyncDeploymentClient.deployment_base_url`."""
|
|
197
205
|
return self._async_client.deployment_base_url(deployment_id)
|
|
206
|
+
|
|
207
|
+
def chat_completions_base_url(self) -> str:
|
|
208
|
+
"""The workspace-scoped chat-completions base URL. See :meth:`AsyncDeploymentClient.chat_completions_base_url`."""
|
|
209
|
+
return self._async_client.chat_completions_base_url()
|
|
File without changes
|
{haystack_enterprise_sdk-0.1.2 → haystack_enterprise_sdk-0.2.0}/haystack_enterprise_sdk/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{haystack_enterprise_sdk-0.1.2 → haystack_enterprise_sdk-0.2.0}/haystack_enterprise_sdk/_console.py
RENAMED
|
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
|
{haystack_enterprise_sdk-0.1.2 → haystack_enterprise_sdk-0.2.0}/haystack_enterprise_sdk/models.py
RENAMED
|
File without changes
|
{haystack_enterprise_sdk-0.1.2 → haystack_enterprise_sdk-0.2.0}/haystack_enterprise_sdk/py.typed
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|