lollms-client 0.19.9__tar.gz → 0.20.0__tar.gz

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.

Files changed (85) hide show
  1. {lollms_client-0.19.9 → lollms_client-0.20.0}/PKG-INFO +1 -1
  2. lollms_client-0.20.0/examples/external_mcp.py +267 -0
  3. lollms_client-0.20.0/examples/run_standard_mcp_example.py +204 -0
  4. {lollms_client-0.19.9 → lollms_client-0.20.0}/lollms_client/__init__.py +1 -1
  5. {lollms_client-0.19.9 → lollms_client-0.20.0}/lollms_client/lollms_core.py +2 -3
  6. lollms_client-0.20.0/lollms_client/mcp_bindings/standard_mcp/__init__.py +519 -0
  7. {lollms_client-0.19.9 → lollms_client-0.20.0}/lollms_client.egg-info/PKG-INFO +1 -1
  8. {lollms_client-0.19.9 → lollms_client-0.20.0}/lollms_client.egg-info/SOURCES.txt +3 -0
  9. {lollms_client-0.19.9 → lollms_client-0.20.0}/LICENSE +0 -0
  10. {lollms_client-0.19.9 → lollms_client-0.20.0}/README.md +0 -0
  11. {lollms_client-0.19.9 → lollms_client-0.20.0}/examples/article_summary/article_summary.py +0 -0
  12. {lollms_client-0.19.9 → lollms_client-0.20.0}/examples/deep_analyze/deep_analyse.py +0 -0
  13. {lollms_client-0.19.9 → lollms_client-0.20.0}/examples/deep_analyze/deep_analyze_multiple_files.py +0 -0
  14. {lollms_client-0.19.9 → lollms_client-0.20.0}/examples/function_calling_with_local_custom_mcp.py +0 -0
  15. {lollms_client-0.19.9 → lollms_client-0.20.0}/examples/generate_a_benchmark_for_safe_store.py +0 -0
  16. {lollms_client-0.19.9 → lollms_client-0.20.0}/examples/generate_and_speak/generate_and_speak.py +0 -0
  17. {lollms_client-0.19.9 → lollms_client-0.20.0}/examples/generate_game_sfx/generate_game_fx.py +0 -0
  18. {lollms_client-0.19.9 → lollms_client-0.20.0}/examples/generate_text_with_multihop_rag_example.py +0 -0
  19. {lollms_client-0.19.9 → lollms_client-0.20.0}/examples/internet_search_with_rag.py +0 -0
  20. {lollms_client-0.19.9 → lollms_client-0.20.0}/examples/local_mcp.py +0 -0
  21. {lollms_client-0.19.9 → lollms_client-0.20.0}/examples/personality_test/chat_test.py +0 -0
  22. {lollms_client-0.19.9 → lollms_client-0.20.0}/examples/personality_test/chat_with_aristotle.py +0 -0
  23. {lollms_client-0.19.9 → lollms_client-0.20.0}/examples/personality_test/tesks_test.py +0 -0
  24. {lollms_client-0.19.9 → lollms_client-0.20.0}/examples/simple_text_gen_test.py +0 -0
  25. {lollms_client-0.19.9 → lollms_client-0.20.0}/examples/simple_text_gen_with_image_test.py +0 -0
  26. {lollms_client-0.19.9 → lollms_client-0.20.0}/examples/test_local_models/local_chat.py +0 -0
  27. {lollms_client-0.19.9 → lollms_client-0.20.0}/examples/text_2_audio.py +0 -0
  28. {lollms_client-0.19.9 → lollms_client-0.20.0}/examples/text_2_image.py +0 -0
  29. {lollms_client-0.19.9 → lollms_client-0.20.0}/examples/text_2_image_diffusers.py +0 -0
  30. {lollms_client-0.19.9 → lollms_client-0.20.0}/examples/text_and_image_2_audio.py +0 -0
  31. {lollms_client-0.19.9 → lollms_client-0.20.0}/examples/text_gen.py +0 -0
  32. {lollms_client-0.19.9 → lollms_client-0.20.0}/examples/text_gen_system_prompt.py +0 -0
  33. {lollms_client-0.19.9 → lollms_client-0.20.0}/lollms_client/llm_bindings/__init__.py +0 -0
  34. {lollms_client-0.19.9 → lollms_client-0.20.0}/lollms_client/llm_bindings/llamacpp/__init__.py +0 -0
  35. {lollms_client-0.19.9 → lollms_client-0.20.0}/lollms_client/llm_bindings/lollms/__init__.py +0 -0
  36. {lollms_client-0.19.9 → lollms_client-0.20.0}/lollms_client/llm_bindings/ollama/__init__.py +0 -0
  37. {lollms_client-0.19.9 → lollms_client-0.20.0}/lollms_client/llm_bindings/openai/__init__.py +0 -0
  38. {lollms_client-0.19.9 → lollms_client-0.20.0}/lollms_client/llm_bindings/openllm/__init__.py +0 -0
  39. {lollms_client-0.19.9 → lollms_client-0.20.0}/lollms_client/llm_bindings/pythonllamacpp/__init__.py +0 -0
  40. {lollms_client-0.19.9 → lollms_client-0.20.0}/lollms_client/llm_bindings/tensor_rt/__init__.py +0 -0
  41. {lollms_client-0.19.9 → lollms_client-0.20.0}/lollms_client/llm_bindings/transformers/__init__.py +0 -0
  42. {lollms_client-0.19.9 → lollms_client-0.20.0}/lollms_client/llm_bindings/vllm/__init__.py +0 -0
  43. {lollms_client-0.19.9 → lollms_client-0.20.0}/lollms_client/lollms_config.py +0 -0
  44. {lollms_client-0.19.9 → lollms_client-0.20.0}/lollms_client/lollms_discussion.py +0 -0
  45. {lollms_client-0.19.9 → lollms_client-0.20.0}/lollms_client/lollms_js_analyzer.py +0 -0
  46. {lollms_client-0.19.9 → lollms_client-0.20.0}/lollms_client/lollms_llm_binding.py +0 -0
  47. {lollms_client-0.19.9 → lollms_client-0.20.0}/lollms_client/lollms_mcp_binding.py +0 -0
  48. {lollms_client-0.19.9 → lollms_client-0.20.0}/lollms_client/lollms_python_analyzer.py +0 -0
  49. {lollms_client-0.19.9 → lollms_client-0.20.0}/lollms_client/lollms_stt_binding.py +0 -0
  50. {lollms_client-0.19.9 → lollms_client-0.20.0}/lollms_client/lollms_tti_binding.py +0 -0
  51. {lollms_client-0.19.9 → lollms_client-0.20.0}/lollms_client/lollms_ttm_binding.py +0 -0
  52. {lollms_client-0.19.9 → lollms_client-0.20.0}/lollms_client/lollms_tts_binding.py +0 -0
  53. {lollms_client-0.19.9 → lollms_client-0.20.0}/lollms_client/lollms_ttv_binding.py +0 -0
  54. {lollms_client-0.19.9 → lollms_client-0.20.0}/lollms_client/lollms_types.py +0 -0
  55. {lollms_client-0.19.9 → lollms_client-0.20.0}/lollms_client/lollms_utilities.py +0 -0
  56. {lollms_client-0.19.9 → lollms_client-0.20.0}/lollms_client/mcp_bindings/local_mcp/__init__.py +0 -0
  57. {lollms_client-0.19.9 → lollms_client-0.20.0}/lollms_client/mcp_bindings/local_mcp/default_tools/file_writer/file_writer.py +0 -0
  58. {lollms_client-0.19.9 → lollms_client-0.20.0}/lollms_client/mcp_bindings/local_mcp/default_tools/generate_image_from_prompt/generate_image_from_prompt.py +0 -0
  59. {lollms_client-0.19.9 → lollms_client-0.20.0}/lollms_client/mcp_bindings/local_mcp/default_tools/internet_search/internet_search.py +0 -0
  60. {lollms_client-0.19.9 → lollms_client-0.20.0}/lollms_client/mcp_bindings/local_mcp/default_tools/python_interpreter/python_interpreter.py +0 -0
  61. {lollms_client-0.19.9 → lollms_client-0.20.0}/lollms_client/stt_bindings/__init__.py +0 -0
  62. {lollms_client-0.19.9 → lollms_client-0.20.0}/lollms_client/stt_bindings/lollms/__init__.py +0 -0
  63. {lollms_client-0.19.9 → lollms_client-0.20.0}/lollms_client/stt_bindings/whisper/__init__.py +0 -0
  64. {lollms_client-0.19.9 → lollms_client-0.20.0}/lollms_client/stt_bindings/whispercpp/__init__.py +0 -0
  65. {lollms_client-0.19.9 → lollms_client-0.20.0}/lollms_client/tti_bindings/__init__.py +0 -0
  66. {lollms_client-0.19.9 → lollms_client-0.20.0}/lollms_client/tti_bindings/dalle/__init__.py +0 -0
  67. {lollms_client-0.19.9 → lollms_client-0.20.0}/lollms_client/tti_bindings/diffusers/__init__.py +0 -0
  68. {lollms_client-0.19.9 → lollms_client-0.20.0}/lollms_client/tti_bindings/gemini/__init__.py +0 -0
  69. {lollms_client-0.19.9 → lollms_client-0.20.0}/lollms_client/tti_bindings/lollms/__init__.py +0 -0
  70. {lollms_client-0.19.9 → lollms_client-0.20.0}/lollms_client/ttm_bindings/__init__.py +0 -0
  71. {lollms_client-0.19.9 → lollms_client-0.20.0}/lollms_client/ttm_bindings/audiocraft/__init__.py +0 -0
  72. {lollms_client-0.19.9 → lollms_client-0.20.0}/lollms_client/ttm_bindings/bark/__init__.py +0 -0
  73. {lollms_client-0.19.9 → lollms_client-0.20.0}/lollms_client/ttm_bindings/lollms/__init__.py +0 -0
  74. {lollms_client-0.19.9 → lollms_client-0.20.0}/lollms_client/tts_bindings/__init__.py +0 -0
  75. {lollms_client-0.19.9 → lollms_client-0.20.0}/lollms_client/tts_bindings/bark/__init__.py +0 -0
  76. {lollms_client-0.19.9 → lollms_client-0.20.0}/lollms_client/tts_bindings/lollms/__init__.py +0 -0
  77. {lollms_client-0.19.9 → lollms_client-0.20.0}/lollms_client/tts_bindings/piper_tts/__init__.py +0 -0
  78. {lollms_client-0.19.9 → lollms_client-0.20.0}/lollms_client/tts_bindings/xtts/__init__.py +0 -0
  79. {lollms_client-0.19.9 → lollms_client-0.20.0}/lollms_client/ttv_bindings/__init__.py +0 -0
  80. {lollms_client-0.19.9 → lollms_client-0.20.0}/lollms_client/ttv_bindings/lollms/__init__.py +0 -0
  81. {lollms_client-0.19.9 → lollms_client-0.20.0}/lollms_client.egg-info/dependency_links.txt +0 -0
  82. {lollms_client-0.19.9 → lollms_client-0.20.0}/lollms_client.egg-info/requires.txt +0 -0
  83. {lollms_client-0.19.9 → lollms_client-0.20.0}/lollms_client.egg-info/top_level.txt +0 -0
  84. {lollms_client-0.19.9 → lollms_client-0.20.0}/pyproject.toml +0 -0
  85. {lollms_client-0.19.9 → lollms_client-0.20.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lollms_client
3
- Version: 0.19.9
3
+ Version: 0.20.0
4
4
  Summary: A client library for LoLLMs generate endpoint
5
5
  Author-email: ParisNeo <parisneoai@gmail.com>
6
6
  License: Apache Software License
@@ -0,0 +1,267 @@
1
+ # File: run_lollms_client_with_mcp_example.py
2
+
3
+ import sys
4
+ import os
5
+ import shutil
6
+ from pathlib import Path
7
+ import json
8
+ import subprocess
9
+ from dotenv import load_dotenv # Import the function
10
+
11
+ # --- Load environment variables from .env file ---
12
+ # Load from .env in the current script's directory, or from project root if specified
13
+ # You can specify a path: load_dotenv(dotenv_path=Path('.') / '.env')
14
+ # By default, it looks for .env in the current working directory or parent directories.
15
+ # For simplicity, let's assume .env is next to this script or in a discoverable location.
16
+ load_dotenv()
17
+
18
+ # --- Python Path Adjustment (same as before) ---
19
+ current_script_dir = Path(__file__).resolve().parent
20
+ project_root_for_lollms_client = current_script_dir.parent
21
+ if str(project_root_for_lollms_client) not in sys.path:
22
+ sys.path.insert(0, str(project_root_for_lollms_client))
23
+ print(f"Added to sys.path: {project_root_for_lollms_client}")
24
+
25
+ # --- Pipmaster and LollmsClient Core Imports (same as before) ---
26
+ try:
27
+ import pipmaster as pm
28
+ except ImportError:
29
+ print("ERROR: pipmaster is not installed or not in PYTHONPATH.")
30
+ sys.exit(1)
31
+
32
+ try:
33
+ from lollms_client import LollmsClient
34
+ from lollms_client.lollms_llm_binding import LollmsLLMBinding
35
+ from ascii_colors import ASCIIColors, trace_exception
36
+ from lollms_client.lollms_types import MSG_TYPE
37
+ except ImportError as e:
38
+ print(f"ERROR: Could not import LollmsClient components: {e}")
39
+ trace_exception(e)
40
+ sys.exit(1)
41
+
42
+
43
+ # --- Dummy Server Scripts (Time and Calculator - same as before) ---
44
+ TIME_SERVER_PY = """
45
+ import asyncio
46
+ from datetime import datetime
47
+ from mcp.server.fastmcp import FastMCP
48
+
49
+ mcp_server = FastMCP("TimeMCP", description="A server that provides the current time.")
50
+
51
+ @mcp_server.tool(description="Returns the current server time and echoes received parameters.")
52
+ def get_current_time(user_id: str = "unknown_user") -> dict:
53
+ return {"time": datetime.now().isoformat(), "params_received": {"user_id": user_id}, "server_name": "TimeServer"}
54
+
55
+ if __name__ == "__main__":
56
+ mcp_server.run(transport="stdio")
57
+ """
58
+
59
+ CALCULATOR_SERVER_PY = """
60
+ import asyncio
61
+ from typing import List, Union
62
+ from mcp.server.fastmcp import FastMCP
63
+
64
+ mcp_server = FastMCP("CalculatorMCP", description="A server that performs addition.")
65
+
66
+ @mcp_server.tool(description="Adds a list of numbers provided in the 'numbers' parameter.")
67
+ def add_numbers(numbers: List[Union[int, float]]) -> dict:
68
+ if not isinstance(numbers, list) or not all(isinstance(x, (int, float)) for x in numbers):
69
+ return {"error": "'numbers' must be a list of numbers."}
70
+ return {"sum": sum(numbers), "server_name": "CalculatorServer"}
71
+
72
+ if __name__ == "__main__":
73
+ mcp_server.run(transport="stdio")
74
+ """
75
+
76
+ # --- Main Function ---
77
+ def main():
78
+ ASCIIColors.red("--- Example: Using LollmsClient with StandardMCPBinding (including external ElevenLabs MCP) ---")
79
+
80
+ # --- 1. Setup Temporary Directory for Dummy MCP Servers ---
81
+ example_base_dir = Path(__file__).parent / "temp_mcp_example_servers"
82
+ if example_base_dir.exists():
83
+ shutil.rmtree(example_base_dir)
84
+ example_base_dir.mkdir(exist_ok=True)
85
+
86
+ time_server_script_path = example_base_dir / "time_server.py"
87
+ with open(time_server_script_path, "w") as f: f.write(TIME_SERVER_PY)
88
+
89
+ calculator_server_script_path = example_base_dir / "calculator_server.py"
90
+ with open(calculator_server_script_path, "w") as f: f.write(CALCULATOR_SERVER_PY)
91
+
92
+ # --- 2. MCP Configuration ---
93
+ initial_mcp_servers = {
94
+ "time_machine": {
95
+ "command": [sys.executable, str(time_server_script_path.resolve())],
96
+ },
97
+ "calc_unit": {
98
+ "command": [sys.executable, str(calculator_server_script_path.resolve())]
99
+ }
100
+ }
101
+
102
+ # --- Configuration for ElevenLabs MCP Server (Optional) ---
103
+ # Variables are now loaded from .env by load_dotenv() at the start of the script
104
+ elevenlabs_api_key = os.getenv("ELEVENLABS_API_KEY")
105
+ elevenlabs_voice_id_from_env = os.getenv("ELEVENLABS_VOICE_ID", "Rachel") # Default if not in .env
106
+ elevenlabs_model_id_from_env = os.getenv("ELEVENLABS_MODEL_ID", "eleven_multilingual_v2") # Default
107
+
108
+ uvx_available = False
109
+ try:
110
+ subprocess.run(["uvx", "--version"], capture_output=True, check=True, text=True, timeout=5)
111
+ uvx_available = True
112
+ ASCIIColors.green("uvx command is available.")
113
+ except (FileNotFoundError, subprocess.CalledProcessError, subprocess.TimeoutExpired):
114
+ ASCIIColors.yellow("uvx command not found, not working, or timed out. ElevenLabs MCP server (via uvx) will not be configured.")
115
+
116
+ if elevenlabs_api_key and uvx_available:
117
+ ASCIIColors.green("ELEVENLABS_API_KEY found (from .env) and uvx available. Configuring ElevenLabs MCP server.")
118
+ initial_mcp_servers["elevenlabs"] = {
119
+ "command": ["uvx"],
120
+ "args": ["elevenlabs-mcp-server"],
121
+ "env": {
122
+ "ELEVENLABS_API_KEY": elevenlabs_api_key,
123
+ "ELEVENLABS_VOICE_ID": elevenlabs_voice_id_from_env,
124
+ "ELEVENLABS_MODEL_ID": elevenlabs_model_id_from_env,
125
+ "ELEVENLABS_OUTPUT_DIR": str(example_base_dir / "elevenlabs_output")
126
+ # Add other ELEVENLABS_ env vars from os.getenv() if needed
127
+ }
128
+ }
129
+ (example_base_dir / "elevenlabs_output").mkdir(exist_ok=True)
130
+ elif not elevenlabs_api_key:
131
+ ASCIIColors.yellow("ELEVENLABS_API_KEY not found in .env file or environment variables. Skipping ElevenLabs MCP server configuration.")
132
+
133
+ mcp_config = {"initial_servers": initial_mcp_servers}
134
+
135
+ # --- 3. Initialize LollmsClient ---
136
+ ASCIIColors.magenta("\n1. Initializing LollmsClient...")
137
+ try:
138
+ client = LollmsClient(
139
+ binding_name="ollama",
140
+ model_name="mistral-nemo:latest",
141
+ mcp_binding_name="standard_mcp",
142
+ mcp_binding_config=mcp_config,
143
+ )
144
+ except Exception as e:
145
+ ASCIIColors.error(f"Failed to initialize LollmsClient: {e}")
146
+ trace_exception(e)
147
+ shutil.rmtree(example_base_dir, ignore_errors=True)
148
+ sys.exit(1)
149
+
150
+ if not client.binding:
151
+ ASCIIColors.error("LollmsClient's LLM binding (ollama) failed to load.")
152
+ shutil.rmtree(example_base_dir, ignore_errors=True)
153
+ sys.exit(1)
154
+ if not client.mcp:
155
+ ASCIIColors.error("LollmsClient's MCP binding (standard_mcp) failed to load.")
156
+ if hasattr(client, 'close'): client.close()
157
+ shutil.rmtree(example_base_dir, ignore_errors=True)
158
+ sys.exit(1)
159
+ ASCIIColors.green("LollmsClient initialized successfully.")
160
+
161
+
162
+ # --- 4. Define Streaming Callback (same as before) ---
163
+ def mcp_streaming_callback(chunk: str, msg_type: MSG_TYPE, metadata: dict = None, history: list = None) -> bool:
164
+ if metadata:
165
+ type_info = metadata.get('type', 'unknown_type')
166
+ if msg_type == MSG_TYPE.MSG_TYPE_STEP_START: ASCIIColors.cyan(f"MCP Step Start ({type_info}): {chunk}")
167
+ elif msg_type == MSG_TYPE.MSG_TYPE_STEP_END: ASCIIColors.cyan(f"MCP Step End ({type_info}): {chunk}")
168
+ elif msg_type == MSG_TYPE.MSG_TYPE_INFO: ASCIIColors.yellow(f"MCP Info ({type_info}): {chunk}")
169
+ elif msg_type == MSG_TYPE.MSG_TYPE_CHUNK: ASCIIColors.green(chunk, end="")
170
+ else: ASCIIColors.green(f"MCP Output ({str(msg_type)}, {type_info}): {chunk}")
171
+ else:
172
+ if msg_type == MSG_TYPE.MSG_TYPE_CHUNK: ASCIIColors.green(chunk, end="")
173
+ else: ASCIIColors.green(f"MCP Output ({str(msg_type)}): {chunk}")
174
+ sys.stdout.flush()
175
+ return True
176
+
177
+ # --- 5. Use generate_with_mcp with local dummy servers ---
178
+ ASCIIColors.magenta("\n2. Calling generate_with_mcp to get current time (local dummy server)...")
179
+ time_prompt = "Hey assistant, what time is it right now?"
180
+ time_response = client.generate_with_mcp(
181
+ prompt=time_prompt,
182
+ streaming_callback=mcp_streaming_callback
183
+ )
184
+ print()
185
+ ASCIIColors.blue(f"Final response for time prompt: {json.dumps(time_response, indent=2)}")
186
+ assert time_response.get("error") is None, f"Time prompt error: {time_response.get('error')}"
187
+ assert time_response.get("final_answer"), "Time prompt no final answer."
188
+ assert len(time_response.get("tool_calls", [])) > 0, "Time prompt should call tool."
189
+ if time_response.get("tool_calls"):
190
+ assert time_response["tool_calls"][0]["name"] == "time_machine::get_current_time", "Incorrect tool for time."
191
+ assert "time" in time_response["tool_calls"][0].get("result", {}).get("output", {}), "Time tool result missing time."
192
+
193
+ ASCIIColors.magenta("\n3. Calling generate_with_mcp for calculation (local dummy server)...")
194
+ calc_prompt = "Can you sum 50, 25, and 7.5 for me?"
195
+ calc_response = client.generate_with_mcp(
196
+ prompt=calc_prompt,
197
+ streaming_callback=mcp_streaming_callback
198
+ )
199
+ print()
200
+ ASCIIColors.blue(f"Final response for calc prompt: {json.dumps(calc_response, indent=2)}")
201
+ assert calc_response.get("error") is None, f"Calc prompt error: {calc_response.get('error')}"
202
+ assert calc_response.get("final_answer"), "Calc prompt no final answer."
203
+ assert len(calc_response.get("tool_calls", [])) > 0, "Calc prompt should call tool."
204
+ if calc_response.get("tool_calls"):
205
+ assert calc_response["tool_calls"][0]["name"] == "calc_unit::add_numbers", "Incorrect tool for calc."
206
+ assert "sum" in calc_response["tool_calls"][0].get("result", {}).get("output", {}), "Calculator tool result missing sum."
207
+
208
+
209
+ # --- 6. Interact with ElevenLabs MCP Server (if configured) ---
210
+ if "elevenlabs" in client.mcp.get_binding_config().get("initial_servers", {}):
211
+ ASCIIColors.magenta("\n4. Interacting with ElevenLabs MCP server...")
212
+
213
+ ASCIIColors.info("Discovering all available tools (including ElevenLabs)...")
214
+ all_mcp_tools = client.mcp.discover_tools(force_refresh=True, timeout_per_server=45) # Longer timeout for external server
215
+ ASCIIColors.green(f"Discovered {len(all_mcp_tools)} tools in total:")
216
+ for tool in all_mcp_tools:
217
+ # Try to get properties keys from input_schema for a more informative print
218
+ props_keys = "N/A"
219
+ if isinstance(tool.get('input_schema'), dict) and isinstance(tool['input_schema'].get('properties'), dict):
220
+ props_keys = list(tool['input_schema']['properties'].keys())
221
+ print(f" - Name: {tool.get('name')}, Desc: {tool.get('description')}, Schema Props: {props_keys}")
222
+
223
+
224
+ elevenlabs_list_voices_tool_name = "elevenlabs::list_voices"
225
+ if any(t['name'] == elevenlabs_list_voices_tool_name for t in all_mcp_tools):
226
+ ASCIIColors.magenta(f"\n4a. Calling '{elevenlabs_list_voices_tool_name}' via LLM prompt...")
227
+
228
+ list_voices_prompt = "Please list all the available voices from the elevenlabs tool."
229
+ voices_response = client.generate_with_mcp(
230
+ prompt=list_voices_prompt,
231
+ streaming_callback=mcp_streaming_callback,
232
+ max_tool_calls=1
233
+ )
234
+ print()
235
+ ASCIIColors.blue(f"Final response for ElevenLabs list_voices prompt: {json.dumps(voices_response, indent=2)}")
236
+
237
+ assert voices_response.get("error") is None, f"ElevenLabs list_voices error: {voices_response.get('error')}"
238
+ assert voices_response.get("final_answer"), "ElevenLabs list_voices no final answer."
239
+ tool_calls = voices_response.get("tool_calls", [])
240
+ assert len(tool_calls) > 0, "ElevenLabs list_voices should call tool."
241
+ if tool_calls:
242
+ assert tool_calls[0]["name"] == elevenlabs_list_voices_tool_name, "Incorrect tool for ElevenLabs list_voices."
243
+ tool_output = tool_calls[0].get("result", {}).get("output")
244
+ assert isinstance(tool_output, list), f"ElevenLabs list_voices output not a list, got: {type(tool_output)}"
245
+ if tool_output:
246
+ ASCIIColors.green(f"First voice from ElevenLabs: {tool_output[0].get('name')} (ID: {tool_output[0].get('voice_id')})")
247
+ else:
248
+ ASCIIColors.yellow(f"Tool '{elevenlabs_list_voices_tool_name}' not found. Skipping ElevenLabs tool execution test.")
249
+ else:
250
+ ASCIIColors.yellow("ElevenLabs MCP server not configured in this run (check .env for API key and uvx availability). Skipping ElevenLabs tests.")
251
+
252
+ # --- 7. Cleanup ---
253
+ ASCIIColors.magenta("\n5. Closing LollmsClient and cleaning up...")
254
+ if client and hasattr(client, 'close'):
255
+ try:
256
+ client.close()
257
+ except Exception as e:
258
+ ASCIIColors.error(f"Error closing LollmsClient: {e}")
259
+ trace_exception(e)
260
+
261
+ ASCIIColors.info("Cleaning up temporary server scripts directory...")
262
+ shutil.rmtree(example_base_dir, ignore_errors=True)
263
+
264
+ ASCIIColors.red("\n--- LollmsClient with MCP Example (including external) Finished ---")
265
+
266
+ if __name__ == "__main__":
267
+ main()
@@ -0,0 +1,204 @@
1
+ # File: run_lollms_client_with_mcp_example.py
2
+
3
+ import sys
4
+ import os
5
+ import shutil
6
+ from pathlib import Path
7
+ import json
8
+ from lollms_client import LollmsClient
9
+ # --- Dynamically adjust Python path to find lollms_client ---
10
+ # This assumes the example script is in a directory, and 'lollms_client' is
11
+ # in a sibling directory or a known relative path. Adjust as needed.
12
+ # For example, if script is in 'lollms_client/examples/' and lollms_client code is in 'lollms_client/'
13
+ # then the parent of the script's parent is the project root.
14
+
15
+ # Get the directory of the current script
16
+ current_script_dir = Path(__file__).resolve().parent
17
+
18
+ # Option 1: If lollms_client is in the parent directory of this script's directory
19
+ # (e.g. script is in 'project_root/examples' and lollms_client is in 'project_root/lollms_client')
20
+ # project_root = current_script_dir.parent
21
+ # lollms_client_path = project_root / "lollms_client" # Assuming this is where lollms_client.py and bindings are
22
+
23
+ # Option 2: If lollms_client package is directly one level up
24
+ # (e.g. script is in 'lollms_client/examples' and lollms_client package is 'lollms_client')
25
+ project_root_for_lollms_client = current_script_dir.parent
26
+ if str(project_root_for_lollms_client) not in sys.path:
27
+ sys.path.insert(0, str(project_root_for_lollms_client))
28
+ print(f"Added to sys.path: {project_root_for_lollms_client}")
29
+
30
+
31
+ # --- Ensure pipmaster is available (core LoLLMs dependency) ---
32
+ try:
33
+ import pipmaster as pm
34
+ except ImportError:
35
+ print("ERROR: pipmaster is not installed or not in PYTHONPATH.")
36
+ sys.exit(1)
37
+
38
+ # --- Import LollmsClient and supporting components ---
39
+ try:
40
+
41
+ from lollms_client.lollms_llm_binding import LollmsLLMBinding # Base for LLM
42
+ from ascii_colors import ASCIIColors, trace_exception
43
+ from lollms_client.lollms_types import MSG_TYPE # Assuming MSG_TYPE is here
44
+ except ImportError as e:
45
+ print(f"ERROR: Could not import LollmsClient components: {e}")
46
+ print("Ensure 'lollms_client' package structure is correct and accessible via PYTHONPATH.")
47
+ print(f"Current sys.path: {sys.path}")
48
+ trace_exception(e)
49
+ sys.exit(1)
50
+
51
+
52
+ # --- Dummy Server Scripts using FastMCP (as per previous successful iteration) ---
53
+ TIME_SERVER_PY = """
54
+ import asyncio
55
+ from datetime import datetime
56
+ from mcp.server.fastmcp import FastMCP
57
+
58
+ mcp_server = FastMCP("TimeMCP", description="A server that provides the current time.")
59
+
60
+ @mcp_server.tool(description="Returns the current server time and echoes received parameters.")
61
+ def get_current_time(user_id: str = "unknown_user") -> dict:
62
+ return {"time": datetime.now().isoformat(), "params_received": {"user_id": user_id}, "server_name": "TimeServer"}
63
+
64
+ if __name__ == "__main__":
65
+ mcp_server.run(transport="stdio")
66
+ """
67
+
68
+ CALCULATOR_SERVER_PY = """
69
+ import asyncio
70
+ from typing import List, Union
71
+ from mcp.server.fastmcp import FastMCP
72
+
73
+ mcp_server = FastMCP("CalculatorMCP", description="A server that performs addition.")
74
+
75
+ @mcp_server.tool(description="Adds a list of numbers provided in the 'numbers' parameter.")
76
+ def add_numbers(numbers: List[Union[int, float]]) -> dict:
77
+ if not isinstance(numbers, list) or not all(isinstance(x, (int, float)) for x in numbers):
78
+ return {"error": "'numbers' must be a list of numbers."}
79
+ return {"sum": sum(numbers), "server_name": "CalculatorServer"}
80
+
81
+ if __name__ == "__main__":
82
+ mcp_server.run(transport="stdio")
83
+ """
84
+
85
+
86
+ def main():
87
+ ASCIIColors.red("--- Example: Using LollmsClient with StandardMCPBinding ---")
88
+
89
+ # --- 1. Setup Temporary Directory for Dummy MCP Servers ---
90
+ example_base_dir = Path(__file__).parent / "temp_mcp_example_servers"
91
+ if example_base_dir.exists():
92
+ shutil.rmtree(example_base_dir)
93
+ example_base_dir.mkdir(exist_ok=True)
94
+
95
+ time_server_script_path = example_base_dir / "time_server.py"
96
+ with open(time_server_script_path, "w") as f: f.write(TIME_SERVER_PY)
97
+
98
+ calculator_server_script_path = example_base_dir / "calculator_server.py"
99
+ with open(calculator_server_script_path, "w") as f: f.write(CALCULATOR_SERVER_PY)
100
+
101
+ # MCP Binding Configuration (for StandardMCPBinding)
102
+ mcp_config = {
103
+ "initial_servers": {
104
+ "time_machine": {
105
+ "command": [sys.executable, str(time_server_script_path.resolve())],
106
+ },
107
+ "calc_unit": {
108
+ "command": [sys.executable, str(calculator_server_script_path.resolve())]
109
+ }
110
+ }
111
+ }
112
+ ASCIIColors.magenta("\n1. Initializing LollmsClient...")
113
+ try:
114
+ client = LollmsClient(
115
+ binding_name="ollama", # Use the dummy LLM binding
116
+ model_name="mistral-nemo:latest",
117
+ mcp_binding_name="standard_mcp",
118
+ mcp_binding_config=mcp_config,
119
+ )
120
+ except Exception as e:
121
+ ASCIIColors.error(f"Failed to initialize LollmsClient: {e}")
122
+ trace_exception(e)
123
+ shutil.rmtree(example_base_dir)
124
+ sys.exit(1)
125
+
126
+ if not client.binding:
127
+ ASCIIColors.error("LollmsClient's LLM binding (dummy_llm) failed to load.")
128
+ shutil.rmtree(example_base_dir)
129
+ sys.exit(1)
130
+ if not client.mcp:
131
+ ASCIIColors.error("LollmsClient's MCP binding (standard_mcp) failed to load.")
132
+ client.close() # Close LLM binding if it loaded
133
+ shutil.rmtree(example_base_dir)
134
+ sys.exit(1)
135
+
136
+ ASCIIColors.green("LollmsClient initialized successfully with DummyLLM and StandardMCP bindings.")
137
+
138
+ # --- 3. Define a streaming callback for generate_with_mcp ---
139
+ def mcp_streaming_callback(chunk: str, msg_type: MSG_TYPE, metadata: dict = None, history: list = None) -> bool:
140
+ if metadata:
141
+ type_info = metadata.get('type', 'unknown_type')
142
+ if msg_type == MSG_TYPE.MSG_TYPE_STEP_START:
143
+ ASCIIColors.cyan(f"MCP Step Start ({type_info}): {chunk}")
144
+ elif msg_type == MSG_TYPE.MSG_TYPE_STEP_END:
145
+ ASCIIColors.cyan(f"MCP Step End ({type_info}): {chunk}")
146
+ elif msg_type == MSG_TYPE.MSG_TYPE_INFO:
147
+ ASCIIColors.yellow(f"MCP Info ({type_info}): {chunk}")
148
+ elif msg_type == MSG_TYPE.MSG_TYPE_CHUNK: # Part of final answer typically
149
+ ASCIIColors.green(chunk, end="") # type: ignore
150
+ else: # FULL, default, etc.
151
+ ASCIIColors.green(f"MCP Output ({str(msg_type)}, {type_info}): {chunk}")
152
+ else:
153
+ if msg_type == MSG_TYPE.MSG_TYPE_CHUNK:
154
+ ASCIIColors.green(chunk, end="") # type: ignore
155
+ else:
156
+ ASCIIColors.green(f"MCP Output ({str(msg_type)}): {chunk}")
157
+ sys.stdout.flush()
158
+ return True # Continue streaming
159
+
160
+ # --- 4. Use generate_with_mcp ---
161
+ ASCIIColors.magenta("\n2. Calling generate_with_mcp to get current time...")
162
+ time_prompt = "Hey assistant, what time is it right now?"
163
+ time_response = client.generate_with_mcp(
164
+ prompt=time_prompt,
165
+ streaming_callback=mcp_streaming_callback,
166
+ interactive_tool_execution=False # Set to True to test interactive mode
167
+ )
168
+ print() # Newline after streaming
169
+ ASCIIColors.blue(f"Final response for time prompt: {json.dumps(time_response, indent=2)}")
170
+
171
+ assert time_response.get("error") is None, f"Time prompt resulted in an error: {time_response.get('error')}"
172
+ assert time_response.get("final_answer"), "Time prompt did not produce a final answer."
173
+ assert len(time_response.get("tool_calls", [])) > 0, "Time prompt should have called a tool."
174
+ assert time_response["tool_calls"][0]["name"] == "time_machine::get_current_time", "Incorrect tool called for time."
175
+ assert "time" in time_response["tool_calls"][0].get("result", {}).get("output", {}), "Time tool result missing time."
176
+
177
+
178
+ ASCIIColors.magenta("\n3. Calling generate_with_mcp for calculation...")
179
+ calc_prompt = "Can you please calculate the sum of 50, 25, and 7.5 for me?"
180
+ calc_response = client.generate_with_mcp(
181
+ prompt=calc_prompt,
182
+ streaming_callback=mcp_streaming_callback
183
+ )
184
+ print() # Newline
185
+ ASCIIColors.blue(f"Final response for calc prompt: {json.dumps(calc_response, indent=2)}")
186
+
187
+ assert calc_response.get("error") is None, f"Calc prompt resulted in an error: {calc_response.get('error')}"
188
+ assert calc_response.get("final_answer"), "Calc prompt did not produce a final answer."
189
+ assert len(calc_response.get("tool_calls", [])) > 0, "Calc prompt should have called a tool."
190
+ assert calc_response["tool_calls"][0]["name"] == "calc_unit::add_numbers", "Incorrect tool called for calculation."
191
+ # The dummy LLM uses hardcoded params [1,2,3] for calc, so result will be 6.
192
+ # A real LLM would extract 50, 25, 7.5.
193
+ # For this dummy test, we check against the dummy's behavior.
194
+ assert calc_response["tool_calls"][0].get("result", {}).get("output", {}).get("sum") == 82.5, "Calculator tool result mismatch for dummy params."
195
+
196
+
197
+ # --- 5. Cleanup ---
198
+ ASCIIColors.info("Cleaning up temporary server scripts and dummy binding directory...")
199
+ shutil.rmtree(example_base_dir, ignore_errors=True)
200
+
201
+ ASCIIColors.red("\n--- LollmsClient with StandardMCPBinding Example Finished Successfully! ---")
202
+
203
+ if __name__ == "__main__":
204
+ main()
@@ -7,7 +7,7 @@ from lollms_client.lollms_utilities import PromptReshaper # Keep general utiliti
7
7
  from lollms_client.lollms_mcp_binding import LollmsMCPBinding, LollmsMCPBindingManager
8
8
 
9
9
 
10
- __version__ = "0.19.9" # Updated version
10
+ __version__ = "0.20.0" # Updated version
11
11
 
12
12
  # Optionally, you could define __all__ if you want to be explicit about exports
13
13
  __all__ = [
@@ -922,7 +922,6 @@ Respond with a JSON object containing ONE of the following structures:
922
922
  for hop_count in range(max_rag_hops + 1):
923
923
  if streaming_callback:
924
924
  streaming_callback(f"Starting RAG Hop {hop_count + 1}", MSG_TYPE.MSG_TYPE_STEP, {"type": "rag_hop_start", "hop": hop_count + 1}, turn_rag_history_for_callback)
925
-
926
925
  txt_previous_queries = f"Previous queries:\n"+'\n'.join(previous_queries)+"\n\n" if len(previous_queries)>0 else ""
927
926
  txt_informations = f"Information:\n"+'\n'.join([f"(from {chunk['document']}):{chunk['content']}" for _, chunk in all_unique_retrieved_chunks_map.items()]) if len(all_unique_retrieved_chunks_map)>0 else "This is the first request. No data received yet. Build a new query."
928
927
  txt_sp = "Your objective is to analyze the provided chunks of information, then decise if they are sufficient to reach the objective. If you need more information, formulate a new query to extract more data."
@@ -930,7 +929,7 @@ Respond with a JSON object containing ONE of the following structures:
930
929
  ```json
931
930
  {
932
931
  "decision": A boolean depicting your decision (true: more data is needed, false: there is enough data to reach objective),
933
- "query": (optional, only if decision is true). A new query to recover more information from the data source (do not use previous queries as they have already been used)
932
+ "query": (str, optional, only if decision is true). A new query to recover more information from the data source (do not use previous queries as they have already been used)
934
933
  }
935
934
  ```
936
935
  """
@@ -942,7 +941,7 @@ Respond with a JSON object containing ONE of the following structures:
942
941
  if not decision:
943
942
  break
944
943
  else:
945
- current_query_for_rag = answer["query"]
944
+ current_query_for_rag = str(answer["query"])
946
945
  except Exception as ex:
947
946
  trace_exception(ex)
948
947