ইন্টেলিজেন্ট অ্যাপ

Android ইন্টেলিজেন্স সিস্টেমের সাথে আপনার অ্যাপ ইন্টিগ্রেট করা

স্পিকার

  • সারা শামসি

    Google-এর প্রোডাক্ট ম্যানেজার

  • Dan Galpin

    ডেভেলপার রিলেশনস ইঞ্জিনিয়ার

সম্পর্কে

টক সম্পর্কে

Android একটি ইন্টেলিজেন্স সিস্টেমে পরিণত হচ্ছে যেখানে Gemini-র মতো এজেন্ট বিভিন্ন অ্যাপ জুড়ে টাস্ক অর্কেস্ট্রেট করে। নতুন ব্যবহারকারীর অভিজ্ঞতা আনলক করতে এবং এইসব এজেন্টিক ওয়ার্কফ্লোতে অংশগ্রহণ করতে, আপনার অ্যাপকে অবশ্যই এর মূল ক্ষমতা প্রকাশ করতে হবে।

এই সেশনটি Android-এর ইন্টেলিজেন্স লেয়ারের সাথে আপনার অ্যাপ ইন্টিগ্রেট করার একটি প্র্যাক্টিক্যাল গাইড। আমরা মূল ইন্টিগ্রেশন পাথ সমীক্ষা করব - যার মধ্যে অন-ডিভাইস Android MCP, সার্ভার-সাইড MCP এবং টাস্ক অটোমেশন অন্তর্ভুক্ত - এবং আপনার ব্যবহারের ক্ষেত্রে সঠিক পাথ বেছে নেওয়ার জন্য একটি সিদ্ধান্ত ফ্রেমওয়ার্ক প্রদান করব। এছাড়াও, আমরা ব্যক্তিগত প্রসঙ্গের সাথে প্রোঅ্যাক্টিভ অভিজ্ঞতা এক্সপ্লোর করব।

আপনি শিখবেন কীভাবে:

  • আপনার অ্যাপের প্রয়োজনীয়তা অনুযায়ী সবচেয়ে উপযুক্ত ইন্টিগ্রেশন পাথ বেছে নিন।
  • রিঅ্যাক্টিভ ও প্রোঅ্যাক্টিভ ইন্টেলিজেন্স ফিচার, দুটিই প্রয়োগ করুন।
  • এজেন্টিক ইন্টিগ্রেশনে পারফর্ম্যান্স ও নিরাপত্তার মধ্যে ভারসাম্য বজায় রাখুন।

সারাংশ

  • Android MCP হল একটি ইউনিফায়েড অন-ডিভাইস টুল কলিং ফ্রেমওয়ার্ক যা Jetpack AppFunctions লাইব্রেরি দ্বারা পরিচালিত। এটি MCP প্রিমিটিভকে Kotlin কনস্ট্রাক্টে ম্যাপ করে এবং Android Binder IPC-তে রান করে।
  • এনক্রিপ্ট করা ডেটা বা ডিভাইস সেন্সর অ্যাক্সেস করার সময় অন-ডিভাইস টুল আরও ভাল কাজ করে। বিকল্প হিসেবে, প্ল্যাটফর্ম-চালিত টাস্ক অটোমেশন প্রি-লোড করা এজেন্টদের কোড পরিবর্তন ছাড়াই টাচ ইভেন্ট সিমুলেট করতে দেয়।
  • এজেন্ট টুল খুঁজে বের করে কল করার সময় আপনার অ্যাপ Android MCP সার্ভার হিসেবে কাজ করে। AppFunctionDeclaration-এর মাধ্যমে টুল ঘোষণা করুন, KDoc বিবরণ প্রদান করুন এবং Kotlin-এর ডিফল্ট ভ্যালুর উপর নির্ভর করা এড়িয়ে চলুন।
  • Connected Apps Platform, বর্তমানে প্রিভিউতে রয়েছে, এটি আপনাকে Gemini-র সাথে আপনার AppFunctions পরীক্ষা করতে দেয়। Google Connected Console-এ আপনার কানেক্ট করা অ্যাপ সেট-আপ করুন, তারপর Play Console-এ আপনার বান্ডেল আপলোড করুন।

রিসোর্স

Gemini Notebook

বিষয়

  • android-mcp
  • appfunctions
  • gemini
  • কানেক্ট করা অ্যাপ
  • model-context-protocol
  • on-device-ai

ট্রান্সস্ক্রিপ্ট করা

ইন্টেলিজেন্স যুগের সূচনা

  • ঠিক আছে।

  • হ্যালো, সবাইকে স্বাগত।

  • আমি ড্যান গ্যালপিন, ডেভেলপার রিলেশনস টিমে কাজ করি।

  • এবং আমি সারা শামসি, Android-এর ডেভেলপার ইন্টেলিজেন্স ফ্রেমওয়ার্কে কর্মরত একজন প্রোডাক্ট ম্যানেজার।

  • আজকের সেশনে আপনাকে স্বাগত জানাতে পেরে আমরা অত্যন্ত আনন্দিত।

  • ব্যবহারকারীরা তাদের ডিভাইসের সাথে কীভাবে ইন্টার‍্যাক্ট করছেন সেই বিষয়ে একটি মৌলিক পরিবর্তন আমরা লক্ষ্য করছি।

  • ওয়েব যুগে, ব্যবহারকারীরা সাধারণত ওয়েব ব্রাউজারে গিয়ে এবং তারপরে সার্চ ফাংশন ব্যবহার করে তাদের যাত্রা শুরু করেন।

  • তারপর, মোবাইল যুগে, আমরা ব্যবহারকারীর আচরণে পরিবর্তন দেখেছি, তারা এককালীন অ্যাকশন সম্পূর্ণ করার জন্য আরও বিশেষ, নেটিভ অ্যাপ ব্যবহার করতে শুরু করেছেন।

  • এখানে একটি উদাহরণ দেওয়া হল।

  • আমার বন্ধু আমাকে ডিনারে আমন্ত্রণ জানিয়ে মেসেজ পাঠিয়েছে।

  • এর উত্তর দিতে, আমি ফ্রি আছি কিনা তা দেখার জন্য আমাকে ক্যালেন্ডার চেক করতে হবে, ক্যালেন্ডারে ইভেন্ট যোগ করতে হবে, সেখানে যাওয়ার জন্য রাইড বুক করতে হবে এবং তারপর বন্ধুকে মেসেজ করে জানাতে হবে যে আমি যেতে পারব।

  • আগে, এর জন্য অ্যাপ-হপিং নামে একটি পদ্ধতি ব্যবহার করতে হত, যেখানে আমাকে মূলত আমার ডিভাইসে বিভিন্ন অ্যাপের মধ্যে টগল করতে হত যাতে আমি আসলে অ্যাকশনগুলি সম্পূর্ণ করতে পারি এবং আমার যাত্রা পূরণ করতে পারি।

  • কিন্তু এখন আমরা ইন্টেলিজেন্স যুগে আছি।

  • ইন্টেলিজেন্স যুগে, ব্যবহারকারীরা তাদের প্রয়োজনীয় নির্দিষ্ট অ্যাকশনের পরিবর্তে উচ্চ-পর্যায়ের উদ্দেশ্য এবং লক্ষ্য প্রকাশ করে তাদের যাত্রা শুরু করছেন।

  • ব্যবহারকারীদের দৃষ্টিভঙ্গিতে একটি মৌলিক পরিবর্তন এসেছে, যেখানে তারা তাদের ডিভাইসকে এমন কিছু হিসেবে দেখছেন যা আসলে তাদের প্রয়োজনগুলি আগে থেকেই অনুমান করে এবং তাদের লক্ষ্য পূরণ করতে সাহায্য করে, শুধু অ্যাকশন নেওয়ার জন্য নয়।

  • আমরা লক্ষ্য করেছি যে, তারা আশা করছেন যে এই ইন্টেলিজেন্স ফ্রেমওয়ার্কটি শুধুমাত্র তাদের মোবাইল ডিভাইসেই নয়, বরং স্মার্টওয়াচ, অটো, টিভি, ল্যাপটপ এবং XR ও চশমা সহ একটি সমৃদ্ধ ফর্ম ফ্যাক্টর ইকোসিস্টেম জুড়ে কাজ করবে।

  • ইন্টেলিজেন্স যুগে, একটি ক্রস-অ্যাপ প্রম্পট আসলে আগে থেকে ডিনারের আমন্ত্রণ জানানোর উদাহরণকে সহজতর করতে পারে।

  • আমার ডিভাইসে থাকা এজেন্ট, একটি প্রম্পটের মাধ্যমেই, আমার ক্যালেন্ডার চেক করা, ইভেন্ট যোগ করা, রাইড বুক করা এবং মেসেজ পাঠানোর মতো প্রয়োজনীয় সব অ্যাকশন একবারে চালু করতে সাহায্য করতে পারে।

  • আজ, আমরা এই স্পেসে Android-এর প্ল্যান সম্পর্কে আরও তথ্য শেয়ার করব এবং তারপর আপনি ডেভেলপার হিসেবে কীভাবে ফার্স্ট-ক্লাস অভিজ্ঞতা তৈরি করতে পারেন তা জানাব।

  • ড্যান।

  • ধন্যবাদ, সারা।

  • তাই, Android তার মূল অংশে ইন্টেলিজেন্স যোগ করা চালিয়ে যাচ্ছে এবং এটি আপনার অ্যাপের জন্য নতুন উপায় অফার করছে, আপনি জানেন, এই ইন্টেলিজেন্স যুগের জন্য প্রস্তুত।

  • And the first of these two developer intelligence pillars is in-app intelligence, and that's about obviously making your apps smarter, bringing intelligence into your app, and you can use on-device models like Gemini Nano 4 through ML Kit to summarize message threads, and rewrite replies, and understand images, or use natural language to call your app's functions.

  • এমনকি আপনি Firebase AI Logic-এর মাধ্যমে সরাসরি আপনার অ্যাপে ক্লাউডে Gemini মডেল ব্যবহার করতে পারেন।

  • তবে, আজ আমাদের ফোকাস হল দ্বিতীয় স্তম্ভের উপর, অর্থাৎ আপনার অ্যাপের ক্ষমতা ইন্টেলিজেন্স সিস্টেমের মধ্যে উপলভ্য আছে কিনা তা নিশ্চিত করা।

  • তাহলে, ইন্টেলিজেন্স সিস্টেম কী?

  • তাই, সারা আগে যে প্রম্পট করা অভিজ্ঞতার কথা বলেছিলেন, সেগুলি সম্পর্কে কথা বলা এবং হোস্ট করার পাশাপাশি, এটি এমন একটি সিস্টেম যা অ্যাপ ইকোসিস্টেম জুড়ে অভিজ্ঞতা উন্নত করার জন্য গভীর ইন্টিগ্রেশনকে সক্ষম করে।

  • তাই, Gemini ইন্টেলিজেন্সের জন্য ডিজাইন করা ডিভাইস, যেমন, সাজেশন, সংক্ষিপ্ত বিজ্ঞপ্তি, উন্নত ভয়েস ইনপুট এবং অটোমেটিক ফর্ম পূরণ করার মতো সুবিধা প্রদান করতে এইসব ইন্টিগ্রেশন পয়েন্ট ব্যবহার করে।

  • এখন, সাজেশন হল প্রোঅ্যাক্টিভ অ্যাসিস্ট্যান্স নামক ফিচারের অংশ।

  • আর এখন পর্যন্ত, আমরা প্রম্পট করা সহায়তা সম্পর্কে কথা বলছি।

  • আমরা সবাই এতে অভ্যস্ত, যেখানে একজন ব্যবহারকারী একজন এজেন্টকে তার হয়ে এক বা একাধিক কাজ সম্পন্ন করতে বলেন।

  • তবে প্রোঅ্যাক্টিভ অ্যাসিস্ট্যান্স আসলে ব্যবহারকারীর পরবর্তী অ্যাকশন বা অ্যাকশনগুলি অনুমান করার সাথে সম্পর্কিত।

  • তাই, ডিনারের উদাহরণে ফিরে গিয়ে বলা যায়, মেসেজের উত্তরে এজেন্ট ব্যবহারকারীকে আগাম সাজেশন দিতে পারে, যার মাধ্যমে তিনি ক্যালেন্ডারে আমন্ত্রণ যোগ করতে বা রাইডশেয়ার বুক করতে পারেন।

  • কিন্তু এটি আসলে কীভাবে ব্যবহারকারীকে প্রম্পট করে?

  • প্রোঅ্যাক্টিভ অ্যাসিস্ট্যান্স লক স্ক্রিন, বিজ্ঞপ্তি, ভার্চুয়াল কীবোর্ড এবং এমনকি বেছে নেওয়া অ্যাপের মধ্যেও অ্যাকশন দেখাতে পারে।

  • তাহলে, আপনার অ্যাপে ট্রাফিক নিয়ে আসার জন্য এই প্রোঅ্যাক্টিভ চিপে আপনার অ্যাপ দেখানোর উপায় কী?

  • আপনি ইতিমধ্যেই লেটেস্ট SDK ভার্সন এবং আমাদের সবচেয়ে আধুনিক টুল ব্যবহার করে অ্যাপ তৈরি করছেন, তবে এখন আপনি সবচেয়ে ভালো যে কাজটি করতে পারেন তা হল আপনার অ্যাপটি প্রম্পট করা সহায়তার জন্য প্রস্তুত কিনা তা নিশ্চিত করা, যার ব্যাপারে সারা পরে কথা বলবেন।

