diff --git a/docker/Dockerfile b/docker/Dockerfile
index 05825c1eb..0355d09c4 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -1,6 +1,6 @@
FROM python:3.11-slim
WORKDIR /app
COPY . .
-RUN pip install flask praisonai==2.0.76 gunicorn markdown
+RUN pip install flask praisonai==2.0.77 gunicorn markdown
EXPOSE 8080
CMD ["gunicorn", "-b", "0.0.0.0:8080", "api:app"]
diff --git a/docs/api/praisonai/deploy.html b/docs/api/praisonai/deploy.html
index ccba7368c..a88b1fc5e 100644
--- a/docs/api/praisonai/deploy.html
+++ b/docs/api/praisonai/deploy.html
@@ -110,7 +110,7 @@
Raises
file.write("FROM python:3.11-slim\n")
file.write("WORKDIR /app\n")
file.write("COPY . .\n")
- file.write("RUN pip install flask praisonai==2.0.76 gunicorn markdown\n")
+ file.write("RUN pip install flask praisonai==2.0.77 gunicorn markdown\n")
file.write("EXPOSE 8080\n")
file.write('CMD ["gunicorn", "-b", "0.0.0.0:8080", "api:app"]\n')
diff --git a/praisonai.rb b/praisonai.rb
index bfad617e7..0bde49f13 100644
--- a/praisonai.rb
+++ b/praisonai.rb
@@ -3,7 +3,7 @@ class Praisonai < Formula
desc "AI tools for various AI applications"
homepage "https://github.com/MervinPraison/PraisonAI"
- url "https://github.com/MervinPraison/PraisonAI/archive/refs/tags/2.0.76.tar.gz"
+ url "https://github.com/MervinPraison/PraisonAI/archive/refs/tags/2.0.77.tar.gz"
sha256 "1828fb9227d10f991522c3f24f061943a254b667196b40b1a3e4a54a8d30ce32" # Replace with actual SHA256 checksum
license "MIT"
diff --git a/praisonai/deploy.py b/praisonai/deploy.py
index a549ff054..07d18618f 100644
--- a/praisonai/deploy.py
+++ b/praisonai/deploy.py
@@ -56,7 +56,7 @@ def create_dockerfile(self):
file.write("FROM python:3.11-slim\n")
file.write("WORKDIR /app\n")
file.write("COPY . .\n")
- file.write("RUN pip install flask praisonai==2.0.76 gunicorn markdown\n")
+ file.write("RUN pip install flask praisonai==2.0.77 gunicorn markdown\n")
file.write("EXPOSE 8080\n")
file.write('CMD ["gunicorn", "-b", "0.0.0.0:8080", "api:app"]\n')
diff --git a/pyproject.toml b/pyproject.toml
index eeae44f4b..bcce0585c 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "PraisonAI"
-version = "2.0.76"
+version = "2.0.77"
description = "PraisonAI is an AI Agents Framework with Self Reflection. PraisonAI application combines PraisonAI Agents, AutoGen, and CrewAI into a low-code solution for building and managing multi-agent LLM systems, focusing on simplicity, customisation, and efficient human-agent collaboration."
readme = "README.md"
license = ""
@@ -12,7 +12,7 @@ dependencies = [
"rich>=13.7",
"markdown>=3.5",
"pyparsing>=3.0.0",
- "praisonaiagents>=0.0.61",
+ "praisonaiagents>=0.0.62",
"python-dotenv>=0.19.0",
"instructor>=1.3.3",
"PyYAML>=6.0",
@@ -84,7 +84,7 @@ autogen = ["pyautogen>=0.2.19", "praisonai-tools>=0.0.7", "crewai"]
[tool.poetry]
name = "PraisonAI"
-version = "2.0.76"
+version = "2.0.77"
description = "PraisonAI is an AI Agents Framework with Self Reflection. PraisonAI application combines PraisonAI Agents, AutoGen, and CrewAI into a low-code solution for building and managing multi-agent LLM systems, focusing on simplicity, customisation, and efficient human–agent collaboration."
authors = ["Mervin Praison"]
license = ""
@@ -102,7 +102,7 @@ python = ">=3.10,<3.13"
rich = ">=13.7"
markdown = ">=3.5"
pyparsing = ">=3.0.0"
-praisonaiagents = ">=0.0.61"
+praisonaiagents = ">=0.0.62"
python-dotenv = ">=0.19.0"
instructor = ">=1.3.3"
PyYAML = ">=6.0"
diff --git a/src/praisonai-agents/llm-tool-call.py b/src/praisonai-agents/llm-tool-call.py
new file mode 100644
index 000000000..e56c63b83
--- /dev/null
+++ b/src/praisonai-agents/llm-tool-call.py
@@ -0,0 +1,18 @@
+from praisonaiagents import Agent
+from praisonaiagents.tools import wiki_search, wiki_summary, wiki_page, wiki_random, wiki_language
+
+agent1 = Agent(
+ instructions="You are a Wikipedia Agent",
+ tools=[wiki_search, wiki_summary, wiki_page, wiki_random, wiki_language],
+ llm="openai/gpt-4o-mini",
+ verbose=10
+)
+agent1.start("history of AI in 1 line")
+
+agent2 = Agent(
+ instructions="You are a Wikipedia Agent",
+ tools=[wiki_search, wiki_summary, wiki_page, wiki_random, wiki_language],
+ llm="gpt-4o-mini",
+ verbose=10
+)
+agent2.start("history of AI in 1 line")
\ No newline at end of file
diff --git a/src/praisonai-agents/praisonaiagents/agent/agent.py b/src/praisonai-agents/praisonaiagents/agent/agent.py
index 721251185..261fc61f0 100644
--- a/src/praisonai-agents/praisonaiagents/agent/agent.py
+++ b/src/praisonai-agents/praisonaiagents/agent/agent.py
@@ -714,6 +714,22 @@ def _chat_completion(self, messages, temperature=0.2, tools=None, stream=True, r
return None
def chat(self, prompt, temperature=0.2, tools=None, output_json=None, output_pydantic=None, reasoning_steps=False):
+ # Log all parameter values when in debug mode
+ if logging.getLogger().getEffectiveLevel() == logging.DEBUG:
+ param_info = {
+ "prompt": str(prompt)[:100] + "..." if isinstance(prompt, str) and len(str(prompt)) > 100 else str(prompt),
+ "temperature": temperature,
+ "tools": [t.__name__ if hasattr(t, "__name__") else str(t) for t in tools] if tools else None,
+ "output_json": str(output_json.__class__.__name__) if output_json else None,
+ "output_pydantic": str(output_pydantic.__class__.__name__) if output_pydantic else None,
+ "reasoning_steps": reasoning_steps,
+ "agent_name": self.name,
+ "agent_role": self.role,
+ "agent_goal": self.goal
+ }
+ logging.debug(f"Agent.chat parameters: {json.dumps(param_info, indent=2, default=str)}")
+
+ start_time = time.time()
reasoning_steps = reasoning_steps or self.reasoning_steps
# Search for existing knowledge if any knowledge is provided
if self.knowledge:
@@ -738,7 +754,7 @@ def chat(self, prompt, temperature=0.2, tools=None, output_json=None, output_pyd
system_prompt=f"{self.backstory}\n\nYour Role: {self.role}\n\nYour Goal: {self.goal}" if self.use_system_prompt else None,
chat_history=self.chat_history,
temperature=temperature,
- tools=tools,
+ tools=self.tools if tools is None else tools,
output_json=output_json,
output_pydantic=output_pydantic,
verbose=self.verbose,
@@ -749,7 +765,7 @@ def chat(self, prompt, temperature=0.2, tools=None, output_json=None, output_pyd
console=self.console,
agent_name=self.name,
agent_role=self.role,
- agent_tools=[t.__name__ if hasattr(t, '__name__') else str(t) for t in self.tools],
+ agent_tools=[t.__name__ if hasattr(t, '__name__') else str(t) for t in (tools if tools is not None else self.tools)],
execute_tool_fn=self.execute_tool, # Pass tool execution function
reasoning_steps=reasoning_steps
)
@@ -757,6 +773,11 @@ def chat(self, prompt, temperature=0.2, tools=None, output_json=None, output_pyd
self.chat_history.append({"role": "user", "content": prompt})
self.chat_history.append({"role": "assistant", "content": response_text})
+ # Log completion time if in debug mode
+ if logging.getLogger().getEffectiveLevel() == logging.DEBUG:
+ total_time = time.time() - start_time
+ logging.debug(f"Agent.chat completed in {total_time:.2f} seconds")
+
return response_text
except Exception as e:
display_error(f"Error in LLM chat: {e}")
@@ -944,6 +965,13 @@ def chat(self, prompt, temperature=0.2, tools=None, output_json=None, output_pyd
display_error(f"Error in chat: {e}", console=self.console)
return None
+ # Log completion time if in debug mode
+ if logging.getLogger().getEffectiveLevel() == logging.DEBUG:
+ total_time = time.time() - start_time
+ logging.debug(f"Agent.chat completed in {total_time:.2f} seconds")
+
+ return response_text
+
def clean_json_output(self, output: str) -> str:
"""Clean and extract JSON from response text."""
cleaned = output.strip()
@@ -958,6 +986,22 @@ def clean_json_output(self, output: str) -> str:
async def achat(self, prompt: str, temperature=0.2, tools=None, output_json=None, output_pydantic=None, reasoning_steps=False):
"""Async version of chat method. TODO: Requires Syncing with chat method."""
+ # Log all parameter values when in debug mode
+ if logging.getLogger().getEffectiveLevel() == logging.DEBUG:
+ param_info = {
+ "prompt": str(prompt)[:100] + "..." if isinstance(prompt, str) and len(str(prompt)) > 100 else str(prompt),
+ "temperature": temperature,
+ "tools": [t.__name__ if hasattr(t, "__name__") else str(t) for t in tools] if tools else None,
+ "output_json": str(output_json.__class__.__name__) if output_json else None,
+ "output_pydantic": str(output_pydantic.__class__.__name__) if output_pydantic else None,
+ "reasoning_steps": reasoning_steps,
+ "agent_name": self.name,
+ "agent_role": self.role,
+ "agent_goal": self.goal
+ }
+ logging.debug(f"Agent.achat parameters: {json.dumps(param_info, indent=2, default=str)}")
+
+ start_time = time.time()
reasoning_steps = reasoning_steps or self.reasoning_steps
try:
# Search for existing knowledge if any knowledge is provided
@@ -996,9 +1040,15 @@ async def achat(self, prompt: str, temperature=0.2, tools=None, output_json=None
self.chat_history.append({"role": "user", "content": prompt})
self.chat_history.append({"role": "assistant", "content": response_text})
+ if logging.getLogger().getEffectiveLevel() == logging.DEBUG:
+ total_time = time.time() - start_time
+ logging.debug(f"Agent.achat completed in {total_time:.2f} seconds")
return response_text
except Exception as e:
display_error(f"Error in LLM chat: {e}")
+ if logging.getLogger().getEffectiveLevel() == logging.DEBUG:
+ total_time = time.time() - start_time
+ logging.debug(f"Agent.achat failed in {total_time:.2f} seconds: {str(e)}")
return None
# For OpenAI client
@@ -1081,7 +1131,11 @@ async def achat(self, prompt: str, temperature=0.2, tools=None, output_json=None
temperature=temperature,
tools=formatted_tools
)
- return await self._achat_completion(response, tools)
+ result = await self._achat_completion(response, tools)
+ if logging.getLogger().getEffectiveLevel() == logging.DEBUG:
+ total_time = time.time() - start_time
+ logging.debug(f"Agent.achat completed in {total_time:.2f} seconds")
+ return result
elif output_json or output_pydantic:
response = await async_client.chat.completions.create(
model=self.llm,
@@ -1090,6 +1144,9 @@ async def achat(self, prompt: str, temperature=0.2, tools=None, output_json=None
response_format={"type": "json_object"}
)
# Return the raw response
+ if logging.getLogger().getEffectiveLevel() == logging.DEBUG:
+ total_time = time.time() - start_time
+ logging.debug(f"Agent.achat completed in {total_time:.2f} seconds")
return response.choices[0].message.content
else:
response = await async_client.chat.completions.create(
@@ -1097,12 +1154,21 @@ async def achat(self, prompt: str, temperature=0.2, tools=None, output_json=None
messages=messages,
temperature=temperature
)
+ if logging.getLogger().getEffectiveLevel() == logging.DEBUG:
+ total_time = time.time() - start_time
+ logging.debug(f"Agent.achat completed in {total_time:.2f} seconds")
return response.choices[0].message.content
except Exception as e:
display_error(f"Error in chat completion: {e}")
+ if logging.getLogger().getEffectiveLevel() == logging.DEBUG:
+ total_time = time.time() - start_time
+ logging.debug(f"Agent.achat failed in {total_time:.2f} seconds: {str(e)}")
return None
except Exception as e:
display_error(f"Error in achat: {e}")
+ if logging.getLogger().getEffectiveLevel() == logging.DEBUG:
+ total_time = time.time() - start_time
+ logging.debug(f"Agent.achat failed in {total_time:.2f} seconds: {str(e)}")
return None
async def _achat_completion(self, response, tools, reasoning_steps=False):
diff --git a/src/praisonai-agents/praisonaiagents/llm/llm.py b/src/praisonai-agents/praisonaiagents/llm/llm.py
index 8bd7e763a..f1e126608 100644
--- a/src/praisonai-agents/praisonaiagents/llm/llm.py
+++ b/src/praisonai-agents/praisonaiagents/llm/llm.py
@@ -172,6 +172,36 @@ def __init__(
# Enable error dropping for cleaner output
litellm.drop_params = True
self._setup_event_tracking(events)
+
+ # Log all initialization parameters when in debug mode
+ if not isinstance(verbose, bool) and verbose >= 10:
+ debug_info = {
+ "model": self.model,
+ "timeout": self.timeout,
+ "temperature": self.temperature,
+ "top_p": self.top_p,
+ "n": self.n,
+ "max_tokens": self.max_tokens,
+ "presence_penalty": self.presence_penalty,
+ "frequency_penalty": self.frequency_penalty,
+ "logit_bias": self.logit_bias,
+ "response_format": self.response_format,
+ "seed": self.seed,
+ "logprobs": self.logprobs,
+ "top_logprobs": self.top_logprobs,
+ "api_version": self.api_version,
+ "stop_phrases": self.stop_phrases,
+ "api_key": "***" if self.api_key else None, # Mask API key for security
+ "base_url": self.base_url,
+ "verbose": self.verbose,
+ "markdown": self.markdown,
+ "self_reflect": self.self_reflect,
+ "max_reflect": self.max_reflect,
+ "min_reflect": self.min_reflect,
+ "reasoning_steps": self.reasoning_steps,
+ "extra_settings": {k: v for k, v in self.extra_settings.items() if k not in ["api_key"]}
+ }
+ logging.debug(f"LLM instance initialized with: {json.dumps(debug_info, indent=2, default=str)}")
def get_response(
self,
@@ -195,6 +225,56 @@ def get_response(
**kwargs
) -> str:
"""Enhanced get_response with all OpenAI-like features"""
+ logging.info(f"Getting response from {self.model}")
+ # Log all self values when in debug mode
+ if logging.getLogger().getEffectiveLevel() == logging.DEBUG:
+ debug_info = {
+ "model": self.model,
+ "timeout": self.timeout,
+ "temperature": self.temperature,
+ "top_p": self.top_p,
+ "n": self.n,
+ "max_tokens": self.max_tokens,
+ "presence_penalty": self.presence_penalty,
+ "frequency_penalty": self.frequency_penalty,
+ "logit_bias": self.logit_bias,
+ "response_format": self.response_format,
+ "seed": self.seed,
+ "logprobs": self.logprobs,
+ "top_logprobs": self.top_logprobs,
+ "api_version": self.api_version,
+ "stop_phrases": self.stop_phrases,
+ "api_key": "***" if self.api_key else None, # Mask API key for security
+ "base_url": self.base_url,
+ "verbose": self.verbose,
+ "markdown": self.markdown,
+ "self_reflect": self.self_reflect,
+ "max_reflect": self.max_reflect,
+ "min_reflect": self.min_reflect,
+ "reasoning_steps": self.reasoning_steps
+ }
+ logging.debug(f"LLM instance configuration: {json.dumps(debug_info, indent=2, default=str)}")
+
+ # Log the parameter values passed to get_response
+ param_info = {
+ "prompt": str(prompt)[:100] + "..." if isinstance(prompt, str) and len(str(prompt)) > 100 else str(prompt),
+ "system_prompt": system_prompt[:100] + "..." if system_prompt and len(system_prompt) > 100 else system_prompt,
+ "chat_history": f"[{len(chat_history)} messages]" if chat_history else None,
+ "temperature": temperature,
+ "tools": [t.__name__ if hasattr(t, "__name__") else str(t) for t in tools] if tools else None,
+ "output_json": str(output_json.__class__.__name__) if output_json else None,
+ "output_pydantic": str(output_pydantic.__class__.__name__) if output_pydantic else None,
+ "verbose": verbose,
+ "markdown": markdown,
+ "self_reflect": self_reflect,
+ "max_reflect": max_reflect,
+ "min_reflect": min_reflect,
+ "agent_name": agent_name,
+ "agent_role": agent_role,
+ "agent_tools": agent_tools,
+ "kwargs": str(kwargs)
+ }
+ logging.debug(f"get_response parameters: {json.dumps(param_info, indent=2, default=str)}")
try:
import litellm
# This below **kwargs** is passed to .completion() directly. so reasoning_steps has to be popped. OR find alternate best way of handling this.
@@ -202,6 +282,23 @@ def get_response(
# Disable litellm debug messages
litellm.set_verbose = False
+ # Format tools if provided
+ formatted_tools = None
+ if tools:
+ formatted_tools = []
+ for tool in tools:
+ if callable(tool):
+ tool_def = self._generate_tool_definition(tool.__name__)
+ elif isinstance(tool, str):
+ tool_def = self._generate_tool_definition(tool)
+ else:
+ continue
+
+ if tool_def:
+ formatted_tools.append(tool_def)
+ if not formatted_tools:
+ formatted_tools = None
+
# Build messages list
messages = []
if system_prompt:
@@ -260,6 +357,7 @@ def get_response(
messages=messages,
temperature=temperature,
stream=False, # force non-streaming
+ tools=formatted_tools,
**{k:v for k,v in kwargs.items() if k != 'reasoning_steps'}
)
reasoning_content = resp["choices"][0]["message"].get("provider_specific_fields", {}).get("reasoning_content")
@@ -291,6 +389,7 @@ def get_response(
for chunk in litellm.completion(
model=self.model,
messages=messages,
+ tools=formatted_tools,
temperature=temperature,
stream=True,
**kwargs
@@ -305,6 +404,7 @@ def get_response(
for chunk in litellm.completion(
model=self.model,
messages=messages,
+ tools=formatted_tools,
temperature=temperature,
stream=True,
**kwargs
@@ -318,6 +418,7 @@ def get_response(
final_response = litellm.completion(
model=self.model,
messages=messages,
+ tools=formatted_tools,
temperature=temperature,
stream=False, # No streaming for tool call check
**kwargs
@@ -552,6 +653,11 @@ def get_response(
except Exception as error:
display_error(f"Error in get_response: {str(error)}")
raise
+
+ # Log completion time if in debug mode
+ if logging.getLogger().getEffectiveLevel() == logging.DEBUG:
+ total_time = time.time() - start_time
+ logging.debug(f"get_response completed in {total_time:.2f} seconds")
async def get_response_async(
self,
@@ -577,6 +683,56 @@ async def get_response_async(
"""Async version of get_response with identical functionality."""
try:
import litellm
+ logging.info(f"Getting async response from {self.model}")
+ # Log all self values when in debug mode
+ if logging.getLogger().getEffectiveLevel() == logging.DEBUG:
+ debug_info = {
+ "model": self.model,
+ "timeout": self.timeout,
+ "temperature": self.temperature,
+ "top_p": self.top_p,
+ "n": self.n,
+ "max_tokens": self.max_tokens,
+ "presence_penalty": self.presence_penalty,
+ "frequency_penalty": self.frequency_penalty,
+ "logit_bias": self.logit_bias,
+ "response_format": self.response_format,
+ "seed": self.seed,
+ "logprobs": self.logprobs,
+ "top_logprobs": self.top_logprobs,
+ "api_version": self.api_version,
+ "stop_phrases": self.stop_phrases,
+ "api_key": "***" if self.api_key else None, # Mask API key for security
+ "base_url": self.base_url,
+ "verbose": self.verbose,
+ "markdown": self.markdown,
+ "self_reflect": self.self_reflect,
+ "max_reflect": self.max_reflect,
+ "min_reflect": self.min_reflect,
+ "reasoning_steps": self.reasoning_steps
+ }
+ logging.debug(f"LLM async instance configuration: {json.dumps(debug_info, indent=2, default=str)}")
+
+ # Log the parameter values passed to get_response_async
+ param_info = {
+ "prompt": str(prompt)[:100] + "..." if isinstance(prompt, str) and len(str(prompt)) > 100 else str(prompt),
+ "system_prompt": system_prompt[:100] + "..." if system_prompt and len(system_prompt) > 100 else system_prompt,
+ "chat_history": f"[{len(chat_history)} messages]" if chat_history else None,
+ "temperature": temperature,
+ "tools": [t.__name__ if hasattr(t, "__name__") else str(t) for t in tools] if tools else None,
+ "output_json": str(output_json.__class__.__name__) if output_json else None,
+ "output_pydantic": str(output_pydantic.__class__.__name__) if output_pydantic else None,
+ "verbose": verbose,
+ "markdown": markdown,
+ "self_reflect": self_reflect,
+ "max_reflect": max_reflect,
+ "min_reflect": min_reflect,
+ "agent_name": agent_name,
+ "agent_role": agent_role,
+ "agent_tools": agent_tools,
+ "kwargs": str(kwargs)
+ }
+ logging.debug(f"get_response_async parameters: {json.dumps(param_info, indent=2, default=str)}")
reasoning_steps = kwargs.pop('reasoning_steps', self.reasoning_steps)
litellm.set_verbose = False
@@ -983,6 +1139,11 @@ async def get_response_async(
raise LLMContextLengthExceededException(str(error))
display_error(f"Error in get_response_async: {str(error)}")
raise
+
+ # Log completion time if in debug mode
+ if logging.getLogger().getEffectiveLevel() == logging.DEBUG:
+ total_time = time.time() - start_time
+ logging.debug(f"get_response_async completed in {total_time:.2f} seconds")
def can_use_tools(self) -> bool:
"""Check if this model can use tool functions"""
@@ -1065,6 +1226,24 @@ def response(
logger.debug("Using synchronous response function")
+ # Log all self values when in debug mode
+ if logging.getLogger().getEffectiveLevel() == logging.DEBUG:
+ debug_info = {
+ "model": self.model,
+ "timeout": self.timeout,
+ "temperature": temperature,
+ "top_p": self.top_p,
+ "n": self.n,
+ "max_tokens": self.max_tokens,
+ "presence_penalty": self.presence_penalty,
+ "frequency_penalty": self.frequency_penalty,
+ "stream": stream,
+ "verbose": verbose,
+ "markdown": markdown,
+ "kwargs": str(kwargs)
+ }
+ logger.debug(f"Response method configuration: {json.dumps(debug_info, indent=2, default=str)}")
+
# Build messages list
messages = []
if system_prompt:
@@ -1150,6 +1329,24 @@ async def aresponse(
logger.debug("Using asynchronous response function")
+ # Log all self values when in debug mode
+ if logging.getLogger().getEffectiveLevel() == logging.DEBUG:
+ debug_info = {
+ "model": self.model,
+ "timeout": self.timeout,
+ "temperature": temperature,
+ "top_p": self.top_p,
+ "n": self.n,
+ "max_tokens": self.max_tokens,
+ "presence_penalty": self.presence_penalty,
+ "frequency_penalty": self.frequency_penalty,
+ "stream": stream,
+ "verbose": verbose,
+ "markdown": markdown,
+ "kwargs": str(kwargs)
+ }
+ logger.debug(f"Async response method configuration: {json.dumps(debug_info, indent=2, default=str)}")
+
# Build messages list
messages = []
if system_prompt:
@@ -1210,4 +1407,117 @@ async def aresponse(
except Exception as error:
display_error(f"Error in response_async: {str(error)}")
- raise
\ No newline at end of file
+ raise
+
+ def _generate_tool_definition(self, function_name: str) -> Optional[Dict]:
+ """Generate a tool definition from a function name."""
+ logging.debug(f"Attempting to generate tool definition for: {function_name}")
+
+ # First try to get the tool definition if it exists
+ tool_def_name = f"{function_name}_definition"
+ tool_def = globals().get(tool_def_name)
+ logging.debug(f"Looking for {tool_def_name} in globals: {tool_def is not None}")
+
+ if not tool_def:
+ import __main__
+ tool_def = getattr(__main__, tool_def_name, None)
+ logging.debug(f"Looking for {tool_def_name} in __main__: {tool_def is not None}")
+
+ if tool_def:
+ logging.debug(f"Found tool definition: {tool_def}")
+ return tool_def
+
+ # Try to find the function
+ func = globals().get(function_name)
+ logging.debug(f"Looking for {function_name} in globals: {func is not None}")
+
+ if not func:
+ import __main__
+ func = getattr(__main__, function_name, None)
+ logging.debug(f"Looking for {function_name} in __main__: {func is not None}")
+
+ if not func or not callable(func):
+ logging.debug(f"Function {function_name} not found or not callable")
+ return None
+
+ import inspect
+ # Handle Langchain and CrewAI tools
+ if inspect.isclass(func) and hasattr(func, 'run') and not hasattr(func, '_run'):
+ original_func = func
+ func = func.run
+ function_name = original_func.__name__
+ elif inspect.isclass(func) and hasattr(func, '_run'):
+ original_func = func
+ func = func._run
+ function_name = original_func.__name__
+
+ sig = inspect.signature(func)
+ logging.debug(f"Function signature: {sig}")
+
+ # Skip self, *args, **kwargs
+ parameters_list = []
+ for name, param in sig.parameters.items():
+ if name == "self":
+ continue
+ if param.kind in (inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.VAR_KEYWORD):
+ continue
+ parameters_list.append((name, param))
+
+ parameters = {
+ "type": "object",
+ "properties": {},
+ "required": []
+ }
+
+ # Parse docstring for parameter descriptions
+ docstring = inspect.getdoc(func)
+ logging.debug(f"Function docstring: {docstring}")
+
+ param_descriptions = {}
+ if docstring:
+ import re
+ param_section = re.split(r'\s*Args:\s*', docstring)
+ logging.debug(f"Param section split: {param_section}")
+ if len(param_section) > 1:
+ param_lines = param_section[1].split('\n')
+ for line in param_lines:
+ line = line.strip()
+ if line and ':' in line:
+ param_name, param_desc = line.split(':', 1)
+ param_descriptions[param_name.strip()] = param_desc.strip()
+
+ logging.debug(f"Parameter descriptions: {param_descriptions}")
+
+ for name, param in parameters_list:
+ param_type = "string" # Default type
+ if param.annotation != inspect.Parameter.empty:
+ if param.annotation == int:
+ param_type = "integer"
+ elif param.annotation == float:
+ param_type = "number"
+ elif param.annotation == bool:
+ param_type = "boolean"
+ elif param.annotation == list:
+ param_type = "array"
+ elif param.annotation == dict:
+ param_type = "object"
+
+ parameters["properties"][name] = {
+ "type": param_type,
+ "description": param_descriptions.get(name, "Parameter description not available")
+ }
+
+ if param.default == inspect.Parameter.empty:
+ parameters["required"].append(name)
+
+ logging.debug(f"Generated parameters: {parameters}")
+ tool_def = {
+ "type": "function",
+ "function": {
+ "name": function_name,
+ "description": docstring.split('\n\n')[0] if docstring else "No description available",
+ "parameters": parameters
+ }
+ }
+ logging.debug(f"Generated tool definition: {tool_def}")
+ return tool_def
\ No newline at end of file
diff --git a/src/praisonai-agents/pyproject.toml b/src/praisonai-agents/pyproject.toml
index 80b934ddd..f2057021c 100644
--- a/src/praisonai-agents/pyproject.toml
+++ b/src/praisonai-agents/pyproject.toml
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "praisonaiagents"
-version = "0.0.61"
+version = "0.0.62"
description = "Praison AI agents for completing complex tasks with Self Reflection Agents"
authors = [
{ name="Mervin Praison" }
diff --git a/src/praisonai-agents/uv.lock b/src/praisonai-agents/uv.lock
index 110b7226f..ad597e722 100644
--- a/src/praisonai-agents/uv.lock
+++ b/src/praisonai-agents/uv.lock
@@ -1868,7 +1868,7 @@ wheels = [
[[package]]
name = "praisonaiagents"
-version = "0.0.61"
+version = "0.0.62"
source = { editable = "." }
dependencies = [
{ name = "openai" },
diff --git a/uv.lock b/uv.lock
index 77f29ed9b..4c78b3778 100644
--- a/uv.lock
+++ b/uv.lock
@@ -3060,7 +3060,7 @@ wheels = [
[[package]]
name = "praisonai"
-version = "2.0.76"
+version = "2.0.77"
source = { editable = "." }
dependencies = [
{ name = "instructor" },
@@ -3197,7 +3197,7 @@ requires-dist = [
{ name = "plotly", marker = "extra == 'realtime'", specifier = ">=5.24.0" },
{ name = "praisonai-tools", marker = "extra == 'autogen'", specifier = ">=0.0.7" },
{ name = "praisonai-tools", marker = "extra == 'crewai'", specifier = ">=0.0.7" },
- { name = "praisonaiagents", specifier = ">=0.0.61" },
+ { name = "praisonaiagents", specifier = ">=0.0.62" },
{ name = "pyautogen", marker = "extra == 'autogen'", specifier = ">=0.2.19" },
{ name = "pydantic", marker = "extra == 'chat'", specifier = "<=2.10.1" },
{ name = "pydantic", marker = "extra == 'code'", specifier = "<=2.10.1" },
@@ -3250,16 +3250,16 @@ wheels = [
[[package]]
name = "praisonaiagents"
-version = "0.0.61"
+version = "0.0.62"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "openai" },
{ name = "pydantic" },
{ name = "rich" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/50/be/09af17ea9eee5135c7b091bca77e1fc7a8ff732a6148497276ff5a06bd2e/praisonaiagents-0.0.61.tar.gz", hash = "sha256:9e178b99a1d2ffe23acd8413810e5d937d910a72fbd851e04d10b0bcb129b48a", size = 102057 }
+sdist = { url = "https://files.pythonhosted.org/packages/26/44/528286f187d8be35839382251407a3b5ec7ecf5fc9b1fafd7e4197b436cb/praisonaiagents-0.0.62.tar.gz", hash = "sha256:d461553d7dc64441544a6ad92e6e446531f1be8e5cbb95261407540756b2cb8a", size = 104636 }
wheels = [
- { url = "https://files.pythonhosted.org/packages/12/1b/5656b79ae6c9f4c4e9e7b22db6800841c3bbab7fbd73c6c7f3381c936e64/praisonaiagents-0.0.61-py3-none-any.whl", hash = "sha256:835ec8a6fb1b2805a150db3d38bd4a1fe88333f078ee00190b102a3a4b2687a6", size = 121044 },
+ { url = "https://files.pythonhosted.org/packages/42/34/c389ba1a9a45b0f7637b073e25717d2f9e0e6e727cc4946182fcf86e8c12/praisonaiagents-0.0.62-py3-none-any.whl", hash = "sha256:f6883ba12642bb3a7af277bfc1c0020276c3ac31bc251ed381f0388b3a61e73b", size = 123702 },
]
[[package]]