@swapai/core 0.1.0
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.
- package/LICENSE +21 -0
- package/README.md +94 -0
- package/THIRD_PARTY_NOTICES.md +6 -0
- package/dist/chunk-SXECHZ3L.js +15 -0
- package/dist/chunk-SXECHZ3L.js.map +1 -0
- package/dist/effect.d.ts +7 -0
- package/dist/effect.js +55 -0
- package/dist/effect.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +1682 -0
- package/dist/index.js.map +1 -0
- package/dist/types-DTbU1MEi.d.ts +51 -0
- package/package.json +65 -0
- package/python/requirements.in +1 -0
- package/python/requirements.lock +823 -0
- package/python/swapai_worker.py +173 -0
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import sys
|
|
8
|
+
import types
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
os.environ["NEEDLE_TELEMETRY"] = "0"
|
|
13
|
+
os.environ["DO_NOT_TRACK"] = "1"
|
|
14
|
+
|
|
15
|
+
EXPECTED_NEEDLE_VERSION = "2.0.14"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def write_message(message: dict[str, Any]) -> None:
|
|
19
|
+
sys.stdout.write(json.dumps(message, separators=(",", ":")) + "\n")
|
|
20
|
+
sys.stdout.flush()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def require_version(needle: Any) -> None:
|
|
24
|
+
version = getattr(needle, "__version__", None)
|
|
25
|
+
if version != EXPECTED_NEEDLE_VERSION:
|
|
26
|
+
raise RuntimeError(
|
|
27
|
+
f"cactus-needle {EXPECTED_NEEDLE_VERSION} is required; found {version}"
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def train(args: argparse.Namespace) -> None:
|
|
32
|
+
import needle
|
|
33
|
+
from needle.model.finetune import build_main, finetune_local
|
|
34
|
+
|
|
35
|
+
require_version(needle)
|
|
36
|
+
checkpoint_dir = Path(args.checkpoint_dir).resolve()
|
|
37
|
+
checkpoint_dir.mkdir(parents=True, exist_ok=True)
|
|
38
|
+
checkpoint = checkpoint_dir / "needle2.pkl"
|
|
39
|
+
output = Path(args.output).resolve()
|
|
40
|
+
output.parent.mkdir(parents=True, exist_ok=True)
|
|
41
|
+
adapter = output.parent / "swapai-lora.pkl"
|
|
42
|
+
with open(args.training_data, "r", encoding="utf-8") as handle:
|
|
43
|
+
example_count = sum(1 for line in handle if line.strip())
|
|
44
|
+
batch_size = min(16, max(1, (example_count + 9) // 10))
|
|
45
|
+
|
|
46
|
+
finetune_local(
|
|
47
|
+
types.SimpleNamespace(
|
|
48
|
+
jsonl_path=str(Path(args.training_data).resolve()),
|
|
49
|
+
checkpoint=str(checkpoint),
|
|
50
|
+
epochs=args.epochs,
|
|
51
|
+
batch_size=batch_size,
|
|
52
|
+
lr=1e-4,
|
|
53
|
+
lora_rank=16,
|
|
54
|
+
lora_alpha=32.0,
|
|
55
|
+
max_len=1024,
|
|
56
|
+
val_split=0.0,
|
|
57
|
+
seed=0,
|
|
58
|
+
generate=0,
|
|
59
|
+
model="deepseek/deepseek-v4-flash",
|
|
60
|
+
workers=1,
|
|
61
|
+
checkpoint_dir=str(checkpoint_dir),
|
|
62
|
+
out=str(adapter),
|
|
63
|
+
qat_bits="auto",
|
|
64
|
+
)
|
|
65
|
+
)
|
|
66
|
+
build_main(
|
|
67
|
+
types.SimpleNamespace(
|
|
68
|
+
checkpoint=str(checkpoint),
|
|
69
|
+
lora=str(adapter),
|
|
70
|
+
out=str(output),
|
|
71
|
+
upload=False,
|
|
72
|
+
bits=None,
|
|
73
|
+
)
|
|
74
|
+
)
|
|
75
|
+
if not output.is_file():
|
|
76
|
+
raise RuntimeError("Needle did not create the requested .cact model")
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def classification_result(response: Any) -> Any:
|
|
80
|
+
if not isinstance(response, dict):
|
|
81
|
+
raise RuntimeError("Needle returned an invalid response")
|
|
82
|
+
calls = response.get("function_calls")
|
|
83
|
+
if not isinstance(calls, list) or len(calls) != 1:
|
|
84
|
+
raise RuntimeError("Needle did not return exactly one classify call")
|
|
85
|
+
call = calls[0]
|
|
86
|
+
if not isinstance(call, dict) or call.get("name") != "classify":
|
|
87
|
+
raise RuntimeError("Needle did not return exactly one classify call")
|
|
88
|
+
arguments = call.get("arguments")
|
|
89
|
+
if not isinstance(arguments, dict) or "result" not in arguments:
|
|
90
|
+
raise RuntimeError("Needle classify call did not contain a result")
|
|
91
|
+
return arguments["result"]
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def serve(args: argparse.Namespace) -> None:
|
|
95
|
+
import needle
|
|
96
|
+
|
|
97
|
+
require_version(needle)
|
|
98
|
+
with open(args.schema, "r", encoding="utf-8") as handle:
|
|
99
|
+
tools = json.load(handle)
|
|
100
|
+
agent = needle.Needle(tools=tools, weights=args.model)
|
|
101
|
+
write_message({"type": "ready", "needleVersion": needle.__version__})
|
|
102
|
+
try:
|
|
103
|
+
for raw_line in sys.stdin:
|
|
104
|
+
request: Any = None
|
|
105
|
+
try:
|
|
106
|
+
request = json.loads(raw_line)
|
|
107
|
+
if request.get("type") == "close":
|
|
108
|
+
write_message({"type": "closed"})
|
|
109
|
+
return
|
|
110
|
+
if request.get("type") != "classify":
|
|
111
|
+
raise ValueError("unknown worker request")
|
|
112
|
+
request_id = request.get("id")
|
|
113
|
+
input_text = request.get("input")
|
|
114
|
+
if not isinstance(request_id, int) or not isinstance(input_text, str):
|
|
115
|
+
raise ValueError("classify requires an integer id and string input")
|
|
116
|
+
agent.reset()
|
|
117
|
+
response = agent.complete(input_text)
|
|
118
|
+
write_message(
|
|
119
|
+
{
|
|
120
|
+
"id": request_id,
|
|
121
|
+
"ok": True,
|
|
122
|
+
"result": classification_result(response),
|
|
123
|
+
}
|
|
124
|
+
)
|
|
125
|
+
except Exception as error:
|
|
126
|
+
request_id = request.get("id") if isinstance(request, dict) else None
|
|
127
|
+
write_message(
|
|
128
|
+
{
|
|
129
|
+
"id": request_id,
|
|
130
|
+
"ok": False,
|
|
131
|
+
"error": {
|
|
132
|
+
"code": "classification_failed",
|
|
133
|
+
"message": str(error),
|
|
134
|
+
},
|
|
135
|
+
}
|
|
136
|
+
)
|
|
137
|
+
finally:
|
|
138
|
+
agent.close()
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def parser() -> argparse.ArgumentParser:
|
|
142
|
+
root = argparse.ArgumentParser(prog="swapai_worker")
|
|
143
|
+
commands = root.add_subparsers(dest="command", required=True)
|
|
144
|
+
|
|
145
|
+
train_parser = commands.add_parser("train")
|
|
146
|
+
train_parser.add_argument("--training-data", required=True)
|
|
147
|
+
train_parser.add_argument("--output", required=True)
|
|
148
|
+
train_parser.add_argument("--checkpoint-dir", required=True)
|
|
149
|
+
train_parser.add_argument("--epochs", type=int, default=10)
|
|
150
|
+
|
|
151
|
+
serve_parser = commands.add_parser("serve")
|
|
152
|
+
serve_parser.add_argument("--model", required=True)
|
|
153
|
+
serve_parser.add_argument("--schema", required=True)
|
|
154
|
+
return root
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def main() -> None:
|
|
158
|
+
args = parser().parse_args()
|
|
159
|
+
if args.command == "train":
|
|
160
|
+
train(args)
|
|
161
|
+
else:
|
|
162
|
+
serve(args)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
if __name__ == "__main__":
|
|
166
|
+
try:
|
|
167
|
+
main()
|
|
168
|
+
except Exception as error:
|
|
169
|
+
if len(sys.argv) > 1 and sys.argv[1] == "serve":
|
|
170
|
+
write_message({"type": "error", "message": str(error)})
|
|
171
|
+
else:
|
|
172
|
+
sys.stderr.write(f"SwapAI Needle worker failed: {error}\n")
|
|
173
|
+
raise
|