Shared Workforce Client
Shared Workforce is a platform for managing and completing repetitive tasks that require human intelligence. For example, tagging and cropping photos, approving text and answering simple questions.
The Shared Workforce client lets you add behaviour to your models so that when an action occurs (for example, a photo is added), details of that action and model are sent to human workers, who can asses the data and return a response, which can be used to trigger further actions in the model (for example, marking it as spam).
You can view a live demo sandbox app at http://catsify.herokuapp.com
The service is currently in private beta and is available as a Heroku add-on. You can apply for an invitation at sharedworkforce.com.
Getting started
Note to Heroku users: Getting started with Heroku takes less than 5 minutes. The best way to get started is to follow the README on the demo app source code.
Step 1 - get an API key
Register for a beta invitation at sharedworkforce.com. Once you are invited, you can create your account and retrieve your API key - which will look something like this:
acdc30b2-14c5-46ee-ba35-11d50edc65ec
Step 2 - add the gem
Add shared_workforce to your Gemfile:
gem "shared_workforce"
Create config/initializers/shared_workforce.rb
SharedWorkforce.configure do |config|
config.api_key = "your-api-key"
config.callback_host = "http://your-website-host"
end
If you're not using Rails, simply require the gem or include it in your Gemfile, set the client configuration settings as above.
Step 3 - define tasks
A class defines the content (for example, an image url) and instructions that the human worker will see when they work on your task, and the methods to be run once the task has been completed.
If, for example, you would like to approve a photo on upload, create a task class file in an app/tasks
directory (or anywhere in the load path).
app/tasks/tag_photo_task.rb
:
class TagPhotoTask
include SharedWorkforce::Task
title 'Please tag our photo'
instruction 'Please look at the photo and tick all that apply.'
answer_type :tags
answer_options ['Offensive', 'Contains Nudity', 'Blurry or low quality', 'Upside down or sideways']
responses_required 1
on_success :moderate_photo
def setup(photo)
self.image_url = photo.url
end
def moderate_photo(photo, responses)
# responses => [{:answer=>['Offensive', 'Contains Nudity']}]
if responses.map {|r| r[:answer]}.flatten.include?('Offensive')
photo.delete
photo.user.quarantine("Uploded offensive photo")
end
end
end
Note: the task definition includes a setup
method which is called automatically whenever the task is initialized. In the example, the task's image_url
(the image shown to the worker) is set from the photo model's url attribute. Any of the task's attributes can be set this way.
Class level attributes are a handy way of defining data that doesn't change between each task. Attributes set on the instance will always override attributes set at the class level.
In most cases, you'll want to explicitly set default task values at the class level
(like title
and instruction
). Setting text
and image_url
values (i.e. the content in question) will usually be done in
the setup
method.
Once you have created a task definition, you can request real human responses for a model instance by calling its create
method. This can be done in an after_create
callback in one of your Active Record models. This will be covered in more detail in the next section.
Step 4 - request tasks
When you publish a task it will be queued for a human worker to complete. You can publish a task in an after_create
callback on a typical Active Record model:
class Photo < ActiveRecord::Base
after_create :request_tags
def request_tags
TagPhotoTask.create(self)
end
end
Note: In the example, the photo model instance (self) is used an argument to the TagPhotoTask.create method. This argument will be available in the setup method and the callback method as shown in the example of a Task Definition in step 3.
When the response(s) from the human workers are collected, the method specified in the on_success
attribute in your task definition will be called. Typically this will take about 15 minutes. You can check the Shared Workforce web site for an up to date status on the current response time.
Step 5 - collect responses
A rake task is provided for collecting the responses during development.
$ rake sw:collect
There is no requirement to run the rake task in production. The webhook will be used to deliver the task responses.
Unit testing
You can test your task definition by calling its methods directly.
it "should quarantine the user" do
photo = Factory(:photo)
task = ClassifyPhotoTask.new(photo)
task.moderate_photo(photo, [{:answer=>['Offensive']}])
photo.user.should be_quarantined
end
Advanced definition options
Task types
SharedWorkforce currently supports 5 types of task. Possible options are:
-
"choice"
: choose one option from a list -
"tags"
: choose any number of options from a list -
"edit"
: edit the text in the 'text' attribute -
"crop"
: crop the photo (see image_crop_ratio) -
"rotate"
: rotate the photo
Multiple responses
SharedWorkforce supports multiple responses for each task. The callback method provides you with an array of responses from multiple workers. You can create your own logic to decide what to do. This is useful if you want to prevent destructive action unless a number of workers agree.
class ApprovePhotoTask
include SharedWorkforce::Task
title 'Approve Photo'
instruction 'Look at this photo. Please tick all that apply.'
responses_required 3
answer_options ['Offensive', 'Contains Nudity', 'Blurry or low quality', 'Upside down or sideways']
answer_type :tags
on_complete :moderate_photo
def moderate_photo(photo, responses)
photo.hide! if responses.map { |r| r[:answer] }.all? { |a| a.include?('Contains Nudity') }
end
end
Replacing tasks
The "replace" option allows you to overwrite or update any existing tasks with the same name and callback params. This could be useful in the example to handle the situation where a user re-uploads their photo - you may only care about the latest one.
class ApprovePhotoTask
include SharedWorkforce::Task
...
replace true
...
end
Cancelling tasks
You can cancel tasks when they are no longer relevant.
class Photo
after_destroy :cancel_tagging_request
def cancel_tagging_request
ApprovePhotoTask.cancel(self)
end
end
Disabling requests during development
You can black-hole requests to Shared Workforce for testing and development by adding the following configuration option in your initializer:
SharedWorkforce.configure do |config|
config.request_class = SharedWorkforce::TaskRequest::BlackHole
end
License
The SharedWorkforce Client is (c) Pigment and released under the MIT license