deepset-mcp 0.0.2__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.
- deepset_mcp/__init__.py +0 -0
- deepset_mcp/agents/__init__.py +0 -0
- deepset_mcp/agents/debugging/__init__.py +0 -0
- deepset_mcp/agents/debugging/debugging_agent.py +37 -0
- deepset_mcp/agents/debugging/system_prompt.md +214 -0
- deepset_mcp/agents/generalist/__init__.py +0 -0
- deepset_mcp/agents/generalist/generalist_agent.py +38 -0
- deepset_mcp/agents/generalist/system_prompt.md +241 -0
- deepset_mcp/api/README.md +536 -0
- deepset_mcp/api/__init__.py +0 -0
- deepset_mcp/api/client.py +277 -0
- deepset_mcp/api/custom_components/__init__.py +0 -0
- deepset_mcp/api/custom_components/models.py +25 -0
- deepset_mcp/api/custom_components/protocols.py +17 -0
- deepset_mcp/api/custom_components/resource.py +56 -0
- deepset_mcp/api/exceptions.py +70 -0
- deepset_mcp/api/haystack_service/__init__.py +0 -0
- deepset_mcp/api/haystack_service/protocols.py +13 -0
- deepset_mcp/api/haystack_service/resource.py +55 -0
- deepset_mcp/api/indexes/__init__.py +0 -0
- deepset_mcp/api/indexes/models.py +63 -0
- deepset_mcp/api/indexes/protocols.py +53 -0
- deepset_mcp/api/indexes/resource.py +138 -0
- deepset_mcp/api/integrations/__init__.py +1 -0
- deepset_mcp/api/integrations/models.py +49 -0
- deepset_mcp/api/integrations/protocols.py +27 -0
- deepset_mcp/api/integrations/resource.py +57 -0
- deepset_mcp/api/pipeline/__init__.py +17 -0
- deepset_mcp/api/pipeline/log_level.py +9 -0
- deepset_mcp/api/pipeline/models.py +235 -0
- deepset_mcp/api/pipeline/protocols.py +83 -0
- deepset_mcp/api/pipeline/resource.py +378 -0
- deepset_mcp/api/pipeline_template/__init__.py +0 -0
- deepset_mcp/api/pipeline_template/models.py +56 -0
- deepset_mcp/api/pipeline_template/protocols.py +17 -0
- deepset_mcp/api/pipeline_template/resource.py +88 -0
- deepset_mcp/api/protocols.py +122 -0
- deepset_mcp/api/secrets/__init__.py +0 -0
- deepset_mcp/api/secrets/models.py +16 -0
- deepset_mcp/api/secrets/protocols.py +29 -0
- deepset_mcp/api/secrets/resource.py +112 -0
- deepset_mcp/api/shared_models.py +17 -0
- deepset_mcp/api/transport.py +336 -0
- deepset_mcp/api/user/__init__.py +0 -0
- deepset_mcp/api/user/protocols.py +11 -0
- deepset_mcp/api/user/resource.py +38 -0
- deepset_mcp/api/workspace/__init__.py +7 -0
- deepset_mcp/api/workspace/models.py +23 -0
- deepset_mcp/api/workspace/protocols.py +41 -0
- deepset_mcp/api/workspace/resource.py +94 -0
- deepset_mcp/benchmark/README.md +425 -0
- deepset_mcp/benchmark/__init__.py +1 -0
- deepset_mcp/benchmark/agent_configs/debugging_agent.yml +10 -0
- deepset_mcp/benchmark/agent_configs/generalist_agent.yml +6 -0
- deepset_mcp/benchmark/dp_validation_error_analysis/__init__.py +0 -0
- deepset_mcp/benchmark/dp_validation_error_analysis/eda.ipynb +757 -0
- deepset_mcp/benchmark/dp_validation_error_analysis/prepare_interaction_data.ipynb +167 -0
- deepset_mcp/benchmark/dp_validation_error_analysis/preprocessing_utils.py +213 -0
- deepset_mcp/benchmark/runner/__init__.py +0 -0
- deepset_mcp/benchmark/runner/agent_benchmark_runner.py +561 -0
- deepset_mcp/benchmark/runner/agent_loader.py +110 -0
- deepset_mcp/benchmark/runner/cli.py +39 -0
- deepset_mcp/benchmark/runner/cli_agent.py +373 -0
- deepset_mcp/benchmark/runner/cli_index.py +71 -0
- deepset_mcp/benchmark/runner/cli_pipeline.py +73 -0
- deepset_mcp/benchmark/runner/cli_tests.py +226 -0
- deepset_mcp/benchmark/runner/cli_utils.py +61 -0
- deepset_mcp/benchmark/runner/config.py +73 -0
- deepset_mcp/benchmark/runner/config_loader.py +64 -0
- deepset_mcp/benchmark/runner/interactive.py +140 -0
- deepset_mcp/benchmark/runner/models.py +203 -0
- deepset_mcp/benchmark/runner/repl.py +67 -0
- deepset_mcp/benchmark/runner/setup_actions.py +238 -0
- deepset_mcp/benchmark/runner/streaming.py +360 -0
- deepset_mcp/benchmark/runner/teardown_actions.py +196 -0
- deepset_mcp/benchmark/runner/tracing.py +21 -0
- deepset_mcp/benchmark/tasks/chat_rag_answers_wrong_format.yml +16 -0
- deepset_mcp/benchmark/tasks/documents_output_wrong.yml +13 -0
- deepset_mcp/benchmark/tasks/jinja_str_instead_of_complex_type.yml +11 -0
- deepset_mcp/benchmark/tasks/jinja_syntax_error.yml +11 -0
- deepset_mcp/benchmark/tasks/missing_output_mapping.yml +14 -0
- deepset_mcp/benchmark/tasks/no_query_input.yml +13 -0
- deepset_mcp/benchmark/tasks/pipelines/chat_agent_jinja_str.yml +141 -0
- deepset_mcp/benchmark/tasks/pipelines/chat_agent_jinja_syntax.yml +141 -0
- deepset_mcp/benchmark/tasks/pipelines/chat_rag_answers_wrong_format.yml +181 -0
- deepset_mcp/benchmark/tasks/pipelines/chat_rag_missing_output_mapping.yml +189 -0
- deepset_mcp/benchmark/tasks/pipelines/rag_documents_wrong_format.yml +193 -0
- deepset_mcp/benchmark/tasks/pipelines/rag_no_query_input.yml +191 -0
- deepset_mcp/benchmark/tasks/pipelines/standard_index.yml +167 -0
- deepset_mcp/initialize_embedding_model.py +12 -0
- deepset_mcp/main.py +133 -0
- deepset_mcp/prompts/deepset_copilot_prompt.md +271 -0
- deepset_mcp/prompts/deepset_debugging_agent.md +214 -0
- deepset_mcp/store.py +5 -0
- deepset_mcp/tool_factory.py +473 -0
- deepset_mcp/tools/__init__.py +0 -0
- deepset_mcp/tools/custom_components.py +52 -0
- deepset_mcp/tools/doc_search.py +83 -0
- deepset_mcp/tools/haystack_service.py +358 -0
- deepset_mcp/tools/haystack_service_models.py +97 -0
- deepset_mcp/tools/indexes.py +129 -0
- deepset_mcp/tools/model_protocol.py +16 -0
- deepset_mcp/tools/pipeline.py +335 -0
- deepset_mcp/tools/pipeline_template.py +116 -0
- deepset_mcp/tools/secrets.py +45 -0
- deepset_mcp/tools/tokonomics/__init__.py +73 -0
- deepset_mcp/tools/tokonomics/decorators.py +396 -0
- deepset_mcp/tools/tokonomics/explorer.py +347 -0
- deepset_mcp/tools/tokonomics/object_store.py +177 -0
- deepset_mcp/tools/workspace.py +61 -0
- deepset_mcp-0.0.2.dist-info/METADATA +288 -0
- deepset_mcp-0.0.2.dist-info/RECORD +114 -0
- deepset_mcp-0.0.2.dist-info/WHEEL +4 -0
- deepset_mcp-0.0.2.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from typing import Protocol
|
|
2
|
+
|
|
3
|
+
from deepset_mcp.api.secrets.models import Secret, SecretList
|
|
4
|
+
from deepset_mcp.api.shared_models import NoContentResponse
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class SecretResourceProtocol(Protocol):
|
|
8
|
+
"""Protocol defining the implementation for SecretResource."""
|
|
9
|
+
|
|
10
|
+
async def list(
|
|
11
|
+
self,
|
|
12
|
+
limit: int = 10,
|
|
13
|
+
field: str = "created_at",
|
|
14
|
+
order: str = "DESC",
|
|
15
|
+
) -> SecretList:
|
|
16
|
+
"""List secrets with pagination."""
|
|
17
|
+
...
|
|
18
|
+
|
|
19
|
+
async def create(self, name: str, secret: str) -> NoContentResponse:
|
|
20
|
+
"""Create a new secret."""
|
|
21
|
+
...
|
|
22
|
+
|
|
23
|
+
async def get(self, secret_id: str) -> Secret:
|
|
24
|
+
"""Get a specific secret by ID."""
|
|
25
|
+
...
|
|
26
|
+
|
|
27
|
+
async def delete(self, secret_id: str) -> NoContentResponse:
|
|
28
|
+
"""Delete a secret by ID."""
|
|
29
|
+
...
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from deepset_mcp.api.exceptions import ResourceNotFoundError
|
|
4
|
+
from deepset_mcp.api.protocols import AsyncClientProtocol
|
|
5
|
+
from deepset_mcp.api.secrets.models import Secret, SecretList
|
|
6
|
+
from deepset_mcp.api.secrets.protocols import SecretResourceProtocol
|
|
7
|
+
from deepset_mcp.api.shared_models import NoContentResponse
|
|
8
|
+
from deepset_mcp.api.transport import raise_for_status
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class SecretResource(SecretResourceProtocol):
|
|
12
|
+
"""Resource for managing secrets in deepset."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, client: AsyncClientProtocol) -> None:
|
|
15
|
+
"""Initialize a SecretResource.
|
|
16
|
+
|
|
17
|
+
:param client: The API client to use for requests.
|
|
18
|
+
"""
|
|
19
|
+
self._client = client
|
|
20
|
+
|
|
21
|
+
async def list(
|
|
22
|
+
self,
|
|
23
|
+
limit: int = 10,
|
|
24
|
+
field: str = "created_at",
|
|
25
|
+
order: str = "DESC",
|
|
26
|
+
) -> SecretList:
|
|
27
|
+
"""List secrets with pagination.
|
|
28
|
+
|
|
29
|
+
:param limit: Maximum number of secrets to return.
|
|
30
|
+
:param field: Field to sort by.
|
|
31
|
+
:param order: Sort order (ASC or DESC).
|
|
32
|
+
|
|
33
|
+
:returns: List of secrets with pagination info.
|
|
34
|
+
"""
|
|
35
|
+
params = {
|
|
36
|
+
"limit": str(limit),
|
|
37
|
+
"field": field,
|
|
38
|
+
"order": order,
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
resp = await self._client.request(
|
|
42
|
+
endpoint="v2/secrets",
|
|
43
|
+
method="GET",
|
|
44
|
+
response_type=dict[str, Any],
|
|
45
|
+
params=params,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
raise_for_status(resp)
|
|
49
|
+
|
|
50
|
+
if resp.json is None:
|
|
51
|
+
raise ResourceNotFoundError("Failed to retrieve secrets.")
|
|
52
|
+
|
|
53
|
+
return SecretList(**resp.json)
|
|
54
|
+
|
|
55
|
+
async def create(self, name: str, secret: str) -> NoContentResponse:
|
|
56
|
+
"""Create a new secret.
|
|
57
|
+
|
|
58
|
+
:param name: The name of the secret.
|
|
59
|
+
:param secret: The secret value.
|
|
60
|
+
|
|
61
|
+
:returns: NoContentResponse indicating successful creation.
|
|
62
|
+
"""
|
|
63
|
+
data = {
|
|
64
|
+
"name": name,
|
|
65
|
+
"secret": secret,
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
resp = await self._client.request(
|
|
69
|
+
endpoint="v2/secrets",
|
|
70
|
+
method="POST",
|
|
71
|
+
data=data,
|
|
72
|
+
response_type=None,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
raise_for_status(resp)
|
|
76
|
+
return NoContentResponse(message="Secret created successfully.")
|
|
77
|
+
|
|
78
|
+
async def get(self, secret_id: str) -> Secret:
|
|
79
|
+
"""Get a specific secret by ID.
|
|
80
|
+
|
|
81
|
+
:param secret_id: The ID of the secret to retrieve.
|
|
82
|
+
|
|
83
|
+
:returns: Secret information.
|
|
84
|
+
"""
|
|
85
|
+
resp = await self._client.request(
|
|
86
|
+
endpoint=f"v2/secrets/{secret_id}",
|
|
87
|
+
method="GET",
|
|
88
|
+
response_type=dict[str, Any],
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
raise_for_status(resp)
|
|
92
|
+
|
|
93
|
+
if resp.json is None:
|
|
94
|
+
raise ResourceNotFoundError(f"Secret '{secret_id}' not found.")
|
|
95
|
+
|
|
96
|
+
return Secret(**resp.json)
|
|
97
|
+
|
|
98
|
+
async def delete(self, secret_id: str) -> NoContentResponse:
|
|
99
|
+
"""Delete a secret by ID.
|
|
100
|
+
|
|
101
|
+
:param secret_id: The ID of the secret to delete.
|
|
102
|
+
|
|
103
|
+
:returns: NoContentResponse indicating successful deletion.
|
|
104
|
+
"""
|
|
105
|
+
resp = await self._client.request(
|
|
106
|
+
endpoint=f"v2/secrets/{secret_id}",
|
|
107
|
+
method="DELETE",
|
|
108
|
+
response_type=None,
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
raise_for_status(resp)
|
|
112
|
+
return NoContentResponse(message="Secret deleted successfully.")
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from pydantic import BaseModel, Field
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class NoContentResponse(BaseModel):
|
|
5
|
+
"""Response model for an empty response."""
|
|
6
|
+
|
|
7
|
+
success: bool = True
|
|
8
|
+
message: str = "No content"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class DeepsetUser(BaseModel):
|
|
12
|
+
"""Model representing a user on the deepset platform."""
|
|
13
|
+
|
|
14
|
+
id: str = Field(alias="user_id")
|
|
15
|
+
given_name: str | None = None
|
|
16
|
+
family_name: str | None = None
|
|
17
|
+
email: str | None = None
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import time
|
|
3
|
+
from collections.abc import AsyncIterator
|
|
4
|
+
from contextlib import AbstractAsyncContextManager, asynccontextmanager
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Any, Generic, Literal, Protocol, TypeVar, cast, overload
|
|
7
|
+
|
|
8
|
+
import httpx
|
|
9
|
+
|
|
10
|
+
from deepset_mcp.api.exceptions import BadRequestError, RequestTimeoutError, ResourceNotFoundError, UnexpectedAPIError
|
|
11
|
+
|
|
12
|
+
T = TypeVar("T")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class StreamReaderProtocol(Protocol):
|
|
16
|
+
"""Protocol for reading from a stream."""
|
|
17
|
+
|
|
18
|
+
async def aread(self) -> bytes:
|
|
19
|
+
"""Read the entire response body."""
|
|
20
|
+
...
|
|
21
|
+
|
|
22
|
+
def aiter_lines(self) -> AsyncIterator[str]:
|
|
23
|
+
"""Iterate over response lines."""
|
|
24
|
+
...
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class TransportResponse(Generic[T]):
|
|
29
|
+
"""Response envelope for regular HTTP transport."""
|
|
30
|
+
|
|
31
|
+
text: str
|
|
32
|
+
status_code: int
|
|
33
|
+
json: T | None = None
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def success(self) -> bool:
|
|
37
|
+
"""Check if the response was successful (status code < 400)."""
|
|
38
|
+
return self.status_code < 400
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass
|
|
42
|
+
class StreamingResponse:
|
|
43
|
+
"""Response envelope for streaming HTTP transport."""
|
|
44
|
+
|
|
45
|
+
status_code: int
|
|
46
|
+
headers: dict[str, str]
|
|
47
|
+
_reader: StreamReaderProtocol
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def success(self) -> bool:
|
|
51
|
+
"""Check if the response was successful (status code < 400)."""
|
|
52
|
+
return self.status_code < 400
|
|
53
|
+
|
|
54
|
+
async def iter_lines(self) -> AsyncIterator[str]:
|
|
55
|
+
"""
|
|
56
|
+
Iterate over response lines.
|
|
57
|
+
|
|
58
|
+
For error responses (status >= 400), reads the entire body and yields it.
|
|
59
|
+
For success responses, yields line by line.
|
|
60
|
+
"""
|
|
61
|
+
if self.status_code >= 400:
|
|
62
|
+
# For errors, read entire body at once
|
|
63
|
+
body = await self._reader.aread()
|
|
64
|
+
if body:
|
|
65
|
+
yield body.decode()
|
|
66
|
+
else:
|
|
67
|
+
# For success, stream line by line
|
|
68
|
+
async for line in self._reader.aiter_lines():
|
|
69
|
+
if line.startswith("data: "): # optionally handle SSE 'data: ' prefix
|
|
70
|
+
yield line[6:]
|
|
71
|
+
yield line
|
|
72
|
+
|
|
73
|
+
async def read_body(self) -> str:
|
|
74
|
+
"""Read the entire response body. Useful for error handling."""
|
|
75
|
+
body = await self._reader.aread()
|
|
76
|
+
return body.decode() if body else ""
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def raise_for_status(response: TransportResponse[Any]) -> None:
|
|
80
|
+
"""Raises the appropriate exception based on the response status code."""
|
|
81
|
+
if response.success:
|
|
82
|
+
return
|
|
83
|
+
|
|
84
|
+
# Map status codes to exception classes
|
|
85
|
+
exception_map = {
|
|
86
|
+
400: BadRequestError,
|
|
87
|
+
404: ResourceNotFoundError,
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if isinstance(response.json, dict):
|
|
91
|
+
detail = response.json.get("details") if response.json else None
|
|
92
|
+
message = response.json.get("message") if response.json else response.text
|
|
93
|
+
else:
|
|
94
|
+
detail = json.dumps(response.json) if response.json else None
|
|
95
|
+
message = response.text
|
|
96
|
+
|
|
97
|
+
# Get exception class
|
|
98
|
+
exception_class = exception_map.get(response.status_code)
|
|
99
|
+
|
|
100
|
+
if exception_class:
|
|
101
|
+
# For specific exceptions (BadRequestError, ResourceNotFoundError)
|
|
102
|
+
raise exception_class(message=message, detail=detail)
|
|
103
|
+
else:
|
|
104
|
+
# For the catch-all case, include the status code
|
|
105
|
+
raise UnexpectedAPIError(
|
|
106
|
+
status_code=response.status_code, message=message or "Unexpected API error", detail=detail
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class TransportProtocol(Protocol):
|
|
111
|
+
"""Protocol for HTTP transport with separate streaming support."""
|
|
112
|
+
|
|
113
|
+
@overload
|
|
114
|
+
async def request(
|
|
115
|
+
self,
|
|
116
|
+
method: str,
|
|
117
|
+
url: str,
|
|
118
|
+
*,
|
|
119
|
+
response_type: type[T],
|
|
120
|
+
timeout: float | None | Literal["config"] = "config",
|
|
121
|
+
**kwargs: Any,
|
|
122
|
+
) -> TransportResponse[T]: ...
|
|
123
|
+
|
|
124
|
+
@overload
|
|
125
|
+
async def request(
|
|
126
|
+
self,
|
|
127
|
+
method: str,
|
|
128
|
+
url: str,
|
|
129
|
+
*,
|
|
130
|
+
response_type: None = None,
|
|
131
|
+
timeout: float | None | Literal["config"] = "config",
|
|
132
|
+
**kwargs: Any,
|
|
133
|
+
) -> TransportResponse[Any]: ...
|
|
134
|
+
|
|
135
|
+
async def request(
|
|
136
|
+
self,
|
|
137
|
+
method: str,
|
|
138
|
+
url: str,
|
|
139
|
+
*,
|
|
140
|
+
response_type: type[T] | None = None,
|
|
141
|
+
timeout: float | None | Literal["config"] = "config",
|
|
142
|
+
**kwargs: Any,
|
|
143
|
+
) -> TransportResponse[Any]:
|
|
144
|
+
"""Send a regular HTTP request and return the response."""
|
|
145
|
+
...
|
|
146
|
+
|
|
147
|
+
def stream(self, method: str, url: str, **kwargs: Any) -> AbstractAsyncContextManager[StreamingResponse]:
|
|
148
|
+
"""
|
|
149
|
+
Open a streaming HTTP connection.
|
|
150
|
+
|
|
151
|
+
Must be used as an async context manager to ensure proper cleanup.
|
|
152
|
+
"""
|
|
153
|
+
...
|
|
154
|
+
|
|
155
|
+
async def close(self) -> None:
|
|
156
|
+
"""Clean up any resources (e.g., close connections)."""
|
|
157
|
+
...
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class _HttpxStreamReader:
|
|
161
|
+
"""Adapter to make httpx.Response conform to StreamReaderProtocol."""
|
|
162
|
+
|
|
163
|
+
def __init__(self, response: httpx.Response):
|
|
164
|
+
self._response = response
|
|
165
|
+
|
|
166
|
+
async def aread(self) -> bytes:
|
|
167
|
+
"""Read the entire response body."""
|
|
168
|
+
return await self._response.aread()
|
|
169
|
+
|
|
170
|
+
async def aiter_lines(self) -> AsyncIterator[str]:
|
|
171
|
+
"""Iterate over response lines."""
|
|
172
|
+
async for line in self._response.aiter_lines():
|
|
173
|
+
yield line
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
class AsyncTransport:
|
|
177
|
+
"""Asynchronous HTTP transport using httpx.AsyncClient."""
|
|
178
|
+
|
|
179
|
+
def __init__(
|
|
180
|
+
self,
|
|
181
|
+
base_url: str,
|
|
182
|
+
api_key: str,
|
|
183
|
+
config: dict[str, Any] | None = None,
|
|
184
|
+
):
|
|
185
|
+
"""
|
|
186
|
+
Initialize an instance of AsyncTransport.
|
|
187
|
+
|
|
188
|
+
Parameters
|
|
189
|
+
----------
|
|
190
|
+
base_url : str
|
|
191
|
+
Base URL for the API
|
|
192
|
+
api_key : str
|
|
193
|
+
Bearer token for authentication
|
|
194
|
+
config : dict, optional
|
|
195
|
+
Configuration for httpx.AsyncClient, e.g., {'timeout': 10.0}
|
|
196
|
+
"""
|
|
197
|
+
config = config or {}
|
|
198
|
+
# Ensure auth header
|
|
199
|
+
headers = config.pop("headers", {})
|
|
200
|
+
headers.setdefault("Authorization", f"Bearer {api_key}")
|
|
201
|
+
# Build client kwargs
|
|
202
|
+
client_kwargs = {
|
|
203
|
+
"base_url": base_url,
|
|
204
|
+
"headers": headers,
|
|
205
|
+
**config,
|
|
206
|
+
}
|
|
207
|
+
self._client = httpx.AsyncClient(**client_kwargs)
|
|
208
|
+
|
|
209
|
+
@overload
|
|
210
|
+
async def request(
|
|
211
|
+
self,
|
|
212
|
+
method: str,
|
|
213
|
+
url: str,
|
|
214
|
+
*,
|
|
215
|
+
response_type: type[T],
|
|
216
|
+
timeout: float | None | Literal["config"] = "config",
|
|
217
|
+
**kwargs: Any,
|
|
218
|
+
) -> TransportResponse[T]: ...
|
|
219
|
+
|
|
220
|
+
@overload
|
|
221
|
+
async def request(
|
|
222
|
+
self,
|
|
223
|
+
method: str,
|
|
224
|
+
url: str,
|
|
225
|
+
*,
|
|
226
|
+
response_type: None = None,
|
|
227
|
+
timeout: float | None | Literal["config"] = "config",
|
|
228
|
+
**kwargs: Any,
|
|
229
|
+
) -> TransportResponse[Any]: ...
|
|
230
|
+
|
|
231
|
+
async def request(
|
|
232
|
+
self,
|
|
233
|
+
method: str,
|
|
234
|
+
url: str,
|
|
235
|
+
*,
|
|
236
|
+
response_type: type[T] | None = None,
|
|
237
|
+
timeout: float | None | Literal["config"] = "config",
|
|
238
|
+
**kwargs: Any,
|
|
239
|
+
) -> TransportResponse[Any]:
|
|
240
|
+
"""
|
|
241
|
+
Send a regular HTTP request and return the response.
|
|
242
|
+
|
|
243
|
+
Parameters
|
|
244
|
+
----------
|
|
245
|
+
method : str
|
|
246
|
+
HTTP method
|
|
247
|
+
url : str
|
|
248
|
+
URL endpoint
|
|
249
|
+
response_type : type[T], optional
|
|
250
|
+
Expected response type for type checking
|
|
251
|
+
timeout : float | None | Literal["config"], optional
|
|
252
|
+
Request timeout in seconds. If "config", uses transport config timeout.
|
|
253
|
+
If None, disables timeout. If float, uses specific timeout.
|
|
254
|
+
**kwargs : Any
|
|
255
|
+
Additional arguments to pass to httpx
|
|
256
|
+
|
|
257
|
+
Returns
|
|
258
|
+
-------
|
|
259
|
+
TransportResponse[T]
|
|
260
|
+
The response with parsed JSON if available
|
|
261
|
+
"""
|
|
262
|
+
if timeout != "config":
|
|
263
|
+
kwargs["timeout"] = timeout
|
|
264
|
+
|
|
265
|
+
start_time = time.time()
|
|
266
|
+
try:
|
|
267
|
+
response = await self._client.request(method, url, **kwargs)
|
|
268
|
+
except httpx.TimeoutException as e:
|
|
269
|
+
duration = time.time() - start_time
|
|
270
|
+
timeout_value = kwargs.get("timeout", "config default")
|
|
271
|
+
|
|
272
|
+
detail = None
|
|
273
|
+
if "search" in url and duration > 60:
|
|
274
|
+
detail = (
|
|
275
|
+
"Search operations can take longer with large document collections or complex pipelines. "
|
|
276
|
+
"Consider increasing the timeout for search requests."
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
raise RequestTimeoutError(
|
|
280
|
+
method=method, url=url, timeout=timeout_value, duration=duration, detail=detail
|
|
281
|
+
) from e
|
|
282
|
+
|
|
283
|
+
if response_type is not None:
|
|
284
|
+
raw = response.json()
|
|
285
|
+
payload: T = cast(T, raw)
|
|
286
|
+
return TransportResponse(text=response.text, status_code=response.status_code, json=payload)
|
|
287
|
+
|
|
288
|
+
try:
|
|
289
|
+
untyped_response = response.json()
|
|
290
|
+
except json.JSONDecodeError:
|
|
291
|
+
untyped_response = None
|
|
292
|
+
|
|
293
|
+
return TransportResponse(text=response.text, status_code=response.status_code, json=untyped_response)
|
|
294
|
+
|
|
295
|
+
def stream(self, method: str, url: str, **kwargs: Any) -> AbstractAsyncContextManager[StreamingResponse]:
|
|
296
|
+
"""
|
|
297
|
+
Open a streaming HTTP connection.
|
|
298
|
+
|
|
299
|
+
Parameters
|
|
300
|
+
----------
|
|
301
|
+
method : str
|
|
302
|
+
HTTP method
|
|
303
|
+
url : str
|
|
304
|
+
URL endpoint
|
|
305
|
+
**kwargs : Any
|
|
306
|
+
Additional arguments to pass to httpx.stream()
|
|
307
|
+
|
|
308
|
+
Yields
|
|
309
|
+
------
|
|
310
|
+
StreamingResponse
|
|
311
|
+
Response object with streaming capabilities
|
|
312
|
+
|
|
313
|
+
Examples
|
|
314
|
+
--------
|
|
315
|
+
async with transport.stream("POST", "/api/stream", json=data) as response:
|
|
316
|
+
if response.success:
|
|
317
|
+
async for line in response.iter_lines():
|
|
318
|
+
process_line(line)
|
|
319
|
+
else:
|
|
320
|
+
error = await response.read_body()
|
|
321
|
+
handle_error(error)
|
|
322
|
+
"""
|
|
323
|
+
|
|
324
|
+
@asynccontextmanager
|
|
325
|
+
async def _stream() -> AsyncIterator[StreamingResponse]:
|
|
326
|
+
async with self._client.stream(method, url, **kwargs) as response:
|
|
327
|
+
reader = _HttpxStreamReader(response)
|
|
328
|
+
yield StreamingResponse(
|
|
329
|
+
status_code=response.status_code, headers=dict(response.headers), _reader=reader
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
return _stream()
|
|
333
|
+
|
|
334
|
+
async def close(self) -> None:
|
|
335
|
+
"""Clean up any resources (e.g., close connections)."""
|
|
336
|
+
await self._client.aclose()
|
|
File without changes
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from typing import Protocol
|
|
2
|
+
|
|
3
|
+
from deepset_mcp.api.shared_models import DeepsetUser
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class UserResourceProtocol(Protocol):
|
|
7
|
+
"""Protocol defining the implementation for UserResource."""
|
|
8
|
+
|
|
9
|
+
async def get(self, user_id: str) -> DeepsetUser:
|
|
10
|
+
"""Get user information by user ID."""
|
|
11
|
+
...
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from deepset_mcp.api.exceptions import ResourceNotFoundError
|
|
4
|
+
from deepset_mcp.api.protocols import AsyncClientProtocol
|
|
5
|
+
from deepset_mcp.api.shared_models import DeepsetUser
|
|
6
|
+
from deepset_mcp.api.transport import raise_for_status
|
|
7
|
+
from deepset_mcp.api.user.protocols import UserResourceProtocol
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class UserResource(UserResourceProtocol):
|
|
11
|
+
"""Resource for managing users in deepset."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, client: AsyncClientProtocol) -> None:
|
|
14
|
+
"""Initialize a UserResource.
|
|
15
|
+
|
|
16
|
+
:param client: The API client to use for requests.
|
|
17
|
+
"""
|
|
18
|
+
self._client = client
|
|
19
|
+
|
|
20
|
+
async def get(self, user_id: str) -> DeepsetUser:
|
|
21
|
+
"""Get user information by user ID.
|
|
22
|
+
|
|
23
|
+
:param user_id: The ID of the user to fetch.
|
|
24
|
+
|
|
25
|
+
:returns: User information.
|
|
26
|
+
"""
|
|
27
|
+
resp = await self._client.request(
|
|
28
|
+
endpoint=f"v1/users/{user_id}",
|
|
29
|
+
method="GET",
|
|
30
|
+
response_type=dict[str, Any],
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
raise_for_status(resp)
|
|
34
|
+
|
|
35
|
+
if resp.json is None:
|
|
36
|
+
raise ResourceNotFoundError(f"User '{user_id}' not found.")
|
|
37
|
+
|
|
38
|
+
return DeepsetUser(**resp.json)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Models for workspace API responses."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
from uuid import UUID
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Workspace(BaseModel):
|
|
10
|
+
"""Model representing a workspace on the deepset platform."""
|
|
11
|
+
|
|
12
|
+
name: str
|
|
13
|
+
workspace_id: UUID
|
|
14
|
+
languages: dict[str, Any]
|
|
15
|
+
default_idle_timeout_in_seconds: int
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class WorkspaceList(BaseModel):
|
|
19
|
+
"""Model representing a list of workspaces."""
|
|
20
|
+
|
|
21
|
+
data: list[Workspace]
|
|
22
|
+
has_more: bool = False
|
|
23
|
+
total: int
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Protocols for workspace resources."""
|
|
2
|
+
|
|
3
|
+
from typing import Protocol
|
|
4
|
+
|
|
5
|
+
from deepset_mcp.api.shared_models import NoContentResponse
|
|
6
|
+
from deepset_mcp.api.workspace.models import Workspace, WorkspaceList
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class WorkspaceResourceProtocol(Protocol):
|
|
10
|
+
"""Protocol defining the interface for workspace resources."""
|
|
11
|
+
|
|
12
|
+
async def list(self) -> WorkspaceList:
|
|
13
|
+
"""List all workspaces.
|
|
14
|
+
|
|
15
|
+
:returns: A WorkspaceList containing all workspaces.
|
|
16
|
+
"""
|
|
17
|
+
...
|
|
18
|
+
|
|
19
|
+
async def get(self, workspace_name: str) -> Workspace:
|
|
20
|
+
"""Get a specific workspace by name.
|
|
21
|
+
|
|
22
|
+
:param workspace_name: Name of the workspace to fetch.
|
|
23
|
+
:returns: A Workspace instance.
|
|
24
|
+
"""
|
|
25
|
+
...
|
|
26
|
+
|
|
27
|
+
async def create(self, name: str) -> NoContentResponse:
|
|
28
|
+
"""Create a new workspace.
|
|
29
|
+
|
|
30
|
+
:param name: Name of the new workspace.
|
|
31
|
+
:returns: NoContentResponse indicating successful creation.
|
|
32
|
+
"""
|
|
33
|
+
...
|
|
34
|
+
|
|
35
|
+
async def delete(self, workspace_name: str) -> NoContentResponse:
|
|
36
|
+
"""Delete a workspace.
|
|
37
|
+
|
|
38
|
+
:param workspace_name: Name of the workspace to delete.
|
|
39
|
+
:returns: NoContentResponse indicating successful deletion.
|
|
40
|
+
"""
|
|
41
|
+
...
|