Coverage for src/local_deep_research/embeddings/sentence_transformer_models.py: 100%
16 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-22 21:20 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-22 21:20 +0000
1"""Curated Sentence Transformer model identities.
3The model key is the stable value stored in user settings and collection
4metadata. Only newly introduced keys may pin a different Hugging Face artifact:
5legacy keys must keep their historic floating-revision semantics because
6collection metadata does not yet persist an artifact revision.
8Pinned ``revision`` values are immutable Hugging Face commit hashes
9that identify the exact model artifact bytes fetched at download time.
10"""
12from dataclasses import dataclass
13from types import MappingProxyType
14from typing import Mapping
17from ..constants import (
18 DEFAULT_LOCAL_SEARCH_MODEL as DEFAULT_SENTENCE_TRANSFORMER_MODEL,
19)
22@dataclass(frozen=True)
23class SentenceTransformerModelSpec:
24 """Artifact policy and display metadata for a curated model."""
26 repository: str
27 revision: str | None
28 dimensions: int
29 description: str
30 max_seq_length: int
31 # Require behavior-changing metadata before an offline load. Missing
32 # Transformer or tokenizer configuration can silently change truncation
33 # or tokenizer selection without raising a missing-weight error.
34 required_cache_files: tuple[str, ...] = (
35 "config.json",
36 "modules.json",
37 "1_Pooling/config.json",
38 "tokenizer_config.json",
39 "sentence_bert_config.json",
40 )
41 # Tokenizer vocabulary, as groups of which at least one file must be
42 # cached. Without it the offline constructor does not fail: it builds a
43 # placeholder tokenizer from the special tokens alone and embeds every
44 # text as the same few ids, which the missing-weight repair never sees.
45 # Legacy WordPiece/MPNet repositories publish both the fast
46 # ``tokenizer.json`` and the slow ``vocab.txt``; either one alone rebuilds
47 # the same tokenizer, so a cache holding only one of them still loads.
48 required_cache_file_alternatives: tuple[tuple[str, ...], ...] = (
49 ("tokenizer.json", "vocab.txt"),
50 )
52 def display_metadata(self) -> dict[str, int | str]:
53 """Return the legacy metadata shape consumed by provider APIs."""
55 return {
56 "dimensions": self.dimensions,
57 "description": self.description,
58 "max_seq_length": self.max_seq_length,
59 }
62SENTENCE_TRANSFORMER_MODELS: Mapping[str, SentenceTransformerModelSpec] = (
63 MappingProxyType(
64 {
65 DEFAULT_SENTENCE_TRANSFORMER_MODEL: SentenceTransformerModelSpec(
66 repository=DEFAULT_SENTENCE_TRANSFORMER_MODEL,
67 revision="e7f32e3c00f91d699e8c43b53106206bcc72bb22", # DevSkim: ignore DS173237 - pinned public Hugging Face commit SHA, not a secret
68 dimensions=768,
69 description=(
70 "Recommended default with higher-quality English and "
71 "long-context document retrieval."
72 ),
73 max_seq_length=8192,
74 # This pinned artifact has no sentence_bert_config.json;
75 # its token limit is inferred from model and tokenizer config.
76 required_cache_files=(
77 "config.json",
78 "modules.json",
79 "1_Pooling/config.json",
80 "tokenizer_config.json",
81 ),
82 # ModernBERT ships a BPE ``tokenizer.json`` and no vocab.txt.
83 required_cache_file_alternatives=(("tokenizer.json",),),
84 ),
85 "all-MiniLM-L6-v2": SentenceTransformerModelSpec(
86 repository="sentence-transformers/all-MiniLM-L6-v2",
87 revision=None,
88 dimensions=384,
89 description="Fast, lightweight model. Good for general use.",
90 max_seq_length=256,
91 ),
92 "all-mpnet-base-v2": SentenceTransformerModelSpec(
93 repository="sentence-transformers/all-mpnet-base-v2",
94 revision=None,
95 dimensions=768,
96 description="Higher quality, slower. Best accuracy.",
97 max_seq_length=384,
98 ),
99 "multi-qa-MiniLM-L6-cos-v1": SentenceTransformerModelSpec(
100 repository=("sentence-transformers/multi-qa-MiniLM-L6-cos-v1"),
101 revision=None,
102 dimensions=384,
103 description="Optimized for question-answering tasks.",
104 max_seq_length=512,
105 ),
106 "paraphrase-multilingual-MiniLM-L12-v2": (
107 SentenceTransformerModelSpec(
108 repository=(
109 "sentence-transformers/"
110 "paraphrase-multilingual-MiniLM-L12-v2"
111 ),
112 revision=None,
113 dimensions=384,
114 description="Supports multiple languages.",
115 max_seq_length=128,
116 # An XLM-R SentencePiece tokenizer, not WordPiece: there
117 # is no vocab.txt fallback, so require ``tokenizer.json``.
118 required_cache_file_alternatives=(("tokenizer.json",),),
119 )
120 ),
121 }
122 )
123)
126def get_sentence_transformer_model_spec(
127 model_name: str,
128) -> SentenceTransformerModelSpec | None:
129 """Resolve either a stable catalog key or its full repository name."""
131 spec = SENTENCE_TRANSFORMER_MODELS.get(model_name)
132 if spec is not None:
133 return spec
135 return next(
136 (
137 candidate
138 for candidate in SENTENCE_TRANSFORMER_MODELS.values()
139 if candidate.repository == model_name
140 ),
141 None,
142 )