১. ভূমিকা
এই কোডল্যাবটি গুগল ক্লাউডের ভার্টেক্স এআই -তে হোস্ট করা জেমিনি লার্জ ল্যাঙ্গুয়েজ মডেল (এলএলএম)-এর উপর আলোকপাত করে। ভার্টেক্স এআই হলো এমন একটি প্ল্যাটফর্ম যা গুগল ক্লাউডের সমস্ত মেশিন লার্নিং পণ্য, পরিষেবা এবং মডেলগুলিকে অন্তর্ভুক্ত করে।
আপনি ল্যাংচেইন৪জে (LangChain4j) ফ্রেমওয়ার্ক ব্যবহার করে জাভার মাধ্যমে জেমিনি এপিআই (Gemini API) -এর সাথে ইন্টারঅ্যাক্ট করবেন। প্রশ্নোত্তর, ধারণা তৈরি, এনটিটি ও স্ট্রাকচার্ড কন্টেন্ট এক্সট্র্যাকশন, রিট্রিভাল অগমেন্টেড জেনারেশন এবং ফাংশন কলিং-এর জন্য এলএলএম (LLM)-এর সুবিধা নিতে আপনি বাস্তব উদাহরণের মধ্য দিয়ে যাবেন।
জেনারেটিভ এআই কী?
জেনারেটিভ এআই বলতে কৃত্রিম বুদ্ধিমত্তা ব্যবহার করে নতুন বিষয়বস্তু, যেমন—পাঠ্য, ছবি, সঙ্গীত, অডিও এবং ভিডিও তৈরি করাকে বোঝায়।
জেনারেটিভ এআই বৃহৎ ভাষা মডেল (এলএলএম) দ্বারা চালিত হয়, যা একই সাথে একাধিক কাজ করতে পারে এবং সারসংক্ষেপ তৈরি, প্রশ্নোত্তর, শ্রেণিবিন্যাস ও আরও অনেক ধরনের গতানুগতিক ধারার বাইরের কাজ সম্পাদন করতে পারে। ন্যূনতম প্রশিক্ষণের মাধ্যমে, খুব অল্প উদাহরণ ডেটা ব্যবহার করে মৌলিক মডেলগুলোকে নির্দিষ্ট ব্যবহারের জন্য অভিযোজিত করা যায়।
জেনারেটিভ এআই কীভাবে কাজ করে?
জেনারেটিভ এআই একটি মেশিন লার্নিং (এমএল) মডেল ব্যবহার করে মানুষের তৈরি কন্টেন্টের ডেটাসেটের মধ্যেকার প্যাটার্ন ও সম্পর্কগুলো শেখে। এরপর এটি সেই শেখা প্যাটার্নগুলো ব্যবহার করে নতুন কন্টেন্ট তৈরি করে।
একটি জেনারেটিভ এআই মডেলকে প্রশিক্ষণ দেওয়ার সবচেয়ে প্রচলিত উপায় হলো সুপারভাইজড লার্নিং ব্যবহার করা। এক্ষেত্রে মডেলটিকে মানুষের তৈরি কিছু কন্টেন্ট এবং সেগুলোর সংশ্লিষ্ট লেবেল দেওয়া হয়। এরপর এটি সেই কন্টেন্টের অনুরূপ কন্টেন্ট তৈরি করতে শেখে।
জেনারেটিভ এআই-এর সাধারণ প্রয়োগগুলো কী কী?
জেনারেটিভ এআই নিম্নলিখিত ক্ষেত্রে ব্যবহার করা যেতে পারে:
- উন্নত চ্যাট এবং সার্চ অভিজ্ঞতার মাধ্যমে গ্রাহক সম্পর্ক আরও উন্নত করুন।
- কথোপকথনমূলক ইন্টারফেস এবং সারসংক্ষেপের মাধ্যমে বিপুল পরিমাণ অসংগঠিত ডেটা অন্বেষণ করুন।
- প্রস্তাবের অনুরোধের উত্তর দেওয়া, বিভিন্ন ভাষায় বিপণন সামগ্রী স্থানীয়করণ করা, গ্রাহক চুক্তিপত্র নিয়ম মেনে চলছে কিনা তা পরীক্ষা করা এবং আরও অনেক পুনরাবৃত্তিমূলক কাজে সহায়তা করা।
গুগল ক্লাউডের কী কী জেনারেটিভ এআই পরিষেবা রয়েছে?
ভার্টেক্স এআই-এর সাহায্যে, আপনি খুব কম বা কোনো এমএল দক্ষতা ছাড়াই আপনার অ্যাপ্লিকেশনগুলিতে ফাউন্ডেশন মডেলগুলির সাথে ইন্টারঅ্যাক্ট করতে, কাস্টমাইজ করতে এবং এম্বেড করতে পারেন। আপনি মডেল গার্ডেন -এ ফাউন্ডেশন মডেলগুলি অ্যাক্সেস করতে পারেন, ভার্টেক্স এআই স্টুডিও- এর একটি সহজ UI-এর মাধ্যমে মডেলগুলি টিউন করতে পারেন, অথবা একটি ডেটা সায়েন্স নোটবুকে মডেলগুলি ব্যবহার করতে পারেন।
ভার্টেক্স এআই সার্চ অ্যান্ড কনভারসেশন ডেভেলপারদের জন্য জেনারেটিভ এআই চালিত সার্চ ইঞ্জিন এবং চ্যাটবট তৈরির দ্রুততম উপায় প্রদান করে।
জেমিনি দ্বারা চালিত, জেমিনি ফর গুগল ক্লাউড হলো একটি এআই-চালিত সহযোগী যা গুগল ক্লাউড এবং আইডিই জুড়ে উপলব্ধ এবং আপনাকে আরও বেশি কাজ দ্রুত করতে সাহায্য করে। জেমিনি কোড অ্যাসিস্ট কোড কমপ্লিশন, কোড জেনারেশন, কোড এক্সপ্লানেশন প্রদান করে এবং প্রযুক্তিগত প্রশ্ন জিজ্ঞাসা করার জন্য এর সাথে চ্যাট করার সুযোগ দেয়।
মিথুন রাশি কী?
জেমিনি হলো গুগল ডিপমাইন্ড দ্বারা তৈরি জেনারেটিভ এআই মডেলের একটি পরিবার, যা মাল্টিমোডাল ব্যবহারের জন্য ডিজাইন করা হয়েছে। মাল্টিমোডাল বলতে বোঝায়, এটি টেক্সট, কোড, ছবি এবং অডিওর মতো বিভিন্ন ধরণের কন্টেন্ট প্রসেস ও জেনারেট করতে পারে।

মিথুন রাশি বিভিন্ন প্রকার ও আকারে পাওয়া যায়:
- জেমিনি ২.০ ফ্ল্যাশ : আমাদের সর্বাধুনিক পরবর্তী প্রজন্মের বৈশিষ্ট্য এবং উন্নত কার্যক্ষমতা।
- জেমিনি ২.০ ফ্ল্যাশ-লাইট : জেমিনি ২.০ ফ্ল্যাশের একটি মডেল যা ব্যয় সাশ্রয় এবং স্বল্প ল্যাটেন্সির জন্য বিশেষভাবে তৈরি।
- জেমিনি ২.৫ প্রো : আমাদের এখন পর্যন্ত সবচেয়ে উন্নত যুক্তি মডেল।
- জেমিনি ২.৫ ফ্ল্যাশ : একটি চিন্তাশীল মডেল যা বহুমুখী সক্ষমতা প্রদান করে। এটি দাম ও পারফরম্যান্সের মধ্যে ভারসাম্য বজায় রেখে ডিজাইন করা হয়েছে।
প্রধান বৈশিষ্ট্য:
- বহুমুখীতা : মিথুন রাশির একাধিক তথ্য বিন্যাস বোঝা এবং পরিচালনা করার ক্ষমতা প্রচলিত শুধুমাত্র-পাঠ্য ভাষার মডেলগুলোর চেয়ে একটি উল্লেখযোগ্য অগ্রগতি।
- পারফরম্যান্স : জেমিনি ২.৫ প্রো অনেক বেঞ্চমার্কে বর্তমান সেরা প্রযুক্তিকেও ছাড়িয়ে গেছে এবং এটিই প্রথম মডেল যা কঠিন MMLU (ম্যাসিভ মাল্টিটাস্ক ল্যাঙ্গুয়েজ আন্ডারস্ট্যান্ডিং) বেঞ্চমার্কে মানব বিশেষজ্ঞদেরও অতিক্রম করেছে।
- নমনীয়তা : জেমিনির বিভিন্ন আকার এটিকে বৃহৎ পরিসরের গবেষণা থেকে শুরু করে মোবাইল ডিভাইসে স্থাপন পর্যন্ত নানা ধরনের ব্যবহারের জন্য উপযোগী করে তোলে।
জাভা থেকে ভার্টেক্স এআই-তে জেমিনির সাথে কীভাবে যোগাযোগ করা যায়?
আপনার কাছে দুটি বিকল্প আছে:
- জেমিনি লাইব্রেরির জন্য অফিসিয়াল ভার্টেক্স এআই জাভা এপিআই ।
- LangChain4j ফ্রেমওয়ার্ক।
এই কোডল্যাবে আপনারা LangChain4j ফ্রেমওয়ার্কটি ব্যবহার করবেন।
LangChain4j ফ্রেমওয়ার্কটি কী?
LangChain4j ফ্রেমওয়ার্কটি আপনার জাভা অ্যাপ্লিকেশনগুলিতে LLM সংহত করার জন্য একটি ওপেন সোর্স লাইব্রেরি। এটি স্বয়ং LLM-এর মতো বিভিন্ন উপাদানকে সমন্বিত করে, এবং এর পাশাপাশি ভেক্টর ডেটাবেস (অর্থগত অনুসন্ধানের জন্য), ডকুমেন্ট লোডার ও স্প্লিটার (ডকুমেন্ট বিশ্লেষণ ও তা থেকে শেখার জন্য), আউটপুট পার্সার এবং আরও অনেক টুলের মতো অন্যান্য সরঞ্জামকেও অন্তর্ভুক্ত করে।
প্রকল্পটি ল্যাংচেইন পাইথন প্রকল্প দ্বারা অনুপ্রাণিত হলেও এর লক্ষ্য ছিল জাভা ডেভেলপারদের পরিষেবা দেওয়া।

