> ## Documentation Index
> Fetch the complete documentation index at: https://docs.commentify.pro/llms.txt
> Use this file to discover all available pages before exploring further.

# Bring-your-own-key AI

> OpenAI-compatible embeddings and chat on your key. Duplicate detection before post, and Filament reply drafts. Commentify never hosts models.

Pro can call an **OpenAI-compatible** embeddings and chat API that you configure. There is no hosted Commentify model, no token meter, and no `COMMENTIFY_AI_KEY` in our cloud. If the provider errors, both features **fail open** (no matches, no draft) rather than blocking the post.

Perspective toxicity is a separate spam checker. See [AI toxicity](https://docs.commentify.pro/toxicity.md).

## Enable it

```env theme={null}
COMMENTIFY_AI=true
COMMENTIFY_AI_KEY=sk-...
# COMMENTIFY_AI_URL=https://api.openai.com/v1
# COMMENTIFY_AI_EMBEDDING_MODEL=text-embedding-3-small
# COMMENTIFY_AI_CHAT_MODEL=gpt-4o-mini
```

```php theme={null}
'ai' => [
    'enabled' => (bool) env('COMMENTIFY_AI', false),
    'base_url' => env('COMMENTIFY_AI_URL', 'https://api.openai.com/v1'),
    'api_key' => env('COMMENTIFY_AI_KEY'),
    'embedding_model' => env('COMMENTIFY_AI_EMBEDDING_MODEL', 'text-embedding-3-small'),
    'chat_model' => env('COMMENTIFY_AI_CHAT_MODEL', 'gpt-4o-mini'),
    'timeout' => 8,
    'chat_timeout' => 30,
    'duplicate_threshold' => 0.82,
    'duplicate_min_chars' => 12,
    'features' => [
        'duplicate_detection' => true,
        'reply_drafts' => true,
    ],
],
```

Run migrations so `commentify_embeddings` exists. Approved comments are embedded when they are approved. Backfill older rows:

```bash theme={null}
php artisan commentify:embed-comments
php artisan commentify:embed-comments --id=48 --sync
```

`--sync` runs inline. Otherwise jobs go on your default queue. Process the queue or embeddings stay empty and duplicate checks return no matches.

## Duplicate detection

Before a **top-level** post, Livewire and the SDKs ask:

`POST /commentify/api/v1/{type}/{id}/duplicates` with `{ "body": "…" }`.

A candidate matches only when **all** of these hold:

1. `COMMENTIFY_AI` is on, a key is set, and `commentify_embeddings` exists
2. After collapsing whitespace, the draft is at least `duplicate_min_chars` (default **12**)
3. The stored comment is **approved**, **top-level**, and on the **same commentable**
4. Cosine similarity of the two vectors is **≥ `duplicate_threshold`** (default **0.82**)

Up to five hits, highest score first. Replies are not compared as questions. Short scraps (`ok`, `thanks`) are skipped.

The UI is an **info** callout (not a red validation error) with permalinks and **Post anyway**. That button submits the held draft and skips the check once.

Embeddings are stored as JSON. Matching is PHP cosine similarity. No pgvector.

## Reply drafts

On the Filament **Moderation queue**, **Draft reply** (when reply drafts are on) queues a job. The model may supply page context:

```php theme={null}
public function commentifyAiContext(): string
{
    return $this->body;
}
```

The draft is a **pending** reply (`is_approved = false`) with `metadata.drafted_by = ai`. It stays off the public thread until the author or a moderator approves it.

## Frontends

`GET ui` reports `features.duplicate_detection` and `features.reply_drafts`. List payloads include `thread.duplicate_detection`. React and Vue intercept in the thread store (`post()` returns `null` when matches exist; `store.postAnyway()` posts the draft). Livewire uses `postAnyway` on the comments component.

> [!WARNING]
>
> Never commit `COMMENTIFY_AI_KEY`. Keep it in `.env`. Commentify does not log the key.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.