The OpenAI SDK is excellent. It’s well-documented, widely supported, and gets out of your way. Start there.
But there are specific moments when the SDK becomes the wrong abstraction — when you’re adding code around it that the SDK was never designed to handle. This is when Neureus makes sense.
Here’s a decision framework based on what we’ve seen teams actually struggle with.
Start with the OpenAI SDK when:
You’re building a single-model application. If your product uses GPT-4o and has no plans to try other models, the OpenAI SDK is perfect. The abstraction overhead of a multi-provider router adds zero value.
You need OpenAI-specific features. Assistants API, fine-tuning, DALL-E, Whisper, vision models with specific OpenAI features — these don’t have direct equivalents in other providers. Use the SDK that targets the provider.
Your team is Python-first. The OpenAI Python SDK is mature and widely used. The ecosystem around it (LangSmith, LlamaIndex integrations, Instructor) is extensive. If your stack is Python, the SDK fits better.
You’re prototyping. npm install openai + OPENAI_API_KEY gets you running in 5 minutes. Don’t add complexity until you have a reason.
Switch to Neureus when you find yourself doing one of these:
1. Adding a model fallback
// You wrote this:
async function generateWithFallback(messages: Message[]) {
try {
return await openai.chat.completions.create({ model: 'gpt-4o', messages });
} catch (err) {
// gpt-4o is down or rate limited — fall back to Claude
return await anthropic.messages.create({ model: 'claude-sonnet-4-6', messages });
}
}
This is 15 lines that doesn’t include response normalization (OpenAI and Anthropic return different shapes), retry logic, or logging. You’ve started building a router.
With Neureus:
const { text } = await neureus.ai.chat({
model: 'gpt-4o',
messages,
fallback: 'claude-sonnet-4-6', // automatic, normalized response
});
2. Pulling in a vector database for context retrieval
// You installed Pinecone and wrote this:
const embeddings = await openai.embeddings.create({ input: query, model: 'text-embedding-3-small' });
const results = await pinecone.query({ vector: embeddings.data[0].embedding, topK: 5 });
const context = results.matches.map(m => m.metadata.text).join('\n');
const response = await openai.chat.completions.create({
messages: [{ role: 'system', content: `Context: ${context}` }, ...userMessages],
});
This is three services (OpenAI Chat, OpenAI Embeddings, Pinecone) with three billing relationships.
With Neureus:
const { text, sources } = await neureus.ai.chat({
model: 'gpt-4o',
messages: userMessages,
rag: { query, topK: 5 }, // ingest once with /rag/ingest, then just add this
});
One API call. RAG retrieval and generation in a single response. Documents are stored in Cloudflare Vectorize; embeddings are via Workers AI.
3. Building an agent with tool use
// The loop you wrote:
let response = await openai.chat.completions.create({ messages, tools });
while (response.choices[0].finish_reason === 'tool_calls') {
const toolCall = response.choices[0].message.tool_calls[0];
const result = await executeTool(toolCall.function.name, JSON.parse(toolCall.function.arguments));
messages.push({ role: 'tool', content: result, tool_call_id: toolCall.id });
response = await openai.chat.completions.create({ messages, tools });
if (messages.length > 20) break; // safety valve
}
This is a ReAct loop without logging, without step limits that are configurable, without human-in-the-loop capability, and without audit history.
With Neureus:
// Define the agent once
const { agentId } = await neureus.agents.create({
name: 'My agent',
model: 'gpt-4o',
tools: yourTools,
});
// Run it — logs, step traces, HITL, configurable limits included
const { result, steps } = await neureus.agents.run(agentId, { goal: userRequest });
4. Sending the same prompt to multiple models and picking the best
// Your current code:
const [gpt4Response, claudeResponse] = await Promise.all([
openai.chat.completions.create({ model: 'gpt-4o', messages }),
anthropic.messages.create({ model: 'claude-sonnet-4-6', messages }),
]);
const scores = await scoreResponses([gpt4Response, claudeResponse]);
const best = scores[0].response;
You’ve built a composite AI pattern manually. This doesn’t handle partial failures, doesn’t log which model won or why, and doesn’t have a clean abstraction.
With Neureus:
const { text, winner } = await neureus.composite.execute({
pattern: 'best_of_n',
n: 2,
models: ['gpt-4o', 'claude-sonnet-4-6'],
messages,
criterion: 'factual_accuracy',
});
5. Needing to run thousands of requests cheaply
// Your current setup: a queue + worker + polling loop + 5x the code
// And you're paying realtime OpenAI prices for asynchronous work
If you’re processing documents, generating product descriptions, or running evaluations asynchronously, you’re overpaying. OpenAI Batch API offers 50% off; Anthropic offers the same. But you need code to submit jobs, poll status, and receive webhooks.
With Neureus:
await neureus.ai.batchCreate({
model: 'gpt-4o-mini',
requests: documents.map(doc => ({
customId: doc.id,
messages: [systemMsg, { role: 'user', content: doc.text }],
})),
webhookUrl: 'https://your-app.com/webhooks/batch',
});
// 40% off realtime. Webhook when done.
The actual migration cost
Switching from openai.chat.completions.create to neureus.ai.chat is a 3-line change:
// Before:
const response = await openai.chat.completions.create({
model: 'gpt-4o',
messages,
});
const text = response.choices[0].message.content;
// After:
const { text } = await neureus.ai.chat({
model: 'gpt-4o',
messages,
});
The response shape changes slightly (.text instead of .choices[0].message.content), but both are synchronous and the error handling pattern is the same.
If you’re using streaming:
// Before:
const stream = await openai.chat.completions.create({ stream: true, ... });
for await (const chunk of stream) {
process.stdout.write(chunk.choices[0].delta.content ?? '');
}
// After:
const stream = await neureus.ai.stream({ ... });
// Returns ReadableStream — pipe it directly to your response
What you give up
The OpenAI SDK has deeper access to OpenAI-specific features. Assistants API threads, file uploads, fine-tuned model management, DALL-E image generation, and Whisper transcription don’t have Neureus equivalents. If you’re building on those features, stay with the SDK.
The OpenAI Python SDK ecosystem is also richer — LangSmith, Instructor (for structured output), LlamaIndex integrations, and the broader Python AI community. Neureus’s TypeScript SDK is solid, but TypeScript-first.
The signal to watch for
The right time to switch is when you’re writing infrastructure code instead of product code. If your lib/ai.ts is growing with retry logic, provider fallback, context assembly, and token counting — those problems are solved. Switch the abstraction layer and get back to building.
The direct SDK is the right tool until it isn’t. When you need RAG, multi-provider routing, agents, or batch — try Neureus. Free tier, no credit card.