lollms-client 0.17.2__py3-none-any.whl → 0.19.1__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
- lollms_client/__init__.py +7 -6
- 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/tti_bindings/dalle/__init__.py +2 -1
- {lollms_client-0.17.2.dist-info → lollms_client-0.19.1.dist-info}/METADATA +71 -16
- {lollms_client-0.17.2.dist-info → lollms_client-0.19.1.dist-info}/RECORD +16 -11
- 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.2.dist-info → lollms_client-0.19.1.dist-info}/WHEEL +0 -0
- {lollms_client-0.17.2.dist-info → lollms_client-0.19.1.dist-info}/licenses/LICENSE +0 -0
- {lollms_client-0.17.2.dist-info → lollms_client-0.19.1.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 ---")
|
|
@@ -152,7 +152,8 @@ class DalleTTIBinding_Impl(LollmsTTIBinding):
|
|
|
152
152
|
# Format size string and validate against the active model for this generation
|
|
153
153
|
size_str = f"{width}x{height}"
|
|
154
154
|
if size_str not in model_props["sizes"]:
|
|
155
|
-
|
|
155
|
+
ASCIIColors.warning(f"Unsupported size '{size_str}' for model '{active_model_name}'. Supported sizes: {model_props['sizes']}. Adjust width/height for this model.")
|
|
156
|
+
size_str = model_props["sizes"][0]
|
|
156
157
|
|
|
157
158
|
# Handle prompt and negative prompt based on the active model
|
|
158
159
|
final_prompt = prompt
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lollms_client
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.19.1
|
|
4
4
|
Summary: A client library for LoLLMs generate endpoint
|
|
5
5
|
Author-email: ParisNeo <parisneoai@gmail.com>
|
|
6
6
|
License: Apache Software License
|
|
@@ -39,7 +39,7 @@ Dynamic: license-file
|
|
|
39
39
|
[](https://github.com/ParisNeo/lollms_client/stargazers/)
|
|
40
40
|
[](https://github.com/ParisNeo/lollms_client/issues)
|
|
41
41
|
|
|
42
|
-
**`lollms_client`** is a powerful and flexible Python library designed to simplify interactions with the **LoLLMs (Lord of Large Language Models)** ecosystem and various other Large Language Model (LLM) backends. It provides a unified API for text generation, multimodal operations (text-to-image, text-to-speech, etc.), function calling
|
|
42
|
+
**`lollms_client`** is a powerful and flexible Python library designed to simplify interactions with the **LoLLMs (Lord of Large Language Models)** ecosystem and various other Large Language Model (LLM) backends. It provides a unified API for text generation, multimodal operations (text-to-image, text-to-speech, etc.), and robust function calling through the Model Context Protocol (MCP).
|
|
43
43
|
|
|
44
44
|
Whether you're connecting to a remote LoLLMs server, an Ollama instance, the OpenAI API, or running models locally using GGUF (via `llama-cpp-python` or a managed `llama.cpp` server), Hugging Face Transformers, or vLLM, `lollms-client` offers a consistent and developer-friendly experience.
|
|
45
45
|
|
|
@@ -47,12 +47,12 @@ Whether you're connecting to a remote LoLLMs server, an Ollama instance, the Ope
|
|
|
47
47
|
|
|
48
48
|
* 🔌 **Versatile Binding System:** Seamlessly switch between different LLM backends (LoLLMs, Ollama, OpenAI, Llama.cpp, Transformers, vLLM, OpenLLM) without major code changes.
|
|
49
49
|
* 🗣️ **Multimodal Support:** Interact with models capable of processing images and generate various outputs like speech (TTS) and images (TTI).
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
* 📞 **Function Calling:** Enable LLMs to invoke your custom Python functions, bridging the gap between language models and external tools or data sources.
|
|
50
|
+
* 🤖 **Function Calling with MCP:** Empowers LLMs to use external tools and functions through the Model Context Protocol (MCP), with built-in support for local Python tool execution via `local_mcp` binding and its default tools (file I/O, internet search, Python interpreter, image generation).
|
|
51
|
+
* 🚀 **Streaming & Callbacks:** Efficiently handle real-time text generation with customizable callback functions, including during MCP interactions.
|
|
53
52
|
* 💬 **Discussion Management:** Utilities to easily manage and format conversation histories for chat applications.
|
|
54
53
|
* ⚙️ **Configuration Management:** Flexible ways to configure bindings and generation parameters.
|
|
55
|
-
* 🧩 **Extensible:** Designed to easily incorporate new LLM backends and modality services.
|
|
54
|
+
* 🧩 **Extensible:** Designed to easily incorporate new LLM backends and modality services, including custom MCP toolsets.
|
|
55
|
+
* 📝 **High-Level Operations:** Includes convenience methods for complex tasks like sequential summarization and deep text analysis directly within `LollmsClient`.
|
|
56
56
|
|
|
57
57
|
## Installation
|
|
58
58
|
|
|
@@ -119,12 +119,61 @@ except Exception as e:
|
|
|
119
119
|
|
|
120
120
|
```
|
|
121
121
|
|
|
122
|
+
### Function Calling with MCP
|
|
123
|
+
|
|
124
|
+
`lollms-client` supports robust function calling via the Model Context Protocol (MCP), allowing LLMs to interact with your custom Python tools or pre-defined utilities.
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
from lollms_client import LollmsClient, MSG_TYPE
|
|
128
|
+
from ascii_colors import ASCIIColors
|
|
129
|
+
import json # For pretty printing results
|
|
130
|
+
|
|
131
|
+
# Example callback for MCP streaming
|
|
132
|
+
def mcp_stream_callback(chunk: str, msg_type: MSG_TYPE, metadata: dict = None, turn_history: list = None) -> bool:
|
|
133
|
+
if msg_type == MSG_TYPE.MSG_TYPE_CHUNK: ASCIIColors.success(chunk, end="", flush=True) # LLM's final answer or thought process
|
|
134
|
+
elif msg_type == MSG_TYPE.MSG_TYPE_STEP_START: ASCIIColors.info(f"\n>> MCP Step Start: {metadata.get('tool_name', chunk)}", flush=True)
|
|
135
|
+
elif msg_type == MSG_TYPE.MSG_TYPE_STEP_END: ASCIIColors.success(f"\n<< MCP Step End: {metadata.get('tool_name', chunk)} -> Result: {json.dumps(metadata.get('result', ''))}", flush=True)
|
|
136
|
+
elif msg_type == MSG_TYPE.MSG_TYPE_INFO and metadata and metadata.get("type") == "tool_call_request": ASCIIColors.info(f"\nAI requests: {metadata.get('name')}({metadata.get('params')})", flush=True)
|
|
137
|
+
return True
|
|
138
|
+
|
|
139
|
+
try:
|
|
140
|
+
# Initialize LollmsClient with an LLM binding and the local_mcp binding
|
|
141
|
+
lc = LollmsClient(
|
|
142
|
+
binding_name="ollama", model_name="mistral", # Example LLM
|
|
143
|
+
mcp_binding_name="local_mcp" # Enables default tools (file_writer, internet_search, etc.)
|
|
144
|
+
# or custom tools if mcp_binding_config.tools_folder_path is set.
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
user_query = "What were the main AI headlines last week and write a summary to 'ai_news.txt'?"
|
|
148
|
+
ASCIIColors.blue(f"User Query: {user_query}")
|
|
149
|
+
ASCIIColors.yellow("AI Processing with MCP (streaming):")
|
|
150
|
+
|
|
151
|
+
mcp_result = lc.generate_with_mcp(
|
|
152
|
+
prompt=user_query,
|
|
153
|
+
streaming_callback=mcp_stream_callback
|
|
154
|
+
)
|
|
155
|
+
print("\n--- End of MCP Interaction ---")
|
|
156
|
+
|
|
157
|
+
if mcp_result.get("error"):
|
|
158
|
+
ASCIIColors.error(f"MCP Error: {mcp_result['error']}")
|
|
159
|
+
else:
|
|
160
|
+
ASCIIColors.cyan(f"\nFinal Answer from AI: {mcp_result.get('final_answer', 'N/A')}")
|
|
161
|
+
ASCIIColors.magenta("\nTool Calls Made:")
|
|
162
|
+
for tc in mcp_result.get("tool_calls", []):
|
|
163
|
+
print(f" - Tool: {tc.get('name')}, Params: {tc.get('params')}, Result (first 50 chars): {str(tc.get('result'))[:50]}...")
|
|
164
|
+
|
|
165
|
+
except Exception as e:
|
|
166
|
+
ASCIIColors.error(f"An error occurred in MCP example: {e}")
|
|
167
|
+
trace_exception(e) # Assuming you have trace_exception utility
|
|
168
|
+
```
|
|
169
|
+
For a comprehensive guide on function calling and setting up tools, please refer to the [Usage Guide (DOC_USE.md)](DOC_USE.md).
|
|
170
|
+
|
|
122
171
|
## Documentation
|
|
123
172
|
|
|
124
173
|
For more in-depth information, please refer to:
|
|
125
174
|
|
|
126
|
-
* **[Usage Guide (DOC_USE.md)](DOC_USE.md):** Learn how to use `LollmsClient`, different bindings, modality features,
|
|
127
|
-
* **[Developer Guide (DOC_DEV.md)](DOC_DEV.md):** Understand the architecture, how to create new bindings, and contribute to the library.
|
|
175
|
+
* **[Usage Guide (DOC_USE.md)](DOC_USE.md):** Learn how to use `LollmsClient`, different bindings, modality features, function calling with MCP, and high-level operations.
|
|
176
|
+
* **[Developer Guide (DOC_DEV.md)](DOC_DEV.md):** Understand the architecture, how to create new bindings (LLM, modality, MCP), and contribute to the library.
|
|
128
177
|
|
|
129
178
|
## Core Concepts
|
|
130
179
|
|
|
@@ -134,8 +183,9 @@ graph LR
|
|
|
134
183
|
|
|
135
184
|
subgraph LollmsClient_Core
|
|
136
185
|
LC -- Manages --> LLB[LLM Binding];
|
|
137
|
-
LC --
|
|
138
|
-
LC --
|
|
186
|
+
LC -- Manages --> MCPB[MCP Binding];
|
|
187
|
+
LC -- Orchestrates --> MCP_Interaction[generate_with_mcp];
|
|
188
|
+
LC -- Provides --> HighLevelOps[High-Level Ops<br>(summarize, deep_analyze etc.)];
|
|
139
189
|
LC -- Provides Access To --> DM[DiscussionManager];
|
|
140
190
|
LC -- Provides Access To --> ModalityBindings[TTS, TTI, STT etc.];
|
|
141
191
|
end
|
|
@@ -148,14 +198,19 @@ graph LR
|
|
|
148
198
|
LLB --> LocalHF[Local HuggingFace<br>(transformers / vLLM)];
|
|
149
199
|
end
|
|
150
200
|
|
|
151
|
-
|
|
201
|
+
MCP_Interaction --> MCPB;
|
|
202
|
+
MCPB --> LocalTools[Local Python Tools<br>(via local_mcp)];
|
|
203
|
+
MCPB --> RemoteTools[Remote MCP Tool Servers<br>(Future Potential)];
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
ModalityBindings --> ModalityServices[Modality Services<br>(e.g., LoLLMs Server TTS/TTI, local Bark/XTTS)];
|
|
152
207
|
```
|
|
153
208
|
|
|
154
|
-
* **`LollmsClient`**: The central class for all interactions. It holds the currently active LLM binding and provides access to modality bindings and
|
|
209
|
+
* **`LollmsClient`**: The central class for all interactions. It holds the currently active LLM binding, an optional MCP binding, and provides access to modality bindings and high-level operations.
|
|
155
210
|
* **LLM Bindings**: These are plugins that allow `LollmsClient` to communicate with different LLM backends. You choose a binding (e.g., `"ollama"`, `"lollms"`, `"pythonllamacpp"`) when you initialize `LollmsClient`.
|
|
211
|
+
* **🔧 MCP Bindings**: Enable tool use and function calling. `lollms-client` includes `local_mcp` for executing Python tools. It discovers tools from a specified folder (or uses its default set), each defined by a `.py` script and a `.mcp.json` metadata file.
|
|
156
212
|
* **Modality Bindings**: Similar to LLM bindings, but for services like Text-to-Speech (`tts`), Text-to-Image (`tti`), etc.
|
|
157
|
-
*
|
|
158
|
-
* **`FunctionCalling_Library`**: Enables you to define Python functions that the LLM can request to execute, allowing for tool usage.
|
|
213
|
+
* **High-Level Operations**: Methods directly on `LollmsClient` (e.g., `sequential_summarize`, `deep_analyze`, `generate_code`, `yes_no`) for performing complex, multi-step AI tasks.
|
|
159
214
|
* **`LollmsDiscussion`**: Helps manage and format conversation histories for chat applications.
|
|
160
215
|
|
|
161
216
|
## Examples
|
|
@@ -164,8 +219,8 @@ The `examples/` directory in this repository contains a rich set of scripts demo
|
|
|
164
219
|
* Basic text generation with different bindings.
|
|
165
220
|
* Streaming and non-streaming examples.
|
|
166
221
|
* Multimodal generation (text with images).
|
|
167
|
-
* Using
|
|
168
|
-
* Implementing and using function calls.
|
|
222
|
+
* Using built-in methods for summarization and Q&A.
|
|
223
|
+
* Implementing and using function calls with **`generate_with_mcp`** and the `local_mcp` binding (see `examples/function_calling_with_local_custom_mcp.py` and `examples/local_mcp.py`).
|
|
169
224
|
* Text-to-Speech and Text-to-Image generation.
|
|
170
225
|
|
|
171
226
|
Explore these examples to see `lollms-client` in action!
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
examples/function_calling_with_local_custom_mcp.py,sha256=g6wOFRB8-p9Cv7hKmQaGzPvtMX3H77gas01QVNEOduM,12407
|
|
2
|
+
examples/local_mcp.py,sha256=w40dgayvHYe01yvekEE0LjcbkpwKjWwJ-9v4_wGYsUk,9113
|
|
1
3
|
examples/simple_text_gen_test.py,sha256=RoX9ZKJjGMujeep60wh5WT_GoBn0O9YKJY6WOy-ZmOc,8710
|
|
2
4
|
examples/simple_text_gen_with_image_test.py,sha256=rR1O5Prcb52UHtJ3c6bv7VuTd1cvbkr5aNZU-v-Rs3Y,9263
|
|
3
5
|
examples/text_2_audio.py,sha256=MfL4AH_NNwl6m0I0ywl4BXRZJ0b9Y_9fRqDIe6O-Sbw,3523
|
|
@@ -9,23 +11,21 @@ examples/text_gen_system_prompt.py,sha256=jRQeGe1IVu_zRHX09CFiDYi7WrK9Zd5FlMqC_g
|
|
|
9
11
|
examples/article_summary/article_summary.py,sha256=CR8mCBNcZEVCR-q34uOmrJyMlG-xk4HkMbsV-TOZEnk,1978
|
|
10
12
|
examples/deep_analyze/deep_analyse.py,sha256=fZNmDrfEAuxEAfdbjAgJYIh1k6wbiuZ4RvwHRvtyUs8,971
|
|
11
13
|
examples/deep_analyze/deep_analyze_multiple_files.py,sha256=fOryShA33P4IFxcxUDe-nJ2kW0v9w9yW8KsToS3ETl8,1032
|
|
12
|
-
examples/function_call/functions_call_with images.py,sha256=jrNtTF7lAzad25Ob0Yv4pwLs12HSzDamKKR9ORkNWjc,1888
|
|
13
14
|
examples/generate_and_speak/generate_and_speak.py,sha256=RAlvRwtEKXCh894l9M3iQbADe8CvF5N442jtRurK02I,13908
|
|
14
15
|
examples/generate_game_sfx/generate_game_fx.py,sha256=MgLNGi4hGBRoyr4bqYuCUdCSqd-ldDVfF0VSDUjgzsg,10467
|
|
15
16
|
examples/personality_test/chat_test.py,sha256=o2jlpoddFc-T592iqAiA29xk3x27KsdK5DluqxBwHqw,1417
|
|
16
17
|
examples/personality_test/chat_with_aristotle.py,sha256=4X_fwubMpd0Eq2rCReS2bgVlUoAqJprjkLXk2Jz6pXU,1774
|
|
17
18
|
examples/personality_test/tesks_test.py,sha256=7LIiwrEbva9WWZOLi34fsmCBN__RZbPpxoUOKA_AtYk,1924
|
|
18
19
|
examples/test_local_models/local_chat.py,sha256=slakja2zaHOEAUsn2tn_VmI4kLx6luLBrPqAeaNsix8,456
|
|
19
|
-
lollms_client/__init__.py,sha256=
|
|
20
|
+
lollms_client/__init__.py,sha256=rbZUoiSGIFLwCBjEUsAC68azS9mIfZ9EXmyasnBDbkY,910
|
|
20
21
|
lollms_client/lollms_config.py,sha256=goEseDwDxYJf3WkYJ4IrLXwg3Tfw73CXV2Avg45M_hE,21876
|
|
21
|
-
lollms_client/lollms_core.py,sha256=
|
|
22
|
+
lollms_client/lollms_core.py,sha256=psVTrEtHYhjy9h014rHLotBC4Aj72PvG2OV0UAjLcvw,102496
|
|
22
23
|
lollms_client/lollms_discussion.py,sha256=9b83m0D894jwpgssWYTQHbVxp1gJoI-J947Ui_dRXII,2073
|
|
23
|
-
lollms_client/lollms_functions.py,sha256=p8SFtmEPqvVCsIz2fZ5HxyOHaxjrAo5c12uTzJnb6m8,3594
|
|
24
24
|
lollms_client/lollms_js_analyzer.py,sha256=01zUvuO2F_lnUe_0NLxe1MF5aHE1hO8RZi48mNPv-aw,8361
|
|
25
25
|
lollms_client/lollms_llm_binding.py,sha256=bdElz_IBx0zZ-85YTT1fyY_mSoHo46tKIMiHYJlKCkM,9809
|
|
26
|
+
lollms_client/lollms_mcp_binding.py,sha256=0rK9HQCBEGryNc8ApBmtOlhKE1Yfn7X7xIQssXxS2Zc,8933
|
|
26
27
|
lollms_client/lollms_python_analyzer.py,sha256=7gf1fdYgXCOkPUkBAPNmr6S-66hMH4_KonOMsADASxc,10246
|
|
27
28
|
lollms_client/lollms_stt_binding.py,sha256=jAUhLouEhh2hmm1bK76ianfw_6B59EHfY3FmLv6DU-g,5111
|
|
28
|
-
lollms_client/lollms_tasks.py,sha256=Tgqces03gPTHFJCcPaeN9vBCsil3SSJX7nQAjCQ2-yg,34393
|
|
29
29
|
lollms_client/lollms_tti_binding.py,sha256=afO0-d-Kqsmh8UHTijTvy6dZAt-XDB6R-IHmdbf-_fs,5928
|
|
30
30
|
lollms_client/lollms_ttm_binding.py,sha256=FjVVSNXOZXK1qvcKEfxdiX6l2b4XdGOSNnZ0utAsbDg,4167
|
|
31
31
|
lollms_client/lollms_tts_binding.py,sha256=5cJYECj8PYLJAyB6SEH7_fhHYK3Om-Y3arkygCnZ24o,4342
|
|
@@ -42,12 +42,17 @@ lollms_client/llm_bindings/pythonllamacpp/__init__.py,sha256=7dM42TCGKh0eV0njNL1
|
|
|
42
42
|
lollms_client/llm_bindings/tensor_rt/__init__.py,sha256=nPaNhGRd-bsG0UlYwcEqjd_UagCMEf5VEbBUW-GWu6A,32203
|
|
43
43
|
lollms_client/llm_bindings/transformers/__init__.py,sha256=9LkqEC5bp1zHgyeGEcPQ3_uqvEAEf_B4p9DztcBaC5w,37211
|
|
44
44
|
lollms_client/llm_bindings/vllm/__init__.py,sha256=2NqeeqYWXNq1aNicdcAwN9DaoL4gq96GZ7hsKErfC6c,32187
|
|
45
|
+
lollms_client/mcp_bindings/local_mcp/__init__.py,sha256=FxS-0PULS3TZScPLDbwHlfyW5joWucmMfn6b9ydyeM0,14341
|
|
46
|
+
lollms_client/mcp_bindings/local_mcp/default_tools/file_writer/file_writer.py,sha256=2pkt1JcEKj61lIA5zuW3s6qkdpQN5rKfooo7bnebx24,3061
|
|
47
|
+
lollms_client/mcp_bindings/local_mcp/default_tools/generate_image_from_prompt/generate_image_from_prompt.py,sha256=THtZsMxNnXZiBdkwoBlfbWY2C5hhDdmPtnM-8cSKN6s,9488
|
|
48
|
+
lollms_client/mcp_bindings/local_mcp/default_tools/internet_search/internet_search.py,sha256=PLC31-D04QKTOTb1uuCHnrAlpysQjsk89yIJngK0VGc,4586
|
|
49
|
+
lollms_client/mcp_bindings/local_mcp/default_tools/python_interpreter/python_interpreter.py,sha256=McDCBVoVrMDYgU7EYtyOY7mCk1uEeTea0PSD69QqDsQ,6228
|
|
45
50
|
lollms_client/stt_bindings/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
46
51
|
lollms_client/stt_bindings/lollms/__init__.py,sha256=jBz3285atdPRqQe9ZRrb-AvjqKRB4f8tjLXjma0DLfE,6082
|
|
47
52
|
lollms_client/stt_bindings/whisper/__init__.py,sha256=vrua7fLwDId9_WiH4y2gXOE0hy3Gr2Ig-z5ScIT2bHI,15447
|
|
48
53
|
lollms_client/stt_bindings/whispercpp/__init__.py,sha256=B45lOn5rSoHXJSG9duPzBEPBOoNTrDzpCUdOL8KHaDM,21874
|
|
49
54
|
lollms_client/tti_bindings/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
50
|
-
lollms_client/tti_bindings/dalle/__init__.py,sha256=
|
|
55
|
+
lollms_client/tti_bindings/dalle/__init__.py,sha256=V-rODzcwaN9eg0wX8DGOQXXcDtnYYDWYD6t6pFNTo0w,23628
|
|
51
56
|
lollms_client/tti_bindings/diffusers/__init__.py,sha256=Hcu3F3KU8h6b_T4Dpl98OsQrsUoecd_z7xOVIEyYoDc,37679
|
|
52
57
|
lollms_client/tti_bindings/gemini/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
53
58
|
lollms_client/tti_bindings/lollms/__init__.py,sha256=GJShFW6Y8MrfM9PXaPrdAp8OUpD6rraSRFt8ZOnrauo,8735
|
|
@@ -62,8 +67,8 @@ lollms_client/tts_bindings/piper_tts/__init__.py,sha256=0IEWG4zH3_sOkSb9WbZzkeV5
|
|
|
62
67
|
lollms_client/tts_bindings/xtts/__init__.py,sha256=FgcdUH06X6ZR806WQe5ixaYx0QoxtAcOgYo87a2qxYc,18266
|
|
63
68
|
lollms_client/ttv_bindings/__init__.py,sha256=UZ8o2izQOJLQgtZ1D1cXoNST7rzqW22rL2Vufc7ddRc,3141
|
|
64
69
|
lollms_client/ttv_bindings/lollms/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
65
|
-
lollms_client-0.
|
|
66
|
-
lollms_client-0.
|
|
67
|
-
lollms_client-0.
|
|
68
|
-
lollms_client-0.
|
|
69
|
-
lollms_client-0.
|
|
70
|
+
lollms_client-0.19.1.dist-info/licenses/LICENSE,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
|
|
71
|
+
lollms_client-0.19.1.dist-info/METADATA,sha256=MKuTL8GsNdArHgSQ_xxhqViSdpEVs0cAK4akk5tNGVM,13374
|
|
72
|
+
lollms_client-0.19.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
73
|
+
lollms_client-0.19.1.dist-info/top_level.txt,sha256=NI_W8S4OYZvJjb0QWMZMSIpOrYzpqwPGYaklhyWKH2w,23
|
|
74
|
+
lollms_client-0.19.1.dist-info/RECORD,,
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import cv2
|
|
2
|
-
from lollms_client import LollmsClient, LollmsTTS, TasksLibrary, FunctionCalling_Library
|
|
3
|
-
import random
|
|
4
|
-
|
|
5
|
-
# Initialize the LollmsClient instance
|
|
6
|
-
lc = LollmsClient()
|
|
7
|
-
tl = TasksLibrary(lc)
|
|
8
|
-
tts = LollmsTTS(lc)
|
|
9
|
-
fcl = FunctionCalling_Library(tl)
|
|
10
|
-
voices = tts.get_voices()
|
|
11
|
-
if voices:
|
|
12
|
-
# Pick a voice randomly
|
|
13
|
-
random_voice = random.choice(voices)
|
|
14
|
-
print(f"Selected voice: {random_voice}")
|
|
15
|
-
|
|
16
|
-
# File path to save the captured image
|
|
17
|
-
file_path = "captured_image.jpg"
|
|
18
|
-
images = []
|
|
19
|
-
# Capture image from webcam and save it to a file
|
|
20
|
-
def capture_image():
|
|
21
|
-
cap = cv2.VideoCapture(0)
|
|
22
|
-
if not cap.isOpened():
|
|
23
|
-
raise Exception("Could not open webcam")
|
|
24
|
-
|
|
25
|
-
ret, frame = cap.read()
|
|
26
|
-
if not ret:
|
|
27
|
-
raise Exception("Failed to capture image")
|
|
28
|
-
images.clear()
|
|
29
|
-
images.append(file_path)
|
|
30
|
-
cv2.imwrite(file_path, frame)
|
|
31
|
-
cap.release()
|
|
32
|
-
return "Image captured successfully"
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
fcl.register_function("capture_image",capture_image,"Captures an image from the user webcam",[])
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
# Function to handle streaming callback
|
|
40
|
-
def cb(chunk, type):
|
|
41
|
-
print(chunk, end="", flush=True)
|
|
42
|
-
|
|
43
|
-
# Generate text with image
|
|
44
|
-
response, function_calls = fcl.generate_with_functions_and_images(prompt="user: take a look at me then tell ma how i look.\nassistant: ", images=images, stream=False, temperature=0.5, streaming_callback=cb)
|
|
45
|
-
print(f"response: {response}")
|
|
46
|
-
if len(function_calls)>0:
|
|
47
|
-
results = fcl.execute_function_calls(function_calls)
|
|
48
|
-
result = "\n".join(results)
|
|
49
|
-
prompt="user: take a look at me then tell ma how i look.\nassistant: "+response + f"\nfunction execution result: {result}\nassistant: "
|
|
50
|
-
response, function_calls = fcl.generate_with_functions_and_images(prompt, images=images, stream=False, temperature=0.5, streaming_callback=cb)
|
|
51
|
-
print(f"response: {response}")
|
|
52
|
-
tts.text2Audio(response, random_voice)
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
from functools import partial
|
|
2
|
-
from lollms_client.lollms_tasks import TasksLibrary
|
|
3
|
-
from typing import Dict, Any, List
|
|
4
|
-
|
|
5
|
-
class FunctionCalling_Library:
|
|
6
|
-
def __init__(self, tasks_library:TasksLibrary):
|
|
7
|
-
self.tl = tasks_library
|
|
8
|
-
self.function_definitions = []
|
|
9
|
-
|
|
10
|
-
def register_function(self, function_name, function_callable, function_description, function_parameters):
|
|
11
|
-
self.function_definitions.append({
|
|
12
|
-
"function_name": function_name,
|
|
13
|
-
"function": function_callable,
|
|
14
|
-
"function_description": function_description,
|
|
15
|
-
"function_parameters": function_parameters
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
def unregister_function(self, function_name):
|
|
19
|
-
self.function_definitions = [func for func in self.function_definitions if func["function_name"] != function_name]
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
def execute_function_calls(self, function_calls: List[Dict[str, Any]]) -> List[Any]:
|
|
24
|
-
"""
|
|
25
|
-
Executes the function calls with the parameters extracted from the generated text,
|
|
26
|
-
using the original functions list to find the right function to execute.
|
|
27
|
-
|
|
28
|
-
Args:
|
|
29
|
-
function_calls (List[Dict[str, Any]]): A list of dictionaries representing the function calls.
|
|
30
|
-
function_definitions (List[Dict[str, Any]]): The original list of functions with their descriptions and callable objects.
|
|
31
|
-
|
|
32
|
-
Returns:
|
|
33
|
-
List[Any]: A list of results from executing the function calls.
|
|
34
|
-
"""
|
|
35
|
-
results = []
|
|
36
|
-
# Convert function_definitions to a dict for easier lookup
|
|
37
|
-
functions_dict = {func['function_name']: func['function'] for func in self.function_definitions}
|
|
38
|
-
|
|
39
|
-
for call in function_calls:
|
|
40
|
-
function_name = call.get("function_name")
|
|
41
|
-
parameters = call.get("function_parameters", [])
|
|
42
|
-
function = functions_dict.get(function_name)
|
|
43
|
-
|
|
44
|
-
if function:
|
|
45
|
-
try:
|
|
46
|
-
# Assuming parameters is a dictionary that maps directly to the function's arguments.
|
|
47
|
-
if type(parameters)==list:
|
|
48
|
-
result = function(*parameters)
|
|
49
|
-
elif type(parameters)==dict:
|
|
50
|
-
result = function(**parameters)
|
|
51
|
-
results.append(result)
|
|
52
|
-
except TypeError as e:
|
|
53
|
-
# Handle cases where the function call fails due to incorrect parameters, etc.
|
|
54
|
-
results.append(f"Error calling {function_name}: {e}")
|
|
55
|
-
else:
|
|
56
|
-
results.append(f"Function {function_name} not found.")
|
|
57
|
-
|
|
58
|
-
return results
|
|
59
|
-
|
|
60
|
-
def generate_with_functions(self, prompt, stream=False, temperature=0.5, streaming_callback=None):
|
|
61
|
-
# Assuming generate_with_function_calls is a method from TasksLibrary
|
|
62
|
-
ai_response, function_calls = self.tl.generate_with_function_calls(prompt, self.function_definitions, callback=streaming_callback)
|
|
63
|
-
return ai_response, function_calls
|
|
64
|
-
|
|
65
|
-
def generate_with_functions_and_images(self, prompt, images=[], stream=False, temperature=0.5, streaming_callback=None):
|
|
66
|
-
# Assuming generate_with_function_calls_and_images is a method from TasksLibrary
|
|
67
|
-
if len(images) > 0:
|
|
68
|
-
ai_response, function_calls = self.tl.generate_with_function_calls_and_images(prompt, images, self.function_definitions, callback=streaming_callback)
|
|
69
|
-
else:
|
|
70
|
-
ai_response, function_calls = self.tl.generate_with_function_calls(prompt, self.function_definitions)
|
|
71
|
-
|
|
72
|
-
return ai_response, function_calls
|