Integrujte OpenAI a Antropické API do webové aplikace
Integrace jazykového modelu API je jednou z nejžádanějších dovedností v moderním vývoji webu. Ať už jde o stavbu chatbota, asistenta pro generování obsahu nebo systém pro analýzu textu, schopnost komunikovat efektivně s API OpenAI e Antropický je to zásadní.
V tomto čtvrtém článku série AI pro webové vývojáře, podrobně prozkoumáme jak integrovat obě API do kompletní webové aplikace. Začneme od konfigurace nejprve analyzujeme struktury zpráv, implementujeme streamování odpovědí a vybudujeme zabezpečenou architekturu se vzory proxy pro ochranu klíčů API.
Co se naučíte
- Konfigurujte a ověřujte pomocí OpenAI a Antropických API
- Pochopte strukturální rozdíly mezi těmito dvěma systémy zpráv
- Implementujte streamování odezvy pro zážitek v reálném čase
- Robustní řešení chyb, opakování a omezení rychlosti
- Počítejte tokeny a optimalizujte náklady na používání
- Vybudujte zabezpečený server proxy pro ochranu klíčů API
- Vytvořte kompletní chatovací rozhraní s podporou streamování
Přehled série
| # | Položka | Soustředit |
|---|---|---|
| 1 | Úvod do RAG | Základní pojmy |
| 2 | RAG s TypeScript a LangChain | Praktické provedení |
| 3 | Porovnání vektorové databáze | Chromadb, Šiška, Weaviate |
| 4 | OpenAI a Antropické API (jste zde) | Integrace API |
| 5 | LLM Local s Ollamou | AI bez cloudu |
| 6 | Jemné doladění vs RAG | Kdy co použít |
| 7 | Agenti AI | Architektura založená na agentech |
| 8 | AI v CI/CD | Inteligentní automatizace |
1. Nastavení a ověřování API
Než budete moci odeslat svou první žádost, musíte získat své přihlašovací údaje a správně nakonfigurovat vývojové prostředí. Oba poskytovatelé vyžadují a Klíč API, který musí být spravován bezpečně.
Získejte klíče API
Pro OpenAI, zaregistrujte se platform.openai.com a vygenerovat klíč
v sekci API Keys. Pro Antropický, přístup ke konzoli na
console.anthropic.com a na panelu nastavení vytvořte klíč API.
Zabezpečení klíče API
Nikdy nezahrnujte klíče API přímo do zdrojového kódu nebo potvrzených souborů na Git. Vždy používejte proměnné prostředí nebo tajnou službu správy. Odhalený klíč může způsobit neočekávané náklady a ohrozit bezpečnost.
# 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
Instalace sad SDK
Oba poskytovatelé nabízejí oficiální sady SDK pro TypeScript/JavaScript, které to usnadňují integrace výrazně. Sady SDK automaticky zpracovávají serializaci, základní deserializace a opakování.
# 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
Inicializace klienta
// 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. Porovnání struktury zpráv
Přestože obě rozhraní API pracují s konceptem zpráv v konverzaci, struktury se výrazně liší. Pochopte tyto rozdíly je zásadní pro budování jednotné abstrakce.
Srovnání struktur
| Charakteristický | OpenAI | Antropický |
|---|---|---|
| Role zpráv | systém, uživatel, asistent, nástroj | uživatel, asistent (samostatný systém) |
| Systémová výzva | Ve zprávách | Vyhrazený parametr system |
| Model | model: "gpt-4o" | model: "claude-sonnet-4-20250514" |
| Maximální výkon | max_tokens (volitelný) | max_tokens (povinný) |
| Teplota | 0,0–2,0 (výchozí 1,0) | 0,0–1,0 (výchozí 1,0) |
| Streamování | Události odeslané serverem | Události odeslané serverem |
| Koncové body | /v1/chat/completions | /v1/messages |
Vyžádáno s 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);
Vyžádejte si u Antropic
// 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. Streamování odpovědí
Streamování je klíčem k zajištění bezproblémového uživatelského zážitku. Místo čekání že je vygenerována celá zpráva, jsou jako model odeslány tokeny vyrábí je, čímž drasticky zkracuje vnímanou čekací dobu.
Streamování s 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);
}
}
Streamování s 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. Správa chyb a opakujte akci
Externí rozhraní API mohou selhat z několika důvodů: omezení rychlosti, časové limity, chyby serveru nebo problémy se sítí. Robustní systém opakování je nezbytný aby byla zajištěna spolehlivost aplikace.
Běžné chybové kódy
| Kód | Význam | Strategie |
|---|---|---|
| 400 | Chybný požadavek | Nezkoušejte to znovu, opravte parametry |
| 401 | Ověření se nezdařilo | Zkontrolujte svůj API klíč |
| 429 | Překročen limit sazby | Počkejte prosím a zkuste to znovu s odvoláním |
| 500 | Chyba serveru | Zkuste to znovu s exponenciálním ústupem |
| 503 | Služba není k dispozici | Zkuste to znovu po několika sekundách |
| 529 | Přetížení (antropické) | Zkuste to znovu s dlouhým odstupem |
// 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. Omezení rychlosti a řízení frekvence
Když vaše aplikace zpracovává mnoho souběžných uživatelů, je důležité ji implementovat systém omezení sazeb na straně klienta, aby se zabránilo překročení stanovených limitů poskytovateli a zajistit spravedlivé využívání zdrojů.
// 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. Počítání tokenů a optimalizace nákladů
Náklady na API jsou přímo úměrné počtu zpracovaných tokenů. Monitorování a optimalizace spotřeby tokenů je zásadní pro udržení nákladů pod kontrolou, zejména v aplikacích s vysokým provozem.
Orientační náklady na 1 milion tokenu (únor 2026)
| Model | Vstup (1 milion tokenu) | Výstup (1 milion tokenu) |
|---|---|---|
| GPT-4o | 2,50 $ | 10,00 $ |
| GPT-4o mini | 0,15 USD | 0,60 $ |
| Claude Sonet 4 | 3,00 $ | 15,00 $ |
| Claude Haiku 3.5 | 0,80 $ | 4,00 $ |
Poznámka: Ceny jsou orientační a mohou se změnit. Aktualizované náklady vždy zkontrolujte v oficiální dokumentaci.
// 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. Vzor proxy pro zabezpečení klíče API
Klíče API nemusí nikdy být vystaven frontendu. Vzor doporučeno je vytvořit proxy server, který mezi nimi funguje jako prostředník prohlížeč uživatele a rozhraní API poskytovatele AI. Frontend komunikuje s vaším server a server spravuje klíče bezpečně.
// 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. Streamování odpovědí do frontendu
Chcete-li přenést zážitek ze streamování do prohlížeče uživatele, proxy serveru musí podporovat Události odeslané serverem (SSE). Tento protokol umožňuje na server k odesílání dat klientovi postupně prostřednictvím jednoho HTTP připojení.
// 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();
}
});
Frontendový klient pro streamování
// 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. Vytvořte kompletní chatovací rozhraní
Kombinací všech dosud sestavených komponent můžeme vytvořit rozhraní Plný chat, který podporuje oba poskytovatele, spravuje historii zpráv a zobrazuje odpovědi streamované v reálném čase.
// 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. Nejlepší postupy a optimalizace
Po implementaci základní integrace existuje několik pokročilých postupů které výrazně zlepšují kvalitu, spolehlivost a efektivitu vaší aplikace.
Unified Provider Abstrakce
Vytvoření společného rozhraní vám umožní přejít od jednoho poskytovatele k druhému bez úpravy kódu aplikace. To také usnadňuje implementaci záložních strategií.
// 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');
}
}
Běžné chyby, kterým je třeba se vyhnout
- Odhalte klíče API ve frontendu: vždy používejte proxy server
- Ignorování limitů sazeb: implementuje omezovač sazeb na straně klienta
- Neřešit časové limity: nakonfigurujte příslušné časové limity pro každý požadavek
- Pošlete celou svou historii: zkrátit staré zprávy a uložit tokeny
- Nesledování nákladů: implementovat upozornění na prahové hodnoty výdajů
- Pevný kód modelu: udělejte šablonu konfigurovatelnou pro snadné aktualizace
Kontrolní seznam integrace
- Klíče API uložené v proměnných prostředí a nikdy v kódu
- Proxy server nakonfigurovaný s CORS a omezením rychlosti
- Opakujte akci s implementovaným exponenciálním stažením
- Streamování funguje u obou poskytovatelů
- Aktivní počítání tokenů a sledování nákladů
- Správa chyb s informativními zprávami pro uživatele
- Integrační testy pro oba poskytovatele
- Centralizované protokolování pro ladění
Závěr
Integrace OpenAI a Antropických API vyžaduje pozornost k detailům ale jakmile je správně nakonfigurován, nabízí pevný základ, na kterém lze stavět výkonné a spolehlivé aplikace AI. Vzor proxy chrání vaše klíče, systém opakování zaručuje odolnost a streamování nabízí zážitek kvalitní uživatel.
V dalším článku se podíváme, jak na to Místní LLM s Ollama, odstranění závislosti na cloudu a otevření zajímavých možností pro soukromí, náklady a offline vývoj.