আপনি যা শিখবেন
- Gemini এবং LangChain4j ব্যবহার করার জন্য কীভাবে একটি জাভা প্রজেক্ট সেটআপ করবেন
- প্রোগ্রামের মাধ্যমে কীভাবে আপনার প্রথম প্রম্পট জেমিনিতে পাঠাবেন
- জেমিনি থেকে প্রতিক্রিয়াগুলি কীভাবে স্ট্রিম করবেন
- একজন ব্যবহারকারী এবং জেমিনির মধ্যে কীভাবে কথোপকথন তৈরি করবেন
- টেক্সট এবং ছবি উভয়ই পাঠিয়ে মাল্টিমোডাল প্রেক্ষাপটে কীভাবে জেমিনি ব্যবহার করবেন
- অসংগঠিত বিষয়বস্তু থেকে কীভাবে দরকারী সংগঠিত তথ্য বের করা যায়
- প্রম্পট টেমপ্লেটগুলি কীভাবে পরিচালনা করবেন
- সেন্টিমেন্ট অ্যানালাইসিসের মতো টেক্সট ক্লাসিফিকেশন কীভাবে করতে হয়
- কীভাবে আপনার নিজের নথিগুলির সাথে চ্যাট করবেন (রিট্রিভাল অগমেন্টেড জেনারেশন)
- ফাংশন কলিংয়ের মাধ্যমে আপনার চ্যাটবটকে কীভাবে আরও উন্নত করবেন
- Ollama এবং TestContainers-এর সাথে স্থানীয়ভাবে Gemma কীভাবে ব্যবহার করবেন
আপনার যা যা লাগবে
- জাভা প্রোগ্রামিং ভাষার জ্ঞান
- একটি গুগল ক্লাউড প্রকল্প
- ক্রোম বা ফায়ারফক্সের মতো একটি ব্রাউজার
২. সেটআপ এবং প্রয়োজনীয়তা
স্ব-গতিতে পরিবেশ সেটআপ
- Google Cloud Console- এ সাইন-ইন করুন এবং একটি নতুন প্রজেক্ট তৈরি করুন অথবা বিদ্যমান কোনো প্রজেক্ট পুনরায় ব্যবহার করুন। যদি আপনার আগে থেকে Gmail বা Google Workspace অ্যাকাউন্ট না থাকে, তবে আপনাকে অবশ্যই একটি তৈরি করতে হবে।



- প্রজেক্টের নামটি হলো এই প্রজেক্টের অংশগ্রহণকারীদের প্রদর্শিত নাম। এটি একটি ক্যারেক্টার স্ট্রিং যা গুগল এপিআই ব্যবহার করে না। আপনি যেকোনো সময় এটি আপডেট করতে পারেন।
- প্রজেক্ট আইডি সমস্ত গুগল ক্লাউড প্রজেক্ট জুড়ে অনন্য এবং অপরিবর্তনীয় (একবার সেট করার পর এটি পরিবর্তন করা যায় না)। ক্লাউড কনসোল স্বয়ংক্রিয়ভাবে একটি অনন্য স্ট্রিং তৈরি করে; সাধারণত এটি কী তা নিয়ে আপনার মাথা ঘামানোর দরকার নেই। বেশিরভাগ কোডল্যাবে, আপনাকে আপনার প্রজেক্ট আইডি উল্লেখ করতে হবে (যা সাধারণত
PROJECT_IDহিসাবে চিহ্নিত করা হয়)। তৈরি করা আইডিটি আপনার পছন্দ না হলে, আপনি এলোমেলোভাবে আরেকটি তৈরি করতে পারেন। বিকল্পভাবে, আপনি আপনার নিজের আইডি দিয়ে চেষ্টা করে দেখতে পারেন যে সেটি উপলব্ধ আছে কিনা। এই ধাপের পরে এটি পরিবর্তন করা যাবে না এবং প্রজেক্টের পুরো সময়কাল জুড়ে এটি অপরিবর্তিত থাকবে। - আপনার অবগতির জন্য জানাচ্ছি যে, তৃতীয় একটি ভ্যালু রয়েছে, যা হলো প্রজেক্ট নম্বর , এবং কিছু এপিআই এটি ব্যবহার করে থাকে। ডকুমেন্টেশনে এই তিনটি ভ্যালু সম্পর্কে আরও বিস্তারিত জানুন।
- এরপর, ক্লাউড রিসোর্স/এপিআই ব্যবহার করার জন্য আপনাকে ক্লাউড কনসোলে বিলিং চালু করতে হবে। এই কোডল্যাবটি সম্পন্ন করতে খুব বেশি খরচ হবে না, এমনকি আদৌ কোনো খরচ নাও হতে পারে। এই টিউটোরিয়ালের পর বিলিং এড়াতে রিসোর্সগুলো বন্ধ করার জন্য, আপনি আপনার তৈরি করা রিসোর্সগুলো অথবা প্রজেক্টটি ডিলিট করে দিতে পারেন। নতুন গুগল ক্লাউড ব্যবহারকারীরা ৩০০ মার্কিন ডলারের ফ্রি ট্রায়াল প্রোগ্রামের জন্য যোগ্য।
ক্লাউড শেল শুরু করুন
যদিও গুগল ক্লাউড আপনার ল্যাপটপ থেকে দূরবর্তীভাবে পরিচালনা করা যায়, এই কোডল্যাবে আপনি ক্লাউড শেল ব্যবহার করবেন, যা ক্লাউডে চালিত একটি কমান্ড লাইন পরিবেশ।
ক্লাউড শেল সক্রিয় করুন
- ক্লাউড কনসোল থেকে, Activate Cloud Shell-এ ক্লিক করুন।
.

আপনি যদি প্রথমবারের মতো ক্লাউড শেল চালু করেন, তাহলে এটি কী তা বর্ণনা করে একটি মধ্যবর্তী স্ক্রিন আপনার সামনে আসবে। যদি একটি মধ্যবর্তী স্ক্রিন আসে, তাহলে 'চালিয়ে যান' (Continue) এ ক্লিক করুন।

ক্লাউড শেল প্রস্তুত করতে এবং এর সাথে সংযোগ স্থাপন করতে মাত্র কয়েক মুহূর্ত সময় লাগা উচিত।

এই ভার্চুয়াল মেশিনটিতে প্রয়োজনীয় সমস্ত ডেভেলপমেন্ট টুলস লোড করা আছে। এটি একটি স্থায়ী ৫ জিবি হোম ডিরেক্টরি প্রদান করে এবং গুগল ক্লাউডে চলে, যা নেটওয়ার্ক পারফরম্যান্স ও অথেনটিকেশনকে ব্যাপকভাবে উন্নত করে। এই কোডল্যাবে আপনার প্রায় সমস্ত কাজই একটি ব্রাউজার দিয়ে করা সম্ভব।
ক্লাউড শেলে সংযুক্ত হওয়ার পর, আপনি দেখতে পাবেন যে আপনাকে প্রমাণীকৃত করা হয়েছে এবং প্রজেক্টটি আপনার প্রজেক্ট আইডিতে সেট করা আছে।
- আপনি প্রমাণীকৃত কিনা তা নিশ্চিত করতে ক্লাউড শেলে নিম্নলিখিত কমান্ডটি চালান:
gcloud auth list
কমান্ড আউটপুট
Credentialed Accounts
ACTIVE ACCOUNT
* <my_account>@<my_domain.com>
To set the active account, run:
$ gcloud config set account `ACCOUNT`
- gcloud কমান্ডটি আপনার প্রজেক্ট সম্পর্কে জানে কিনা তা নিশ্চিত করতে ক্লাউড শেলে নিম্নলিখিত কমান্ডটি চালান:
gcloud config list project
কমান্ড আউটপুট
[core] project = <PROJECT_ID>
যদি তা না থাকে, তবে আপনি এই কমান্ডটি দিয়ে এটি সেট করতে পারেন:
gcloud config set project <PROJECT_ID>
কমান্ড আউটপুট
Updated property [core/project].
৩. আপনার ডেভেলপমেন্ট পরিবেশ প্রস্তুত করা
এই কোডল্যাবে, আপনারা ক্লাউড শেল টার্মিনাল এবং ক্লাউড শেল এডিটর ব্যবহার করে আপনাদের জাভা প্রোগ্রামগুলো তৈরি করবেন।
ভার্টেক্স এআই এপিআই সক্রিয় করুন
গুগল ক্লাউড কনসোলে, নিশ্চিত করুন যে আপনার প্রোজেক্টের নামটি কনসোলের শীর্ষে প্রদর্শিত হচ্ছে। যদি তা না হয়, তাহলে প্রোজেক্ট সিলেক্টর খোলার জন্য ‘সিলেক্ট এ প্রোজেক্ট’-এ ক্লিক করুন এবং আপনার কাঙ্ক্ষিত প্রোজেক্টটি নির্বাচন করুন।
আপনি গুগল ক্লাউড কনসোলের ভার্টেক্স এআই বিভাগ থেকে অথবা ক্লাউড শেল টার্মিনাল থেকে ভার্টেক্স এআই এপিআই সক্রিয় করতে পারেন।
গুগল ক্লাউড কনসোল থেকে সক্রিয় করতে, প্রথমে, গুগল ক্লাউড কনসোল মেনুর Vertex AI বিভাগে যান:

Vertex AI ড্যাশবোর্ডে ‘Enable All Recommended APIs’-এ ক্লিক করুন।
এর মাধ্যমে বেশ কয়েকটি এপিআই সক্রিয় করা যাবে, কিন্তু কোডল্যাবের জন্য সবচেয়ে গুরুত্বপূর্ণটি হলো aiplatform.googleapis.com ।
বিকল্পভাবে, আপনি নিম্নলিখিত কমান্ডের মাধ্যমে ক্লাউড শেল টার্মিনাল থেকেও এই API-টি সক্রিয় করতে পারেন:
gcloud services enable aiplatform.googleapis.com
গিটহাব রিপোজিটরি ক্লোন করুন
ক্লাউড শেল টার্মিনালে, এই কোডল্যাবের জন্য রিপোজিটরিটি ক্লোন করুন:
git clone https://github.com/glaforge/gemini-workshop-for-java-developers.git
প্রজেক্টটি চালানোর জন্য প্রস্তুত কিনা তা পরীক্ষা করতে, আপনি 'Hello World' প্রোগ্রামটি চালিয়ে দেখতে পারেন।
নিশ্চিত করুন যে আপনি সর্বোচ্চ স্তরের ফোল্ডারে আছেন:
cd gemini-workshop-for-java-developers/
গ্রেডল র্যাপার তৈরি করুন:
gradle wrapper
gradlew দিয়ে চালান :
./gradlew run
আপনি নিম্নলিখিত আউটপুট দেখতে পাবেন:
.. > Task :app:run Hello World!
ক্লাউড এডিটর খুলুন এবং সেটআপ করুন
ক্লাউড শেল থেকে ক্লাউড কোড এডিটর দিয়ে কোডটি খুলুন:

