Compare commits

...

3 Commits

3 changed files with 125 additions and 96 deletions

130
README.md
View File

@ -46,79 +46,80 @@ cmm [global options] command [command options]
The `question` command is used to ask, create, and process questions. The `question` command is used to ask, create, and process questions.
```bash ```bash
cmm question [-t OTAGS]... [-k ATAGS]... [-x XTAGS]... [-o OUTTAGS]... [-A AI] [-M MODEL] [-n NUM] [-m MAX] [-T TEMP] (-a ASK | -c CREATE | -r REPEAT | -p PROCESS) [-O] [-s SOURCE]... [-S SOURCE]... cmm question [-t OTAGS]... [-k ATAGS]... [-x XTAGS]... [-o OUTTAGS]... [-A AI_ID] [-M MODEL] [-n NUM] [-m MAX] [-T TEMP] (-a QUESTION | -c QUESTION | -r [MESSAGE ...] | -p [MESSAGE ...]) [-O] [-s FILE]... [-S FILE]...
``` ```
* `-t, --or-tags OTAGS` : List of tags (one must match) * `-t, --or-tags OTAGS`: List of tags (one must match)
* `-k, --and-tags ATAGS` : List of tags (all must match) * `-k, --and-tags ATAGS`: List of tags (all must match)
* `-x, --exclude-tags XTAGS` : List of tags to exclude * `-x, --exclude-tags XTAGS`: List of tags to exclude
* `-o, --output-tags OUTTAGS` : List of output tags (default: use input tags) * `-o, --output-tags OUTTAGS`: List of output tags (default: use input tags)
* `-A, --AI AI` : AI ID to use * `-A, --AI AI_ID`: AI ID to use
* `-M, --model MODEL` : Model to use * `-M, --model MODEL`: Model to use
* `-n, --num-answers NUM` : Number of answers to request * `-n, --num-answers NUM`: Number of answers to request
* `-m, --max-tokens MAX` : Max. number of tokens * `-m, --max-tokens MAX`: Max. number of tokens
* `-T, --temperature TEMP` : Temperature value * `-T, --temperature TEMP`: Temperature value
* `-a, --ask ASK` : Ask a question * `-a, --ask QUESTION`: Ask a question
* `-c, --create CREATE` : Create a question * `-c, --create QUESTION`: Create a question
* `-r, --repeat REPEAT` : Repeat a question * `-r, --repeat [MESSAGE ...]`: Repeat a question
* `-p, --process PROCESS` : Process existing questions * `-p, --process [MESSAGE ...]`: Process existing questions
* `-O, --overwrite` : Overwrite existing messages when repeating them * `-O, --overwrite`: Overwrite existing messages when repeating them
* `-s, --source-text SOURCE` : Add content of a file to the query * `-s, --source-text FILE`: Add content of a file to the query
* `-S, --source-code SOURCE` : Add source code file content to the chat history * `-S, --source-code FILE`: Add source code file content to the chat history
#### Hist #### Hist
The `hist` command is used to print the chat history. The `hist` command is used to print the chat history.
```bash ```bash
cmm hist [-t OTAGS]... [-k ATAGS]... [-x XTAGS]... [-w] [-W] [-S] [-A ANSWER] [-Q QUESTION] cmm hist [-t OTAGS]... [-k ATAGS]... [-x XTAGS]... [-w] [-W] [-S] [-A SUBSTRING] [-Q SUBSTRING]
``` ```
* `-t, --or-tags OTAGS` : List of tags (one must match) * `-t, --or-tags OTAGS`: List of tags (one must match)
* `-k, --and-tags ATAGS` : List of tags (all must match) * `-k, --and-tags ATAGS`: List of tags (all must match)
* `-x, --exclude-tags XTAGS` : List of tags to exclude * `-x, --exclude-tags XTAGS`: List of tags to exclude
* `-w, --with-tags` : Print chat history with tags * `-w, --with-tags`: Print chat history with tags
* `-W, --with-files` : Print chat history with filenames * `-W, --with-files`: Print chat history with filenames
* `-S, --source-code-only` : Print only source code * `-S, --source-code-only`: Only print embedded source code
* `-A, --answer ANSWER` : Search for answer substring * `-A, --answer SUBSTRING`: Search for answer substring
* `-Q, --question QUESTION` : Search for question substring * `-Q, --question SUBSTRING`: Search for question substring
#### Tags #### Tags
The `tags` command is used to manage tags. The `tags` command is used to manage tags.
```bash ```bash
cmm tags (-l | -p PREFIX | -c CONTENT) cmm tags (-l | -p PREFIX | -c SUBSTRING)
``` ```
* `-l, --list` : List all tags and their frequency * `-l, --list`: List all tags and their frequency
* `-p, --prefix PREFIX` : Filter tags by prefix * `-p, --prefix PREFIX`: Filter tags by prefix
* `-c, --contain CONTENT` : Filter tags by contained substring * `-c, --contain SUBSTRING`: Filter tags by contained substring
#### Config #### Config
The `config` command is used to manage the configuration. The `config` command is used to manage the configuration.
```bash ```bash
cmm config (-l | -m | -c CREATE) cmm config (-l | -m | -c FILE)
``` ```
* `-l, --list-models` : List all available models * `-l, --list-models`: List all available models
* `-m, --print-model` : Print the currently configured model * `-m, --print-model`: Print the currently configured model
* `-c, --create CREATE` : Create config with default settings in the given file * `-c, --create FILE`: Create config with default settings in the given file
#### Print #### Print
The `print` command is used to print message files. The `print` command is used to print message files.
```bash ```bash
cmm print -f FILE [-q | -a | -S] cmm print (-f FILE | -l) [-q | -a | -S]
``` ```
* `-f, --file FILE` : File to print * `-f, --file FILE`: Print given file
* `-q, --question` : Print only question * `-l, --latest`: Print latest message
* `-a, --answer` : Print only answer * `-q, --question`: Only print the question
* `-S, --only-source-code` : Print only source code * `-a, --answer`: Only print the answer
* `-S, --only-source-code`: Only print embedded source code
### Examples ### Examples
@ -160,18 +161,27 @@ cmm print -f example.yaml
## Configuration ## Configuration
The configuration file (`.config.yaml`) should contain the following fields: The default configuration filename is `.config.yaml` (it is searched in the current working directory).
Use the command `cmm config --create <FILENAME>` to create a default configuration:
- `openai`: ```
- `api_key`: Your OpenAI API key. cache: .
- `model`: The name of the OpenAI model to use (e.g. "text-davinci-002"). db: ./db/
- `temperature`: The temperature value for the model. ais:
- `max_tokens`: The maximum number of tokens for the model. myopenai:
- `top_p`: The top P value for the model. name: openai
- `frequency_penalty`: The frequency penalty value. model: gpt-3.5-turbo-16k
- `presence_penalty`: The presence penalty value. api_key: 0123456789
- `system`: The system message used to set the behavior of the AI. temperature: 1.0
- `db`: The directory where the question-answer pairs are stored in YAML files. max_tokens: 4000
top_p: 1.0
frequency_penalty: 0.0
presence_penalty: 0.0
system: You are an assistant
```
Each AI has its own section and the name of that section is called the 'AI ID' (in the example above it is `myopenai`).
The AI ID can be any string, as long as it's unique within the `ais` section. The AI ID is used for all commands that support the `AI` parameter and it's also stored within each message file.
## Autocompletion ## Autocompletion
@ -186,33 +196,33 @@ After adding this line, restart your shell or run `source <your-shell-config-fil
## Contributing ## Contributing
### Enable commit hooks ### Enable commit hooks
``` ```bash
pip install pre-commit pip install pre-commit
pre-commit install pre-commit install
``` ```
### Execute tests before opening a PR ### Execute tests before opening a PR
``` ```bash
pytest pytest
``` ```
### Consider using `pyenv` / `pyenv-virtualenv` ### Consider using `pyenv` / `pyenv-virtualenv`
Short installation instructions: Short installation instructions:
* install `pyenv`: * install `pyenv`:
``` ```bash
cd ~ cd ~
git clone https://github.com/pyenv/pyenv .pyenv git clone https://github.com/pyenv/pyenv .pyenv
cd ~/.pyenv && src/configure && make -C src cd ~/.pyenv && src/configure && make -C src
``` ```
* make sure that `~/.pyenv/shims` and `~/.pyenv/bin` are the first entries in your `PATH`, e. g. by setting it in `~/.bashrc` * make sure that `~/.pyenv/shims` and `~/.pyenv/bin` are the first entries in your `PATH`, e.g., by setting it in `~/.bashrc`
* add the following to your `~/.bashrc` (after setting `PATH`): `eval "$(pyenv init -)"` * add the following to your `~/.bashrc` (after setting `PATH`): `eval "$(pyenv init -)"`
* create a new terminal or source the changes (e. g. `source ~/.bashrc`) * create a new terminal or source the changes (e.g., `source ~/.bashrc`)
* install `virtualenv` * install `virtualenv`
``` ```bash
git clone https://github.com/pyenv/pyenv-virtualenv.git $(pyenv root)/plugins/pyenv-virtualenv git clone https://github.com/pyenv/pyenv-virtualenv.git $(pyenv root)/plugins/pyenv-virtualenv
``` ```
* add the following to your `~/.bashrc` (after the commands above): `eval "$(pyenv virtualenv-init -)` * add the following to your `~/.bashrc` (after the commands above): `eval "$(pyenv virtualenv-init -)`
* create a new terminal or source the changes (e. g. `source ~/.bashrc`) * create a new terminal or source the changes (e.g., `source ~/.bashrc`)
* go back to the `ChatMasterMind` repo and create a virtual environment with the latest `Python`, e. g. `3.11.4`: * go back to the `ChatMasterMind` repo and create a virtual environment with the latest `Python`, e.g., `3.11.4`:
``` ```bash
cd <CMM_REPO_PATH> cd <CMM_REPO_PATH>
pyenv install 3.11.4 pyenv install 3.11.4
pyenv virtualenv 3.11.4 py311 pyenv virtualenv 3.11.4 py311
@ -223,5 +233,3 @@ pyenv activate py311
## License ## License
This project is licensed under the terms of the WTFPL License. This project is licensed under the terms of the WTFPL License.

View File

@ -3,25 +3,43 @@ import argparse
from pathlib import Path from pathlib import Path
from ..configuration import Config from ..configuration import Config
from ..message import Message, MessageError from ..message import Message, MessageError
from ..chat import ChatDB
def print_message(message: Message, args: argparse.Namespace) -> None:
"""
Print given message according to give arguments.
"""
if args.question:
print(message.question)
elif args.answer:
print(message.answer)
elif message.answer and args.only_source_code:
for code in message.answer.source_code():
print(code)
else:
print(message.to_str())
def print_cmd(args: argparse.Namespace, config: Config) -> None: def print_cmd(args: argparse.Namespace, config: Config) -> None:
""" """
Handler for the 'print' command. Handler for the 'print' command.
""" """
fname = Path(args.file) # print given file
try: if args.file is not None:
message = Message.from_file(fname) fname = Path(args.file)
if message: try:
if args.question: message = Message.from_file(fname)
print(message.question) if message:
elif args.answer: print_message(message, args)
print(message.answer) except MessageError:
elif message.answer and args.only_source_code: print(f"File is not a valid message: {args.file}")
for code in message.answer.source_code(): sys.exit(1)
print(code) # print latest message
else: elif args.latest:
print(message.to_str()) chat = ChatDB.from_dir(Path(config.cache), Path(config.db))
except MessageError: latest = chat.msg_latest(loc='disk')
print(f"File is not a valid message: {args.file}") if not latest:
sys.exit(1) print("No message found!")
sys.exit(1)
print_message(latest, args)

View File

@ -44,13 +44,13 @@ def create_parser() -> argparse.ArgumentParser:
help='List of tags to exclude', metavar='XTAGS') help='List of tags to exclude', metavar='XTAGS')
etag_arg.completer = tags_completer # type: ignore etag_arg.completer = tags_completer # type: ignore
otag_arg = tag_parser.add_argument('-o', '--output-tags', nargs='+', otag_arg = tag_parser.add_argument('-o', '--output-tags', nargs='+',
help='List of output tags (default: use input tags)', metavar='OUTTAGS') help='List of output tags (default: use input tags)', metavar='OUTAGS')
otag_arg.completer = tags_completer # type: ignore otag_arg.completer = tags_completer # type: ignore
# a parent parser for all commands that support AI configuration # a parent parser for all commands that support AI configuration
ai_parser = argparse.ArgumentParser(add_help=False) ai_parser = argparse.ArgumentParser(add_help=False)
ai_parser.add_argument('-A', '--AI', help='AI ID to use') ai_parser.add_argument('-A', '--AI', help='AI ID to use', metavar='AI_ID')
ai_parser.add_argument('-M', '--model', help='Model to use') ai_parser.add_argument('-M', '--model', help='Model to use', metavar='MODEL')
ai_parser.add_argument('-n', '--num-answers', help='Number of answers to request', type=int, default=1) ai_parser.add_argument('-n', '--num-answers', help='Number of answers to request', type=int, default=1)
ai_parser.add_argument('-m', '--max-tokens', help='Max. nr. of tokens', type=int) ai_parser.add_argument('-m', '--max-tokens', help='Max. nr. of tokens', type=int)
ai_parser.add_argument('-T', '--temperature', help='Temperature value', type=float) ai_parser.add_argument('-T', '--temperature', help='Temperature value', type=float)
@ -61,14 +61,15 @@ def create_parser() -> argparse.ArgumentParser:
aliases=['q']) aliases=['q'])
question_cmd_parser.set_defaults(func=question_cmd) question_cmd_parser.set_defaults(func=question_cmd)
question_group = question_cmd_parser.add_mutually_exclusive_group(required=True) question_group = question_cmd_parser.add_mutually_exclusive_group(required=True)
question_group.add_argument('-a', '--ask', nargs='+', help='Ask a question') question_group.add_argument('-a', '--ask', nargs='+', help='Ask a question', metavar='QUESTION')
question_group.add_argument('-c', '--create', nargs='+', help='Create a question') question_group.add_argument('-c', '--create', nargs='+', help='Create a question', metavar='QUESTION')
question_group.add_argument('-r', '--repeat', nargs='*', help='Repeat a question') question_group.add_argument('-r', '--repeat', nargs='*', help='Repeat a question', metavar='MESSAGE')
question_group.add_argument('-p', '--process', nargs='*', help='Process existing questions') question_group.add_argument('-p', '--process', nargs='*', help='Process existing questions', metavar='MESSAGE')
question_cmd_parser.add_argument('-O', '--overwrite', help='Overwrite existing messages when repeating them', question_cmd_parser.add_argument('-O', '--overwrite', help='Overwrite existing messages when repeating them',
action='store_true') action='store_true')
question_cmd_parser.add_argument('-s', '--source-text', nargs='+', help='Add content of a file to the query') question_cmd_parser.add_argument('-s', '--source-text', nargs='+', help='Add content of a file to the query', metavar='FILE')
question_cmd_parser.add_argument('-S', '--source-code', nargs='+', help='Add source code file content to the chat history') question_cmd_parser.add_argument('-S', '--source-code', nargs='+', help='Add source code file content to the chat history',
metavar='FILE')
# 'hist' command parser # 'hist' command parser
hist_cmd_parser = cmdparser.add_parser('hist', parents=[tag_parser], hist_cmd_parser = cmdparser.add_parser('hist', parents=[tag_parser],
@ -79,10 +80,10 @@ def create_parser() -> argparse.ArgumentParser:
action='store_true') action='store_true')
hist_cmd_parser.add_argument('-W', '--with-files', help="Print chat history with filenames.", hist_cmd_parser.add_argument('-W', '--with-files', help="Print chat history with filenames.",
action='store_true') action='store_true')
hist_cmd_parser.add_argument('-S', '--source-code-only', help='Print only source code', hist_cmd_parser.add_argument('-S', '--source-code-only', help='Only print embedded source code',
action='store_true') action='store_true')
hist_cmd_parser.add_argument('-A', '--answer', help='Search for answer substring') hist_cmd_parser.add_argument('-A', '--answer', help='Search for answer substring', metavar='SUBSTRING')
hist_cmd_parser.add_argument('-Q', '--question', help='Search for question substring') hist_cmd_parser.add_argument('-Q', '--question', help='Search for question substring', metavar='SUBSTRING')
# 'tags' command parser # 'tags' command parser
tags_cmd_parser = cmdparser.add_parser('tags', tags_cmd_parser = cmdparser.add_parser('tags',
@ -92,8 +93,8 @@ def create_parser() -> argparse.ArgumentParser:
tags_group = tags_cmd_parser.add_mutually_exclusive_group(required=True) tags_group = tags_cmd_parser.add_mutually_exclusive_group(required=True)
tags_group.add_argument('-l', '--list', help="List all tags and their frequency", tags_group.add_argument('-l', '--list', help="List all tags and their frequency",
action='store_true') action='store_true')
tags_cmd_parser.add_argument('-p', '--prefix', help="Filter tags by prefix") tags_cmd_parser.add_argument('-p', '--prefix', help="Filter tags by prefix", metavar='PREFIX')
tags_cmd_parser.add_argument('-c', '--contain', help="Filter tags by contained substring") tags_cmd_parser.add_argument('-c', '--contain', help="Filter tags by contained substring", metavar='SUBSTRING')
# 'config' command parser # 'config' command parser
config_cmd_parser = cmdparser.add_parser('config', config_cmd_parser = cmdparser.add_parser('config',
@ -106,18 +107,20 @@ def create_parser() -> argparse.ArgumentParser:
action='store_true') action='store_true')
config_group.add_argument('-m', '--print-model', help="Print the currently configured model", config_group.add_argument('-m', '--print-model', help="Print the currently configured model",
action='store_true') action='store_true')
config_group.add_argument('-c', '--create', help="Create config with default settings in the given file") config_group.add_argument('-c', '--create', help="Create config with default settings in the given file", metavar='FILE')
# 'print' command parser # 'print' command parser
print_cmd_parser = cmdparser.add_parser('print', print_cmd_parser = cmdparser.add_parser('print',
help="Print message files.", help="Print message files.",
aliases=['p']) aliases=['p'])
print_cmd_parser.set_defaults(func=print_cmd) print_cmd_parser.set_defaults(func=print_cmd)
print_cmd_parser.add_argument('-f', '--file', help='File to print', required=True) print_group = print_cmd_parser.add_mutually_exclusive_group(required=True)
print_group.add_argument('-f', '--file', help='Print given message file', metavar='FILE')
print_group.add_argument('-l', '--latest', help='Print latest message', action='store_true')
print_cmd_modes = print_cmd_parser.add_mutually_exclusive_group() print_cmd_modes = print_cmd_parser.add_mutually_exclusive_group()
print_cmd_modes.add_argument('-q', '--question', help='Print only question', action='store_true') print_cmd_modes.add_argument('-q', '--question', help='Only print the question', action='store_true')
print_cmd_modes.add_argument('-a', '--answer', help='Print only answer', action='store_true') print_cmd_modes.add_argument('-a', '--answer', help='Only print the answer', action='store_true')
print_cmd_modes.add_argument('-S', '--only-source-code', help='Print only source code', action='store_true') print_cmd_modes.add_argument('-S', '--only-source-code', help='Only print embedded source code', action='store_true')
argcomplete.autocomplete(parser) argcomplete.autocomplete(parser)
return parser return parser