Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 08:25:15 +08:00
commit 0c577730d5
20 changed files with 5085 additions and 0 deletions

View File

@@ -0,0 +1,12 @@
{
"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.",
"version": "1.0.0",
"author": {
"name": "Jeremy Dawes",
"email": "jeremy@jezweb.net"
},
"skills": [
"./"
]
}

3
README.md Normal file
View File

@@ -0,0 +1,3 @@
# 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.

1292
SKILL.md Normal file

File diff suppressed because it is too large Load Diff

109
plugin.lock.json Normal file
View File

@@ -0,0 +1,109 @@
{
"$schema": "internal://schemas/plugin.lock.v1.json",
"pluginId": "gh:jezweb/claude-skills:skills/openai-assistants",
"normalized": {
"repo": null,
"ref": "refs/tags/v20251128.0",
"commit": "044bfce173a2cbf723d28b7853c92379476da6a2",
"treeHash": "ff884027d18fceda67c0432d9c653abaa070f9727c7792c2400a0447de04412b",
"generatedAt": "2025-11-28T10:19:01.147312Z",
"toolVersion": "publish_plugins.py@0.2.0"
},
"origin": {
"remote": "git@github.com:zhongweili/42plugin-data.git",
"branch": "master",
"commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390",
"repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data"
},
"manifest": {
"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.",
"version": "1.0.0"
},
"content": {
"files": [
{
"path": "README.md",
"sha256": "c5898c0b70530d8d842fa4834d2f054401bf8cefe1a2267b28bd28c57be7bcc7"
},
{
"path": "SKILL.md",
"sha256": "1dc400edc193d07ae79474a518c685accf1b31131895b60057f832fbed6c5c43"
},
{
"path": "references/top-errors.md",
"sha256": "e2b06393f97eff808b9c215e3df66c8ac8c4f9e3c5592f62544d49fc99ccaf6b"
},
{
"path": "references/assistants-api-v2.md",
"sha256": "44503832782dc6bab67006234bd906b066e8853ae85568c7ea4b9fd174488526"
},
{
"path": "references/code-interpreter-guide.md",
"sha256": "66ce1a7fba0093a72885aaa845fe0b6f8fe0dd3e8ab5e4e3c4ade8e19ac7f335"
},
{
"path": "references/migration-from-v1.md",
"sha256": "d4ba13447a2979bbcb8c191efbb2b5297e21f4fb9ed68057d6994a07b71e26f3"
},
{
"path": "references/file-search-rag-guide.md",
"sha256": "4ebb60d3010e3f0c895a6527e8373f5acc197b3a3c7264147616ef7fe2e3ffb7"
},
{
"path": "references/vector-stores.md",
"sha256": "371cfdd07941bfef867081af3cb7992b2e059e2b2bc1f708e5f574cc7d8713b0"
},
{
"path": "references/thread-lifecycle.md",
"sha256": "01fbc10480407f3930b2b5996fa725e7ec348187069fe952cbbc2648294930c3"
},
{
"path": "scripts/check-versions.sh",
"sha256": "ee917b26e7b8c5afbd0c52e6465929e10efb4f7be086d3497fc00fa365677a63"
},
{
"path": ".claude-plugin/plugin.json",
"sha256": "cf72bb9c4bfad1c013dc123718e612439d656ded9e1250e047fe86ae5433f2b0"
},
{
"path": "templates/vector-store-setup.ts",
"sha256": "9087fa7a3dea50731c5446b906aca8a63c2c0aa241dd1525ff7e294f3abdfa3e"
},
{
"path": "templates/code-interpreter-assistant.ts",
"sha256": "f19e9a37635d9048a9da6e0e01c5c99f7c27cdd47b813f71aff4a6a7da8b93ce"
},
{
"path": "templates/basic-assistant.ts",
"sha256": "439c713f0b0cb45ed039d15cb544221be5244c80fadd47bcf86d51596eac5834"
},
{
"path": "templates/file-search-assistant.ts",
"sha256": "565102a29f23bb5d481a2347f3e0386148cedd40d22524415814ebbbf287a5c0"
},
{
"path": "templates/streaming-assistant.ts",
"sha256": "ae871b3b2855c93e8c6a4ba6d736e7cfb34bf4b552d9ac989b13ae6d618f8eb3"
},
{
"path": "templates/thread-management.ts",
"sha256": "701be59628cfcf8c3c699be04c48b21aa6e90186945360cf30a13f7da8fa99f1"
},
{
"path": "templates/package.json",
"sha256": "7cfdc0841a770146d55b7ae0142ebcd5625f9296bd436f516b45f278143c60bd"
},
{
"path": "templates/function-calling-assistant.ts",
"sha256": "2f3da2dbd8ccdbed3ceab4b9af915dbb01272e7c6c9a04d13cf51f23e490c265"
}
],
"dirSha256": "ff884027d18fceda67c0432d9c653abaa070f9727c7792c2400a0447de04412b"
},
"security": {
"scannedAt": null,
"scannerVersion": null,
"flags": []
}
}

View File

@@ -0,0 +1,242 @@
# Assistants API v2 - Complete Overview
**Version**: v2 (v1 deprecated Dec 18, 2024)
**Status**: Production (Deprecated H1 2026)
**Replacement**: [Responses API](../../openai-responses/SKILL.md)
---
## Architecture
The Assistants API provides stateful conversational AI through four main objects:
```
Assistant (configured AI entity)
Thread (conversation container)
Messages (user + assistant messages)
Runs (execution on thread)
```
---
## Core 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
- **Tool Resources**: Vector stores, files
- **Metadata**: Custom key-value pairs
**Lifecycle**: Create once, reuse many times.
### 2. Threads
Conversation containers:
- **Persistent**: Store entire conversation history
- **Capacity**: Up to 100,000 messages
- **Reusable**: One thread per user for continuity
- **Metadata**: Track ownership, session info
### 3. Messages
Individual conversation turns:
- **Roles**: user, assistant
- **Content**: Text, images, files
- **Attachments**: Files with tool associations
- **Metadata**: Custom tracking info
### 4. Runs
Asynchronous execution:
- **States**: queued → in_progress → completed/failed/requires_action
- **Streaming**: Real-time SSE events
- **Tool Calls**: Automatic handling or requires_action
- **Timeouts**: 10-minute max execution
---
## Workflow Patterns
### Basic Pattern
```typescript
// 1. Create assistant (once)
const assistant = await openai.beta.assistants.create({...});
// 2. Create thread (per conversation)
const thread = await openai.beta.threads.create();
// 3. Add message
await openai.beta.threads.messages.create(thread.id, {...});
// 4. Run
const run = await openai.beta.threads.runs.create(thread.id, {
assistant_id: assistant.id,
});
// 5. Poll for completion
while (run.status !== 'completed') {
await sleep(1000);
run = await openai.beta.threads.runs.retrieve(thread.id, run.id);
}
// 6. Get response
const messages = await openai.beta.threads.messages.list(thread.id);
```
### Streaming Pattern
```typescript
const stream = await openai.beta.threads.runs.stream(thread.id, {
assistant_id: assistant.id,
});
for await (const event of stream) {
if (event.event === 'thread.message.delta') {
process.stdout.write(event.data.delta.content?.[0]?.text?.value || '');
}
}
```
---
## Tools
### 1. Code Interpreter
- **Purpose**: Execute Python code
- **Capabilities**: Data analysis, charts, file processing
- **File Support**: CSV, JSON, images, etc.
- **Outputs**: Text logs, image files
### 2. File Search
- **Purpose**: Semantic search over documents
- **Capacity**: Up to 10,000 files per assistant
- **Technology**: Vector + keyword search
- **Pricing**: $0.10/GB/day (first 1GB free)
### 3. Function Calling
- **Purpose**: Custom tools integration
- **Pattern**: requires_action → submit_tool_outputs
- **Timeout**: Must respond within 10 minutes
- **Parallel**: Multiple functions can be called at once
---
## Key Limits
| Resource | Limit |
|----------|-------|
| Assistant instructions | 256,000 characters |
| Thread messages | 100,000 per thread |
| Tools per assistant | 128 tools |
| Vector store files | 10,000 per assistant |
| File size | 512 MB per file |
| Run execution time | 10 minutes |
| Metadata pairs | 16 per object |
---
## Pricing
### API Calls
- Same as Chat Completions (pay per token)
- Run usage reported in `run.usage`
### Vector Stores
- **Storage**: $0.10/GB/day
- **Free tier**: First 1GB
- **Auto-expiration**: Configurable
---
## Migration Timeline
- **✅ Dec 18, 2024**: v1 deprecated (no longer accessible)
- **⏳ H1 2026**: v2 planned sunset
- **✅ Now**: Responses API available (recommended replacement)
**Action**: Plan migration to Responses API for new projects.
---
## Best Practices
1. **Reuse Assistants**: Create once, use many times
2. **One Thread Per User**: Maintain conversation continuity
3. **Check Active Runs**: Before creating new runs
4. **Stream for UX**: Better user experience than polling
5. **Set Timeouts**: Prevent infinite polling
6. **Clean Up**: Delete old threads and vector stores
7. **Monitor Costs**: Track token usage and storage
---
## Common Patterns
### Multi-User Chatbot
```typescript
const userThreads = new Map<string, string>();
async function getUserThread(userId: string) {
if (!userThreads.has(userId)) {
const thread = await openai.beta.threads.create({
metadata: { user_id: userId },
});
userThreads.set(userId, thread.id);
}
return userThreads.get(userId)!;
}
```
### RAG Application
```typescript
// 1. Create vector store with documents
const vectorStore = await openai.beta.vectorStores.create({...});
await openai.beta.vectorStores.fileBatches.create(vectorStore.id, {...});
// 2. Create assistant with file_search
const assistant = await openai.beta.assistants.create({
tools: [{ type: "file_search" }],
tool_resources: {
file_search: { vector_store_ids: [vectorStore.id] },
},
});
```
### Data Analysis
```typescript
const assistant = await openai.beta.assistants.create({
tools: [{ type: "code_interpreter" }],
});
// Upload data
const file = await openai.files.create({...});
// Attach to message
await openai.beta.threads.messages.create(thread.id, {
content: "Analyze this data",
attachments: [{ file_id: file.id, tools: [{ type: "code_interpreter" }] }],
});
```
---
## Official Documentation
- **API Reference**: https://platform.openai.com/docs/api-reference/assistants
- **Overview**: https://platform.openai.com/docs/assistants/overview
- **Tools**: https://platform.openai.com/docs/assistants/tools
- **Migration**: https://platform.openai.com/docs/assistants/whats-new
---
**Last Updated**: 2025-10-25

View File

