Threaded moderation
A reply often only makes sense next to the posts before it: "same to you" is harmless alone and an insult after one. Send who wrote the content and the earlier posts of its conversation with the upload, and CalmLens reads the content in that context. It still moderates only the content itself.
Send the conversation with an upload
Add two optional fields to any upload, such as a comment checked with client.text.classify():
| Field | Description |
|---|---|
authorId | Your identifier for who wrote the content, such as a user ID. Optional. |
context.items | Earlier posts in the conversation, oldest first. Each has text and an optional authorId. |
const reply = await client.text.classify({
text: "Same to you",
authorId: "user-2",
context: {
items: [
{ text: "Has anyone tried the new update?", authorId: "user-1" },
{ text: "You would say that", authorId: "user-3" },
],
},
});
console.log(reply.classification.moderation.status, reply.context);
Author IDs only tell CalmLens which posts share a speaker, so it can see whether the content replies to, quotes or addresses someone else. They are compared with each other and never interpreted, so any stable opaque value works. Don't send names or descriptions of the people in the thread.
The fields work for every type of upload: text, images, documents, websites, audio and video. A meme posted as a reply is read against the posts before it in the same way as text. Images and documents a website or document contains are read with the same conversation as the upload they came from.
What the conversation can and cannot do
CalmLens uses the earlier posts to read the content:
- who it addresses, and what words such as "you", "they" or "this" refer to;
- whether it quotes, reports or counters something said earlier, or is sarcastic;
- whether it continues or endorses an earlier post.
The earlier posts are never moderated themselves, and nothing in them is flagged on the content's behalf. A harmful earlier post doesn't make a harmless reply harmful, and context never creates a category the content doesn't contain. Instructions written in earlier posts are ignored.
A missing conversation is never treated as evidence that content is quoting, reporting or countering something, so leaving it out never makes content more likely to be allowed. Content sent without a conversation is classified on its own.
Limits
| Limit | Value |
|---|---|
| Earlier posts per request | Up to 20 |
| Characters in one earlier post | Up to 2,000 |
| Characters of earlier posts per request | Up to 10,000 |
authorId length | Up to 256 characters |
| Earlier posts CalmLens uses | The most recent 5 that fit in 2,000 characters |
Characters are counted as Unicode code points, so an emoji counts once. CalmLens walks back from the most recent post and stops at the fifth post, or at the first older post that would take the total over 2,000 characters. The most recent post is always used. A request over the limits is refused with a validation/ error naming the field.
The SDKs send only the posts CalmLens uses. A raw file upload carries the conversation in its query string as JSON (context={"items":[...]}), alongside the other asset fields. Upload presets don't accept a conversation from the browser, since the browser could supply a misleading one: classify replies from your server, or upload the file through a preset and send the conversation with a server-side upload.
Results, storage and billing
The asset records authorId and the context CalmLens used, so you can see what a decision was based on, and retries and detailed reports read the same conversation. The earlier posts are other people's text, so they're kept only as long as the content: when the content is removed, after processing unless processingOptions.keepContentAfterProcessing is on, by a purge or on request, context becomes null. authorId stays with the asset, like externalId.
Earlier posts are not billed. Text moderation is billed for the content's own tokens, as on the pricing page, whatever conversation you send.
An identical upload with an identical conversation reuses the earlier result. The same content with a different conversation is classified again.