
আমাদের আগের পর্বে আমরা যখন স্ট্রিমিং এজেন্টে Tool Calling যোগ করে এজেন্টকে কাজ করার ক্ষমতা দিয়েছিলাম, তখন Vercel AI SDK-এর streamText এবং maxSteps: 5 ব্যবহার করে আমাদের তিনটি Zod টুল সরাসরি লাইভ চ্যাটের সাথে যুক্ত করেছিলাম। সাধারণ এক বা দুই ধাপের টুল কলের জন্য সেটি দারুণ কাজ করে। কিন্তু বাস্তবে ইউজার যখন এমন কোনো জটিল ও বহুমুখী সমস্যা নিয়ে আসেন—যেখানে ১ নম্বর ধাপের ডকুমেন্টেশন সার্চ থেকে পাওয়া নির্দিষ্ট এরর কোড বা কম্পোনেন্টের নাম বিশ্লেষণ করে ২ নম্বর ধাপে ইস্যু ট্র্যাকার চেক করতে হবে, এবং সেখানে কোনো ওপেন বাগ না থাকলেই কেবল ৩ নম্বর ধাপে নতুন টিকেট ড্রাফট করতে হবে—তখন সাধারণ লিনিয়ার লুপের বদলে আমাদের দরকার হয় আরও শক্তিশালী এবং নিয়ন্ত্রিত মাল্টি-স্টেপ অর্কেস্ট্রেশন।
Next.js প্রজেক্টে একাধিক ধাপে চিন্তা করতে পারা (Multi-step Reasoning) AI Agent বানানোর জন্য LangChain.js (@langchain/core এবং langchain)-এর DynamicStructuredTool, agent_scratchpad-সহ ChatPromptTemplate, এবং createToolCallingAgent ও AgentExecutor ব্যবহার করা হয়। এই আর্কিটেকচারটির সবচেয়ে বড় শক্তি হলো—এটি প্রতিটি ধাপের চিন্তা (Thought), অ্যাকশন (Action) এবং টুলের ফলাফল (Observation) একটি সুশৃঙ্খল স্ক্র্যাচপ্যাড মেমোরিতে জমা রাখে, কোনো ধাপে মডেল ভুল প্যারামিটার পাঠালে নিজে থেকেই তা সংশোধন (Self-Correction) করে নেয় এবং প্রতিটি মধ্যবর্তী ধাপের সম্পূর্ণ লগ (intermediateSteps) আমাদের হাতে তুলে দেয়।
চিন্তা করে দেখুন, আমাদের DevPulse এজেন্টকে কোনো ডেভেলপার যদি বলেন—'টোকেন রিফ্রেশ করার সময় আমাদের ক্লায়েন্টে বারবার HTTP 429 এরর আসছে। আগে রেট-লিমিট ডকুমেন্টেশন চেক করে দেখো বার্স্ট পলিসি কী, এরপর দেখো এই নিয়ে কোনো বাগ টিকেট খোলা আছে কি না, আর যদি আগে থেকে রিপোর্ট করা না থাকে তবেই একটা নতুন টিকেট ড্রাফট করো।'—এই কাজটি কিন্তু এজেন্ট এক লাফে করতে পারবে না। তাকে মানুষের মতোই ধাপে ধাপে আগাতে হবে: আগে ডকস পড়া -> সেখান থেকে তথ্য নিয়ে বাগ খোঁজা -> বাগটি আগে থেকেই আছে কি না তা যাচাই করা -> এবং ডুপ্লিকেট বাগ থাকলে নতুন টিকেট ড্রাফট করা থেকে বিরত থাকা।
কখন Vercel AI SDK-এর maxSteps যথেষ্ট আর কখন LangChain.js দরকার?
একজন ফুল-স্ট্যাক TypeScript ডেভেলপার হিসেবে আপনার মনে হতেই পারে—'গত পর্বে তো আমরা streamText-এ maxSteps: 5 দিয়েই একাধিক টুল কল করেছিলাম, তাহলে আবার আলাদা করে LangChain.js শেখার দরকার কী?'
উত্তরটা লুকিয়ে আছে Agent Scratchpad এবং মধ্যবর্তী ধাপগুলোর ওপর আপনার কতটুকু নিয়ন্ত্রণ দরকার তার ওপর:
- Vercel AI SDK (
streamText+maxSteps): যখন আপনার মূল লক্ষ্য দ্রুতগতির UI স্ট্রিমিং এবং সাধারণ ১-২টি টুল কল করে সাথে সাথে উত্তর দেখানো, তখন এটি সেরা। এখানে দুটি ধাপের মাঝখানে টুলের আউটপুট মডিফাই করা বা কাস্টম পার্সিং এরর রিকভারি করার সুযোগ সীমিত থাকে। - LangChain.js (
createToolCallingAgent+AgentExecutor): যখন আপনার এজেন্টকে ৩-৪টি আন্তঃনির্ভরশীল (Interdependent) ধাপে সিদ্ধান্ত নিতে হয়, প্রতিটি ধাপেরagent_scratchpadনিখুঁতভাবে কন্ট্রোল করতে হয়, এবং Zod স্কিমা ভ্যালিডেশন ফেইল করলে ক্র্যাশ না করে মডেলকে দিয়েই ভুল আর্গুমেন্ট ঠিক করিয়ে (handleParsingErrors) পরের ধাপে যেতে হয়—তখন LangChain.js অপরিহার্য।
সবচেয়ে মজার ব্যাপার হলো, আমাদের ১২ ও ১৩তম পর্বে তৈরি করা blueprint.ts এবং tools.ts ফাইল দুটি এতটাই মডুলার ছিল যে, এক লাইন বিজনেস লজিক না বদলেই আমরা সেগুলো আজ সরাসরি LangChain.js-এ ব্যবহার করব!

