praisonaiagents 0.0.67__py3-none-any.whl → 0.0.69__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
 
@@ -430,15 +436,37 @@ class LLM:
430
436
 
431
437
  # Handle tool calls
432
438
  if tool_calls and execute_tool_fn:
439
+ # Convert tool_calls to a serializable format for all providers
440
+ serializable_tool_calls = []
441
+ for tc in tool_calls:
442
+ if isinstance(tc, dict):
443
+ serializable_tool_calls.append(tc) # Already a dict
444
+ else:
445
+ # Convert object to dict
446
+ serializable_tool_calls.append({
447
+ "id": tc.id,
448
+ "type": getattr(tc, 'type', "function"),
449
+ "function": {
450
+ "name": tc.function.name,
451
+ "arguments": tc.function.arguments
452
+ }
453
+ })
433
454
  messages.append({
434
455
  "role": "assistant",
435
456
  "content": response_text,
436
- "tool_calls": tool_calls
457
+ "tool_calls": serializable_tool_calls
437
458
  })
438
459
 
439
460
  for tool_call in tool_calls:
440
- function_name = tool_call["function"]["name"]
441
- arguments = json.loads(tool_call["function"]["arguments"])
461
+ # Handle both object and dict access patterns
462
+ if isinstance(tool_call, dict):
463
+ function_name = tool_call["function"]["name"]
464
+ arguments = json.loads(tool_call["function"]["arguments"])
465
+ tool_call_id = tool_call["id"]
466
+ else:
467
+ function_name = tool_call.function.name
468
+ arguments = json.loads(tool_call.function.arguments)
469
+ tool_call_id = tool_call.id
442
470
 
443
471
  logging.debug(f"[TOOL_EXEC_DEBUG] About to execute tool {function_name} with args: {arguments}")
444
472
  tool_result = execute_tool_fn(function_name, arguments)
@@ -456,18 +484,11 @@ class LLM:
456
484
  logging.debug(f"[TOOL_EXEC_DEBUG] About to display tool call with message: {display_message}")
457
485
  display_tool_call(display_message, console=console)
458
486
 
459
- messages.append({
460
- "role": "tool",
461
- "tool_call_id": tool_call["id"],
462
- "content": json.dumps(tool_result)
463
- })
464
- else:
465
- logging.debug("[TOOL_EXEC_DEBUG] Verbose mode off, not displaying tool call")
466
- messages.append({
467
- "role": "tool",
468
- "tool_call_id": tool_call["id"],
469
- "content": "Function returned an empty output"
470
- })
487
+ messages.append({
488
+ "role": "tool",
489
+ "tool_call_id": tool_call_id,
490
+ "content": json.dumps(tool_result) if tool_result is not None else "Function returned an empty output"
491
+ })
471
492
 
472
493
  # If reasoning_steps is True, do a single non-streaming call
473
494
  if reasoning_steps:
@@ -924,15 +945,37 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
924
945
  tool_calls = tool_response.choices[0].message.get("tool_calls")
925
946
 
926
947
  if tool_calls:
948
+ # Convert tool_calls to a serializable format for all providers
949
+ serializable_tool_calls = []
950
+ for tc in tool_calls:
951
+ if isinstance(tc, dict):
952
+ serializable_tool_calls.append(tc) # Already a dict
953
+ else:
954
+ # Convert object to dict
955
+ serializable_tool_calls.append({
956
+ "id": tc.id,
957
+ "type": getattr(tc, 'type', "function"),
958
+ "function": {
959
+ "name": tc.function.name,
960
+ "arguments": tc.function.arguments
961
+ }
962
+ })
927
963
  messages.append({
928
964
  "role": "assistant",
929
965
  "content": response_text,
930
- "tool_calls": tool_calls
966
+ "tool_calls": serializable_tool_calls
931
967
  })
932
968
 
933
969
  for tool_call in tool_calls:
934
- function_name = tool_call.function.name
935
- arguments = json.loads(tool_call.function.arguments)
970
+ # Handle both object and dict access patterns
971
+ if isinstance(tool_call, dict):
972
+ function_name = tool_call["function"]["name"]
973
+ arguments = json.loads(tool_call["function"]["arguments"])
974
+ tool_call_id = tool_call["id"]
975
+ else:
976
+ function_name = tool_call.function.name
977
+ arguments = json.loads(tool_call.function.arguments)
978
+ tool_call_id = tool_call.id
936
979
 
