UNPKG

workers-ai-provider

Version:

Workers AI Provider for the vercel AI SDK

130 lines (108 loc) 3.68 kB
import type { LanguageModelV3, SharedV3Warning } from "@ai-sdk/provider"; import type { AISearchChatSettings } from "./aisearch-chat-settings"; import { convertToWorkersAIChatMessages } from "./convert-to-workersai-chat-messages"; import { mapWorkersAIUsage } from "./map-workersai-usage"; import { getMappedStream, prependStreamStart } from "./streaming"; import { processToolCalls } from "./utils"; import type { TextGenerationModels } from "./workersai-models"; type AISearchChatConfig = { provider: string; binding: AutoRAG; gateway?: GatewayOptions; }; export class AISearchChatLanguageModel implements LanguageModelV3 { readonly specificationVersion = "v3"; readonly defaultObjectGenerationMode = "json"; readonly supportedUrls: Record<string, RegExp[]> | PromiseLike<Record<string, RegExp[]>> = {}; readonly modelId: TextGenerationModels; readonly settings: AISearchChatSettings; private readonly config: AISearchChatConfig; constructor( modelId: TextGenerationModels, settings: AISearchChatSettings, config: AISearchChatConfig, ) { this.modelId = modelId; this.settings = settings; this.config = config; } get provider(): string { return this.config.provider; } private getWarnings({ tools, frequencyPenalty, presencePenalty, responseFormat, }: Parameters<LanguageModelV3["doGenerate"]>[0]): SharedV3Warning[] { const warnings: SharedV3Warning[] = []; if (tools != null && tools.length > 0) { console.warn( "[workers-ai-provider] Tools are not supported by AI Search. They will be ignored.", ); warnings.push({ feature: "tools", type: "unsupported" }); } if (frequencyPenalty != null) { warnings.push({ feature: "frequencyPenalty", type: "unsupported" }); } if (presencePenalty != null) { warnings.push({ feature: "presencePenalty", type: "unsupported" }); } if (responseFormat?.type === "json") { warnings.push({ feature: "responseFormat", type: "unsupported" }); } return warnings; } /** * Build the search query from messages. * Flattens the conversation into a single string for aiSearch. */ private buildQuery(prompt: Parameters<LanguageModelV3["doGenerate"]>[0]["prompt"]): string { const { messages } = convertToWorkersAIChatMessages(prompt); return messages.map(({ content, role }) => `${role}: ${content}`).join("\n\n"); } async doGenerate( options: Parameters<LanguageModelV3["doGenerate"]>[0], ): Promise<Awaited<ReturnType<LanguageModelV3["doGenerate"]>>> { const warnings = this.getWarnings(options); const query = this.buildQuery(options.prompt); const output = await this.config.binding.aiSearch({ query }); return { finishReason: { unified: "stop", raw: "stop" }, content: [ ...output.data.map(({ file_id, filename, score }) => ({ type: "source" as const, sourceType: "url" as const, id: file_id, url: filename, providerMetadata: { attributes: { score }, }, })), { type: "text" as const, text: output.response, }, ...processToolCalls(output as unknown as Record<string, unknown>), ], usage: mapWorkersAIUsage(output as unknown as Record<string, unknown>), warnings, }; } async doStream( options: Parameters<LanguageModelV3["doStream"]>[0], ): Promise<Awaited<ReturnType<LanguageModelV3["doStream"]>>> { const warnings = this.getWarnings(options); const query = this.buildQuery(options.prompt); const response = await this.config.binding.aiSearch({ query, stream: true, }); return { stream: prependStreamStart( getMappedStream(response as unknown as Response | ReadableStream<Uint8Array>), warnings, ), }; } }