schift 0.3.0__tar.gz → 0.3.3__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.
- schift-0.3.3/.github/workflows/publish.yml +52 -0
- schift-0.3.3/.nojekyll +0 -0
- {schift-0.3.0 → schift-0.3.3}/PKG-INFO +31 -2
- {schift-0.3.0 → schift-0.3.3}/README.md +30 -1
- {schift-0.3.0 → schift-0.3.3}/pyproject.toml +1 -1
- {schift-0.3.0 → schift-0.3.3}/schift/_http.py +4 -0
- schift-0.3.3/schift/aggregate.py +18 -0
- schift-0.3.3/schift/artifacts.py +31 -0
- schift-0.3.3/schift/benchmark_suites.py +29 -0
- schift-0.3.3/schift/buckets.py +124 -0
- schift-0.3.3/schift/completions.py +25 -0
- schift-0.3.3/schift/drift.py +33 -0
- schift-0.3.3/schift/jobs.py +31 -0
- schift-0.3.3/schift/tasks.py +41 -0
- schift-0.3.3/schift/workflows.py +116 -0
- schift-0.3.0/.gitignore +0 -63
- schift-0.3.0/schift/aggregate.py +0 -64
- schift-0.3.0/schift/artifacts.py +0 -93
- schift-0.3.0/schift/benchmark_suites.py +0 -147
- schift-0.3.0/schift/buckets.py +0 -242
- schift-0.3.0/schift/completions.py +0 -176
- schift-0.3.0/schift/drift.py +0 -131
- schift-0.3.0/schift/jobs.py +0 -102
- schift-0.3.0/schift/tasks.py +0 -104
- schift-0.3.0/schift/workflows.py +0 -330
- {schift-0.3.0 → schift-0.3.3}/bench_report.html +0 -0
- {schift-0.3.0 → schift-0.3.3}/docs/ADAPTER_INTERFACE_CONTRACT.md +0 -0
- {schift-0.3.0 → schift-0.3.3}/docs/CLIENT_AUTH_CONTRACT.md +0 -0
- {schift-0.3.0 → schift-0.3.3}/docs/DB_CONTRACT.md +0 -0
- {schift-0.3.0 → schift-0.3.3}/docs/DB_RECORD_SCHEMA.md +0 -0
- {schift-0.3.0 → schift-0.3.3}/docs/EMBED_CONTRACT.md +0 -0
- {schift-0.3.0 → schift-0.3.3}/docs/MIGRATE_BENCH_CONTRACT.md +0 -0
- {schift-0.3.0 → schift-0.3.3}/docs/MIGRATE_FIT_CONTRACT.md +0 -0
- {schift-0.3.0 → schift-0.3.3}/docs/MIGRATE_RUN_CONTRACT.md +0 -0
- {schift-0.3.0 → schift-0.3.3}/docs/MODEL_CATALOG_CONTRACT.md +0 -0
- {schift-0.3.0 → schift-0.3.3}/docs/ROUTING_DECISION_SCHEMA.md +0 -0
- {schift-0.3.0 → schift-0.3.3}/docs/ROUTING_EVENTS_CONTRACT.md +0 -0
- {schift-0.3.0 → schift-0.3.3}/docs/ROUTING_POLICY_CONTRACT.md +0 -0
- {schift-0.3.0 → schift-0.3.3}/docs/SDK_FUNCTION_INVENTORY.md +0 -0
- {schift-0.3.0 → schift-0.3.3}/docs/SDK_MODULE_BOUNDARIES.md +0 -0
- {schift-0.3.0 → schift-0.3.3}/docs/SEARCH_CONTRACT.md +0 -0
- {schift-0.3.0 → schift-0.3.3}/docs/SEARCH_QUERY_SCHEMA.md +0 -0
- {schift-0.3.0 → schift-0.3.3}/docs/USAGE_CONTRACT.md +0 -0
- {schift-0.3.0 → schift-0.3.3}/schift/__init__.py +0 -0
- {schift-0.3.0 → schift-0.3.3}/schift/_migrate_module.py +0 -0
- {schift-0.3.0 → schift-0.3.3}/schift/adapters/__init__.py +0 -0
- {schift-0.3.0 → schift-0.3.3}/schift/adapters/base.py +0 -0
- {schift-0.3.0 → schift-0.3.3}/schift/adapters/chroma.py +0 -0
- {schift-0.3.0 → schift-0.3.3}/schift/adapters/elasticsearch.py +0 -0
- {schift-0.3.0 → schift-0.3.3}/schift/adapters/file.py +0 -0
- {schift-0.3.0 → schift-0.3.3}/schift/adapters/milvus.py +0 -0
- {schift-0.3.0 → schift-0.3.3}/schift/adapters/mongodb.py +0 -0
- {schift-0.3.0 → schift-0.3.3}/schift/adapters/pgvector.py +0 -0
- {schift-0.3.0 → schift-0.3.3}/schift/adapters/pinecone.py +0 -0
- {schift-0.3.0 → schift-0.3.3}/schift/adapters/qdrant.py +0 -0
- {schift-0.3.0 → schift-0.3.3}/schift/adapters/redis.py +0 -0
- {schift-0.3.0 → schift-0.3.3}/schift/adapters/registry.py +0 -0
- {schift-0.3.0 → schift-0.3.3}/schift/adapters/weaviate.py +0 -0
- {schift-0.3.0 → schift-0.3.3}/schift/bench.py +0 -0
- {schift-0.3.0 → schift-0.3.3}/schift/catalog.py +0 -0
- {schift-0.3.0 → schift-0.3.3}/schift/chat.py +0 -0
- {schift-0.3.0 → schift-0.3.3}/schift/client.py +0 -0
- {schift-0.3.0 → schift-0.3.3}/schift/db.py +0 -0
- {schift-0.3.0 → schift-0.3.3}/schift/embed.py +0 -0
- {schift-0.3.0 → schift-0.3.3}/schift/migrate.py +0 -0
- {schift-0.3.0 → schift-0.3.3}/schift/projection.py +0 -0
- {schift-0.3.0 → schift-0.3.3}/schift/query.py +0 -0
- {schift-0.3.0 → schift-0.3.3}/schift/report.py +0 -0
- {schift-0.3.0 → schift-0.3.3}/schift/rerank.py +0 -0
- {schift-0.3.0 → schift-0.3.3}/schift/routing.py +0 -0
- {schift-0.3.0 → schift-0.3.3}/schift/schift_client.py +20 -20
- {schift-0.3.0 → schift-0.3.3}/schift/search.py +0 -0
- {schift-0.3.0 → schift-0.3.3}/schift/tools.py +0 -0
- {schift-0.3.0 → schift-0.3.3}/schift/usage.py +0 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
name: Publish schift to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
publish:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
|
|
15
|
+
steps:
|
|
16
|
+
- name: Checkout
|
|
17
|
+
uses: actions/checkout@v4
|
|
18
|
+
|
|
19
|
+
- name: Setup Python
|
|
20
|
+
uses: actions/setup-python@v5
|
|
21
|
+
with:
|
|
22
|
+
python-version: "3.11"
|
|
23
|
+
|
|
24
|
+
- name: Install build tools
|
|
25
|
+
run: python -m pip install --upgrade build twine
|
|
26
|
+
|
|
27
|
+
- name: Set pyproject version from release tag
|
|
28
|
+
if: github.event_name == 'release'
|
|
29
|
+
run: |
|
|
30
|
+
TAG_VERSION="${GITHUB_REF_NAME#v}"
|
|
31
|
+
sed -i "s/^version = \".*\"$/version = \"${TAG_VERSION}\"/" pyproject.toml
|
|
32
|
+
|
|
33
|
+
- name: Build package
|
|
34
|
+
run: python -m build
|
|
35
|
+
|
|
36
|
+
- name: Check distribution
|
|
37
|
+
run: twine check dist/*
|
|
38
|
+
|
|
39
|
+
- name: Ensure PyPI token exists
|
|
40
|
+
if: github.event_name == 'release'
|
|
41
|
+
run: |
|
|
42
|
+
if [ -z "${{ secrets.PYPI_API_TOKEN }}" ]; then
|
|
43
|
+
echo "PYPI_API_TOKEN secret is missing"
|
|
44
|
+
exit 1
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
- name: Publish to PyPI
|
|
48
|
+
if: github.event_name == 'release'
|
|
49
|
+
env:
|
|
50
|
+
TWINE_USERNAME: __token__
|
|
51
|
+
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
|
|
52
|
+
run: twine upload dist/*
|
schift-0.3.3/.nojekyll
ADDED
|
File without changes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: schift
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.3
|
|
4
4
|
Summary: Embedding model migration SDK. Switch models without re-embedding.
|
|
5
5
|
Project-URL: Homepage, https://schift.io
|
|
6
6
|
Project-URL: Documentation, https://docs.schift.io
|
|
@@ -62,7 +62,7 @@ Python client and local migration toolkit for Schift.
|
|
|
62
62
|
|
|
63
63
|
The package currently exposes two public client styles:
|
|
64
64
|
|
|
65
|
-
- `Schift`: modular client for live API operations such as catalog lookup, embedding, routing, search, usage, and hosted collection management.
|
|
65
|
+
- `Schift`: modular client for live API operations such as bucket ingest, catalog lookup, embedding, routing, search, usage, and hosted collection management.
|
|
66
66
|
- `Client`: legacy projection client for fitting and downloading `Projection` objects that run locally.
|
|
67
67
|
|
|
68
68
|
## Install
|
|
@@ -125,6 +125,35 @@ with Schift() as client:
|
|
|
125
125
|
|
|
126
126
|
For long-running processes, keep one `Schift` instance and call `close()` during shutdown.
|
|
127
127
|
|
|
128
|
+
## Bucket Ingest And Search
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
from schift import Schift
|
|
132
|
+
|
|
133
|
+
with Schift() as client:
|
|
134
|
+
bucket = client.buckets.create(name="finance-docs")
|
|
135
|
+
upload = client.buckets.upload(
|
|
136
|
+
bucket["id"],
|
|
137
|
+
[("files", ("q1-report.pdf", open("q1-report.pdf", "rb").read(), "application/pdf"))],
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
jobs = client.buckets.list_jobs(bucket_id=bucket["id"])
|
|
141
|
+
hits = client.buckets.search(
|
|
142
|
+
bucket["id"],
|
|
143
|
+
"revenue guidance",
|
|
144
|
+
top_k=5,
|
|
145
|
+
filter={"metadata": {"quarter": "Q1"}},
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
print(upload["bucket_id"])
|
|
149
|
+
print(jobs[0]["status"] if jobs else "queued")
|
|
150
|
+
print(hits[0] if hits else "no hits")
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Use `client.db` when you want a generic collection retrieval surface, `POST /v1/chat` for bucket-backed RAG chat with sources, and `POST /v1/chat/completions` for OpenAI-compatible LLM routing without bucket context.
|
|
154
|
+
|
|
155
|
+
The bucket helper also exposes `get_job()`, `list_jobs()`, `wait_for_job()`, and `poll_job()` so scripts can keep ingest orchestration in one place.
|
|
156
|
+
|
|
128
157
|
## Quickstart
|
|
129
158
|
|
|
130
159
|
```python
|
|
@@ -4,7 +4,7 @@ Python client and local migration toolkit for Schift.
|
|
|
4
4
|
|
|
5
5
|
The package currently exposes two public client styles:
|
|
6
6
|
|
|
7
|
-
- `Schift`: modular client for live API operations such as catalog lookup, embedding, routing, search, usage, and hosted collection management.
|
|
7
|
+
- `Schift`: modular client for live API operations such as bucket ingest, catalog lookup, embedding, routing, search, usage, and hosted collection management.
|
|
8
8
|
- `Client`: legacy projection client for fitting and downloading `Projection` objects that run locally.
|
|
9
9
|
|
|
10
10
|
## Install
|
|
@@ -67,6 +67,35 @@ with Schift() as client:
|
|
|
67
67
|
|
|
68
68
|
For long-running processes, keep one `Schift` instance and call `close()` during shutdown.
|
|
69
69
|
|
|
70
|
+
## Bucket Ingest And Search
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
from schift import Schift
|
|
74
|
+
|
|
75
|
+
with Schift() as client:
|
|
76
|
+
bucket = client.buckets.create(name="finance-docs")
|
|
77
|
+
upload = client.buckets.upload(
|
|
78
|
+
bucket["id"],
|
|
79
|
+
[("files", ("q1-report.pdf", open("q1-report.pdf", "rb").read(), "application/pdf"))],
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
jobs = client.buckets.list_jobs(bucket_id=bucket["id"])
|
|
83
|
+
hits = client.buckets.search(
|
|
84
|
+
bucket["id"],
|
|
85
|
+
"revenue guidance",
|
|
86
|
+
top_k=5,
|
|
87
|
+
filter={"metadata": {"quarter": "Q1"}},
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
print(upload["bucket_id"])
|
|
91
|
+
print(jobs[0]["status"] if jobs else "queued")
|
|
92
|
+
print(hits[0] if hits else "no hits")
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Use `client.db` when you want a generic collection retrieval surface, `POST /v1/chat` for bucket-backed RAG chat with sources, and `POST /v1/chat/completions` for OpenAI-compatible LLM routing without bucket context.
|
|
96
|
+
|
|
97
|
+
The bucket helper also exposes `get_job()`, `list_jobs()`, `wait_for_job()`, and `poll_job()` so scripts can keep ingest orchestration in one place.
|
|
98
|
+
|
|
70
99
|
## Quickstart
|
|
71
100
|
|
|
72
101
|
```python
|
|
@@ -42,6 +42,10 @@ class HttpClient:
|
|
|
42
42
|
resp = self._client.put(path, json=data)
|
|
43
43
|
return self._handle(resp)
|
|
44
44
|
|
|
45
|
+
def patch(self, path: str, data: Any = None) -> Any:
|
|
46
|
+
resp = self._client.patch(path, json=data)
|
|
47
|
+
return self._handle(resp)
|
|
48
|
+
|
|
45
49
|
def delete(self, path: str) -> Any:
|
|
46
50
|
resp = self._client.delete(path)
|
|
47
51
|
return self._handle(resp)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from schift._http import HttpClient
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class AggregateModule:
|
|
9
|
+
def __init__(self, http: HttpClient):
|
|
10
|
+
self._http = http
|
|
11
|
+
|
|
12
|
+
def __call__(self, collection: str, group_by: str, filter_key: Optional[str] = None, filter_value: Optional[str] = None):
|
|
13
|
+
payload = {"collection": collection, "group_by": group_by}
|
|
14
|
+
if filter_key is not None:
|
|
15
|
+
payload["filter_key"] = filter_key
|
|
16
|
+
if filter_value is not None:
|
|
17
|
+
payload["filter_value"] = filter_value
|
|
18
|
+
return self._http.post("/aggregate", data=payload)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from schift._http import HttpClient
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ArtifactsModule:
|
|
9
|
+
def __init__(self, http: HttpClient):
|
|
10
|
+
self._http = http
|
|
11
|
+
|
|
12
|
+
def create(self, kind: str, uri: str, checksum: Optional[str] = None, dims: Optional[int] = None, content_type: Optional[str] = None, label: Optional[str] = None):
|
|
13
|
+
payload = {"kind": kind, "uri": uri}
|
|
14
|
+
if checksum is not None:
|
|
15
|
+
payload["checksum"] = checksum
|
|
16
|
+
if dims is not None:
|
|
17
|
+
payload["dims"] = dims
|
|
18
|
+
if content_type is not None:
|
|
19
|
+
payload["content_type"] = content_type
|
|
20
|
+
if label is not None:
|
|
21
|
+
payload["label"] = label
|
|
22
|
+
return self._http.post("/artifacts", data=payload)
|
|
23
|
+
|
|
24
|
+
def list(self, kind: Optional[str] = None):
|
|
25
|
+
params = {}
|
|
26
|
+
if kind is not None:
|
|
27
|
+
params["kind"] = kind
|
|
28
|
+
return self._http.get("/artifacts", params=params)
|
|
29
|
+
|
|
30
|
+
def get(self, artifact_id: str):
|
|
31
|
+
return self._http.get(f"/artifacts/{artifact_id}")
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from schift._http import HttpClient
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class BenchmarkSuitesModule:
|
|
9
|
+
def __init__(self, http: HttpClient):
|
|
10
|
+
self._http = http
|
|
11
|
+
|
|
12
|
+
def create(self, name: str, source_model: str, target_model: str, sample_ratios: Optional[dict] = None, **kwargs):
|
|
13
|
+
payload = {"name": name, "source_model": source_model, "target_model": target_model}
|
|
14
|
+
if sample_ratios is not None:
|
|
15
|
+
payload["sample_ratios"] = sample_ratios
|
|
16
|
+
payload.update(kwargs)
|
|
17
|
+
return self._http.post("/benchmark-suites", data=payload)
|
|
18
|
+
|
|
19
|
+
def list(self):
|
|
20
|
+
return self._http.get("/benchmark-suites")
|
|
21
|
+
|
|
22
|
+
def get(self, suite_id: str):
|
|
23
|
+
return self._http.get(f"/benchmark-suites/{suite_id}")
|
|
24
|
+
|
|
25
|
+
def list_runs(self, suite_id: str):
|
|
26
|
+
return self._http.get(f"/benchmark-suites/{suite_id}/runs")
|
|
27
|
+
|
|
28
|
+
def get_run(self, run_id: str):
|
|
29
|
+
return self._http.get(f"/benchmark-runs/{run_id}")
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import time
|
|
5
|
+
from typing import Any, Optional
|
|
6
|
+
|
|
7
|
+
from schift._http import HttpClient
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class BucketsModule:
|
|
11
|
+
def __init__(self, http: HttpClient):
|
|
12
|
+
self._http = http
|
|
13
|
+
|
|
14
|
+
def create(self, name: str, description: Optional[str] = None):
|
|
15
|
+
payload = {"name": name}
|
|
16
|
+
if description is not None:
|
|
17
|
+
payload["description"] = description
|
|
18
|
+
return self._http.post("/buckets", data=payload)
|
|
19
|
+
|
|
20
|
+
def list(self):
|
|
21
|
+
return self._http.get("/buckets")
|
|
22
|
+
|
|
23
|
+
def delete(self, bucket_id: str):
|
|
24
|
+
return self._http.delete(f"/buckets/{bucket_id}")
|
|
25
|
+
|
|
26
|
+
def search(
|
|
27
|
+
self,
|
|
28
|
+
bucket_id: str,
|
|
29
|
+
query: str,
|
|
30
|
+
top_k: int = 10,
|
|
31
|
+
mode: str = "hybrid",
|
|
32
|
+
rerank: bool = False,
|
|
33
|
+
model: Optional[str] = None,
|
|
34
|
+
filter: Optional[dict[str, Any]] = None,
|
|
35
|
+
):
|
|
36
|
+
payload = {"query": query, "top_k": top_k, "mode": mode, "rerank": rerank}
|
|
37
|
+
if model is not None:
|
|
38
|
+
payload["model"] = model
|
|
39
|
+
if filter is not None:
|
|
40
|
+
payload["filter"] = filter
|
|
41
|
+
return self._http.post(f"/buckets/{bucket_id}/search", data=payload)
|
|
42
|
+
|
|
43
|
+
def graph(self, bucket_id: str, query: Optional[str] = None, top_k: int = 10):
|
|
44
|
+
params = {"top_k": top_k}
|
|
45
|
+
if query is not None:
|
|
46
|
+
params["query"] = query
|
|
47
|
+
return self._http.get(f"/buckets/{bucket_id}/graph", params=params)
|
|
48
|
+
|
|
49
|
+
def upload(self, bucket_id: str, files, ocr_strategy: Optional[str] = None, chunk_size: Optional[int] = None, chunk_overlap: Optional[int] = None):
|
|
50
|
+
meta = {}
|
|
51
|
+
if ocr_strategy is not None:
|
|
52
|
+
meta["ocr_strategy"] = ocr_strategy
|
|
53
|
+
if chunk_size is not None:
|
|
54
|
+
meta["chunk_size"] = chunk_size
|
|
55
|
+
if chunk_overlap is not None:
|
|
56
|
+
meta["chunk_overlap"] = chunk_overlap
|
|
57
|
+
form_data = {"payload": json.dumps(meta)} if meta else {}
|
|
58
|
+
return self._http._post_form_with_files(f"/buckets/{bucket_id}/upload", form_data, files)
|
|
59
|
+
|
|
60
|
+
def get_job(self, job_id: str):
|
|
61
|
+
return self._http.get(f"/jobs/{job_id}")
|
|
62
|
+
|
|
63
|
+
def list_jobs(
|
|
64
|
+
self,
|
|
65
|
+
bucket_id: Optional[str] = None,
|
|
66
|
+
status: Optional[str] = None,
|
|
67
|
+
limit: Optional[int] = None,
|
|
68
|
+
):
|
|
69
|
+
params = {}
|
|
70
|
+
if bucket_id is not None:
|
|
71
|
+
params["bucket_id"] = bucket_id
|
|
72
|
+
if status is not None:
|
|
73
|
+
params["status"] = status
|
|
74
|
+
if limit is not None:
|
|
75
|
+
params["limit"] = limit
|
|
76
|
+
return self._http.get("/jobs", params=params)
|
|
77
|
+
|
|
78
|
+
def wait_for_job(
|
|
79
|
+
self,
|
|
80
|
+
job_id: str,
|
|
81
|
+
poll_interval: float = 1.0,
|
|
82
|
+
timeout: float = 300.0,
|
|
83
|
+
terminal_statuses: tuple[str, ...] = ("ready", "failed", "cancelled"),
|
|
84
|
+
):
|
|
85
|
+
deadline = time.monotonic() + timeout
|
|
86
|
+
while True:
|
|
87
|
+
job = self.get_job(job_id)
|
|
88
|
+
status = job.get("status") if isinstance(job, dict) else None
|
|
89
|
+
if status in terminal_statuses:
|
|
90
|
+
return job
|
|
91
|
+
if time.monotonic() >= deadline:
|
|
92
|
+
raise TimeoutError(f"Timed out waiting for job {job_id}")
|
|
93
|
+
time.sleep(poll_interval)
|
|
94
|
+
|
|
95
|
+
def poll_job(
|
|
96
|
+
self,
|
|
97
|
+
job_id: str,
|
|
98
|
+
poll_interval: float = 1.0,
|
|
99
|
+
timeout: float = 300.0,
|
|
100
|
+
terminal_statuses: tuple[str, ...] = ("ready", "failed", "cancelled"),
|
|
101
|
+
):
|
|
102
|
+
return self.wait_for_job(
|
|
103
|
+
job_id,
|
|
104
|
+
poll_interval=poll_interval,
|
|
105
|
+
timeout=timeout,
|
|
106
|
+
terminal_statuses=terminal_statuses,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
def add_edges(self, bucket_id: str, edges: list):
|
|
110
|
+
return self._http.post(f"/buckets/{bucket_id}/edges", data={"edges": edges})
|
|
111
|
+
|
|
112
|
+
def list_edges(self, bucket_id: str, node_id: str, direction: Optional[str] = None, relation: Optional[str] = None):
|
|
113
|
+
params = {}
|
|
114
|
+
if direction is not None:
|
|
115
|
+
params["direction"] = direction
|
|
116
|
+
if relation is not None:
|
|
117
|
+
params["relation"] = relation
|
|
118
|
+
return self._http.get(f"/buckets/{bucket_id}/edges/{node_id}", params=params)
|
|
119
|
+
|
|
120
|
+
def delete_edge(self, bucket_id: str, source: str, target: str, relation: Optional[str] = None):
|
|
121
|
+
payload = {"source": source, "target": target}
|
|
122
|
+
if relation is not None:
|
|
123
|
+
payload["relation"] = relation
|
|
124
|
+
return self._http.delete_json(f"/buckets/{bucket_id}/edges", data=payload)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from schift._http import HttpClient
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class CompletionsModule:
|
|
9
|
+
def __init__(self, http: HttpClient):
|
|
10
|
+
self._http = http
|
|
11
|
+
|
|
12
|
+
def create(self, model: str, messages: list, temperature: Optional[float] = None, max_tokens: Optional[int] = None, top_p: Optional[float] = None, stream: bool = False, stop=None):
|
|
13
|
+
payload = {"model": model, "messages": messages, "stream": stream}
|
|
14
|
+
if temperature is not None:
|
|
15
|
+
payload["temperature"] = temperature
|
|
16
|
+
if max_tokens is not None:
|
|
17
|
+
payload["max_tokens"] = max_tokens
|
|
18
|
+
if top_p is not None:
|
|
19
|
+
payload["top_p"] = top_p
|
|
20
|
+
if stop is not None:
|
|
21
|
+
payload["stop"] = stop
|
|
22
|
+
return self._http.post("/chat/completions", data=payload)
|
|
23
|
+
|
|
24
|
+
def models(self):
|
|
25
|
+
return self._http.get("/models")
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from schift._http import HttpClient
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class DriftModule:
|
|
9
|
+
def __init__(self, http: HttpClient):
|
|
10
|
+
self._http = http
|
|
11
|
+
|
|
12
|
+
def create_monitor(self, name: str, suite_id: str, cadence: Optional[str] = None, min_recovery_r10: Optional[float] = None):
|
|
13
|
+
payload = {"name": name, "suite_id": suite_id}
|
|
14
|
+
if cadence is not None:
|
|
15
|
+
payload["cadence"] = cadence
|
|
16
|
+
if min_recovery_r10 is not None:
|
|
17
|
+
payload["min_recovery_r10"] = min_recovery_r10
|
|
18
|
+
return self._http.post("/drift-monitors", data=payload)
|
|
19
|
+
|
|
20
|
+
def list_monitors(self):
|
|
21
|
+
return self._http.get("/drift-monitors")
|
|
22
|
+
|
|
23
|
+
def get_monitor(self, monitor_id: str):
|
|
24
|
+
return self._http.get(f"/drift-monitors/{monitor_id}")
|
|
25
|
+
|
|
26
|
+
def list_runs(self, monitor_id: str):
|
|
27
|
+
return self._http.get(f"/drift-monitors/{monitor_id}/runs")
|
|
28
|
+
|
|
29
|
+
def get_run(self, drift_run_id: str):
|
|
30
|
+
return self._http.get(f"/drift-runs/{drift_run_id}")
|
|
31
|
+
|
|
32
|
+
def due(self):
|
|
33
|
+
return self._http.get("/drift-monitor-due")
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from schift._http import HttpClient
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class JobsModule:
|
|
9
|
+
def __init__(self, http: HttpClient):
|
|
10
|
+
self._http = http
|
|
11
|
+
|
|
12
|
+
def get(self, job_id: str):
|
|
13
|
+
return self._http.get(f"/jobs/{job_id}")
|
|
14
|
+
|
|
15
|
+
def list(self, org_id: Optional[str] = None, bucket_id: Optional[str] = None, status: Optional[str] = None, limit: Optional[int] = None):
|
|
16
|
+
params = {}
|
|
17
|
+
if org_id is not None:
|
|
18
|
+
params["org_id"] = org_id
|
|
19
|
+
if bucket_id is not None:
|
|
20
|
+
params["bucket_id"] = bucket_id
|
|
21
|
+
if status is not None:
|
|
22
|
+
params["status"] = status
|
|
23
|
+
if limit is not None:
|
|
24
|
+
params["limit"] = limit
|
|
25
|
+
return self._http.get("/jobs", params=params)
|
|
26
|
+
|
|
27
|
+
def cancel(self, job_id: str):
|
|
28
|
+
return self._http.post(f"/jobs/{job_id}/cancel")
|
|
29
|
+
|
|
30
|
+
def reprocess(self, job_id: str):
|
|
31
|
+
return self._http.post(f"/jobs/{job_id}/reprocess")
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from schift._http import HttpClient
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TasksModule:
|
|
9
|
+
def __init__(self, http: HttpClient):
|
|
10
|
+
self._http = http
|
|
11
|
+
|
|
12
|
+
def similarity(self, text_a: str, text_b: str, model: Optional[str] = None):
|
|
13
|
+
payload = {"text_a": text_a, "text_b": text_b}
|
|
14
|
+
if model is not None:
|
|
15
|
+
payload["model"] = model
|
|
16
|
+
return self._http.post("/similarity", data=payload)
|
|
17
|
+
|
|
18
|
+
def cluster(self, texts: list, n_clusters: int = 5, model: Optional[str] = None):
|
|
19
|
+
payload = {"texts": texts, "n_clusters": n_clusters}
|
|
20
|
+
if model is not None:
|
|
21
|
+
payload["model"] = model
|
|
22
|
+
return self._http.post("/cluster", data=payload)
|
|
23
|
+
|
|
24
|
+
def classify(
|
|
25
|
+
self,
|
|
26
|
+
text: str,
|
|
27
|
+
labels: list,
|
|
28
|
+
model: Optional[str] = None,
|
|
29
|
+
temperature: float = 1.0,
|
|
30
|
+
examples: Optional[list] = None,
|
|
31
|
+
):
|
|
32
|
+
payload = {"text": text, "labels": labels}
|
|
33
|
+
if model is not None:
|
|
34
|
+
payload["model"] = model
|
|
35
|
+
if temperature != 1.0:
|
|
36
|
+
payload["temperature"] = temperature
|
|
37
|
+
if examples:
|
|
38
|
+
payload["examples"] = [
|
|
39
|
+
{"text": ex["text"], "label": ex["label"]} for ex in examples
|
|
40
|
+
]
|
|
41
|
+
return self._http.post("/classify", data=payload)
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from schift._http import HttpClient
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class WorkflowsModule:
|
|
9
|
+
def __init__(self, http: HttpClient):
|
|
10
|
+
self._http = http
|
|
11
|
+
|
|
12
|
+
def create(self, name: str, description: Optional[str] = None, template: Optional[str] = None, graph: Optional[dict] = None):
|
|
13
|
+
payload = {"name": name}
|
|
14
|
+
if description is not None:
|
|
15
|
+
payload["description"] = description
|
|
16
|
+
if template is not None:
|
|
17
|
+
payload["template"] = template
|
|
18
|
+
if graph is not None:
|
|
19
|
+
payload["graph"] = graph
|
|
20
|
+
return self._http.post("/workflows", data=payload)
|
|
21
|
+
|
|
22
|
+
def list(self):
|
|
23
|
+
return self._http.get("/workflows")
|
|
24
|
+
|
|
25
|
+
def get(self, workflow_id: str):
|
|
26
|
+
return self._http.get(f"/workflows/{workflow_id}")
|
|
27
|
+
|
|
28
|
+
def update(self, workflow_id: str, name: Optional[str] = None, description: Optional[str] = None, status: Optional[str] = None, graph: Optional[dict] = None):
|
|
29
|
+
payload = {}
|
|
30
|
+
if name is not None:
|
|
31
|
+
payload["name"] = name
|
|
32
|
+
if description is not None:
|
|
33
|
+
payload["description"] = description
|
|
34
|
+
if status is not None:
|
|
35
|
+
payload["status"] = status
|
|
36
|
+
if graph is not None:
|
|
37
|
+
payload["graph"] = graph
|
|
38
|
+
return self._http.patch(f"/workflows/{workflow_id}", data=payload)
|
|
39
|
+
|
|
40
|
+
def delete(self, workflow_id: str):
|
|
41
|
+
return self._http.delete(f"/workflows/{workflow_id}")
|
|
42
|
+
|
|
43
|
+
def add_block(self, workflow_id: str, block_id: str, type: str, config: Optional[dict] = None, position: Optional[dict] = None):
|
|
44
|
+
payload = {"block_id": block_id, "type": type}
|
|
45
|
+
if config is not None:
|
|
46
|
+
payload["config"] = config
|
|
47
|
+
if position is not None:
|
|
48
|
+
payload["position"] = position
|
|
49
|
+
return self._http.post(f"/workflows/{workflow_id}/blocks", data=payload)
|
|
50
|
+
|
|
51
|
+
def remove_block(self, workflow_id: str, block_id: str):
|
|
52
|
+
return self._http.delete(f"/workflows/{workflow_id}/blocks/{block_id}")
|
|
53
|
+
|
|
54
|
+
def add_edge(self, workflow_id: str, source: str, target: str, source_port: Optional[str] = None, target_port: Optional[str] = None):
|
|
55
|
+
payload = {"source": source, "target": target}
|
|
56
|
+
if source_port is not None:
|
|
57
|
+
payload["source_port"] = source_port
|
|
58
|
+
if target_port is not None:
|
|
59
|
+
payload["target_port"] = target_port
|
|
60
|
+
return self._http.post(f"/workflows/{workflow_id}/edges", data=payload)
|
|
61
|
+
|
|
62
|
+
def remove_edge(self, workflow_id: str, edge_id: str):
|
|
63
|
+
return self._http.delete(f"/workflows/{workflow_id}/edges/{edge_id}")
|
|
64
|
+
|
|
65
|
+
def run(self, workflow_id: str, inputs: Optional[dict] = None, mode: str = "sync"):
|
|
66
|
+
"""Execute a workflow.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
workflow_id: Workflow ID.
|
|
70
|
+
inputs: Execution inputs.
|
|
71
|
+
mode: ``"sync"`` (default) blocks until complete;
|
|
72
|
+
``"async"`` returns ``{"id": ..., "workflow_id": ..., "status": "pending"}``
|
|
73
|
+
immediately — poll with :meth:`get_run` / :meth:`get_run_logs`.
|
|
74
|
+
"""
|
|
75
|
+
qs = f"?mode={mode}" if mode != "sync" else ""
|
|
76
|
+
return self._http.post(f"/workflows/{workflow_id}/run{qs}", data={"inputs": inputs or {}})
|
|
77
|
+
|
|
78
|
+
def run_async(self, workflow_id: str, inputs: Optional[dict] = None):
|
|
79
|
+
"""Shorthand for ``run(workflow_id, inputs, mode="async")``."""
|
|
80
|
+
return self.run(workflow_id, inputs, mode="async")
|
|
81
|
+
|
|
82
|
+
def list_runs(self, workflow_id: str):
|
|
83
|
+
return self._http.get(f"/workflows/{workflow_id}/runs")
|
|
84
|
+
|
|
85
|
+
def get_run(self, workflow_id: str, run_id: str):
|
|
86
|
+
return self._http.get(f"/workflows/{workflow_id}/runs/{run_id}")
|
|
87
|
+
|
|
88
|
+
def get_run_logs(self, workflow_id: str, run_id: str, after_seq: int = 0):
|
|
89
|
+
"""Get per-block execution logs for a run.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
after_seq: Only return logs with ``seq > after_seq`` (for polling).
|
|
93
|
+
"""
|
|
94
|
+
qs = f"?after_seq={after_seq}" if after_seq > 0 else ""
|
|
95
|
+
return self._http.get(f"/workflows/{workflow_id}/runs/{run_id}/logs{qs}")
|
|
96
|
+
|
|
97
|
+
def validate(self, workflow_id: str):
|
|
98
|
+
return self._http.post(f"/workflows/{workflow_id}/validate")
|
|
99
|
+
|
|
100
|
+
def import_yaml(self, yaml_str: str):
|
|
101
|
+
return self._http.post("/workflows/import", data={"yaml": yaml_str})
|
|
102
|
+
|
|
103
|
+
def export_yaml(self, workflow_id: str):
|
|
104
|
+
return self._http.get(f"/workflows/{workflow_id}/export")
|
|
105
|
+
|
|
106
|
+
def generate(self, prompt: str, model: Optional[str] = None):
|
|
107
|
+
payload = {"prompt": prompt}
|
|
108
|
+
if model is not None:
|
|
109
|
+
payload["model"] = model
|
|
110
|
+
return self._http.post("/workflows/generate", data=payload)
|
|
111
|
+
|
|
112
|
+
def block_types(self):
|
|
113
|
+
return self._http.get("/workflows/meta/block-types")
|
|
114
|
+
|
|
115
|
+
def templates(self):
|
|
116
|
+
return self._http.get("/workflows/meta/templates")
|