OpenAI বা Claude API Next.js প্রজেক্টে যুক্ত করে প্রথম Chatbot Agent চালু করা

OpenAI বা Claude API Next.js প্রজেক্টে যুক্ত করে প্রথম Chatbot Agent চালু করা

Z
জাহিদ হাসান তন্ময়
১৬ অক্টোবর, ২০২৬
13 মিনিটের পড়া
🇬🇧 Read in English
|
0 views
অডিও ওভারভিউ ও পডকাস্ট
~১ মিনিট কুইক ওভারভিউ
0%

OpenAI বা Claude API Next.js প্রজেক্টে যুক্ত করে প্রথম Chatbot Agent চালু করা
OpenAI বা Claude API Next.js প্রজেক্টে যুক্ত করে প্রথম Chatbot Agent চালু করা

আমাদের আগের পর্বে আমরা যখন Next.js 14 Server Action দিয়ে আমাদের প্রথম AI Agent ব্যাকএন্ড এবং টুল লুপ তৈরি করেছিলাম, তখন পুরো পাইপলাইনটি বিনা খরচে টেস্ট করার জন্য আমরা একটি ডিটারমিনিস্টিক (if/else) প্ল্যানার ব্যবহার করেছিলাম। এতে আমাদের Zod ভ্যালিডেশন, maxSteps: 5 লিমিট এবং AgentStepTrace লগিং শতভাগ প্রস্তুত হয়ে গেছে। কিন্তু সত্যিকারের ইউজার যখন অচেনা কোনো বাগ বা কোডের সমস্যা নিয়ে প্রশ্ন করবে, তখন হার্ডকোডেড লজিক দিয়ে আর কাজ চলবে না—এখন আমাদের এজেন্টের ভেতরে যুক্ত করতে হবে আসল LLM ব্রেইন।

Next.js প্রজেক্টে OpenAI (gpt-4o) কিংবা Anthropic Claude (claude-3-5-sonnet-latest) API যুক্ত করে একটি প্রোডাকশন-গ্রেড Chatbot Agent চালু করার সবচেয়ে কার্যকর উপায় হলো দুই প্রোভাইডারের SDK-কে একটি কমন সার্ভার-সাইড অ্যাডাপ্টার (llm-client.ts)-এর ভেতরে নিয়ে আসা। এর ফলে আপনার API Key ক্লায়েন্ট ব্রাউজার থেকে পুরোপুরি গোপন থাকে, সিস্টেম প্রম্পট ও মেসেজ হিস্ট্রি স্বয়ংক্রিয়ভাবে নরমালাইজ হয় এবং মাত্র একটি কনফিগারেশন ফ্ল্যাগ বদলেই আপনি OpenAI ও Claude-এর মধ্যে সুইচ করতে পারেন।

ইন্টারনেটে বেশিরভাগ সাধারণ টিউটোরিয়ালে দেখানো হয় কীভাবে একটি API রাউটের ভেতরে সরাসরি new OpenAI() লিখে একটা সিঙ্গেল মেসেজ পাঠিয়ে উত্তর প্রিন্ট করতে হয়। কিন্তু বাস্তবে যখন আপনি একটি মাল্টি-টার্ন (Multi-turn) এজেন্ট বানাবেন, তখন আপনাকে আগের মেসেজগুলোর কনটেক্সট ধরে রাখা, টোকেন খরচ হিসাব করা এবং দুই প্রোভাইডারের পেলোড ফরম্যাটের পার্থক্য সামলানোর মতো গুরুত্বপূর্ণ ইঞ্জিনিয়ারিং দিকগুলো মাথায় রাখতে হবে।

কেন একই সাথে OpenAI এবং Claude—দুই প্রোভাইডারের সাপোর্ট রাখা জরুরি?

