Intellligent Job Scheduling Using AI
How my new project interprets user-specified task schedules without cron or libraries such as rufus and whenever
As the author of the book Patterns of Application Development Using AI, I’m always on the lookout for innovative ways to leverage the power of AI in my projects. This past weekend, while working on a new version of Ahhlife, my popular daily journaling app, I encountered an opportunity to let AI take the reins in scheduling the recurring prompt emails sent to users. In this blog post, I’ll share the approach I took and the code that made it possible, showcasing the ideas from my book in action.
Ahhlife is designed to help users maintain a daily journaling habit by sending them email prompts at their preferred schedule. In exisiting versions of the app, scheduling these prompts involves a combination of cron jobs, background workers, and complex conditional logic. However, with the help of my Raix library and the concepts from my book, I was able to streamline the process and let AI handle the heavy lifting.
…for now, this AI-based approach, as computationally inefficient as it is let me rewrite a traditionally complicated part of the Ahhlife logic from scratch in about an hour using TDD.
The key component in this AI-driven scheduling approach is the ScheduleCheck
class. This class encapsulates the logic for determining whether it's time to send a user their daily prompt email based on their specified schedule and the current time. Here’s a slightly simplified version of that class.
class ScheduleCheck
include Raix::ChatCompletion
include Raix::FunctionDispatch
attr_accessor :user
function :should_send_email, "Time to send a new email?", send: { type: "boolean" } do |params|
user.prompt! if params[:send]
end
SYSTEM_PROMPT = <<~PROMPT
Your job is to determine if it is time to send the user an email based
on the current time and their stated schedule preference.
Compare the current date and time to the user's stated schedule.
Is it currently within one hour of their stated schedule?
PROMPT
def initialize(user:)
self.user = user
transcript << { system: SYSTEM_PROMPT }
transcript << { system: "It is now #{Time.now.in_time_zone(user.timezone.presence).strftime('%A, %B %d, %Y %I:%M %p')}" }
transcript << { system: "User was last sent email at #{user.last_prompted_at.in_time_zone(user.timezone.presence).strftime('%A, %B %d, %Y %I:%M %p')}" }
transcript << { user: "My desired schedule is #{user.schedule.presence || 'daily at 8am'}." }
end
def call
chat_completion
end
def max_tokens
100
end
def model
"openai/gpt-4o"
end
en
The ScheduleCheck
class leverages the power of AI through the Raix library, which provides a seamless interface for integrating AI capabilities into Ruby applications. By including Raix::ChatCompletion
and Raix::FunctionDispatch
, the class gains the ability to engage in chat-based interactions with an AI model and dispatch functions based on the AI's responses.
The should_send_email
tool function is defined using the function
macro provided by Raix. This implementation of the function takes a boolean parameter send
and triggers the user's prompt email if the AI determines that it's time to send the email.
The initialize
method sets up the context for the AI by providing relevant information such as the current time, the user's last prompt timestamp, and their desired schedule. This context is crucial for the AI to make an informed decision about whether to send the prompt email.
When the call
method is invoked, it initiates a chat completion with the AI model, passing along the context and the user's schedule preference. The AI model, powered by OpenAI's GPT-4o, analyzes the provided information and determines whether it's within one hour of the user's desired schedule. If the AI concludes that it's time to send the email, it invokes the should_send_email
function with send
set to true
, triggering the prompt email to be sent to the user.
Execution cost at the scale of this project is negligible, about $0.000465 (USD) per check, despite using the relatively expensive GPT4o model. It would be less expensive if I used GPT4o-mini, but I didn’t find it to be as reliable in my testing, especially with edge cases. I suspect it can be made to work, but I didn’t want to invest the time.
At some point in the future, if this new version of Ahhlife becomes very popular I might need to take a different more cost-effective approach. But for now, this AI-based approach, as computationally inefficient as it is let me rewrite a traditionally complicated part of the Ahhlife logic from scratch in about an hour using TDD.
To ensure the reliability and accuracy of this AI-driven scheduling approach, I wrote comprehensive RSpec tests that cover various scenarios, including different user schedules, time zones, and edge cases. These tests give me confidence that the AI is making the right decisions and sending prompt emails at the appropriate times, even with extraordinary specifications like “only on Memorial Day”.
context "when the user makes an unusual schedule" do
before do
user.update(schedule: "only on Memorial Day")
end
it 'prompts the user on Memorial Day' do
Timecop.freeze(Time.zone.parse("2024-05-27 09:05:00")) do # Memorial Day
expect(user).to receive(:prompt!)
subject.call
end
end
If you’re wanting to experiment with this approach in your own project, here’s another couple of associated components that make it come together.
class RecurringPromptJob < ApplicationJob
queue_as :default
def perform
User.active.needs_prompting.each do |user|
ScheduleCheck.new(user: user).call
end
end
end
class User < ApplicationRecord
# only relevant parts of the User class included here
attribute :schedule, :string, default: "daily at 8am"
scope :active, -> { where(disabled: false) }
scope :needs_prompting, -> { where(last_prompted_at: nil).or(where("last_prompted_at < ?", 24.hours.ago)) }
This is just one example of how the concepts and techniques from my book can be applied in real-world projects to unlock the potential of AI in application development. By embracing AI and incorporating it into our development workflows, we can build smarter, more efficient, and more user-centric applications.
Stay tuned for more blog posts where I’ll share additional examples and experiences from my journey of integrating AI into my applications. Until then, happy coding!