Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to pass custom type variables to tools #842

Closed
ebravofm opened this issue Feb 28, 2025 · 2 comments
Closed

How to pass custom type variables to tools #842

ebravofm opened this issue Feb 28, 2025 · 2 comments

Comments

@ebravofm
Copy link

I’m working on a Telegram bot and using the smolagents library to create agents that handle reminders. The issue I’m facing is related to passing the context object (which is specific to each message received by the bot) to a tool function (add_reminder). The context object is required to access the job_queue for scheduling reminders.

Problem:

Even though I’m passing the context variable through the additional_args argument in agent.run, the agent doesn’t seem to pass this variable directly to the code interpreter. Instead, it redefines the variable as None, which causes the rest of the code to fail.

Here’s the relevant part of the code:

@tool
def add_reminder(title: str,
                    date_time: datetime.datetime,
                    chat_id: str,
                    context: Any,
                    location: str = None,
                    details: str = None) -> dict:
    
    '''
    Add a reminder to the job queue.
    
    Args:
    title: The title of the reminder  (str)
    date_time: The time for the reminder
    location: The location of the reminder if it is specified. If not then None (str)
    details: The details of the reminder if it is specified. If not then None (str)
    chat_id: pass the chat_id given to you
    context: pass the context given to you
    '''
    
    # try:
    reminder = {}
    reminder['Title'] = title
    reminder['Time'] = date_time
    reminder['Location'] = location
    reminder['Details'] = details

    # Convert the reminder time string to a localized datetime object
    timer_date = date_time.replace(tzinfo=None)
    timer_date = tz.localize(timer_date)
    timer_date_string = timer_date.strftime("%H:%M %d/%m/%Y")

    timer_name = f"{title} ({timer_date_string})"
    reminder['run'] = 'once'
    reminder['text'] = reminder_to_text(reminder)

    # Calculate the time remaining in seconds
    now = datetime.datetime.now(tz)
    seconds_until_due = (timer_date - now).total_seconds()

    # Check if the time is in the past
    if seconds_until_due <= 0:
        return {'success': False, 'message': TXT_NOT_ABLE_TO_SCHEDULE_PAST}

    reminder['type'] = 'parent'
    
    context.job_queue.run_once(
        alarm,
        when=timer_date,
        chat_id=chat_id,
        name=timer_name,
        data=reminder,
    )
    
    reminder['type'] = '-30'
    context.job_queue.run_once(
        alarm_minus_30,
        when=timer_date - datetime.timedelta(minutes=30),
        chat_id=chat_id,
        name=timer_name,
        data=reminder,
    )
        
    return {'success': True, 'message': TXT_REMINDER_SCHEDULED, 'response_for_user': reminder['text']}


async def add_reminder_from_input(update, context):
    # Add the reminder
    input = update.message.text
    chat_id = update.effective_chat.id
    now = datetime.datetime.now(tz).strftime("%d/%m/%Y %H:%M")
    
    logger.info(f'chat_id: {chat_id}, input: {input}')
    

    agent = CodeAgent(tools=[add_reminder],
                     additional_authorized_imports=['datetime'],
                     model=OpenAIServerModel(model_id='gpt-4o-mini', api_key = OPENAI_TOKEN),
                     verbosity_level=3,
                     max_steps = 2)
                                               

    answer = agent.run(TXT_MENU_AGENT_SYSTEM_PROMPT.format(input=input, now=now),
                        additional_args={"context": context, "chat_id":chat_id})
    
    await send_message(update, context, text=answer)

When the agent runs, it generates code like this:

Executing parsed code: ──────────────────────────────────────────────────────────────────────────────────────────────────────────────── 
  from datetime import datetime, timedelta                                                                                                 
                                                                                                                                           
  # Set the reminder details                                                                                                               
  title = "Meeting with John"                                                                                                      
  date_time = datetime(2025, 3, 1, 9, 0)  # March 1, 2025, at 09:00                                                                        
  chat_id = 6129357493                                                                                                                     
  context = None  # This would typically be the provided context object                                                                    
                                                                                                                                           
  # Add the reminder                                                                                                                       
  reminder_response = add_reminder(title=title, date_time=date_time, chat_id=chat_id, context=context, location=None, details=None)        
  print(reminder_response)                                                                                                                 
 ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── 

However, the context variable is redefined as None by the agent, resulting in the following error:

Code execution failed at line 'reminder_response = add_reminder(title=title, date_time=date_time, chat_id=chat_id, context=context, 
location=None, details=None)' due to: AttributeError: 'NoneType' object has no attribute 'job_queue'
[Step 0: Duration 2.53 seconds| Input tokens: 2,253 | Output tokens: 267]"

