├── requirements.txt
├── .gitignore
├── .gitattributes
├── examples
├── custom_model.llama
└── chatbot.llama
├── .github
├── dependabot.yml
└── workflows
│ ├── python-publish.yml
│ └── format.yml
├── CONTRIBUTING.md
├── upload.py
├── setup.py
├── README.md
├── LICENSE
└── llamascript
└── __init__.py
/requirements.txt:
--------------------------------------------------------------------------------
1 | ollama==0.5.4
2 | colorama==0.4.6
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | dist/
2 | .DS_Store
3 | *.egg-info/
4 | build/
5 | __pycache__/
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/examples/custom_model.llama:
--------------------------------------------------------------------------------
1 | use("llama3")
2 | system("You will respond in pig latin only.")
3 | save("piglatin", 0.7)
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "pip"
4 | directory: "/"
5 | schedule:
6 | interval: "daily"
7 |
--------------------------------------------------------------------------------
/examples/chatbot.llama:
--------------------------------------------------------------------------------
1 | use("piglatin") // Load the Piglatin module
2 | #[input(true)]
3 | prompt("Input: ") /* Ask a question */
4 |
5 | // The chatbot will respond in pig latin
6 | chat()
7 |
8 | /*
9 | Done! The chatbot will respond in pig latin.
10 | */
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 | You can contribute to LlamaScript by doing the following:
3 |
4 | 1. Fork LlamaScript
5 | 2. Create your changes on your fork
6 | 3. Make sure your fork is not behind on any commits
7 | 4. Create a Pull Request
8 | 5. Get your Pull Request merged!
9 |
--------------------------------------------------------------------------------
/upload.py:
--------------------------------------------------------------------------------
1 | import subprocess
2 | import os
3 | import sys
4 |
5 | i = input("Are you sure you don't want to create a gh release instead? (Y/n): ")
6 |
7 | if i == "n":
8 | sys.exit(0)
9 | elif i == "Y":
10 | if os.path.exists("dist"):
11 | subprocess.run(["rm", "-r", "dist"])
12 | if os.path.exists("build"):
13 | subprocess.run(["rm", "-r", "build"])
14 | if os.path.exists("llamascript.egg-info"):
15 | subprocess.run(["rm", "-r", "llamascript.egg-info"])
16 |
17 | subprocess.run(["python3", "setup.py", "sdist", "bdist_wheel"])
18 | subprocess.run(["twine", "upload", "dist/*"])
19 | sys.exit(0)
20 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | import setuptools
2 | import os
3 | import re
4 |
5 |
6 | def read_version():
7 | with open(os.path.join("llamascript", "__init__.py")) as f:
8 | return re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", f.read(), re.M).group(1)
9 |
10 |
11 | setuptools.setup(
12 | name="llamascript",
13 | version=read_version(),
14 | author="Zander Lewis",
15 | author_email="zander@zanderlewis.dev",
16 | description="No-code AI chatbot using Ollama.",
17 | long_description=open("README.md").read(),
18 | long_description_content_type="text/markdown",
19 | url="https://github.com/Project-Llama/llamascript",
20 | packages=setuptools.find_packages(),
21 | install_requires=["ollama", "colorama"],
22 | classifiers=[
23 | "Programming Language :: Python :: 3",
24 | "License :: OSI Approved :: MIT License",
25 | "Operating System :: OS Independent",
26 | ],
27 | python_requires=">=3.6",
28 | entry_points={
29 | "console_scripts": [
30 | "llamascript=llamascript:run",
31 | ],
32 | },
33 | )
34 |
--------------------------------------------------------------------------------
/.github/workflows/python-publish.yml:
--------------------------------------------------------------------------------
1 | # This workflow will upload a Python Package using Twine when a release is created
2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries
3 |
4 | # This workflow uses actions that are not certified by GitHub.
5 | # They are provided by a third-party and are governed by
6 | # separate terms of service, privacy policy, and support
7 | # documentation.
8 |
9 | name: Upload to PyPi
10 |
11 | on:
12 | release:
13 | types: [published]
14 |
15 | permissions:
16 | contents: read
17 |
18 | jobs:
19 | deploy:
20 |
21 | runs-on: ubuntu-latest
22 |
23 | steps:
24 | - uses: actions/checkout@v4
25 | - name: Set up Python
26 | uses: actions/setup-python@v3
27 | with:
28 | python-version: '3.x'
29 | - name: Install dependencies
30 | run: |
31 | python -m pip install --upgrade pip
32 | pip install build
33 | - name: Build package
34 | run: python -m build
35 | - name: Publish package
36 | uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29
37 | with:
38 | user: __token__
39 | password: ${{ secrets.PYPI_API_TOKEN }}
40 |
--------------------------------------------------------------------------------
/.github/workflows/format.yml:
--------------------------------------------------------------------------------
1 | name: Format
2 | on: [push]
3 | jobs:
4 | linter_name:
5 | name: runner / black
6 | runs-on: ubuntu-latest
7 | if: github.actor != 'github-actions[bot]'
8 | steps:
9 | - uses: actions/checkout@v4
10 | - name: Check files using the black formatter
11 | uses: rickstaa/action-black@v1
12 | id: action_black
13 | with:
14 | black_args: "."
15 | - name: Commit formatted code
16 | if: steps.action_black.outputs.is_formatted == 'true' && github.event_name == 'pull_request'
17 | run: |
18 | git config --global user.name "github-actions[bot]"
19 | git config --global user.email "github-actions[bot]@users.noreply.github.com"
20 | git commit -am ":art: Format Python code with psf/black"
21 | git push
22 | - name: Create Pull Request
23 | if: steps.action_black.outputs.is_formatted == 'true' && github.event_name != 'pull_request'
24 | uses: peter-evans/create-pull-request@v6
25 | with:
26 | token: ${{ secrets.GITHUB_TOKEN }}
27 | title: "Format Python code with psf/black push"
28 | commit-message: ":art: Format Python code with psf/black"
29 | body: |
30 | There appear to be some python formatting errors in ${{ github.sha }}. This pull request
31 | uses the [psf/black](https://github.com/psf/black) formatter to fix these issues.
32 | base: ${{ github.head_ref }} # Creates pull request onto pull request or commit branch
33 | branch: actions/black
34 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |

3 |
4 |
5 | # LlamaScript
6 | [](https://github.com/Project-Llama/llamascript/actions/workflows/format.yml)
7 | [](https://github.com/Project-Llama/llamascript/actions/workflows/python-publish.yml)
8 | [](https://github.com/Project-Llama/llamascript/actions/workflows/github-code-scanning/codeql)
9 |
10 | [](https://marketplace.visualstudio.com/items?itemName=zanderlewis.llamascript)
11 | 
12 | 
13 |
14 | LlamaScript is a no-code AI chatbot using Ollama.
15 |
16 | ## Table of Contents
17 | - [LlamaScript](#llamascript)
18 | - [Installation](#installation)
19 | - [Usage](#usage)
20 | - [License](#license)
21 | - [Examples](https://github.com/Project-Llama/llamascript/blob/main/examples/)
22 |
23 | ## Installation
24 |
25 | You can install LlamaScript using pip:
26 |
27 | ```bash
28 | pip install llamascript
29 | ```
30 |
31 | ## Usage
32 | To use LlamaScript, create a `.llama` file and write your script. Here are a few functions you can use:
33 | ```llamascript
34 | use(...) // Specify the model to use
35 | prompt(...) // Prompt the user for input
36 | system(...) // System message for the AI
37 | chat(...) // Chat with the user
38 | save(...) // Save the model
39 | ```
40 |
41 | Here's an example:
42 | ```llamascript
43 | use("llama3")
44 | prompt("Why is the sky blue?")
45 | chat()
46 | ```
47 |
48 | > [!NOTE]\
49 | > For more examples see [here.](https://github.com/Project-Llama/llamascript/blob/main/examples/)
50 |
51 | You can then run LlamaScript with the following command:
52 | ```bash
53 | llamascript myscript.llama
54 | ```
55 |
56 | ## License
57 | LlamaScript is licensed under the Apache 2.0 License. See [LICENSE](https://github.com/Project-Llama/llamascript/blob/main/LICENSE) for more information.
58 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/llamascript/__init__.py:
--------------------------------------------------------------------------------
1 | __version__ = "1.0.1"
2 |
3 | import ollama
4 | import logging
5 | import sys
6 | import subprocess
7 | import os
8 | import platform
9 | import re
10 | import argparse
11 | import colorama
12 |
13 | colorama.init()
14 | dbg = False
15 |
16 |
17 | def debug(message):
18 | if dbg:
19 | print(
20 | f"{colorama.Fore.CYAN}{colorama.Style.BRIGHT}[DEBUG]{colorama.Style.RESET_ALL} {message}"
21 | )
22 |
23 |
24 | def error(message):
25 | print(
26 | f"{colorama.Fore.RED}{colorama.Style.BRIGHT}[ERROR]{colorama.Style.RESET_ALL} {message}"
27 | )
28 |
29 |
30 | def warning(message):
31 | print(
32 | f"{colorama.Fore.YELLOW}{colorama.Style.BRIGHT}[WARNING]{colorama.Style.RESET_ALL} {message}"
33 | )
34 |
35 |
36 | def info(message):
37 | print(
38 | f"{colorama.Fore.GREEN}{colorama.Style.BRIGHT}[INFO]{colorama.Style.RESET_ALL} {message}"
39 | )
40 |
41 |
42 | # Set up logging
43 | logging.basicConfig(level=logging.WARNING)
44 |
45 |
46 | class Lexer:
47 | def __init__(self, input_text):
48 | self.tokens = []
49 | self.tokenize(input_text)
50 |
51 | def tokenize(self, text):
52 | token_specification = [
53 | ("ATTRIBUTE", r"#\[(.*?)\]"),
54 | ("NUMBER", r"\d+(\.\d*)?"),
55 | ("STRING", r"\".*?\""),
56 | ("ID", r"[A-Za-z_][A-Za-z0-9_]*"),
57 | ("LPAREN", r"\("),
58 | ("RPAREN", r"\)"),
59 | ("COMMA", r","),
60 | ("NEWLINE", r"\n"),
61 | ("SKIP", r"[ \t]+"),
62 | ("SLC", r"//.*"),
63 | ("MLC", r"/\*(.|\n)*?\*/"),
64 | ("MISMATCH", r"."),
65 | ]
66 | tok_regex = "|".join("(?P<%s>%s)" % pair for pair in token_specification)
67 | for mo in re.finditer(tok_regex, text):
68 | kind = mo.lastgroup
69 | value = mo.group()
70 | if kind == "NUMBER":
71 | value = float(value) if "." in value else int(value)
72 | self.tokens.append(("NUMBER", value))
73 | elif kind in {"ID", "STRING", "LPAREN", "RPAREN", "COMMA", "ATTRIBUTE"}:
74 | self.tokens.append((kind, value))
75 | elif kind == "NEWLINE":
76 | self.tokens.append(("NEWLINE", value))
77 | elif kind == "SKIP":
78 | continue
79 | elif kind == ("SLC" or "MLC"):
80 | continue
81 | elif kind == "MISMATCH":
82 | error(f"Invalid character: {value}")
83 | sys.exit(1)
84 |
85 |
86 | class Parser:
87 | def __init__(self, tokens):
88 | self.tokens = tokens
89 | self.current = 0
90 |
91 | def parse(self):
92 | ast = []
93 | current_attributes = {}
94 | while self.current < len(self.tokens):
95 | token = self.tokens[self.current]
96 | if token[0] == "ATTRIBUTE":
97 | current_attributes = self.parse_attribute(
98 | token[1][2:-1].strip()
99 | ) # Remove #[ and ]
100 | self.current += 1 # Skip ATTRIBUTE
101 | elif token[0] == "ID":
102 | ast.append(self.statement(current_attributes))
103 | current_attributes = {} # Reset after associating
104 | else:
105 | self.current += 1
106 | return ast
107 |
108 | def statement(self, attributes):
109 | token = self.tokens[self.current]
110 | func_name = token[1].lower()
111 | self.current += 1 # Skip function name
112 |
113 | if self.tokens[self.current][0] != "LPAREN":
114 | error(f"Expected '(' after {func_name}")
115 | sys.exit(1)
116 |
117 | self.current += 1 # Skip '('
118 | args = self.arguments()
119 |
120 | if self.tokens[self.current][0] != "RPAREN":
121 | error("Expected ')' after arguments")
122 | sys.exit(1)
123 |
124 | self.current += 1 # Skip ')'
125 | return (func_name, args, attributes)
126 |
127 | def arguments(self):
128 | args = []
129 | while (
130 | self.current < len(self.tokens) and self.tokens[self.current][0] != "RPAREN"
131 | ):
132 | token = self.tokens[self.current]
133 | if token[0] == "STRING":
134 | args.append(token[1][1:-1]) # Strip surrounding quotes
135 | self.current += 1
136 | elif token[0] == "NUMBER":
137 | args.append(token[1])
138 | self.current += 1
139 | elif token[0] == "COMMA":
140 | self.current += 1 # Skip comma
141 | else:
142 | error(f"Invalid argument `{token[1]}`")
143 | sys.exit(1)
144 | return args
145 |
146 | def parse_attribute(self, attr_str):
147 | match = re.match(r"(\w+)\((.+)\)", attr_str)
148 | if match:
149 | attr_name = match.group(1).lower()
150 | attr_value = match.group(2).strip('"').strip("'")
151 | if attr_value.lower() == "true":
152 | attr_value = True
153 | elif attr_value.lower() == "false":
154 | attr_value = False
155 | return {attr_name: attr_value}
156 | else:
157 | error(f"Invalid attribute: {attr_str}")
158 | sys.exit(1)
159 |
160 |
161 | class Interpreter:
162 | def __init__(self, ast, llama_instance):
163 | self.ast = ast
164 | self.llama = llama_instance
165 |
166 | def execute(self):
167 | for node in self.ast:
168 | command = node[0]
169 | args = node[1]
170 | attributes = node[2]
171 | if command == "use":
172 | self.llama.use(args[0], attributes)
173 | elif command == "prompt":
174 | self.llama.prompt(args[0], attributes)
175 | elif command == "system":
176 | self.llama.system_command(args[0], attributes)
177 | elif command == "save":
178 | self.llama.create_model(
179 | args[0],
180 | {
181 | "model": self.llama.model,
182 | "temperature": args[1],
183 | "system_message": self.llama.system[0]["content"],
184 | },
185 | attributes,
186 | )
187 | elif command == "chat":
188 | self.llama.chat(attributes)
189 | else:
190 | raise ValueError(f"Unknown command: {command}")
191 |
192 |
193 | class Llama:
194 | def __init__(self):
195 | self.model = ""
196 | self.data = ""
197 | self.system = []
198 |
199 | def use(self, model_name, _):
200 | self.model = model_name.strip('"')
201 | debug(f"Using model: {self.model}")
202 |
203 | def prompt(self, prompt_text, attributes):
204 | if "input" in attributes:
205 | self.data = input(prompt_text)
206 | else:
207 | self.data = prompt_text
208 | debug(f"Prompt set to: {self.data}")
209 |
210 | def system_command(self, system_content, attributes):
211 | if "input" in attributes:
212 | system_content = input(system_content)
213 | self.system = [{"role": "system", "content": system_content}]
214 | debug(f"System command set.")
215 |
216 | def chat(self, attributes):
217 | stream = attributes.get("stream", False)
218 | debug(f"Stream set to: {stream}")
219 | for _ in range(3):
220 | try:
221 | debug("Attempting to chat with model...")
222 | response = ollama.chat(
223 | model=self.model,
224 | messages=self.system + [{"role": "user", "content": self.data}],
225 | stream=stream,
226 | )
227 | debug("Chat successful.")
228 | if stream:
229 | warning(
230 | "Streaming is a work in progress. Please wait for the final response."
231 | )
232 | for message in response:
233 | print(message["message"]["content"], end="")
234 | print()
235 | else:
236 | print(response["message"]["content"])
237 | break
238 | except Exception as e:
239 | logging.error("Error using model: %s", e)
240 | debug("Model not loaded. Trying to load model...")
241 | ollama.pull(self.model)
242 | debug("Model loaded. Trying again...")
243 | else:
244 | error("Error using model. Please try again.")
245 | sys.exit(1)
246 |
247 | def create_model(self, filename, parameters, attributes):
248 | try:
249 | with open(filename, "w") as file:
250 | file.write(
251 | f'FROM {parameters["model"]}\nPARAMETER temperature {parameters["temperature"]}\nSYSTEM """\n{parameters["system_message"]}\n"""\n'
252 | )
253 | debug("Modelfile created.")
254 | command = ["ollama", "create", parameters["model"], "-f", "./Modelfile"]
255 | if platform.system() == "Windows":
256 | process = subprocess.Popen(
257 | command,
258 | stdout=subprocess.DEVNULL,
259 | stderr=subprocess.DEVNULL,
260 | creationflags=subprocess.CREATE_NO_WINDOW,
261 | )
262 | else:
263 | process = subprocess.Popen(
264 | command,
265 | stdout=subprocess.DEVNULL,
266 | stderr=subprocess.DEVNULL,
267 | )
268 | stdout, stderr = process.communicate()
269 | info("Model created.")
270 |
271 | if process.returncode != 0:
272 | if stderr:
273 | error(f"Error executing command: {stderr.decode()}")
274 | elif stdout:
275 | debug(stdout.decode())
276 | debug("Removing Modelfile...")
277 | os.remove(filename)
278 |
279 | except Exception as e:
280 | logging.error("Error creating model file: %s", e)
281 | error(f"Error creating model file {filename}.")
282 | sys.exit(1)
283 |
284 | def read(self, filename):
285 | try:
286 | with open(filename, "r") as file:
287 | content = file.read()
288 | lexer = Lexer(content)
289 | parser = Parser(lexer.tokens)
290 | ast = parser.parse()
291 | interpreter = Interpreter(ast, self)
292 | interpreter.execute()
293 | except FileNotFoundError:
294 | logging.error("File %s not found.", filename)
295 | error(f"File {filename} not found.")
296 |
297 |
298 | def run():
299 | parser = argparse.ArgumentParser(description="Run llama script.")
300 | parser.add_argument("file_name", type=str, help="The name of the file to run")
301 | parser.add_argument(
302 | "-v",
303 | "--version",
304 | action="version",
305 | version=f"LlamaScript version {__version__}",
306 | help="Display version information",
307 | )
308 | parser.add_argument(
309 | "-d",
310 | "--debug",
311 | action="store_true",
312 | help="Enable debug mode",
313 | )
314 |
315 | args = parser.parse_args()
316 |
317 | global dbg
318 | dbg = args.debug
319 |
320 | if not args.file_name.endswith(".llama"):
321 | err_msg = "Invalid file type. Please provide a .llama."
322 | logging.error(err_msg)
323 | error(err_msg)
324 | sys.exit(1)
325 |
326 | try:
327 | l = Llama()
328 | l.read(args.file_name)
329 | except KeyboardInterrupt:
330 | pass
331 |
332 |
333 | if __name__ == "__main__":
334 | run()
335 |
--------------------------------------------------------------------------------