用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/diegosouzapw/awesome-omni-skill --skill convex-ai命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
基于 SOC 职业分类
正在显示 SKILL.md
| name | convex-ai |
| description | Convex AI Integration - OpenAI, actions, streaming, and AI patterns with database integration |
| globs | ["convex/**/*.ts","**/*ai*.ts","**/*openai*.ts"] |
| triggers | ["openai","gpt","ai","llm","chat completion","generate","use node","action","OPENAI_API_KEY","ctx.runAction"] |
Complete guide for integrating AI capabilities (OpenAI, Google, etc.) with Convex, including actions, streaming, and best practices.
Install the OpenAI package:
npm install openai
Actions are the right place for AI calls because they can run for up to 10 minutes and make external API calls.
// convex/ai.ts
"use node";
import { action } from "./_generated/server";
import { v } from "convex/values";
import OpenAI from "openai";
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
export const generateText = action({
args: {
prompt: v.string(),
},
returns: v.string(),
handler: async (ctx, args) => {
const response = await openai.chat.completions.create({
model: "gpt-4o-mini",
messages: [{ role: "user", content: args.prompt }],
});
return response.choices[0].message.content ?? "";
},
});
// convex/ai.ts
"use node";
import { action, internalQuery } from "./_generated/server";
import { internal } from "./_generated/api";
import { v } from "convex/values";
import OpenAI from "openai";
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
export const generateResponse = action({
args: {
conversationId: v.id("conversations"),
},
returns: v.string(),
handler: async (ctx, args) => {
// Load context from the database
const messages = await ctx.runQuery(internal.ai.loadMessages, {
conversationId: args.conversationId,
});
const response = await openai.chat.completions.create({
model: "gpt-4o",
messages: messages,
});
const content = response.choices[]..;
(!content) {
();
}
ctx.(internal.., {
: args.,
content,
});
content;
},
});
loadMessages = ({
: {
: v.(),
},
: v.(
v.({
: v.(v.(), v.(), v.()),
: v.(),
})
),
: (ctx, args) => {
messages = ctx.
.()
.(, q.(, args.))
.()
.();
messages.( ({
: msg. | | ,
: msg.,
}));
},
});
Use the scheduler to generate AI responses asynchronously:
// convex/messages.ts
import { mutation, internalMutation, internalAction } from "./_generated/server";
import { internal } from "./_generated/api";
import { v } from "convex/values";
export const sendMessage = mutation({
args: {
conversationId: v.id("conversations"),
content: v.string(),
},
returns: v.null(),
handler: async (ctx, args) => {
// Save user message
await ctx.db.insert("messages", {
conversationId: args.conversationId,
role: "user",
content: args.content,
});
// Schedule AI response (runs immediately but async)
await ctx.scheduler.runAfter(0, internal.ai.generateResponse, {
conversationId: args.conversationId,
});
return null;
},
});
When an AI action needs to update the database:
// convex/ai.ts
"use node";
import { internalAction, internalMutation } from "./_generated/server";
import { internal } from "./_generated/api";
import { v } from "convex/values";
import OpenAI from "openai";
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
export const processWithAI = internalAction({
args: {
documentId: v.id("documents"),
},
returns: v.null(),
handler: async (ctx, args) => {
// 1. Load data from database
const document = await ctx.runQuery(internal.documents.get, {
id: args.documentId,
});
if (!document) {
throw new Error("Document not found");
}
// 2. Call AI
const response = await openai...({
: ,
: [
{ : , : },
{ : , : . },
],
});
summary = response.[].. ?? ;
ctx.(internal.., {
: args.,
summary,
});
;
},
});
updateSummary = ({
: {
: v.(),
: v.(),
},
: v.(),
: (ctx, args) => {
ctx..(args., { : args. });
;
},
});
If you're using Chef's WebContainer environment, you have access to bundled OpenAI tokens:
// convex/ai.ts
import { action } from "./_generated/server";
import { v } from "convex/values";
import OpenAI from "openai";
// Use Chef's bundled OpenAI
const openai = new OpenAI({
baseURL: process.env.CONVEX_OPENAI_BASE_URL,
apiKey: process.env.CONVEX_OPENAI_API_KEY,
});
export const generateText = action({
args: {
prompt: v.string(),
},
returns: v.string(),
handler: async (ctx, args) => {
const resp = await openai.chat.completions.create({
model: "gpt-4.1-nano", // or "gpt-4o-mini"
messages: [{ role: "user", content: args.prompt }],
});
return resp.choices[0].message.content ?? "";
},
});
Available models:
gpt-4.1-nano (preferred for speed/cost)gpt-4o-miniLimitations:
"use node";
import { action } from "./_generated/server";
import { v } from "convex/values";
import OpenAI from "openai";
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
export const safeGenerate = action({
args: {
prompt: v.string(),
},
returns: v.union(
v.object({ success: v.literal(true), content: v.string() }),
v.object({ success: v.literal(false), error: v.string() })
),
handler: async (ctx, args) => {
try {
const response = await openai.chat.completions.create({
model: "gpt-4o-mini",
messages: [{ role: "user", content: args.prompt }],
});
content = response.[]..;
(!content) {
{ : , : };
}
{ : , content };
} (error) {
message = error ? error. : ;
{ : , : message };
}
},
});
import { useAction } from "convex/react";
import { api } from "../convex/_generated/api";
import { useState } from "react";
function AIChat() {
const generateResponse = useAction(api.ai.generateText);
const [prompt, setPrompt] = useState("");
const [response, setResponse] = useState("");
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
if (!prompt.trim()) return;
setIsLoading(true);
setError(null);
try {
const result = await generateResponse({ prompt });
setResponse(result);
} catch (err) {
(err ? err. : );
} {
();
}
}
(
);
}
Environment variables are available via process.env in all Convex functions:
// Works in queries, mutations, actions, and HTTP actions
const apiKey = process.env.MY_API_KEY;
const baseUrl = process.env.MY_SERVICE_URL;
OPENAI_API_KEY=sk-...
RESEND_API_KEY=re_...
RESEND_DOMAIN=yourdomain.com
RESEND_WEBHOOK_SECRET=whsec_...
To prevent abuse and control costs:
// convex/ai.ts
import { action, query } from "./_generated/server";
import { v } from "convex/values";
import { getAuthUserId } from "@convex-dev/auth/server";
const RATE_LIMIT_WINDOW = 60 * 1000; // 1 minute
const MAX_REQUESTS = 10;
export const generateWithRateLimit = action({
args: {
prompt: v.string(),
},
returns: v.string(),
handler: async (ctx, args) => {
// Check rate limit
const canProceed = await ctx.runQuery(internal.ai.checkRateLimit);
if (!canProceed) {
throw new Error("Rate limit exceeded. Please wait before trying again.");
}
// Record this request
await ctx.runMutation(internal.ai.recordRequest);
// Make AI call
const response = await openai.chat.completions.({
: ,
: [{ : , : args. }],
});
response.[].. ?? ;
},
});
checkRateLimit = ({
: {},
: v.(),
: (ctx) => {
userId = (ctx);
(!userId) ;
windowStart = .() - ;
recentRequests = ctx.
.()
.(,
q.(, userId).(, windowStart)
)
.();
recentRequests. < ;
},
});
recordRequest = ({
: {},
: v.(),
: (ctx) => {
userId = (ctx);
(!userId) ();
ctx..(, {
userId,
: .(),
});
;
},
});
"use node"; at the top of files with external API calls