praisonaiagents 0.0.153__py3-none-any.whl → 0.0.154__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- praisonaiagents/agent/agent.py +7 -1
- praisonaiagents/llm/llm.py +10 -3
- praisonaiagents/mcp/mcp_http_stream.py +124 -17
- {praisonaiagents-0.0.153.dist-info → praisonaiagents-0.0.154.dist-info}/METADATA +1 -1
- {praisonaiagents-0.0.153.dist-info → praisonaiagents-0.0.154.dist-info}/RECORD +7 -7
- {praisonaiagents-0.0.153.dist-info → praisonaiagents-0.0.154.dist-info}/WHEEL +0 -0
- {praisonaiagents-0.0.153.dist-info → praisonaiagents-0.0.154.dist-info}/top_level.txt +0 -0
praisonaiagents/agent/agent.py
CHANGED
@@ -383,6 +383,7 @@ class Agent:
|
|
383
383
|
llm_config['base_url'] = base_url
|
384
384
|
if api_key:
|
385
385
|
llm_config['api_key'] = api_key
|
386
|
+
llm_config['metrics'] = metrics
|
386
387
|
self.llm_instance = LLM(**llm_config)
|
387
388
|
else:
|
388
389
|
# Create LLM with model string and base_url
|
@@ -390,7 +391,8 @@ class Agent:
|
|
390
391
|
self.llm_instance = LLM(
|
391
392
|
model=model_name,
|
392
393
|
base_url=base_url,
|
393
|
-
api_key=api_key
|
394
|
+
api_key=api_key,
|
395
|
+
metrics=metrics
|
394
396
|
)
|
395
397
|
self._using_custom_llm = True
|
396
398
|
except ImportError as e:
|
@@ -406,6 +408,9 @@ class Agent:
|
|
406
408
|
if api_key and 'api_key' not in llm:
|
407
409
|
llm = llm.copy()
|
408
410
|
llm['api_key'] = api_key
|
411
|
+
# Add metrics parameter
|
412
|
+
llm = llm.copy()
|
413
|
+
llm['metrics'] = metrics
|
409
414
|
self.llm_instance = LLM(**llm) # Pass all dict items as kwargs
|
410
415
|
self._using_custom_llm = True
|
411
416
|
except ImportError as e:
|
@@ -421,6 +426,7 @@ class Agent:
|
|
421
426
|
llm_params = {'model': llm}
|
422
427
|
if api_key:
|
423
428
|
llm_params['api_key'] = api_key
|
429
|
+
llm_params['metrics'] = metrics
|
424
430
|
self.llm_instance = LLM(**llm_params)
|
425
431
|
self._using_custom_llm = True
|
426
432
|
|
praisonaiagents/llm/llm.py
CHANGED
@@ -260,6 +260,7 @@ class LLM:
|
|
260
260
|
self.max_reflect = extra_settings.get('max_reflect', 3)
|
261
261
|
self.min_reflect = extra_settings.get('min_reflect', 1)
|
262
262
|
self.reasoning_steps = extra_settings.get('reasoning_steps', False)
|
263
|
+
self.metrics = extra_settings.get('metrics', False)
|
263
264
|
|
264
265
|
# Token tracking
|
265
266
|
self.last_token_metrics: Optional[TokenMetrics] = None
|
@@ -954,7 +955,8 @@ class LLM:
|
|
954
955
|
final_response = resp
|
955
956
|
|
956
957
|
# Track token usage
|
957
|
-
self.
|
958
|
+
if self.metrics:
|
959
|
+
self._track_token_usage(final_response, model)
|
958
960
|
|
959
961
|
# Execute callbacks and display based on verbose setting
|
960
962
|
generation_time_val = time.time() - current_time
|
@@ -1135,7 +1137,8 @@ class LLM:
|
|
1135
1137
|
response_text = response_content if response_content is not None else ""
|
1136
1138
|
|
1137
1139
|
# Track token usage
|
1138
|
-
|
1140
|
+
if self.metrics:
|
1141
|
+
self._track_token_usage(final_response, self.model)
|
1139
1142
|
|
1140
1143
|
# Execute callbacks and display based on verbose setting
|
1141
1144
|
if verbose and not interaction_displayed:
|
@@ -1284,7 +1287,8 @@ class LLM:
|
|
1284
1287
|
response_text = response_content if response_content is not None else ""
|
1285
1288
|
|
1286
1289
|
# Track token usage
|
1287
|
-
|
1290
|
+
if self.metrics:
|
1291
|
+
self._track_token_usage(final_response, self.model)
|
1288
1292
|
|
1289
1293
|
# Execute callbacks and display based on verbose setting
|
1290
1294
|
if verbose and not interaction_displayed:
|
@@ -2887,6 +2891,9 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
|
|
2887
2891
|
if not TokenMetrics or not _token_collector:
|
2888
2892
|
return None
|
2889
2893
|
|
2894
|
+
# Note: metrics check moved to call sites for performance
|
2895
|
+
# This method should only be called when self.metrics=True
|
2896
|
+
|
2890
2897
|
try:
|
2891
2898
|
usage = response.get("usage", {})
|
2892
2899
|
if not usage:
|
@@ -5,12 +5,14 @@ over HTTP Stream transport, implementing the Streamable HTTP transport protocol.
|
|
5
5
|
"""
|
6
6
|
|
7
7
|
import asyncio
|
8
|
+
import atexit
|
8
9
|
import logging
|
9
10
|
import threading
|
10
11
|
import inspect
|
11
12
|
import json
|
12
13
|
import time
|
13
14
|
import uuid
|
15
|
+
import weakref
|
14
16
|
from typing import List, Dict, Any, Optional, Callable, Iterable, Union
|
15
17
|
from urllib.parse import urlparse, urljoin
|
16
18
|
|
@@ -25,6 +27,10 @@ logger = logging.getLogger("mcp-http-stream")
|
|
25
27
|
# Global event loop for async operations
|
26
28
|
_event_loop = None
|
27
29
|
|
30
|
+
# Global registry of active clients for cleanup
|
31
|
+
_active_clients = weakref.WeakSet()
|
32
|
+
_cleanup_registered = False
|
33
|
+
|
28
34
|
def get_event_loop():
|
29
35
|
"""Get or create a global event loop."""
|
30
36
|
global _event_loop
|
@@ -34,6 +40,31 @@ def get_event_loop():
|
|
34
40
|
return _event_loop
|
35
41
|
|
36
42
|
|
43
|
+
def _cleanup_all_clients():
|
44
|
+
"""Clean up all active clients at program exit."""
|
45
|
+
if not _active_clients:
|
46
|
+
return
|
47
|
+
|
48
|
+
# Create a copy to avoid modification during iteration
|
49
|
+
clients_to_cleanup = list(_active_clients)
|
50
|
+
|
51
|
+
for client in clients_to_cleanup:
|
52
|
+
try:
|
53
|
+
if hasattr(client, '_force_cleanup'):
|
54
|
+
client._force_cleanup()
|
55
|
+
except Exception:
|
56
|
+
# Ignore exceptions during cleanup
|
57
|
+
pass
|
58
|
+
|
59
|
+
|
60
|
+
def _register_cleanup():
|
61
|
+
"""Register the cleanup function to run at program exit."""
|
62
|
+
global _cleanup_registered
|
63
|
+
if not _cleanup_registered:
|
64
|
+
atexit.register(_cleanup_all_clients)
|
65
|
+
_cleanup_registered = True
|
66
|
+
|
67
|
+
|
37
68
|
class HTTPStreamMCPTool:
|
38
69
|
"""A wrapper for an MCP tool that can be used with praisonaiagents."""
|
39
70
|
|
@@ -388,6 +419,7 @@ class HTTPStreamMCPClient:
|
|
388
419
|
self.session = None
|
389
420
|
self.tools = []
|
390
421
|
self.transport = None
|
422
|
+
self._closed = False
|
391
423
|
|
392
424
|
# Set up logging
|
393
425
|
if debug:
|
@@ -396,6 +428,10 @@ class HTTPStreamMCPClient:
|
|
396
428
|
# Set to WARNING by default to hide INFO messages
|
397
429
|
logger.setLevel(logging.WARNING)
|
398
430
|
|
431
|
+
# Register this client for cleanup and setup exit handler
|
432
|
+
_active_clients.add(self)
|
433
|
+
_register_cleanup()
|
434
|
+
|
399
435
|
self._initialize()
|
400
436
|
|
401
437
|
def _initialize(self):
|
@@ -456,6 +492,10 @@ class HTTPStreamMCPClient:
|
|
456
492
|
timeout=self.timeout
|
457
493
|
)
|
458
494
|
tools.append(wrapper)
|
495
|
+
|
496
|
+
# Set up cleanup finalizer now that transport and session are created
|
497
|
+
self._finalizer = weakref.finalize(self, self._static_cleanup,
|
498
|
+
self.transport, self._session_context)
|
459
499
|
|
460
500
|
return tools
|
461
501
|
|
@@ -477,30 +517,97 @@ class HTTPStreamMCPClient:
|
|
477
517
|
|
478
518
|
async def aclose(self):
|
479
519
|
"""Async cleanup method to close all resources."""
|
480
|
-
if self.
|
481
|
-
|
482
|
-
|
483
|
-
|
520
|
+
if self._closed:
|
521
|
+
return
|
522
|
+
|
523
|
+
self._closed = True
|
524
|
+
|
525
|
+
try:
|
526
|
+
if hasattr(self, '_session_context') and self._session_context:
|
527
|
+
await self._session_context.__aexit__(None, None, None)
|
528
|
+
except Exception:
|
529
|
+
pass
|
530
|
+
|
531
|
+
try:
|
532
|
+
if self.transport:
|
533
|
+
await self.transport.__aexit__(None, None, None)
|
534
|
+
except Exception:
|
535
|
+
pass
|
484
536
|
|
485
537
|
def close(self):
|
486
538
|
"""Synchronous cleanup method to close all resources."""
|
487
|
-
if
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
539
|
+
if self._closed:
|
540
|
+
return
|
541
|
+
|
542
|
+
try:
|
543
|
+
# Use the global event loop for non-blocking cleanup
|
544
|
+
loop = get_event_loop()
|
545
|
+
if not loop.is_closed():
|
546
|
+
# Schedule cleanup without blocking - add callback for fallback
|
547
|
+
future = asyncio.run_coroutine_threadsafe(self.aclose(), loop)
|
548
|
+
|
549
|
+
# Add a completion callback for fallback cleanup if async fails
|
550
|
+
def _cleanup_callback(fut):
|
551
|
+
try:
|
552
|
+
fut.result() # This will raise if aclose() failed
|
553
|
+
except Exception:
|
554
|
+
# If async cleanup failed, try force cleanup
|
555
|
+
try:
|
556
|
+
self._force_cleanup()
|
557
|
+
except Exception:
|
558
|
+
pass
|
559
|
+
|
560
|
+
future.add_done_callback(_cleanup_callback)
|
561
|
+
else:
|
562
|
+
# Event loop is closed, use force cleanup immediately
|
563
|
+
self._force_cleanup()
|
564
|
+
except Exception:
|
565
|
+
# If async scheduling fails, try force cleanup
|
566
|
+
self._force_cleanup()
|
567
|
+
|
568
|
+
def _force_cleanup(self):
|
569
|
+
"""Force cleanup of resources synchronously (for emergencies)."""
|
570
|
+
if self._closed:
|
571
|
+
return
|
572
|
+
|
573
|
+
self._closed = True
|
574
|
+
|
575
|
+
# Force close transport session if it exists
|
576
|
+
try:
|
577
|
+
if self.transport and hasattr(self.transport, '_session') and self.transport._session:
|
578
|
+
session = self.transport._session
|
579
|
+
if not session.closed:
|
580
|
+
# Force close the aiohttp session
|
581
|
+
if hasattr(session, '_connector') and session._connector:
|
582
|
+
try:
|
583
|
+
# Close connector directly
|
584
|
+
session._connector.close()
|
585
|
+
except Exception:
|
586
|
+
pass
|
587
|
+
except Exception:
|
588
|
+
pass
|
589
|
+
|
590
|
+
@staticmethod
|
591
|
+
def _static_cleanup(transport, session_context):
|
592
|
+
"""Static cleanup method for weakref finalizer."""
|
593
|
+
try:
|
594
|
+
# This is called by weakref finalizer, so we can't do async operations
|
595
|
+
# Just ensure any session is closed if possible
|
596
|
+
if transport and hasattr(transport, '_session') and transport._session:
|
597
|
+
session = transport._session
|
598
|
+
if not session.closed and hasattr(session, '_connector'):
|
599
|
+
try:
|
600
|
+
session._connector.close()
|
601
|
+
except Exception:
|
602
|
+
pass
|
603
|
+
except Exception:
|
604
|
+
pass
|
497
605
|
|
498
606
|
def __del__(self):
|
499
607
|
"""Cleanup when object is garbage collected."""
|
500
608
|
try:
|
501
|
-
|
502
|
-
|
503
|
-
self.close()
|
609
|
+
if not self._closed:
|
610
|
+
self._force_cleanup()
|
504
611
|
except Exception:
|
505
612
|
# Never raise exceptions in __del__
|
506
613
|
pass
|
@@ -6,7 +6,7 @@ praisonaiagents/flow_display.py,sha256=E84J_H3h8L-AqL_F1JzEUInQYdjmIEuNL1LZr4__H
|
|
6
6
|
praisonaiagents/main.py,sha256=NuAmE-ZrH4X0O9ysNA2AfxEQ8APPssO_ZR_f7h97QOo,17370
|
7
7
|
praisonaiagents/session.py,sha256=FHWButPBaFGA4x1U_2gImroQChHnFy231_aAa_n5KOQ,20364
|
8
8
|
praisonaiagents/agent/__init__.py,sha256=KBqW_augD-HcaV3FL88gUmhDCpwnSTavGENi7RqneTo,505
|
9
|
-
praisonaiagents/agent/agent.py,sha256=
|
9
|
+
praisonaiagents/agent/agent.py,sha256=pecp8Bt7_vXCB4MfUuMTZ3no4WipKOzFPGhFF5ADC5Y,144243
|
10
10
|
praisonaiagents/agent/context_agent.py,sha256=zNI2Waghn5eo8g3QM1Dc7ZNSr2xw41D87GIK81FjW-Y,107489
|
11
11
|
praisonaiagents/agent/handoff.py,sha256=Saq0chqfvC6Zf5UbXvmctybbehqnotrXn72JsS-76Q0,13099
|
12
12
|
praisonaiagents/agent/image_agent.py,sha256=xKDhW8T1Y3e15lQpY6N2pdvBNJmAoWDibJa4BYa-Njs,10205
|
@@ -21,13 +21,13 @@ praisonaiagents/knowledge/__init__.py,sha256=xL1Eh-a3xsHyIcU4foOWF-JdWYIYBALJH9b
|
|
21
21
|
praisonaiagents/knowledge/chunking.py,sha256=G6wyHa7_8V0_7VpnrrUXbEmUmptlT16ISJYaxmkSgmU,7678
|
22
22
|
praisonaiagents/knowledge/knowledge.py,sha256=OzK81oA6sjk9nAUWphS7AkXxvalrv2AHB4FtHjzYgxI,30115
|
23
23
|
praisonaiagents/llm/__init__.py,sha256=SqdU1pRqPrR6jZeWYyDeTvmZKCACywk0v4P0k5Fuowk,1107
|
24
|
-
praisonaiagents/llm/llm.py,sha256=
|
24
|
+
praisonaiagents/llm/llm.py,sha256=155R1XHZLSDZsq67Hmglwc4N_SE2gKgid0KCFYNX3ww,176594
|
25
25
|
praisonaiagents/llm/model_capabilities.py,sha256=cxOvZcjZ_PIEpUYKn3S2FMyypfOSfbGpx4vmV7Y5vhI,3967
|
26
26
|
praisonaiagents/llm/model_router.py,sha256=Jy2pShlkLxqXF3quz-MRB3-6L9vaUSgUrf2YJs_Tsg0,13995
|
27
27
|
praisonaiagents/llm/openai_client.py,sha256=3EVjIs3tnBNFDy_4ZxX9DJVq54kS0FMm38m5Gkpun7U,57234
|
28
28
|
praisonaiagents/mcp/__init__.py,sha256=ibbqe3_7XB7VrIcUcetkZiUZS1fTVvyMy_AqCSFG8qc,240
|
29
29
|
praisonaiagents/mcp/mcp.py,sha256=ChaSwLCcFBB9b8eNuj0DoKbK1EqpyF1T_7xz0FX-5-A,23264
|
30
|
-
praisonaiagents/mcp/mcp_http_stream.py,sha256=
|
30
|
+
praisonaiagents/mcp/mcp_http_stream.py,sha256=TDFWMJMo8VqLXtXCW73REpmkU3t9n7CAGMa9b4dhI-c,23366
|
31
31
|
praisonaiagents/mcp/mcp_sse.py,sha256=KO10tAgZ5vSKeRhkJIZcdJ0ZmhRybS39i1KybWt4D7M,9128
|
32
32
|
praisonaiagents/memory/__init__.py,sha256=aEFdhgtTqDdMhc_JCWM-f4XI9cZIj7Wz5g_MUa-0amg,397
|
33
33
|
praisonaiagents/memory/memory.py,sha256=HjanP8sSi91wifvPkQDH40uGYdDZPOeir29fCu6y-b8,64584
|
@@ -67,7 +67,7 @@ praisonaiagents/tools/xml_tools.py,sha256=iYTMBEk5l3L3ryQ1fkUnNVYK-Nnua2Kx2S0dxN
|
|
67
67
|
praisonaiagents/tools/yaml_tools.py,sha256=uogAZrhXV9O7xvspAtcTfpKSQYL2nlOTvCQXN94-G9A,14215
|
68
68
|
praisonaiagents/tools/yfinance_tools.py,sha256=s2PBj_1v7oQnOobo2fDbQBACEHl61ftG4beG6Z979ZE,8529
|
69
69
|
praisonaiagents/tools/train/data/generatecot.py,sha256=H6bNh-E2hqL5MW6kX3hqZ05g9ETKN2-kudSjiuU_SD8,19403
|
70
|
-
praisonaiagents-0.0.
|
71
|
-
praisonaiagents-0.0.
|
72
|
-
praisonaiagents-0.0.
|
73
|
-
praisonaiagents-0.0.
|
70
|
+
praisonaiagents-0.0.154.dist-info/METADATA,sha256=55cpHdKamPJ_tSTDYyXli-REfXLqafPKW7GH-Gf_Sdo,2146
|
71
|
+
praisonaiagents-0.0.154.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
72
|
+
praisonaiagents-0.0.154.dist-info/top_level.txt,sha256=_HsRddrJ23iDx5TTqVUVvXG2HeHBL5voshncAMDGjtA,16
|
73
|
+
praisonaiagents-0.0.154.dist-info/RECORD,,
|
File without changes
|
File without changes
|