متوسط
⏱ ٣٥ دقيقة قراءة
© بوابة الذكاء الاصطناعي ٢٠٢٦-٠٦-٠٢
تصميم بوابة LLM متعددة المزودين في Next.js باستخدام معالجات مسار App Router للتبديل الديناميكي بين طلبات الإنتاج بين معماريات OpenAI وAnthropic مع الحفاظ على سياق الحالة.
المتطلبات الأساسية
- Node.js ١٨.x أو أعلى
- Next.js ١٤.x أو ١٥.x (هيكل App Router)
- مفاتيح API صالحة تم تكوينها في وحدات تحكم المطورين لـ OpenAI وAnthropic
- الإلمام بحمولات API متعددة الأشكال وتكوين حالة React
ما الذي نبنيه
غالبًا ما تتطلب هندسة الذكاء الاصطناعي في الإنتاج تكرار النماذج أو التوجيه المتخصص — توجيه المهام الإبداعية إلى Claude والتحديثات الهيكلية الصارمة إلى GPT. في هذا الدليل، نبني طبقة تجريد API موحدة تقوم بتنظيف وتخطيط وتنفيذ المحادثات متعددة الأدوار عبر كلا النظامين بشكل نظيف.
الإعداد والتثبيت
ابدأ طبقة تطبيق Next.js نظيفة وقم بتنزيل حزم SDK الرسمية لكلا البائعين:
npx create-next-app@latest multi-provider-chat --ts --no-tailwind --app --src-dir=false
cd multi-provider-chat
npm install openai @anthropic-ai/sdk
املأ مصفوفة البيئة المحلية الخاصة بك داخل .env.local في دليل الجذر الخاص بك:
OPENAI_API_KEY=your_openai_project_secret_key
ANTHROPIC_API_KEY=your_anthropic_live_secret_key
الخطوة ١: هندسة بوابة النموذج الموحدة
قم بإنشاء معالج مسار الخادم الخاص بك في app/api/chat/route.js. نقوم بإنشاء كلا العميلين بشكل صريح وننشئ محلل تجريد للتعامل مع اختلافات التخطيط الهيكلي بين مصفوفة الخيارات الخاصة بـ OpenAI وكتل المحتوى الخاصة بـ Anthropic.
import { NextResponse } from 'next/server';
import { OpenAI } from 'openai';
import { Anthropic } from '@anthropic-ai/sdk';
const openai = new OpenAI();
const anthropic = new Anthropic();
export async function POST(req) {
try {
const { provider, messages } = await req.json();
if (!messages || !Array.isArray(messages)) {
return NextResponse.json({ error: 'Malformed message history payload' }, { status: 400 });
}
let replyText = '';
if (provider === 'openai') {
const response = await openai.chat.completions.create({
model: 'gpt-4o-mini',
messages: messages.map(msg => ({
role: msg.role,
content: msg.content
}))
});
replyText = response.choices[0].message.content;
} else if (provider === 'anthropic') {
// Anthropic separates the 'system' message from the historical array
const systemMessage = messages.find(m => m.role === 'system')?.content || 'You are a precise assistant.';
const conversationHistory = messages.filter(m => m.role !== 'system');
const response = await anthropic.messages.create({
model: 'claude-3-5-haiku-20241022',
max_tokens: 1024,
system: systemMessage,
messages: conversationHistory.map(msg => ({
role: msg.role === 'assistant' ? 'assistant' : 'user', // Safe role mapping
content: msg.content
}))
});
replyText = response.content[0].text;
} else {
return NextResponse.json({ error: 'Unsupported provider routing request' }, { status: 400 });
}
return NextResponse.json({ reply: replyText });
} catch (error) {
console.error('Gateway Route Exception:', error);
return NextResponse.json({ error: error.message || 'Internal processing error' }, { status: 500 });
}
}
الخطوة ٢: بناء مكون الواجهة متعددة الأشكال
قم بإنشاء بنية مكون الواجهة الأمامية الخاصة بك في app/page.js. نقوم بتنفيذ وظائف تحديث الحالة القياسية للحفاظ على تتبع تاريخ المحادثة مع توفير قائمة منسدلة صريحة لتبديل المزودين ديناميكيًا.
'use client';
import { useState } from 'react';
export default function HomeChatWorkspace() {
const [messages, setMessages] = useState([
{ role: 'system', content: 'You are a helpful software engineering consultant.' }
]);
const [input, setInput] = useState('');
const [provider, setProvider] = useState('openai'); // Default routing value
const [isLoading, setIsLoading] = useState(false);
const handleFormSubmit = async (e) => {
e.preventDefault();
if (!input.trim() || isLoading) return;
const incomingUserNode = { role: 'user', content: input };
setInput('');
setIsLoading(true);
// Update UI state with user message immediately
const updatedHistory = [...messages, incomingUserNode];
setMessages(updatedHistory);
try {
const response = await fetch('/api/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ provider, messages: updatedHistory })
});
const data = await response.json();
if (!response.ok) throw new Error(data.error || 'Server rejected request');
setMessages((prev) => [...prev, { role: 'assistant', content: data.reply }]);
} catch (err) {
console.error('Inference Error:', err);
setMessages((prev) => [...prev, { role: 'system', content: `Error: ${err.message}` }]);
} finally {
setIsLoading(false);
}
};
return (
لوحة التحكم متعددة النماذج
setProvider(e.target.value)} disabled={isLoading}>
المحرك: GPT-4o-Mini
المحرك: Claude-3.5-Haiku
{messages.filter(m => m.role !== 'system').map((msg, idx) => (
{msg.role === 'user' ? 'أنت' : 'الذكاء الاصطناعي'}: {msg.content}
))}
setInput(e.target.value)}
placeholder="اطرح سؤالاً تقنيًا..."
disabled={isLoading}
style={{ flex: 1, padding: '10px', borderRadius: '4px', border: '1px solid #ccc' }}
/>
);
}
'user' متبوعًا بعقدة 'user' أخرى)، فسيرفض SDK الحمولة مع خطأ تحقق ٤٠٠. تأكد دائمًا من أن خط أنابيب التعيين الخاص بك ينظف تسلسلات بنية المصفوفة قبل إرسال التحديثات إلى محرك Claude.اختبار محرك التوجيه الخاص بك
قم بتشغيل مساحة عمل تطبيق Next.js محليًا:
npm run devافتح http://localhost:3000. اختر **GPT-4o-Mini** واطرح سؤالاً. ثم قم بتبديل الخيار المنسدل إلى **Claude-3.5-Haiku** واطرح أسئلة متابعة مثل “هل يمكنك إعادة صياغة سؤالي الأخير بلغة Rust؟” لاحظ كيف أن مصفوفة السياق المستمرة ترسم بأمان بغض النظر عن تبديلات التبديل الخاصة بك.
ما الذي يجب بناؤه بعد ذلك
- تحويل النظام لاستخدام أجزاء البث بشكل متزامن عبر كلا المكونين باستخدام أغلفة
ReadableStream. - تنفيذ تسجيل مقارنة النماذج الآلي لقياس تباينات زمن التنفيذ بين موجهات OpenAI وAnthropic.
- إضافة كتلة منطقية تمريرية تقوم بتبديل المزودين تلقائيًا إذا واجه أحد البائعين خطأ ٥٠٣ في حد المعدل.