jerpint commited on
Commit
5b7d0e6
Β·
unverified Β·
1 Parent(s): 71e7dd8

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 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
- " separator=\"<br>\",\n",
28
- " link_format=\"markdown\",\n",
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": "base",
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.12 (main, Apr 5 2022, 01:52:34) \n[Clang 12.0.0 ]"
127
  },
128
  "vscode": {
129
  "interpreter": {
130
- "hash": "5abee959af829406add33dbeab4b81a0c8afd11a2faef151b217e9aebad2d8c1"
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
- link_format="slack",
30
- text_after_response="""I'm a bot πŸ€– and not always perfect.
31
  For more info, view the full documentation here (https://docs.mila.quebec/) or contact [email protected]
32
  """,
33
  text_before_prompt="""
@@ -62,8 +62,7 @@ orion_cfg = ChatbotConfig(
62
  "max_tokens": 200,
63
  },
64
  separator="\n",
65
- link_format="slack",
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
- link_format="slack",
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
- link_format="slack",
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 [email protected]
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 Formatter, HTMLFormatter, MarkdownFormatter, SlackFormatter
14
- from buster.formatter.base import Response, Source
15
-
16
- FORMATTERS = {"text": Formatter, "slack": SlackFormatter, "html": HTMLFormatter, "markdown": MarkdownFormatter}
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
- link_format: the type of format to render links with, e.g. slack or markdown
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
- text_after_response: Generic response to add the the chatbot's reply.
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
- link_format: str = "slack"
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
- text_after_response: str = "I'm a chatbot, bleep bloop."
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["name"], dct["url"], dct["similarity"])
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: Formatter = None) -> str:
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 formatter(response, sources)
 
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 Formatter
2
- from .html import HTMLFormatter
3
- from .markdown import MarkdownFormatter
4
- from .slack import SlackFormatter
 
 
5
 
6
- __all__ = [Formatter, HTMLFormatter, MarkdownFormatter, SlackFormatter]
 
 
 
 
 
 
 
 
 
 
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
- name: str
8
  url: str
9
  question_similarity: float
10
  # TODO Add answer similarity.
@@ -20,12 +20,18 @@ class Response:
20
 
21
 
22
  @dataclass
23
- class Formatter:
 
24
  source_template: str = "{source.name} (relevance: {source.question_similarity:2.3f})"
25
- error_msg_template: str = "Something went wrong: {response.error_msg}"
26
  error_fallback_template: str = "Something went very wrong."
27
- sourced_answer_template: str = "{response.text}\n\nSources:\n{sources}\n\nBut what do I know, I'm a chatbot."
28
- unsourced_answer_template: str = "{response.text}\n\nBut what do I know, I'm a chatbot."
 
 
 
 
 
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 not sources_list:
52
- return self.sourced_answer_template.format(response=response, sources=sources_list)
 
 
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 Formatter, Response, Source
6
 
7
 
8
  @dataclass
9
- class HTMLFormatter(Formatter):
10
  """Format the answer in HTML."""
11
 
12
- source_template: str = """<li><a href='{source.url}'>πŸ”— {source.name}</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,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.name), source.url, source.question_similarity) for source in sources)
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 Formatter, Source
5
 
6
 
7
  @dataclass
8
- class MarkdownFormatter(Formatter):
9
  """Format the answer in markdown."""
10
 
11
- source_template: str = """[πŸ”— {source.name}]({source.url}), relevance: {source.question_similarity:2.3f}"""
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.base import Formatter, Source
5
 
6
 
7
  @dataclass
8
- class SlackFormatter(Formatter):
9
  """Format the answer for Slack."""
10
 
11
- source_template: str = """<{source.url}|πŸ”— {source.name}>, relevance: {source.question_similarity:2.3f}"""
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.8"
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]