nucore-ai 1.0.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.
- nucore_ai-1.0.0/LICENSE +21 -0
- nucore_ai-1.0.0/MANIFEST.in +1 -0
- nucore_ai-1.0.0/PKG-INFO +84 -0
- nucore_ai-1.0.0/README.md +69 -0
- nucore_ai-1.0.0/pyproject.toml +25 -0
- nucore_ai-1.0.0/requirements.txt +4 -0
- nucore_ai-1.0.0/setup.cfg +4 -0
- nucore_ai-1.0.0/src/assistant/assistant.py +372 -0
- nucore_ai-1.0.0/src/config/__init__.py +3 -0
- nucore_ai-1.0.0/src/config/config.py +68 -0
- nucore_ai-1.0.0/src/nucore/__init__.py +17 -0
- nucore_ai-1.0.0/src/nucore/cmd.py +61 -0
- nucore_ai-1.0.0/src/nucore/editor.py +183 -0
- nucore_ai-1.0.0/src/nucore/linkdef.py +39 -0
- nucore_ai-1.0.0/src/nucore/loader.py +323 -0
- nucore_ai-1.0.0/src/nucore/node.py +58 -0
- nucore_ai-1.0.0/src/nucore/nodedef.py +87 -0
- nucore_ai-1.0.0/src/nucore/nucore.py +532 -0
- nucore_ai-1.0.0/src/nucore/nucore_backend_api.py +329 -0
- nucore_ai-1.0.0/src/nucore/nucore_programs.py +73 -0
- nucore_ai-1.0.0/src/nucore/profile.py +40 -0
- nucore_ai-1.0.0/src/nucore/uom.py +935 -0
- nucore_ai-1.0.0/src/nucore_ai.egg-info/PKG-INFO +84 -0
- nucore_ai-1.0.0/src/nucore_ai.egg-info/SOURCES.txt +34 -0
- nucore_ai-1.0.0/src/nucore_ai.egg-info/dependency_links.txt +1 -0
- nucore_ai-1.0.0/src/nucore_ai.egg-info/requires.txt +4 -0
- nucore_ai-1.0.0/src/nucore_ai.egg-info/top_level.txt +5 -0
- nucore_ai-1.0.0/src/rag/__init__.py +9 -0
- nucore_ai-1.0.0/src/rag/device_rag_formatter.py +189 -0
- nucore_ai-1.0.0/src/rag/rag_data_struct.py +46 -0
- nucore_ai-1.0.0/src/rag/rag_formatter.py +28 -0
- nucore_ai-1.0.0/src/rag/rag_processor.py +302 -0
- nucore_ai-1.0.0/src/rag/reranker.py +43 -0
- nucore_ai-1.0.0/src/rag/static_info_rag_formatter.py +58 -0
- nucore_ai-1.0.0/src/rag/tools_rag_formatter.py +52 -0
- nucore_ai-1.0.0/src/server/start_servers.py +126 -0
nucore_ai-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 NuCoreAI
|
|
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 @@
|
|
|
1
|
+
include requirements.txt
|
nucore_ai-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: nucore-ai
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Lightweight library for command/control/optimization/automation of smart home devices using LLMs
|
|
5
|
+
Author-email: Michel Kohanim <michel@universal-devices.com>, "P.A. SCHEMBRI" <pa.schembri@advanced-stack.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/NuCoreAI/nucore-ai.git
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Dist: requests
|
|
11
|
+
Requires-Dist: transformers
|
|
12
|
+
Requires-Dist: sentence-transformers
|
|
13
|
+
Requires-Dist: chromadb
|
|
14
|
+
Dynamic: license-file
|
|
15
|
+
|
|
16
|
+
# NuCoreAI Platform!
|
|
17
|
+
|
|
18
|
+
## Goal
|
|
19
|
+
|
|
20
|
+
This library goal is to convert a user query written in natural language to commands, queries, and programs in any NuCore enabled platform (currently eisy).
|
|
21
|
+
|
|
22
|
+
## Quick start
|
|
23
|
+
|
|
24
|
+
Installation:
|
|
25
|
+
|
|
26
|
+
```shell
|
|
27
|
+
git clone https://github.com/NuCoreAI/nucore-ai.git
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## llama.cpp compile and build
|
|
31
|
+
1. Download llama.cpp
|
|
32
|
+
2. Go to the directory and do as per one of the options below:
|
|
33
|
+
|
|
34
|
+
### No GPU
|
|
35
|
+
```shell
|
|
36
|
+
cmake -B build.blis -DGGML_BLAS=on -DGGML_BLAS_VENDOR=FLAME
|
|
37
|
+
```
|
|
38
|
+
followed by
|
|
39
|
+
```shell
|
|
40
|
+
cmake --build build.blis --conifg release
|
|
41
|
+
```
|
|
42
|
+
This will install llama.cpp binaries in build.blis directory local to llama.cpp installation. The reason we are using build.blis directory is that you may want to experiment with the GPU version
|
|
43
|
+
|
|
44
|
+
### Nvidia GPU
|
|
45
|
+
On Ubuntu:
|
|
46
|
+
```shell
|
|
47
|
+
sudo ubuntu-drivers install
|
|
48
|
+
sudo apt install nvidia-utils-{latest version}
|
|
49
|
+
sudo apt install nvidia-cuda-toolkit
|
|
50
|
+
```
|
|
51
|
+
Now you are ready to build:
|
|
52
|
+
```shell
|
|
53
|
+
cmake -B build.cuda -DGGML_CUDA=on
|
|
54
|
+
```
|
|
55
|
+
followed by
|
|
56
|
+
```shell
|
|
57
|
+
cmake --build build.cuda --config release
|
|
58
|
+
```
|
|
59
|
+
If you have x running, you may want to have it release resources. First use nvidia-smi utility to see what's running and how much memory is being used by other things:
|
|
60
|
+
```shell
|
|
61
|
+
sudo nvidia-smi
|
|
62
|
+
```
|
|
63
|
+
if anything is running and using memory:
|
|
64
|
+
1. Make the prime display point to the integrated one (say intel)
|
|
65
|
+
```shell
|
|
66
|
+
sudo prime-select intel
|
|
67
|
+
```
|
|
68
|
+
2. Then, make it on demand
|
|
69
|
+
```shell
|
|
70
|
+
sudo prime-select on-demand
|
|
71
|
+
```
|
|
72
|
+
3. Make sure your system sees it:
|
|
73
|
+
```shell
|
|
74
|
+
nvidia-smi
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## The Model
|
|
78
|
+
You are going to be using a finetuned version of Qwen2.5-Coder-7B [here](https://huggingface.co/mkohanim/qwen2.5-coder-nucore-7b)
|
|
79
|
+
|
|
80
|
+
## Testing
|
|
81
|
+
For testing, you can either use a live eisy or use example profiles/nodes [here](https://github.com/NuCoreAI/ai-workflow)
|
|
82
|
+
|
|
83
|
+
## Documentation
|
|
84
|
+
The code is very well documented but we have not yet made and official documentation.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# NuCoreAI Platform!
|
|
2
|
+
|
|
3
|
+
## Goal
|
|
4
|
+
|
|
5
|
+
This library goal is to convert a user query written in natural language to commands, queries, and programs in any NuCore enabled platform (currently eisy).
|
|
6
|
+
|
|
7
|
+
## Quick start
|
|
8
|
+
|
|
9
|
+
Installation:
|
|
10
|
+
|
|
11
|
+
```shell
|
|
12
|
+
git clone https://github.com/NuCoreAI/nucore-ai.git
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## llama.cpp compile and build
|
|
16
|
+
1. Download llama.cpp
|
|
17
|
+
2. Go to the directory and do as per one of the options below:
|
|
18
|
+
|
|
19
|
+
### No GPU
|
|
20
|
+
```shell
|
|
21
|
+
cmake -B build.blis -DGGML_BLAS=on -DGGML_BLAS_VENDOR=FLAME
|
|
22
|
+
```
|
|
23
|
+
followed by
|
|
24
|
+
```shell
|
|
25
|
+
cmake --build build.blis --conifg release
|
|
26
|
+
```
|
|
27
|
+
This will install llama.cpp binaries in build.blis directory local to llama.cpp installation. The reason we are using build.blis directory is that you may want to experiment with the GPU version
|
|
28
|
+
|
|
29
|
+
### Nvidia GPU
|
|
30
|
+
On Ubuntu:
|
|
31
|
+
```shell
|
|
32
|
+
sudo ubuntu-drivers install
|
|
33
|
+
sudo apt install nvidia-utils-{latest version}
|
|
34
|
+
sudo apt install nvidia-cuda-toolkit
|
|
35
|
+
```
|
|
36
|
+
Now you are ready to build:
|
|
37
|
+
```shell
|
|
38
|
+
cmake -B build.cuda -DGGML_CUDA=on
|
|
39
|
+
```
|
|
40
|
+
followed by
|
|
41
|
+
```shell
|
|
42
|
+
cmake --build build.cuda --config release
|
|
43
|
+
```
|
|
44
|
+
If you have x running, you may want to have it release resources. First use nvidia-smi utility to see what's running and how much memory is being used by other things:
|
|
45
|
+
```shell
|
|
46
|
+
sudo nvidia-smi
|
|
47
|
+
```
|
|
48
|
+
if anything is running and using memory:
|
|
49
|
+
1. Make the prime display point to the integrated one (say intel)
|
|
50
|
+
```shell
|
|
51
|
+
sudo prime-select intel
|
|
52
|
+
```
|
|
53
|
+
2. Then, make it on demand
|
|
54
|
+
```shell
|
|
55
|
+
sudo prime-select on-demand
|
|
56
|
+
```
|
|
57
|
+
3. Make sure your system sees it:
|
|
58
|
+
```shell
|
|
59
|
+
nvidia-smi
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## The Model
|
|
63
|
+
You are going to be using a finetuned version of Qwen2.5-Coder-7B [here](https://huggingface.co/mkohanim/qwen2.5-coder-nucore-7b)
|
|
64
|
+
|
|
65
|
+
## Testing
|
|
66
|
+
For testing, you can either use a live eisy or use example profiles/nodes [here](https://github.com/NuCoreAI/ai-workflow)
|
|
67
|
+
|
|
68
|
+
## Documentation
|
|
69
|
+
The code is very well documented but we have not yet made and official documentation.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "nucore-ai"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Lightweight library for command/control/optimization/automation of smart home devices using LLMs"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
authors = [
|
|
12
|
+
{ name="Michel Kohanim", email="michel@universal-devices.com" },
|
|
13
|
+
{ name="P.A. SCHEMBRI", email="pa.schembri@advanced-stack.com" }
|
|
14
|
+
]
|
|
15
|
+
dependencies = [
|
|
16
|
+
"requests",
|
|
17
|
+
"transformers",
|
|
18
|
+
"sentence-transformers",
|
|
19
|
+
"chromadb",
|
|
20
|
+
]
|
|
21
|
+
urls = { "Homepage" = "https://github.com/NuCoreAI/nucore-ai.git" }
|
|
22
|
+
|
|
23
|
+
[tool.setuptools]
|
|
24
|
+
package-dir = {"" = "src"}
|
|
25
|
+
packages = ["assistant", "config", "rag", "nucore", "server"]
|
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import requests, os
|
|
3
|
+
import json
|
|
4
|
+
import httpx
|
|
5
|
+
import asyncio, argparse
|
|
6
|
+
|
|
7
|
+
from nucore import NuCore
|
|
8
|
+
from config import AIConfig
|
|
9
|
+
|
|
10
|
+
"""
|
|
11
|
+
Best option
|
|
12
|
+
build.cuda/bin/llama-server -m /home/michel/workspace/nucore/models/finetuned/qwen2.5-coder-dls-7b/qwen2.5-coder-dls-7b-Q4_K_M.gguf --jinja --host localhost -c 60000 --port 8013 -t 15 --n-gpu-layers 32 --batch-size 8192
|
|
13
|
+
"""
|
|
14
|
+
from importlib.resources import files
|
|
15
|
+
|
|
16
|
+
# Assuming this code is inside your_package/module.py
|
|
17
|
+
data_path = files('assistant').joinpath('nucore.system.prompt')
|
|
18
|
+
|
|
19
|
+
with data_path.open('r', encoding='utf-8') as f:
|
|
20
|
+
system_prompt = f.read().strip()
|
|
21
|
+
|
|
22
|
+
config = AIConfig()
|
|
23
|
+
|
|
24
|
+
class NuCoreAssistant:
|
|
25
|
+
def __init__(self, args, websocket=None):
|
|
26
|
+
self.websocket=websocket
|
|
27
|
+
self.sent_system_prompt=False
|
|
28
|
+
if not args:
|
|
29
|
+
raise ValueError("Arguments are required to initialize NuCoreAssistant")
|
|
30
|
+
self.nuCore = NuCore(
|
|
31
|
+
collection_path=args.collection_path if args.collection_path else os.path.join(os.path.expanduser("~"), ".nucore_db"),
|
|
32
|
+
collection_name="nucore.assistant",
|
|
33
|
+
backend_url=args.url,
|
|
34
|
+
backend_username=args.username,
|
|
35
|
+
backend_password=args.password
|
|
36
|
+
)
|
|
37
|
+
model_url = args.model_url+"/v1/chat/completion" if args.model_url else config.getModelURL()
|
|
38
|
+
if not model_url:
|
|
39
|
+
raise ValueError("Model URL is required to initialize NuCoreAssistant")
|
|
40
|
+
self.__model_url__ = model_url
|
|
41
|
+
self.__model_auth_token__ = args.model_auth_token if args.model_auth_token else None
|
|
42
|
+
print (self.__model_url__)
|
|
43
|
+
self.nuCore.load()
|
|
44
|
+
|
|
45
|
+
def set_remote_model_access_token(self, token: str):
|
|
46
|
+
"""
|
|
47
|
+
You are responsible for refreshing the access token
|
|
48
|
+
Set the remote model access token.
|
|
49
|
+
:param token: The access token to set.
|
|
50
|
+
"""
|
|
51
|
+
self.__model_auth_token__ = token
|
|
52
|
+
|
|
53
|
+
async def create_automation_routine(self,routines:list):
|
|
54
|
+
"""
|
|
55
|
+
Create automation routines in NuCore.
|
|
56
|
+
:param routines: A list of routines to create.
|
|
57
|
+
:return: The result of the routine creation.
|
|
58
|
+
**for now, just a stub **
|
|
59
|
+
"""
|
|
60
|
+
return self.nuCore.create_automation_routines(routines)
|
|
61
|
+
|
|
62
|
+
async def process_property_query(self, prop_query:list):
|
|
63
|
+
if not prop_query or len(prop_query) == 0:
|
|
64
|
+
return "No property query provided"
|
|
65
|
+
for property in prop_query:
|
|
66
|
+
# Process the property query
|
|
67
|
+
device_id = property.get('device_id')
|
|
68
|
+
if not device_id:
|
|
69
|
+
print(f"No device ID provided for property query: {property}")
|
|
70
|
+
continue
|
|
71
|
+
properties = await self.nuCore.get_properties(device_id)
|
|
72
|
+
if not properties:
|
|
73
|
+
print(f"No properties found for device {property['device_id']}")
|
|
74
|
+
continue
|
|
75
|
+
prop_id = property.get('property_id')
|
|
76
|
+
prop_name = property.get('property_name')
|
|
77
|
+
device_name = self.nuCore.get_device_name(device_id)
|
|
78
|
+
if not device_name:
|
|
79
|
+
device_name = device_id
|
|
80
|
+
if prop_id:
|
|
81
|
+
prop = properties.get(prop_id)
|
|
82
|
+
if prop:
|
|
83
|
+
text = f"\nNuCore: {prop_name if prop_name else prop_id} for {device_name} is: {prop.formatted if prop.formatted else prop.value}"
|
|
84
|
+
#await self.send_user_content_to_llm(text)
|
|
85
|
+
await self.send_response(text, True)
|
|
86
|
+
else:
|
|
87
|
+
print( f"Property {prop_id} not found for device {property['device_id']}")
|
|
88
|
+
else:
|
|
89
|
+
print(f"No property ID provided for device {property['device_id']}")
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
async def process_tool_call(self,full_response:str, begin_marker, end_marker):
|
|
93
|
+
if not full_response or not begin_marker or not end_marker:
|
|
94
|
+
return None
|
|
95
|
+
|
|
96
|
+
#we need an ordered command list to process. The order is important:
|
|
97
|
+
# first command must run first and second next etc.
|
|
98
|
+
parameters = [] #ordered set of commands
|
|
99
|
+
|
|
100
|
+
command_pattern = re.compile(rf'{begin_marker}(.*?){end_marker}', re.DOTALL)
|
|
101
|
+
matches = command_pattern.findall(full_response)
|
|
102
|
+
|
|
103
|
+
for match in matches:
|
|
104
|
+
try:
|
|
105
|
+
# Remove any comments that start with //, # or /* and end with */
|
|
106
|
+
match = re.sub(r'//.*?$|#.*?$|/\*.*?\*/', '', match, flags=re.MULTILINE)
|
|
107
|
+
# Remove any leading or trailing whitespace
|
|
108
|
+
match = match.strip()
|
|
109
|
+
if not match:
|
|
110
|
+
continue
|
|
111
|
+
# Parse the JSON block
|
|
112
|
+
parameter_json = json.loads(match.strip())
|
|
113
|
+
parameters.append(parameter_json)
|
|
114
|
+
except json.JSONDecodeError as e:
|
|
115
|
+
print(f"Error decoding JSON from nucore block: {e}")
|
|
116
|
+
return None
|
|
117
|
+
|
|
118
|
+
if len(parameters) > 0:
|
|
119
|
+
if begin_marker == "__BEGIN_NUCORE_COMMAND__":
|
|
120
|
+
return await self.nuCore.send_commands(parameters)
|
|
121
|
+
|
|
122
|
+
elif begin_marker == "__BEGIN_NUCORE_PROPERTY_QUERY__":
|
|
123
|
+
return await self.process_property_query(parameters)
|
|
124
|
+
|
|
125
|
+
return None
|
|
126
|
+
|
|
127
|
+
async def send_response(self, message, is_end=False):
|
|
128
|
+
if not message:
|
|
129
|
+
return
|
|
130
|
+
if self.websocket:
|
|
131
|
+
await self.websocket.send_json({
|
|
132
|
+
"sender": "bot",
|
|
133
|
+
"message": message,
|
|
134
|
+
"end": 'true' if is_end else 'false'
|
|
135
|
+
})
|
|
136
|
+
else:
|
|
137
|
+
print(message, end="", flush=True)
|
|
138
|
+
|
|
139
|
+
async def send_user_content_to_llm(self, user_content):
|
|
140
|
+
"""
|
|
141
|
+
Send user content to the LLM for processing.
|
|
142
|
+
:param user_content: The content provided by the user.
|
|
143
|
+
"""
|
|
144
|
+
if not user_content:
|
|
145
|
+
print("No user content provided, exiting ...")
|
|
146
|
+
return
|
|
147
|
+
user_message = {
|
|
148
|
+
"role": "user",
|
|
149
|
+
"content": f"{user_content.strip()}"
|
|
150
|
+
}
|
|
151
|
+
messages = [user_message]
|
|
152
|
+
payload={
|
|
153
|
+
"messages": messages,
|
|
154
|
+
"stream": False,
|
|
155
|
+
"temperature": 2.0,
|
|
156
|
+
"max_tokens": 60_000,
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
response = requests.post(self.__model_url__, json=payload, headers={
|
|
160
|
+
"Authorization": f"Bearer {self.__model_auth_token__}" if self.__model_auth_token__ else "",
|
|
161
|
+
})
|
|
162
|
+
response.raise_for_status()
|
|
163
|
+
await self.send_response(response.json()["choices"][0]["message"]["content"])
|
|
164
|
+
return None
|
|
165
|
+
|
|
166
|
+
async def process_customer_input(self, query:str, num_rag_results=5, rerank=True):
|
|
167
|
+
"""
|
|
168
|
+
Process the customer input by sending it to the AI model and handling the response.
|
|
169
|
+
:param query: The customer input to process.
|
|
170
|
+
:param num_rag_results: The number of RAG results to use for the actual query
|
|
171
|
+
:param rerank: Whether to rerank the results.
|
|
172
|
+
"""
|
|
173
|
+
|
|
174
|
+
if not query:
|
|
175
|
+
print("No query provided, exiting ...")
|
|
176
|
+
messages =[]
|
|
177
|
+
|
|
178
|
+
device_docs = ""
|
|
179
|
+
if not self.nuCore.load_devices(include_profiles=False):
|
|
180
|
+
raise ValueError("Failed to load devices from NuCore. Please check your configuration.")
|
|
181
|
+
|
|
182
|
+
# Load RAG documents
|
|
183
|
+
#if not self.nuCore.load_rag_docs(dump=False):
|
|
184
|
+
# raise ValueError("Failed to load RAG documents from NuCore. Please check your configuration.")
|
|
185
|
+
rag = self.nuCore.format_nodes()
|
|
186
|
+
if not rag:
|
|
187
|
+
raise ValueError(f"Warning: No RAG documents found for node {self.nuCore.url}. Skipping.")
|
|
188
|
+
|
|
189
|
+
rag_docs = rag["documents"]
|
|
190
|
+
if not rag_docs:
|
|
191
|
+
raise ValueError(f"Warning: No documents found in RAG for node {self.nuCore.url}. Skipping.")
|
|
192
|
+
|
|
193
|
+
for rag_doc in rag_docs:
|
|
194
|
+
device_docs += "\n" + rag_doc
|
|
195
|
+
|
|
196
|
+
sprompt = system_prompt.replace("{device_docs}", device_docs)
|
|
197
|
+
sprompt.strip()
|
|
198
|
+
with open(f"/tmp/ai.prompt", "w") as f:
|
|
199
|
+
f.write(sprompt)
|
|
200
|
+
|
|
201
|
+
system_message = {
|
|
202
|
+
"role": "system",
|
|
203
|
+
"content": sprompt
|
|
204
|
+
}
|
|
205
|
+
query= query.strip()
|
|
206
|
+
if not query:
|
|
207
|
+
await self.send_response("No query provided, exiting ...", True)
|
|
208
|
+
return None
|
|
209
|
+
if query.startswith("?"):
|
|
210
|
+
query = "\n"+query[1:].strip() # Remove leading '?' if presented
|
|
211
|
+
else:
|
|
212
|
+
# This is a code-only query, so we don't need to send the system prompt
|
|
213
|
+
query = f"**code-only** **no-explanation**\n{query}"
|
|
214
|
+
|
|
215
|
+
user_message = {
|
|
216
|
+
"role": "user",
|
|
217
|
+
"content": f"USER QUERY:{query}"
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
#first use rag for relevant documents
|
|
221
|
+
#rag_results = self.nuCore.query(query, num_rag_results, rerank)
|
|
222
|
+
#context = None
|
|
223
|
+
#if rag_results:
|
|
224
|
+
# context = "***Relevant documents***\n"
|
|
225
|
+
# for document in rag_results['documents']:
|
|
226
|
+
# context += f"---\n{document}"
|
|
227
|
+
#
|
|
228
|
+
# query = query.strip() if not context else f"{context.strip()}\n\n Customer Question: {query.strip()}"
|
|
229
|
+
|
|
230
|
+
# print (f"\n\n*********************Customer Query: {query}********************\n\n")
|
|
231
|
+
|
|
232
|
+
# if rag_results:
|
|
233
|
+
# print(f"\n\n*********************Top 5 Query Results:(Rerank = {rerank})********************\n\n")
|
|
234
|
+
# for i in range(len(rag_results['ids'])):
|
|
235
|
+
# print(f"{i+1}. {rag_results['ids'][i]} - {rag_results['distances'][i]} - {rag_results['relevance_scores'][i]}")
|
|
236
|
+
# print("\n\n***************************************************************\n\n")
|
|
237
|
+
|
|
238
|
+
#if not self.sent_system_prompt:
|
|
239
|
+
messages.append(system_message)
|
|
240
|
+
self.sent_system_prompt = True
|
|
241
|
+
|
|
242
|
+
messages.append(user_message)
|
|
243
|
+
# Step 1: Get tool call
|
|
244
|
+
payload={
|
|
245
|
+
"messages": messages,
|
|
246
|
+
"stream": True,
|
|
247
|
+
'cache_prompt':True,
|
|
248
|
+
"n_keep": -1,
|
|
249
|
+
"temperature": 0.0,
|
|
250
|
+
"max_tokens": 60_000,
|
|
251
|
+
}
|
|
252
|
+
full_response = ""
|
|
253
|
+
try:
|
|
254
|
+
with httpx.stream("POST", self.__model_url__, timeout=100, json=payload,headers={
|
|
255
|
+
"Authorization": f"Bearer {self.__model_auth_token__}" if self.__model_auth_token__ else "",
|
|
256
|
+
}) as response:
|
|
257
|
+
if response.status_code == 401 or response.status_code == 403:
|
|
258
|
+
print(f"Authorization token is invalid or expired. You need to refresh it.")
|
|
259
|
+
return None
|
|
260
|
+
elif response.status_code == 500:
|
|
261
|
+
print(f"Internal server error. Please try again later (most probably the authorization token is invalid or expired).")
|
|
262
|
+
return None
|
|
263
|
+
else:
|
|
264
|
+
for line in response.iter_lines():
|
|
265
|
+
if line.startswith("data: "):
|
|
266
|
+
token_data = line[len(b"data: "):]
|
|
267
|
+
try:
|
|
268
|
+
finish_reason = json.loads(token_data.strip())['choices'][0]['finish_reason']
|
|
269
|
+
if finish_reason == "stop":
|
|
270
|
+
break
|
|
271
|
+
token_data = json.loads(token_data.strip())['choices'][0]['delta'].get('content', '')
|
|
272
|
+
except json.JSONDecodeError:
|
|
273
|
+
continue
|
|
274
|
+
if token_data:
|
|
275
|
+
# Print the token data as it arrives
|
|
276
|
+
if isinstance(token_data, bytes):
|
|
277
|
+
token_data = token_data.decode("utf-8")
|
|
278
|
+
await self.send_response(token_data, False)
|
|
279
|
+
#full_response += token_data # Collect the token data
|
|
280
|
+
full_response += token_data
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
# now parse the full response and look for blocks between __NUCORE_COMMAND_BEGIN__ and __NUCORE_COMMAND_END__.
|
|
284
|
+
# convert the blocks to json and add to list
|
|
285
|
+
await self.process_tool_call(full_response, "__BEGIN_NUCORE_COMMAND__", "__END_NUCORE_COMMAND__")
|
|
286
|
+
await self.process_tool_call(full_response, "__BEGIN_NUCORE_PROPERTY_QUERY__", "__END_NUCORE_PROPERTY_QUERY__")
|
|
287
|
+
|
|
288
|
+
except Exception as e:
|
|
289
|
+
print(f"An error occurred while processing the customer input: {e}")
|
|
290
|
+
return None
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
async def main(args):
|
|
294
|
+
print("Welcome to NuCore AI Assistant!")
|
|
295
|
+
print("Type 'quit' to exit")
|
|
296
|
+
assistant = NuCoreAssistant(args, websocket=None) # Replace with actual websocket connection if needed
|
|
297
|
+
i=0
|
|
298
|
+
|
|
299
|
+
while True:
|
|
300
|
+
try:
|
|
301
|
+
user_input = input("\nWhat can I do for you? > " if i==0 else "\n> ").strip()
|
|
302
|
+
i+=1
|
|
303
|
+
|
|
304
|
+
if not user_input:
|
|
305
|
+
print("Please enter a valid request")
|
|
306
|
+
continue
|
|
307
|
+
|
|
308
|
+
if user_input.lower() == 'quit':
|
|
309
|
+
print("Goodbye!")
|
|
310
|
+
break
|
|
311
|
+
|
|
312
|
+
print(f"\n>>>>>>>>>>\n")
|
|
313
|
+
|
|
314
|
+
await assistant.process_customer_input(user_input, num_rag_results=3, rerank=False)
|
|
315
|
+
print ("\n\n<<<<<<<<<<\n")
|
|
316
|
+
|
|
317
|
+
except Exception as e:
|
|
318
|
+
print(f"An error occurred: {e}")
|
|
319
|
+
continue
|
|
320
|
+
|
|
321
|
+
if __name__ == "__main__":
|
|
322
|
+
parser = argparse.ArgumentParser(
|
|
323
|
+
description="Loader for NuCore Profile and Nodes XML files."
|
|
324
|
+
)
|
|
325
|
+
parser.add_argument(
|
|
326
|
+
"--url",
|
|
327
|
+
dest="url",
|
|
328
|
+
type=str,
|
|
329
|
+
required=False,
|
|
330
|
+
help="The URL to fetch nodes and profiles from the nucore platform",
|
|
331
|
+
)
|
|
332
|
+
parser.add_argument(
|
|
333
|
+
"--username",
|
|
334
|
+
dest="username",
|
|
335
|
+
type=str,
|
|
336
|
+
required=False,
|
|
337
|
+
help="The username to authenticate with the nucore platform",
|
|
338
|
+
)
|
|
339
|
+
parser.add_argument(
|
|
340
|
+
"--password",
|
|
341
|
+
dest="password",
|
|
342
|
+
type=str,
|
|
343
|
+
required=False,
|
|
344
|
+
help="The password to authenticate with the nucore platform",
|
|
345
|
+
)
|
|
346
|
+
parser.add_argument(
|
|
347
|
+
"--collection_path",
|
|
348
|
+
dest="collection_path",
|
|
349
|
+
type=str,
|
|
350
|
+
required=False,
|
|
351
|
+
help="The path to the embedding collection db. If not provided, defaults to ~/.nucore_db.",
|
|
352
|
+
)
|
|
353
|
+
parser.add_argument(
|
|
354
|
+
"--model_url",
|
|
355
|
+
dest="model_url",
|
|
356
|
+
type=str,
|
|
357
|
+
required=False,
|
|
358
|
+
help="The URL of the remote model. If provided, this should be a valid URL that responds to OpenAI's API requests.",
|
|
359
|
+
)
|
|
360
|
+
parser.add_argument(
|
|
361
|
+
"--model_auth_token",
|
|
362
|
+
dest="model_auth_token",
|
|
363
|
+
type=str,
|
|
364
|
+
required=False,
|
|
365
|
+
help="Optional authentication token for the remote model API (if required by the remote model) to be used in the Authorization header. You are responsible for refreshing the token if needed.",
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
args = parser.parse_args()
|
|
370
|
+
asyncio.run(main(args))
|
|
371
|
+
|
|
372
|
+
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#configuration file for defaults
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
DEFAULT_NUCORE_INSTALL_DIR = "workspace/nucore"
|
|
5
|
+
|
|
6
|
+
LLAMA_CPP_DIR = os.path.join(DEFAULT_NUCORE_INSTALL_DIR, "llama.cpp")
|
|
7
|
+
LLAMA_CPP_EXECUTABLE = os.path.join(os.path.expanduser("~"), LLAMA_CPP_DIR, "build.blis/bin/llama-server")
|
|
8
|
+
LLAMA_CPP_EXECUTABLE_WITH_GPU = os.path.join(os.path.expanduser("~"), LLAMA_CPP_DIR, "build.cuda/bin/llama-server")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class AIConfig:
|
|
12
|
+
def __init__(self, install_dir:str=None, models_path:str=None):
|
|
13
|
+
if not install_dir:
|
|
14
|
+
install_dir = os.path.join(os.path.expanduser('~'), DEFAULT_NUCORE_INSTALL_DIR)
|
|
15
|
+
self.__models_path__:str = models_path if models_path else os.path.join(install_dir, "models")
|
|
16
|
+
|
|
17
|
+
self.__ragdb_file__ = "ragdb"
|
|
18
|
+
|
|
19
|
+
self.__model_host__="localhost"
|
|
20
|
+
self.__model_port__=8013
|
|
21
|
+
self.__model_url__=f"http://{self.__model_host__}:{self.__model_port__}/v1/chat/completions"
|
|
22
|
+
self.__llm_model__ = "finetuned/qwen2.5-coder-dls-7b/qwen2.5-coder-nucore-7b-Q4_K_M.gguf"
|
|
23
|
+
self.__llm_model_params__ = "-c 60000 --temp 0.0 --repeat-penalty 1.1 --n-gpu-layers 100 --batch-size 8196"
|
|
24
|
+
self.__llm_model_server_args__ = f"-m {os.path.join(self.__models_path__,self.__llm_model__)} --host {self.__model_host__} --port {self.__model_port__} {self.__llm_model_params__}"
|
|
25
|
+
|
|
26
|
+
self.__reranker_host__="localhost"
|
|
27
|
+
self.__reranker_port__=8026
|
|
28
|
+
self.__reranker_url__=f"http://{self.__reranker_host__}:{self.__reranker_port__}/v1/rerank"
|
|
29
|
+
self.__reranker_model__ = "bge-reranker-v2-m3.gguf"
|
|
30
|
+
self.__reranker_model_params__ = "--reranking --temp 0.0 "
|
|
31
|
+
self.__reranker_model_server_args__ = f"-m {os.path.join(self.__models_path__,self.__reranker_model__)} --host {self.__reranker_host__} --port {self.__reranker_port__} {self.__reranker_model_params__}"
|
|
32
|
+
|
|
33
|
+
self.__embedding_host__="localhost"
|
|
34
|
+
self.__embedding_port__=8052
|
|
35
|
+
self.__embedding_url__=f"http://{self.__embedding_host__}:{self.__embedding_port__}/v1/embeddings"
|
|
36
|
+
self.__embedding_model__ = "Qwen3-Embedding-0.6B-f16.gguf"
|
|
37
|
+
self.__embedding_model_params__ = "--embeddings --pooling mean -ub 2048"
|
|
38
|
+
self.__embedding_model_server_args__ = f"-m {os.path.join(self.__models_path__,self.__embedding_model__)} --host {self.__embedding_host__} --port {self.__embedding_port__} {self.__embedding_model_params__}"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def getLLMModel(self, model:str=None):
|
|
42
|
+
if not model:
|
|
43
|
+
model = self.__llm_model__
|
|
44
|
+
|
|
45
|
+
return os.path.join(self.__models_path__, model)
|
|
46
|
+
|
|
47
|
+
def getRerankerModel(self, model:str=None):
|
|
48
|
+
if not model:
|
|
49
|
+
model = self.__llm_reranker_model__
|
|
50
|
+
|
|
51
|
+
return os.path.join(self.__models_path__, model)
|
|
52
|
+
|
|
53
|
+
def getEmbeddingModel(self, model:str=None):
|
|
54
|
+
if not model:
|
|
55
|
+
model = self.__llm_reranker_model__
|
|
56
|
+
|
|
57
|
+
return os.path.join(self.__models_path__, model)
|
|
58
|
+
|
|
59
|
+
def getModelURL(self):
|
|
60
|
+
return self.__model_url__
|
|
61
|
+
|
|
62
|
+
def getRerankerURL(self):
|
|
63
|
+
return self.__reranker_url__
|
|
64
|
+
|
|
65
|
+
def getEmbeddingURL(self):
|
|
66
|
+
return self.__embedding_url__
|
|
67
|
+
|
|
68
|
+
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from .nucore_backend_api import NuCoreBackendAPI
|
|
2
|
+
from .nucore_programs import NuCorePrograms
|
|
3
|
+
from .cmd import Command, CommandParameter
|
|
4
|
+
from .editor import Editor, EditorMinMaxRange, EditorSubsetRange
|
|
5
|
+
from .linkdef import LinkDef, LinkParameter
|
|
6
|
+
from .node import Node, TypeInfo
|
|
7
|
+
from .nodedef import NodeDef, NodeProperty, NodeCommands, NodeLinks
|
|
8
|
+
from .profile import Profile, Family, Instance
|
|
9
|
+
from .uom import UOMEntry, get_uom_by_id
|
|
10
|
+
from .linkdef import LinkDef
|
|
11
|
+
from .node import Node
|
|
12
|
+
from .nodedef import NodeDef, NodeProperty, NodeCommands, NodeLinks, Property
|
|
13
|
+
from .profile import Profile, Family, Instance
|
|
14
|
+
from .uom import UOMEntry, get_uom_by_id
|
|
15
|
+
from .nucore import NuCore, NuCoreError
|
|
16
|
+
|
|
17
|
+
__all__ = ["NuCore", "EditorMinMaxRange", "TypeInfo", "LinkParameter", "Property", "EditorSubsetRange", "NuCoreError", "Node", "NuCoreBackendAPI", "NuCorePrograms", "Command", "CommandParameter", "Editor", "LinkDef", "NodeDef", "NodeProperty", "NodeCommands", "NodeLinks", "Profile", "Family", "Instance", "UOMEntry", get_uom_by_id]
|