Guides
Chat Integration
Call the component actions from your app.
The component exposes a built-in chat action that handles tool calling, delta-based streaming, and message persistence.
Using chat.send
// convex/chat.ts
import { v } from "convex/values";
import { action } from "./_generated/server";
import { components } from "./_generated/api";
export const sendMessage = action({
args: { conversationId: v.string(), message: v.string() },
handler: async (ctx, args) => {
return await ctx.runAction(components.databaseChat.chat.send, {
conversationId: args.conversationId as any,
message: args.message,
config: {
apiKey: process.env.OPENROUTER_API_KEY!,
model: "openai/gpt-4o",
systemPrompt: "You are a helpful assistant.",
tools: [],
maxMessagesForLLM: 50,
toolContext: { orgId: "org_123" },
},
});
},
});chat.send config options:
apiKey(required): OpenRouter API key.model(optional): defaults toopenai/gpt-4o.systemPrompt(optional): falls back to the component default if omitted.tools(optional): array ofDatabaseChatTool.maxMessagesForLLM(optional): defaults to 50.toolContext(optional): server-only context merged into tool args.
External ID scoping
Use chat.sendForExternalId to enforce ownership checks:
return await ctx.runAction(components.databaseChat.chat.sendForExternalId, {
conversationId: args.conversationId as any,
externalId: args.externalId,
message: args.message,
config: { apiKey: process.env.OPENROUTER_API_KEY! },
});If the conversation is missing or not owned by the externalId, the action
throws Not found to avoid leaking existence across tenants.
Client wrapper alternative
The defineDatabaseChat client (see Client Wrapper)
wraps the same actions and helps you pass tools and system prompts once instead
of per call.