tribalmemory 0.1.0__py3-none-any.whl
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.
- tribalmemory/__init__.py +3 -0
- tribalmemory/a21/__init__.py +38 -0
- tribalmemory/a21/config/__init__.py +20 -0
- tribalmemory/a21/config/providers.py +104 -0
- tribalmemory/a21/config/system.py +184 -0
- tribalmemory/a21/container/__init__.py +8 -0
- tribalmemory/a21/container/container.py +212 -0
- tribalmemory/a21/providers/__init__.py +32 -0
- tribalmemory/a21/providers/base.py +241 -0
- tribalmemory/a21/providers/deduplication.py +99 -0
- tribalmemory/a21/providers/lancedb.py +232 -0
- tribalmemory/a21/providers/memory.py +128 -0
- tribalmemory/a21/providers/mock.py +54 -0
- tribalmemory/a21/providers/openai.py +151 -0
- tribalmemory/a21/providers/timestamp.py +88 -0
- tribalmemory/a21/system.py +293 -0
- tribalmemory/cli.py +298 -0
- tribalmemory/interfaces.py +306 -0
- tribalmemory/mcp/__init__.py +9 -0
- tribalmemory/mcp/__main__.py +6 -0
- tribalmemory/mcp/server.py +484 -0
- tribalmemory/performance/__init__.py +1 -0
- tribalmemory/performance/benchmarks.py +285 -0
- tribalmemory/performance/corpus_generator.py +171 -0
- tribalmemory/portability/__init__.py +1 -0
- tribalmemory/portability/embedding_metadata.py +320 -0
- tribalmemory/server/__init__.py +9 -0
- tribalmemory/server/__main__.py +6 -0
- tribalmemory/server/app.py +187 -0
- tribalmemory/server/config.py +115 -0
- tribalmemory/server/models.py +206 -0
- tribalmemory/server/routes.py +378 -0
- tribalmemory/services/__init__.py +15 -0
- tribalmemory/services/deduplication.py +115 -0
- tribalmemory/services/embeddings.py +273 -0
- tribalmemory/services/import_export.py +506 -0
- tribalmemory/services/memory.py +275 -0
- tribalmemory/services/vector_store.py +360 -0
- tribalmemory/testing/__init__.py +22 -0
- tribalmemory/testing/embedding_utils.py +110 -0
- tribalmemory/testing/fixtures.py +123 -0
- tribalmemory/testing/metrics.py +256 -0
- tribalmemory/testing/mocks.py +560 -0
- tribalmemory/testing/semantic_expansions.py +91 -0
- tribalmemory/utils.py +23 -0
- tribalmemory-0.1.0.dist-info/METADATA +275 -0
- tribalmemory-0.1.0.dist-info/RECORD +51 -0
- tribalmemory-0.1.0.dist-info/WHEEL +5 -0
- tribalmemory-0.1.0.dist-info/entry_points.txt +3 -0
- tribalmemory-0.1.0.dist-info/licenses/LICENSE +190 -0
- tribalmemory-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
"""Performance benchmarks for Tribal Memory.
|
|
2
|
+
|
|
3
|
+
Provides functions to measure retrieval latency, embedding throughput,
|
|
4
|
+
and cache effectiveness using mock services for CI-friendly execution.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import random
|
|
8
|
+
import statistics
|
|
9
|
+
import time
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
|
|
12
|
+
from ..testing.mocks import MockEmbeddingService, MockVectorStore
|
|
13
|
+
from .corpus_generator import CorpusConfig, generate_corpus
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class LatencyStats:
|
|
18
|
+
"""Latency percentile statistics in milliseconds."""
|
|
19
|
+
p50: float
|
|
20
|
+
p95: float
|
|
21
|
+
p99: float
|
|
22
|
+
mean: float
|
|
23
|
+
min: float
|
|
24
|
+
max: float
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class BenchmarkResult:
|
|
29
|
+
"""Result of a retrieval latency benchmark."""
|
|
30
|
+
corpus_size: int
|
|
31
|
+
num_queries: int
|
|
32
|
+
stats: LatencyStats
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class ThroughputResult:
|
|
37
|
+
"""Result of an embedding throughput benchmark."""
|
|
38
|
+
total_embeddings: int
|
|
39
|
+
total_time_ms: float
|
|
40
|
+
embeddings_per_second: float
|
|
41
|
+
batch_size: int
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass
|
|
45
|
+
class CacheResult:
|
|
46
|
+
"""Result of a cache effectiveness benchmark.
|
|
47
|
+
|
|
48
|
+
'Cache hits' here means queries that were repeats of previously
|
|
49
|
+
seen queries (simulating a query cache). 'Cache misses' are
|
|
50
|
+
first-time queries that would require full embedding + retrieval.
|
|
51
|
+
"""
|
|
52
|
+
total_queries: int
|
|
53
|
+
cache_hits: int # Repeated queries (would be served from cache)
|
|
54
|
+
cache_misses: int # First-seen queries (require full retrieval)
|
|
55
|
+
hit_rate: float
|
|
56
|
+
avg_hit_latency_ms: float
|
|
57
|
+
avg_miss_latency_ms: float
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
async def benchmark_retrieval_latency(
|
|
61
|
+
corpus_size: int = 1000,
|
|
62
|
+
num_queries: int = 50,
|
|
63
|
+
seed: int = 42,
|
|
64
|
+
) -> BenchmarkResult:
|
|
65
|
+
"""Benchmark retrieval latency at a given corpus size.
|
|
66
|
+
|
|
67
|
+
Populates a mock store with `corpus_size` entries, then
|
|
68
|
+
measures latency of `num_queries` random recall operations.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
corpus_size: Number of memories to populate.
|
|
72
|
+
num_queries: Number of recall queries to measure.
|
|
73
|
+
seed: Random seed for reproducibility.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
BenchmarkResult with p50/p95/p99 latency stats.
|
|
77
|
+
"""
|
|
78
|
+
rng = random.Random(seed)
|
|
79
|
+
|
|
80
|
+
# Use small embedding dimension for benchmark speed (the dimension
|
|
81
|
+
# doesn't affect retrieval algorithm complexity, just constant factors)
|
|
82
|
+
embedding_dim = 64
|
|
83
|
+
embedding_service = MockEmbeddingService(
|
|
84
|
+
embedding_dim=embedding_dim, skip_latency=True
|
|
85
|
+
)
|
|
86
|
+
vector_store = MockVectorStore(embedding_service)
|
|
87
|
+
|
|
88
|
+
# Populate corpus
|
|
89
|
+
corpus = generate_corpus(CorpusConfig(size=corpus_size, seed=seed))
|
|
90
|
+
for entry in corpus:
|
|
91
|
+
entry.embedding = await embedding_service.embed(entry.content)
|
|
92
|
+
await vector_store.store(entry)
|
|
93
|
+
|
|
94
|
+
# Generate query texts
|
|
95
|
+
queries = [
|
|
96
|
+
rng.choice(corpus).content[:50] # Use prefix of random entry
|
|
97
|
+
for _ in range(num_queries)
|
|
98
|
+
]
|
|
99
|
+
|
|
100
|
+
# Pre-compute query embeddings (not part of latency measurement)
|
|
101
|
+
query_embeddings = [
|
|
102
|
+
await embedding_service.embed(q) for q in queries
|
|
103
|
+
]
|
|
104
|
+
|
|
105
|
+
# Measure retrieval latencies (vector store only — the core path)
|
|
106
|
+
latencies: list[float] = []
|
|
107
|
+
for qe in query_embeddings:
|
|
108
|
+
start = time.perf_counter()
|
|
109
|
+
await vector_store.recall(qe, limit=5, min_similarity=0.1)
|
|
110
|
+
elapsed_ms = (time.perf_counter() - start) * 1000
|
|
111
|
+
latencies.append(elapsed_ms)
|
|
112
|
+
|
|
113
|
+
latencies.sort()
|
|
114
|
+
stats = LatencyStats(
|
|
115
|
+
p50=_percentile(latencies, 50),
|
|
116
|
+
p95=_percentile(latencies, 95),
|
|
117
|
+
p99=_percentile(latencies, 99),
|
|
118
|
+
mean=statistics.mean(latencies),
|
|
119
|
+
min=min(latencies),
|
|
120
|
+
max=max(latencies),
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
return BenchmarkResult(
|
|
124
|
+
corpus_size=corpus_size,
|
|
125
|
+
num_queries=num_queries,
|
|
126
|
+
stats=stats,
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
async def benchmark_batch_embedding_throughput(
|
|
131
|
+
num_texts: int = 500,
|
|
132
|
+
batch_size: int = 50,
|
|
133
|
+
seed: int = 42,
|
|
134
|
+
) -> ThroughputResult:
|
|
135
|
+
"""Benchmark embedding generation throughput.
|
|
136
|
+
|
|
137
|
+
Measures how many embeddings per second the service can produce,
|
|
138
|
+
comparing single vs batch modes.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
num_texts: Total number of texts to embed.
|
|
142
|
+
batch_size: Size of each batch (1 for single mode).
|
|
143
|
+
seed: Random seed for reproducibility.
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
ThroughputResult with throughput metrics.
|
|
147
|
+
"""
|
|
148
|
+
service = MockEmbeddingService(embedding_dim=64, skip_latency=True)
|
|
149
|
+
|
|
150
|
+
# Generate texts
|
|
151
|
+
corpus = generate_corpus(CorpusConfig(size=num_texts, seed=seed))
|
|
152
|
+
texts = [entry.content for entry in corpus]
|
|
153
|
+
|
|
154
|
+
# Embed in batches
|
|
155
|
+
start = time.perf_counter()
|
|
156
|
+
for i in range(0, len(texts), batch_size):
|
|
157
|
+
batch = texts[i:i + batch_size]
|
|
158
|
+
await service.embed_batch(batch)
|
|
159
|
+
total_ms = (time.perf_counter() - start) * 1000
|
|
160
|
+
|
|
161
|
+
return ThroughputResult(
|
|
162
|
+
total_embeddings=num_texts,
|
|
163
|
+
total_time_ms=total_ms,
|
|
164
|
+
embeddings_per_second=num_texts / (total_ms / 1000) if total_ms > 0 else 0,
|
|
165
|
+
batch_size=batch_size,
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
async def benchmark_cache_effectiveness(
|
|
170
|
+
corpus_size: int = 500,
|
|
171
|
+
num_queries: int = 100,
|
|
172
|
+
repeat_ratio: float = 0.5,
|
|
173
|
+
seed: int = 42,
|
|
174
|
+
) -> CacheResult:
|
|
175
|
+
"""Benchmark query cache effectiveness.
|
|
176
|
+
|
|
177
|
+
Simulates a realistic workload with a mix of repeated and unique
|
|
178
|
+
queries, measuring cache hit rates and latency impact.
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
corpus_size: Number of memories in the store.
|
|
182
|
+
num_queries: Total number of queries to run.
|
|
183
|
+
repeat_ratio: Fraction of queries that are repeats (0.0-1.0).
|
|
184
|
+
seed: Random seed for reproducibility.
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
CacheResult with hit rate and latency metrics.
|
|
188
|
+
"""
|
|
189
|
+
rng = random.Random(seed)
|
|
190
|
+
|
|
191
|
+
# Use small embedding dimension for benchmark speed
|
|
192
|
+
embedding_dim = 64
|
|
193
|
+
embedding_service = MockEmbeddingService(
|
|
194
|
+
embedding_dim=embedding_dim, skip_latency=True
|
|
195
|
+
)
|
|
196
|
+
vector_store = MockVectorStore(embedding_service)
|
|
197
|
+
|
|
198
|
+
# Populate corpus
|
|
199
|
+
corpus = generate_corpus(CorpusConfig(size=corpus_size, seed=seed))
|
|
200
|
+
for entry in corpus:
|
|
201
|
+
entry.embedding = await embedding_service.embed(entry.content)
|
|
202
|
+
await vector_store.store(entry)
|
|
203
|
+
|
|
204
|
+
# Generate a pool of truly unique queries (sample without replacement)
|
|
205
|
+
pool_size = min(len(corpus), num_queries * 2)
|
|
206
|
+
unique_pool = rng.sample(corpus, k=pool_size)
|
|
207
|
+
unique_queries = list(dict.fromkeys(
|
|
208
|
+
entry.content[:50] for entry in unique_pool
|
|
209
|
+
)) # Deduplicate while preserving order
|
|
210
|
+
|
|
211
|
+
seen_queries: list[str] = [] # Ordered list for repeat selection
|
|
212
|
+
seen_set: set[str] = set()
|
|
213
|
+
cache_hits = 0
|
|
214
|
+
cache_misses = 0
|
|
215
|
+
hit_latencies: list[float] = []
|
|
216
|
+
miss_latencies: list[float] = []
|
|
217
|
+
unique_idx = 0 # Track position in unique pool (no replacement)
|
|
218
|
+
|
|
219
|
+
# Cache of embeddings to simulate cache behavior
|
|
220
|
+
embedding_cache: dict[str, list[float]] = {}
|
|
221
|
+
|
|
222
|
+
for i in range(num_queries):
|
|
223
|
+
if rng.random() < repeat_ratio and seen_queries:
|
|
224
|
+
# Pick a previously seen query (repeat)
|
|
225
|
+
query = rng.choice(seen_queries)
|
|
226
|
+
is_repeat = True
|
|
227
|
+
else:
|
|
228
|
+
# Pick next unique query (sequential, no replacement)
|
|
229
|
+
if unique_idx < len(unique_queries):
|
|
230
|
+
query = unique_queries[unique_idx]
|
|
231
|
+
unique_idx += 1
|
|
232
|
+
else:
|
|
233
|
+
# Exhausted unique pool, fall back to random
|
|
234
|
+
query = rng.choice(unique_queries)
|
|
235
|
+
is_repeat = query in seen_set
|
|
236
|
+
|
|
237
|
+
# Simulate cache: reuse embedding if seen before
|
|
238
|
+
if query in embedding_cache:
|
|
239
|
+
query_embedding = embedding_cache[query]
|
|
240
|
+
else:
|
|
241
|
+
query_embedding = await embedding_service.embed(query)
|
|
242
|
+
embedding_cache[query] = query_embedding
|
|
243
|
+
|
|
244
|
+
start = time.perf_counter()
|
|
245
|
+
await vector_store.recall(query_embedding, limit=5, min_similarity=0.3)
|
|
246
|
+
elapsed_ms = (time.perf_counter() - start) * 1000
|
|
247
|
+
|
|
248
|
+
if is_repeat:
|
|
249
|
+
cache_hits += 1
|
|
250
|
+
hit_latencies.append(elapsed_ms)
|
|
251
|
+
else:
|
|
252
|
+
cache_misses += 1
|
|
253
|
+
miss_latencies.append(elapsed_ms)
|
|
254
|
+
|
|
255
|
+
if query not in seen_set:
|
|
256
|
+
seen_queries.append(query)
|
|
257
|
+
seen_set.add(query)
|
|
258
|
+
|
|
259
|
+
hit_rate = cache_hits / num_queries if num_queries > 0 else 0.0
|
|
260
|
+
|
|
261
|
+
return CacheResult(
|
|
262
|
+
total_queries=num_queries,
|
|
263
|
+
cache_hits=cache_hits,
|
|
264
|
+
cache_misses=cache_misses,
|
|
265
|
+
hit_rate=hit_rate,
|
|
266
|
+
avg_hit_latency_ms=(
|
|
267
|
+
statistics.mean(hit_latencies) if hit_latencies else 0.0
|
|
268
|
+
),
|
|
269
|
+
avg_miss_latency_ms=(
|
|
270
|
+
statistics.mean(miss_latencies) if miss_latencies else 0.0
|
|
271
|
+
),
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def _percentile(sorted_data: list[float], pct: int) -> float:
|
|
276
|
+
"""Calculate percentile from sorted data using nearest-rank method.
|
|
277
|
+
|
|
278
|
+
Uses simple index-based lookup without interpolation. Sufficient
|
|
279
|
+
for benchmark reporting where exact percentile precision isn't critical.
|
|
280
|
+
"""
|
|
281
|
+
if not sorted_data:
|
|
282
|
+
return 0.0
|
|
283
|
+
idx = int(len(sorted_data) * pct / 100)
|
|
284
|
+
idx = min(idx, len(sorted_data) - 1)
|
|
285
|
+
return sorted_data[idx]
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"""Synthetic corpus generator for scale/performance testing.
|
|
2
|
+
|
|
3
|
+
Generates realistic memory entries with varied content, tags,
|
|
4
|
+
and source types for benchmarking retrieval and storage.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import random
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from typing import Optional
|
|
10
|
+
|
|
11
|
+
from ..interfaces import MemoryEntry, MemorySource
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class CorpusConfig:
|
|
16
|
+
"""Configuration for corpus generation."""
|
|
17
|
+
size: int = 1000
|
|
18
|
+
seed: Optional[int] = None
|
|
19
|
+
min_content_words: int = 5
|
|
20
|
+
max_content_words: int = 30
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# Realistic memory content templates
|
|
24
|
+
_TEMPLATES = [
|
|
25
|
+
"User prefers {preference} for {domain}",
|
|
26
|
+
"Meeting with {person} scheduled for {time}",
|
|
27
|
+
"{person} mentioned they like {preference}",
|
|
28
|
+
"Project {project} uses {technology} for {purpose}",
|
|
29
|
+
"Important: {fact} about {topic}",
|
|
30
|
+
"User's {attribute} is {value}",
|
|
31
|
+
"{person} works at {company} on {project}",
|
|
32
|
+
"Reminder: {task} is due {time}",
|
|
33
|
+
"The {tool} configuration uses {setting}",
|
|
34
|
+
"Conversation about {topic} with {person}",
|
|
35
|
+
"{person} prefers {preference} over {alternative}",
|
|
36
|
+
"Bug in {project}: {description}",
|
|
37
|
+
"Decision: use {technology} for {purpose}",
|
|
38
|
+
"User asked about {topic} in the context of {domain}",
|
|
39
|
+
"Note: {fact} regarding {topic}",
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
_PERSONS = [
|
|
43
|
+
"Joe", "Alice", "Bob", "Charlie", "Diana", "Eve",
|
|
44
|
+
"Frank", "Grace", "Hank", "Iris", "Jake", "Karen",
|
|
45
|
+
]
|
|
46
|
+
_PREFERENCES = [
|
|
47
|
+
"dark mode", "TypeScript", "Python", "concise responses",
|
|
48
|
+
"morning meetings", "async communication", "vim", "VS Code",
|
|
49
|
+
"functional programming", "microservices", "monorepos",
|
|
50
|
+
"test-driven development", "pair programming", "remote work",
|
|
51
|
+
]
|
|
52
|
+
_DOMAINS = [
|
|
53
|
+
"web development", "machine learning", "DevOps", "UI design",
|
|
54
|
+
"backend services", "data engineering", "mobile apps",
|
|
55
|
+
"cloud infrastructure", "security", "performance optimization",
|
|
56
|
+
]
|
|
57
|
+
_PROJECTS = [
|
|
58
|
+
"Wally", "TribalMemory", "OpenClaw", "Dashboard",
|
|
59
|
+
"API Gateway", "Auth Service", "Analytics", "Notifications",
|
|
60
|
+
]
|
|
61
|
+
_TECHNOLOGIES = [
|
|
62
|
+
"React", "FastAPI", "PostgreSQL", "Redis", "Docker",
|
|
63
|
+
"Kubernetes", "LanceDB", "OpenAI", "Tailscale", "Synapse",
|
|
64
|
+
]
|
|
65
|
+
_TOPICS = [
|
|
66
|
+
"embedding models", "vector search", "memory portability",
|
|
67
|
+
"performance tuning", "caching strategies", "deduplication",
|
|
68
|
+
"schema migrations", "API versioning", "error handling",
|
|
69
|
+
"security best practices", "testing strategies", "CI/CD",
|
|
70
|
+
]
|
|
71
|
+
_TAGS_POOL = [
|
|
72
|
+
"preferences", "meetings", "projects", "technical",
|
|
73
|
+
"personal", "work", "urgent", "low-priority",
|
|
74
|
+
"architecture", "bugs", "decisions", "reminders",
|
|
75
|
+
]
|
|
76
|
+
_SOURCES = [
|
|
77
|
+
MemorySource.USER_EXPLICIT,
|
|
78
|
+
MemorySource.AUTO_CAPTURE,
|
|
79
|
+
MemorySource.CROSS_INSTANCE,
|
|
80
|
+
]
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def generate_corpus(config: Optional[CorpusConfig] = None) -> list[MemoryEntry]:
|
|
84
|
+
"""Generate a synthetic corpus of memory entries.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
config: Corpus generation configuration. Uses defaults if None.
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
List of MemoryEntry objects with varied content and metadata.
|
|
91
|
+
"""
|
|
92
|
+
config = config or CorpusConfig()
|
|
93
|
+
rng = random.Random(config.seed)
|
|
94
|
+
|
|
95
|
+
entries: list[MemoryEntry] = []
|
|
96
|
+
for _ in range(config.size):
|
|
97
|
+
template = rng.choice(_TEMPLATES)
|
|
98
|
+
content = _fill_template(template, rng)
|
|
99
|
+
|
|
100
|
+
tags = rng.sample(_TAGS_POOL, k=rng.randint(1, 3))
|
|
101
|
+
source = rng.choice(_SOURCES)
|
|
102
|
+
|
|
103
|
+
entry = MemoryEntry(
|
|
104
|
+
content=content,
|
|
105
|
+
tags=tags,
|
|
106
|
+
source_type=source,
|
|
107
|
+
source_instance=f"instance-{rng.randint(1, 5)}",
|
|
108
|
+
)
|
|
109
|
+
entries.append(entry)
|
|
110
|
+
|
|
111
|
+
return entries
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _fill_template(template: str, rng: random.Random) -> str:
|
|
115
|
+
"""Fill a template with random realistic values."""
|
|
116
|
+
replacements = {
|
|
117
|
+
"{preference}": rng.choice(_PREFERENCES),
|
|
118
|
+
"{domain}": rng.choice(_DOMAINS),
|
|
119
|
+
"{person}": rng.choice(_PERSONS),
|
|
120
|
+
"{time}": rng.choice([
|
|
121
|
+
"next Monday", "tomorrow", "Friday afternoon",
|
|
122
|
+
"end of sprint", "Q2", "next week",
|
|
123
|
+
]),
|
|
124
|
+
"{project}": rng.choice(_PROJECTS),
|
|
125
|
+
"{technology}": rng.choice(_TECHNOLOGIES),
|
|
126
|
+
"{purpose}": rng.choice([
|
|
127
|
+
"the backend", "testing", "deployment", "monitoring",
|
|
128
|
+
"data storage", "real-time updates", "authentication",
|
|
129
|
+
]),
|
|
130
|
+
"{topic}": rng.choice(_TOPICS),
|
|
131
|
+
"{attribute}": rng.choice([
|
|
132
|
+
"timezone", "favorite language", "team", "role",
|
|
133
|
+
"preferred editor", "working hours",
|
|
134
|
+
]),
|
|
135
|
+
"{value}": rng.choice([
|
|
136
|
+
"Mountain Time", "Python", "engineering", "senior dev",
|
|
137
|
+
"VS Code", "9am-5pm", "night owl hours",
|
|
138
|
+
]),
|
|
139
|
+
"{company}": rng.choice([
|
|
140
|
+
"Google", "a startup", "Anthropic", "OpenAI",
|
|
141
|
+
"Meta", "a consulting firm",
|
|
142
|
+
]),
|
|
143
|
+
"{tool}": rng.choice([
|
|
144
|
+
"Docker", "Kubernetes", "Nginx", "Redis",
|
|
145
|
+
"PostgreSQL", "LanceDB",
|
|
146
|
+
]),
|
|
147
|
+
"{setting}": rng.choice([
|
|
148
|
+
"port 8080", "max_connections=100", "debug=false",
|
|
149
|
+
"cache_ttl=3600", "workers=4",
|
|
150
|
+
]),
|
|
151
|
+
"{fact}": rng.choice([
|
|
152
|
+
"embeddings need normalization", "cache invalidation is hard",
|
|
153
|
+
"deadline was moved", "requirements changed",
|
|
154
|
+
"API rate limit is 100/min", "tests must pass before merge",
|
|
155
|
+
]),
|
|
156
|
+
"{task}": rng.choice([
|
|
157
|
+
"code review", "deploy to staging", "update docs",
|
|
158
|
+
"run benchmarks", "fix flaky test", "merge PR",
|
|
159
|
+
]),
|
|
160
|
+
"{alternative}": rng.choice(_PREFERENCES),
|
|
161
|
+
"{description}": rng.choice([
|
|
162
|
+
"query timeout under load", "missing error handler",
|
|
163
|
+
"incorrect cache key", "race condition in startup",
|
|
164
|
+
"memory leak in long sessions",
|
|
165
|
+
]),
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
result = template
|
|
169
|
+
for key, value in replacements.items():
|
|
170
|
+
result = result.replace(key, value)
|
|
171
|
+
return result
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Portability module for embedding model metadata and bundle import/export."""
|