31 KiB
name, description, license
| name | description | license |
|---|---|---|
| openai-assistants | Build stateful chatbots with OpenAI Assistants API v2 - Code Interpreter, File Search (10k files), Function Calling. ⚠️ Sunset H1 2026; use openai-responses for new projects. Use when: building stateful chatbots, implementing RAG, or troubleshooting "thread has active run", vector store delays, polling timeouts, or file upload errors. | MIT |
OpenAI Assistants API v2
Status: Production Ready (Deprecated H1 2026) Package: openai@6.7.0 Last Updated: 2025-10-25 v1 Deprecated: December 18, 2024 v2 Sunset: H1 2026 (migrate to Responses API)
⚠️ Important: Deprecation Notice
OpenAI announced that the Assistants API will be deprecated in favor of the Responses API.
Timeline:
- ✅ Dec 18, 2024: Assistants API v1 deprecated
- ⏳ H1 2026: Planned sunset of Assistants API v2
- ✅ Now: Responses API available (recommended for new projects)
Should you still use this skill?
- ✅ Yes, if: You have existing Assistants API code (12-18 month migration window)
- ✅ Yes, if: You need to maintain legacy applications
- ✅ Yes, if: Planning migration from Assistants → Responses
- ❌ No, if: Starting a new project (use openai-responses skill instead)
Migration Path:
See references/migration-to-responses.md for complete migration guide.
Table of Contents
- Quick Start
- Core Concepts
- Assistants
- Threads
- Messages
- Runs
- Streaming Runs
- Tools
- Vector Stores
- File Uploads
- Thread Lifecycle Management
- Error Handling
- Production Best Practices
- Relationship to Other Skills
Quick Start
Installation
npm install openai@6.7.0
Environment Setup
export OPENAI_API_KEY="sk-..."
Basic Assistant (Node.js SDK)
import OpenAI from 'openai';
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
// 1. Create an assistant
const assistant = await openai.beta.assistants.create({
name: "Math Tutor",
instructions: "You are a personal math tutor. Write and run code to answer math questions.",
tools: [{ type: "code_interpreter" }],
model: "gpt-4o",
});
// 2. Create a thread
const thread = await openai.beta.threads.create();
// 3. Add a message to the thread
await openai.beta.threads.messages.create(thread.id, {
role: "user",
content: "I need to solve the equation `3x + 11 = 14`. Can you help me?",
});
// 4. Create a run
const run = await openai.beta.threads.runs.create(thread.id, {
assistant_id: assistant.id,
});
// 5. Poll for completion
let runStatus = await openai.beta.threads.runs.retrieve(thread.id, run.id);
while (runStatus.status !== 'completed') {
await new Promise(resolve => setTimeout(resolve, 1000));
runStatus = await openai.beta.threads.runs.retrieve(thread.id, run.id);
}
// 6. Retrieve messages
const messages = await openai.beta.threads.messages.list(thread.id);
console.log(messages.data[0].content[0].text.value);
Basic Assistant (Fetch - Cloudflare Workers)
// 1. Create assistant
const assistant = await fetch('https://api.openai.com/v1/assistants', {
method: 'POST',
headers: {
'Authorization': `Bearer ${env.OPENAI_API_KEY}`,
'Content-Type': 'application/json',
'OpenAI-Beta': 'assistants=v2',
},
body: JSON.stringify({
name: "Math Tutor",
instructions: "You are a helpful math tutor.",
model: "gpt-4o",
}),
});
const assistantData = await assistant.json();
// 2. Create thread
const thread = await fetch('https://api.openai.com/v1/threads', {
method: 'POST',
headers: {
'Authorization': `Bearer ${env.OPENAI_API_KEY}`,
'Content-Type': 'application/json',
'OpenAI-Beta': 'assistants=v2',
},
});
const threadData = await thread.json();
// 3. Add message and create run
const run = await fetch(`https://api.openai.com/v1/threads/${threadData.id}/runs`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${env.OPENAI_API_KEY}`,
'Content-Type': 'application/json',
'OpenAI-Beta': 'assistants=v2',
},
body: JSON.stringify({
assistant_id: assistantData.id,
additional_messages: [{
role: "user",
content: "What is 3x + 11 = 14?",
}],
}),
});
// Poll for completion...
Core Concepts
The Assistants API uses four main objects:
1. Assistants
Configured AI entities with:
- Instructions (system prompt, max 256k characters)
- Model (gpt-4o, gpt-5, etc.)
- Tools (Code Interpreter, File Search, Functions)
- File attachments
- Metadata
2. Threads
Conversation containers that:
- Store message history
- Persist across runs
- Can have metadata
- Support up to 100,000 messages
3. Messages
Individual messages in a thread:
- User messages (input)
- Assistant messages (output)
- Can include file attachments
- Support text and image content
4. Runs
Execution of an assistant on a thread:
- Asynchronous processing
- Multiple states (queued, in_progress, completed, failed, etc.)
- Can stream results
- Handle tool calls automatically
Assistants
Create an Assistant
const assistant = await openai.beta.assistants.create({
name: "Data Analyst",
instructions: "You are a data analyst. Use code interpreter to analyze data and create visualizations.",
model: "gpt-4o",
tools: [
{ type: "code_interpreter" },
{ type: "file_search" },
],
tool_resources: {
file_search: {
vector_store_ids: ["vs_abc123"],
},
},
metadata: {
department: "analytics",
version: "1.0",
},
});
Parameters:
model(required): Model ID (gpt-4o, gpt-5, gpt-4-turbo)instructions: System prompt (max 256k characters in v2, was 32k in v1)name: Assistant name (max 256 characters)description: Description (max 512 characters)tools: Array of tools (max 128 tools)tool_resources: Resources for tools (vector stores, files)temperature: 0-2 (default 1)top_p: 0-1 (default 1)response_format: "auto", "json_object", or JSON schemametadata: Key-value pairs (max 16 pairs)
Retrieve an Assistant
const assistant = await openai.beta.assistants.retrieve("asst_abc123");
Update an Assistant
const updatedAssistant = await openai.beta.assistants.update("asst_abc123", {
instructions: "Updated instructions",
tools: [{ type: "code_interpreter" }, { type: "file_search" }],
});
Delete an Assistant
await openai.beta.assistants.del("asst_abc123");
List Assistants
const assistants = await openai.beta.assistants.list({
limit: 20,
order: "desc",
});
Threads
Threads store conversation history and persist across runs.
Create a Thread
// Empty thread
const thread = await openai.beta.threads.create();
// Thread with initial messages
const thread = await openai.beta.threads.create({
messages: [
{
role: "user",
content: "Hello! I need help with Python.",
metadata: { source: "web" },
},
],
metadata: {
user_id: "user_123",
session_id: "session_456",
},
});
Retrieve a Thread
const thread = await openai.beta.threads.retrieve("thread_abc123");
Update Thread Metadata
const thread = await openai.beta.threads.update("thread_abc123", {
metadata: {
user_id: "user_123",
last_active: new Date().toISOString(),
},
});
Delete a Thread
await openai.beta.threads.del("thread_abc123");
⚠️ Warning: Deleting a thread also deletes all messages and runs. Cannot be undone.
Messages
Add a Message to a Thread
const message = await openai.beta.threads.messages.create("thread_abc123", {
role: "user",
content: "Can you analyze this data?",
attachments: [
{
file_id: "file_abc123",
tools: [{ type: "code_interpreter" }],
},
],
metadata: {
timestamp: new Date().toISOString(),
},
});
Parameters:
role: "user" only (assistant messages created by runs)content: Text or array of content blocksattachments: Files with associated toolsmetadata: Key-value pairs
Retrieve a Message
const message = await openai.beta.threads.messages.retrieve(
"thread_abc123",
"msg_abc123"
);
List Messages
const messages = await openai.beta.threads.messages.list("thread_abc123", {
limit: 20,
order: "desc", // "asc" or "desc"
});
// Iterate through messages
for (const message of messages.data) {
console.log(`${message.role}: ${message.content[0].text.value}`);
}
Update Message Metadata
const message = await openai.beta.threads.messages.update(
"thread_abc123",
"msg_abc123",
{
metadata: {
edited: "true",
edit_timestamp: new Date().toISOString(),
},
}
);
Delete a Message
await openai.beta.threads.messages.del("thread_abc123", "msg_abc123");
Runs
Runs execute an assistant on a thread.
Create a Run
const run = await openai.beta.threads.runs.create("thread_abc123", {
assistant_id: "asst_abc123",
instructions: "Please address the user as Jane Doe.",
additional_messages: [
{
role: "user",
content: "Can you help me with this?",
},
],
});
Parameters:
assistant_id(required): Assistant to useinstructions: Override assistant instructionsadditional_messages: Add messages before runningtools: Override assistant toolsmetadata: Key-value pairstemperature: Override temperaturetop_p: Override top_pmax_prompt_tokens: Limit input tokensmax_completion_tokens: Limit output tokens
Retrieve a Run
const run = await openai.beta.threads.runs.retrieve(
"thread_abc123",
"run_abc123"
);
console.log(run.status); // queued, in_progress, requires_action, completed, failed, etc.
Run States
| State | Description |
|---|---|
queued |
Run is waiting to start |
in_progress |
Run is executing |
requires_action |
Function calling needs your input |
cancelling |
Cancellation in progress |
cancelled |
Run was cancelled |
failed |
Run failed (check last_error) |
completed |
Run finished successfully |
expired |
Run expired (max 10 minutes) |
Polling Pattern
async function pollRunCompletion(threadId: string, runId: string) {
let run = await openai.beta.threads.runs.retrieve(threadId, runId);
while (['queued', 'in_progress', 'cancelling'].includes(run.status)) {
await new Promise(resolve => setTimeout(resolve, 1000)); // Wait 1 second
run = await openai.beta.threads.runs.retrieve(threadId, runId);
}
if (run.status === 'failed') {
throw new Error(`Run failed: ${run.last_error?.message}`);
}
if (run.status === 'requires_action') {
// Handle function calling (see Function Calling section)
return run;
}
return run; // completed
}
const run = await openai.beta.threads.runs.create(threadId, { assistant_id: assistantId });
const completedRun = await pollRunCompletion(threadId, run.id);
Cancel a Run
const run = await openai.beta.threads.runs.cancel("thread_abc123", "run_abc123");
⚠️ Important: Cancellation is asynchronous. Check status becomes cancelled.
List Runs
const runs = await openai.beta.threads.runs.list("thread_abc123", {
limit: 10,
order: "desc",
});
Streaming Runs
Stream run events in real-time using Server-Sent Events (SSE).
Basic Streaming
const stream = await openai.beta.threads.runs.stream("thread_abc123", {
assistant_id: "asst_abc123",
});
for await (const event of stream) {
if (event.event === 'thread.message.delta') {
const delta = event.data.delta.content?.[0]?.text?.value;
if (delta) {
process.stdout.write(delta);
}
}
}
Stream Event Types
| Event | Description |
|---|---|
thread.run.created |
Run was created |
thread.run.in_progress |
Run started |
thread.run.step.created |
Step created (tool call, message creation) |
thread.run.step.delta |
Step progress update |
thread.message.created |
Message created |
thread.message.delta |
Message content streaming |
thread.message.completed |
Message finished |
thread.run.completed |
Run finished |
thread.run.failed |
Run failed |
thread.run.requires_action |
Function calling needed |
Complete Streaming Example
async function streamAssistantResponse(threadId: string, assistantId: string) {
const stream = await openai.beta.threads.runs.stream(threadId, {
assistant_id: assistantId,
});
for await (const event of stream) {
switch (event.event) {
case 'thread.run.created':
console.log('\\nRun started...');
break;
case 'thread.message.delta':
const delta = event.data.delta.content?.[0];
if (delta?.type === 'text' && delta.text?.value) {
process.stdout.write(delta.text.value);
}
break;
case 'thread.run.step.delta':
const toolCall = event.data.delta.step_details;
if (toolCall?.type === 'tool_calls') {
const codeInterpreter = toolCall.tool_calls?.[0]?.code_interpreter;
if (codeInterpreter?.input) {
console.log('\\nExecuting code:', codeInterpreter.input);
}
}
break;
case 'thread.run.completed':
console.log('\\n\\nRun completed!');
break;
case 'thread.run.failed':
console.error('\\nRun failed:', event.data.last_error);
break;
case 'thread.run.requires_action':
// Handle function calling
console.log('\\nFunction calling required');
break;
}
}
}
Tools
Assistants API supports three types of tools:
Code Interpreter
Executes Python code in a sandboxed environment.
Capabilities:
- Run Python code
- Generate charts/graphs
- Process files (CSV, JSON, text, images, etc.)
- Return file outputs (images, data files)
- Install packages (limited set available)
Example:
const assistant = await openai.beta.assistants.create({
name: "Data Analyst",
instructions: "You are a data analyst. Use Python to analyze data and create visualizations.",
model: "gpt-4o",
tools: [{ type: "code_interpreter" }],
});
// Upload a file
const file = await openai.files.create({
file: fs.createReadStream("sales_data.csv"),
purpose: "assistants",
});
// Create thread with file
const thread = await openai.beta.threads.create({
messages: [{
role: "user",
content: "Analyze this sales data and create a visualization.",
attachments: [{
file_id: file.id,
tools: [{ type: "code_interpreter" }],
}],
}],
});
// Run
const run = await openai.beta.threads.runs.create(thread.id, {
assistant_id: assistant.id,
});
// Poll for completion and retrieve outputs
Output Files:
Code Interpreter can generate files (images, CSVs, etc.). Access them via:
const messages = await openai.beta.threads.messages.list(thread.id);
const message = messages.data[0];
for (const content of message.content) {
if (content.type === 'image_file') {
const fileId = content.image_file.file_id;
const fileContent = await openai.files.content(fileId);
// Save or process file
}
}
File Search
Semantic search over uploaded documents using vector stores.
Key Features:
- Up to 10,000 files per assistant (500x more than v1)
- Automatic chunking and embedding
- Vector + keyword search
- Parallel queries with multi-threading
- Advanced reranking
Pricing:
- $0.10/GB/day for vector storage
- First 1GB free
Example:
// 1. Create vector store
const vectorStore = await openai.beta.vectorStores.create({
name: "Product Documentation",
metadata: { category: "docs" },
});
// 2. Upload files to vector store
const file = await openai.files.create({
file: fs.createReadStream("product_guide.pdf"),
purpose: "assistants",
});
await openai.beta.vectorStores.files.create(vectorStore.id, {
file_id: file.id,
});
// 3. Create assistant with file search
const assistant = await openai.beta.assistants.create({
name: "Product Support",
instructions: "Use file search to answer questions about our products.",
model: "gpt-4o",
tools: [{ type: "file_search" }],
tool_resources: {
file_search: {
vector_store_ids: [vectorStore.id],
},
},
});
// 4. Create thread and run
const thread = await openai.beta.threads.create({
messages: [{
role: "user",
content: "How do I install the product?",
}],
});
const run = await openai.beta.threads.runs.create(thread.id, {
assistant_id: assistant.id,
});
Best Practices:
- Wait for vector store status to be
completedbefore using - Use metadata for filtering (coming soon)
- Chunk large documents appropriately
- Monitor storage costs
Function Calling
Define custom functions for the assistant to call.
Example:
const assistant = await openai.beta.assistants.create({
name: "Weather Assistant",
instructions: "You help users get weather information.",
model: "gpt-4o",
tools: [{
type: "function",
function: {
name: "get_weather",
description: "Get the current weather for a location",
parameters: {
type: "object",
properties: {
location: {
type: "string",
description: "City name, e.g., 'San Francisco'",
},
unit: {
type: "string",
enum: ["celsius", "fahrenheit"],
description: "Temperature unit",
},
},
required: ["location"],
},
},
}],
});
// Create thread and run
const thread = await openai.beta.threads.create({
messages: [{
role: "user",
content: "What's the weather in San Francisco?",
}],
});
let run = await openai.beta.threads.runs.create(thread.id, {
assistant_id: assistant.id,
});
// Poll until requires_action
while (run.status === 'in_progress' || run.status === 'queued') {
await new Promise(resolve => setTimeout(resolve, 1000));
run = await openai.beta.threads.runs.retrieve(thread.id, run.id);
}
if (run.status === 'requires_action') {
const toolCalls = run.required_action.submit_tool_outputs.tool_calls;
const toolOutputs = [];
for (const toolCall of toolCalls) {
if (toolCall.function.name === 'get_weather') {
const args = JSON.parse(toolCall.function.arguments);
// Call your actual weather API
const weather = await getWeatherAPI(args.location, args.unit);
toolOutputs.push({
tool_call_id: toolCall.id,
output: JSON.stringify(weather),
});
}
}
// Submit tool outputs
run = await openai.beta.threads.runs.submitToolOutputs(thread.id, run.id, {
tool_outputs: toolOutputs,
});
// Continue polling...
}
Vector Stores
Vector stores enable efficient semantic search over large document collections.
Create a Vector Store
const vectorStore = await openai.beta.vectorStores.create({
name: "Legal Documents",
metadata: {
department: "legal",
category: "contracts",
},
expires_after: {
anchor: "last_active_at",
days: 7, // Auto-delete 7 days after last use
},
});
Add Files to Vector Store
Single File:
const file = await openai.files.create({
file: fs.createReadStream("contract.pdf"),
purpose: "assistants",
});
await openai.beta.vectorStores.files.create(vectorStore.id, {
file_id: file.id,
});
Batch Upload:
const fileBatch = await openai.beta.vectorStores.fileBatches.create(vectorStore.id, {
file_ids: ["file_abc123", "file_def456", "file_ghi789"],
});
// Poll for batch completion
let batch = await openai.beta.vectorStores.fileBatches.retrieve(vectorStore.id, fileBatch.id);
while (batch.status === 'in_progress') {
await new Promise(resolve => setTimeout(resolve, 1000));
batch = await openai.beta.vectorStores.fileBatches.retrieve(vectorStore.id, fileBatch.id);
}
Check Vector Store Status
const vectorStore = await openai.beta.vectorStores.retrieve("vs_abc123");
console.log(vectorStore.status); // "in_progress", "completed", "failed"
console.log(vectorStore.file_counts); // { in_progress: 0, completed: 50, failed: 0 }
⚠️ Important: Wait for status: "completed" before using with file search.
List Vector Stores
const stores = await openai.beta.vectorStores.list({
limit: 20,
order: "desc",
});
Update Vector Store
const vectorStore = await openai.beta.vectorStores.update("vs_abc123", {
name: "Updated Name",
metadata: { updated: "true" },
});
Delete Vector Store
await openai.beta.vectorStores.del("vs_abc123");
File Uploads
Upload files for use with Code Interpreter or File Search.
Upload a File
import fs from 'fs';
const file = await openai.files.create({
file: fs.createReadStream("document.pdf"),
purpose: "assistants",
});
console.log(file.id); // file_abc123
Supported Formats:
- Code Interpreter: .c, .cpp, .csv, .docx, .html, .java, .json, .md, .pdf, .php, .pptx, .py, .rb, .tex, .txt, .css, .jpeg, .jpg, .js, .gif, .png, .tar, .ts, .xlsx, .xml, .zip
- File Search: .c, .cpp, .docx, .html, .java, .json, .md, .pdf, .php, .pptx, .py, .rb, .tex, .txt, .css, .js, .ts, .go
Size Limits:
- Code Interpreter: 512 MB per file
- File Search: 512 MB per file
- Vector Store: Up to 10,000 files
Retrieve File Info
const file = await openai.files.retrieve("file_abc123");
Download File Content
const content = await openai.files.content("file_abc123");
// Returns binary content
Delete a File
await openai.files.del("file_abc123");
List Files
const files = await openai.files.list({
purpose: "assistants",
});
Thread Lifecycle Management
Proper thread lifecycle management prevents common errors.
Pattern 1: One Thread Per User
async function getOrCreateUserThread(userId: string): Promise<string> {
// Check if thread exists in your database
let threadId = await db.getThreadIdForUser(userId);
if (!threadId) {
// Create new thread
const thread = await openai.beta.threads.create({
metadata: { user_id: userId },
});
threadId = thread.id;
await db.saveThreadIdForUser(userId, threadId);
}
return threadId;
}
Pattern 2: Active Run Check
async function ensureNoActiveRun(threadId: string) {
const runs = await openai.beta.threads.runs.list(threadId, {
limit: 1,
order: "desc",
});
const latestRun = runs.data[0];
if (latestRun && ['queued', 'in_progress', 'cancelling'].includes(latestRun.status)) {
throw new Error('Thread already has an active run. Wait or cancel first.');
}
}
// Before creating new run
await ensureNoActiveRun(threadId);
const run = await openai.beta.threads.runs.create(threadId, { assistant_id });
Pattern 3: Thread Cleanup
async function cleanupOldThreads(maxAgeHours = 24) {
const threads = await openai.beta.threads.list({ limit: 100 });
for (const thread of threads.data) {
const createdAt = new Date(thread.created_at * 1000);
const ageHours = (Date.now() - createdAt.getTime()) / (1000 * 60 * 60);
if (ageHours > maxAgeHours) {
await openai.beta.threads.del(thread.id);
}
}
}
Error Handling
Common Errors and Solutions
1. Thread Already Has Active Run
Error: 400 Can't add messages to thread_xxx while a run run_xxx is active.
Solution:
// Wait for run to complete or cancel it
const run = await openai.beta.threads.runs.retrieve(threadId, runId);
if (['queued', 'in_progress'].includes(run.status)) {
await openai.beta.threads.runs.cancel(threadId, runId);
// Wait for cancellation
while (run.status !== 'cancelled') {
await new Promise(resolve => setTimeout(resolve, 500));
run = await openai.beta.threads.runs.retrieve(threadId, runId);
}
}
2. Run Polling Timeout
Long-running tasks may exceed reasonable polling windows.
Solution:
async function pollWithTimeout(threadId: string, runId: string, maxSeconds = 300) {
const startTime = Date.now();
while (true) {
const run = await openai.beta.threads.runs.retrieve(threadId, runId);
if (!['queued', 'in_progress'].includes(run.status)) {
return run;
}
const elapsed = (Date.now() - startTime) / 1000;
if (elapsed > maxSeconds) {
await openai.beta.threads.runs.cancel(threadId, runId);
throw new Error('Run exceeded timeout');
}
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
3. Vector Store Not Ready
Using vector store before indexing completes.
Solution:
async function waitForVectorStore(vectorStoreId: string) {
let store = await openai.beta.vectorStores.retrieve(vectorStoreId);
while (store.status === 'in_progress') {
await new Promise(resolve => setTimeout(resolve, 2000));
store = await openai.beta.vectorStores.retrieve(vectorStoreId);
}
if (store.status === 'failed') {
throw new Error('Vector store indexing failed');
}
return store;
}
4. File Upload Format Issues
Unsupported file formats cause errors.
Solution:
const SUPPORTED_FORMATS = {
code_interpreter: ['.csv', '.json', '.pdf', '.txt', '.py', '.js', '.xlsx'],
file_search: ['.pdf', '.docx', '.txt', '.md', '.html'],
};
function validateFile(filename: string, tool: string) {
const ext = filename.substring(filename.lastIndexOf('.')).toLowerCase();
if (!SUPPORTED_FORMATS[tool].includes(ext)) {
throw new Error(`Unsupported file format for ${tool}: ${ext}`);
}
}
See references/top-errors.md for complete error catalog.
Production Best Practices
1. Use Assistant IDs (Don't Recreate)
❌ Bad:
// Creates new assistant on every request!
const assistant = await openai.beta.assistants.create({ ... });
✅ Good:
// Create once, store ID, reuse
const ASSISTANT_ID = process.env.ASSISTANT_ID || await createAssistant();
async function createAssistant() {
const assistant = await openai.beta.assistants.create({ ... });
console.log('Save this ID:', assistant.id);
return assistant.id;
}
2. Implement Proper Error Handling
async function createRunWithRetry(threadId: string, assistantId: string, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await openai.beta.threads.runs.create(threadId, {
assistant_id: assistantId,
});
} catch (error) {
if (error.status === 429) {
// Rate limit - wait and retry
await new Promise(resolve => setTimeout(resolve, 2000 * (i + 1)));
continue;
}
if (error.message?.includes('active run')) {
// Wait for active run to complete
await new Promise(resolve => setTimeout(resolve, 5000));
continue;
}
throw error; // Other errors
}
}
throw new Error('Max retries exceeded');
}
3. Monitor Costs
// Track usage
const run = await openai.beta.threads.runs.retrieve(threadId, runId);
console.log('Tokens used:', run.usage);
// { prompt_tokens: 150, completion_tokens: 200, total_tokens: 350 }
// Set limits
const run = await openai.beta.threads.runs.create(threadId, {
assistant_id: assistantId,
max_prompt_tokens: 1000,
max_completion_tokens: 500,
});
4. Clean Up Resources
// Delete old threads
async function cleanupUserThread(userId: string) {
const threadId = await db.getThreadIdForUser(userId);
if (threadId) {
await openai.beta.threads.del(threadId);
await db.deleteThreadIdForUser(userId);
}
}
// Delete unused vector stores
async function cleanupVectorStores(keepDays = 30) {
const stores = await openai.beta.vectorStores.list({ limit: 100 });
for (const store of stores.data) {
const ageSeconds = Date.now() / 1000 - store.created_at;
const ageDays = ageSeconds / (60 * 60 * 24);
if (ageDays > keepDays) {
await openai.beta.vectorStores.del(store.id);
}
}
}
5. Use Streaming for Better UX
// Show progress in real-time
async function streamToUser(threadId: string, assistantId: string) {
const stream = await openai.beta.threads.runs.stream(threadId, {
assistant_id: assistantId,
});
for await (const event of stream) {
if (event.event === 'thread.message.delta') {
const delta = event.data.delta.content?.[0]?.text?.value;
if (delta) {
// Send to user immediately
sendToClient(delta);
}
}
}
}
Relationship to Other Skills
vs. openai-api Skill
openai-api (Chat Completions):
- Stateless requests
- Manual history management
- Direct responses
- Use for: Simple text generation, function calling
openai-assistants:
- Stateful conversations (threads)
- Automatic history management
- Built-in tools (Code Interpreter, File Search)
- Use for: Chatbots, data analysis, RAG
vs. openai-responses Skill
openai-responses (Responses API):
- ✅ Recommended for new projects
- Better reasoning preservation
- Modern MCP integration
- Active development
openai-assistants:
- ⚠️ Deprecated in H1 2026
- Use for legacy apps
- Migration path available
Migration: See references/migration-to-responses.md
Migration from v1 to v2
v1 deprecated: December 18, 2024
Key Changes:
- Retrieval → File Search:
retrievaltool replaced withfile_search - Vector Stores: Files now organized in vector stores (10,000 file limit)
- Instructions Limit: Increased from 32k to 256k characters
- File Attachments: Now message-level instead of assistant-level
See references/migration-from-v1.md for complete guide.
Next Steps
Templates:
templates/basic-assistant.ts- Simple math tutortemplates/code-interpreter-assistant.ts- Data analysistemplates/file-search-assistant.ts- RAG with vector storestemplates/function-calling-assistant.ts- Custom toolstemplates/streaming-assistant.ts- Real-time streaming
References:
references/top-errors.md- 12 common errors and solutionsreferences/thread-lifecycle.md- Thread management patternsreferences/vector-stores.md- Vector store deep dive
Related Skills:
openai-responses- Modern replacement (recommended)openai-api- Chat Completions (stateless)
Last Updated: 2025-10-25 Package Version: openai@6.7.0 Status: Production Ready (Deprecated H1 2026)