8000 Added account deactivation on spam detection of profile/bio updates by CodexVeritas · Pull Request #1484 · Metaculus/metaculus · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Added account deactivation on spam detection of profile/bio updates #1484

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

Merged
merged 13 commits into from
Nov 27, 2024

Conversation

CodexVeritas
Copy link
Collaborator
@CodexVeritas CodexVeritas commented Nov 21, 2024

Issue: #1386
New spam detection flow:

  • Someone makes an account
  • They come to edit their profile
  • When they submit a profile edit (i.e. with the bio section) the information is passed to a spam check
  • The spam check always passes if the user has been around for at least 7 days
  • Otherwise GPT Analyzes the bio and decides whether its spam
  • If spam is detected, there is an alert given the the user on the front end, an email is sent, and the user is logged out after 10sec

Identification rates:

  • I tested on the ~300 profiles that has at least one comment or prediction and has a bio. The spam detection getting 16% positives on these. A number of these were spam in and of themselves, so I'm putting the true false-positive rate around 2-7%. The first iteration was at ~35%.
  • I tested on a smaller set of verified spam and the identification is getting 100% on the spam profiles

@CodexVeritas
Copy link
Collaborator Author
CodexVeritas commented Nov 21, 2024

@lsabor You mentioned that the 2-10% false positive rate was too high. Should I shoot for a 1-3%? There are some profiles which are hard to distinguish from spam. I'm guessing so few people change their bio that this is probably an ok rate (and people will understand why they were flagged when they are). Only 300 have bios and a comment/prediction of our 210,000 users (granted probably ~50% of those users are spam).

For reference, we technically have 210k users, 71k users with bios, 25k users have forecasted. Only ~300 have bios AND have forecasted/commented. Given this, I'm guessing that ~50% of users are spam. Over the lifetime of Metaculus this would have resulted in 6-30 false deactivations who could email us to remedy the situation (probably 50% from people doing something stupid like throwing html into their bio, or making something legit sound fishy). And actually this would be more like 0-10 since they might have changed their bios after the 7 day range

Though more iterations couldn't hurt, just curious how much of a difference it will make since they can reach out if there is a problem.

@CodexVeritas
Copy link
Collaborator Author

Also looks like I need to apply flake8 formatting. Let me fix that real quick.

@CodexVeritas
Copy link
Collaborator Author

@lsabor Who would I invite to request review? I should invite Sylvain for the final QA review because he assigned the ticket to me, but for the preliminary review, will just anyone from the team do?

users/models.py Outdated
Comment on lines 106 to 119
send_email_async.send(
subject="Your Metaculus Account Has Been Deactivated",
message=textwrap.dedent(
"""Your Metaculus account has been deactivated by an administrator or an automated system. Possible reasons could include
- Suspicious activity
- Spam/Ad/Inappropriate content in comments
- Spam/Ad/Inappropriate content in profile bio
- Manual review for bot and spam accounts

If you believe this was done in error, please contact support@metaculus.com and we will reactivate your account."""
),
from_email=settings.EMAIL_HOST_USER,
recipient_list=[self.email],
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should keep models isolated from business logic like emails sending, interacting with brokers and etc. I'd propose to create a soft_delete_user service in users.services and put this logic there.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense! To clarify, you are suggesting just to extract this to another file, but still call it within the soft_delete() function?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could go either way on it triggering here.
This means an admin soft deleting users from the admin panel would trigger this. I'm not sure if that's desired.
Then again, soft deleting a user inappropriately should probably give them that info.

users/views.py Outdated
Comment on lines 526 to 527
async def _ask_gpt_to_check_for_spam(
bio_plus_websites: str, email: str
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you move all these business-logic functions to utils/services not to overload views.py?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep! I was debating whether it added too much to this file as well.

users/views.py Outdated
)
is_spam = "TRUE" in gpt_response
except Exception as e:
logger.info(f"AI call failed while checking for spam defaulting to FALSE. Error: {e}")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's have this be a "warning" for now so we get pinged in slack to catch any rampant issues right away.
After it's stable, we can change it back to "info"

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. I had it 'warning' at first before another comment you mentioned in slack about logging. I'll return it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, maybe I've been contradicting myself. Sorry!

My current opinion is that an error from a failed call to an LLM service should be high enough priority we get a slack notification for it. This way, if it constantly fails, we can easily get on the case to fix the code!

If the service succeeds and detects spam, that should only be an info or debug level log because things are operating as they should.

Copy link
Contributor
@lsabor lsabor left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great. I like the overall work and the basic functionality.

Really the only comments I've left are about shifting the code around, rather than significantly changing the approach.

I didn't review the test. Will do on next pass after next pass of edits.

@lsabor
Copy link
Contributor
lsabor commented Nov 22, 2024

Identification rates:

I tested on the ~300 profiles that has at least one comment or prediction and has a bio. The spam detection getting 16% positives on these. A number of these were spam in and of themselves, so I'm putting the true false-positive rate around 2-7%. The first iteration was at ~35%.
I tested on a smaller set of verified spam and the identification is getting 100% on the spam profiles

I'm generally much more worried about false positive error as it's super inconvenient if the user is legitmate.
Can you do more testing on definitely not spam accounts.
I'd suggest playing around with the filters on the admin page: https://www.metaculus.com/admin/users/user/ to find ?definitely? not spam accounts with bios. Maybe: https://www.metaculus.com/admin/users/user/?authored_comments=1%2B&bio_length=1%2B&forecasted=Yes&is_active__exact=1

@CodexVeritas
Copy link
Collaborator Author
CodexVeritas commented Nov 22, 2024

@lsabor

I'm generally much more worried about false positive error as it's super inconvenient if the user is legitmate. Can you do more testing on definitely not spam accounts. I'd suggest playing around with the filters on the admin page: https://www.metaculus.com/admin/users/user/ to find ?definitely? not spam accounts with bios. Maybe: https://www.metaculus.com/admin/users/user/?authored_comments=1%2B&bio_length=1%2B&forecasted=Yes&is_active__exact=1

Just make sure you've already read this since you responded to the wrong comment: #1484 (comment).

Though generally I'm down to spend another iteration on spam detection. So to clarify then, what false positive rate are you thinking (since I'm guessing current repercussions are low right now)? To do more general testing I'll randomly sample and verify good profiles from the 300 users with bios and >1 comments/forecasts and randomly sample from the 71k users with bios (though most of those will be spam). Then I can get a more direct false positive rate without eyeballing based on ones it got wrong in my local csv.