আমরা আমাদের DevPulse এজেন্টটি তৈরি করছি টেকনিক্যাল ডকুমেন্টেশন সার্চ এবং বাগ ট্রায়াজ করার জন্য। প্রোডাকশন সিস্টেমে শুধুমাত্র একটি LLM কোম্পানির ওপর নির্ভর করে থাকা বেশ ঝুঁকিপূর্ণ। তাছাড়া কাজের ধরন অনুযায়ী দুটি মডেলেরই আলাদা শক্তি রয়েছে:

  • Claude 3.5 Sonnet (@anthropic-ai/sdk): বড় সিস্টেম প্রম্পট অক্ষরে অক্ষরে মেনে চলা, কোড অ্যানালাইসিস করা এবং নেগেটিভ নির্দেশনা (যেমন: 'ডকুমেন্টেশনে না থাকলে বানিয়ে কোনো এরর কোড বলবে না') মানার ক্ষেত্রে দারুণ কাজ করে।
  • OpenAI GPT-4o / GPT-4o-mini (openai): খুব দ্রুত রেসপন্স শুরু করা এবং কম খরচে ছোট ছোট ট্রায়াজ বা ক্লাসিফিকেশন টাস্ক শেষ করার জন্য অত্যন্ত কার্যকর।

সমস্যা হলো, OpenAI এবং Anthropic-এর API রিকোয়েস্ট পাঠানোর নিয়ম এক নয়। আপনি যদি আপনার Server Action বা React কম্পোনেন্টের ভেতরে সরাসরি OpenAI-এর ফরম্যাট হার্ডকোড করে ফেলেন, তবে পরে Claude-এ সুইচ করতে গেলে পুরো ব্যাকএন্ড আবার নতুন করে লিখতে হবে। তাই শুরুতেই একটি Unified Adapter বানিয়ে নেওয়া হলো সবচেয়ে বুদ্ধিমানের কাজ।

ইউনিফাইড এলএলএম অ্যাডাপ্টারের মাধ্যমে মেসেজ স্যানিটাইজ করে দুটি আলাদা এআই মডেলে রাউটিং করার থ্রিডি ভিজ্যুয়ালাইজেশন
ইউনিফাইড এলএলএম অ্যাডাপ্টারের মাধ্যমে মেসেজ স্যানিটাইজ করে দুটি আলাদা এআই মডেলে রাউটিং করার থ্রিডি ভিজ্যুয়ালাইজেশন

OpenAI ও Claude API-এর ৩টি বড় টেকনিক্যাল পার্থক্য

কোড লেখার আগে চলুন দেখে নিই দুই প্রোভাইডারের ভেতরে ঠিক কোন তিনটি জায়গায় অমিল রয়েছে, যা আমাদের অ্যাডাপ্টার নিজে থেকেই সমাধান করবে:

১. System Prompt পাঠানোর জায়গা: OpenAI-তে messages অ্যারের একদম শুরুতে { role: 'system', content: '...' } অবজেক্ট হিসেবে সিস্টেম প্রম্পট পাঠানো যায়। কিন্তু Anthropic-এর messages অ্যারেতে role: 'system' পাঠালেই সাথে সাথে 400 Bad Request এরর দেয়—সেখানে system দিতে হয় আলাদা টপ-লেভেল প্রপার্টি হিসেবে। ২. User ও Assistant রোলের ক্রম (Alternation Rule): Claude API-তে মেসেজ হিস্ট্রি সবসময় user দিয়ে শুরু হতে হয় এবং প্রতি ধাপে বাধ্যতামূলকভাবে user -> assistant -> user ক্রম মানতে হয়। কোনো কারণে নেটওয়ার্ক এররের জন্য যদি পরপর দুটি user মেসেজ অ্যারেতে ঢুকে পড়ে, তবে Claude রিকোয়েস্ট রিজেক্ট করে দেয়। ৩. আউটপুট ও টোকেন ডেটার স্ট্রাকচার: OpenAI উত্তর দেয় choices[0].message.content-এর ভেতরে এবং টোকেন হিসাব দেয় prompt_tokens ও completion_tokens নামে। অন্যদিকে Claude উত্তর দেয় কন্টেন্ট ব্লকের অ্যারে হিসেবে (content[0].text) এবং টোকেন রিটার্ন করে input_tokens ও output_tokens নামে।