প্রম্পট করা সহায়তা ও Android MCP

  • ধন্যবাদ, ড্যান।

  • ঠিক আছে, তাই প্রম্পট করা সহায়তার মধ্যে ব্যবহারকারীরা সরাসরি কোনও এজেন্টের কাছে যান এবং এটিকে টাস্ক শুরু ও অর্কেস্ট্রেট করতে বলেন, এমনকি অ্যাপ জুড়েও।

  • এটি আগে ম্যানুয়ালি করা এবং সময়সাপেক্ষ ওয়ার্কফ্লো অটোমেট করতে পারে।

  • Android-এ আমাদের ইন্টেলিজেন্স সিস্টেমের জন্য, আমরা মূলত Gemini-র মতো সিস্টেম এজেন্টের উপর ফোকাস করছি যা ব্যবহারকারীর ডিভাইস জুড়ে অ্যাপে ট্যাপ করতে পারে।

  • এই অভিজ্ঞতায় আপনার অ্যাপ দেখানোর এবং সিস্টেম এজেন্টের সাথে ইন্টার‍্যাক্ট করার কয়েকটি আলাদা উপায় আছে।

  • আমাদের প্ল্যাটফর্ম-চালিত টাস্ক অটোমেশন সমাধান রয়েছে এবং তারপরে সিস্টেম এজেন্টে সরাসরি API ইন্টিগ্রেশন রয়েছে।

  • আমরা সবসময় সাজেস্ট করি যে যেখানে সম্ভব সেখানে ডাইরেক্ট API ইন্টিগ্রেশন ব্যবহার করে তৈরি করুন।

  • এগুলি ডেভেলপার-নিয়ন্ত্রিত, তাই আপনার অ্যাপ ও ব্র্যান্ড আসলে কীভাবে দেখানো হবে তা আপনি সিদ্ধান্ত নিতে ও নিয়ন্ত্রণ করতে পারবেন।

  • এছাড়াও, এগুলি আরও দ্রুত ও নির্ভরযোগ্য এবং আপনার ব্যবহারকারীদের আরও প্রিমিয়াম অভিজ্ঞতা প্রদান করে।

  • এজেন্টের সাথে ইন্টিগ্রেট করতে চাইলে, আপনি দুটি API পাথ বিবেচনা করতে পারেন।

  • মডেল কন্টেক্সট প্রোটোকল বা সংক্ষেপে MCP হল সার্ভার-সাইড ফাংশন কলিংয়ের জন্য ইন্ডাস্ট্রির সেরা পদ্ধতি।

  • তবে অন-ডিভাইস টুল কলিংয়ের জন্য, আমরা আনুষ্ঠানিকভাবে Android MCP-এর ঘোষণা করছি, যা Android-এ ইউনিফায়েড ফাংশন কলিং ফ্রেমওয়ার্ক।

  • Android MCP আমাদের Jetpack AppFunctions লাইব্রেরি দ্বারা পরিচালিত হয়, যা প্রযুক্তিগত এবং ধারণাগতভাবে MCP-এর সাথে ঘনিষ্ঠভাবে সারিবদ্ধ, পাশাপাশি একটি পরিচিত এবং ইডিওমেটিক Kotlin ইমপ্লিমেন্টেশনকে সমর্থন করে যার সাথে অনেক Android ডেভেলপার পরিচিত।

  • এর ফলে API প্রয়োগ করা তুলনামূলকভাবে সহজ ও সাবলীল হয় এবং আগে থেকে সেট-আপ করা সার্ভার-সাইড MCP ইন্টিগ্রেশন থেকে আপনি যা শিখেছেন তা কাজে লাগাতে পারেন।

  • অনেক MCP প্রিমিটিভ, Jetpack AppFunctions কনস্ট্রাক্টে ওয়ান-টু-ওয়ান ম্যাপ করে।

  • এটি ব্যাখ্যা করার জন্য কিছু উদাহরণ দেখে নেওয়া যাক।

  • একটি MCP টুল এন্ড পয়েন্ট AppFunctionDeclaration-এর সাথে অ্যানোটেট করা Kotlin ফাংশনে সুন্দরভাবে ম্যাপ করে।

  • আপনার pydantic মডেল, AppFunctionSerializable-এর সাথে অ্যানোটেট করা Kotlin ডেটা ক্লাসে ম্যাপ করে।

  • তবে, স্ট্রিমেবল HTTP এবং/অথবা স্ট্যান্ডার্ড I/O ট্রান্সপোর্টের পরিবর্তে, Android MCP অপ্টিমাইজ করা Android Binder IPC-তে রান করে।

  • আজ, আমরা অত্যন্ত আনন্দের সাথে জানাচ্ছি যে Jetpack লাইব্রেরি, AppFunctions, এখন বিটা ভার্সনে উপলভ্য।

