@rizom/ops 0.2.0-alpha.679 → 0.2.0-alpha.680

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.
@@ -87,6 +87,7 @@ export interface ResolvedUserIdentity {
87
87
  profileKind?: string | undefined;
88
88
  anchorKind?: AnchorKind | undefined;
89
89
  embeddingEnabled?: boolean | undefined;
90
+ embeddingModel?: string | undefined;
90
91
  plugins?: UserConfig["plugins"];
91
92
  spaces?: string[] | undefined;
92
93
  topicExtractionEnabled?: boolean | undefined;
package/dist/schema.d.ts CHANGED
@@ -141,6 +141,7 @@ export declare const userSchema: z.ZodObject<{
141
141
  plugins: z.ZodOptional<typeof userPluginsSchema>;
142
142
  spaces: z.ZodOptional<z.ZodArray<z.ZodString>>;
143
143
  embeddingEnabled: z.ZodOptional<z.ZodBoolean>;
144
+ embeddingModel: z.ZodOptional<z.ZodString>;
144
145
  topicExtractionEnabled: z.ZodOptional<z.ZodBoolean>;
145
146
  skillDerivationEnabled: z.ZodOptional<z.ZodBoolean>;
146
147
  swotDerivationEnabled: z.ZodOptional<z.ZodBoolean>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rizom/ops",
3
- "version": "0.2.0-alpha.679",
3
+ "version": "0.2.0-alpha.680",
4
4
  "description": "Operator CLI for managing private brain fleet registry repos",
5
5
  "keywords": [
6
6
  "brains",
@@ -207,6 +207,14 @@ jobs:
207
207
  SERVICE_NAME: rover
208
208
  run: bun deploy/scripts/create-predeploy-backup.ts
209
209
 
210
+ - name: Pull local models
211
+ env:
212
+ SERVER_IP: ${{ steps.provision.outputs.server_ip }}
213
+ LOCAL_MODELS: ${{ steps.user_config.outputs.local_models }}
214
+ run: |
215
+ SSH_USER="$(ruby -e 'require "yaml"; config = YAML.load_file("deploy/kamal/deploy.yml") || {}; puts(config.dig("ssh", "user") || "root")')" \
216
+ bun deploy/scripts/pull-local-models.ts
217
+
210
218
  - name: Release operator-confirmed stale deploy lock
211
219
  if: ${{ inputs.release_stale_lock }}
212
220
  env:
@@ -230,6 +238,7 @@ jobs:
230
238
  WWW_DOMAIN: ${{ steps.user_config.outputs.www_domain }}
231
239
  PREVIEW_DOMAIN: ${{ steps.user_config.outputs.preview_domain }}
232
240
  BRAIN_YAML_PATH: ${{ steps.user_config.outputs.brain_yaml_path }}
241
+ LOCAL_MODELS: ${{ steps.user_config.outputs.local_models }}
233
242
  run: kamal setup --skip-push -c deploy/kamal/deploy.yml
234
243
 
235
244
  - name: Install container health watchdog
@@ -0,0 +1,69 @@
1
+ import { requireEnv } from "./helpers";
2
+
3
+ /** The model runtime the `models` accessory runs; kamal-deploy.yml pins the same image. */
4
+ export const MODEL_RUNTIME_IMAGE = "ollama/ollama:0.40.11";
5
+ const MODELS_VOLUME = "/opt/brain-models:/root/.ollama";
6
+ const MODEL_TAG = /^[A-Za-z0-9][A-Za-z0-9._:/-]*$/;
7
+
8
+ /**
9
+ * The local models the deploy names (`LOCAL_MODELS`, the `local:` models of
10
+ * brain.yaml without their prefix), once each.
11
+ */
12
+ export function parseLocalModels(value: string | undefined): string[] {
13
+ const models = (value ?? "").split(/[\s,]+/).filter(Boolean);
14
+ const invalid = models.find((model) => !MODEL_TAG.test(model));
15
+ if (invalid !== undefined) {
16
+ throw new Error(`Invalid local model "${invalid}"`);
17
+ }
18
+ return [...new Set(models)];
19
+ }
20
+
21
+ /**
22
+ * Pulls each model into the models volume with a one-off runtime container,
23
+ * so the models are on disk before the brain starts, whether or not the
24
+ * accessory runs yet.
25
+ */
26
+ export function buildLocalModelPullScript(models: readonly string[]): string {
27
+ const pulls = models.map((model) => `ollama pull ${model}`).join(" && ");
28
+ return `#!/usr/bin/env bash
29
+ set -euo pipefail
30
+ SUDO=""
31
+ if [ "$(id -u)" -ne 0 ]; then
32
+ SUDO=sudo
33
+ fi
34
+
35
+ $SUDO mkdir -p /opt/brain-models
36
+ $SUDO docker run --rm --entrypoint /bin/sh -v ${MODELS_VOLUME} ${MODEL_RUNTIME_IMAGE} -c '
37
+ ollama serve >/dev/null 2>&1 &
38
+ for attempt in $(seq 1 30); do
39
+ ollama list >/dev/null 2>&1 && break
40
+ sleep 1
41
+ done
42
+ ${pulls}
43
+ '
44
+ `;
45
+ }
46
+
47
+ export async function pullLocalModels(): Promise<void> {
48
+ const models = parseLocalModels(process.env["LOCAL_MODELS"]);
49
+ if (models.length === 0) {
50
+ console.log("No local models in brain.yaml; nothing to pull.");
51
+ return;
52
+ }
53
+ const serverIp = requireEnv("SERVER_IP");
54
+ const sshUser = requireEnv("SSH_USER").trim();
55
+ if (!sshUser) throw new Error("Missing SSH_USER");
56
+ const child = Bun.spawn(["ssh", `${sshUser}@${serverIp}`, "bash -s"], {
57
+ stdin: new Blob([buildLocalModelPullScript(models)]),
58
+ stdout: "inherit",
59
+ stderr: "inherit",
60
+ });
61
+ const exitCode = await child.exited;
62
+ if (exitCode !== 0) {
63
+ throw new Error(`Pulling local models failed with exit ${exitCode}`);
64
+ }
65
+ }
66
+
67
+ if (import.meta.main) {
68
+ await pullLocalModels();
69
+ }
@@ -25,6 +25,20 @@ if (!brainDomain) {
25
25
  throw new Error(`Missing domain in ${brainYamlPath}`);
26
26
  }
27
27
 
28
+ // The models the runtime beside the brain serves: brain.yaml's local:
29
+ // models, unprefixed. None means the deploy runs no runtime.
30
+ const localModels = [
31
+ ...new Set(
32
+ [
33
+ ...brainYaml.matchAll(
34
+ /^(?:embeddingModel|answerModel|visionModel|model):\s*['"]?local:([^'"\s]+)/gm,
35
+ ),
36
+ ]
37
+ .map((match) => match[1] ?? "")
38
+ .filter(Boolean),
39
+ ),
40
+ ].join(",");
41
+
28
42
  const registry = await loadPilotRegistry(process.cwd());
29
43
  const user = registry.users.find((entry) => entry.handle === handle);
30
44
  if (!user) {
@@ -69,6 +83,7 @@ const outputs: Record<string, string> = {
69
83
  image_repository: `ghcr.io/${repository}`,
70
84
  image_tag: imageTag,
71
85
  registry_username: repositoryOwner,
86
+ local_models: localModels,
72
87
  };
73
88
 
74
89
  const required = ["brain_version", "registry_username"];