1293 lines
31 KiB
Markdown
1293 lines
31 KiB
Markdown
---
|
|
name: openai-assistants
|
|
description: |
|
|
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.
|
|
license: 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](../openai-responses/SKILL.md).**
|
|
|
|
**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
|
|
|
|
1. [Quick Start](#quick-start)
|
|
2. [Core Concepts](#core-concepts)
|
|
3. [Assistants](#assistants)
|
|
4. [Threads](#threads)
|
|
5. [Messages](#messages)
|
|
6. [Runs](#runs)
|
|
7. [Streaming Runs](#streaming-runs)
|
|
8. [Tools](#tools)
|
|
- [Code Interpreter](#code-interpreter)
|
|
- [File Search](#file-search)
|
|
- [Function Calling](#function-calling)
|
|
9. [Vector Stores](#vector-stores)
|
|
10. [File Uploads](#file-uploads)
|
|
11. [Thread Lifecycle Management](#thread-lifecycle-management)
|
|
12. [Error Handling](#error-handling)
|
|
13. [Production Best Practices](#production-best-practices)
|
|
14. [Relationship to Other Skills](#relationship-to-other-skills)
|
|
|
|
---
|
|
|
|
## Quick Start
|
|
|
|
### Installation
|
|
|
|
```bash
|
|
npm install openai@6.7.0
|
|
```
|
|
|
|
### Environment Setup
|
|
|
|
```bash
|
|
export OPENAI_API_KEY="sk-..."
|
|
```
|
|
|
|
### Basic Assistant (Node.js SDK)
|
|
|
|
```typescript
|
|
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)
|
|
|
|
```typescript
|
|
// 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
|
|
|
|
```typescript
|
|
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 schema
|
|
- `metadata`: Key-value pairs (max 16 pairs)
|
|
|
|
### Retrieve an Assistant
|
|
|
|
```typescript
|
|
const assistant = await openai.beta.assistants.retrieve("asst_abc123");
|
|
```
|
|
|
|
### Update an Assistant
|
|
|
|
```typescript
|
|
const updatedAssistant = await openai.beta.assistants.update("asst_abc123", {
|
|
instructions: "Updated instructions",
|
|
tools: [{ type: "code_interpreter" }, { type: "file_search" }],
|
|
});
|
|
```
|
|
|
|
### Delete an Assistant
|
|
|
|
```typescript
|
|
await openai.beta.assistants.del("asst_abc123");
|
|
```
|
|
|
|
### List Assistants
|
|
|
|
```typescript
|
|
const assistants = await openai.beta.assistants.list({
|
|
limit: 20,
|
|
order: "desc",
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## Threads
|
|
|
|
Threads store conversation history and persist across runs.
|
|
|
|
### Create a Thread
|
|
|
|
```typescript
|
|
// 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
|
|
|
|
```typescript
|
|
const thread = await openai.beta.threads.retrieve("thread_abc123");
|
|
```
|
|
|
|
### Update Thread Metadata
|
|
|
|
```typescript
|
|
const thread = await openai.beta.threads.update("thread_abc123", {
|
|
metadata: {
|
|
user_id: "user_123",
|
|
last_active: new Date().toISOString(),
|
|
},
|
|
});
|
|
```
|
|
|
|
### Delete a Thread
|
|
|
|
```typescript
|
|
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
|
|
|
|
```typescript
|
|
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 blocks
|
|
- `attachments`: Files with associated tools
|
|
- `metadata`: Key-value pairs
|
|
|
|
### Retrieve a Message
|
|
|
|
```typescript
|
|
const message = await openai.beta.threads.messages.retrieve(
|
|
"thread_abc123",
|
|
"msg_abc123"
|
|
);
|
|
```
|
|
|
|
### List Messages
|
|
|
|
```typescript
|
|
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
|
|
|
|
```typescript
|
|
const message = await openai.beta.threads.messages.update(
|
|
"thread_abc123",
|
|
"msg_abc123",
|
|
{
|
|
metadata: {
|
|
edited: "true",
|
|
edit_timestamp: new Date().toISOString(),
|
|
},
|
|
}
|
|
);
|
|
```
|
|
|
|
### Delete a Message
|
|
|
|
```typescript
|
|
await openai.beta.threads.messages.del("thread_abc123", "msg_abc123");
|
|
```
|
|
|
|
---
|
|
|
|
## Runs
|
|
|
|
Runs execute an assistant on a thread.
|
|
|
|
### Create a Run
|
|
|
|
```typescript
|
|
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 use
|
|
- `instructions`: Override assistant instructions
|
|
- `additional_messages`: Add messages before running
|
|
- `tools`: Override assistant tools
|
|
- `metadata`: Key-value pairs
|
|
- `temperature`: Override temperature
|
|
- `top_p`: Override top_p
|
|
- `max_prompt_tokens`: Limit input tokens
|
|
- `max_completion_tokens`: Limit output tokens
|
|
|
|
### Retrieve a Run
|
|
|
|
```typescript
|
|
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
|
|
|
|
```typescript
|
|
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
|
|
|
|
```typescript
|
|
const run = await openai.beta.threads.runs.cancel("thread_abc123", "run_abc123");
|
|
```
|
|
|
|
**⚠️ Important**: Cancellation is asynchronous. Check `status` becomes `cancelled`.
|
|
|
|
### List Runs
|
|
|
|
```typescript
|
|
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
|
|
|
|
```typescript
|
|
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
|
|
|
|
```typescript
|
|
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:**
|
|
|
|
```typescript
|
|
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:
|
|
|
|
```typescript
|
|
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:**
|
|
|
|
```typescript
|
|
// 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 `completed` before 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:**
|
|
|
|
```typescript
|
|
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
|
|
|
|
```typescript
|
|
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:**
|
|
|
|
```typescript
|
|
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:**
|
|
|
|
```typescript
|
|
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
|
|
|
|
```typescript
|
|
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
|
|
|
|
```typescript
|
|
const stores = await openai.beta.vectorStores.list({
|
|
limit: 20,
|
|
order: "desc",
|
|
});
|
|
```
|
|
|
|
### Update Vector Store
|
|
|
|
```typescript
|
|
const vectorStore = await openai.beta.vectorStores.update("vs_abc123", {
|
|
name: "Updated Name",
|
|
metadata: { updated: "true" },
|
|
});
|
|
```
|
|
|
|
### Delete Vector Store
|
|
|
|
```typescript
|
|
await openai.beta.vectorStores.del("vs_abc123");
|
|
```
|
|
|
|
---
|
|
|
|
## File Uploads
|
|
|
|
Upload files for use with Code Interpreter or File Search.
|
|
|
|
### Upload a File
|
|
|
|
```typescript
|
|
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
|
|
|
|
```typescript
|
|
const file = await openai.files.retrieve("file_abc123");
|
|
```
|
|
|
|
### Download File Content
|
|
|
|
```typescript
|
|
const content = await openai.files.content("file_abc123");
|
|
// Returns binary content
|
|
```
|
|
|
|
### Delete a File
|
|
|
|
```typescript
|
|
await openai.files.del("file_abc123");
|
|
```
|
|
|
|
### List Files
|
|
|
|
```typescript
|
|
const files = await openai.files.list({
|
|
purpose: "assistants",
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## Thread Lifecycle Management
|
|
|
|
Proper thread lifecycle management prevents common errors.
|
|
|
|
### Pattern 1: One Thread Per User
|
|
|
|
```typescript
|
|
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
|
|
|
|
```typescript
|
|
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
|
|
|
|
```typescript
|
|
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:**
|
|
```typescript
|
|
// 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:**
|
|
```typescript
|
|
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:**
|
|
```typescript
|
|
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:**
|
|
```typescript
|
|
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:**
|
|
```typescript
|
|
// Creates new assistant on every request!
|
|
const assistant = await openai.beta.assistants.create({ ... });
|
|
```
|
|
|
|
**✅ Good:**
|
|
```typescript
|
|
// 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
|
|
|
|
```typescript
|
|
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
|
|
|
|
```typescript
|
|
// 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
|
|
|
|
```typescript
|
|
// 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
|
|
|
|
```typescript
|
|
// 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:**
|
|
1. **Retrieval → File Search**: `retrieval` tool replaced with `file_search`
|
|
2. **Vector Stores**: Files now organized in vector stores (10,000 file limit)
|
|
3. **Instructions Limit**: Increased from 32k to 256k characters
|
|
4. **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 tutor
|
|
- `templates/code-interpreter-assistant.ts` - Data analysis
|
|
- `templates/file-search-assistant.ts` - RAG with vector stores
|
|
- `templates/function-calling-assistant.ts` - Custom tools
|
|
- `templates/streaming-assistant.ts` - Real-time streaming
|
|
|
|
**References:**
|
|
- `references/top-errors.md` - 12 common errors and solutions
|
|
- `references/thread-lifecycle.md` - Thread management patterns
|
|
- `references/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)
|