textjenerator 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- textjenerator-0.1.0/LICENSE +21 -0
- textjenerator-0.1.0/PKG-INFO +112 -0
- textjenerator-0.1.0/README.md +94 -0
- textjenerator-0.1.0/pyproject.toml +30 -0
- textjenerator-0.1.0/src/textjenerator/config.py +23 -0
- textjenerator-0.1.0/src/textjenerator/core/text_generator.py +129 -0
- textjenerator-0.1.0/src/textjenerator/examples/generate_text.py +7 -0
- textjenerator-0.1.0/src/textjenerator/examples/split_workflow.py +30 -0
- textjenerator-0.1.0/src/textjenerator/models/__init__.py +5 -0
- textjenerator-0.1.0/src/textjenerator/models/llama_cpp.py +95 -0
- textjenerator-0.1.0/src/textjenerator/models/registry.py +54 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Warren Davies
|
|
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.
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: textjenerator
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: AI text generator
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Author: Warren Davies
|
|
8
|
+
Requires-Python: >=3.10,<3.15
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
14
|
+
Requires-Dist: llama-cpp-python (>=0.3.16,<0.4.0)
|
|
15
|
+
Requires-Dist: torch (>=2.9.1,<3.0.0)
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
# Text Jenerator
|
|
19
|
+
|
|
20
|
+
A Unified Python Interface for Modular Text Generation.
|
|
21
|
+
|
|
22
|
+
Text Jenerator is a framework designed to provide a consistent, abstract interface for deploying and interacting with various Large Language Models (LLMs). By separating the generic configuration and workflow from the model-specific implementation, it enables you to easily switch between different model backends—such as local GGUF models (via Llama-CPP), Hugging Face Transformers, or proprietary APIs (e.g., OpenAI, Gemini), with no code changes besides the config you pass.
|
|
23
|
+
|
|
24
|
+
## Features
|
|
25
|
+
|
|
26
|
+
* **Modular Architecture**: Uses Abstract Base Classes (ABC) to enforce a clean separation between the model details, meaning you only need to instantiate the TextGenerator class and call `generate_text()`, no matter which model you're using.
|
|
27
|
+
* **Automatic Setup**: Handles generic tasks like device detection (CPU/CUDA) and data type selection (bfloat16, float16, float32) in the base class.
|
|
28
|
+
* **Plug-and-Play Models**: Easily integrate new models by creating concrete child classes that implement two simple abstract methods (create_pipeline and run_pipeline).
|
|
29
|
+
* **Model Registry**: Uses a decorator (@register_model) to automatically track and load available generation backends.
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
pip install textjenerator
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The core package has minimal dependencies. Model-specific implementations (e.g., for Llama-CPP or Transformers) require their respective libraries.
|
|
38
|
+
|
|
39
|
+
## Models
|
|
40
|
+
|
|
41
|
+
Text Jenerator is BYOM. You can put the local model path or a HuggingFace ID in your config.
|
|
42
|
+
|
|
43
|
+
## Quick Start & Usage
|
|
44
|
+
|
|
45
|
+
### Config
|
|
46
|
+
|
|
47
|
+
Your project should include a config file (defaults are at textjenerator.config). A basic config might look like this:
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
config = {
|
|
51
|
+
# model
|
|
52
|
+
"model": "llama-cpp",
|
|
53
|
+
"model_path": ".models/Llama-3.2-3B-Instruct-Q4_K_M.gguf",
|
|
54
|
+
|
|
55
|
+
# hardware/system
|
|
56
|
+
"device": "cpu",
|
|
57
|
+
"dtype": "float32",
|
|
58
|
+
|
|
59
|
+
# LLM
|
|
60
|
+
"max_context_size": 4096,
|
|
61
|
+
"number_of_threads": 8 ,
|
|
62
|
+
"verbose_warnings": False,
|
|
63
|
+
"messages_to_keep_in_context": 4,
|
|
64
|
+
"max_tokens_per_response": 256,
|
|
65
|
+
"temperature": 0.7 ,
|
|
66
|
+
"top_p": 0.9,
|
|
67
|
+
"top_k": 50,
|
|
68
|
+
"messages": [
|
|
69
|
+
{"role": "system", "content": """You are Jenbot, an expert, helpful, and diligent assistant. You provide the user with accurate answers to their queries. You are polite, friendly, and a little sarcastic."""},
|
|
70
|
+
{"role": "user", "content": """Hi, who are you?"""},
|
|
71
|
+
]
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Run the Generation
|
|
76
|
+
|
|
77
|
+
To run the main workflow:
|
|
78
|
+
|
|
79
|
+
```py
|
|
80
|
+
from textjenerator.models import registry
|
|
81
|
+
from config import config # or load your config however you prefer
|
|
82
|
+
|
|
83
|
+
text_generator = registry.get_model_class(config) # Choose which model you want from the registry
|
|
84
|
+
response = text_generator.generate_text() # create pipeline and run it
|
|
85
|
+
print(response)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
You can also do these steps separately if you want to keep the model in memory. In this case you can pass a new config, typically this will be to include the cumulative chat history, but you could also change other settings on the fly e.g.:
|
|
89
|
+
|
|
90
|
+
* Reduce temperature for a coding question
|
|
91
|
+
* Increase temperature for a creative writing request
|
|
92
|
+
* Increase max_tokens_per_response if you expect a longer reply
|
|
93
|
+
|
|
94
|
+
```py
|
|
95
|
+
text_generator = registry.get_model_class(config)
|
|
96
|
+
text_generator.create_pipeline()
|
|
97
|
+
response = text_generator.run_pipeline()
|
|
98
|
+
print(response)
|
|
99
|
+
|
|
100
|
+
# or pass another, doesn't have to be the whole thing:
|
|
101
|
+
new_config = {
|
|
102
|
+
"messages": [
|
|
103
|
+
{"role": "system", "content": """You are Jenbot, an expert, helpful, and diligent assistant. You provide the user with accurate answers to their queries. You are polite, friendly, and a little sarcastic."""},
|
|
104
|
+
{"role": "user", "content": """Hi, who are you?"""},
|
|
105
|
+
{"role": "assistant", "content": """You must be wondering who I am. Well, let me introduce myself: I'm Jenbot, your friendly AI assistant. I'm here to help answer any questions you may have, provide information on a wide range of topics, and even offer a dash of sarcasm when the situation calls for it."""},
|
|
106
|
+
{"role": "user", "content": """What are your thoughts on the hard problem of consciousness?"""},
|
|
107
|
+
]
|
|
108
|
+
}
|
|
109
|
+
response = text_generator.run_pipeline(new_config)
|
|
110
|
+
print(response)
|
|
111
|
+
```
|
|
112
|
+
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Text Jenerator
|
|
2
|
+
|
|
3
|
+
A Unified Python Interface for Modular Text Generation.
|
|
4
|
+
|
|
5
|
+
Text Jenerator is a framework designed to provide a consistent, abstract interface for deploying and interacting with various Large Language Models (LLMs). By separating the generic configuration and workflow from the model-specific implementation, it enables you to easily switch between different model backends—such as local GGUF models (via Llama-CPP), Hugging Face Transformers, or proprietary APIs (e.g., OpenAI, Gemini), with no code changes besides the config you pass.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
* **Modular Architecture**: Uses Abstract Base Classes (ABC) to enforce a clean separation between the model details, meaning you only need to instantiate the TextGenerator class and call `generate_text()`, no matter which model you're using.
|
|
10
|
+
* **Automatic Setup**: Handles generic tasks like device detection (CPU/CUDA) and data type selection (bfloat16, float16, float32) in the base class.
|
|
11
|
+
* **Plug-and-Play Models**: Easily integrate new models by creating concrete child classes that implement two simple abstract methods (create_pipeline and run_pipeline).
|
|
12
|
+
* **Model Registry**: Uses a decorator (@register_model) to automatically track and load available generation backends.
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
pip install textjenerator
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
The core package has minimal dependencies. Model-specific implementations (e.g., for Llama-CPP or Transformers) require their respective libraries.
|
|
21
|
+
|
|
22
|
+
## Models
|
|
23
|
+
|
|
24
|
+
Text Jenerator is BYOM. You can put the local model path or a HuggingFace ID in your config.
|
|
25
|
+
|
|
26
|
+
## Quick Start & Usage
|
|
27
|
+
|
|
28
|
+
### Config
|
|
29
|
+
|
|
30
|
+
Your project should include a config file (defaults are at textjenerator.config). A basic config might look like this:
|
|
31
|
+
|
|
32
|
+
```json
|
|
33
|
+
config = {
|
|
34
|
+
# model
|
|
35
|
+
"model": "llama-cpp",
|
|
36
|
+
"model_path": ".models/Llama-3.2-3B-Instruct-Q4_K_M.gguf",
|
|
37
|
+
|
|
38
|
+
# hardware/system
|
|
39
|
+
"device": "cpu",
|
|
40
|
+
"dtype": "float32",
|
|
41
|
+
|
|
42
|
+
# LLM
|
|
43
|
+
"max_context_size": 4096,
|
|
44
|
+
"number_of_threads": 8 ,
|
|
45
|
+
"verbose_warnings": False,
|
|
46
|
+
"messages_to_keep_in_context": 4,
|
|
47
|
+
"max_tokens_per_response": 256,
|
|
48
|
+
"temperature": 0.7 ,
|
|
49
|
+
"top_p": 0.9,
|
|
50
|
+
"top_k": 50,
|
|
51
|
+
"messages": [
|
|
52
|
+
{"role": "system", "content": """You are Jenbot, an expert, helpful, and diligent assistant. You provide the user with accurate answers to their queries. You are polite, friendly, and a little sarcastic."""},
|
|
53
|
+
{"role": "user", "content": """Hi, who are you?"""},
|
|
54
|
+
]
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Run the Generation
|
|
59
|
+
|
|
60
|
+
To run the main workflow:
|
|
61
|
+
|
|
62
|
+
```py
|
|
63
|
+
from textjenerator.models import registry
|
|
64
|
+
from config import config # or load your config however you prefer
|
|
65
|
+
|
|
66
|
+
text_generator = registry.get_model_class(config) # Choose which model you want from the registry
|
|
67
|
+
response = text_generator.generate_text() # create pipeline and run it
|
|
68
|
+
print(response)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
You can also do these steps separately if you want to keep the model in memory. In this case you can pass a new config, typically this will be to include the cumulative chat history, but you could also change other settings on the fly e.g.:
|
|
72
|
+
|
|
73
|
+
* Reduce temperature for a coding question
|
|
74
|
+
* Increase temperature for a creative writing request
|
|
75
|
+
* Increase max_tokens_per_response if you expect a longer reply
|
|
76
|
+
|
|
77
|
+
```py
|
|
78
|
+
text_generator = registry.get_model_class(config)
|
|
79
|
+
text_generator.create_pipeline()
|
|
80
|
+
response = text_generator.run_pipeline()
|
|
81
|
+
print(response)
|
|
82
|
+
|
|
83
|
+
# or pass another, doesn't have to be the whole thing:
|
|
84
|
+
new_config = {
|
|
85
|
+
"messages": [
|
|
86
|
+
{"role": "system", "content": """You are Jenbot, an expert, helpful, and diligent assistant. You provide the user with accurate answers to their queries. You are polite, friendly, and a little sarcastic."""},
|
|
87
|
+
{"role": "user", "content": """Hi, who are you?"""},
|
|
88
|
+
{"role": "assistant", "content": """You must be wondering who I am. Well, let me introduce myself: I'm Jenbot, your friendly AI assistant. I'm here to help answer any questions you may have, provide information on a wide range of topics, and even offer a dash of sarcasm when the situation calls for it."""},
|
|
89
|
+
{"role": "user", "content": """What are your thoughts on the hard problem of consciousness?"""},
|
|
90
|
+
]
|
|
91
|
+
}
|
|
92
|
+
response = text_generator.run_pipeline(new_config)
|
|
93
|
+
print(response)
|
|
94
|
+
```
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "textjenerator"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "AI text generator"
|
|
5
|
+
authors = [{ name = "Warren Davies"}]
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
license = "MIT"
|
|
8
|
+
homepage = "https://github.com/WarrenDavies/text-jenerator"
|
|
9
|
+
repository = "https://github.com/WarrenDavies/text-jenerator"
|
|
10
|
+
packages = [
|
|
11
|
+
{ include = "textjenerator", from = "src" }
|
|
12
|
+
]
|
|
13
|
+
requires-python = ">=3.10,<3.15"
|
|
14
|
+
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"Programming Language :: Python :: 3.12",
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
"Operating System :: OS Independent",
|
|
20
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
[tool.poetry.dependencies]
|
|
24
|
+
python = ">=3.10,<3.15"
|
|
25
|
+
llama-cpp-python = ">=0.3.16,<0.4.0"
|
|
26
|
+
torch = "^2.9.1"
|
|
27
|
+
|
|
28
|
+
[build-system]
|
|
29
|
+
requires = ["poetry-core>=1.0.0"]
|
|
30
|
+
build-backend = "poetry.core.masonry.api"
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
config = {
|
|
2
|
+
# model
|
|
3
|
+
"model": "llama-cpp",
|
|
4
|
+
"model_path": ".models/Llama-3.2-3B-Instruct-Q4_K_M.gguf",
|
|
5
|
+
|
|
6
|
+
# hardware/system
|
|
7
|
+
"device": "cpu",
|
|
8
|
+
"dtype": "float32",
|
|
9
|
+
"number_of_threads": 8,
|
|
10
|
+
"n_gpu_layers": -1,
|
|
11
|
+
|
|
12
|
+
# LLM
|
|
13
|
+
"verbose_warnings": False,
|
|
14
|
+
"max_context_size": 4096,
|
|
15
|
+
"max_tokens_per_response": 256,
|
|
16
|
+
"temperature": .9 ,
|
|
17
|
+
"top_p": 0.9,
|
|
18
|
+
"top_k": 50,
|
|
19
|
+
"messages": [
|
|
20
|
+
{"role": "system", "content": """You are Jenbot, an expert, helpful, and diligent assistant. You provide the user with accurate answers to their queries. You are polite, friendly, and a little sarcastic."""},
|
|
21
|
+
{"role": "user", "content": """Hi, who are you?"""},
|
|
22
|
+
]
|
|
23
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
import torch
|
|
5
|
+
|
|
6
|
+
from textjenerator.config import config
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TextGenerator(ABC):
|
|
10
|
+
"""
|
|
11
|
+
Abstract base class for text generation. This class handles the generic configuration and execution flow and manages device (CPU/CUDA) /data type (e.g., bfloat16) setup.
|
|
12
|
+
|
|
13
|
+
Subclasses must implement create_pipeline() and run_pipeline()
|
|
14
|
+
|
|
15
|
+
Attributes:
|
|
16
|
+
config (dict): Configuration dictionary containing model parameters, paths, and settings.
|
|
17
|
+
DTYPES_MAP (dict): A mapping from string names (e.g., "bfloat16") to torch.dtype objects.
|
|
18
|
+
Add: pipe (Any): The initialized model pipeline (to be set by subclasses).
|
|
19
|
+
Add: response (str/Any): The generated text or model output (to be set by subclasses).
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(self, config = config):
|
|
23
|
+
"""
|
|
24
|
+
Initializes the object with a config.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
config (dict): A dictionary containing configuration parameters. Default config comes from textjenerator.config
|
|
28
|
+
"""
|
|
29
|
+
self.config = config
|
|
30
|
+
self.pipe = None
|
|
31
|
+
self.response = None
|
|
32
|
+
self.dtype = None
|
|
33
|
+
self.device = None
|
|
34
|
+
self.DTYPES_MAP = {
|
|
35
|
+
"bfloat16": torch.bfloat16,
|
|
36
|
+
"float16": torch.float16,
|
|
37
|
+
"float32": torch.float32,
|
|
38
|
+
}
|
|
39
|
+
self.detect_device_and_dtype()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def detect_device_and_dtype(self):
|
|
43
|
+
"""
|
|
44
|
+
If 'device' or 'dtype' in config are set to "detect", this method attempts
|
|
45
|
+
to choose the optimal settings based on hardware availability (e.g., CUDA).
|
|
46
|
+
This method modifies self.device and self.dtype based on hardware availability and configuration settings.
|
|
47
|
+
"""
|
|
48
|
+
if self.config["device"] == "detect":
|
|
49
|
+
self.set_device()
|
|
50
|
+
else:
|
|
51
|
+
self.device = self.config["device"]
|
|
52
|
+
|
|
53
|
+
self.set_dtype()
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def set_device(self):
|
|
57
|
+
"""
|
|
58
|
+
Sets the computation device based on CUDA availability.
|
|
59
|
+
|
|
60
|
+
Sets `self.device` to 'cuda' if available, otherwise defaults to 'cpu'.
|
|
61
|
+
"""
|
|
62
|
+
if torch.cuda.is_available():
|
|
63
|
+
self.device = "cuda"
|
|
64
|
+
else:
|
|
65
|
+
self.device = "cpu"
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def set_dtype(self):
|
|
69
|
+
"""
|
|
70
|
+
Sets the torch data type based on the device and configuration.
|
|
71
|
+
|
|
72
|
+
If config['dtype'] is "detect":
|
|
73
|
+
- Sets to torch.bfloat16 if device is 'cuda'.
|
|
74
|
+
- Sets to torch.float32 otherwise.
|
|
75
|
+
Otherwise, maps the string config to the actual torch.dtype object in self.DTYPES_MAP.
|
|
76
|
+
"""
|
|
77
|
+
if self.config["dtype"] == "detect":
|
|
78
|
+
if self.device == "cuda":
|
|
79
|
+
self.dtype = torch.bfloat16
|
|
80
|
+
self.config["dtype"] = "bfloat16"
|
|
81
|
+
else:
|
|
82
|
+
self.dtype = torch.float32
|
|
83
|
+
self.config["dtype"] = "float32"
|
|
84
|
+
return
|
|
85
|
+
|
|
86
|
+
self.dtype = self.DTYPES_MAP[self.config["dtype"]]
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def merge_config(self, config):
|
|
90
|
+
merged_config = merged_config = copy.deepcopy(self.config)
|
|
91
|
+
merged_config.update(config)
|
|
92
|
+
|
|
93
|
+
return merged_config
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@abstractmethod
|
|
97
|
+
def create_pipeline(self):
|
|
98
|
+
"""
|
|
99
|
+
Abstract method to initialize the model pipeline.
|
|
100
|
+
|
|
101
|
+
Subclasses must implement this to load the specific model and tokenizer/pipeline object, assigning it to self.pipe (e.g., a Hugging Face Pipeline object).
|
|
102
|
+
"""
|
|
103
|
+
pass
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@abstractmethod
|
|
107
|
+
def run_pipeline(self):
|
|
108
|
+
"""
|
|
109
|
+
Abstract method to execute the model pipeline.
|
|
110
|
+
|
|
111
|
+
Subclasses must implement this to execute the model using self.pipe and store the final generated text in self.response (str).
|
|
112
|
+
"""
|
|
113
|
+
pass
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def generate_text(self):
|
|
117
|
+
"""
|
|
118
|
+
Generates text by orchestrating the pipeline creation and execution. This is the primary public method for text generation.
|
|
119
|
+
|
|
120
|
+
Steps:
|
|
121
|
+
1. Creates the pipeline.
|
|
122
|
+
2. Runs the pipeline implementation.
|
|
123
|
+
|
|
124
|
+
Returns: str: The generated text, which is the value of self.response after the pipeline runs.
|
|
125
|
+
"""
|
|
126
|
+
self.create_pipeline()
|
|
127
|
+
self.run_pipeline()
|
|
128
|
+
return self.response
|
|
129
|
+
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from textjenerator.models import registry
|
|
2
|
+
from textjenerator.config import config
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
text_generator = registry.get_model_class(config)
|
|
6
|
+
|
|
7
|
+
# You can call create_pipeline() and run_pipeline() separately
|
|
8
|
+
# This keeps the model in memory making the response time faster.
|
|
9
|
+
# You can pass another config on each call the run_pipeline()
|
|
10
|
+
# e.g., to add new messages in a conversation, or change settings
|
|
11
|
+
# such as lowering temperature for a coding task.
|
|
12
|
+
text_generator.create_pipeline()
|
|
13
|
+
|
|
14
|
+
# Now you can call run_pipeline, which without arguments will
|
|
15
|
+
# generate based on the config passed when creating the object
|
|
16
|
+
response = text_generator.run_pipeline()
|
|
17
|
+
print(response)
|
|
18
|
+
|
|
19
|
+
# or pass another, doesn't have to be the whole thing:
|
|
20
|
+
new_config = {
|
|
21
|
+
"max_tokens_per_response": 512,
|
|
22
|
+
"messages": [
|
|
23
|
+
{"role": "system", "content": """You are Jenbot, an expert, helpful, and diligent assistant. You provide the user with accurate answers to their queries. You are polite, friendly, and a little sarcastic."""},
|
|
24
|
+
{"role": "user", "content": """Hi, who are you?"""},
|
|
25
|
+
{"role": "assistant", "content": """You must be wondering who I am. Well, let me introduce myself: I'm Jenbot, your friendly AI assistant. I'm here to help answer any questions you may have, provide information on a wide range of topics, and even offer a dash of sarcasm when the situation calls for it."""},
|
|
26
|
+
{"role": "user", "content": """What are your thoughts on the hard problem of consciousness?"""},
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
response = text_generator.run_pipeline(new_config)
|
|
30
|
+
print(response)
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
from llama_cpp import Llama
|
|
2
|
+
|
|
3
|
+
from textjenerator.models.registry import register_model
|
|
4
|
+
from textjenerator.core.text_generator import TextGenerator
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@register_model("llama-cpp")
|
|
8
|
+
class LlamaCPP(TextGenerator):
|
|
9
|
+
"""
|
|
10
|
+
Concrete implementation of TextGenerator using the llama-cpp-python library
|
|
11
|
+
for running GGUF-formatted LLMs (e.g., Llama 3, Mistral) efficiently on CPU.
|
|
12
|
+
|
|
13
|
+
It implements the core abstract methods:
|
|
14
|
+
1. create_pipeline: Initializes the llama_cpp.Llama object.
|
|
15
|
+
2. run_pipeline: Executes text generation via the create_chat_completion API.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(self, config):
|
|
19
|
+
"""
|
|
20
|
+
Initializes the text generator.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
config (dict): Configuration dictionary. Must include standard TextGenerator
|
|
24
|
+
keys plus model-specific keys.
|
|
25
|
+
llm (llama_cpp.Llama | None): The initialized Llama CPP model object.
|
|
26
|
+
"""
|
|
27
|
+
super().__init__(config)
|
|
28
|
+
self.llm = None
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def create_pipeline(self):
|
|
32
|
+
"""
|
|
33
|
+
Loads the pipeline using llama_cpp.Llama and applies configurations.
|
|
34
|
+
|
|
35
|
+
Note that llama-cpp-python handles its own hardware acceleration, so the device
|
|
36
|
+
config (cpu/CUDA) in the parent class is not used here.
|
|
37
|
+
|
|
38
|
+
Requires the following keys in self.config:
|
|
39
|
+
* model_path (str): Path to the GGUF model file.
|
|
40
|
+
* max_context_size (int): The context window size (n_ctx).
|
|
41
|
+
* number_of_threads (int): The number of threads to use (n_threads).
|
|
42
|
+
* verbose_warnings (bool): Enable/disable verbose warnings.
|
|
43
|
+
* n_gpu_layers (int): Number of layers to offload to the GPU (optional, defaults to 0).
|
|
44
|
+
|
|
45
|
+
Raises:
|
|
46
|
+
KeyError: If specific config keys (like model_path) are missing.
|
|
47
|
+
"""
|
|
48
|
+
self.llm = Llama(
|
|
49
|
+
model_path=self.config["model_path"],
|
|
50
|
+
n_ctx=self.config["max_context_size"],
|
|
51
|
+
n_threads=self.config["number_of_threads"],
|
|
52
|
+
verbose=self.config["verbose_warnings"],
|
|
53
|
+
n_gpu_layers=self.config["n_gpu_layers"]
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def run_pipeline(self, config=None):
|
|
58
|
+
"""
|
|
59
|
+
Executes the inference using the chat completion API.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
config (dict | None): A dictionary containing temporary overrides for
|
|
63
|
+
this specific inference call (e.g., the next message).
|
|
64
|
+
These settings are merged with and override the permanent
|
|
65
|
+
self.config for this run only. If None, self.config is used directly.
|
|
66
|
+
|
|
67
|
+
Requires the following keys in onfig:
|
|
68
|
+
* messages (list): The chat history in the expected Llama format.
|
|
69
|
+
* max_tokens_per_response (int): Max new tokens to generate.
|
|
70
|
+
* temperature (float): Sampling temperature.
|
|
71
|
+
* top_p (float): Top-p sampling value.
|
|
72
|
+
* top_k (int): Top-k sampling value.
|
|
73
|
+
|
|
74
|
+
The final generated text is stored in self.response and returned.
|
|
75
|
+
"""
|
|
76
|
+
if config:
|
|
77
|
+
config = self.merge_config(config)
|
|
78
|
+
else:
|
|
79
|
+
config = self.config
|
|
80
|
+
|
|
81
|
+
output = self.llm.create_chat_completion(
|
|
82
|
+
messages=config["messages"],
|
|
83
|
+
max_tokens=config["max_tokens_per_response"],
|
|
84
|
+
temperature=config["temperature"],
|
|
85
|
+
top_p=config["top_p"],
|
|
86
|
+
top_k=config["top_k"],
|
|
87
|
+
)
|
|
88
|
+
choice = output["choices"][0]["message"]
|
|
89
|
+
output_text = (choice.get("content") or "").strip()
|
|
90
|
+
if not output_text:
|
|
91
|
+
output_text = "[No response generated.]"
|
|
92
|
+
self.response = output_text
|
|
93
|
+
|
|
94
|
+
return self.response
|
|
95
|
+
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Implements a simple decorator-based model registry for dynamically managing
|
|
3
|
+
different model classes.
|
|
4
|
+
|
|
5
|
+
This system allows concrete subclasses to register themselves
|
|
6
|
+
using a specific string key, enabling the application to instantiate the
|
|
7
|
+
correct class based on a configuration setting without explicit imports.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
MODEL_REGISTRY = {}
|
|
11
|
+
|
|
12
|
+
def register_model(name):
|
|
13
|
+
"""
|
|
14
|
+
A decorator factory used to register a subclass.
|
|
15
|
+
|
|
16
|
+
The decorated class is stored in the global MODEL_REGISTRY dictionary
|
|
17
|
+
under the provided `name`.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
name (str): The string key used to reference the model class
|
|
21
|
+
in the configuration.
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
Callable: A decorator function that takes a class and registers it.
|
|
25
|
+
"""
|
|
26
|
+
def decorator(cls):
|
|
27
|
+
MODEL_REGISTRY[name] = cls
|
|
28
|
+
return cls
|
|
29
|
+
return decorator
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def get_model_class(config):
|
|
33
|
+
"""
|
|
34
|
+
Retrieves and instantiates the correct class based on the
|
|
35
|
+
configuration dictionary.
|
|
36
|
+
|
|
37
|
+
It looks up the class in MODEL_REGISTRY using the key found in
|
|
38
|
+
`config['model']`.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
config (dict): The configuration dictionary, which must contain a
|
|
42
|
+
'model' key corresponding to a registered model name.
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
BaseClass: An instantiated object of the registered generator class.
|
|
46
|
+
|
|
47
|
+
Raises:
|
|
48
|
+
KeyError: If the value of `config['model']` is not found in the
|
|
49
|
+
MODEL_REGISTRY.
|
|
50
|
+
"""
|
|
51
|
+
ModelClass = MODEL_REGISTRY[config["model"]]
|
|
52
|
+
model_object = ModelClass(config)
|
|
53
|
+
|
|
54
|
+
return model_object
|