praisonaiagents 0.0.66__py3-none-any.whl → 0.0.68__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.
@@ -421,6 +421,12 @@ class Agent:
421
421
  # Pass the entire string so LiteLLM can parse provider/model
422
422
  self.llm_instance = LLM(model=llm)
423
423
  self._using_custom_llm = True
424
+
425
+ # Ensure tools are properly accessible when using custom LLM
426
+ if tools:
427
+ logging.debug(f"Tools passed to Agent with custom LLM: {tools}")
428
+ # Store the tools for later use
429
+ self.tools = tools
424
430
  except ImportError as e:
425
431
  raise ImportError(
426
432
  "LLM features requested but dependencies not installed. "
@@ -519,9 +525,20 @@ Your Goal: {self.goal}
519
525
  """
520
526
  logging.debug(f"{self.name} executing tool {function_name} with arguments: {arguments}")
521
527
 
528
+ # Special handling for MCP tools
529
+ # Check if tools is an MCP instance with the requested function name
530
+ from ..mcp.mcp import MCP
531
+ if isinstance(self.tools, MCP):
532
+ logging.debug(f"Looking for MCP tool {function_name}")
533
+ # Check if any of the MCP tools match the function name
534
+ for mcp_tool in self.tools.runner.tools:
535
+ if hasattr(mcp_tool, 'name') and mcp_tool.name == function_name:
536
+ logging.debug(f"Found matching MCP tool: {function_name}")
537
+ return self.tools.runner.call_tool(function_name, arguments)
538
+
522
539
  # Try to find the function in the agent's tools list first
523
540
  func = None
524
- for tool in self.tools:
541
+ for tool in self.tools if isinstance(self.tools, (list, tuple)) else []:
525
542
  if (callable(tool) and getattr(tool, '__name__', '') == function_name) or \
526
543
  (inspect.isclass(tool) and tool.__name__ == function_name):
527
544
  func = tool
@@ -643,24 +660,64 @@ Your Goal: {self.goal}
643
660
  logging.warning(f"Tool {tool} not recognized")
644
661
 
645
662
  try:
646
- if stream:
647
- # Process as streaming response with formatted tools
648
- final_response = self._process_stream_response(
649
- messages,
650
- temperature,
651
- start_time,
652
- formatted_tools=formatted_tools if formatted_tools else None,
653
- reasoning_steps=reasoning_steps
654
- )
663
+ # Use the custom LLM instance if available
664
+ if self._using_custom_llm and hasattr(self, 'llm_instance'):
665
+ if stream:
666
+ # Debug logs for tool info
667
+ if formatted_tools:
668
+ logging.debug(f"Passing {len(formatted_tools)} formatted tools to LLM instance: {formatted_tools}")
669
+
670
+ # Use the LLM instance for streaming responses
671
+ final_response = self.llm_instance.get_response(
672
+ prompt=messages[1:], # Skip system message as LLM handles it separately
673
+ system_prompt=messages[0]['content'] if messages and messages[0]['role'] == 'system' else None,
674
+ temperature=temperature,
675
+ tools=formatted_tools if formatted_tools else None,
676
+ verbose=self.verbose,
677
+ markdown=self.markdown,
678
+ stream=True,
679
+ console=self.console,
680
+ execute_tool_fn=self.execute_tool,
681
+ agent_name=self.name,
682
+ agent_role=self.role,
683
+ reasoning_steps=reasoning_steps
684
+ )
685
+ else:
686
+ # Non-streaming with custom LLM
687
+ final_response = self.llm_instance.get_response(
688
+ prompt=messages[1:],
689
+ system_prompt=messages[0]['content'] if messages and messages[0]['role'] == 'system' else None,
690
+ temperature=temperature,
691
+ tools=formatted_tools if formatted_tools else None,
692
+ verbose=self.verbose,
693
+ markdown=self.markdown,
694
+ stream=False,
695
+ console=self.console,
696
+ execute_tool_fn=self.execute_tool,
697
+ agent_name=self.name,
698
+ agent_role=self.role,
699
+ reasoning_steps=reasoning_steps
700
+ )
655
701
  else:
656
- # Process as regular non-streaming response
657
- final_response = client.chat.completions.create(
658
- model=self.llm,
659
- messages=messages,
660
- temperature=temperature,
661
- tools=formatted_tools if formatted_tools else None,
662
- stream=False
663
- )
702
+ # Use the standard OpenAI client approach
703
+ if stream:
704
+ # Process as streaming response with formatted tools
705
+ final_response = self._process_stream_response(
706
+ messages,
707
+ temperature,
708
+ start_time,
709
+ formatted_tools=formatted_tools if formatted_tools else None,
710
+ reasoning_steps=reasoning_steps
711
+ )
712
+ else:
713
+ # Process as regular non-streaming response
714
+ final_response = client.chat.completions.create(
715
+ model=self.llm,
716
+ messages=messages,
717
+ temperature=temperature,
718
+ tools=formatted_tools if formatted_tools else None,
719
+ stream=False
720
+ )
664
721
 
665
722
  tool_calls = getattr(final_response.choices[0].message, 'tool_calls', None)
666
723
 
@@ -748,13 +805,26 @@ Your Goal: {self.goal}
748
805
 
749
806
  if self._using_custom_llm:
750
807
  try:
808
+ # Special handling for MCP tools when using provider/model format
809
+ tool_param = self.tools if tools is None else tools
810
+
811
+ # Convert MCP tool objects to OpenAI format if needed
812
+ if tool_param is not None:
813
+ from ..mcp.mcp import MCP
814
+ if isinstance(tool_param, MCP) and hasattr(tool_param, 'to_openai_tool'):
815
+ logging.debug("Converting MCP tool to OpenAI format")
816
+ openai_tool = tool_param.to_openai_tool()
817
+ if openai_tool:
818
+ tool_param = [openai_tool]
819
+ logging.debug(f"Converted MCP tool: {tool_param}")
820
+
751
821
  # Pass everything to LLM class
752
822
  response_text = self.llm_instance.get_response(
753
823
  prompt=prompt,
754
824
  system_prompt=f"{self.backstory}\n\nYour Role: {self.role}\n\nYour Goal: {self.goal}" if self.use_system_prompt else None,
755
825
  chat_history=self.chat_history,
756
826
  temperature=temperature,
757
- tools=self.tools if tools is None else tools,
827
+ tools=tool_param,
758
828
  output_json=output_json,
759
829
  output_pydantic=output_pydantic,
760
830
  verbose=self.verbose,
@@ -289,15 +289,21 @@ class LLM:
289
289
  if tools:
290
290
  formatted_tools = []
291
291
  for tool in tools:
292
- if callable(tool):
292
+ # Check if the tool is already in OpenAI format (e.g. from MCP.to_openai_tool())
293
+ if isinstance(tool, dict) and 'type' in tool and tool['type'] == 'function':
294
+ logging.debug(f"Using pre-formatted OpenAI tool: {tool['function']['name']}")
295
+ formatted_tools.append(tool)
296
+ elif callable(tool):
293
297
  tool_def = self._generate_tool_definition(tool.__name__)
298
+ if tool_def:
299
+ formatted_tools.append(tool_def)
294
300
  elif isinstance(tool, str):
295
301
  tool_def = self._generate_tool_definition(tool)
302
+ if tool_def:
303
+ formatted_tools.append(tool_def)
296
304
  else:
297
- continue
305
+ logging.debug(f"Skipping tool of unsupported type: {type(tool)}")
298
306
 
299
- if tool_def:
300
- formatted_tools.append(tool_def)
301
307
  if not formatted_tools:
302
308
  formatted_tools = None
303
309
 
@@ -0,0 +1,6 @@
1
+ """
2
+ Model Context Protocol (MCP) integration for PraisonAI Agents.
3
+ """
4
+ from .mcp import MCP
5
+
6
+ __all__ = ["MCP"]
@@ -4,7 +4,9 @@ import queue
4
4
  import time
5
5
  import inspect
6
6
  import shlex
7
- from typing import Dict, Any, List, Optional, Callable, Iterable, Union
7
+ import logging
8
+ import os
9
+ from typing import Any, List, Optional, Callable, Iterable, Union
8
10
  from functools import wraps, partial
9
11
 
10
12
  from mcp import ClientSession, StdioServerParameters
@@ -128,7 +130,7 @@ class MCP:
128
130
  ```
129
131
  """
130
132
 
131
- def __init__(self, command_or_string=None, args=None, *, command=None, **kwargs):
133
+ def __init__(self, command_or_string=None, args=None, *, command=None, timeout=60, debug=False, **kwargs):
132
134
  """
133
135
  Initialize the MCP connection and get tools.
134
136
 
@@ -136,8 +138,11 @@ class MCP:
136
138
  command_or_string: Either:
137
139
  - The command to run the MCP server (e.g., Python path)
138
140
  - A complete command string (e.g., "/path/to/python /path/to/app.py")
141
+ - For NPX: 'npx' command with args for smithery tools
139
142
  args: Arguments to pass to the command (when command_or_string is the command)
140
143
  command: Alternative parameter name for backward compatibility
144
+ timeout: Timeout in seconds for MCP server initialization and tool calls (default: 60)
145
+ debug: Enable debug logging for MCP operations (default: False)
141
146
  **kwargs: Additional parameters for StdioServerParameters
142
147
  """
143
148
  # Handle backward compatibility with named parameter 'command'
@@ -168,9 +173,24 @@ class MCP:
168
173
  # Wait for initialization
169
174
  if not self.runner.initialized.wait(timeout=30):
170
175
  print("Warning: MCP initialization timed out")
176
+
177
+ # Store additional parameters
178
+ self.timeout = timeout
179
+ self.debug = debug
180
+
181
+ if debug:
182
+ logging.getLogger("mcp-wrapper").setLevel(logging.DEBUG)
183
+
184
+ # Automatically detect if this is an NPX command
185
+ self.is_npx = cmd == 'npx' or (isinstance(cmd, str) and os.path.basename(cmd) == 'npx')
171
186
 
172
- # Generate tool functions immediately and store them
173
- self._tools = self._generate_tool_functions()
187
+ # For NPX-based MCP servers, use a different approach
188
+ if self.is_npx:
189
+ self._function_declarations = []
190
+ self._initialize_npx_mcp_tools(cmd, arguments)
191
+ else:
192
+ # Generate tool functions immediately and store them
193
+ self._tools = self._generate_tool_functions()
174
194
 
175
195
  def _generate_tool_functions(self) -> List[Callable]:
176
196
  """
@@ -264,6 +284,27 @@ class MCP:
264
284
 
265
285
  return wrapper
266
286
 
287
+ def _initialize_npx_mcp_tools(self, cmd, arguments):
288
+ """Initialize the NPX MCP tools by extracting tool definitions."""
289
+ try:
290
+ # For NPX tools, we'll use the same approach as regular MCP tools
291
+ # but we need to handle the initialization differently
292
+ if self.debug:
293
+ logging.debug(f"Initializing NPX MCP tools with command: {cmd} {' '.join(arguments)}")
294
+
295
+ # Generate tool functions using the regular MCP approach
296
+ self._tools = self._generate_tool_functions()
297
+
298
+ if self.debug:
299
+ logging.debug(f"Generated {len(self._tools)} NPX MCP tools")
300
+
301
+ except Exception as e:
302
+ if self.debug:
303
+ logging.error(f"Failed to initialize NPX MCP tools: {e}")
304
+ raise RuntimeError(f"Failed to initialize NPX MCP tools: {e}")
305
+
306
+
307
+
267
308
  def __iter__(self) -> Iterable[Callable]:
268
309
  """
269
310
  Allow the MCP instance to be used directly as an iterable of tools.
@@ -272,6 +313,45 @@ class MCP:
272
313
  """
273
314
  return iter(self._tools)
274
315
 
316
+ def to_openai_tool(self):
317
+ """Convert the MCP tool to an OpenAI-compatible tool definition.
318
+
319
+ This method is specifically invoked by the Agent class when using
320
+ provider/model format (e.g., "openai/gpt-4o-mini").
321
+
322
+ Returns:
323
+ dict: OpenAI-compatible tool definition
324
+ """
325
+ # For simplicity, we'll convert the first tool only if multiple exist
326
+ # More complex implementations could handle multiple tools
327
+ if not self.runner.tools:
328
+ logging.warning("No MCP tools available to convert to OpenAI format")
329
+ return None
330
+
331
+ # Get the first tool's schema
332
+ tool = self.runner.tools[0]
333
+
334
+ # Create OpenAI tool definition
335
+ parameters = {}
336
+ if hasattr(tool, 'inputSchema') and tool.inputSchema:
337
+ parameters = tool.inputSchema
338
+ else:
339
+ # Create a minimal schema if none exists
340
+ parameters = {
341
+ "type": "object",
342
+ "properties": {},
343
+ "required": []
344
+ }
345
+
346
+ return {
347
+ "type": "function",
348
+ "function": {
349
+ "name": tool.name,
350
+ "description": tool.description if hasattr(tool, 'description') else f"Call the {tool.name} tool",
351
+ "parameters": parameters
352
+ }
353
+ }
354
+
275
355
  def __del__(self):
276
356
  """Clean up resources when the object is garbage collected."""
277
357
  if hasattr(self, 'runner'):
@@ -1,11 +1,12 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: praisonaiagents
3
- Version: 0.0.66
3
+ Version: 0.0.68
4
4
  Summary: Praison AI agents for completing complex tasks with Self Reflection Agents
5
5
  Author: Mervin Praison
6
6
  Requires-Dist: pydantic
7
7
  Requires-Dist: rich
8
8
  Requires-Dist: openai
9
+ Requires-Dist: mcp==1.6.0
9
10
  Provides-Extra: memory
10
11
  Requires-Dist: chromadb>=0.5.23; extra == "memory"
11
12
  Provides-Extra: knowledge
@@ -1,7 +1,7 @@
1
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
- praisonaiagents/agent/agent.py,sha256=h3s0-1M88zujllDHnKijHmYeVihD75d-K9s2Y3IHLY4,61850
4
+ praisonaiagents/agent/agent.py,sha256=NiTUqkibCKW6Rx0HbR1peY3TtOcQl2O8XdrEV__gPlM,65805
5
5
  praisonaiagents/agent/image_agent.py,sha256=-5MXG594HVwSpFMcidt16YBp7udtik-Cp7eXlzLE1fY,8696
6
6
  praisonaiagents/agents/__init__.py,sha256=_1d6Pqyk9EoBSo7E68sKyd1jDRlN1vxvVIRpoMc0Jcw,168
7
7
  praisonaiagents/agents/agents.py,sha256=KLfSuz6nGEJM-n4xCXdtIkES6cUI-LwvMxI3R-MjrD0,37862
@@ -10,8 +10,9 @@ praisonaiagents/knowledge/__init__.py,sha256=xL1Eh-a3xsHyIcU4foOWF-JdWYIYBALJH9b
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
- praisonaiagents/llm/llm.py,sha256=6QMRW47fgFozibzaqxa3dwxlD756rMLCRjL3eNsw8QQ,74088
14
- praisonaiagents/mcp/mcp.py,sha256=xvORgbsbsdaY_j3jKCP8BH3ERoKi_ZK8zM57mobC2Ac,10488
13
+ praisonaiagents/llm/llm.py,sha256=l7Z2QjD9eFy0Zq5bwTVK7VOUHxeTyx866YWt3fS3vz8,74606
14
+ praisonaiagents/mcp/__init__.py,sha256=IkYdrAK1bDQDm_0t3Wjt63Zwv3_IJgqz84Wqz9GH2iQ,111
15
+ praisonaiagents/mcp/mcp.py,sha256=BPPf5AIPXx28PaJJqOg6T3NRyymQH9YAD-Km7Ma9-KA,13681
15
16
  praisonaiagents/memory/memory.py,sha256=I8dOTkrl1i-GgQbDcrFOsSruzJ7MiI6Ys37DK27wrUs,35537
16
17
  praisonaiagents/process/__init__.py,sha256=lkYbL7Hn5a0ldvJtkdH23vfIIZLIcanK-65C0MwaorY,52
17
18
  praisonaiagents/process/process.py,sha256=HPw84OhnKQW3EyrDkpoQu0DcpxThbrzR2hWUgwQh9Pw,59955
@@ -38,7 +39,7 @@ praisonaiagents/tools/xml_tools.py,sha256=iYTMBEk5l3L3ryQ1fkUnNVYK-Nnua2Kx2S0dxN
38
39
  praisonaiagents/tools/yaml_tools.py,sha256=uogAZrhXV9O7xvspAtcTfpKSQYL2nlOTvCQXN94-G9A,14215
39
40
  praisonaiagents/tools/yfinance_tools.py,sha256=s2PBj_1v7oQnOobo2fDbQBACEHl61ftG4beG6Z979ZE,8529
40
41
  praisonaiagents/tools/train/data/generatecot.py,sha256=H6bNh-E2hqL5MW6kX3hqZ05g9ETKN2-kudSjiuU_SD8,19403
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,,
42
+ praisonaiagents-0.0.68.dist-info/METADATA,sha256=wMy9WDu6aGcQWrQIFT1UMOfB-wcvrGqpeDwXqbQXIvM,856
43
+ praisonaiagents-0.0.68.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
44
+ praisonaiagents-0.0.68.dist-info/top_level.txt,sha256=_HsRddrJ23iDx5TTqVUVvXG2HeHBL5voshncAMDGjtA,16
45
+ praisonaiagents-0.0.68.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (76.1.0)
2
+ Generator: setuptools (78.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5