Initial commit
This commit is contained in:
12
.claude-plugin/plugin.json
Normal file
12
.claude-plugin/plugin.json
Normal 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
3
README.md
Normal 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.
|
||||||
109
plugin.lock.json
Normal file
109
plugin.lock.json
Normal 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": []
|
||||||
|
}
|
||||||
|
}
|
||||||
242
references/assistants-api-v2.md
Normal file
242
references/assistants-api-v2.md
Normal 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
|
||||||
338
references/code-interpreter-guide.md
Normal file
338
references/code-interpreter-guide.md
Normal 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
|
||||||
364
references/file-search-rag-guide.md
Normal file
364
references/file-search-rag-guide.md
Normal 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
|
||||||
188
references/migration-from-v1.md
Normal file
188
references/migration-from-v1.md
Normal 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
|
||||||
271
references/thread-lifecycle.md
Normal file
271
references/thread-lifecycle.md
Normal 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
446
references/top-errors.md
Normal 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
422
references/vector-stores.md
Normal 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
49
scripts/check-versions.sh
Executable 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"
|
||||||
87
templates/basic-assistant.ts
Normal file
87
templates/basic-assistant.ts
Normal 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);
|
||||||
136
templates/code-interpreter-assistant.ts
Normal file
136
templates/code-interpreter-assistant.ts
Normal 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);
|
||||||
213
templates/file-search-assistant.ts
Normal file
213
templates/file-search-assistant.ts
Normal 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);
|
||||||
247
templates/function-calling-assistant.ts
Normal file
247
templates/function-calling-assistant.ts
Normal 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
23
templates/package.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
165
templates/streaming-assistant.ts
Normal file
165
templates/streaming-assistant.ts
Normal 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);
|
||||||
237
templates/thread-management.ts
Normal file
237
templates/thread-management.ts
Normal 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);
|
||||||
241
templates/vector-store-setup.ts
Normal file
241
templates/vector-store-setup.ts
Normal 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);
|
||||||
Reference in New Issue
Block a user