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 CHANGED
@@ -1,5 +1,5 @@
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
5
  from .server import start, handle, context_user, context_screen
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
- origin = get_origin(expected_type)
106
- if origin is None:
107
- return isinstance(variable, expected_type)
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
- # Check if the type matches the generic type
111
- if not isinstance(variable, origin):
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 not re.search(r'json', str_prompt, re.IGNORECASE):
139
- jtype = jstype(type_value)
140
- format = " dd/mm/yyyy string" if type_value == 'date' else f'a JSON {jtype}' if jtype != 'string' else jtype
141
- str_prompt = f"System: You are an intelligent and extremely smart assistant. Output STRONGLY in format {format}. DO NOT OUTPUT ANY COMMENTARY." + str_prompt
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
- if isinstance(type_value, dict):
162
- for k, v in type_value.items():
163
- if k not in parsed:
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
- Unishare.llm_model = ChatOpenAI(temperature = temperature)
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.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.4.dist-info/METADATA,sha256=tiL_tGRdP0YggAdpr7dh6YheOxvp1ImzRHgqscxijYc,27265
2
- unisi-0.4.4.dist-info/WHEEL,sha256=9P2ygRxDrTJz3gsagc0Z96ukrxjr-LFBGOgv3AuKlCA,90
3
- unisi-0.4.4.dist-info/entry_points.txt,sha256=6OYgBcLyFCUgeqLgnvMyOJxPCWzgy7se4rLPKtNonMs,34
4
- unisi-0.4.4.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
5
- unisi/__init__.py,sha256=prG4FwJzpNJRX1trto0x_4Bne3kkpEX1dUxcRnIxWVw,301
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=ORITYgC--_QPyBRumoacQmUIsQG5d7UO10956l5Fg2c,9893
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.4.dist-info/RECORD,,
58
+ unisi-0.4.5.dist-info/RECORD,,
File without changes