@functional-systems/srcx 0.1.0-dev.19 → 0.1.0-dev.20

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/CHANGELOG.md CHANGED
@@ -2,6 +2,9 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ - Distinguish Qwen setup, cache, device, input-size and timeout failures with
6
+ actionable messages while keeping raw worker diagnostics private.
7
+
5
8
  - Record three developer search/read investigations with concrete change maps,
6
9
  bounded live usage and source verification, separate from ranking benchmarks.
7
10
 
package/README.md CHANGED
@@ -183,6 +183,22 @@ or fallback. Empty candidate pools skip model execution. Existing search default
183
183
  and managed embedding usage rules remain unchanged; wider pools increase source
184
184
  verification work. See the [integration validation](VALIDATION.md#local-reranker-integration).
185
185
 
186
+ Failures include a fixed remediation message where the cause is known:
187
+
188
+ | Failure | Action |
189
+ | ------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
190
+ | Python executable missing or not executable | Check the path and execute permissions in `SRCX_RERANK_PYTHON`. |
191
+ | Missing or incompatible Python imports | Install `runtime/requirements.txt` using that Python environment. |
192
+ | Missing or incomplete model cache | Download the pinned revision and all files listed above; check `HF_HUB_CACHE`. |
193
+ | Invalid or unavailable device | Use `SRCX_RERANK_DEVICE=auto` or `cpu`, or an available `mps`/`cuda` device. |
194
+ | Request exceeds 4 MiB | Reduce `--candidates` or shorten the query. |
195
+ | A query/chunk pair exceeds 8,192 tokens | Shorten the query or omit `--rerank`; reducing candidate count does not shorten an individual chunk. |
196
+ | Worker exceeds 120 seconds | Reduce `--candidates` or select an available faster device. |
197
+
198
+ Unknown worker failures remain generic. Raw subprocess output and library
199
+ exceptions are never included in these messages. Failed reranking does not
200
+ silently return the original ranking; rerun without `--rerank` to opt out.
201
+
186
202
  For practical search → read → change-planning examples, see the
187
203
  [developer workflow pilot](https://github.com/lambdadb/srcx/blob/develop/eval/DEVELOPER-WORKFLOW-PILOT.md).
188
204
  It follows three maintenance investigations through related source and tests,
package/VALIDATION.md CHANGED
@@ -2,6 +2,33 @@
2
2
 
3
3
  Date: 2026-09-26 (Asia/Seoul).
4
4
 
5
+ ## Qwen failure diagnostics
6
+
7
+ Known worker failures now produce distinct setup, cache, device and input-limit
8
+ messages; host launch and deadline failures have separate remediation. Unknown
9
+ failures remain generic, and raw worker output is not forwarded. Search defaults,
10
+ scores, source verification and the no-fallback policy are unchanged.
11
+
12
+ Local validation passed 97 Node tests, nine Python checks (six worker and three
13
+ local-reranker protocol checks), three installed-package CLI checks, typecheck,
14
+ formatting and version consistency. Reserved worker exits and unexpected exits
15
+ were exercised with real synthetic subprocesses. Deadline, permission and
16
+ unexpected-signal mapping used a mocked process callback; the test asserted the
17
+ unchanged 120-second/SIGKILL policy rather than waiting for a real timeout.
18
+
19
+ Six additional offline checks used the existing Python 3.12 environment and
20
+ cached pinned model: missing executable, missing Python imports, empty cache,
21
+ invalid device, unavailable CUDA, and successful MPS inference. All produced the
22
+ expected outcome; the successful two-candidate query returned finite scores and
23
+ ranked the sorting function above an unrelated reader. No LambdaDB calls, document
24
+ embeddings or model downloads were needed. This is an error-handling and inference
25
+ smoke check, not a retrieval-quality comparison.
26
+
27
+ The retained report is `.srcx/diagnostics/report.json` in the
28
+ `srcx-reranker-diagnostics` worktree, SHA-256
29
+ `7725a96f6c5bf5b4a6a4f01369e2dbea6a8ce16d70692f6e1039774f42564e67`.
30
+ The local runner and Node/package logs are retained alongside it.
31
+
5
32
  ## Developer workflow pilot
6
33
 
7
34
  The [workflow pilot](https://github.com/lambdadb/srcx/blob/develop/eval/DEVELOPER-WORKFLOW-PILOT.md)
package/dist/rerank.js CHANGED
@@ -3,6 +3,14 @@ import { fileURLToPath } from "node:url";
3
3
  import { invariant } from "./common.js";
4
4
  export const QWEN_MODEL = "Qwen/Qwen3-Reranker-0.6B";
5
5
  export const QWEN_REVISION = "e61197ed45024b0ed8a2d74b80b4d909f1255473";
6
+ // Reserved worker exit codes; never interpret or forward library stderr.
7
+ const WORKER_FAILURES = {
8
+ 20: "Qwen Python dependencies are missing or incompatible. Install runtime/requirements.txt with the Python selected by SRCX_RERANK_PYTHON.",
9
+ 21: "Qwen model cache is missing or incomplete. Download the pinned model revision from README and check HF_HUB_CACHE.",
10
+ 22: "Qwen device is invalid or unavailable. Set SRCX_RERANK_DEVICE to auto or cpu, or a supported mps/cuda device.",
11
+ 23: "Qwen query/chunk pair exceeds 8,192 tokens. Shorten the query or use search without --rerank; source is not truncated.",
12
+ 24: "Qwen request exceeds 4 MiB. Reduce --candidates or shorten the query.",
13
+ };
6
14
  export function candidateLimit(limit, options) {
7
15
  invariant(Number.isInteger(limit) && limit >= 1 && limit <= 100, "Limit must be an integer from 1 to 100.");
8
16
  invariant(options.rerank === undefined || options.rerank === "qwen", "Unknown reranker.");
@@ -20,7 +28,7 @@ export async function qwenScores(query, candidates) {
20
28
  candidates.length <= 100 &&
21
29
  new Set(candidates.map((c) => c.id)).size === candidates.length, "Invalid reranker candidates.");
22
30
  const input = JSON.stringify({ query, candidates });
23
- invariant(Buffer.byteLength(input) <= 4 * 1024 * 1024, "Reranker input exceeds 4 MiB.");
31
+ invariant(Buffer.byteLength(input) <= 4 * 1024 * 1024, "Reranker input exceeds 4 MiB. Reduce --candidates or shorten the query; no fallback was applied.");
24
32
  const python = process.env.SRCX_RERANK_PYTHON;
25
33
  invariant(python, "Set SRCX_RERANK_PYTHON to the prepared Python executable.");
26
34
  const start = performance.now();
@@ -37,10 +45,21 @@ export async function qwenScores(query, candidates) {
37
45
  TOKENIZERS_PARALLELISM: "false",
38
46
  },
39
47
  }, (error, output) => {
40
- if (error)
41
- reject(new Error("Qwen reranking failed or exceeded 120 seconds. Check Python dependencies, cached model and SRCX_RERANK_DEVICE; no fallback was applied."));
42
- else
43
- resolve(output);
48
+ if (!error)
49
+ return resolve(output);
50
+ let message = "Qwen reranking failed. Check Python dependencies, cached model and SRCX_RERANK_DEVICE.";
51
+ if (error.code === "ENOENT")
52
+ message =
53
+ "Qwen Python executable was not found. Set SRCX_RERANK_PYTHON to an existing Python executable.";
54
+ else if (error.code === "EACCES")
55
+ message =
56
+ "Qwen Python executable cannot be started. Check execute permissions for SRCX_RERANK_PYTHON.";
57
+ else if (error.killed && error.signal === "SIGKILL" && !error.code)
58
+ message =
59
+ "Qwen reranking exceeded 120 seconds. Reduce --candidates or use an available faster device via SRCX_RERANK_DEVICE.";
60
+ else if (typeof error.code === "number")
61
+ message = WORKER_FAILURES[error.code] ?? message;
62
+ reject(new Error(`${message} No fallback was applied.`));
44
63
  });
45
64
  // An early worker exit can close stdin before Node finishes writing.
46
65
  child.stdin?.on("error", () => { });
@@ -1 +1 @@
1
- {"version":3,"file":"rerank.js","sourceRoot":"","sources":["../src/rerank.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"rerank.js","sourceRoot":"","sources":["../src/rerank.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@functional-systems/srcx",
3
- "version": "0.1.0-dev.19",
3
+ "version": "0.1.0-dev.20",
4
4
  "private": false,
5
5
  "description": "Version-aware code search on LambdaDB",
6
6
  "type": "module",
@@ -64,5 +64,5 @@
64
64
  "url": "https://github.com/lambdadb/srcx/issues"
65
65
  },
66
66
  "license": "Apache-2.0",
67
- "gitHead": "cd7dbc9d4682f7713669ff7c7b98e0481b583c11"
67
+ "gitHead": "d1cdcfd08f3ca8becb5d6a5cd8c42552429509c1"
68
68
  }
package/runtime/qwen.py CHANGED
@@ -4,6 +4,7 @@ import json
4
4
  import math
5
5
  import os
6
6
  import sys
7
+ from pathlib import Path
7
8
 
8
9
  MODEL = "Qwen/Qwen3-Reranker-0.6B"
9
10
  REVISION = "e61197ed45024b0ed8a2d74b80b4d909f1255473"
@@ -14,6 +15,19 @@ PREFIX = (
14
15
  )
15
16
  SUFFIX = "<|im_end|>\n<|im_start|>assistant\n<think>\n\n</think>\n\n"
16
17
  INSTRUCTION = "Given a code search query, retrieve code passages that answer the query."
18
+ CACHE_FILES = ["config.json", "generation_config.json", "model.safetensors",
19
+ "tokenizer.json", "tokenizer_config.json", "vocab.json", "merges.txt"]
20
+
21
+
22
+ class WorkerFailure(ValueError):
23
+ """Exit codes: 20 imports, 21 cache, 22 device, 23 pair tokens, 24 request bytes.
24
+
25
+ The Node caller maps codes to messages; exception text never crosses the boundary.
26
+ """
27
+
28
+ def __init__(self, code):
29
+ super().__init__("Qwen worker failure")
30
+ self.code = code
17
31
 
18
32
 
19
33
  def require(condition, message):
@@ -25,7 +39,8 @@ def encode(tokenizer, query, text):
25
39
  body = f"<Instruct>: {INSTRUCTION}\n<Query>: {query}\n<Document>: {text}"
26
40
  ids = sum((tokenizer.encode(p, add_special_tokens=False)
27
41
  for p in (PREFIX, body, SUFFIX)), [])
28
- require(len(ids) <= 8192, "Pair exceeds 8192 tokens; no truncation")
42
+ if len(ids) > 8192:
43
+ raise WorkerFailure(23)
29
44
  return ids
30
45
 
31
46
 
@@ -45,22 +60,33 @@ def validate(body):
45
60
 
46
61
  def score(body):
47
62
  os.environ["HF_HUB_OFFLINE"] = "1"
48
- from huggingface_hub import snapshot_download
49
- from transformers import AutoTokenizer, AutoModelForCausalLM
50
- import torch
51
-
52
- path = snapshot_download(MODEL, revision=REVISION, local_files_only=True,
53
- allow_patterns=["config.json", "generation_config.json", "model.safetensors",
54
- "tokenizer.json", "tokenizer_config.json", "vocab.json", "merges.txt"])
63
+ device = os.environ.get("SRCX_RERANK_DEVICE", "auto")
64
+ if device not in ("auto", "cpu", "mps", "cuda"):
65
+ raise WorkerFailure(22)
66
+ try:
67
+ from huggingface_hub import snapshot_download
68
+ from huggingface_hub.errors import LocalEntryNotFoundError
69
+ from transformers import AutoTokenizer, AutoModelForCausalLM
70
+ import torch
71
+ except ImportError:
72
+ raise WorkerFailure(20) from None
73
+ if device == "auto":
74
+ device = "cuda" if torch.cuda.is_available() else "mps" if torch.backends.mps.is_available() else "cpu"
75
+ elif ((device == "cuda" and not torch.cuda.is_available()) or
76
+ (device == "mps" and not torch.backends.mps.is_available())):
77
+ raise WorkerFailure(22)
78
+ try:
79
+ path = snapshot_download(MODEL, revision=REVISION, local_files_only=True,
80
+ allow_patterns=CACHE_FILES)
81
+ except LocalEntryNotFoundError:
82
+ raise WorkerFailure(21) from None
83
+ if not all((Path(path) / name).is_file() for name in CACHE_FILES):
84
+ raise WorkerFailure(21)
55
85
  tokenizer = AutoTokenizer.from_pretrained(path, local_files_only=True, trust_remote_code=False)
56
86
  # Reuse exact duplicate code within this query, retaining every candidate ID.
57
87
  encoded = {c["text"]: None for c in body["candidates"]}
58
88
  for text in encoded:
59
89
  encoded[text] = encode(tokenizer, body["query"], text)
60
- device = os.environ.get("SRCX_RERANK_DEVICE", "auto")
61
- require(device in ("auto", "cpu", "mps", "cuda"), "Invalid device")
62
- if device == "auto":
63
- device = "cuda" if torch.cuda.is_available() else "mps" if torch.backends.mps.is_available() else "cpu"
64
90
  model = AutoModelForCausalLM.from_pretrained(
65
91
  path, local_files_only=True, trust_remote_code=False,
66
92
  dtype=torch.float32, attn_implementation="sdpa",
@@ -83,15 +109,24 @@ def score(body):
83
109
 
84
110
  def main():
85
111
  raw = sys.stdin.buffer.read(4 * 1024 * 1024 + 1)
86
- require(len(raw) <= 4 * 1024 * 1024, "Request exceeds 4 MiB")
112
+ if len(raw) > 4 * 1024 * 1024:
113
+ raise WorkerFailure(24)
87
114
  body = validate(json.loads(raw))
88
115
  print(json.dumps(score(body), allow_nan=False))
89
116
 
90
117
 
91
- if __name__ == "__main__":
118
+ def run():
92
119
  try:
93
120
  main()
121
+ except WorkerFailure as error:
122
+ # The Node caller maps these codes to fixed, actionable messages.
123
+ return error.code
94
124
  except Exception:
95
125
  # Libraries may include source text or environment values in exceptions.
96
126
  print("Qwen worker failed; verify setup, device and input size.", file=sys.stderr)
97
- sys.exit(1)
127
+ return 1
128
+ return 0
129
+
130
+
131
+ if __name__ == "__main__":
132
+ sys.exit(run())
package/src/rerank.ts CHANGED
@@ -4,6 +4,14 @@ import { invariant } from "./common.js";
4
4
 
5
5
  export const QWEN_MODEL = "Qwen/Qwen3-Reranker-0.6B";
6
6
  export const QWEN_REVISION = "e61197ed45024b0ed8a2d74b80b4d909f1255473";
7
+ // Reserved worker exit codes; never interpret or forward library stderr.
8
+ const WORKER_FAILURES: Record<number, string> = {
9
+ 20: "Qwen Python dependencies are missing or incompatible. Install runtime/requirements.txt with the Python selected by SRCX_RERANK_PYTHON.",
10
+ 21: "Qwen model cache is missing or incomplete. Download the pinned model revision from README and check HF_HUB_CACHE.",
11
+ 22: "Qwen device is invalid or unavailable. Set SRCX_RERANK_DEVICE to auto or cpu, or a supported mps/cuda device.",
12
+ 23: "Qwen query/chunk pair exceeds 8,192 tokens. Shorten the query or use search without --rerank; source is not truncated.",
13
+ 24: "Qwen request exceeds 4 MiB. Reduce --candidates or shorten the query.",
14
+ };
7
15
  export type Candidate = { id: string; text: string };
8
16
  export type RerankOptions = {
9
17
  rerank?: "qwen";
@@ -49,7 +57,7 @@ export async function qwenScores(query: string, candidates: Candidate[]) {
49
57
  const input = JSON.stringify({ query, candidates });
50
58
  invariant(
51
59
  Buffer.byteLength(input) <= 4 * 1024 * 1024,
52
- "Reranker input exceeds 4 MiB.",
60
+ "Reranker input exceeds 4 MiB. Reduce --candidates or shorten the query; no fallback was applied.",
53
61
  );
54
62
  const python = process.env.SRCX_RERANK_PYTHON;
55
63
  invariant(
@@ -74,13 +82,21 @@ export async function qwenScores(query: string, candidates: Candidate[]) {
74
82
  },
75
83
  },
76
84
  (error, output) => {
77
- if (error)
78
- reject(
79
- new Error(
80
- "Qwen reranking failed or exceeded 120 seconds. Check Python dependencies, cached model and SRCX_RERANK_DEVICE; no fallback was applied.",
81
- ),
82
- );
83
- else resolve(output);
85
+ if (!error) return resolve(output);
86
+ let message =
87
+ "Qwen reranking failed. Check Python dependencies, cached model and SRCX_RERANK_DEVICE.";
88
+ if (error.code === "ENOENT")
89
+ message =
90
+ "Qwen Python executable was not found. Set SRCX_RERANK_PYTHON to an existing Python executable.";
91
+ else if (error.code === "EACCES")
92
+ message =
93
+ "Qwen Python executable cannot be started. Check execute permissions for SRCX_RERANK_PYTHON.";
94
+ else if (error.killed && error.signal === "SIGKILL" && !error.code)
95
+ message =
96
+ "Qwen reranking exceeded 120 seconds. Reduce --candidates or use an available faster device via SRCX_RERANK_DEVICE.";
97
+ else if (typeof error.code === "number")
98
+ message = WORKER_FAILURES[error.code] ?? message;
99
+ reject(new Error(`${message} No fallback was applied.`));
84
100
  },
85
101
  );
86
102
  // An early worker exit can close stdin before Node finishes writing.