mirror of
https://github.com/danswer-ai/danswer.git
synced 2025-09-20 13:05:49 +02:00
New prompt + show quotes on hover (#404)
This commit is contained in:
@@ -1,9 +1,13 @@
|
|||||||
import abc
|
import abc
|
||||||
|
import json
|
||||||
from collections.abc import Iterator
|
from collections.abc import Iterator
|
||||||
from copy import copy
|
from copy import copy
|
||||||
|
|
||||||
import tiktoken
|
import tiktoken
|
||||||
|
from langchain.schema.messages import AIMessage
|
||||||
from langchain.schema.messages import BaseMessage
|
from langchain.schema.messages import BaseMessage
|
||||||
|
from langchain.schema.messages import HumanMessage
|
||||||
|
from langchain.schema.messages import SystemMessage
|
||||||
|
|
||||||
from danswer.chunking.models import InferenceChunk
|
from danswer.chunking.models import InferenceChunk
|
||||||
from danswer.direct_qa.interfaces import AnswerQuestionReturn
|
from danswer.direct_qa.interfaces import AnswerQuestionReturn
|
||||||
@@ -13,11 +17,16 @@ from danswer.direct_qa.interfaces import DanswerAnswerPiece
|
|||||||
from danswer.direct_qa.interfaces import DanswerQuotes
|
from danswer.direct_qa.interfaces import DanswerQuotes
|
||||||
from danswer.direct_qa.interfaces import QAModel
|
from danswer.direct_qa.interfaces import QAModel
|
||||||
from danswer.direct_qa.qa_prompts import JsonChatProcessor
|
from danswer.direct_qa.qa_prompts import JsonChatProcessor
|
||||||
|
from danswer.direct_qa.qa_prompts import SAMPLE_JSON_RESPONSE
|
||||||
|
from danswer.direct_qa.qa_prompts import UNCERTAINTY_PAT
|
||||||
from danswer.direct_qa.qa_prompts import WeakModelFreeformProcessor
|
from danswer.direct_qa.qa_prompts import WeakModelFreeformProcessor
|
||||||
from danswer.direct_qa.qa_utils import process_model_tokens
|
from danswer.direct_qa.qa_utils import process_model_tokens
|
||||||
from danswer.llm.llm import LLM
|
from danswer.llm.llm import LLM
|
||||||
from danswer.llm.utils import dict_based_prompt_to_langchain_prompt
|
from danswer.llm.utils import dict_based_prompt_to_langchain_prompt
|
||||||
from danswer.llm.utils import str_prompt_to_langchain_prompt
|
from danswer.llm.utils import str_prompt_to_langchain_prompt
|
||||||
|
from danswer.utils.logger import setup_logger
|
||||||
|
|
||||||
|
logger = setup_logger()
|
||||||
|
|
||||||
|
|
||||||
class QAHandler(abc.ABC):
|
class QAHandler(abc.ABC):
|
||||||
@@ -84,6 +93,51 @@ class SimpleChatQAHandler(QAHandler):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class JsonChatQAUnshackledHandler(QAHandler):
|
||||||
|
def build_prompt(
|
||||||
|
self, query: str, context_chunks: list[InferenceChunk]
|
||||||
|
) -> list[BaseMessage]:
|
||||||
|
prompt: list[BaseMessage] = []
|
||||||
|
|
||||||
|
complete_answer_not_found_response = (
|
||||||
|
'{"answer": "' + UNCERTAINTY_PAT + '", "quotes": []}'
|
||||||
|
)
|
||||||
|
prompt.append(
|
||||||
|
SystemMessage(
|
||||||
|
content=(
|
||||||
|
"Use the following pieces of context to answer the users question. Your response "
|
||||||
|
"should be in JSON format and contain an answer and (optionally) quotes that help support the answer. "
|
||||||
|
"Your responses should be informative, detailed, and consider all possibilities and edge cases. "
|
||||||
|
f"If you don't know the answer, respond with '{complete_answer_not_found_response}'\n"
|
||||||
|
f"Sample response:\n\n{json.dumps(SAMPLE_JSON_RESPONSE)}"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
prompt.append(
|
||||||
|
SystemMessage(
|
||||||
|
content='Start by reading the following documents and responding with "Acknowledged".'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
for chunk in context_chunks:
|
||||||
|
prompt.append(SystemMessage(content=chunk.content))
|
||||||
|
prompt.append(AIMessage(content="Acknowledged"))
|
||||||
|
|
||||||
|
prompt.append(HumanMessage(content=f"Question: {query}\n"))
|
||||||
|
|
||||||
|
return prompt
|
||||||
|
|
||||||
|
def process_response(
|
||||||
|
self,
|
||||||
|
tokens: Iterator[str],
|
||||||
|
context_chunks: list[InferenceChunk],
|
||||||
|
) -> AnswerQuestionStreamReturn:
|
||||||
|
yield from process_model_tokens(
|
||||||
|
tokens=tokens,
|
||||||
|
context_docs=context_chunks,
|
||||||
|
is_json_prompt=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _tiktoken_trim_chunks(
|
def _tiktoken_trim_chunks(
|
||||||
chunks: list[InferenceChunk], max_chunk_toks: int = 512
|
chunks: list[InferenceChunk], max_chunk_toks: int = 512
|
||||||
) -> list[InferenceChunk]:
|
) -> list[InferenceChunk]:
|
||||||
|
@@ -29,6 +29,8 @@ import {
|
|||||||
FiChevronRight,
|
FiChevronRight,
|
||||||
FiChevronLeft,
|
FiChevronLeft,
|
||||||
FiAlertTriangle,
|
FiAlertTriangle,
|
||||||
|
FiZoomIn,
|
||||||
|
FiCopy,
|
||||||
} from "react-icons/fi";
|
} from "react-icons/fi";
|
||||||
import { SiBookstack } from "react-icons/si";
|
import { SiBookstack } from "react-icons/si";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
@@ -227,6 +229,20 @@ export const TriangleAlertIcon = ({
|
|||||||
return <FiAlertTriangle size={size} className={className} />;
|
return <FiAlertTriangle size={size} className={className} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const ZoomInIcon = ({
|
||||||
|
size = 16,
|
||||||
|
className = defaultTailwindCSS,
|
||||||
|
}: IconProps) => {
|
||||||
|
return <FiZoomIn size={size} className={className} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CopyIcon = ({
|
||||||
|
size = 16,
|
||||||
|
className = defaultTailwindCSS,
|
||||||
|
}: IconProps) => {
|
||||||
|
return <FiCopy size={size} className={className} />;
|
||||||
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// COMPANY LOGOS
|
// COMPANY LOGOS
|
||||||
//
|
//
|
||||||
|
@@ -1,6 +1,79 @@
|
|||||||
import { Quote } from "@/lib/search/interfaces";
|
import { Quote } from "@/lib/search/interfaces";
|
||||||
import { ResponseSection, StatusOptions } from "./ResponseSection";
|
import { ResponseSection, StatusOptions } from "./ResponseSection";
|
||||||
import { getSourceIcon } from "@/components/source";
|
import { getSourceIcon } from "@/components/source";
|
||||||
|
import { CheckmarkIcon, CopyIcon } from "@/components/icons/icons";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
const QuoteDisplay = ({ quoteInfo }: { quoteInfo: Quote }) => {
|
||||||
|
const [detailIsOpen, setDetailIsOpen] = useState(false);
|
||||||
|
const [copyClicked, setCopyClicked] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="relative"
|
||||||
|
onMouseEnter={() => {
|
||||||
|
setDetailIsOpen(true);
|
||||||
|
}}
|
||||||
|
onMouseLeave={() => setDetailIsOpen(false)}
|
||||||
|
>
|
||||||
|
{detailIsOpen && (
|
||||||
|
<div className="absolute top-0 mt-10 pt-2 z-50">
|
||||||
|
<div className="rounded-lg shadow bg-gray-800 w-96 p-3 text-sm leading-relaxed text-gray-200">
|
||||||
|
<div className="flex mt-1">
|
||||||
|
<div>
|
||||||
|
<b>Quote:</b> <i>{quoteInfo.quote}</i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="my-auto ml-1"
|
||||||
|
onClick={() => {
|
||||||
|
navigator.clipboard.writeText(quoteInfo.quote);
|
||||||
|
setCopyClicked(true);
|
||||||
|
setTimeout(() => {
|
||||||
|
setCopyClicked(false);
|
||||||
|
}, 1000);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{copyClicked ? (
|
||||||
|
<CheckmarkIcon
|
||||||
|
className="my-auto flex flex-shrink-0 text-gray-500 hover:text-gray-400 cursor-pointer"
|
||||||
|
size={20}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<CopyIcon
|
||||||
|
className="my-auto flex flex-shrink-0 text-gray-500 hover:text-gray-400 cursor-pointer"
|
||||||
|
size={20}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<button className="text-sm flex w-fit">
|
||||||
|
<a
|
||||||
|
className="flex max-w-[300px] shrink box-border p-2 border border-gray-800 rounded-lg hover:bg-gray-800"
|
||||||
|
href={quoteInfo.link || undefined}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
{getSourceIcon(quoteInfo.source_type, 20)}
|
||||||
|
<p className="truncate break-all ml-2 mr-2">
|
||||||
|
{quoteInfo.semantic_identifier || quoteInfo.document_id}
|
||||||
|
</p>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
{/* <div
|
||||||
|
className="cursor-pointer h-full pt-2 pb-2 px-1 border-t border-b border-r border-gray-800 rounded-r-lg hover:bg-gray-800"
|
||||||
|
onClick={() => setDetailIsOpen(!detailIsOpen)}
|
||||||
|
>
|
||||||
|
<div className="pt-0.5 mx-auto h-[20px]">
|
||||||
|
<ZoomInIcon className="text-gray-500" size={14} />
|
||||||
|
</div>
|
||||||
|
</div> */}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
interface QuotesSectionProps {
|
interface QuotesSectionProps {
|
||||||
quotes: Quote[] | null;
|
quotes: Quote[] | null;
|
||||||
@@ -37,20 +110,9 @@ const QuotesBody = ({ quotes, isFetching }: QuotesSectionProps) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-wrap">
|
<div className="flex flex-wrap gap-2">
|
||||||
{quotes!.map((quoteInfo) => (
|
{quotes!.map((quoteInfo) => (
|
||||||
<a
|
<QuoteDisplay quoteInfo={quoteInfo} key={quoteInfo.document_id} />
|
||||||
key={quoteInfo.document_id}
|
|
||||||
className="p-2 mr-1 border border-gray-800 rounded-lg text-sm flex max-w-[280px] hover:bg-gray-800 w-fit"
|
|
||||||
href={quoteInfo.link || undefined}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
{getSourceIcon(quoteInfo.source_type, 20)}
|
|
||||||
<p className="truncate break-all ml-2 mr-1">
|
|
||||||
{quoteInfo.semantic_identifier || quoteInfo.document_id}
|
|
||||||
</p>
|
|
||||||
</a>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
Reference in New Issue
Block a user