Regardless, I should probably look deeper into the 71k with bios and no comments/forecasts in case most of these are actually not spam.

@CodexVeritas
Copy link
Collaborator Author
CodexVeritas commented Nov 25, 2024

Added timeout for gpt, improved the gpt prompt to an estimated ~3% false positive rate (which is only for those submitting in the first 7 days of their account being active). There is an estimated 3-20% false negative rate (didn't evaluate this one as deeply).

I moved the spam check to a service file, and moved the users/services.py into users/sservices/common.py. After some research into Dramatiq and a discussion with Hlib, I decided not to make the spam check a dramatic actor to allow for instant feedback to the front end.

Some stats:
Total Users: 126596
Users with Bios: 76310 -> with a random sample of 20 I manually found 20/20 bios being spam. With a random sample of 300, GPT found 91.67% were spam.
Users with forecasts: 25676
Users with comments: 4077
Users with both bio and forecast or comment: 322 -> ~3% false positives on these

I'll be on travel for the next week, so no promises, but I can probably a few small fixes requested if they are blocking merge. We should probably get approval from Sylvain about the false positive rate.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be curious to hear whether the skipped tests I left in here as a quick check for the spam prompt should be something I should remove. I could see it as clutter in one way, but could also help with debugging later, and give context for what spam vs good accounts look like.

Copy link
Contributor
@lsabor lsabor Nov 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is correct to leave this skipped. I might instead want to move it away from automated testing entirely and instead put it into a command that can be run (e.g. python manage.py spam_testing) or something. Basically, we really shouldn't have our testing suite make api calls to real paid services - espeically if people clone the codebase and then the tests fail because they don't have an openAI api key. Instead, it'd be better to have this as a "run it manually whenever you update the prompting or a new OpenAI model comes out" command. I don't think this is important to put more effort into though since we won't run it often. I'd rather see this MVP get merged with the test skipped than have it delayed and make you spend more hours on it.

This testing module is great though.

Comment on lines +42 to +44
self.assert_response_is_403_with_proper_error_code(response)
self.assert_user_is_deactivated_and_unchanged(user1, original_bio, new_bio)
self.assert_notification_email_sent(mock_send_mail, self.user_email)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cool, I actually really like this method of testing - Nice!

Comment on lines +111 to +113
logger.info(
f"AI call failed while checking for spam defaulting to FALSE. Error: {e}"
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be a logger.warning

Comment on lines +60 to +64
logger.warning(
f"User {user.username} was soft deleted for spam bio: {bio_plus_website[:100]}... "
f"The reason was: {reasoning[:100]}... "
f"It took {duration:.2f} seconds to check. gpt_was_used: {gpt_was_used}"
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be a logger.info

Comment on lines +456 to +470
is_spam, _ = check_profile_update_for_spam(
user, cast(UserUpdateProfileSerializer, serializer)
)

if is_spam:
user.soft_delete()
user.save()
send_deactivation_email(user.email)
return Response(
data={
"message": "This bio seems to be spam. Please contact support@metaculus.com if you believe this was a mistake.",
"error_code": "SPAM_DETECTED",
},
status=status.HTTP_403_FORBIDDEN,
)
Copy link
Contributor
@lsabor lsabor Nov 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is the part that Hlib mentioned would be worth running in a Task instead of synchronous.

I think this will still be fast enough, so let's leave it as it is and change later only if necessary. But can you add a comment before line 456 to the effect of "spam detection is done synchronously, if profile updating takes too long, move this to an async task instead"?

Copy link
Contributor
@lsabor lsabor left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great, nice work!

I have a few comments that you should review, but the only one important to deal with is changing the logging levels in spam_detection.py

Otherwise, ready to merge. :)

@lsabor
Copy link
Contributor
lsabor commented Nov 26, 2024

Just make sure you've already read...

On second thought, with what you've said I think this failure rate is totally acceptable. Let's not change anything until someone complains.

I should invite Sylvain for the final QA review because he assigned the ticket to me

No need. Sylvain should be informed to do QA on the results live in production (or dev if it's deployed there first). But Sylvain won't do any code review, so pinging him here isn't worth it. Instead, move the ticket to QA and ping Sylvain it's ready for him to close if he's happy with the initial results.

@CodexVeritas CodexVeritas merged commit a162fdf into main Nov 27, 2024
2 checks passed
@CodexVeritas CodexVeritas deleted the feat/spam-detection branch November 27, 2024 18:49
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

Successfully merging this pull request may close these issues.

3 participants
0