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.
@@ -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
 
@@ -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._track_token_usage(final_response, model)
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
- self._track_token_usage(final_response, self.model)
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
- self._track_token_usage(final_response, self.model)
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.transport:
481
- await self.transport.__aexit__(None, None, None)
482
- if hasattr(self, '_session_context') and self._session_context:
483
- await self._session_context.__aexit__(None, None, None)
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 hasattr(self, 'transport') and self.transport and not getattr(self.transport, '_closed', False):
488
- try:
489
- # Use the global event loop for non-blocking cleanup
490
- loop = get_event_loop()
491
- if not loop.is_closed():
492
- # Schedule cleanup without blocking
493
- asyncio.run_coroutine_threadsafe(self.aclose(), loop)
494
- except Exception:
495
- # Silently ignore cleanup errors to avoid impacting performance
496
- pass
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
- # Simple, lightweight cleanup
502
- if hasattr(self, 'transport') and self.transport:
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: praisonaiagents
3
- Version: 0.0.153
3
+ Version: 0.0.154
4
4
  Summary: Praison AI agents for completing complex tasks with Self Reflection Agents
5
5
  Author: Mervin Praison
6
6
  Requires-Python: >=3.10
@@ -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=VXRMG1xwog1x_YP71AXvFT7p5Ik4kXU0016AEQMzdHc,143988
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=HFN5pJUogqeCYcDk1b4JJe6VXbEFMXyQBZ-byKaObVQ,176229
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=3lCzZWdY6_X6fgQjQWDsB9_bB2vxdKsUs2PFmNt98Pw,19763
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.153.dist-info/METADATA,sha256=XSemYARB8KbWxIROYpBGh21HcmzkANfMVG7aBt2frsc,2146
71
- praisonaiagents-0.0.153.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
72
- praisonaiagents-0.0.153.dist-info/top_level.txt,sha256=_HsRddrJ23iDx5TTqVUVvXG2HeHBL5voshncAMDGjtA,16
73
- praisonaiagents-0.0.153.dist-info/RECORD,,
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,,