mdcontext 0.1.0 → 0.2.0
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.
- package/.changeset/config.json +9 -9
- package/.claude/settings.local.json +25 -0
- package/.github/workflows/claude-code-review.yml +44 -0
- package/.github/workflows/claude.yml +85 -0
- package/CONTRIBUTING.md +186 -0
- package/NOTES/NOTES +44 -0
- package/README.md +206 -3
- package/biome.json +1 -1
- package/dist/chunk-23UPXDNL.js +3044 -0
- package/dist/chunk-2W7MO2DL.js +1366 -0
- package/dist/chunk-3NUAZGMA.js +1689 -0
- package/dist/chunk-7TOWB2XB.js +366 -0
- package/dist/chunk-7XOTOADQ.js +3065 -0
- package/dist/chunk-AH2PDM2K.js +3042 -0
- package/dist/chunk-BNXWSZ63.js +3742 -0
- package/dist/chunk-BTL5DJVU.js +3222 -0
- package/dist/chunk-HDHYG7E4.js +104 -0
- package/dist/chunk-HLR4KZBP.js +3234 -0
- package/dist/chunk-IP3FRFEB.js +1045 -0
- package/dist/chunk-KHU56VDO.js +3042 -0
- package/dist/chunk-KRYIFLQR.js +85 -89
- package/dist/chunk-LBSDNLEM.js +287 -0
- package/dist/chunk-MNTQ7HCP.js +2643 -0
- package/dist/chunk-MUJELQQ6.js +1387 -0
- package/dist/chunk-MXJGMSLV.js +2199 -0
- package/dist/chunk-N6QJGC3Z.js +2636 -0
- package/dist/chunk-OBELGBPM.js +1713 -0
- package/dist/chunk-OT7R5XTA.js +3192 -0
- package/dist/chunk-P7X4RA2T.js +106 -0
- package/dist/chunk-PIDUQNC2.js +3185 -0
- package/dist/chunk-POGCDIH4.js +3187 -0
- package/dist/chunk-PSIEOQGZ.js +3043 -0
- package/dist/chunk-PVRT3IHA.js +3238 -0
- package/dist/chunk-QNN4TT23.js +1430 -0
- package/dist/chunk-RE3R45RJ.js +3042 -0
- package/dist/chunk-S7E6TFX6.js +718 -657
- package/dist/chunk-SG6GLU4U.js +1378 -0
- package/dist/chunk-SJCDV2ST.js +274 -0
- package/dist/chunk-SYE5XLF3.js +104 -0
- package/dist/chunk-T5VLYBZD.js +103 -0
- package/dist/chunk-TOQB7VWU.js +3238 -0
- package/dist/chunk-VFNMZ4ZQ.js +3228 -0
- package/dist/chunk-VVTGZNBT.js +1533 -1423
- package/dist/chunk-W7Q4RFEV.js +104 -0
- package/dist/chunk-XTYYVRLO.js +3190 -0
- package/dist/chunk-Y6MDYVJD.js +3063 -0
- package/dist/cli/main.js +4072 -629
- package/dist/index.d.ts +420 -33
- package/dist/index.js +8 -15
- package/dist/mcp/server.js +103 -7
- package/dist/schema-BAWSG7KY.js +22 -0
- package/dist/schema-E3QUPL26.js +20 -0
- package/dist/schema-EHL7WUT6.js +20 -0
- package/docs/019-USAGE.md +44 -5
- package/docs/020-current-implementation.md +8 -8
- package/docs/021-DOGFOODING-FINDINGS.md +1 -1
- package/docs/CONFIG.md +1123 -0
- package/docs/ERRORS.md +383 -0
- package/docs/summarization.md +320 -0
- package/justfile +40 -0
- package/package.json +39 -33
- package/research/INDEX.md +315 -0
- package/research/code-review/README.md +90 -0
- package/research/code-review/cli-error-handling-review.md +979 -0
- package/research/code-review/code-review-validation-report.md +464 -0
- package/research/code-review/main-ts-review.md +1128 -0
- package/research/config-docs/SUMMARY.md +357 -0
- package/research/config-docs/TEST-RESULTS.md +776 -0
- package/research/config-docs/TODO.md +542 -0
- package/research/config-docs/analysis.md +744 -0
- package/research/config-docs/fix-validation.md +502 -0
- package/research/config-docs/help-audit.md +264 -0
- package/research/config-docs/help-system-analysis.md +890 -0
- package/research/frontmatter/COMMENTS-ARE-SKIPPED.md +149 -0
- package/research/frontmatter/LLM-CODE-NAVIGATION.md +276 -0
- package/research/issue-review.md +603 -0
- package/research/llm-summarization/agent-cli-tools-2026.md +1082 -0
- package/research/llm-summarization/alternative-providers-2026.md +1428 -0
- package/research/llm-summarization/anthropic-2026.md +367 -0
- package/research/llm-summarization/claude-cli-integration.md +1706 -0
- package/research/llm-summarization/cli-integration-patterns.md +3155 -0
- package/research/llm-summarization/openai-2026.md +473 -0
- package/research/llm-summarization/openai-compatible-providers-2026.md +1022 -0
- package/research/llm-summarization/opencode-cli-integration.md +1552 -0
- package/research/llm-summarization/prompt-engineering-2026.md +1426 -0
- package/research/llm-summarization/prototype-results.md +56 -0
- package/research/llm-summarization/provider-switching-patterns-2026.md +2153 -0
- package/research/llm-summarization/typescript-llm-libraries-2026.md +2436 -0
- package/research/mdcontext-pudding/00-EXECUTIVE-SUMMARY.md +282 -0
- package/research/mdcontext-pudding/01-index-embed.md +956 -0
- package/research/mdcontext-pudding/02-search-COMMANDS.md +142 -0
- package/research/mdcontext-pudding/02-search-SUMMARY.md +146 -0
- package/research/mdcontext-pudding/02-search.md +970 -0
- package/research/mdcontext-pudding/03-context.md +779 -0
- package/research/mdcontext-pudding/04-navigation-and-analytics.md +803 -0
- package/research/mdcontext-pudding/04-tree.md +704 -0
- package/research/mdcontext-pudding/05-config.md +1038 -0
- package/research/mdcontext-pudding/06-links-summary.txt +87 -0
- package/research/mdcontext-pudding/06-links.md +679 -0
- package/research/mdcontext-pudding/07-stats.md +693 -0
- package/research/mdcontext-pudding/BUG-FIX-PLAN.md +388 -0
- package/research/mdcontext-pudding/P0-BUG-VALIDATION.md +167 -0
- package/research/mdcontext-pudding/README.md +168 -0
- package/research/mdcontext-pudding/TESTING-SUMMARY.md +128 -0
- package/research/research-quality-review.md +834 -0
- package/research/semantic-search/embedding-text-analysis.md +156 -0
- package/research/semantic-search/multi-word-failure-reproduction.md +171 -0
- package/research/semantic-search/query-processing-analysis.md +207 -0
- package/research/semantic-search/root-cause-and-solution.md +114 -0
- package/research/semantic-search/threshold-validation-report.md +69 -0
- package/research/semantic-search/vector-search-analysis.md +63 -0
- package/research/test-path-issues.md +276 -0
- package/review/ALP-76/1-error-type-design.md +962 -0
- package/review/ALP-76/2-error-handling-patterns.md +906 -0
- package/review/ALP-76/3-error-presentation.md +624 -0
- package/review/ALP-76/4-test-coverage.md +625 -0
- package/review/ALP-76/5-migration-completeness.md +440 -0
- package/review/ALP-76/6-effect-best-practices.md +755 -0
- package/scripts/apply-branch-protection.sh +47 -0
- package/scripts/branch-protection-templates.json +79 -0
- package/scripts/prototype-summarization.ts +346 -0
- package/scripts/rebuild-hnswlib.js +32 -37
- package/scripts/setup-branch-protection.sh +64 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/active-provider.json +7 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/bm25.json +541 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/bm25.meta.json +5 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/config.json +8 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/embeddings/openai_text-embedding-3-small_512/vectors.bin +0 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/embeddings/openai_text-embedding-3-small_512/vectors.meta.bin +0 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/indexes/documents.json +60 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/indexes/links.json +13 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/indexes/sections.json +1197 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/configuration-management.md +99 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/distributed-systems.md +92 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/error-handling.md +78 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/failure-automation.md +55 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/job-context.md +69 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/process-orchestration.md +99 -0
- package/src/cli/argv-preprocessor.test.ts +2 -2
- package/src/cli/cli.test.ts +230 -33
- package/src/cli/commands/config-cmd.ts +642 -0
- package/src/cli/commands/context.ts +97 -9
- package/src/cli/commands/duplicates.ts +122 -0
- package/src/cli/commands/embeddings.ts +529 -0
- package/src/cli/commands/index-cmd.ts +210 -30
- package/src/cli/commands/index.ts +3 -0
- package/src/cli/commands/search.ts +894 -64
- package/src/cli/commands/stats.ts +3 -0
- package/src/cli/commands/tree.ts +26 -5
- package/src/cli/config-layer.ts +176 -0
- package/src/cli/error-handler.test.ts +235 -0
- package/src/cli/error-handler.ts +655 -0
- package/src/cli/flag-schemas.ts +66 -0
- package/src/cli/help.ts +209 -7
- package/src/cli/main.ts +348 -58
- package/src/cli/options.ts +10 -0
- package/src/cli/shared-error-handling.ts +199 -0
- package/src/cli/utils.ts +150 -17
- package/src/config/file-provider.test.ts +320 -0
- package/src/config/file-provider.ts +273 -0
- package/src/config/index.ts +72 -0
- package/src/config/integration.test.ts +667 -0
- package/src/config/precedence.test.ts +277 -0
- package/src/config/precedence.ts +451 -0
- package/src/config/schema.test.ts +414 -0
- package/src/config/schema.ts +603 -0
- package/src/config/service.test.ts +320 -0
- package/src/config/service.ts +243 -0
- package/src/config/testing.test.ts +264 -0
- package/src/config/testing.ts +110 -0
- package/src/core/types.ts +6 -33
- package/src/duplicates/detector.test.ts +183 -0
- package/src/duplicates/detector.ts +414 -0
- package/src/duplicates/index.ts +18 -0
- package/src/embeddings/embedding-namespace.test.ts +300 -0
- package/src/embeddings/embedding-namespace.ts +947 -0
- package/src/embeddings/heading-boost.test.ts +222 -0
- package/src/embeddings/hnsw-build-options.test.ts +198 -0
- package/src/embeddings/hyde.test.ts +272 -0
- package/src/embeddings/hyde.ts +264 -0
- package/src/embeddings/index.ts +2 -0
- package/src/embeddings/openai-provider.ts +332 -83
- package/src/embeddings/pricing.json +22 -0
- package/src/embeddings/provider-constants.ts +204 -0
- package/src/embeddings/provider-errors.test.ts +967 -0
- package/src/embeddings/provider-errors.ts +565 -0
- package/src/embeddings/provider-factory.test.ts +240 -0
- package/src/embeddings/provider-factory.ts +225 -0
- package/src/embeddings/provider-integration.test.ts +788 -0
- package/src/embeddings/query-preprocessing.test.ts +187 -0
- package/src/embeddings/semantic-search-threshold.test.ts +508 -0
- package/src/embeddings/semantic-search.ts +780 -93
- package/src/embeddings/types.ts +293 -16
- package/src/embeddings/vector-store.ts +486 -77
- package/src/embeddings/voyage-provider.ts +313 -0
- package/src/errors/errors.test.ts +845 -0
- package/src/errors/index.ts +533 -0
- package/src/index/ignore-patterns.test.ts +354 -0
- package/src/index/ignore-patterns.ts +305 -0
- package/src/index/indexer.ts +286 -48
- package/src/index/storage.ts +94 -30
- package/src/index/types.ts +40 -2
- package/src/index/watcher.ts +67 -9
- package/src/index.ts +22 -0
- package/src/integration/search-keyword.test.ts +678 -0
- package/src/mcp/server.ts +135 -6
- package/src/parser/parser.ts +18 -19
- package/src/parser/section-filter.test.ts +277 -0
- package/src/parser/section-filter.ts +125 -3
- package/src/search/__tests__/hybrid-search.test.ts +650 -0
- package/src/search/bm25-store.ts +366 -0
- package/src/search/cross-encoder.test.ts +253 -0
- package/src/search/cross-encoder.ts +406 -0
- package/src/search/fuzzy-search.test.ts +419 -0
- package/src/search/fuzzy-search.ts +273 -0
- package/src/search/hybrid-search.ts +448 -0
- package/src/search/path-matcher.test.ts +276 -0
- package/src/search/path-matcher.ts +33 -0
- package/src/search/searcher.test.ts +99 -1
- package/src/search/searcher.ts +189 -67
- package/src/search/wink-bm25.d.ts +30 -0
- package/src/summarization/cli-providers/claude.ts +202 -0
- package/src/summarization/cli-providers/detection.test.ts +273 -0
- package/src/summarization/cli-providers/detection.ts +118 -0
- package/src/summarization/cli-providers/index.ts +8 -0
- package/src/summarization/cost.test.ts +139 -0
- package/src/summarization/cost.ts +102 -0
- package/src/summarization/error-handler.test.ts +127 -0
- package/src/summarization/error-handler.ts +111 -0
- package/src/summarization/index.ts +102 -0
- package/src/summarization/pipeline.test.ts +498 -0
- package/src/summarization/pipeline.ts +231 -0
- package/src/summarization/prompts.test.ts +269 -0
- package/src/summarization/prompts.ts +133 -0
- package/src/summarization/provider-factory.test.ts +396 -0
- package/src/summarization/provider-factory.ts +178 -0
- package/src/summarization/types.ts +184 -0
- package/src/summarize/summarizer.ts +104 -35
- package/src/types/huggingface-transformers.d.ts +66 -0
- package/tests/fixtures/cli/.mdcontext/active-provider.json +7 -0
- package/tests/fixtures/cli/.mdcontext/embeddings/openai_text-embedding-3-small_512/vectors.bin +0 -0
- package/tests/fixtures/cli/.mdcontext/embeddings/openai_text-embedding-3-small_512/vectors.meta.bin +0 -0
- package/tests/fixtures/cli/.mdcontext/indexes/documents.json +4 -4
- package/tests/fixtures/cli/.mdcontext/indexes/sections.json +14 -0
- package/tests/integration/embed-index.test.ts +712 -0
- package/tests/integration/search-context.test.ts +469 -0
- package/tests/integration/search-semantic.test.ts +522 -0
- package/vitest.config.ts +1 -6
- package/AGENTS.md +0 -46
- package/tests/fixtures/cli/.mdcontext/vectors.bin +0 -0
- package/tests/fixtures/cli/.mdcontext/vectors.meta.json +0 -1264
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HyDE (Hypothetical Document Embeddings) Query Expansion
|
|
3
|
+
*
|
|
4
|
+
* HyDE improves retrieval by generating a hypothetical document that would
|
|
5
|
+
* answer the query, then searching using that document's embedding instead
|
|
6
|
+
* of the raw query embedding.
|
|
7
|
+
*
|
|
8
|
+
* This bridges the semantic gap between short questions and detailed documents,
|
|
9
|
+
* providing 10-30% improvement on ambiguous or complex queries.
|
|
10
|
+
*
|
|
11
|
+
* Paper: "Precise Zero-Shot Dense Retrieval without Relevance Labels"
|
|
12
|
+
* https://arxiv.org/abs/2212.10496
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { Effect, Redacted } from 'effect'
|
|
16
|
+
import OpenAI from 'openai'
|
|
17
|
+
import { ApiKeyMissingError, EmbeddingError } from '../errors/index.js'
|
|
18
|
+
|
|
19
|
+
// ============================================================================
|
|
20
|
+
// Types
|
|
21
|
+
// ============================================================================
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Configuration for HyDE query expansion.
|
|
25
|
+
*/
|
|
26
|
+
export interface HydeOptions {
|
|
27
|
+
/**
|
|
28
|
+
* OpenAI API key. Can be a plain string or Redacted<string>.
|
|
29
|
+
* Falls back to OPENAI_API_KEY env var if not provided.
|
|
30
|
+
*/
|
|
31
|
+
readonly apiKey?: string | Redacted.Redacted<string> | undefined
|
|
32
|
+
/** Model to use for hypothetical document generation. Default: gpt-4o-mini */
|
|
33
|
+
readonly model?: string | undefined
|
|
34
|
+
/** Maximum tokens for the generated document. Default: 256 */
|
|
35
|
+
readonly maxTokens?: number | undefined
|
|
36
|
+
/** Temperature for generation. Lower = more focused. Default: 0.3 */
|
|
37
|
+
readonly temperature?: number | undefined
|
|
38
|
+
/** Custom system prompt for document generation */
|
|
39
|
+
readonly systemPrompt?: string | undefined
|
|
40
|
+
/** Base URL for OpenAI-compatible API (for local models) */
|
|
41
|
+
readonly baseURL?: string | undefined
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Result from HyDE query expansion.
|
|
46
|
+
*/
|
|
47
|
+
export interface HydeResult {
|
|
48
|
+
/** The generated hypothetical document */
|
|
49
|
+
readonly hypotheticalDocument: string
|
|
50
|
+
/** The original query for reference */
|
|
51
|
+
readonly originalQuery: string
|
|
52
|
+
/** Model used for generation */
|
|
53
|
+
readonly model: string
|
|
54
|
+
/** Tokens used for generation */
|
|
55
|
+
readonly tokensUsed: number
|
|
56
|
+
/** Estimated cost of the LLM call */
|
|
57
|
+
readonly cost: number
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ============================================================================
|
|
61
|
+
// Constants
|
|
62
|
+
// ============================================================================
|
|
63
|
+
|
|
64
|
+
const DEFAULT_MODEL = 'gpt-4o-mini'
|
|
65
|
+
const DEFAULT_MAX_TOKENS = 256
|
|
66
|
+
const DEFAULT_TEMPERATURE = 0.3
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Default system prompt for generating hypothetical documents.
|
|
70
|
+
* Designed to produce concise, factual content that matches documentation style.
|
|
71
|
+
*/
|
|
72
|
+
const DEFAULT_SYSTEM_PROMPT = `You are a technical documentation assistant. Given a user's question, write a short, factual passage that would appear in documentation answering this question.
|
|
73
|
+
|
|
74
|
+
Guidelines:
|
|
75
|
+
- Write 2-4 concise paragraphs
|
|
76
|
+
- Use technical but accessible language
|
|
77
|
+
- Include specific details, code examples, or configuration options where relevant
|
|
78
|
+
- Focus on directly answering the question
|
|
79
|
+
- Do not include greetings, preambles, or meta-commentary
|
|
80
|
+
- Write as if this is an excerpt from existing documentation`
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Pricing data for LLM models (per 1M tokens).
|
|
84
|
+
*/
|
|
85
|
+
const LLM_PRICING: Record<string, { input: number; output: number }> = {
|
|
86
|
+
'gpt-4o-mini': { input: 0.15, output: 0.6 },
|
|
87
|
+
'gpt-4o': { input: 2.5, output: 10 },
|
|
88
|
+
'gpt-4-turbo': { input: 10, output: 30 },
|
|
89
|
+
'gpt-3.5-turbo': { input: 0.5, output: 1.5 },
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ============================================================================
|
|
93
|
+
// HyDE Implementation
|
|
94
|
+
// ============================================================================
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Generate a hypothetical document that would answer the query.
|
|
98
|
+
*
|
|
99
|
+
* This is the core of HyDE - we ask an LLM to write what documentation
|
|
100
|
+
* answering this query would look like. The resulting text is then
|
|
101
|
+
* embedded and used for similarity search.
|
|
102
|
+
*
|
|
103
|
+
* @param query - The user's search query
|
|
104
|
+
* @param options - HyDE configuration options
|
|
105
|
+
* @returns The generated hypothetical document
|
|
106
|
+
*
|
|
107
|
+
* @throws ApiKeyMissingError - When OPENAI_API_KEY is not set
|
|
108
|
+
* @throws EmbeddingError - When LLM call fails (reusing error type for consistency)
|
|
109
|
+
*/
|
|
110
|
+
export const generateHypotheticalDocument = (
|
|
111
|
+
query: string,
|
|
112
|
+
options: HydeOptions = {},
|
|
113
|
+
): Effect.Effect<HydeResult, ApiKeyMissingError | EmbeddingError> =>
|
|
114
|
+
Effect.gen(function* () {
|
|
115
|
+
// Get API key - resolve from options or environment, normalize to Redacted
|
|
116
|
+
const rawApiKey = options.apiKey ?? process.env.OPENAI_API_KEY
|
|
117
|
+
if (!rawApiKey) {
|
|
118
|
+
return yield* Effect.fail(
|
|
119
|
+
new ApiKeyMissingError({
|
|
120
|
+
provider: 'OpenAI',
|
|
121
|
+
envVar: 'OPENAI_API_KEY',
|
|
122
|
+
}),
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Wrap in Redacted if it's a plain string
|
|
127
|
+
const redactedApiKey = Redacted.isRedacted(rawApiKey)
|
|
128
|
+
? rawApiKey
|
|
129
|
+
: Redacted.make(rawApiKey)
|
|
130
|
+
|
|
131
|
+
const client = new OpenAI({
|
|
132
|
+
apiKey: Redacted.value(redactedApiKey), // Only expose when creating client
|
|
133
|
+
baseURL: options.baseURL,
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
const model = options.model ?? DEFAULT_MODEL
|
|
137
|
+
const maxTokens = options.maxTokens ?? DEFAULT_MAX_TOKENS
|
|
138
|
+
const temperature = options.temperature ?? DEFAULT_TEMPERATURE
|
|
139
|
+
const systemPrompt = options.systemPrompt ?? DEFAULT_SYSTEM_PROMPT
|
|
140
|
+
|
|
141
|
+
// Generate hypothetical document
|
|
142
|
+
const response = yield* Effect.tryPromise({
|
|
143
|
+
try: async () =>
|
|
144
|
+
client.chat.completions.create({
|
|
145
|
+
model,
|
|
146
|
+
messages: [
|
|
147
|
+
{ role: 'system', content: systemPrompt },
|
|
148
|
+
{ role: 'user', content: query },
|
|
149
|
+
],
|
|
150
|
+
max_tokens: maxTokens,
|
|
151
|
+
temperature,
|
|
152
|
+
}),
|
|
153
|
+
catch: (error) =>
|
|
154
|
+
new EmbeddingError({
|
|
155
|
+
reason: classifyLLMError(error),
|
|
156
|
+
message: error instanceof Error ? error.message : String(error),
|
|
157
|
+
provider: 'openai',
|
|
158
|
+
cause: error,
|
|
159
|
+
}),
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
const content = response.choices[0]?.message?.content ?? ''
|
|
163
|
+
const inputTokens = response.usage?.prompt_tokens ?? 0
|
|
164
|
+
const outputTokens = response.usage?.completion_tokens ?? 0
|
|
165
|
+
const totalTokens = inputTokens + outputTokens
|
|
166
|
+
|
|
167
|
+
// Calculate cost
|
|
168
|
+
const pricing = LLM_PRICING[model] ?? LLM_PRICING['gpt-4o-mini']!
|
|
169
|
+
const cost =
|
|
170
|
+
(inputTokens / 1_000_000) * pricing.input +
|
|
171
|
+
(outputTokens / 1_000_000) * pricing.output
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
hypotheticalDocument: content,
|
|
175
|
+
originalQuery: query,
|
|
176
|
+
model,
|
|
177
|
+
tokensUsed: totalTokens,
|
|
178
|
+
cost,
|
|
179
|
+
}
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Classify an LLM error into a known category.
|
|
184
|
+
*/
|
|
185
|
+
const classifyLLMError = (
|
|
186
|
+
error: unknown,
|
|
187
|
+
): 'RateLimit' | 'QuotaExceeded' | 'Network' | 'ModelError' | 'Unknown' => {
|
|
188
|
+
if (error instanceof OpenAI.RateLimitError) {
|
|
189
|
+
return 'RateLimit'
|
|
190
|
+
}
|
|
191
|
+
if (error instanceof OpenAI.BadRequestError) {
|
|
192
|
+
const msg = (error.message || '').toLowerCase()
|
|
193
|
+
if (msg.includes('model')) return 'ModelError'
|
|
194
|
+
}
|
|
195
|
+
if (error instanceof OpenAI.APIConnectionError) {
|
|
196
|
+
return 'Network'
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (!(error instanceof Error)) return 'Unknown'
|
|
200
|
+
const msg = error.message.toLowerCase()
|
|
201
|
+
|
|
202
|
+
if (msg.includes('429') || msg.includes('rate limit')) return 'RateLimit'
|
|
203
|
+
if (msg.includes('quota') || msg.includes('billing')) return 'QuotaExceeded'
|
|
204
|
+
if (msg.includes('econnrefused') || msg.includes('network')) return 'Network'
|
|
205
|
+
if (msg.includes('model') && msg.includes('not found')) return 'ModelError'
|
|
206
|
+
|
|
207
|
+
return 'Unknown'
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Check if HyDE is available (API key is set).
|
|
212
|
+
*
|
|
213
|
+
* @returns true if HyDE can be used
|
|
214
|
+
*/
|
|
215
|
+
export const isHydeAvailable = (): boolean => {
|
|
216
|
+
return Boolean(process.env.OPENAI_API_KEY)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Detect if a query would benefit from HyDE expansion.
|
|
221
|
+
*
|
|
222
|
+
* HyDE works best for:
|
|
223
|
+
* - Questions (who, what, where, when, why, how)
|
|
224
|
+
* - Complex or ambiguous queries
|
|
225
|
+
* - Queries seeking procedural information
|
|
226
|
+
*
|
|
227
|
+
* HyDE works poorly for:
|
|
228
|
+
* - Single-word queries
|
|
229
|
+
* - Exact phrase searches
|
|
230
|
+
* - Very short queries (< 3 words)
|
|
231
|
+
*
|
|
232
|
+
* @param query - The search query
|
|
233
|
+
* @returns true if HyDE would likely help
|
|
234
|
+
*/
|
|
235
|
+
export const shouldUseHyde = (query: string): boolean => {
|
|
236
|
+
const normalizedQuery = query.toLowerCase().trim()
|
|
237
|
+
const words = normalizedQuery.split(/\s+/)
|
|
238
|
+
|
|
239
|
+
// Skip very short queries
|
|
240
|
+
if (words.length < 3) return false
|
|
241
|
+
|
|
242
|
+
// Skip if it looks like an exact phrase search
|
|
243
|
+
if (query.startsWith('"') && query.endsWith('"')) return false
|
|
244
|
+
|
|
245
|
+
// Questions are good candidates
|
|
246
|
+
const questionPatterns = [
|
|
247
|
+
/^(how|what|why|when|where|who|which)\s/i,
|
|
248
|
+
/^(can|could|should|would|is|are|does|do)\s/i,
|
|
249
|
+
/\?$/,
|
|
250
|
+
]
|
|
251
|
+
if (questionPatterns.some((p) => p.test(normalizedQuery))) return true
|
|
252
|
+
|
|
253
|
+
// Procedural queries are good candidates
|
|
254
|
+
const proceduralPatterns = [
|
|
255
|
+
/\b(setup|install|configure|implement|create|build|fix|debug|resolve)\b/i,
|
|
256
|
+
/\b(step|guide|tutorial|example|documentation)\b/i,
|
|
257
|
+
]
|
|
258
|
+
if (proceduralPatterns.some((p) => p.test(normalizedQuery))) return true
|
|
259
|
+
|
|
260
|
+
// Longer queries (6+ words) often benefit
|
|
261
|
+
if (words.length >= 6) return true
|
|
262
|
+
|
|
263
|
+
return false
|
|
264
|
+
}
|
package/src/embeddings/index.ts
CHANGED