Android MCP-এর মূল ব্যবহারিক ক্ষেত্র

  • ঠিক আছে, আমি নিশ্চিত যে আপনি সম্ভবত ভাবছেন যে কখন একটি API পাথ ব্যবহার করা যুক্তিযুক্ত এবং কখন Android MCP ব্যবহার করা যুক্তিযুক্ত, বিশেষ করে।

  • একটি সামগ্রিক নীতি হিসেবে, ব্যবহারকারীর অভিজ্ঞতা সম্পূর্ণ করার জন্য অন-ডিভাইস বা লোকাল প্রসঙ্গ ব্যবহার করতে চাইলে, Android MCP বিশেষভাবে উপযোগী।

  • Android MCP-এর কিছু উদাহরণ দেখা যাক যেখানে এটি সত্যিই দারুণ কাজ করে।

  • বিশেষ করে, স্থানীয়, এনক্রিপ্ট করা ডেটা অ্যাক্সেস করার প্রয়োজন হলে, Android MCP ব্যবহার করার কথা বিবেচনা করা উচিত।

  • মেসেজিং ও কমিউনিকেশন অ্যাপের ক্ষেত্রে এটি বিশেষভাবে গুরুত্বপূর্ণ, যেখানে এন্ড-টু-এন্ড এনক্রিপশনের কারণে মেসেজের ইতিহাস ডিভাইসে রাখতে হয়।

  • Android MCP, সম্পূর্ণভাবে অন-ডিভাইস এবং অফলাইন অ্যাকশনের জন্যও উপযোগী, যেখানে এজেন্টরা এটিতে সহায়তা করে।

  • আমাদের Wear OS পার্টনার, Samsung Health, দৌড়ানো ও ব্যায়াম শুরু করার জন্য একটি ফাংশন চালু করেছে, যা সম্পূর্ণভাবে অন-ডিভাইসে সাজানো হয়েছে।

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

  • আপনি যদি ডিভাইসের সেন্সর অ্যাক্সেস করতে চান বা ডিভাইসের হার্ডওয়্যারের সাথে সরাসরি ইন্টার‍্যাক্ট করতে চান, তাহলে Android MCP হল পছন্দের API পাথ।

  • যেমন, অনেক অটোমোটিভ অ্যাপ ব্যবহারকারীদের UWB কানেক্টিভিটির মাধ্যমে এবং প্রায়ই হ্যান্ডস-ফ্রি পদ্ধতিতে তাদের গাড়ির দরজা আনলক করার অনুমতি দিতে চায়।

  • তাই, Android MCP এই বিষয়ে সাহায্য করতে পারে।

  • এবং অবশেষে, আপনার অ্যাপ বা API-এর জন্য কোনও উপলভ্য ক্লাউড এন্ডপয়েন্ট না থাকলে এটি আপনার সমাধান।

  • আমাদের ডিভাইস প্রস্তুতকারকের অনেক ফার্স্ট-পার্টি অ্যাপ শুধুমাত্র ক্লায়েন্ট-সাইড, তাই তারা এই কারণে Android MCP ব্যবহার করছে।

  • যেমন, Xiaomi Gallery-র মতো শুধুমাত্র ক্লায়েন্ট-সাইড গ্যালারি অ্যাপ থেকে ফটো সার্চ ও রিট্রিভ করার জন্য Gemini ব্যবহার করা যেতে পারে।

  • এখানে, আমি Gemini-কে বলতে পারি, "গত সপ্তাহে Shaomi Xiaomi Gallery-তে Android বটের যে ফটো তুলেছিলাম, সেটি খুঁজে দাও।"

  • Xiaomi ফটো সার্চ করার জন্য একটি অ্যাপ ফাংশন প্রকাশ করেছে, যা Gemini কল করে এবং প্রাসঙ্গিক ফটোগুলি সরাসরি Gemini অ্যাপ UI-তে ফেরত আসে এবং সারফেস করে, যাতে ব্যবহারকারীকে কখনই অ্যাপ ছেড়ে যেতে না হয়।

  • এবং আমরা Android OS-এ অন-ডিভাইস অ্যাকশনের জন্য একই আর্কিটেকচারের উপর নির্ভর করছি।

  • Android MCP, ডিভাইস সহায়তা ফিচারকে শক্তিশালী করে যা সম্প্রতি লঞ্চ করা হয়েছে। এটি Gemini অ্যাপে সরাসরি Android OS ইন্টিগ্রেশন।

  • এই ক্ষমতা ইতিমধ্যেই Pixel এবং Lenovo ডিভাইসে লঞ্চ করা হয়েছে এবং এটি বর্তমানে আরও বিস্তৃত Android OEM ইকোসিস্টেম জুড়ে স্কেল আউট হচ্ছে।

  • ব্যবহারকারীরা Gemini-তে গিয়ে ডিভাইস সংক্রান্ত অভিজ্ঞতা উন্নত করার জন্য কীভাবে তাদের সেটিংস, ডিভাইস সেটিংস এবং সিস্টেম সেটিংস অপ্টিমাইজ করতে পারেন তা জিজ্ঞাসা করে ডিভাইস সহায়তা ব্যবহার করতে পারেন।

  • যেমন, বিভিন্ন মেনু খুঁজে দেখার পরিবর্তে, বর্ণান্ধ ব্যবহারকারী Gemini-কে বলতে পারেন, "আমি বর্ণান্ধ।

  • এই ফোনটি আমার ব্যবহারের জন্য আরও সুবিধাজনক করে তোলার উপায় কী?"

  • Gemini লোকাল অ্যাপ ফাংশন কল ও ইনভোক করবে এবং অনুরোধের মাধ্যমে বুদ্ধিমত্তার সাথে কারণ জানাবে এবং ব্যবহারকারী যাতে তার সিস্টেম সেটিংস পরিবর্তন করতে পারেন তার জন্য প্রকৃত উপায়ের সাজেশন দেবে এবং অভিজ্ঞতা উন্নত করতে তার ডিভাইস সেটিংস পরিবর্তন করার নির্দিষ্ট উপায় জানাবে।

  • তারপর, অ্যাপ-মধ্যস্থ কন্ট্রোল সরাসরি Gemini অ্যাপ UI-তে দেখানো হয়, যাতে ব্যবহারকারীরা অবিলম্বে কন্ট্রোল করতে পারেন।

  • যেমন, কালারব্লাইন্ড অপ্টিমাইজেশনের অনুরোধ পূরণ করার জন্য, Gemini আসলে এই রঙ-সংশোধন টগল দেখিয়েছে, যাতে ব্যবহারকারীরা চটপট পরিবর্তন করতে পারেন।

  • Android MCP অভিজ্ঞতা আগে থেকেই বাছাই করা ডিভাইসে উপলভ্য এবং Android ইকোসিস্টেমে আরও বেশি ব্যবহারের কেস নিয়ে আসার জন্য আমরা একাধিক পার্টনারের সাথে কাজ করছি।

  • যদিও Android MCP টুলের স্পষ্ট ঘোষণা আমাদের গোল্ড স্ট্যান্ডার্ড, আমরা জানি যে এমন কিছু ঘটনা ঘটবে যেখানে ব্যবহারকারীরা এজেন্টদের জটিল বা কম-সাধারণ ব্যবহারের কেস সম্পূর্ণ করতে বলবেন এবং এখানেই আমাদের প্ল্যাটফর্ম-চালিত টাস্ক অটোমেশন ফিচারটি কাজে আসে।

  • এই মডেলের অধীনে, OEM প্রি-লোড করা এজেন্ট অ্যাপ সারফেসে টাচ ইভেন্ট সিমুলেট করতে Android-এর কম্পিউটার-কন্ট্রোল API ব্যবহার করতে পারে।

  • এর জন্য ডেভেলপারদের কোনও কোড পরিবর্তন করতে হয় না এবং অ্যাপ স্ক্রিন বিশ্লেষণ করে ব্যবহারকারীর মতো এজেন্টকে UI-এর মাধ্যমে ট্যাপ করতে হয়।

  • ব্যবহারকারীর নিয়ন্ত্রণ এবং নিরাপত্তা নিশ্চিত করতে, পেমেন্ট ফ্লোর মতো সম্ভাব্য সংবেদনশীল অ্যাকশনে এজেন্টদের পজ করা এবং এগিয়ে যাওয়ার জন্য ব্যবহারকারীর কনফার্মেশন চাওয়া অত্যন্ত গুরুত্বপূর্ণ।

  • আসুন, একটি উদাহরণ দেখে নেওয়া যাক।

  • এখানে, একজন ব্যবহারকারী কফি এবং চকোলেট ট্যুরের একটি ফটো তুলেছেন এবং তিনি Expedia-কে জিজ্ঞাসা করতে চান, অথবা তিনি Gemini-কে Expedia-তে দেখতে বলতে চান যে তারা একই ধরনের ট্যুর খুঁজে পেতে পারে কিনা, তবে অংশগ্রহণকারীর সংখ্যা ছয়জনে অ্যাডজাস্ট করতে চান।

  • সরাসরি API ইন্টিগ্রেশনের প্রয়োজন ছাড়াই, Gemini ব্যাকগ্রাউন্ডে Expedia-র UI-তে ঠিক সেইভাবেই নেভিগেট করে যেভাবে একজন ব্যবহারকারী করেন।

  • এটি তারপরে প্রাসঙ্গিক বিকল্পগুলি দেখায় এবং চূড়ান্ত কনফার্মেশন ও বুকিংয়ের সময় ব্যবহারকারীকে নির্বিঘ্নে হ্যান্ড অফ করে।

  • এটি বোঝা গুরুত্বপূর্ণ যে এই তিনটি ভিন্ন পথ একে অপরের থেকে আলাদা নয় এবং সেগুলি আসলে একসাথে আরও ভাল কাজ করে।

  • ব্যবহারকারী এবং ডেভেলপারদের আশা করা উচিত যে এজেন্টরা সার্ভার ইমপ্লিমেন্টেশন এবং লোকাল Android অ্যাপ জুড়ে টুলগুলি সমন্বয় করতে সক্ষম হবে যাতে ব্যবহারকারীদের সবচেয়ে ব্যাপক এবং সামগ্রিক অভিজ্ঞতা প্রদান করা যায়।

