diff --git a/.changeset/tired-spoons-nail.md b/.changeset/tired-spoons-nail.md new file mode 100644 index 000000000..ae30d2f6e --- /dev/null +++ b/.changeset/tired-spoons-nail.md @@ -0,0 +1,29 @@ +--- +'@tanstack/tests-adapters': minor +'@tanstack/preact-ai-devtools': minor +'@tanstack/react-ai-devtools': minor +'@tanstack/solid-ai-devtools': minor +'@tanstack/smoke-tests-e2e': minor +'@tanstack/ai-openrouter': minor +'@tanstack/ai-anthropic': minor +'@tanstack/ai-devtools-core': minor +'@tanstack/ai-react-ui': minor +'@tanstack/ai-solid-ui': minor +'@tanstack/ai-client': minor +'@tanstack/ai-gemini': minor +'@tanstack/ai-ollama': minor +'@tanstack/ai-openai': minor +'@tanstack/ai-preact': minor +'@tanstack/ai-svelte': minor +'@tanstack/ai-vue-ui': minor +'@tanstack/ai-react': minor +'@tanstack/ai-solid': minor +'@tanstack/ai-grok': minor +'@tanstack/ai-vue': minor +'ts-svelte-chat': minor +'@tanstack/ai': minor +'vanilla-chat': minor +'ts-vue-chat': minor +--- + +Added embed/embedMany activity and Gemini embeddings adapter diff --git a/packages/typescript/ai-gemini/src/adapters/embedding.ts b/packages/typescript/ai-gemini/src/adapters/embedding.ts new file mode 100644 index 000000000..851c15997 --- /dev/null +++ b/packages/typescript/ai-gemini/src/adapters/embedding.ts @@ -0,0 +1,165 @@ +import { BaseEmbeddingAdapter } from '@tanstack/ai/adapters' +import { + createGeminiClient, + generateId, + getGeminiApiKeyFromEnv, +} from '../utils' +import { + validateTaskType, + validateValue, +} from '../embedding/embedding-provider-options' +import type { GoogleGenAI } from '@google/genai' +import type { + EmbedManyOptions, + EmbedManyResult, + EmbedOptions, + EmbedResult, +} from '@tanstack/ai' +import type { GeminiEmbeddingModels } from '../model-meta' +import type { GeminiClientConfig } from '../utils' +import type { + GeminiEmbeddingModelProviderOptionsByName, + GeminiEmbeddingProviderOptions, +} from '../embedding/embedding-provider-options' + +/** + * Configuration for Gemini embedding adapter + */ +export interface GeminiEmbeddingConfig extends GeminiClientConfig {} + +export class GeminiEmbeddingAdapter< + TModel extends GeminiEmbeddingModels, +> extends BaseEmbeddingAdapter { + readonly kind = 'embedding' as const + readonly name = 'gemini' as const + + // Type-only property - never assigned at runtime + declare '~types': { + providerOptions: GeminiEmbeddingProviderOptions + modelProviderOptionsByName: GeminiEmbeddingModelProviderOptionsByName + } + + private client: GoogleGenAI + + constructor(config: GeminiEmbeddingConfig, model: TModel) { + super({}, model) + this.client = createGeminiClient(config) + } + + async embed( + options: EmbedOptions, + ): Promise { + const { model, value, modelOptions } = options + + validateValue({ value, model }) + validateTaskType({ taskType: modelOptions?.taskType, model }) + + const { totalTokens } = await this.client.models.countTokens({ + model, + contents: value, + }) + + const { embeddings } = await this.client.models.embedContent({ + model, + contents: value, + config: { + ...modelOptions, + }, + }) + + return { + embedding: embeddings?.[0]?.values || [], + id: generateId(this.name), + model, + usage: totalTokens ? { totalTokens } : undefined, + } + } + + async embedMany( + options: EmbedManyOptions, + ): Promise { + const { model, values, modelOptions } = options + + validateValue({ value: values, model }) + validateTaskType({ taskType: modelOptions?.taskType, model }) + + const { totalTokens } = await this.client.models.countTokens({ + model, + contents: values, + }) + + const { embeddings } = await this.client.models.embedContent({ + model, + contents: values, + config: { + ...modelOptions, + }, + }) + + return { + embeddings: embeddings?.map((embedding) => embedding.values || []) || [], + id: generateId(this.name), + model, + usage: totalTokens ? { totalTokens } : undefined, + } + } +} + +/** + * Creates a Gemini embedding adapter with explicit API key. + * Type resolution happens here at the call site. + * + * @param model - The model name (e.g., 'embedding-001') + * @param apiKey - Your Google API key + * @param config - Optional additional configuration + * @returns Configured Gemini embedding adapter instance with resolved types + * + * @example + * ```typescript + * const adapter = createGeminiEmbedding('embedding-001', "your-api-key"); + * + * const result = await embed({ + * adapter, + * value: 'Hello, world!' + * }); + * ``` + */ +export function createGeminiEmbedding( + model: TModel, + apiKey: string, + config?: Omit, +): GeminiEmbeddingAdapter { + return new GeminiEmbeddingAdapter({ apiKey, ...config }, model) +} + +/** + * Creates a Gemini embedding adapter with automatic API key detection from environment variables. + * Type resolution happens here at the call site. + * + * Looks for `GOOGLE_API_KEY` or `GEMINI_API_KEY` in: + * - `process.env` (Node.js) + * - `window.env` (Browser with injected env) + * + * @param model - The model name (e.g., 'embedding-001') + * @param config - Optional configuration (excluding apiKey which is auto-detected) + * @returns Configured Gemini embedding adapter instance with resolved types + * @throws Error if GOOGLE_API_KEY or GEMINI_API_KEY is not found in environment + * + * @example + * ```typescript + * // Automatically uses GOOGLE_API_KEY from environment + * const adapter = geminiEmbedding('embedding-001'); + * + * const result = await embed({ + * adapter, + * value: 'Hello, world!' + * }); + * ``` + */ +export function geminiEmbedding( + model: TModel, + config?: Omit, +): GeminiEmbeddingAdapter { + const apiKey = getGeminiApiKeyFromEnv() + return createGeminiEmbedding(model, apiKey, config) +} diff --git a/packages/typescript/ai-gemini/src/embedding/embedding-provider-options.ts b/packages/typescript/ai-gemini/src/embedding/embedding-provider-options.ts new file mode 100644 index 000000000..a62b7d3ce --- /dev/null +++ b/packages/typescript/ai-gemini/src/embedding/embedding-provider-options.ts @@ -0,0 +1,80 @@ +import type { HttpOptions } from '@google/genai' +import type { GeminiEmbeddingModels } from '../model-meta' + +const VALID_TASK_TYPES = [ + 'SEMANTIC_SIMILARITY', + 'CLASSIFICATION', + 'CLUSTERING', + 'RETRIEVAL_DOCUMENT', + 'RETRIEVAL_QUERY', + 'CODE_RETRIEVAL_QUERY', + 'QUESTION_ANSWERING', + 'FACT_VERIFICATION', +] as const + +type TaskType = (typeof VALID_TASK_TYPES)[number] + +export interface GeminiEmbeddingProviderOptions { + /** Used to override HTTP request options. */ + httpOptions?: HttpOptions + /** + * Type of task for which the embedding will be used. + */ + taskType?: TaskType + /** + * Title for the text. Only applicable when TaskType is `RETRIEVAL_DOCUMENT`. + */ + title?: string + /** + * Reduced dimension for the output embedding. If set, + * excessive values in the output embedding are truncated from the end. + * Supported by newer models since 2024 only. You cannot set this value if + * using the earlier model (`models/embedding-001`). + */ + outputDimensionality?: number +} + +export type GeminiEmbeddingModelProviderOptionsByName = { + [K in GeminiEmbeddingModels]: GeminiEmbeddingProviderOptions +} + +/** + * Validates the task type + */ +export function validateTaskType(options: { + taskType: TaskType | undefined + model: string +}) { + const { taskType, model } = options + if (!taskType) return + + if (!VALID_TASK_TYPES.includes(taskType)) { + throw new Error(`Invalid task type "${taskType}" for model "${model}".`) + } +} + +/** + * Validates the value to embed is not empty + */ +export function validateValue(options: { + value: string | Array + model: string +}): void { + const { value, model } = options + if (Array.isArray(value)) { + if (value.length === 0) { + throw new Error(`Value array cannot be empty for model "${model}".`) + } + for (const v of value) { + if (!v || v.trim().length === 0) { + throw new Error( + `Value array cannot contain empty values for model "${model}".`, + ) + } + } + } else { + if (!value || value.trim().length === 0) { + throw new Error(`Value cannot be empty for model "${model}".`) + } + } +} diff --git a/packages/typescript/ai-gemini/src/index.ts b/packages/typescript/ai-gemini/src/index.ts index c60ce0756..6a191ad71 100644 --- a/packages/typescript/ai-gemini/src/index.ts +++ b/packages/typescript/ai-gemini/src/index.ts @@ -51,12 +51,22 @@ export { type GeminiTTSProviderOptions, } from './adapters/tts' +// Embedding adapter +export { + GeminiEmbeddingAdapter, + createGeminiEmbedding, + geminiEmbedding, + type GeminiEmbeddingConfig, +} from './adapters/embedding' + // Re-export models from model-meta for convenience export { GEMINI_MODELS as GeminiTextModels } from './model-meta' +export { GEMINI_EMBEDDING_MODELS as GeminiEmbeddingModels } from './model-meta' export { GEMINI_IMAGE_MODELS as GeminiImageModels } from './model-meta' export { GEMINI_TTS_MODELS as GeminiTTSModels } from './model-meta' export { GEMINI_TTS_VOICES as GeminiTTSVoices } from './model-meta' export type { GeminiModels as GeminiTextModel } from './model-meta' +export type { GeminiEmbeddingModels as GeminiEmbeddingModel } from './model-meta' export type { GeminiImageModels as GeminiImageModel } from './model-meta' export type { GeminiTTSVoice } from './model-meta' diff --git a/packages/typescript/ai-gemini/src/model-meta.ts b/packages/typescript/ai-gemini/src/model-meta.ts index 54761a026..a9069326e 100644 --- a/packages/typescript/ai-gemini/src/model-meta.ts +++ b/packages/typescript/ai-gemini/src/model-meta.ts @@ -12,7 +12,7 @@ interface ModelMeta { name: string supports: { input: Array<'text' | 'image' | 'audio' | 'video' | 'document'> - output: Array<'text' | 'image' | 'audio' | 'video'> + output: Array<'text' | 'image' | 'audio' | 'video' | 'embedding'> capabilities?: Array< | 'audio_generation' | 'batch_api' @@ -678,6 +678,28 @@ const IMAGEN_3 = { GeminiCommonConfigOptions & GeminiCachedContentOptions > + +const GEMINI_EMBEDDING_001 = { + name: 'embedding-001', + max_input_tokens: 2048, + supports: { + input: ['text'], + output: ['embedding'], + }, + pricing: { + input: { + normal: 0, + }, + output: { + normal: 0.15, + }, + }, +} as const satisfies ModelMeta< + GeminiToolConfigOptions & + GeminiSafetyOptions & + GeminiCommonConfigOptions & + GeminiCachedContentOptions +> /** const VEO_3_1_PREVIEW = { name: 'veo-3.1-generate-preview', @@ -833,6 +855,10 @@ export const GEMINI_MODELS = [ export type GeminiModels = (typeof GEMINI_MODELS)[number] +export const GEMINI_EMBEDDING_MODELS = [GEMINI_EMBEDDING_001.name] as const + +export type GeminiEmbeddingModels = (typeof GEMINI_EMBEDDING_MODELS)[number] + export type GeminiImageModels = (typeof GEMINI_IMAGE_MODELS)[number] export const GEMINI_IMAGE_MODELS = [ diff --git a/packages/typescript/ai/src/activities/embed/adapter.ts b/packages/typescript/ai/src/activities/embed/adapter.ts new file mode 100644 index 000000000..36e58fffa --- /dev/null +++ b/packages/typescript/ai/src/activities/embed/adapter.ts @@ -0,0 +1,91 @@ +import type { + EmbedManyOptions, + EmbedManyResult, + EmbedOptions, + EmbedResult, +} from '../../types' + +// =========================== +// Activity Kind +// =========================== + +/** The adapter kind this activity handles */ +export const kind = 'embedding' as const + +export interface EmbeddingAdapterConfig { + apiKey?: string + baseUrl?: string + timeout?: number + maxRetries?: number + headers?: Record +} + +export interface EmbeddingAdapter< + TModel extends string = string, + TProviderOptions extends object = Record, +> { + /** Discriminator for adapter kind - used to determine API shape */ + readonly kind: 'embedding' + /** Adapter name identifier */ + readonly name: string + /** The model this adapter is configured for */ + readonly model: TModel + + /** + * @internal Type-only properties for inference. Not assigned at runtime. + */ + '~types': { + providerOptions: TProviderOptions + } + + /** + * Generate embeddings for text + */ + embed: (options: EmbedOptions) => Promise + + /** + * Generate embeddings for multiple texts + */ + embedMany: ( + options: EmbedManyOptions, + ) => Promise +} + +/** + * An EmbeddingAdapter with any/unknown type parameters. + * Useful as a constraint in generic functions and interfaces. + */ +export type AnyEmbeddingAdapter = EmbeddingAdapter + +/** + * Abstract base class for embed adapters. + * Extend this class to implement an embed adapter for a specific provider. + * + * Generic parameters match EmbedAdapter - all pre-resolved by the provider function. + */ +export abstract class BaseEmbeddingAdapter< + TModel extends string = string, + TProviderOptions extends object = Record, +> implements EmbeddingAdapter { + readonly kind = 'embedding' as const + abstract readonly name: string + readonly model: TModel + + // Type-only property - never assigned at runtime + declare '~types': { + providerOptions: TProviderOptions + } + + protected config: EmbeddingAdapterConfig + + constructor(config: EmbeddingAdapterConfig = {}, model: TModel) { + this.config = config + this.model = model + } + + abstract embed(options: EmbedOptions): Promise + + abstract embedMany( + options: EmbedManyOptions, + ): Promise +} diff --git a/packages/typescript/ai/src/activities/embed/embed-many.ts b/packages/typescript/ai/src/activities/embed/embed-many.ts new file mode 100644 index 000000000..2b0fb5e29 --- /dev/null +++ b/packages/typescript/ai/src/activities/embed/embed-many.ts @@ -0,0 +1,61 @@ +import { aiEventClient } from '../../event-client' +import { createId } from '../utils/id' +import type { EmbedManyResult } from '../../types' +import type { EmbeddingAdapter, kind } from './adapter' + +export interface EmbedManyActivityOptions< + TAdapter extends EmbeddingAdapter, +> { + /** The embedding adapter to use (must be created with a model) */ + adapter: TAdapter & { kind: typeof kind } + /** The values to convert to embeddings */ + values: Array + /** Model-specific options for embedding */ + modelOptions?: TAdapter['~types']['providerOptions'] +} + +/** Result type for the embed-many activity */ +export type EmbedManyActivityResult = Promise + +export async function embedMany< + TAdapter extends EmbeddingAdapter, +>(options: EmbedManyActivityOptions): EmbedManyActivityResult { + const { adapter, ...rest } = options + const model = adapter.model + const requestId = createId('embed-many') + const startTime = Date.now() + + aiEventClient.emit('embed-many:request:started', { + requestId, + provider: adapter.name, + model, + values: rest.values, + modelOptions: rest.modelOptions as Record | undefined, + timestamp: startTime, + }) + + const result = await adapter.embedMany({ ...rest, model }) + const duration = Date.now() - startTime + + aiEventClient.emit('embed-many:request:completed', { + requestId, + provider: adapter.name, + model, + embeddings: result.embeddings, + duration, + modelOptions: rest.modelOptions as Record | undefined, + timestamp: Date.now(), + }) + + if (result.usage) { + aiEventClient.emit('embed-many:usage', { + requestId, + provider: adapter.name, + model, + usage: result.usage, + timestamp: Date.now(), + }) + } + + return result +} diff --git a/packages/typescript/ai/src/activities/embed/embed.ts b/packages/typescript/ai/src/activities/embed/embed.ts new file mode 100644 index 000000000..5a8d4bb4d --- /dev/null +++ b/packages/typescript/ai/src/activities/embed/embed.ts @@ -0,0 +1,61 @@ +import { aiEventClient } from '../../event-client' +import { createId } from '../utils/id' +import type { EmbedResult } from '../../types' +import type { EmbeddingAdapter, kind } from './adapter' + +export interface EmbedActivityOptions< + TAdapter extends EmbeddingAdapter, +> { + /** The embedding adapter to use (must be created with a model) */ + adapter: TAdapter & { kind: typeof kind } + /** The value to convert to embedding */ + value: string + /** Model-specific options for embedding */ + modelOptions?: TAdapter['~types']['providerOptions'] +} + +/** Result type for the embed activity */ +export type EmbedActivityResult = Promise + +export async function embed>( + options: EmbedActivityOptions, +): EmbedActivityResult { + const { adapter, ...rest } = options + const model = adapter.model + const requestId = createId('embed') + const startTime = Date.now() + + aiEventClient.emit('embed:request:started', { + requestId, + provider: adapter.name, + model, + value: rest.value, + modelOptions: rest.modelOptions as Record | undefined, + timestamp: startTime, + }) + + const result = await adapter.embed({ ...rest, model }) + const duration = Date.now() - startTime + + aiEventClient.emit('embed:request:completed', { + requestId, + provider: adapter.name, + model, + embedding: result.embedding, + duration, + modelOptions: rest.modelOptions as Record | undefined, + timestamp: Date.now(), + }) + + if (result.usage) { + aiEventClient.emit('embed:usage', { + requestId, + provider: adapter.name, + model, + usage: result.usage, + timestamp: Date.now(), + }) + } + + return result +} diff --git a/packages/typescript/ai/src/activities/embed/index.ts b/packages/typescript/ai/src/activities/embed/index.ts new file mode 100644 index 000000000..9df865328 --- /dev/null +++ b/packages/typescript/ai/src/activities/embed/index.ts @@ -0,0 +1,7 @@ +export { embed } from './embed' +export { embedMany } from './embed-many' +export { kind } from './adapter' + +// Re-export adapter types +export { BaseEmbeddingAdapter } from './adapter' +export type { AnyEmbeddingAdapter, EmbeddingAdapterConfig } from './adapter' diff --git a/packages/typescript/ai/src/activities/generateImage/index.ts b/packages/typescript/ai/src/activities/generateImage/index.ts index d573b32f1..bcee9d7b5 100644 --- a/packages/typescript/ai/src/activities/generateImage/index.ts +++ b/packages/typescript/ai/src/activities/generateImage/index.ts @@ -6,8 +6,9 @@ */ import { aiEventClient } from '../../event-client.js' -import type { ImageAdapter } from './adapter' +import { createId } from '../utils/id' import type { ImageGenerationResult } from '../../types' +import type { ImageAdapter } from './adapter' // =========================== // Activity Kind @@ -83,10 +84,6 @@ export interface ImageActivityOptions< /** Result type for the image activity */ export type ImageActivityResult = Promise -function createId(prefix: string): string { - return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}` -} - // =========================== // Activity Implementation // =========================== diff --git a/packages/typescript/ai/src/activities/generateSpeech/index.ts b/packages/typescript/ai/src/activities/generateSpeech/index.ts index 39645ebab..af7daad91 100644 --- a/packages/typescript/ai/src/activities/generateSpeech/index.ts +++ b/packages/typescript/ai/src/activities/generateSpeech/index.ts @@ -6,8 +6,9 @@ */ import { aiEventClient } from '../../event-client.js' -import type { TTSAdapter } from './adapter' +import { createId } from '../utils/id.js' import type { TTSResult } from '../../types' +import type { TTSAdapter } from './adapter' // =========================== // Activity Kind @@ -62,10 +63,6 @@ export interface TTSActivityOptions< /** Result type for the TTS activity */ export type TTSActivityResult = Promise -function createId(prefix: string): string { - return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}` -} - // =========================== // Activity Implementation // =========================== diff --git a/packages/typescript/ai/src/activities/generateTranscription/index.ts b/packages/typescript/ai/src/activities/generateTranscription/index.ts index dff9a477b..5cc67c0eb 100644 --- a/packages/typescript/ai/src/activities/generateTranscription/index.ts +++ b/packages/typescript/ai/src/activities/generateTranscription/index.ts @@ -6,8 +6,9 @@ */ import { aiEventClient } from '../../event-client.js' -import type { TranscriptionAdapter } from './adapter' +import { createId } from '../utils/id' import type { TranscriptionResult } from '../../types' +import type { TranscriptionAdapter } from './adapter' // =========================== // Activity Kind @@ -62,10 +63,6 @@ export interface TranscriptionActivityOptions< /** Result type for the transcription activity */ export type TranscriptionActivityResult = Promise -function createId(prefix: string): string { - return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}` -} - // =========================== // Activity Implementation // =========================== diff --git a/packages/typescript/ai/src/activities/generateVideo/index.ts b/packages/typescript/ai/src/activities/generateVideo/index.ts index 9a7a46aa4..01ffe0167 100644 --- a/packages/typescript/ai/src/activities/generateVideo/index.ts +++ b/packages/typescript/ai/src/activities/generateVideo/index.ts @@ -8,12 +8,13 @@ */ import { aiEventClient } from '../../event-client.js' -import type { VideoAdapter } from './adapter' +import { createId } from '../utils/id' import type { VideoJobResult, VideoStatusResult, VideoUrlResult, } from '../../types' +import type { VideoAdapter } from './adapter' // =========================== // Activity Kind @@ -34,14 +35,6 @@ export type VideoProviderOptions = ? TAdapter['~types']['providerOptions'] : object -// =========================== -// Activity Options Types - -function createId(prefix: string): string { - return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}` -} -// =========================== - /** * Base options shared by all video activity operations. * The model is extracted from the adapter's model property. diff --git a/packages/typescript/ai/src/activities/index.ts b/packages/typescript/ai/src/activities/index.ts index 521675a71..219661eb0 100644 --- a/packages/typescript/ai/src/activities/index.ts +++ b/packages/typescript/ai/src/activities/index.ts @@ -141,6 +141,19 @@ export { type AnyTranscriptionAdapter, } from './generateTranscription/adapter' +// =========================== +// Embedding Activity +// =========================== + +export { kind as embeddingKind, embed, embedMany } from './embed/index' + +export { + BaseEmbeddingAdapter, + type EmbeddingAdapter, + type EmbeddingAdapterConfig, + type AnyEmbeddingAdapter, +} from './embed/adapter' + // =========================== // Adapter Union Types // =========================== diff --git a/packages/typescript/ai/src/activities/summarize/index.ts b/packages/typescript/ai/src/activities/summarize/index.ts index ddee4280a..6ffce87e0 100644 --- a/packages/typescript/ai/src/activities/summarize/index.ts +++ b/packages/typescript/ai/src/activities/summarize/index.ts @@ -6,12 +6,13 @@ */ import { aiEventClient } from '../../event-client.js' -import type { SummarizeAdapter } from './adapter' +import { createId } from '../utils/id' import type { StreamChunk, SummarizationOptions, SummarizationResult, } from '../../types' +import type { SummarizeAdapter } from './adapter' // =========================== // Activity Kind @@ -81,14 +82,6 @@ export type SummarizeActivityResult = ? AsyncIterable : Promise -// =========================== -// Helper Functions -// =========================== - -function createId(prefix: string): string { - return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}` -} - // =========================== // Activity Implementation // =========================== diff --git a/packages/typescript/ai/src/activities/utils/id.ts b/packages/typescript/ai/src/activities/utils/id.ts new file mode 100644 index 000000000..7ec803573 --- /dev/null +++ b/packages/typescript/ai/src/activities/utils/id.ts @@ -0,0 +1,3 @@ +export function createId(prefix: string): string { + return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}` +} diff --git a/packages/typescript/ai/src/event-client.ts b/packages/typescript/ai/src/event-client.ts index b76d67055..0870a7008 100644 --- a/packages/typescript/ai/src/event-client.ts +++ b/packages/typescript/ai/src/event-client.ts @@ -33,6 +33,10 @@ export interface ImageUsage { totalTokens?: number } +export interface EmbeddingsUsage { + totalTokens?: number +} + interface BaseEventContext { timestamp: number requestId?: string @@ -384,6 +388,62 @@ export interface VideoUsageEvent extends BaseEventContext { usage: TokenUsage } +// =========================== +// Embed Events +// =========================== + +/** Emitted when an embed request starts. */ +export interface EmbedRequestStartedEvent extends BaseEventContext { + requestId: string + provider: string + model: string + value: string +} + +/** Emitted when an embed request completes. */ +export interface EmbedRequestCompletedEvent extends BaseEventContext { + requestId: string + provider: string + model: string + embedding: Array + duration: number +} + +/** Emitted when embed usage metrics are available. */ +export interface EmbedUsageEvent extends BaseEventContext { + requestId: string + model: string + usage: EmbeddingsUsage +} + +// =========================== +// EmbedMany Events +// =========================== + +/** Emitted when an embed many request starts. */ +export interface EmbedManyRequestStartedEvent extends BaseEventContext { + requestId: string + provider: string + model: string + values: Array +} + +/** Emitted when an embed many request completes. */ +export interface EmbedManyRequestCompletedEvent extends BaseEventContext { + requestId: string + provider: string + model: string + embeddings: Array> + duration: number +} + +/** Emitted when embed many usage metrics are available. */ +export interface EmbedManyUsageEvent extends BaseEventContext { + requestId: string + model: string + usage: EmbeddingsUsage +} + // =========================== // Client Events // =========================== @@ -475,6 +535,14 @@ export interface AIDevtoolsEventMap { 'tanstack-ai-devtools:video:request:completed': VideoRequestCompletedEvent 'tanstack-ai-devtools:video:usage': VideoUsageEvent + // Embed events + 'tanstack-ai-devtools:embed:request:started': EmbedRequestStartedEvent + 'tanstack-ai-devtools:embed:request:completed': EmbedRequestCompletedEvent + 'tanstack-ai-devtools:embed:usage': EmbedUsageEvent + 'tanstack-ai-devtools:embed-many:request:started': EmbedManyRequestStartedEvent + 'tanstack-ai-devtools:embed-many:request:completed': EmbedManyRequestCompletedEvent + 'tanstack-ai-devtools:embed-many:usage': EmbedManyUsageEvent + // Client events 'tanstack-ai-devtools:client:created': ClientCreatedEvent 'tanstack-ai-devtools:client:loading:changed': ClientLoadingChangedEvent diff --git a/packages/typescript/ai/src/types.ts b/packages/typescript/ai/src/types.ts index 7bd3c52d9..cb0174ad3 100644 --- a/packages/typescript/ai/src/types.ts +++ b/packages/typescript/ai/src/types.ts @@ -1,4 +1,5 @@ import type { StandardJSONSchemaV1 } from '@standard-schema/spec' +import type { EmbeddingsUsage } from './event-client' /** * Tool call states - track the lifecycle of a tool call @@ -1172,3 +1173,47 @@ export interface DefaultMessageMetadataByModality { video: unknown document: unknown } + +// ============================================================================ +// Embed Types +// ============================================================================ + +export interface EmbedOptions { + /** The model to use for embedding */ + model: string + /** The text value to embed */ + value: string + /** Model-specific options for embedding */ + modelOptions?: TProviderOptions +} + +export interface EmbedManyOptions { + /** The model to use for embedding */ + model: string + /** The text values to embed */ + values: Array + /** Model-specific options for embedding */ + modelOptions?: TProviderOptions +} + +export interface EmbedResult { + /** Unique identifier for the embedding */ + id: string + /** Model used for embedding */ + model: string + /** The embedding vector */ + embedding: Array + /** Usage metadata */ + usage?: EmbeddingsUsage +} + +export interface EmbedManyResult { + /** Unique identifier for the embedding */ + id: string + /** Model used for embedding */ + model: string + /** The embedding vectors */ + embeddings: Array> + /** Usage metadata */ + usage?: EmbeddingsUsage +}