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.
- package/LICENSE +15 -0
- package/README.md +244 -0
- package/dist/benchmark.d.ts +10 -0
- package/dist/benchmark.d.ts.map +1 -0
- package/dist/benchmark.js +35 -0
- package/dist/benchmark.js.map +1 -0
- package/dist/cache-file.d.ts +40 -0
- package/dist/cache-file.d.ts.map +1 -0
- package/dist/cache-file.js +163 -0
- package/dist/cache-file.js.map +1 -0
- package/dist/cache.d.ts +23 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +58 -0
- package/dist/cache.js.map +1 -0
- package/dist/cli.d.ts +32 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +256 -0
- package/dist/cli.js.map +1 -0
- package/dist/data/benchmarks.json +61 -0
- package/dist/embeddings.d.ts +50 -0
- package/dist/embeddings.d.ts.map +1 -0
- package/dist/embeddings.js +118 -0
- package/dist/embeddings.js.map +1 -0
- package/dist/errors.d.ts +18 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +42 -0
- package/dist/errors.js.map +1 -0
- package/dist/filters.d.ts +11 -0
- package/dist/filters.d.ts.map +1 -0
- package/dist/filters.js +66 -0
- package/dist/filters.js.map +1 -0
- package/dist/index.d.ts +52 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +110 -0
- package/dist/index.js.map +1 -0
- package/dist/params.d.ts +21 -0
- package/dist/params.d.ts.map +1 -0
- package/dist/params.js +50 -0
- package/dist/params.js.map +1 -0
- package/dist/query.d.ts +23 -0
- package/dist/query.d.ts.map +1 -0
- package/dist/query.js +81 -0
- package/dist/query.js.map +1 -0
- package/dist/registry/huggingface.d.ts +24 -0
- package/dist/registry/huggingface.d.ts.map +1 -0
- package/dist/registry/huggingface.js +97 -0
- package/dist/registry/huggingface.js.map +1 -0
- package/dist/registry/ollama.d.ts +3 -0
- package/dist/registry/ollama.d.ts.map +1 -0
- package/dist/registry/ollama.js +78 -0
- package/dist/registry/ollama.js.map +1 -0
- package/dist/registry/provider.d.ts +12 -0
- package/dist/registry/provider.d.ts.map +1 -0
- package/dist/registry/provider.js +3 -0
- package/dist/registry/provider.js.map +1 -0
- package/dist/scorer.d.ts +45 -0
- package/dist/scorer.d.ts.map +1 -0
- package/dist/scorer.js +161 -0
- package/dist/scorer.js.map +1 -0
- package/dist/types.d.ts +147 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -0
- 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 @@
|
|
|
1
|
+
{"version":3,"file":"provider.js","sourceRoot":"","sources":["../../src/registry/provider.ts"],"names":[],"mappings":""}
|
package/dist/scorer.d.ts
ADDED
|
@@ -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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"}
|
package/dist/types.d.ts
ADDED
|
@@ -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
|
+
}
|