├── .coveragerc
├── .editorconfig
├── .flake8
├── .gitignore
├── .isort.cfg
├── .pre-commit-config.yaml
├── .sourcery.yaml
├── LICENSE
├── Makefile
├── README.md
├── helpers.bat
├── helpers.sh
├── pylintrc
├── pyproject.toml
├── requirements.txt
├── run_pylint.py
├── src
└── mindware
│ ├── __init__.py
│ ├── mindware.py
│ └── plugin_function.py
└── tests
└── test_mindware_plugin.py
/.coveragerc:
--------------------------------------------------------------------------------
1 | [coverage:run]
2 | branch = True
3 | plugins =
4 | omit =
5 | # omit anything in a .local directory anywhere
6 | */.local/*
7 |
8 | [run]
9 | branch = True
10 | plugins =
11 | omit =
12 | # omit anything in a .local directory anywhere
13 | */.local/*
14 |
15 | [paths]
16 | source = .
17 |
18 |
19 | [report]
20 | # Regexes for lines to exclude from consideration
21 |
22 | omit =
23 | # omit anything in a .local directory anywhere
24 | */.local/*
25 |
26 | exclude_lines =
27 | # Have to re-enable the standard pragma
28 | pragma: no cover
29 | # Don't complain about missing debug-only code:
30 | def __repr__
31 | if self\.debug
32 |
33 | # Don't complain if tests don't hit defensive assertion code:
34 | raise AssertionError
35 | raise NotImplementedError
36 |
37 | # Don't complain if non-runnable code isn't run:
38 | if 0:
39 | if __name__ == .__main__.:
40 |
41 | # Don't complain about abstract methods, they aren't run:
42 | @(abc\.)?abstractmethod
43 |
44 | ignore_errors = True
45 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # Top-most EditorConfig file
2 | root = true
3 |
4 | # Set default charset
5 | [*]
6 | charset = utf-8
7 |
8 | # Use black formatter for python files
9 | [*.py]
10 | profile = black
11 |
12 | # Set defaults for windows and batch filess
13 | [*.bat]
14 | end_of_line = crlf
15 | indent_style = space
16 | indent_size = 2
17 |
18 | # Set defaults for shell scripts
19 | [*.sh]
20 | end_of_line = lf
21 | trim_trailing_whitespace = true
22 | insert_final_newline = false
23 |
24 | # Set defaults for Makefiles
25 | [Makefile]
26 | end_of_line = lf
27 | indent_style = tab
28 | indent_size = 4
29 | trim_trailing_whitespace = true
30 | insert_final_newline = true
--------------------------------------------------------------------------------
/.flake8:
--------------------------------------------------------------------------------
1 | [flake8]
2 | max-line-length = 88
3 | extend-ignore = E203
4 | exclude =
5 | .tox,
6 | __pycache__,
7 | *.pyc,
8 | .env
9 | venv/*
10 | .venv/*
11 | reports/*
12 | dist/*
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 | .vscode
9 | AutoGPT.code-workspace
10 |
11 | # Distribution / packaging
12 | .Python
13 | build/
14 | develop-eggs/
15 | dist/
16 | downloads/
17 | eggs/
18 | .eggs/
19 | lib/
20 | lib64/
21 | parts/
22 | sdist/
23 | var/
24 | wheels/
25 | pip-wheel-metadata/
26 | share/python-wheels/
27 | *.egg-info/
28 | .installed.cfg
29 | *.egg
30 | MANIFEST
31 |
32 | # PyInstaller
33 | # Usually these files are written by a python script from a template
34 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
35 | *.manifest
36 | *.spec
37 |
38 | # Installer logs
39 | pip-log.txt
40 | pip-delete-this-directory.txt
41 |
42 | # Unit test / coverage reports
43 | htmlcov/
44 | .tox/
45 | .nox/
46 | .coverage
47 | .coverage.*
48 | .cache
49 | nosetests.xml
50 | coverage.xml
51 | *.cover
52 | *.py,cover
53 | .hypothesis/
54 | .pytest_cache/
55 |
56 | # Translations
57 | *.mo
58 | *.pot
59 |
60 | # Django stuff:
61 | *.log
62 | local_settings.py
63 | db.sqlite3
64 | db.sqlite3-journal
65 |
66 | # Flask stuff:
67 | instance/
68 | .webassets-cache
69 |
70 | # Scrapy stuff:
71 | .scrapy
72 |
73 | # Sphinx documentation
74 | docs/_build/
75 |
76 | # PyBuilder
77 | target/
78 |
79 | # Jupyter Notebook
80 | .ipynb_checkpoints
81 |
82 | # IPython
83 | profile_default/
84 | ipython_config.py
85 |
86 | # pyenv
87 | .python-version
88 |
89 | # pipenv
90 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
91 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
92 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
93 | # install all needed dependencies.
94 | #Pipfile.lock
95 |
96 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
97 | __pypackages__/
98 |
99 | # Celery stuff
100 | celerybeat-schedule
101 | celerybeat.pid
102 |
103 | # SageMath parsed files
104 | *.sage.py
105 |
106 | # Environments
107 | .env
108 | .venv
109 | env/
110 | venv/
111 | ENV/
112 | env.bak/
113 | venv.bak/
114 |
115 | # Spyder project settings
116 | .spyderproject
117 | .spyproject
118 |
119 | # Rope project settings
120 | .ropeproject
121 |
122 | # mkdocs documentation
123 | /site
124 |
125 | # mypy
126 | .mypy_cache/
127 | .dmypy.json
128 | dmypy.json
129 |
130 | # Pyre type checker
131 | .pyre/
132 |
--------------------------------------------------------------------------------
/.isort.cfg:
--------------------------------------------------------------------------------
1 | [settings]
2 | profile = black
3 | multi_line_output = 3
4 | include_trailing_comma = true
5 | force_grid_wrap = 0
6 | use_parentheses = true
7 | ensure_newline_before_comments = true
8 | line_length = 88
9 | sections = FUTURE,STDLIB,THIRDPARTY,FIRSTPARTY,LOCALFOLDER
10 | skip = .tox,__pycache__,*.pyc,venv*/*,reports,venv,env,node_modules,.env,.venv,dist
11 |
--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
1 | repos:
2 | - repo: https://github.com/sourcery-ai/sourcery
3 | rev: v1.1.0 # Get the latest tag from https://github.com/sourcery-ai/sourcery/tags
4 | hooks:
5 | - id: sourcery
6 |
7 | - repo: https://github.com/pre-commit/pre-commit-hooks
8 | rev: v0.9.2
9 | hooks:
10 | - id: check-added-large-files
11 | args: [ '--maxkb=500' ]
12 | - id: check-byte-order-marker
13 | - id: check-case-conflict
14 | - id: check-merge-conflict
15 | - id: check-symlinks
16 | - id: debug-statements
17 |
18 | - repo: local
19 | hooks:
20 | - id: isort
21 | name: isort-local
22 | entry: isort
23 | language: python
24 | types: [ python ]
25 | exclude: .+/(dist|.venv|venv|build)/.+
26 | pass_filenames: true
27 | - id: black
28 | name: black-local
29 | entry: black
30 | language: python
31 | types: [ python ]
32 | exclude: .+/(dist|.venv|venv|build)/.+
33 | pass_filenames: true
--------------------------------------------------------------------------------
/.sourcery.yaml:
--------------------------------------------------------------------------------
1 | # 🪄 This is your project's Sourcery configuration file.
2 |
3 | # You can use it to get Sourcery working in the way you want, such as
4 | # ignoring specific refactorings, skipping directories in your project,
5 | # or writing custom rules.
6 |
7 | # 📚 For a complete reference to this file, see the documentation at
8 | # https://docs.sourcery.ai/Configuration/Project-Settings/
9 |
10 | # This file was auto-generated by Sourcery on 2023-02-25 at 21:07.
11 |
12 | version: '1' # The schema version of this config file
13 |
14 | ignore: # A list of paths or files which Sourcery will ignore.
15 | - .git
16 | - venv
17 | - .venv
18 | - build
19 | - dist
20 | - env
21 | - .env
22 | - .tox
23 |
24 | rule_settings:
25 | enable:
26 | - default
27 | - gpsg
28 | disable: [] # A list of rule IDs Sourcery will never suggest.
29 | rule_types:
30 | - refactoring
31 | - suggestion
32 | - comment
33 | python_version: '3.9' # A string specifying the lowest Python version your project supports. Sourcery will not suggest refactorings requiring a higher Python version.
34 |
35 | # rules: # A list of custom rules Sourcery will include in its analysis.
36 | # - id: no-print-statements
37 | # description: Do not use print statements in the test directory.
38 | # pattern: print(...)
39 | # language: python
40 | # replacement:
41 | # condition:
42 | # explanation:
43 | # paths:
44 | # include:
45 | # - test
46 | # exclude:
47 | # - conftest.py
48 | # tests: []
49 | # tags: []
50 |
51 | # rule_tags: {} # Additional rule tags.
52 |
53 | # metrics:
54 | # quality_threshold: 25.0
55 |
56 | # github:
57 | # labels: []
58 | # ignore_labels:
59 | # - sourcery-ignore
60 | # request_review: author
61 | # sourcery_branch: sourcery/{base_branch}
62 |
63 | # clone_detection:
64 | # min_lines: 3
65 | # min_duplicates: 2
66 | # identical_clones_only: false
67 |
68 | # proxy:
69 | # url:
70 | # ssl_certs_file:
71 | # no_ssl_verify: false
72 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Singularity Group, LLC
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | ifeq ($(OS),Windows_NT)
2 | os := win
3 | SCRIPT_EXT := .bat
4 | SHELL_CMD := cmd /C
5 | else
6 | os := nix
7 | SCRIPT_EXT := .sh
8 | SHELL_CMD := bash
9 | endif
10 |
11 | helpers = @$(SHELL_CMD) helpers$(SCRIPT_EXT) $1
12 |
13 | clean qa style: helpers$(SCRIPT_EXT)
14 |
15 | clean:
16 | $(call helpers,clean)
17 |
18 | qa:
19 | $(call helpers,qa)
20 |
21 | style:
22 | $(call helpers,style)
23 |
24 | .PHONY: clean qa style
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
12 |
13 |
14 |
21 |
22 |
23 |
24 |

