Send low-confidence decisions to a person
Comments come in, and each has to stay up or come down under your community rules. Most are clear; a few are not. Let the model decide the clear ones, and stop for a moderator only when it is unsure: its confidence decides which comments reach a person.
Set up
Install the library:
pip install "typellm>=0.7.0" pandasGet an API key from your dashboard:
from typellm import TypeLLMClient
client = TypeLLMClient(api_key="YOUR_API_KEY")RULES holds the community rules, and COMMENTS eight comments, from plain praise and spam to sarcasm and a link to the commenter's own blog. See the notebook for them.
Write the workflow
decision returns its probabilities, so it has a confidence. moderator is answered by your side, with "source": "user", and only when that confidence is below 0.8. Each path writes its own notice to the author: notice after a sure decision, reviewed_notice after the moderator's.
QUESTIONS = {
"decision": {
"type": "string",
"enum": ["allow", "remove"],
"return_probabilities": True,
"instructions": "Should this comment stay up under the community rules?",
},
"moderator": {
"type": "string",
"enum": ["allow", "remove"],
"source": "user",
"instructions": "A moderator's decision on this comment.",
"when": {"decision": {"confidence": {"lt": 0.8}}},
},
"notice": {
"type": "string",
"depends_on": ["decision"],
"instructions": "One sentence to the comment's author about the decision.",
"when": {"decision": {"confidence": {"gte": 0.8}}},
},
"reviewed_notice": {
"type": "string",
"depends_on": ["moderator"],
"instructions": "One sentence to the comment's author about the moderator's decision.",
},
}Moderate a batch
Every comment is one call, and the calls run side by side. A sure decision finishes its call; an unsure one stops it with status "needs_input":
from concurrent.futures import ThreadPoolExecutor
def moderate(comment):
return client.generate(context=f"{RULES}\n\nComment: {comment}", questions=QUESTIONS)
with ThreadPoolExecutor(8) as pool:
responses = list(pool.map(moderate, COMMENTS))| Comment | Model | Confidence | Status |
|---|---|---|---|
| Great write-up, the section on caching saved me a day of debugging. Thanks! | allow | 1.00 | completed |
| Buy followers cheap!!! 10k for $5, DM me on telegram @growfast | remove | 1.00 | completed |
| You are an idiot and everyone at your company should be fired. | remove | 1.00 | completed |
| Wow, what a genius take. Truly, nobody has ever thought of this before. | allow | 0.73 | needs_input |
| This library is garbage and so is the documentation. | allow | 0.86 | completed |
| People like the author shouldn't be allowed to write about security. | remove | 0.41 | needs_input |
| Check out my blog post on the same topic, I think it adds some context: example.com/caching | allow | 0.12 | needs_input |
| lol this is so dumb, I'm dying | allow | 1.00 | completed |
The model is sure of spam, insults and praise, and of harsh criticism of a library, which the rules allow. The three it is unsure of are the ones a person would hesitate over too: sarcasm, a jab at the author rather than the post, and a link that could be spam or a helpful addition.
Let the moderator decide the unsure ones
Here the moderator's decisions are a dictionary; in your code, put the comment in a review queue and resume when a person has decided. A stopped call can wait up to 24 hours, and resuming does not start it over.
MODERATOR = {
"Wow, what a genius take. Truly, nobody has ever thought of this before.": "allow",
"People like the author shouldn't be allowed to write about security.": "remove",
"Check out my blog post on the same topic, I think it adds some context: example.com/caching": "allow",
}
for i, (comment, response) in enumerate(zip(COMMENTS, responses)):
if response.status == "needs_input":
responses[i] = client.resume(response, {"moderator": MODERATOR[comment]})Final result
| Comment | Decided by | Final | Notice |
|---|---|---|---|
| Great write-up, the section on caching saved me a day… | model | allow | Your comment has been approved and is now visible to the community. |
| Buy followers cheap!!! 10k for $5… | model | remove | Your comment has been removed for violating the community rule against spam and ads. |
| You are an idiot and everyone at your company… | model | remove | Your comment was removed for violating the community rule against harassment and insults. |
| Wow, what a genius take… | moderator | allow | Your comment has been reviewed and approved for posting. |
| This library is garbage and so is the documentation. | model | allow | Your comment has been allowed as it expresses criticism of the product rather than targeting individuals. |
| People like the author shouldn't be allowed to write about security. | moderator | remove | Your comment was removed because it constitutes harassment by targeting the author personally rather than criticizing their ideas. |
| Check out my blog post on the same topic… | moderator | allow | Your comment has been reviewed and approved for posting. |
| lol this is so dumb, I'm dying | model | allow | Your comment has been reviewed and is allowed to remain in the community. |
Five comments were decided at once; a person saw three. The threshold sets that balance: raise it and more comments reach a moderator, lower it and fewer do. Set it on a labelled sample of your own comments, at the lowest value that still gives the accuracy you need. See Tools and human input for how a call stops and resumes. The notebook runs all of it with your key: open it in Colab.