How to pass attributes to a Custom Tool

Team

I am working on a custom tool that needs inputs passed from the frontend.
As an example, this tool needs to update MongodB.

What should be the right way ?
The syntax below kind of work “sometimes” but it is not predictable.

I have the following issue:

"I encountered an error while trying to use the tool. This was the error: 1 validation error for QueryInputSchema
query
  Input should be a valid string.... "

when the query is 
## Tool Input: 
"{\"query\": \"please do X,Y,X ---- > it works

but the query has description, then it breaks and I don;t know why description is added... 
## Tool Input: 
"{\"query\": {\"description\": \"please do X,Y,X ---->it breaks

Is it the right syntax and approach to pass variable to a custom tool ?

    @agent
    def mongodb_administrator(self) -> Agent:
        """Creates the MongoDB administrator agent"""
        return Agent(
            config=self.agents['mongodb_administrator'],
            tools=[Update_MongodB(
                name=self.name,
                dateobirth=self.dateobirth,
            )],
            verbose=True,
            llm=self.llm,
            allow_delegation=False,
            max_iter=5
        )

The code kind of work but too unpredictable unfortunately so I guess there is a better way :slight_smile: Your help would be greatly appreciated.

Arnaud

Can you share the MongoDB tool code? Feel free to remove any sensitive parts.

It could boil down to validating the input before passing it to the tool. The input seems to vary due to the LLM arbitrarily deciding the format to output the query.

Hi Tony

This is the error that blocks me :

Error: the Action Input is not a valid key, value dictionary.

The first agent produces the format I am expecting but the last one struggles with the input.
I have defined a model.py that structure the output exactly as the mongodB schema but still… I am stuck with this

Error: the Action Input is not a valid key, value dictionary.
# Agent: MongodB Senior Administrator
## Thought: Thought: I need to ensure that the data is structured correctly for the MongoDB update. I will format the input data according to the specified criteria.
## Using tool: Update_MongodB
## Tool Input:
"{\"input_data\": {\"Id\": \"4ilqI0k\", \"days\": [{\"event_date\": \"2024-11-22\", \"events\": [{\"folder\": \"Shopping\", \"eventType\": \"Delivery\", \"title\": \"******************** *********appointment.\", \"address\": null, \"attendees\": [\"Axel\"], \"reminder\": {\"enabled\": true, \"remind_at\": \"2024-12-02T08:00:00.000Z\"}, \"source\": null, \"notification_date\": null}]}]}}"
## Tool Output:
Error: the Action Input is not a valid key, value dictionary.

My Crew

@crew
def crew(self) -> Crew:
    """Creates the Calendar crew"""
    return Crew(
      agents=[
      self.calendar_manager_assistant(),
      self.senior_personal_assistant(),
      self.mongodb_administrator(),
      ],
      tasks=[
      self.collect_calendar_events(),
      self.compile_calendar_events(),
      self.Update_Calendar_to_MongodB(),
      ],
      process=Process.sequential,
      verbose=True,
      #memory=True,
      callback_manager=self.callback_manager
    )

my tasks

@task
def collect_calendar_events(self) -> Task:
      """Creates the grades collection task"""
      return Task(
          config=self.tasks['collect_calendar_events'],
          agent=self.calendar_manager_assistant(),
          #output_pydantic=CalendarEvent
      )

@task
def compile_calendar_events(self) -> Task:
      """Creates the grades collection task"""
      return Task(
          config=self.tasks['compile_calendar_events'],
          agent=self.senior_personal_assistant(),
          context=[self.collect_calendar_events()],
          output_pydantic=CalendarEvent
      )

@task
def Update_Calendar_to_MongodB(self) -> Task:
      """Updates MongoDB with grades"""
      return Task(
          config=self.tasks['Update_Calendar_to_MongodB'],
          agent=self.mongodb_administrator(),
          context=[self.compile_calendar_events()],
          #output_pydantic=CalendarEvent
      )

The tool

tools/Update_MongodB.py
from crewai_tools import BaseTool
from pymongo import MongoClient, errors
from typing import Dict, Any
from pydantic import BaseModel, Field, PrivateAttr
import os
from datetime import datetime, timezone
import logging
import json

# Set up logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