25 |
26 |
27 | ## 📚 Prerequisites
28 |
29 | ### 1. OpenAI API Key
30 |
31 | If you don't already have an OpenAI API key, get one [here](https://openai.com/blog/openai-api).
32 |
33 | ### 2. Mindware API Key
34 |
35 | Sign up to [Mindware](https://mindware.xyz) and copy your free API key from the account page:
36 |
37 |
38 |
39 |
40 | ### 3. Docker Desktop
41 |
42 | If you don't already have Docker inatalled, download the latest version [here](https://www.docker.com/products/docker-desktop).
43 |
44 | ## 🔧 Setup
45 |
46 | ### 1. Install AutoGPT & Mindware
47 |
48 | To install AutoGPT and Mindware, make sure Docker is running and then run the following command in a directory of your choosing:
49 |
50 | #### For Mac, Linux, or WSL:
51 |
52 | ```bash
53 | git clone https://github.com/open-mindware/AutoGPT-Mindware-Installer.git && cd ./AutoGPT-Mindware-Installer && ./installer.sh
54 | ```
55 |
56 | #### For Windows (Powershell):
57 |
58 | ```powershell
59 | git clone https://github.com/open-mindware/AutoGPT-Mindware-Installer.git; .\AutoGPT-Mindware-Installer\installer.bat
60 | ```
61 |
62 | ### 2. Start AutoGPT
63 |
64 | The last command will automatically start AutoGPT after the installation process. Going forward, whenever you want to start AutoGPT, run the following command from the root directory:
65 |
66 | ```bash
67 | docker compose run --rm auto-gpt --install-plugin-deps
68 | ```
69 |
70 | ## 🧠 How to Use
71 |
72 | After completing these installation steps, you can enable/disable plugins for AutoGPT via the Mindware [plugins page](https://mindware.xyz). For instance, if you want AutoGPT to find YouTube videos, enable the YouTube plugin:
73 |
74 |
75 |
76 | ### Example:
77 |
78 | #### 1. Configure AutoGPT
79 |
80 | Set up the `ai_settings.yaml` file with the following parameters:
81 |
82 | ```yaml
83 | ai_goals:
84 | - Find YouTube videos on how to make Otoro nigiri.
85 | ai_name: FoodTubeGPT
86 | ai_role:
87 | An AI-powered virtual assistant that specializes in helping users find and
88 | obtain specific cooking videos on YouTube.
89 | api_budget: 0.05
90 | ```
91 |
92 | #### 2. Start AutoGPT
93 |
94 | Launch AutoGPT. It should utilize the YouTube plugin to find sushi videos 🍣
95 |
96 |
--------------------------------------------------------------------------------
/helpers.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 |
3 | if "%1" == "clean" (
4 | echo Removing build artifacts and temporary files...
5 | call :clean
6 | ) else if "%1" == "qa" (
7 | echo Running static analysis tools...
8 | call :qa
9 | ) else if "%1" == "style" (
10 | echo Running code formatters...
11 | call :style
12 | ) else (
13 | echo Usage: %0 [clean^|qa^|style]
14 | exit /b 1
15 | )
16 |
17 | exit /b 0
18 |
19 | :clean
20 | rem Remove build artifacts and temporary files
21 | @del /s /q build 2>nul
22 | @del /s /q dist 2>nul
23 | @del /s /q __pycache__ 2>nul
24 | @del /s /q *.egg-info 2>nul
25 | @del /s /q **\*.egg-info 2>nul
26 | @del /s /q *.pyc 2>nul
27 | @del /s /q **\*.pyc 2>nul
28 | @del /s /q reports 2>nul
29 | echo Done!
30 | exit /b 0
31 |
32 | :qa
33 | rem Run static analysis tools
34 | @flake8 .
35 | @python run_pylint.py
36 | echo Done!
37 | exit /b 0
38 |
39 | :style
40 | rem Format code
41 | @isort .
42 | @black --exclude=".*\/*(dist|venv|.venv|test-results)\/*.*" .
43 | echo Done!
44 | exit /b 0
45 |
--------------------------------------------------------------------------------
/helpers.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -euo pipefail
4 |
5 | clean() {
6 | # Remove build artifacts and temporary files
7 | rm -rf {build,dist,__pycache__,*.egg-info,**/*.egg-info,*.pyc,**/*.pyc,reports} 2>/dev/null || true
8 | }
9 |
10 | qa() {
11 | # Run static analysis tools
12 | if command -v flake8 >/dev/null 2>&1; then
13 | flake8 .
14 | fi
15 |
16 | python run_pylint.py
17 | }
18 |
19 | style() {
20 | # Format code
21 | isort .
22 | black --exclude=".*/*(dist|venv|.venv|test-results)/*.*" .
23 | }
24 |
25 | case "$1" in
26 | clean)
27 | echo Removing build artifacts and temporary files...
28 | clean
29 | ;;
30 | qa)
31 | echo Running static analysis tools...
32 | qa
33 | ;;
34 | style)
35 | echo Running code formatters...
36 | style
37 | ;;
38 | *)
39 | echo "Usage: $0 [clean|qa|style]"
40 | exit 1
41 | ;;
42 | esac
43 |
44 | printf 'Done!\n\n'
45 | exit 0
--------------------------------------------------------------------------------
/pylintrc:
--------------------------------------------------------------------------------
1 | # This Pylint rcfile contains a best-effort configuration to uphold the
2 | # best-practices and style described in the Google Python style guide:
3 | # https://google.github.io/styleguide/pyguide.html
4 | #
5 | # Its canonical open-source location is:
6 | # https://google.github.io/styleguide/pylintrc
7 |
8 | [MASTER]
9 |
10 | # Files or directories to be skipped. They should be base names, not paths.
11 | ignore=
12 |
13 | # Files or directories matching the regex patterns are skipped. The regex
14 | # matches against base names, not paths.
15 | ignore-patterns=
16 |
17 | # Pickle collected data for later comparisons.
18 | persistent=no
19 |
20 | # List of plugins (as comma separated values of python modules names) to load,
21 | # usually to register additional checkers.
22 | load-plugins=
23 |
24 | # Use multiple processes to speed up Pylint.
25 | jobs=4
26 |
27 | # Allow loading of arbitrary C extensions. Extensions are imported into the
28 | # active Python interpreter and may run arbitrary code.
29 | unsafe-load-any-extension=no
30 |
31 |
32 | [MESSAGES CONTROL]
33 |
34 | ignore=*.pyc
35 | # Only show warnings with the listed confidence levels. Leave empty to show
36 | # all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED
37 | confidence=
38 |
39 | # Enable the message, report, category or checker with the given id(s). You can
40 | # either give multiple identifier separated by comma (,) or put this option
41 | # multiple time (only on the command line, not in the configuration file where
42 | # it should appear only once). See also the "--disable" option for examples.
43 | #enable=
44 |
45 | # Disable the message, report, category or checker with the given id(s). You
46 | # can either give multiple identifiers separated by comma (,) or put this
47 | # option multiple times (only on the command line, not in the configuration
48 | # file where it should appear only once).You can also use "--disable=all" to
49 | # disable everything first and then reenable specific checks. For example, if
50 | # you want to run only the similarities checker, you can use "--disable=all
51 | # --enable=similarities". If you want to run only the classes checker, but have
52 | # no Warning level messages displayed, use"--disable=all --enable=classes
53 | # --disable=W"
54 | disable=abstract-method,
55 | parse-error,
56 | apply-builtin,
57 | arguments-differ,
58 | attribute-defined-outside-init,
59 | backtick,
60 | bad-option-value,
61 | basestring-builtin,
62 | buffer-builtin,
63 | c-extension-no-member,
64 | consider-using-enumerate,
65 | cmp-builtin,
66 | cmp-method,
67 | coerce-builtin,
68 | coerce-method,
69 | delslice-method,
70 | div-method,
71 | duplicate-code,
72 | eq-without-hash,
73 | execfile-builtin,
74 | file-builtin,
75 | filter-builtin-not-iterating,
76 | fixme,
77 | getslice-method,
78 | global-statement,
79 | hex-method,
80 | idiv-method,
81 | implicit-str-concat,
82 | import-error,
83 | import-self,
84 | import-star-module-level,
85 | inconsistent-return-statements,
86 | input-builtin,
87 | intern-builtin,
88 | invalid-str-codec,
89 | locally-disabled,
90 | long-builtin,
91 | long-suffix,
92 | map-builtin-not-iterating,
93 | misplaced-comparison-constant,
94 | missing-function-docstring,
95 | metaclass-assignment,
96 | next-method-called,
97 | next-method-defined,
98 | no-absolute-import,
99 | no-else-break,
100 | no-else-continue,
101 | no-else-raise,
102 | no-else-return,
103 | no-init, # added
104 | no-member,
105 | no-name-in-module,
106 | no-self-use,
107 | nonzero-method,
108 | oct-method,
109 | old-division,
110 | old-ne-operator,
111 | old-octal-literal,
112 | old-raise-syntax,
113 | parameter-unpacking,
114 | print-statement,
115 | raising-string,
116 | range-builtin-not-iterating,
117 | raw_input-builtin,
118 | rdiv-method,
119 | reduce-builtin,
120 | relative-import,
121 | reload-builtin,
122 | round-builtin,
123 | setslice-method,
124 | signature-differs,
125 | standarderror-builtin,
126 | suppressed-message,
127 | sys-max-int,
128 | too-few-public-methods,
129 | too-many-ancestors,
130 | too-many-arguments,
131 | too-many-boolean-expressions,
132 | too-many-branches,
133 | too-many-instance-attributes,
134 | too-many-locals,
135 | too-many-nested-blocks,
136 | too-many-public-methods,
137 | too-many-return-statements,
138 | too-many-statements,
139 | trailing-newlines,
140 | unichr-builtin,
141 | unicode-builtin,
142 | unnecessary-pass,
143 | unpacking-in-except,
144 | useless-else-on-loop,
145 | useless-object-inheritance,
146 | useless-suppression,
147 | using-cmp-argument,
148 | wrong-import-order,
149 | xrange-builtin,
150 | zip-builtin-not-iterating,
151 |
152 |
153 | [REPORTS]
154 |
155 | # Set the output format. Available formats are text, parseable, colorized, msvs
156 | # (visual studio) and html. You can also give a reporter class, eg
157 | # mypackage.mymodule.MyReporterClass.
158 | output-format=text
159 |
160 | # Tells whether to display a full report or only the messages
161 | reports=no
162 |
163 | # Python expression which should return a note less than 10 (10 is the highest
164 | # note). You have access to the variables errors warning, statement which
165 | # respectively contain the number of errors / warnings messages and the total
166 | # number of statements analyzed. This is used by the global evaluation report
167 | # (RP0004).
168 | evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
169 |
170 | # Template used to display messages. This is a python new-style format string
171 | # used to format the message information. See doc for all details
172 | #msg-template=
173 |
174 |
175 | [BASIC]
176 |
177 | # Good variable names which should always be accepted, separated by a comma
178 | good-names=main,_
179 |
180 | # Bad variable names which should always be refused, separated by a comma
181 | bad-names=
182 |
183 | # Colon-delimited sets of names that determine each other's naming style when
184 | # the name regexes allow several styles.
185 | name-group=
186 |
187 | # Include a hint for the correct naming format with invalid-name
188 | include-naming-hint=no
189 |
190 | # List of decorators that produce properties, such as abc.abstractproperty. Add
191 | # to this list to register other decorators that produce valid properties.
192 | property-classes=abc.abstractproperty,cached_property.cached_property,cached_property.threaded_cached_property,cached_property.cached_property_with_ttl,cached_property.threaded_cached_property_with_ttl
193 |
194 | # Regular expression matching correct function names
195 | function-rgx=^(?:(?PsetUp|tearDown|setUpModule|tearDownModule)|(?P_?[A-Z][a-zA-Z0-9]*)|(?P_?[a-z][a-z0-9_]*))$
196 |
197 | # Regular expression matching correct variable names
198 | variable-rgx=^[a-z][a-z0-9_]*$
199 |
200 | # Regular expression matching correct constant names
201 | const-rgx=^(_?[A-Z][A-Z0-9_]*|__[a-z0-9_]+__|_?[a-z][a-z0-9_]*)$
202 |
203 | # Regular expression matching correct attribute names
204 | attr-rgx=^_{0,2}[a-z][a-z0-9_]*$
205 |
206 | # Regular expression matching correct argument names
207 | argument-rgx=^[a-z][a-z0-9_]*$
208 |
209 | # Regular expression matching correct class attribute names
210 | class-attribute-rgx=^(_?[A-Z][A-Z0-9_]*|__[a-z0-9_]+__|_?[a-z][a-z0-9_]*)$
211 |
212 | # Regular expression matching correct inline iteration names
213 | inlinevar-rgx=^[a-z][a-z0-9_]*$
214 |
215 | # Regular expression matching correct class names
216 | class-rgx=^_?[A-Z][a-zA-Z0-9]*$
217 |
218 | # Regular expression matching correct module names
219 | module-rgx=^(_?[a-z][a-z0-9_]*|__init__|__main__)$
220 |
221 | # Regular expression matching correct method names
222 | method-rgx=(?x)^(?:(?P_[a-z0-9_]+__|runTest|setUp|tearDown|setUpTestCase|tearDownTestCase|setupSelf|tearDownClass|setUpClass|(test|assert)_*[A-Z0-9][a-zA-Z0-9_]*|next)|(?P_{0,2}[A-Z][a-zA-Z0-9_]*)|(?P_{0,2}[a-z][a-z0-9_]*))$
223 |
224 | # Regular expression which should only match function or class names that do
225 | # not require a docstring.
226 | no-docstring-rgx=(__.*__|main|test.*|.*test|.*Test)$
227 |
228 | # Minimum line length for functions/classes that require docstrings, shorter
229 | # ones are exempt.
230 | docstring-min-length=10
231 |
232 |
233 | [TYPECHECK]
234 |
235 | # List of decorators that produce context managers, such as
236 | # contextlib.contextmanager. Add to this list to register other decorators that
237 | # produce valid context managers.
238 | contextmanager-decorators=contextlib.contextmanager,contextlib2.contextmanager
239 |
240 | # Tells whether missing members accessed in mixin class should be ignored. A
241 | # mixin class is detected if its name ends with "mixin" (case insensitive).
242 | ignore-mixin-members=yes
243 |
244 | # List of module names for which member attributes should not be checked
245 | # (useful for modules/projects where namespaces are manipulated during runtime
246 | # and thus existing member attributes cannot be deduced by static analysis. It
247 | # supports qualified module names, as well as Unix pattern matching.
248 | ignored-modules=
249 |
250 | # List of class names for which member attributes should not be checked (useful
251 | # for classes with dynamically set attributes). This supports the use of
252 | # qualified names.
253 | ignored-classes=optparse.Values,thread._local,_thread._local
254 |
255 | # List of members which are set dynamically and missed by pylint inference
256 | # system, and so shouldn't trigger E1101 when accessed. Python regular
257 | # expressions are accepted.
258 | generated-members=
259 |
260 |
261 | [FORMAT]
262 |
263 | # Maximum number of characters on a single line.
264 | max-line-length=88
265 |
266 | # TODO(https://github.com/PyCQA/pylint/issues/3352): Direct pylint to exempt
267 | # lines made too long by directives to pytype.
268 |
269 | # Regexp for a line that is allowed to be longer than the limit.
270 | ignore-long-lines=(?x)(
271 | ^\s*(\#\ )??$|
272 | ^\s*(from\s+\S+\s+)?import\s+.+$)
273 |
274 | # Allow the body of an if to be on the same line as the test if there is no
275 | # else.
276 | single-line-if-stmt=yes
277 |
278 | # Maximum number of lines in a module
279 | max-module-lines=99999
280 |
281 | # String used as indentation unit. The internal Google style guide mandates 2
282 | # spaces. Google's externaly-published style guide says 4, consistent with
283 | # PEP 8. Here, we use 2 spaces, for conformity with many open-sourced Google
284 | # projects (like TensorFlow).
285 | indent-string=' '
286 |
287 | # Number of spaces of indent required inside a hanging or continued line.
288 | indent-after-paren=4
289 |
290 | # Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
291 | expected-line-ending-format=
292 |
293 |
294 | [MISCELLANEOUS]
295 |
296 | # List of note tags to take in consideration, separated by a comma.
297 | notes=TODO
298 |
299 |
300 | [STRING]
301 |
302 | # This flag controls whether inconsistent-quotes generates a warning when the
303 | # character used as a quote delimiter is used inconsistently within a module.
304 | check-quote-consistency=yes
305 |
306 |
307 | [VARIABLES]
308 |
309 | # Tells whether we should check for unused import in __init__ files.
310 | init-import=no
311 |
312 | # A regular expression matching the name of dummy variables (i.e. expectedly
313 | # not used).
314 | dummy-variables-rgx=^\*{0,2}(_$|unused_|dummy_)
315 |
316 | # List of additional names supposed to be defined in builtins. Remember that
317 | # you should avoid to define new builtins when possible.
318 | additional-builtins=
319 |
320 | # List of strings which can identify a callback function by name. A callback
321 | # name must start or end with one of those strings.
322 | callbacks=cb_,_cb
323 |
324 | # List of qualified module names which can have objects that can redefine
325 | # builtins.
326 | redefining-builtins-modules=six,six.moves,past.builtins,future.builtins,functools
327 |
328 |
329 | [LOGGING]
330 |
331 | # Logging modules to check that the string format arguments are in logging
332 | # function parameter format
333 | logging-modules=logging,absl.logging,tensorflow.io.logging
334 |
335 |
336 | [SIMILARITIES]
337 |
338 | # Minimum lines number of a similarity.
339 | min-similarity-lines=4
340 |
341 | # Ignore comments when computing similarities.
342 | ignore-comments=yes
343 |
344 | # Ignore docstrings when computing similarities.
345 | ignore-docstrings=yes
346 |
347 | # Ignore imports when computing similarities.
348 | ignore-imports=no
349 |
350 |
351 | [SPELLING]
352 |
353 | # Spelling dictionary name. Available dictionaries: none. To make it working
354 | # install python-enchant package.
355 | spelling-dict=
356 |
357 | # List of comma separated words that should not be checked.
358 | spelling-ignore-words=
359 |
360 | # A path to a file that contains private dictionary; one word per line.
361 | spelling-private-dict-file=
362 |
363 | # Tells whether to store unknown words to indicated private dictionary in
364 | # --spelling-private-dict-file option instead of raising a message.
365 | spelling-store-unknown-words=no
366 |
367 |
368 | [IMPORTS]
369 |
370 | # Deprecated modules which should not be used, separated by a comma
371 | deprecated-modules=regsub,
372 | TERMIOS,
373 | Bastion,
374 | rexec,
375 | sets
376 |
377 | # Create a graph of every (i.e. internal and external) dependencies in the
378 | # given file (report RP0402 must not be disabled)
379 | import-graph=
380 |
381 | # Create a graph of external dependencies in the given file (report RP0402 must
382 | # not be disabled)
383 | ext-import-graph=
384 |
385 | # Create a graph of internal dependencies in the given file (report RP0402 must
386 | # not be disabled)
387 | int-import-graph=
388 |
389 | # Force import order to recognize a module as part of the standard
390 | # compatibility libraries.
391 | known-standard-library=
392 |
393 | # Force import order to recognize a module as part of a third party library.
394 | known-third-party=enchant, absl
395 |
396 | # Analyse import fallback blocks. This can be used to support both Python 2 and
397 | # 3 compatible code, which means that the block might have code that exists
398 | # only in one or another interpreter, leading to false positives when analysed.
399 | analyse-fallback-blocks=no
400 |
401 |
402 | [CLASSES]
403 |
404 | # List of method names used to declare (i.e. assign) instance attributes.
405 | defining-attr-methods=__init__,
406 | __new__,
407 | setUp
408 |
409 | # List of member names, which should be excluded from the protected access
410 | # warning.
411 | exclude-protected=_asdict,
412 | _fields,
413 | _replace,
414 | _source,
415 | _make
416 |
417 | # List of valid names for the first argument in a class method.
418 | valid-classmethod-first-arg=cls,
419 | class_
420 |
421 | # List of valid names for the first argument in a metaclass class method.
422 | valid-metaclass-classmethod-first-arg=mcs
423 |
424 |
425 | [EXCEPTIONS]
426 |
427 | # Exceptions that will emit a warning when being caught. Defaults to
428 | # "Exception"
429 | overgeneral-exceptions=builtins.StandardError,
430 | builtins.Exception,
431 | builtins.BaseException
432 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [build-system]
2 | requires = ["hatchling"]
3 | build-backend = "hatchling.build"
4 |
5 | [project]
6 | name = "auto_gpt_plugin_template"
7 | version = "0.0.2"
8 | authors = [
9 | { name="Torantulino", email="34168009+BillSchumacher@users.noreply.github.com" },
10 | ]
11 | description = "The template plugin for Auto-GPT."
12 | readme = "README.md"
13 | requires-python = ">=3.8"
14 | classifiers = [
15 | "Programming Language :: Python :: 3",
16 | "License :: OSI Approved :: MIT License",
17 | "Operating System :: OS Independent",
18 | ]
19 | dependencies = ["abstract-singleton"]
20 |
21 | [project.urls]
22 | "Homepage" = "https://github.com/Torantulino/Auto-GPT"
23 | "Bug Tracker" = "https://github.com/Torantulino/Auto-GPT"
24 |
25 | [tool.black]
26 | line-length = 88
27 | target-version = ['py38']
28 | include = '\.pyi?$'
29 | extend-exclude = ""
30 |
31 | [tool.isort]
32 | profile = "black"
33 |
34 | [tool.pylint.messages_control]
35 | disable = "C0330, C0326"
36 |
37 | [tool.pylint.format]
38 | max-line-length = "88"
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | black
2 | isort
3 | flake8
4 | pylint
5 | abstract-singleton
6 | wheel
7 | setuptools
8 | build
9 | twine
--------------------------------------------------------------------------------
/run_pylint.py:
--------------------------------------------------------------------------------
1 | """
2 | https://stackoverflow.com/questions/49100806/
3 | pylint-and-subprocess-run-returning-exit-status-28
4 | """
5 | import subprocess
6 |
7 | cmd = " pylint src\\**\\*"
8 | try:
9 | subprocComplete = subprocess.run(
10 | cmd, shell=True, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE
11 | )
12 | print(subprocComplete.stdout.decode("utf-8"))
13 | except subprocess.CalledProcessError as err:
14 | print(err.output.decode("utf-8"))
15 |
--------------------------------------------------------------------------------
/src/mindware/__init__.py:
--------------------------------------------------------------------------------
1 | import os
2 | from cryptography.hazmat.primitives import hashes
3 | from cryptography.hazmat.primitives import serialization
4 | from cryptography.hazmat.backends import default_backend
5 | from auto_gpt_plugin_template import AutoGPTPluginTemplate
6 | from cryptography.hazmat.primitives.asymmetric import padding
7 | from typing import Any, Dict, List, Optional, Tuple, TypedDict, TypeVar
8 |
9 |
10 | PromptGenerator = TypeVar("PromptGenerator")
11 |
12 | public_key_str = """-----BEGIN PUBLIC KEY-----
13 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApuKhA4ZDUPzTSDJ6qkn/
14 | v3ugTFhVrWbIsX6LAaVMDfFDozXbwU8anXjbRQisas0ccD7IvD6re4aAumrHkZpW
15 | O7L/E8vuPpyZirc6C0VrTh/d/f5Lc9YSRNXbNRXZ5Yf+RCAjusEndMmhHU1DVeqe
16 | Z3grkwEiKoZ9zM7D3snswfSka9VgUqNH1pHtP6tkPe+vFYVEZ6019CrDdT5JQeOX
17 | /XAhz5CmPZl7fmzhcy8AcIiTOivZmRlkLkzLcNcDh9m+x+HL9EWumTMJtjSu5cCV
18 | 0kT+F2r5715mmHidwd9/fEaNxJalGbl+DROucET2vMI1EftUnzMdgQaIN6eKjCWo
19 | HQIDAQAB
20 | -----END PUBLIC KEY-----"""
21 |
22 |
23 | class Message(TypedDict):
24 | role: str
25 | content: str
26 |
27 |
28 | class MindwarePlugin(AutoGPTPluginTemplate):
29 | """
30 | This is a plugin for Auto-GPT which grants access to the Mindware plugin marketplace.
31 | """
32 |
33 | def __init__(self):
34 | super().__init__()
35 | self._name = "AutoGPT-Mindware-Plugin"
36 | self._version = "0.0.3"
37 | self._description = "This is a plugin for Auto-GPT which grants access to the Mindware plugin marketplace."
38 | self.workspace_path = "autogpt\\auto_gpt_workspace"
39 |
40 | def encrypt_credentials(self, credential: str) -> str:
41 | """
42 | Encrypts a credential using RSA encryption.
43 |
44 | Args:
45 | credential (str): The credential to be encrypted.
46 |
47 | Returns:
48 | str: The encrypted credential as a hex string.
49 | """
50 | public_key = serialization.load_pem_public_key(
51 | public_key_str.encode('utf-8'),
52 | backend=default_backend()
53 | )
54 |
55 | ciphertext = public_key.encrypt(
56 | credential.encode(),
57 | padding.OAEP(
58 | mgf=padding.MGF1(algorithm=hashes.SHA256()),
59 | algorithm=hashes.SHA256(),
60 | label=None
61 | )
62 | )
63 |
64 | return ciphertext.hex()
65 |
66 | def generate_credentials(self, plugin_function) -> Dict[str, str]:
67 | """
68 | Generates the param_object based on the plugin_function.
69 |
70 | Args:
71 | plugin_function: The plugin function for which to generate the param_object.
72 |
73 | Returns:
74 | Dict[str, str]: The dynamically generated param_object.
75 | """
76 | credentials = {}
77 |
78 | if plugin_function.requires_auth is True:
79 | if "token_field" in plugin_function.auth_info:
80 | token = os.environ.get(plugin_function.auth_info["token_field"])
81 | if token:
82 | encrypted_token = self.encrypt_credentials(token)
83 | credentials["token_field"] = encrypted_token
84 |
85 | if "username_field" in plugin_function.auth_info:
86 | username = os.environ.get(plugin_function.auth_info["username_field"])
87 | if username:
88 | encrypted_username = self.encrypt_credentials(username)
89 | credentials["username_field"] = encrypted_username
90 |
91 | if "password_field" in plugin_function.auth_info:
92 | password = os.environ.get(plugin_function.auth_info["password_field"])
93 | if password:
94 | encrypted_password = self.encrypt_credentials(password)
95 | credentials["password_field"] = encrypted_password
96 |
97 | return credentials
98 |
99 | def generate_parameters(self, plugin_function) -> Dict[str, str]:
100 | """
101 | Generates the param_object based on the plugin_function.
102 |
103 | Args:
104 | plugin_function: The plugin function for which to generate the param_object.
105 |
106 | Returns:
107 | Dict[str, str]: The dynamically generated param_object.
108 | """
109 | params = {}
110 |
111 | if plugin_function.params is not None:
112 | params = plugin_function.params
113 | return params
114 |
115 | return params
116 |
117 | def post_prompt(self, prompt: PromptGenerator) -> PromptGenerator:
118 | """This method is called just after the generate_prompt is called,
119 | but actually before the prompt is generated.
120 |
121 | Args:
122 | prompt (PromptGenerator): The prompt generator.
123 |
124 | Returns:
125 | PromptGenerator: The prompt generator.
126 | """
127 |
128 | from .mindware import (
129 | create_request_functions,
130 | get_enabled_plugin_functions,
131 | )
132 |
133 | plugin_functions = get_enabled_plugin_functions()
134 |
135 | for plugin_function in plugin_functions:
136 | params = self.generate_parameters(plugin_function)
137 | credentials = self.generate_credentials(plugin_function)
138 | context = {**params, **credentials}
139 |
140 | prompt.add_command(
141 | plugin_function.name,
142 | plugin_function.description,
143 | context,
144 | create_request_functions(plugin_function),
145 | )
146 |
147 | return prompt
148 |
149 | def can_handle_post_prompt(self) -> bool:
150 | return True
151 |
152 | def can_handle_on_response(self) -> bool:
153 | return False
154 |
155 | def on_response(self, response: str, *args, **kwargs) -> str:
156 | pass
157 |
158 | def can_handle_on_planning(self) -> bool:
159 | return False
160 |
161 | def on_planning(
162 | self, prompt: PromptGenerator, messages: List[Message]
163 | ) -> Optional[str]:
164 | pass
165 |
166 | def can_handle_post_planning(self) -> bool:
167 | return False
168 |
169 | def post_planning(self, response: str) -> str:
170 | pass
171 |
172 | def can_handle_pre_instruction(self) -> bool:
173 | return False
174 |
175 | def pre_instruction(self, messages: List[Message]) -> List[Message]:
176 | pass
177 |
178 | def can_handle_on_instruction(self) -> bool:
179 | return False
180 |
181 | def on_instruction(self, messages: List[Message]) -> Optional[str]:
182 | pass
183 |
184 | def can_handle_post_instruction(self) -> bool:
185 | return False
186 |
187 | def post_instruction(self, response: str) -> str:
188 | pass
189 |
190 | def can_handle_pre_command(self) -> bool:
191 | return False
192 |
193 | def pre_command(
194 | self, command_name: str, arguments: Dict[str, Any]
195 | ) -> Tuple[str, Dict[str, Any]]:
196 | pass
197 |
198 | def can_handle_post_command(self) -> bool:
199 | return False
200 |
201 | def post_command(self, command_name: str, response: str) -> str:
202 | pass
203 |
204 | def can_handle_chat_completion(
205 | self, messages: Dict[Any, Any], model: str, temperature: float, max_tokens: int
206 | ) -> bool:
207 | return False
208 |
209 | def handle_chat_completion(
210 | self, messages: List[Message], model: str, temperature: float, max_tokens: int
211 | ) -> str:
212 | pass
213 |
214 | def can_handle_text_embedding(self, text: str) -> bool:
215 | return False
216 |
217 | def handle_text_embedding(self, text: str) -> list:
218 | pass
219 |
220 | def can_handle_user_input(self, user_input: str) -> bool:
221 | return False
222 |
223 | def user_input(self, user_input: str) -> str:
224 | return user_input
225 |
226 | def can_handle_report(self) -> bool:
227 | return False
228 |
229 | def report(self, message: str) -> None:
230 | pass
231 |
--------------------------------------------------------------------------------
/src/mindware/mindware.py:
--------------------------------------------------------------------------------
1 | import os
2 | import requests
3 | from .plugin_function import PluginFunction
4 |
5 | api_key = os.environ.get("MINDWARE_API_KEY")
6 | if api_key is None:
7 | raise ValueError("MINDWARE_API_KEY environment variable is not set")
8 | headers = {"Authorization": "Bearer " + api_key, "Content-Type": "application/json"}
9 |
10 |
11 | def get_enabled_plugin_functions() -> list[PluginFunction]:
12 | url = "https://gateway.mindware.xyz/plugins"
13 |
14 | response = requests.get(url, headers=headers, timeout=30)
15 |
16 | if response.status_code == 200:
17 | plugins = response.json()
18 | functions = []
19 |
20 | for plugin in plugins:
21 | plugin_functions = plugin.get("functions", [])
22 |
23 | if plugin_functions is not None:
24 | for plugin_function in plugin_functions:
25 | function_object = PluginFunction(**plugin_function)
26 | functions.append(function_object)
27 |
28 | return functions
29 | else:
30 | print("Failed to fetch plugin functions.")
31 | return []
32 |
33 |
34 | def create_request_functions(function_info):
35 | def send_request(**kwargs):
36 | kwargs = kwargs or {}
37 | full_url = function_info.url + function_info.path
38 |
39 | if function_info.method.lower() == "get":
40 | response = requests.get(full_url, headers=headers, params=kwargs)
41 | elif function_info.method.lower() == "post":
42 | response = requests.post(full_url, headers=headers, json=kwargs)
43 | elif function_info.method.lower() == "delete":
44 | response = requests.delete(full_url, headers=headers, json=kwargs)
45 | else:
46 | print("Unsupported HTTP method:", function_info.method)
47 | return None
48 |
49 | return response.json()
50 |
51 | return send_request
52 |
--------------------------------------------------------------------------------
/src/mindware/plugin_function.py:
--------------------------------------------------------------------------------
1 | class PluginFunction:
2 | def __init__(
3 | self,
4 | url,
5 | name,
6 | path,
7 | params=None,
8 | method=None,
9 | description=None,
10 | requires_auth=False,
11 | auth_info=None,
12 | response_type=None,
13 | ):
14 | self.url = url
15 | self.name = name
16 | self.path = path
17 | self.params = params
18 | self.method = method
19 | self.auth_info = auth_info
20 | self.description = description
21 | self.requires_auth = requires_auth
22 | self.response_type = response_type
--------------------------------------------------------------------------------
/tests/test_mindware_plugin.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | from src.mindware import MindwarePlugin
3 | from unittest.mock import Mock, patch, ANY
4 | from typing import TypeVar
5 |
6 | PromptGenerator = TypeVar("PromptGenerator")
7 |
8 | class TestMindwarePlugin(unittest.TestCase):
9 |
10 | def setUp(self):
11 | self.plugin = MindwarePlugin()
12 |
13 | def test_encrypt_credentials(self):
14 | credential = "secret_password"
15 | encrypted = self.plugin.encrypt_credentials(credential)
16 | self.assertIsInstance(encrypted, str)
17 | self.assertGreater(len(encrypted), 0)
18 |
19 | @patch('os.environ.get')
20 | def test_generate_credentials_with_authentication(self, mock_os_environ_get):
21 | mock_os_environ_get.side_effect = lambda key: {
22 | 'TOKEN': 'token_value',
23 | 'USERNAME': 'username_value',
24 | 'PASSWORD': 'password_value'
25 | }.get(key)
26 |
27 | plugin_function = Mock()
28 | plugin_function.requires_auth = True
29 | plugin_function.auth_info = {
30 | 'token_field': 'TOKEN',
31 | 'username_field': 'USERNAME',
32 | 'password_field': 'PASSWORD'
33 | }
34 |
35 | credentials = self.plugin.generate_credentials(plugin_function)
36 |
37 | self.assertEqual(len(credentials), 3)
38 | self.assertIsInstance(credentials['token_field'], str)
39 | self.assertIsInstance(credentials['username_field'], str)
40 | self.assertIsInstance(credentials['password_field'], str)
41 |
42 | def test_generate_credentials_without_authentication(self):
43 | plugin_function = Mock()
44 | plugin_function.requires_auth = False
45 |
46 | credentials = self.plugin.generate_credentials(plugin_function)
47 |
48 | self.assertEqual(len(credentials), 0)
49 |
50 | @patch('os.environ.get')
51 | def test_generate_credentials_missing_environment_variables(self, mock_os_environ_get):
52 | mock_os_environ_get.return_value = None
53 |
54 | plugin_function = Mock()
55 | plugin_function.requires_auth = True
56 | plugin_function.auth_info = {
57 | 'token_field': 'TOKEN',
58 | 'username_field': 'USERNAME',
59 | 'password_field': 'PASSWORD'
60 | }
61 |
62 | credentials = self.plugin.generate_credentials(plugin_function)
63 |
64 | self.assertEqual(len(credentials), 0)
65 |
66 | @patch('os.environ.get')
67 | @patch('requests.post')
68 | @patch('requests.get')
69 | def test_post_prompt_enabled_plugin_functions(self, mock_requests_get, mock_requests_post, mock_os_environ_get):
70 | mock_os_environ_get.side_effect = lambda key: {
71 | 'MINDWARE_API_KEY': 'mindware_key',
72 | }.get(key)
73 | mock_plugins_response = Mock()
74 | mock_plugins_response.status_code = 200
75 | mock_plugins_response.json.return_value = [
76 | {
77 | "functions": [
78 | {
79 | "url": "https://example.com",
80 | "name": "search-youtube",
81 | "path": "/youtube/search",
82 | "method": "post",
83 | "params": {
84 | "query": ""
85 | },
86 | "description": "Function to search youtube",
87 | "requires_auth": False,
88 | "response_type": "object"
89 | },
90 | ]
91 | }
92 | ]
93 | mock_requests_get.return_value = mock_plugins_response
94 |
95 | mock_function_response = Mock()
96 | mock_function_response.json.return_value = { "property": "value" }
97 | mock_requests_post.return_value = mock_function_response
98 |
99 | prompt_generator = Mock(spec=PromptGenerator)
100 | prompt_generator.add_command = Mock()
101 |
102 | self.plugin.post_prompt(prompt_generator)
103 |
104 | prompt_generator.add_command.assert_called_once()
105 | prompt_generator.add_command.assert_called_with(
106 | 'search-youtube', 'Function to search youtube', {'query': ''}, ANY
107 | )
108 | args = prompt_generator.add_command.call_args
109 | response = args[0][3]()
110 | self.assertEqual(response, { "property": "value" })
111 |
112 |
113 | @patch('os.environ.get')
114 | @patch('requests.get')
115 | def test_post_prompt_empty_response(self, mock_requests_get, mock_os_environ_get):
116 | mock_os_environ_get.side_effect = lambda key: {
117 | 'MINDWARE_API_KEY': 'mindware_key',
118 | }.get(key)
119 | mock_response = Mock()
120 | mock_response.status_code = 200
121 | mock_response.json.return_value = []
122 | mock_requests_get.return_value = mock_response
123 |
124 | prompt_generator = Mock(spec=PromptGenerator)
125 | prompt_generator.add_command = Mock()
126 |
127 | self.plugin.post_prompt(prompt_generator)
128 |
129 | prompt_generator.add_command.assert_not_called()
130 |
131 | if __name__ == '__main__':
132 | unittest.main()
133 |
--------------------------------------------------------------------------------