ক্লাউড কোড এডিটরে, File -> Open Folder নির্বাচন করে কোডল্যাব সোর্স ফোল্ডারটি খুলুন এবং কোডল্যাব সোর্স ফোল্ডারটি নির্দেশ করুন (যেমন, /home/username/gemini-workshop-for-java-developers/ )।
পরিবেশ ভেরিয়েবল সেটআপ করুন
ক্লাউড কোড এডিটর-এ Terminal -> New Terminal নির্বাচন করে একটি নতুন টার্মিনাল খুলুন। কোড উদাহরণগুলো চালানোর জন্য প্রয়োজনীয় দুটি এনভায়রনমেন্ট ভেরিয়েবল সেট আপ করুন:
- PROJECT_ID — আপনার গুগল ক্লাউড প্রজেক্ট আইডি
- অবস্থান — যে অঞ্চলে জেমিনি মডেলটি স্থাপন করা হয়েছে
ভেরিয়েবলগুলো নিম্নরূপে এক্সপোর্ট করুন:
export PROJECT_ID=$(gcloud config get-value project) export LOCATION=us-central1
৪. জেমিনি মডেলে প্রথম আহ্বান
প্রকল্পটি এখন সঠিকভাবে সেট আপ করা হয়ে গেছে, তাই জেমিনি এপিআই (Gemini API) কল করার সময় হয়েছে।
app/src/main/java/gemini/workshop ডিরেক্টরিতে থাকা QA.java ফাইলটি দেখুন:
package gemini.workshop;
import dev.langchain4j.model.vertexai.VertexAiGeminiChatModel;
import dev.langchain4j.model.chat.ChatLanguageModel;
public class QA {
public static void main(String[] args) {
ChatLanguageModel model = VertexAiGeminiChatModel.builder()
.project(System.getenv("PROJECT_ID"))
.location(System.getenv("LOCATION"))
.modelName("gemini-2.0-flash")
.build();
System.out.println(model.generate("Why is the sky blue?"));
}
}
এই প্রথম উদাহরণে, আপনাকে VertexAiGeminiChatModel ক্লাসটি ইম্পোর্ট করতে হবে, যেটি ChatModel ইন্টারফেসটি ইমপ্লিমেন্ট করে।
main মেথডে, আপনি VertexAiGeminiChatModel এর বিল্ডার ব্যবহার করে চ্যাট ল্যাঙ্গুয়েজ মডেল কনফিগার করেন এবং নিম্নলিখিত বিষয়গুলো নির্দিষ্ট করেন:
- প্রকল্প
- অবস্থান
- মডেলের নাম (
gemini-2.0-flash)।
এখন যেহেতু ল্যাঙ্গুয়েজ মডেলটি প্রস্তুত, আপনি generate() মেথডটি কল করে আপনার প্রম্পট, প্রশ্ন অথবা এলএলএম-এ পাঠানোর জন্য নির্দেশাবলী পাস করতে পারেন। এখানে, আপনি একটি সহজ প্রশ্ন জিজ্ঞাসা করছেন যে কী কারণে আকাশ নীল হয়।
ভিন্ন প্রশ্ন বা কাজ চেষ্টা করার জন্য এই নির্দেশটি নির্দ্বিধায় পরিবর্তন করুন।
সোর্স কোড রুট ফোল্ডারে নমুনাটি চালান:
./gradlew run -q -DjavaMainClass=gemini.workshop.QA
আপনি এর মতো একটি আউটপুট দেখতে পাবেন:
The sky appears blue because of a phenomenon called Rayleigh scattering. When sunlight enters the atmosphere, it is made up of a mixture of different wavelengths of light, each with a different color. The different wavelengths of light interact with the molecules and particles in the atmosphere in different ways. The shorter wavelengths of light, such as those corresponding to blue and violet light, are more likely to be scattered in all directions by these particles than the longer wavelengths of light, such as those corresponding to red and orange light. This is because the shorter wavelengths of light have a smaller wavelength and are able to bend around the particles more easily. As a result of Rayleigh scattering, the blue light from the sun is scattered in all directions, and it is this scattered blue light that we see when we look up at the sky. The blue light from the sun is not actually scattered in a single direction, so the color of the sky can vary depending on the position of the sun in the sky and the amount of dust and water droplets in the atmosphere.
অভিনন্দন, আপনি জেমিনিতে আপনার প্রথম কলটি করেছেন!
স্ট্রিমিং প্রতিক্রিয়া
আপনি কি লক্ষ্য করেছেন যে প্রতিক্রিয়াটি কয়েক সেকেন্ডের মধ্যেই একবারে দেওয়া হয়েছিল? স্ট্রিমিং রেসপন্স ভ্যারিয়েন্টের কল্যাণে, প্রতিক্রিয়াটি পর্যায়ক্রমে পাওয়াও সম্ভব। স্ট্রিমিং রেসপন্স পদ্ধতিতে, মডেলটি প্রতিক্রিয়াটি উপলব্ধ হওয়ার সাথে সাথে খণ্ড খণ্ড করে ফেরত পাঠায়।
এই কোডল্যাবে আমরা নন-স্ট্রিমিং রেসপন্স নিয়েই কাজ করব, কিন্তু স্ট্রিমিং রেসপন্স কীভাবে করা যায় তা দেখতে চলুন একবার দেখে নেওয়া যাক।
app/src/main/java/gemini/workshop ডিরেক্টরিতে থাকা StreamQA.java ফাইলে আপনি স্ট্রিমিং রেসপন্সটি কার্যকর অবস্থায় দেখতে পাবেন:
package gemini.workshop;
import dev.langchain4j.model.chat.StreamingChatLanguageModel;
import dev.langchain4j.model.vertexai.VertexAiGeminiStreamingChatModel;
import static dev.langchain4j.model.LambdaStreamingResponseHandler.onNext;
public class StreamQA {
public static void main(String[] args) {
StreamingChatLanguageModel model = VertexAiGeminiStreamingChatModel.builder()
.project(System.getenv("PROJECT_ID"))
.location(System.getenv("LOCATION"))
.modelName("gemini-2.0-flash")
.maxOutputTokens(4000)
.build();
model.generate("Why is the sky blue?", onNext(System.out::println));
}
}
এবার, আমরা VertexAiGeminiStreamingChatModel স্ট্রিমিং ক্লাস ভ্যারিয়েন্টটি ইম্পোর্ট করব, যেটি StreamingChatLanguageModel ইন্টারফেসটি ইমপ্লিমেন্ট করে। এছাড়াও আপনাকে LambdaStreamingResponseHandler.onNext স্ট্যাটিকভাবে ইম্পোর্ট করতে হবে, যা একটি সুবিধাজনক মেথড এবং জাভা ল্যাম্বডা এক্সপ্রেশন ব্যবহার করে স্ট্রিমিং হ্যান্ডলার তৈরি করার জন্য StreamingResponseHandler প্রদান করে।
এবার, generate() মেথডের সিগনেচারটি কিছুটা ভিন্ন। স্ট্রিং রিটার্ন করার পরিবর্তে, রিটার্ন টাইপটি হলো void। প্রম্পটের পাশাপাশি, আপনাকে একটি স্ট্রিমিং রেসপন্স হ্যান্ডলার পাস করতে হবে। এখানে, উপরে উল্লিখিত static import-এর কল্যাণে, আমরা একটি ল্যাম্বডা এক্সপ্রেশন সংজ্ঞায়িত করতে পারি যা আপনি onNext() মেথডে পাস করবেন। রেসপন্সের প্রতিটি নতুন অংশ উপলব্ধ হলে ল্যাম্বডা এক্সপ্রেশনটি কল করা হয়, আর রেসপন্সটি শুধুমাত্র কোনো ত্রুটি ঘটলেই কল করা হয়।
দৌড়:
./gradlew run -q -DjavaMainClass=gemini.workshop.StreamQA
আপনি আগের ক্লাসের মতোই একটি উত্তর পাবেন, কিন্তু এবার আপনি লক্ষ্য করবেন যে, সম্পূর্ণ উত্তরটি প্রদর্শিত হওয়ার জন্য অপেক্ষা না করে, উত্তরটি আপনার শেলে ক্রমান্বয়ে ভেসে উঠবে।
অতিরিক্ত কনফিগারেশন
কনফিগারেশনের জন্য, আমরা শুধুমাত্র প্রজেক্ট, লোকেশন এবং মডেলের নাম নির্ধারণ করেছি, কিন্তু মডেলের জন্য আপনি আরও অন্যান্য প্যারামিটার নির্দিষ্ট করতে পারেন:
-
temperature(Float temp)— প্রতিক্রিয়াটি কতটা সৃজনশীল হবে তা নির্ধারণ করার জন্য (০ মানে কম সৃজনশীল এবং প্রায়শই তথ্যভিত্তিক, আর ২ মানে আরও সৃজনশীল আউটপুট)। -
topP(Float topP)— সেই সম্ভাব্য শব্দগুলো নির্বাচন করার জন্য, যাদের মোট সম্ভাবনার যোগফল ঐ ফ্লোটিং পয়েন্ট সংখ্যাটির (০ এবং ১ এর মধ্যে) সমান হয়। -
topK(Integer topK)— টেক্সট সম্পূর্ণ করার জন্য সর্বাধিক সংখ্যক সম্ভাব্য শব্দের (১ থেকে ৪০ এর মধ্যে) মধ্য থেকে দৈবচয়নের মাধ্যমে একটি শব্দ নির্বাচন করা। -
maxOutputTokens(Integer max)— মডেল দ্বারা প্রদত্ত উত্তরের সর্বোচ্চ দৈর্ঘ্য নির্দিষ্ট করার জন্য (সাধারণত, ৪টি টোকেন প্রায় ৩টি শব্দের প্রতিনিধিত্ব করে) -
maxRetries(Integer retries)— যদি আপনার অনুরোধের সংখ্যা নির্ধারিত সীমা অতিক্রম করে যায়, অথবা প্ল্যাটফর্মটি কোনো প্রযুক্তিগত সমস্যার সম্মুখীন হয়, তাহলে আপনি মডেলটিকে কলটি ৩ বার পুনরায় চেষ্টা করাতে পারেন।
এখন পর্যন্ত আপনি মিথুনকে একটিমাত্র প্রশ্ন করেছেন, কিন্তু আপনারা একাধিক পালাক্রমেও কথোপকথন করতে পারেন। পরবর্তী অংশে আপনি সে বিষয়েই জানবেন।
৫. মিথুন রাশির সাথে চ্যাট করুন
পূর্ববর্তী ধাপে, আপনি একটিমাত্র প্রশ্ন করেছিলেন। এখন একজন ব্যবহারকারী এবং এলএলএম-এর মধ্যে একটি প্রকৃত কথোপকথনের সময় এসেছে। প্রতিটি প্রশ্ন ও উত্তর পূর্ববর্তীগুলোর ওপর ভিত্তি করে একটি প্রকৃত আলোচনা গড়ে তুলতে পারে।
app/src/main/java/gemini/workshop ফোল্ডারে থাকা Conversation.java ফাইলটি দেখুন:
package gemini.workshop;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.vertexai.VertexAiGeminiChatModel;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.service.AiServices;
import java.util.List;
public class Conversation {
public static void main(String[] args) {
ChatLanguageModel model = VertexAiGeminiChatModel.builder()
.project(System.getenv("PROJECT_ID"))
.location(System.getenv("LOCATION"))
.modelName("gemini-2.0-flash")
.build();
MessageWindowChatMemory chatMemory = MessageWindowChatMemory.builder()
.maxMessages(20)
.build();
interface ConversationService {
String chat(String message);
}
ConversationService conversation =
AiServices.builder(ConversationService.class)
.chatLanguageModel(model)
.chatMemory(chatMemory)
.build();
List.of(
"Hello!",
"What is the country where the Eiffel tower is situated?",
"How many inhabitants are there in that country?"
).forEach( message -> {
System.out.println("\nUser: " + message);
System.out.println("Gemini: " + conversation.chat(message));
});
}
}
এই ক্লাসে দুটি নতুন আকর্ষণীয় ইম্পোর্ট:
-
MessageWindowChatMemory— এমন একটি ক্লাস যা কথোপকথনের একাধিক পালা পরিচালনা করতে এবং পূর্ববর্তী প্রশ্ন ও উত্তরগুলো স্থানীয় মেমরিতে সংরক্ষণ করতে সাহায্য করবে। -
AiServices— একটি উচ্চ-স্তরের অ্যাবস্ট্রাকশন ক্লাস যা চ্যাট মডেল এবং চ্যাট মেমরিকে একত্রিত করবে।
মূল মেথডে, আপনি মডেল, চ্যাট মেমরি এবং এআই সার্ভিস সেট আপ করবেন। প্রজেক্ট, লোকেশন এবং মডেলের নামের তথ্য দিয়ে মডেলটি যথারীতি কনফিগার করা হয়।
চ্যাট মেমরির জন্য, আমরা MessageWindowChatMemory এর বিল্ডার ব্যবহার করে এমন একটি মেমরি তৈরি করি যা সর্বশেষ আদান-প্রদান করা ২০টি বার্তা সংরক্ষণ করে। এটি কথোপকথনের উপর একটি স্লাইডিং উইন্ডো, যার কনটেক্সট আমাদের জাভা ক্লাস ক্লায়েন্টে স্থানীয়ভাবে সংরক্ষিত থাকে।
এরপর আপনি সেই AI service তৈরি করেন যা চ্যাট মডেলকে চ্যাট মেমরির সাথে সংযুক্ত করে।
লক্ষ্য করুন, এআই সার্ভিসটি কীভাবে আমাদের সংজ্ঞায়িত একটি কাস্টম ConversationService ইন্টারফেস ব্যবহার করে, যা LangChain4j ইমপ্লিমেন্ট করে এবং যা একটি String কোয়েরি গ্রহণ করে ও একটি String রেসপন্স ফেরত দেয়।
এখন, মিথুনের সাথে কথোপকথনের পালা। প্রথমে একটি সাধারণ শুভেচ্ছা পাঠানো হয়, তারপর আইফেল টাওয়ার কোন দেশে অবস্থিত তা জানার জন্য একটি প্রথম প্রশ্ন করা হয়। লক্ষ্য করুন যে শেষ বাক্যটি প্রথম প্রশ্নের উত্তরের সাথে সম্পর্কিত, কারণ আপনি জানতে চাইছেন যে আইফেল টাওয়ার যে দেশে অবস্থিত সেখানে কতজন বাসিন্দা রয়েছে, যদিও আগের উত্তরে দেশটির নাম স্পষ্টভাবে উল্লেখ করা হয়নি। এটি দেখায় যে প্রতিটি প্রম্পটের সাথে পূর্ববর্তী প্রশ্ন এবং উত্তর পাঠানো হয়।
নমুনাটি চালান:
./gradlew run -q -DjavaMainClass=gemini.workshop.Conversation
আপনি এইগুলোর মতো তিনটি উত্তর দেখতে পাবেন:
User: Hello! Gemini: Hi there! How can I assist you today? User: What is the country where the Eiffel tower is situated? Gemini: France User: How many inhabitants are there in that country? Gemini: As of 2023, the population of France is estimated to be around 67.8 million.
আপনি জেমিনির সাথে একক প্রশ্ন করতে পারেন বা একাধিক পালায় কথোপকথন করতে পারেন, কিন্তু এখন পর্যন্ত ইনপুট ছিল শুধুমাত্র টেক্সট। ছবির ব্যাপারে কী বলা যায়? চলুন, পরের ধাপে ছবি নিয়ে আলোচনা করা যাক।
৬. মিথুনের সাথে মাল্টিমোডালিটি
জেমিনি একটি মাল্টিমোডাল মডেল। এটি শুধু টেক্সটই নয়, ছবি বা এমনকি ভিডিও-ও ইনপুট হিসেবে গ্রহণ করে। এই অংশে, আপনারা টেক্সট ও ছবি একসাথে ব্যবহারের একটি উদাহরণ দেখতে পাবেন।
আপনার কি মনে হয় মিথুন রাশি এই বিড়ালটিকে চিনতে পারবে?

