praisonaiagents 0.0.64__py3-none-any.whl → 0.0.66__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.
@@ -10,6 +10,7 @@ from .tools.tools import Tools
10
10
  from .agents.autoagents import AutoAgents
11
11
  from .knowledge.knowledge import Knowledge
12
12
  from .knowledge.chunking import Chunking
13
+ from .mcp.mcp import MCP
13
14
  from .main import (
14
15
  TaskOutput,
15
16
  ReflectionOutput,
@@ -51,5 +52,6 @@ __all__ = [
51
52
  'sync_display_callbacks',
52
53
  'async_display_callbacks',
53
54
  'Knowledge',
54
- 'Chunking'
55
+ 'Chunking',
56
+ 'MCP'
55
57
  ]
@@ -477,8 +477,14 @@ Context:
477
477
  else:
478
478
  self.run_task(task_id)
479
479
 
480
- async def astart(self, content=None, **kwargs):
481
- """Async version of start method"""
480
+ async def astart(self, content=None, return_dict=False, **kwargs):
481
+ """Async version of start method
482
+
483
+ Args:
484
+ content: Optional content to add to all tasks' context
485
+ return_dict: If True, returns the full results dictionary instead of only the final response
486
+ **kwargs: Additional arguments
487
+ """
482
488
  if content:
483
489
  # Add content to context of all tasks
484
490
  for task in self.tasks.values():
@@ -488,10 +494,25 @@ Context:
488
494
  task.context.append(content)
489
495
 
490
496
  await self.arun_all_tasks()
491
- return {
497
+
498
+ # Get results
499
+ results = {
492
500
  "task_status": self.get_all_tasks_status(),
493
501
  "task_results": {task_id: self.get_task_result(task_id) for task_id in self.tasks}
494
502
  }
503
+
504
+ # By default, return only the final agent's response
505
+ if not return_dict:
506
+ # Get the last task (assuming sequential processing)
507
+ task_ids = list(self.tasks.keys())
508
+ if task_ids:
509
+ last_task_id = task_ids[-1]
510
+ last_result = self.get_task_result(last_task_id)
511
+ if last_result:
512
+ return last_result.raw
513
+
514
+ # Return full results dict if return_dict is True or if no final result was found
515
+ return results
495
516
 
496
517
  def save_output_to_file(self, task, task_output):
497
518
  if task.output_file:
@@ -801,8 +822,14 @@ Context:
801
822
  return str(agent[0])
802
823
  return None
803
824
 
804
- def start(self, content=None, **kwargs):
805
- """Start agent execution with optional content and config"""
825
+ def start(self, content=None, return_dict=False, **kwargs):
826
+ """Start agent execution with optional content and config
827
+
828
+ Args:
829
+ content: Optional content to add to all tasks' context
830
+ return_dict: If True, returns the full results dictionary instead of only the final response
831
+ **kwargs: Additional arguments
832
+ """
806
833
  if content:
807
834
  # Add content to context of all tasks
808
835
  for task in self.tasks.values():
@@ -815,10 +842,25 @@ Context:
815
842
 
816
843
  # Run tasks as before
817
844
  self.run_all_tasks()
818
- return {
845
+
846
+ # Get results
847
+ results = {
819
848
  "task_status": self.get_all_tasks_status(),
820
849
  "task_results": {task_id: self.get_task_result(task_id) for task_id in self.tasks}
821
850
  }
851
+
852
+ # By default, return only the final agent's response
853
+ if not return_dict:
854
+ # Get the last task (assuming sequential processing)
855
+ task_ids = list(self.tasks.keys())
856
+ if task_ids:
857
+ last_task_id = task_ids[-1]
858
+ last_result = self.get_task_result(last_task_id)
859
+ if last_result:
860
+ return last_result.raw
861
+
862
+ # Return full results dict if return_dict is True or if no final result was found
863
+ return results
822
864
 
823
865
  def set_state(self, key: str, value: Any) -> None:
824
866
  """Set a state value"""
@@ -0,0 +1,278 @@
1
+ import asyncio
2
+ import threading
3
+ import queue
4
+ import time
5
+ import inspect
6
+ import shlex
7
+ from typing import Dict, Any, List, Optional, Callable, Iterable, Union
8
+ from functools import wraps, partial
9
+
10
+ from mcp import ClientSession, StdioServerParameters
11
+ from mcp.client.stdio import stdio_client
12
+
13
+ class MCPToolRunner(threading.Thread):
14
+ """A dedicated thread for running MCP operations."""
15
+
16
+ def __init__(self, server_params):
17
+ super().__init__(daemon=True)
18
+ self.server_params = server_params
19
+ self.queue = queue.Queue()
20
+ self.result_queue = queue.Queue()
21
+ self.initialized = threading.Event()
22
+ self.tools = []
23
+ self.start()
24
+
25
+ def run(self):
26
+ """Main thread function that processes MCP requests."""
27
+ asyncio.run(self._run_async())
28
+
29
+ async def _run_async(self):
30
+ """Async entry point for MCP operations."""
31
+ try:
32
+ # Set up MCP session
33
+ async with stdio_client(self.server_params) as (read, write):
34
+ async with ClientSession(read, write) as session:
35
+ # Initialize connection
36
+ await session.initialize()
37
+
38
+ # Get tools
39
+ tools_result = await session.list_tools()
40
+ self.tools = tools_result.tools
41
+
42
+ # Signal that initialization is complete
43
+ self.initialized.set()
44
+
45
+ # Process requests
46
+ while True:
47
+ try:
48
+ # Check for new requests
49
+ try:
50
+ item = self.queue.get(block=False)
51
+ if item is None: # Shutdown signal
52
+ break
53
+
54
+ tool_name, arguments = item
55
+ try:
56
+ result = await session.call_tool(tool_name, arguments)
57
+ self.result_queue.put((True, result))
58
+ except Exception as e:
59
+ self.result_queue.put((False, str(e)))
60
+ except queue.Empty:
61
+ pass
62
+
63
+ # Give other tasks a chance to run
64
+ await asyncio.sleep(0.01)
65
+ except asyncio.CancelledError:
66
+ break
67
+ except Exception as e:
68
+ self.initialized.set() # Ensure we don't hang
69
+ self.result_queue.put((False, f"MCP initialization error: {str(e)}"))
70
+
71
+ def call_tool(self, tool_name, arguments):
72
+ """Call an MCP tool and wait for the result."""
73
+ if not self.initialized.is_set():
74
+ self.initialized.wait(timeout=30)
75
+ if not self.initialized.is_set():
76
+ return "Error: MCP initialization timed out"
77
+
78
+ # Put request in queue
79
+ self.queue.put((tool_name, arguments))
80
+
81
+ # Wait for result
82
+ success, result = self.result_queue.get()
83
+ if not success:
84
+ return f"Error: {result}"
85
+
86
+ # Process result
87
+ if hasattr(result, 'content') and result.content:
88
+ if hasattr(result.content[0], 'text'):
89
+ return result.content[0].text
90
+ return str(result.content[0])
91
+ return str(result)
92
+
93
+ def shutdown(self):
94
+ """Signal the thread to shut down."""
95
+ self.queue.put(None)
96
+
97
+
98
+ class MCP:
99
+ """
100
+ Model Context Protocol (MCP) integration for PraisonAI Agents.
101
+
102
+ This class provides a simple way to connect to MCP servers and use their tools
103
+ within PraisonAI agents.
104
+
105
+ Example:
106
+ ```python
107
+ from praisonaiagents import Agent
108
+ from praisonaiagents.mcp import MCP
109
+
110
+ # Method 1: Using command and args separately
111
+ agent = Agent(
112
+ instructions="You are a helpful assistant...",
113
+ llm="gpt-4o-mini",
114
+ tools=MCP(
115
+ command="/path/to/python",
116
+ args=["/path/to/app.py"]
117
+ )
118
+ )
119
+
120
+ # Method 2: Using a single command string
121
+ agent = Agent(
122
+ instructions="You are a helpful assistant...",
123
+ llm="gpt-4o-mini",
124
+ tools=MCP("/path/to/python /path/to/app.py")
125
+ )
126
+
127
+ agent.start("What is the stock price of Tesla?")
128
+ ```
129
+ """
130
+
131
+ def __init__(self, command_or_string=None, args=None, *, command=None, **kwargs):
132
+ """
133
+ Initialize the MCP connection and get tools.
134
+
135
+ Args:
136
+ command_or_string: Either:
137
+ - The command to run the MCP server (e.g., Python path)
138
+ - A complete command string (e.g., "/path/to/python /path/to/app.py")
139
+ args: Arguments to pass to the command (when command_or_string is the command)
140
+ command: Alternative parameter name for backward compatibility
141
+ **kwargs: Additional parameters for StdioServerParameters
142
+ """
143
+ # Handle backward compatibility with named parameter 'command'
144
+ if command_or_string is None and command is not None:
145
+ command_or_string = command
146
+
147
+ # Handle the single string format
148
+ if isinstance(command_or_string, str) and args is None:
149
+ # Split the string into command and args using shell-like parsing
150
+ parts = shlex.split(command_or_string)
151
+ if not parts:
152
+ raise ValueError("Empty command string")
153
+
154
+ cmd = parts[0]
155
+ arguments = parts[1:] if len(parts) > 1 else []
156
+ else:
157
+ # Use the original format with separate command and args
158
+ cmd = command_or_string
159
+ arguments = args or []
160
+
161
+ self.server_params = StdioServerParameters(
162
+ command=cmd,
163
+ args=arguments,
164
+ **kwargs
165
+ )
166
+ self.runner = MCPToolRunner(self.server_params)
167
+
168
+ # Wait for initialization
169
+ if not self.runner.initialized.wait(timeout=30):
170
+ print("Warning: MCP initialization timed out")
171
+
172
+ # Generate tool functions immediately and store them
173
+ self._tools = self._generate_tool_functions()
174
+
175
+ def _generate_tool_functions(self) -> List[Callable]:
176
+ """
177
+ Generate functions for each MCP tool.
178
+
179
+ Returns:
180
+ List[Callable]: Functions that can be used as tools
181
+ """
182
+ tool_functions = []
183
+
184
+ for tool in self.runner.tools:
185
+ wrapper = self._create_tool_wrapper(tool)
186
+ tool_functions.append(wrapper)
187
+
188
+ return tool_functions
189
+
190
+ def _create_tool_wrapper(self, tool):
191
+ """Create a wrapper function for an MCP tool."""
192
+ # Determine parameter names from the schema
193
+ param_names = []
194
+ param_annotations = {}
195
+ required_params = []
196
+
197
+ if hasattr(tool, 'inputSchema') and tool.inputSchema:
198
+ properties = tool.inputSchema.get("properties", {})
199
+ required = tool.inputSchema.get("required", [])
200
+
201
+ for name, prop in properties.items():
202
+ param_names.append(name)
203
+
204
+ # Set annotation based on property type
205
+ prop_type = prop.get("type", "string")
206
+ if prop_type == "string":
207
+ param_annotations[name] = str
208
+ elif prop_type == "integer":
209
+ param_annotations[name] = int
210
+ elif prop_type == "number":
211
+ param_annotations[name] = float
212
+ elif prop_type == "boolean":
213
+ param_annotations[name] = bool
214
+ elif prop_type == "array":
215
+ param_annotations[name] = list
216
+ elif prop_type == "object":
217
+ param_annotations[name] = dict
218
+ else:
219
+ param_annotations[name] = Any
220
+
221
+ if name in required:
222
+ required_params.append(name)
223
+
224
+ # Create the function signature
225
+ params = []
226
+ for name in param_names:
227
+ is_required = name in required_params
228
+ param = inspect.Parameter(
229
+ name=name,
230
+ kind=inspect.Parameter.POSITIONAL_OR_KEYWORD,
231
+ default=inspect.Parameter.empty if is_required else None,
232
+ annotation=param_annotations.get(name, Any)
233
+ )
234
+ params.append(param)
235
+
236
+ # Create function template to be properly decorated
237
+ def template_function(*args, **kwargs):
238
+ return None
239
+
240
+ # Create a proper function with the correct signature
241
+ template_function.__signature__ = inspect.Signature(params)
242
+ template_function.__annotations__ = param_annotations
243
+ template_function.__name__ = tool.name
244
+ template_function.__qualname__ = tool.name
245
+ template_function.__doc__ = tool.description
246
+
247
+ # Create the actual function using a decorator
248
+ @wraps(template_function)
249
+ def wrapper(*args, **kwargs):
250
+ # Map positional args to parameter names
251
+ all_args = {}
252
+ for i, arg in enumerate(args):
253
+ if i < len(param_names):
254
+ all_args[param_names[i]] = arg
255
+
256
+ # Add keyword args
257
+ all_args.update(kwargs)
258
+
259
+ # Call the tool
260
+ return self.runner.call_tool(tool.name, all_args)
261
+
262
+ # Make sure the wrapper has the correct signature for inspection
263
+ wrapper.__signature__ = inspect.Signature(params)
264
+
265
+ return wrapper
266
+
267
+ def __iter__(self) -> Iterable[Callable]:
268
+ """
269
+ Allow the MCP instance to be used directly as an iterable of tools.
270
+
271
+ This makes it possible to pass the MCP instance directly to the Agent's tools parameter.
272
+ """
273
+ return iter(self._tools)
274
+
275
+ def __del__(self):
276
+ """Clean up resources when the object is garbage collected."""
277
+ if hasattr(self, 'runner'):
278
+ self.runner.shutdown()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: praisonaiagents
3
- Version: 0.0.64
3
+ Version: 0.0.66
4
4
  Summary: Praison AI agents for completing complex tasks with Self Reflection Agents
5
5
  Author: Mervin Praison
6
6
  Requires-Dist: pydantic
@@ -1,16 +1,17 @@
1
- praisonaiagents/__init__.py,sha256=frdIvimDY-kU9j-9yXV1z4NtXypfPvyvlnac5mgBCuQ,1288
1
+ praisonaiagents/__init__.py,sha256=Z2_rSA6mYozz0r3ioUgKzl3QV8uWRDS_QaqPg2oGjqg,1324
2
2
  praisonaiagents/main.py,sha256=l29nGEbV2ReBi4szURbnH0Fk0w2F_QZTmECysyZjYcA,15066
3
3
  praisonaiagents/agent/__init__.py,sha256=j0T19TVNbfZcClvpbZDDinQxZ0oORgsMrMqx16jZ-bA,128
4
4
  praisonaiagents/agent/agent.py,sha256=h3s0-1M88zujllDHnKijHmYeVihD75d-K9s2Y3IHLY4,61850
5
5
  praisonaiagents/agent/image_agent.py,sha256=-5MXG594HVwSpFMcidt16YBp7udtik-Cp7eXlzLE1fY,8696
6
6
  praisonaiagents/agents/__init__.py,sha256=_1d6Pqyk9EoBSo7E68sKyd1jDRlN1vxvVIRpoMc0Jcw,168
7
- praisonaiagents/agents/agents.py,sha256=94YPQl-hl-EPY6-Xk2Rj9wlIs9YtiLQbsutSOXWX8QI,36156
7
+ praisonaiagents/agents/agents.py,sha256=KLfSuz6nGEJM-n4xCXdtIkES6cUI-LwvMxI3R-MjrD0,37862
8
8
  praisonaiagents/agents/autoagents.py,sha256=olYDn--rlJp-SckxILqmREkkgNlzCgEEcAUzfMj-54E,13518
9
9
  praisonaiagents/knowledge/__init__.py,sha256=xL1Eh-a3xsHyIcU4foOWF-JdWYIYBALJH9bge0Ujuto,246
10
10
  praisonaiagents/knowledge/chunking.py,sha256=FzoNY0q8MkvG4gADqk4JcRhmH3lcEHbRdonDgitQa30,6624
11
11
  praisonaiagents/knowledge/knowledge.py,sha256=fQNREDiwdoisfIxJBLVkteXgq_8Gbypfc3UaZbxf5QY,13210
12
12
  praisonaiagents/llm/__init__.py,sha256=ttPQQJQq6Tah-0updoEXDZFKWtJAM93rBWRoIgxRWO8,689
13
13
  praisonaiagents/llm/llm.py,sha256=6QMRW47fgFozibzaqxa3dwxlD756rMLCRjL3eNsw8QQ,74088
14
+ praisonaiagents/mcp/mcp.py,sha256=xvORgbsbsdaY_j3jKCP8BH3ERoKi_ZK8zM57mobC2Ac,10488
14
15
  praisonaiagents/memory/memory.py,sha256=I8dOTkrl1i-GgQbDcrFOsSruzJ7MiI6Ys37DK27wrUs,35537
15
16
  praisonaiagents/process/__init__.py,sha256=lkYbL7Hn5a0ldvJtkdH23vfIIZLIcanK-65C0MwaorY,52
16
17
  praisonaiagents/process/process.py,sha256=HPw84OhnKQW3EyrDkpoQu0DcpxThbrzR2hWUgwQh9Pw,59955
@@ -37,7 +38,7 @@ praisonaiagents/tools/xml_tools.py,sha256=iYTMBEk5l3L3ryQ1fkUnNVYK-Nnua2Kx2S0dxN
37
38
  praisonaiagents/tools/yaml_tools.py,sha256=uogAZrhXV9O7xvspAtcTfpKSQYL2nlOTvCQXN94-G9A,14215
38
39
  praisonaiagents/tools/yfinance_tools.py,sha256=s2PBj_1v7oQnOobo2fDbQBACEHl61ftG4beG6Z979ZE,8529
39
40
  praisonaiagents/tools/train/data/generatecot.py,sha256=H6bNh-E2hqL5MW6kX3hqZ05g9ETKN2-kudSjiuU_SD8,19403
40
- praisonaiagents-0.0.64.dist-info/METADATA,sha256=-tEl9Hf9UGdFXGT1K9IF2XoT4RvsA5WDrzNr1P9L5OU,830
41
- praisonaiagents-0.0.64.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
42
- praisonaiagents-0.0.64.dist-info/top_level.txt,sha256=_HsRddrJ23iDx5TTqVUVvXG2HeHBL5voshncAMDGjtA,16
43
- praisonaiagents-0.0.64.dist-info/RECORD,,
41
+ praisonaiagents-0.0.66.dist-info/METADATA,sha256=mZDRbV3kjDru4ehwjOVkMLJAKZeYq79yZB_8VjQDzUA,830
42
+ praisonaiagents-0.0.66.dist-info/WHEEL,sha256=beeZ86-EfXScwlR_HKu4SllMC9wUEj_8Z_4FJ3egI2w,91
43
+ praisonaiagents-0.0.66.dist-info/top_level.txt,sha256=_HsRddrJ23iDx5TTqVUVvXG2HeHBL5voshncAMDGjtA,16
44
+ praisonaiagents-0.0.66.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.8.2)
2
+ Generator: setuptools (76.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5