provider.d.ts 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. /**
  2. * provider.ts - Embedding provider abstraction
  3. *
  4. * Production embeddings use a commercial OpenAI-compatible API. The `local`
  5. * kind remains readable only for historical config and is rejected by the factory.
  6. *
  7. * The factory in `./factory.ts` selects an implementation based on env vars,
  8. * a CLI flag, or `~/.config/qmd/config.json`.
  9. */
  10. /**
  11. * Single embedding result
  12. */
  13. export type ProviderEmbedding = {
  14. embedding: number[];
  15. /** Model identifier used to produce this embedding (matches content_vectors.model in DB) */
  16. model: string;
  17. };
  18. /**
  19. * Supported provider kinds
  20. */
  21. export type ProviderKind = "local" | "openai";
  22. /**
  23. * Healthcheck result for provider startup verification
  24. */
  25. export type ProviderHealth = {
  26. ok: boolean;
  27. /** Model identifier reported by the provider */
  28. model: string;
  29. /** Embedding dimensions (e.g. 768 for embeddinggemma-300M) */
  30. dimensions?: number;
  31. /** Detail message (error reason on failure, status on success) */
  32. detail?: string;
  33. };
  34. /**
  35. * Per-call options for provider embedding
  36. */
  37. export type ProviderEmbedOptions = {
  38. /** Optional model id override (rare; usually provider has a fixed model) */
  39. model?: string;
  40. /** Abort signal for cancellation / timeout */
  41. signal?: AbortSignal;
  42. };
  43. /**
  44. * Provider interface for commercial embedding adapters and historical readers.
  45. *
  46. * Implementations MUST:
  47. * - Return `null` (not throw) for individual texts that fail to embed;
  48. * the caller will count it as an error and continue.
  49. * - Honor `options.signal` for cancellation.
  50. * - Be safe to call concurrently for `embedBatch`.
  51. */
  52. export interface EmbeddingProvider {
  53. /** Provider kind tag — useful for logging and factory introspection */
  54. readonly kind: ProviderKind;
  55. /**
  56. * Stable model identifier reported to the caller.
  57. *
  58. * MUST match what's stored in `content_vectors.model` for the existing
  59. * index — otherwise the model-id guard refuses to embed.
  60. */
  61. getModelId(): string;
  62. /**
  63. * Embedding vector dimensions. May return `undefined` before the first call
  64. * (some providers probe lazily). Once known, MUST stay stable.
  65. */
  66. getDimensions(): number | undefined;
  67. /**
  68. * Healthcheck — verifies the provider is reachable and the model is loaded.
  69. * Should NOT throw — return `{ ok: false, detail: ... }` on failure.
  70. *
  71. * For HTTP providers: ping `/health` endpoint.
  72. */
  73. healthcheck(signal?: AbortSignal): Promise<ProviderHealth>;
  74. /**
  75. * Embed a single text. Returns `null` on per-call failure.
  76. */
  77. embed(text: string, options?: ProviderEmbedOptions): Promise<ProviderEmbedding | null>;
  78. /**
  79. * Embed multiple texts in a batch (more efficient than calling `embed` N times).
  80. *
  81. * Output array length MUST equal input array length. Failed entries are `null`.
  82. * Implementations are responsible for chunking large batches per their
  83. * upstream limits (e.g. OpenAI provider chunks to 64).
  84. */
  85. embedBatch(texts: string[], options?: ProviderEmbedOptions): Promise<(ProviderEmbedding | null)[]>;
  86. /**
  87. * Optional: most recent error message from a swallowed per-chunk failure.
  88. *
  89. * Per-chunk errors are intentionally swallowed (slot becomes `null`) so a
  90. * single bad text does not abort a 1000-doc embed run. Callers that need
  91. * to surface a meaningful error (e.g. the dimension-probe call site in
  92. * `store.ts` when even the first chunk fails) can read this field to
  93. * include the underlying cause (HTTP status, malformed JSON, timeout,
  94. * abort reason, …) in their own error message.
  95. *
  96. * Returns `undefined` when the most recent call succeeded or no call has
  97. * happened yet. Implementations MUST clear it on success.
  98. *
  99. * Optional so 3rd-party `EmbeddingProvider` implementations remain source-
  100. * compatible; callers must guard with `provider.getLastError?.()`.
  101. */
  102. getLastError?(): string | undefined;
  103. /** Release any held resources (HTTP keep-alive sockets, model handles, …) */
  104. dispose(): Promise<void>;
  105. }
  106. /**
  107. * Error thrown when the provider's reported model id does not match the
  108. * model id baked into existing `content_vectors` rows. Forces user to
  109. * re-embed (`qmd embed -f`) or pin the matching model id.
  110. */
  111. export declare class ModelMismatchError extends Error {
  112. readonly providerModel: string;
  113. readonly existingModels: string[];
  114. constructor(providerModel: string, existingModels: string[]);
  115. }
  116. /**
  117. * Verify that the provider's model id is compatible with the existing
  118. * `content_vectors` entries. Pass-through (no-op) if the table is empty
  119. * (fresh DB) or if the model id appears in the distinct set.
  120. *
  121. * Caller passes `existingModels` (typically result of
  122. * `SELECT DISTINCT model FROM content_vectors`).
  123. */
  124. export declare function assertModelCompatible(providerModel: string, existingModels: string[]): void;