smallm 0.5.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.
Files changed (64) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +244 -0
  3. package/dist/benchmark.d.ts +10 -0
  4. package/dist/benchmark.d.ts.map +1 -0
  5. package/dist/benchmark.js +35 -0
  6. package/dist/benchmark.js.map +1 -0
  7. package/dist/cache-file.d.ts +40 -0
  8. package/dist/cache-file.d.ts.map +1 -0
  9. package/dist/cache-file.js +163 -0
  10. package/dist/cache-file.js.map +1 -0
  11. package/dist/cache.d.ts +23 -0
  12. package/dist/cache.d.ts.map +1 -0
  13. package/dist/cache.js +58 -0
  14. package/dist/cache.js.map +1 -0
  15. package/dist/cli.d.ts +32 -0
  16. package/dist/cli.d.ts.map +1 -0
  17. package/dist/cli.js +256 -0
  18. package/dist/cli.js.map +1 -0
  19. package/dist/data/benchmarks.json +61 -0
  20. package/dist/embeddings.d.ts +50 -0
  21. package/dist/embeddings.d.ts.map +1 -0
  22. package/dist/embeddings.js +118 -0
  23. package/dist/embeddings.js.map +1 -0
  24. package/dist/errors.d.ts +18 -0
  25. package/dist/errors.d.ts.map +1 -0
  26. package/dist/errors.js +42 -0
  27. package/dist/errors.js.map +1 -0
  28. package/dist/filters.d.ts +11 -0
  29. package/dist/filters.d.ts.map +1 -0
  30. package/dist/filters.js +66 -0
  31. package/dist/filters.js.map +1 -0
  32. package/dist/index.d.ts +52 -0
  33. package/dist/index.d.ts.map +1 -0
  34. package/dist/index.js +110 -0
  35. package/dist/index.js.map +1 -0
  36. package/dist/params.d.ts +21 -0
  37. package/dist/params.d.ts.map +1 -0
  38. package/dist/params.js +50 -0
  39. package/dist/params.js.map +1 -0
  40. package/dist/query.d.ts +23 -0
  41. package/dist/query.d.ts.map +1 -0
  42. package/dist/query.js +81 -0
  43. package/dist/query.js.map +1 -0
  44. package/dist/registry/huggingface.d.ts +24 -0
  45. package/dist/registry/huggingface.d.ts.map +1 -0
  46. package/dist/registry/huggingface.js +97 -0
  47. package/dist/registry/huggingface.js.map +1 -0
  48. package/dist/registry/ollama.d.ts +3 -0
  49. package/dist/registry/ollama.d.ts.map +1 -0
  50. package/dist/registry/ollama.js +78 -0
  51. package/dist/registry/ollama.js.map +1 -0
  52. package/dist/registry/provider.d.ts +12 -0
  53. package/dist/registry/provider.d.ts.map +1 -0
  54. package/dist/registry/provider.js +3 -0
  55. package/dist/registry/provider.js.map +1 -0
  56. package/dist/scorer.d.ts +45 -0
  57. package/dist/scorer.d.ts.map +1 -0
  58. package/dist/scorer.js +161 -0
  59. package/dist/scorer.js.map +1 -0
  60. package/dist/types.d.ts +147 -0
  61. package/dist/types.d.ts.map +1 -0
  62. package/dist/types.js +8 -0
  63. package/dist/types.js.map +1 -0
  64. package/package.json +45 -0
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ollamaProvider = void 0;
4
+ /** Default local Ollama REST API address — this is Ollama's standard local port. */
5
+ const OLLAMA_API_BASE = "http://localhost:11434";
6
+ /**
7
+ * Extracts a billions-of-params number from Ollama's `parameter_size`
8
+ * string (e.g. "7B" -> 7, "1.1B" -> 1.1). Returns null if unparseable —
9
+ * same "unknown, don't guess" posture as params.ts uses for HuggingFace ids.
10
+ */
11
+ function parseParamsB(parameterSize) {
12
+ if (!parameterSize)
13
+ return null;
14
+ const match = parameterSize.match(/(\d+(?:\.\d+)?)\s*[bB]/);
15
+ if (!match)
16
+ return null;
17
+ const num = parseFloat(match[1]);
18
+ return Number.isFinite(num) ? num : null;
19
+ }
20
+ /**
21
+ * Queries a locally running Ollama installation for available models.
22
+ *
23
+ * Per the v0.4 guide's Do's/Don'ts: "handle Ollama-not-running gracefully
24
+ * (empty results + a clear error, not a crash)". Interpreted here as: never
25
+ * let a connection failure reject the whole findModels() call (that would
26
+ * be surprising when Ollama is just one of possibly several providers being
27
+ * queried) — instead, log a clear, actionable message and resolve to an
28
+ * empty candidate list. This mirrors the unknown-value philosophy used
29
+ * throughout the rest of the package: absence of data is never treated as
30
+ * a hard failure.
31
+ *
32
+ * Also per Do's/Don'ts: this ONLY queries what's already available locally.
33
+ * It never attempts to pull, install, or download a model.
34
+ */
35
+ async function listOllamaModels() {
36
+ let response;
37
+ try {
38
+ response = await fetch(`${OLLAMA_API_BASE}/api/tags`);
39
+ }
40
+ catch {
41
+ console.warn(`[smallm] Could not reach local Ollama at ${OLLAMA_API_BASE} — is Ollama running? Returning no Ollama candidates.`);
42
+ return [];
43
+ }
44
+ if (!response.ok) {
45
+ console.warn(`[smallm] Local Ollama responded with status ${response.status} — returning no Ollama candidates.`);
46
+ return [];
47
+ }
48
+ let data;
49
+ try {
50
+ data = (await response.json());
51
+ }
52
+ catch {
53
+ console.warn("[smallm] Could not parse Ollama's response — returning no Ollama candidates.");
54
+ return [];
55
+ }
56
+ return (data.models ?? []).map((item) => ({
57
+ id: item.name,
58
+ provider: "ollama",
59
+ // Ollama doesn't classify models by HF-style pipeline_tag — leaving
60
+ // this undefined means the task hard filter treats it as "unknown,
61
+ // can't determine" and lets it through, same as any other missing
62
+ // metadata (not a provider-specific special case).
63
+ pipeline_tag: undefined,
64
+ tags: item.details?.families ?? [],
65
+ // Ollama's local API doesn't expose a download-count concept.
66
+ downloads: 0,
67
+ paramsB: parseParamsB(item.details?.parameter_size),
68
+ paramsSource: item.details?.parameter_size ? "tag" : "unknown",
69
+ // Ollama doesn't report context window via this endpoint either — same
70
+ // known limitation already flagged for HuggingFace models elsewhere.
71
+ contextWindow: null,
72
+ }));
73
+ }
74
+ exports.ollamaProvider = {
75
+ name: "ollama",
76
+ listCandidates: async () => listOllamaModels(),
77
+ };
78
+ //# sourceMappingURL=ollama.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ollama.js","sourceRoot":"","sources":["../../src/registry/ollama.ts"],"names":[],"mappings":";;;AAGA,oFAAoF;AACpF,MAAM,eAAe,GAAG,wBAAwB,CAAC;AAqBjD;;;;GAIG;AACH,SAAS,YAAY,CAAC,aAAiC;IACrD,IAAI,CAAC,aAAa;QAAE,OAAO,IAAI,CAAC;IAChC,MAAM,KAAK,GAAG,aAAa,CAAC,KAAK,CAAC,wBAAwB,CAAC,CAAC;IAC5D,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,MAAM,GAAG,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACjC,OAAO,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;AAC3C,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,KAAK,UAAU,gBAAgB;IAC7B,IAAI,QAAkB,CAAC;IACvB,IAAI,CAAC;QACH,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,eAAe,WAAW,CAAC,CAAC;IACxD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,CAAC,IAAI,CACV,4CAA4C,eAAe,uDAAuD,CACnH,CAAC;QACF,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,OAAO,CAAC,IAAI,CACV,+CAA+C,QAAQ,CAAC,MAAM,oCAAoC,CACnG,CAAC;QACF,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,IAAI,IAAwB,CAAC;IAC7B,IAAI,CAAC;QACH,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAuB,CAAC;IACvD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,CAAC,IAAI,CAAC,8EAA8E,CAAC,CAAC;QAC7F,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,OAAO,CAAC,IAAI,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAY,EAAE,CAAC,CAAC;QAClD,EAAE,EAAE,IAAI,CAAC,IAAI;QACb,QAAQ,EAAE,QAAQ;QAClB,oEAAoE;QACpE,mEAAmE;QACnE,kEAAkE;QAClE,mDAAmD;QACnD,YAAY,EAAE,SAAS;QACvB,IAAI,EAAE,IAAI,CAAC,OAAO,EAAE,QAAQ,IAAI,EAAE;QAClC,8DAA8D;QAC9D,SAAS,EAAE,CAAC;QACZ,OAAO,EAAE,YAAY,CAAC,IAAI,CAAC,OAAO,EAAE,cAAc,CAAC;QACnD,YAAY,EAAE,IAAI,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS;QAC9D,uEAAuE;QACvE,qEAAqE;QACrE,aAAa,EAAE,IAAI;KACpB,CAAC,CAAC,CAAC;AACN,CAAC;AAEY,QAAA,cAAc,GAAa;IACtC,IAAI,EAAE,QAAQ;IACd,cAAc,EAAE,KAAK,IAAI,EAAE,CAAC,gBAAgB,EAAE;CAC/C,CAAC"}
@@ -0,0 +1,12 @@
1
+ import { ModelQuery, RawModel, ProviderName } from "../types";
2
+ /**
3
+ * A model registry that can list candidate models for a query. Both
4
+ * registry/huggingface.ts and registry/ollama.ts implement this. filters.ts
5
+ * and scorer.ts never see provider-specific shapes — only the shared
6
+ * RawModel — so they stay fully provider-agnostic (v0.4 Do's/Don'ts).
7
+ */
8
+ export interface Provider {
9
+ name: ProviderName;
10
+ listCandidates(query: ModelQuery): Promise<RawModel[]>;
11
+ }
12
+ //# sourceMappingURL=provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../src/registry/provider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAE9D;;;;;GAKG;AACH,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,YAAY,CAAC;IACnB,cAAc,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;CACxD"}
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.js","sourceRoot":"","sources":["../../src/registry/provider.ts"],"names":[],"mappings":""}
@@ -0,0 +1,45 @@
1
+ import { HFModel, ModelQuery, ScoringMode } from "./types";
2
+ export interface ScoreBreakdown {
3
+ task: number;
4
+ context: number;
5
+ size: number;
6
+ domain: number;
7
+ /** Weighted sum of the four components above, 0-100. */
8
+ total: number;
9
+ }
10
+ export declare function scoreModel(model: HFModel, query: ModelQuery, expectedPipelineTag: string): ScoreBreakdown;
11
+ export declare function generateReasonWhy(breakdown: ScoreBreakdown): string;
12
+ export interface Rankable {
13
+ score: number;
14
+ downloads: number;
15
+ }
16
+ /**
17
+ * Sort comparator: higher score first. When two scores land within
18
+ * TIE_THRESHOLD points of each other, break the tie by download count
19
+ * (higher downloads first) instead — a minor popularity signal, not a
20
+ * scoring component.
21
+ */
22
+ export declare function compareModels(a: Rankable, b: Rankable): number;
23
+ /** Cosine-similarity-based score (0-100) between the free-text task and the model's available text signals. */
24
+ export declare function scoreEmbeddingSimilarity(model: HFModel, taskText: string): number;
25
+ export interface ScoringResult {
26
+ /** Always computed (needed for hybrid blending and as a reference), regardless of mode. */
27
+ ruleBreakdown: ScoreBreakdown;
28
+ /** Only present when scoringMode is "embedding" or "hybrid". */
29
+ embeddingScore?: number;
30
+ /** The score actually used for ModelMatch.score, per scoringMode. */
31
+ finalScore: number;
32
+ scoringMode: ScoringMode;
33
+ }
34
+ /**
35
+ * Computes the final score for a model under the requested scoringMode.
36
+ * - "rule" (default): finalScore = ruleBreakdown.total, unchanged from v0.1/v0.2.
37
+ * - "embedding": finalScore = embeddingScore alone.
38
+ * - "hybrid": finalScore = ruleScore*0.60 + embeddingScore*0.40 (locked weights).
39
+ * Hard filters (filters.ts) run before this, identically regardless of mode —
40
+ * this function only ever sees candidates that already passed them.
41
+ */
42
+ export declare function computeFinalScore(model: HFModel, query: ModelQuery, expectedPipelineTag: string): ScoringResult;
43
+ /** Mode-aware reasonWhy: rule-only text for "rule", an embedding note added/substituted otherwise. */
44
+ export declare function generateReasonWhyForResult(result: ScoringResult): string;
45
+ //# sourceMappingURL=scorer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scorer.d.ts","sourceRoot":"","sources":["../src/scorer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAM3D,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,wDAAwD;IACxD,KAAK,EAAE,MAAM,CAAC;CACf;AAsCD,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,mBAAmB,EAAE,MAAM,GAAG,cAAc,CASzG;AAID,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,cAAc,GAAG,MAAM,CAwBnE;AAID,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,GAAG,MAAM,CAK9D;AAmBD,+GAA+G;AAC/G,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAKjF;AAED,MAAM,WAAW,aAAa;IAC5B,2FAA2F;IAC3F,aAAa,EAAE,cAAc,CAAC;IAC9B,gEAAgE;IAChE,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,qEAAqE;IACrE,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,WAAW,CAAC;CAC1B;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,OAAO,EACd,KAAK,EAAE,UAAU,EACjB,mBAAmB,EAAE,MAAM,GAC1B,aAAa,CAmBf;AAED,sGAAsG;AACtG,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM,CAoBxE"}
package/dist/scorer.js ADDED
@@ -0,0 +1,161 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.scoreModel = scoreModel;
4
+ exports.generateReasonWhy = generateReasonWhy;
5
+ exports.compareModels = compareModels;
6
+ exports.scoreEmbeddingSimilarity = scoreEmbeddingSimilarity;
7
+ exports.computeFinalScore = computeFinalScore;
8
+ exports.generateReasonWhyForResult = generateReasonWhyForResult;
9
+ const embeddings_1 = require("./embeddings");
10
+ const NEUTRAL_SCORE = 50;
11
+ const TIE_THRESHOLD = 1;
12
+ // ---- Component scorers (Section 4.3) ----
13
+ /** Task match: 50% weight. Filter already guarantees compatibility or "unknown". */
14
+ function scoreTask(model, expectedPipelineTag) {
15
+ if (!model.pipeline_tag)
16
+ return NEUTRAL_SCORE; // unknown metadata
17
+ return model.pipeline_tag === expectedPipelineTag ? 100 : 0;
18
+ }
19
+ /**
20
+ * Context window fit: 20% weight. contextWindow is unknown for every model
21
+ * in the MVP pipeline (list endpoint doesn't return it) — see index.ts notes.
22
+ * Formula kept general so it does the right thing once contextWindow is
23
+ * ever populated: exactly meeting the requirement -> 50, double or more -> 100.
24
+ */
25
+ function scoreContext(model, contextLength) {
26
+ if (model.contextWindow == null)
27
+ return NEUTRAL_SCORE;
28
+ const ratio = model.contextWindow / contextLength; // >= 1, guaranteed by hard filter
29
+ const score = 50 + Math.min(1, ratio - 1) * 50;
30
+ return Math.min(100, Math.max(0, score));
31
+ }
32
+ /** Size fit: 15% weight. Closer to (without exceeding) maxParamsB scores higher. */
33
+ function scoreSize(model, maxParamsB) {
34
+ if (model.paramsB == null)
35
+ return NEUTRAL_SCORE; // unknown metadata
36
+ if (maxParamsB === undefined)
37
+ return NEUTRAL_SCORE; // no budget constraint given
38
+ return Math.min(100, Math.max(0, (model.paramsB / maxParamsB) * 100));
39
+ }
40
+ /** Domain match: 15% weight. */
41
+ function scoreDomain(model, domain) {
42
+ if (domain === undefined)
43
+ return NEUTRAL_SCORE; // component not requested
44
+ const tags = model.tags ?? [];
45
+ const found = tags.some((tag) => tag.toLowerCase() === domain.toLowerCase());
46
+ return found ? 100 : 0;
47
+ }
48
+ function scoreModel(model, query, expectedPipelineTag) {
49
+ const task = scoreTask(model, expectedPipelineTag);
50
+ const context = scoreContext(model, query.contextLength);
51
+ const size = scoreSize(model, query.maxParamsB);
52
+ const domain = scoreDomain(model, query.domain);
53
+ const total = Math.round(task * 0.5 + context * 0.2 + size * 0.15 + domain * 0.15);
54
+ return { task, context, size, domain, total };
55
+ }
56
+ // ---- reasonWhy generation (Section 4.6) ----
57
+ function generateReasonWhy(breakdown) {
58
+ const parts = [];
59
+ if (breakdown.task >= 90)
60
+ parts.push("strong task match");
61
+ else if (breakdown.task >= 50)
62
+ parts.push("reasonable task fit");
63
+ else
64
+ parts.push("uncertain task fit");
65
+ if (breakdown.context >= 90)
66
+ parts.push("generous context window");
67
+ else if (breakdown.context >= 50)
68
+ parts.push("sufficient context window");
69
+ else
70
+ parts.push("tight context window");
71
+ if (breakdown.size >= 80)
72
+ parts.push("comfortably within your parameter limit");
73
+ else if (breakdown.size >= 50)
74
+ parts.push("reasonable size fit");
75
+ else
76
+ parts.push("well under your size budget");
77
+ if (breakdown.domain >= 90)
78
+ parts.push("matches your domain focus");
79
+ // domain omitted from sentence when neutral/not requested or unmatched —
80
+ // it's the least essential signal and keeps the sentence readable.
81
+ // Capitalize first part, join naturally.
82
+ const [first, ...rest] = parts;
83
+ const capitalized = first.charAt(0).toUpperCase() + first.slice(1);
84
+ if (rest.length === 0)
85
+ return capitalized + ".";
86
+ return `${capitalized}, ${rest.slice(0, -1).join(", ")}${rest.length > 1 ? ", and " : " and "}${rest[rest.length - 1]}.`;
87
+ }
88
+ /**
89
+ * Sort comparator: higher score first. When two scores land within
90
+ * TIE_THRESHOLD points of each other, break the tie by download count
91
+ * (higher downloads first) instead — a minor popularity signal, not a
92
+ * scoring component.
93
+ */
94
+ function compareModels(a, b) {
95
+ if (Math.abs(a.score - b.score) <= TIE_THRESHOLD) {
96
+ return b.downloads - a.downloads;
97
+ }
98
+ return b.score - a.score;
99
+ }
100
+ // ---- v0.3: embedding-based scoring + mode blending ----
101
+ const HYBRID_RULE_WEIGHT = 0.6;
102
+ const HYBRID_EMBEDDING_WEIGHT = 0.4;
103
+ /**
104
+ * Builds the best available free-text description of a model from what the
105
+ * MVP/v0.2 pipeline actually has on hand (list endpoint only — no per-model
106
+ * detail fetch, same limitation flagged elsewhere for contextWindow). This
107
+ * is an approximation, not a real model card description: id (de-slugged),
108
+ * pipeline_tag, and tags.
109
+ */
110
+ function modelDescriptionText(model) {
111
+ const idWords = model.id.replace(/[/_-]/g, " ");
112
+ return [idWords, model.pipeline_tag ?? "", ...(model.tags ?? [])].join(" ");
113
+ }
114
+ /** Cosine-similarity-based score (0-100) between the free-text task and the model's available text signals. */
115
+ function scoreEmbeddingSimilarity(model, taskText) {
116
+ const taskVector = (0, embeddings_1.embed)(taskText);
117
+ const modelVector = (0, embeddings_1.embed)(modelDescriptionText(model));
118
+ const similarity = (0, embeddings_1.cosineSimilarity)(taskVector, modelVector); // ~[0,1] for these non-negative vectors
119
+ return Math.min(100, Math.max(0, similarity * 100));
120
+ }
121
+ /**
122
+ * Computes the final score for a model under the requested scoringMode.
123
+ * - "rule" (default): finalScore = ruleBreakdown.total, unchanged from v0.1/v0.2.
124
+ * - "embedding": finalScore = embeddingScore alone.
125
+ * - "hybrid": finalScore = ruleScore*0.60 + embeddingScore*0.40 (locked weights).
126
+ * Hard filters (filters.ts) run before this, identically regardless of mode —
127
+ * this function only ever sees candidates that already passed them.
128
+ */
129
+ function computeFinalScore(model, query, expectedPipelineTag) {
130
+ const mode = query.scoringMode ?? "rule";
131
+ const ruleBreakdown = scoreModel(model, query, expectedPipelineTag);
132
+ if (mode === "rule") {
133
+ return { ruleBreakdown, finalScore: ruleBreakdown.total, scoringMode: mode };
134
+ }
135
+ const embeddingScore = scoreEmbeddingSimilarity(model, query.task);
136
+ if (mode === "embedding") {
137
+ return { ruleBreakdown, embeddingScore, finalScore: Math.round(embeddingScore), scoringMode: mode };
138
+ }
139
+ // hybrid
140
+ const finalScore = Math.round(ruleBreakdown.total * HYBRID_RULE_WEIGHT + embeddingScore * HYBRID_EMBEDDING_WEIGHT);
141
+ return { ruleBreakdown, embeddingScore, finalScore, scoringMode: mode };
142
+ }
143
+ /** Mode-aware reasonWhy: rule-only text for "rule", an embedding note added/substituted otherwise. */
144
+ function generateReasonWhyForResult(result) {
145
+ if (result.scoringMode === "rule") {
146
+ return generateReasonWhy(result.ruleBreakdown);
147
+ }
148
+ const embeddingScore = result.embeddingScore ?? 0;
149
+ const embeddingPhrase = embeddingScore >= 75
150
+ ? "strong semantic similarity to your task description"
151
+ : embeddingScore >= 40
152
+ ? "moderate semantic similarity to your task description"
153
+ : "weak semantic similarity to your task description";
154
+ if (result.scoringMode === "embedding") {
155
+ return embeddingPhrase.charAt(0).toUpperCase() + embeddingPhrase.slice(1) + ".";
156
+ }
157
+ // hybrid: rule-based reasoning plus the embedding signal
158
+ const ruleReason = generateReasonWhy(result.ruleBreakdown);
159
+ return `${ruleReason.slice(0, -1)}, plus ${embeddingPhrase}.`;
160
+ }
161
+ //# sourceMappingURL=scorer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scorer.js","sourceRoot":"","sources":["../src/scorer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Shared types for smallm.
3
+ * This is the LOCKED contract (see MVP guide, Section 3).
4
+ * Do not change shapes here without flagging it — everything else depends on this file.
5
+ */
6
+ /** What the caller sends in to findModels(). */
7
+ export interface ModelQuery {
8
+ /** What kind of task the model needs to do. Open string allowed for forward-compat. */
9
+ task: "summarize" | "classify" | "extract" | "chat" | "code" | "translate" | string;
10
+ /** REQUIRED. Minimum context window needed, in tokens. Treated as a HARD filter. */
11
+ contextLength: number;
12
+ /** Optional hint about target hardware. v0.4: widened to also accept a
13
+ * structured HardwareSpec — existing string-enum callers keep working. */
14
+ hardware?: "cpu" | "gpu-low" | "gpu-high" | HardwareSpec;
15
+ /** Optional domain tag to prefer, e.g. "medical", "legal". Used in scoring only. */
16
+ domain?: string;
17
+ /** User-set size cutoff in billions of params, e.g. 7 = "under 7B". HARD filter. */
18
+ maxParamsB?: number;
19
+ /**
20
+ * RESERVED for v0.2+. Accepted on the type for API stability, but NOT
21
+ * scored or filtered on in MVP (HF metadata doesn't give reliable latency data).
22
+ */
23
+ maxLatencyMs?: number;
24
+ /** Max number of results returned. Defaults to 5 if omitted. */
25
+ limit?: number;
26
+ /**
27
+ * v0.2 addition, optional. Configures the file-based cache
28
+ * (directory + TTL). Additive — omitting it preserves v0.1 defaults.
29
+ */
30
+ cacheOptions?: CacheOptions;
31
+ /**
32
+ * v0.3 addition, optional. "rule" (default, v0.1/v0.2 behavior) uses only
33
+ * the weighted rule-based scorer. "embedding" scores purely on text
34
+ * similarity between `task` and each candidate's available text signals.
35
+ * "hybrid" blends both per the locked 60/40 formula (see scorer.ts).
36
+ * Additive — omitting it preserves prior-version behavior exactly.
37
+ */
38
+ scoringMode?: ScoringMode;
39
+ /**
40
+ * v0.4 addition, optional. Which registries to query. Defaults to
41
+ * ["huggingface"] when omitted — identical behavior to v0.1-v0.3.
42
+ */
43
+ providers?: ProviderName[];
44
+ }
45
+ /** v0.3: which scoring strategy to use. */
46
+ export type ScoringMode = "rule" | "embedding" | "hybrid";
47
+ /** v0.4: which registries a query can target. */
48
+ export type ProviderName = "huggingface" | "ollama";
49
+ /**
50
+ * v0.4: structured hardware spec carrying real VRAM figures, for more
51
+ * precise filtering than the coarse cpu/gpu-low/gpu-high enum allows.
52
+ * ModelQuery.hardware is WIDENED to accept this in addition to the
53
+ * original string enum — see that field's doc comment.
54
+ */
55
+ export interface HardwareSpec {
56
+ type: "cpu" | "gpu";
57
+ vramGB?: number;
58
+ }
59
+ /** A single scored model returned to the caller. */
60
+ export interface ModelMatch {
61
+ name: string;
62
+ /** v0.4: widened from the literal "huggingface" to include "ollama". */
63
+ provider: ProviderName;
64
+ /** null if size could not be detected from id/tags/config. */
65
+ paramsB: number | null;
66
+ /** null if context window could not be determined. */
67
+ contextWindow: number | null;
68
+ /** Human-readable explanation, generated from the structured score breakdown. */
69
+ reasonWhy: string;
70
+ /** 0–100 */
71
+ score: number;
72
+ /**
73
+ * v0.3 addition, optional. Which scoring mode actually produced this
74
+ * result's score — useful when a caller wants to confirm which strategy
75
+ * ran. Always populated in practice by findModels(), but kept optional
76
+ * on the type per the locked v0.3 contract (so a hand-constructed
77
+ * ModelMatch from older code, e.g. in a mock, still type-checks).
78
+ */
79
+ scoringMode?: ScoringMode;
80
+ }
81
+ /**
82
+ * v0.4: shared shape every Provider implementation maps its raw data into,
83
+ * before candidates reach filters.ts / scorer.ts. Those two files only
84
+ * ever see this shape — never provider-specific data — so they stay fully
85
+ * provider-agnostic (per the v0.4 guide's Do's/Don'ts). NOT part of the
86
+ * public contract.
87
+ */
88
+ export interface RawModel {
89
+ /** Provider-specific model id, e.g. "meta-llama/Llama-3-8B-Instruct" (HF) or "llama3:8b" (Ollama). */
90
+ id: string;
91
+ /**
92
+ * Which provider this candidate came from. Optional rather than required
93
+ * purely so pre-v0.4 internal call sites/test fixtures (written before
94
+ * the provider abstraction existed) keep type-checking unmodified —
95
+ * every real Provider implementation always sets it in practice, and
96
+ * index.ts falls back to "huggingface" if it's ever missing.
97
+ */
98
+ provider?: ProviderName;
99
+ /** Task classification for the model, e.g. "text-generation". HuggingFace-specific concept; undefined for Ollama. */
100
+ pipeline_tag?: string;
101
+ /** Free-form tags — used for domain matching etc. */
102
+ tags?: string[];
103
+ /** Download count — used only for tie-breaking (Section 4.5), not scoring. HuggingFace-specific; undefined for Ollama. */
104
+ downloads?: number;
105
+ /** Filled in by params.ts after extraction. Not present on raw provider response. */
106
+ paramsB?: number | null;
107
+ /** Filled in by params.ts after extraction. Not present on raw provider response. */
108
+ paramsSource?: ParamsSource;
109
+ /** Context window in tokens, if known. May come from config data. */
110
+ contextWindow?: number | null;
111
+ }
112
+ /**
113
+ * Backward-compatible alias. filters.ts, scorer.ts, params.ts, and their
114
+ * existing tests were written against "HFModel" before the v0.4 provider
115
+ * abstraction existed — same shape now that HuggingFace is one of possibly
116
+ * several providers, so this is kept as an alias rather than forcing a
117
+ * rename at every call site.
118
+ */
119
+ export type HFModel = RawModel;
120
+ /**
121
+ * Internal-only tracking type. Explains WHERE a paramsB value came from,
122
+ * for debugging. Must NOT be exported from index.ts, and must NOT appear
123
+ * on ModelMatch.
124
+ */
125
+ export type ParamsSource = "id" | "tag" | "config" | "unknown";
126
+ /**
127
+ * A single measured-latency data point for a (model, hardware) pair.
128
+ * Shipped as a checked-in, versioned JSON dataset — not measured live.
129
+ */
130
+ export interface BenchmarkEntry {
131
+ modelId: string;
132
+ hardware: "cpu" | "gpu-low" | "gpu-high";
133
+ avgLatencyMs: number;
134
+ /** ISO date string — benchmark data goes stale, so track when it was taken. */
135
+ measuredAt: string;
136
+ /** Conditions the measurement was taken under. */
137
+ promptTokens: number;
138
+ outputTokens: number;
139
+ }
140
+ /** Options for the v0.2 file-based cache. */
141
+ export interface CacheOptions {
142
+ /** Default: OS temp dir + '/smallm-cache' */
143
+ dir?: string;
144
+ /** Default: same TTL as MVP's in-memory cache (5 minutes). */
145
+ ttlMs?: number;
146
+ }
147
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ /**
3
+ * Shared types for smallm.
4
+ * This is the LOCKED contract (see MVP guide, Section 3).
5
+ * Do not change shapes here without flagging it — everything else depends on this file.
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":";AAAA;;;;GAIG"}
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "smallm",
3
+ "version": "0.5.0",
4
+ "description": "Find the right small language model (SLM) for a task, based on given context. Live rule-based matching against the HuggingFace API.",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "bin": {
8
+ "smallm": "dist/cli.js"
9
+ },
10
+ "files": [
11
+ "dist"
12
+ ],
13
+ "directories": {
14
+ "test": "test"
15
+ },
16
+ "scripts": {
17
+ "build": "tsc",
18
+ "postbuild": "node -e \"try { require('fs').chmodSync('dist/cli.js', 0o755); } catch (e) {}\"",
19
+ "test": "vitest run",
20
+ "test:watch": "vitest"
21
+ },
22
+ "keywords": [
23
+ "llm",
24
+ "slm",
25
+ "small-language-model",
26
+ "huggingface",
27
+ "model-selection",
28
+ "cli"
29
+ ],
30
+ "author": "HongZiro",
31
+ "license": "ISC",
32
+ "devDependencies": {
33
+ "@types/node": "^26.2.0",
34
+ "eslint": "^10.8.1",
35
+ "typescript": "^7.0.2",
36
+ "vitest": "^4.1.10"
37
+ },
38
+ "repository": {
39
+ "type": "git",
40
+ "url": "https://github.com/dhruvil05/smallm"
41
+ },
42
+ "bugs": {
43
+ "url": "https://github.com/dhruvil05/smallm/issues"
44
+ }
45
+ }