UNPKG

@langchain/community

Version:
226 lines (225 loc) 8.82 kB
import { __exportAll } from "../_virtual/_rolldown/runtime.js"; import { ReasoningJsonOutputParser, ReasoningStructuredOutputParser } from "../utils/output_parsers.js"; import { isInteropZodSchema } from "@langchain/core/utils/types"; import { JsonOutputParser, StructuredOutputParser } from "@langchain/core/output_parsers"; import { BaseChatModel } from "@langchain/core/language_models/chat_models"; import { AIMessage, AIMessageChunk, ChatMessageChunk, HumanMessageChunk, SystemMessageChunk } from "@langchain/core/messages"; import { ChatGenerationChunk } from "@langchain/core/outputs"; import { getEnvironmentVariable } from "@langchain/core/utils/env"; import { concat } from "@langchain/core/utils/stream"; import { RunnablePassthrough, RunnableSequence } from "@langchain/core/runnables"; import { toJsonSchema } from "@langchain/core/utils/json_schema"; import OpenAI from "openai"; //#region src/chat_models/perplexity.ts var perplexity_exports = /* @__PURE__ */ __exportAll({ ChatPerplexity: () => ChatPerplexity }); /** * Wrapper around Perplexity large language models that use the Chat endpoint. */ var ChatPerplexity = class extends BaseChatModel { static lc_name() { return "ChatPerplexity"; } model; temperature; maxTokens; apiKey; timeout; streaming; topP; searchDomainFilter; returnImages; returnRelatedQuestions; searchRecencyFilter; topK; presencePenalty; frequencyPenalty; searchMode; reasoningEffort; searchAfterDateFilter; searchBeforeDateFilter; lastUpdatedAfterFilter; lastUpdatedBeforeFilter; disableSearch; enableSearchClassifier; webSearchOptions; client; constructor(fields) { super(fields ?? {}); this.model = fields.model; this.temperature = fields?.temperature ?? this.temperature; this.maxTokens = fields?.maxTokens; this.apiKey = fields?.apiKey ?? getEnvironmentVariable("PERPLEXITY_API_KEY"); this.streaming = fields?.streaming ?? this.streaming; this.timeout = fields?.timeout; this.topP = fields?.topP ?? this.topP; this.returnImages = fields?.returnImages ?? this.returnImages; this.returnRelatedQuestions = fields?.returnRelatedQuestions ?? this.returnRelatedQuestions; this.topK = fields?.topK ?? this.topK; this.presencePenalty = fields?.presencePenalty ?? this.presencePenalty; this.frequencyPenalty = fields?.frequencyPenalty ?? this.frequencyPenalty; this.searchDomainFilter = fields?.searchDomainFilter ?? this.searchDomainFilter; this.searchRecencyFilter = fields?.searchRecencyFilter ?? this.searchRecencyFilter; this.searchMode = fields?.searchMode; this.reasoningEffort = fields?.reasoningEffort; this.searchAfterDateFilter = fields?.searchAfterDateFilter; this.searchBeforeDateFilter = fields?.searchBeforeDateFilter; this.webSearchOptions = fields?.webSearchOptions; if (!this.apiKey) throw new Error("Perplexity API key not found"); this.client = new OpenAI({ apiKey: this.apiKey, baseURL: "https://api.perplexity.ai" }); } _llmType() { return "perplexity"; } /** * Get the parameters used to invoke the model */ invocationParams(options) { return { model: this.model, temperature: this.temperature, max_tokens: this.maxTokens, stream: this.streaming, top_p: this.topP, return_images: this.returnImages, return_related_questions: this.returnRelatedQuestions, top_k: this.topK, presence_penalty: this.presencePenalty, frequency_penalty: this.frequencyPenalty, response_format: options?.response_format, search_domain_filter: this.searchDomainFilter, search_recency_filter: this.searchRecencyFilter, search_mode: this.searchMode, reasoning_effort: this.reasoningEffort, search_after_date_filter: this.searchAfterDateFilter, search_before_date_filter: this.searchBeforeDateFilter, last_updated_after_filter: this.lastUpdatedAfterFilter, last_updated_before_filter: this.lastUpdatedBeforeFilter, disable_search: this.disableSearch, enable_search_classifier: this.enableSearchClassifier, web_search_options: this.webSearchOptions }; } /** * Convert a message to a format that the model expects */ messageToPerplexityRole(message) { if (message._getType() === "human") return { role: "user", content: message.content.toString() }; else if (message._getType() === "ai") return { role: "assistant", content: message.content.toString() }; else if (message._getType() === "system") return { role: "system", content: message.content.toString() }; throw new Error(`Unknown message type: ${message}`); } async _generate(messages, options, runManager) { const tokenUsage = {}; const messagesList = messages.map((message) => this.messageToPerplexityRole(message)); if (this.streaming) { const stream = this._streamResponseChunks(messages, options, runManager); const finalChunks = {}; for await (const chunk of stream) { const index = chunk.generationInfo?.completion ?? 0; if (finalChunks[index] === void 0) finalChunks[index] = chunk; else finalChunks[index] = concat(finalChunks[index], chunk); } return { generations: Object.entries(finalChunks).sort(([aKey], [bKey]) => parseInt(aKey, 10) - parseInt(bKey, 10)).map(([_, value]) => value) }; } const response = await this.client.chat.completions.create({ messages: messagesList, ...this.invocationParams(options), stream: false }); const { message } = response.choices[0]; const generations = []; generations.push({ text: message.content ?? "", message: new AIMessage({ content: message.content ?? "", additional_kwargs: { citations: response.citations } }) }); if (response.usage) { tokenUsage.promptTokens = response.usage.prompt_tokens; tokenUsage.completionTokens = response.usage.completion_tokens; tokenUsage.totalTokens = response.usage.total_tokens; } return { generations, llmOutput: { tokenUsage } }; } async *_streamResponseChunks(messages, options, runManager) { const messagesList = messages.map((message) => this.messageToPerplexityRole(message)); const stream = await this.client.chat.completions.create({ messages: messagesList, ...this.invocationParams(options), stream: true }); let firstChunk = true; for await (const chunk of stream) { const choice = chunk.choices[0]; const { delta } = choice; const citations = chunk.citations ?? []; if (!delta.content) continue; let messageChunk; if (delta.role === "user") messageChunk = new HumanMessageChunk({ content: delta.content }); else if (delta.role === "assistant") messageChunk = new AIMessageChunk({ content: delta.content }); else if (delta.role === "system") messageChunk = new SystemMessageChunk({ content: delta.content }); else messageChunk = new ChatMessageChunk({ content: delta.content, role: delta.role ?? "assistant" }); if (firstChunk) { messageChunk.additional_kwargs.citations = citations; firstChunk = false; } yield new ChatGenerationChunk({ message: messageChunk, text: delta.content, generationInfo: { finishReason: choice.finish_reason } }); if (runManager) await runManager.handleLLMNewToken(delta.content); } } withStructuredOutput(outputSchema, config) { if (config?.strict) throw new Error(`"strict" mode is not supported for this model.`); let schema = outputSchema; if (isInteropZodSchema(schema)) schema = toJsonSchema(schema); const name = config?.name; const description = schema.description ?? "Format to use when returning your response"; const method = config?.method ?? "jsonSchema"; const includeRaw = config?.includeRaw; if (method !== "jsonSchema") throw new Error(`Perplexity only supports "jsonSchema" as a structured output method.`); const llm = this.withConfig({ response_format: { type: "json_schema", json_schema: { name: name ?? "extract", description, schema } } }); let outputParser; const isReasoningModel = this.model.toLowerCase().includes("reasoning"); if (isInteropZodSchema(schema)) if (isReasoningModel) outputParser = new ReasoningStructuredOutputParser(schema); else outputParser = StructuredOutputParser.fromZodSchema(schema); else if (isReasoningModel) outputParser = new ReasoningJsonOutputParser(schema); else outputParser = new JsonOutputParser(); if (!includeRaw) return llm.pipe(outputParser); const parserAssign = RunnablePassthrough.assign({ parsed: (input, config) => outputParser.invoke(input.raw, config) }); const parserNone = RunnablePassthrough.assign({ parsed: () => null }); const parsedWithFallback = parserAssign.withFallbacks({ fallbacks: [parserNone] }); return RunnableSequence.from([{ raw: llm }, parsedWithFallback]); } }; //#endregion export { ChatPerplexity, perplexity_exports }; //# sourceMappingURL=perplexity.js.map