@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.
@@ -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