Android MCP-এর টেকনিক্যাল দিক সম্পর্কে বিস্তারিত তথ্য

  • ঠিক আছে।

  • ধন্যবাদ, সারা।

  • আমরা Android MCP সম্পর্কে আরও বিশদে জানব।

  • এবং যখন সম্ভব, আমি চাই আপনি জানুন যে আমি Jetpacker থেকে MCP কোড স্নিপেটগুলি টানছি, যা আমাদের ভ্রমণ স্যাম্পেল অ্যাপ, যা আসলে Antigravity-এর সাহায্যে তৈরি করা হয়েছে।

  • আপনি এটি মূল বক্তব্যে দেখেছেন।

  • এতে ML Kit Gen AI এবং ML Kit স্পিচ রিকগনিশন API-এর সাথে অন-ডিভাইস ইন্টিগ্রেশন সহ সব ধরনের দুর্দান্ত ইন্টেলিজেন্ট ফিচার রয়েছে।

  • এখন Android MCP এবং ক্লাউড-ভিত্তিক MCP-এর একই ধরনের স্ট্রাকচার আছে।

  • উভয়েরই ক্লায়েন্ট ও সার্ভার আছে।

  • এখন MCP-এর দুনিয়ায়, সার্ভার টুল প্রদান করে এবং ক্লায়েন্ট এইসব টুল খুঁজে পেতে ও কল করতে পারে।

  • এখন Android MCP-এর মাধ্যমে, আপনার অ্যাপ হল সার্ভার এবং এজেন্ট হল ক্লায়েন্ট।

  • Android প্ল্যাটফর্ম টুল রেজিস্ট্রি হিসেবে কাজ করে।

  • এখন অ্যাপগুলি সার্ভার প্রদান করার জন্য একটি পরিষেবা রেজিস্টার করার মাধ্যমে শুরু হয় এবং আমরা এখানে আমাদের স্যাম্পেলে AppFunctions Jetpack লাইব্রেরি ডিপেন্ডেন্সি যোগ করার মাধ্যমে শুরু করব।

    implementation(libs.androidx.appfunctions)
    ksp(libs.androidx.appfunctions.compiler)
  • এবং তারপর আমরা AppFunctionService তৈরি করতে KSP অ্যানোটেশন ব্যবহার করব।

  • এখন এই পরিষেবার অর্থ হল আপনার অ্যাপের ফাংশন আসলে Android লাইফসাইকেলকে সঠিকভাবে সম্মান করতে পারে।

  • আমাদের AppFunctions ব্যবহার করতে পারে এমন ডেটা রিপোজিটরি ইনজেক্ট করতে আমরা Hilt-এর সাথে ডিপেন্ডেন্সি ইনজেকশন ব্যবহার করতে পারি।

    @RequiresApi(36)
    @AndroidEntryPoint
    @AppFunctionServiceEntryPoint(
        serviceName = "JetPackerAppFunctionService",
        appFunctionXmlFileName = "jetpacker_app_function_service"
    )
    abstract class BaseJetPackerAppFunctionService : AppFunctionService() {
        @Inject internal lateinit var tripDao: TripDao
        @Inject internal lateinit var expenseDao: ExpenseDao
        ...
  • দুঃখিত।

  • আমাদের ডায়াগ্রামে ফিরে আসা যাক।

  • আমাদের Android MCP সার্ভারের জন্য পরিষেবা সেট-আপ করার পরে, অ্যাপটি AppFunctions-এর আকারে টুল রেজিস্টার করে, সাথে টুল দ্বারা ব্যবহৃত যেকোনও সংশ্লিষ্ট ডেটা মডেলও রেজিস্টার করে।

  • এখন আপনি AppFunctionSerializable অ্যানোটেশন ব্যবহার করে ডেটা মডেল রেজিস্টার করতে পারবেন এবং এখানে এই KDoc বেশ ভালো।

    /** Represents an event in a trip itinerary. */
    @AppFunctionSerializable(isDescribedByKDoc = true)
    data class TimelineEventSerializable(
      /** The event's unique identifier. */
      val id: String,
      /** The trip ID this event belongs to. */
      val tripId: String,
      /** The type of event (e.g., TRANSPORTATION, ACCOMMODATION, FOOD_AND_DRINK). */
      val type: String,
      /** The timestamp of the event. */
      val timestamp: Long,
      /** The title of the event. */
      val title: String,
      /** The location of the event. */
      val location: String,
      /** A description of the event. */
      val description: String?,
      /** The title of the trip this event belongs to. */
      val tripTitle: String? = null,
      /** Optional title of a matched itinerary event when added as a reminder. */
      val matchedEventTitle: String? = null,
      /** Execution status of the tool call (e.g., SUCCESS). */
      val status: String? = "SUCCESS",
      /** Human-readable completion message instructing the agent not to repeat the call. */
      val message: String? = null,
    )
  • এটি মেটাডেটা হিসেবে ব্যবহার করা হবে যা এজেন্টকে মডেলটি কীভাবে ব্যবহার করতে হবে তা বুঝতে সাহায্য করবে।

  • আপনার ফাংশনকে টুল হিসেবে রেজিস্টার করতে আপনি AppFunctionDeclaration অ্যানোটেশন ব্যবহার করতে পারেন।

    @AppFunctionDeclaration(isDescribedByKDoc = true)
    suspend fun getItinerary(tripId: String? = null): List<TimelineEventSerializable> {
      return withContext(Dispatchers.IO) {
        val trip = resolveTrip(tripId = tripId)
        val targetTripId = trip?.id ?: ActiveTripSession.activeTripId ?: tripId?.takeIf { it.isNotBlank() } ?: "2026-1"
        val dbEvents = eventDao.getEventsForTrip(targetTripId).first()
        val events =
          if (dbEvents.isNotEmpty()) { dbEvents } else {
            val fallback = DummyData.events.filter { it.tripId == targetTripId }
            if (fallback.isNotEmpty()) { eventDao.insertEvents(fallback) }
            fallback
          }
        events.map { it.toSerializable(tripTitle = trip?.title) }
      }
    }
  • এবং isDescribedByKDoc true হিসেবে সেট করা থাকলে, KDoc এজেন্টকে একটি বিবরণ প্রদান করে যাতে আপনার ফাংশন কী করে তা বোঝা যায় এবং এটি মানুষকেও এটি বুঝতে সাহায্য করে।

    /**
     * Retrieves the itinerary events (with their eventId, title, type, and location) for a specific
     * trip. Note: getItinerary returns event summaries only; call getEventDetails(eventId) with a
     * specific event's ID to retrieve ticket prices, hotel nightly rates, baggage allowance, or
     * visitor tips.
     *
     * @param tripId Optional ID of the trip (e.g., '2026-1'). Defaults to the active trip if omitted.
     * @return A list of events in the itinerary.
     */
    @AppFunctionDeclaration(isDescribedByKDoc = true)
    suspend fun getItinerary(tripId: String? = null): List<TimelineEventSerializable> {
      return withContext(Dispatchers.IO) {
        val trip = resolveTrip(tripId = tripId)
        val targetTripId = trip?.id ?: ActiveTripSession.activeTripId ?: tripId?.takeIf { it.isNotBlank() } ?: "2026-1"
        val dbEvents = eventDao.getEventsForTrip(targetTripId).first()
    ...
  • প্যারামিটার ডিজাইনের জন্য কিছু পেশাদার পদ্ধতি এখানে দেওয়া হল।

    /**
     * @param tripId Optional ID of the trip (e.g., '2026-1').
     *   Defaults to the active trip if omitted.
     * @return A list of events in the itinerary.
     */
    @AppFunctionDeclaration(isDescribedByKDoc = true)
    suspend fun getItinerary(tripId: String? = null): List<TimelineEventSerializable> {
      return withContext(Dispatchers.IO) {
        // resolveTrip defaults to the active trip if tripId == null
        val trip = resolveTrip(tripId = tripId)
  • প্রথমত, আপনাকে স্পষ্ট, স্বতন্ত্র নাম সহ প্যারামিটারের ন্যূনতম সাবসেটের উপর ফোকাস করতে হবে।

  • অপ্রয়োজনীয় প্যারামিটারকে নালেবেল করুন এবং ফাংশন বডিতে তাদের ডিফল্ট সমাধান করুন।

  • এটি শুধু একটি পেশাদার পদ্ধতি নয়।

  • এটি সত্যিই কাজ করবে না, কারণ Kotlin ডিফল্ট মান উপেক্ষা করা হবে কারণ AppFunctions লাইব্রেরি বাদ দেওয়া নালেবল প্যারামিটারের জন্য নাল পাস করে।

  • আমি প্রথমটির উদাহরণও দেখাব না, তবে ক্যাচ-অল প্যারামিটার ব্যবহার করবেন না।

  • সম্পূর্ণ প্রসঙ্গ বা কথোপকথনের ইতিহাসের মতো বিষয়গুলি, এগুলি খুবই খারাপ।

  • এগুলি এজেন্টদের প্রচুর পরিমাণে ডেটা পাস করতে এবং এক্সফিল্ট্রেশনের দিকে পরিচালিত করতে পারে এবং এটি একটি খারাপ অভ্যাস।

  • যেহেতু AppFunction ডিফল্ট হিসেবে UI থ্রেডে এক্সিকিউট হয়, তাই আপনি উপযুক্ত কোরুটিন ডিসপ্যাচার বেছে নেওয়ার মাধ্যমে নিশ্চিত করতে চান যে দীর্ঘ সময় ধরে চলা অপারেশন ব্যাকগ্রাউন্ডে হয়।

    @AppFunctionDeclaration(isDescribedByKDoc = true)
    suspend fun getItinerary(tripId: String? = null): List<TimelineEventSerializable> {
      return withContext(Dispatchers.IO) {
        val trip = resolveTrip(tripId = tripId)
        val targetTripId = trip?.id ?: ActiveTripSession.activeTripId ?:
        tripId?.takeIf { it.isNotBlank() } ?: "2026-1"
        val dbEvents = eventDao.getEventsForTrip(targetTripId).first()
        val events =
          if (dbEvents.isNotEmpty()) { dbEvents } else {
            val fallback = DummyData.events.filter { it.tripId == targetTripId }
            ...
  • আপনি চান যে, প্রয়োজন হলে বিস্তারিত তথ্য সহ AppFunction ব্যতিক্রম থ্রো করতে, যাতে এজেন্ট বুঝতে পারে যে কীভাবে কোনও সমস্যার সমাধান করতে হয়।

    @AppFunctionDeclaration(isDescribedByKDoc = true)
    suspend fun send(
        message: Message
    ): Result {
      ...
      throw AppFunctionException(
        errorCode = AppFunctionException.ErrorCode.ERROR_INVALID_ARGUMENT,
        errorMessage = "To address has invalid syntax!"
      )
    }
  • ঠিক আছে, এখন আমরা একটি AppFunction পেয়েছি, আসুন, এখানে আমাদের আর্কিটেকচার ডায়াগ্রামে ফিরে যাই।

  • AppFunctions কল করার জন্য এজেন্ট ক্লায়েন্ট অ্যাপ প্ল্যাটফর্মে রেজিস্টার করে।

  • সুতরাং, এটি Gemini-র মতো কিছু, উদাহরণস্বরূপ।

  • এবং এটি করার জন্য, তাদের একটি বিশেষ অনুমতি থাকতে হবে এবং রেজিস্টার করা এজেন্টরা Android প্ল্যাটফর্ম থেকে AppFunction মেটাডেটা দেখতে পারবেন এবং ব্যবহারকারীর অনুরোধ পূরণ করার জন্য তাদের এক বা একাধিক রেজিস্টার করা AppFunction কার্যকর করা উচিত কিনা তা সিদ্ধান্ত নিতে পারবেন।

  • এখন এক বা একাধিক AppFunction-এর কথা বলতে গেলে, এজেন্ট আসলে কীভাবে সিদ্ধান্ত নেয়?

  • তবে, আপনাকে শেষ যে কাজটি করতে হবে তা হল অ্যাপ মেটাডেটা ফাইলে আপনার অ্যাপের ক্ষমতা বর্ণনা করা।

    <AppFunctionAppMetadata
    xmlns:appfn="http://schemas.android.com/apk/androidx.appfunctions"
    
      appfn:description="This app manages trips and travel expenses.
    
      Operational Patterns:
        - Use 'getAllTrips' to find the correct 'tripId' before
          adding expenses or managing trip details.
    
      Constraints:
        - Expenses must be associated with a valid trip ID."
    
      appfn:displayDescription="@string/user_visible_description" />
  • এবং এজেন্টরা প্রথমে আপনার AppFunctionAppMetadata-এর মধ্যে এই বিবরণ পড়ে টুল বেছে নেয় এবং তারপরে আবার-- তারপর আসলে প্রতিটি AppFunction-এর সাথে বিবরণ পড়ে।

  • তাই, আপনার অ্যাপ বা টুলের প্রাথমিক ডোমেনের সংক্ষিপ্ত ব্যাখ্যা দিয়ে বিবরণের শুরুতেই ফোকাস করতে চান।

  • এবং আপনি অপারেশনাল প্যাটার্ন অনুযায়ী এটি ফলো করতে পারেন যাতে এজেন্ট অপারেশনের লজিক্যাল ফ্লো বুঝতে পারে এবং এটি পূর্বশর্তের আউটলাইন তৈরি করতে বা টাস্ক সম্পূর্ণ করার জন্য এজেন্টকে সবচেয়ে ভালো পথের দিকে নির্দেশ করার জন্য ভালো।

  • এছাড়াও, আপনি সীমাবদ্ধতা সংক্রান্ত একটি বিভাগ যোগ করতে পারেন যা ইনপুট ডেটার ধরনের লজিক্যাল সীমা নির্ধারণ করে বা অপারেশনাল প্যাটার্নকে আরও শক্তিশালী করে।

  • এগুলি হল Android MCP-এর প্রাথমিক তথ্য, তবে আপনি আরও কিছু করতে পারেন।

  • আপনি একটি কাজ করতে পারেন, যা বেশ ভালো, তা হল আপনি AppFunctions ডায়নামিকভাবে চালু ও বন্ধ করতে পারবেন।

    viewModelScope.launch {
        val newState =
            if (enabled) {
                AppFunctionManager.APP_FUNCTION_STATE_ENABLED
            } else {
                AppFunctionManager.APP_FUNCTION_STATE_DISABLED
            }
        appFunctionManager?.setAppFunctionEnabled(id, newState)
    }
  • And this is important, for example, if you wanted AppFunction to only be available if the user is logged in.

  • আপনি আরও একটি কাজ করতে পারেন, তা হল আপনার অ্যাপের নির্দিষ্ট স্ক্রিন বা অ্যাক্টিভিটির জন্য AppFunctions তৈরি করা।

    @AppFunctionSignature(
        scope = AppFunctionMetadata.SCOPE_ACTIVITY,
        appFunctionXmlFileName = "cart_functions"
    )
    fun interface AddCurrentItemToCart {
        /** Adds the item currently shown on screen to the cart. Returns the new cart size. */
        suspend fun addToCart(quantity: Int): Int
    }
  • তাই AppFunction ঘোষণা করার পরিবর্তে, আপনি AppFunctionSignature ঘোষণা করেন।

  • তারপর আপনি UI-তে AppFunction-এর জন্য হ্যান্ডলার প্রয়োগ করতে পারবেন।

    val addToCartAppFunctionAdapter =
        appFunctionManager.getHandleAppFunctionRequestAdapter<AddCurrentItemToCart>()
    
    coroutineScope.launch {
        appFunctionManager.handleAppFunction(
            addToCartAppFunctionAdapter.adapt { quantity ->
                val newCartSize = cart.addItem(quantity)
                newCartSize
            }
        )
    }
  • আর এর সবচেয়ে ভালো দিক হল, হ্যান্ডলারের লাইফসাইকেল অটোমেটিক আপনার ব্যবহার করা কো-রুটিন স্কোপের লাইফসাইকেলের সাথে যুক্ত হয়ে যায়।

  • এখন আপনি এই AppFunctions অনুরোধ অ্যাডাপ্টার ক্যাশে করতে চাইবেন, দুঃখিত, AppFunctions অনুরোধ অ্যাডাপ্টার হ্যান্ডেল করুন, কারণ লাইব্রেরিটি এটিকে ইনস্ট্যান্টিয়েট করার জন্য ব্যাকগ্রাউন্ডে রিফ্লেকশন ব্যবহার করে।

  • এছাড়াও, স্কোপ করা AppFunctions তৈরি করার ফাংশনালিটি আছে যার রেজিস্ট্রেশন সরাসরি Android লাইফসাইকেলের সাথে যুক্ত নয়।

  • এখন, শেষ একটি জিনিস হল আমাদের কাছে অ্যাক্সেস লেভেল নামে এই জিনিসটি রয়েছে।

    @AppFunctionDeclaration(isDescribedByKDoc = true)
    @AppFunctionAccessLevel(
        level = AppFunctionMetadata.ACCESS_LEVEL_SELF,
        isCompatEnforcementEnabled = true,
    )
    suspend fun getItinerary(tripId: String? = null): List<TimelineEventSerializable> {
      return withContext(Dispatchers.IO) {
      ...
  • এবং আপনি নিজের বা সিস্টেমের বা বিশ্বস্ত অ্যাক্সেস লেভেল পেতে ও ব্যবহার করতে পারেন এবং এটি দেখায় যে কোন ফাংশনগুলি শুধুমাত্র আপনার নিজস্ব ইন-অ্যাপ এজেন্টের ব্যবহারের জন্য, যেমন ML Kit GenAI-এর মাধ্যমে তৈরি করা, কোন ফাংশনগুলি শুধুমাত্র সিস্টেমের জন্য উপলভ্য এবং কোন ফাংশনগুলি বিশ্বস্ত সিস্টেম এজেন্টের জন্য উপলভ্য।

  • এবং আপনার নিজস্ব ইন-অ্যাপ এজেন্ট তৈরি করতে সাহায্য করার জন্য, আমরা আগেই ঘোষণা করেছি, আপনি দেখেছেন যে ML Kit তার ইউনিফায়েড টুল কলিং ফ্রেমওয়ার্কের অংশ হিসেবে AppFunctions-কে নেটিভভাবে সাপোর্ট করে।

  • আপনি একবার এগুলি নির্ধারণ করেন এবং সিস্টেম-লেভেল এজেন্ট ও লোকাল ইন-অ্যাপ মডেল জুড়ে কভারেজ পান যা আপনাকে ফ্র্যাগমেন্টেড টুল ডিক্লারেশনের মতো বিষয় এড়াতে সাহায্য করে।

  • আরও জানতে, আপনি এই আলোচনাটি দেখতে পারেন, ML Kit GenAI-এর মাধ্যমে আপনার অ্যাপে এজেন্টসুলভ দক্ষতা আনলক করুন, অথবা আপনি আসলে শুধু গিয়ে দেখতে পারেন যে Jetpacker-এ লেটেস্ট কী কী আছে।

  • এটা বেশ ভালো।

  • ঠিক আছে, সারাকে আবার কল করছি।

  • ঠিক আছে।

  • তাহলে, ড্যান আপনাকে দেখিয়েছেন যে Android MCP কীভাবে কাজ করে।

  • আসুন, এটি বাস্তবে প্রয়োগ করা যাক।

  • মনে রাখবেন, আপনার অ্যাপ হল একটি সার্ভার এবং এজেন্ট হল ক্লায়েন্ট, তাই আপনার অ্যাপ যাতে তাদের জন্য একটি ভালো সার্ভার হয় তা নিশ্চিত করাই লক্ষ্য হওয়া উচিত।

  • আমরা Jetpacker, আমাদের ট্রাভেল স্যাম্পেল অ্যাপ, প্রথম টুল ডিক্লারেশন থেকে Gemini-র মাধ্যমে আসল ফাংশন কল পর্যন্ত অনুসরণ করব।

  • আপনি নিজে AppFunctions লিখতে পারেন অথবা শুরু করার জন্য কোডিং এজেন্টের সাহায্য নিতে পারেন।

  • আমরা AppFunctions স্কিল রিলিজ করেছি যা আপনাকে ফাংশন লেখা শুরু করতে সাহায্য করতে পারে। আপনি Android CLI-এর মাধ্যমে Android Studio-তে এটি হুক আপ করতে পারেন বা আপনার পছন্দের কোডিং এজেন্টের সাথে ব্যবহার করতে পারেন।

    android skills add appfunctions
  • স্কিলটি তিনটি ধাপে কাজ করে।

  • প্রথমে, এটি আপনার কোডবেস বিশ্লেষণ করে ব্যবহারকারীর মূল অভিজ্ঞতা শনাক্ত করে যা মিডিয়া চালানো, নোট তৈরি করা বা মেসেজ পাঠানোর মতো উপযোগী টুল তৈরি করবে।

  • তারপর, এটি তৈরি করে, এটি আসলে AppFunction ঘোষণা তৈরি করে যা সেইসব জার্নিকে সম্পূর্ণভাবে প্রকাশ করে, যার সাথে থাকে তাদের প্রয়োজনীয় বাস্তবায়ন এবং প্ল্যাটফর্ম সাপোর্ট।

  • যেমন, Gradle ও KSP, অ্যাপ মেটাডেটা XML এবং ডিপেন্ডেন্সি ইনজেকশন।

  • সবশেষে, এটি আপনার KDoc বিবরণ রিফাইন করবে কারণ আপনার ফাংশন কখন এবং কীভাবে কল করতে হবে তা সিদ্ধান্ত নিতে এজেন্ট ঠিক এটিই ব্যবহার করে।

  • আমরা এই ধরনের ফলাফল খুঁজছি।

  • এটি Jetpacker-এর জন্য একটি addExpense ফাংশন।

    /**
     * Adds an expense to a trip.
     *
     * @param tripId ID of the trip. If null, the active trip is used.
     * @param category Expense category. If null, it's inferred from the title.
     */
    @AppFunctionDeclaration(isDescribedByKDoc = true)
    suspend fun addExpense(
        title: String,
        amount: Double,
        currency: String,
        tripId: String? = null,
        category: String? = null,
    ): ExpenseSerializable = withContext(Dispatchers.IO) {
        if (amount <= 0.0) {
            throw AppFunctionInvalidArgumentException("amount must be greater than zero")
        }
        val trip = resolveTrip(tripId)
            ?: throw AppFunctionElementNotFoundException("No trip with ID $tripId")
        val expense = Expense(trip.id, title, amount, currency,
            category = category ?: inferExpenseCategory(title))
        expenseDao.insertExpense(expense)
        expense.toSerializable(remainingBudgetUsd(trip.id))
    }
  • বিবরণ হল এক লাইনের বাক্য যা একটি ক্রিয়াপদ এবং রিসোর্স দিয়ে শুরু হয়: ট্রিপে খরচ যোগ করে।

  • ট্রিপ ও ক্যাটেগরি ফিল্ড নালেবেল কারণ ব্যবহারকারী সবসময় এটি নির্দিষ্ট নাও করতে পারেন, তবে ফাংশনটি বডিতে সেগুলি সমাধান করবে।

  • এবং কোনও অনুরোধ সফল না হলে, এটি একটি AppFunctionException থ্রো করে যাতে এজেন্ট জানতে পারে কী ভুল হয়েছে।

অ্যাপের ফাংশন ও এজেন্ট টেস্ট করা

  • ঠিক আছে, এবার টেস্টিংয়ের ব্যাপারে দেখা যাক।

  • আপনার ফাংশন এখন, আপনার ফাংশন এখন এমন পরিষেবা যা এজেন্ট ব্যবহার করতে পারে।

  • তাই আপনাকে আসলে দুটি অংশ পরীক্ষা করতে হবে: আপনার ফাংশন যেভাবে আচরণ করে এবং এজেন্ট যেভাবে আচরণ করে।

  • আপনার ফাংশনের জন্য, আমরা ইউনিট টেস্ট দিয়ে শুরু করব যা আপনি হয়ত আগেই লিখে ফেলেছেন, তবে এজেন্ট যখন ফাংশন কল করে তখন কয়েকটি বিষয় মাথায় রাখতে হবে।

  • প্রথমত, Kotlin-এর ডিফল্ট ভ্যালু এখানে প্রযোজ্য নয়।

  • বাদ দেওয়া প্যারামিটার null, false বা 0 হিসেবে আসে।

  • তাই, আগে ডিফল্ট হিসেবে ১০ সেট করা থাকলে, এখানে তা ০ হবে।

  • এছাড়াও, কলটি মূল থ্রেডে শুরু হয় এবং টেস্ট থেকে আপনার ফাংশন কল করা সবসময় স্পষ্ট নাও হতে পারে, তাই আপনার ব্যাকগ্রাউন্ড ডিসপ্যাচারে স্ট্যান্ডার্ড IO চালানো উচিত।

  • কিছু ব্যর্থ হলে, একটি AppFunctionException থ্রো করুন।

  • অন্য কিছু হলে, আপনার এজেন্ট একটি সাধারণ অজানা সমস্যার মেসেজ পাবে, যার সাথে আপনার র ব্যতিক্রম মেসেজ অ্যাটাচ করা থাকবে।

  • এবং আপনার ইনপুট একটি মডেল থেকে আসবে, তাই আপনাকে অবশ্যই ফাঁকা, USD-এর মতো ডলারের সমার্থক শব্দ এবং রেঞ্জের বাইরের ভ্যালুর মতো এজ কেস সিনারিও পরীক্ষা করতে হবে।

  • এজেন্টের ক্ষেত্রে, আপনি জানতে চাইবেন যে কোনও এজেন্ট সঠিক সময়ে আপনার টুল বেছে নেয় কিনা এবং এটি ফাংশনটি সঠিকভাবে কার্যকর করে কিনা।

  • প্রতিটি এজেন্ট একটু আলাদাভাবে আচরণ করে, তাই আপনার ফাংশনগুলি যে কোনও এজেন্টের সাথে পরীক্ষা করা গুরুত্বপূর্ণ যা আপনি আশা করেন যে সেগুলি ব্যবহার করবে।

  • আপনি পরীক্ষা করার জন্য আমাদের ওপেন-সোর্স স্যাম্পেল এজেন্ট ব্যবহার করতে পারেন অথবা এই ইন্টিগ্রেশন পরীক্ষা করার জন্য Gemini ব্যবহার করতে পারেন।

Connected Apps Platform-এর মাধ্যমে Gemini-র সাথে কানেক্ট করা

  • Gemini কীভাবে আপনার AppFunctions-এর সাথে ইন্টার‍্যাক্ট করে তা দেখতে চাইলে অথবা Gemini অ্যাপে Android MCP লঞ্চ করতে চাইলে, আপনি Connected Apps Platform-এর মাধ্যমে তা করতে পারবেন। এটি আমরা আজই লঞ্চ করেছি এবং বর্তমানে প্রিভিউতে রয়েছে।

  • কানেক্টেড অ্যাপ প্ল্যাটফর্মের মাধ্যমে Gemini ও Search-এ আপনার অ্যাপ এক্সটেন্ড করার ব্যাপারে আমাদের আগের আলোচনা চেক করে আপনি আরও জানতে পারবেন।

  • আপনি প্রিভিউ প্রোগ্রামে যোগ দেওয়ার পরে, Gemini-এর সাথে টেস্ট করার প্রসেসে দুটি ধাপ আছে।

  • প্রথমে, Google Connected Console-এ কানেক্ট করা অ্যাপে, আমরা একটি কানেক্ট করা অ্যাপ তৈরি করব যা Gemini আসলে ব্যবহার করতে পারবে।

  • তারপর, দ্বিতীয়ত, Play Console-এ, আমাদের আপনার অ্যাপ বান্ডেল আপলোড করতে হবে, যাতে আমরা AppFunctions অ্যাক্সেস করতে পারি এবং আপনি ইন্টার্নাল টেস্টিং ট্র্যাক শুরু করতে পারেন, যাতে টেস্টাররা আসলে ফাংশন যাচাই করতে পারেন।

  • তাহলে, Google Connected Console-এ শুরু করা যাক।

  • এখানে, আপনি উপরে ডানদিকের বোতাম, "অ্যাপ তৈরি করুন" ব্যবহার করে একটি কানেক্ট করা অ্যাপ তৈরি করবেন।

  • কানেক্ট করা অ্যাপ তৈরি করার সময়, এই স্ক্রিনে Android MCP বেছে নিন।

  • আপনি যদি সার্ভার-সাইড MCP ইমপ্লিমেন্টেশন তৈরি করেন, তাহলে এখানে আপনার সার্ভার কোড আপলোড করতে হবে।

  • আপনি "সেট-আপ করুন" বিকল্পে ক্লিক করলে, আপনার ডেভেলপার অ্যাকাউন্টের সাথে যুক্ত যেকোনও Google Play অ্যাপ আসলে এখানে দেখানো হবে এবং আপনার কানেক্ট করা অ্যাপের সাথে লিঙ্ক করার জন্য প্রস্তুত থাকবে।

  • আপনার আগে থেকে কোনও Google Play অ্যাপ না থাকলে, আপনাকে প্রথমে Play Console-এ একটি তৈরি করতে হবে।

  • এখানে, আমাদের কাছে আগে থেকেই Jetpacker আছে, তাই আমরা এগিয়ে যাব এবং সেটি লিঙ্ক করব।

  • এরপর, ব্যবহারকারীরা আপনার অ্যাপের সাথে ইন্টার‍্যাক্ট করার চেষ্টা করলে Gemini ফ্রন্ট এন্ডে যে বিবরণ দেখতে পাবেন তা যোগ করুন।

  • এর মধ্যে অ্যাপ আইকন, ডিসপ্লে নাম, আপনার কানেক্ট করা অ্যাপের ক্ষমতা এবং উদাহরণ হিসেবে প্রম্পট অন্তর্ভুক্ত থাকে যা ব্যবহারকারীরা এটির সাথে ইন্টার‍্যাক্ট করার জন্য ব্যবহার করতে পারেন।

  • ব্যবহারকারীদের সবচেয়ে ভালোভাবে গাইড করার জন্য এগুলি বর্ণনামূলক হওয়া উচিত।

  • ঠিক আছে, কানেক্ট করা অ্যাপ সেট-আপ করার প্রক্রিয়া এখানেই শেষ, তবে আপনি হয়ত ভাবছেন যে কানেক্ট করা অ্যাপ আসলে কীভাবে ফাংশন অ্যাক্সেস করে।

  • যেহেতু ফাংশনগুলি আপনার অ্যাপ কোডের মধ্যে রয়েছে, তাই আমাদের এখন Play Console-এ যেতে হবে এবং আপনার অ্যাপ বান্ডেল আপলোড করতে হবে এবং একটি টেস্ট ট্র্যাক তৈরি করতে হবে, যাতে আমরা প্রকৃত ব্যবহারকারীদের সাথে এটি পরীক্ষা করতে পারি।

  • তাই, এই স্ক্রিনে, আমরা আসলে অ্যাপ বান্ডেল আপলোড করব এবং তারপরে আমরা এখানে দেখতে পাব যে Play, AppFunctions শনাক্ত করতে পারে এবং আপনি সেগুলিকে সরাসরি আপনার কানেক্ট করা অ্যাপের সাথে লিঙ্ক করতে পারবেন।

  • এখন, আমরা আপনার ইন্টিগ্রেট করা ফাংশন সহ Jetpack কানেক্ট করা অ্যাপ দেখতে পাচ্ছি।

  • এরপর, আমাদের পরীক্ষক যোগ করতে হবে, যাতে আপনার ফাংশন যাচাই করার জন্য এবং সেগুলি সঠিকভাবে কাজ করছে কিনা তা নিশ্চিত করার জন্য কেউ প্রস্তুত থাকে।

  • তাই, এখানে আপনার কানেক্ট করা অ্যাপ ভার্সনে ক্লিক করুন।

  • এই স্ক্রিনে, আপনি "টেস্টার ম্যানেজ করুন" বিকল্প বেছে নিতে এবং টেস্টারদের একটি সীমিত তালিকা কনফিগার করতে পারবেন, যারা Gemini-তে আপনার কানেক্ট করা অ্যাপ উপলভ্য হলে তা অ্যাক্সেস করতে পারবেন।

  • অন্য যেকোনও রিলিজের মতোই আপনি এই টেস্টিং ট্র্যাকে টেস্টার যোগ করতে পারবেন।

  • আপনার Google Play অ্যাপের জন্য যে পরীক্ষকদের তালিকা আছে, কানেক্ট করা অ্যাপের জন্যও একই তালিকা আছে কিনা তা নিশ্চিত করুন।

Gemini অ্যাপ ইন্টিগ্রেশনের ডেমো

  • ঠিক আছে, এবার ইন্টিগ্রেশন টেস্ট করা যাক।

  • ব্যবহারকারীর ফোনে Jetpacker অ্যাপে এই সামিটের জন্য ট্রিপ আগে থেকেই লগ করা আছে।

  • অ্যাপ কানেক্ট করার জন্য আমি Gemini খুলব।

  • আমাকে নিশ্চিত করতে হবে যে আমার ডিভাইসে অ্যাপের সেই একই ভার্সন ইনস্টল করা আছে যা আমরা আগে Play-তে আপলোড করেছি এবং আমাদের Gemini-র জন্য এমন একটি অ্যাকাউন্ট ব্যবহার করা উচিত যা পরীক্ষকের তালিকায় যোগ করা হয়েছে।

  • তাই এখানে, আমি Jetpacker-এর কথা উল্লেখ করব এবং Mountain View-তে আমার হোটেল রুমের কনফার্মেশন নম্বর জানতে চাইব।

  • Gemini ডিভাইসে Jetpacker-এর AppFunctions কল করে এবং উত্তর দেয়, "রেডউড গ্রোভ হোটেলে RED11223।"

  • এরপর, আমি জিজ্ঞাসা করব, "ADS-এর জন্য আমার কত বাজেট বাকি আছে?"

  • এবং ফাংশন কল $১৮৫০ রিটার্ন করবে।

  • এখন, আমি একটি পরিবর্তন করতে চাই, তাই আমি Gemini-কে আমার ADS ট্রিপে এয়ারপোর্ট Uber-এর জন্য $৪৫ যোগ করতে বলব।

  • আমার ভ্রমণসূচীতে সরাসরি এটি যোগ করার জন্য Gemini আগে থেকে আমাদের addExpense ফাংশন কল করে।

  • আর আমি Jetpacker খুললে, খরচটি আগে থেকেই সেখানে থাকে এবং সেই অনুযায়ী বাজেট কমে যায়।

  • আপনি যদি আজই Gemini-র জন্য কানেক্ট করা অ্যাপ তৈরি করা শুরু করতে চান, তাহলে Connected Apps Platform-এ অ্যাক্সেসের জন্য আবেদন করুন, যা বর্তমানে প্রিভিউতে রয়েছে।

মোবাইল এজেন্টের ভবিষ্যৎ ও সারসংক্ষেপ

  • ঠিক আছে।

  • তাহলে, এবার শেষ করা যাক।

  • তাই, আমরা মোবাইল কম্পিউটিংয়ের ক্ষেত্রে এক বিশাল পরিবর্তনের শুরুতে রয়েছি এবং আপনার অ্যাপ রেডি কিনা তা নিশ্চিত করার সময় এসে গেছে।

  • তাই, আপনার অ্যাপের কোর হাই-ভ্যালু ব্যবহারকারীর অভিজ্ঞতা শনাক্ত করে শুরু করুন।

  • সার্ভার-সাইড MCP ব্যবহার করে আপনার ক্লাউডের ব্যাক এন্ড এক্সপোজ করুন এবং এন্ড-টু-এন্ড এনক্রিপ্ট করা ডেটা, লোকাল অপারেশন, ডিভাইস সেন্সর এবং অন্যান্য অন-ডিভাইস জিনিসের সাথে কাজ করতে Android MCP ব্যবহার করুন।

  • এখন, প্রোটোটাইপ করতে, আপনি Android Studio এবং Android CLI ডাউনলোড করতে পারবেন।

  • আপনি নিজের বিল্ড ফাইলে AndroidX AppFunctions বিটা ডিপেন্ডেন্সি যোগ করতে এবং লোকালি আপনার প্রথম অন-ডিভাইস ফাংশন পরীক্ষা করতে পারেন।

  • Gemini-র মাধ্যমে প্রোডাকশন শুরু করার জন্য আগেভাগে রেজিস্টার করতে, আপনি এখানে এই QR কোডের মাধ্যমে সাইন-আপ করতে পারবেন।

  • আর অবশ্যই, Connected Apps Platform-এর সাথে কাজ করার ব্যাপারে এই সম্পর্কিত আলোচনাগুলি চেক করতে ভুলবেন না এবং কীভাবে আপনি ML Kit বা আপনার নিজস্ব এজেন্টের সাথে ইন্টার্নাল AppFunctions ব্যবহার করে আপনার নিজস্ব ইন-অ্যাপ এজেন্ট চালাতে পারেন তাও জানতে পারবেন।

  • আর সবশেষে, সময় দেওয়ার জন্য আপনাকে অনেক ধন্যবাদ।

  • Android-এর জন্য আপনি যে দুর্দান্ত অভিজ্ঞতা তৈরি করছেন তা দেখার জন্য আমরা অধীর আগ্রহে অপেক্ষা করছি।

কোড

কোড স্নিপেট

"Technical deep dive into Android MCP" ভিডিওতে ১১:৫৬-এ Kotlin Script স্নিপেট
implementation(libs.androidx.appfunctions)
ksp(libs.androidx.appfunctions.compiler)
"Technical deep dive into Android MCP" ভিডিওর ১২:১৪-এ Kotlin স্নিপেট
@RequiresApi(36)
@AndroidEntryPoint
@AppFunctionServiceEntryPoint(
    serviceName = "JetPackerAppFunctionService",
    appFunctionXmlFileName = "jetpacker_app_function_service"
)
abstract class BaseJetPackerAppFunctionService : AppFunctionService() {
    @Inject internal lateinit var tripDao: TripDao
    @Inject internal lateinit var expenseDao: ExpenseDao
    ...
"Technical deep dive into Android MCP" ভিডিওর ১২:২৯-এ Kotlin স্নিপেট
/** Represents an event in a trip itinerary. */
@AppFunctionSerializable(isDescribedByKDoc = true)
data class TimelineEventSerializable(
  /** The event's unique identifier. */
  val id: String,
  /** The trip ID this event belongs to. */
  val tripId: String,
  /** The type of event (e.g., TRANSPORTATION, ACCOMMODATION, FOOD_AND_DRINK). */
  val type: String,
  /** The timestamp of the event. */
  val timestamp: Long,
  /** The title of the event. */
  val title: String,
  /** The location of the event. */
  val location: String,
  /** A description of the event. */
  val description: String?,
  /** The title of the trip this event belongs to. */
  val tripTitle: String? = null,
  /** Optional title of a matched itinerary event when added as a reminder. */
  val matchedEventTitle: String? = null,
  /** Execution status of the tool call (e.g., SUCCESS). */
  val status: String? = "SUCCESS",
  /** Human-readable completion message instructing the agent not to repeat the call. */
  val message: String? = null,
)
"Technical deep dive into Android MCP" ভিডিওর 12:38-এ Kotlin স্নিপেট
@AppFunctionDeclaration(isDescribedByKDoc = true)
suspend fun getItinerary(tripId: String? = null): List<TimelineEventSerializable> {
  return withContext(Dispatchers.IO) {
    val trip = resolveTrip(tripId = tripId)
    val targetTripId = trip?.id ?: ActiveTripSession.activeTripId ?: tripId?.takeIf { it.isNotBlank() } ?: "2026-1"
    val dbEvents = eventDao.getEventsForTrip(targetTripId).first()
    val events =
      if (dbEvents.isNotEmpty()) { dbEvents } else {
        val fallback = DummyData.events.filter { it.tripId == targetTripId }
        if (fallback.isNotEmpty()) { eventDao.insertEvents(fallback) }
        fallback
      }
    events.map { it.toSerializable(tripTitle = trip?.title) }
  }
}
"Technical deep dive into Android MCP" ভিডিওর 12:44-এ Kotlin স্নিপেট
/**
 * Retrieves the itinerary events (with their eventId, title, type, and location) for a specific
 * trip. Note: getItinerary returns event summaries only; call getEventDetails(eventId) with a
 * specific event's ID to retrieve ticket prices, hotel nightly rates, baggage allowance, or
 * visitor tips.
 *
 * @param tripId Optional ID of the trip (e.g., '2026-1'). Defaults to the active trip if omitted.
 * @return A list of events in the itinerary.
 */
@AppFunctionDeclaration(isDescribedByKDoc = true)
suspend fun getItinerary(tripId: String? = null): List<TimelineEventSerializable> {
  return withContext(Dispatchers.IO) {
    val trip = resolveTrip(tripId = tripId)
    val targetTripId = trip?.id ?: ActiveTripSession.activeTripId ?: tripId?.takeIf { it.isNotBlank() } ?: "2026-1"
    val dbEvents = eventDao.getEventsForTrip(targetTripId).first()
...
"Technical deep dive into Android MCP" ভিডিওর ১২:৫২-এ Kotlin স্নিপেট
/**
 * @param tripId Optional ID of the trip (e.g., '2026-1').
 *   Defaults to the active trip if omitted.
 * @return A list of events in the itinerary.
 */
@AppFunctionDeclaration(isDescribedByKDoc = true)
suspend fun getItinerary(tripId: String? = null): List<TimelineEventSerializable> {
  return withContext(Dispatchers.IO) {
    // resolveTrip defaults to the active trip if tripId == null
    val trip = resolveTrip(tripId = tripId)
"Technical deep dive into Android MCP" ভিডিওর 13:30-এ Kotlin স্নিপেট
@AppFunctionDeclaration(isDescribedByKDoc = true)
suspend fun getItinerary(tripId: String? = null): List<TimelineEventSerializable> {
  return withContext(Dispatchers.IO) {
    val trip = resolveTrip(tripId = tripId)
    val targetTripId = trip?.id ?: ActiveTripSession.activeTripId ?:
    tripId?.takeIf { it.isNotBlank() } ?: "2026-1"
    val dbEvents = eventDao.getEventsForTrip(targetTripId).first()
    val events =
      if (dbEvents.isNotEmpty()) { dbEvents } else {
        val fallback = DummyData.events.filter { it.tripId == targetTripId }
        ...
"Technical deep dive into Android MCP"-এ 13:40-এ Kotlin স্নিপেট
@AppFunctionDeclaration(isDescribedByKDoc = true)
suspend fun send(
    message: Message
): Result {
  ...
  throw AppFunctionException(
    errorCode = AppFunctionException.ErrorCode.ERROR_INVALID_ARGUMENT,
    errorMessage = "To address has invalid syntax!"
  )
}
"Android MCP-এর টেকনিক্যাল ডিপ ডাইভ" ভিডিওর 14:15-এ XML স্নিপেট
<AppFunctionAppMetadata
xmlns:appfn="http://schemas.android.com/apk/androidx.appfunctions"

  appfn:description="This app manages trips and travel expenses.

  Operational Patterns:
    - Use 'getAllTrips' to find the correct 'tripId' before
      adding expenses or managing trip details.

  Constraints:
    - Expenses must be associated with a valid trip ID."

  appfn:displayDescription="@string/user_visible_description" />
"Technical deep dive into Android MCP" ভিডিওর 15:00-এ Kotlin স্নিপেট
viewModelScope.launch {
    val newState =
        if (enabled) {
            AppFunctionManager.APP_FUNCTION_STATE_ENABLED
        } else {
            AppFunctionManager.APP_FUNCTION_STATE_DISABLED
        }
    appFunctionManager?.setAppFunctionEnabled(id, newState)
}
"Technical deep dive into Android MCP" ভিডিওর 15:12-এ Kotlin স্নিপেট
@AppFunctionSignature(
    scope = AppFunctionMetadata.SCOPE_ACTIVITY,
    appFunctionXmlFileName = "cart_functions"
)
fun interface AddCurrentItemToCart {
    /** Adds the item currently shown on screen to the cart. Returns the new cart size. */
    suspend fun addToCart(quantity: Int): Int
}
"Technical deep dive into Android MCP" ভিডিওর 15:22-এ Kotlin স্নিপেট
val addToCartAppFunctionAdapter =
    appFunctionManager.getHandleAppFunctionRequestAdapter<AddCurrentItemToCart>()

coroutineScope.launch {
    appFunctionManager.handleAppFunction(
        addToCartAppFunctionAdapter.adapt { quantity ->
            val newCartSize = cart.addItem(quantity)
            newCartSize
        }
    )
}
"Technical deep dive into Android MCP" ভিডিওর 15:50-এ Kotlin স্নিপেট
@AppFunctionDeclaration(isDescribedByKDoc = true)
@AppFunctionAccessLevel(
    level = AppFunctionMetadata.ACCESS_LEVEL_SELF,
    isCompatEnforcementEnabled = true,
)
suspend fun getItinerary(tripId: String? = null): List<TimelineEventSerializable> {
  return withContext(Dispatchers.IO) {
  ...
"Technical deep dive into Android MCP" ভিডিওর ১৭:০৫-এ Shell স্নিপেট
android skills add appfunctions
"Technical deep dive into Android MCP" ভিডিওর ১৭:৫৪-এ Kotlin স্নিপেট
/**
 * Adds an expense to a trip.
 *
 * @param tripId ID of the trip. If null, the active trip is used.
 * @param category Expense category. If null, it's inferred from the title.
 */
@AppFunctionDeclaration(isDescribedByKDoc = true)
suspend fun addExpense(
    title: String,
    amount: Double,
    currency: String,
    tripId: String? = null,
    category: String? = null,
): ExpenseSerializable = withContext(Dispatchers.IO) {
    if (amount <= 0.0) {
        throw AppFunctionInvalidArgumentException("amount must be greater than zero")
    }
    val trip = resolveTrip(tripId)
        ?: throw AppFunctionElementNotFoundException("No trip with ID $tripId")
    val expense = Expense(trip.id, title, amount, currency,
        category = category ?: inferExpenseCategory(title))
    expenseDao.insertExpense(expense)
    expense.toSerializable(remainingBudgetUsd(trip.id))
}