@@ -0,0 +1,338 @@
# Code Interpreter Guide
Complete guide to using Code Interpreter with the Assistants API.
---
## What is Code Interpreter?
A built-in tool that executes Python code in a sandboxed environment, enabling:
- Data analysis and processing
- Mathematical computations
- Chart and graph generation
- File parsing (CSV, JSON, Excel, etc.)
- Data transformations
---
## Setup
```typescript
const assistant = await openai.beta.assistants.create({
name: "Data Analyst",
instructions: "You analyze data and create visualizations.",
tools: [{ type: "code_interpreter" }],
model: "gpt-4o",
});
```
---
## File Uploads
### Upload Data Files
```typescript
const file = await openai.files.create({
file: fs.createReadStream("data.csv"),
purpose: "assistants",
});
```
### Attach to Messages
```typescript
await openai.beta.threads.messages.create(thread.id, {
role: "user",
content: "Analyze this sales data",
attachments: [{
file_id: file.id,
tools: [{ type: "code_interpreter" }],
}],
});
```
---
## Supported File Formats
**Data Files**:
- `.csv`, `.json`, `.xlsx` - Tabular data
- `.txt`, `.md` - Text files
- `.pdf`, `.docx`, `.pptx` - Documents (text extraction)
**Code Files**:
- `.py`, `.js`, `.ts`, `.java`, `.cpp` - Source code
**Images** (for processing, not vision):
- `.png`, `.jpg`, `.jpeg`, `.gif` - Image manipulation
**Archives**:
- `.zip`, `.tar` - Compressed files
**Size Limit**: 512 MB per file
---
## Common Use Cases
### 1. Data Analysis
```typescript
const thread = await openai.beta.threads.create({
messages: [{
role: "user",
content: "Calculate the average, median, and standard deviation of the revenue column",
attachments: [{
file_id: csvFileId,
tools: [{ type: "code_interpreter" }],
}],
}],
});
```
### 2. Data Visualization
```typescript
await openai.beta.threads.messages.create(thread.id, {
role: "user",
content: "Create a line chart showing revenue over time",
});
// After run completes, download the generated image
const messages = await openai.beta.threads.messages.list(thread.id);
for (const content of messages.data[0].content) {
if (content.type === 'image_file') {
const imageData = await openai.files.content(content.image_file.file_id);
const buffer = Buffer.from(await imageData.arrayBuffer());
fs.writeFileSync('chart.png', buffer);
}
}
```
### 3. File Conversion
```typescript
await openai.beta.threads.messages.create(thread.id, {
role: "user",
content: "Convert this Excel file to CSV format",
attachments: [{
file_id: excelFileId,
tools: [{ type: "code_interpreter" }],
}],
});
```
---
## Retrieving Outputs
### Text Output
```typescript
const messages = await openai.beta.threads.messages.list(thread.id);
const response = messages.data[0];
for (const content of response.content) {
if (content.type === 'text') {
console.log(content.text.value);
}
}
```
### Generated Files (Charts, CSVs)
```typescript
for (const content of response.content) {
if (content.type === 'image_file') {
const fileId = content.image_file.file_id;
const data = await openai.files.content(fileId);
const buffer = Buffer.from(await data.arrayBuffer());
fs.writeFileSync(`output_${fileId}.png`, buffer);
}
}
```
### Execution Logs
```typescript
const runSteps = await openai.beta.threads.runs.steps.list(thread.id, run.id);
for (const step of runSteps.data) {
if (step.step_details.type === 'tool_calls') {
for (const toolCall of step.step_details.tool_calls) {
if (toolCall.type === 'code_interpreter') {
console.log('Code:', toolCall.code_interpreter.input);
console.log('Output:', toolCall.code_interpreter.outputs);
}
}
}
}
```
---
## Python Environment
### Available Libraries
The Code Interpreter sandbox includes common libraries:
- **Data**: pandas, numpy
- **Math**: scipy, sympy
- **Plotting**: matplotlib, seaborn
- **ML**: scikit-learn (limited)
- **Utils**: requests, PIL, csv, json
**Note**: Not all PyPI packages available. Use standard library where possible.
### Environment Limits
- **Execution Time**: Part of 10-minute run limit
- **Memory**: Limited (exact amount not documented)
- **Disk Space**: Files persist during run only
- **Network**: No outbound internet access
---
## Best Practices
### 1. Clear Instructions
```typescript
// ❌ Vague
"Analyze the data"
// ✅ Specific
"Calculate the mean, median, and mode for each numeric column. Create a bar chart comparing these metrics."
```
### 2. File Download Immediately
```typescript
// Generated files are temporary - download right after completion
if (run.status === 'completed') {
const messages = await openai.beta.threads.messages.list(thread.id);
// Download all image files immediately
for (const message of messages.data) {
for (const content of message.content) {
if (content.type === 'image_file') {
await downloadFile(content.image_file.file_id);
}
}
}
}
```
### 3. Error Handling
```typescript
const runSteps = await openai.beta.threads.runs.steps.list(thread.id, run.id);
for (const step of runSteps.data) {
if (step.step_details.type === 'tool_calls') {
for (const toolCall of step.step_details.tool_calls) {
if (toolCall.type === 'code_interpreter') {
const outputs = toolCall.code_interpreter.outputs;
for (const output of outputs) {
if (output.type === 'logs' && output.logs.includes('Error')) {
console.error('Execution error:', output.logs);
}
}
}
}
}
}
```
---
## Common Patterns
### Pattern: Iterative Analysis
```typescript
// 1. Upload data
const file = await openai.files.create({...});
// 2. Initial analysis
await sendMessage("What are the columns and data types?");
// 3. Follow-up based on results
await sendMessage("Show the distribution of the 'category' column");
// 4. Visualization
await sendMessage("Create a heatmap of correlations between numeric columns");
```
### Pattern: Multi-File Processing
```typescript
await openai.beta.threads.messages.create(thread.id, {
role: "user",
content: "Merge these two CSV files on the 'id' column",
attachments: [
{ file_id: file1Id, tools: [{ type: "code_interpreter" }] },
{ file_id: file2Id, tools: [{ type: "code_interpreter" }] },
],
});
```
---
## Troubleshooting
### Issue: Code Execution Fails
**Symptoms**: Run completes but no output/error in logs
**Solutions**:
- Check file format compatibility
- Verify file isn't corrupted
- Ensure data is in expected format (headers, encoding)
- Try simpler request first to verify setup
### Issue: Generated Files Not Found
**Symptoms**: `image_file.file_id` doesn't exist
**Solutions**:
- Download immediately after run completes
- Check run steps for actual outputs
- Verify code execution succeeded
### Issue: Timeout on Large Files
**Symptoms**: Run exceeds 10-minute limit
**Solutions**:
- Split large files into smaller chunks
- Request specific analysis (not "analyze everything")
- Use sampling for exploratory analysis
---
## Example Prompts
**Data Exploration**:
- "Summarize this dataset: shape, columns, data types, missing values"
- "Show the first 10 rows"
- "What are the unique values in the 'status' column?"
**Statistical Analysis**:
- "Calculate descriptive statistics for all numeric columns"
- "Perform correlation analysis between price and quantity"
- "Detect outliers using the IQR method"
**Visualization**:
- "Create a histogram of the 'age' distribution"
- "Plot revenue trends over time with a moving average"
- "Generate a scatter plot of height vs weight, colored by gender"
**Data Transformation**:
- "Remove rows with missing values"
- "Normalize the 'sales' column to 0-1 range"
- "Convert dates to YYYY-MM-DD format"
---
**Last Updated**: 2025-10-25

View File