LangChain.js-এর ভেতরে Multi-step Reasoning কীভাবে কাজ করে?
একটি মাল্টি-স্টেপ এজেন্ট মূলত একটি ইটারেটিভ স্টেট মেশিন (State Machine) হিসেবে কাজ করে, যার কেন্দ্রবিন্দুতে থাকে agent_scratchpad। প্রতিবার এজেন্ট যখন একটি টুল কল করে, তখন সেই টুলের নাম, আর্গুমেন্ট এবং টুল থেকে পাওয়া JSON ফলাফল এই স্ক্র্যাচপ্যাডে জমা হয় এবং পরের ধাপে LLM পুরো স্ক্র্যাচপ্যাড পড়ে সিদ্ধান্ত নেয় পরের কদম কী হবে।
নিচের ফ্লোচার্টে দেখুন ইউজারের একটি ৩-অংশের জটিল প্রশ্ন কীভাবে আমাদের LangChain.js এজেন্ট ধাপে ধাপে সমাধান করে:
`mermaid flowchart TD Query([ইউজারের বহুমুখী প্রশ্ন]) --> Prompt[ChatPromptTemplate + সিস্টেম রুলস] Prompt --> Scratchpad[agent_scratchpad: আগের ধাপের Action ও Observation] Scratchpad --> Brain{LangChain Tool-Calling Agent Claude 3.5 Sonnet / GPT-4o}
Brain -->|ধাপ ১: ডকুমেন্টেশন পলিসি জানা দরকার| Tool1[Tool: searchKnowledgeBase
রেট-লিমিট ডকস রিটার্ন] Tool1 --> Obs1[Observation 1 স্ক্র্যাচপ্যাডে যুক্ত হলো] Obs1 --> Scratchpad
Brain -->|ধাপ ২: ডকসের তথ্য দিয়ে বাগ চেক| Tool2[Tool: checkIssueTracker
ওপেন ইস্যু ISS-402 পাওয়া গেল] Tool2 --> Obs2[Observation 2 স্ক্র্যাচপ্যাডে যুক্ত হলো] Obs2 --> Scratchpad
Brain -->|ধাপ ৩: যাচাই ও চূড়ান্ত সিদ্ধান্ত| Final[ডুপ্লিকেট টিকেট ড্রাফট স্কিপ করে চূড়ান্ত উত্তর + intermediateSteps রিটার্ন]
`
আর্কিটেকচার ফ্লো সামারি (Architecture Flow Summary): ইউজারের প্রশ্নটি সিস্টেম প্রম্পট ও একটি খালি agent_scratchpad-সহ ChatPromptTemplate-এ প্রবেশ করে। ১ নম্বর ধাপে এজেন্ট searchKnowledgeBase কল করে এবং তার ফলাফল (Observation 1) স্ক্র্যাচপ্যাডে যুক্ত হয়; ২ নম্বর ধাপে সেই তথ্যের সূত্র ধরে এজেন্ট checkIssueTracker কল করে দেখে ISS-402 নামের একটি বাগ আগে থেকেই খোলা আছে (Observation 2); ফলে ৩ নম্বর ধাপে সে অযথা নতুন টিকেট ড্রাফট না করে দুটি ধাপের তথ্য মিলিয়ে চূড়ান্ত উত্তর ও প্রতিটি ধাপের ট্রেস লগ রিটার্ন করে।
ধাপ ১: মডুলার LangChain.js প্যাকেজ ইনস্টল করা
আধুনিক LangChain.js (v0.3+) এখন ছোট ছোট আলাদা প্যাকেজে বিভক্ত, যাতে আপনার Next.js সার্ভারলেস বান্ডেল অযথা ভারী না হয়। টার্মিনালে নিচের কমান্ডটি চালিয়ে প্রয়োজনীয় প্যাকেজগুলো ইনস্টল করে নিন:
bash npm install langchain @langchain/core @langchain/openai @langchain/anthropic zod server-only
আমাদের .env.local ফাইলে আগের পর্বের OPENAI_API_KEY এবং ANTHROPIC_API_KEY দেওয়াই আছে, তাই নতুন কোনো কী যোগ করার প্রয়োজন নেই।
ধাপ ২: আমাদের Zod স্কিমাগুলোকে LangChain Structured Tool-এ রূপান্তর করা
চলুন এবার তৈরি করি src/lib/agent/langchain-tools.ts। এখানে আমরা @langchain/core/tools থেকে tool ফাংশনটি ব্যবহার করে আমাদের blueprint.ts-এর Zod স্কিমা এবং tools.ts-এর executeAgentTool ফাংশনকে যুক্ত করব।
এখানে একটি অত্যন্ত গুরুত্বপূর্ণ নিয়ম মনে রাখবেন: LangChain.js-এর টুল ফাংশন থেকে সবসময় Serialized String (JSON.stringify) রিটার্ন করতে হয়, যাতে তা সরাসরি ToolMessage হিসেবে agent_scratchpad-এ বসতে পারে:
`typescript // src/lib/agent/langchain-tools.ts import 'server-only'; import { tool } from '@langchain/core/tools'; import { searchDocsSchema, checkIssuesSchema, draftTicketSchema, } from './blueprint'; import { executeAgentTool } from './tools';
export const searchDocsLangChainTool = tool( async (input) => { const result = await executeAgentTool('searchKnowledgeBase', input); return JSON.stringify(result); }, { name: 'searchKnowledgeBase', description: 'Step 1 priority tool: Search internal technical documentation for rate limits, auth rules, and architecture specifications.', schema: searchDocsSchema, } );
export const checkIssuesLangChainTool = tool( async (input) => { const result = await executeAgentTool('checkIssueTracker', input); return JSON.stringify(result); }, { name: 'checkIssueTracker', description: 'Step 2 verification tool: Query the repository issue tracker using exact keywords or component names discovered in documentation or user logs.', schema: checkIssuesSchema, } );
export const draftTicketLangChainTool = tool( async (input) => { const result = await executeAgentTool('draftIssueTicket', input); return JSON.stringify(result); }, { name: 'draftIssueTicket', description: 'Step 3 conditional action tool: Draft a new bug ticket ONLY AFTER verifying via checkIssueTracker that no open issue already exists for the bug.', schema: draftTicketSchema, } );
export const devPulseLangChainTools = [ searchDocsLangChainTool, checkIssuesLangChainTool, draftTicketLangChainTool, ]; `
উপরের কোডে প্রতিটি টুলের description খেয়াল করুন। আমরা ইচ্ছাকৃতভাবেই সেখানে কাজের ক্রম ('Step 1 priority tool', 'Step 2 verification tool', 'Step 3 conditional action tool — ONLY AFTER verifying via checkIssueTracker') লিখে দিয়েছি। এর ফলে এজেন্ট কখনোই ইস্যু ট্র্যাকার চেক করার আগে অন্ধভাবে নতুন বাগ টিকেট ড্রাফট করে ফেলবে না।
ধাপ ৩: Multi-Step Orchestrator তৈরি করা (multi-step-agent.ts)
এবার আমরা লিখব আমাদের মূল মাল্টি-স্টেপ ইঞ্জিন src/lib/agent/multi-step-agent.ts। এখানে আমরা ChatPromptTemplate, MessagesPlaceholder('agent_scratchpad'), createToolCallingAgent এবং AgentExecutor একসাথে যুক্ত করব:
`typescript // src/lib/agent/multi-step-agent.ts import 'server-only'; import { ChatOpenAI } from '@langchain/openai'; import { ChatAnthropic } from '@langchain/anthropic'; import { ChatPromptTemplate, MessagesPlaceholder, } from '@langchain/core/prompts'; import { AIMessage, HumanMessage, type BaseMessage, } from '@langchain/core/messages'; import { createToolCallingAgent, AgentExecutor } from 'langchain/agents'; import { type AgentConfig, type AgentStepTrace, type ToolName, } from './blueprint'; import { sanitizeConversationHistory, type ChatMessage, } from './llm-client'; import { devPulseLangChainTools } from './langchain-tools';
function createLangChainModel(config: AgentConfig) { if (config.modelId === 'gpt-4o') { return new ChatOpenAI({ model: 'gpt-4o', temperature: config.temperature, apiKey: process.env.OPENAI_API_KEY, }); }
return new ChatAnthropic({ model: 'claude-3-5-sonnet-latest', temperature: config.temperature, apiKey: process.env.ANTHROPIC_API_KEY, }); }
function convertToLangChainMessages(messages: ChatMessage[]): BaseMessage[] { const sanitized = sanitizeConversationHistory(messages, 10); return sanitized.map((m) => m.role === 'user' ? new HumanMessage(m.content) : new AIMessage(m.content) ); }
export interface MultiStepExecutionResult { finalAnswer: string; steps: AgentStepTrace[]; totalDurationMs: number; }
export async function runMultiStepDevPulseAgent( history: ChatMessage[], userInput: string, config: AgentConfig ): Promise { const startTime = Date.now(); const llm = createLangChainModel(config);
const prompt = ChatPromptTemplate.fromMessages([ [ 'system', [ config.systemPrompt, 'You are operating in Multi-Step Reasoning Mode.', 'Follow this strict workflow when diagnosing issues:', '1. First, search documentation (searchKnowledgeBase) to verify expected system behavior.', '2. Second, check the issue tracker (checkIssueTracker) using the exact error code or component.', '3. Third, only call draftIssueTicket if the user asks to log a bug AND no matching open issue exists.', '4. Synthesize findings clearly, citing both the doc path and issue ID.', ].join('\n'), ], new MessagesPlaceholder('chat_history'), ['human', '{input}'], new MessagesPlaceholder('agent_scratchpad'), ]);
const agent = createToolCallingAgent({ llm, tools: devPulseLangChainTools, prompt, });
const executor = new AgentExecutor({ agent, tools: devPulseLangChainTools, maxIterations: config.maxSteps, returnIntermediateSteps: true, handleParsingErrors: 'Tool argument validation failed. Check the required Zod schema fields and retry with valid parameters.', });
const chatHistory = convertToLangChainMessages(history); const response = await executor.invoke({ input: userInput, chat_history: chatHistory, });
const rawSteps = Array.isArray(response.intermediateSteps) ? response.intermediateSteps : [];
const steps: AgentStepTrace[] = rawSteps.map((step, idx) => { const toolName = step.action?.tool as ToolName | undefined; const toolInput = step.action?.toolInput; let parsedObservation: unknown = step.observation;
if (typeof step.observation === 'string') {
try {
parsedObservation = JSON.parse(step.observation);
} catch {
parsedObservation = step.observation;
}
}
const rawLog =
typeof step.action?.log === 'string' && step.action.log.trim().length > 0
? step.action.log.trim()
: `Executed ${toolName ?? 'tool'} in multi-step chain.`;
return {
stepIndex: idx + 1,
thoughtSummary: rawLog.slice(0, 220),
selectedTool: toolName,
toolInput,
toolOutput: parsedObservation,
durationMs: Math.round((Date.now() - startTime) / Math.max(rawSteps.length, 1)),
};
});
return { finalAnswer: typeof response.output === 'string' ? response.output : JSON.stringify(response.output), steps, totalDurationMs: Date.now() - startTime, }; } `
এই কোডটিতে তিনটি অসাধারণ ইঞ্জিনিয়ারিং মেকানিজম কাজ করছে:
১. new MessagesPlaceholder('agent_scratchpad'): প্রম্পট টেমপ্লেটের একদম নিচে থাকা এই প্লেসহোল্ডারটিতেই LangChain তার প্রতিটি ধাপের টুল কল এবং টুলের রিটার্ন করা JSON ফলাফল জমা রাখে। এর কারণেই ২ নম্বর ধাপে এজেন্ট পরিষ্কার দেখতে পায় ১ নম্বর ধাপে সে কী তথ্য পেয়েছিল।
২. handleParsingErrors দিয়ে সেলফ-কারেকশন: ধরুন ২ নম্বর ধাপে মডেল ভুল করে Zod স্কিমার বাইরে কোনো প্যারামিটার পাঠিয়ে দিল। সাধারণ কোডে তখন পুরো রিকোয়েস্ট ক্র্যাশ করে। কিন্তু এখানে AgentExecutor সেই এররটি ধরে আমাদের দেওয়া নির্দেশনাসহ আবার মডেলের স্ক্র্যাচপ্যাডে পাঠিয়ে দেয়, যাতে মডেল পরের ধাপে নিজেই সঠিক প্যারামিটার দিয়ে আবার টুলটি কল করতে পারে!
৩. returnIntermediateSteps: true: এটি চালু থাকার কারণে এজেন্ট ভেতরে ভেতরে কয়টি ধাপে কোন টুল কল করল, কী চিন্তা করল (step.action.log) এবং কী আউটপুট পেল (step.observation)—তার পুরো অ্যারে আমরা আমাদের চেনা AgentStepTrace ফরম্যাটে কনভার্ট করে নিতে পারছি।
ধাপ ৪: Server Action ও Multi-Step Reasoning UI তৈরি করা
এবার চলুন src/app/actions/multi-step.ts ফাইলে একটি Server Action তৈরি করি এবং ফ্রন্টএন্ডে প্রতিটি ধাপের চেইন (Chain of Thought) দেখানোর জন্য একটি কনসোল বানাই:
`typescript // src/app/actions/multi-step.ts 'use server';
import { z } from 'zod'; import { defaultDevPulseConfig, type AgentStepTrace, } from '@/lib/agent/blueprint'; import { runMultiStepDevPulseAgent } from '@/lib/agent/multi-step-agent';
const multiStepInputSchema = z.object({ prompt: z.string().trim().min(5).max(3000), modelId: z .enum(['gpt-4o', 'claude-3-5-sonnet-latest']) .default('claude-3-5-sonnet-latest'), });
export interface MultiStepActionResponse { success: boolean; finalAnswer: string; steps: AgentStepTrace[]; totalDurationMs: number; errorMessage?: string; }
export async function executeMultiStepTriage( rawInput: unknown ): Promise { const parsed = multiStepInputSchema.safeParse(rawInput); if (!parsed.success) { return { success: false, finalAnswer: '', steps: [], totalDurationMs: 0, errorMessage: parsed.error.issues.map((i) => i.message).join('; '), }; }
try { const result = await runMultiStepDevPulseAgent( [], parsed.data.prompt, { ...defaultDevPulseConfig, modelId: parsed.data.modelId, } );
return {
success: true,
finalAnswer: result.finalAnswer,
steps: result.steps,
totalDurationMs: result.totalDurationMs,
};
} catch (err) { const message = err instanceof Error ? err.message : 'Multi-step execution failed'; return { success: false, finalAnswer: '', steps: [], totalDurationMs: 0, errorMessage: message, }; } } `
এবার src/components/MultiStepTriageConsole.tsx কম্পোনেন্টটি তৈরি করুন, যেখানে ইউজার একটি জটিল প্রম্পট দিয়ে এজেন্টের প্রতিটি ধাপের চিন্তা ও ফলাফল আলাদা আলাদা কার্ডে দেখতে পাবেন:
`tsx // src/components/MultiStepTriageConsole.tsx 'use client';
import { useState, useTransition } from 'react'; import { executeMultiStepTriage, type MultiStepActionResponse, } from '@/app/actions/multi-step';
export default function MultiStepTriageConsole() { const [prompt, setPrompt] = useState( 'Our client gets HTTP 429 during token refresh. Check the rate-limit docs first, then verify if an open issue exists for 429 bursts, and only draft a ticket if it is undocumented.' ); const [result, setResult] = useState<MultiStepActionResponse | null>(null); const [isPending, startTransition] = useTransition();
function handleRun(e: React.FormEvent) { e.preventDefault(); if (!prompt.trim() || isPending) return;
startTransition(async () => {
const res = await executeMultiStepTriage({
prompt,
modelId: 'claude-3-5-sonnet-latest',
});
setResult(res);
});
}
return ( DevPulse Multi-Step Reasoning Engine (LangChain.js) Sequential Scratchpad Chaining | Self-Correcting Zod Parser
<form onSubmit={handleRun} className='space-y-3'>
<textarea
value={prompt}
onChange={(e) => setPrompt(e.target.value)}
rows={3}
className='w-full p-3 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 ? 'Executing Multi-Step Chain...' : 'Run Multi-Step Agent'}
</button>
</form>
{result && result.success && (
<div className='space-y-4 pt-3 border-t border-slate-800'>
<div className='space-y-2'>
<div className='text-xs uppercase tracking-wider text-slate-400 font-semibold'>
Chained Reasoning Steps ({result.steps.length} iterations • {result.totalDurationMs}ms)
</div>
{result.steps.map((step) => (
<div
key={step.stepIndex}
className='p-3 rounded-lg bg-slate-950 border border-purple-900/60 text-xs space-y-1.5'
>
<div className='flex justify-between font-mono text-purple-400'>
<span>
Step {step.stepIndex} → {step.selectedTool}
</span>
<span>Input: {JSON.stringify(step.toolInput)}</span>
</div>
<div className='text-slate-300'>{step.thoughtSummary}</div>
<div className='text-[11px] font-mono text-emerald-400 bg-slate-900/90 p-2 rounded truncate'>
Observation: {JSON.stringify(step.toolOutput)}
</div>
</div>
))}
</div>
<div className='p-4 rounded-lg bg-slate-950 border border-cyan-800/60 text-sm whitespace-pre-wrap leading-relaxed'>
{result.finalAnswer}
</div>
</div>
)}
</div>
); } `

