Initial commit
This commit is contained in:
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
|
||||
Reference in New Issue
Block a user