mirror of
https://github.com/run-llama/LlamaIndexTS.git
synced 2026-07-15 06:52:45 -04:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a517415034 | |||
| 865af67610 | |||
| 5869b75959 | |||
| 2bbd641c0a | |||
| 01482a2038 | |||
| 6eea759c05 | |||
| efa5b01a63 | |||
| bc48f8f30b | |||
| 6a4fe6ee3d | |||
| 52f5c9c58f | |||
| 5cbe6ef871 | |||
| 9b740c5144 | |||
| ad9707e479 |
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
Document,
|
||||
Prompt,
|
||||
ResponseSynthesizer,
|
||||
TreeSummarize,
|
||||
TreeSummarizePrompt,
|
||||
@@ -7,16 +8,15 @@ import {
|
||||
serviceContextFromDefaults,
|
||||
} from "llamaindex";
|
||||
|
||||
const treeSummarizePrompt: TreeSummarizePrompt = ({ context, query }) => {
|
||||
return `Context information from multiple sources is below.
|
||||
const treeSummarizePrompt: TreeSummarizePrompt =
|
||||
new Prompt(`Context information from multiple sources is below.
|
||||
---------------------
|
||||
${context}
|
||||
{{context}}
|
||||
---------------------
|
||||
Given the information from multiple sources and not prior knowledge.
|
||||
Answer the query in the style of a Shakespeare play"
|
||||
Query: ${query}
|
||||
Answer:`;
|
||||
};
|
||||
Query: {{query}}
|
||||
Answer:`);
|
||||
|
||||
async function main() {
|
||||
const documents = new Document({
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import {
|
||||
CompactAndRefine,
|
||||
OpenAI,
|
||||
Prompt,
|
||||
ResponseSynthesizer,
|
||||
serviceContextFromDefaults,
|
||||
VectorStoreIndex,
|
||||
serviceContextFromDefaults,
|
||||
} from "llamaindex";
|
||||
import { PapaCSVReader } from "llamaindex/readers";
|
||||
|
||||
@@ -22,14 +23,12 @@ async function main() {
|
||||
serviceContext,
|
||||
});
|
||||
|
||||
const csvPrompt = ({ context = "", query = "" }) => {
|
||||
return `The following CSV file is loaded from ${path}
|
||||
const csvPrompt = new Prompt(`The following CSV file is loaded from {{path}}
|
||||
\`\`\`csv
|
||||
${context}
|
||||
{{context}}
|
||||
\`\`\`
|
||||
Given the CSV file, generate me Typescript code to answer the question: ${query}. You can use built in NodeJS functions but avoid using third party libraries.
|
||||
`;
|
||||
};
|
||||
Given the CSV file, generate me Typescript code to answer the question: {{query}. You can use built in NodeJS functions but avoid using third party libraries.
|
||||
`);
|
||||
|
||||
const responseSynthesizer = new ResponseSynthesizer({
|
||||
responseBuilder: new CompactAndRefine(serviceContext, csvPrompt),
|
||||
|
||||
@@ -2,14 +2,16 @@ import fs from "node:fs/promises";
|
||||
|
||||
import {
|
||||
Anthropic,
|
||||
anthropicTextQaPrompt,
|
||||
CompactAndRefine,
|
||||
Document,
|
||||
ResponseSynthesizer,
|
||||
serviceContextFromDefaults,
|
||||
Settings,
|
||||
VectorStoreIndex,
|
||||
serviceContextFromDefaults,
|
||||
} from "llamaindex";
|
||||
|
||||
Settings.prompt.llm = "claude";
|
||||
|
||||
async function main() {
|
||||
// Load essay from abramov.txt in Node
|
||||
const path = "node_modules/llamaindex/examples/abramov.txt";
|
||||
@@ -23,10 +25,7 @@ async function main() {
|
||||
const serviceContext = serviceContextFromDefaults({ llm: new Anthropic() });
|
||||
|
||||
const responseSynthesizer = new ResponseSynthesizer({
|
||||
responseBuilder: new CompactAndRefine(
|
||||
serviceContext,
|
||||
anthropicTextQaPrompt,
|
||||
),
|
||||
responseBuilder: new CompactAndRefine(serviceContext),
|
||||
});
|
||||
|
||||
const index = await VectorStoreIndex.fromDocuments([document], {
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
"mammoth": "^1.6.0",
|
||||
"md-utils-ts": "^2.0.0",
|
||||
"mongodb": "^6.3.0",
|
||||
"mustache": "^4.2.0",
|
||||
"notion-md-crawler": "^0.0.2",
|
||||
"openai": "^4.26.1",
|
||||
"papaparse": "^5.4.1",
|
||||
@@ -41,12 +42,14 @@
|
||||
"rake-modified": "^1.0.8",
|
||||
"replicate": "^0.25.2",
|
||||
"string-strip-html": "^13.4.6",
|
||||
"wikipedia": "^2.1.2",
|
||||
"wink-nlp": "^1.14.3",
|
||||
"wikipedia": "^2.1.2"
|
||||
"yaml": "^2.4.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@swc/cli": "^0.3.9",
|
||||
"@swc/core": "^1.4.2",
|
||||
"@types/mustache": "^4.2.5",
|
||||
"concurrently": "^8.2.2",
|
||||
"glob": "^10.3.10",
|
||||
"madge": "^6.1.0",
|
||||
|
||||
@@ -96,7 +96,7 @@ export class SummaryChatHistory extends ChatHistory {
|
||||
do {
|
||||
promptMessages = [
|
||||
{
|
||||
content: this.summaryPrompt({
|
||||
content: this.summaryPrompt.format({
|
||||
context: messagesToHistoryStr(messagesToSummarize),
|
||||
}),
|
||||
role: "user" as MessageType,
|
||||
|
||||
+79
-102
@@ -1,5 +1,8 @@
|
||||
import type { SubQuestion } from "./engines/query/types.js";
|
||||
import type { ChatMessage } from "./llm/types.js";
|
||||
|
||||
import { Prompt } from "./prompts/types.js";
|
||||
|
||||
import type { ToolMetadata } from "./types.js";
|
||||
|
||||
/**
|
||||
@@ -24,67 +27,42 @@ DEFAULT_TEXT_QA_PROMPT_TMPL = (
|
||||
)
|
||||
*/
|
||||
|
||||
export const defaultTextQaPrompt = ({ context = "", query = "" }) => {
|
||||
return `Context information is below.
|
||||
---------------------
|
||||
${context}
|
||||
---------------------
|
||||
Given the context information and not prior knowledge, answer the query.
|
||||
Query: ${query}
|
||||
Answer:`;
|
||||
};
|
||||
export const defaultTextQaPrompt = new Prompt(`
|
||||
default: >
|
||||
Context information is below.
|
||||
---------------------
|
||||
{{context}}
|
||||
---------------------
|
||||
Given the context information and not prior knowledge, answer the query.
|
||||
Query: {{query}}
|
||||
Answer:
|
||||
llm-claude: >
|
||||
Context information:
|
||||
<context>
|
||||
{{context}}
|
||||
</context>
|
||||
Given the context information and not prior knowledge, answer the query.
|
||||
Query: {{query}};
|
||||
`);
|
||||
|
||||
export type TextQaPrompt = typeof defaultTextQaPrompt;
|
||||
export const defaultSummaryPrompt = new Prompt(`
|
||||
default: >
|
||||
Write a summary of the following. Try to use only the information provided. Try to include as many key details as possible.
|
||||
|
||||
export const anthropicTextQaPrompt: TextQaPrompt = ({
|
||||
context = "",
|
||||
query = "",
|
||||
}) => {
|
||||
return `Context information:
|
||||
<context>
|
||||
${context}
|
||||
</context>
|
||||
Given the context information and not prior knowledge, answer the query.
|
||||
Query: ${query}`;
|
||||
};
|
||||
{{context}}
|
||||
|
||||
/*
|
||||
DEFAULT_SUMMARY_PROMPT_TMPL = (
|
||||
"Write a summary of the following. Try to use only the "
|
||||
"information provided. "
|
||||
"Try to include as many key details as possible.\n"
|
||||
"\n"
|
||||
"\n"
|
||||
"{context_str}\n"
|
||||
"\n"
|
||||
"\n"
|
||||
'SUMMARY:"""\n'
|
||||
)
|
||||
*/
|
||||
SUMMARY:"""
|
||||
llm-claude: >
|
||||
Summarize the following text. Try to use only the information provided. Try to include as many key details as possible.
|
||||
<original-text>
|
||||
{{context}}
|
||||
</original-text>
|
||||
|
||||
export const defaultSummaryPrompt = ({ context = "" }) => {
|
||||
return `Write a summary of the following. Try to use only the information provided. Try to include as many key details as possible.
|
||||
|
||||
|
||||
${context}
|
||||
|
||||
|
||||
SUMMARY:"""
|
||||
`;
|
||||
};
|
||||
SUMMARY:
|
||||
`);
|
||||
|
||||
export type SummaryPrompt = typeof defaultSummaryPrompt;
|
||||
|
||||
export const anthropicSummaryPrompt: SummaryPrompt = ({ context = "" }) => {
|
||||
return `Summarize the following text. Try to use only the information provided. Try to include as many key details as possible.
|
||||
<original-text>
|
||||
${context}
|
||||
</original-text>
|
||||
|
||||
SUMMARY:
|
||||
`;
|
||||
};
|
||||
|
||||
/*
|
||||
DEFAULT_REFINE_PROMPT_TMPL = (
|
||||
"The original query is as follows: {query_str}\n"
|
||||
@@ -101,20 +79,17 @@ DEFAULT_REFINE_PROMPT_TMPL = (
|
||||
)
|
||||
*/
|
||||
|
||||
export const defaultRefinePrompt = ({
|
||||
query = "",
|
||||
existingAnswer = "",
|
||||
context = "",
|
||||
}) => {
|
||||
return `The original query is as follows: ${query}
|
||||
We have provided an existing answer: ${existingAnswer}
|
||||
We have the opportunity to refine the existing answer (only if needed) with some more context below.
|
||||
------------
|
||||
${context}
|
||||
------------
|
||||
Given the new context, refine the original answer to better answer the query. If the context isn't useful, return the original answer.
|
||||
Refined Answer:`;
|
||||
};
|
||||
export const defaultRefinePrompt = new Prompt(`
|
||||
default: >
|
||||
The original query is as follows: {{query}}
|
||||
We have provided an existing answer: {{existingAnswer}}
|
||||
We have the opportunity to refine the existing answer (only if needed) with some more context below.
|
||||
------------
|
||||
{{context}}
|
||||
------------
|
||||
Given the new context, refine the original answer to better answer the query. If the context isn't useful, return the original answer.
|
||||
Refined Answer:
|
||||
`);
|
||||
|
||||
export type RefinePrompt = typeof defaultRefinePrompt;
|
||||
|
||||
@@ -131,49 +106,51 @@ DEFAULT_TREE_SUMMARIZE_TMPL = (
|
||||
)
|
||||
*/
|
||||
|
||||
export const defaultTreeSummarizePrompt = ({ context = "", query = "" }) => {
|
||||
return `Context information from multiple sources is below.
|
||||
---------------------
|
||||
${context}
|
||||
---------------------
|
||||
Given the information from multiple sources and not prior knowledge, answer the query.
|
||||
Query: ${query}
|
||||
Answer:`;
|
||||
};
|
||||
export const defaultTreeSummarizePrompt = new Prompt(`
|
||||
default: >
|
||||
Context information from multiple sources is below.
|
||||
---------------------
|
||||
{{context}}
|
||||
---------------------
|
||||
Given the information from multiple sources and not prior knowledge, answer the query.
|
||||
Query: {{query}}
|
||||
Answer:
|
||||
`);
|
||||
|
||||
export type TreeSummarizePrompt = typeof defaultTreeSummarizePrompt;
|
||||
|
||||
export const defaultChoiceSelectPrompt = ({ context = "", query = "" }) => {
|
||||
return `A list of documents is shown below. Each document has a number next to it along
|
||||
with a summary of the document. A question is also provided.
|
||||
Respond with the numbers of the documents
|
||||
you should consult to answer the question, in order of relevance, as well
|
||||
as the relevance score. The relevance score is a number from 1-10 based on
|
||||
how relevant you think the document is to the question.
|
||||
Do not include any documents that are not relevant to the question.
|
||||
Example format:
|
||||
Document 1:
|
||||
<summary of document 1>
|
||||
export const defaultChoiceSelectPrompt = new Prompt(`
|
||||
default: >
|
||||
A list of documents is shown below. Each document has a number next to it along
|
||||
with a summary of the document. A question is also provided.
|
||||
Respond with the numbers of the documents
|
||||
you should consult to answer the question, in order of relevance, as well
|
||||
as the relevance score. The relevance score is a number from 1-10 based on
|
||||
how relevant you think the document is to the question.
|
||||
Do not include any documents that are not relevant to the question.
|
||||
Example format:
|
||||
Document 1:
|
||||
<summary of document 1>
|
||||
|
||||
Document 2:
|
||||
<summary of document 2>
|
||||
Document 2:
|
||||
<summary of document 2>
|
||||
|
||||
...
|
||||
...
|
||||
|
||||
Document 10:\n<summary of document 10>
|
||||
Document 10:\n<summary of document 10>
|
||||
|
||||
Question: <question>
|
||||
Answer:
|
||||
Doc: 9, Relevance: 7
|
||||
Doc: 3, Relevance: 4
|
||||
Doc: 7, Relevance: 3
|
||||
Question: <question>
|
||||
Answer:
|
||||
Doc: 9, Relevance: 7
|
||||
Doc: 3, Relevance: 4
|
||||
Doc: 7, Relevance: 3
|
||||
|
||||
Let's try this now:
|
||||
Let's try this now:
|
||||
|
||||
${context}
|
||||
Question: ${query}
|
||||
Answer:`;
|
||||
};
|
||||
{{context}}
|
||||
Question: {{query}}
|
||||
Answer:
|
||||
`);
|
||||
|
||||
export type ChoiceSelectPrompt = typeof defaultChoiceSelectPrompt;
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { globalsHelper } from "./GlobalsHelper.js";
|
||||
import type { SimplePrompt } from "./Prompt.js";
|
||||
import { SentenceSplitter } from "./TextSplitter.js";
|
||||
import {
|
||||
DEFAULT_CHUNK_OVERLAP_RATIO,
|
||||
@@ -8,8 +7,18 @@ import {
|
||||
DEFAULT_PADDING,
|
||||
} from "./constants.js";
|
||||
|
||||
export function getEmptyPromptTxt(prompt: SimplePrompt) {
|
||||
return prompt({});
|
||||
import { Prompt } from "./prompts/types.js";
|
||||
|
||||
import { messagesToPrompt } from "./prompts/utils.js";
|
||||
|
||||
export function getEmptyPromptTxt(prompt: Prompt) {
|
||||
const emptyPrompt = prompt.format({});
|
||||
|
||||
if (Array.isArray(emptyPrompt)) {
|
||||
return messagesToPrompt(emptyPrompt);
|
||||
}
|
||||
|
||||
return emptyPrompt;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -18,7 +27,7 @@ export function getEmptyPromptTxt(prompt: SimplePrompt) {
|
||||
* @param prompts
|
||||
* @returns
|
||||
*/
|
||||
export function getBiggestPrompt(prompts: SimplePrompt[]) {
|
||||
export function getBiggestPrompt(prompts: Prompt[]) {
|
||||
const emptyPromptTexts = prompts.map(getEmptyPromptTxt);
|
||||
const emptyPromptLengths = emptyPromptTexts.map((text) => text.length);
|
||||
const maxEmptyPromptLength = Math.max(...emptyPromptLengths);
|
||||
@@ -59,7 +68,7 @@ export class PromptHelper {
|
||||
* @param prompt
|
||||
* @returns
|
||||
*/
|
||||
private getAvailableContextSize(prompt: SimplePrompt) {
|
||||
private getAvailableContextSize(prompt: Prompt) {
|
||||
const emptyPromptText = getEmptyPromptTxt(prompt);
|
||||
const promptTokens = this.tokenizer(emptyPromptText);
|
||||
const numPromptTokens = promptTokens.length;
|
||||
@@ -74,11 +83,7 @@ export class PromptHelper {
|
||||
* @param padding
|
||||
* @returns
|
||||
*/
|
||||
private getAvailableChunkSize(
|
||||
prompt: SimplePrompt,
|
||||
numChunks = 1,
|
||||
padding = 5,
|
||||
) {
|
||||
private getAvailableChunkSize(prompt: Prompt, numChunks = 1, padding = 5) {
|
||||
const availableContextSize = this.getAvailableContextSize(prompt);
|
||||
|
||||
const result = Math.floor(availableContextSize / numChunks) - padding;
|
||||
@@ -98,7 +103,7 @@ export class PromptHelper {
|
||||
* @returns
|
||||
*/
|
||||
getTextSplitterGivenPrompt(
|
||||
prompt: SimplePrompt,
|
||||
prompt: Prompt,
|
||||
numChunks = 1,
|
||||
padding = DEFAULT_PADDING,
|
||||
) {
|
||||
@@ -118,11 +123,7 @@ export class PromptHelper {
|
||||
* @param padding
|
||||
* @returns
|
||||
*/
|
||||
repack(
|
||||
prompt: SimplePrompt,
|
||||
textChunks: string[],
|
||||
padding = DEFAULT_PADDING,
|
||||
) {
|
||||
repack(prompt: Prompt, textChunks: string[], padding = DEFAULT_PADDING) {
|
||||
const textSplitter = this.getTextSplitterGivenPrompt(prompt, 1, padding);
|
||||
const combinedStr = textChunks.join("\n\n");
|
||||
return textSplitter.splitText(combinedStr);
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
type PromptConfig = {
|
||||
llm?: string;
|
||||
lang?: string;
|
||||
};
|
||||
|
||||
interface Config {
|
||||
prompt: PromptConfig;
|
||||
}
|
||||
|
||||
// Determine the global object based on the environment
|
||||
const globalObject: any =
|
||||
typeof window !== "undefined"
|
||||
? window
|
||||
: typeof global !== "undefined"
|
||||
? global
|
||||
: {};
|
||||
|
||||
// Initialize or access a global config object
|
||||
const globalConfigKey = "__GLOBAL_LITS__";
|
||||
|
||||
if (!globalObject[globalConfigKey]) {
|
||||
globalObject[globalConfigKey] = {
|
||||
prompt: {},
|
||||
} satisfies Config;
|
||||
}
|
||||
|
||||
export const Settings: Config = globalObject[globalConfigKey];
|
||||
@@ -68,11 +68,11 @@ export class CorrectnessEvaluator extends PromptMixin implements BaseEvaluator {
|
||||
const messages: ChatMessage[] = [
|
||||
{
|
||||
role: "system",
|
||||
content: this.correctnessPrompt(),
|
||||
content: this.correctnessPrompt.format({}),
|
||||
},
|
||||
{
|
||||
role: "user",
|
||||
content: defaultUserPrompt({
|
||||
content: defaultUserPrompt.format({
|
||||
query,
|
||||
generatedAnswer: response,
|
||||
referenceAnswer: reference || "(NO REFERENCE ANSWER SUPPLIED)",
|
||||
|
||||
@@ -1,155 +1,130 @@
|
||||
export const defaultUserPrompt = ({
|
||||
query,
|
||||
referenceAnswer,
|
||||
generatedAnswer,
|
||||
}: {
|
||||
query: string;
|
||||
referenceAnswer: string;
|
||||
generatedAnswer: string;
|
||||
}) => `
|
||||
## User Query
|
||||
${query}
|
||||
import { Prompt } from "./../prompts/types.js";
|
||||
|
||||
## Reference Answer
|
||||
${referenceAnswer}
|
||||
export const defaultUserPrompt = new Prompt(`
|
||||
default: >
|
||||
## User Query
|
||||
{{query}}
|
||||
|
||||
## Generated Answer
|
||||
${generatedAnswer}
|
||||
`;
|
||||
## Reference Answer
|
||||
{{referenceAnswer}}
|
||||
|
||||
## Generated Answer
|
||||
{{generatedAnswer}}
|
||||
`);
|
||||
|
||||
export type UserPrompt = typeof defaultUserPrompt;
|
||||
|
||||
export const defaultCorrectnessSystemPrompt =
|
||||
() => `You are an expert evaluation system for a question answering chatbot.
|
||||
export const defaultCorrectnessSystemPrompt = new Prompt(`
|
||||
default: >
|
||||
You are an expert evaluation system for a question answering chatbot.
|
||||
|
||||
You are given the following information:
|
||||
- a user query, and
|
||||
- a generated answer
|
||||
You are given the following information:
|
||||
- a user query, and
|
||||
- a generated answer
|
||||
|
||||
You may also be given a reference answer to use for reference in your evaluation.
|
||||
You may also be given a reference answer to use for reference in your evaluation.
|
||||
|
||||
Your job is to judge the relevance and correctness of the generated answer.
|
||||
Output a single score that represents a holistic evaluation.
|
||||
You must return your response in a line with only the score.
|
||||
Do not return answers in any other format.
|
||||
On a separate line provide your reasoning for the score as well.
|
||||
Your job is to judge the relevance and correctness of the generated answer.
|
||||
Output a single score that represents a holistic evaluation.
|
||||
You must return your response in a line with only the score.
|
||||
Do not return answers in any other format.
|
||||
On a separate line provide your reasoning for the score as well.
|
||||
|
||||
Follow these guidelines for scoring:
|
||||
- Your score has to be between 1 and 5, where 1 is the worst and 5 is the best.
|
||||
- If the generated answer is not relevant to the user query,
|
||||
you should give a score of 1.
|
||||
- If the generated answer is relevant but contains mistakes,
|
||||
you should give a score between 2 and 3.
|
||||
- If the generated answer is relevant and fully correct,
|
||||
you should give a score between 4 and 5.
|
||||
Follow these guidelines for scoring:
|
||||
- Your score has to be between 1 and 5, where 1 is the worst and 5 is the best.
|
||||
- If the generated answer is not relevant to the user query,
|
||||
you should give a score of 1.
|
||||
- If the generated answer is relevant but contains mistakes,
|
||||
you should give a score between 2 and 3.
|
||||
- If the generated answer is relevant and fully correct,
|
||||
you should give a score between 4 and 5.
|
||||
|
||||
Example Response:
|
||||
4.0
|
||||
The generated answer has the exact same metrics as the reference answer
|
||||
but it is not as concise.
|
||||
`;
|
||||
Example Response:
|
||||
4.0
|
||||
The generated answer has the exact same metrics as the reference answer
|
||||
but it is not as concise.
|
||||
`);
|
||||
|
||||
export type CorrectnessSystemPrompt = typeof defaultCorrectnessSystemPrompt;
|
||||
|
||||
export const defaultFaithfulnessRefinePrompt = ({
|
||||
query,
|
||||
context,
|
||||
existingAnswer,
|
||||
}: {
|
||||
query: string;
|
||||
context: string;
|
||||
existingAnswer: string;
|
||||
}) => `
|
||||
We want to understand if the following information is present
|
||||
in the context information: ${query}
|
||||
We have provided an existing YES/NO answer: ${existingAnswer}
|
||||
We have the opportunity to refine the existing answer
|
||||
(only if needed) with some more context below.
|
||||
------------
|
||||
${context}
|
||||
------------
|
||||
If the existing answer was already YES, still answer YES.
|
||||
If the information is present in the new context, answer YES.
|
||||
Otherwise answer NO.
|
||||
`;
|
||||
export const defaultFaithfulnessRefinePrompt = new Prompt(`
|
||||
default: >
|
||||
We want to understand if the following information is present
|
||||
in the context information: {{query}}
|
||||
We have provided an existing YES/NO answer: {{existingAnswer}}
|
||||
We have the opportunity to refine the existing answer
|
||||
(only if needed) with some more context below.
|
||||
------------
|
||||
{{context}}
|
||||
------------
|
||||
If the existing answer was already YES, still answer YES.
|
||||
If the information is present in the new context, answer YES.
|
||||
Otherwise answer NO.
|
||||
`);
|
||||
|
||||
export type FaithfulnessRefinePrompt = typeof defaultFaithfulnessRefinePrompt;
|
||||
|
||||
export const defaultFaithfulnessTextQaPrompt = ({
|
||||
query,
|
||||
context,
|
||||
}: {
|
||||
query: string;
|
||||
context: string;
|
||||
}) => `
|
||||
Please tell if a given piece of information
|
||||
is supported by the context.
|
||||
You need to answer with either YES or NO.
|
||||
Answer YES if any of the context supports the information, even
|
||||
if most of the context is unrelated.
|
||||
Some examples are provided below.
|
||||
export const defaultFaithfulnessTextQaPrompt = new Prompt(`
|
||||
default: >
|
||||
Please tell if a given piece of information
|
||||
is supported by the context.
|
||||
You need to answer with either YES or NO.
|
||||
Answer YES if any of the context supports the information, even
|
||||
if most of the context is unrelated.
|
||||
Some examples are provided below.
|
||||
|
||||
Information: Apple pie is generally double-crusted.
|
||||
Context: An apple pie is a fruit pie in which the principal filling
|
||||
ingredient is apples.
|
||||
Apple pie is often served with whipped cream, ice cream
|
||||
('apple pie à la mode'), custard or cheddar cheese.
|
||||
It is generally double-crusted, with pastry both above
|
||||
and below the filling; the upper crust may be solid or
|
||||
latticed (woven of crosswise strips).
|
||||
Answer: YES
|
||||
Information: Apple pies tastes bad.
|
||||
Context: An apple pie is a fruit pie in which the principal filling
|
||||
ingredient is apples.
|
||||
Apple pie is often served with whipped cream, ice cream
|
||||
('apple pie à la mode'), custard or cheddar cheese.
|
||||
It is generally double-crusted, with pastry both above
|
||||
and below the filling; the upper crust may be solid or
|
||||
latticed (woven of crosswise strips).
|
||||
Answer: NO
|
||||
Information: ${query}
|
||||
Context: ${context}
|
||||
Answer:
|
||||
`;
|
||||
Information: Apple pie is generally double-crusted.
|
||||
Context: An apple pie is a fruit pie in which the principal filling
|
||||
ingredient is apples.
|
||||
Apple pie is often served with whipped cream, ice cream
|
||||
('apple pie à la mode'), custard or cheddar cheese.
|
||||
It is generally double-crusted, with pastry both above
|
||||
and below the filling; the upper crust may be solid or
|
||||
latticed (woven of crosswise strips).
|
||||
Answer: YES
|
||||
Information: Apple pies tastes bad.
|
||||
Context: An apple pie is a fruit pie in which the principal filling
|
||||
ingredient is apples.
|
||||
Apple pie is often served with whipped cream, ice cream
|
||||
('apple pie à la mode'), custard or cheddar cheese.
|
||||
It is generally double-crusted, with pastry both above
|
||||
and below the filling; the upper crust may be solid or
|
||||
latticed (woven of crosswise strips).
|
||||
Answer: NO
|
||||
Information: {{query}}
|
||||
Context: {{context}}
|
||||
Answer:
|
||||
`);
|
||||
|
||||
export type FaithfulnessTextQAPrompt = typeof defaultFaithfulnessTextQaPrompt;
|
||||
|
||||
export const defaultRelevancyEvalPrompt = ({
|
||||
query,
|
||||
context,
|
||||
}: {
|
||||
query: string;
|
||||
context: string;
|
||||
}) => `Your task is to evaluate if the response for the query is in line with the context information provided.
|
||||
You have two options to answer. Either YES/ NO.
|
||||
Answer - YES, if the response for the query is in line with context information otherwise NO.
|
||||
Query and Response: ${query}
|
||||
Context: ${context}
|
||||
Answer: `;
|
||||
export const defaultRelevancyEvalPrompt = new Prompt(`
|
||||
default: >
|
||||
Your task is to evaluate if the response for the query is in line with the context information provided.
|
||||
You have two options to answer. Either YES/ NO.
|
||||
Answer - YES, if the response for the query is in line with context information otherwise NO.
|
||||
Query and Response: {{query}}
|
||||
Context: {{context}}
|
||||
Answer:
|
||||
`);
|
||||
|
||||
export type RelevancyEvalPrompt = typeof defaultRelevancyEvalPrompt;
|
||||
|
||||
export const defaultRelevancyRefinePrompt = ({
|
||||
query,
|
||||
existingAnswer,
|
||||
contextMsg,
|
||||
}: {
|
||||
query: string;
|
||||
existingAnswer: string;
|
||||
contextMsg: string;
|
||||
}) => `We want to understand if the following query and response is
|
||||
in line with the context information:
|
||||
${query}
|
||||
We have provided an existing YES/NO answer:
|
||||
${existingAnswer}
|
||||
We have the opportunity to refine the existing answer
|
||||
(only if needed) with some more context below.
|
||||
------------
|
||||
${contextMsg}
|
||||
------------
|
||||
If the existing answer was already YES, still answer YES.
|
||||
If the information is present in the new context, answer YES.
|
||||
Otherwise answer NO.
|
||||
`;
|
||||
export const defaultRelevancyRefinePrompt = new Prompt(`
|
||||
default: >
|
||||
We want to understand if the following query and response is
|
||||
in line with the context information:
|
||||
{{query}}
|
||||
We have provided an existing YES/NO answer:
|
||||
{{existingAnswer}}
|
||||
We have the opportunity to refine the existing answer
|
||||
(only if needed) with some more context below.
|
||||
------------
|
||||
{{contextMsg}}
|
||||
------------
|
||||
If the existing answer was already YES, still answer YES.
|
||||
If the information is present in the new context, answer YES.
|
||||
Otherwise answer NO.
|
||||
`);
|
||||
|
||||
export type RelevancyRefinePrompt = typeof defaultRelevancyRefinePrompt;
|
||||
|
||||
@@ -8,6 +8,7 @@ export * from "./QuestionGenerator.js";
|
||||
export * from "./Response.js";
|
||||
export * from "./Retriever.js";
|
||||
export * from "./ServiceContext.js";
|
||||
export * from "./Settings.js";
|
||||
export * from "./TextSplitter.js";
|
||||
export * from "./agent/index.js";
|
||||
export * from "./callbacks/CallbackManager.js";
|
||||
|
||||
@@ -354,7 +354,7 @@ export class SummaryIndexLLMRetriever implements BaseRetriever {
|
||||
const input = { context: fmtBatchStr, query: query };
|
||||
const rawResponse = (
|
||||
await this.serviceContext.llm.complete({
|
||||
prompt: this.choiceSelectPrompt(input),
|
||||
prompt: this.choiceSelectPrompt.format(input),
|
||||
})
|
||||
).text;
|
||||
|
||||
|
||||
@@ -10,11 +10,75 @@ import type {
|
||||
LLMCompletionParamsStreaming,
|
||||
LLMMetadata,
|
||||
} from "./types.js";
|
||||
|
||||
import { streamConverter } from "./utils.js";
|
||||
|
||||
export abstract class BaseLLM implements LLM {
|
||||
abstract metadata: LLMMetadata;
|
||||
|
||||
predict(
|
||||
params: LLMCompletionParamsStreaming,
|
||||
): Promise<AsyncIterable<CompletionResponse>>;
|
||||
predict(params: LLMCompletionParamsNonStreaming): Promise<CompletionResponse>;
|
||||
async predict(
|
||||
params: LLMCompletionParamsStreaming | LLMCompletionParamsNonStreaming,
|
||||
): Promise<CompletionResponse | AsyncIterable<CompletionResponse>> {
|
||||
const { prompt, parentEvent, stream } = params;
|
||||
|
||||
if (Array.isArray(prompt)) {
|
||||
if (stream) {
|
||||
const stream = await this.chat({
|
||||
messages: prompt,
|
||||
parentEvent,
|
||||
stream: true,
|
||||
});
|
||||
return streamConverter(stream, (chunk) => {
|
||||
return {
|
||||
text: chunk.delta,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
const chatResponse = await this.chat({
|
||||
messages: prompt,
|
||||
parentEvent,
|
||||
});
|
||||
return { text: chatResponse.message.content as string };
|
||||
}
|
||||
|
||||
if (typeof prompt === "string") {
|
||||
if (stream) {
|
||||
const stream = await this.complete({
|
||||
prompt,
|
||||
parentEvent,
|
||||
stream: true,
|
||||
});
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
return this.complete({
|
||||
prompt,
|
||||
parentEvent,
|
||||
});
|
||||
}
|
||||
|
||||
if (stream) {
|
||||
const stream = await this.complete({
|
||||
prompt,
|
||||
parentEvent,
|
||||
stream: true,
|
||||
});
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
return this.complete({
|
||||
prompt,
|
||||
parentEvent,
|
||||
});
|
||||
}
|
||||
|
||||
complete(
|
||||
params: LLMCompletionParamsStreaming,
|
||||
): Promise<AsyncIterable<CompletionResponse>>;
|
||||
|
||||
@@ -64,6 +64,16 @@ export class Ollama extends BaseEmbedding implements LLM {
|
||||
};
|
||||
}
|
||||
|
||||
predict(
|
||||
params: LLMCompletionParamsStreaming,
|
||||
): Promise<AsyncIterable<CompletionResponse>>;
|
||||
predict(params: LLMCompletionParamsNonStreaming): Promise<CompletionResponse>;
|
||||
predict(
|
||||
params: unknown,
|
||||
): Promise<AsyncIterable<CompletionResponse>> | Promise<CompletionResponse> {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
|
||||
chat(
|
||||
params: LLMChatParamsStreaming,
|
||||
): Promise<AsyncIterable<ChatResponseChunk>>;
|
||||
|
||||
@@ -6,6 +6,15 @@ import type { Event } from "../callbacks/CallbackManager.js";
|
||||
*/
|
||||
export interface LLM {
|
||||
metadata: LLMMetadata;
|
||||
/**
|
||||
* Predict the next completion from the LLM
|
||||
* *
|
||||
* @param params
|
||||
*/
|
||||
predict(
|
||||
params: LLMCompletionParamsStreaming,
|
||||
): Promise<AsyncIterable<CompletionResponse>>;
|
||||
predict(params: LLMCompletionParamsNonStreaming): Promise<CompletionResponse>;
|
||||
/**
|
||||
* Get a chat response from the LLM
|
||||
*
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
export * from "./Mixin.js";
|
||||
export * from "./types.js";
|
||||
export * from "./utils.js";
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
import type { ChatMessage } from "../llm/types.js";
|
||||
|
||||
import { Settings } from "../Settings.js";
|
||||
|
||||
import mustache from "mustache";
|
||||
|
||||
import * as yaml from "yaml";
|
||||
|
||||
interface Env {
|
||||
llm?: string;
|
||||
lang?: string;
|
||||
}
|
||||
|
||||
type Inputs = Record<string, string>;
|
||||
|
||||
type FunctionResult = string | ChatMessage[];
|
||||
|
||||
interface ParsedTemplate {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export class Prompt {
|
||||
template: string;
|
||||
|
||||
constructor(template: string) {
|
||||
this.template = template;
|
||||
}
|
||||
|
||||
format(inputs: Inputs, env?: Env): FunctionResult {
|
||||
const parsedTemplate: ParsedTemplate = yaml.parse(this.template);
|
||||
|
||||
let templateSection = null;
|
||||
let defaultSection = null;
|
||||
|
||||
// Determine the language and LLM settings to use, either from the environment or the global settings.
|
||||
const envLang = env?.lang || Settings?.prompt?.lang;
|
||||
const envLlm = env?.llm || Settings?.prompt?.llm;
|
||||
|
||||
// Regular expressions for matching language and LLM keys, including defaults.
|
||||
const langRegex = new RegExp(`^lang-(${envLang}|default)$`, "i");
|
||||
const llmRegex = new RegExp(`^llm-(${envLlm}|default)$`, "i");
|
||||
|
||||
// First, look for language matches or language defaults.
|
||||
Object.entries(parsedTemplate).forEach(([key, value]) => {
|
||||
if (langRegex.test(key)) {
|
||||
templateSection = value; // Select the matching language section or a default language section.
|
||||
|
||||
// Then, within the selected language section, look for LLM matches or LLM defaults.
|
||||
if (templateSection && typeof templateSection === "object") {
|
||||
Object.entries(templateSection).forEach(
|
||||
([nestedKey, nestedValue]) => {
|
||||
if (llmRegex.test(nestedKey)) {
|
||||
templateSection = nestedValue; // Further refine to LLM section if available, including defaults.
|
||||
} else if (/^default$/.test(nestedKey)) {
|
||||
templateSection = nestedValue;
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
} else if (llmRegex.test(key)) {
|
||||
templateSection = value;
|
||||
} else if (/^default$/.test(key)) {
|
||||
// Keep track of a root-level default to use if no other matches are found.
|
||||
defaultSection = value;
|
||||
}
|
||||
});
|
||||
|
||||
// If no specific matches were found, use the root-level default section if available.
|
||||
if (!templateSection && defaultSection) {
|
||||
templateSection = defaultSection;
|
||||
} else if (!templateSection && !defaultSection) {
|
||||
// If no matches were found, set a single prompt
|
||||
templateSection = parsedTemplate;
|
||||
}
|
||||
|
||||
// Process the selected template section for message rendering or other logic.
|
||||
if (templateSection && templateSection["messages"]) {
|
||||
const result: ChatMessage[] = [];
|
||||
for (const message of templateSection["messages"] as ChatMessage[]) {
|
||||
result.push({
|
||||
content: mustache.render(message.content, inputs),
|
||||
role: message.role,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
} else if (typeof templateSection === "string") {
|
||||
const renderedResult = mustache.render(templateSection, inputs);
|
||||
return renderedResult;
|
||||
} else {
|
||||
throw new Error("Invalid template section");
|
||||
}
|
||||
}
|
||||
|
||||
partial(inputs: Record<string, string>): Prompt {
|
||||
const template = mustache.render(this.template, inputs);
|
||||
return new Prompt(template);
|
||||
}
|
||||
|
||||
toJSON(): Record<string, any> {
|
||||
return {
|
||||
template: this.template,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import type { ChatMessage } from "../llm/types.js";
|
||||
|
||||
export const messagesToPrompt = (messages: ChatMessage[]): string => {
|
||||
const stringMessages = [];
|
||||
for (const message of messages) {
|
||||
const role = message.role;
|
||||
const content = message.content;
|
||||
let stringMessage = `${role}: ${content}`;
|
||||
|
||||
const additionalKwargs = message.additionalKwargs;
|
||||
if (additionalKwargs) {
|
||||
stringMessage += `\n${additionalKwargs}`;
|
||||
}
|
||||
stringMessages.push(stringMessage);
|
||||
}
|
||||
|
||||
stringMessages.push(`assistant: `);
|
||||
return stringMessages.join("\n");
|
||||
};
|
||||
@@ -6,8 +6,11 @@ import { serviceContextFromDefaults } from "../ServiceContext.js";
|
||||
import { imageToDataUrl } from "../embeddings/index.js";
|
||||
import type { MessageContentDetail } from "../llm/types.js";
|
||||
import { PromptMixin } from "../prompts/Mixin.js";
|
||||
import type { TextQaPrompt } from "./../Prompt.js";
|
||||
|
||||
import { defaultTextQaPrompt } from "./../Prompt.js";
|
||||
|
||||
import { Prompt } from "./../prompts/types.js";
|
||||
|
||||
import type {
|
||||
BaseSynthesizer,
|
||||
SynthesizeParamsNonStreaming,
|
||||
@@ -20,7 +23,7 @@ export class MultiModalResponseSynthesizer
|
||||
{
|
||||
serviceContext: ServiceContext;
|
||||
metadataMode: MetadataMode;
|
||||
textQATemplate: TextQaPrompt;
|
||||
textQATemplate: Prompt;
|
||||
|
||||
constructor({
|
||||
serviceContext,
|
||||
@@ -34,15 +37,13 @@ export class MultiModalResponseSynthesizer
|
||||
this.textQATemplate = textQATemplate ?? defaultTextQaPrompt;
|
||||
}
|
||||
|
||||
protected _getPrompts(): { textQATemplate: TextQaPrompt } {
|
||||
protected _getPrompts(): { textQATemplate: Prompt } {
|
||||
return {
|
||||
textQATemplate: this.textQATemplate,
|
||||
};
|
||||
}
|
||||
|
||||
protected _updatePrompts(promptsDict: {
|
||||
textQATemplate: TextQaPrompt;
|
||||
}): void {
|
||||
protected _updatePrompts(promptsDict: { textQATemplate: Prompt }): void {
|
||||
if (promptsDict.textQATemplate) {
|
||||
this.textQATemplate = promptsDict.textQATemplate;
|
||||
}
|
||||
@@ -70,7 +71,9 @@ export class MultiModalResponseSynthesizer
|
||||
);
|
||||
// TODO: use builders to generate context
|
||||
const context = textChunks.join("\n\n");
|
||||
const textPrompt = this.textQATemplate({ context, query });
|
||||
|
||||
const textQaPrompt = this.textQATemplate.format({ context, query });
|
||||
|
||||
const images = await Promise.all(
|
||||
imageNodes.map(async (node: ImageNode) => {
|
||||
return {
|
||||
@@ -81,11 +84,17 @@ export class MultiModalResponseSynthesizer
|
||||
} as MessageContentDetail;
|
||||
}),
|
||||
);
|
||||
|
||||
// TODO: handle chat message prompt
|
||||
if (Array.isArray(textQaPrompt)) {
|
||||
throw new Error("textQaPrompt must be a string");
|
||||
}
|
||||
|
||||
const prompt: MessageContentDetail[] = [
|
||||
{ type: "text", text: textPrompt },
|
||||
{ type: "text", text: textQaPrompt },
|
||||
...images,
|
||||
];
|
||||
const response = await this.serviceContext.llm.complete({
|
||||
const response = await this.serviceContext.llm.predict({
|
||||
prompt,
|
||||
parentEvent,
|
||||
});
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
import type { Event } from "../callbacks/CallbackManager.js";
|
||||
import type { LLM } from "../llm/index.js";
|
||||
import type { ChatMessage, LLM } from "../llm/index.js";
|
||||
import { streamConverter } from "../llm/utils.js";
|
||||
import type {
|
||||
RefinePrompt,
|
||||
SimplePrompt,
|
||||
TextQaPrompt,
|
||||
TreeSummarizePrompt,
|
||||
} from "../Prompt.js";
|
||||
import {
|
||||
defaultRefinePrompt,
|
||||
defaultTextQaPrompt,
|
||||
@@ -22,6 +16,8 @@ import type {
|
||||
ResponseBuilderParamsStreaming,
|
||||
} from "./types.js";
|
||||
|
||||
import { Prompt } from "../prompts/types.js";
|
||||
|
||||
/**
|
||||
* Response modes of the response synthesizer
|
||||
*/
|
||||
@@ -37,9 +33,9 @@ enum ResponseMode {
|
||||
*/
|
||||
export class SimpleResponseBuilder implements ResponseBuilder {
|
||||
llm: LLM;
|
||||
textQATemplate: TextQaPrompt;
|
||||
textQATemplate: Prompt;
|
||||
|
||||
constructor(serviceContext: ServiceContext, textQATemplate?: TextQaPrompt) {
|
||||
constructor(serviceContext: ServiceContext, textQATemplate?: Prompt) {
|
||||
this.llm = serviceContext.llm;
|
||||
this.textQATemplate = textQATemplate ?? defaultTextQaPrompt;
|
||||
}
|
||||
@@ -63,7 +59,8 @@ export class SimpleResponseBuilder implements ResponseBuilder {
|
||||
context: textChunks.join("\n\n"),
|
||||
};
|
||||
|
||||
const prompt = this.textQATemplate(input);
|
||||
const prompt = this.textQATemplate.format(input);
|
||||
|
||||
if (stream) {
|
||||
const response = await this.llm.complete({ prompt, parentEvent, stream });
|
||||
return streamConverter(response, (chunk) => chunk.text);
|
||||
@@ -80,13 +77,13 @@ export class SimpleResponseBuilder implements ResponseBuilder {
|
||||
export class Refine extends PromptMixin implements ResponseBuilder {
|
||||
llm: LLM;
|
||||
promptHelper: PromptHelper;
|
||||
textQATemplate: TextQaPrompt;
|
||||
refineTemplate: RefinePrompt;
|
||||
textQATemplate: Prompt;
|
||||
refineTemplate: Prompt;
|
||||
|
||||
constructor(
|
||||
serviceContext: ServiceContext,
|
||||
textQATemplate?: TextQaPrompt,
|
||||
refineTemplate?: RefinePrompt,
|
||||
textQATemplate?: Prompt,
|
||||
refineTemplate?: Prompt,
|
||||
) {
|
||||
super();
|
||||
|
||||
@@ -97,8 +94,8 @@ export class Refine extends PromptMixin implements ResponseBuilder {
|
||||
}
|
||||
|
||||
protected _getPrompts(): {
|
||||
textQATemplate: RefinePrompt;
|
||||
refineTemplate: RefinePrompt;
|
||||
textQATemplate: Prompt;
|
||||
refineTemplate: Prompt;
|
||||
} {
|
||||
return {
|
||||
textQATemplate: this.textQATemplate,
|
||||
@@ -107,8 +104,8 @@ export class Refine extends PromptMixin implements ResponseBuilder {
|
||||
}
|
||||
|
||||
protected _updatePrompts(prompts: {
|
||||
textQATemplate: RefinePrompt;
|
||||
refineTemplate: RefinePrompt;
|
||||
textQATemplate: Prompt;
|
||||
refineTemplate: Prompt;
|
||||
}): void {
|
||||
if (prompts.textQATemplate) {
|
||||
this.textQATemplate = prompts.textQATemplate;
|
||||
@@ -166,19 +163,21 @@ export class Refine extends PromptMixin implements ResponseBuilder {
|
||||
stream: boolean,
|
||||
parentEvent?: Event,
|
||||
) {
|
||||
const textQATemplate: SimplePrompt = (input) =>
|
||||
this.textQATemplate({ ...input, query: queryStr });
|
||||
const textChunks = this.promptHelper.repack(textQATemplate, [textChunk]);
|
||||
const textChunks = this.promptHelper.repack(this.textQATemplate, [
|
||||
textChunk,
|
||||
]);
|
||||
|
||||
let response: AsyncIterable<string> | string | undefined = 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({
|
||||
prompt: this.textQATemplate.format({
|
||||
context: chunk,
|
||||
query: queryStr,
|
||||
}),
|
||||
parentEvent,
|
||||
stream: stream && lastChunk,
|
||||
@@ -205,20 +204,21 @@ export class Refine extends PromptMixin implements ResponseBuilder {
|
||||
stream: boolean,
|
||||
parentEvent?: Event,
|
||||
) {
|
||||
const refineTemplate: SimplePrompt = (input) =>
|
||||
this.refineTemplate({ ...input, query: queryStr });
|
||||
|
||||
const textChunks = this.promptHelper.repack(refineTemplate, [textChunk]);
|
||||
const textChunks = this.promptHelper.repack(this.refineTemplate, [
|
||||
textChunk,
|
||||
]);
|
||||
|
||||
let response: AsyncIterable<string> | string = 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({
|
||||
prompt: this.refineTemplate.format({
|
||||
context: chunk,
|
||||
existingAnswer: response as string,
|
||||
query: queryStr,
|
||||
}),
|
||||
parentEvent,
|
||||
stream: stream && lastChunk,
|
||||
@@ -228,15 +228,15 @@ export class Refine extends PromptMixin implements ResponseBuilder {
|
||||
}
|
||||
|
||||
async complete(params: {
|
||||
prompt: string;
|
||||
prompt: string | ChatMessage[];
|
||||
stream: boolean;
|
||||
parentEvent?: Event;
|
||||
}): Promise<AsyncIterable<string> | string> {
|
||||
if (params.stream) {
|
||||
const response = await this.llm.complete({ ...params, stream: true });
|
||||
const response = await this.llm.predict({ ...params, stream: true });
|
||||
return streamConverter(response, (chunk) => chunk.text);
|
||||
} else {
|
||||
const response = await this.llm.complete({ ...params, stream: false });
|
||||
const response = await this.llm.predict({ ...params, stream: false });
|
||||
return response.text;
|
||||
}
|
||||
}
|
||||
@@ -261,12 +261,10 @@ export class CompactAndRefine extends Refine {
|
||||
| ResponseBuilderParamsNonStreaming): Promise<
|
||||
AsyncIterable<string> | string
|
||||
> {
|
||||
const textQATemplate: SimplePrompt = (input) =>
|
||||
this.textQATemplate({ ...input, query: query });
|
||||
const refineTemplate: SimplePrompt = (input) =>
|
||||
this.refineTemplate({ ...input, query: query });
|
||||
|
||||
const maxPrompt = getBiggestPrompt([textQATemplate, refineTemplate]);
|
||||
const maxPrompt = getBiggestPrompt([
|
||||
this.textQATemplate,
|
||||
this.refineTemplate,
|
||||
]);
|
||||
const newTexts = this.promptHelper.repack(maxPrompt, textChunks);
|
||||
const params = {
|
||||
query,
|
||||
@@ -290,12 +288,9 @@ export class CompactAndRefine extends Refine {
|
||||
export class TreeSummarize extends PromptMixin implements ResponseBuilder {
|
||||
llm: LLM;
|
||||
promptHelper: PromptHelper;
|
||||
summaryTemplate: TreeSummarizePrompt;
|
||||
summaryTemplate: Prompt;
|
||||
|
||||
constructor(
|
||||
serviceContext: ServiceContext,
|
||||
summaryTemplate?: TreeSummarizePrompt,
|
||||
) {
|
||||
constructor(serviceContext: ServiceContext, summaryTemplate?: Prompt) {
|
||||
super();
|
||||
|
||||
this.llm = serviceContext.llm;
|
||||
@@ -303,15 +298,13 @@ export class TreeSummarize extends PromptMixin implements ResponseBuilder {
|
||||
this.summaryTemplate = summaryTemplate ?? defaultTreeSummarizePrompt;
|
||||
}
|
||||
|
||||
protected _getPrompts(): { summaryTemplate: TreeSummarizePrompt } {
|
||||
protected _getPrompts(): { summaryTemplate: Prompt } {
|
||||
return {
|
||||
summaryTemplate: this.summaryTemplate,
|
||||
};
|
||||
}
|
||||
|
||||
protected _updatePrompts(prompts: {
|
||||
summaryTemplate: TreeSummarizePrompt;
|
||||
}): void {
|
||||
protected _updatePrompts(prompts: { summaryTemplate: Prompt }): void {
|
||||
if (prompts.summaryTemplate) {
|
||||
this.summaryTemplate = prompts.summaryTemplate;
|
||||
}
|
||||
@@ -342,11 +335,13 @@ export class TreeSummarize extends PromptMixin implements ResponseBuilder {
|
||||
);
|
||||
|
||||
if (packedTextChunks.length === 1) {
|
||||
const prompt = this.summaryTemplate.partial({
|
||||
context: packedTextChunks[0],
|
||||
query,
|
||||
});
|
||||
|
||||
const params = {
|
||||
prompt: this.summaryTemplate({
|
||||
context: packedTextChunks[0],
|
||||
query,
|
||||
}),
|
||||
prompt,
|
||||
parentEvent,
|
||||
};
|
||||
if (stream) {
|
||||
@@ -356,21 +351,20 @@ export class TreeSummarize extends PromptMixin implements ResponseBuilder {
|
||||
return (await this.llm.complete(params)).text;
|
||||
} else {
|
||||
const summaries = await Promise.all(
|
||||
packedTextChunks.map((chunk) =>
|
||||
this.llm.complete({
|
||||
prompt: this.summaryTemplate({
|
||||
context: chunk,
|
||||
query,
|
||||
}),
|
||||
parentEvent,
|
||||
}),
|
||||
),
|
||||
packedTextChunks.map((chunk) => {
|
||||
const prompt = this.summaryTemplate.partial({
|
||||
context: chunk,
|
||||
query,
|
||||
});
|
||||
return this.llm.complete({ prompt, parentEvent });
|
||||
}),
|
||||
);
|
||||
|
||||
const params = {
|
||||
query,
|
||||
textChunks: summaries.map((s) => s.text),
|
||||
};
|
||||
|
||||
if (stream) {
|
||||
return this.getResponse({
|
||||
...params,
|
||||
@@ -398,7 +392,4 @@ export function getResponseBuilder(
|
||||
}
|
||||
}
|
||||
|
||||
export type ResponseBuilderPrompts =
|
||||
| TextQaPrompt
|
||||
| TreeSummarizePrompt
|
||||
| RefinePrompt;
|
||||
export type ResponseBuilderPrompts = Prompt | Prompt;
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import { Settings, SimpleDirectoryReader, VectorStoreIndex } from "./index.js";
|
||||
|
||||
// Update llm and prompt
|
||||
Settings.prompt.llm = "claude";
|
||||
|
||||
// Update lang
|
||||
Settings.prompt.lang = "en";
|
||||
|
||||
async function main() {
|
||||
const documents = await new SimpleDirectoryReader().loadData({
|
||||
directoryPath: "../examples",
|
||||
});
|
||||
|
||||
const index = await VectorStoreIndex.fromDocuments(documents);
|
||||
|
||||
const retriever = await index.asRetriever({});
|
||||
|
||||
retriever.similarityTopK = 10;
|
||||
|
||||
const queryEngine = index.asQueryEngine({
|
||||
retriever,
|
||||
});
|
||||
|
||||
// Query the engine
|
||||
const query = "Tell me about abramov";
|
||||
|
||||
const response = await queryEngine.query({
|
||||
query,
|
||||
});
|
||||
|
||||
console.log({
|
||||
response,
|
||||
});
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -4,7 +4,7 @@
|
||||
"version": "0.0.2",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"test": "vitest run"
|
||||
"test": "vitest run --silent=false"
|
||||
},
|
||||
"devDependencies": {
|
||||
"llamaindex": "workspace:*",
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { Prompt } from "llamaindex/prompts/types";
|
||||
|
||||
describe("Prompt", () => {
|
||||
it("should format messages prompt", () => {
|
||||
const prompt = new Prompt(`
|
||||
messages:
|
||||
- content: hello, {{name}}
|
||||
role: user
|
||||
`);
|
||||
const result = prompt.format({ name: "world" });
|
||||
expect(result).toEqual([{ content: "hello, world", role: "user" }]);
|
||||
});
|
||||
|
||||
it("should format single prompt", () => {
|
||||
const prompt = new Prompt("hello, {{name}}");
|
||||
const result = prompt.format({ name: "world" });
|
||||
expect(result).toEqual("hello, world");
|
||||
});
|
||||
|
||||
it("should format a prompt with a language section", () => {
|
||||
const prompt = new Prompt(
|
||||
"lang-en:\n messages:\n - content: 'hello, {{name}}'\n role: user",
|
||||
);
|
||||
const result = prompt.format({ name: "world" }, { lang: "en" });
|
||||
expect(result).toEqual([{ content: "hello, world", role: "user" }]);
|
||||
});
|
||||
|
||||
it("should format a prompt with a language section and LLM section", () => {
|
||||
const prompt = new Prompt(`
|
||||
lang-en:
|
||||
llm-gpt3:
|
||||
messages:
|
||||
- content: 'hello, {{name}}'
|
||||
role: user
|
||||
`);
|
||||
const result = prompt.format(
|
||||
{ name: "world" },
|
||||
{ lang: "en", llm: "gpt3" },
|
||||
);
|
||||
expect(result).toEqual([{ content: "hello, world", role: "user" }]);
|
||||
});
|
||||
|
||||
it("should format a prompt with a default language section and LLM section", () => {
|
||||
const prompt = new Prompt(`
|
||||
lang-default:
|
||||
llm-gpt3:
|
||||
messages:
|
||||
- content: hello, {{name}}
|
||||
role: user
|
||||
`);
|
||||
const result = prompt.format({ name: "world" }, { llm: "gpt3" });
|
||||
expect(result).toEqual([{ content: "hello, world", role: "user" }]);
|
||||
});
|
||||
|
||||
it("should format a prompt with a default language section and default LLM section", () => {
|
||||
const prompt = new Prompt(`
|
||||
lang-default:
|
||||
llm-default:
|
||||
messages:
|
||||
- content: hello, {{name}}
|
||||
role: user
|
||||
`);
|
||||
const result = prompt.format({ name: "world" });
|
||||
expect(result).toEqual([{ content: "hello, world", role: "user" }]);
|
||||
});
|
||||
|
||||
it("should format a default prompt", () => {
|
||||
const prompt = new Prompt(`
|
||||
default:
|
||||
hello, {{name}}
|
||||
`);
|
||||
const result = prompt.format({ name: "world" });
|
||||
expect(result).toEqual("hello, world");
|
||||
});
|
||||
});
|
||||
@@ -29,6 +29,7 @@
|
||||
"mammoth": "^1.6.0",
|
||||
"md-utils-ts": "^2.0.0",
|
||||
"mongodb": "^6.3.0",
|
||||
"mustache": "^4.2.0",
|
||||
"notion-md-crawler": "^0.0.2",
|
||||
"openai": "^4.26.1",
|
||||
"papaparse": "^5.4.1",
|
||||
@@ -40,8 +41,9 @@
|
||||
"rake-modified": "^1.0.8",
|
||||
"replicate": "^0.25.2",
|
||||
"string-strip-html": "^13.4.6",
|
||||
"wikipedia": "^2.1.2",
|
||||
"wink-nlp": "^1.14.3",
|
||||
"wikipedia": "^2.1.2"
|
||||
"yaml": "^2.4.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
|
||||
Generated
+33
-3
@@ -243,6 +243,9 @@ importers:
|
||||
mongodb:
|
||||
specifier: ^6.3.0
|
||||
version: 6.3.0
|
||||
mustache:
|
||||
specifier: ^4.2.0
|
||||
version: 4.2.0
|
||||
notion-md-crawler:
|
||||
specifier: ^0.0.2
|
||||
version: 0.0.2
|
||||
@@ -282,6 +285,9 @@ importers:
|
||||
wink-nlp:
|
||||
specifier: ^1.14.3
|
||||
version: 1.14.3
|
||||
yaml:
|
||||
specifier: ^2.4.1
|
||||
version: 2.4.1
|
||||
devDependencies:
|
||||
'@swc/cli':
|
||||
specifier: ^0.3.9
|
||||
@@ -289,6 +295,9 @@ importers:
|
||||
'@swc/core':
|
||||
specifier: ^1.4.2
|
||||
version: 1.4.2
|
||||
'@types/mustache':
|
||||
specifier: ^4.2.5
|
||||
version: 4.2.5
|
||||
concurrently:
|
||||
specifier: ^8.2.2
|
||||
version: 8.2.2
|
||||
@@ -388,6 +397,9 @@ importers:
|
||||
mongodb:
|
||||
specifier: ^6.3.0
|
||||
version: 6.3.0
|
||||
mustache:
|
||||
specifier: ^4.2.0
|
||||
version: 4.2.0
|
||||
notion-md-crawler:
|
||||
specifier: ^0.0.2
|
||||
version: 0.0.2
|
||||
@@ -427,6 +439,9 @@ importers:
|
||||
wink-nlp:
|
||||
specifier: ^1.14.3
|
||||
version: 1.14.3
|
||||
yaml:
|
||||
specifier: ^2.4.1
|
||||
version: 2.4.1
|
||||
|
||||
packages/edge/e2e/test-edge-runtime:
|
||||
dependencies:
|
||||
@@ -2419,7 +2434,7 @@ packages:
|
||||
'@docusaurus/react-loadable': 5.5.2(react@18.2.0)
|
||||
'@docusaurus/types': 3.1.1(react-dom@18.2.0)(react@18.2.0)
|
||||
'@types/history': 4.7.11
|
||||
'@types/react': 18.2.65
|
||||
'@types/react': 18.2.66
|
||||
'@types/react-router-config': 5.0.11
|
||||
'@types/react-router-dom': 5.3.3
|
||||
react: 18.2.0
|
||||
@@ -4574,6 +4589,10 @@ packages:
|
||||
/@types/ms@0.7.34:
|
||||
resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==}
|
||||
|
||||
/@types/mustache@4.2.5:
|
||||
resolution: {integrity: sha512-PLwiVvTBg59tGFL/8VpcGvqOu3L4OuveNvPi0EYbWchRdEVP++yRUXJPFl+CApKEq13017/4Nf7aQ5lTtHUNsA==}
|
||||
dev: true
|
||||
|
||||
/@types/node-fetch@2.6.11:
|
||||
resolution: {integrity: sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==}
|
||||
dependencies:
|
||||
@@ -4668,7 +4687,7 @@ packages:
|
||||
resolution: {integrity: sha512-WmSAg7WgqW7m4x8Mt4N6ZyKz0BubSj/2tVUMsAHp+Yd2AMwcSbeFq9WympT19p5heCFmF97R9eD5uUR/t4HEqw==}
|
||||
dependencies:
|
||||
'@types/history': 4.7.11
|
||||
'@types/react': 18.2.65
|
||||
'@types/react': 18.2.66
|
||||
'@types/react-router': 5.1.20
|
||||
dev: true
|
||||
|
||||
@@ -4692,7 +4711,7 @@ packages:
|
||||
resolution: {integrity: sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==}
|
||||
dependencies:
|
||||
'@types/history': 4.7.11
|
||||
'@types/react': 18.2.65
|
||||
'@types/react': 18.2.66
|
||||
dev: true
|
||||
|
||||
/@types/react@18.2.48:
|
||||
@@ -11080,6 +11099,11 @@ packages:
|
||||
dns-packet: 5.6.1
|
||||
thunky: 1.1.0
|
||||
|
||||
/mustache@4.2.0:
|
||||
resolution: {integrity: sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==}
|
||||
hasBin: true
|
||||
dev: false
|
||||
|
||||
/mz@2.7.0:
|
||||
resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==}
|
||||
dependencies:
|
||||
@@ -15836,6 +15860,12 @@ packages:
|
||||
engines: {node: '>= 14'}
|
||||
dev: true
|
||||
|
||||
/yaml@2.4.1:
|
||||
resolution: {integrity: sha512-pIXzoImaqmfOrL7teGUBt/T7ZDnyeGBWyXQBvOVhLkWLN37GXv8NMLK406UY6dS51JfcQHsmcW5cJ441bHg6Lg==}
|
||||
engines: {node: '>= 14'}
|
||||
hasBin: true
|
||||
dev: false
|
||||
|
||||
/yargs-parser@18.1.3:
|
||||
resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
Reference in New Issue
Block a user