unisi 0.4.4__py3-none-any.whl → 0.4.6__py3-none-any.whl
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.
- unisi/__init__.py +2 -2
- unisi/autotest.py +11 -1
- unisi/llmrag.py +116 -44
- unisi/server.py +6 -0
- unisi/units.py +9 -3
- unisi/users.py +3 -3
- {unisi-0.4.4.dist-info → unisi-0.4.6.dist-info}/METADATA +2 -1
- {unisi-0.4.4.dist-info → unisi-0.4.6.dist-info}/RECORD +11 -11
- {unisi-0.4.4.dist-info → unisi-0.4.6.dist-info}/WHEEL +0 -0
- {unisi-0.4.4.dist-info → unisi-0.4.6.dist-info}/entry_points.txt +0 -0
- {unisi-0.4.4.dist-info → unisi-0.4.6.dist-info}/licenses/LICENSE +0 -0
unisi/__init__.py
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
from .utils import *
|
2
|
-
from .llmrag import Q
|
2
|
+
from .llmrag import Q, Qx
|
3
3
|
from .units import *
|
4
4
|
from .users import User
|
5
|
-
from .server import start, handle, context_user, context_screen
|
5
|
+
from .server import start, handle, test, context_user, context_screen
|
6
6
|
from .tables import *
|
7
7
|
from .containers import *
|
8
8
|
from .proxy import *
|
unisi/autotest.py
CHANGED
@@ -181,7 +181,17 @@ def check_module(module):
|
|
181
181
|
errors.insert(0, f"\nErrors in screen {screen.name}, file name {module.__file__}:")
|
182
182
|
return errors
|
183
183
|
|
184
|
-
def run_tests(user):
|
184
|
+
def run_tests(user):
|
185
|
+
if config.autotest:
|
186
|
+
for test_fn in Unishare.test_list:
|
187
|
+
try:
|
188
|
+
if asyncio.iscoroutinefunction(test_fn):
|
189
|
+
asyncio.run(test_fn())
|
190
|
+
else:
|
191
|
+
test_fn()
|
192
|
+
except Exception as e:
|
193
|
+
user.log(f'Test function {test_fn.__name__} failed with exception: {e}')
|
194
|
+
|
185
195
|
errors = []
|
186
196
|
for module in user.screens:
|
187
197
|
module_errors = check_module(module)
|
unisi/llmrag.py
CHANGED
@@ -3,6 +3,7 @@ from .common import Unishare
|
|
3
3
|
from langchain_groq import ChatGroq
|
4
4
|
from langchain_openai import ChatOpenAI
|
5
5
|
from langchain_mistralai import ChatMistralAI
|
6
|
+
from langchain_xai import ChatXAI
|
6
7
|
from langchain_google_genai import (
|
7
8
|
ChatGoogleGenerativeAI,
|
8
9
|
HarmBlockThreshold,
|
@@ -10,7 +11,7 @@ from langchain_google_genai import (
|
|
10
11
|
)
|
11
12
|
from datetime import datetime
|
12
13
|
import os, inspect, re, json
|
13
|
-
from typing import get_origin, get_args
|
14
|
+
from typing import get_origin, get_args, Any, Union
|
14
15
|
|
15
16
|
class QueryCache:
|
16
17
|
ITEM_SEPARATOR = "§¶†‡◊•→±"
|
@@ -102,24 +103,88 @@ def is_type(variable, expected_type):
|
|
102
103
|
"""
|
103
104
|
Check if the variable matches the expected type hint.
|
104
105
|
"""
|
105
|
-
|
106
|
-
if
|
107
|
-
|
106
|
+
# Handle explicit mapping of expected keys to types: {'name': str, 'age': int}
|
107
|
+
if isinstance(expected_type, dict):
|
108
|
+
if not isinstance(variable, dict):
|
109
|
+
return False
|
110
|
+
for key_pattern, sub_type in expected_type.items():
|
111
|
+
# direct key match
|
112
|
+
if key_pattern in variable:
|
113
|
+
if not is_type(variable[key_pattern], sub_type):
|
114
|
+
return False
|
115
|
+
continue
|
116
|
+
return True
|
117
|
+
|
118
|
+
# Handle typing hints (e.g., List[int], Dict[str,int], Union[..., ...], Optional[T], Tuple[T,...])
|
119
|
+
origin = get_origin(expected_type)
|
108
120
|
args = get_args(expected_type)
|
109
|
-
|
110
|
-
#
|
111
|
-
if
|
121
|
+
|
122
|
+
# No typing origin: expected_type may be a bare type or typing.Any
|
123
|
+
if origin is None:
|
124
|
+
# typing.Any
|
125
|
+
if expected_type is Any:
|
126
|
+
return True
|
127
|
+
# None as expected type
|
128
|
+
if expected_type is None:
|
129
|
+
return variable is None
|
130
|
+
# expected_type could be a builtin type or a tuple of types
|
131
|
+
if isinstance(expected_type, type) or isinstance(expected_type, tuple):
|
132
|
+
return isinstance(variable, expected_type)
|
133
|
+
# fallback: try isinstance; for other unexpected cases, be permissive
|
134
|
+
try:
|
135
|
+
return isinstance(variable, expected_type)
|
136
|
+
except Exception:
|
137
|
+
return False
|
138
|
+
|
139
|
+
# Handle Union / Optional
|
140
|
+
if origin is Union:
|
141
|
+
return any(is_type(variable, arg) for arg in args)
|
142
|
+
|
143
|
+
# Handle List[...] and Set[...]
|
144
|
+
if origin in (list, set):
|
145
|
+
expected_container = list if origin is list else set
|
146
|
+
if not isinstance(variable, expected_container):
|
147
|
+
return False
|
148
|
+
if not args:
|
149
|
+
return True
|
150
|
+
return all(is_type(item, args[0]) for item in variable)
|
151
|
+
|
152
|
+
# Handle Tuple[...] or tuple
|
153
|
+
if origin is tuple:
|
154
|
+
if not isinstance(variable, tuple):
|
155
|
+
return False
|
156
|
+
if not args:
|
157
|
+
return True
|
158
|
+
# Homogeneous tuple: Tuple[T, ...]
|
159
|
+
if len(args) == 2 and args[1] is Ellipsis:
|
160
|
+
return all(is_type(item, args[0]) for item in variable)
|
161
|
+
# Fixed-length tuple: Tuple[T1, T2, ...]
|
162
|
+
if len(variable) != len(args):
|
163
|
+
return False
|
164
|
+
return all(is_type(item, typ) for item, typ in zip(variable, args))
|
165
|
+
|
166
|
+
# Handle Dict[K, V]
|
167
|
+
if origin is dict:
|
168
|
+
if not isinstance(variable, dict):
|
169
|
+
return False
|
170
|
+
if not args:
|
171
|
+
return True
|
172
|
+
key_type, val_type = args
|
173
|
+
return all(is_type(k, key_type) and is_type(v, val_type) for k, v in variable.items())
|
174
|
+
|
175
|
+
# Handle Literal[...] (available in typing)
|
176
|
+
try:
|
177
|
+
from typing import Literal
|
178
|
+
if origin is Literal:
|
179
|
+
return any(variable == lit for lit in args)
|
180
|
+
except Exception:
|
181
|
+
pass
|
182
|
+
|
183
|
+
# Fallback: check using isinstance against the origin if possible
|
184
|
+
try:
|
185
|
+
return isinstance(variable, origin)
|
186
|
+
except Exception:
|
112
187
|
return False
|
113
|
-
|
114
|
-
if not args:
|
115
|
-
return True
|
116
|
-
|
117
|
-
if origin is list:
|
118
|
-
return all(isinstance(item, args[0]) for item in variable)
|
119
|
-
elif origin is dict:
|
120
|
-
return all(isinstance(k, args[0]) and isinstance(v, args[1]) for k, v in variable.items())
|
121
|
-
|
122
|
-
return False
|
123
188
|
|
124
189
|
def remove_comments(json_str):
|
125
190
|
# Regular expression to remove single-line comments (// ...)
|
@@ -128,17 +193,20 @@ def remove_comments(json_str):
|
|
128
193
|
json_str = re.sub(r'/\*.*?\*/', '', json_str, flags=re.DOTALL)
|
129
194
|
return json_str
|
130
195
|
|
131
|
-
def Q(str_prompt, type_value = str, blank = True, **format_model):
|
132
|
-
"""returns LLM async call for a question
|
196
|
+
def Q(str_prompt, type_value = str, blank = True, extend = True, format = True, **format_model):
|
197
|
+
"""returns LLM async call for a question, `extend = True` adds system prompt,
|
198
|
+
'identity' in format_model can be used to set the assistant identity"""
|
133
199
|
llm = Unishare.llm_model
|
134
|
-
if '{' in str_prompt:
|
200
|
+
if format and '{' in str_prompt:
|
135
201
|
caller_frame = inspect.currentframe().f_back
|
136
202
|
format_model = caller_frame.f_locals | format_model if format_model else caller_frame.f_locals
|
137
203
|
str_prompt = str_prompt.format(**format_model)
|
138
|
-
if
|
139
|
-
|
140
|
-
|
141
|
-
|
204
|
+
if extend:
|
205
|
+
if type_value is not None:
|
206
|
+
jtype = jstype(type_value)
|
207
|
+
format = " dd/mm/yyyy string" if type_value == 'date' else f'a JSON {jtype}' if jtype != 'string' else jtype
|
208
|
+
str_prompt = f" Output STRONGLY in format {format}. DO NOT OUTPUT ANY COMMENTARY." + str_prompt
|
209
|
+
str_prompt = format_model.get('identity', 'You are an intelligent and extremely smart assistant.') + str_prompt
|
142
210
|
async def f():
|
143
211
|
if Unishare.llm_cache:
|
144
212
|
if content := Unishare.llm_cache.get(str_prompt):
|
@@ -158,27 +226,16 @@ def Q(str_prompt, type_value = str, blank = True, **format_model):
|
|
158
226
|
parsed = json.loads(clean_js)
|
159
227
|
except json.JSONDecodeError as e:
|
160
228
|
raise ValueError(f'Invalid JSON: {js}, \n Query: {str_prompt}')
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
for k2, v2 in parsed.items():
|
165
|
-
if re.fullmatch(k, k2, re.IGNORECASE) is not None:
|
166
|
-
parsed[k] = parsed.pop(k2)
|
167
|
-
break
|
168
|
-
else:
|
169
|
-
if blank:
|
170
|
-
parsed[k] = None
|
171
|
-
continue
|
172
|
-
raise KeyError(f'Key {k} not found in {parsed}')
|
173
|
-
|
174
|
-
if not is_type(parsed[k], v):
|
175
|
-
raise TypeError(f'Invalid type for {k}: {type(parsed[k])} != {v}')
|
176
|
-
else:
|
177
|
-
if not is_type(parsed, type_value):
|
178
|
-
raise TypeError(f'Invalid type: {type(parsed)} != {type_value}')
|
229
|
+
|
230
|
+
if not is_type(parsed, type_value):
|
231
|
+
raise TypeError(f'Invalid type: {type(parsed)} != {type_value}')
|
179
232
|
return parsed
|
180
233
|
return f()
|
181
234
|
|
235
|
+
def Qx(str_prompt, type_value = str):
|
236
|
+
"""returns LLM async call for a question, without formatting or extending the prompt"""
|
237
|
+
return Q(str_prompt, type_value, format = False, extend = False)
|
238
|
+
|
182
239
|
def setup_llmrag():
|
183
240
|
import config #the module is loaded before config analysis
|
184
241
|
temperature = getattr(config, 'temperature', 0.0)
|
@@ -197,6 +254,11 @@ def setup_llmrag():
|
|
197
254
|
return
|
198
255
|
|
199
256
|
type = type.lower()
|
257
|
+
model_kwargs={}
|
258
|
+
reasoning = getattr(config, 'reasoning', None)
|
259
|
+
if reasoning:
|
260
|
+
model_kwargs['reasoning'] = {"effort": reasoning, 'enabled': True}
|
261
|
+
|
200
262
|
match type:
|
201
263
|
case 'host':
|
202
264
|
api_key_from_config = os.environ.get(api_key_config) if api_key_config else None
|
@@ -205,10 +267,21 @@ def setup_llmrag():
|
|
205
267
|
api_key = api_key,
|
206
268
|
temperature = temperature,
|
207
269
|
openai_api_base = address,
|
270
|
+
model_kwargs=model_kwargs,
|
208
271
|
model = model
|
209
272
|
)
|
210
273
|
case 'openai':
|
211
|
-
|
274
|
+
|
275
|
+
Unishare.llm_model = ChatOpenAI(temperature = temperature, model_kwargs=model_kwargs)
|
276
|
+
|
277
|
+
case 'xai':
|
278
|
+
Unishare.llm_model = ChatXAI(
|
279
|
+
model = model,
|
280
|
+
temperature = temperature,
|
281
|
+
max_tokens = None,
|
282
|
+
timeout = None,
|
283
|
+
max_retries = 2,
|
284
|
+
)
|
212
285
|
|
213
286
|
case 'groq':
|
214
287
|
Unishare.llm_model = ChatGroq(
|
@@ -251,4 +324,3 @@ async def get_property(name, context = '', type = str, options = None):
|
|
251
324
|
Unishare.message_logger(e)
|
252
325
|
return None
|
253
326
|
return value
|
254
|
-
|
unisi/server.py
CHANGED
@@ -85,6 +85,12 @@ def handle(unit, event):
|
|
85
85
|
|
86
86
|
Unishare.handle = handle
|
87
87
|
|
88
|
+
Unishare.test_list = []
|
89
|
+
|
90
|
+
def test(fn):
|
91
|
+
Unishare.test_list.append(fn)
|
92
|
+
return fn
|
93
|
+
|
88
94
|
async def post_handler(request):
|
89
95
|
reader = await request.multipart()
|
90
96
|
field = await reader.next()
|
unisi/units.py
CHANGED
@@ -49,7 +49,10 @@ class ChangedProxy:
|
|
49
49
|
self._unit._mark_changed ()
|
50
50
|
|
51
51
|
def __iter__(self):
|
52
|
-
|
52
|
+
for item in self._obj:
|
53
|
+
if not callable(item) and not isinstance(item, atomics):
|
54
|
+
item = ChangedProxy(item, self._unit)
|
55
|
+
yield item
|
53
56
|
|
54
57
|
def __len__(self):
|
55
58
|
try:
|
@@ -69,8 +72,7 @@ class ChangedProxy:
|
|
69
72
|
|
70
73
|
def __getstate__(self):
|
71
74
|
return self._obj
|
72
|
-
|
73
|
-
atomics = (int, float, complex, bool, str, bytes, ChangedProxy, type(None))
|
75
|
+
|
74
76
|
|
75
77
|
class Unit:
|
76
78
|
action_list = set(['complete', 'update', 'changed','delete','append', 'modify'])
|
@@ -108,6 +110,8 @@ class Unit:
|
|
108
110
|
#it is correct condition order
|
109
111
|
if name[0] != "_" and self._mark_changed:
|
110
112
|
self._mark_changed(name, value)
|
113
|
+
if not callable(value) and not isinstance(value, atomics):
|
114
|
+
value = ChangedProxy(value, self)
|
111
115
|
super().__setattr__(name, value)
|
112
116
|
|
113
117
|
def mutate(self, obj):
|
@@ -169,6 +173,8 @@ class Unit:
|
|
169
173
|
|
170
174
|
def __repr__(self):
|
171
175
|
return f'{type(self).__name__}({self.name})'
|
176
|
+
|
177
|
+
atomics = (int, float, complex, bool, str, bytes, ChangedProxy, type(None), Unit)
|
172
178
|
|
173
179
|
Line = Unit("__Line__", type = 'line')
|
174
180
|
|
unisi/users.py
CHANGED
@@ -96,10 +96,10 @@ class User:
|
|
96
96
|
setattr(screen, var, getattr(module, var, val))
|
97
97
|
if not isinstance(screen.blocks, list):
|
98
98
|
screen.blocks = [screen.blocks]
|
99
|
-
|
99
|
+
|
100
|
+
if User.toolbar and User.toolbar[0] not in screen.toolbar:
|
100
101
|
screen.toolbar += User.toolbar
|
101
|
-
|
102
|
-
screen.toolbar = User.toolbar
|
102
|
+
|
103
103
|
if User.count > 0:
|
104
104
|
screen.set_reactivity(self)
|
105
105
|
module.screen = screen#ChangedProxy(screen, screen)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: unisi
|
3
|
-
Version: 0.4.
|
3
|
+
Version: 0.4.6
|
4
4
|
Summary: Unified System Interface, GUI and Remote API
|
5
5
|
Author-Email: UNISI Tech <g.dernovoy@gmail.com>
|
6
6
|
License: Apache-2.0
|
@@ -21,6 +21,7 @@ Requires-Dist: langchain-community
|
|
21
21
|
Requires-Dist: langchain-openai
|
22
22
|
Requires-Dist: langchain-google-genai
|
23
23
|
Requires-Dist: langchain_mistralai
|
24
|
+
Requires-Dist: langchain-xai
|
24
25
|
Requires-Dist: word2number
|
25
26
|
Description-Content-Type: text/markdown
|
26
27
|
|
@@ -1,9 +1,9 @@
|
|
1
|
-
unisi-0.4.
|
2
|
-
unisi-0.4.
|
3
|
-
unisi-0.4.
|
4
|
-
unisi-0.4.
|
5
|
-
unisi/__init__.py,sha256=
|
6
|
-
unisi/autotest.py,sha256=
|
1
|
+
unisi-0.4.6.dist-info/METADATA,sha256=y5IRgwr-ybfc7Aokz81UFXfCXFLPNOhPOaRB6RxcgnA,27294
|
2
|
+
unisi-0.4.6.dist-info/WHEEL,sha256=9P2ygRxDrTJz3gsagc0Z96ukrxjr-LFBGOgv3AuKlCA,90
|
3
|
+
unisi-0.4.6.dist-info/entry_points.txt,sha256=6OYgBcLyFCUgeqLgnvMyOJxPCWzgy7se4rLPKtNonMs,34
|
4
|
+
unisi-0.4.6.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
5
|
+
unisi/__init__.py,sha256=XGnpUm_JhO5D41VSq_uEL-6hdtvwT8FZJ-pOVqApf9g,311
|
6
|
+
unisi/autotest.py,sha256=lQ22S9n01SWFc2dzpA3qgDriGLiz-hQRBrMc2UkZaFE,9324
|
7
7
|
unisi/common.py,sha256=QHSS-pQDtLZxeZls0eDl8-EmYJZuaRmYO1M9izCly-Y,5791
|
8
8
|
unisi/containers.py,sha256=yl5CZrTK3HmNJJxpO7SmzIIsBMehwfCb_otaWG7GriM,7416
|
9
9
|
unisi/dbunits.py,sha256=g1h9pjoSwJrJ0Hmph-xv5jE1yfrxJeXGfuSnyVTg3j8,7438
|
@@ -14,14 +14,14 @@ unisi/jsoncomparison/config.py,sha256=LbdLJE1KIebFq_tX7zcERhPvopKhnzcTqMCnS3jN12
|
|
14
14
|
unisi/jsoncomparison/errors.py,sha256=wqphE1Xn7K6n16uvUhDC45m2BxbsMUhIF2olPbhqf4o,1192
|
15
15
|
unisi/jsoncomparison/ignore.py,sha256=xfF0a_BBEyGdZBoq-ovpCpawgcX8SRwwp7IrGnu1c2w,2634
|
16
16
|
unisi/kdb.py,sha256=K-Lqc3e9hLTwO0i1ilTC6qrwZp90tXjLm7HFb_lM1Os,13621
|
17
|
-
unisi/llmrag.py,sha256=
|
17
|
+
unisi/llmrag.py,sha256=HCEP8apy0FtIbLm0koG8BxCxjXS12Xqlo-9fZkGbsDs,12576
|
18
18
|
unisi/multimon.py,sha256=YKwCuvMsMfdgOGkJoqiqh_9wywXMeo9bUhHmbAIUeSE,4060
|
19
19
|
unisi/proxy.py,sha256=QMHSSFJtmVZIexIMAsuFNlF5JpnYNG90rkTM3PYJhY4,7750
|
20
20
|
unisi/reloader.py,sha256=t7z0NgaeJX52044ue_LxITa99WMuE5Jra9qkMEeGhTg,6941
|
21
|
-
unisi/server.py,sha256=
|
21
|
+
unisi/server.py,sha256=BU8ERe0ewbJtuZeEGLqU30UMDFSENzO9xEh4BsQSejM,6759
|
22
22
|
unisi/tables.py,sha256=1ZjR_eJ_vf0h26kdv2PjLV3t2cPMoqCV17MQjfilTX0,13810
|
23
|
-
unisi/units.py,sha256=
|
24
|
-
unisi/users.py,sha256=
|
23
|
+
unisi/units.py,sha256=5MVNJLFptVef1jKcDeDeKEPPti_t5Q5TrLeSt5xaaNk,11973
|
24
|
+
unisi/users.py,sha256=yq6rJzK62-NGcvwGIMgeRgBIGV0aDdngIwYgZxwnUII,17085
|
25
25
|
unisi/utils.py,sha256=WD3O0hZzVQmcqQK6Ivv35kGmx4FC3enV1RtiZC_EndQ,2721
|
26
26
|
unisi/voicecom.py,sha256=QzS1gIrBeGLO5dEwiu7KIEdJIIVbPBZFGb5nY632Ws8,16707
|
27
27
|
unisi/web/css/262.46daf72a.css,sha256=esQA388EJR7xe_L5NwO30wvjDEuseTf9pqiacAjyAX8,3508
|
@@ -55,4 +55,4 @@ unisi/web/js/sigma.ce21336a.js,sha256=ngST-065XWOdnR_Xn7U6oGNHTL8fyiOEI9V8-BWRvl
|
|
55
55
|
unisi/web/js/sigma.ce21336a.js.gz,sha256=zv6oToZZFCfmrZ4G4fw0sOncVe8-dyYNWh2v5QLKZp4,51965
|
56
56
|
unisi/web/js/vendor.6a64dcc5.js,sha256=OSNK2nadU2DnSOEYQQcAmelybITOFZXMxnRyaDoT3yU,747104
|
57
57
|
unisi/web/js/vendor.6a64dcc5.js.gz,sha256=nmtqRzQRWaToxgHxI9hfJd3UrUCg2-fd-0Fjc4H4wu8,245827
|
58
|
-
unisi-0.4.
|
58
|
+
unisi-0.4.6.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|