Integreer de OpenAI en Anthropic API in een webapp
Taalmodel API-integratie is een van de meest gevraagde vaardigheden in moderne webontwikkeling. Of het nu gaat om het bouwen van een chatbot, een assistent voor het genereren van inhoud of een tekstanalysesysteem, het vermogen om te communiceren effectief met de API's van Open AI e Antropisch het is fundamenteel.
In dit vierde artikel uit de serie AI voor webontwikkelaars, zullen we in detail onderzoeken hoe je beide API’s integreert tot een complete webapplicatie. We beginnen bij de configuratie In eerste instantie zullen we berichtstructuren analyseren en responsstreaming implementeren en we zullen een veilige architectuur bouwen met proxypatronen om de API-sleutels te beschermen.
Wat je gaat leren
- Configureer en authenticeer met OpenAI en Anthropic API's
- Begrijp de structurele verschillen tussen de twee berichtsystemen
- Implementeer antwoordstreaming voor een realtime ervaring
- Ga krachtig om met fouten, nieuwe pogingen en snelheidsbeperking
- Tel tokens en optimaliseer de gebruikskosten
- Bouw een veilige backend-proxy om API-sleutels te beschermen
- Creëer een complete chatinterface met streamingondersteuning
Serieoverzicht
| # | Item | Focus |
|---|---|---|
| 1 | Introductie tot RAG | Fundamentele concepten |
| 2 | RAG met TypeScript en LangChain | Praktische implementatie |
| 3 | Vectordatabase vergeleken | Chromadb, Dennenappel, Weaviate |
| 4 | OpenAI en Anthropic API's (u bent hier) | API-integratie |
| 5 | LLM Lokaal met Ollama | AI zonder cloud |
| 6 | Verfijning versus RAG | Wanneer wat te gebruiken |
| 7 | AI-agenten | Agent-gebaseerde architectuur |
| 8 | AI in CI/CD | Intelligente automatisering |
1. API-installatie en authenticatie
Voordat u uw eerste aanvraag kunt indienen, heeft u uw inloggegevens nodig en de ontwikkelomgeving correct configureren. Beide aanbieders vereisen een API-sleutel die veilig moet worden beheerd.
API-sleutels verkrijgen
Voor Open AI, schrijf je in platform.openai.com en genereer een sleutel
in het gedeelte API-sleutels. Voor Antropisch, ga naar de console op
console.anthropic.com en maak een API-sleutel in het instellingenpaneel.
API-sleutelbeveiliging
Neem API-sleutels nooit rechtstreeks op in de broncode of vastgelegde bestanden op Git. Gebruik altijd omgevingsvariabelen of een geheime beheerservice. Een blootgestelde sleutel kan onverwachte kosten met zich meebrengen en de veiligheid in gevaar brengen.
# File .env (mai committare su Git!)
OPENAI_API_KEY=sk-proj-xxxxxxxxxxxxxxxxxxxxxxxx
ANTHROPIC_API_KEY=sk-ant-xxxxxxxxxxxxxxxxxxxxxxxx
# Configurazione opzionale
OPENAI_ORG_ID=org-xxxxxxxxxxxxxxxx
OPENAI_MODEL=gpt-4o
ANTHROPIC_MODEL=claude-sonnet-4-20250514
De SDK's installeren
Beide providers bieden officiële SDK's voor TypeScript/JavaScript aan die het u gemakkelijk maken integratie aanzienlijk. SDK's verwerken automatisch serialisatie, basisdeserialisatie en opnieuw proberen.
# SDK ufficiali
npm install openai @anthropic-ai/sdk
# Gestione variabili d'ambiente
npm install dotenv
# Per il server proxy Express
npm install express cors
npm install -D @types/express @types/cors
Initialisatie van de klant
// src/lib/ai-clients.ts
import OpenAI from 'openai';
import Anthropic from '@anthropic-ai/sdk';
import dotenv from 'dotenv';
dotenv.config();
// Client OpenAI
export const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
organization: process.env.OPENAI_ORG_ID, // opzionale
});
// Client Anthropic
export const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY,
});
// Verifica configurazione
export function validateConfig(): boolean {
if (!process.env.OPENAI_API_KEY) {
console.error('OPENAI_API_KEY non configurata');
return false;
}
if (!process.env.ANTHROPIC_API_KEY) {
console.error('ANTHROPIC_API_KEY non configurata');
return false;
}
return true;
}
2. Berichtstructuur vergelijken
Hoewel beide API’s werken met het concept van berichten in een gesprek, de structuren verschillen aanzienlijk. Begrijp deze verschillen het is essentieel voor het opbouwen van een verenigde abstractie.
Vergelijking van structuren
| Kenmerkend | Open AI | Antropisch |
|---|---|---|
| Berichtrollen | systeem, gebruiker, assistent, tool | gebruiker, assistent (apart systeem) |
| Systeemprompt | In de berichten | Speciale parameter system |
| Model | model: "gpt-4o" | model: "claude-sonnet-4-20250514" |
| Maximale output | max_tokens (optioneel) | max_tokens (verplicht) |
| Temperatuur | 0,0 - 2,0 (standaard 1,0) | 0,0 - 1,0 (standaard 1,0) |
| Streamen | Door de server verzonden gebeurtenissen | Door de server verzonden gebeurtenissen |
| Eindpunten | /v1/chat/voltooiingen | /v1/berichten |
Aangevraagd bij OpenAI
// src/lib/openai-chat.ts
import { openai } from './ai-clients';
interface ChatMessage {
role: 'system' | 'user' | 'assistant';
content: string;
}
export async function chatWithOpenAI(
messages: ChatMessage[],
model: string = 'gpt-4o'
): Promise<string> {
const response = await openai.chat.completions.create({
model,
messages,
temperature: 0.7,
max_tokens: 2048,
});
return response.choices[0].message.content ?? '';
}
// Esempio di utilizzo
const risposta = await chatWithOpenAI([
{ role: 'system', content: 'Sei un assistente tecnico esperto.' },
{ role: 'user', content: 'Spiega il pattern Observer in TypeScript.' },
]);
console.log(risposta);
Aanvraag bij Anthropic
// src/lib/anthropic-chat.ts
import { anthropic } from './ai-clients';
interface ChatMessage {
role: 'user' | 'assistant';
content: string;
}
export async function chatWithAnthropic(
messages: ChatMessage[],
systemPrompt: string = '',
model: string = 'claude-sonnet-4-20250514'
): Promise<string> {
const response = await anthropic.messages.create({
model,
max_tokens: 2048,
system: systemPrompt,
messages,
});
// Il contenuto è un array di blocchi
const textBlock = response.content.find(
(block) => block.type === 'text'
);
return textBlock?.text ?? '';
}
// Esempio di utilizzo
const risposta = await chatWithAnthropic(
[{ role: 'user', content: 'Spiega il pattern Observer in TypeScript.' }],
'Sei un assistente tecnico esperto.'
);
console.log(risposta);
3. Streaming van reacties
Streaming is de sleutel tot het bieden van een naadloze gebruikerservaring. In plaats van te wachten dat het hele bericht wordt gegenereerd, worden tokens als model verzonden produceert ze, waardoor de waargenomen wachttijd drastisch wordt verminderd.
Streamen met OpenAI
// src/lib/openai-stream.ts
import { openai } from './ai-clients';
export async function* streamOpenAI(
messages: Array<{ role: string; content: string }>,
model: string = 'gpt-4o'
): AsyncGenerator<string> {
const stream = await openai.chat.completions.create({
model,
messages: messages as any,
stream: true,
temperature: 0.7,
});
for await (const chunk of stream) {
const content = chunk.choices[0]?.delta?.content;
if (content) {
yield content;
}
}
}
// Utilizzo
async function main() {
const messages = [
{ role: 'user', content: 'Scrivi una funzione di ordinamento.' }
];
for await (const token of streamOpenAI(messages)) {
process.stdout.write(token);
}
}
Streamen met Anthropic
// src/lib/anthropic-stream.ts
import { anthropic } from './ai-clients';
export async function* streamAnthropic(
messages: Array<{ role: string; content: string }>,
systemPrompt: string = '',
model: string = 'claude-sonnet-4-20250514'
): AsyncGenerator<string> {
const stream = anthropic.messages.stream({
model,
max_tokens: 2048,
system: systemPrompt,
messages: messages as any,
});
for await (const event of stream) {
if (
event.type === 'content_block_delta' &&
event.delta.type === 'text_delta'
) {
yield event.delta.text;
}
}
}
// Utilizzo con metriche
async function main() {
const start = Date.now();
let totalTokens = 0;
for await (const token of streamAnthropic(
[{ role: 'user', content: 'Scrivi una funzione di ordinamento.' }],
'Sei un programmatore esperto.'
)) {
process.stdout.write(token);
totalTokens++;
}
console.log(`\nTempo: ${Date.now() - start}ms, Token: ${totalTokens}`);
}
4. Foutbeheer en opnieuw proberen
Externe API's kunnen om meerdere redenen mislukken: snelheidslimieten, time-outs, serverfouten of netwerkproblemen. Een robuust systeem voor opnieuw proberen is essentieel om de betrouwbaarheid van de applicatie te garanderen.
Veelvoorkomende foutcodes
| Code | Betekenis | Strategie |
|---|---|---|
| 400 | Verkeerd opgemaakt verzoek | Probeer het niet opnieuw, corrigeer de parameters |
| 401 | Authenticatie mislukt | Controleer uw API-sleutel |
| 429 | Tarieflimiet overschreden | Wacht even en probeer het opnieuw met uitstel |
| 500 | Serverfout | Probeer het opnieuw met exponentieel uitstel |
| 503 | Dienst niet beschikbaar | Probeer het na een paar seconden opnieuw |
| 529 | Overbelasting (antropisch) | Probeer het opnieuw met een lang uitstel |
// src/lib/retry.ts
interface RetryConfig {
maxRetries: number;
baseDelay: number; // millisecondi
maxDelay: number; // millisecondi
backoffFactor: number;
}
const DEFAULT_CONFIG: RetryConfig = {
maxRetries: 3,
baseDelay: 1000,
maxDelay: 30000,
backoffFactor: 2,
};
export async function withRetry<T>(
fn: () => Promise<T>,
config: Partial<RetryConfig> = {}
): Promise<T> {
const cfg = { ...DEFAULT_CONFIG, ...config };
let lastError: Error | null = null;
for (let attempt = 0; attempt <= cfg.maxRetries; attempt++) {
try {
return await fn();
} catch (error: any) {
lastError = error;
// Non ritentare per errori client (4xx eccetto 429)
if (error.status && error.status >= 400 &&
error.status < 500 && error.status !== 429) {
throw error;
}
if (attempt === cfg.maxRetries) break;
// Calcola il delay con jitter
const delay = Math.min(
cfg.baseDelay * Math.pow(cfg.backoffFactor, attempt) +
Math.random() * 1000,
cfg.maxDelay
);
console.warn(
`Tentativo ${attempt + 1} fallito. ` +
`Retry tra ${Math.round(delay)}ms...`
);
// Usa Retry-After se disponibile
const retryAfter = error.headers?.get?.('retry-after');
const waitTime = retryAfter
? parseInt(retryAfter) * 1000
: delay;
await new Promise(r => setTimeout(r, waitTime));
}
}
throw lastError;
}
5. Snelheidsbeperking en frequentiecontrole
Wanneer uw applicatie veel gelijktijdige gebruikers verwerkt, is het van cruciaal belang om deze te implementeren een tariefbegrenzingssysteem aan de cliëntzijde om te voorkomen dat de opgelegde limieten worden overschreden door aanbieders en zorgen voor een eerlijk gebruik van hulpbronnen.
// src/lib/rate-limiter.ts
export class TokenBucketRateLimiter {
private tokens: number;
private lastRefill: number;
constructor(
private maxTokens: number,
private refillRate: number, // token al secondo
) {
this.tokens = maxTokens;
this.lastRefill = Date.now();
}
private refill(): void {
const now = Date.now();
const elapsed = (now - this.lastRefill) / 1000;
this.tokens = Math.min(
this.maxTokens,
this.tokens + elapsed * this.refillRate
);
this.lastRefill = now;
}
async acquire(): Promise<void> {
this.refill();
if (this.tokens < 1) {
const waitTime = (1 - this.tokens) / this.refillRate * 1000;
await new Promise(r => setTimeout(r, waitTime));
this.refill();
}
this.tokens -= 1;
}
}
// Limiti per provider
const openaiLimiter = new TokenBucketRateLimiter(60, 1); // 60 RPM
const anthropicLimiter = new TokenBucketRateLimiter(50, 0.83); // 50 RPM
6. Tokentelling en kostenoptimalisatie
API-kosten zijn direct evenredig aan het aantal verwerkte tokens. Het monitoren en optimaliseren van het tokenverbruik is cruciaal om de kosten op peil te houden onder controle, vooral bij toepassingen met veel verkeer.
Indicatieve kosten voor 1M-token (februari 2026)
| Model | Invoer (1M-token) | Uitgang (1M-token) |
|---|---|---|
| GPT-4o | $ 2,50 | $ 10,00 |
| GPT-4o mini | $ 0,15 | $ 0,60 |
| Claude Sonnet4 | $ 3,00 | $ 15,00 |
| Claude Haiku3.5 | $ 0,80 | $ 4,00 |
Opmerking: Prijzen zijn indicatief en onder voorbehoud. Controleer altijd de officiële documentatie op bijgewerkte kosten.
// src/lib/token-counter.ts
import { encoding_for_model } from 'tiktoken';
export class TokenCounter {
private encoder;
constructor(model: string = 'gpt-4o') {
this.encoder = encoding_for_model(model as any);
}
count(text: string): number {
return this.encoder.encode(text).length;
}
countMessages(
messages: Array<{ role: string; content: string }>
): number {
let total = 0;
for (const msg of messages) {
total += 4; // overhead per messaggio
total += this.count(msg.role);
total += this.count(msg.content);
}
total += 2; // overhead della richiesta
return total;
}
estimateCost(
inputTokens: number,
outputTokens: number,
inputCostPer1M: number,
outputCostPer1M: number
): number {
return (
(inputTokens / 1_000_000) * inputCostPer1M +
(outputTokens / 1_000_000) * outputCostPer1M
);
}
dispose(): void {
this.encoder.free();
}
}
7. Proxypatroon voor API-sleutelbeveiliging
API-sleutels hoeven dat niet te doen nooit worden blootgesteld aan de frontend. Het patroon aanbevolen is om een proxyserver te maken die als tussenpersoon fungeert tussen de browser van de gebruiker en de API's van de AI-provider. De frontend communiceert met die van jou server, en de server beheert de sleutels veilig.
// src/server/proxy.ts
import express from 'express';
import cors from 'cors';
import { openai, anthropic } from '../lib/ai-clients';
import { withRetry } from '../lib/retry';
const app = express();
app.use(cors({ origin: process.env.ALLOWED_ORIGINS?.split(',') }));
app.use(express.json());
// Middleware di rate limiting per IP
const ipRequests = new Map<string, number[]>();
function rateLimitMiddleware(
req: express.Request,
res: express.Response,
next: express.NextFunction
) {
const ip = req.ip ?? 'unknown';
const now = Date.now();
const windowMs = 60_000; // 1 minuto
const maxRequests = 20;
const requests = (ipRequests.get(ip) ?? [])
.filter(t => now - t < windowMs);
if (requests.length >= maxRequests) {
return res.status(429).json({
error: 'Troppe richieste. Riprova tra un minuto.'
});
}
requests.push(now);
ipRequests.set(ip, requests);
next();
}
app.use('/api', rateLimitMiddleware);
// Endpoint chat unificato
app.post('/api/chat', async (req, res) => {
const { provider, messages, system, model } = req.body;
try {
let content: string;
if (provider === 'openai') {
const result = await withRetry(() =>
openai.chat.completions.create({
model: model ?? 'gpt-4o',
messages,
temperature: 0.7,
})
);
content = result.choices[0].message.content ?? '';
} else {
const result = await withRetry(() =>
anthropic.messages.create({
model: model ?? 'claude-sonnet-4-20250514',
max_tokens: 2048,
system: system ?? '',
messages,
})
);
const textBlock = result.content.find(b => b.type === 'text');
content = textBlock?.text ?? '';
}
res.json({ content });
} catch (error: any) {
res.status(error.status ?? 500).json({
error: error.message ?? 'Errore interno del server'
});
}
});
app.listen(3001, () =>
console.log('Proxy AI in ascolto su porta 3001')
);
8. Streaming van reacties naar de frontend
Om de streamingervaring naar de browser van de gebruiker te brengen, de proxyserver moet ondersteunen Door de server verzonden gebeurtenissen (SSE). Dit protocol maakt het mogelijk naar de server om gegevens stapsgewijs naar de client te sturen via een enkele HTTP-verbinding.
// src/server/stream-endpoint.ts
app.post('/api/chat/stream', async (req, res) => {
const { provider, messages, system, model } = req.body;
// Configura headers SSE
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
try {
if (provider === 'openai') {
const stream = await openai.chat.completions.create({
model: model ?? 'gpt-4o',
messages,
stream: true,
});
for await (const chunk of stream) {
const content = chunk.choices[0]?.delta?.content;
if (content) {
res.write(`data: ${JSON.stringify({ content })}\n\n`);
}
}
} else {
const stream = anthropic.messages.stream({
model: model ?? 'claude-sonnet-4-20250514',
max_tokens: 2048,
system: system ?? '',
messages,
});
for await (const event of stream) {
if (
event.type === 'content_block_delta' &&
event.delta.type === 'text_delta'
) {
res.write(
`data: ${JSON.stringify({ content: event.delta.text })}\n\n`
);
}
}
}
res.write('data: [DONE]\n\n');
res.end();
} catch (error: any) {
res.write(
`data: ${JSON.stringify({ error: error.message })}\n\n`
);
res.end();
}
});
Frontend-client voor streaming
// src/app/services/ai-chat.service.ts
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
@Injectable({ providedIn: 'root' })
export class AiChatService {
private apiUrl = '/api/chat';
streamChat(
provider: 'openai' | 'anthropic',
messages: Array<{ role: string; content: string }>,
system?: string
): Observable<string> {
return new Observable(subscriber => {
const controller = new AbortController();
fetch(`${this.apiUrl}/stream`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ provider, messages, system }),
signal: controller.signal,
})
.then(response => {
const reader = response.body!.getReader();
const decoder = new TextDecoder();
function read() {
reader.read().then(({ done, value }) => {
if (done) {
subscriber.complete();
return;
}
const text = decoder.decode(value);
const lines = text.split('\n');
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = line.slice(6);
if (data === '[DONE]') {
subscriber.complete();
return;
}
try {
const parsed = JSON.parse(data);
if (parsed.content) {
subscriber.next(parsed.content);
}
} catch {
// Ignora righe non parsabili
}
}
}
read();
});
}
read();
})
.catch(err => subscriber.error(err));
return () => controller.abort();
});
}
}
9. Bouw een complete chatinterface
Door alle tot nu toe gebouwde componenten te combineren, kunnen we een interface creëren Volledige chat die beide providers ondersteunt, de berichtgeschiedenis beheert en toont antwoorden die in realtime worden gestreamd.
// src/app/components/chat/chat.component.ts
import { Component, signal } from '@angular/core';
import { AiChatService } from '../../services/ai-chat.service';
interface Message {
role: 'user' | 'assistant';
content: string;
provider?: string;
timestamp: Date;
}
@Component({
selector: 'app-chat',
standalone: true,
template: `...`,
})
export class ChatComponent {
messages = signal<Message[]>([]);
inputText = signal('');
isLoading = signal(false);
provider = signal<'openai' | 'anthropic'>('anthropic');
currentStream = signal('');
constructor(private chatService: AiChatService) {}
sendMessage(): void {
const text = this.inputText().trim();
if (!text || this.isLoading()) return;
// Aggiungi messaggio utente
this.messages.update(msgs => [
...msgs,
{ role: 'user', content: text, timestamp: new Date() }
]);
this.inputText.set('');
this.isLoading.set(true);
this.currentStream.set('');
// Prepara messaggi per l'API
const apiMessages = this.messages().map(m => ({
role: m.role,
content: m.content,
}));
// Avvia streaming
this.chatService.streamChat(
this.provider(),
apiMessages,
'Sei un assistente esperto e amichevole.'
).subscribe({
next: (token) => {
this.currentStream.update(s => s + token);
},
complete: () => {
this.messages.update(msgs => [
...msgs,
{
role: 'assistant',
content: this.currentStream(),
provider: this.provider(),
timestamp: new Date(),
}
]);
this.currentStream.set('');
this.isLoading.set(false);
},
error: (err) => {
console.error('Errore streaming:', err);
this.isLoading.set(false);
},
});
}
}
10. Beste praktijken en optimalisatie
Na het implementeren van de basisintegratie zijn er verschillende geavanceerde praktijken die de kwaliteit, betrouwbaarheid en efficiëntie aanzienlijk verbeteren van uw aanvraag.
Uniforme providerabstractie
Door een gemeenschappelijke interface te creëren, kunt u van de ene provider naar de andere overstappen zonder de applicatiecode te wijzigen. Dit maakt de implementatie ook eenvoudiger van terugvalstrategieën.
// src/lib/unified-ai.ts
interface AIProvider {
chat(messages: ChatMessage[], system?: string): Promise<string>;
stream(messages: ChatMessage[], system?: string): AsyncGenerator<string>;
}
export class UnifiedAI {
private providers: Map<string, AIProvider> = new Map();
private primaryProvider: string;
constructor(primary: string) {
this.primaryProvider = primary;
}
registerProvider(name: string, provider: AIProvider): void {
this.providers.set(name, provider);
}
async chatWithFallback(
messages: ChatMessage[],
system?: string
): Promise<{ content: string; provider: string }> {
const providerOrder = [
this.primaryProvider,
...Array.from(this.providers.keys())
.filter(p => p !== this.primaryProvider)
];
for (const name of providerOrder) {
const provider = this.providers.get(name);
if (!provider) continue;
try {
const content = await withRetry(
() => provider.chat(messages, system),
{ maxRetries: 2 }
);
return { content, provider: name };
} catch (error) {
console.warn(`Provider ${name} fallito, provo il successivo...`);
}
}
throw new Error('Tutti i provider AI sono non disponibili');
}
}
Veelvoorkomende fouten die u moet vermijden
- Maak de API-sleutels zichtbaar in de frontend: gebruik altijd een proxyserver
- Tarieflimieten negeren: implementeert een snelheidsbegrenzer aan de clientzijde
- Ga niet om met time-outs: configureer de juiste time-outs voor elk verzoek
- Stuur uw volledige geschiedenis: oude berichten afkappen om tokens te bewaren
- Kosten die niet worden bijgehouden: waarschuwingen over uitgavendrempels implementeren
- Het model hardcoderen: maak de sjabloon configureerbaar voor eenvoudige updates
Integratiechecklist
- API-sleutels opgeslagen in omgevingsvariabelen en nooit in code
- Proxyserver geconfigureerd met CORS en snelheidsbeperking
- Probeer het opnieuw met exponentiële uitstel geïmplementeerd
- Streaming werkt voor beide providers
- Actieve tokentelling en kostenbewaking
- Foutbeheer met informatieve berichten voor de gebruiker
- Integratietesten voor beide providers
- Gecentraliseerde logboekregistratie voor foutopsporing
Conclusie
Het integreren van OpenAI en Anthropic API's vereist aandacht voor detail maar als het eenmaal correct is geconfigureerd, biedt het een solide basis om op voort te bouwen krachtige en betrouwbare AI-toepassingen. Het proxypatroon beschermt uw sleutels, het retry-systeem garandeert veerkracht en de streaming biedt een ervaring kwaliteit gebruiker.
In het volgende artikel zullen we onderzoeken hoe u dit kunt doen Lokale LLM's met Ollama, het elimineren van de afhankelijkheid van de cloud en het openen van interessante mogelijkheden voor privacy, kosten en offline ontwikkeling.







