← Files InsForgeARCHIVED FILE

skills/insforge/ai/chat-completions.md

4.46 KB · Oct 3, 2026 · 06:30 UTC

↓ Download file

# Chat Completions

Use OpenRouter chat completions for chatbot responses, summarization,
classification, and structured text. Use InsForge for sessions, messages,
permissions, audit history, and realtime UI updates.

Official OpenRouter references:

- [Chat completions](https://openrouter.ai/docs/api-reference/chat-completion)
- [OpenAI SDK with OpenRouter](https://openrouter.ai/docs/guides/community/openai-sdk)
- [Parameters](https://openrouter.ai/docs/api-reference/parameters)
- [Streaming](https://openrouter.ai/docs/api-reference/streaming)

## Setup

Start with [overview.md](overview.md): run `npx @insforge/cli ai setup`, keep
`OPENROUTER_API_KEY` server-side, and initialize the OpenAI SDK with
`baseURL: 'https://openrouter.ai/api/v1'`.

## Store a Response

For a chatbot app, keep session/message rows in InsForge and protect them with
RLS. In a server route/action, insert the user message, call OpenRouter, then
insert the assistant response. Derive ownership from the authenticated
server-side context or enforce it through the parent chat session's RLS policy;
never trust a browser-supplied `user_id`. InsForge database inserts use array
format.

```typescript
type ChatRole = 'system' | 'user' | 'assistant'

async function sendChatMessage(sessionId: string, content: string) {
  const { data: userData, error: userError } = await insforge.auth.getCurrentUser()
  if (userError || !userData?.user?.id) {
    throw userError ?? new Error('Authentication required')
  }

  const history = await insforge.database
    .from('chat_messages')
    .select('role, content')
    .eq('session_id', sessionId)
    .order('created_at', { ascending: true })

  if (history.error) {
    throw history.error
  }

  const { error: userInsertError } = await insforge.database
    .from('chat_messages')
    .insert([{
      session_id: sessionId,
      user_id: userData.user.id,
      role: 'user',
      content,
    }])

  if (userInsertError) {
    throw userInsertError
  }

  const previousMessages = (history.data ?? []).map((message: {
    role: ChatRole
    content: string
  }) => ({
    role: message.role,
    content: message.content,
  }))

  const completion = await openai.chat.completions.create({
    model: process.env.OPENROUTER_CHAT_MODEL ?? 'openai/gpt-4o',
    messages: [
      { role: 'system', content: 'Answer clearly and stay on topic.' },
      ...previousMessages,
      { role: 'user', content },
    ],
    max_completion_tokens: 500,
  })

  const answer = completion.choices[0]?.message?.content ?? ''

  const { data, error: assistantInsertError } = await insforge.database
    .from('chat_messages')
    .insert([{
      session_id: sessionId,
      user_id: userData.user.id,
      role: 'assistant',
      content: answer,
      model: completion.model,
      prompt_tokens: completion.usage?.prompt_tokens,
      completion_tokens: completion.usage?.completion_tokens,
    }])
    .select()

  if (assistantInsertError) {
    throw assistantInsertError
  }

  return data?.[0]
}
```

For streaming chat, stream tokens to the browser for UX, buffer the final
assistant text on the server, and insert one final `assistant` row when the
stream completes. Only store token-by-token deltas if the product needs replay
or detailed debugging.

## Best Practices

1. Store chat sessions and messages in InsForge, not local browser state, when
   the app needs history, sync, moderation, billing, or support debugging.
2. Use RLS on chat tables; never trust a client-supplied `user_id` without
   checking session ownership.
3. Save `model`, token usage, and error state on assistant rows for auditing and
   cost analysis.
4. For streaming, persist the final assistant message once; avoid writing every
   token unless replay is a product requirement.
5. Use InsForge Realtime to fan out newly inserted assistant messages to other
   tabs/devices if the chat UI needs live sync.

## Common Mistakes

| Mistake | Fix |
|---------|-----|
| Calling OpenRouter from the browser | Route through your server so the key stays private |
| Keeping chatbot history only in React state | Store sessions/messages in InsForge and secure them with RLS |
| Inserting a single object instead of an array | Use `.insert([{ ... }])` |
| Using invented or stale model IDs | Query OpenRouter models before implementing |
| Omitting token limits in user-triggered endpoints | Set `max_completion_tokens` for predictable cost |
| Treating OpenAI SDK errors like InsForge `{ data, error }` results | OpenAI SDK calls throw; catch exceptions separately |

SHA-256: 6b4cb94c94f700cff6fdd908e80d1c8ce3d1b2fc2a87a25aa1b1cbdb3a470743