উইকিপিডিয়া থেকে নেওয়া বরফের মধ্যে একটি বিড়ালের ছবি।
app/src/main/java/gemini/workshop ডিরেক্টরিতে থাকা Multimodal.java ফাইলটি দেখুন:
package gemini.workshop;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.vertexai.VertexAiGeminiChatModel;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.model.output.Response;
import dev.langchain4j.data.message.ImageContent;
import dev.langchain4j.data.message.TextContent;
public class Multimodal {
static final String CAT_IMAGE_URL =
"https://upload.wikimedia.org/wikipedia/" +
"commons/b/b6/Felis_catus-cat_on_snow.jpg";
public static void main(String[] args) {
ChatLanguageModel model = VertexAiGeminiChatModel.builder()
.project(System.getenv("PROJECT_ID"))
.location(System.getenv("LOCATION"))
.modelName("gemini-2.0-flash")
.build();
UserMessage userMessage = UserMessage.from(
ImageContent.from(CAT_IMAGE_URL),
TextContent.from("Describe the picture")
);
Response<AiMessage> response = model.generate(userMessage);
System.out.println(response.content().text());
}
}
ইমপোর্ট অংশে লক্ষ্য করুন, আমরা বিভিন্ন ধরণের মেসেজ এবং কন্টেন্টের মধ্যে পার্থক্য করি। একটি UserMessage TextContent এবং ImageContent উভয় অবজেক্টই থাকতে পারে। এখানেই মাল্টিমোডালিটির প্রয়োগ: অর্থাৎ টেক্সট এবং ছবির মিশ্রণ। আমরা শুধু একটি সাধারণ স্ট্রিং প্রম্পট পাঠাই না, বরং একটি আরও সুগঠিত অবজেক্ট পাঠাই যা ব্যবহারকারীর মেসেজকে উপস্থাপন করে এবং এটি একটি ইমেজ ও একটি টেক্সট কন্টেন্ট অংশ দিয়ে গঠিত। মডেলটি একটি Response ফেরত পাঠায়, যার মধ্যে একটি AiMessage থাকে।
এরপর আপনি content() মাধ্যমে রেসপন্স থেকে AiMessage এবং text() সাহায্যে মেসেজটির টেক্সট পেয়ে যান।
নমুনাটি চালান:
./gradlew run -q -DjavaMainClass=gemini.workshop.Multimodal
ছবিটির নাম থেকেই এর বিষয়বস্তু সম্পর্কে একটা ধারণা পাওয়া যাচ্ছিল, কিন্তু জেমিনির আউটপুট নিম্নোক্তটির অনুরূপ:
A cat with brown fur is walking in the snow. The cat has a white patch of fur on its chest and white paws. The cat is looking at the camera.
ছবি এবং টেক্সট প্রম্পটের মিশ্রণ আকর্ষণীয় ব্যবহারের সুযোগ তৈরি করে। আপনি এমন অ্যাপ্লিকেশন তৈরি করতে পারেন যা:
- ছবিতে থাকা লেখা শনাক্ত করুন।
- ছবিটি প্রদর্শনের জন্য নিরাপদ কিনা তা যাচাই করুন।
- ছবির ক্যাপশন তৈরি করুন।
- সাধারণ পাঠ্য বিবরণসহ ছবির একটি ডেটাবেস থেকে অনুসন্ধান করুন।
ছবি থেকে তথ্য বের করার পাশাপাশি, আপনি অসংগঠিত টেক্সট থেকেও তথ্য বের করতে পারেন। পরবর্তী অংশে আপনি সেটাই শিখতে চলেছেন।
৭. অসংগঠিত পাঠ্য থেকে সংগঠিত তথ্য বের করা
এমন অনেক পরিস্থিতি আছে যেখানে রিপোর্ট ডকুমেন্ট, ইমেল বা অন্যান্য দীর্ঘ টেক্সটে গুরুত্বপূর্ণ তথ্য অসংগঠিতভাবে দেওয়া থাকে। আদর্শগতভাবে, আপনি চাইবেন এই অসংগঠিত টেক্সট থেকে মূল বিবরণগুলো সংগঠিত অবজেক্ট আকারে বের করে আনতে। চলুন দেখি আপনি কীভাবে তা করতে পারেন।
ধরুন, আপনি কোনো ব্যক্তির জীবনী, সিভি বা বিবরণ থেকে তার নাম এবং বয়স বের করতে চান। আপনি একটি চতুরভাবে পরিবর্তিত প্রম্পটের মাধ্যমে LLM-কে অসংগঠিত টেক্সট থেকে JSON বের করার নির্দেশ দিতে পারেন (এটিকে সাধারণত "প্রম্পট ইঞ্জিনিয়ারিং" বলা হয়)।
কিন্তু নীচের উদাহরণে, JSON আউটপুট বর্ণনা করে একটি প্রম্পট তৈরি করার পরিবর্তে, আমরা জেমিনির একটি শক্তিশালী বৈশিষ্ট্য ব্যবহার করব যাকে স্ট্রাকচার্ড আউটপুট বা কখনও কখনও কনস্ট্রেইন্ড জেনারেশন বলা হয়, যা মডেলটিকে একটি নির্দিষ্ট JSON স্কিমা অনুসরণ করে শুধুমাত্র বৈধ JSON কন্টেন্ট আউটপুট করতে বাধ্য করে।
app/src/main/java/gemini/workshop এ থাকা ExtractData.java ফাইলটি দেখুন:
package gemini.workshop;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.vertexai.VertexAiGeminiChatModel;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.service.SystemMessage;
import static dev.langchain4j.model.vertexai.SchemaHelper.fromClass;
public class ExtractData {
record Person(String name, int age) { }
interface PersonExtractor {
@SystemMessage("""
Your role is to extract the name and age
of the person described in the biography.
""")
Person extractPerson(String biography);
}
public static void main(String[] args) {
ChatLanguageModel model = VertexAiGeminiChatModel.builder()
.project(System.getenv("PROJECT_ID"))
.location(System.getenv("LOCATION"))
.modelName("gemini-2.0-flash")
.responseMimeType("application/json")
.responseSchema(fromClass(Person.class))
.build();
PersonExtractor extractor = AiServices.create(PersonExtractor.class, model);
String bio = """
Anna is a 23 year old artist based in Brooklyn, New York. She was born and
raised in the suburbs of Chicago, where she developed a love for art at a
young age. She attended the School of the Art Institute of Chicago, where
she studied painting and drawing. After graduating, she moved to New York
City to pursue her art career. Anna's work is inspired by her personal
experiences and observations of the world around her. She often uses bright
colors and bold lines to create vibrant and energetic paintings. Her work
has been exhibited in galleries and museums in New York City and Chicago.
""";
Person person = extractor.extractPerson(bio);
System.out.println(person.name()); // Anna
System.out.println(person.age()); // 23
}
}
চলুন এই ফাইলের বিভিন্ন ধাপগুলো দেখে নেওয়া যাক:
- একজন ব্যক্তির বিবরণ (নাম এবং বয়স) উপস্থাপন করার জন্য একটি
Personরেকর্ডকে সংজ্ঞায়িত করা হয়। -
PersonExtractorইন্টারফেসে এমন একটি মেথড সংজ্ঞায়িত করা আছে, যা একটি অসংগঠিত টেক্সট স্ট্রিং পেলে একটিPersonইনস্ট্যান্স রিটার্ন করে। -
extractPerson()ফাংশনটি@SystemMessageঅ্যানোটেশন দ্বারা চিহ্নিত, যা এর সাথে একটি নির্দেশনামূলক প্রম্পট যুক্ত করে। এই প্রম্পটটিই মডেলকে তথ্য নিষ্কাশনে পথ দেখাবে এবং বিস্তারিত তথ্য একটি JSON ডকুমেন্ট আকারে ফেরত দেবে, যা আপনার জন্য পার্স করা হবে এবং একটিPersonইনস্ট্যান্সে আনমার্শাল করা হবে।
এবার main() মেথডের বিষয়বস্তু দেখা যাক:
- চ্যাট মডেলটি কনফিগার এবং ইনস্ট্যানশিয়েট করা হয়েছে। আমরা মডেল বিল্ডার ক্লাসের দুটি নতুন মেথড ব্যবহার করছি:
responseMimeType()এবংresponseSchema()। প্রথমটি জেমিনিকে আউটপুটে বৈধ JSON তৈরি করতে নির্দেশ দেয়। দ্বিতীয় মেথডটি যে JSON অবজেক্টটি রিটার্ন করা হবে তার স্কিমা নির্ধারণ করে। এছাড়াও, এটি একটি সুবিধাজনক মেথডের কাছে দায়িত্ব অর্পণ করে, যা একটি জাভা ক্লাস বা রেকর্ডকে যথাযথ JSON স্কিমাতে রূপান্তর করতে সক্ষম। - LangChain4j-এর
AiServicesক্লাসের মাধ্যমে একটিPersonExtractorঅবজেক্ট তৈরি করা হয়। - তারপর, আপনি অসংগঠিত টেক্সট থেকে ব্যক্তির বিবরণ বের করতে এবং নাম ও বয়স সহ একটি
Personইনস্ট্যান্স ফেরত পেতে কেবলPerson person = extractor.extractPerson(...)কল করতে পারেন।
নমুনাটি চালান:
./gradlew run -q -DjavaMainClass=gemini.workshop.ExtractData
আপনি নিম্নলিখিত আউটপুট দেখতে পাবেন:
Anna 23
হ্যাঁ, ইনি অ্যানা এবং ওদের বয়স ২৩!
এই AiServices পদ্ধতির মাধ্যমে আপনি স্ট্রংলি টাইপড অবজেক্ট নিয়ে কাজ করেন। আপনি সরাসরি LLM-এর সাথে ইন্টারঅ্যাক্ট করছেন না। এর পরিবর্তে, আপনি কনক্রিট ক্লাস নিয়ে কাজ করছেন, যেমন সংগৃহীত ব্যক্তিগত তথ্য উপস্থাপনের জন্য Person রেকর্ড, এবং আপনার কাছে একটি PersonExtractor অবজেক্ট আছে যার extractPerson() মেথডটি একটি Person ইনস্ট্যান্স রিটার্ন করে। LLM-এর ধারণাটি এখানে অ্যাবস্ট্রাক্ট করা থাকে, এবং একজন জাভা ডেভেলপার হিসেবে, এই PersonExtractor ইন্টারফেসটি ব্যবহার করার সময় আপনি কেবল সাধারণ ক্লাস এবং অবজেক্টই ম্যানিপুলেট করছেন।
৮. প্রম্পট টেমপ্লেট ব্যবহার করে প্রম্পট তৈরি করুন
যখন আপনি কিছু সাধারণ নির্দেশাবলী বা প্রশ্ন ব্যবহার করে একটি LLM-এর সাথে ইন্টারঅ্যাক্ট করেন, তখন সেই প্রম্পটের একটি অংশ থাকে যা কখনও পরিবর্তন হয় না, আর অন্য অংশগুলোতে ডেটা থাকে। উদাহরণস্বরূপ, আপনি যদি রেসিপি তৈরি করতে চান, তাহলে আপনি "আপনি একজন প্রতিভাবান শেফ, অনুগ্রহ করে নিম্নলিখিত উপকরণগুলো দিয়ে একটি রেসিপি তৈরি করুন: ..."-এর মতো একটি প্রম্পট ব্যবহার করতে পারেন, এবং তারপর সেই টেক্সটের শেষে উপকরণগুলো যুক্ত করবেন। প্রম্পট টেমপ্লেটগুলো এই কাজের জন্যই ব্যবহৃত হয় — যা প্রোগ্রামিং ল্যাঙ্গুয়েজের ইন্টারপোলেটেড স্ট্রিং-এর মতো। একটি প্রম্পট টেমপ্লেটে প্লেসহোল্ডার থাকে, যা আপনি LLM-কে নির্দিষ্টভাবে কল করার জন্য সঠিক ডেটা দিয়ে প্রতিস্থাপন করতে পারেন।
আরও নির্দিষ্টভাবে বলতে গেলে, আসুন app/src/main/java/gemini/workshop ডিরেক্টরিতে থাকা TemplatePrompt.java অধ্যয়ন করি:
package gemini.workshop;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.vertexai.VertexAiGeminiChatModel;
import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.model.input.Prompt;
import dev.langchain4j.model.input.PromptTemplate;
import dev.langchain4j.model.output.Response;
import java.util.HashMap;
import java.util.Map;
public class TemplatePrompt {
public static void main(String[] args) {
ChatLanguageModel model = VertexAiGeminiChatModel.builder()
.project(System.getenv("PROJECT_ID"))
.location(System.getenv("LOCATION"))
.modelName("gemini-2.0-flash")
.maxOutputTokens(500)
.temperature(1.0f)
.topK(40)
.topP(0.95f)
.maxRetries(3)
.build();
PromptTemplate promptTemplate = PromptTemplate.from("""
You're a friendly chef with a lot of cooking experience.
Create a recipe for a {{dish}} with the following ingredients: \
{{ingredients}}, and give it a name.
"""
);
Map<String, Object> variables = new HashMap<>();
variables.put("dish", "dessert");
variables.put("ingredients", "strawberries, chocolate, and whipped cream");
Prompt prompt = promptTemplate.apply(variables);
Response<AiMessage> response = model.generate(prompt.toUserMessage());
System.out.println(response.content().text());
}
}
যথারীতি, আপনি উচ্চ তাপমাত্রা এবং উচ্চ topP ও topK মান ব্যবহার করে, যথেষ্ট সৃজনশীলতার সাথে VertexAiGeminiChatModel মডেলটি কনফিগার করবেন। তারপর, আমাদের প্রম্পটের স্ট্রিংটি পাস করে, এর from() স্ট্যাটিক মেথডসহ একটি PromptTemplate তৈরি করবেন এবং ডাবল কার্লি-ব্রেসেস প্লেসহোল্ডার ভেরিয়েবল ও ব্যবহার করবেন।
চূড়ান্ত প্রম্পটটি তৈরি করার জন্য আপনাকে apply() ফাংশনটি কল করতে হবে, যা কী/ভ্যালু জোড়ের একটি ম্যাপ গ্রহণ করে। এই ম্যাপে প্লেসহোল্ডারের নাম এবং যে স্ট্রিং ভ্যালুটি দিয়ে এটিকে প্রতিস্থাপন করা হবে, তা উল্লেখ থাকে।
সবশেষে, আপনি prompt.toUserMessage() নির্দেশনা ব্যবহার করে সেই প্রম্পট থেকে একটি ব্যবহারকারী বার্তা তৈরি করার মাধ্যমে Gemini মডেলের ` generate() পদ্ধতিটি কল করবেন।
নমুনাটি চালান:
./gradlew run -q -DjavaMainClass=gemini.workshop.TemplatePrompt
আপনি এমন একটি আউটপুট দেখতে পাবেন যা দেখতে এটির মতো:
**Strawberry Shortcake** Ingredients: * 1 pint strawberries, hulled and sliced * 1/2 cup sugar * 1/4 cup cornstarch * 1/4 cup water * 1 tablespoon lemon juice * 1/2 cup heavy cream, whipped * 1/4 cup confectioners' sugar * 1/4 teaspoon vanilla extract * 6 graham cracker squares, crushed Instructions: 1. In a medium saucepan, combine the strawberries, sugar, cornstarch, water, and lemon juice. Bring to a boil over medium heat, stirring constantly. Reduce heat and simmer for 5 minutes, or until the sauce has thickened. 2. Remove from heat and let cool slightly. 3. In a large bowl, combine the whipped cream, confectioners' sugar, and vanilla extract. Beat until soft peaks form. 4. To assemble the shortcakes, place a graham cracker square on each of 6 dessert plates. Top with a scoop of whipped cream, then a spoonful of strawberry sauce. Repeat layers, ending with a graham cracker square. 5. Serve immediately. **Tips:** * For a more elegant presentation, you can use fresh strawberries instead of sliced strawberries. * If you don't have time to make your own whipped cream, you can use store-bought whipped cream.
ম্যাপে থাকা dish এবং ingredients -এর মানগুলো নির্দ্বিধায় পরিবর্তন করুন, তাপমাত্রা, topK ও tokP মান সামান্য পরিবর্তন করে কোডটি পুনরায় চালান। এর মাধ্যমে আপনি LLM-এর উপর এই প্যারামিটারগুলো পরিবর্তনের প্রভাব পর্যবেক্ষণ করতে পারবেন।
এলএলএম কলের জন্য পুনঃব্যবহারযোগ্য এবং প্যারামিটার পরিবর্তনযোগ্য নির্দেশাবলী তৈরির একটি ভালো উপায় হলো প্রম্পট টেমপ্লেট। আপনি ডেটা পাস করতে পারেন এবং আপনার ব্যবহারকারীদের দেওয়া বিভিন্ন মানের জন্য প্রম্পটগুলো কাস্টমাইজ করতে পারেন।
৯. ফিউ-শট প্রম্পটিং ব্যবহার করে টেক্সট ক্লাসিফিকেশন
এলএলএমরা পাঠ্যকে বিভিন্ন শ্রেণীতে ভাগ করতে বেশ পারদর্শী। আপনি কিছু পাঠ্যের উদাহরণ এবং সেগুলোর সংশ্লিষ্ট শ্রেণী উল্লেখ করে একজন এলএলএমকে এই কাজে সাহায্য করতে পারেন। এই পদ্ধতিকে প্রায়শই ‘ফিউ শট প্রম্পটিং’ বলা হয়।
এক বিশেষ ধরনের টেক্সট ক্লাসিফিকেশন, অর্থাৎ সেন্টিমেন্ট অ্যানালাইসিস করার জন্য, চলুন app/src/main/java/gemini/workshop ডিরেক্টরিতে থাকা TextClassification.java খুলি।
package gemini.workshop;
import com.google.cloud.vertexai.api.Schema;
import com.google.cloud.vertexai.api.Type;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.vertexai.VertexAiGeminiChatModel;
import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.service.SystemMessage;
import java.util.List;
public class TextClassification {
enum Sentiment { POSITIVE, NEUTRAL, NEGATIVE }
public static void main(String[] args) {
ChatLanguageModel model = VertexAiGeminiChatModel.builder()
.project(System.getenv("PROJECT_ID"))
.location(System.getenv("LOCATION"))
.modelName("gemini-2.0-flash")
.maxOutputTokens(10)
.maxRetries(3)
.responseSchema(Schema.newBuilder()
.setType(Type.STRING)
.addAllEnum(List.of("POSITIVE", "NEUTRAL", "NEGATIVE"))
.build())
.build();
interface SentimentAnalysis {
@SystemMessage("""
Analyze the sentiment of the text below.
Respond only with one word to describe the sentiment.
""")
Sentiment analyze(String text);
}
MessageWindowChatMemory memory = MessageWindowChatMemory.withMaxMessages(10);
memory.add(UserMessage.from("This is fantastic news!"));
memory.add(AiMessage.from(Sentiment.POSITIVE.name()));
memory.add(UserMessage.from("Pi is roughly equal to 3.14"));
memory.add(AiMessage.from(Sentiment.NEUTRAL.name()));
memory.add(UserMessage.from("I really disliked the pizza. Who would use pineapples as a pizza topping?"));
memory.add(AiMessage.from(Sentiment.NEGATIVE.name()));
SentimentAnalysis sentimentAnalysis =
AiServices.builder(SentimentAnalysis.class)
.chatLanguageModel(model)
.chatMemory(memory)
.build();
System.out.println(sentimentAnalysis.analyze("I love strawberries!"));
}
}
একটি Sentiment enum কোনো একটি সেন্টিমেন্টের বিভিন্ন মান তালিকাভুক্ত করে: নেতিবাচক, নিরপেক্ষ বা ইতিবাচক।
main() মেথডে, আপনি যথারীতি Gemini চ্যাট মডেলটি তৈরি করেন, কিন্তু একটি ছোট সর্বোচ্চ আউটপুট টোকেন সংখ্যা সহ, কারণ আপনি শুধুমাত্র একটি সংক্ষিপ্ত প্রতিক্রিয়া চান: টেক্সটটি POSITIVE , NEGATIVE , বা NEUTRAL । এবং মডেলটিকে শুধুমাত্র এই মানগুলি ফেরত দেওয়ার জন্য সীমাবদ্ধ করতে, আপনি ডেটা এক্সট্র্যাকশন বিভাগে আবিষ্কৃত স্ট্রাকচার্ড আউটপুট সাপোর্টের সুবিধা নিতে পারেন। এই কারণেই responseSchema() মেথডটি ব্যবহৃত হয়। এবার, আপনি স্কিমা ডেফিনিশন অনুমান করার জন্য SchemaHelper এর সুবিধাজনক মেথডটি ব্যবহার করছেন না, বরং স্কিমা ডেফিনিশন দেখতে কেমন তা বোঝার জন্য আপনি Schema বিল্ডার ব্যবহার করবেন।
মডেলটি কনফিগার করা হয়ে গেলে, আপনি একটি SentimentAnalysis ইন্টারফেস তৈরি করেন যা LangChain4j-এর AiServices LLM ব্যবহার করে আপনার জন্য ইমপ্লিমেন্ট করবে। এই ইন্টারফেসে analyze() নামে একটি মেথড থাকে। এটি ইনপুট হিসেবে বিশ্লেষণের জন্য টেক্সট গ্রহণ করে এবং একটি Sentiment enum ভ্যালু রিটার্ন করে। সুতরাং, আপনি কেবল একটি স্ট্রংলি টাইপড অবজেক্ট নিয়ে কাজ করছেন যা শনাক্তকৃত সেন্টিমেন্টের ক্লাসকে প্রতিনিধিত্ব করে।
এরপর, মডেলকে তার শ্রেণিবিন্যাসের কাজে উৎসাহিত করার জন্য কিছু উদাহরণ দিতে, আপনি একটি চ্যাট মেমরি তৈরি করেন যেখানে ব্যবহারকারীর বার্তা এবং এআই প্রতিক্রিয়ার জোড়া পাঠানো হয়, যা মূল লেখা এবং তার সাথে সম্পর্কিত অনুভূতিকে উপস্থাপন করে।
চলুন, আমাদের SentimentAnalysis ইন্টারফেস, ব্যবহৃত মডেল এবং ফিউ-শট উদাহরণসহ চ্যাট মেমরি পাস করে AiServices.builder() মেথডের মাধ্যমে সবকিছু একসাথে যুক্ত করি। সবশেষে, বিশ্লেষণ করার জন্য টেক্সট দিয়ে analyze() মেথডটি কল করুন।
নমুনাটি চালান:
./gradlew run -q -DjavaMainClass=gemini.workshop.TextClassification
আপনি একটিমাত্র শব্দ দেখতে পাবেন:
POSITIVE
মনে হচ্ছে স্ট্রবেরি ভালোবাসা একটি ইতিবাচক অনুভূতি!
১০. পুনরুদ্ধার বর্ধিত প্রজন্ম
এলএলএম-কে বিপুল পরিমাণ টেক্সটের উপর প্রশিক্ষণ দেওয়া হয়। তবে, এর জ্ঞান কেবল প্রশিক্ষণের সময় দেখা তথ্যের মধ্যেই সীমাবদ্ধ থাকে। মডেল প্রশিক্ষণের নির্ধারিত তারিখের পরে যদি নতুন কোনো তথ্য প্রকাশিত হয়, তবে সেই বিবরণগুলো মডেলের কাছে উপলব্ধ হবে না। ফলে, মডেলটি যেসব তথ্য দেখেনি, সেগুলোর উপর করা প্রশ্নের উত্তর দিতে পারবে না।
এই কারণেই রিট্রিভাল অগমেন্টেড জেনারেশন (RAG)- এর মতো পদ্ধতি, যা এই বিভাগে আলোচনা করা হবে, একজন এলএলএম-কে তার ব্যবহারকারীদের অনুরোধ পূরণ করার জন্য প্রয়োজনীয় অতিরিক্ত তথ্য সরবরাহ করতে সাহায্য করে; যেমন—আরও সাম্প্রতিক তথ্য দিয়ে উত্তর দেওয়া, অথবা প্রশিক্ষণের সময় সহজলভ্য নয় এমন ব্যক্তিগত তথ্য প্রদান করা।
চলুন কথোপকথনের প্রসঙ্গে ফিরে আসি। এবার, আপনি আপনার ডকুমেন্টগুলো সম্পর্কে প্রশ্ন করতে পারবেন। আপনি এমন একটি চ্যাটবট তৈরি করবেন যা আপনার ডকুমেন্টগুলোকে ছোট ছোট অংশে ("চাঙ্ক") বিভক্ত করে রাখা একটি ডেটাবেস থেকে প্রাসঙ্গিক তথ্য সংগ্রহ করতে সক্ষম হবে এবং মডেলটি শুধুমাত্র তার প্রশিক্ষণে থাকা জ্ঞানের উপর নির্ভর না করে, সেই তথ্য ব্যবহার করে তার উত্তরগুলোকে ভিত্তি দেবে।
RAG-তে দুটি পর্যায় রয়েছে:
- ইনজেশন পর্যায় — ডকুমেন্টগুলো মেমোরিতে লোড করা হয়, সেগুলোকে ছোট ছোট খণ্ডে বিভক্ত করা হয়, এবং ভেক্টর এমবেডিং (খণ্ডগুলোর একটি উচ্চ বহুমাত্রিক ভেক্টর উপস্থাপনা) গণনা করে একটি ভেক্টর ডেটাবেসে সংরক্ষণ করা হয়, যা সিমান্টিক সার্চ করতে সক্ষম। এই ইনজেশন পর্যায়টি সাধারণত একবার করা হয়, যখন ডকুমেন্ট কর্পাসে নতুন ডকুমেন্ট যুক্ত করার প্রয়োজন হয়।

- কোয়েরি পর্যায় — ব্যবহারকারীরা এখন ডকুমেন্টগুলো সম্পর্কে প্রশ্ন করতে পারবেন। প্রশ্নটিকেও একটি ভেক্টরে রূপান্তরিত করা হবে এবং ডাটাবেসের অন্য সব ভেক্টরের সাথে তুলনা করা হবে। সবচেয়ে সাদৃশ্যপূর্ণ ভেক্টরগুলো সাধারণত শব্দার্থগতভাবে সম্পর্কিত হয় এবং ভেক্টর ডাটাবেস থেকে সেগুলো ফেরত দেওয়া হয়। এরপর, LLM-কে কথোপকথনের প্রেক্ষাপট, ডাটাবেস থেকে ফেরত আসা ভেক্টরগুলোর সাথে সঙ্গতিপূর্ণ টেক্সটের অংশগুলো দেওয়া হয় এবং সেই অংশগুলোর দিকে তাকিয়ে এর উত্তরের ভিত্তি তৈরি করতে বলা হয়।

আপনার নথি প্রস্তুত করুন
এই নতুন উদাহরণটির জন্য, আপনি একটি কাল্পনিক গাড়ি নির্মাতা প্রতিষ্ঠানের একটি কাল্পনিক গাড়ির মডেল—সিম্বাল স্টারলাইট গাড়িটি—সম্পর্কে প্রশ্ন করবেন! মূল ধারণাটি হলো, একটি কাল্পনিক গাড়ি সম্পর্কিত কোনো নথি যেন সেই মডেল সম্পর্কে থাকা জ্ঞানের অংশ না হয়। সুতরাং, যদি জেমিনি এই গাড়িটি সম্পর্কে প্রশ্নগুলোর সঠিক উত্তর দিতে পারে, তাহলে এর অর্থ হলো RAG পদ্ধতিটি কাজ করছে: এটি আপনার নথিটির মধ্যে অনুসন্ধান করতে সক্ষম।
চ্যাটবট বাস্তবায়ন করুন
চলুন দেখে নেওয়া যাক কীভাবে দ্বি-পর্যায়ের পদ্ধতিটি তৈরি করা যায়: প্রথমে ডকুমেন্ট ইনজেশন এবং তারপর কোয়েরি পর্ব (যাকে "রিট্রিভাল ফেজ"ও বলা হয়), যখন ব্যবহারকারীরা ডকুমেন্টটি সম্পর্কে প্রশ্ন করেন।
এই উদাহরণে, উভয় পর্যায় একই ক্লাসে বাস্তবায়িত হয়েছে। সাধারণত, আপনার একটি অ্যাপ্লিকেশন থাকবে যা ডেটা গ্রহণের কাজটি করবে, এবং আরেকটি অ্যাপ্লিকেশন থাকবে যা আপনার ব্যবহারকারীদের চ্যাটবট ইন্টারফেসটি প্রদান করবে।
এছাড়াও, এই উদাহরণে আমরা একটি ইন-মেমরি ভেক্টর ডেটাবেস ব্যবহার করব। বাস্তব প্রোডাকশন পরিস্থিতিতে, ডেটা গ্রহণ এবং কোয়েরি করার পর্যায়গুলো দুটি স্বতন্ত্র অ্যাপ্লিকেশনে বিভক্ত থাকবে এবং ভেক্টরগুলো একটি স্বতন্ত্র ডেটাবেসে সংরক্ষণ করা হবে।
নথি গ্রহণ
ডকুমেন্ট ইনজেশন পর্বের সর্বপ্রথম ধাপ হলো আমাদের কাল্পনিক গাড়ি সম্পর্কিত পিডিএফ ফাইলটি খুঁজে বের করা এবং সেটি পড়ার জন্য একটি PdfParser প্রস্তুত করা:
URL url = new URI("https://raw.githubusercontent.com/meteatamel/genai-beyond-basics/main/samples/grounding/vertexai-search/cymbal-starlight-2024.pdf").toURL();
ApachePdfBoxDocumentParser pdfParser = new ApachePdfBoxDocumentParser();
Document document = pdfParser.parse(url.openStream());
প্রথমে প্রচলিত চ্যাট ল্যাঙ্গুয়েজ মডেল তৈরি করার পরিবর্তে, আপনাকে একটি এমবেডিং মডেলের ইনস্ট্যান্স তৈরি করতে হয়। এটি একটি বিশেষ মডেল যার কাজ হলো টেক্সটের অংশগুলোর (শব্দ, বাক্য বা এমনকি অনুচ্ছেদ) ভেক্টর উপস্থাপনা তৈরি করা। এটি টেক্সট রেসপন্স ফেরত না দিয়ে, ফ্লোটিং পয়েন্ট সংখ্যার ভেক্টর ফেরত দেয়।
VertexAiEmbeddingModel embeddingModel = VertexAiEmbeddingModel.builder()
.endpoint(System.getenv("LOCATION") + "-aiplatform.googleapis.com:443")
.project(System.getenv("PROJECT_ID"))
.location(System.getenv("LOCATION"))
.publisher("google")
.modelName("text-embedding-005")
.maxRetries(3)
.build();
Next, you will need a few classes to collaborate together to:
- Load and split the PDF document in chunks.
- Create vector embeddings for all of these chunks.
InMemoryEmbeddingStore<TextSegment> embeddingStore =
new InMemoryEmbeddingStore<>();
EmbeddingStoreIngestor storeIngestor = EmbeddingStoreIngestor.builder()
.documentSplitter(DocumentSplitters.recursive(500, 100))
.embeddingModel(embeddingModel)
.embeddingStore(embeddingStore)
.build();
storeIngestor.ingest(document);
An instance of InMemoryEmbeddingStore , an in-memory vector database, is created to store the vector embeddings.
The document is split in chunks thanks to the DocumentSplitters class. It is going to split the text of the PDF file into snippets of 500 characters, with an overlap of 100 characters (with the following chunk, to avoid cutting words or sentences, in bits and pieces).
The store ingestor links the document splitter, the embedding model to calculate the vectors, and the in-memory vector database. Then, the ingest() method will take care of doing the ingestion.
Now, the first phase is over, the document has been transformed into text chunks with their associated vector embeddings, and stored in the vector database.
Asking questions
It's time to get ready to ask questions! Create a chat model to start the conversation:
ChatLanguageModel model = VertexAiGeminiChatModel.builder()
.project(System.getenv("PROJECT_ID"))
.location(System.getenv("LOCATION"))
.modelName("gemini-2.0-flash")
.maxOutputTokens(1000)
.build();
You also need a retriever class to link the vector database (in the embeddingStore variable) with the embedding model. Its job is to query the vector database by computing a vector embedding for the user's query, to find similar vectors in the database:
EmbeddingStoreContentRetriever retriever =
new EmbeddingStoreContentRetriever(embeddingStore, embeddingModel);
Create an interface that represents a car expert assistant, that's an interface that the AiServices class will implement for you to interact with the model:
interface CarExpert {
Result<String> ask(String question);
}
The CarExpert interface returns a string response wrapped in LangChain4j's Result class. Why use this wrapper? Because not only it will give you the answer, but it will also let you examine the chunks from the database that have been returned by the content retriever. That way, you can display the sources of the document(s) that are used to ground the final answer to the user.
At this point, you can configure a new AI service:
CarExpert expert = AiServices.builder(CarExpert.class)
.chatLanguageModel(model)
.chatMemory(MessageWindowChatMemory.withMaxMessages(10))
.contentRetriever(retriever)
.build();
This service binds together:
- The chat language model that you configured earlier.
- A chat memory to keep track of the conversation.
- The retriever compares a vector embedding query to the vectors in the database.
.retrievalAugmentor(DefaultRetrievalAugmentor.builder()
.contentInjector(DefaultContentInjector.builder()
.promptTemplate(PromptTemplate.from("""
You are an expert in car automotive, and you answer concisely.
Here is the question: {{userMessage}}
Answer using the following information:
{{contents}}
"""))
.build())
.contentRetriever(retriever)
.build())
You're finally ready to ask your questions!
List.of(
"What is the cargo capacity of Cymbal Starlight?",
"What's the emergency roadside assistance phone number?",
"Are there some special kits available on that car?"
).forEach(query -> {
Result<String> response = expert.ask(query);
System.out.printf("%n=== %s === %n%n %s %n%n", query, response.content());
System.out.println("SOURCE: " + response.sources().getFirst().textSegment().text());
});
The full source code is in RAG.java in app/src/main/java/gemini/workshop directory.
Run the sample:
./gradlew -q run -DjavaMainClass=gemini.workshop.RAG
In the output, you should see answers to your questions:
=== What is the cargo capacity of Cymbal Starlight? === The Cymbal Starlight 2024 has a cargo capacity of 13.5 cubic feet. SOURCE: Cargo The Cymbal Starlight 2024 has a cargo capacity of 13.5 cubic feet. The cargo area is located in the trunk of the vehicle. To access the cargo area, open the trunk lid using the trunk release lever located in the driver's footwell. When loading cargo into the trunk, be sure to distribute the weight evenly. Do not overload the trunk, as this could affect the vehicle's handling and stability. Luggage === What's the emergency roadside assistance phone number? === The emergency roadside assistance phone number is 1-800-555-1212. SOURCE: Chapter 18: Emergencies Roadside Assistance If you experience a roadside emergency, such as a flat tire or a dead battery, you can call roadside assistance for help. Roadside assistance is available 24 hours a day, 7 days a week. To call roadside assistance, dial the following number: 1-800-555-1212 When you call roadside assistance, be prepared to provide the following information: Your name and contact information Your vehicle's make, model, and year Your vehicle's location === Are there some special kits available on that car? === Yes, the Cymbal Starlight comes with a tire repair kit. SOURCE: Lane keeping assist: This feature helps to keep you in your lane by gently steering the vehicle back into the lane if you start to drift. Adaptive cruise control: This feature automatically adjusts your speed to maintain a safe following distance from the vehicle in front of you. Forward collision warning: This feature warns you if you are approaching another vehicle too quickly. Automatic emergency braking: This feature can automatically apply the brakes to avoid a collision.
11. Function calling
There are situations where you would like an LLM to have access to external systems, like a remote web API that retrieves information or have an action, or services that perform some kind of computation. For example:
Remote web APIs:
- Track and update customer orders.
- Find or create a ticket in an issue tracker.
- Fetch real time data like stock quotes or IoT sensor measurements.
- Send an email.
Computation tools:
- A calculator for more advanced math problems.
- Code interpretation for running code when LLMs need reasoning logic.
- Convert natural language requests into SQL queries so that an LLM can query a database.
Function calling (sometimes called tools, or tool use) is the ability for the model to request one or more function calls to be made on its behalf, so it can properly answer a user's prompt with fresher data.
Given a particular prompt from a user, and the knowledge of existing functions that can be relevant to that context, an LLM can reply with a function call request. The application integrating the LLM can then call the function on its behalf, and then reply back to the LLM with a response, and the LLM then interprets back by replying with a textual answer.
Four steps of function calling
Let's have a look at an example of function calling: getting information about the weather forecast.
If you ask Gemini or any other LLM about the weather in Paris, they would reply by saying that it has no information about the current weather forecast. If you want the LLM to have real time access to the weather data, you need to define some functions it can request to be used.
Take a look at the following diagram:

1️⃣ First, a user asks about the weather in Paris. The chatbot app (using LangChain4j) knows there are one or more functions that are at its disposal to help the LLM fulfill the query. The chatbot both sends the initial prompt, as well as the list of functions that can be called. Here, a function called getWeather() which takes a string parameter for the location.

As the LLM doesn't know about weather forecasts, instead of replying via text, it sends back a function execution request. The chatbot must call the getWeather() function with "Paris" as location parameter.
2️⃣ The chatbot invokes that function on behalf of the LLM, retrieves the function response. Here, we imagine that the response is {"forecast": "sunny"} .

3️⃣ The chatbot app sends the JSON response back to the LLM.

4️⃣ The LLM looks at the JSON response, interprets that information, and eventually replies back with the text that the weather is sunny in Paris.

Each step as code
First, you'll configure the Gemini model as usual:
ChatLanguageModel model = VertexAiGeminiChatModel.builder()
.project(System.getenv("PROJECT_ID"))
.location(System.getenv("LOCATION"))
.modelName("gemini-2.0-flash")
.maxOutputTokens(100)
.build();
You define a tool specification that describes the function that can be called:
ToolSpecification weatherToolSpec = ToolSpecification.builder()
.name("getWeather")
.description("Get the weather forecast for a given location or city")
.parameters(JsonObjectSchema.builder()
.addStringProperty(
"location",
"the location or city to get the weather forecast for")
.build())
.build();
The name of the function is defined, as well as the name and type of the parameter, but notice that both the function and the parameters are given descriptions. Descriptions are very important and help the LLM really understand what a function can do, and thus judge whether this function needs to be called in the context of the conversation.
Let's start step #1, by sending the initial question about the weather in Paris:
List<ChatMessage> allMessages = new ArrayList<>();
// 1) Ask the question about the weather
UserMessage weatherQuestion = UserMessage.from("What is the weather in Paris?");
allMessages.add(weatherQuestion);
In step #2, we pass the tool we'd like the model to use, and the model replies with a too execution request:
// 2) The model replies with a function call request
Response<AiMessage> messageResponse = model.generate(allMessages, weatherToolSpec);
ToolExecutionRequest toolExecutionRequest = messageResponse.content().toolExecutionRequests().getFirst();
System.out.println("Tool execution request: " + toolExecutionRequest);
allMessages.add(messageResponse.content());
Step #3. At this point, we know what function the LLM would like us to call. In the code, we're not making a real call to an external API, we just return an hypothetical weather forecast directly:
// 3) We send back the result of the function call
ToolExecutionResultMessage toolExecResMsg = ToolExecutionResultMessage.from(toolExecutionRequest,
"{\"location\":\"Paris\",\"forecast\":\"sunny\", \"temperature\": 20}");
allMessages.add(toolExecResMsg);
And in step #4, the LLM learns about the function execution result, and can then synthesize a textual response:
// 4) The model answers with a sentence describing the weather
Response<AiMessage> weatherResponse = model.generate(allMessages);
System.out.println("Answer: " + weatherResponse.content().text());
The full source code is in FunctionCalling.java in app/src/main/java/gemini/workshop directory.
Run the sample:
./gradlew run -q -DjavaMainClass=gemini.workshop.FunctionCalling
You should see an output similar to the following:
Tool execution request: ToolExecutionRequest { id = null, name = "getWeatherForecast", arguments = "{"location":"Paris"}" }
Answer: The weather in Paris is sunny with a temperature of 20 degrees Celsius.
You can see in the output above the tool execution request, as well as the answer.
12. LangChain4j handles function calling
In the previous step, you saw how the normal text question/answer and function request/response interactions are interleaved, and in between, you provided the requested function response directly, without calling a real function.
However, LangChain4j also offers a higher-level abstraction that can handle the function calls transparently for you, while handling the conversation as usual.
Single function call
Let's have a look at FunctionCallingAssistant.java , piece by piece.
First, you create a record that will represent the function's response data structure:
record WeatherForecast(String location, String forecast, int temperature) {}
The response contains information about the location, the forecast, and the temperature.
Then you create a class that contains the actual function you want to make available to the model:
static class WeatherForecastService {
@Tool("Get the weather forecast for a location")
WeatherForecast getForecast(@P("Location to get the forecast for") String location) {
if (location.equals("Paris")) {
return new WeatherForecast("Paris", "Sunny", 20);
} else if (location.equals("London")) {
return new WeatherForecast("London", "Rainy", 15);
} else {
return new WeatherForecast("Unknown", "Unknown", 0);
}
}
}
Note that this class contains a single function, but it is annotated with the @Tool annotation which corresponds to the description of the function the model can request to call.
The parameters of the function (a single one here) is also annotated, but with this short @P annotation, which also gives a description of the parameter. You could add as many functions as you wanted, to make them available to the model, for more complex scenarios.
In this class, you return some canned responses, but if you wanted to call a real external weather forecast service, this is in the body of that method that you would make the call to that service.
As we saw when you created a ToolSpecification in the previous approach, it's important to document what a function does, and describe what the parameters correspond to. This helps the model understand how and when this function can be used.
Next, LangChain4j lets you provide an interface that corresponds to the contract you want to use to interact with the model. Here, it's a simple interface that takes in a string representing the user message, and returns a string corresponding to the model's response:
interface WeatherAssistant {
String chat(String userMessage);
}
It is also possible to use more complex signatures that involve LangChain4j's UserMessage (for a user message) or AiMessage (for a model response), or even a TokenStream , if you want to handle more advanced situations, as those more complicated objects also contain extra information such as the number of tokens consumed, etc. But for simplicity sake, we'll just take string in input, and string in output.
Let's finish with the main() method that ties all the pieces together:
public static void main(String[] args) {
ChatLanguageModel model = VertexAiGeminiChatModel.builder()
.project(System.getenv("PROJECT_ID"))
.location(System.getenv("LOCATION"))
.modelName("gemini-2.0-flash")
.build();
WeatherForecastService weatherForecastService = new WeatherForecastService();
WeatherAssistant assistant = AiServices.builder(WeatherAssistant.class)
.chatLanguageModel(model)
.chatMemory(MessageWindowChatMemory.withMaxMessages(10))
.tools(weatherForecastService)
.build();
System.out.println(assistant.chat("What is the weather in Paris?"));
System.out.println(assistant.chat("Is it warmer in London or in Paris?"));
}
As usual, you configure the Gemini chat model. Then you instantiate your weather forecast service that contains the "function" that the model will request us to call.
Now, you use the AiServices class again to bind the chat model, the chat memory, and the tool (ie. the weather forecast service with its function). AiServices returns an object that implements your WeatherAssistant interface you defined. The only thing left is to call the chat() method of that assistant. When invoking it, you will only see the text responses, but the function call requests and the function call responses will not be visible from the developer, and those requests will be handled automatically and transparently. If Gemini thinks a function should be called, it'll reply with the function call request, and LangChain4j will take care of calling the local function on your behalf.
Run the sample:
./gradlew run -q -DjavaMainClass=gemini.workshop.FunctionCallingAssistant
You should see an output similar to the following:
OK. The weather in Paris is sunny with a temperature of 20 degrees.
It is warmer in Paris (20 degrees) than in London (15 degrees).
This was an example of a single function.
Multiple function calls
You can also have multiple functions and let LangChain4j handle multiple function calls on your behalf. Take a look at MultiFunctionCallingAssistant.java for a multiple function example.
It has a function to convert currencies:
@Tool("Convert amounts between two currencies")
double convertCurrency(
@P("Currency to convert from") String fromCurrency,
@P("Currency to convert to") String toCurrency,
@P("Amount to convert") double amount) {
double result = amount;
if (fromCurrency.equals("USD") && toCurrency.equals("EUR")) {
result = amount * 0.93;
} else if (fromCurrency.equals("USD") && toCurrency.equals("GBP")) {
result = amount * 0.79;
}
System.out.println(
"convertCurrency(fromCurrency = " + fromCurrency +
", toCurrency = " + toCurrency +
", amount = " + amount + ") == " + result);
return result;
}
Another function to get the value of a stock:
@Tool("Get the current value of a stock in US dollars")
double getStockPrice(@P("Stock symbol") String symbol) {
double result = 170.0 + 10 * new Random().nextDouble();
System.out.println("getStockPrice(symbol = " + symbol + ") == " + result);
return result;
}
Another function to apply a percentage to a given amount:
@Tool("Apply a percentage to a given amount")
double applyPercentage(@P("Initial amount") double amount, @P("Percentage between 0-100 to apply") double percentage) {
double result = amount * (percentage / 100);
System.out.println("applyPercentage(amount = " + amount + ", percentage = " + percentage + ") == " + result);
return result;
}
You can then combine all these functions and a MultiTools class and ask questions like "What is 10% of AAPL stock price converted from USD to EUR?""
public static void main(String[] args) {
ChatLanguageModel model = VertexAiGeminiChatModel.builder()
.project(System.getenv("PROJECT_ID"))
.location(System.getenv("LOCATION"))
.modelName("gemini-2.0-flash")
.maxOutputTokens(100)
.build();
MultiTools multiTools = new MultiTools();
MultiToolsAssistant assistant = AiServices.builder(MultiToolsAssistant.class)
.chatLanguageModel(model)
.chatMemory(withMaxMessages(10))
.tools(multiTools)
.build();
System.out.println(assistant.chat(
"What is 10% of the AAPL stock price converted from USD to EUR?"));
}
Run it as follows:
./gradlew run -q -DjavaMainClass=gemini.workshop.MultiFunctionCallingAssistant
And you should see the multiple functions called:
getStockPrice(symbol = AAPL) == 172.8022224055534 convertCurrency(fromCurrency = USD, toCurrency = EUR, amount = 172.8022224055534) == 160.70606683716468 applyPercentage(amount = 160.70606683716468, percentage = 10.0) == 16.07060668371647 10% of the AAPL stock price converted from USD to EUR is 16.07060668371647 EUR.
Towards Agents
Function calling is a great extension mechanism for large language models like Gemini. It enables us to build more complex systems often called "agents" or "AI assistants". These agents can interact with the external world via external APIs and with services that can have side effects on the external environment (like sending emails, creating tickets, etc.)
When creating such powerful agents, you should do so responsibly. You should consider a human-in-the-loop before making automatic actions. It's important to keep safety in mind when designing LLM-powered agents that interact with the external world.
13. Running Gemma with Ollama and TestContainers
So far, we've been using Gemini but there's also Gemma , its little sister model.
Gemma is a family of lightweight, state-of-the-art open models built from the same research and technology used to create the Gemini models. The latest Gemma model is Gemma3 available in four sizes: 1B ( text-only ), 4B, 12B and 27B. Their weights are freely available, and their small sizes means you can run it on your own, even on your laptop or in Cloud Shell.
How do you run Gemma?
There are many ways to run Gemma: in the cloud, via Vertex AI with a click of a button, or GKE with some GPUs, but you can also run it locally.
One good option to run Gemma locally is with Ollama , a tool that lets you run small models, like Llama, Mistral, and many others on your local machine. It's similar to Docker but for LLMs.
Install Ollama following the instruction for your Operating System.
If you are using a Linux environment you will need to enable Ollama first after installing it.
ollama serve > /dev/null 2>&1 &
Once installed locally, you can run commands to pull a model:
ollama pull gemma3:1b
Wait for the model to be pulled. This can take some time.
Run the model:
ollama run gemma3:1b
Now, you can interact with the model:
>>> Hello! Hello! It's nice to hear from you. What can I do for you today?
To exit the prompt press Ctrl+D
Running Gemma in Ollama on TestContainers
Instead of having to install and run Ollama locally, you can use Ollama within a container, handled by TestContainers .
TestContainers is not only useful for testing, but you can also use it for executing containers. There's even a specific OllamaContainer you can take advantage of!
Here's the whole picture:

বাস্তবায়ন
Let's have a look at GemmaWithOllamaContainer.java , piece by piece.
First, you need to create a derived Ollama container that pulls in the Gemma model. This image either already exists from a previous run or it will be created. If the image already exists, you're just going to tell TestContainers that you want to substitute the default Ollama image with your Gemma-powered variant:
private static final String TC_OLLAMA_GEMMA3 = "tc-ollama-gemma3-1b";
public static final String GEMMA_3 = "gemma3:1b";
// Creating an Ollama container with Gemma 3 if it doesn't exist.
private static OllamaContainer createGemmaOllamaContainer() throws IOException, InterruptedException {
// Check if the custom Gemma Ollama image exists already
List<Image> listImagesCmd = DockerClientFactory.lazyClient()
.listImagesCmd()
.withImageNameFilter(TC_OLLAMA_GEMMA3)
.exec();
if (listImagesCmd.isEmpty()) {
System.out.println("Creating a new Ollama container with Gemma 3 image...");
OllamaContainer ollama = new OllamaContainer("ollama/ollama:0.7.1");
System.out.println("Starting Ollama...");
ollama.start();
System.out.println("Pulling model...");
ollama.execInContainer("ollama", "pull", GEMMA_3);
System.out.println("Committing to image...");
ollama.commitToImage(TC_OLLAMA_GEMMA3);
return ollama;
}
System.out.println("Ollama image substitution...");
// Substitute the default Ollama image with our Gemma variant
return new OllamaContainer(
DockerImageName.parse(TC_OLLAMA_GEMMA3)
.asCompatibleSubstituteFor("ollama/ollama"));
}
Next, you create and start an Ollama test container and then create an Ollama chat model, by pointing at the address and port of the container with the model you want to use. Finally, you just invoke model.generate(yourPrompt) as usual:
public static void main(String[] args) throws IOException, InterruptedException {
OllamaContainer ollama = createGemmaOllamaContainer();
ollama.start();
ChatLanguageModel model = OllamaChatModel.builder()
.baseUrl(String.format("http://%s:%d", ollama.getHost(), ollama.getFirstMappedPort()))
.modelName(GEMMA_3)
.build();
String response = model.generate("Why is the sky blue?");
System.out.println(response);
}
Run it as follows:
./gradlew run -q -DjavaMainClass=gemini.workshop.GemmaWithOllamaContainer
The first run will take a while to create and run the container but once done, you should see Gemma responding:
INFO: Container ollama/ollama:0.7.1 started in PT7.228339916S
The sky appears blue due to Rayleigh scattering. Rayleigh scattering is a phenomenon that occurs when sunlight interacts with molecules in the Earth's atmosphere.
* **Scattering particles:** The main scattering particles in the atmosphere are molecules of nitrogen (N2) and oxygen (O2).
* **Wavelength of light:** Blue light has a shorter wavelength than other colors of light, such as red and yellow.
* **Scattering process:** When blue light interacts with these molecules, it is scattered in all directions.
* **Human eyes:** Our eyes are more sensitive to blue light than other colors, so we perceive the sky as blue.
This scattering process results in a blue appearance for the sky, even though the sun is actually emitting light of all colors.
In addition to Rayleigh scattering, other atmospheric factors can also influence the color of the sky, such as dust particles, aerosols, and clouds.
You have Gemma running in Cloud Shell!
১৪. অভিনন্দন
Congratulations, you've successfully built your first Generative AI chat application in Java using LangChain4j and the Gemini API! You discovered along the way that multimodal large language models are pretty powerful and capable of handling various tasks like question/answering, even on your own documentation, data extraction, interacting with external APIs, and more.
এরপর কী?
It's your turn to enhance your applications with powerful LLM integrations!
আরও পড়ুন
- Generative AI common use cases
- Training resources on Generative AI
- Interact with Gemini through Generative AI Studio
- দায়িত্বশীল এআই