mirror of
https://github.com/danswer-ai/danswer.git
synced 2025-07-04 03:31:23 +02:00
ensure consistency of answers + update llm relevance prompting (#2045)
This commit is contained in:
@ -11,7 +11,13 @@ it must contain information that is USEFUL for answering the query.
|
|||||||
If the section contains ANY useful information, that is good enough, \
|
If the section contains ANY useful information, that is good enough, \
|
||||||
it does not need to fully answer the every part of the user query.
|
it does not need to fully answer the every part of the user query.
|
||||||
|
|
||||||
|
|
||||||
|
Title: {{title}}
|
||||||
|
|
||||||
|
{{optional_metadata}}
|
||||||
|
|
||||||
Reference Section:
|
Reference Section:
|
||||||
|
|
||||||
```
|
```
|
||||||
{{chunk_text}}
|
{{chunk_text}}
|
||||||
```
|
```
|
||||||
|
@ -212,11 +212,17 @@ def filter_sections(
|
|||||||
section.center_chunk.content if use_chunk else section.combined_content
|
section.center_chunk.content if use_chunk else section.combined_content
|
||||||
for section in sections_to_filter
|
for section in sections_to_filter
|
||||||
]
|
]
|
||||||
|
metadata_list = [section.center_chunk.metadata for section in sections_to_filter]
|
||||||
|
titles = [
|
||||||
|
section.center_chunk.semantic_identifier for section in sections_to_filter
|
||||||
|
]
|
||||||
|
|
||||||
llm_chunk_selection = llm_batch_eval_sections(
|
llm_chunk_selection = llm_batch_eval_sections(
|
||||||
query=query.query,
|
query=query.query,
|
||||||
section_contents=contents,
|
section_contents=contents,
|
||||||
llm=llm,
|
llm=llm,
|
||||||
|
titles=titles,
|
||||||
|
metadata_list=metadata_list,
|
||||||
)
|
)
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
@ -12,17 +12,33 @@ from danswer.utils.threadpool_concurrency import run_functions_tuples_in_paralle
|
|||||||
logger = setup_logger()
|
logger = setup_logger()
|
||||||
|
|
||||||
|
|
||||||
def llm_eval_section(query: str, section_content: str, llm: LLM) -> bool:
|
def llm_eval_section(
|
||||||
|
query: str,
|
||||||
|
section_content: str,
|
||||||
|
llm: LLM,
|
||||||
|
title: str,
|
||||||
|
metadata: dict[str, str | list[str]],
|
||||||
|
) -> bool:
|
||||||
|
def _get_metadata_str(metadata: dict[str, str | list[str]]) -> str:
|
||||||
|
metadata_str = "\n\nMetadata:\n"
|
||||||
|
for key, value in metadata.items():
|
||||||
|
value_str = ", ".join(value) if isinstance(value, list) else value
|
||||||
|
metadata_str += f"{key} - {value_str}\n"
|
||||||
|
return metadata_str + "\nContent:"
|
||||||
|
|
||||||
def _get_usefulness_messages() -> list[dict[str, str]]:
|
def _get_usefulness_messages() -> list[dict[str, str]]:
|
||||||
|
metadata_str = _get_metadata_str(metadata) if metadata else ""
|
||||||
messages = [
|
messages = [
|
||||||
{
|
{
|
||||||
"role": "user",
|
"role": "user",
|
||||||
"content": SECTION_FILTER_PROMPT.format(
|
"content": SECTION_FILTER_PROMPT.format(
|
||||||
chunk_text=section_content, user_query=query
|
title=title,
|
||||||
|
chunk_text=section_content,
|
||||||
|
user_query=query,
|
||||||
|
optional_metadata=metadata_str,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
return messages
|
return messages
|
||||||
|
|
||||||
def _extract_usefulness(model_output: str) -> bool:
|
def _extract_usefulness(model_output: str) -> bool:
|
||||||
@ -34,9 +50,6 @@ def llm_eval_section(query: str, section_content: str, llm: LLM) -> bool:
|
|||||||
|
|
||||||
messages = _get_usefulness_messages()
|
messages = _get_usefulness_messages()
|
||||||
filled_llm_prompt = dict_based_prompt_to_langchain_prompt(messages)
|
filled_llm_prompt = dict_based_prompt_to_langchain_prompt(messages)
|
||||||
# When running in a batch, it takes as long as the longest thread
|
|
||||||
# And when running a large batch, one may fail and take the whole timeout
|
|
||||||
# instead cap it to 5 seconds
|
|
||||||
model_output = message_to_string(llm.invoke(filled_llm_prompt))
|
model_output = message_to_string(llm.invoke(filled_llm_prompt))
|
||||||
logger.debug(model_output)
|
logger.debug(model_output)
|
||||||
|
|
||||||
@ -44,7 +57,12 @@ def llm_eval_section(query: str, section_content: str, llm: LLM) -> bool:
|
|||||||
|
|
||||||
|
|
||||||
def llm_batch_eval_sections(
|
def llm_batch_eval_sections(
|
||||||
query: str, section_contents: list[str], llm: LLM, use_threads: bool = True
|
query: str,
|
||||||
|
section_contents: list[str],
|
||||||
|
llm: LLM,
|
||||||
|
titles: list[str],
|
||||||
|
metadata_list: list[dict[str, str | list[str]]],
|
||||||
|
use_threads: bool = True,
|
||||||
) -> list[bool]:
|
) -> list[bool]:
|
||||||
if DISABLE_LLM_DOC_RELEVANCE:
|
if DISABLE_LLM_DOC_RELEVANCE:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
@ -54,8 +72,10 @@ def llm_batch_eval_sections(
|
|||||||
|
|
||||||
if use_threads:
|
if use_threads:
|
||||||
functions_with_args: list[tuple[Callable, tuple]] = [
|
functions_with_args: list[tuple[Callable, tuple]] = [
|
||||||
(llm_eval_section, (query, section_content, llm))
|
(llm_eval_section, (query, section_content, llm, title, metadata))
|
||||||
for section_content in section_contents
|
for section_content, title, metadata in zip(
|
||||||
|
section_contents, titles, metadata_list
|
||||||
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
logger.debug(
|
logger.debug(
|
||||||
@ -70,6 +90,8 @@ def llm_batch_eval_sections(
|
|||||||
|
|
||||||
else:
|
else:
|
||||||
return [
|
return [
|
||||||
llm_eval_section(query, section_content, llm)
|
llm_eval_section(query, section_content, llm, title, metadata)
|
||||||
for section_content in section_contents
|
for section_content, title, metadata in zip(
|
||||||
|
section_contents, titles, metadata_list
|
||||||
|
)
|
||||||
]
|
]
|
||||||
|
@ -14,6 +14,7 @@ import { useContext, useEffect, useState } from "react";
|
|||||||
import { Tooltip } from "../tooltip/Tooltip";
|
import { Tooltip } from "../tooltip/Tooltip";
|
||||||
import KeyboardSymbol from "@/lib/browserUtilities";
|
import KeyboardSymbol from "@/lib/browserUtilities";
|
||||||
import { SettingsContext } from "../settings/SettingsProvider";
|
import { SettingsContext } from "../settings/SettingsProvider";
|
||||||
|
import { DISABLE_LLM_DOC_RELEVANCE } from "@/lib/constants";
|
||||||
|
|
||||||
const getSelectedDocumentIds = (
|
const getSelectedDocumentIds = (
|
||||||
documents: SearchDanswerDocument[],
|
documents: SearchDanswerDocument[],
|
||||||
@ -140,6 +141,7 @@ export const SearchResultsDisplay = ({
|
|||||||
showAll ||
|
showAll ||
|
||||||
(searchResponse &&
|
(searchResponse &&
|
||||||
searchResponse.additional_relevance &&
|
searchResponse.additional_relevance &&
|
||||||
|
searchResponse.additional_relevance[doc.document_id] &&
|
||||||
searchResponse.additional_relevance[doc.document_id].relevant) ||
|
searchResponse.additional_relevance[doc.document_id].relevant) ||
|
||||||
doc.is_relevant
|
doc.is_relevant
|
||||||
);
|
);
|
||||||
@ -175,7 +177,8 @@ export const SearchResultsDisplay = ({
|
|||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<div className="font-bold flex justify-between text-emphasis border-b mb-3 pb-1 border-border text-lg">
|
<div className="font-bold flex justify-between text-emphasis border-b mb-3 pb-1 border-border text-lg">
|
||||||
<p>Results</p>
|
<p>Results</p>
|
||||||
{(contentEnriched || searchResponse.additional_relevance) && (
|
{!DISABLE_LLM_DOC_RELEVANCE &&
|
||||||
|
(contentEnriched || searchResponse.additional_relevance) && (
|
||||||
<Tooltip delayDuration={1000} content={`${commandSymbol}O`}>
|
<Tooltip delayDuration={1000} content={`${commandSymbol}O`}>
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@ -204,6 +207,7 @@ export const SearchResultsDisplay = ({
|
|||||||
: agenticResults
|
: agenticResults
|
||||||
? "Focus"
|
? "Focus"
|
||||||
: "Show All"}
|
: "Show All"}
|
||||||
|
|
||||||
<span className="ml-1">
|
<span className="ml-1">
|
||||||
{!sweep ? (
|
{!sweep ? (
|
||||||
<BroomIcon className="h-4 w-4" />
|
<BroomIcon className="h-4 w-4" />
|
||||||
|
@ -170,11 +170,12 @@ export const SearchSection = ({
|
|||||||
if (existingSearchIdRaw == null) {
|
if (existingSearchIdRaw == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
function extractFirstUserMessage(
|
function extractFirstMessageByType(
|
||||||
chatSession: SearchSession
|
chatSession: SearchSession,
|
||||||
|
messageType: "user" | "assistant"
|
||||||
): string | null {
|
): string | null {
|
||||||
const userMessage = chatSession?.messages.find(
|
const userMessage = chatSession?.messages.find(
|
||||||
(msg) => msg.message_type === "user"
|
(msg) => msg.message_type === messageType
|
||||||
);
|
);
|
||||||
return userMessage ? userMessage.message : null;
|
return userMessage ? userMessage.message : null;
|
||||||
}
|
}
|
||||||
@ -184,14 +185,18 @@ export const SearchSection = ({
|
|||||||
`/api/query/search-session/${existingSearchessionId}`
|
`/api/query/search-session/${existingSearchessionId}`
|
||||||
);
|
);
|
||||||
const searchSession = (await response.json()) as SearchSession;
|
const searchSession = (await response.json()) as SearchSession;
|
||||||
const message = extractFirstUserMessage(searchSession);
|
const userMessage = extractFirstMessageByType(searchSession, "user");
|
||||||
|
const assistantMessage = extractFirstMessageByType(
|
||||||
|
searchSession,
|
||||||
|
"assistant"
|
||||||
|
);
|
||||||
|
|
||||||
if (message) {
|
if (userMessage) {
|
||||||
setQuery(message);
|
setQuery(userMessage);
|
||||||
const danswerDocs: SearchResponse = {
|
const danswerDocs: SearchResponse = {
|
||||||
documents: searchSession.documents,
|
documents: searchSession.documents,
|
||||||
suggestedSearchType: null,
|
suggestedSearchType: null,
|
||||||
answer: null,
|
answer: assistantMessage || "Search response not found",
|
||||||
quotes: null,
|
quotes: null,
|
||||||
selectedDocIndices: null,
|
selectedDocIndices: null,
|
||||||
error: null,
|
error: null,
|
||||||
|
Reference in New Issue
Block a user