নিচের ডায়াগ্রামে দেখুন আমাদের Next.js Server Action কীভাবে একটি ইউনিফাইড অ্যাডাপ্টারের মাধ্যমে এই দুই API-এর সাথে কথা বলে:

`mermaid flowchart TD UI([React Chatbot UI - DevPulseChat.tsx]) -->|messages + modelId| SA[Next.js Server Action - runDevPulseChatbot] SA --> Sanitize[Zod Validation + Sliding Window History Normalizer] Sanitize --> Adapter{Unified LLM Adapter - llm-client.ts}

Adapter -->|modelId = gpt-4o| OpenAI[OpenAI SDK: chat.completions.create]
Adapter -->|modelId = claude-3-5-sonnet| Claude[Anthropic SDK: messages.create]

OpenAI --> Normalize[Normalize Reply + Token Telemetry]
Claude --> Normalize

Normalize --> Trace[Append AgentStepTrace with Latency & Token Count]
Trace --> UI

`

আর্কিটেকচার ফ্লো সামারি (Architecture Flow Summary): React ফ্রন্টএন্ড থেকে ইউজারের চ্যাট হিস্ট্রি এবং সিলেক্ট করা modelId সরাসরি Next.js Server Action-এ যায়, যেখানে Zod ভ্যালিডেশন ও মেসেজ স্যানিটাইজেশন সম্পন্ন হয়। এরপর আমাদের ইউনিফাইড অ্যাডাপ্টার (llm-client.ts) মডেল অনুযায়ী OpenAI বা Anthropic SDK কল করে, প্রাপ্ত উত্তর ও টোকেন সংখ্যাকে একটি অভিন্ন ফরম্যাটে সাজিয়ে AgentStepTrace-এ লগ করে এবং নিরাপদে UI-তে ফিরিয়ে দেয়।

ধাপ ১: অফিশিয়াল SDK ইনস্টল এবং .env.local কনফিগারেশন

প্রথমে আপনার Next.js প্রজেক্টের টার্মিনালে নিচের কমান্ডটি চালিয়ে OpenAI ও Anthropic-এর অফিশিয়াল SDK এবং server-only প্যাকেজটি ইনস্টল করে নিন:

bash npm install openai @anthropic-ai/sdk zod server-only

এরপর প্রজেক্টের রুট ফোল্ডারে .env.local ফাইলে আপনার API Key দুটি যোগ করুন। ভুলেও কোনো ভেরিয়েবলের নামের আগে NEXT_PUBLIC_ বসাবেন না:

`ini

.env.local

OPENAI_API_KEY=sk-proj-your-openai-key-here ANTHROPIC_API_KEY=sk-ant-your-anthropic-key-here `

ধাপ ২: Unified LLM Adapter তৈরি করা (src/lib/agent/llm-client.ts)

চলুন এবার তৈরি করি আমাদের মূল ব্রেইন মডিউল src/lib/agent/llm-client.ts। এই ফাইলের প্রথম লাইনে আমরা 'server-only' যুক্ত করেছি, যাতে কোনো ক্লায়েন্ট কম্পোনেন্ট ভুলেও এই ফাইল ইমপোর্ট করলে সাথে সাথে বিল্ড এরর দেখায় এবং আপনার সিক্রেট কী কখনোই ফাঁস না হয়।

