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.
- praisonaiagents/__init__.py +3 -1
- praisonaiagents/agents/agents.py +48 -6
- praisonaiagents/mcp/mcp.py +278 -0
- {praisonaiagents-0.0.64.dist-info → praisonaiagents-0.0.66.dist-info}/METADATA +1 -1
- {praisonaiagents-0.0.64.dist-info → praisonaiagents-0.0.66.dist-info}/RECORD +7 -6
- {praisonaiagents-0.0.64.dist-info → praisonaiagents-0.0.66.dist-info}/WHEEL +1 -1
- {praisonaiagents-0.0.64.dist-info → praisonaiagents-0.0.66.dist-info}/top_level.txt +0 -0
praisonaiagents/__init__.py
CHANGED
@@ -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
|
]
|
praisonaiagents/agents/agents.py
CHANGED
@@ -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
|
-
|
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
|
-
|
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,16 +1,17 @@
|
|
1
|
-
praisonaiagents/__init__.py,sha256=
|
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=
|
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.
|
41
|
-
praisonaiagents-0.0.
|
42
|
-
praisonaiagents-0.0.
|
43
|
-
praisonaiagents-0.0.
|
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,,
|
File without changes
|