60 Commits

Author SHA1 Message Date
juk0de d4021eeb11 configuration: made 'default' AI ID optional 2023-09-11 07:38:49 +02:00
juk0de c143c001f9 configuration: improved config file format 2023-09-10 19:57:06 +02:00
juk0de 59b851650a question_cmd: when no tags are specified, no tags are selected 2023-09-10 19:57:06 +02:00
juk0de 6f71a2ff69 message: to_file() now uses intermediate temporary file 2023-09-10 19:57:06 +02:00
juk0de eca44b14cb message: fixed matching with empty tag sets 2023-09-10 19:55:21 +02:00
juk0de b48667bfa0 openai: stores AI.ID instead of AI.name in message 2023-09-10 19:18:44 +02:00
juk0de 533ee1c1a9 question_cmd: added message filtering by tags 2023-09-10 19:18:44 +02:00
juk0de cf50818f28 question_cmd: fixed '--ask' command 2023-09-10 19:18:44 +02:00
juk0de dd3d3ffc82 chat: added check for existing files when creating new filenames 2023-09-10 19:18:44 +02:00
juk0de 1e3bfdd67f chat: added 'update_messages()' function and test 2023-09-10 19:14:11 +02:00
juk0de 53582a7123 question_cmd: fixed source code extraction and added a testcase 2023-09-10 19:14:11 +02:00
Oleksandr Kozachuk 39b518a8a6 Small fixes. 2023-09-09 16:05:27 +02:00
Oleksandr Kozachuk d22877a0f1 Port print arguments -q/-a/-S from main to restructuring. 2023-09-09 15:38:40 +02:00
Oleksandr Kozachuk 7cf62c54ef Allow in question -s for just sourcing file and -S to source file with ``` encapsulation. 2023-09-09 15:16:17 +02:00
juk0de 5fb5dde550 question cmd: added tests 2023-09-09 09:12:21 +02:00
juk0de c0b7d17587 question_cmd: fixes 2023-09-09 08:51:44 +02:00
juk0de 76f2373397 configuration: added tests 2023-09-09 08:31:45 +02:00
juk0de eaa399bcb9 configuration et al: implemented new Config format 2023-09-09 08:31:45 +02:00
juk0de b1a23394fc cmm: splitted commands into separate modules (and more cleanup) 2023-09-09 08:31:45 +02:00
juk0de 2df9dd6427 cmm: removed all the old code and modules 2023-09-08 13:04:11 +02:00
juk0de 74a26b8c2f setup: added 'ais' subfolder 2023-09-08 09:44:07 +02:00
juk0de 893917e455 test_main: temporarily disabled all testcases 2023-09-07 07:51:46 +02:00
juk0de ba5aa1fbc7 cmm: added 'question' command 2023-09-06 08:23:09 +02:00
juk0de eb2fcba99d added new module 'ai_factory' 2023-09-06 08:23:09 +02:00
juk0de b7e3ca7ca7 added new module 'openai.py' 2023-09-06 08:23:09 +02:00
juk0de aa322de718 added new module 'ai.py' 2023-09-06 08:23:09 +02:00
juk0de bf1cbff6a2 cmm: the 'print' command now uses 'Message.from_file()' 2023-09-06 08:23:09 +02:00
juk0de f93a57c00d cmm: tags completion now uses 'Message.tags_from_dir' (fixes tag completion for me) 2023-09-06 08:23:09 +02:00
juk0de b0504aedbe cmm: the 'hist' command now uses the new 'ChatDB' 2023-09-06 08:23:09 +02:00
juk0de eb0d97ddc8 cmm: the 'tags' command now uses the new 'ChatDB' 2023-09-06 08:23:09 +02:00
juk0de 7e25a08d6e chat: added functions for finding and deleting messages 2023-09-06 08:23:09 +02:00
juk0de 63040b3688 message / chat: output improvements 2023-09-04 23:03:29 +02:00
juk0de 6e2d5009c1 chat: new possibilites for adding messages and better tests 2023-09-04 08:58:08 +02:00
juk0de 44cd1fab45 message: added rename_tags() function and test 2023-09-03 09:09:39 +02:00
juk0de 4b0f40bccd message: fixed Answer header for TXT format 2023-09-03 09:09:39 +02:00
juk0de fa292fb73a message: improved robustness of Question and Answer content checks and tests 2023-09-03 09:09:39 +02:00
juk0de f9d749cdd8 chat: added clear_cache() function and test 2023-09-03 09:09:39 +02:00
juk0de ba56caf013 chat: improved history printing 2023-09-03 09:09:39 +02:00
juk0de d80c3962bd chat: fixed handling of unsupported files in DB and chache dir 2023-09-03 09:09:39 +02:00
juk0de ddfe29b951 chat: added tags_frequency() function and test 2023-09-03 09:09:39 +02:00
juk0de d93598a74f configuration: added AIConfig class 2023-09-03 09:09:39 +02:00
juk0de 7f612bfc17 added tokens() function to Message and Chat 2023-09-02 07:45:44 +02:00
juk0de 93290da5b5 added tests for 'chat.py' 2023-09-02 07:45:44 +02:00
juk0de 9f4897a5b8 added new module 'chat.py' 2023-09-02 07:45:44 +02:00
juk0de 214a6919db tags: some clarification and new tests 2023-08-31 15:47:58 +02:00
juk0de b83cbb719b added 'message_in()' function and test 2023-08-31 09:21:51 +02:00
juk0de 8e1cdee3bf fixed Message.filter_tags 2023-08-30 08:22:50 +02:00
juk0de 73d2a9ea3b fixed test case file cleanup 2023-08-29 11:36:01 +02:00
juk0de 169f1bb458 fixed handling empty tags in TXT file 2023-08-28 14:53:04 +02:00
juk0de 7f91a2b567 Added tags filtering (prefix and contained string) to TagLine and Message 2023-08-28 14:53:04 +02:00
juk0de fc1b8006a0 tests: added testcases for Message.from/to_file() and others 2023-08-26 19:22:15 +02:00
juk0de aa89270876 tests: splitted 'test_main.py' into 3 modules 2023-08-26 19:22:15 +02:00
juk0de 0d6a6dd604 gitignore: added vim session file 2023-08-26 19:22:15 +02:00
juk0de 580c86e948 tags: TagLine constructor now supports multiline taglines and multiple spaces 2023-08-26 19:22:15 +02:00
juk0de 879831d7f5 configuration: added 'as_dict()' as an instance function 2023-08-26 19:22:15 +02:00
juk0de dfc1261931 added testcases for messages.py 2023-08-26 19:22:10 +02:00
juk0de 173a46a9b5 added new module 'message.py' 2023-08-26 10:52:38 +02:00
juk0de 604e5ccf73 tags.py: converted most TagLine functions to module functions 2023-08-18 12:21:46 +02:00
juk0de ef46f5efc9 added testcases for Tag and TagLine classes 2023-08-18 12:21:46 +02:00
juk0de b13a68836a added new module 'tags.py' with classes 'Tag' and 'TagLine' 2023-08-18 12:21:46 +02:00
12 changed files with 126 additions and 153 deletions
+6
View File
@@ -66,3 +66,9 @@ class AI(Protocol):
and is not implemented for all AIs.
"""
raise NotImplementedError
def print(self) -> None:
"""
Print some info about the current AI, like system message.
"""
pass
+12 -6
View File
@@ -4,25 +4,31 @@ Creates different AI instances, based on the given configuration.
import argparse
from typing import cast
from .configuration import Config, OpenAIConfig, default_ai_ID
from .configuration import Config, AIConfig, OpenAIConfig
from .ai import AI, AIError
from .ais.openai import OpenAI
def create_ai(args: argparse.Namespace, config: Config) -> AI:
def create_ai(args: argparse.Namespace, config: Config) -> AI: # noqa: 11
"""
Creates an AI subclass instance from the given arguments
and configuration file.
and configuration file. If AI has not been set in the
arguments, it searches for the ID 'default'. If that
is not found, it uses the first AI in the list.
"""
ai_conf: AIConfig
if args.AI:
try:
ai_conf = config.ais[args.AI]
except KeyError:
raise AIError(f"AI ID '{args.AI}' does not exist in this configuration")
elif default_ai_ID in config.ais:
ai_conf = config.ais[default_ai_ID]
elif 'default' in config.ais:
ai_conf = config.ais['default']
else:
raise AIError("No AI name given and no default exists")
try:
ai_conf = next(iter(config.ais.values()))
except StopIteration:
raise AIError("No AI found in this configuration")
if ai_conf.name == 'openai':
ai = OpenAI(cast(OpenAIConfig, ai_conf))
+15 -6
View File
@@ -43,16 +43,20 @@ class OpenAI(AI):
n=num_answers,
frequency_penalty=self.config.frequency_penalty,
presence_penalty=self.config.presence_penalty)
answers: list[Message] = []
for choice in response['choices']: # type: ignore
question.answer = Answer(response['choices'][0]['message']['content'])
question.tags = otags
question.ai = self.ID
question.model = self.config.model
answers: list[Message] = [question]
for choice in response['choices'][1:]: # type: ignore
answers.append(Message(question=question.question,
answer=Answer(choice['message']['content']),
tags=otags,
ai=self.name,
ai=self.ID,
model=self.config.model))
return AIResponse(answers, Tokens(response['usage']['prompt'],
response['usage']['completion'],
response['usage']['total']))
return AIResponse(answers, Tokens(response['usage']['prompt_tokens'],
response['usage']['completion_tokens'],
response['usage']['total_tokens']))
def models(self) -> list[str]:
"""
@@ -95,3 +99,8 @@ class OpenAI(AI):
def tokens(self, data: Union[Message, Chat]) -> int:
raise NotImplementedError
def print(self) -> None:
print(f"MODEL: {self.config.model}")
print("=== SYSTEM ===")
print(self.config.system)
+4 -1
View File
@@ -62,7 +62,10 @@ def make_file_path(dir_path: Path,
Create a file_path for the given directory using the
given file_suffix and ID generator function.
"""
return dir_path / f"{next_fid():04d}{file_suffix}"
file_path = dir_path / f"{next_fid():04d}{file_suffix}"
while file_path.exists():
file_path = dir_path / f"{next_fid():04d}{file_suffix}"
return file_path
def write_dir(dir_path: Path,
+18 -9
View File
@@ -3,7 +3,7 @@ from pathlib import Path
from itertools import zip_longest
from ..configuration import Config
from ..chat import ChatDB
from ..message import Message, Question, source_code
from ..message import Message, MessageFilter, Question, source_code
from ..ai_factory import create_ai
from ..ai import AI, AIResponse
@@ -52,8 +52,12 @@ def question_cmd(args: argparse.Namespace, config: Config) -> None:
"""
Handler for the 'question' command.
"""
mfilter = MessageFilter(tags_or=args.or_tags if args.or_tags is not None else set(),
tags_and=args.and_tags if args.and_tags is not None else set(),
tags_not=args.exclude_tags if args.exclude_tags is not None else set())
chat = ChatDB.from_dir(cache_path=Path('.'),
db_path=Path(config.db))
db_path=Path(config.db),
mfilter=mfilter)
# if it's a new question, create and store it immediately
if args.ask or args.create:
message = create_message(chat, args)
@@ -63,23 +67,28 @@ def question_cmd(args: argparse.Namespace, config: Config) -> None:
# create the correct AI instance
ai: AI = create_ai(args, config)
if args.ask:
ai.print()
chat.print(paged=False)
response: AIResponse = ai.request(message,
chat,
args.num_answers, # FIXME
args.output_tags) # FIXME
assert response
# TODO:
# * add answer to the message above (and create
# more messages for any additional answers)
pass
elif args.repeat:
chat.update_messages([response.messages[0]])
chat.add_to_cache(response.messages[1:])
for idx, msg in enumerate(response.messages):
print(f"=== ANSWER {idx+1} ===")
print(msg.answer)
if response.tokens:
print("===============")
print(response.tokens)
elif args.repeat is not None:
lmessage = chat.latest_message()
assert lmessage
# TODO: repeat either the last question or the
# one(s) given in 'args.repeat' (overwrite
# existing ones if 'args.overwrite' is True)
pass
elif args.process:
elif args.process is not None:
# TODO: process either all questions without an
# answer or the one(s) given in 'args.process'
pass
+19 -6
View File
@@ -9,7 +9,6 @@ OpenAIConfigInst = TypeVar('OpenAIConfigInst', bound='OpenAIConfig')
supported_ais: list[str] = ['openai']
default_ai_ID: str = 'default'
default_config_path = '.config.yaml'
@@ -17,6 +16,18 @@ class ConfigError(Exception):
pass
def str_presenter(dumper: yaml.Dumper, data: str) -> yaml.ScalarNode:
"""
Changes the YAML dump style to multiline syntax for multiline strings.
"""
if len(data.splitlines()) > 1:
return dumper.represent_scalar('tag:yaml.org,2002:str', data, style='|')
return dumper.represent_scalar('tag:yaml.org,2002:str', data)
yaml.add_representer(str, str_presenter)
@dataclass
class AIConfig:
"""
@@ -46,15 +57,15 @@ class OpenAIConfig(AIConfig):
# all members have default values, so we can easily create
# a default configuration
ID: str = 'default'
ID: str = 'myopenai'
api_key: str = '0123456789'
system: str = 'You are an assistant'
model: str = 'gpt-3.5-turbo-16k'
temperature: float = 1.0
max_tokens: int = 4000
top_p: float = 1.0
frequency_penalty: float = 0.0
presence_penalty: float = 0.0
system: str = 'You are an assistant'
@classmethod
def from_dict(cls: Type[OpenAIConfigInst], source: dict[str, Any]) -> OpenAIConfigInst:
@@ -62,14 +73,14 @@ class OpenAIConfig(AIConfig):
Create OpenAIConfig from a dict.
"""
res = cls(
system=str(source['system']),
api_key=str(source['api_key']),
model=str(source['model']),
max_tokens=int(source['max_tokens']),
temperature=float(source['temperature']),
top_p=float(source['top_p']),
frequency_penalty=float(source['frequency_penalty']),
presence_penalty=float(source['presence_penalty'])
presence_penalty=float(source['presence_penalty']),
system=str(source['system'])
)
# overwrite default ID if provided
if 'ID' in source:
@@ -148,6 +159,8 @@ class Config:
def as_dict(self) -> dict[str, Any]:
res = asdict(self)
# add the AI name manually (as first element)
# (not done by 'asdict' because it's a class variable)
for ID, conf in res['ais'].items():
conf.update({'name': self.ais[ID].name})
res['ais'][ID] = {**{'name': self.ais[ID].name}, **conf}
return res
+1 -104
View File
@@ -18,110 +18,7 @@ from .commands.print import print_cmd
def tags_completer(prefix: str, parsed_args: Any, **kwargs: Any) -> list[str]:
config = Config.from_file(parsed_args.config)
return get_tags_unique(config, prefix)
def tags_cmd(args: argparse.Namespace, config: Config) -> None:
"""
Handler for the 'tags' command.
"""
chat = ChatDB.from_dir(cache_path=Path('.'),
db_path=Path(config.db))
if args.list:
tags_freq = chat.tags_frequency(args.prefix, args.contain)
for tag, freq in tags_freq.items():
print(f"- {tag}: {freq}")
# TODO: add renaming
def config_cmd(args: argparse.Namespace) -> None:
"""
Handler for the 'config' command.
"""
if args.create:
Config.create_default(Path(args.create))
def question_cmd(args: argparse.Namespace, config: Config) -> None:
"""
Handler for the 'question' command.
"""
chat = ChatDB.from_dir(cache_path=Path('.'),
db_path=Path(config.db))
# if it's a new question, create and store it immediately
if args.ask or args.create:
# FIXME: add sources to the question
message = Message(question=Question(args.question),
tags=args.ouput_tags, # FIXME
ai=args.ai,
model=args.model)
chat.add_to_cache([message])
if args.create:
return
# create the correct AI instance
ai: AI = create_ai(args, config)
if args.ask:
response: AIResponse = ai.request(message,
chat,
args.num_answers, # FIXME
args.otags) # FIXME
assert response
# TODO:
# * add answer to the message above (and create
# more messages for any additional answers)
pass
elif args.repeat:
lmessage = chat.latest_message()
assert lmessage
# TODO: repeat either the last question or the
# one(s) given in 'args.repeat' (overwrite
# existing ones if 'args.overwrite' is True)
pass
elif args.process:
# TODO: process either all questions without an
# answer or the one(s) given in 'args.process'
pass
def hist_cmd(args: argparse.Namespace, config: Config) -> None:
"""
Handler for the 'hist' command.
"""
mfilter = MessageFilter(tags_or=args.or_tags,
tags_and=args.and_tags,
tags_not=args.exclude_tags,
question_contains=args.question,
answer_contains=args.answer)
chat = ChatDB.from_dir(Path('.'),
Path(config.db),
mfilter=mfilter)
chat.print(args.source_code_only,
args.with_tags,
args.with_files)
def print_cmd(args: argparse.Namespace, config: Config) -> None:
"""
Handler for the 'print' command.
"""
fname = Path(args.file)
try:
message = Message.from_file(fname)
if message:
print(message.to_str(source_code_only=args.source_code_only))
except MessageError:
print(f"File is not a valid message: {args.file}")
sys.exit(1)
if args.source_code_only:
display_source_code(data['answer'])
elif args.answer:
print(data['answer'].strip())
elif args.question:
print(data['question'].strip())
else:
print(dump_data(data).strip())
return list(Message.tags_from_dir(Path(config.db), prefix=prefix))
def create_parser() -> argparse.ArgumentParser:
+16 -10
View File
@@ -3,6 +3,8 @@ Module implementing message related functions and classes.
"""
import pathlib
import yaml
import tempfile
import shutil
from typing import Type, TypeVar, ClassVar, Optional, Any, Union, Final, Literal, Iterable
from dataclasses import dataclass, asdict, field
from .tags import Tag, TagLine, TagError, match_tags, rename_tags
@@ -312,7 +314,7 @@ class Message():
mfilter.tags_not if mfilter else None)
else:
message = cls.__from_file_yaml(file_path)
if message and (not mfilter or (mfilter and message.match(mfilter))):
if message and (mfilter is None or message.match(mfilter)):
return message
else:
return None
@@ -445,16 +447,18 @@ class Message():
* Answer.txt_header
* Answer
"""
with open(file_path, "w") as fd:
with tempfile.NamedTemporaryFile(dir=file_path.parent, prefix=file_path.name, mode="w", delete=False) as temp_fd:
temp_file_path = pathlib.Path(temp_fd.name)
if self.tags:
fd.write(f'{TagLine.from_set(self.tags)}\n')
temp_fd.write(f'{TagLine.from_set(self.tags)}\n')
if self.ai:
fd.write(f'{AILine.from_ai(self.ai)}\n')
temp_fd.write(f'{AILine.from_ai(self.ai)}\n')
if self.model:
fd.write(f'{ModelLine.from_model(self.model)}\n')
fd.write(f'{Question.txt_header}\n{self.question}\n')
temp_fd.write(f'{ModelLine.from_model(self.model)}\n')
temp_fd.write(f'{Question.txt_header}\n{self.question}\n')
if self.answer:
fd.write(f'{Answer.txt_header}\n{self.answer}\n')
temp_fd.write(f'{Answer.txt_header}\n{self.answer}\n')
shutil.move(temp_file_path, file_path)
def __to_file_yaml(self, file_path: pathlib.Path) -> None:
"""
@@ -466,7 +470,8 @@ class Message():
* Message.ai_yaml_key: str [Optional]
* Message.model_yaml_key: str [Optional]
"""
with open(file_path, "w") as fd:
with tempfile.NamedTemporaryFile(dir=file_path.parent, prefix=file_path.name, mode="w", delete=False) as temp_fd:
temp_file_path = pathlib.Path(temp_fd.name)
data: YamlDict = {Question.yaml_key: str(self.question)}
if self.answer:
data[Answer.yaml_key] = str(self.answer)
@@ -476,7 +481,8 @@ class Message():
data[self.model_yaml_key] = self.model
if self.tags:
data[self.tags_yaml_key] = sorted([str(tag) for tag in self.tags])
yaml.dump(data, fd, sort_keys=False)
yaml.dump(data, temp_fd, sort_keys=False)
shutil.move(temp_file_path, file_path)
def filter_tags(self, prefix: Optional[str] = None, contain: Optional[str] = None) -> set[Tag]:
"""
@@ -508,7 +514,7 @@ class Message():
Return True if all attributes match, else False.
"""
mytags = self.tags or set()
if (((mfilter.tags_or or mfilter.tags_and or mfilter.tags_not)
if (((mfilter.tags_or is not None or mfilter.tags_and is not None or mfilter.tags_not is not None)
and not match_tags(mytags, mfilter.tags_or, mfilter.tags_and, mfilter.tags_not)) # noqa: W503
or (mfilter.ai and (not self.ai or mfilter.ai != self.ai)) # noqa: W503
or (mfilter.model and (not self.model or mfilter.model != self.model)) # noqa: W503
+2 -2
View File
@@ -10,7 +10,7 @@ from chatmastermind.ais.openai import OpenAI
class TestCreateAI(unittest.TestCase):
def setUp(self) -> None:
self.args = MagicMock(spec=argparse.Namespace)
self.args.AI = 'default'
self.args.AI = 'myopenai'
self.args.model = None
self.args.max_tokens = None
self.args.temperature = None
@@ -18,7 +18,7 @@ class TestCreateAI(unittest.TestCase):
def test_create_ai_from_args(self) -> None:
# Create an AI with the default configuration
config = Config()
self.args.AI = 'default'
self.args.AI = 'myopenai'
ai = create_ai(self.args, config)
self.assertIsInstance(ai, OpenAI)
+20 -2
View File
@@ -202,7 +202,25 @@ class TestChatDB(unittest.TestCase):
self.assertEqual(chat_db.messages[1].file_path,
pathlib.Path(self.db_path.name, '0003.txt'))
def test_chat_db_filter(self) -> None:
def test_chat_db_from_dir_filter_tags(self) -> None:
chat_db = ChatDB.from_dir(pathlib.Path(self.cache_path.name),
pathlib.Path(self.db_path.name),
mfilter=MessageFilter(tags_or={Tag('tag1')}))
self.assertEqual(len(chat_db.messages), 1)
self.assertEqual(chat_db.cache_path, pathlib.Path(self.cache_path.name))
self.assertEqual(chat_db.db_path, pathlib.Path(self.db_path.name))
self.assertEqual(chat_db.messages[0].file_path,
pathlib.Path(self.db_path.name, '0001.txt'))
def test_chat_db_from_dir_filter_tags_empty(self) -> None:
chat_db = ChatDB.from_dir(pathlib.Path(self.cache_path.name),
pathlib.Path(self.db_path.name),
mfilter=MessageFilter(tags_or=set(),
tags_and=set(),
tags_not=set()))
self.assertEqual(len(chat_db.messages), 0)
def test_chat_db_from_dir_filter_answer(self) -> None:
chat_db = ChatDB.from_dir(pathlib.Path(self.cache_path.name),
pathlib.Path(self.db_path.name),
mfilter=MessageFilter(answer_contains='Answer 2'))
@@ -213,7 +231,7 @@ class TestChatDB(unittest.TestCase):
pathlib.Path(self.db_path.name, '0002.yaml'))
self.assertEqual(chat_db.messages[0].answer, 'Answer 2')
def test_chat_db_from_messges(self) -> None:
def test_chat_db_from_messages(self) -> None:
chat_db = ChatDB.from_messages(pathlib.Path(self.cache_path.name),
pathlib.Path(self.db_path.name),
messages=[self.message1, self.message2,
+7 -7
View File
@@ -59,7 +59,7 @@ class TestConfig(unittest.TestCase):
source_dict = {
'db': './test_db/',
'ais': {
'default': {
'myopenai': {
'name': 'openai',
'system': 'Custom system',
'api_key': '9876543210',
@@ -75,10 +75,10 @@ class TestConfig(unittest.TestCase):
config = Config.from_dict(source_dict)
self.assertEqual(config.db, './test_db/')
self.assertEqual(len(config.ais), 1)
self.assertEqual(config.ais['default'].name, 'openai')
self.assertEqual(cast(OpenAIConfig, config.ais['default']).system, 'Custom system')
self.assertEqual(config.ais['myopenai'].name, 'openai')
self.assertEqual(cast(OpenAIConfig, config.ais['myopenai']).system, 'Custom system')
# check that 'ID' has been added
self.assertEqual(config.ais['default'].ID, 'default')
self.assertEqual(config.ais['myopenai'].ID, 'myopenai')
def test_create_default_should_create_default_config(self) -> None:
Config.create_default(Path(self.test_file.name))
@@ -117,8 +117,8 @@ class TestConfig(unittest.TestCase):
config = Config(
db='./test_db/',
ais={
'default': OpenAIConfig(
ID='default',
'myopenai': OpenAIConfig(
ID='myopenai',
system='Custom system',
api_key='9876543210',
model='custom_model',
@@ -135,7 +135,7 @@ class TestConfig(unittest.TestCase):
saved_config = yaml.load(f, Loader=yaml.FullLoader)
self.assertEqual(saved_config['db'], './test_db/')
self.assertEqual(len(saved_config['ais']), 1)
self.assertEqual(saved_config['ais']['default']['system'], 'Custom system')
self.assertEqual(saved_config['ais']['myopenai']['system'], 'Custom system')
def test_from_file_error_unknown_ai(self) -> None:
source_dict = {
+6
View File
@@ -300,6 +300,12 @@ This is a question.
MessageFilter(tags_or={Tag('tag1')}))
self.assertIsNone(message)
def test_from_file_txt_empty_tags_dont_match(self) -> None:
message = Message.from_file(self.file_path_min,
MessageFilter(tags_or=set(),
tags_and=set()))
self.assertIsNone(message)
def test_from_file_txt_no_tags_match_tags_not(self) -> None:
message = Message.from_file(self.file_path_min,
MessageFilter(tags_not={Tag('tag1')}))