`typescript // src/lib/agent/llm-client.ts import 'server-only'; import OpenAI from 'openai'; import Anthropic from '@anthropic-ai/sdk'; import { type AgentConfig } from './blueprint';

export interface ChatMessage { role: 'user' | 'assistant'; content: string; }

export interface NormalizedLLMResponse { reply: string; modelUsed: string; promptTokens: number; completionTokens: number; latencyMs: number; }

function getOpenAIClient(): OpenAI { const apiKey = process.env.OPENAI_API_KEY; if (!apiKey) { throw new Error('Missing OPENAI_API_KEY in server environment variables.'); } return new OpenAI({ apiKey }); }

function getAnthropicClient(): Anthropic { const apiKey = process.env.ANTHROPIC_API_KEY; if (!apiKey) { throw new Error('Missing ANTHROPIC_API_KEY in server environment variables.'); } return new Anthropic({ apiKey }); }

export function sanitizeConversationHistory( messages: ChatMessage[], maxWindowSize = 10 ): ChatMessage[] { const trimmed = messages.slice(-maxWindowSize); const merged: ChatMessage[] = [];

for (const msg of trimmed) { const cleanContent = msg.content.trim(); if (!cleanContent) continue;

const previous = merged[merged.length - 1];
if (previous && previous.role === msg.role) {
  previous.content = `${previous.content}\n\n${cleanContent}`;
} else {
  merged.push({ role: msg.role, content: cleanContent });
}

}

if (merged.length > 0 && merged[0].role === 'assistant') { merged.shift(); }

return merged; }

export async function generateAgentReply( messages: ChatMessage[], config: AgentConfig ): Promise { const startTime = Date.now(); const safeMessages = sanitizeConversationHistory(messages, 10);

if (config.modelId === 'gpt-4o') { const openai = getOpenAIClient(); const response = await openai.chat.completions.create({ model: 'gpt-4o', temperature: config.temperature, max_tokens: 1024, messages: [ { role: 'system', content: config.systemPrompt }, ...safeMessages.map((m) => ({ role: m.role, content: m.content, })), ], });

const replyText =
  response.choices[0]?.message?.content ??
  'No response generated by OpenAI.';

return {
  reply: replyText,
  modelUsed: response.model,
  promptTokens: response.usage?.prompt_tokens ?? 0,
  completionTokens: response.usage?.completion_tokens ?? 0,
  latencyMs: Date.now() - startTime,
};

}

const anthropic = getAnthropicClient(); const response = await anthropic.messages.create({ model: 'claude-3-5-sonnet-latest', max_tokens: 1024, temperature: config.temperature, system: config.systemPrompt, messages: safeMessages.map((m) => ({ role: m.role, content: m.content, })), });

const firstTextBlock = response.content.find( (block) => block.type === 'text' ); const replyText = firstTextBlock && 'text' in firstTextBlock ? firstTextBlock.text : 'No text block returned by Claude.';

return { reply: replyText, modelUsed: response.model, promptTokens: response.usage.input_tokens, completionTokens: response.usage.output_tokens, latencyMs: Date.now() - startTime, }; } `

উপরের কোডে sanitizeConversationHistory() ফাংশনটি খেয়াল করুন। এটি একাধারে তিনটি বড় প্রোডাকশন সমস্যার সমাধান করছে:

  • Sliding Window (slice(-10)): চ্যাট যত লম্বাই হোক না কেন, এটি সর্বশেষ ১০টি মেসেজ পাঠায়—যাতে অযথা পুরনো মেসেজের জন্য আপনার API টোকেন বিল না বাড়ে।
  • রোলের ক্রম ঠিক রাখা: পরপর দুটি একই রোলের (user বা assistant) মেসেজ থাকলে সেগুলোকে মার্জ করে নেয়, যাতে Claude API কখনোই ক্র্যাশ না করে।
  • খালি মেসেজ ফিল্টার: দুর্ঘটনাবশত কোনো ফাঁকা স্ট্রিং গেলে তা বাদ দিয়ে দেয়।

ধাপ ৩: Server Action আপডেট করা (src/app/actions/agent.ts)

এবার আমরা আগের পর্বের src/app/actions/agent.ts ফাইলটিকে আপডেট করব যাতে এটি আমাদের নতুন generateAgentReply() কল করে এবং কতগুলো ইনপুট ও আউটপুট টোকেন খরচ হলো তা AgentStepTrace-এ সংরক্ষণ করে:

`typescript // src/app/actions/agent.ts 'use server';

import { z } from 'zod'; import { defaultDevPulseConfig, type AgentStepTrace, } from '@/lib/agent/blueprint'; import { generateAgentReply, type ChatMessage, } from '@/lib/agent/llm-client';

const chatTurnSchema = z.object({ sessionId: z.string().min(8), modelId: z.enum(['gpt-4o', 'claude-3-5-sonnet-latest']).default('claude-3-5-sonnet-latest'), messages: z .array( z.object({ role: z.enum(['user', 'assistant']), content: z.string().trim().min(1).max(4000), }) ) .min(1), });

export interface ChatbotAgentResponse { success: boolean; reply: string; modelUsed: string; promptTokens: number; completionTokens: number; steps: AgentStepTrace[]; errorMessage?: string; }

export async function runDevPulseChatbot( rawInput: unknown ): Promise { const parsed = chatTurnSchema.safeParse(rawInput);

if (!parsed.success) { return { success: false, reply: '', modelUsed: 'none', promptTokens: 0, completionTokens: 0, steps: [], errorMessage: parsed.error.issues.map((i) => i.message).join('; '), }; }

const { messages, modelId } = parsed.data; const activeConfig = { ...defaultDevPulseConfig, modelId, };

try { const llmResult = await generateAgentReply( messages as ChatMessage[], activeConfig );

const traceStep: AgentStepTrace = {
  stepIndex: 1,
  thoughtSummary: `Invoked ${llmResult.modelUsed} (${llmResult.promptTokens} in / ${llmResult.completionTokens} out tokens)`,
  durationMs: llmResult.latencyMs,
};

return {
  success: true,
  reply: llmResult.reply,
  modelUsed: llmResult.modelUsed,
  promptTokens: llmResult.promptTokens,
  completionTokens: llmResult.completionTokens,
  steps: [traceStep],
};

} catch (err) { const message = err instanceof Error ? err.message : 'Unexpected LLM provider error'; return { success: false, reply: '', modelUsed: modelId, promptTokens: 0, completionTokens: 0, steps: [], errorMessage: message, }; } } `

ধাপ ৪: ইন্টারঅ্যাক্টিভ চ্যাটবট এজেন্ট UI তৈরি (src/components/DevPulseChat.tsx)

এবার ফ্রন্টএন্ডে আমাদের চ্যাট ইন্টারফেসটি বানানোর পালা। নিচের কম্পোনেন্টটিতে ইউজার সরাসরি ড্রপডাউন থেকে Claude 3.5 Sonnet এবং OpenAI GPT-4o পরিবর্তন করতে পারবে এবং প্রতিটি উত্তরের নিচে কত টোকেন ও কত মিলিসেকেন্ড সময় লাগল তা দেখতে পাবে:

`tsx // src/components/DevPulseChat.tsx 'use client';

import { useState, useTransition } from 'react'; import { runDevPulseChatbot, type ChatbotAgentResponse, } from '@/app/actions/agent'; import { type ChatMessage } from '@/lib/agent/llm-client';

export default function DevPulseChat() { const [messages, setMessages] = useState<ChatMessage[]>([]); const [input, setInput] = useState(''); const [modelId, setModelId] = useState<'claude-3-5-sonnet-latest' | 'gpt-4o'>( 'claude-3-5-sonnet-latest' ); const [lastMetrics, setLastMetrics] = useState<ChatbotAgentResponse | null>(null); const [isPending, startTransition] = useTransition();

function handleSend(e: React.FormEvent) { e.preventDefault(); const trimmed = input.trim(); if (!trimmed || isPending) return;

const updatedMessages: ChatMessage[] = [
  ...messages,
  { role: 'user', content: trimmed },
];
setMessages(updatedMessages);
setInput('');

startTransition(async () => {
  const res = await runDevPulseChatbot({
    sessionId: 'devpulse-local-session-01',
    modelId,
    messages: updatedMessages,
  });

  setLastMetrics(res);
  if (res.success) {
    setMessages([
      ...updatedMessages,
      { role: 'assistant', content: res.reply },
    ]);
  }
});

}

return ( DevPulse Chatbot Agent System Contract Active | Sliding Window: 10 turns <select value={modelId} onChange={(e) => setModelId(e.target.value as 'claude-3-5-sonnet-latest' | 'gpt-4o') } className='bg-slate-950 border border-slate-700 rounded-lg px-3 py-1.5 text-xs text-slate-200' > Claude 3.5 Sonnet OpenAI GPT-4o

  <div className='h-80 overflow-y-auto space-y-3 p-3 bg-slate-950 rounded-lg border border-slate-800/80'>
    {messages.length === 0 && (
      <p className='text-xs text-slate-500 text-center py-12'>
        Ask DevPulse a technical question about API rate limits, auth, or bug triage.
      </p>
    )}
    {messages.map((m, idx) => (
      <div
        key={idx}
        className={`p-3 rounded-lg text-sm ${
          m.role === 'user'
            ? 'bg-cyan-950/50 border border-cyan-800/50 ml-12'
            : 'bg-slate-900 border border-slate-800 mr-12'
        }`}
      >
        <div className='text-[10px] uppercase tracking-wider text-slate-400 mb-1'>
          {m.role}
        </div>
        <div className='whitespace-pre-wrap'>{m.content}</div>
      </div>
    ))}
  </div>

  <form onSubmit={handleSend} className='flex gap-2'>
    <input
      value={input}
      onChange={(e) => setInput(e.target.value)}
      placeholder='Describe an issue or ask about API behavior...'
      className='flex-1 px-3 py-2 rounded-lg bg-slate-950 border border-slate-700 text-sm'
    />
    <button
      type='submit'
      disabled={isPending}
      className='px-4 py-2 rounded-lg bg-cyan-600 hover:bg-cyan-500 disabled:opacity-50 text-sm font-semibold'
    >
      {isPending ? 'Thinking...' : 'Send'}
    </button>
  </form>

  {lastMetrics && (
    <div className='text-xs text-slate-400 flex justify-between pt-2 border-t border-slate-800'>
      <span>Model: {lastMetrics.modelUsed}</span>
      <span>
        Tokens: {lastMetrics.promptTokens} in / {lastMetrics.completionTokens} out
      </span>
      <span>
        Latency: {lastMetrics.steps[0]?.durationMs ?? 0}ms
      </span>
    </div>
  )}
</div>

); } `

