promptimizer 0.1.36 → 0.1.37

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.
@@ -1,4 +1,5 @@
1
1
  import type { ChatMessage, Classification, Tier } from "./types";
2
2
  export declare function classifyMessages(messages: ChatMessage[]): Classification;
3
+ /** Production classifier — prefer over-routing hard work over silent quality loss. */
3
4
  export declare function classifyText(text: string): Classification;
4
5
  export declare function difficultyTier(complexity: number): Tier;
package/dist/index.cjs CHANGED
@@ -136,6 +136,7 @@ var Promptimizer = class _Promptimizer {
136
136
 
137
137
  // src/classify.ts
138
138
  var HIGH_RISK = /* @__PURE__ */ new Set(["system_design", "safety_sensitive", "code_debug", "reasoning"]);
139
+ var TIER_RANK = { economy: 0, standard: 1, frontier: 2 };
139
140
  function textFrom(messages) {
140
141
  return messages.map((m) => {
141
142
  if (typeof m.content === "string") return m.content;
@@ -148,34 +149,46 @@ function classifyMessages(messages) {
148
149
  function classifyText(text) {
149
150
  const words = text.split(/\s+/).filter(Boolean).length;
150
151
  const lines = text.split("\n").length;
152
+ const numbered = (text.match(/\(\s*\d+\s*\)/g) ?? []).length + (text.match(/^\s*\d+[\).:]/gm) ?? []).length + (text.match(/\b\d+\.\s+[A-Za-z]/g) ?? []).length;
153
+ const constraintWords = (text.match(
154
+ /\b(must|include|constraint|requirement|requirements|need to|ensure|exactly-once|idempotent)\b/gi
155
+ ) ?? []).length;
151
156
  const features = {
152
157
  code_fence: /```/.test(text),
153
- code_kw: /\b(def |class |function |import |fn |pub |async |SELECT |goroutine|mutex|traceback)\b/i.test(
158
+ code_kw: /\b(def |class |function |import |fn |pub |async |goroutine|mutex|traceback)\b/i.test(text) || /\b(SELECT\b.+\bFROM\b|\bJOIN\b.+\bON\b|postgresql|\bpsql\b)/i.test(text),
159
+ math: /(\$\$|\\frac|prove that|expected value|O\([nN]\)|\d+\s*[\*\^×x]\s*\d+|infinitely many|factorial|prime)/i.test(
154
160
  text
155
161
  ),
156
- math: /(\$\$|\\frac|prove that|expected value|O\([nN]\)|\d+\s*[\*\^]\s*\d+)/i.test(text),
157
- design: /\b(design|architect|rate limiter|distributed|consistency|shard|1 million QPS)\b/i.test(
162
+ design: /\b(design(?:ing)?|architect(?:ure|ing)?|rate[\s-]?limiter|distributed|consistency|shard(?:ing)?|multi-?region|multi-?tenant|qps|edge regions?|data[\s-]?plane|failure modes?|outbox|ledger|webhook|saas|billing pipeline|consensus|raft|paxos|quorum|leader election|log replication|network partition|cap theorem|eventual consistency|token bucket|sliding window)\b/i.test(
158
163
  text
159
164
  ),
160
- reason: /\b(prove|why does|walk through|step by step|derive|contradiction|p-value)\b/i.test(
165
+ reason: /\b(prove|proof|theorem|lemma|why does|walk through|step by step|derive|contradiction|p-value|induction|euclid|sketch (?:a |the )?proof)\b/i.test(
166
+ text
167
+ ),
168
+ debug: /\b(bug|race|panic|fails on|diagnose|deadlock|concurrency hazard|code review|correctness)\b/i.test(
161
169
  text
162
170
  ),
163
- debug: /\b(bug|race|panic|fails on|diagnose|deadlock)\b/i.test(text),
164
171
  summarize: /\b(summarize|tl;dr|in two sentences|eli5)\b/i.test(text),
165
172
  translate: /\b(translate|traduce)\b/i.test(text),
166
173
  creative: /\b(write a (poem|story|song)|haiku)\b/i.test(text),
167
- safety: /\b(refund|legal|medical|hipaa|lawsuit|diagnosis)\b/i.test(text),
168
- analysis: /\b(compare|trade-?off|versus|analyse|analyze|evaluate|should we)\b/i.test(text),
169
- constraints: (text.match(/\b(must|include|constraints?|requirements?)\b/gi) ?? []).length,
174
+ safety: /\b(refund|legal|medical|hipaa|lawsuit|diagnosis|gdpr|erasure|pii|compliance|audit trail)\b/i.test(
175
+ text
176
+ ),
177
+ analysis: /\b(compare|trade-?off|versus|analyse|analyze|evaluate|should we|outline architecture)\b/i.test(
178
+ text
179
+ ),
180
+ multi_part: numbered >= 3 || numbered >= 2 && constraintWords >= 1,
181
+ constraints: constraintWords + Math.min(4, numbered),
170
182
  words,
171
183
  lines,
172
- question_marks: (text.match(/\?/g) ?? []).length
184
+ question_marks: (text.match(/\?/g) ?? []).length,
185
+ long_form: words >= 55 || lines >= 8
173
186
  };
174
187
  const category = categoryOf(features);
175
188
  const complexity = complexityOf(features, category);
176
189
  const p_small_quality = pSmallQuality(features, category, complexity);
177
190
  const quality_risk = riskOf(category, complexity, p_small_quality);
178
- const recommended_tier = tierFromP(p_small_quality);
191
+ const recommended_tier = recommendTier(p_small_quality, complexity, category, quality_risk);
179
192
  const signals = [
180
193
  features.code_fence,
181
194
  features.code_kw,
@@ -183,9 +196,10 @@ function classifyText(text) {
183
196
  features.design,
184
197
  features.reason,
185
198
  features.debug,
186
- features.safety
199
+ features.safety,
200
+ features.multi_part
187
201
  ].filter(Boolean).length;
188
- const confidence = category === "factual_recall" && words < 16 ? 0.9 : Math.min(0.95, 0.55 + 0.12 * signals);
202
+ const confidence = category === "factual_recall" && words < 16 ? 0.92 : Math.min(0.96, 0.55 + 0.1 * signals);
189
203
  return {
190
204
  complexity,
191
205
  category,
@@ -201,47 +215,64 @@ function classifyText(text) {
201
215
  };
202
216
  }
203
217
  function categoryOf(h) {
204
- if (h.safety) return "safety_sensitive";
205
218
  if (h.design) return "system_design";
219
+ if (h.safety && !h.design) return "safety_sensitive";
206
220
  if (h.debug && (h.code_fence || h.code_kw)) return "code_debug";
207
- if (h.code_fence || h.code_kw) return "code_generation";
221
+ if (h.debug && h.long_form) return "code_debug";
208
222
  if (h.math && h.reason) return "reasoning";
209
- if (h.math) return "math";
210
223
  if (h.reason) return "reasoning";
224
+ if (h.code_fence || h.code_kw) return "code_generation";
225
+ if (h.math) return "math";
226
+ if (h.safety) return "safety_sensitive";
211
227
  if (h.translate) return "translation";
212
228
  if (h.summarize) return "summarization";
213
229
  if (h.creative) return "creative";
214
- if (h.analysis) return "analysis";
215
- if (h.words < 24 && h.question_marks >= 1) return "factual_recall";
230
+ if (h.analysis || h.multi_part) return "analysis";
231
+ if (h.words < 28 && h.question_marks >= 1) return "factual_recall";
216
232
  return h.words > 80 ? "analysis" : "factual_recall";
217
233
  }
218
234
  function complexityOf(h, category) {
219
235
  let score = 1;
220
- if (h.words > 40) score += 1;
221
- if (h.words > 120) score += 1;
222
- if (h.lines > 12 || h.code_fence) score += 1;
236
+ if (h.words > 35) score += 1;
237
+ if (h.words > 90) score += 1;
238
+ if (h.lines > 10 || h.code_fence) score += 1;
223
239
  if (h.constraints >= 2) score += 1;
224
- if (h.design || h.reason) score += 1;
240
+ if (h.constraints >= 4 || h.multi_part) score += 1;
241
+ if (h.design) score += 1;
242
+ if (h.reason) score += 1;
225
243
  if (h.debug) score += 1;
226
- if (["system_design", "reasoning", "safety_sensitive"].includes(category)) score = Math.max(score + 1, 4);
227
- if (category === "code_generation") score = Math.max(score, 3);
244
+ if (h.safety && (h.design || h.multi_part)) score += 1;
245
+ if (category === "system_design") score = Math.max(score, 4);
246
+ if (category === "reasoning") score = Math.max(score, 4);
247
+ if (category === "safety_sensitive") score = Math.max(score, 4);
228
248
  if (category === "code_debug") score = Math.max(score, 4);
229
- if (category === "factual_recall" && h.words < 20) score = Math.min(score, 2);
249
+ if (category === "code_generation") score = Math.max(score, 3);
250
+ if (category === "analysis" && (h.multi_part || h.long_form)) score = Math.max(score, 3);
251
+ if (h.multi_part && (h.design || h.safety || h.analysis)) score = Math.max(score, 4);
252
+ if (category === "factual_recall" && h.words < 28 && !h.design && !h.reason && !h.debug) {
253
+ score = Math.min(score, 2);
254
+ }
255
+ if (category === "math" && h.words < 24 && !h.reason) {
256
+ score = Math.min(Math.max(score, 1), 2);
257
+ }
230
258
  return Math.max(1, Math.min(5, score));
231
259
  }
232
260
  function pSmallQuality(h, category, complexity) {
233
261
  let p = 0.96;
234
- if (h.design || category === "system_design") p -= 0.28;
235
- if (h.safety || category === "safety_sensitive") p -= 0.3;
236
- if (h.debug || category === "code_debug") p -= 0.22;
237
- if (h.reason || category === "reasoning") p -= 0.18;
238
- if (complexity >= 5) p -= 0.22;
239
- else if (complexity >= 4) p -= 0.14;
240
- else if (complexity === 3) p -= 0.06;
241
- if (h.words > 120) p -= 0.07;
242
- if (h.constraints >= 2) p -= 0.07;
243
- if (category === "code_generation") p -= 0.05;
244
- if (category === "factual_recall" && h.words < 24) p = Math.max(p, 0.94);
262
+ if (h.design || category === "system_design") p -= 0.3;
263
+ if (h.safety || category === "safety_sensitive") p -= 0.32;
264
+ if (h.debug || category === "code_debug") p -= 0.24;
265
+ if (h.reason || category === "reasoning") p -= 0.22;
266
+ if (h.multi_part) p -= 0.1;
267
+ if (complexity >= 5) p -= 0.24;
268
+ else if (complexity >= 4) p -= 0.16;
269
+ else if (complexity === 3) p -= 0.07;
270
+ if (h.words > 100) p -= 0.07;
271
+ if (h.constraints >= 3) p -= 0.08;
272
+ if (category === "code_generation") p -= 0.06;
273
+ if (category === "analysis" && complexity >= 3) p -= 0.08;
274
+ if (category === "factual_recall" && h.words < 28) p = Math.max(p, 0.94);
275
+ if (category === "math" && h.words < 24 && !h.reason) p = Math.max(p, 0.9);
245
276
  return Number(Math.min(0.99, Math.max(0.05, p)).toFixed(3));
246
277
  }
247
278
  function riskOf(category, complexity, p) {
@@ -254,6 +285,21 @@ function tierFromP(p) {
254
285
  if (p >= 0.72) return "standard";
255
286
  return "frontier";
256
287
  }
288
+ function recommendTier(p, complexity, category, risk) {
289
+ const fromP = tierFromP(p);
290
+ const fromComplexity = difficultyTier(complexity);
291
+ let tier = maxTier(fromP, fromComplexity);
292
+ if (risk === "high") tier = maxTier(tier, "standard");
293
+ if (["system_design", "reasoning", "safety_sensitive"].includes(category) && complexity >= 4) {
294
+ tier = maxTier(tier, complexity >= 5 ? "frontier" : "standard");
295
+ }
296
+ if (category === "system_design" && complexity >= 4) tier = maxTier(tier, "frontier");
297
+ if (category === "reasoning" && complexity >= 4) tier = maxTier(tier, "standard");
298
+ return tier;
299
+ }
300
+ function maxTier(a, b) {
301
+ return TIER_RANK[a] >= TIER_RANK[b] ? a : b;
302
+ }
257
303
  function difficultyTier(complexity) {
258
304
  if (complexity <= 2) return "economy";
259
305
  if (complexity === 3) return "standard";
package/dist/index.js CHANGED
@@ -101,6 +101,7 @@ var Promptimizer = class _Promptimizer {
101
101
 
102
102
  // src/classify.ts
103
103
  var HIGH_RISK = /* @__PURE__ */ new Set(["system_design", "safety_sensitive", "code_debug", "reasoning"]);
104
+ var TIER_RANK = { economy: 0, standard: 1, frontier: 2 };
104
105
  function textFrom(messages) {
105
106
  return messages.map((m) => {
106
107
  if (typeof m.content === "string") return m.content;
@@ -113,34 +114,46 @@ function classifyMessages(messages) {
113
114
  function classifyText(text) {
114
115
  const words = text.split(/\s+/).filter(Boolean).length;
115
116
  const lines = text.split("\n").length;
117
+ const numbered = (text.match(/\(\s*\d+\s*\)/g) ?? []).length + (text.match(/^\s*\d+[\).:]/gm) ?? []).length + (text.match(/\b\d+\.\s+[A-Za-z]/g) ?? []).length;
118
+ const constraintWords = (text.match(
119
+ /\b(must|include|constraint|requirement|requirements|need to|ensure|exactly-once|idempotent)\b/gi
120
+ ) ?? []).length;
116
121
  const features = {
117
122
  code_fence: /```/.test(text),
118
- code_kw: /\b(def |class |function |import |fn |pub |async |SELECT |goroutine|mutex|traceback)\b/i.test(
123
+ code_kw: /\b(def |class |function |import |fn |pub |async |goroutine|mutex|traceback)\b/i.test(text) || /\b(SELECT\b.+\bFROM\b|\bJOIN\b.+\bON\b|postgresql|\bpsql\b)/i.test(text),
124
+ math: /(\$\$|\\frac|prove that|expected value|O\([nN]\)|\d+\s*[\*\^×x]\s*\d+|infinitely many|factorial|prime)/i.test(
119
125
  text
120
126
  ),
121
- math: /(\$\$|\\frac|prove that|expected value|O\([nN]\)|\d+\s*[\*\^]\s*\d+)/i.test(text),
122
- design: /\b(design|architect|rate limiter|distributed|consistency|shard|1 million QPS)\b/i.test(
127
+ design: /\b(design(?:ing)?|architect(?:ure|ing)?|rate[\s-]?limiter|distributed|consistency|shard(?:ing)?|multi-?region|multi-?tenant|qps|edge regions?|data[\s-]?plane|failure modes?|outbox|ledger|webhook|saas|billing pipeline|consensus|raft|paxos|quorum|leader election|log replication|network partition|cap theorem|eventual consistency|token bucket|sliding window)\b/i.test(
123
128
  text
124
129
  ),
125
- reason: /\b(prove|why does|walk through|step by step|derive|contradiction|p-value)\b/i.test(
130
+ reason: /\b(prove|proof|theorem|lemma|why does|walk through|step by step|derive|contradiction|p-value|induction|euclid|sketch (?:a |the )?proof)\b/i.test(
131
+ text
132
+ ),
133
+ debug: /\b(bug|race|panic|fails on|diagnose|deadlock|concurrency hazard|code review|correctness)\b/i.test(
126
134
  text
127
135
  ),
128
- debug: /\b(bug|race|panic|fails on|diagnose|deadlock)\b/i.test(text),
129
136
  summarize: /\b(summarize|tl;dr|in two sentences|eli5)\b/i.test(text),
130
137
  translate: /\b(translate|traduce)\b/i.test(text),
131
138
  creative: /\b(write a (poem|story|song)|haiku)\b/i.test(text),
132
- safety: /\b(refund|legal|medical|hipaa|lawsuit|diagnosis)\b/i.test(text),
133
- analysis: /\b(compare|trade-?off|versus|analyse|analyze|evaluate|should we)\b/i.test(text),
134
- constraints: (text.match(/\b(must|include|constraints?|requirements?)\b/gi) ?? []).length,
139
+ safety: /\b(refund|legal|medical|hipaa|lawsuit|diagnosis|gdpr|erasure|pii|compliance|audit trail)\b/i.test(
140
+ text
141
+ ),
142
+ analysis: /\b(compare|trade-?off|versus|analyse|analyze|evaluate|should we|outline architecture)\b/i.test(
143
+ text
144
+ ),
145
+ multi_part: numbered >= 3 || numbered >= 2 && constraintWords >= 1,
146
+ constraints: constraintWords + Math.min(4, numbered),
135
147
  words,
136
148
  lines,
137
- question_marks: (text.match(/\?/g) ?? []).length
149
+ question_marks: (text.match(/\?/g) ?? []).length,
150
+ long_form: words >= 55 || lines >= 8
138
151
  };
139
152
  const category = categoryOf(features);
140
153
  const complexity = complexityOf(features, category);
141
154
  const p_small_quality = pSmallQuality(features, category, complexity);
142
155
  const quality_risk = riskOf(category, complexity, p_small_quality);
143
- const recommended_tier = tierFromP(p_small_quality);
156
+ const recommended_tier = recommendTier(p_small_quality, complexity, category, quality_risk);
144
157
  const signals = [
145
158
  features.code_fence,
146
159
  features.code_kw,
@@ -148,9 +161,10 @@ function classifyText(text) {
148
161
  features.design,
149
162
  features.reason,
150
163
  features.debug,
151
- features.safety
164
+ features.safety,
165
+ features.multi_part
152
166
  ].filter(Boolean).length;
153
- const confidence = category === "factual_recall" && words < 16 ? 0.9 : Math.min(0.95, 0.55 + 0.12 * signals);
167
+ const confidence = category === "factual_recall" && words < 16 ? 0.92 : Math.min(0.96, 0.55 + 0.1 * signals);
154
168
  return {
155
169
  complexity,
156
170
  category,
@@ -166,47 +180,64 @@ function classifyText(text) {
166
180
  };
167
181
  }
168
182
  function categoryOf(h) {
169
- if (h.safety) return "safety_sensitive";
170
183
  if (h.design) return "system_design";
184
+ if (h.safety && !h.design) return "safety_sensitive";
171
185
  if (h.debug && (h.code_fence || h.code_kw)) return "code_debug";
172
- if (h.code_fence || h.code_kw) return "code_generation";
186
+ if (h.debug && h.long_form) return "code_debug";
173
187
  if (h.math && h.reason) return "reasoning";
174
- if (h.math) return "math";
175
188
  if (h.reason) return "reasoning";
189
+ if (h.code_fence || h.code_kw) return "code_generation";
190
+ if (h.math) return "math";
191
+ if (h.safety) return "safety_sensitive";
176
192
  if (h.translate) return "translation";
177
193
  if (h.summarize) return "summarization";
178
194
  if (h.creative) return "creative";
179
- if (h.analysis) return "analysis";
180
- if (h.words < 24 && h.question_marks >= 1) return "factual_recall";
195
+ if (h.analysis || h.multi_part) return "analysis";
196
+ if (h.words < 28 && h.question_marks >= 1) return "factual_recall";
181
197
  return h.words > 80 ? "analysis" : "factual_recall";
182
198
  }
183
199
  function complexityOf(h, category) {
184
200
  let score = 1;
185
- if (h.words > 40) score += 1;
186
- if (h.words > 120) score += 1;
187
- if (h.lines > 12 || h.code_fence) score += 1;
201
+ if (h.words > 35) score += 1;
202
+ if (h.words > 90) score += 1;
203
+ if (h.lines > 10 || h.code_fence) score += 1;
188
204
  if (h.constraints >= 2) score += 1;
189
- if (h.design || h.reason) score += 1;
205
+ if (h.constraints >= 4 || h.multi_part) score += 1;
206
+ if (h.design) score += 1;
207
+ if (h.reason) score += 1;
190
208
  if (h.debug) score += 1;
191
- if (["system_design", "reasoning", "safety_sensitive"].includes(category)) score = Math.max(score + 1, 4);
192
- if (category === "code_generation") score = Math.max(score, 3);
209
+ if (h.safety && (h.design || h.multi_part)) score += 1;
210
+ if (category === "system_design") score = Math.max(score, 4);
211
+ if (category === "reasoning") score = Math.max(score, 4);
212
+ if (category === "safety_sensitive") score = Math.max(score, 4);
193
213
  if (category === "code_debug") score = Math.max(score, 4);
194
- if (category === "factual_recall" && h.words < 20) score = Math.min(score, 2);
214
+ if (category === "code_generation") score = Math.max(score, 3);
215
+ if (category === "analysis" && (h.multi_part || h.long_form)) score = Math.max(score, 3);
216
+ if (h.multi_part && (h.design || h.safety || h.analysis)) score = Math.max(score, 4);
217
+ if (category === "factual_recall" && h.words < 28 && !h.design && !h.reason && !h.debug) {
218
+ score = Math.min(score, 2);
219
+ }
220
+ if (category === "math" && h.words < 24 && !h.reason) {
221
+ score = Math.min(Math.max(score, 1), 2);
222
+ }
195
223
  return Math.max(1, Math.min(5, score));
196
224
  }
197
225
  function pSmallQuality(h, category, complexity) {
198
226
  let p = 0.96;
199
- if (h.design || category === "system_design") p -= 0.28;
200
- if (h.safety || category === "safety_sensitive") p -= 0.3;
201
- if (h.debug || category === "code_debug") p -= 0.22;
202
- if (h.reason || category === "reasoning") p -= 0.18;
203
- if (complexity >= 5) p -= 0.22;
204
- else if (complexity >= 4) p -= 0.14;
205
- else if (complexity === 3) p -= 0.06;
206
- if (h.words > 120) p -= 0.07;
207
- if (h.constraints >= 2) p -= 0.07;
208
- if (category === "code_generation") p -= 0.05;
209
- if (category === "factual_recall" && h.words < 24) p = Math.max(p, 0.94);
227
+ if (h.design || category === "system_design") p -= 0.3;
228
+ if (h.safety || category === "safety_sensitive") p -= 0.32;
229
+ if (h.debug || category === "code_debug") p -= 0.24;
230
+ if (h.reason || category === "reasoning") p -= 0.22;
231
+ if (h.multi_part) p -= 0.1;
232
+ if (complexity >= 5) p -= 0.24;
233
+ else if (complexity >= 4) p -= 0.16;
234
+ else if (complexity === 3) p -= 0.07;
235
+ if (h.words > 100) p -= 0.07;
236
+ if (h.constraints >= 3) p -= 0.08;
237
+ if (category === "code_generation") p -= 0.06;
238
+ if (category === "analysis" && complexity >= 3) p -= 0.08;
239
+ if (category === "factual_recall" && h.words < 28) p = Math.max(p, 0.94);
240
+ if (category === "math" && h.words < 24 && !h.reason) p = Math.max(p, 0.9);
210
241
  return Number(Math.min(0.99, Math.max(0.05, p)).toFixed(3));
211
242
  }
212
243
  function riskOf(category, complexity, p) {
@@ -219,6 +250,21 @@ function tierFromP(p) {
219
250
  if (p >= 0.72) return "standard";
220
251
  return "frontier";
221
252
  }
253
+ function recommendTier(p, complexity, category, risk) {
254
+ const fromP = tierFromP(p);
255
+ const fromComplexity = difficultyTier(complexity);
256
+ let tier = maxTier(fromP, fromComplexity);
257
+ if (risk === "high") tier = maxTier(tier, "standard");
258
+ if (["system_design", "reasoning", "safety_sensitive"].includes(category) && complexity >= 4) {
259
+ tier = maxTier(tier, complexity >= 5 ? "frontier" : "standard");
260
+ }
261
+ if (category === "system_design" && complexity >= 4) tier = maxTier(tier, "frontier");
262
+ if (category === "reasoning" && complexity >= 4) tier = maxTier(tier, "standard");
263
+ return tier;
264
+ }
265
+ function maxTier(a, b) {
266
+ return TIER_RANK[a] >= TIER_RANK[b] ? a : b;
267
+ }
222
268
  function difficultyTier(complexity) {
223
269
  if (complexity <= 2) return "economy";
224
270
  if (complexity === 3) return "standard";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "promptimizer",
3
- "version": "0.1.36",
3
+ "version": "0.1.37",
4
4
  "description": "OpenAI-compatible SDK for Promptimizer — BYOK model routing, classification, and prompt caching.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",