937
980
  tool_result = await execute_tool_fn(function_name, arguments)
938
981
 
@@ -943,17 +986,11 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
943
986
  else:
944
987
  display_message += "Function returned no output"
945
988
  display_tool_call(display_message, console=console)
946
- messages.append({
947
- "role": "tool",
948
- "tool_call_id": tool_call.id,
949
- "content": json.dumps(tool_result)
950
- })
951
- else:
952
- messages.append({
953
- "role": "tool",
954
- "tool_call_id": tool_call.id,
955
- "content": "Function returned an empty output"
956
- })
989
+ messages.append({
990
+ "role": "tool",
991
+ "tool_call_id": tool_call_id,
992
+ "content": json.dumps(tool_result) if tool_result is not None else "Function returned an empty output"
993
+ })
957
994
 
958
995
  # Get response after tool calls
959
996
  response_text = ""
@@ -313,6 +313,45 @@ class MCP:
313
313
  """
314
314
  return iter(self._tools)
315
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
+
316
355
  def __del__(self):
317
356
  """Clean up resources when the object is garbage collected."""
318
357
  if hasattr(self, 'runner'):
@@ -1,11 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: praisonaiagents
3
- Version: 0.0.67
3
+ Version: 0.0.69
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,9 +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
13
+ praisonaiagents/llm/llm.py,sha256=9UhKoTcInXfCFFmQfBEzs3G0t4dJ4yoEpQ5eUG9VC0Q,76574
14
14
  praisonaiagents/mcp/__init__.py,sha256=IkYdrAK1bDQDm_0t3Wjt63Zwv3_IJgqz84Wqz9GH2iQ,111
15
- praisonaiagents/mcp/mcp.py,sha256=7EJo2Eyw89ePQzeKUzRZgW3E2ztLkMwQj2lCjFlzecQ,12281
15
+ praisonaiagents/mcp/mcp.py,sha256=BPPf5AIPXx28PaJJqOg6T3NRyymQH9YAD-Km7Ma9-KA,13681
16
16
  praisonaiagents/memory/memory.py,sha256=I8dOTkrl1i-GgQbDcrFOsSruzJ7MiI6Ys37DK27wrUs,35537
17
17
  praisonaiagents/process/__init__.py,sha256=lkYbL7Hn5a0ldvJtkdH23vfIIZLIcanK-65C0MwaorY,52
18
18
  praisonaiagents/process/process.py,sha256=HPw84OhnKQW3EyrDkpoQu0DcpxThbrzR2hWUgwQh9Pw,59955
@@ -39,7 +39,7 @@ praisonaiagents/tools/xml_tools.py,sha256=iYTMBEk5l3L3ryQ1fkUnNVYK-Nnua2Kx2S0dxN
39
39
  praisonaiagents/tools/yaml_tools.py,sha256=uogAZrhXV9O7xvspAtcTfpKSQYL2nlOTvCQXN94-G9A,14215
40
40
  praisonaiagents/tools/yfinance_tools.py,sha256=s2PBj_1v7oQnOobo2fDbQBACEHl61ftG4beG6Z979ZE,8529
41
41
  praisonaiagents/tools/train/data/generatecot.py,sha256=H6bNh-E2hqL5MW6kX3hqZ05g9ETKN2-kudSjiuU_SD8,19403
42
- praisonaiagents-0.0.67.dist-info/METADATA,sha256=sGyO2OxLcuZGGmq-2WZHg9RIvSB1tLPFOUpEst8VeU0,830
43
- praisonaiagents-0.0.67.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
44
- praisonaiagents-0.0.67.dist-info/top_level.txt,sha256=_HsRddrJ23iDx5TTqVUVvXG2HeHBL5voshncAMDGjtA,16
45
- praisonaiagents-0.0.67.dist-info/RECORD,,
42
+ praisonaiagents-0.0.69.dist-info/METADATA,sha256=ymaq8EM2AZeNlbT-ISjYgHXMHbnHpX53O_Hkcrb5KtA,856
43
+ praisonaiagents-0.0.69.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
44
+ praisonaiagents-0.0.69.dist-info/top_level.txt,sha256=_HsRddrJ23iDx5TTqVUVvXG2HeHBL5voshncAMDGjtA,16
45
+ praisonaiagents-0.0.69.dist-info/RECORD,,