@wei840222/qmd 2026.9.27 → 2026.9.28

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,4 @@
1
1
  {
2
- "commit": "ffd0c56",
3
- "builtAt": "2026-09-27T09:25:17.053Z"
2
+ "commit": "65f6a34",
3
+ "builtAt": "2026-09-27T15:15:31.166Z"
4
4
  }
package/dist/hybrid.d.ts CHANGED
@@ -11,6 +11,8 @@ export declare class Hybrid implements LLM {
11
11
  get local(): LLM;
12
12
  get supportsExpand(): boolean;
13
13
  get supportsRerank(): boolean;
14
+ get rerankModelName(): string | undefined;
15
+ get generateModelName(): string | undefined;
14
16
  embed(text: string, options?: EmbedOptions): Promise<EmbeddingResult | null>;
15
17
  generate(prompt: string, options?: GenerateOptions): Promise<GenerateResult | null>;
16
18
  modelExists(model: string): Promise<ModelInfo>;
package/dist/hybrid.js CHANGED
@@ -22,6 +22,21 @@ export class Hybrid {
22
22
  get supportsRerank() {
23
23
  return Boolean(this.remoteJev?.supportsRerank || this.remoteLLM?.supportsRerank);
24
24
  }
25
+ get rerankModelName() {
26
+ if (this.remoteJev?.supportsRerank) {
27
+ return this.remoteJev.rerankModelName;
28
+ }
29
+ if (this.remoteLLM?.supportsRerank && this.remoteLLM.rerankModelName) {
30
+ return this.remoteLLM.rerankModelName;
31
+ }
32
+ return this.localLLM.rerankModelName;
33
+ }
34
+ get generateModelName() {
35
+ if (this.remoteLLM?.supportsExpand && this.remoteLLM.generateModelName) {
36
+ return this.remoteLLM.generateModelName;
37
+ }
38
+ return this.localLLM.generateModelName;
39
+ }
25
40
  async embed(text, options) {
26
41
  return this.localLLM.embed(text, options);
27
42
  }
package/dist/llm.d.ts CHANGED
@@ -114,6 +114,8 @@ export type GenerateOptions = {
114
114
  */
115
115
  export type RerankOptions = {
116
116
  model?: string;
117
+ timeZone?: string;
118
+ context?: string;
117
119
  };
118
120
  /**
119
121
  * Options for LLM sessions
@@ -1,6 +1,7 @@
1
1
  import { TypeSafeClient } from "@typesafe-ai/sdk";
2
2
  import type { RerankDocument, RerankOptions, RerankResult, SearchIntentGuidance } from "./llm.js";
3
3
  export declare const DEFAULT_JEV_TIMEOUT_MS = 30000;
4
+ export declare const DEFAULT_JEV_RERANK_BATCH_SIZE = 40;
4
5
  export interface JevSystemOneClient {
5
6
  systemOne(request: any, options?: any): Promise<any>;
6
7
  }
@@ -9,6 +10,7 @@ export interface RemoteJevOptions {
9
10
  baseUrl?: string;
10
11
  model?: string;
11
12
  concurrency?: number;
13
+ batchSize?: number;
12
14
  timeoutMs?: number;
13
15
  client?: TypeSafeClient | JevSystemOneClient;
14
16
  }
@@ -24,10 +26,12 @@ export interface JevIntentClassification {
24
26
  export declare class RemoteJev {
25
27
  readonly model: string;
26
28
  readonly concurrency: number;
29
+ readonly batchSize: number;
27
30
  readonly client: TypeSafeClient | JevSystemOneClient;
28
31
  constructor(options?: RemoteJevOptions);
29
32
  get supportsRerank(): boolean;
30
33
  get supportsExpand(): boolean;
34
+ get rerankModelName(): string;
31
35
  resetCircuitBreaker(): void;
32
36
  classifyIntent(query: string, options?: {
33
37
  context?: string;
@@ -1,6 +1,7 @@
1
1
  import { TypeSafeClient, choice, noul } from "@typesafe-ai/sdk";
2
2
  import { getFormattedLocalTime } from "./remote-llm.js";
3
3
  export const DEFAULT_JEV_TIMEOUT_MS = 30000;
4
+ export const DEFAULT_JEV_RERANK_BATCH_SIZE = 40;
4
5
  export const JEV_STRATEGY_PLAYBOOK = {
5
6
  code_search: {
6
7
  label: "Code Search",
@@ -30,10 +31,12 @@ export const JEV_STRATEGY_PLAYBOOK = {
30
31
  export class RemoteJev {
31
32
  model;
32
33
  concurrency;
34
+ batchSize;
33
35
  client;
34
36
  constructor(options = {}) {
35
37
  this.model = options.model?.trim() || "jev-1.13";
36
38
  this.concurrency = options.concurrency ?? 10;
39
+ this.batchSize = options.batchSize ?? DEFAULT_JEV_RERANK_BATCH_SIZE;
37
40
  if (options.client) {
38
41
  this.client = options.client;
39
42
  }
@@ -54,6 +57,9 @@ export class RemoteJev {
54
57
  get supportsExpand() {
55
58
  return true;
56
59
  }
60
+ get rerankModelName() {
61
+ return `jev:${this.model}`;
62
+ }
57
63
  resetCircuitBreaker() {
58
64
  // Kept for interface compatibility; error handling is handled per-request in caller/fallback
59
65
  }
@@ -92,38 +98,63 @@ export class RemoteJev {
92
98
  if (documents.length === 0) {
93
99
  return { results: [], model: `jev:${this.model}` };
94
100
  }
95
- const rerankQuestion = noul("Does this candidate document answer or address the search query?", {
101
+ const state = { query };
102
+ const timeZone = typeof options === "object" && options !== null ? options.timeZone : undefined;
103
+ state.current_time = getFormattedLocalTime(new Date(), timeZone);
104
+ if (typeof options === "object" && options !== null && options.context) {
105
+ state.context = options.context;
106
+ }
107
+ const criteria = {
96
108
  true: "The candidate directly addresses the query's specific question, requirement, or topic, satisfying any time or entity constraints.",
97
109
  false: "The candidate is only on a similar topic, outside the requested time window, or unrelated to the query's specific need.",
98
- });
99
- const results = await pMap(documents, async (doc, index) => {
100
- const text = typeof doc === "string" ? doc : doc.text;
101
- const file = typeof doc === "string" ? doc : doc.file;
102
- const candidate = truncateCandidateText(text, 1500);
103
- const state = { query, candidate };
104
- if (typeof doc !== "string" && doc.title) {
105
- state.title = doc.title;
106
- }
107
- if (typeof doc !== "string" && doc.file) {
108
- state.file = doc.file;
110
+ };
111
+ const batches = [];
112
+ for (let offset = 0; offset < documents.length; offset += this.batchSize) {
113
+ batches.push({ docs: documents.slice(offset, offset + this.batchSize), offset });
114
+ }
115
+ const batchResults = await pMap(batches, async ({ docs: batchDocs, offset }) => {
116
+ const questions = {};
117
+ for (let i = 0; i < batchDocs.length; i++) {
118
+ const doc = batchDocs[i];
119
+ const text = typeof doc === "string" ? doc : doc.text;
120
+ const candidate = truncateCandidateText(text, 1500);
121
+ const instructions = {
122
+ candidate,
123
+ question: "Does `candidate` directly answer or address the search query in `query`?",
124
+ };
125
+ if (typeof doc !== "string" && doc.title) {
126
+ instructions.title = doc.title;
127
+ }
128
+ if (typeof doc !== "string" && doc.file) {
129
+ instructions.file = doc.file;
130
+ }
131
+ questions[`cand_${i}`] = noul(instructions, criteria);
109
132
  }
110
- const timeZone = typeof options === "object" && options !== null ? options.timeZone : undefined;
111
- state.current_time = getFormattedLocalTime(new Date(), timeZone);
112
133
  const response = await this.client.systemOne({
113
134
  state,
114
135
  model: this.model,
115
- questions: { is_relevant: rerankQuestion },
136
+ questions,
116
137
  });
117
- return {
118
- file,
119
- score: response.answers.is_relevant.noul,
120
- index,
121
- };
138
+ const results = [];
139
+ for (let i = 0; i < batchDocs.length; i++) {
140
+ const doc = batchDocs[i];
141
+ const file = typeof doc === "string" ? doc : doc.file;
142
+ const key = `cand_${i}`;
143
+ const answer = response?.answers?.[key];
144
+ const score = typeof answer?.noul === "number" ? answer.noul : 0;
145
+ results.push({
146
+ file,
147
+ score,
148
+ index: offset + i,
149
+ });
150
+ }
151
+ return results;
122
152
  }, this.concurrency);
153
+ const flattened = batchResults.flat();
123
154
  // Sort by score descending
124
- results.sort((a, b) => b.score - a.score);
155
+ flattened.sort((a, b) => b.score - a.score);
125
156
  return {
126
- results,
157
+ results: flattened,
127
158
  model: `jev:${this.model}`,
128
159
  };
129
160
  }
@@ -33,6 +33,8 @@ export declare class RemoteLLM implements LLM {
33
33
  constructor(options: RemoteLLMOptions);
34
34
  get supportsExpand(): boolean;
35
35
  get supportsRerank(): boolean;
36
+ get rerankModelName(): string | undefined;
37
+ get generateModelName(): string | undefined;
36
38
  embed(_text: string, _options?: EmbedOptions): Promise<EmbeddingResult | null>;
37
39
  generate(_prompt: string, _options?: GenerateOptions): Promise<GenerateResult | null>;
38
40
  modelExists(_model: string): Promise<ModelInfo>;
@@ -112,6 +112,12 @@ export class RemoteLLM {
112
112
  get supportsRerank() {
113
113
  return Boolean(this.rerankApiUrl && this.rerankApiModel && !this.rerankCircuitBroken);
114
114
  }
115
+ get rerankModelName() {
116
+ return this.rerankApiModel;
117
+ }
118
+ get generateModelName() {
119
+ return this.generateApiModel;
120
+ }
115
121
  async embed(_text, _options) {
116
122
  // Embedding is handled separately by EmbeddingProvider
117
123
  return null;
package/dist/store.js CHANGED
@@ -2711,11 +2711,19 @@ export function createStore(dbPath, options = {}) {
2711
2711
  searchFTS: (query, limit, collectionName, filter) => searchFTS(db, query, limit, collectionName, filter),
2712
2712
  searchVec: (query, model, limit, collectionFilter, session, precomputedEmbedding, filter) => searchVec(db, query, model, limit, collectionFilter, session, precomputedEmbedding, store.embeddingProvider, store.authorizeRemoteRequest, store.llm, filter),
2713
2713
  // Query expansion & reranking
2714
- expandQuery: (query, model, expansionContext, options) => expandQuery(query, model ?? store.localLlm?.generateModelName ?? store.llm?.generateModelName ?? DEFAULT_QUERY_MODEL, db, expansionContext, store.llm, options),
2715
- invalidateExpansionCache: (query, expansionContext, options) => deleteExpansionCacheEntry(db, query, store.localLlm?.generateModelName ?? store.llm?.generateModelName ?? DEFAULT_QUERY_MODEL, expansionContext, options),
2714
+ expandQuery: (query, model, expansionContext, options) => {
2715
+ const activeGenerateModel = store.llm?.generateModelName ?? store.localLlm?.generateModelName ?? DEFAULT_QUERY_MODEL;
2716
+ return expandQuery(query, model ?? activeGenerateModel, db, expansionContext, store.llm, options);
2717
+ },
2718
+ invalidateExpansionCache: (query, expansionContext, options) => {
2719
+ const activeGenerateModel = store.llm?.generateModelName ?? store.localLlm?.generateModelName ?? DEFAULT_QUERY_MODEL;
2720
+ return deleteExpansionCacheEntry(db, query, activeGenerateModel, expansionContext, options);
2721
+ },
2716
2722
  rerank: (query, documents, model, rerankContext) => {
2717
2723
  const llm = getLlm(store);
2718
- return rerank(query, documents, model ?? store.localLlm?.rerankModelName ?? llm?.rerankModelName ?? DEFAULT_RERANK_MODEL, db, rerankContext, store.llm ?? llm);
2724
+ const activeLlm = store.llm ?? llm;
2725
+ const activeRerankModel = activeLlm?.rerankModelName ?? store.localLlm?.rerankModelName ?? llm?.rerankModelName ?? DEFAULT_RERANK_MODEL;
2726
+ return rerank(query, documents, model ?? activeRerankModel, db, rerankContext, activeLlm);
2719
2727
  },
2720
2728
  // Document retrieval
2721
2729
  findDocument: (filename, options) => findDocument(db, filename, options),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wei840222/qmd",
3
- "version": "2026.9.27",
3
+ "version": "2026.9.28",
4
4
  "packageManager": "pnpm@11.15.1",
5
5
  "description": "Query Markup Documents - On-device hybrid search for markdown files with BM25, vector search, and LLM reranking",
6
6
  "type": "module",