ডার্ক গ্লাসমরফিজম চ্যাটবট এজেন্ট ইন্টারফেস যেখানে ইউজার ও অ্যাসিস্ট্যান্ট মেসেজ, মডেল সুইচ টগল এবং নিচে টোকেন টেলিমেট্রি বার দেখা যাচ্ছে
ডার্ক গ্লাসমরফিজম চ্যাটবট এজেন্ট ইন্টারফেস যেখানে ইউজার ও অ্যাসিস্ট্যান্ট মেসেজ, মডেল সুইচ টগল এবং নিচে টোকেন টেলিমেট্রি বার দেখা যাচ্ছে

দুই প্রোভাইডার একসাথে যুক্ত করতে গিয়ে আমার নিজের একটি ডিবাগিং অভিজ্ঞতা

আমি যখন প্রথমবার আমার একটি Next.js প্রজেক্টে OpenAI এবং Claude—দুই মডেলের আউটপুট তুলনা করার জন্য একই চ্যাট স্টেট (messages) দিয়ে ব্যাকএন্ড প্রোটোটাইপ বানিয়েছিলাম, তখন প্রথম কয়েকবার চ্যাট করার সময় কোনো সমস্যাই ধরা পড়েনি। বাগটা সামনে আসে ঠিক তখনই, যখন মাঝপথে একবার নেটওয়ার্ক টাইমআউট হয়।

হয়েছিল কী—আমি একটা প্রশ্ন পাঠানোর পর নেটওয়ার্ক স্লো থাকায় সার্ভার থেকে উত্তর আসেনি, কিন্তু আমার React স্টেটে ইউজারের মেসেজটা ঠিকই যুক্ত হয়ে গিয়েছিল। আমি যখন পরের বার আরেকটি প্রশ্ন লিখে সেন্ড করলাম, তখন messages অ্যারের শেষের দিকে পরপর দুটি { role: 'user' } অবজেক্ট জমা হয়ে গেল। ড্রপডাউনে যখন gpt-4o সিলেক্ট করা ছিল, OpenAI কোনো এরর ছাড়াই পরপর দুটি ইউজার মেসেজ প্রসেস করে উত্তর দিয়ে দিল। কিন্তু যেই আমি মডেল পাল্টে claude-3-5-sonnet-latest সিলেক্ট করলাম, অমনি ব্যাকএন্ড থেকে 400 invalid_request_error: roles must alternate between user and assistant এরর এসে পুরো চ্যাট থেমে গেল!

