UNPKG

@llamaindex/core

Version:
356 lines (350 loc) 13.3 kB
Object.defineProperty(exports, '__esModule', { value: true }); var env = require('@llamaindex/env'); var index_cjs$1 = require('../../global/dist/index.cjs'); var index_cjs$2 = require('../../indices/dist/index.cjs'); var index_cjs = require('../../prompts/dist/index.cjs'); var index_cjs$3 = require('../../schema/dist/index.cjs'); var zod = require('zod'); var index_cjs$4 = require('../../utils/dist/index.cjs'); class BaseSynthesizer extends index_cjs.PromptMixin { constructor(options){ super(); this.llm = options.llm ?? index_cjs$1.Settings.llm; this.promptHelper = options.promptHelper ?? index_cjs$2.PromptHelper.fromLLMMetadata(this.llm.metadata); } async synthesize(query, stream = false) { const callbackManager = index_cjs$1.Settings.callbackManager; const id = env.randomUUID(); callbackManager.dispatchEvent("synthesize-start", { id, query }); let response; if (query.nodes.length === 0) { if (stream) { response = index_cjs$3.EngineResponse.fromResponse("Empty Response", true, []); } else { response = index_cjs$3.EngineResponse.fromResponse("Empty Response", false, []); } } else { const queryMessage = typeof query.query === "string" ? query.query : query.query.query; response = await this.getResponse(queryMessage, query.nodes, stream); } callbackManager.dispatchEvent("synthesize-end", { id, query, response }); return response; } } async function createContentPerModality(prompt, type, nodes, extraParams, metadataMode) { switch(type){ case index_cjs$3.ModalityType.TEXT: return [ { type: "text", text: prompt.format({ ...extraParams, context: nodes.map((r)=>r.getContent(metadataMode)).join("\n\n") }) } ]; case index_cjs$3.ModalityType.IMAGE: return Promise.all(nodes.map(async (node)=>{ return { type: "image_url", image_url: { url: await index_cjs$4.imageToDataUrl(node.image) } }; })); default: return []; } } async function createMessageContent(prompt, nodes, extraParams = {}, metadataMode = index_cjs$3.MetadataMode.NONE) { const content = []; const nodeMap = index_cjs$3.splitNodesByType(nodes); for(const type in nodeMap){ // for each retrieved modality type, create message content const nodes = nodeMap[type]; if (nodes) { content.push(...await createContentPerModality(prompt, type, nodes, extraParams, metadataMode)); } } return content; } const responseModeSchema = zod.z.enum([ "refine", "compact", "tree_summarize", "multi_modal" ]); /** * A response builder that uses the query to ask the LLM generate a better response using multiple text chunks. */ class Refine extends BaseSynthesizer { constructor(options){ super(options); this.textQATemplate = options.textQATemplate ?? index_cjs.defaultTextQAPrompt; this.refineTemplate = options.refineTemplate ?? index_cjs.defaultRefinePrompt; } _getPromptModules() { return {}; } _getPrompts() { return { textQATemplate: this.textQATemplate, refineTemplate: this.refineTemplate }; } _updatePrompts(prompts) { if (prompts.textQATemplate) { this.textQATemplate = prompts.textQATemplate; } if (prompts.refineTemplate) { this.refineTemplate = prompts.refineTemplate; } } async getResponse(query, nodes, stream) { let response = undefined; const textChunks = nodes.map(({ node })=>node.getContent(index_cjs$3.MetadataMode.LLM)); for(let i = 0; i < textChunks.length; i++){ const text = textChunks[i]; const lastChunk = i === textChunks.length - 1; if (!response) { response = await this.giveResponseSingle(query, text, !!stream && lastChunk); } else { response = await this.refineResponseSingle(response, query, text, !!stream && lastChunk); } } if (response === undefined) { response = stream ? async function*() { yield ""; }() : ""; } if (typeof response === "string") { return index_cjs$3.EngineResponse.fromResponse(response, false, nodes); } else { return index_cjs$4.streamConverter(response, (text)=>index_cjs$3.EngineResponse.fromResponse(text, true, nodes)); } } async giveResponseSingle(query, textChunk, stream) { const textQATemplate = this.textQATemplate.partialFormat({ query: index_cjs$4.extractText(query) }); const textChunks = this.promptHelper.repack(textQATemplate, [ textChunk ]); let response = undefined; for(let i = 0; i < textChunks.length; i++){ const chunk = textChunks[i]; const lastChunk = i === textChunks.length - 1; if (!response) { response = await this.complete({ prompt: textQATemplate.format({ context: chunk }), stream: stream && lastChunk }); } else { response = await this.refineResponseSingle(response, query, chunk, stream && lastChunk); } } return response; } async refineResponseSingle(initialReponse, query, textChunk, stream) { const refineTemplate = this.refineTemplate.partialFormat({ query: index_cjs$4.extractText(query) }); const textChunks = this.promptHelper.repack(refineTemplate, [ textChunk ]); let response = initialReponse; for(let i = 0; i < textChunks.length; i++){ const chunk = textChunks[i]; const lastChunk = i === textChunks.length - 1; response = await this.complete({ prompt: refineTemplate.format({ context: chunk, existingAnswer: response }), stream: stream && lastChunk }); } return response; } async complete(params) { if (params.stream) { const response = await this.llm.complete({ ...params, stream: true }); return index_cjs$4.streamConverter(response, (chunk)=>chunk.text); } else { const response = await this.llm.complete({ ...params, stream: false }); return response.text; } } } /** * CompactAndRefine is a slight variation of Refine that first compacts the text chunks into the smallest possible number of chunks. */ class CompactAndRefine extends Refine { async getResponse(query, nodes, stream) { const textQATemplate = this.textQATemplate.partialFormat({ query: index_cjs$4.extractText(query) }); const refineTemplate = this.refineTemplate.partialFormat({ query: index_cjs$4.extractText(query) }); const textChunks = nodes.map(({ node })=>node.getContent(index_cjs$3.MetadataMode.LLM)); const maxPrompt = index_cjs$2.getBiggestPrompt([ textQATemplate, refineTemplate ]); const newTexts = this.promptHelper.repack(maxPrompt, textChunks); const newNodes = newTexts.map((text)=>new index_cjs$3.TextNode({ text })); if (stream) { const streamResponse = await super.getResponse(query, newNodes.map((node)=>({ node })), true); return index_cjs$4.streamConverter(streamResponse, (chunk)=>{ chunk.sourceNodes = nodes; return chunk; }); } const originalResponse = await super.getResponse(query, newNodes.map((node)=>({ node })), false); originalResponse.sourceNodes = nodes; return originalResponse; } } /** * TreeSummarize repacks the text chunks into the smallest possible number of chunks and then summarizes them, then recursively does so until there's one chunk left. */ class TreeSummarize extends BaseSynthesizer { constructor(options){ super(options); this.summaryTemplate = options.summaryTemplate ?? index_cjs.defaultTreeSummarizePrompt; } _getPromptModules() { return {}; } _getPrompts() { return { summaryTemplate: this.summaryTemplate }; } _updatePrompts(prompts) { if (prompts.summaryTemplate) { this.summaryTemplate = prompts.summaryTemplate; } } async getResponse(query, nodes, stream) { const textChunks = nodes.map(({ node })=>node.getContent(index_cjs$3.MetadataMode.LLM)); if (!textChunks || textChunks.length === 0) { throw new Error("Must have at least one text chunk"); } // Should we send the query here too? const packedTextChunks = this.promptHelper.repack(this.summaryTemplate, textChunks); if (packedTextChunks.length === 1) { const params = { prompt: this.summaryTemplate.format({ context: packedTextChunks[0], query: index_cjs$4.extractText(query) }) }; if (stream) { const response = await this.llm.complete({ ...params, stream }); return index_cjs$4.streamConverter(response, (chunk)=>index_cjs$3.EngineResponse.fromResponse(chunk.text, true, nodes)); } return index_cjs$3.EngineResponse.fromResponse((await this.llm.complete(params)).text, false, nodes); } else { const summaries = await Promise.all(packedTextChunks.map((chunk)=>this.llm.complete({ prompt: this.summaryTemplate.format({ context: chunk, query: index_cjs$4.extractText(query) }) }))); if (stream) { return this.getResponse(query, summaries.map((s)=>({ node: new index_cjs$3.TextNode({ text: s.text }) })), true); } return this.getResponse(query, summaries.map((s)=>({ node: new index_cjs$3.TextNode({ text: s.text }) })), false); } } } class MultiModal extends BaseSynthesizer { constructor({ textQATemplate, metadataMode, ...options } = {}){ super(options); this.metadataMode = metadataMode ?? index_cjs$3.MetadataMode.NONE; this.textQATemplate = textQATemplate ?? index_cjs.defaultTextQAPrompt; } _getPromptModules() { return {}; } _getPrompts() { return { textQATemplate: this.textQATemplate }; } _updatePrompts(promptsDict) { if (promptsDict.textQATemplate) { this.textQATemplate = promptsDict.textQATemplate; } } async getResponse(query, nodes, stream) { const prompt = await createMessageContent(this.textQATemplate, nodes.map(({ node })=>node), // this might not be good as this remove the image information { query: index_cjs$4.extractText(query) }, this.metadataMode); const llm = this.llm; if (stream) { const response = await llm.complete({ prompt, stream }); return index_cjs$4.streamConverter(response, ({ text })=>index_cjs$3.EngineResponse.fromResponse(text, true, nodes)); } const response = await llm.complete({ prompt }); return index_cjs$3.EngineResponse.fromResponse(response.text, false, nodes); } } const modeToSynthesizer = { compact: CompactAndRefine, refine: Refine, tree_summarize: TreeSummarize, multi_modal: MultiModal }; function getResponseSynthesizer(mode, options = {}) { const Synthesizer = modeToSynthesizer[mode]; if (!Synthesizer) { throw new Error(`Invalid response mode: ${mode}`); } return new Synthesizer(options); } exports.BaseSynthesizer = BaseSynthesizer; exports.CompactAndRefine = CompactAndRefine; exports.MultiModal = MultiModal; exports.Refine = Refine; exports.TreeSummarize = TreeSummarize; exports.createMessageContent = createMessageContent; exports.getResponseSynthesizer = getResponseSynthesizer; exports.responseModeSchema = responseModeSchema;