lollms-client 0.17.1__py3-none-any.whl → 0.19.0__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.
Potentially problematic release.
This version of lollms-client might be problematic. Click here for more details.
- examples/function_calling_with_local_custom_mcp.py +250 -0
- examples/local_mcp.py +171 -0
- examples/text_2_image.py +8 -3
- examples/text_2_image_diffusers.py +274 -0
- lollms_client/__init__.py +7 -6
- lollms_client/llm_bindings/llamacpp/__init__.py +8 -8
- lollms_client/lollms_core.py +345 -10
- lollms_client/lollms_mcp_binding.py +198 -0
- lollms_client/mcp_bindings/local_mcp/__init__.py +311 -0
- lollms_client/mcp_bindings/local_mcp/default_tools/file_writer/file_writer.py +74 -0
- lollms_client/mcp_bindings/local_mcp/default_tools/generate_image_from_prompt/generate_image_from_prompt.py +195 -0
- lollms_client/mcp_bindings/local_mcp/default_tools/internet_search/internet_search.py +107 -0
- lollms_client/mcp_bindings/local_mcp/default_tools/python_interpreter/python_interpreter.py +141 -0
- lollms_client/stt_bindings/whisper/__init__.py +1 -1
- lollms_client/tti_bindings/dalle/__init__.py +433 -0
- lollms_client/tti_bindings/diffusers/__init__.py +692 -0
- lollms_client/tti_bindings/gemini/__init__.py +0 -0
- {lollms_client-0.17.1.dist-info → lollms_client-0.19.0.dist-info}/METADATA +1 -1
- {lollms_client-0.17.1.dist-info → lollms_client-0.19.0.dist-info}/RECORD +22 -13
- examples/function_call/functions_call_with images.py +0 -52
- lollms_client/lollms_functions.py +0 -72
- lollms_client/lollms_tasks.py +0 -691
- {lollms_client-0.17.1.dist-info → lollms_client-0.19.0.dist-info}/WHEEL +0 -0
- {lollms_client-0.17.1.dist-info → lollms_client-0.19.0.dist-info}/licenses/LICENSE +0 -0
- {lollms_client-0.17.1.dist-info → lollms_client-0.19.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
from typing import Dict, Any, List
|
|
2
|
+
|
|
3
|
+
# --- Package Management ---
|
|
4
|
+
_duckduckgo_search_installed = False
|
|
5
|
+
_installation_error_message = ""
|
|
6
|
+
try:
|
|
7
|
+
import pipmaster as pm
|
|
8
|
+
pm.ensure_packages(["duckduckgo_search"])
|
|
9
|
+
from duckduckgo_search import DDGS
|
|
10
|
+
_duckduckgo_search_installed = True
|
|
11
|
+
except Exception as e:
|
|
12
|
+
_installation_error_message = str(e)
|
|
13
|
+
DDGS = None # Ensure DDGS is None if import fails
|
|
14
|
+
# --- End Package Management ---
|
|
15
|
+
|
|
16
|
+
def execute(params: Dict[str, Any]) -> Dict[str, Any]:
|
|
17
|
+
"""
|
|
18
|
+
Performs an internet search using DuckDuckGo.
|
|
19
|
+
"""
|
|
20
|
+
if not _duckduckgo_search_installed:
|
|
21
|
+
return {
|
|
22
|
+
"search_results": [],
|
|
23
|
+
"error": f"Required library 'duckduckgo_search' is not installed or import failed: {_installation_error_message}"
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
query = params.get("query")
|
|
27
|
+
num_results = params.get("num_results", 5)
|
|
28
|
+
|
|
29
|
+
if not query:
|
|
30
|
+
return {"search_results": [], "error": "'query' parameter is required."}
|
|
31
|
+
if not isinstance(num_results, int) or num_results <= 0:
|
|
32
|
+
num_results = 5 # Default to 5 if invalid
|
|
33
|
+
|
|
34
|
+
try:
|
|
35
|
+
search_results_formatted: List[Dict[str, str]] = []
|
|
36
|
+
# DDGS().text returns a generator, max_results limits it.
|
|
37
|
+
# Note: The DDGS library might sometimes return fewer results than max_results.
|
|
38
|
+
with DDGS() as ddgs:
|
|
39
|
+
results = ddgs.text(keywords=query, max_results=num_results)
|
|
40
|
+
if results:
|
|
41
|
+
for r in results:
|
|
42
|
+
search_results_formatted.append({
|
|
43
|
+
"title": r.get("title", "N/A"),
|
|
44
|
+
"link": r.get("href", "#"),
|
|
45
|
+
"snippet": r.get("body", "N/A")
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
if not search_results_formatted and results is None: # Check if ddgs.text itself returned None
|
|
49
|
+
return {"search_results": [], "error": "Search returned no results or failed to connect."}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
return {"search_results": search_results_formatted, "error": None}
|
|
53
|
+
|
|
54
|
+
except Exception as e:
|
|
55
|
+
# Log the exception for server-side debugging if possible
|
|
56
|
+
# For now, just return it in the error field.
|
|
57
|
+
print(f"Error during internet search: {str(e)}") # Basic logging
|
|
58
|
+
# from ascii_colors import trace_exception (if you want to import it here)
|
|
59
|
+
# trace_exception(e)
|
|
60
|
+
return {"search_results": [], "error": f"An unexpected error occurred during search: {str(e)}"}
|
|
61
|
+
|
|
62
|
+
if __name__ == '__main__':
|
|
63
|
+
import json
|
|
64
|
+
print("--- Internet Search Tool Test ---")
|
|
65
|
+
|
|
66
|
+
if not _duckduckgo_search_installed:
|
|
67
|
+
print(f"Cannot run test: duckduckgo_search not installed. Error: {_installation_error_message}")
|
|
68
|
+
else:
|
|
69
|
+
# Test 1: Simple search
|
|
70
|
+
params1 = {"query": "What is the capital of France?", "num_results": 3}
|
|
71
|
+
result1 = execute(params1)
|
|
72
|
+
print(f"\nTest 1 Result (Query: '{params1['query']}'):\n{json.dumps(result1, indent=2)}")
|
|
73
|
+
if result1.get("search_results"):
|
|
74
|
+
assert len(result1["search_results"]) <= 3
|
|
75
|
+
assert "error" not in result1 or result1["error"] is None
|
|
76
|
+
else:
|
|
77
|
+
print(f"Warning: Test 1 might have failed to retrieve results. Error: {result1.get('error')}")
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
# Test 2: Search with more results
|
|
81
|
+
params2 = {"query": "Latest AI breakthroughs", "num_results": 6}
|
|
82
|
+
result2 = execute(params2)
|
|
83
|
+
print(f"\nTest 2 Result (Query: '{params2['query']}'):\n{json.dumps(result2, indent=2)}")
|
|
84
|
+
if result2.get("search_results"):
|
|
85
|
+
assert len(result2["search_results"]) <= 6
|
|
86
|
+
else:
|
|
87
|
+
print(f"Warning: Test 2 might have failed to retrieve results. Error: {result2.get('error')}")
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
# Test 3: No query
|
|
91
|
+
params3 = {}
|
|
92
|
+
result3 = execute(params3)
|
|
93
|
+
print(f"\nTest 3 Result (No query):\n{json.dumps(result3, indent=2)}")
|
|
94
|
+
assert result3["error"] is not None
|
|
95
|
+
assert "query' parameter is required" in result3["error"]
|
|
96
|
+
|
|
97
|
+
# Test 4: Invalid num_results
|
|
98
|
+
params4 = {"query": "python programming", "num_results": -1}
|
|
99
|
+
result4 = execute(params4) # Should default to 5
|
|
100
|
+
print(f"\nTest 4 Result (Invalid num_results):\n{json.dumps(result4, indent=2)}")
|
|
101
|
+
if result4.get("search_results"):
|
|
102
|
+
assert len(result4["search_results"]) <= 5
|
|
103
|
+
else:
|
|
104
|
+
print(f"Warning: Test 4 might have failed to retrieve results. Error: {result4.get('error')}")
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
print("\n--- Tests Finished ---")
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import tempfile
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Dict, Any
|
|
5
|
+
import sys
|
|
6
|
+
import json
|
|
7
|
+
import pipmaster as pm
|
|
8
|
+
pm.ensure_packages(["RestrictedPython"])
|
|
9
|
+
|
|
10
|
+
import RestrictedPython
|
|
11
|
+
from RestrictedPython import compile_restricted, safe_globals
|
|
12
|
+
from RestrictedPython.PrintCollector import PrintCollector
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def execute(params: Dict[str, Any]) -> Dict[str, Any]:
|
|
16
|
+
"""
|
|
17
|
+
Executes a snippet of Python code in a restricted environment.
|
|
18
|
+
"""
|
|
19
|
+
code = params.get("code")
|
|
20
|
+
timeout_seconds = params.get("timeout_seconds", 10)
|
|
21
|
+
|
|
22
|
+
if not code:
|
|
23
|
+
return {
|
|
24
|
+
"stdout": "",
|
|
25
|
+
"stderr": "Error: 'code' parameter is required.",
|
|
26
|
+
"returned_value": None,
|
|
27
|
+
"execution_status": "error"
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
# For this local tool, we'll use RestrictedPython for a degree of safety.
|
|
31
|
+
# A more robust solution for untrusted code would involve containers (e.g., Docker)
|
|
32
|
+
# or a more heavily sandboxed environment. RestrictedPython is not foolproof.
|
|
33
|
+
|
|
34
|
+
restricted_globals = dict(safe_globals) # type: ignore
|
|
35
|
+
restricted_globals['_print_'] = PrintCollector # Capture print statements
|
|
36
|
+
restricted_globals['_getattr_'] = RestrictedPython.Guards.safer_getattr # Allow safer attribute access
|
|
37
|
+
# Add more globals if needed, e.g., math module, but be cautious.
|
|
38
|
+
# restricted_globals['math'] = math
|
|
39
|
+
|
|
40
|
+
stdout_capture = ""
|
|
41
|
+
stderr_capture = ""
|
|
42
|
+
returned_value_str = None
|
|
43
|
+
status = "error" # Default to error
|
|
44
|
+
|
|
45
|
+
try:
|
|
46
|
+
# Compile the code in restricted mode
|
|
47
|
+
# Adding "<string>" as the filename for tracebacks
|
|
48
|
+
byte_code = compile_restricted(code, filename='<inline_script>', mode='exec')
|
|
49
|
+
|
|
50
|
+
# Prepare a dictionary for local variables, including the print collector
|
|
51
|
+
local_vars = {}
|
|
52
|
+
|
|
53
|
+
# Execute the compiled code
|
|
54
|
+
# Note: RestrictedPython's exec doesn't directly support timeout.
|
|
55
|
+
# A more complex setup with threading or multiprocessing would be needed for true timeout.
|
|
56
|
+
# For simplicity, this example doesn't implement a hard timeout for RestrictedPython's exec.
|
|
57
|
+
# The timeout_seconds param is more of a hint for now or for alternative execution methods.
|
|
58
|
+
|
|
59
|
+
exec(byte_code, restricted_globals, local_vars)
|
|
60
|
+
|
|
61
|
+
stdout_capture = restricted_globals['_print']() # Get captured prints
|
|
62
|
+
# RestrictedPython itself doesn't easily separate stdout/stderr from exec.
|
|
63
|
+
# Errors during compilation or guarded execution will raise exceptions.
|
|
64
|
+
|
|
65
|
+
# Try to get a returned value if the script used 'return' (not typical for 'exec' mode)
|
|
66
|
+
# or if a specific variable was set to indicate a return.
|
|
67
|
+
# For this example, we won't try to capture implicit returns from 'exec'.
|
|
68
|
+
# If the executed code sets a variable like `result = ...`, it would be in `local_vars`.
|
|
69
|
+
# We'll consider `returned_value` as None for simplicity with 'exec'.
|
|
70
|
+
|
|
71
|
+
status = "success"
|
|
72
|
+
|
|
73
|
+
except SyntaxError as se:
|
|
74
|
+
stderr_capture = f"SyntaxError: {se}"
|
|
75
|
+
status = "error"
|
|
76
|
+
except RestrictedPython.Guards. इलाकोंOnlyĐiPythonGuardViolation as rpe: # Common RestrictedPython error
|
|
77
|
+
stderr_capture = f"RestrictedPythonError: {rpe}"
|
|
78
|
+
status = "error"
|
|
79
|
+
except NameError as ne: # Undeclared variable
|
|
80
|
+
stderr_capture = f"NameError: {ne}"
|
|
81
|
+
status = "error"
|
|
82
|
+
except TypeError as te:
|
|
83
|
+
stderr_capture = f"TypeError: {te}"
|
|
84
|
+
status = "error"
|
|
85
|
+
except Exception as e:
|
|
86
|
+
stderr_capture = f"Execution Error: {type(e).__name__}: {e}"
|
|
87
|
+
status = "error"
|
|
88
|
+
trace_exception(e) # For more detailed logging if needed
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
"stdout": stdout_capture,
|
|
92
|
+
"stderr": stderr_capture,
|
|
93
|
+
"returned_value": returned_value_str, # Typically None with exec
|
|
94
|
+
"execution_status": status
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
if __name__ == '__main__':
|
|
99
|
+
# Example Tests
|
|
100
|
+
print("--- Python Interpreter Tool Test ---")
|
|
101
|
+
|
|
102
|
+
# Test 1: Simple print
|
|
103
|
+
params1 = {"code": "print('Hello from restricted Python!')\nx = 10 + 5\nprint(f'Result is {x}')"}
|
|
104
|
+
result1 = execute(params1)
|
|
105
|
+
print(f"\nTest 1 Result:\n{json.dumps(result1, indent=2)}")
|
|
106
|
+
assert result1["execution_status"] == "success"
|
|
107
|
+
assert "Hello from restricted Python!" in result1["stdout"]
|
|
108
|
+
assert "Result is 15" in result1["stdout"]
|
|
109
|
+
|
|
110
|
+
# Test 2: Code with an error (e.g., trying to access restricted features)
|
|
111
|
+
params2 = {"code": "import os\nprint(os.getcwd())"} # os is usually restricted
|
|
112
|
+
result2 = execute(params2)
|
|
113
|
+
print(f"\nTest 2 Result (expected error):\n{json.dumps(result2, indent=2)}")
|
|
114
|
+
assert result2["execution_status"] == "error"
|
|
115
|
+
assert "RestrictedPythonError" in result2["stderr"] or "NameError" in result2["stderr"] # Depending on how os import fails
|
|
116
|
+
|
|
117
|
+
# Test 3: Syntax error
|
|
118
|
+
params3 = {"code": "print('Hello without closing quote"}
|
|
119
|
+
result3 = execute(params3)
|
|
120
|
+
print(f"\nTest 3 Result (syntax error):\n{json.dumps(result3, indent=2)}")
|
|
121
|
+
assert result3["execution_status"] == "error"
|
|
122
|
+
assert "SyntaxError" in result3["stderr"]
|
|
123
|
+
|
|
124
|
+
# Test 4: No code
|
|
125
|
+
params4 = {"code": ""}
|
|
126
|
+
result4 = execute(params4) # Should succeed with empty output or be handled if empty code is invalid.
|
|
127
|
+
# Current RestrictedPython might raise error on empty.
|
|
128
|
+
print(f"\nTest 4 Result (empty code):\n{json.dumps(result4, indent=2)}")
|
|
129
|
+
# Depending on compile_restricted behavior for empty string, this might be success or error.
|
|
130
|
+
# It seems compile_restricted can handle empty strings.
|
|
131
|
+
assert result4["execution_status"] == "success"
|
|
132
|
+
assert result4["stdout"] == ""
|
|
133
|
+
|
|
134
|
+
# Test 5: Attempting a disallowed attribute
|
|
135
|
+
params5 = {"code": "x = ().__class__"} # Example of trying to access something disallowed
|
|
136
|
+
result5 = execute(params5)
|
|
137
|
+
print(f"\nTest 5 Result (disallowed attribute):\n{json.dumps(result5, indent=2)}")
|
|
138
|
+
assert result5["execution_status"] == "error"
|
|
139
|
+
assert "RestrictedPythonError" in result5["stderr"]
|
|
140
|
+
|
|
141
|
+
print("\n--- Tests Finished ---")
|
|
@@ -30,7 +30,7 @@ try:
|
|
|
30
30
|
torch_index_url = None
|
|
31
31
|
if preferred_torch_device_for_install == "cuda":
|
|
32
32
|
# Specify a common CUDA version index. Pip should resolve the correct torch version.
|
|
33
|
-
# As of late 2023/early 2024, cu118 or cu121 are common. Let's use
|
|
33
|
+
# As of late 2023/early 2024, cu118 or cu121 are common. Let's use cu126.
|
|
34
34
|
# Users with different CUDA setups might need to pre-install torch manually.
|
|
35
35
|
torch_index_url = "https://download.pytorch.org/whl/cu126"
|
|
36
36
|
ASCIIColors.info(f"Attempting to ensure PyTorch with CUDA support (target index: {torch_index_url})")
|