@@ -0,0 +1,364 @@
# File Search & RAG Guide
Complete guide to implementing Retrieval-Augmented Generation (RAG) with the Assistants API.
---
## What is File Search?
A built-in tool for semantic search over documents using vector stores:
- **Capacity**: Up to 10,000 files per assistant (vs 20 in v1)
- **Technology**: Vector + keyword search with reranking
- **Automatic**: Chunking, embedding, and indexing handled by OpenAI
- **Pricing**: $0.10/GB/day (first 1GB free)
---
## Architecture
```
Documents (PDF, DOCX, MD, etc.)
Vector Store (chunking + embeddings)
Assistant with file_search tool
Semantic Search + Reranking
Retrieved Context + LLM Generation
```
---
## Quick Setup
### 1. Create Vector Store
```typescript
const vectorStore = await openai.beta.vectorStores.create({
name: "Product Documentation",
expires_after: {
anchor: "last_active_at",
days: 30,
},
});
```
### 2. Upload Documents
```typescript
const files = await Promise.all([
openai.files.create({ file: fs.createReadStream("doc1.pdf"), purpose: "assistants" }),
openai.files.create({ file: fs.createReadStream("doc2.md"), purpose: "assistants" }),
]);
const batch = await openai.beta.vectorStores.fileBatches.create(vectorStore.id, {
file_ids: files.map(f => f.id),
});
```
### 3. Wait for Indexing
```typescript
let batch = await openai.beta.vectorStores.fileBatches.retrieve(vectorStore.id, batch.id);
while (batch.status === 'in_progress') {
await new Promise(r => setTimeout(r, 2000));
batch = await openai.beta.vectorStores.fileBatches.retrieve(vectorStore.id, batch.id);
}
```
### 4. Create Assistant
```typescript
const assistant = await openai.beta.assistants.create({
name: "Knowledge Base Assistant",
instructions: "Answer questions using the file search tool. Always cite your sources.",
tools: [{ type: "file_search" }],
tool_resources: {
file_search: {
vector_store_ids: [vectorStore.id],
},
},
model: "gpt-4o",
});
```
---
## Supported File Formats
- `.pdf` - PDFs (most common)
- `.docx` - Word documents
- `.md`, `.txt` - Plain text
- `.html` - HTML documents
- `.json` - JSON data
- `.py`, `.js`, `.ts`, `.cpp`, `.java` - Code files
**Size Limits**:
- **Per file**: 512 MB
- **Total per vector store**: Limited by pricing ($0.10/GB/day)
---
## Chunking Strategy
OpenAI automatically chunks documents using:
- **Max chunk size**: ~800 tokens (configurable internally)
- **Overlap**: Ensures context continuity
- **Hierarchy**: Preserves document structure (headers, sections)
### Optimize for Better Results
**Document Structure**:
```markdown
# Main Topic
## Subtopic 1
Content here...
## Subtopic 2
Content here...
```
**Clear Sections**: Use headers to organize content
**Concise Paragraphs**: Avoid very long paragraphs (500+ words)
**Self-Contained**: Each section should make sense independently
---
## Improving Search Quality
### 1. Better Instructions
```typescript
const assistant = await openai.beta.assistants.create({
instructions: `You are a support assistant. When answering:
1. Use file_search to find relevant information
2. Synthesize information from multiple sources
3. Always provide citations with file names
4. If information isn't found, say so clearly
5. Don't make up information not in the documents`,
tools: [{ type: "file_search" }],
// ...
});
```
### 2. Query Refinement
Encourage users to be specific:
- ❌ "How do I install?"
- ✅ "How do I install the product on Windows 10?"
### 3. Multi-Document Answers
File Search automatically retrieves from multiple documents and combines information.
---
## Citations
### Accessing Citations
```typescript
const messages = await openai.beta.threads.messages.list(thread.id);
const response = messages.data[0];
for (const content of response.content) {
if (content.type === 'text') {
console.log('Answer:', content.text.value);
// Citations
if (content.text.annotations) {
for (const annotation of content.text.annotations) {
if (annotation.type === 'file_citation') {
console.log('Source:', annotation.file_citation.file_id);
console.log('Quote:', annotation.file_citation.quote);
}
}
}
}
}
```
### Displaying Citations
```typescript
let answer = response.content[0].text.value;
// Replace citation markers with clickable links
for (const annotation of response.content[0].text.annotations) {
if (annotation.type === 'file_citation') {
const citation = `[${annotation.text}](source: ${annotation.file_citation.file_id})`;
answer = answer.replace(annotation.text, citation);
}
}
console.log(answer);
```
---
## Cost Management
### Pricing Structure
- **Storage**: $0.10/GB/day
- **Free tier**: First 1GB
- **Example**: 5GB = $0.40/day = $12/month
### Optimization Strategies
1. **Auto-Expiration**:
```typescript
const vectorStore = await openai.beta.vectorStores.create({
expires_after: {
anchor: "last_active_at",
days: 7, // Delete after 7 days of inactivity
},
});
```
2. **Cleanup Old Stores**:
```typescript
async function cleanupOldVectorStores() {
const stores = await openai.beta.vectorStores.list({ limit: 100 });
for (const store of stores.data) {
const ageDays = (Date.now() / 1000 - store.created_at) / (60 * 60 * 24);
if (ageDays > 30) {
await openai.beta.vectorStores.del(store.id);
}
}
}
```
3. **Monitor Usage**:
```typescript
const store = await openai.beta.vectorStores.retrieve(vectorStoreId);
const sizeGB = store.usage_bytes / (1024 * 1024 * 1024);
const costPerDay = Math.max(0, (sizeGB - 1) * 0.10);
console.log(`Daily cost: $${costPerDay.toFixed(4)}`);
```
---
## Advanced Patterns
### Pattern: Multi-Tenant Knowledge Bases
```typescript
// Separate vector store per tenant
const tenantStore = await openai.beta.vectorStores.create({
name: `Tenant ${tenantId} KB`,
metadata: { tenant_id: tenantId },
});
// Or: Single store with namespace simulation via file metadata
await openai.files.create({
file: fs.createReadStream("doc.pdf"),
purpose: "assistants",
metadata: { tenant_id: tenantId }, // Coming soon
});
```
### Pattern: Versioned Documentation
```typescript
// Version 1.0
const v1Store = await openai.beta.vectorStores.create({
name: "Docs v1.0",
metadata: { version: "1.0" },
});
// Version 2.0
const v2Store = await openai.beta.vectorStores.create({
name: "Docs v2.0",
metadata: { version: "2.0" },
});
// Switch based on user preference
const storeId = userVersion === "1.0" ? v1Store.id : v2Store.id;
```
### Pattern: Hybrid Search (File Search + Code Interpreter)
```typescript
const assistant = await openai.beta.assistants.create({
tools: [
{ type: "file_search" },
{ type: "code_interpreter" },
],
tool_resources: {
file_search: {
vector_store_ids: [docsVectorStoreId],
},
},
});
// Assistant can search docs AND analyze attached data files
await openai.beta.threads.messages.create(thread.id, {
content: "Compare this sales data against the targets in our planning docs",
attachments: [{
file_id: salesDataFileId,
tools: [{ type: "code_interpreter" }],
}],
});
```
---
## Troubleshooting
### No Results Found
**Causes**:
- Vector store not fully indexed
- Poor query formulation
- Documents lack relevant content
**Solutions**:
- Wait for `status: "completed"`
- Refine query to be more specific
- Check document quality and structure
### Irrelevant Results
**Causes**:
- Poor document structure
- Too much noise in documents
- Vague queries
**Solutions**:
- Add clear section headers
- Remove boilerplate/repetitive content
- Improve query specificity
### High Costs
**Causes**:
- Too many vector stores
- Large files that don't expire
- Duplicate content
**Solutions**:
- Set auto-expiration
- Deduplicate documents
- Delete unused stores
---
## Best Practices
1. **Structure documents** with clear headers and sections
2. **Wait for indexing** before using vector store
3. **Set auto-expiration** to manage costs
4. **Monitor storage** regularly
5. **Provide citations** in responses
6. **Refine queries** for better results
7. **Clean up** old vector stores
---
**Last Updated**: 2025-10-25

View File

@@ -0,0 +1,188 @@
# Migration from Assistants API v1 to v2
**v1 Deprecated**: December 18, 2024 (no longer accessible)
**v2 Status**: Production (Deprecated H1 2026 in favor of Responses API)
---
## Breaking Changes
### 1. Retrieval Tool → File Search
**v1:**
```typescript
{
tools: [{ type: "retrieval" }],
file_ids: ["file_abc123", "file_def456"]
}
```
**v2:**
```typescript
{
tools: [{ type: "file_search" }],
tool_resources: {
file_search: {
vector_store_ids: ["vs_abc123"]
}
}
}
```
**Action**: Create vector stores and migrate files.
### 2. File Attachments
**v1**: Files attached at assistant level
**v2**: Files attached at message level
**v1:**
```typescript
const assistant = await openai.beta.assistants.create({
file_ids: ["file_abc123"],
});
```
**v2:**
```typescript
await openai.beta.threads.messages.create(thread.id, {
content: "...",
attachments: [{
file_id: "file_abc123",
tools: [{ type: "code_interpreter" }]
}],
});
```
### 3. Instructions Character Limit
- **v1**: 32,000 characters
- **v2**: 256,000 characters (8x increase)
---
## Migration Steps
### Step 1: Create Vector Stores
```typescript
// Old v1 approach
const assistant = await openai.beta.assistants.create({
tools: [{ type: "retrieval" }],
file_ids: fileIds, // Direct attachment
});
// New v2 approach
const vectorStore = await openai.beta.vectorStores.create({
name: "Knowledge Base",
});
await openai.beta.vectorStores.fileBatches.create(vectorStore.id, {
file_ids: fileIds,
});
const assistant = await openai.beta.assistants.create({
tools: [{ type: "file_search" }],
tool_resources: {
file_search: {
vector_store_ids: [vectorStore.id],
},
},
});
```
### Step 2: Update File Attachments
```typescript
// Move file attachments from assistant to messages
await openai.beta.threads.messages.create(thread.id, {
role: "user",
content: "Analyze this file",
attachments: [{
file_id: "file_abc123",
tools: [{ type: "code_interpreter" }],
}],
});
```
### Step 3: Test Thoroughly
- Verify file search returns expected results
- Check Code Interpreter file handling
- Test streaming if used
- Validate function calling patterns
---
## New v2 Features
### 1. Massive File Capacity
- **v1**: ~20 files per assistant
- **v2**: 10,000 files per assistant (500x increase)
### 2. Better Search Performance
- Vector + keyword search
- Parallel query processing
- Advanced reranking
### 3. Auto-Expiration
```typescript
const vectorStore = await openai.beta.vectorStores.create({
expires_after: {
anchor: "last_active_at",
days: 30,
},
});
```
### 4. Batch File Operations
```typescript
const batch = await openai.beta.vectorStores.fileBatches.create(vectorStoreId, {
file_ids: ["file_1", "file_2", "file_3"],
});
```
---
## Cost Implications
### v1 (Deprecated)
- No separate storage costs for retrieval
### v2
- **Storage**: $0.10/GB/day for vector stores
- **Free tier**: First 1GB
- **Optimization**: Use auto-expiration
---
## Recommended Path Forward
**For existing v1 applications:**
1. Migrate to v2 immediately (v1 no longer works)
2. Plan migration to Responses API (v2 sunset in H1 2026)
**For new applications:**
- ✅ Use [Responses API](../../openai-responses/SKILL.md)
- ❌ Don't use Assistants API (being deprecated)
---
## Migration Checklist
- [ ] Update to openai SDK 6.7.0+
- [ ] Create vector stores for file search
- [ ] Migrate file attachments to message level
- [ ] Test file search results
- [ ] Update to `file_search` from `retrieval`
- [ ] Implement vector store cleanup
- [ ] Monitor storage costs
- [ ] Plan migration to Responses API
---
**Last Updated**: 2025-10-25

View File

@@ -0,0 +1,271 @@
# Thread Lifecycle Management
Complete guide to managing threads effectively to avoid errors and optimize performance.
---
## Thread States
A thread progresses through these states based on run activity:
```
Idle (no active runs)
Active (run in progress)
Requires Action (function calling)
Completed / Failed / Cancelled
Idle (ready for next run)
```
---
## Common Patterns
### Pattern 1: One Thread Per User
**Use Case**: Chatbots, support assistants
```typescript
// In-memory cache (use database in production)
const userThreads = new Map<string, string>();
async function getOrCreateUserThread(userId: string): Promise<string> {
let threadId = userThreads.get(userId);
if (!threadId) {
const thread = await openai.beta.threads.create({
metadata: { user_id: userId },
});
threadId = thread.id;
userThreads.set(userId, threadId);
}
return threadId;
}
```
**Benefits**:
- Conversation continuity
- Automatic history management
- Simple architecture
**Drawbacks**:
- Long threads consume memory
- 100k message limit eventually
### Pattern 2: Session-Based Threads
**Use Case**: Temporary conversations
```typescript
async function createSessionThread(userId: string, sessionId: string) {
return await openai.beta.threads.create({
metadata: {
user_id: userId,
session_id: sessionId,
expires_at: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(),
},
});
}
```
**Benefits**:
- Clear boundaries
- Easy cleanup
- Fresh context
### Pattern 3: Topic-Based Threads
**Use Case**: Multi-topic conversations
```typescript
async function getTopicThread(userId: string, topic: string) {
const key = `${userId}:${topic}`;
// Separate threads for different topics
return await getOrCreateThread(key);
}
```
---
## Active Run Management
### Check for Active Runs
```typescript
async function hasActiveRun(threadId: string): Promise<boolean> {
const runs = await openai.beta.threads.runs.list(threadId, {
limit: 1,
order: 'desc',
});
const latestRun = runs.data[0];
return latestRun && ['queued', 'in_progress', 'cancelling'].includes(latestRun.status);
}
```
### Safe Run Creation
```typescript
async function createRunSafely(
threadId: string,
assistantId: string,
cancelIfActive = true
) {
// Check for active runs
const runs = await openai.beta.threads.runs.list(threadId, {
limit: 1,
order: 'desc',
});
const latestRun = runs.data[0];
if (latestRun && ['queued', 'in_progress'].includes(latestRun.status)) {
if (cancelIfActive) {
await openai.beta.threads.runs.cancel(threadId, latestRun.id);
// Wait for cancellation
while (latestRun.status !== 'cancelled') {
await new Promise(r => setTimeout(r, 500));
latestRun = await openai.beta.threads.runs.retrieve(threadId, latestRun.id);
}
} else {
throw new Error('Thread has active run');
}
}
return await openai.beta.threads.runs.create(threadId, {
assistant_id: assistantId,
});
}
```
---
## Cleanup Strategies
### Time-Based Cleanup
```typescript
async function cleanupOldThreads(maxAgeHours: number = 24) {
// Query your database for old threads
const oldThreads = await db.getThreadsOlderThan(maxAgeHours);
for (const threadId of oldThreads) {
await openai.beta.threads.del(threadId);
await db.deleteThreadRecord(threadId);
}
}
```
### Message Count-Based
```typescript
async function archiveIfTooLong(threadId: string, maxMessages: number = 1000) {
const messages = await openai.beta.threads.messages.list(threadId);
if (messages.data.length >= maxMessages) {
// Archive to database
await db.archiveThread(threadId, messages.data);
// Create new thread
return await openai.beta.threads.create({
metadata: { previous_thread: threadId },
});
}
return threadId;
}
```
### Safe Deletion
```typescript
async function safeDeleteThread(threadId: string) {
// Cancel all active runs first
const runs = await openai.beta.threads.runs.list(threadId);
for (const run of runs.data) {
if (['queued', 'in_progress'].includes(run.status)) {
await openai.beta.threads.runs.cancel(threadId, run.id);
}
}
// Wait a moment for cancellations
await new Promise(r => setTimeout(r, 1000));
// Delete thread
await openai.beta.threads.del(threadId);
}
```
---
## Error Handling
### Concurrent Run Prevention
```typescript
class ThreadManager {
private locks = new Map<string, Promise<any>>();
async executeRun(threadId: string, assistantId: string) {
// Wait if another run is in progress
if (this.locks.has(threadId)) {
await this.locks.get(threadId);
}
// Create lock
const runPromise = this._runAssistant(threadId, assistantId);
this.locks.set(threadId, runPromise);
try {
return await runPromise;
} finally {
this.locks.delete(threadId);
}
}
private async _runAssistant(threadId: string, assistantId: string) {
// Run logic here
}
}
```
---
## Best Practices
1. **Always check for active runs** before creating new ones
2. **Use metadata** to track ownership and expiration
3. **Implement cleanup** to manage costs
4. **Set reasonable limits** (message count, age)
5. **Handle errors gracefully** (active run conflicts)
6. **Archive old conversations** before deletion
7. **Use locks** for concurrent access
---
## Monitoring
```typescript
async function getThreadStats(threadId: string) {
const thread = await openai.beta.threads.retrieve(threadId);
const messages = await openai.beta.threads.messages.list(threadId);
const runs = await openai.beta.threads.runs.list(threadId);
return {
threadId: thread.id,
createdAt: new Date(thread.created_at * 1000),
messageCount: messages.data.length,
runCount: runs.data.length,
lastRun: runs.data[0],
metadata: thread.metadata,
};
}
```
---
**Last Updated**: 2025-10-25

446
references/top-errors.md Normal file
View File

@@ -0,0 +1,446 @@
# Top 12 Errors - OpenAI Assistants API
**Last Updated**: 2025-10-25
This document catalogs the most common errors encountered when working with the Assistants API v2 and their solutions.
---
## 1. Thread Already Has Active Run
**Error Message**:
```
Error: 400 Can't add messages to thread_xxx while a run run_xxx is active.
```
**Cause**: Trying to create a new run or add messages while another run is still processing (status: `queued`, `in_progress`, or `cancelling`).
**Solution**:
```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)) {
// Wait for completion or cancel
await openai.beta.threads.runs.cancel(threadId, latestRun.id);
// Poll until cancelled
let run = latestRun;
while (run.status !== 'cancelled') {
await new Promise(resolve => setTimeout(resolve, 500));
run = await openai.beta.threads.runs.retrieve(threadId, run.id);
}
}
}
```
**Prevention**: Always check for active runs before creating new ones.
**Source**: [OpenAI Community](https://community.openai.com/t/error-running-thread-already-has-an-active-run/782118)
---
## 2. Run Polling Timeout
**Error**: Run never completes within reasonable polling window (300+ seconds).
**Cause**: Long-running tasks (complex code execution, large file processing) exceed expected completion time.
**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 of ${maxSeconds}s`);
}
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
```
**Prevention**: Set appropriate timeouts and use streaming for better UX.
---
## 3. Vector Store Indexing Delay
**Error**: File search returns no results despite files being uploaded.
**Cause**: Using vector store before indexing completes (async process).
**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);
console.log(`Indexing: ${store.file_counts.completed}/${store.file_counts.total}`);
}
if (store.status === 'failed') {
throw new Error('Vector store indexing failed');
}
return store; // status: 'completed'
}
```
**Prevention**: Always wait for `status: "completed"` before using vector store with assistants.
**Source**: [OpenAI Community](https://community.openai.com/t/assistants-api-file-search-and-vector-stores/863944)
---
## 4. File Search Relevance Issues
**Error**: File search returns irrelevant or incomplete results.
**Cause**: Poor document chunking, lack of context, or query optimization needed.
**Solution**:
- **Better instructions**: Guide assistant on how to use file search
- **Structured documents**: Use clear headers, sections, and formatting
- **Metadata**: Add descriptive metadata to files (coming soon)
- **Query refinement**: Encourage users to be specific
```typescript
const assistant = await openai.beta.assistants.create({
instructions: `You are a support assistant. When answering:
1. Use file_search to find relevant documentation
2. Quote specific sections with citations
3. If information isn't found, say so clearly
4. Provide context around the answer`,
tools: [{ type: "file_search" }],
// ...
});
```
**Prevention**: Structure documents well and provide clear assistant instructions.
---
## 5. Code Interpreter File Output Not Found
**Error**: `image_file.file_id` referenced but file doesn't exist or can't be downloaded.
**Cause**: Files generated by Code Interpreter are temporary and may be cleaned up before retrieval.
**Solution**:
```typescript
// Retrieve and save immediately after run completes
const messages = await openai.beta.threads.messages.list(threadId);
const responseMessage = messages.data[0];
for (const content of responseMessage.content) {
if (content.type === 'image_file') {
try {
const fileData = await openai.files.content(content.image_file.file_id);
const buffer = Buffer.from(await fileData.arrayBuffer());
fs.writeFileSync(`output_${content.image_file.file_id}.png`, buffer);
} catch (error) {
console.error('File no longer available:', error);
}
}
}
```
**Prevention**: Download generated files immediately after run completion.
**Source**: [Medium - Code Interpreter Tutorial](https://tmmtt.medium.com/openai-assistant-api-with-code-interpreter-e7f382bff83e)
---
## 6. Thread Message Limit Exceeded
**Error**: `400 Thread has exceeded the maximum number of messages (100,000)`.
**Cause**: Very long conversations hitting the 100k message limit.
**Solution**:
```typescript
async function archiveAndStartNewThread(oldThreadId: string, userId: string) {
// Get conversation summary
const messages = await openai.beta.threads.messages.list(oldThreadId, {
limit: 50,
});
// Save to database
await db.archiveThread(oldThreadId, messages.data);
// Create new thread
const newThread = await openai.beta.threads.create({
metadata: {
user_id: userId,
previous_thread: oldThreadId,
},
});
return newThread.id;
}
```
**Prevention**: Archive old threads and create new ones periodically.
---
## 7. Function Calling Timeout
**Error**: Run expires (status: `expired`) while waiting for tool outputs.
**Cause**: Tool execution takes too long (max 10 minutes for run).
**Solution**:
```typescript
if (run.status === 'requires_action') {
const toolCalls = run.required_action.submit_tool_outputs.tool_calls;
const toolOutputs = [];
for (const toolCall of toolCalls) {
try {
// Add timeout to function execution
const output = await Promise.race([
executeFunction(toolCall.function.name, JSON.parse(toolCall.function.arguments)),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Function timeout')), 30000)
),
]);
toolOutputs.push({
tool_call_id: toolCall.id,
output: JSON.stringify(output),
});
} catch (error) {
// Return error as output
toolOutputs.push({
tool_call_id: toolCall.id,
output: JSON.stringify({ error: error.message }),
});
}
}
await openai.beta.threads.runs.submitToolOutputs(threadId, run.id, {
tool_outputs: toolOutputs,
});
}
```
**Prevention**: Implement timeouts on function execution and return errors gracefully.
---
## 8. Streaming Run Interruption
**Error**: Stream connection closes unexpectedly or events stop arriving.
**Cause**: Network issues, server errors, or run failures.
**Solution**:
```typescript
async function streamWithRetry(threadId: string, assistantId: string) {
try {
const stream = await openai.beta.threads.runs.stream(threadId, {
assistant_id: assistantId,
});
for await (const event of stream) {
// Handle events
if (event.event === 'error') {
throw new Error('Stream error');
}
}
} catch (error) {
console.error('Stream interrupted:', error);
// Fall back to polling
const runs = await openai.beta.threads.runs.list(threadId, {
limit: 1,
order: 'desc',
});
const run = runs.data[0];
return pollRunCompletion(threadId, run.id);
}
}
```
**Prevention**: Implement fallback to polling if streaming fails.
**Source**: [OpenAI Community](https://community.openai.com/t/streaming-stopped-at-thread-run-requires-action-when-handling-openai-assistants-function-calling/943674)
---
## 9. Vector Store Quota Limits
**Error**: `429 Rate limit reached for vector store operations`.
**Cause**: Too many vector store operations or storage exceeded.
**Solution**:
- **Monitor storage**: Check `usage_bytes` regularly
- **Delete unused stores**: Clean up old vector stores
- **Batch operations**: Use batch file uploads instead of individual uploads
```typescript
async function cleanupOldVectorStores(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);
console.log(`Deleted vector store: ${store.id}`);
}
}
}
```
**Prevention**: Set auto-expiration on vector stores and monitor costs.
---
## 10. File Upload Format Incompatibility
**Error**: `400 Unsupported file type for this tool`.
**Cause**: Uploading file format not supported by the tool.
**Solution**:
```typescript
const 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'
],
};
function validateFileFormat(filename: string, tool: 'code_interpreter' | 'file_search') {
const ext = filename.substring(filename.lastIndexOf('.')).toLowerCase();
if (!SUPPORTED_FORMATS[tool].includes(ext)) {
throw new Error(`Unsupported file format for ${tool}: ${ext}`);
}
}
// Validate before upload
validateFileFormat('data.csv', 'code_interpreter'); // OK
validateFileFormat('video.mp4', 'file_search'); // Throws error
```
**Prevention**: Validate file formats before uploading.
---
## 11. Assistant Instructions Token Limit
**Error**: `400 Instructions exceed maximum length`.
**Cause**: Instructions field exceeds 256,000 characters (v2 limit).
**Solution**:
- **Use file search**: Put long instructions in documents
- **Concise instructions**: Be clear and brief
- **System messages**: Use thread-level messages for context
```typescript
// ❌ Bad: Very long instructions
const assistant = await openai.beta.assistants.create({
instructions: "..." // 300k characters
});
// ✅ Good: Concise instructions + file search
const assistant = await openai.beta.assistants.create({
instructions: "You are a support assistant. Use file_search to find answers in the knowledge base.",
tools: [{ type: "file_search" }],
tool_resources: {
file_search: {
vector_store_ids: [vectorStoreId], // Long content here
},
},
});
```
**Prevention**: Keep instructions under 256k characters; use file search for knowledge.
---
## 12. Thread Deletion While Run Active
**Error**: `400 Cannot delete thread while run is active`.
**Cause**: Attempting to delete a thread that has an active run.
**Solution**:
```typescript
async function safeDeleteThread(threadId: string) {
// Cancel active runs first
const runs = await openai.beta.threads.runs.list(threadId);
for (const run of runs.data) {
if (['queued', 'in_progress'].includes(run.status)) {
await openai.beta.threads.runs.cancel(threadId, run.id);
// Wait for cancellation
let runStatus = run;
while (runStatus.status !== 'cancelled') {
await new Promise(resolve => setTimeout(resolve, 500));
runStatus = await openai.beta.threads.runs.retrieve(threadId, run.id);
}
}
}
// Now safe to delete
await openai.beta.threads.del(threadId);
}
```
**Prevention**: Cancel all active runs before deleting threads.
---
## Quick Reference
| Error | Quick Fix |
|-------|-----------|
| Thread has active run | Cancel or wait for run completion |
| Polling timeout | Set timeout and cancel long runs |
| Vector store not ready | Wait for `status: "completed"` |
| File search no results | Check indexing complete, improve queries |
| Code Interpreter file lost | Download immediately after run |
| 100k message limit | Archive old threads, start new ones |
| Function timeout | Add timeouts to function execution |
| Stream interrupted | Fall back to polling |
| Vector store quota | Clean up old stores, use batch uploads |
| Unsupported file format | Validate file extensions before upload |
| Instructions too long | Use file search for knowledge |
| Can't delete thread | Cancel active runs first |
---
**Additional Resources**:
- [OpenAI Assistants API Docs](https://platform.openai.com/docs/assistants)
- [OpenAI Community Forum](https://community.openai.com/c/api/assistants-api/49)
- [API Reference](https://platform.openai.com/docs/api-reference/assistants)

422
references/vector-stores.md Normal file
View File

@@ -0,0 +1,422 @@
# Vector Stores - Complete Reference
In-depth guide to OpenAI's Vector Stores for the Assistants API.
---
## Overview
Vector Stores provide scalable semantic search infrastructure for the file_search tool:
- **Capacity**: Up to 10,000 files per assistant
- **Automatic**: Chunking, embedding, indexing
- **Search**: Vector + keyword hybrid with reranking
- **Pricing**: $0.10/GB/day (first 1GB free)
---
## Creating Vector Stores
### Basic Creation
```typescript
const vectorStore = await openai.beta.vectorStores.create({
name: "Company Knowledge Base",
});
```
### With Auto-Expiration
```typescript
const vectorStore = await openai.beta.vectorStores.create({
name: "Temporary KB",
expires_after: {
anchor: "last_active_at",
days: 7,
},
});
```
**Anchors**:
- `last_active_at`: Expires N days after last use (recommended)
- `created_at`: Expires N days after creation (not yet available)
### With Metadata
```typescript
const vectorStore = await openai.beta.vectorStores.create({
name: "Q4 2025 Documentation",
metadata: {
department: "sales",
quarter: "Q4-2025",
version: "1.0",
},
});
```
---
## Adding Files
### Single File Upload
```typescript
// 1. Upload file to OpenAI
const file = await openai.files.create({
file: fs.createReadStream("document.pdf"),
purpose: "assistants",
});
// 2. Add to vector store
await openai.beta.vectorStores.files.create(vectorStore.id, {
file_id: file.id,
});
```
### Batch Upload (Recommended)
```typescript
// Upload multiple files
const files = await Promise.all([
openai.files.create({ file: fs.createReadStream("doc1.pdf"), purpose: "assistants" }),
openai.files.create({ file: fs.createReadStream("doc2.md"), purpose: "assistants" }),
openai.files.create({ file: fs.createReadStream("doc3.docx"), purpose: "assistants" }),
]);
// Batch add to vector store
const batch = await openai.beta.vectorStores.fileBatches.create(vectorStore.id, {
file_ids: files.map(f => f.id),
});
// Monitor progress
let batchStatus = batch;
while (batchStatus.status === 'in_progress') {
await new Promise(r => setTimeout(r, 1000));
batchStatus = await openai.beta.vectorStores.fileBatches.retrieve(
vectorStore.id,
batch.id
);
console.log(`${batchStatus.file_counts.completed}/${batchStatus.file_counts.total}`);
}
```
**Benefits of Batch Upload**:
- Faster processing (parallel indexing)
- Single operation to track
- Better error handling
---
## Vector Store States
| State | Description |
|-------|-------------|
| `in_progress` | Files being indexed |
| `completed` | All files indexed successfully |
| `failed` | Indexing failed |
| `expired` | Auto-expiration triggered |
**Important**: Wait for `completed` before using with assistants.
---
## File Management
### List Files in Vector Store
```typescript
const files = await openai.beta.vectorStores.files.list(vectorStore.id, {
limit: 100,
});
for (const file of files.data) {
console.log(`${file.id}: ${file.status}`);
}
```
### Remove File from Vector Store
```typescript
await openai.beta.vectorStores.files.del(vectorStore.id, fileId);
```
**Note**: This removes the file from the vector store but doesn't delete the file from OpenAI's storage.
### Check File Status
```typescript
const file = await openai.beta.vectorStores.files.retrieve(vectorStore.id, fileId);
console.log(file.status); // "in_progress", "completed", "failed"
if (file.status === 'failed') {
console.error(file.last_error);
}
```
---
## Pricing & Cost Management
### Pricing Structure
- **Storage**: $0.10 per GB per day
- **Free tier**: First 1GB
- **Calculation**: Total vector store size (not original file size)
**Example Costs**:
| Original Files | Vector Store Size | Daily Cost | Monthly Cost |
|----------------|-------------------|------------|--------------|
| 500 MB | 0.5 GB | $0.00 | $0.00 (free tier) |
| 2 GB | 2 GB | $0.10 | $3.00 |
| 10 GB | 10 GB | $0.90 | $27.00 |
| 50 GB | 50 GB | $4.90 | $147.00 |
### Monitor Usage
```typescript
const store = await openai.beta.vectorStores.retrieve(vectorStoreId);
const sizeGB = store.usage_bytes / (1024 * 1024 * 1024);
const costPerDay = Math.max(0, (sizeGB - 1) * 0.10);
const costPerMonth = costPerDay * 30;
console.log(`Storage: ${sizeGB.toFixed(2)} GB`);
console.log(`Cost: $${costPerDay.toFixed(4)}/day ($${costPerMonth.toFixed(2)}/month)`);
```
### Cost Optimization
**1. Auto-Expiration**:
```typescript
expires_after: {
anchor: "last_active_at",
days: 30,
}
```
**2. Regular Cleanup**:
```typescript
async function cleanupUnusedVectorStores() {
const stores = await openai.beta.vectorStores.list({ limit: 100 });
for (const store of stores.data) {
const ageDays = (Date.now() / 1000 - store.created_at) / (60 * 60 * 24);
if (ageDays > 90) { // 90 days old
await openai.beta.vectorStores.del(store.id);
console.log(`Deleted: ${store.name}`);
}
}
}
```
**3. Deduplicate Content**:
- Remove duplicate files before upload
- Combine similar documents
- Archive old versions
---
## Using with Assistants
### Attach to Assistant
```typescript
const assistant = await openai.beta.assistants.create({
name: "Support Bot",
tools: [{ type: "file_search" }],
tool_resources: {
file_search: {
vector_store_ids: [vectorStore.id],
},
},
model: "gpt-4o",
});
```
### Multiple Vector Stores
```typescript
// Combine multiple knowledge bases
tool_resources: {
file_search: {
vector_store_ids: [generalKBId, productDocsId, policyDocsId],
},
}
```
**Limit**: Maximum of 1 vector store per assistant in current API (subject to change).
---
## Advanced Operations
### Update Metadata
```typescript
const updated = await openai.beta.vectorStores.update(vectorStoreId, {
name: "Updated Name",
metadata: {
version: "2.0",
last_updated: new Date().toISOString(),
},
});
```
### Retrieve Vector Store Details
```typescript
const store = await openai.beta.vectorStores.retrieve(vectorStoreId);
console.log({
id: store.id,
name: store.name,
status: store.status,
usage_bytes: store.usage_bytes,
file_counts: store.file_counts,
created_at: new Date(store.created_at * 1000),
expires_at: store.expires_at ? new Date(store.expires_at * 1000) : null,
metadata: store.metadata,
});
```
### List All Vector Stores
```typescript
const stores = await openai.beta.vectorStores.list({
limit: 20,
order: "desc",
});
for (const store of stores.data) {
console.log(`${store.name}: ${store.file_counts.completed} files`);
}
```
---
## Best Practices
### 1. Pre-Process Documents
- Remove headers/footers
- Clean formatting
- Extract text from images (OCR separately)
- Organize with clear structure
### 2. Monitor Indexing
```typescript
async function waitForIndexing(vectorStoreId: string, batchId: string) {
let batch;
const startTime = Date.now();
do {
batch = await openai.beta.vectorStores.fileBatches.retrieve(vectorStoreId, batchId);
if (batch.status === 'failed') {
throw new Error('Batch indexing failed');
}
console.log(`Progress: ${batch.file_counts.completed}/${batch.file_counts.total}`);
await new Promise(r => setTimeout(r, 2000));
// Timeout after 10 minutes
if (Date.now() - startTime > 600000) {
throw new Error('Indexing timeout');
}
} while (batch.status === 'in_progress');
return batch;
}
```
### 3. Set Reasonable Expiration
```typescript
// For temporary projects
expires_after: { anchor: "last_active_at", days: 7 }
// For active knowledge bases
expires_after: { anchor: "last_active_at", days: 90 }
// For permanent KB (no expiration)
// Don't set expires_after
```
### 4. Tag with Metadata
```typescript
metadata: {
project: "project-alpha",
environment: "production",
version: "1.0",
owner: "team@company.com",
}
```
---
## Troubleshooting
### Files Not Indexing
**Check file status**:
```typescript
const file = await openai.beta.vectorStores.files.retrieve(vectorStoreId, fileId);
if (file.status === 'failed') {
console.error(file.last_error);
}
```
**Common causes**:
- Unsupported file format
- Corrupted file
- File too large (>512 MB)
### Vector Store Shows `failed` Status
**Check batch details**:
```typescript
const batch = await openai.beta.vectorStores.fileBatches.retrieve(vectorStoreId, batchId);
console.log(batch.file_counts); // Check failed count
```
**Solutions**:
- Remove failed files
- Re-upload with correct format
- Check error messages
### High Storage Costs
**Audit vector stores**:
```typescript
const stores = await openai.beta.vectorStores.list({ limit: 100 });
let totalGB = 0;
for (const store of stores.data) {
const sizeGB = store.usage_bytes / (1024 * 1024 * 1024);
totalGB += sizeGB;
console.log(`${store.name}: ${sizeGB.toFixed(2)} GB`);
}
console.log(`Total: ${totalGB.toFixed(2)} GB = $${((totalGB - 1) * 0.10).toFixed(2)}/day`);
```
---
## Limits
| Resource | Limit |
|----------|-------|
| Files per vector store | 10,000 |
| Vector stores per account | Not documented |
| File size | 512 MB |
| Storage (billable) | Unlimited (pay per GB) |
| Indexing time | Varies by size |
---
**Last Updated**: 2025-10-25

49
scripts/check-versions.sh Executable file
View File

@@ -0,0 +1,49 @@
#!/bin/bash
# OpenAI Assistants API - Package Version Checker
# Verifies that the correct package versions are installed
set -e
echo "🔍 Checking OpenAI Assistants API package versions..."
echo ""
# Check if npm is installed
if ! command -v npm &> /dev/null; then
echo "❌ npm not found. Please install Node.js and npm."
exit 1
fi
# Check openai package
echo "📦 Checking openai package..."
INSTALLED_VERSION=$(npm list openai --depth=0 2>/dev/null | grep openai@ | sed 's/.*openai@//' | sed 's/ .*//' || echo "not installed")
REQUIRED_VERSION="6.7.0"
if [ "$INSTALLED_VERSION" = "not installed" ]; then
echo " ❌ openai package not installed"
echo " Run: npm install openai@${REQUIRED_VERSION}"
exit 1
fi
# Compare versions
INSTALLED_MAJOR=$(echo $INSTALLED_VERSION | cut -d. -f1)
REQUIRED_MAJOR=$(echo $REQUIRED_VERSION | cut -d. -f1)
if [ "$INSTALLED_MAJOR" -lt "$REQUIRED_MAJOR" ]; then
echo " ⚠️ Installed: $INSTALLED_VERSION (outdated)"
echo " ✅ Required: ${REQUIRED_VERSION}+"
echo " Run: npm install openai@latest"
exit 1
else
echo " ✅ Installed: $INSTALLED_VERSION"
fi
echo ""
echo "📋 Current package versions:"
npm list openai tsx typescript @types/node --depth=0 2>/dev/null || true
echo ""
echo "✅ All package versions are compatible!"
echo ""
echo "💡 To update to latest versions:"
echo " npm install openai@latest tsx@latest typescript@latest @types/node@latest"

View File

@@ -0,0 +1,87 @@
/**
* Basic Assistant Example
*
* Demonstrates the fundamental workflow:
* 1. Create an assistant
* 2. Create a thread
* 3. Add a message
* 4. Create a run
* 5. Poll for completion
* 6. Retrieve the response
*/
import OpenAI from 'openai';
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
async function main() {
console.log('🤖 Creating Math Tutor Assistant...\n');
// 1. Create an assistant
const assistant = await openai.beta.assistants.create({
name: "Math Tutor",
instructions: "You are a personal math tutor. When asked a question, write and run Python code to answer the question.",
tools: [{ type: "code_interpreter" }],
model: "gpt-4o",
});
console.log(`✅ Assistant created: ${assistant.id}\n`);
// 2. Create a thread
const thread = await openai.beta.threads.create();
console.log(`✅ Thread created: ${thread.id}\n`);
// 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?",
});
console.log('✅ Message added to thread\n');
// 4. Create a run
console.log('🏃 Creating run...\n');
const run = await openai.beta.threads.runs.create(thread.id, {
assistant_id: assistant.id,
});
// 5. Poll for completion
console.log('⏳ Waiting for completion...\n');
let runStatus = await openai.beta.threads.runs.retrieve(thread.id, run.id);
while (runStatus.status !== 'completed') {
if (runStatus.status === 'failed' || runStatus.status === 'cancelled') {
console.error(`❌ Run ${runStatus.status}:`, runStatus.last_error);
process.exit(1);
}
await new Promise(resolve => setTimeout(resolve, 1000));
runStatus = await openai.beta.threads.runs.retrieve(thread.id, run.id);
console.log(` Status: ${runStatus.status}`);
}
console.log('\n✅ Run completed!\n');
// 6. Retrieve messages
const messages = await openai.beta.threads.messages.list(thread.id);
console.log('💬 Response:\n');
const response = messages.data[0].content[0];
if (response.type === 'text') {
console.log(response.text.value);
}
// Usage stats
console.log('\n📊 Usage:');
console.log(` Prompt tokens: ${runStatus.usage?.prompt_tokens}`);
console.log(` Completion tokens: ${runStatus.usage?.completion_tokens}`);
console.log(` Total tokens: ${runStatus.usage?.total_tokens}`);
// Cleanup (optional)
// await openai.beta.assistants.del(assistant.id);
// await openai.beta.threads.del(thread.id);
}
main().catch(console.error);

View File

@@ -0,0 +1,136 @@
/**
* Code Interpreter Assistant
*
* Demonstrates:
* - Python code execution
* - File uploads for data analysis
* - Retrieving generated files (charts, CSVs)
* - Data visualization
*/
import OpenAI from 'openai';
import fs from 'fs';
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
async function main() {
console.log('📊 Creating Data Analyst Assistant...\n');
// 1. Create assistant with code interpreter
const assistant = await openai.beta.assistants.create({
name: "Data Analyst",
instructions: "You are a data analyst. Analyze data and create visualizations. Always explain your approach and findings.",
tools: [{ type: "code_interpreter" }],
model: "gpt-4o",
});
console.log(`✅ Assistant created: ${assistant.id}\n`);
// 2. Upload a data file (CSV example)
// For this example, create a sample CSV
const csvData = `date,revenue,expenses
2025-01-01,10000,4000
2025-01-02,12000,4500
2025-01-03,9500,4200
2025-01-04,15000,5000
2025-01-05,13500,4800`;
fs.writeFileSync('sample_data.csv', csvData);
const file = await openai.files.create({
file: fs.createReadStream('sample_data.csv'),
purpose: 'assistants',
});
console.log(`✅ File uploaded: ${file.id}\n`);
// 3. Create thread with file attachment
const thread = await openai.beta.threads.create({
messages: [{
role: "user",
content: "Analyze this revenue data. Calculate total revenue, average daily revenue, and create a visualization showing revenue and expenses over time.",
attachments: [{
file_id: file.id,
tools: [{ type: "code_interpreter" }],
}],
}],
});
console.log(`✅ Thread created: ${thread.id}\n`);
// 4. Run the assistant
console.log('🏃 Running analysis...\n');
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 (!['completed', 'failed', 'cancelled'].includes(runStatus.status)) {
await new Promise(resolve => setTimeout(resolve, 1000));
runStatus = await openai.beta.threads.runs.retrieve(thread.id, run.id);
console.log(` Status: ${runStatus.status}`);
}
if (runStatus.status !== 'completed') {
console.error(`❌ Run ${runStatus.status}:`, runStatus.last_error);
process.exit(1);
}
console.log('\n✅ Analysis completed!\n');
// 6. Retrieve the response
const messages = await openai.beta.threads.messages.list(thread.id);
const responseMessage = messages.data[0];
console.log('💬 Analysis Results:\n');
for (const content of responseMessage.content) {
if (content.type === 'text') {
console.log(content.text.value);
console.log('\n---\n');
}
// Download generated image files (charts)
if (content.type === 'image_file') {
const imageFileId = content.image_file.file_id;
console.log(`📈 Chart generated: ${imageFileId}`);
// Download the image
const imageData = await openai.files.content(imageFileId);
const imageBuffer = Buffer.from(await imageData.arrayBuffer());
fs.writeFileSync(`chart_${imageFileId}.png`, imageBuffer);
console.log(` Saved as: chart_${imageFileId}.png\n`);
}
}
// 7. Check run steps to see code that was executed
const runSteps = await openai.beta.threads.runs.steps.list(thread.id, run.id);
console.log('🔍 Execution Steps:\n');
for (const step of runSteps.data) {
if (step.step_details.type === 'tool_calls') {
for (const toolCall of step.step_details.tool_calls) {
if (toolCall.type === 'code_interpreter') {
console.log('Python code executed:');
console.log(toolCall.code_interpreter.input);
console.log('\nOutput:');
console.log(toolCall.code_interpreter.outputs);
console.log('\n---\n');
}
}
}
}
// Cleanup
fs.unlinkSync('sample_data.csv');
console.log('\n📊 Usage:');
console.log(` Total tokens: ${runStatus.usage?.total_tokens}`);
}
main().catch(console.error);

View File

@@ -0,0 +1,213 @@
/**
* File Search Assistant (RAG)
*
* Demonstrates:
* - Creating a vector store
* - Uploading documents
* - Semantic search with file_search tool
* - Retrieving answers with citations
*/
import OpenAI from 'openai';
import fs from 'fs';
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
async function main() {
console.log('📚 Creating Knowledge Base Assistant...\n');
// 1. Create a vector store
const vectorStore = await openai.beta.vectorStores.create({
name: "Product Documentation",
expires_after: {
anchor: "last_active_at",
days: 7, // Auto-delete after 7 days of inactivity
},
});
console.log(`✅ Vector store created: ${vectorStore.id}\n`);
// 2. Upload documents to vector store
// Create sample documents
const doc1 = `# Product Installation Guide
To install our product:
1. Download the installer from our website
2. Run the installer with administrator privileges
3. Follow the on-screen instructions
4. Restart your computer after installation
System Requirements:
- Windows 10 or later / macOS 11 or later
- 4GB RAM minimum, 8GB recommended
- 500MB free disk space`;
const doc2 = `# Troubleshooting Guide
Common Issues:
1. Installation Fails
- Ensure you have administrator privileges
- Disable antivirus temporarily
- Check disk space
2. Application Won't Start
- Update graphics drivers
- Run compatibility troubleshooter
- Reinstall the application
3. Performance Issues
- Close other applications
- Increase virtual memory
- Check for updates`;
fs.writeFileSync('install_guide.md', doc1);
fs.writeFileSync('troubleshooting.md', doc2);
// Upload files
const file1 = await openai.files.create({
file: fs.createReadStream('install_guide.md'),
purpose: 'assistants',
});
const file2 = await openai.files.create({
file: fs.createReadStream('troubleshooting.md'),
purpose: 'assistants',
});
console.log(`✅ Files uploaded: ${file1.id}, ${file2.id}\n`);
// Add files to vector store (batch upload)
const fileBatch = await openai.beta.vectorStores.fileBatches.create(
vectorStore.id,
{
file_ids: [file1.id, file2.id],
}
);
console.log('⏳ Indexing files...\n');
// Poll for vector store 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
);
}
console.log(`✅ Indexing complete! Status: ${batch.status}\n`);
// 3. Create assistant with file search
const assistant = await openai.beta.assistants.create({
name: "Product Support Assistant",
instructions: "You are a helpful product support assistant. Use the file search tool to answer questions about installation, troubleshooting, and product usage. Always cite your sources.",
tools: [{ type: "file_search" }],
tool_resources: {
file_search: {
vector_store_ids: [vectorStore.id],
},
},
model: "gpt-4o",
});
console.log(`✅ Assistant created: ${assistant.id}\n`);
// 4. Create thread and ask questions
const thread = await openai.beta.threads.create();
await openai.beta.threads.messages.create(thread.id, {
role: "user",
content: "How do I install the product?",
});
console.log('❓ Question: How do I install the product?\n');
// 5. Run
const run = await openai.beta.threads.runs.create(thread.id, {
assistant_id: assistant.id,
});
// Poll for completion
let runStatus = await openai.beta.threads.runs.retrieve(thread.id, run.id);
while (!['completed', 'failed'].includes(runStatus.status)) {
await new Promise(resolve => setTimeout(resolve, 1000));
runStatus = await openai.beta.threads.runs.retrieve(thread.id, run.id);
}
// 6. Retrieve response
const messages = await openai.beta.threads.messages.list(thread.id);
const response = messages.data[0];
console.log('💬 Answer:\n');
for (const content of response.content) {
if (content.type === 'text') {
console.log(content.text.value);
// Check for citations
if (content.text.annotations && content.text.annotations.length > 0) {
console.log('\n📎 Citations:');
for (const annotation of content.text.annotations) {
if (annotation.type === 'file_citation') {
console.log(` File: ${annotation.file_citation.file_id}`);
console.log(` Quote: ${annotation.file_citation.quote}`);
}
}
}
}
}
console.log('\n---\n');
// Ask another question
await openai.beta.threads.messages.create(thread.id, {
role: "user",
content: "What should I do if the application won't start?",
});
console.log('❓ Question: What should I do if the application won\'t start?\n');
const run2 = await openai.beta.threads.runs.create(thread.id, {
assistant_id: assistant.id,
});
let runStatus2 = await openai.beta.threads.runs.retrieve(thread.id, run2.id);
while (!['completed', 'failed'].includes(runStatus2.status)) {
await new Promise(resolve => setTimeout(resolve, 1000));
runStatus2 = await openai.beta.threads.runs.retrieve(thread.id, run2.id);
}
const messages2 = await openai.beta.threads.messages.list(thread.id);
const response2 = messages2.data[0];
console.log('💬 Answer:\n');
for (const content of response2.content) {
if (content.type === 'text') {
console.log(content.text.value);
}
}
// 7. Vector store stats
const storeInfo = await openai.beta.vectorStores.retrieve(vectorStore.id);
console.log('\n📊 Vector Store Stats:');
console.log(` Files: ${storeInfo.file_counts.completed}`);
console.log(` Size: ${storeInfo.usage_bytes} bytes`);
// Cleanup
fs.unlinkSync('install_guide.md');
fs.unlinkSync('troubleshooting.md');
console.log('\n💡 Note: Vector store will auto-delete after 7 days of inactivity');
console.log(` Or manually delete with: await openai.beta.vectorStores.del("${vectorStore.id}")`);
}
main().catch(console.error);

View File

@@ -0,0 +1,247 @@
/**
* Function Calling Assistant
*
* Demonstrates:
* - Defining custom functions
* - Handling requires_action state
* - Submitting tool outputs
* - Multiple function calls
*/
import OpenAI from 'openai';
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
// Mock weather API
async function getWeather(location: string, unit: 'celsius' | 'fahrenheit' = 'celsius') {
// In production, call a real weather API
const temps = { celsius: 22, fahrenheit: 72 };
return {
location,
temperature: temps[unit],
unit,
conditions: "Partly cloudy",
humidity: 65,
};
}
// Mock stock price API
async function getStockPrice(symbol: string) {
// In production, call a real stock API
const prices: Record<string, number> = {
AAPL: 185.50,
GOOGL: 142.75,
MSFT: 420.30,
};
return {
symbol,
price: prices[symbol] || 100.00,
currency: 'USD',
timestamp: new Date().toISOString(),
};
}
async function main() {
console.log('🛠️ Creating Function Calling Assistant...\n');
// 1. Create assistant with functions
const assistant = await openai.beta.assistants.create({
name: "Multi-Tool Assistant",
instructions: "You are a helpful assistant that can check weather and stock prices. Use the available functions to answer user questions.",
tools: [
{
type: "function",
function: {
name: "get_weather",
description: "Get the current weather for a location",
parameters: {
type: "object",
properties: {
location: {
type: "string",
description: "The city name, e.g., 'San Francisco'",
},
unit: {
type: "string",
enum: ["celsius", "fahrenheit"],
description: "Temperature unit",
},
},
required: ["location"],
},
},
},
{
type: "function",
function: {
name: "get_stock_price",
description: "Get the current stock price for a symbol",
parameters: {
type: "object",
properties: {
symbol: {
type: "string",
description: "Stock symbol, e.g., 'AAPL' for Apple",
},
},
required: ["symbol"],
},
},
},
],
model: "gpt-4o",
});
console.log(`✅ Assistant created: ${assistant.id}\n`);
// 2. Create thread
const thread = await openai.beta.threads.create({
messages: [{
role: "user",
content: "What's the weather in London and what's Apple's stock price?",
}],
});
console.log(`✅ Thread created: ${thread.id}\n`);
console.log('❓ User: What\'s the weather in London and what\'s Apple\'s stock price?\n');
// 3. Create run
let run = await openai.beta.threads.runs.create(thread.id, {
assistant_id: assistant.id,
});
console.log('🏃 Running assistant...\n');
// 4. Poll and handle function calls
while (true) {
run = await openai.beta.threads.runs.retrieve(thread.id, run.id);
console.log(` Status: ${run.status}`);
if (run.status === 'completed') {
break;
}
if (run.status === 'failed' || run.status === 'cancelled' || run.status === 'expired') {
console.error(`❌ Run ${run.status}:`, run.last_error);
process.exit(1);
}
if (run.status === 'requires_action') {
console.log('\n🔧 Function calls required:\n');
const toolCalls = run.required_action!.submit_tool_outputs.tool_calls;
const toolOutputs = [];
for (const toolCall of toolCalls) {
console.log(` Function: ${toolCall.function.name}`);
console.log(` Arguments: ${toolCall.function.arguments}`);
const args = JSON.parse(toolCall.function.arguments);
let output;
// Execute the function
if (toolCall.function.name === 'get_weather') {
output = await getWeather(args.location, args.unit);
console.log(` Result: ${output.temperature}°${output.unit === 'celsius' ? 'C' : 'F'}, ${output.conditions}`);
} else if (toolCall.function.name === 'get_stock_price') {
output = await getStockPrice(args.symbol);
console.log(` Result: $${output.price}`);
}
toolOutputs.push({
tool_call_id: toolCall.id,
output: JSON.stringify(output),
});
console.log('');
}
// Submit tool outputs
console.log('📤 Submitting tool outputs...\n');
run = await openai.beta.threads.runs.submitToolOutputs(
thread.id,
run.id,
{ tool_outputs: toolOutputs }
);
}
await new Promise(resolve => setTimeout(resolve, 1000));
}
console.log('\n✅ Run completed!\n');
// 5. Retrieve final response
const messages = await openai.beta.threads.messages.list(thread.id);
const response = messages.data[0];
console.log('💬 Assistant Response:\n');
for (const content of response.content) {
if (content.type === 'text') {
console.log(content.text.value);
}
}
// Ask another question
console.log('\n---\n');
await openai.beta.threads.messages.create(thread.id, {
role: "user",
content: "How about Microsoft's stock?",
});
console.log('❓ User: How about Microsoft\'s stock?\n');
run = await openai.beta.threads.runs.create(thread.id, {
assistant_id: assistant.id,
});
// Handle function calls again
while (true) {
run = await openai.beta.threads.runs.retrieve(thread.id, run.id);
if (run.status === 'completed') {
break;
}
if (run.status === 'requires_action') {
const toolCalls = run.required_action!.submit_tool_outputs.tool_calls;
const toolOutputs = [];
for (const toolCall of toolCalls) {
const args = JSON.parse(toolCall.function.arguments);
if (toolCall.function.name === 'get_stock_price') {
const output = await getStockPrice(args.symbol);
console.log(` 🔧 Called get_stock_price(${args.symbol}): $${output.price}`);
toolOutputs.push({
tool_call_id: toolCall.id,
output: JSON.stringify(output),
});
}
}
run = await openai.beta.threads.runs.submitToolOutputs(
thread.id,
run.id,
{ tool_outputs: toolOutputs }
);
}
await new Promise(resolve => setTimeout(resolve, 1000));
}
const messages2 = await openai.beta.threads.messages.list(thread.id);
const response2 = messages2.data[0];
console.log('\n💬 Assistant Response:\n');
for (const content of response2.content) {
if (content.type === 'text') {
console.log(content.text.value);
}
}
}
main().catch(console.error);

23
templates/package.json Normal file
View File

@@ -0,0 +1,23 @@
{
"name": "openai-assistants-templates",
"version": "1.0.0",
"description": "OpenAI Assistants API v2 templates",
"type": "module",
"scripts": {
"basic": "tsx templates/basic-assistant.ts",
"code-interpreter": "tsx templates/code-interpreter-assistant.ts",
"file-search": "tsx templates/file-search-assistant.ts",
"function-calling": "tsx templates/function-calling-assistant.ts",
"streaming": "tsx templates/streaming-assistant.ts",
"thread-management": "tsx templates/thread-management.ts",
"vector-store": "tsx templates/vector-store-setup.ts"
},
"dependencies": {
"openai": "^6.7.0"
},
"devDependencies": {
"@types/node": "^20.10.0",
"tsx": "^4.7.0",
"typescript": "^5.3.3"
}
}

View File

@@ -0,0 +1,165 @@
/**
* Streaming Assistant
*
* Demonstrates:
* - Real-time streaming with Server-Sent Events (SSE)
* - Handling different event types
* - Streaming message deltas
* - Tool call progress updates
*/
import OpenAI from 'openai';
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
async function main() {
console.log('🌊 Creating Streaming Assistant...\n');
// 1. Create assistant
const assistant = await openai.beta.assistants.create({
name: "Streaming Tutor",
instructions: "You are a helpful tutor. Explain concepts clearly and use code interpreter when helpful.",
tools: [{ type: "code_interpreter" }],
model: "gpt-4o",
});
console.log(`✅ Assistant created: ${assistant.id}\n`);
// 2. Create thread
const thread = await openai.beta.threads.create({
messages: [{
role: "user",
content: "Explain the Fibonacci sequence and calculate the first 10 numbers.",
}],
});
console.log(`✅ Thread created: ${thread.id}\n`);
console.log('💬 User: Explain the Fibonacci sequence and calculate the first 10 numbers.\n');
console.log('🤖 Assistant: ');
// 3. Create streaming run
const stream = await openai.beta.threads.runs.stream(thread.id, {
assistant_id: assistant.id,
});
// Track state
let currentMessageId: string | null = null;
let fullResponse = '';
// 4. Handle stream events
for await (const event of stream) {
switch (event.event) {
case 'thread.run.created':
console.log('[Run started]\n');
break;
case 'thread.run.queued':
console.log('[Run queued...]\n');
break;
case 'thread.run.in_progress':
console.log('[Processing...]\n');
break;
case 'thread.message.created':
currentMessageId = event.data.id;
break;
case 'thread.message.delta':
// Stream text content in real-time
const delta = event.data.delta.content?.[0];
if (delta?.type === 'text' && delta.text?.value) {
process.stdout.write(delta.text.value);
fullResponse += delta.text.value;
}
break;
case 'thread.message.completed':
console.log('\n\n[Message completed]\n');
break;
case 'thread.run.step.created':
const step = event.data;
if (step.type === 'tool_calls') {
console.log('\n[Tool call initiated...]\n');
}
break;
case 'thread.run.step.delta':
// Show code interpreter progress
const stepDelta = event.data.delta.step_details;
if (stepDelta?.type === 'tool_calls') {
const toolCall = stepDelta.tool_calls?.[0];
if (toolCall?.type === 'code_interpreter') {
if (toolCall.code_interpreter?.input) {
console.log('\n🔧 Executing Python code:\n');
console.log(toolCall.code_interpreter.input);
console.log('\n');
}
if (toolCall.code_interpreter?.outputs) {
for (const output of toolCall.code_interpreter.outputs) {
if (output.type === 'logs') {
console.log('📋 Output:', output.logs);
}
}
}
}
}
break;
case 'thread.run.step.completed':
console.log('[Step completed]\n');
break;
case 'thread.run.completed':
console.log('\n✅ Run completed!\n');
break;
case 'thread.run.failed':
console.error('\n❌ Run failed:', event.data.last_error);
break;
case 'thread.run.requires_action':
console.log('\n⚠ Requires action (function calling needed)');
break;
case 'error':
console.error('\n❌ Stream error:', event.data);
break;
}
}
console.log('---\n');
// Ask a follow-up question
await openai.beta.threads.messages.create(thread.id, {
role: "user",
content: "Can you explain how recursion works in that sequence?",
});
console.log('💬 User: Can you explain how recursion works in that sequence?\n');
console.log('🤖 Assistant: ');
const stream2 = await openai.beta.threads.runs.stream(thread.id, {
assistant_id: assistant.id,
});
for await (const event of stream2) {
if (event.event === 'thread.message.delta') {
const delta = event.data.delta.content?.[0];
if (delta?.type === 'text' && delta.text?.value) {
process.stdout.write(delta.text.value);
}
}
if (event.event === 'thread.run.completed') {
console.log('\n\n✅ Streaming complete!\n');
}
}
}
main().catch(console.error);

View File

@@ -0,0 +1,237 @@
/**
* Thread Lifecycle Management
*
* Demonstrates:
* - Creating and reusing threads
* - Checking for active runs
* - Thread cleanup patterns
* - Error handling for common issues
*/
import OpenAI from 'openai';
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
// Simulate database storage
const userThreads = new Map<string, string>();
/**
* Get or create a thread for a user
*/
async function getOrCreateUserThread(userId: string): Promise<string> {
console.log(`🔍 Checking thread for user: ${userId}`);
// Check if thread exists
let threadId = userThreads.get(userId);
if (!threadId) {
console.log(' No existing thread found. Creating new thread...');
const thread = await openai.beta.threads.create({
metadata: {
user_id: userId,
created_at: new Date().toISOString(),
},
});
threadId = thread.id;
userThreads.set(userId, threadId);
console.log(` ✅ Thread created: ${threadId}`);
} else {
console.log(` ✅ Existing thread found: ${threadId}`);
}
return threadId;
}
/**
* Check if thread has an active run
*/
async function hasActiveRun(threadId: string): Promise<boolean> {
const runs = await openai.beta.threads.runs.list(threadId, {
limit: 1,
order: 'desc',
});
const latestRun = runs.data[0];
return latestRun && ['queued', 'in_progress', 'cancelling'].includes(latestRun.status);
}
/**
* Wait for active run to complete or cancel it
*/
async function ensureNoActiveRun(threadId: string, cancelIfActive = false) {
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)) {
if (cancelIfActive) {
console.log(` ⚠️ Active run detected: ${latestRun.id}. Cancelling...`);
await openai.beta.threads.runs.cancel(threadId, latestRun.id);
// Wait for cancellation
let run = latestRun;
while (run.status !== 'cancelled') {
await new Promise(resolve => setTimeout(resolve, 500));
run = await openai.beta.threads.runs.retrieve(threadId, run.id);
}
console.log(' ✅ Run cancelled');
} else {
throw new Error(
`Thread ${threadId} has an active run (${latestRun.id}). ` +
`Wait for completion or set cancelIfActive=true`
);
}
}
}
/**
* Send a message safely (check for active runs first)
*/
async function sendMessage(
threadId: string,
assistantId: string,
message: string
): Promise<string> {
console.log(`\n💬 Sending message to thread ${threadId}...`);
// Ensure no active run
await ensureNoActiveRun(threadId, true);
// Add message
await openai.beta.threads.messages.create(threadId, {
role: 'user',
content: message,
});
console.log(' ✅ Message added');
// Create run
console.log(' 🏃 Creating run...');
const run = await openai.beta.threads.runs.create(threadId, {
assistant_id: assistantId,
});
// Poll for completion
let runStatus = await openai.beta.threads.runs.retrieve(threadId, run.id);
while (!['completed', 'failed', 'cancelled'].includes(runStatus.status)) {
await new Promise(resolve => setTimeout(resolve, 1000));
runStatus = await openai.beta.threads.runs.retrieve(threadId, run.id);
}
if (runStatus.status !== 'completed') {
throw new Error(`Run ${runStatus.status}: ${runStatus.last_error?.message}`);
}
console.log(' ✅ Run completed');
// Get response
const messages = await openai.beta.threads.messages.list(threadId, {
limit: 1,
order: 'desc',
});
const responseContent = messages.data[0].content[0];
if (responseContent.type === 'text') {
return responseContent.text.value;
}
return '';
}
/**
* Clean up old threads
*/
async function cleanupOldThreads(maxAgeHours: number = 24) {
console.log(`\n🧹 Cleaning up threads older than ${maxAgeHours} hours...`);
let deletedCount = 0;
for (const [userId, threadId] of userThreads.entries()) {
try {
const thread = await openai.beta.threads.retrieve(threadId);
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(threadId);
userThreads.delete(userId);
deletedCount++;
console.log(` ✅ Deleted thread for user ${userId} (age: ${ageHours.toFixed(1)}h)`);
}
} catch (error) {
console.error(` ❌ Error deleting thread ${threadId}:`, error);
}
}
console.log(`\n Total threads deleted: ${deletedCount}`);
}
/**
* Main demo
*/
async function main() {
console.log('🧵 Thread Lifecycle Management Demo\n');
// Create an assistant
const assistant = await openai.beta.assistants.create({
name: "Demo Assistant",
instructions: "You are a helpful assistant.",
model: "gpt-4o",
});
console.log(`✅ Assistant created: ${assistant.id}\n`);
// Simulate multiple users
const user1 = 'user_alice';
const user2 = 'user_bob';
// User 1: First message
let thread1 = await getOrCreateUserThread(user1);
let response1 = await sendMessage(thread1, assistant.id, "Hello! What's 2+2?");
console.log(`\n🤖 Response: ${response1}\n`);
// User 2: First message
let thread2 = await getOrCreateUserThread(user2);
let response2 = await sendMessage(thread2, assistant.id, "What's the capital of France?");
console.log(`\n🤖 Response: ${response2}\n`);
// User 1: Second message (reuses thread)
thread1 = await getOrCreateUserThread(user1);
response1 = await sendMessage(thread1, assistant.id, "Can you multiply that by 3?");
console.log(`\n🤖 Response: ${response1}\n`);
// Check for active runs
console.log('\n📊 Thread Status:');
const hasActive1 = await hasActiveRun(thread1);
const hasActive2 = await hasActiveRun(thread2);
console.log(` User 1 thread active: ${hasActive1}`);
console.log(` User 2 thread active: ${hasActive2}`);
// List messages in thread 1
console.log(`\n📜 Conversation history for user 1:`);
const messages = await openai.beta.threads.messages.list(thread1);
for (const message of messages.data.reverse()) {
const content = message.content[0];
if (content.type === 'text') {
console.log(` ${message.role}: ${content.text.value}`);
}
}
// Cleanup demo (set to 0 hours to delete all)
// await cleanupOldThreads(0);
console.log('\n✅ Demo complete!');
console.log(`\n💡 Tips:`);
console.log(' - Always check for active runs before creating new ones');
console.log(' - Reuse threads for conversation continuity');
console.log(' - Clean up old threads to manage costs');
console.log(' - Use metadata to track thread ownership');
}
main().catch(console.error);