class Update_MongodB(BaseTool):
        name: str = "Update_MongodB"
        description: str = "Tool to update MongoDB database "
        uniqueid: str = Field(..., description="The unique ID for database operations")
        
        _client: Any = PrivateAttr(default=None)
        _db: Any = PrivateAttr(default=None)
        _collection: Any = PrivateAttr(default=None)

        def __init__(self, **data):
              super().__init__(**data)

              try:
                  mongo_uri = os.getenv("*****")
                  if not mongo_uri:
                  raise ValueError("*******")
              
                  self._client = MongoClient(
                        mongo_uri,
                        serverSelectionTimeoutMS=5000,
                        connectTimeoutMS=5000,
                        socketTimeoutMS=5000
                  )

                  self._client.admin.command('ping')
                  self._db = self._client['*****']
                  self._collection = self._db['****']

              except Exception as e:
                  print(f"ERROR in initialization: {str(e)}")
                  raise

          def _standardize_data(self, data: Any) -> Dict:
                """Standardize input data to match schema"""

                try:
                      # Convert string to dict if needed
                      if isinstance(data, str):
                      data = json.loads(data)
                      
                      # Handle nested data structure
                      if isinstance(data, dict):
                        if 'data' in data:
                          data = data['data']
                          if 'description' in data:
                          data = data['description']
                      return self._standardize_event_structure(data)
                
                except Exception as e:
                      print(f"ERROR in _standardize_data: {str(e)}")
                      raise

          def _standardize_event_structure(self, data: Dict) -> Dict:
                """Helper method to standardize event structure"""
                try:
                
                      standardized_data = {
                            "clerkId": data.get('clerkId', self.clerk_id),
                            "days": []
                      }
      
                      for day in data.get('days', []):
                            standardized_day = {
                            
                            "date": day.get('date') or day.get('event_date'),
                            "events": []
                            }
            
                      for event in day.get('events', []):
                            standardized_event = self._standardize_event(event)
                      
                      if standardized_event:
                            standardized_day['events'].append(standardized_event)
                      
                      if standardized_day['events']:
                            standardized_data['days'].append(standardized_day)
                      
                      return standardized_data
      
                except Exception as e:
                raise

          def _run(self, input_data: Any) -> Dict[str, Any]:
                """Execute the tool's primary function"""

                try:
                      # Convert input to dictionary if it's a string
                      if isinstance(input_data, str):
                          print("Attempting to parse JSON string...")
                          try:
                              data = json.loads(input_data)
                          except json.JSONDecodeError as e:
                              raise ValueError("Invalid JSON input")
                          else:
                              data = input_data
                              print("\nExtracting nested data...")
                              # Extract nested data if needed
                              if isinstance(data, dict):
                                  if 'input' in data:
                    
                                      data = data['input']
                                      if 'data' in data:
                                      data = data['data']
                                      if 'description' in data:
                                      data = data['description']
                                      print(f"Extracted data: {data}")
                
                            # Validate required fields
                            if not isinstance(data, dict):
                
                                raise ValueError(f"Expected dictionary input, got {type(data)}")
                
                if 'clerkId' not in data:
                data['clerkId'] = self.clerk_id
                
                if 'days' not in data or not isinstance(data['days'], list):
                raise ValueError("Input must contain 'days' array")
                
                # Update MongoDB
                
                update_result = self._update_calendar_structure(data)
                
                return {
                "status": "success",
                "message": "Successfully updated calendar events",
                "data": {
                "clerkId": self.clerk_id,
                "update_result": update_result
                }
                }
                
                except Exception as e:
                print(f"\nERROR in _run method: {str(e)}")
                print(f"Error type: {type(e)}")
                print(f"Error details: {str(e)}")
                return {
                "status": "error",
                "message": str(e),
                "data": None
                }

          def __del__(self):
                """Cleanup MongoDB connection"""
                try:
                      if hasattr(self, '_client') and self._client:
                      self._client.close()
                      logger.info("MongoDB connection closed")
                except Exception as e:
                      logger.error(f"Error closing MongoDB connection: {str(e)}")

Thanks a lot for your support.
Kind Regards
Arnaud

@Arnaud This error seems to be a common issue. Based on reports from other developers, the following solutions often work:

  • Try to adjust agent and task configurations.
  • Try to use a more capable LLM.

Sources:

  1. CrewAI forum: Action Input Error
  2. GitHub issue: [BUG] Tool inputs being fed through as an array instead of dictionary · Issue #1344 · crewAIInc/crewAI · GitHub

Thanks for the details. What LLM are you using btw?

So some updates.
I was using get-4o-mini and tried get-4o with no luck.
It turns out that some attributes in the mongodB schema blocked the upload process. So I started with just one attribute, made the upload work and adding attributes by attributes. step by step :slight_smile:

Overall, it works great.
Keep up the good work CrewAI!

1 Like

Glad you found the solution! :slight_smile:

1 Like

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.