praisonaiagents 0.0.155__tar.gz → 0.0.157__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.
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/PKG-INFO +1 -1
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/llm/llm.py +193 -44
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/llm/openai_client.py +76 -14
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents.egg-info/PKG-INFO +1 -1
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/pyproject.toml +1 -1
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/README.md +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/__init__.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/_logging.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/_warning_patch.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/agent/__init__.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/agent/agent.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/agent/context_agent.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/agent/handoff.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/agent/image_agent.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/agent/router_agent.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/agents/__init__.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/agents/agents.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/agents/autoagents.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/approval.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/flow_display.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/guardrails/__init__.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/guardrails/guardrail_result.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/guardrails/llm_guardrail.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/knowledge/__init__.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/knowledge/chunking.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/knowledge/knowledge.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/llm/__init__.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/llm/model_capabilities.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/llm/model_router.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/main.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/mcp/__init__.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/mcp/mcp.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/mcp/mcp_http_stream.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/mcp/mcp_sse.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/memory/__init__.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/memory/memory.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/process/__init__.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/process/process.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/session.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/task/__init__.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/task/task.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/telemetry/__init__.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/telemetry/integration.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/telemetry/performance_cli.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/telemetry/performance_monitor.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/telemetry/performance_utils.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/telemetry/telemetry.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/telemetry/token_collector.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/telemetry/token_telemetry.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/tools/README.md +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/tools/__init__.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/tools/arxiv_tools.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/tools/calculator_tools.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/tools/csv_tools.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/tools/duckdb_tools.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/tools/duckduckgo_tools.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/tools/excel_tools.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/tools/file_tools.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/tools/json_tools.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/tools/mongodb_tools.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/tools/newspaper_tools.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/tools/pandas_tools.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/tools/python_tools.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/tools/searxng_tools.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/tools/shell_tools.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/tools/spider_tools.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/tools/test.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/tools/tools.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/tools/train/data/generatecot.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/tools/wikipedia_tools.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/tools/xml_tools.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/tools/yaml_tools.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/tools/yfinance_tools.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents.egg-info/SOURCES.txt +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents.egg-info/dependency_links.txt +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents.egg-info/requires.txt +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents.egg-info/top_level.txt +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/setup.cfg +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/tests/test-graph-memory.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/tests/test.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/tests/test_basic_agents_demo.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/tests/test_context_agent.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/tests/test_embedding_logging.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/tests/test_fix_comprehensive.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/tests/test_gemini_streaming_fix.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/tests/test_handoff_compatibility.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/tests/test_http_stream_basic.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/tests/test_llm_self_reflection_direct.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/tests/test_ollama_async_fix.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/tests/test_ollama_fix.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/tests/test_ollama_sequential_fix.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/tests/test_posthog_fixed.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/tests/test_self_reflection_comprehensive.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/tests/test_self_reflection_fix_simple.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/tests/test_self_reflection_fix_verification.py +0 -0
- {praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/tests/test_validation_feedback.py +0 -0
@@ -7,6 +7,7 @@ from typing import Any, Dict, List, Optional, Union, Literal, Callable
|
|
7
7
|
from pydantic import BaseModel
|
8
8
|
import time
|
9
9
|
import json
|
10
|
+
import xml.etree.ElementTree as ET
|
10
11
|
from ..main import (
|
11
12
|
display_error,
|
12
13
|
display_tool_call,
|
@@ -53,6 +54,9 @@ class LLM:
|
|
53
54
|
Anthropic, and others through LiteLLM.
|
54
55
|
"""
|
55
56
|
|
57
|
+
# Class-level flag for one-time logging configuration
|
58
|
+
_logging_configured = False
|
59
|
+
|
56
60
|
# Default window sizes for different models (75% of actual to be safe)
|
57
61
|
MODEL_WINDOWS = {
|
58
62
|
# OpenAI
|
@@ -103,6 +107,57 @@ class LLM:
|
|
103
107
|
# Ollama iteration threshold for summary generation
|
104
108
|
OLLAMA_SUMMARY_ITERATION_THRESHOLD = 1
|
105
109
|
|
110
|
+
@classmethod
|
111
|
+
def _configure_logging(cls):
|
112
|
+
"""Configure logging settings once for all LLM instances."""
|
113
|
+
try:
|
114
|
+
import litellm
|
115
|
+
# Disable telemetry
|
116
|
+
litellm.telemetry = False
|
117
|
+
|
118
|
+
# Set litellm options globally
|
119
|
+
litellm.set_verbose = False
|
120
|
+
litellm.success_callback = []
|
121
|
+
litellm._async_success_callback = []
|
122
|
+
litellm.callbacks = []
|
123
|
+
|
124
|
+
# Suppress all litellm debug info
|
125
|
+
litellm.suppress_debug_info = True
|
126
|
+
if hasattr(litellm, '_logging'):
|
127
|
+
litellm._logging._disable_debugging()
|
128
|
+
|
129
|
+
# Always suppress litellm's internal debug messages
|
130
|
+
logging.getLogger("litellm.utils").setLevel(logging.WARNING)
|
131
|
+
logging.getLogger("litellm.main").setLevel(logging.WARNING)
|
132
|
+
logging.getLogger("litellm.litellm_logging").setLevel(logging.WARNING)
|
133
|
+
logging.getLogger("litellm.transformation").setLevel(logging.WARNING)
|
134
|
+
|
135
|
+
# Allow httpx logging when LOGLEVEL=debug, otherwise suppress it
|
136
|
+
loglevel = os.environ.get('LOGLEVEL', 'INFO').upper()
|
137
|
+
if loglevel == 'DEBUG':
|
138
|
+
logging.getLogger("litellm.llms.custom_httpx.http_handler").setLevel(logging.INFO)
|
139
|
+
else:
|
140
|
+
logging.getLogger("litellm.llms.custom_httpx.http_handler").setLevel(logging.WARNING)
|
141
|
+
|
142
|
+
# Keep asyncio at WARNING unless explicitly in high debug mode
|
143
|
+
logging.getLogger("asyncio").setLevel(logging.WARNING)
|
144
|
+
logging.getLogger("selector_events").setLevel(logging.WARNING)
|
145
|
+
|
146
|
+
# Enable error dropping for cleaner output
|
147
|
+
litellm.drop_params = True
|
148
|
+
# Enable parameter modification for providers like Anthropic
|
149
|
+
litellm.modify_params = True
|
150
|
+
|
151
|
+
if hasattr(litellm, '_logging'):
|
152
|
+
litellm._logging._disable_debugging()
|
153
|
+
warnings.filterwarnings("ignore", category=RuntimeWarning)
|
154
|
+
|
155
|
+
cls._logging_configured = True
|
156
|
+
|
157
|
+
except ImportError:
|
158
|
+
# If litellm not installed, we'll handle it in __init__
|
159
|
+
pass
|
160
|
+
|
106
161
|
def _log_llm_config(self, method_name: str, **config):
|
107
162
|
"""Centralized debug logging for LLM configuration and parameters.
|
108
163
|
|
@@ -186,47 +241,13 @@ class LLM:
|
|
186
241
|
events: List[Any] = [],
|
187
242
|
**extra_settings
|
188
243
|
):
|
244
|
+
# Configure logging only once at the class level
|
245
|
+
if not LLM._logging_configured:
|
246
|
+
LLM._configure_logging()
|
247
|
+
|
248
|
+
# Import litellm after logging is configured
|
189
249
|
try:
|
190
250
|
import litellm
|
191
|
-
# Disable telemetry
|
192
|
-
litellm.telemetry = False
|
193
|
-
|
194
|
-
# Set litellm options globally
|
195
|
-
litellm.set_verbose = False
|
196
|
-
litellm.success_callback = []
|
197
|
-
litellm._async_success_callback = []
|
198
|
-
litellm.callbacks = []
|
199
|
-
|
200
|
-
# Suppress all litellm debug info
|
201
|
-
litellm.suppress_debug_info = True
|
202
|
-
if hasattr(litellm, '_logging'):
|
203
|
-
litellm._logging._disable_debugging()
|
204
|
-
|
205
|
-
verbose = extra_settings.get('verbose', True)
|
206
|
-
|
207
|
-
# Always suppress litellm's internal debug messages
|
208
|
-
# These are from external libraries and not useful for debugging user code
|
209
|
-
logging.getLogger("litellm.utils").setLevel(logging.WARNING)
|
210
|
-
logging.getLogger("litellm.main").setLevel(logging.WARNING)
|
211
|
-
|
212
|
-
# Allow httpx logging when LOGLEVEL=debug, otherwise suppress it
|
213
|
-
loglevel = os.environ.get('LOGLEVEL', 'INFO').upper()
|
214
|
-
if loglevel == 'DEBUG':
|
215
|
-
logging.getLogger("litellm.llms.custom_httpx.http_handler").setLevel(logging.INFO)
|
216
|
-
else:
|
217
|
-
logging.getLogger("litellm.llms.custom_httpx.http_handler").setLevel(logging.WARNING)
|
218
|
-
|
219
|
-
logging.getLogger("litellm.litellm_logging").setLevel(logging.WARNING)
|
220
|
-
logging.getLogger("litellm.transformation").setLevel(logging.WARNING)
|
221
|
-
litellm.suppress_debug_messages = True
|
222
|
-
if hasattr(litellm, '_logging'):
|
223
|
-
litellm._logging._disable_debugging()
|
224
|
-
warnings.filterwarnings("ignore", category=RuntimeWarning)
|
225
|
-
|
226
|
-
# Keep asyncio at WARNING unless explicitly in high debug mode
|
227
|
-
logging.getLogger("asyncio").setLevel(logging.WARNING)
|
228
|
-
logging.getLogger("selector_events").setLevel(logging.WARNING)
|
229
|
-
|
230
251
|
except ImportError:
|
231
252
|
raise ImportError(
|
232
253
|
"LiteLLM is required but not installed. "
|
@@ -252,22 +273,29 @@ class LLM:
|
|
252
273
|
self.base_url = base_url
|
253
274
|
self.events = events
|
254
275
|
self.extra_settings = extra_settings
|
255
|
-
self.
|
276
|
+
self._console = None # Lazy load console when needed
|
256
277
|
self.chat_history = []
|
257
|
-
self.verbose = verbose
|
278
|
+
self.verbose = extra_settings.get('verbose', True)
|
258
279
|
self.markdown = extra_settings.get('markdown', True)
|
259
280
|
self.self_reflect = extra_settings.get('self_reflect', False)
|
260
281
|
self.max_reflect = extra_settings.get('max_reflect', 3)
|
261
282
|
self.min_reflect = extra_settings.get('min_reflect', 1)
|
262
283
|
self.reasoning_steps = extra_settings.get('reasoning_steps', False)
|
263
284
|
self.metrics = extra_settings.get('metrics', False)
|
285
|
+
# Auto-detect XML tool format for known models, or allow manual override
|
286
|
+
self.xml_tool_format = extra_settings.get('xml_tool_format', 'auto')
|
264
287
|
|
265
288
|
# Token tracking
|
266
289
|
self.last_token_metrics: Optional[TokenMetrics] = None
|
267
290
|
self.session_token_metrics: Optional[TokenMetrics] = None
|
268
291
|
self.current_agent_name: Optional[str] = None
|
269
292
|
|
293
|
+
# Cache for formatted tools and messages
|
294
|
+
self._formatted_tools_cache = {}
|
295
|
+
self._max_cache_size = 100
|
296
|
+
|
270
297
|
# Enable error dropping for cleaner output
|
298
|
+
import litellm
|
271
299
|
litellm.drop_params = True
|
272
300
|
# Enable parameter modification for providers like Anthropic
|
273
301
|
litellm.modify_params = True
|
@@ -301,6 +329,14 @@ class LLM:
|
|
301
329
|
reasoning_steps=self.reasoning_steps,
|
302
330
|
extra_settings=self.extra_settings
|
303
331
|
)
|
332
|
+
|
333
|
+
@property
|
334
|
+
def console(self):
|
335
|
+
"""Lazily initialize Rich Console only when needed."""
|
336
|
+
if self._console is None:
|
337
|
+
from rich.console import Console
|
338
|
+
self._console = Console()
|
339
|
+
return self._console
|
304
340
|
|
305
341
|
def _is_ollama_provider(self) -> bool:
|
306
342
|
"""Detect if this is an Ollama provider regardless of naming convention"""
|
@@ -326,6 +362,25 @@ class LLM:
|
|
326
362
|
|
327
363
|
return False
|
328
364
|
|
365
|
+
def _is_qwen_provider(self) -> bool:
|
366
|
+
"""Detect if this is a Qwen provider"""
|
367
|
+
if not self.model:
|
368
|
+
return False
|
369
|
+
|
370
|
+
# Check for Qwen patterns in model name
|
371
|
+
model_lower = self.model.lower()
|
372
|
+
return any(pattern in model_lower for pattern in ["qwen", "qwen2", "qwen2.5"])
|
373
|
+
|
374
|
+
def _supports_xml_tool_format(self) -> bool:
|
375
|
+
"""Check if the model should use XML tool format"""
|
376
|
+
if self.xml_tool_format == 'auto':
|
377
|
+
# Auto-detect based on known models that use XML format
|
378
|
+
return self._is_qwen_provider()
|
379
|
+
elif self.xml_tool_format in [True, 'true', 'True']:
|
380
|
+
return True
|
381
|
+
else:
|
382
|
+
return False
|
383
|
+
|
329
384
|
def _generate_ollama_tool_summary(self, tool_results: List[Any], response_text: str) -> Optional[str]:
|
330
385
|
"""
|
331
386
|
Generate a summary from tool results for Ollama to prevent infinite loops.
|
@@ -625,6 +680,10 @@ class LLM:
|
|
625
680
|
if any(self.model.startswith(prefix) for prefix in ["gemini-", "gemini/"]):
|
626
681
|
return True
|
627
682
|
|
683
|
+
# Models with XML tool format support streaming with tools
|
684
|
+
if self._supports_xml_tool_format():
|
685
|
+
return True
|
686
|
+
|
628
687
|
# For other providers, default to False to be safe
|
629
688
|
# This ensures we make a single non-streaming call rather than risk
|
630
689
|
# missing tool calls or making duplicate calls
|
@@ -733,6 +792,29 @@ class LLM:
|
|
733
792
|
|
734
793
|
return fixed_schema
|
735
794
|
|
795
|
+
def _get_tools_cache_key(self, tools):
|
796
|
+
"""Generate a cache key for tools list."""
|
797
|
+
if tools is None:
|
798
|
+
return "none"
|
799
|
+
if not tools:
|
800
|
+
return "empty"
|
801
|
+
# Create a simple hash based on tool names/content
|
802
|
+
tool_parts = []
|
803
|
+
for tool in tools:
|
804
|
+
if isinstance(tool, dict) and 'type' in tool and tool['type'] == 'function':
|
805
|
+
if 'function' in tool and isinstance(tool['function'], dict) and 'name' in tool['function']:
|
806
|
+
tool_parts.append(f"openai:{tool['function']['name']}")
|
807
|
+
elif callable(tool) and hasattr(tool, '__name__'):
|
808
|
+
tool_parts.append(f"callable:{tool.__name__}")
|
809
|
+
elif isinstance(tool, str):
|
810
|
+
tool_parts.append(f"string:{tool}")
|
811
|
+
elif isinstance(tool, dict) and len(tool) == 1:
|
812
|
+
tool_name = next(iter(tool.keys()))
|
813
|
+
tool_parts.append(f"gemini:{tool_name}")
|
814
|
+
else:
|
815
|
+
tool_parts.append(f"other:{id(tool)}")
|
816
|
+
return "|".join(sorted(tool_parts))
|
817
|
+
|
736
818
|
def _format_tools_for_litellm(self, tools: Optional[List[Any]]) -> Optional[List[Dict]]:
|
737
819
|
"""Format tools for LiteLLM - handles all tool formats.
|
738
820
|
|
@@ -751,6 +833,11 @@ class LLM:
|
|
751
833
|
"""
|
752
834
|
if not tools:
|
753
835
|
return None
|
836
|
+
|
837
|
+
# Check cache first
|
838
|
+
tools_key = self._get_tools_cache_key(tools)
|
839
|
+
if tools_key in self._formatted_tools_cache:
|
840
|
+
return self._formatted_tools_cache[tools_key]
|
754
841
|
|
755
842
|
formatted_tools = []
|
756
843
|
for tool in tools:
|
@@ -808,8 +895,12 @@ class LLM:
|
|
808
895
|
except (TypeError, ValueError) as e:
|
809
896
|
logging.error(f"Tools are not JSON serializable: {e}")
|
810
897
|
return None
|
811
|
-
|
812
|
-
|
898
|
+
|
899
|
+
# Cache the formatted tools
|
900
|
+
result = formatted_tools if formatted_tools else None
|
901
|
+
if len(self._formatted_tools_cache) < self._max_cache_size:
|
902
|
+
self._formatted_tools_cache[tools_key] = result
|
903
|
+
return result
|
813
904
|
|
814
905
|
def get_response(
|
815
906
|
self,
|
@@ -956,7 +1047,7 @@ class LLM:
|
|
956
1047
|
|
957
1048
|
# Track token usage
|
958
1049
|
if self.metrics:
|
959
|
-
self._track_token_usage(final_response, model)
|
1050
|
+
self._track_token_usage(final_response, self.model)
|
960
1051
|
|
961
1052
|
# Execute callbacks and display based on verbose setting
|
962
1053
|
generation_time_val = time.time() - current_time
|
@@ -1362,6 +1453,64 @@ class LLM:
|
|
1362
1453
|
except (json.JSONDecodeError, KeyError) as e:
|
1363
1454
|
logging.debug(f"Could not parse Ollama tool call from response: {e}")
|
1364
1455
|
|
1456
|
+
# Parse tool calls from XML format in response text
|
1457
|
+
# Try for known XML models first, or fallback for any model that might output XML
|
1458
|
+
if not tool_calls and response_text and formatted_tools:
|
1459
|
+
# Check if this model is known to use XML format, or try as fallback
|
1460
|
+
should_try_xml = (self._supports_xml_tool_format() or
|
1461
|
+
# Fallback: try XML if response contains XML-like tool call tags
|
1462
|
+
'<tool_call>' in response_text)
|
1463
|
+
|
1464
|
+
if should_try_xml:
|
1465
|
+
tool_calls = []
|
1466
|
+
|
1467
|
+
# Try proper XML parsing first
|
1468
|
+
try:
|
1469
|
+
# Wrap in root element if multiple tool_call tags exist
|
1470
|
+
xml_content = f"<root>{response_text}</root>"
|
1471
|
+
root = ET.fromstring(xml_content)
|
1472
|
+
tool_call_elements = root.findall('.//tool_call')
|
1473
|
+
|
1474
|
+
for idx, element in enumerate(tool_call_elements):
|
1475
|
+
if element.text:
|
1476
|
+
try:
|
1477
|
+
tool_json = json.loads(element.text.strip())
|
1478
|
+
if isinstance(tool_json, dict) and "name" in tool_json:
|
1479
|
+
tool_calls.append({
|
1480
|
+
"id": f"tool_{iteration_count}_{idx}",
|
1481
|
+
"type": "function",
|
1482
|
+
"function": {
|
1483
|
+
"name": tool_json["name"],
|
1484
|
+
"arguments": json.dumps(tool_json.get("arguments", {}))
|
1485
|
+
}
|
1486
|
+
})
|
1487
|
+
except (json.JSONDecodeError, KeyError) as e:
|
1488
|
+
logging.debug(f"Could not parse tool call JSON: {e}")
|
1489
|
+
continue
|
1490
|
+
except ET.ParseError:
|
1491
|
+
# Fallback to regex if XML parsing fails
|
1492
|
+
tool_call_pattern = r'<tool_call>\s*(\{(?:[^{}]|{[^{}]*})*\})\s*</tool_call>'
|
1493
|
+
matches = re.findall(tool_call_pattern, response_text, re.DOTALL)
|
1494
|
+
|
1495
|
+
for idx, match in enumerate(matches):
|
1496
|
+
try:
|
1497
|
+
tool_json = json.loads(match.strip())
|
1498
|
+
if isinstance(tool_json, dict) and "name" in tool_json:
|
1499
|
+
tool_calls.append({
|
1500
|
+
"id": f"tool_{iteration_count}_{idx}",
|
1501
|
+
"type": "function",
|
1502
|
+
"function": {
|
1503
|
+
"name": tool_json["name"],
|
1504
|
+
"arguments": json.dumps(tool_json.get("arguments", {}))
|
1505
|
+
}
|
1506
|
+
})
|
1507
|
+
except (json.JSONDecodeError, KeyError) as e:
|
1508
|
+
logging.debug(f"Could not parse XML tool call: {e}")
|
1509
|
+
continue
|
1510
|
+
|
1511
|
+
if tool_calls:
|
1512
|
+
logging.debug(f"Parsed {len(tool_calls)} tool call(s) from XML format")
|
1513
|
+
|
1365
1514
|
# For Ollama, if response is empty but we have tools, prompt for tool usage
|
1366
1515
|
if self._is_ollama_provider() and (not response_text or response_text.strip() == "") and formatted_tools and iteration_count == 0:
|
1367
1516
|
messages.append({
|
@@ -230,19 +230,34 @@ class OpenAIClient:
|
|
230
230
|
f"(e.g., 'http://localhost:1234/v1') and you can use a placeholder API key by setting OPENAI_API_KEY='{LOCAL_SERVER_API_KEY_PLACEHOLDER}'"
|
231
231
|
)
|
232
232
|
|
233
|
-
# Initialize
|
234
|
-
self._sync_client =
|
233
|
+
# Initialize clients lazily
|
234
|
+
self._sync_client = None
|
235
235
|
self._async_client = None
|
236
236
|
|
237
237
|
# Set up logging
|
238
238
|
self.logger = logging.getLogger(__name__)
|
239
239
|
|
240
|
-
# Initialize console
|
241
|
-
self.
|
240
|
+
# Initialize console lazily
|
241
|
+
self._console = None
|
242
|
+
|
243
|
+
# Cache for formatted tools and fixed schemas
|
244
|
+
self._formatted_tools_cache = {}
|
245
|
+
self._fixed_schema_cache = {}
|
246
|
+
self._max_cache_size = 100
|
247
|
+
|
248
|
+
@property
|
249
|
+
def console(self):
|
250
|
+
"""Lazily initialize Rich Console only when needed."""
|
251
|
+
if self._console is None:
|
252
|
+
from rich.console import Console
|
253
|
+
self._console = Console()
|
254
|
+
return self._console
|
242
255
|
|
243
256
|
@property
|
244
257
|
def sync_client(self) -> OpenAI:
|
245
|
-
"""Get the synchronous OpenAI client."""
|
258
|
+
"""Get the synchronous OpenAI client (lazy initialization)."""
|
259
|
+
if self._sync_client is None:
|
260
|
+
self._sync_client = OpenAI(api_key=self.api_key, base_url=self.base_url)
|
246
261
|
return self._sync_client
|
247
262
|
|
248
263
|
@property
|
@@ -350,6 +365,35 @@ class OpenAIClient:
|
|
350
365
|
|
351
366
|
return fixed_schema
|
352
367
|
|
368
|
+
def _get_tools_cache_key(self, tools: List[Any]) -> str:
|
369
|
+
"""Generate a cache key for tools."""
|
370
|
+
parts = []
|
371
|
+
for tool in tools:
|
372
|
+
if isinstance(tool, dict):
|
373
|
+
# For dict tools, use sorted JSON representation
|
374
|
+
parts.append(json.dumps(tool, sort_keys=True))
|
375
|
+
elif callable(tool):
|
376
|
+
# For functions, use module.name
|
377
|
+
parts.append(f"{tool.__module__}.{tool.__name__}")
|
378
|
+
elif isinstance(tool, str):
|
379
|
+
# For string tools, use as-is
|
380
|
+
parts.append(tool)
|
381
|
+
elif isinstance(tool, list):
|
382
|
+
# For lists, recursively process
|
383
|
+
subparts = []
|
384
|
+
for subtool in tool:
|
385
|
+
if isinstance(subtool, dict):
|
386
|
+
subparts.append(json.dumps(subtool, sort_keys=True))
|
387
|
+
elif callable(subtool):
|
388
|
+
subparts.append(f"{subtool.__module__}.{subtool.__name__}")
|
389
|
+
else:
|
390
|
+
subparts.append(str(subtool))
|
391
|
+
parts.append(f"[{','.join(subparts)}]")
|
392
|
+
else:
|
393
|
+
# For other types, use string representation
|
394
|
+
parts.append(str(tool))
|
395
|
+
return "|".join(parts)
|
396
|
+
|
353
397
|
def format_tools(self, tools: Optional[List[Any]]) -> Optional[List[Dict]]:
|
354
398
|
"""
|
355
399
|
Format tools for OpenAI API.
|
@@ -370,6 +414,11 @@ class OpenAIClient:
|
|
370
414
|
"""
|
371
415
|
if not tools:
|
372
416
|
return None
|
417
|
+
|
418
|
+
# Check cache first
|
419
|
+
cache_key = self._get_tools_cache_key(tools)
|
420
|
+
if cache_key in self._formatted_tools_cache:
|
421
|
+
return self._formatted_tools_cache[cache_key]
|
373
422
|
|
374
423
|
formatted_tools = []
|
375
424
|
for tool in tools:
|
@@ -424,8 +473,13 @@ class OpenAIClient:
|
|
424
473
|
except (TypeError, ValueError) as e:
|
425
474
|
logging.error(f"Tools are not JSON serializable: {e}")
|
426
475
|
return None
|
476
|
+
|
477
|
+
# Cache the result
|
478
|
+
result = formatted_tools if formatted_tools else None
|
479
|
+
if result is not None and len(self._formatted_tools_cache) < self._max_cache_size:
|
480
|
+
self._formatted_tools_cache[cache_key] = result
|
427
481
|
|
428
|
-
return
|
482
|
+
return result
|
429
483
|
|
430
484
|
def _generate_tool_definition(self, func: Callable) -> Optional[Dict]:
|
431
485
|
"""Generate a tool definition from a callable function."""
|
@@ -546,7 +600,7 @@ class OpenAIClient:
|
|
546
600
|
console = self.console
|
547
601
|
|
548
602
|
# Create the response stream
|
549
|
-
response_stream = self.
|
603
|
+
response_stream = self.sync_client.chat.completions.create(
|
550
604
|
model=model,
|
551
605
|
messages=messages,
|
552
606
|
temperature=temperature,
|
@@ -723,7 +777,7 @@ class OpenAIClient:
|
|
723
777
|
params["tool_choice"] = tool_choice
|
724
778
|
|
725
779
|
try:
|
726
|
-
return self.
|
780
|
+
return self.sync_client.chat.completions.create(**params)
|
727
781
|
except Exception as e:
|
728
782
|
self.logger.error(f"Error creating completion: {e}")
|
729
783
|
raise
|
@@ -1173,7 +1227,7 @@ class OpenAIClient:
|
|
1173
1227
|
while iteration_count < max_iterations:
|
1174
1228
|
try:
|
1175
1229
|
# Create streaming response
|
1176
|
-
response_stream = self.
|
1230
|
+
response_stream = self.sync_client.chat.completions.create(
|
1177
1231
|
model=model,
|
1178
1232
|
messages=messages,
|
1179
1233
|
temperature=temperature,
|
@@ -1298,7 +1352,7 @@ class OpenAIClient:
|
|
1298
1352
|
Parsed response according to the response_format
|
1299
1353
|
"""
|
1300
1354
|
try:
|
1301
|
-
response = self.
|
1355
|
+
response = self.sync_client.beta.chat.completions.parse(
|
1302
1356
|
model=model,
|
1303
1357
|
messages=messages,
|
1304
1358
|
temperature=temperature,
|
@@ -1346,14 +1400,14 @@ class OpenAIClient:
|
|
1346
1400
|
|
1347
1401
|
def close(self):
|
1348
1402
|
"""Close the OpenAI clients."""
|
1349
|
-
if hasattr(self._sync_client, 'close'):
|
1403
|
+
if self._sync_client and hasattr(self._sync_client, 'close'):
|
1350
1404
|
self._sync_client.close()
|
1351
1405
|
if self._async_client and hasattr(self._async_client, 'close'):
|
1352
1406
|
self._async_client.close()
|
1353
1407
|
|
1354
1408
|
async def aclose(self):
|
1355
1409
|
"""Asynchronously close the OpenAI clients."""
|
1356
|
-
if hasattr(self._sync_client, 'close'):
|
1410
|
+
if self._sync_client and hasattr(self._sync_client, 'close'):
|
1357
1411
|
await asyncio.to_thread(self._sync_client.close)
|
1358
1412
|
if self._async_client and hasattr(self._async_client, 'aclose'):
|
1359
1413
|
await self._async_client.aclose()
|
@@ -1361,6 +1415,7 @@ class OpenAIClient:
|
|
1361
1415
|
|
1362
1416
|
# Global client instance (similar to main.py pattern)
|
1363
1417
|
_global_client = None
|
1418
|
+
_global_client_params = None
|
1364
1419
|
|
1365
1420
|
def get_openai_client(api_key: Optional[str] = None, base_url: Optional[str] = None) -> OpenAIClient:
|
1366
1421
|
"""
|
@@ -1373,9 +1428,16 @@ def get_openai_client(api_key: Optional[str] = None, base_url: Optional[str] = N
|
|
1373
1428
|
Returns:
|
1374
1429
|
OpenAIClient instance
|
1375
1430
|
"""
|
1376
|
-
global _global_client
|
1431
|
+
global _global_client, _global_client_params
|
1432
|
+
|
1433
|
+
# Normalize parameters for comparison
|
1434
|
+
normalized_api_key = api_key or os.getenv("OPENAI_API_KEY")
|
1435
|
+
normalized_base_url = base_url
|
1436
|
+
current_params = (normalized_api_key, normalized_base_url)
|
1377
1437
|
|
1378
|
-
if
|
1438
|
+
# Only create new client if parameters changed or first time
|
1439
|
+
if _global_client is None or _global_client_params != current_params:
|
1379
1440
|
_global_client = OpenAIClient(api_key=api_key, base_url=base_url)
|
1441
|
+
_global_client_params = current_params
|
1380
1442
|
|
1381
1443
|
return _global_client
|
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
|
File without changes
|
File without changes
|
{praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/guardrails/guardrail_result.py
RENAMED
File without changes
|
{praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/guardrails/llm_guardrail.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/llm/model_capabilities.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
|
File without changes
|
{praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/telemetry/integration.py
RENAMED
File without changes
|
{praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/telemetry/performance_cli.py
RENAMED
File without changes
|
{praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/telemetry/performance_monitor.py
RENAMED
File without changes
|
{praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/telemetry/performance_utils.py
RENAMED
File without changes
|
File without changes
|
{praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/telemetry/token_collector.py
RENAMED
File without changes
|
{praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/telemetry/token_telemetry.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/tools/calculator_tools.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
{praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/tools/duckduckgo_tools.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/tools/newspaper_tools.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
|
{praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/tools/train/data/generatecot.py
RENAMED
File without changes
|
{praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents/tools/wikipedia_tools.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/praisonaiagents.egg-info/dependency_links.txt
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
|
{praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/tests/test_llm_self_reflection_direct.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/tests/test_self_reflection_comprehensive.py
RENAMED
File without changes
|
{praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/tests/test_self_reflection_fix_simple.py
RENAMED
File without changes
|
{praisonaiagents-0.0.155 → praisonaiagents-0.0.157}/tests/test_self_reflection_fix_verification.py
RENAMED
File without changes
|
File without changes
|