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 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()
@@ -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)
@@ -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
+ }
@@ -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