lollms-client 0.26.0__tar.gz → 0.27.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.
- {lollms_client-0.26.0 → lollms_client-0.27.0}/PKG-INFO +1 -1
- {lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/__init__.py +3 -2
- {lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/llm_bindings/claude/__init__.py +1 -1
- lollms_client-0.27.0/lollms_client/llm_bindings/grok/__init__.py +536 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/lollms_core.py +95 -4
- {lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/lollms_discussion.py +13 -4
- {lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/lollms_llm_binding.py +4 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client.egg-info/PKG-INFO +1 -1
- {lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client.egg-info/SOURCES.txt +1 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/LICENSE +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/README.md +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/examples/article_summary/article_summary.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/examples/console_discussion/console_app.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/examples/console_discussion.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/examples/deep_analyze/deep_analyse.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/examples/deep_analyze/deep_analyze_multiple_files.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/examples/function_calling_with_local_custom_mcp.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/examples/generate_a_benchmark_for_safe_store.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/examples/generate_and_speak/generate_and_speak.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/examples/generate_game_sfx/generate_game_fx.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/examples/generate_text_with_multihop_rag_example.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/examples/gradio_chat_app.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/examples/gradio_lollms_chat.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/examples/internet_search_with_rag.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/examples/lollms_discussions_test.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/examples/mcp_examples/external_mcp.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/examples/mcp_examples/local_mcp.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/examples/mcp_examples/openai_mcp.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/examples/mcp_examples/run_remote_mcp_example_v2.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/examples/mcp_examples/run_standard_mcp_example.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/examples/simple_text_gen_test.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/examples/simple_text_gen_with_image_test.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/examples/test_local_models/local_chat.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/examples/text_2_audio.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/examples/text_2_image.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/examples/text_2_image_diffusers.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/examples/text_and_image_2_audio.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/examples/text_gen.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/examples/text_gen_system_prompt.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/llm_bindings/__init__.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/llm_bindings/azure_openai/__init__.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/llm_bindings/gemini/__init__.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/llm_bindings/groq/__init__.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/llm_bindings/hugging_face_inference_api/__init__.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/llm_bindings/litellm/__init__.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/llm_bindings/llamacpp/__init__.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/llm_bindings/lollms/__init__.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/llm_bindings/mistral/__init__.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/llm_bindings/ollama/__init__.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/llm_bindings/open_router/__init__.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/llm_bindings/openai/__init__.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/llm_bindings/openllm/__init__.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/llm_bindings/pythonllamacpp/__init__.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/llm_bindings/tensor_rt/__init__.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/llm_bindings/transformers/__init__.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/llm_bindings/vllm/__init__.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/lollms_config.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/lollms_js_analyzer.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/lollms_mcp_binding.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/lollms_personality.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/lollms_python_analyzer.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/lollms_stt_binding.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/lollms_tti_binding.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/lollms_ttm_binding.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/lollms_tts_binding.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/lollms_ttv_binding.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/lollms_types.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/lollms_utilities.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/mcp_bindings/local_mcp/__init__.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/mcp_bindings/local_mcp/default_tools/file_writer/file_writer.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/mcp_bindings/local_mcp/default_tools/generate_image_from_prompt/generate_image_from_prompt.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/mcp_bindings/local_mcp/default_tools/internet_search/internet_search.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/mcp_bindings/local_mcp/default_tools/python_interpreter/python_interpreter.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/mcp_bindings/remote_mcp/__init__.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/mcp_bindings/standard_mcp/__init__.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/stt_bindings/__init__.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/stt_bindings/lollms/__init__.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/stt_bindings/whisper/__init__.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/stt_bindings/whispercpp/__init__.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/tti_bindings/__init__.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/tti_bindings/dalle/__init__.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/tti_bindings/diffusers/__init__.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/tti_bindings/gemini/__init__.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/tti_bindings/lollms/__init__.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/ttm_bindings/__init__.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/ttm_bindings/audiocraft/__init__.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/ttm_bindings/bark/__init__.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/ttm_bindings/lollms/__init__.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/tts_bindings/__init__.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/tts_bindings/bark/__init__.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/tts_bindings/lollms/__init__.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/tts_bindings/piper_tts/__init__.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/tts_bindings/xtts/__init__.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/ttv_bindings/__init__.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/ttv_bindings/lollms/__init__.py +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client.egg-info/dependency_links.txt +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client.egg-info/requires.txt +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client.egg-info/top_level.txt +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/pyproject.toml +0 -0
- {lollms_client-0.26.0 → lollms_client-0.27.0}/setup.cfg +0 -0
|
@@ -6,9 +6,9 @@ from lollms_client.lollms_personality import LollmsPersonality
|
|
|
6
6
|
from lollms_client.lollms_utilities import PromptReshaper # Keep general utilities
|
|
7
7
|
# Import new MCP binding classes
|
|
8
8
|
from lollms_client.lollms_mcp_binding import LollmsMCPBinding, LollmsMCPBindingManager
|
|
9
|
+
from lollms_client.lollms_llm_binding import LollmsLLMBindingManager
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
__version__ = "0.26.0" # Updated version
|
|
11
|
+
__version__ = "0.27.0" # Updated version
|
|
12
12
|
|
|
13
13
|
# Optionally, you could define __all__ if you want to be explicit about exports
|
|
14
14
|
__all__ = [
|
|
@@ -21,5 +21,6 @@ __all__ = [
|
|
|
21
21
|
"LollmsDataManager",
|
|
22
22
|
"PromptReshaper",
|
|
23
23
|
"LollmsMCPBinding", # Export LollmsMCPBinding ABC
|
|
24
|
+
"LollmsLLMBindingManager",
|
|
24
25
|
"LollmsMCPBindingManager", # Export LollmsMCPBindingManager
|
|
25
26
|
]
|
|
@@ -329,7 +329,7 @@ class ClaudeBinding(LollmsLLMBinding):
|
|
|
329
329
|
# Note: count_tokens doesn't use a system prompt, so it's safe.
|
|
330
330
|
# However, for consistency, we could add one if needed by the logic.
|
|
331
331
|
# For now, this is fine as it only counts user content tokens.
|
|
332
|
-
response = self.client.count_tokens( # Changed from messages.count_tokens to top-level client method
|
|
332
|
+
response = self.client.messages.count_tokens( # Changed from messages.count_tokens to top-level client method
|
|
333
333
|
model=self.model_name,
|
|
334
334
|
messages=[{"role": "user", "content": text}]
|
|
335
335
|
)
|
|
@@ -0,0 +1,536 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import os
|
|
3
|
+
import json
|
|
4
|
+
import requests
|
|
5
|
+
from io import BytesIO
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Optional, Callable, List, Union, Dict
|
|
8
|
+
|
|
9
|
+
from lollms_client.lollms_discussion import LollmsDiscussion, LollmsMessage
|
|
10
|
+
from lollms_client.lollms_llm_binding import LollmsLLMBinding
|
|
11
|
+
from lollms_client.lollms_types import MSG_TYPE
|
|
12
|
+
from ascii_colors import ASCIIColors, trace_exception
|
|
13
|
+
|
|
14
|
+
import pipmaster as pm
|
|
15
|
+
|
|
16
|
+
# Ensure the required packages are installed
|
|
17
|
+
pm.ensure_packages(["requests", "pillow", "tiktoken"])
|
|
18
|
+
|
|
19
|
+
from PIL import Image, ImageDraw
|
|
20
|
+
import tiktoken
|
|
21
|
+
|
|
22
|
+
BindingName = "GrokBinding"
|
|
23
|
+
|
|
24
|
+
# API Endpoint
|
|
25
|
+
GROK_API_BASE_URL = "https://api.x.ai/v1"
|
|
26
|
+
|
|
27
|
+
# A hardcoded list to be used as a fallback if the API call fails
|
|
28
|
+
_FALLBACK_MODELS = [
|
|
29
|
+
{'model_name': 'grok-1', 'display_name': 'Grok 1', 'description': 'The flagship conversational model from xAI.', 'owned_by': 'xAI'},
|
|
30
|
+
{'model_name': 'grok-1.5', 'display_name': 'Grok 1.5', 'description': 'The latest multimodal model from xAI.', 'owned_by': 'xAI'},
|
|
31
|
+
{'model_name': 'grok-1.5-vision-preview', 'display_name': 'Grok 1.5 Vision (Preview)', 'description': 'Multimodal model with vision capabilities (preview).', 'owned_by': 'xAI'},
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
# Helper to check if a string is a valid path to an image
|
|
35
|
+
def is_image_path(path_str: str) -> bool:
|
|
36
|
+
try:
|
|
37
|
+
p = Path(path_str)
|
|
38
|
+
return p.is_file() and p.suffix.lower() in ['.png', '.jpg', '.jpeg', '.gif', '.bmp', '.webp']
|
|
39
|
+
except Exception:
|
|
40
|
+
return False
|
|
41
|
+
|
|
42
|
+
# Helper to get image media type for base64 URI
|
|
43
|
+
def get_media_type_for_uri(image_path: Union[str, Path]) -> str:
|
|
44
|
+
path = Path(image_path)
|
|
45
|
+
ext = path.suffix.lower()
|
|
46
|
+
if ext == ".jpg" or ext == ".jpeg":
|
|
47
|
+
return "image/jpeg"
|
|
48
|
+
elif ext == ".png":
|
|
49
|
+
return "image/png"
|
|
50
|
+
elif ext == ".gif":
|
|
51
|
+
return "image/gif"
|
|
52
|
+
elif ext == ".webp":
|
|
53
|
+
return "image/webp"
|
|
54
|
+
else:
|
|
55
|
+
# Default to PNG as it's lossless and widely supported
|
|
56
|
+
return "image/png"
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class GrokBinding(LollmsLLMBinding):
|
|
60
|
+
"""xAI Grok-specific binding implementation."""
|
|
61
|
+
|
|
62
|
+
def __init__(self,
|
|
63
|
+
host_address: str = None, # Ignored, for compatibility
|
|
64
|
+
model_name: str = "grok-1.5-vision-preview",
|
|
65
|
+
service_key: str = None,
|
|
66
|
+
verify_ssl_certificate: bool = True, # Ignored, for compatibility
|
|
67
|
+
**kwargs
|
|
68
|
+
):
|
|
69
|
+
"""
|
|
70
|
+
Initialize the Grok binding.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
model_name (str): Name of the Grok model to use.
|
|
74
|
+
service_key (str): xAI API key.
|
|
75
|
+
"""
|
|
76
|
+
super().__init__(binding_name=BindingName)
|
|
77
|
+
self.model_name = model_name
|
|
78
|
+
self.service_key = service_key
|
|
79
|
+
self.base_url = kwargs.get("base_url", GROK_API_BASE_URL)
|
|
80
|
+
self._cached_models: Optional[List[Dict[str, str]]] = None
|
|
81
|
+
|
|
82
|
+
if not self.service_key:
|
|
83
|
+
self.service_key = os.getenv("XAI_API_KEY")
|
|
84
|
+
|
|
85
|
+
if not self.service_key:
|
|
86
|
+
raise ValueError("xAI API key is required. Please set it via the 'service_key' parameter or the XAI_API_KEY environment variable.")
|
|
87
|
+
|
|
88
|
+
self.headers = {
|
|
89
|
+
"Authorization": f"Bearer {self.service_key}",
|
|
90
|
+
"Content-Type": "application/json"
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
def _construct_parameters(self,
|
|
94
|
+
temperature: float,
|
|
95
|
+
top_p: float,
|
|
96
|
+
n_predict: int) -> Dict[str, any]:
|
|
97
|
+
"""Builds a parameters dictionary for the Grok API."""
|
|
98
|
+
params = {"stream": True} # Always stream from the API
|
|
99
|
+
if temperature is not None: params['temperature'] = float(temperature)
|
|
100
|
+
if top_p is not None: params['top_p'] = top_p
|
|
101
|
+
# Grok has a model-specific max_tokens, but we can request less.
|
|
102
|
+
if n_predict is not None: params['max_tokens'] = n_predict
|
|
103
|
+
return params
|
|
104
|
+
|
|
105
|
+
def _process_and_handle_stream(self,
|
|
106
|
+
response: requests.Response,
|
|
107
|
+
stream: bool,
|
|
108
|
+
streaming_callback: Optional[Callable[[str, MSG_TYPE], None]]
|
|
109
|
+
) -> Union[str, dict]:
|
|
110
|
+
"""Helper to process streaming responses from the API."""
|
|
111
|
+
full_response_text = ""
|
|
112
|
+
|
|
113
|
+
try:
|
|
114
|
+
for line in response.iter_lines():
|
|
115
|
+
if line:
|
|
116
|
+
decoded_line = line.decode('utf-8')
|
|
117
|
+
if decoded_line.startswith('data: '):
|
|
118
|
+
json_str = decoded_line[len('data: '):]
|
|
119
|
+
if json_str.strip() == "[DONE]":
|
|
120
|
+
break
|
|
121
|
+
try:
|
|
122
|
+
chunk = json.loads(json_str)
|
|
123
|
+
if chunk['choices']:
|
|
124
|
+
delta = chunk['choices'][0].get('delta', {})
|
|
125
|
+
content = delta.get('content', '')
|
|
126
|
+
if content:
|
|
127
|
+
full_response_text += content
|
|
128
|
+
if stream and streaming_callback:
|
|
129
|
+
if not streaming_callback(content, MSG_TYPE.MSG_TYPE_CHUNK):
|
|
130
|
+
# Stop streaming if the callback returns False
|
|
131
|
+
return full_response_text
|
|
132
|
+
except json.JSONDecodeError:
|
|
133
|
+
ASCIIColors.warning(f"Could not decode JSON chunk: {json_str}")
|
|
134
|
+
continue
|
|
135
|
+
|
|
136
|
+
# This handles both cases:
|
|
137
|
+
# - If stream=True, we have already sent chunks. We return the full string.
|
|
138
|
+
# - If stream=False, we have buffered the whole response and now return it.
|
|
139
|
+
return full_response_text
|
|
140
|
+
|
|
141
|
+
except Exception as ex:
|
|
142
|
+
error_message = f"An unexpected error occurred while processing the Grok stream: {str(ex)}"
|
|
143
|
+
trace_exception(ex)
|
|
144
|
+
return {"status": False, "error": error_message}
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def generate_text(self,
|
|
148
|
+
prompt: str,
|
|
149
|
+
images: Optional[List[str]] = None,
|
|
150
|
+
system_prompt: str = "",
|
|
151
|
+
n_predict: Optional[int] = 2048,
|
|
152
|
+
stream: Optional[bool] = False,
|
|
153
|
+
temperature: float = 0.7,
|
|
154
|
+
top_p: float = 0.9,
|
|
155
|
+
repeat_penalty: float = 1.1, # Not supported
|
|
156
|
+
repeat_last_n: int = 64, # Not supported
|
|
157
|
+
seed: Optional[int] = None, # Not supported
|
|
158
|
+
n_threads: Optional[int] = None, # Not applicable
|
|
159
|
+
ctx_size: int | None = None, # Determined by model
|
|
160
|
+
streaming_callback: Optional[Callable[[str, MSG_TYPE], None]] = None,
|
|
161
|
+
**kwargs
|
|
162
|
+
) -> Union[str, dict]:
|
|
163
|
+
"""
|
|
164
|
+
Generate text using the Grok model.
|
|
165
|
+
"""
|
|
166
|
+
if not self.service_key:
|
|
167
|
+
return {"status": False, "error": "xAI API key not configured."}
|
|
168
|
+
|
|
169
|
+
api_params = self._construct_parameters(temperature, top_p, n_predict)
|
|
170
|
+
|
|
171
|
+
messages = []
|
|
172
|
+
if system_prompt and system_prompt.strip():
|
|
173
|
+
messages.append({"role": "system", "content": system_prompt})
|
|
174
|
+
|
|
175
|
+
user_content = []
|
|
176
|
+
if prompt and prompt.strip():
|
|
177
|
+
user_content.append({"type": "text", "text": prompt})
|
|
178
|
+
|
|
179
|
+
if images:
|
|
180
|
+
for image_data in images:
|
|
181
|
+
try:
|
|
182
|
+
if is_image_path(image_data):
|
|
183
|
+
media_type = get_media_type_for_uri(image_data)
|
|
184
|
+
with open(image_data, "rb") as image_file:
|
|
185
|
+
b64_data = base64.b64encode(image_file.read()).decode('utf-8')
|
|
186
|
+
else: # Assume it's a base64 string
|
|
187
|
+
b64_data = image_data
|
|
188
|
+
media_type = "image/png" # Assume PNG if raw base64
|
|
189
|
+
|
|
190
|
+
user_content.append({
|
|
191
|
+
"type": "image_url",
|
|
192
|
+
"image_url": {"url": f"data:{media_type};base64,{b64_data}"}
|
|
193
|
+
})
|
|
194
|
+
except Exception as e:
|
|
195
|
+
error_msg = f"Failed to process image: {e}"
|
|
196
|
+
ASCIIColors.error(error_msg)
|
|
197
|
+
return {"status": False, "error": error_msg}
|
|
198
|
+
|
|
199
|
+
if not user_content:
|
|
200
|
+
if stream and streaming_callback:
|
|
201
|
+
streaming_callback("", MSG_TYPE.MSG_TYPE_FINISHED_MESSAGE)
|
|
202
|
+
return ""
|
|
203
|
+
|
|
204
|
+
messages.append({"role": "user", "content": user_content})
|
|
205
|
+
|
|
206
|
+
payload = {
|
|
207
|
+
"model": self.model_name,
|
|
208
|
+
"messages": messages,
|
|
209
|
+
**api_params
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
try:
|
|
213
|
+
response = requests.post(
|
|
214
|
+
f"{self.base_url}/chat/completions",
|
|
215
|
+
headers=self.headers,
|
|
216
|
+
json=payload,
|
|
217
|
+
stream=True # We always use the streaming endpoint
|
|
218
|
+
)
|
|
219
|
+
response.raise_for_status()
|
|
220
|
+
|
|
221
|
+
return self._process_and_handle_stream(response, stream, streaming_callback)
|
|
222
|
+
|
|
223
|
+
except requests.exceptions.RequestException as ex:
|
|
224
|
+
error_message = f"Grok API request failed: {str(ex)}"
|
|
225
|
+
try: # Try to get more info from the response body
|
|
226
|
+
error_message += f"\nResponse: {ex.response.text}"
|
|
227
|
+
except:
|
|
228
|
+
pass
|
|
229
|
+
trace_exception(ex)
|
|
230
|
+
return {"status": False, "error": error_message}
|
|
231
|
+
except Exception as ex:
|
|
232
|
+
error_message = f"An unexpected error occurred with Grok API: {str(ex)}"
|
|
233
|
+
trace_exception(ex)
|
|
234
|
+
return {"status": False, "error": error_message}
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def chat(self,
|
|
238
|
+
discussion: LollmsDiscussion,
|
|
239
|
+
branch_tip_id: Optional[str] = None,
|
|
240
|
+
n_predict: Optional[int] = 2048,
|
|
241
|
+
stream: Optional[bool] = False,
|
|
242
|
+
temperature: float = 0.7,
|
|
243
|
+
top_p: float = 0.9,
|
|
244
|
+
streaming_callback: Optional[Callable[[str, MSG_TYPE], None]] = None,
|
|
245
|
+
**kwargs
|
|
246
|
+
) -> Union[str, dict]:
|
|
247
|
+
"""
|
|
248
|
+
Conduct a chat session with the Grok model using a LollmsDiscussion object.
|
|
249
|
+
"""
|
|
250
|
+
if not self.service_key:
|
|
251
|
+
return {"status": "error", "message": "xAI API key not configured."}
|
|
252
|
+
|
|
253
|
+
system_prompt = discussion.system_prompt
|
|
254
|
+
discussion_messages = discussion.get_messages(branch_tip_id)
|
|
255
|
+
|
|
256
|
+
messages = []
|
|
257
|
+
if system_prompt and system_prompt.strip():
|
|
258
|
+
messages.append({"role": "system", "content": system_prompt})
|
|
259
|
+
|
|
260
|
+
for msg in discussion_messages:
|
|
261
|
+
role = 'assistant' if msg.sender_type == "assistant" else 'user'
|
|
262
|
+
|
|
263
|
+
content_parts = []
|
|
264
|
+
if msg.content and msg.content.strip():
|
|
265
|
+
content_parts.append({"type": "text", "text": msg.content})
|
|
266
|
+
|
|
267
|
+
if msg.images:
|
|
268
|
+
for file_path in msg.images:
|
|
269
|
+
if is_image_path(file_path):
|
|
270
|
+
try:
|
|
271
|
+
media_type = get_media_type_for_uri(file_path)
|
|
272
|
+
with open(file_path, "rb") as image_file:
|
|
273
|
+
b64_data = base64.b64encode(image_file.read()).decode('utf-8')
|
|
274
|
+
content_parts.append({
|
|
275
|
+
"type": "image_url",
|
|
276
|
+
"image_url": {"url": f"data:{media_type};base64,{b64_data}"}
|
|
277
|
+
})
|
|
278
|
+
except Exception as e:
|
|
279
|
+
ASCIIColors.warning(f"Could not load image {file_path}: {e}")
|
|
280
|
+
|
|
281
|
+
# Grok API expects content to be a string for assistant, or list for user.
|
|
282
|
+
if role == 'user':
|
|
283
|
+
messages.append({'role': role, 'content': content_parts})
|
|
284
|
+
else: # assistant
|
|
285
|
+
# Assistants can't send images, so we just extract the text.
|
|
286
|
+
text_content = next((part['text'] for part in content_parts if part['type'] == 'text'), "")
|
|
287
|
+
if text_content:
|
|
288
|
+
messages.append({'role': role, 'content': text_content})
|
|
289
|
+
|
|
290
|
+
if not messages or messages[-1]['role'] != 'user':
|
|
291
|
+
return {"status": "error", "message": "Cannot start chat without a user message."}
|
|
292
|
+
|
|
293
|
+
api_params = self._construct_parameters(temperature, top_p, n_predict)
|
|
294
|
+
|
|
295
|
+
payload = {
|
|
296
|
+
"model": self.model_name,
|
|
297
|
+
"messages": messages,
|
|
298
|
+
**api_params
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
try:
|
|
302
|
+
response = requests.post(
|
|
303
|
+
f"{self.base_url}/chat/completions",
|
|
304
|
+
headers=self.headers,
|
|
305
|
+
json=payload,
|
|
306
|
+
stream=True
|
|
307
|
+
)
|
|
308
|
+
response.raise_for_status()
|
|
309
|
+
|
|
310
|
+
return self._process_and_handle_stream(response, stream, streaming_callback)
|
|
311
|
+
|
|
312
|
+
except requests.exceptions.RequestException as ex:
|
|
313
|
+
error_message = f"Grok API request failed: {str(ex)}"
|
|
314
|
+
try:
|
|
315
|
+
error_message += f"\nResponse: {ex.response.text}"
|
|
316
|
+
except:
|
|
317
|
+
pass
|
|
318
|
+
trace_exception(ex)
|
|
319
|
+
return {"status": "error", "message": error_message}
|
|
320
|
+
except Exception as ex:
|
|
321
|
+
error_message = f"An unexpected error occurred with Grok API: {str(ex)}"
|
|
322
|
+
trace_exception(ex)
|
|
323
|
+
return {"status": "error", "message": error_message}
|
|
324
|
+
|
|
325
|
+
def tokenize(self, text: str) -> list:
|
|
326
|
+
"""
|
|
327
|
+
Tokenize the input text.
|
|
328
|
+
Note: Grok doesn't expose a public tokenizer API.
|
|
329
|
+
Using tiktoken's cl100k_base for a reasonable estimate.
|
|
330
|
+
"""
|
|
331
|
+
try:
|
|
332
|
+
encoding = tiktoken.get_encoding("cl100k_base")
|
|
333
|
+
return encoding.encode(text)
|
|
334
|
+
except:
|
|
335
|
+
return list(text.encode('utf-8'))
|
|
336
|
+
|
|
337
|
+
def detokenize(self, tokens: list) -> str:
|
|
338
|
+
"""
|
|
339
|
+
Detokenize a list of tokens.
|
|
340
|
+
Note: Based on the placeholder tokenizer.
|
|
341
|
+
"""
|
|
342
|
+
try:
|
|
343
|
+
encoding = tiktoken.get_encoding("cl100k_base")
|
|
344
|
+
return encoding.decode(tokens)
|
|
345
|
+
except:
|
|
346
|
+
return bytes(tokens).decode('utf-8', errors='ignore')
|
|
347
|
+
|
|
348
|
+
def count_tokens(self, text: str) -> int:
|
|
349
|
+
"""
|
|
350
|
+
Count tokens from a text using the fallback tokenizer.
|
|
351
|
+
"""
|
|
352
|
+
return len(self.tokenize(text))
|
|
353
|
+
|
|
354
|
+
def embed(self, text: str, **kwargs) -> List[float]:
|
|
355
|
+
"""
|
|
356
|
+
Get embeddings for the input text.
|
|
357
|
+
Note: xAI does not provide a dedicated embedding model API.
|
|
358
|
+
"""
|
|
359
|
+
ASCIIColors.warning("xAI does not offer a public embedding API. This method is not implemented.")
|
|
360
|
+
raise NotImplementedError("Grok binding does not support embeddings.")
|
|
361
|
+
|
|
362
|
+
def get_model_info(self) -> dict:
|
|
363
|
+
"""Return information about the current Grok model setup."""
|
|
364
|
+
return {
|
|
365
|
+
"name": self.binding_name,
|
|
366
|
+
"host_address": self.base_url,
|
|
367
|
+
"model_name": self.model_name,
|
|
368
|
+
"supports_structured_output": False,
|
|
369
|
+
"supports_vision": "vision" in self.model_name or "grok-1.5" == self.model_name,
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
def listModels(self) -> List[Dict[str, str]]:
|
|
373
|
+
"""
|
|
374
|
+
Lists available models from the xAI API.
|
|
375
|
+
Caches the result to avoid repeated API calls.
|
|
376
|
+
Falls back to a static list if the API call fails.
|
|
377
|
+
"""
|
|
378
|
+
if self._cached_models is not None:
|
|
379
|
+
return self._cached_models
|
|
380
|
+
|
|
381
|
+
if not self.service_key:
|
|
382
|
+
ASCIIColors.warning("Cannot fetch models without an API key. Using fallback list.")
|
|
383
|
+
self._cached_models = _FALLBACK_MODELS
|
|
384
|
+
return self._cached_models
|
|
385
|
+
|
|
386
|
+
try:
|
|
387
|
+
ASCIIColors.info("Fetching available models from xAI API...")
|
|
388
|
+
response = requests.get(f"{self.base_url}/models", headers=self.headers, timeout=15)
|
|
389
|
+
response.raise_for_status()
|
|
390
|
+
|
|
391
|
+
data = response.json()
|
|
392
|
+
|
|
393
|
+
if "data" in data and isinstance(data["data"], list):
|
|
394
|
+
models_data = data["data"]
|
|
395
|
+
formatted_models = []
|
|
396
|
+
for model in models_data:
|
|
397
|
+
model_id = model.get("id")
|
|
398
|
+
if not model_id: continue
|
|
399
|
+
|
|
400
|
+
display_name = model_id.replace("-", " ").title()
|
|
401
|
+
description = f"Context: {model.get('context_window', 'N/A')} tokens."
|
|
402
|
+
|
|
403
|
+
formatted_models.append({
|
|
404
|
+
'model_name': model_id,
|
|
405
|
+
'display_name': display_name,
|
|
406
|
+
'description': description,
|
|
407
|
+
'owned_by': model.get('owned_by', 'xAI')
|
|
408
|
+
})
|
|
409
|
+
|
|
410
|
+
self._cached_models = formatted_models
|
|
411
|
+
ASCIIColors.green(f"Successfully fetched {len(self._cached_models)} models.")
|
|
412
|
+
return self._cached_models
|
|
413
|
+
else:
|
|
414
|
+
raise ValueError("API response is malformed.")
|
|
415
|
+
|
|
416
|
+
except Exception as e:
|
|
417
|
+
ASCIIColors.error(f"Failed to fetch models from xAI API: {e}")
|
|
418
|
+
ASCIIColors.warning("Using hardcoded fallback list of models.")
|
|
419
|
+
trace_exception(e)
|
|
420
|
+
self._cached_models = _FALLBACK_MODELS
|
|
421
|
+
return self._cached_models
|
|
422
|
+
|
|
423
|
+
def load_model(self, model_name: str) -> bool:
|
|
424
|
+
"""Set the model name for subsequent operations."""
|
|
425
|
+
self.model_name = model_name
|
|
426
|
+
ASCIIColors.info(f"Grok model set to: {model_name}. It will be used on the next API call.")
|
|
427
|
+
return True
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
if __name__ == '__main__':
|
|
431
|
+
# Example Usage (requires XAI_API_KEY environment variable)
|
|
432
|
+
if 'XAI_API_KEY' not in os.environ:
|
|
433
|
+
ASCIIColors.red("Error: XAI_API_KEY environment variable not set.")
|
|
434
|
+
print("Please get your key from xAI and set it as an environment variable.")
|
|
435
|
+
exit(1)
|
|
436
|
+
|
|
437
|
+
ASCIIColors.yellow("--- Testing GrokBinding ---")
|
|
438
|
+
|
|
439
|
+
# --- Configuration ---
|
|
440
|
+
test_model_name = "grok-1"
|
|
441
|
+
test_vision_model_name = "grok-1.5-vision-preview"
|
|
442
|
+
|
|
443
|
+
try:
|
|
444
|
+
# --- Initialization ---
|
|
445
|
+
ASCIIColors.cyan("\n--- Initializing Binding ---")
|
|
446
|
+
binding = GrokBinding(model_name=test_model_name)
|
|
447
|
+
ASCIIColors.green("Binding initialized successfully.")
|
|
448
|
+
|
|
449
|
+
# --- List Models ---
|
|
450
|
+
ASCIIColors.cyan("\n--- Listing Models (dynamic) ---")
|
|
451
|
+
models = binding.listModels()
|
|
452
|
+
if models:
|
|
453
|
+
ASCIIColors.green(f"Found {len(models)} models.")
|
|
454
|
+
for m in models:
|
|
455
|
+
print(f"- {m['model_name']} ({m['display_name']})")
|
|
456
|
+
else:
|
|
457
|
+
ASCIIColors.error("Failed to list models.")
|
|
458
|
+
|
|
459
|
+
# --- Count Tokens ---
|
|
460
|
+
ASCIIColors.cyan("\n--- Counting Tokens ---")
|
|
461
|
+
sample_text = "Hello, world! This is a test from the Grok binding."
|
|
462
|
+
token_count = binding.count_tokens(sample_text)
|
|
463
|
+
ASCIIColors.green(f"Token count for '{sample_text}': {token_count} (using tiktoken)")
|
|
464
|
+
|
|
465
|
+
# --- Text Generation (Non-Streaming) ---
|
|
466
|
+
ASCIIColors.cyan("\n--- Text Generation (Non-Streaming) ---")
|
|
467
|
+
prompt_text = "Explain who Elon Musk is in one sentence."
|
|
468
|
+
ASCIIColors.info(f"Prompt: {prompt_text}")
|
|
469
|
+
generated_text = binding.generate_text(prompt_text, n_predict=100, stream=False, system_prompt="Be very concise.")
|
|
470
|
+
if isinstance(generated_text, str):
|
|
471
|
+
ASCIIColors.green(f"Generated text:\n{generated_text}")
|
|
472
|
+
else:
|
|
473
|
+
ASCIIColors.error(f"Generation failed: {generated_text}")
|
|
474
|
+
|
|
475
|
+
# --- Text Generation (Streaming) ---
|
|
476
|
+
ASCIIColors.cyan("\n--- Text Generation (Streaming) ---")
|
|
477
|
+
|
|
478
|
+
full_streamed_text = ""
|
|
479
|
+
def stream_callback(chunk: str, msg_type: int):
|
|
480
|
+
ASCIIColors.green(chunk, end="", flush=True)
|
|
481
|
+
full_streamed_text += chunk
|
|
482
|
+
return True
|
|
483
|
+
|
|
484
|
+
ASCIIColors.info(f"Prompt: {prompt_text}")
|
|
485
|
+
result = binding.generate_text(prompt_text, n_predict=150, stream=True, streaming_callback=stream_callback)
|
|
486
|
+
print("\n--- End of Stream ---")
|
|
487
|
+
ASCIIColors.green(f"Full streamed text (for verification): {result}")
|
|
488
|
+
assert result == full_streamed_text
|
|
489
|
+
|
|
490
|
+
# --- Embeddings ---
|
|
491
|
+
ASCIIColors.cyan("\n--- Embeddings ---")
|
|
492
|
+
try:
|
|
493
|
+
binding.embed("This should fail.")
|
|
494
|
+
except NotImplementedError as e:
|
|
495
|
+
ASCIIColors.green(f"Successfully caught expected error for embeddings: {e}")
|
|
496
|
+
|
|
497
|
+
# --- Vision Model Test ---
|
|
498
|
+
dummy_image_path = "grok_dummy_test_image.png"
|
|
499
|
+
try:
|
|
500
|
+
available_model_names = [m['model_name'] for m in models]
|
|
501
|
+
if test_vision_model_name not in available_model_names:
|
|
502
|
+
ASCIIColors.warning(f"Vision test model '{test_vision_model_name}' not available. Skipping vision test.")
|
|
503
|
+
else:
|
|
504
|
+
img = Image.new('RGB', (250, 60), color=('red'))
|
|
505
|
+
d = ImageDraw.Draw(img)
|
|
506
|
+
d.text((10, 10), "This is a test image for Grok", fill=('white'))
|
|
507
|
+
img.save(dummy_image_path)
|
|
508
|
+
ASCIIColors.info(f"Created dummy image: {dummy_image_path}")
|
|
509
|
+
|
|
510
|
+
ASCIIColors.cyan(f"\n--- Vision Generation (using {test_vision_model_name}) ---")
|
|
511
|
+
binding.load_model(test_vision_model_name)
|
|
512
|
+
vision_prompt = "Describe this image. What does the text say?"
|
|
513
|
+
ASCIIColors.info(f"Vision Prompt: {vision_prompt} with image {dummy_image_path}")
|
|
514
|
+
|
|
515
|
+
vision_response = binding.generate_text(
|
|
516
|
+
prompt=vision_prompt,
|
|
517
|
+
images=[dummy_image_path],
|
|
518
|
+
n_predict=100,
|
|
519
|
+
stream=False
|
|
520
|
+
)
|
|
521
|
+
if isinstance(vision_response, str):
|
|
522
|
+
ASCIIColors.green(f"Vision model response: {vision_response}")
|
|
523
|
+
else:
|
|
524
|
+
ASCIIColors.error(f"Vision generation failed: {vision_response}")
|
|
525
|
+
except Exception as e:
|
|
526
|
+
ASCIIColors.error(f"Error during vision test: {e}")
|
|
527
|
+
trace_exception(e)
|
|
528
|
+
finally:
|
|
529
|
+
if os.path.exists(dummy_image_path):
|
|
530
|
+
os.remove(dummy_image_path)
|
|
531
|
+
|
|
532
|
+
except Exception as e:
|
|
533
|
+
ASCIIColors.error(f"An error occurred during testing: {e}")
|
|
534
|
+
trace_exception(e)
|
|
535
|
+
|
|
536
|
+
ASCIIColors.yellow("\nGrokBinding test finished.")
|
|
@@ -879,7 +879,7 @@ Don't forget encapsulate the code inside a html code tag. This is mandatory.
|
|
|
879
879
|
|
|
880
880
|
formatted_agent_history = "No actions taken yet in this turn."
|
|
881
881
|
if agent_work_history:
|
|
882
|
-
history_parts = [ f"### Step {i+1}:\n**Thought
|
|
882
|
+
history_parts = [ f"### Step {i+1}:\n**Thought:**\n{entry['thought']}\n**Action:** Called tool `{entry['tool_name']}` with parameters `{json.dumps(entry['tool_params'])}`\n**Observation (Tool Output):**\n```json\n{json.dumps(entry['tool_result'], indent=2)}\n```" for i, entry in enumerate(agent_work_history)]
|
|
883
883
|
formatted_agent_history = "\n\n".join(history_parts)
|
|
884
884
|
|
|
885
885
|
llm_decision = None
|
|
@@ -1580,8 +1580,8 @@ Provide your response as a single JSON object inside a JSON markdown tag. Use th
|
|
|
1580
1580
|
|
|
1581
1581
|
# Add the new put_code_in_buffer tool definition
|
|
1582
1582
|
available_tools.append({
|
|
1583
|
-
"name": "
|
|
1584
|
-
"description": """Generates and stores code into a buffer to be used by another tool. You can put the uuid of the generated code into the fields that require long code among the tools. If no tool requires code as input do not use
|
|
1583
|
+
"name": "put_code_in_buffer",
|
|
1584
|
+
"description": """Generates and stores code into a buffer to be used by another tool. You can put the uuid of the generated code into the fields that require long code among the tools. If no tool requires code as input do not use put_code_in_buffer. put_code_in_buffer do not execute the code nor does it audit it.""",
|
|
1585
1585
|
"input_schema": {"type": "object", "properties": {"prompt": {"type": "string", "description": "A detailed natural language description of the code's purpose and requirements."}, "language": {"type": "string", "description": "The programming language of the generated code. By default it uses python."}}, "required": ["prompt"]}
|
|
1586
1586
|
})
|
|
1587
1587
|
# Add the new refactor_scratchpad tool definition
|
|
@@ -1658,7 +1658,7 @@ Provide your response as a single JSON object inside a JSON markdown tag. Use th
|
|
|
1658
1658
|
|
|
1659
1659
|
|
|
1660
1660
|
current_scratchpad += f"\n\n### Step {i+1}: Thought\n{thought}"
|
|
1661
|
-
log_event(f"**Thought
|
|
1661
|
+
log_event(f"**Thought**:\n{thought}", MSG_TYPE.MSG_TYPE_THOUGHT_CONTENT)
|
|
1662
1662
|
|
|
1663
1663
|
if not tool_name:
|
|
1664
1664
|
# Handle error...
|
|
@@ -1918,6 +1918,97 @@ Do not split the code in multiple tags.
|
|
|
1918
1918
|
|
|
1919
1919
|
return code_content # Return the (potentially completed) code content or None
|
|
1920
1920
|
|
|
1921
|
+
def generate_structured_content(
|
|
1922
|
+
self,
|
|
1923
|
+
prompt,
|
|
1924
|
+
output_format,
|
|
1925
|
+
extra_system_prompt=None,
|
|
1926
|
+
**kwargs
|
|
1927
|
+
):
|
|
1928
|
+
"""
|
|
1929
|
+
Generates structured data (a dict) from a prompt using a JSON template.
|
|
1930
|
+
|
|
1931
|
+
This method is a high-level wrapper around `generate_code`, specializing it
|
|
1932
|
+
for JSON output. It ensures the LLM sticks to a predefined structure,
|
|
1933
|
+
and then parses the output into a Python dictionary.
|
|
1934
|
+
|
|
1935
|
+
Args:
|
|
1936
|
+
prompt (str):
|
|
1937
|
+
The user's request (e.g., "Extract the name, age, and city of the person described").
|
|
1938
|
+
output_format (dict or str):
|
|
1939
|
+
A Python dictionary or a JSON string representing the desired output
|
|
1940
|
+
structure. This will be used as a template for the LLM.
|
|
1941
|
+
Example: {"name": "string", "age": "integer", "city": "string"}
|
|
1942
|
+
extra_system_prompt (str, optional):
|
|
1943
|
+
Additional instructions for the system prompt, to be appended to the
|
|
1944
|
+
main instructions. Defaults to None.
|
|
1945
|
+
**kwargs:
|
|
1946
|
+
Additional keyword arguments to be passed directly to the
|
|
1947
|
+
`generate_code` method (e.g., temperature, max_size, top_k, debug).
|
|
1948
|
+
|
|
1949
|
+
Returns:
|
|
1950
|
+
dict: The parsed JSON data as a Python dictionary, or None if
|
|
1951
|
+
generation or parsing fails.
|
|
1952
|
+
"""
|
|
1953
|
+
# 1. Validate and prepare the template string from the output_format
|
|
1954
|
+
if isinstance(output_format, dict):
|
|
1955
|
+
# Convert the dictionary to a nicely formatted JSON string for the template
|
|
1956
|
+
template_str = json.dumps(output_format, indent=2)
|
|
1957
|
+
elif isinstance(output_format, str):
|
|
1958
|
+
# Assume it's already a valid JSON string template
|
|
1959
|
+
template_str = output_format
|
|
1960
|
+
else:
|
|
1961
|
+
# It's good practice to fail early for invalid input types
|
|
1962
|
+
raise TypeError("output_format must be a dict or a JSON string.")
|
|
1963
|
+
|
|
1964
|
+
# 2. Construct a specialized system prompt for structured data generation
|
|
1965
|
+
system_prompt = (
|
|
1966
|
+
"You are a highly skilled AI assistant that processes user requests "
|
|
1967
|
+
"and returns structured data in JSON format. You must strictly adhere "
|
|
1968
|
+
"to the provided JSON template, filling in the values accurately based "
|
|
1969
|
+
"on the user's prompt. Do not add any commentary, explanations, or text "
|
|
1970
|
+
"outside of the final JSON code block. Your entire response must be a single "
|
|
1971
|
+
"valid JSON object within a markdown code block."
|
|
1972
|
+
)
|
|
1973
|
+
if extra_system_prompt:
|
|
1974
|
+
system_prompt += f"\n\nAdditional instructions:\n{extra_system_prompt}"
|
|
1975
|
+
|
|
1976
|
+
# 3. Call the underlying generate_code method with JSON-specific settings
|
|
1977
|
+
if kwargs.get('debug'):
|
|
1978
|
+
ASCIIColors.info("Generating structured content...")
|
|
1979
|
+
|
|
1980
|
+
json_string = self.generate_code(
|
|
1981
|
+
prompt=prompt,
|
|
1982
|
+
system_prompt=system_prompt,
|
|
1983
|
+
template=template_str,
|
|
1984
|
+
language="json",
|
|
1985
|
+
code_tag_format="markdown", # Sticking to markdown is generally more reliable
|
|
1986
|
+
**kwargs # Pass other params like temperature, top_k, etc.
|
|
1987
|
+
)
|
|
1988
|
+
|
|
1989
|
+
# 4. Parse the result and return
|
|
1990
|
+
if not json_string:
|
|
1991
|
+
# generate_code already logs the error, so no need for another message
|
|
1992
|
+
return None
|
|
1993
|
+
|
|
1994
|
+
if kwargs.get('debug'):
|
|
1995
|
+
ASCIIColors.info("Parsing generated JSON string...")
|
|
1996
|
+
print(f"--- Raw JSON String ---\n{json_string}\n-----------------------")
|
|
1997
|
+
|
|
1998
|
+
try:
|
|
1999
|
+
# Use the provided robust parser
|
|
2000
|
+
parsed_json = self.robust_json_parser(json_string)
|
|
2001
|
+
|
|
2002
|
+
if parsed_json is None:
|
|
2003
|
+
ASCIIColors.warning("Failed to robustly parse the generated JSON.")
|
|
2004
|
+
return None
|
|
2005
|
+
|
|
2006
|
+
return parsed_json
|
|
2007
|
+
|
|
2008
|
+
except Exception as e:
|
|
2009
|
+
ASCIIColors.error(f"An unexpected error occurred during JSON parsing: {e}")
|
|
2010
|
+
return None
|
|
2011
|
+
|
|
1921
2012
|
|
|
1922
2013
|
def extract_code_blocks(self, text: str, format: str = "markdown") -> List[dict]:
|
|
1923
2014
|
"""
|
|
@@ -374,7 +374,9 @@ class LollmsDiscussion:
|
|
|
374
374
|
object.__setattr__(self, '_message_index', None)
|
|
375
375
|
object.__setattr__(self, '_messages_to_delete_from_db', set())
|
|
376
376
|
object.__setattr__(self, '_is_db_backed', db_manager is not None)
|
|
377
|
-
|
|
377
|
+
|
|
378
|
+
object.__setattr__(self, '_system_prompt', None)
|
|
379
|
+
|
|
378
380
|
if self._is_db_backed:
|
|
379
381
|
if not db_discussion_obj and not discussion_id:
|
|
380
382
|
raise ValueError("Either discussion_id or db_discussion_obj must be provided for DB-backed discussions.")
|
|
@@ -634,6 +636,9 @@ class LollmsDiscussion:
|
|
|
634
636
|
A dictionary with 'user_message' and 'ai_message' LollmsMessage objects,
|
|
635
637
|
where the 'ai_message' will contain rich metadata if an agentic turn was used.
|
|
636
638
|
"""
|
|
639
|
+
if personality is not None:
|
|
640
|
+
object.__setattr__(self, '_system_prompt', personality.system_prompt)
|
|
641
|
+
|
|
637
642
|
if self.max_context_size is not None:
|
|
638
643
|
self.summarize_and_prune(self.max_context_size)
|
|
639
644
|
|
|
@@ -684,6 +689,7 @@ class LollmsDiscussion:
|
|
|
684
689
|
use_data_store=use_data_store,
|
|
685
690
|
max_reasoning_steps=max_reasoning_steps,
|
|
686
691
|
images=images,
|
|
692
|
+
system_prompt = self._system_prompt,
|
|
687
693
|
debug=debug, # Pass the debug flag down
|
|
688
694
|
**kwargs
|
|
689
695
|
)
|
|
@@ -880,7 +886,7 @@ class LollmsDiscussion:
|
|
|
880
886
|
return "" if format_type == "lollms_text" else []
|
|
881
887
|
|
|
882
888
|
branch = self.get_branch(branch_tip_id)
|
|
883
|
-
full_system_prompt = self.
|
|
889
|
+
full_system_prompt = self._system_prompt # Simplified for clarity
|
|
884
890
|
participants = self.participants or {}
|
|
885
891
|
|
|
886
892
|
def get_full_content(msg: 'LollmsMessage') -> str:
|
|
@@ -941,7 +947,10 @@ class LollmsDiscussion:
|
|
|
941
947
|
# --- OPENAI & OLLAMA CHAT FORMATS ---
|
|
942
948
|
messages = []
|
|
943
949
|
if full_system_prompt:
|
|
944
|
-
|
|
950
|
+
if format_type == "markdown":
|
|
951
|
+
messages.append(f"system: {full_system_prompt}")
|
|
952
|
+
else:
|
|
953
|
+
messages.append({"role": "system", "content": full_system_prompt})
|
|
945
954
|
|
|
946
955
|
for msg in branch:
|
|
947
956
|
if msg.sender_type == 'user':
|
|
@@ -1055,4 +1064,4 @@ class LollmsDiscussion:
|
|
|
1055
1064
|
self.commit()
|
|
1056
1065
|
return discussion_title
|
|
1057
1066
|
except Exception as ex:
|
|
1058
|
-
trace_exception(ex)
|
|
1067
|
+
trace_exception(ex)
|
|
@@ -302,3 +302,7 @@ class LollmsLLMBindingManager:
|
|
|
302
302
|
list[str]: List of binding names.
|
|
303
303
|
"""
|
|
304
304
|
return [binding_dir.name for binding_dir in self.llm_bindings_dir.iterdir() if binding_dir.is_dir() and (binding_dir / "__init__.py").exists()]
|
|
305
|
+
|
|
306
|
+
def get_available_bindings():
|
|
307
|
+
bindings_dir = Path(__file__).parent/"llm_bindings"
|
|
308
|
+
return [binding_dir.name for binding_dir in bindings_dir.iterdir() if binding_dir.is_dir() and (binding_dir / "__init__.py").exists()]
|
|
@@ -54,6 +54,7 @@ lollms_client/llm_bindings/__init__.py
|
|
|
54
54
|
lollms_client/llm_bindings/azure_openai/__init__.py
|
|
55
55
|
lollms_client/llm_bindings/claude/__init__.py
|
|
56
56
|
lollms_client/llm_bindings/gemini/__init__.py
|
|
57
|
+
lollms_client/llm_bindings/grok/__init__.py
|
|
57
58
|
lollms_client/llm_bindings/groq/__init__.py
|
|
58
59
|
lollms_client/llm_bindings/hugging_face_inference_api/__init__.py
|
|
59
60
|
lollms_client/llm_bindings/litellm/__init__.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lollms_client-0.26.0 → lollms_client-0.27.0}/examples/deep_analyze/deep_analyze_multiple_files.py
RENAMED
|
File without changes
|
{lollms_client-0.26.0 → lollms_client-0.27.0}/examples/function_calling_with_local_custom_mcp.py
RENAMED
|
File without changes
|
{lollms_client-0.26.0 → lollms_client-0.27.0}/examples/generate_a_benchmark_for_safe_store.py
RENAMED
|
File without changes
|
{lollms_client-0.26.0 → lollms_client-0.27.0}/examples/generate_and_speak/generate_and_speak.py
RENAMED
|
File without changes
|
{lollms_client-0.26.0 → lollms_client-0.27.0}/examples/generate_game_sfx/generate_game_fx.py
RENAMED
|
File without changes
|
{lollms_client-0.26.0 → lollms_client-0.27.0}/examples/generate_text_with_multihop_rag_example.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lollms_client-0.26.0 → lollms_client-0.27.0}/examples/mcp_examples/run_remote_mcp_example_v2.py
RENAMED
|
File without changes
|
{lollms_client-0.26.0 → lollms_client-0.27.0}/examples/mcp_examples/run_standard_mcp_example.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/llm_bindings/azure_openai/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/llm_bindings/litellm/__init__.py
RENAMED
|
File without changes
|
{lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/llm_bindings/llamacpp/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/llm_bindings/mistral/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/llm_bindings/open_router/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/llm_bindings/openllm/__init__.py
RENAMED
|
File without changes
|
{lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/llm_bindings/pythonllamacpp/__init__.py
RENAMED
|
File without changes
|
{lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/llm_bindings/tensor_rt/__init__.py
RENAMED
|
File without changes
|
{lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/llm_bindings/transformers/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/mcp_bindings/local_mcp/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/mcp_bindings/remote_mcp/__init__.py
RENAMED
|
File without changes
|
{lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/mcp_bindings/standard_mcp/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/stt_bindings/whisper/__init__.py
RENAMED
|
File without changes
|
{lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/stt_bindings/whispercpp/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/tti_bindings/diffusers/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/ttm_bindings/audiocraft/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lollms_client-0.26.0 → lollms_client-0.27.0}/lollms_client/tts_bindings/piper_tts/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|