unisi 0.4.4__py3-none-any.whl → 0.4.5__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 +1 -1
- unisi/llmrag.py +116 -44
- {unisi-0.4.4.dist-info → unisi-0.4.5.dist-info}/METADATA +2 -1
- {unisi-0.4.4.dist-info → unisi-0.4.5.dist-info}/RECORD +7 -7
- {unisi-0.4.4.dist-info → unisi-0.4.5.dist-info}/WHEEL +0 -0
- {unisi-0.4.4.dist-info → unisi-0.4.5.dist-info}/entry_points.txt +0 -0
- {unisi-0.4.4.dist-info → unisi-0.4.5.dist-info}/licenses/LICENSE +0 -0
unisi/__init__.py
CHANGED
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
|
-
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: unisi
|
3
|
-
Version: 0.4.
|
3
|
+
Version: 0.4.5
|
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,8 +1,8 @@
|
|
1
|
-
unisi-0.4.
|
2
|
-
unisi-0.4.
|
3
|
-
unisi-0.4.
|
4
|
-
unisi-0.4.
|
5
|
-
unisi/__init__.py,sha256=
|
1
|
+
unisi-0.4.5.dist-info/METADATA,sha256=F9i5Z3CMamg7aIz3ozUXFaLyu9HtxwXKMuE1JT0h44M,27294
|
2
|
+
unisi-0.4.5.dist-info/WHEEL,sha256=9P2ygRxDrTJz3gsagc0Z96ukrxjr-LFBGOgv3AuKlCA,90
|
3
|
+
unisi-0.4.5.dist-info/entry_points.txt,sha256=6OYgBcLyFCUgeqLgnvMyOJxPCWzgy7se4rLPKtNonMs,34
|
4
|
+
unisi-0.4.5.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
5
|
+
unisi/__init__.py,sha256=IDN0JIutBidYTSYd6mM8ST4YU7WufpOFt_8OqHoPHyg,305
|
6
6
|
unisi/autotest.py,sha256=M6T8q1Yutqr1A1qUBBbdI3LpTlS7ejiGtfdZ9Yvyfqw,8942
|
7
7
|
unisi/common.py,sha256=QHSS-pQDtLZxeZls0eDl8-EmYJZuaRmYO1M9izCly-Y,5791
|
8
8
|
unisi/containers.py,sha256=yl5CZrTK3HmNJJxpO7SmzIIsBMehwfCb_otaWG7GriM,7416
|
@@ -14,7 +14,7 @@ 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
|
@@ -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.5.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|