View File

@@ -0,0 +1,241 @@
/**
* Vector Store Setup
*
* Demonstrates:
* - Creating vector stores
* - Batch file uploads
* - Monitoring indexing progress
* - Vector store management
* - Cost optimization
*/
import OpenAI from 'openai';
import fs from 'fs';
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
async function main() {
console.log('📦 Vector Store Setup Guide\n');
// 1. Create a vector store
console.log('Step 1: Creating vector store...\n');
const vectorStore = await openai.beta.vectorStores.create({
name: "Company Knowledge Base",
metadata: {
department: "engineering",
version: "1.0",
},
expires_after: {
anchor: "last_active_at",
days: 30, // Auto-delete after 30 days of inactivity
},
});
console.log(`✅ Vector store created: ${vectorStore.id}`);
console.log(` Name: ${vectorStore.name}`);
console.log(` Status: ${vectorStore.status}`);
console.log(` Auto-expires: ${vectorStore.expires_after?.days} days after last use\n`);
// 2. Prepare sample documents
console.log('Step 2: Preparing documents...\n');
const documents = [
{
filename: 'api_docs.md',
content: `# API Documentation
## Authentication
All API requests require an API key in the Authorization header.
## Rate Limits
- Free tier: 100 requests/hour
- Pro tier: 1000 requests/hour
## Endpoints
- GET /api/users - List users
- POST /api/users - Create user
- GET /api/users/:id - Get user details`,
},
{
filename: 'deployment_guide.md',
content: `# Deployment Guide
## Prerequisites
- Docker installed
- Kubernetes cluster running
- kubectl configured
## Steps
1. Build Docker image: docker build -t app:latest .
2. Push to registry: docker push registry/app:latest
3. Deploy: kubectl apply -f deployment.yaml
4. Verify: kubectl get pods`,
},
{
filename: 'security_policy.md',
content: `# Security Policy
## Password Requirements
- Minimum 12 characters
- Must include uppercase, lowercase, numbers, symbols
- Cannot reuse last 5 passwords
## Access Control
- Use SSO for authentication
- Enable 2FA for all accounts
- Review access logs monthly
## Incident Response
- Report security issues to security@company.com
- Critical incidents escalated within 1 hour`,
},
];
// Write files to disk
const fileIds: string[] = [];
for (const doc of documents) {
fs.writeFileSync(doc.filename, doc.content);
console.log(` 📄 Created: ${doc.filename}`);
}
// 3. Upload files
console.log('\nStep 3: Uploading files to OpenAI...\n');
for (const doc of documents) {
const file = await openai.files.create({
file: fs.createReadStream(doc.filename),
purpose: 'assistants',
});
fileIds.push(file.id);
console.log(` ✅ Uploaded: ${doc.filename} (${file.id})`);
// Clean up local file
fs.unlinkSync(doc.filename);
}
// 4. Add files to vector store (batch upload)
console.log('\nStep 4: Adding files to vector store...\n');
const fileBatch = await openai.beta.vectorStores.fileBatches.create(
vectorStore.id,
{
file_ids: fileIds,
}
);
console.log(` 📦 Batch created: ${fileBatch.id}`);
console.log(` Files in batch: ${fileBatch.file_counts.total}`);
// 5. Monitor indexing progress
console.log('\nStep 5: Monitoring indexing progress...\n');
let batch = fileBatch;
let lastStatus = '';
while (batch.status === 'in_progress') {
batch = await openai.beta.vectorStores.fileBatches.retrieve(
vectorStore.id,
fileBatch.id
);
const statusMsg = ` Status: ${batch.status} | ` +
`Completed: ${batch.file_counts.completed}/${batch.file_counts.total} | ` +
`Failed: ${batch.file_counts.failed}`;
if (statusMsg !== lastStatus) {
console.log(statusMsg);
lastStatus = statusMsg;
}
await new Promise(resolve => setTimeout(resolve, 1000));
}
console.log(`\n ✅ Indexing ${batch.status}!`);
if (batch.file_counts.failed > 0) {
console.log(` ⚠️ ${batch.file_counts.failed} files failed to index`);
// List failed files
const files = await openai.beta.vectorStores.files.list(vectorStore.id);
for (const file of files.data) {
if (file.status === 'failed') {
console.log(` - File ${file.id}: ${file.last_error?.message}`);
}
}
}
// 6. Get vector store details
console.log('\nStep 6: Vector store statistics...\n');
const updatedStore = await openai.beta.vectorStores.retrieve(vectorStore.id);
console.log(` 📊 Statistics:`);
console.log(` Total files: ${updatedStore.file_counts.completed}`);
console.log(` Storage used: ${updatedStore.usage_bytes} bytes (${(updatedStore.usage_bytes / 1024 / 1024).toFixed(2)} MB)`);
console.log(` Status: ${updatedStore.status}`);
// 7. Cost estimation
const storageMB = updatedStore.usage_bytes / 1024 / 1024;
const storageGB = storageMB / 1024;
const costPerDay = Math.max(0, (storageGB - 1) * 0.10); // First 1GB free, then $0.10/GB/day
const costPerMonth = costPerDay * 30;
console.log(`\n 💰 Cost Estimation:`);
console.log(` Storage: ${storageGB.toFixed(4)} GB`);
console.log(` Cost per day: $${costPerDay.toFixed(4)} (first 1GB free)`);
console.log(` Cost per month: $${costPerMonth.toFixed(2)}`);
// 8. List all files in vector store
console.log('\nStep 7: Files in vector store...\n');
const filesInStore = await openai.beta.vectorStores.files.list(vectorStore.id);
for (const file of filesInStore.data) {
console.log(` 📄 ${file.id}`);
console.log(` Status: ${file.status}`);
console.log(` Created: ${new Date(file.created_at * 1000).toISOString()}`);
}
// 9. Management operations
console.log('\nStep 8: Management operations...\n');
// Update vector store metadata
const updated = await openai.beta.vectorStores.update(vectorStore.id, {
metadata: {
department: "engineering",
version: "1.0",
last_updated: new Date().toISOString(),
},
});
console.log(' ✅ Metadata updated');
// List all vector stores
const allStores = await openai.beta.vectorStores.list({ limit: 5 });
console.log(`\n 📚 Total vector stores in account: ${allStores.data.length}`);
for (const store of allStores.data) {
console.log(` - ${store.name} (${store.id}): ${store.file_counts.completed} files`);
}
// 10. Cleanup instructions
console.log('\n💡 Cleanup Instructions:\n');
console.log(' To delete individual files:');
console.log(` await openai.beta.vectorStores.files.del("${vectorStore.id}", "file_id");`);
console.log('');
console.log(' To delete entire vector store:');
console.log(` await openai.beta.vectorStores.del("${vectorStore.id}");`);
console.log('');
console.log(' Note: Vector store will auto-delete after 30 days of inactivity (configured above)');
console.log('\n✅ Vector store setup complete!');
console.log(`\n🔑 Save this ID to use with assistants:`);
console.log(` ${vectorStore.id}`);
}
main().catch(console.error);