সেই সমস্যার পরই আমি sanitizeConversationHistory() ফাংশনটি তৈরি করি, যা API কল করার ঠিক আগ মুহূর্তে পরপর থাকা একই রোলের মেসেজগুলোকে একসাথে জোড়া লাগিয়ে নেয় এবং systemPrompt-কে সবসময় মূল মেসেজ অ্যারে থেকে আলাদা রাখে। এই ছোট্ট নরমালাইজেশন লেয়ারটি থাকার কারণে এখন ইউজার যেভাবেই মেসেজ পাঠাক না কেন, দুই প্রোভাইডারের কোথাও আর 400 এরর আসে না।

OpenAI ও Claude API যুক্ত করার সময় যে ৪টি ভুল এড়িয়ে চলবেন

পরের পর্বে স্ট্রিমিং যুক্ত করার আগে দেখে নিন আপনার কোডে যেন এই সাধারণ ভুলগুলো না থাকে:

১. ফাইলের একদম শুরুতে গ্লোবাল স্কোপে SDK ইনস্ট্যান্স তৈরি করা

অনেকেই ফাইলের শুরুতেই const openai = new OpenAI() লিখে রাখেন। কিন্তু আপনি যখন Vercel বা Docker-এ next build কমান্ড চালাবেন, তখন বিল্ড এনভায়রনমেন্টে যদি API Key না থাকে, তবে SDK সাথে সাথে এরর থ্রো করে পুরো বিল্ড ফেল করিয়ে দেবে। তাই সবসময় getOpenAIClient()-এর মতো ফাংশনের ভেতরে ফাংশন কল হওয়ার সময় (Lazy Initialization) ক্লায়েন্ট তৈরি করবেন।

২. চ্যাট হিস্ট্রির আকার সীমাবদ্ধ না রাখা (Unbounded Context)

LLM নিজে থেকে আগের কোনো কথাই মনে রাখে না; প্রতিবার নতুন মেসেজের সাথে আগের সব মেসেজ পাঠাতে হয়। আপনি যদি স্লাইডিং উইন্ডো (slice(-10)) ব্যবহার না করে ৫০টা মেসেজের পুরো অ্যারে প্রতিবার পাঠান, তবে কয়েক মিনিটের চ্যাটেই হাজার হাজার ইনপুট টোকেন পুড়ে যাবে।

৩. ক্লায়েন্ট কম্পোনেন্টে API Key বা SDK ইমপোর্ট করা

কখনোই .env ফাইলে NEXT_PUBLIC_OPENAI_API_KEY লিখবেন না কিংবা 'use client' ফাইলে SDK ইমপোর্ট করবেন না। এতে যে কেউ ব্রাউজারের Network ট্যাব খুললেই আপনার API Key চুরি করে নিতে পারবে। আমাদের মতো import 'server-only' ব্যবহার করলে এই ভুল হওয়ার কোনো সুযোগই থাকে না।

৪. বড় উত্তরের ক্ষেত্রে ইউজারকে দীর্ঘক্ষণ অপেক্ষায় রাখা

লক্ষ্য করে দেখবেন, এখন আমাদের generateAgentReply() ফাংশনটি পুরো উত্তর তৈরি হওয়া পর্যন্ত অপেক্ষা করে এবং ৪-৬ সেকেন্ড পর একসাথে পুরো লেখাটা স্ক্রিনে দেখায়। ছোট উত্তরের জন্য এটি ঠিক থাকলেও বড় টেকনিক্যাল ব্যাখ্যার সময় ইউজার স্ক্রিনের দিকে তাকিয়ে বিরক্ত হয়ে যায়। আর ঠিক এই সমস্যাটি সমাধান করার জন্যই আমরা পরের পর্বে (পর্ব ১৫) Vercel AI SDK দিয়ে Response Streaming তৈরি করব।

প্রায়শই জিজ্ঞাসিত প্রশ্নাবলী (FAQ)

