vectoramp 0.1.0__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.
- vectoramp/__init__.py +45 -0
- vectoramp/client.py +133 -0
- vectoramp/connections.py +123 -0
- vectoramp/embeddings.py +39 -0
- vectoramp/exceptions.py +22 -0
- vectoramp/py.typed +0 -0
- vectoramp/resources.py +1531 -0
- vectoramp/sources.py +506 -0
- vectoramp/transport.py +198 -0
- vectoramp/types.py +145 -0
- vectoramp-0.1.0.dist-info/METADATA +464 -0
- vectoramp-0.1.0.dist-info/RECORD +15 -0
- vectoramp-0.1.0.dist-info/WHEEL +4 -0
- vectoramp-0.1.0.dist-info/licenses/LICENSE +201 -0
- vectoramp-0.1.0.dist-info/licenses/NOTICE +8 -0
vectoramp/__init__.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""VectorAmp Python SDK."""
|
|
2
|
+
|
|
3
|
+
from .client import VectorAmp
|
|
4
|
+
from .embeddings import (
|
|
5
|
+
EMBEDDING_DIMENSIONS,
|
|
6
|
+
EMBEDDINGS,
|
|
7
|
+
OPENAI_TEXT_EMBEDDING_3_LARGE,
|
|
8
|
+
OPENAI_TEXT_EMBEDDING_3_SMALL,
|
|
9
|
+
VECTORAMP_EMBEDDING_4B,
|
|
10
|
+
openai,
|
|
11
|
+
)
|
|
12
|
+
from .exceptions import APIError, AuthenticationError, VectorAmpError
|
|
13
|
+
from .resources import Dataset
|
|
14
|
+
from .sources import (
|
|
15
|
+
ConfluenceSource,
|
|
16
|
+
FileUploadSource,
|
|
17
|
+
GCSSource,
|
|
18
|
+
GenericSource,
|
|
19
|
+
GoogleDriveSource,
|
|
20
|
+
JiraSource,
|
|
21
|
+
S3Source,
|
|
22
|
+
WebSource,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
"APIError",
|
|
27
|
+
"AuthenticationError",
|
|
28
|
+
"ConfluenceSource",
|
|
29
|
+
"Dataset",
|
|
30
|
+
"EMBEDDING_DIMENSIONS",
|
|
31
|
+
"EMBEDDINGS",
|
|
32
|
+
"FileUploadSource",
|
|
33
|
+
"GCSSource",
|
|
34
|
+
"GenericSource",
|
|
35
|
+
"GoogleDriveSource",
|
|
36
|
+
"JiraSource",
|
|
37
|
+
"OPENAI_TEXT_EMBEDDING_3_LARGE",
|
|
38
|
+
"OPENAI_TEXT_EMBEDDING_3_SMALL",
|
|
39
|
+
"S3Source",
|
|
40
|
+
"VectorAmp",
|
|
41
|
+
"VECTORAMP_EMBEDDING_4B",
|
|
42
|
+
"VectorAmpError",
|
|
43
|
+
"WebSource",
|
|
44
|
+
"openai",
|
|
45
|
+
]
|
vectoramp/client.py
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"""Top-level VectorAmp client."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from types import TracebackType
|
|
7
|
+
from typing import Iterator, Optional, Type
|
|
8
|
+
|
|
9
|
+
import httpx
|
|
10
|
+
|
|
11
|
+
from .connections import ConnectionsResource
|
|
12
|
+
from .resources import DatasetsResource, IngestionResource, IntelligenceResource, SchedulesResource
|
|
13
|
+
from .transport import BaseTransport, RestTransport
|
|
14
|
+
from .types import JSON, ConversationTurn
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class VectorAmp:
|
|
18
|
+
"""Synchronous VectorAmp API client.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
api_key: API key to send as bearer auth. Defaults to
|
|
22
|
+
``VECTORAMP_API_KEY`` when omitted.
|
|
23
|
+
base_url: API origin. Defaults to ``https://api.vectoramp.com``.
|
|
24
|
+
timeout: Request timeout in seconds. Defaults to ``30.0``.
|
|
25
|
+
transport: Optional custom transport, primarily for tests or adapters.
|
|
26
|
+
http_client: Optional ``httpx.Client`` used by the default REST transport.
|
|
27
|
+
|
|
28
|
+
Attributes:
|
|
29
|
+
datasets: Dataset management, search, embedding, and insertion APIs.
|
|
30
|
+
ingestion: Source, upload, and ingestion-job APIs.
|
|
31
|
+
sources: Alias for ``ingestion``.
|
|
32
|
+
connections: OAuth connection management for providers.
|
|
33
|
+
schedules: Recurring ingestion schedule management.
|
|
34
|
+
intelligence: RAG query and streaming APIs.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(
|
|
38
|
+
self,
|
|
39
|
+
api_key: Optional[str] = None,
|
|
40
|
+
*,
|
|
41
|
+
base_url: str = "https://api.vectoramp.com",
|
|
42
|
+
timeout: float = 30.0,
|
|
43
|
+
transport: Optional[BaseTransport] = None,
|
|
44
|
+
http_client: Optional[httpx.Client] = None,
|
|
45
|
+
) -> None:
|
|
46
|
+
resolved_api_key = api_key or os.getenv("VECTORAMP_API_KEY") or ""
|
|
47
|
+
self.transport = transport or RestTransport(
|
|
48
|
+
api_key=resolved_api_key,
|
|
49
|
+
base_url=base_url,
|
|
50
|
+
timeout=timeout,
|
|
51
|
+
client=http_client,
|
|
52
|
+
)
|
|
53
|
+
self.datasets = DatasetsResource(self.transport, client=self)
|
|
54
|
+
self.ingestion = IngestionResource(self.transport)
|
|
55
|
+
self.sources = self.ingestion
|
|
56
|
+
self.connections = ConnectionsResource(self.transport)
|
|
57
|
+
self.schedules = SchedulesResource(self.transport)
|
|
58
|
+
self.intelligence = IntelligenceResource(self.transport)
|
|
59
|
+
|
|
60
|
+
def ask(
|
|
61
|
+
self,
|
|
62
|
+
query: str,
|
|
63
|
+
*,
|
|
64
|
+
dataset_id: Optional[str] = None,
|
|
65
|
+
top_k: int = 5,
|
|
66
|
+
conversation_history: Optional[list[ConversationTurn]] = None,
|
|
67
|
+
include_sources: bool = True,
|
|
68
|
+
) -> JSON:
|
|
69
|
+
"""Ask a non-streaming RAG question.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
query: Natural-language question.
|
|
73
|
+
dataset_id: Optional dataset to ground the answer in. When omitted,
|
|
74
|
+
the API chooses its configured/default scope.
|
|
75
|
+
top_k: Number of retrieved chunks to consider. Defaults to ``5``.
|
|
76
|
+
conversation_history: Optional prior chat turns.
|
|
77
|
+
include_sources: Whether to include source chunks. Defaults to ``True``.
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
JSON response from ``/intelligence/query``.
|
|
81
|
+
"""
|
|
82
|
+
return self.intelligence.query(
|
|
83
|
+
query,
|
|
84
|
+
dataset_id=dataset_id,
|
|
85
|
+
top_k=top_k,
|
|
86
|
+
conversation_history=conversation_history,
|
|
87
|
+
include_sources=include_sources,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
def ask_stream(
|
|
91
|
+
self,
|
|
92
|
+
query: str,
|
|
93
|
+
*,
|
|
94
|
+
dataset_id: Optional[str] = None,
|
|
95
|
+
top_k: int = 5,
|
|
96
|
+
conversation_history: Optional[list[ConversationTurn]] = None,
|
|
97
|
+
include_sources: bool = True,
|
|
98
|
+
) -> Iterator[JSON]:
|
|
99
|
+
"""Yield Server-Sent Event chunks for a streaming RAG answer.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
query: Natural-language question.
|
|
103
|
+
dataset_id: Optional dataset to ground the answer in. When omitted,
|
|
104
|
+
the API chooses its configured/default scope.
|
|
105
|
+
top_k: Number of retrieved chunks to consider. Defaults to ``5``.
|
|
106
|
+
conversation_history: Optional prior chat turns.
|
|
107
|
+
include_sources: Whether to include source chunks. Defaults to ``True``.
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
Iterator of JSON Server-Sent Event payloads.
|
|
111
|
+
"""
|
|
112
|
+
yield from self.intelligence.stream(
|
|
113
|
+
query,
|
|
114
|
+
dataset_id=dataset_id,
|
|
115
|
+
top_k=top_k,
|
|
116
|
+
conversation_history=conversation_history,
|
|
117
|
+
include_sources=include_sources,
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
def close(self) -> None:
|
|
121
|
+
"""Close the underlying transport and HTTP connection pool."""
|
|
122
|
+
self.transport.close()
|
|
123
|
+
|
|
124
|
+
def __enter__(self) -> "VectorAmp":
|
|
125
|
+
return self
|
|
126
|
+
|
|
127
|
+
def __exit__(
|
|
128
|
+
self,
|
|
129
|
+
exc_type: Optional[Type[BaseException]],
|
|
130
|
+
exc: Optional[BaseException],
|
|
131
|
+
traceback: Optional[TracebackType],
|
|
132
|
+
) -> None:
|
|
133
|
+
self.close()
|
vectoramp/connections.py
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""OAuth connection management for the VectorAmp SDK.
|
|
2
|
+
|
|
3
|
+
Connections hold the stored OAuth grant for a provider (Google Drive, Atlassian,
|
|
4
|
+
etc.). They live at the gateway root ``/connections`` namespace (not under
|
|
5
|
+
``/ingestion``) and are referenced from a source via ``connection_id``.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import time
|
|
11
|
+
from typing import Callable, Optional
|
|
12
|
+
|
|
13
|
+
from .transport import BaseTransport
|
|
14
|
+
from .types import JSON
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _print_authorization_url(url: str) -> None:
|
|
18
|
+
"""Default ``on_url`` handler: print the authorization URL and instructions."""
|
|
19
|
+
print(
|
|
20
|
+
"To authorize this connection, open the following URL in your browser:\n"
|
|
21
|
+
f" {url}\n"
|
|
22
|
+
"Waiting for authorization to complete..."
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ConnectionsResource:
|
|
27
|
+
"""OAuth connection management.
|
|
28
|
+
|
|
29
|
+
A connection captures the OAuth authorization for a provider so that
|
|
30
|
+
ingestion sources can reuse it via ``connection_id`` instead of embedding
|
|
31
|
+
raw credentials. Use :meth:`connect` for the full interactive
|
|
32
|
+
create-authorize-poll flow, or the lower-level CRUD methods directly.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def __init__(self, transport: BaseTransport) -> None:
|
|
36
|
+
self._transport = transport
|
|
37
|
+
|
|
38
|
+
def list(self, *, provider: Optional[str] = None) -> JSON:
|
|
39
|
+
"""List connections for the calling organization.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
provider: Optional provider filter (e.g. ``"google_drive"``).
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
Connection list JSON from ``/connections``.
|
|
46
|
+
"""
|
|
47
|
+
return self._transport.request("GET", "/connections", params={"provider": provider})
|
|
48
|
+
|
|
49
|
+
def create(self, provider: str, *, source_type: Optional[str] = None) -> JSON:
|
|
50
|
+
"""Create a pending connection and return its authorization URL.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
provider: OAuth provider identifier (e.g. ``"google_drive"``).
|
|
54
|
+
source_type: Optional source type the connection will be used for.
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
Created connection JSON with ``id``, ``provider``, ``status``, and
|
|
58
|
+
``authorization_url``.
|
|
59
|
+
"""
|
|
60
|
+
body: JSON = {"provider": provider}
|
|
61
|
+
if source_type is not None:
|
|
62
|
+
body["source_type"] = source_type
|
|
63
|
+
return self._transport.request("POST", "/connections", json_body=body)
|
|
64
|
+
|
|
65
|
+
def get(self, connection_id: str) -> JSON:
|
|
66
|
+
"""Return one connection by id."""
|
|
67
|
+
return self._transport.request("GET", f"/connections/{connection_id}")
|
|
68
|
+
|
|
69
|
+
def delete(self, connection_id: str) -> JSON:
|
|
70
|
+
"""Delete a connection."""
|
|
71
|
+
return self._transport.request("DELETE", f"/connections/{connection_id}")
|
|
72
|
+
|
|
73
|
+
def connect(
|
|
74
|
+
self,
|
|
75
|
+
provider: str,
|
|
76
|
+
*,
|
|
77
|
+
source_type: Optional[str] = None,
|
|
78
|
+
on_url: Optional[Callable[[str], None]] = None,
|
|
79
|
+
poll_interval: float = 2.0,
|
|
80
|
+
timeout: float = 300.0,
|
|
81
|
+
) -> JSON:
|
|
82
|
+
"""Run the full create-authorize-poll connection flow.
|
|
83
|
+
|
|
84
|
+
Creates a connection, surfaces its ``authorization_url`` (via ``on_url``,
|
|
85
|
+
which defaults to printing the URL and instructions), then polls until the
|
|
86
|
+
connection reports ``status == "connected"`` or ``timeout`` elapses.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
provider: OAuth provider identifier (e.g. ``"google_drive"``).
|
|
90
|
+
source_type: Optional source type the connection will be used for.
|
|
91
|
+
on_url: Callback invoked with the authorization URL. Defaults to
|
|
92
|
+
printing the URL and instructions to stdout.
|
|
93
|
+
poll_interval: Seconds between status polls. Defaults to ``2.0``.
|
|
94
|
+
timeout: Maximum seconds to wait for authorization. Defaults to ``300.0``.
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
The connected connection JSON.
|
|
98
|
+
|
|
99
|
+
Raises:
|
|
100
|
+
ValueError: If the create response lacks an id.
|
|
101
|
+
TimeoutError: If the connection is not connected before ``timeout``.
|
|
102
|
+
"""
|
|
103
|
+
connection = self.create(provider, source_type=source_type)
|
|
104
|
+
connection_id = connection.get("id") or connection.get("connection_id")
|
|
105
|
+
if connection_id is None:
|
|
106
|
+
raise ValueError("Connection creation response did not include id.")
|
|
107
|
+
connection_id = str(connection_id)
|
|
108
|
+
|
|
109
|
+
authorization_url = connection.get("authorization_url")
|
|
110
|
+
if authorization_url is not None:
|
|
111
|
+
callback = on_url or _print_authorization_url
|
|
112
|
+
callback(str(authorization_url))
|
|
113
|
+
|
|
114
|
+
deadline = time.monotonic() + timeout
|
|
115
|
+
while True:
|
|
116
|
+
current = self.get(connection_id)
|
|
117
|
+
if current.get("status") == "connected":
|
|
118
|
+
return current
|
|
119
|
+
if time.monotonic() >= deadline:
|
|
120
|
+
raise TimeoutError(
|
|
121
|
+
f"Connection {connection_id} was not connected within {timeout}s."
|
|
122
|
+
)
|
|
123
|
+
time.sleep(poll_interval)
|
vectoramp/embeddings.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Embedding helpers for dataset creation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Dict, Literal, Mapping
|
|
6
|
+
|
|
7
|
+
EmbeddingConfig = Dict[str, str]
|
|
8
|
+
OpenAIEmbeddingSize = Literal["small", "large"]
|
|
9
|
+
|
|
10
|
+
VECTORAMP_EMBEDDING_4B = "VectorAmp-Embedding-4B"
|
|
11
|
+
OPENAI_TEXT_EMBEDDING_3_SMALL = "text-embedding-3-small"
|
|
12
|
+
OPENAI_TEXT_EMBEDDING_3_LARGE = "text-embedding-3-large"
|
|
13
|
+
|
|
14
|
+
EMBEDDING_DIMENSIONS: Mapping[str, int] = {
|
|
15
|
+
VECTORAMP_EMBEDDING_4B: 2560,
|
|
16
|
+
OPENAI_TEXT_EMBEDDING_3_SMALL: 1536,
|
|
17
|
+
OPENAI_TEXT_EMBEDDING_3_LARGE: 3072,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def openai(size: OpenAIEmbeddingSize = "small") -> EmbeddingConfig:
|
|
22
|
+
"""Return an OpenAI BYOM embedding config for dataset creation."""
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
"provider": "openai",
|
|
26
|
+
"model": (
|
|
27
|
+
OPENAI_TEXT_EMBEDDING_3_LARGE
|
|
28
|
+
if size == "large"
|
|
29
|
+
else OPENAI_TEXT_EMBEDDING_3_SMALL
|
|
30
|
+
),
|
|
31
|
+
"secret_ref": "emb:openai:api_key",
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
EMBEDDINGS = {
|
|
36
|
+
"vectoramp_4b": {"provider": "vectoramp", "model": VECTORAMP_EMBEDDING_4B},
|
|
37
|
+
"openai_small": openai("small"),
|
|
38
|
+
"openai_large": openai("large"),
|
|
39
|
+
}
|
vectoramp/exceptions.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""VectorAmp SDK exceptions."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class VectorAmpError(Exception):
|
|
9
|
+
"""Base SDK exception."""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AuthenticationError(VectorAmpError):
|
|
13
|
+
"""Raised when no API key is configured."""
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class APIError(VectorAmpError):
|
|
17
|
+
"""Raised for non-successful API responses."""
|
|
18
|
+
|
|
19
|
+
def __init__(self, status_code: int, message: str, *, response: Any = None) -> None:
|
|
20
|
+
self.status_code = status_code
|
|
21
|
+
self.response = response
|
|
22
|
+
super().__init__(f"VectorAmp API error {status_code}: {message}")
|
vectoramp/py.typed
ADDED
|
File without changes
|