mirror of
https://github.com/logancyang/obsidian-copilot.git
synced 2026-07-22 07:50:24 +00:00
Implement HyDE for QA (#690)
This commit is contained in:
parent
4c54ab6352
commit
59068fa494
5 changed files with 54 additions and 15 deletions
|
|
@ -272,6 +272,7 @@ export default class ChainManager {
|
|||
maxK: this.settings.maxSourceChunks, // The maximum number of docs (chunks) to retrieve
|
||||
kIncrement: 2,
|
||||
},
|
||||
chatModel,
|
||||
options.debug
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -134,7 +134,8 @@ class ChainFactory {
|
|||
// and lose the follow up question altogether.
|
||||
const condenseQuestionTemplate = `Given the following conversation and a follow up question,
|
||||
summarize the conversation as context and keep the follow up question unchanged, in its original language.
|
||||
Then combine the summary and the follow up question to construct a standalone question.
|
||||
If the follow up question is unrelated to its preceding messages, return the question directly.
|
||||
If it is related, then combine the summary and the follow up question to construct a standalone question.
|
||||
Make sure to keep any [[]] wrapped note titles in the question unchanged.
|
||||
|
||||
Chat History:
|
||||
|
|
|
|||
|
|
@ -361,7 +361,7 @@ ${chatContent}`;
|
|||
};
|
||||
|
||||
if (currentChain === ChainType.LONG_NOTE_QA_CHAIN) {
|
||||
setChain(ChainType.LONG_NOTE_QA_CHAIN, { noteFile });
|
||||
setChain(ChainType.LONG_NOTE_QA_CHAIN, { noteFile, debug });
|
||||
}
|
||||
|
||||
addMessage(activeNoteOnMessage);
|
||||
|
|
|
|||
|
|
@ -818,7 +818,7 @@ export default class CopilotPlugin extends Plugin {
|
|||
systemMessage: this.settings.userSystemPrompt || DEFAULT_SYSTEM_PROMPT,
|
||||
chatContextTurns: Number(contextTurns),
|
||||
chainType: ChainType.LLM_CHAIN, // Set LLM_CHAIN as default ChainType
|
||||
options: { forceNewCreation: true } as SetChainOptions,
|
||||
options: { forceNewCreation: true, debug: this.settings.debug } as SetChainOptions,
|
||||
openAIProxyBaseUrl: this.settings.openAIProxyBaseUrl,
|
||||
openAIEmbeddingProxyBaseUrl: this.settings.openAIEmbeddingProxyBaseUrl,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,7 +2,9 @@ import { extractNoteTitles, getNoteFileFromTitle } from "@/utils";
|
|||
import VectorDBManager from "@/vectorDBManager";
|
||||
import { BaseRetriever } from "@langchain/core/retrievers";
|
||||
import { VectorStore } from "@langchain/core/vectorstores";
|
||||
import { BaseLanguageModel } from "langchain/base_language";
|
||||
import { Document } from "langchain/document";
|
||||
import { ChatPromptTemplate } from "langchain/prompts";
|
||||
import {
|
||||
ScoreThresholdRetriever,
|
||||
ScoreThresholdRetrieverInput,
|
||||
|
|
@ -12,13 +14,21 @@ import { Vault } from "obsidian";
|
|||
export class HybridRetriever<V extends VectorStore> extends BaseRetriever {
|
||||
public lc_namespace = ["hybrid_retriever"];
|
||||
|
||||
private llm: BaseLanguageModel;
|
||||
private queryRewritePrompt: ChatPromptTemplate;
|
||||
|
||||
constructor(
|
||||
private db: PouchDB.Database,
|
||||
private vault: Vault,
|
||||
private options: ScoreThresholdRetrieverInput<V>,
|
||||
llm: BaseLanguageModel,
|
||||
private debug?: boolean
|
||||
) {
|
||||
super();
|
||||
this.llm = llm;
|
||||
this.queryRewritePrompt = ChatPromptTemplate.fromTemplate(
|
||||
"Please write a passage to answer the question.\nQuestion: {question}\nPassage:"
|
||||
);
|
||||
}
|
||||
|
||||
async getRelevantDocuments(query: string): Promise<Document[]> {
|
||||
|
|
@ -26,20 +36,11 @@ export class HybridRetriever<V extends VectorStore> extends BaseRetriever {
|
|||
const noteTitles = extractNoteTitles(query);
|
||||
// Retrieve chunks for explicitly mentioned note titles
|
||||
const explicitChunks = await this.getExplicitChunks(noteTitles);
|
||||
if (this.debug) {
|
||||
console.log(
|
||||
"*** HYBRID RETRIEVER DEBUG INFO: ***",
|
||||
"\nHybrid Retriever Query: ",
|
||||
query,
|
||||
"\nNote Titles extracted: ",
|
||||
noteTitles,
|
||||
"\nExplicit Chunks:",
|
||||
explicitChunks
|
||||
);
|
||||
}
|
||||
|
||||
// Generate a hypothetical answer passage
|
||||
const rewrittenQuery = await this.rewriteQuery(query);
|
||||
// Perform vector similarity search using ScoreThresholdRetriever
|
||||
const vectorChunks = await this.getVectorChunks(query);
|
||||
const vectorChunks = await this.getVectorChunks(rewrittenQuery);
|
||||
|
||||
// Combine explicit and vector chunks, removing duplicates while maintaining order
|
||||
const uniqueChunks = new Set<string>(explicitChunks.map((chunk) => chunk.pageContent));
|
||||
|
|
@ -53,10 +54,46 @@ export class HybridRetriever<V extends VectorStore> extends BaseRetriever {
|
|||
}
|
||||
}
|
||||
|
||||
if (this.debug) {
|
||||
console.log(
|
||||
"*** HyDE HYBRID RETRIEVER DEBUG INFO: ***",
|
||||
"\nOriginal Query: ",
|
||||
query,
|
||||
"\n\nRewritten Query: ",
|
||||
rewrittenQuery,
|
||||
"\n\nNote Titles extracted: ",
|
||||
noteTitles,
|
||||
"\n\nExplicit Chunks:",
|
||||
explicitChunks,
|
||||
"\n\nVector Chunks:",
|
||||
vectorChunks,
|
||||
"\n\nCombined Chunks:",
|
||||
combinedChunks
|
||||
);
|
||||
}
|
||||
|
||||
// Make sure the combined chunks are at most maxK
|
||||
return combinedChunks.slice(0, this.options.maxK);
|
||||
}
|
||||
|
||||
private async rewriteQuery(query: string): Promise<string> {
|
||||
try {
|
||||
const promptResult = await this.queryRewritePrompt.format({ question: query });
|
||||
const rewrittenQueryObject = await this.llm.invoke(promptResult);
|
||||
|
||||
// Directly return the content assuming it's structured as expected
|
||||
if (rewrittenQueryObject && "content" in rewrittenQueryObject) {
|
||||
return rewrittenQueryObject.content;
|
||||
}
|
||||
console.warn("Unexpected rewrittenQuery format. Falling back to original query.");
|
||||
return query;
|
||||
} catch (error) {
|
||||
console.error("Error in rewriteQuery:", error);
|
||||
// If there's an error, return the original query
|
||||
return query;
|
||||
}
|
||||
}
|
||||
|
||||
private async getExplicitChunks(noteTitles: string[]): Promise<Document[]> {
|
||||
const explicitChunks: Document[] = [];
|
||||
for (const noteTitle of noteTitles) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue