Spaces:
Runtime error
Runtime error
Fix formatting issues (#56)
Browse files* Rename formatter
* override response without sources
* Add gradio formatter
* add factory for responses
* Fix circular imports
- buster/apps/gradio_app.ipynb +8 -6
- buster/apps/slackbot.py +5 -8
- buster/chatbot.py +21 -17
- buster/formatter/__init__.py +16 -5
- buster/formatter/base.py +16 -8
- buster/formatter/factory.py +22 -0
- buster/formatter/gradio.py +28 -0
- buster/formatter/html.py +4 -4
- buster/formatter/markdown.py +3 -12
- buster/formatter/slack.py +3 -12
- pyproject.toml +1 -1
buster/apps/gradio_app.ipynb
CHANGED
|
@@ -9,6 +9,9 @@
|
|
| 9 |
},
|
| 10 |
"outputs": [],
|
| 11 |
"source": [
|
|
|
|
|
|
|
|
|
|
| 12 |
"import gradio as gr\n",
|
| 13 |
"\n",
|
| 14 |
"from buster.chatbot import Chatbot, ChatbotConfig\n",
|
|
@@ -24,9 +27,8 @@
|
|
| 24 |
" \"engine\": \"text-davinci-003\",\n",
|
| 25 |
" \"max_tokens\": 500,\n",
|
| 26 |
" },\n",
|
| 27 |
-
"
|
| 28 |
-
"
|
| 29 |
-
" text_after_response=\"I'm a bot π€ trained to answer huggingface π€ transformers questions. My answers aren't always perfect.\",\n",
|
| 30 |
" text_before_prompt=\"\"\"You are a slack chatbot assistant answering technical questions about huggingface transformers, a library to train transformers in python.\n",
|
| 31 |
"Make sure to format your answers in Markdown format, including code block and snippets.\n",
|
| 32 |
"Do not include any links to urls or hyperlinks in your answers.\n",
|
|
@@ -109,7 +111,7 @@
|
|
| 109 |
],
|
| 110 |
"metadata": {
|
| 111 |
"kernelspec": {
|
| 112 |
-
"display_name": "
|
| 113 |
"language": "python",
|
| 114 |
"name": "python3"
|
| 115 |
},
|
|
@@ -123,11 +125,11 @@
|
|
| 123 |
"name": "python",
|
| 124 |
"nbconvert_exporter": "python",
|
| 125 |
"pygments_lexer": "ipython3",
|
| 126 |
-
"version": "3.9
|
| 127 |
},
|
| 128 |
"vscode": {
|
| 129 |
"interpreter": {
|
| 130 |
-
"hash": "
|
| 131 |
}
|
| 132 |
}
|
| 133 |
},
|
|
|
|
| 9 |
},
|
| 10 |
"outputs": [],
|
| 11 |
"source": [
|
| 12 |
+
"%load_ext autoreload\n",
|
| 13 |
+
"%autoreload 2\n",
|
| 14 |
+
"\n",
|
| 15 |
"import gradio as gr\n",
|
| 16 |
"\n",
|
| 17 |
"from buster.chatbot import Chatbot, ChatbotConfig\n",
|
|
|
|
| 27 |
" \"engine\": \"text-davinci-003\",\n",
|
| 28 |
" \"max_tokens\": 500,\n",
|
| 29 |
" },\n",
|
| 30 |
+
" link_format=\"gradio\",\n",
|
| 31 |
+
" response_footnote=\"I'm a bot π€ trained to answer huggingface π€ transformers questions. My answers aren't always perfect.\",\n",
|
|
|
|
| 32 |
" text_before_prompt=\"\"\"You are a slack chatbot assistant answering technical questions about huggingface transformers, a library to train transformers in python.\n",
|
| 33 |
"Make sure to format your answers in Markdown format, including code block and snippets.\n",
|
| 34 |
"Do not include any links to urls or hyperlinks in your answers.\n",
|
|
|
|
| 111 |
],
|
| 112 |
"metadata": {
|
| 113 |
"kernelspec": {
|
| 114 |
+
"display_name": "buster",
|
| 115 |
"language": "python",
|
| 116 |
"name": "python3"
|
| 117 |
},
|
|
|
|
| 125 |
"name": "python",
|
| 126 |
"nbconvert_exporter": "python",
|
| 127 |
"pygments_lexer": "ipython3",
|
| 128 |
+
"version": "3.10.9"
|
| 129 |
},
|
| 130 |
"vscode": {
|
| 131 |
"interpreter": {
|
| 132 |
+
"hash": "bfa91706490f6a3314a87f4853806d905e46027cd889e58fcad4739e8600f624"
|
| 133 |
}
|
| 134 |
}
|
| 135 |
},
|
buster/apps/slackbot.py
CHANGED
|
@@ -26,8 +26,8 @@ mila_doc_cfg = ChatbotConfig(
|
|
| 26 |
"max_tokens": 200,
|
| 27 |
},
|
| 28 |
separator="\n",
|
| 29 |
-
|
| 30 |
-
|
| 31 |
For more info, view the full documentation here (https://docs.mila.quebec/) or contact support@mila.quebec
|
| 32 |
""",
|
| 33 |
text_before_prompt="""
|
|
@@ -62,8 +62,7 @@ orion_cfg = ChatbotConfig(
|
|
| 62 |
"max_tokens": 200,
|
| 63 |
},
|
| 64 |
separator="\n",
|
| 65 |
-
|
| 66 |
-
text_after_response="I'm a bot π€ and not always perfect.",
|
| 67 |
text_before_prompt="""You are a slack chatbot assistant answering technical questions about orion, a hyperparameter optimization library written in python.
|
| 68 |
Make sure to format your answers in Markdown format, including code block and snippets.
|
| 69 |
Do not include any links to urls or hyperlinks in your answers.
|
|
@@ -95,8 +94,7 @@ pytorch_cfg = ChatbotConfig(
|
|
| 95 |
"max_tokens": 500,
|
| 96 |
},
|
| 97 |
separator="\n",
|
| 98 |
-
|
| 99 |
-
text_after_response="I'm a bot π€ and not always perfect.",
|
| 100 |
text_before_prompt="""You are a slack chatbot assistant answering technical questions about pytorch, a library to train neural networks written in python.
|
| 101 |
Make sure to format your answers in Markdown format, including code block and snippets.
|
| 102 |
Do not include any links to urls or hyperlinks in your answers.
|
|
@@ -128,8 +126,7 @@ hf_transformers_cfg = ChatbotConfig(
|
|
| 128 |
"max_tokens": 500,
|
| 129 |
},
|
| 130 |
separator="\n",
|
| 131 |
-
|
| 132 |
-
text_after_response="I'm a bot π€ and not always perfect.",
|
| 133 |
text_before_prompt="""You are a slack chatbot assistant answering technical questions about huggingface transformers, a library to train transformers in python.
|
| 134 |
Make sure to format your answers in Markdown format, including code block and snippets.
|
| 135 |
Do not include any links to urls or hyperlinks in your answers.
|
|
|
|
| 26 |
"max_tokens": 200,
|
| 27 |
},
|
| 28 |
separator="\n",
|
| 29 |
+
response_format="slack",
|
| 30 |
+
response_footnote="""I'm a bot π€ and not always perfect.
|
| 31 |
For more info, view the full documentation here (https://docs.mila.quebec/) or contact support@mila.quebec
|
| 32 |
""",
|
| 33 |
text_before_prompt="""
|
|
|
|
| 62 |
"max_tokens": 200,
|
| 63 |
},
|
| 64 |
separator="\n",
|
| 65 |
+
response_format="slack",
|
|
|
|
| 66 |
text_before_prompt="""You are a slack chatbot assistant answering technical questions about orion, a hyperparameter optimization library written in python.
|
| 67 |
Make sure to format your answers in Markdown format, including code block and snippets.
|
| 68 |
Do not include any links to urls or hyperlinks in your answers.
|
|
|
|
| 94 |
"max_tokens": 500,
|
| 95 |
},
|
| 96 |
separator="\n",
|
| 97 |
+
response_format="slack",
|
|
|
|
| 98 |
text_before_prompt="""You are a slack chatbot assistant answering technical questions about pytorch, a library to train neural networks written in python.
|
| 99 |
Make sure to format your answers in Markdown format, including code block and snippets.
|
| 100 |
Do not include any links to urls or hyperlinks in your answers.
|
|
|
|
| 126 |
"max_tokens": 500,
|
| 127 |
},
|
| 128 |
separator="\n",
|
| 129 |
+
response_format="slack",
|
|
|
|
| 130 |
text_before_prompt="""You are a slack chatbot assistant answering technical questions about huggingface transformers, a library to train transformers in python.
|
| 131 |
Make sure to format your answers in Markdown format, including code block and snippets.
|
| 132 |
Do not include any links to urls or hyperlinks in your answers.
|
buster/chatbot.py
CHANGED
|
@@ -10,11 +10,12 @@ import promptlayer
|
|
| 10 |
from openai.embeddings_utils import cosine_similarity, get_embedding
|
| 11 |
|
| 12 |
from buster.documents import get_documents_manager_from_extension
|
| 13 |
-
from buster.formatter import
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
|
|
|
| 18 |
|
| 19 |
logger = logging.getLogger(__name__)
|
| 20 |
logging.basicConfig(level=logging.INFO)
|
|
@@ -41,10 +42,10 @@ class ChatbotConfig:
|
|
| 41 |
max_words: maximum number of words the retrieved documents can be. Will truncate otherwise.
|
| 42 |
completion_kwargs: kwargs for the OpenAI.Completion() method
|
| 43 |
separator: the separator to use, can be either "\n" or <p> depending on rendering.
|
| 44 |
-
|
| 45 |
unknown_prompt: Prompt to use to generate the "I don't know" embedding to compare to.
|
| 46 |
text_before_prompt: Text to prompt GPT with before the user prompt, but after the documentation.
|
| 47 |
-
|
| 48 |
"""
|
| 49 |
|
| 50 |
documents_file: str = "buster/data/document_embeddings.tar.gz"
|
|
@@ -65,11 +66,11 @@ class ChatbotConfig:
|
|
| 65 |
}
|
| 66 |
)
|
| 67 |
separator: str = "\n"
|
| 68 |
-
|
| 69 |
unknown_prompt: str = "I Don't know how to answer your question."
|
| 70 |
text_before_documents: str = "You are a chatbot answering questions.\n"
|
| 71 |
text_before_prompt: str = "Answer the following question:\n"
|
| 72 |
-
|
| 73 |
|
| 74 |
|
| 75 |
class Chatbot:
|
|
@@ -78,6 +79,12 @@ class Chatbot:
|
|
| 78 |
self.cfg = cfg
|
| 79 |
self._init_documents()
|
| 80 |
self._init_unk_embedding()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
|
| 82 |
def _init_documents(self):
|
| 83 |
filepath = self.cfg.documents_file
|
|
@@ -183,10 +190,12 @@ class Chatbot:
|
|
| 183 |
)
|
| 184 |
if relevant:
|
| 185 |
sources = (
|
| 186 |
-
Source(dct["
|
| 187 |
for dct in matched_documents.to_dict(orient="records")
|
| 188 |
)
|
| 189 |
else:
|
|
|
|
|
|
|
| 190 |
sources = tuple()
|
| 191 |
|
| 192 |
return response, sources
|
|
@@ -211,16 +220,11 @@ class Chatbot:
|
|
| 211 |
# Likely that the answer is meaningful, add the top sources
|
| 212 |
return score < unk_threshold
|
| 213 |
|
| 214 |
-
def process_input(self, question: str, formatter:
|
| 215 |
"""
|
| 216 |
Main function to process the input question and generate a formatted output.
|
| 217 |
"""
|
| 218 |
|
| 219 |
-
if formatter is None and self.cfg.link_format not in FORMATTERS:
|
| 220 |
-
raise ValueError(f"Unknown link format {self.cfg.link_format}")
|
| 221 |
-
elif formatter is None:
|
| 222 |
-
formatter = FORMATTERS[self.cfg.link_format]()
|
| 223 |
-
|
| 224 |
logger.info(f"User Question:\n{question}")
|
| 225 |
|
| 226 |
# We make sure there is always a newline at the end of the question to avoid completing the question.
|
|
@@ -241,4 +245,4 @@ class Chatbot:
|
|
| 241 |
)
|
| 242 |
response, sources = self.generate_response(prompt, matched_documents, self.cfg.unknown_prompt)
|
| 243 |
|
| 244 |
-
return
|
|
|
|
| 10 |
from openai.embeddings_utils import cosine_similarity, get_embedding
|
| 11 |
|
| 12 |
from buster.documents import get_documents_manager_from_extension
|
| 13 |
+
from buster.formatter import (
|
| 14 |
+
Response,
|
| 15 |
+
ResponseFormatter,
|
| 16 |
+
Source,
|
| 17 |
+
response_formatter_factory,
|
| 18 |
+
)
|
| 19 |
|
| 20 |
logger = logging.getLogger(__name__)
|
| 21 |
logging.basicConfig(level=logging.INFO)
|
|
|
|
| 42 |
max_words: maximum number of words the retrieved documents can be. Will truncate otherwise.
|
| 43 |
completion_kwargs: kwargs for the OpenAI.Completion() method
|
| 44 |
separator: the separator to use, can be either "\n" or <p> depending on rendering.
|
| 45 |
+
response_format: the type of format to render links with, e.g. slack or markdown
|
| 46 |
unknown_prompt: Prompt to use to generate the "I don't know" embedding to compare to.
|
| 47 |
text_before_prompt: Text to prompt GPT with before the user prompt, but after the documentation.
|
| 48 |
+
reponse_footnote: Generic response to add the the chatbot's reply.
|
| 49 |
"""
|
| 50 |
|
| 51 |
documents_file: str = "buster/data/document_embeddings.tar.gz"
|
|
|
|
| 66 |
}
|
| 67 |
)
|
| 68 |
separator: str = "\n"
|
| 69 |
+
response_format: str = "slack"
|
| 70 |
unknown_prompt: str = "I Don't know how to answer your question."
|
| 71 |
text_before_documents: str = "You are a chatbot answering questions.\n"
|
| 72 |
text_before_prompt: str = "Answer the following question:\n"
|
| 73 |
+
response_footnote: str = "I'm a bot π€ and not always perfect."
|
| 74 |
|
| 75 |
|
| 76 |
class Chatbot:
|
|
|
|
| 79 |
self.cfg = cfg
|
| 80 |
self._init_documents()
|
| 81 |
self._init_unk_embedding()
|
| 82 |
+
self._init_response_formatter()
|
| 83 |
+
|
| 84 |
+
def _init_response_formatter(self):
|
| 85 |
+
self.response_formatter = response_formatter_factory(
|
| 86 |
+
format=self.cfg.response_format, response_footnote=self.cfg.response_footnote
|
| 87 |
+
)
|
| 88 |
|
| 89 |
def _init_documents(self):
|
| 90 |
filepath = self.cfg.documents_file
|
|
|
|
| 190 |
)
|
| 191 |
if relevant:
|
| 192 |
sources = (
|
| 193 |
+
Source(dct["source"], dct["url"], dct["similarity"])
|
| 194 |
for dct in matched_documents.to_dict(orient="records")
|
| 195 |
)
|
| 196 |
else:
|
| 197 |
+
# Override the answer with a generic unknown prompt, without sources.
|
| 198 |
+
response = Response(text=self.cfg.unknown_prompt)
|
| 199 |
sources = tuple()
|
| 200 |
|
| 201 |
return response, sources
|
|
|
|
| 220 |
# Likely that the answer is meaningful, add the top sources
|
| 221 |
return score < unk_threshold
|
| 222 |
|
| 223 |
+
def process_input(self, question: str, formatter: ResponseFormatter = None) -> str:
|
| 224 |
"""
|
| 225 |
Main function to process the input question and generate a formatted output.
|
| 226 |
"""
|
| 227 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 228 |
logger.info(f"User Question:\n{question}")
|
| 229 |
|
| 230 |
# We make sure there is always a newline at the end of the question to avoid completing the question.
|
|
|
|
| 245 |
)
|
| 246 |
response, sources = self.generate_response(prompt, matched_documents, self.cfg.unknown_prompt)
|
| 247 |
|
| 248 |
+
return self.response_formatter(response, sources)
|
buster/formatter/__init__.py
CHANGED
|
@@ -1,6 +1,17 @@
|
|
| 1 |
-
from .base import
|
| 2 |
-
from .
|
| 3 |
-
from .
|
| 4 |
-
from .
|
|
|
|
|
|
|
| 5 |
|
| 6 |
-
__all__ = [
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .base import Response, ResponseFormatter, Source
|
| 2 |
+
from .factory import response_formatter_factory
|
| 3 |
+
from .gradio import GradioResponseFormatter
|
| 4 |
+
from .html import HTMLResponseFormatter
|
| 5 |
+
from .markdown import MarkdownResponseFormatter
|
| 6 |
+
from .slack import SlackResponseFormatter
|
| 7 |
|
| 8 |
+
__all__ = [
|
| 9 |
+
Source,
|
| 10 |
+
Response,
|
| 11 |
+
ResponseFormatter,
|
| 12 |
+
HTMLResponseFormatter,
|
| 13 |
+
MarkdownResponseFormatter,
|
| 14 |
+
SlackResponseFormatter,
|
| 15 |
+
GradioResponseFormatter,
|
| 16 |
+
response_formatter_factory,
|
| 17 |
+
]
|
buster/formatter/base.py
CHANGED
|
@@ -4,7 +4,7 @@ from typing import Iterable, NamedTuple
|
|
| 4 |
|
| 5 |
# Should be from the `documents` module.
|
| 6 |
class Source(NamedTuple):
|
| 7 |
-
|
| 8 |
url: str
|
| 9 |
question_similarity: float
|
| 10 |
# TODO Add answer similarity.
|
|
@@ -20,12 +20,18 @@ class Response:
|
|
| 20 |
|
| 21 |
|
| 22 |
@dataclass
|
| 23 |
-
class
|
|
|
|
| 24 |
source_template: str = "{source.name} (relevance: {source.question_similarity:2.3f})"
|
| 25 |
-
error_msg_template: str = "Something went wrong
|
| 26 |
error_fallback_template: str = "Something went very wrong."
|
| 27 |
-
sourced_answer_template: str =
|
| 28 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
|
| 30 |
def source_item(self, source: Source) -> str:
|
| 31 |
"""Format a single source item."""
|
|
@@ -48,10 +54,12 @@ class Formatter:
|
|
| 48 |
def answer(self, response: Response, sources: Iterable[Source]) -> str:
|
| 49 |
"""Format an answer and its sources."""
|
| 50 |
sources_list = self.sources_list(sources)
|
| 51 |
-
if
|
| 52 |
-
return self.sourced_answer_template.format(
|
|
|
|
|
|
|
| 53 |
|
| 54 |
-
return self.unsourced_answer_template.format(response=response)
|
| 55 |
|
| 56 |
def __call__(self, response: Response, sources: Iterable[Source]) -> str:
|
| 57 |
"""Format an answer and its sources, or an error message."""
|
|
|
|
| 4 |
|
| 5 |
# Should be from the `documents` module.
|
| 6 |
class Source(NamedTuple):
|
| 7 |
+
source: str
|
| 8 |
url: str
|
| 9 |
question_similarity: float
|
| 10 |
# TODO Add answer similarity.
|
|
|
|
| 20 |
|
| 21 |
|
| 22 |
@dataclass
|
| 23 |
+
class ResponseFormatter:
|
| 24 |
+
response_footnote: str
|
| 25 |
source_template: str = "{source.name} (relevance: {source.question_similarity:2.3f})"
|
| 26 |
+
error_msg_template: str = """Something went wrong:\n{response.error_msg}"""
|
| 27 |
error_fallback_template: str = "Something went very wrong."
|
| 28 |
+
sourced_answer_template: str = (
|
| 29 |
+
"""{response.text}\n\n"""
|
| 30 |
+
"""π Here are the sources I used to answer your question:\n"""
|
| 31 |
+
"""{sources}\n\n"""
|
| 32 |
+
"""{footnote}"""
|
| 33 |
+
)
|
| 34 |
+
unsourced_answer_template: str = "{response.text}\n\n{footnote}"
|
| 35 |
|
| 36 |
def source_item(self, source: Source) -> str:
|
| 37 |
"""Format a single source item."""
|
|
|
|
| 54 |
def answer(self, response: Response, sources: Iterable[Source]) -> str:
|
| 55 |
"""Format an answer and its sources."""
|
| 56 |
sources_list = self.sources_list(sources)
|
| 57 |
+
if sources_list:
|
| 58 |
+
return self.sourced_answer_template.format(
|
| 59 |
+
response=response, sources=sources_list, footnote=self.response_footnote
|
| 60 |
+
)
|
| 61 |
|
| 62 |
+
return self.unsourced_answer_template.format(response=response, footnote=self.response_footnote)
|
| 63 |
|
| 64 |
def __call__(self, response: Response, sources: Iterable[Source]) -> str:
|
| 65 |
"""Format an answer and its sources, or an error message."""
|
buster/formatter/factory.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import logging
|
| 2 |
+
|
| 3 |
+
import buster.formatter as F
|
| 4 |
+
|
| 5 |
+
logger = logging.getLogger(__name__)
|
| 6 |
+
logging.basicConfig(level=logging.INFO)
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
def response_formatter_factory(format: str, **kwargs):
|
| 10 |
+
logger.info(f"Using formatter: {format}")
|
| 11 |
+
if format == "text":
|
| 12 |
+
return F.ResponseFormatter(**kwargs)
|
| 13 |
+
elif format == "slack":
|
| 14 |
+
return F.SlackResponseFormatter(**kwargs)
|
| 15 |
+
elif format == "HTML":
|
| 16 |
+
return F.HTMLResponseFormatter(**kwargs)
|
| 17 |
+
elif format == "gradio":
|
| 18 |
+
return F.GradioResponseFormatter(**kwargs)
|
| 19 |
+
elif format == "markdown":
|
| 20 |
+
return F.MarkdownResponseFormatter(**kwargs)
|
| 21 |
+
else:
|
| 22 |
+
raise ValueError(f"Undefined {format=}")
|
buster/formatter/gradio.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from dataclasses import dataclass
|
| 2 |
+
from typing import Iterable
|
| 3 |
+
|
| 4 |
+
from buster.formatter import ResponseFormatter, Source
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
@dataclass
|
| 8 |
+
class GradioResponseFormatter(ResponseFormatter):
|
| 9 |
+
"""Format the answer for gradio chat interface."""
|
| 10 |
+
|
| 11 |
+
error_msg_template: str = """Something went wrong:<br>{response.error_msg}"""
|
| 12 |
+
error_fallback_template: str = "Something went very wrong."
|
| 13 |
+
sourced_answer_template: str = (
|
| 14 |
+
"""{response.text}<br><br>"""
|
| 15 |
+
"""π Here are the sources I used to answer your question:<br>"""
|
| 16 |
+
"""{sources}<br><br>"""
|
| 17 |
+
"""{footnote}"""
|
| 18 |
+
)
|
| 19 |
+
unsourced_answer_template: str = "{response.text}<br><br>{footnote}"
|
| 20 |
+
source_template: str = """[π {source.source}]({source.url}), relevance: {source.question_similarity:2.3f}"""
|
| 21 |
+
|
| 22 |
+
def sources_list(self, sources: Iterable[Source]) -> str | None:
|
| 23 |
+
"""Format sources into a list."""
|
| 24 |
+
items = [self.source_item(source) for source in sources]
|
| 25 |
+
if not items:
|
| 26 |
+
return None # No list needed.
|
| 27 |
+
|
| 28 |
+
return "<br>".join(items)
|
buster/formatter/html.py
CHANGED
|
@@ -2,14 +2,14 @@ import html
|
|
| 2 |
from dataclasses import dataclass
|
| 3 |
from typing import Iterable
|
| 4 |
|
| 5 |
-
from buster.formatter.base import
|
| 6 |
|
| 7 |
|
| 8 |
@dataclass
|
| 9 |
-
class
|
| 10 |
"""Format the answer in HTML."""
|
| 11 |
|
| 12 |
-
source_template: str = """<li><a href='{source.url}'>π {source.
|
| 13 |
error_msg_template: str = """<div class="error">Something went wrong:\n<p>{response.error_msg}</p></div>"""
|
| 14 |
error_fallback_template: str = """<div class="error">Something went very wrong.</div>"""
|
| 15 |
sourced_answer_template: str = (
|
|
@@ -37,5 +37,5 @@ class HTMLFormatter(Formatter):
|
|
| 37 |
response.error,
|
| 38 |
html.escape(response.error_msg) if response.error_msg else response.error_msg,
|
| 39 |
)
|
| 40 |
-
sources = (Source(html.escape(source.
|
| 41 |
return super().__call__(response, sources)
|
|
|
|
| 2 |
from dataclasses import dataclass
|
| 3 |
from typing import Iterable
|
| 4 |
|
| 5 |
+
from buster.formatter.base import Response, ResponseFormatter, Source
|
| 6 |
|
| 7 |
|
| 8 |
@dataclass
|
| 9 |
+
class HTMLResponseFormatter(ResponseFormatter):
|
| 10 |
"""Format the answer in HTML."""
|
| 11 |
|
| 12 |
+
source_template: str = """<li><a href='{source.url}'>π {source.source}</a></li>"""
|
| 13 |
error_msg_template: str = """<div class="error">Something went wrong:\n<p>{response.error_msg}</p></div>"""
|
| 14 |
error_fallback_template: str = """<div class="error">Something went very wrong.</div>"""
|
| 15 |
sourced_answer_template: str = (
|
|
|
|
| 37 |
response.error,
|
| 38 |
html.escape(response.error_msg) if response.error_msg else response.error_msg,
|
| 39 |
)
|
| 40 |
+
sources = (Source(html.escape(source.source), source.url, source.question_similarity) for source in sources)
|
| 41 |
return super().__call__(response, sources)
|
buster/formatter/markdown.py
CHANGED
|
@@ -1,23 +1,14 @@
|
|
| 1 |
from dataclasses import dataclass
|
| 2 |
from typing import Iterable
|
| 3 |
|
| 4 |
-
from buster.formatter.base import
|
| 5 |
|
| 6 |
|
| 7 |
@dataclass
|
| 8 |
-
class
|
| 9 |
"""Format the answer in markdown."""
|
| 10 |
|
| 11 |
-
source_template: str = """[π {source.
|
| 12 |
-
error_msg_template: str = """Something went wrong:\n{response.error_msg}"""
|
| 13 |
-
error_fallback_template: str = """Something went very wrong."""
|
| 14 |
-
sourced_answer_template: str = (
|
| 15 |
-
"""{response.text}\n\n"""
|
| 16 |
-
"""π Here are the sources I used to answer your question:\n"""
|
| 17 |
-
"""{sources}\n\n"""
|
| 18 |
-
"""I'm a chatbot, bleep bloop."""
|
| 19 |
-
)
|
| 20 |
-
unsourced_answer_template: str = """{response.text}\n\nI'm a chatbot, bleep bloop."""
|
| 21 |
|
| 22 |
def sources_list(self, sources: Iterable[Source]) -> str | None:
|
| 23 |
"""Format sources into a list."""
|
|
|
|
| 1 |
from dataclasses import dataclass
|
| 2 |
from typing import Iterable
|
| 3 |
|
| 4 |
+
from buster.formatter.base import ResponseFormatter, Source
|
| 5 |
|
| 6 |
|
| 7 |
@dataclass
|
| 8 |
+
class MarkdownResponseFormatter(ResponseFormatter):
|
| 9 |
"""Format the answer in markdown."""
|
| 10 |
|
| 11 |
+
source_template: str = """[π {source.source}]({source.url}), relevance: {source.question_similarity:2.3f}"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
|
| 13 |
def sources_list(self, sources: Iterable[Source]) -> str | None:
|
| 14 |
"""Format sources into a list."""
|
buster/formatter/slack.py
CHANGED
|
@@ -1,23 +1,14 @@
|
|
| 1 |
from dataclasses import dataclass
|
| 2 |
from typing import Iterable
|
| 3 |
|
| 4 |
-
from buster.formatter
|
| 5 |
|
| 6 |
|
| 7 |
@dataclass
|
| 8 |
-
class
|
| 9 |
"""Format the answer for Slack."""
|
| 10 |
|
| 11 |
-
source_template: str = """<{source.url}|π {source.
|
| 12 |
-
error_msg_template: str = """Something went wrong:\n{response.error_msg}"""
|
| 13 |
-
error_fallback_template: str = """Something went very wrong."""
|
| 14 |
-
sourced_answer_template: str = (
|
| 15 |
-
"""{response.text}\n\n"""
|
| 16 |
-
"""π Here are the sources I used to answer your question:\n"""
|
| 17 |
-
"""{sources}\n\n"""
|
| 18 |
-
"""I'm a chatbot, bleep bloop."""
|
| 19 |
-
)
|
| 20 |
-
unsourced_answer_template: str = """{response.text}\n\nI'm a chatbot, bleep bloop."""
|
| 21 |
|
| 22 |
def sources_list(self, sources: Iterable[Source]) -> str | None:
|
| 23 |
"""Format sources into a list."""
|
|
|
|
| 1 |
from dataclasses import dataclass
|
| 2 |
from typing import Iterable
|
| 3 |
|
| 4 |
+
from buster.formatter import ResponseFormatter, Source
|
| 5 |
|
| 6 |
|
| 7 |
@dataclass
|
| 8 |
+
class SlackResponseFormatter(ResponseFormatter):
|
| 9 |
"""Format the answer for Slack."""
|
| 10 |
|
| 11 |
+
source_template: str = """<{source.url}|π {source.source}>, relevance: {source.question_similarity:2.3f}"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
|
| 13 |
def sources_list(self, sources: Iterable[Source]) -> str | None:
|
| 14 |
"""Format sources into a list."""
|
pyproject.toml
CHANGED
|
@@ -7,7 +7,7 @@ name = "buster"
|
|
| 7 |
version = "0.0.1"
|
| 8 |
description = "buster the bot for the mila cluster"
|
| 9 |
readme = "README.md"
|
| 10 |
-
requires-python = ">=3.
|
| 11 |
dynamic = ["dependencies"]
|
| 12 |
|
| 13 |
[tool.setuptools.dynamic]
|
|
|
|
| 7 |
version = "0.0.1"
|
| 8 |
description = "buster the bot for the mila cluster"
|
| 9 |
readme = "README.md"
|
| 10 |
+
requires-python = ">=3.10"
|
| 11 |
dynamic = ["dependencies"]
|
| 12 |
|
| 13 |
[tool.setuptools.dynamic]
|