archgraph-argo 0.20.2 → 0.20.3

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.
@@ -240,12 +240,12 @@ async function executeWpP2Retrieval({
240
240
  const lexicalTopK = hybridTopK();
241
241
  const fusionK = rrfK();
242
242
  const fusionWeights = hybridWeights();
243
- const rerank = isRerankEnabled();
243
+ const rerank = isRerankEnabled() && request.rerank !== false;
244
244
  const rerankOptions = rerankConfig();
245
245
  const rerankProvider = rerank ? resolveRerankProvider(configurationEvidence.configuration) : null;
246
246
  // Rerank needs a larger candidate pool than the final top-K.
247
247
  const pool = rerank ? Math.max(topK, rerankOptions.poolSize) : topK;
248
- const seedsByType = {};
248
+ const channelSeeds = [];
249
249
  for (const channel of CHANNELS) {
250
250
  const vectorSeeds = await exhaustChannel({
251
251
  channel,
@@ -266,17 +266,27 @@ async function executeWpP2Retrieval({
266
266
  });
267
267
  seeds = fuseChannelSeeds({ vectorSeeds, lexicalSeeds, k: fusionK, limit: Math.max(pool, lexicalTopK), weights: fusionWeights });
268
268
  }
269
- if (rerank && seeds.length > 1) {
270
- const ordered = await rerankCandidates({
271
- query: request.intent,
272
- candidates: seeds,
273
- provider: rerankProvider,
274
- transport: composition.transport,
275
- maxReturn: rerankOptions.maxReturn,
276
- });
277
- // fail-open: a null/empty order keeps the original ordering
278
- seeds = applyRerankOrder(seeds, ordered, topK);
279
- }
269
+ channelSeeds.push({ channel, seeds });
270
+ }
271
+ if (rerank) {
272
+ // Rerank every channel CONCURRENTLY: the LLM calls dominate latency and are
273
+ // independent, so parallelizing turns the cost from sum(channels) into
274
+ // ~one call. fail-open: a null/empty order keeps the original ordering.
275
+ const rerankedSeeds = await Promise.all(channelSeeds.map(({ seeds }) => (
276
+ seeds.length > 1
277
+ ? rerankCandidates({
278
+ query: request.intent,
279
+ candidates: seeds,
280
+ provider: rerankProvider,
281
+ transport: composition.transport,
282
+ maxReturn: rerankOptions.maxReturn,
283
+ }).then(ordered => applyRerankOrder(seeds, ordered, topK))
284
+ : seeds
285
+ )));
286
+ channelSeeds.forEach((entry, index) => { entry.seeds = rerankedSeeds[index]; });
287
+ }
288
+ const seedsByType = {};
289
+ for (const { channel, seeds } of channelSeeds) {
280
290
  seedsByType[channel.key] = seeds;
281
291
  }
282
292
  return completeSemanticResult({
@@ -6,9 +6,18 @@
6
6
  // the caller keeps the original ordering. Pure helpers are exported for tests.
7
7
 
8
8
  const DEFAULT_RERANK_MODEL = 'qwen-turbo';
9
+ // Candidate pool size. The pool is the recall CEILING: a target the seed stage
10
+ // ranks outside the pool can never be recovered by rerank, so it must NOT be
11
+ // shrunk for speed. Keep the original 20. With DeepSeek thinking disabled a
12
+ // single rerank call is ~flat across candidate counts (8-40 all ~1s), so a
13
+ // larger pool costs ~nothing -- never trade recall for latency here.
9
14
  const DEFAULT_RERANK_POOL = 20;
10
15
  const DEFAULT_RERANK_RETURN = 8;
11
- const DEFAULT_RERANK_TIMEOUT_MS = 8000;
16
+ // Per-call timeout. Channel reranks run concurrently, so the end-to-end rerank
17
+ // cost is ~one timeout, not N. 3.5s keeps the whole semantic query under ~5s
18
+ // while still letting typical calls (1-4s) complete; slower calls fail open to
19
+ // the pre-rerank (fused) order. Overridable via ARGO_SEMANTIC_RERANK_TIMEOUT_MS.
20
+ const DEFAULT_RERANK_TIMEOUT_MS = 3500;
12
21
 
13
22
  function rerankTimeoutMs(env = process.env) {
14
23
  const value = Number(env && env.ARGO_SEMANTIC_RERANK_TIMEOUT_MS);
@@ -110,6 +119,11 @@ async function rerankCandidates({ query, candidates, provider, transport, maxRet
110
119
  model,
111
120
  temperature: 0,
112
121
  response_format: { type: 'json_object' },
122
+ // DeepSeek's default is a "thinking" model: for a listwise ranking it spends
123
+ // 5k-10k reasoning tokens per call (measured 6-12s, highly variable) with no
124
+ // accuracy gain. Disable thinking for a fast, deterministic rerank
125
+ // (deepseek-flash: ~6s -> ~0.7s, reasoning tokens -> 0).
126
+ ...(provider.provider === 'deepseek' ? { thinking: { type: 'disabled' } } : {}),
113
127
  messages: [
114
128
  { role: 'system', content: 'You rank architecture elements by relevance to a query. Return ONLY JSON {"order":[ids best-first]} using only the candidate ids.' },
115
129
  { role: 'user', content: `Query: ${query}\n\nCandidates (id\\ttext):\n${list.map(candidate => `${candidate.id}\t${candidateText(candidate)}`).join('\n')}\n\nReturn up to ${Math.min(maxReturn || DEFAULT_RERANK_RETURN, list.length)} ids best-first.` },
@@ -2801,7 +2801,7 @@ async function buildSemanticDedupAdvisory(context, mutations, dependencies) {
2801
2801
  const intent = [element.type, element.name, element.description]
2802
2802
  .filter(part => typeof part === 'string' && part.trim() !== '')
2803
2803
  .join(' ');
2804
- const retrieved = await journey.query({ purpose: 'general', intent, topK: SEMANTIC_DEDUP_TOP_K });
2804
+ const retrieved = await journey.query({ purpose: 'general', intent, topK: SEMANTIC_DEDUP_TOP_K, rerank: false });
2805
2805
  const source = retrieved && (retrieved.result || retrieved.document) || retrieved;
2806
2806
  const subset = buildCanonicalSemanticDocumentSubset(source, context.document);
2807
2807
  const elements = subset && subset.status === 'passed' && subset.document
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "archgraph-argo",
3
- "version": "0.20.2",
3
+ "version": "0.20.3",
4
4
  "description": "Deploy the ArchGraph ARGO toolchain, skills, and rules (schema, scripts, argo-init skill, global rule) with one command.",
5
5
  "license": "MIT",
6
6
  "bin": {