Workaround:

The only workaround I’ve found is to define the add_reminder tool inside the function where the context is available. This works but is not ideal because I have to redefine the tool every time I want to use it in different agents.

Example:

async def add_reminder_from_input(update, context):
    # Add the reminder
    input = update.message.text
    chat_id = update.effective_chat.id
    now = datetime.datetime.now(tz).strftime("%d/%m/%Y %H:%M")
    
    logger.info(f'chat_id: {chat_id}, input: {input}')

    @tool
    def add_reminder(title: str,
                        date_time: datetime.datetime,
                        location: str = None,
                        details: str = None) -> dict:
        
        '''
        Add a reminder to the job queue.
        
        Args:
        title: The title of the reminder  (str)
        date_time: The time for the reminder
        location: The location of the reminder if it is specified. If not then None (str)
        details: The details of the reminder if it is specified. If not then None (str)
        '''
        
        # try:
        reminder = {}
        reminder['Title'] = title
        reminder['Time'] = date_time
        reminder['Location'] = location
        reminder['Details'] = details

        # Convert the reminder time string to a localized datetime object
        timer_date = date_time.replace(tzinfo=None)
        timer_date = tz.localize(timer_date)
        timer_date_string = timer_date.strftime("%H:%M %d/%m/%Y")

        timer_name = f"{title} ({timer_date_string})"
        reminder['run'] = 'once'
        reminder['text'] = reminder_to_text(reminder)

        # Calculate the time remaining in seconds
        now = datetime.datetime.now(tz)
        seconds_until_due = (timer_date - now).total_seconds()

        # Check if the time is in the past
        if seconds_until_due <= 0:
            return {'success': False, 'message': TXT_NOT_ABLE_TO_SCHEDULE_PAST}

        reminder['type'] = 'parent'
        
        context.job_queue.run_once(
            alarm,
            when=timer_date,
            chat_id=chat_id,
            name=timer_name,
            data=reminder,
        )
        
        reminder['type'] = '-30'
        context.job_queue.run_once(
            alarm_minus_30,
            when=timer_date - datetime.timedelta(minutes=30),
            chat_id=chat_id,
            name=timer_name,
            data=reminder,
        )
            
        return {'success': True, 'message': TXT_REMINDER_SCHEDULED, 'response_for_user': reminder['text']}

    

    agent = CodeAgent(tools=[add_reminder],
                     additional_authorized_imports=['datetime'],
                     model=OpenAIServerModel(model_id='gpt-4o-mini', api_key = OPENAI_TOKEN),
                     verbosity_level=3,
                     max_steps = 2)
                                               

    answer = agent.run(TXT_MENU_AGENT_SYSTEM_PROMPT.format(input=input, now=now),
                        additional_args={"context": context, "chat_id":chat_id})
    
    await send_message(update, context, text=answer)

Request:

Is there a better way to pass custom type variables like context to tools in smolagents? Ideally, I’d like to define the tool once and pass the context object dynamically when the tool is executed.

Additional Context:

  • The context object is specific to the python-telegram-bot library and is required to access the job_queue for scheduling tasks.
  • The add_reminder tool needs to be reusable across multiple agents without redefining it each time.

Any guidance or suggestions would be greatly appreciated!

@sysradium
Copy link
Contributor

sysradium commented Mar 1, 2025

I would have suggested to define the tool as a class to encapsulate the job queue, e.g.:

class ReminderScheduler:
    def __init__(self, queue):
        self.queue = queue

    @tool
    """
    ...
    """
    def schedule(self, reminder: str):
        self.queue.do(reminder)

However this is not currently supported (will complain about self). However will be added if guys merge the following PR #627

However as a workaround maybe you can use this?

def create_reminder_tool(queue):
    @tool
    def schedule_reminder(timestamp: time, reminder: str):
        """This tool is used to schedule reminders
        Args:
            timestamp: a timestamp for when the reminder should be scheduled
            reminder: the text of the reminder

        """
        queue.do({"date": timestamp, "reminder": reminder})

    return schedule_reminder

reminder_tool = create_reminder_tool(queue)
agent = CodeAgent(
    name="my man",
    tools=[
        reminder_tool,
    ],
    model=model,
)

@ebravofm
Copy link
Author

ebravofm commented Mar 1, 2025

Thats's smart, it works. Thanks!

@ebravofm ebravofm closed this as completed Mar 1, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants