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.
Files changed (74) hide show
  1. schift-0.3.3/.github/workflows/publish.yml +52 -0
  2. schift-0.3.3/.nojekyll +0 -0
  3. {schift-0.3.0 → schift-0.3.3}/PKG-INFO +31 -2
  4. {schift-0.3.0 → schift-0.3.3}/README.md +30 -1
  5. {schift-0.3.0 → schift-0.3.3}/pyproject.toml +1 -1
  6. {schift-0.3.0 → schift-0.3.3}/schift/_http.py +4 -0
  7. schift-0.3.3/schift/aggregate.py +18 -0
  8. schift-0.3.3/schift/artifacts.py +31 -0
  9. schift-0.3.3/schift/benchmark_suites.py +29 -0
  10. schift-0.3.3/schift/buckets.py +124 -0
  11. schift-0.3.3/schift/completions.py +25 -0
  12. schift-0.3.3/schift/drift.py +33 -0
  13. schift-0.3.3/schift/jobs.py +31 -0
  14. schift-0.3.3/schift/tasks.py +41 -0
  15. schift-0.3.3/schift/workflows.py +116 -0
  16. schift-0.3.0/.gitignore +0 -63
  17. schift-0.3.0/schift/aggregate.py +0 -64
  18. schift-0.3.0/schift/artifacts.py +0 -93
  19. schift-0.3.0/schift/benchmark_suites.py +0 -147
  20. schift-0.3.0/schift/buckets.py +0 -242
  21. schift-0.3.0/schift/completions.py +0 -176
  22. schift-0.3.0/schift/drift.py +0 -131
  23. schift-0.3.0/schift/jobs.py +0 -102
  24. schift-0.3.0/schift/tasks.py +0 -104
  25. schift-0.3.0/schift/workflows.py +0 -330
  26. {schift-0.3.0 → schift-0.3.3}/bench_report.html +0 -0
  27. {schift-0.3.0 → schift-0.3.3}/docs/ADAPTER_INTERFACE_CONTRACT.md +0 -0
  28. {schift-0.3.0 → schift-0.3.3}/docs/CLIENT_AUTH_CONTRACT.md +0 -0
  29. {schift-0.3.0 → schift-0.3.3}/docs/DB_CONTRACT.md +0 -0
  30. {schift-0.3.0 → schift-0.3.3}/docs/DB_RECORD_SCHEMA.md +0 -0
  31. {schift-0.3.0 → schift-0.3.3}/docs/EMBED_CONTRACT.md +0 -0
  32. {schift-0.3.0 → schift-0.3.3}/docs/MIGRATE_BENCH_CONTRACT.md +0 -0
  33. {schift-0.3.0 → schift-0.3.3}/docs/MIGRATE_FIT_CONTRACT.md +0 -0
  34. {schift-0.3.0 → schift-0.3.3}/docs/MIGRATE_RUN_CONTRACT.md +0 -0
  35. {schift-0.3.0 → schift-0.3.3}/docs/MODEL_CATALOG_CONTRACT.md +0 -0
  36. {schift-0.3.0 → schift-0.3.3}/docs/ROUTING_DECISION_SCHEMA.md +0 -0
  37. {schift-0.3.0 → schift-0.3.3}/docs/ROUTING_EVENTS_CONTRACT.md +0 -0
  38. {schift-0.3.0 → schift-0.3.3}/docs/ROUTING_POLICY_CONTRACT.md +0 -0
  39. {schift-0.3.0 → schift-0.3.3}/docs/SDK_FUNCTION_INVENTORY.md +0 -0
  40. {schift-0.3.0 → schift-0.3.3}/docs/SDK_MODULE_BOUNDARIES.md +0 -0
  41. {schift-0.3.0 → schift-0.3.3}/docs/SEARCH_CONTRACT.md +0 -0
  42. {schift-0.3.0 → schift-0.3.3}/docs/SEARCH_QUERY_SCHEMA.md +0 -0
  43. {schift-0.3.0 → schift-0.3.3}/docs/USAGE_CONTRACT.md +0 -0
  44. {schift-0.3.0 → schift-0.3.3}/schift/__init__.py +0 -0
  45. {schift-0.3.0 → schift-0.3.3}/schift/_migrate_module.py +0 -0
  46. {schift-0.3.0 → schift-0.3.3}/schift/adapters/__init__.py +0 -0
  47. {schift-0.3.0 → schift-0.3.3}/schift/adapters/base.py +0 -0
  48. {schift-0.3.0 → schift-0.3.3}/schift/adapters/chroma.py +0 -0
  49. {schift-0.3.0 → schift-0.3.3}/schift/adapters/elasticsearch.py +0 -0
  50. {schift-0.3.0 → schift-0.3.3}/schift/adapters/file.py +0 -0
  51. {schift-0.3.0 → schift-0.3.3}/schift/adapters/milvus.py +0 -0
  52. {schift-0.3.0 → schift-0.3.3}/schift/adapters/mongodb.py +0 -0
  53. {schift-0.3.0 → schift-0.3.3}/schift/adapters/pgvector.py +0 -0
  54. {schift-0.3.0 → schift-0.3.3}/schift/adapters/pinecone.py +0 -0
  55. {schift-0.3.0 → schift-0.3.3}/schift/adapters/qdrant.py +0 -0
  56. {schift-0.3.0 → schift-0.3.3}/schift/adapters/redis.py +0 -0
  57. {schift-0.3.0 → schift-0.3.3}/schift/adapters/registry.py +0 -0
  58. {schift-0.3.0 → schift-0.3.3}/schift/adapters/weaviate.py +0 -0
  59. {schift-0.3.0 → schift-0.3.3}/schift/bench.py +0 -0
  60. {schift-0.3.0 → schift-0.3.3}/schift/catalog.py +0 -0
  61. {schift-0.3.0 → schift-0.3.3}/schift/chat.py +0 -0
  62. {schift-0.3.0 → schift-0.3.3}/schift/client.py +0 -0
  63. {schift-0.3.0 → schift-0.3.3}/schift/db.py +0 -0
  64. {schift-0.3.0 → schift-0.3.3}/schift/embed.py +0 -0
  65. {schift-0.3.0 → schift-0.3.3}/schift/migrate.py +0 -0
  66. {schift-0.3.0 → schift-0.3.3}/schift/projection.py +0 -0
  67. {schift-0.3.0 → schift-0.3.3}/schift/query.py +0 -0
  68. {schift-0.3.0 → schift-0.3.3}/schift/report.py +0 -0
  69. {schift-0.3.0 → schift-0.3.3}/schift/rerank.py +0 -0
  70. {schift-0.3.0 → schift-0.3.3}/schift/routing.py +0 -0
  71. {schift-0.3.0 → schift-0.3.3}/schift/schift_client.py +20 -20
  72. {schift-0.3.0 → schift-0.3.3}/schift/search.py +0 -0
  73. {schift-0.3.0 → schift-0.3.3}/schift/tools.py +0 -0
  74. {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.0
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
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "schift"
7
- version = "0.3.0"
7
+ version = "0.3.3"
8
8
  description = "Embedding model migration SDK. Switch models without re-embedding."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -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")