আমার নিজের একটি ডিবাগিং অভিজ্ঞতা: যখন এজেন্ট পরপর ৫ বার একই টুল কল করে থেমে যাচ্ছিল!
আমি যখন প্রথমবার Next.js ব্যাকএন্ডে LangChain.js-এর AgentExecutor দিয়ে ডকুমেন্টেশন সার্চ এবং ইস্যু ট্র্যাকার চেক করার মাল্টি-স্টেপ চেইন তৈরি করেছিলাম, তখন খুব অদ্ভুত একটা ইনফিনিট লুপের মতো সমস্যায় পড়েছিলাম—এজেন্টটি ১ নম্বর ধাপে searchKnowledgeBase কল করার পর ২ নম্বর ধাপে checkIssueTracker-এ না গিয়ে আবারও হুবহু একই কুয়েরি দিয়ে searchKnowledgeBase কল করছিল এবং এভাবে ৫ বার (maxIterations: 5) একই টুল কল করে শেষমেশ হাল ছেড়ে দিচ্ছিল!
শুরুতে আমি ভেবেছিলাম সমস্যাটা বোধহয় সিস্টেম প্রম্পটে। আমি প্রম্পটে বড় হাতের অক্ষরে 'NEVER CALL THE SAME TOOL TWICE' লিখে দিলাম, কিন্তু তাতেও কোনো লাভ হলো না। অবশেষে আমি যখন AgentExecutor-এর ভেতরের agent_scratchpad মেসেজগুলো কনসোলে প্রিন্ট করলাম, তখন আসল ভুলটা চোখের সামনে ধরা পড়ল: আমার একটি টুল ফাংশনের ভেতরে আমি JSON.stringify(result) না করে সরাসরি জাভাস্ক্রিপ্ট অবজেক্ট রিটার্ন করে দিয়েছিলাম এবং আরেকটি কন্ডিশনে কোনো return স্টেটমেন্ট না থাকায় সেখান থেকে undefined যাচ্ছিল!
LangChain যখন টুলের আউটপুট নিয়ে ToolMessage বানিয়ে agent_scratchpad-এ বসায়, তখন সেখানে যদি প্রপার স্ট্রিংয়ের বদলে undefined বা '[object Object]' যায়, তখন পরের ধাপে LLM স্ক্র্যাচপ্যাড পড়ে দেখে টুল থেকে কোনো পাঠযোগ্য ডেটাই আসেনি! ফলে সে মনে করে আগের কলটি বোধহয় মিস হয়েছে এবং একই টুল আবার কল করে। যেই মুহূর্তে আমি প্রতিটি টুলের রিটার্নে JSON.stringify(result) নিশ্চিত করলাম এবং প্রম্পট টেমপ্লেটের একদম শেষে new MessagesPlaceholder('agent_scratchpad') ঠিকমতো বসালাম, সাথে সাথেই এজেন্ট ১ নম্বর ধাপে ডকস পড়ে ২ নম্বর ধাপে ইস্যু চেক করে নিখুঁতভাবে কাজ শেষ করল।
LangChain.js দিয়ে Multi-Step Agent বানানোর সময় যে ৪টি ভুল এড়িয়ে চলবেন
পরের পর্বে (পর্ব ১৯) নিজের কাস্টম টুল তৈরি নিয়ে কাজ করার আগে নিশ্চিত হয়ে নিন আপনার কোডে যেন এই ৪টি ভুল না থাকে:
১. প্রম্পট টেমপ্লেটে MessagesPlaceholder('agent_scratchpad') দিতে ভুলে যাওয়া
createToolCallingAgent ব্যবহার করার সময় প্রম্পটের একদম নিচে new MessagesPlaceholder('agent_scratchpad') থাকা বাধ্যতামূলক। এটি না থাকলে বা বানান ভুল হলে এজেন্ট আগের ধাপে কোন টুল কল করেছিল এবং কী উত্তর পেয়েছিল তার কিছুই মনে রাখতে পারবে না।
২. পুরনো ও ডেপ্রিকেটেড initializeAgentExecutorWithOptions ব্যবহার করা
ইউটিউব বা পুরনো অনেক ব্লগে এখনো initializeAgentExecutorWithOptions দেখানো হয়, যা টেক্সট পার্সিংয়ের (Action: ...) ওপর নির্ভর করত এবং প্রায়ই ভেঙে যেত। আধুনিক LangChain.js-এ সবসময় createToolCallingAgent (অথবা LangGraph-এর createReactAgent) ব্যবহার করবেন, যা সরাসরি OpenAI ও Claude-এর নেটিভ টুল-কলিং স্কিমা ব্যবহার করে।
৩. handleParsingErrors চালু না রাখা
মাল্টি-স্টেপ এজেন্ট যখন ৩-৪ ধাপে কাজ করে, তখন মাঝখানের কোনো একটি ধাপে মডেল ভুলবশত Zod স্কিমার বাইরে কোনো মান পাঠিয়ে দিতেই পারে। আপনি যদি AgentExecutor-এ handleParsingErrors স্ট্রিং না দেন, তবে সামান্য ভ্যালিডেশন এররেই পুরো Server Action ক্র্যাশ করবে। এটি চালু রাখলে এজেন্ট নিজেই এরর মেসেজ পড়ে পরের ধাপে সঠিক প্যারামিটার পাঠিয়ে দেয়।
৪. ক্লায়েন্ট কম্পোনেন্টে ('use client') ভুল করে LangChain ইমপোর্ট করা
@langchain/core বা langchain/agents কখনোই ফ্রন্টএন্ড ক্লায়েন্ট ফাইলে ইমপোর্ট করবেন না। এতে ব্রাউজার বান্ডেল সাইজ বিশাল হয়ে যাবে এবং Node.js ডিপেন্ডেন্সি এরর আসবে। সবসময় import 'server-only' দেওয়া সার্ভার ফাইলে LangChain রাখবেন এবং Server Action বা API Route দিয়ে কল করবেন।
প্রায়শই জিজ্ঞাসিত প্রশ্নাবলী (FAQ)
মাল্টি-স্টেপ এজেন্টের জন্য কি createToolCallingAgent + AgentExecutor ব্যবহার করব, নাকি LangGraph.js?
একক এজেন্টের সিকোয়েনশিয়াল মাল্টি-স্টেপ রিজনিং (যেখানে একটি এজেন্টই ৩-৫ ধাপে টুল কল করে এবং স্ক্র্যাচপ্যাড দেখে সিদ্ধান্ত নেয়) বানানোর জন্য createToolCallingAgent এবং AgentExecutor সবচেয়ে সহজ ও কার্যকর। তবে যখন আপনার প্রজেক্টে একাধিক আলাদা এজেন্ট (Multi-Agent System) বা জটিল সাইক্লিক গ্রাফ ও স্টেট চেকপয়েন্ট দরকার হবে (যা আমরা এই সিরিজেরই পরের দিকে ডিজাইন করব), তখন @langchain/langgraph ব্যবহার করা উত্তম।
Vercel AI SDK এবং LangChain.js—দুই জায়গাতেই কি একই Zod স্কিমা ব্যবহার করা যায়?
হ্যাঁ, শতভাগ করা যায়! আমাদের src/lib/agent/langchain-tools.ts ফাইলে যেমনটি দেখেছেন, আমরা ১২তম পর্বে তৈরি করা blueprint.ts-এর হুবহু একই Zod স্কিমাগুলোই এখানে ব্যবহার করেছি। মূল ভ্যালিডেশন স্কিমা ফ্রেমওয়ার্ক থেকে আলাদা রাখার কারণে আপনি যেকোনো সময় নিজের ইচ্ছেমতো লাইব্রেরি ব্যবহার করতে পারবেন।
মাল্টি-স্টেপ এজেন্টে agent_scratchpad-এর কারণে যেন অতিরিক্ত টোকেন খরচ না হয় সেজন্য কী করব?
মনে রাখবেন, প্রতিটি ধাপের টুল আউটপুট (Observation) স্ক্র্যাচপ্যাডে জমা হয়ে পরের ধাপে আবার LLM-এর কাছে যায়। তাই টোকেন খরচ নিয়ন্ত্রণে রাখতে প্রতিটি টুল থেকে শুধুমাত্র প্রয়োজনীয় ফিল্ডগুলো ছোট JSON আকারে (৩০০-৫০০ টোকেনের মধ্যে) রিটার্ন করবেন এবং AgentExecutor-এ সবসময় maxIterations: 5 লিমিট দিয়ে রাখবেন।
কাস্টম ওয়েব অ্যাপ বা এআই অটোমেশন বানাতে চান?
আমি স্টার্টআপ ও আধুনিক ব্যবসার জন্য স্কেলেবল ফুল-স্ট্যাক ওয়েব অ্যাপ্লিকেশন (Next.js, React, Node, PostgreSQL) এবং ইন্টেলিজেন্ট এআই এজেন্ট সলিউশন তৈরি করি। আপনার প্রজেক্টের আইডিয়া নিয়ে কথা বলা যাক!
আর্টিকেলটি কি আপনার ভালো লেগেছে?
তন্ময়ের কাজকে সাপোর্ট করতে তালি (Clap) দিয়ে উৎসাহিত করুন!
জাহিদ হাসান তন্ময়
সফটওয়্যার ডেভেলপার
MERN ফুল-স্ট্যাক ডেভেলপার এবং AI এজেন্ট ডেভেলপার, ঢাকা, বাংলাদেশ। ওয়েব ডেভেলপমেন্ট, রিঅ্যাক্ট, লারাভেল এবং আমার লার্নিং জার্নি নিয়ে লিখছি।