আমার প্রথম AI Agent-এর জন্য OpenAI (gpt-4o) নাকি Claude (claude-3-5-sonnet-latest)—কোনটা দিয়ে শুরু করা ভালো?

আমাদের তৈরি করা অ্যাডাপ্টারে দুটিই একসাথে কাজ করে, তাই আপনি যেকোনো একটি বা দুটিই রাখতে পারেন। তবে কোড অ্যানালাইসিস, বড় ডকুমেন্টেশন বোঝা এবং কড়া সিস্টেম প্রম্পট মেনে চলার জন্য claude-3-5-sonnet-latest অসাধারণ কাজ করে। আর দ্রুত ক্লাসিফিকেশন বা তুলনামূলক কম খরচে টেস্ট করার জন্য gpt-4o বা gpt-4o-mini বেশ সুবিধাজনক।

অফিশিয়াল SDK (openai / @anthropic-ai/sdk) ইনস্টল না করে সাধারণ fetch() ব্যবহার করলে কি কোনো সমস্যা আছে?

সাধারণ fetch() দিয়েও API কল করা যায়, কিন্তু অফিশিয়াল SDK ব্যবহার করলে আপনি পাচ্ছেন বিল্ট-ইন TypeScript অটো-কমপ্লিট, নেটওয়ার্ক বা রেট-লিমিট (429) এরর হলে স্বয়ংক্রিয় Retry মেকানিজম এবং টাইমআউট হ্যান্ডলিং—যা ম্যানুয়ালি fetch() দিয়ে লিখতে গেলে অনেক অতিরিক্ত কোড মেইনটেইন করতে হয়।

এজেন্ট যখন বড় উত্তর দেয় তখন ৫-৭ সেকেন্ড ধরে শুধু 'Thinking...' দেখায় কেন এবং এর সমাধান কী?

বর্তমানে আমরা সাধারণ Request-Response পদ্ধতিতে API কল করছি, যেখানে মডেল শেষ শব্দটা লেখা শেষ না করা পর্যন্ত সার্ভার কোনো ডেটা ফ্রন্টএন্ডে পাঠায় না। এর সমাধান হলো Response Streaming, যা আমরা ঠিক পরের পর্বেই (পর্ব ১৫) Vercel AI SDK ব্যবহার করে আমাদের এই প্রজেক্টে যুক্ত করব।

🟢 ফ্রিল্যান্স ও কনট্রাক্ট প্রজেক্টের জন্য উন্মুক্ত

কাস্টম ওয়েব অ্যাপ বা এআই অটোমেশন বানাতে চান?

আমি স্টার্টআপ ও আধুনিক ব্যবসার জন্য স্কেলেবল ফুল-স্ট্যাক ওয়েব অ্যাপ্লিকেশন (Next.js, React, Node, PostgreSQL) এবং ইন্টেলিজেন্ট এআই এজেন্ট সলিউশন তৈরি করি। আপনার প্রজেক্টের আইডিয়া নিয়ে কথা বলা যাক!

ফুল-স্ট্যাক এমভিপি (MVP) ডেভেলপমেন্ট
অটোনোমাস এআই এজেন্ট ও এলএলএম ইন্টিগ্রেশন
পারফেক্ট এসইও ও সর্বোচ্চ পারফরম্যান্স
ক্লিন কোড ও মডার্ন আর্কিটেকচার

আর্টিকেলটি কি আপনার ভালো লেগেছে?

তন্ময়ের কাজকে সাপোর্ট করতে তালি (Clap) দিয়ে উৎসাহিত করুন!

0 views
Z

জাহিদ হাসান তন্ময়

সফটওয়্যার ডেভেলপার

MERN ফুল-স্ট্যাক ডেভেলপার এবং AI এজেন্ট ডেভেলপার, ঢাকা, বাংলাদেশ। ওয়েব ডেভেলপমেন্ট, রিঅ্যাক্ট, লারাভেল এবং আমার লার্নিং জার্নি নিয়ে লিখছি।

Related Articles

Stay Updated

Subscribe to get insights on full-stack architecture, AI agent engineering, and web development.