AP2 এবং UCP দিয়ে এজেন্ট কমার্স সুরক্ষিত করুন

১. সংক্ষিপ্ত বিবরণ

এই কোডল্যাবে, আপনি একটি ADK এজেন্ট চালাবেন যা দুটি ওপেন-সোর্স কমার্স প্রোটোকল ব্যবহার করে একাধিক থিয়েটার মার্চেন্টের কাছ থেকে সিনেমার টিকিট বুক করে।

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

সিনেএজেন্ট নামক ডেমো অ্যাপটি ভিন্ন ভিন্ন সক্ষমতা (আসন নির্বাচন, বিশেষায়িত ফরম্যাট এবং অর্থপ্রদানের পদ্ধতি) সম্পন্ন দুটি নকল থিয়েটার মার্চেন্টের সাথে সংযুক্ত হয় এবং অনুসন্ধান থেকে শুরু করে অর্থপ্রদান পর্যন্ত সম্পূর্ণ বুকিং প্রক্রিয়াটি পরিচালনা করে।

আপনি যা শিখবেন

  • /.well-known/ucp প্রোফাইলের মাধ্যমে UCP মার্চেন্ট ডিসকভারি কীভাবে কাজ করে
  • একটি ADK এজেন্ট কীভাবে ক্যাটালগ অনুসন্ধান করতে এবং চেকআউট তৈরি করতে UCP ব্যবহার করে
  • AP2 কীভাবে লেনদেন সুরক্ষিত করে (CartMandate, PaymentMandate)
  • এজেন্টিক ই-কমার্স সুরক্ষিত করতে এন্ড-টু-এন্ড UCP এবং AP2 প্রোটোকল কীভাবে কাজ করে

আপনার যা যা লাগবে

  • বিলিং সক্ষম একটি গুগল ক্লাউড প্রজেক্ট
  • ক্রোমের মতো একটি ওয়েব ব্রাউজার
  • পাইথন ৩.১১+

এই কোডল্যাবটি সেইসব মধ্যম স্তরের ডেভেলপারদের জন্য, যাদের পাইথন এবং গুগল ক্লাউড সম্পর্কে কিছুটা ধারণা আছে। এই কোডল্যাবটি সম্পূর্ণ করতে প্রায় ১৫ মিনিট সময় লাগে।

এই কোডল্যাবে তৈরি রিসোর্সগুলোর খরচ ৫ ডলারের কম হওয়া উচিত।

২. ইউসিপি এবং এপি২ প্রোটোকল বোঝা

এজেন্ট তৈরির কাজে ঝাঁপিয়ে পড়ার আগে, চলুন সেই দুটি প্রোটোকল বুঝে নিই যা এই সুরক্ষিত এজেন্টিক বাণিজ্যকে সম্ভব করে তোলে।

সার্বজনীন বাণিজ্য প্রোটোকল ( ইউসিপি )

UCP কৃত্রিম বুদ্ধিমত্তার এজেন্টদের ব্যবসায়ীদের সাথে যোগাযোগের পদ্ধতিকে প্রমিত করে। এটি একটি প্রমিত রিসোর্স মডেল প্রবর্তনের মাধ্যমে, এজেন্টদের প্রতিটি দোকানের জন্য নিজস্ব এপিআই শেখার সমস্যার সমাধান করে।

এটি যেভাবে কাজ করে:

  1. আবিষ্কার : প্রতিটি UCP-সম্মত মার্চেন্ট একটি নির্দিষ্ট স্থানে একটি প্রোফাইল প্রকাশ করে: /.well-known/ucp । উদাহরণ: এভারলেনের UCP এন্ডপয়েন্ট । যখন কোনো এজেন্ট এই প্রোফাইলটি পড়ে, তখন এটি যা খোঁজে তা হলো:
    • কার্যক্ষমতা : একটি ব্যবসা যে স্বতন্ত্র মূল বৈশিষ্ট্যগুলো সমর্থন করে, যেমন ক্যাটালগ অনুসন্ধান বা চেকআউট।
    • সার্ভিস : ডেটা আদান-প্রদানের জন্য ব্যবহৃত নিম্ন-স্তরের যোগাযোগ স্তর। উদাহরণ: REST API, MCP (মডেল কনটেক্সট প্রোটোকল), A2A (এজেন্টটুএজেন্ট প্রোটোকল)।
    • এক্সটেনশন : কোনো ব্যবসায়ীর যদি বিশেষ কোনো আচরণের প্রয়োজন হয়, তবে তিনি এই প্রোফাইলে কাস্টম এক্সটেনশন নির্ধারণ করতে পারেন।
  2. অপারেশনসমূহ : একবার আবিষ্কৃত হলে, এজেন্ট অপারেশন সম্পাদন করার জন্য প্রদত্ত সার্ভিস এন্ডপয়েন্টটি ব্যবহার করে। এই কোডল্যাবে, আমরা সার্ভিস ট্রান্সপোর্ট হিসেবে মডেল কনটেক্সট প্রোটোকল (MCP) ব্যবহার করি। এজেন্ট আবিষ্কৃত সক্ষমতাগুলো—যেমন প্রোডাক্ট অনুসন্ধান করা, চেকআউট তৈরি করা এবং কেনাকাটা সম্পন্ন করা—ব্যবহার করার জন্য এই এন্ডপয়েন্টে JSON-RPC 2.0 কল করে।

ইউসিপি ওয়ার্কফ্লো

এজেন্ট পেমেন্ট প্রোটোকল ( AP2 )

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

এটি যেভাবে কাজ করে:

  1. কার্ট ম্যান্ডেট : যখন কোনো এজেন্ট UCP প্রোটোকল ব্যবহার করে একটি চেকআউট তৈরি করেন, তখন মার্চেন্ট একটি CartMandate ফেরত দেন। এটি একটি JSON অবজেক্ট, যাতে কার্টের বিবরণ এবং মার্চেন্টের একটি ক্রিপ্টোগ্রাফিক স্বাক্ষর থাকে। এটি একটি প্রাইস লক গ্যারান্টি হিসেবে কাজ করে। এই ম্যান্ডেট জারি করার পর মার্চেন্ট আর মূল্য পরিবর্তন করতে পারেন না।
  2. পেমেন্ট ম্যান্ডেট : কার্টের বিষয়বস্তু যাচাই করার পর, ব্যবহারকারী (অথবা ব্যবহারকারীর পক্ষে এজেন্ট) পেমেন্ট অনুমোদন করার জন্য একটি PaymentMandate তৈরি করেন। এই PaymentMandate CartMandate নির্দেশ করে এবং এতে ব্যবহারকারীর ক্রিপ্টোগ্রাফিক স্বাক্ষর (বা অনুমোদন টোকেন) অন্তর্ভুক্ত থাকে।
  3. দ্বৈত স্বাক্ষর যাচাইকরণ : মার্চেন্ট উভয় ম্যান্ডেটই গ্রহণ করেন। তিনি CartMandate নিজের স্বাক্ষর এবং PaymentMandate ) ব্যবহারকারীর স্বাক্ষর যাচাই করেন। যদি উভয়ই বৈধ হয়, তবে লেনদেনটি সম্পন্ন হয়।

এই 'ডাবল লক' সিস্টেমটি নিশ্চিত করে যে মার্চেন্টরা অতিরিক্ত চার্জ নিতে পারবে না এবং এজেন্টরা অনুমোদন ছাড়া খরচ করতে পারবে না। প্রোডাকশনে, ব্যবহারকারীর গোপনীয়তা রক্ষা করার জন্য এই নিয়মগুলো SD-JWT (সিলেক্টিভ ডিসক্লোজার JWT) ব্যবহার করে।

AP2 ওয়ার্কফ্লো

৩. আপনার পরিবেশ প্রস্তুত করুন

গুগল ক্লাউড প্রজেক্ট সেটআপ

একটি গুগল ক্লাউড প্রজেক্ট তৈরি করুন

  1. গুগল ক্লাউড কনসোলের প্রজেক্ট সিলেক্টর পেজে, একটি গুগল ক্লাউড প্রজেক্ট নির্বাচন করুন বা তৈরি করুন
  2. আপনার ক্লাউড প্রোজেক্টের জন্য বিলিং চালু আছে কিনা তা নিশ্চিত করুন। কোনো প্রোজেক্টে বিলিং চালু আছে কিনা তা কীভাবে পরীক্ষা করবেন, তা জেনে নিন।

ক্লাউড শেল শুরু করুন

ক্লাউড শেল হলো গুগল ক্লাউডে চালিত একটি কমান্ড-লাইন পরিবেশ, যা প্রয়োজনীয় টুলস সহ আগে থেকেই লোড করা থাকে।

  1. Google Cloud কনসোলের শীর্ষে থাকা Activate Cloud Shell-এ ক্লিক করুন।
  2. ক্লাউড শেলে সংযুক্ত হওয়ার পর, আপনার প্রমাণীকরণ যাচাই করুন:
    gcloud auth list
    
  3. আপনার প্রজেক্টটি কনফিগার করা হয়েছে কিনা তা নিশ্চিত করুন:
    gcloud config get project
    
  4. আপনার প্রজেক্টটি প্রত্যাশা অনুযায়ী সেট করা না থাকলে, এটি সেট করুন:
    export PROJECT_ID=<YOUR_PROJECT_ID>
    gcloud config set project $PROJECT_ID
    

জেমিনি মডেলগুলিতে অ্যাক্সেস করুন

আপনার ক্লাউড শেল এনভায়রনমেন্টে নিম্নলিখিত কমান্ডগুলো কপি-পেস্ট করুন। এর মাধ্যমে সিনে এজেন্ট যে জেমিনি মডেলগুলো ব্যবহার করবে, সেগুলোতে অ্যাক্সেস পাওয়া যাবে।

export GOOGLE_CLOUD_PROJECT=$PROJECT_ID
export GOOGLE_CLOUD_LOCATION=global
export GOOGLE_GENAI_USE_VERTEXAI=True

ডিরেক্টরি কাঠামো সেটআপ করুন

এজেন্টের জন্য একটি নতুন ডিরেক্টরি তৈরি করতে নিম্নলিখিত কমান্ডগুলি কপি-পেস্ট করুন:

mkdir -p agent_payments
cd agent_payments

নির্ভরতা ইনস্টল করুন

পরিবেশ ও নির্ভরতা ব্যবস্থাপনার জন্য uv সাথে গুগল ক্লাউড শেল আগে থেকেই ইনস্টল করা থাকে।

  1. আপনার agent_payments ফোল্ডারের রুটে একটি pyproject.toml ফাইল তৈরি করুন এবং এতে নিম্নলিখিত বিষয়বস্তু যোগ করুন। এই ফাইলটি প্রোজেক্টের মেটাডেটা এবং নির্ভরতা নির্ধারণ করে।
[project]
name = "agent-payments-demo"
version = "0.1.0"
description = "CineAgent booking agent using UCP and AP2"
requires-python = ">=3.11"
dependencies = [
    "google-adk>=1.29.0",
    "google-genai>=1.27.0",
    "fastapi>=0.115.0",
    "uvicorn>=0.34.0",
    "httpx>=0.28.0",
    "ap2 @ git+https://github.com/google-agentic-commerce/AP2.git@main",
    "ucp-sdk @ git+https://github.com/Universal-Commerce-Protocol/python-sdk.git@main",
]
  1. ভার্চুয়াল এনভায়রনমেন্ট তৈরি করতে এবং সমস্ত ডিপেন্ডেন্সি ইনস্টল করতে নিম্নলিখিত কমান্ডটি চালান:
uv sync
  1. uv দ্বারা তৈরি ভার্চুয়াল পরিবেশটি সক্রিয় করুন:
source .venv/bin/activate

৪. এজেন্টকে সংজ্ঞায়িত করুন

যেকোনো টুল লজিক লেখার আগে, agent.py নামের একটি ফাইলে এজেন্টটিকে সংজ্ঞায়িত করা যাক। এই এজেন্টটি মুভি বুকিং প্রক্রিয়ার অর্কেস্ট্রেটর হিসেবে কাজ করবে।

agent.py তৈরি করুন :

"""CineAgent — movie ticket booking agent using UCP and AP2."""

from google.adk.agents import Agent
from google.adk.tools import FunctionTool
from agent_payments.tools import (
    discover_theaters,
    search_movies,
    get_movie_detail,
    create_checkout,
    complete_purchase,
)

root_agent = Agent(
    model="gemini-3.1-pro-preview",
    name="cineagent",
    description="Movie ticket booking agent using UCP and AP2.",
    instruction="""You are CineAgent, a movie ticket booking assistant.

You help users find and book movie tickets across multiple theaters
using UCP (Universal Commerce Protocol) and AP2 (Agent Payments).

**Your tools:**
- discover_theaters: Find theaters and what they support
- search_movies: Search movies across all theaters
- get_movie_detail: Get showtimes at a specific theater
- create_checkout: Start a checkout session
- complete_purchase: Finalize with AP2 mandate signing

**Rules:**
- Always call discover_theaters first if you haven't yet
- Keep responses concise — summarize and suggest next steps
- Prices from tools are in cents (1500 = $15.00)
- Never invent data — only state what tools return
""",
    tools=[
        discover_theaters,
        search_movies,
        get_movie_detail,
        create_checkout,
        FunctionTool(complete_purchase, require_confirmation=True),
    ],
)

কোডের ব্যাখ্যা

চলুন এই এজেন্ট সংজ্ঞায় কী ঘটছে তা বিশ্লেষণ করা যাক:

  • model="gemini-3.1-pro-preview" : আমরা জটিল যুক্তি ও সরঞ্জাম ব্যবহারের জন্য সর্বশেষ জেমিনি প্রো প্রিভিউ মডেলটি ব্যবহার করছি।
  • instruction : এটি সেই নির্দেশ যা এজেন্টের আচরণকে পরিচালিত করে। এটি এজেন্টকে স্পষ্টভাবে UCP এবং AP2 ব্যবহার করতে বলে, উপলব্ধ সরঞ্জামগুলির তালিকা দেয় এবং "কখনোই মনগড়া ডেটা তৈরি করবে না" ও "দাম সেন্টে হবে"-এর মতো গুরুত্বপূর্ণ নিয়ম নির্ধারণ করে দেয়।
  • tools : এটি পাইথন ফাংশনগুলোর একটি তালিকা (যা আমরা পরবর্তীতে তৈরি করব) যেগুলো এজেন্ট ব্যবহারকারীর অনুরোধের ভিত্তিতে কল করার জন্য বেছে নিতে পারে।
  • require_confirmation : আপনি যেকোনো টুলকে FunctionTool(my_function,require_confirmation=True) দিয়ে র‍্যাপ করতে পারেন। এটি ট্রিগার হলে, টুলটি এক্সিকিউট করার আগে এজেন্টটি থেমে যায় এবং একটি সাধারণ "হ্যাঁ" বা "না" অনুমোদনের জন্য অপেক্ষা করে। এখানে, complete_purchase টুলটি এক্সিকিউট করার আগে, এজেন্টটি মানুষের নিশ্চিতকরণের জন্য থেমে যায়।

সরঞ্জাম তালিকা

এজেন্ট ডেফিনিশন ঘোষণা করে যে আমাদের কী তৈরি করতে হবে। প্রতিটি টুল UCP বা AP2 প্রোটোকলের একটি নির্দিষ্ট অপারেশনের সাথে ম্যাপ করা থাকে:

সরঞ্জাম

এটা যা করে

প্রোটোকল অ্যাকশন

discover_theaters

ব্যবসায়ীদের এবং তাদের সক্ষমতা খুঁজুন

কোয়েরি /.well-known/ucp

search_movies

ব্যবসায়ীদের মধ্যে ক্যাটালগ অনুসন্ধান করুন

JSON-RPC থেকে MCP এন্ডপয়েন্ট

get_movie_detail

একটি নির্দিষ্ট দোকানে শো-টাইম জেনে নিন।

JSON-RPC থেকে MCP এন্ডপয়েন্ট

create_checkout

চেকআউট সেশন শুরু করুন

JSON-RPC থেকে MCP এন্ডপয়েন্ট

complete_purchase

পেমেন্ট অনুমোদন করুন এবং অর্ডারটি সম্পন্ন করুন।

এপি২ ম্যান্ডেটে স্বাক্ষর করে এমসিপি-র কাছে পাঠিয়েছেন

জেমিনি মডেলটি কথোপকথনের উপর ভিত্তি করে সিদ্ধান্ত নেবে কখন কোন টুলকে ডাকা হবে । এরপর আমাদের কাজ হলো tools.py ফাইলে প্রতিটি টুলের কার্যকারিতা বাস্তবায়ন করা।

৫. এজেন্ট টুল তৈরি করুন: আবিষ্কার ও ব্রাউজিং

এখন এজেন্ট সিনেমা ব্রাউজ ও আবিষ্কার করার জন্য যে টুলগুলো ব্যবহার করবে, সেগুলো বাস্তবায়ন করা যাক। প্রতিটি টুল একটি UCP অপারেশনকে ঘিরে থাকে।

tools.py নামে একটি নতুন ফাইল তৈরি করুন এবং নিম্নলিখিত কোডটি কপি-পেস্ট করুন:

"""Agent tools — each one wraps a UCP or AP2 operation."""

import asyncio
import json

from .ucp import UCPClient
from .ap2 import AP2Handler

# Initialize clients directly
_merchant_urls = ["http://localhost:8081", "http://localhost:8082"]
_ucp = UCPClient()
_ap2 = AP2Handler()

সেটআপ বোঝা

এই কোডল্যাবটিকে এজেন্ট তৈরির উপর কেন্দ্রীভূত রাখার জন্য, আমরা দুটি হেল্পার ক্লাস ব্যবহার করছি: UCPClient এবং AP2Handler , যেগুলো নিয়ে আমরা পরবর্তী ধাপে আলোচনা করব।

  • এগুলো কী? : এগুলো হলো হাতে লেখা হেল্পার ক্লাস, যা আমরা এই কোডল্যাবের জন্য তৈরি করেছি মক মার্চেন্টদের সাথে ইন্টারঅ্যাকশন সিমুলেট করার জন্য। যেহেতু অফিশিয়াল UCP এবং AP2 SDK এখনও পাওয়া যাচ্ছে না , তাই আমরা এই হেল্পারগুলো ব্যবহার করে এই শূন্যস্থান পূরণ করছি। প্রোডাকশন এনভায়রনমেন্টে, অফিশিয়াল SDK-গুলো পাওয়া গেলেই আপনারা সেগুলো ব্যবহার করবেন।
  • আপাতত, এগুলোকে সহায়ক অবজেক্ট হিসেবে বিবেচনা করুন :
    • _ucp.discover(url) : কোনো মার্চেন্টের প্রোফাইল ফেচ করে।
    • _ucp.mcp_call(url, method, params) : মার্চেন্টের MCP এন্ডপয়েন্টে একটি JSON-RPC 2.0 অনুরোধ পাঠায়।

থিয়েটার আবিষ্কার করুন

এই টুলটি ইউসিপি প্রক্রিয়ার প্রথম ধাপ। এটি খুঁজে বের করে যে কোন কোন মার্চেন্ট বিদ্যমান এবং তারা কী সমর্থন করে।

tools.py তে যোগ করুন:

async def discover_theaters() -> str:
    """Discover available theater merchants and their capabilities via UCP."""
    theaters = []
    for url in _merchant_urls:
        info = await _ucp.discover(url)
        theaters.append(
            {
                "url": url,
                "name": info["name"],
                "capabilities": info["capabilities"],
                "payment_handlers": info["payment_handlers"],
            }
        )
    return json.dumps(theaters, indent=2)

এর কাজ হলো :

  1. এটি সার্ভার দ্বারা প্রদত্ত মার্চেন্ট ইউআরএল-এর একটি তালিকার উপর পুনরাবৃত্তি করে। এই কোডল্যাবের পরবর্তী অংশে আমরা দুটি মক মার্চেন্ট সেটআপ করব।
  2. প্রতিটি URL-এর জন্য, এটি _ucp.discover(url) কল করে, যা /.well-known/ucp এন্ডপয়েন্টে হিট করে।
  3. এটি নাম, সক্ষমতা এবং পেমেন্ট হ্যান্ডলারদের একটি সারসংক্ষেপ তালিকায় সংগ্রহ করে।
  4. এটি এজেন্টের পড়ার জন্য তালিকাটিকে একটি JSON স্ট্রিং হিসেবে ফেরত দেয়।

সিনেমা অনুসন্ধান করুন

এই টুলটি খুঁজে পাওয়া সমস্ত বিক্রেতার মধ্যে অনুসন্ধান করে এবং ফলাফলগুলোকে একত্রিত করে। এটি অত্যন্ত গুরুত্বপূর্ণ, কারণ একই সিনেমা একাধিক প্রেক্ষাগৃহে ভিন্ন ভিন্ন ফরম্যাটে (আইম্যাক্স, ডলবি) এবং ভিন্ন ভিন্ন দামে প্রদর্শিত হতে পারে।

tools.py তে যোগ করুন:

async def search_movies(query: str = "") -> str:
    """Search for movies across all theaters. Use '' to browse all."""
    all_movies = {}
    for url, merchant in _ucp.merchants.items():
        result = await _ucp.mcp_call(url, "search_catalog", {"query": query})
        for product in result.get("products", []):
            mid = product["id"]
            if mid not in all_movies:
                all_movies[mid] = {
                    "id": mid,
                    "title": product["title"],
                    "categories": product.get("categories", []),
                    "theaters": {},
                }
            showtimes = []
            for v in product.get("variants", []):
                opts = {
                    o["name"]: o["value"]
                    for o in v.get("selected_options", [])
                }
                showtimes.append(
                    {
                        "id": v["id"],
                        "format": opts.get("format", "Standard"),
                        "time": opts.get("time", ""),
                        "price": v.get("price", {}),
                        "seats": v.get("availability", {}).get(
                            "seats_available", 0
                        ),
                    }
                )
            all_movies[mid]["theaters"][url] = {
                "name": merchant["name"],
                "showtimes": showtimes,
            }
    return json.dumps(list(all_movies.values()), indent=2)

এর কাজ হলো :

  1. এটি আবিষ্কৃত সমস্ত প্রেক্ষাগৃহের মধ্যে চক্রাকারে ঘোরে।
  2. এটি মার্চেন্টের MCP এন্ডপয়েন্টে একটি সার্চ ক্যাটালগ JSON-RPC রিকোয়েস্ট ( _ucp.mcp_call(url, "search_catalog", {"query": query}) ) পাঠায়।
  3. এরপর এটি ফলাফলগুলো পার্স করে সিনেমা এবং তাদের 'ভেরিয়েন্ট' (যা নির্দিষ্ট শো-টাইম এবং ফরম্যাট নির্দেশ করে) খুঁজে বের করার জন্য কিছু পরিষ্করণ করে। সিনেমাগুলোকে আইডি অনুসারে গ্রুপ করা হয়, যাতে ব্যবহারকারী একই সিনেমার একাধিক এন্ট্রি দেখতে না পান।

চলচ্চিত্রের বিস্তারিত জানুন

এই টুলটি একটি নির্দিষ্ট প্রেক্ষাগৃহে একটি নির্দিষ্ট সিনেমার সম্পূর্ণ ক্যাটালগের বিবরণ খুঁজে বের করে।

tools.py তে যোগ করুন:

async def get_movie_detail(movie_id: str, merchant_url: str) -> str:
    """Get detailed showtimes for a movie at a specific theater."""
    result = await _ucp.mcp_call(
        merchant_url, "lookup_catalog", {"product_id": movie_id}
    )
    return json.dumps(result, indent=2)

এর কাজ হলো :

  • এটি নির্দিষ্ট movie_id পাস করে মার্চেন্টের MCP এন্ডপয়েন্টে lookup_catalog মেথডটিকে কল করে। এর ফলে সেই নির্দিষ্ট থিয়েটারের শো-টাইম এবং সিটের প্রাপ্যতার মতো বিস্তারিত তথ্য ফেরত আসে।

৬. এজেন্ট টুল তৈরি করুন: চেকআউট ও পেমেন্ট

এই টুলগুলো চেকআউট এবং ক্রয় প্রক্রিয়া পরিচালনা করে। এখানেই নিরাপদ লেনদেন নিশ্চিত করতে AP2 প্রোটোকল কার্যকর ভূমিকা পালন করে।

চেকআউট তৈরি করুন

এই টুলটি একটি নির্দিষ্ট থিয়েটারে একটি নির্দিষ্ট শো-টাইমের জন্য চেকআউট সেশন শুরু করে।

tools.py তে যোগ করুন:

async def create_checkout(
    merchant_url: str, showtime_id: str, quantity: int = 1
) -> str:
    """Create a checkout session for tickets at a theater."""
    result = await _ucp.mcp_call(merchant_url, "create_checkout", {
        "checkout": {
            "line_items": [
                {"item": {"id": showtime_id}, "quantity": quantity}
            ],
            "context": {"country": "US", "currency": "USD"},
        }
    })
    return json.dumps(result, indent=2)

এর কাজ হলো :

  1. এটি মার্চেন্টের MCP এন্ডপয়েন্টে create_checkout মেথডটিকে কল করে।
  2. এটি ব্যবহারকারীর অনুরোধ করা showtime_id এবং quantity প্রেরণ করে।
  3. মার্চেন্ট একটি JSON অবজেক্ট ফেরত দেয়, যাতে একটি AP2 CartMandate থাকে।

দ্রষ্টব্য : আপনি যদি রেসপন্স ডেটা পরীক্ষা করেন, তাহলে দেখবেন ap2.cart_mandate এ একটি merchant_authorization ফিল্ড রয়েছে। এটি হলো মার্চেন্টের ক্রিপ্টোগ্রাফিক সিগনেচার যা উদ্ধৃত মূল্যকে লক করে রাখে। তারা পরবর্তীতে এটি পরিবর্তন করতে পারে না!

সম্পূর্ণ ক্রয়

এই টুলটিতে তিনটি ঘটনা ঘটে:

  1. আমরা চেকআউট থেকে কার্টম্যান্ডেটটি নিয়ে তা (নকলভাবে) যাচাই করি।
  2. পেমেন্ট ম্যান্ডেট (নকল) তৈরি করুন এবং স্বাক্ষর করুন।
  3. ক্রয়টি সম্পন্ন করার জন্য স্বাক্ষরিত ম্যান্ডেটটি মার্চেন্টের কাছে পাঠিয়ে দিন।

tools.py তে যোগ করুন:

async def complete_purchase(
    checkout_id: str, merchant_url: str, payment_method: str = "card"
) -> str:
    """Complete purchase with AP2 payment authorization."""
    # 1. Get the CartMandate from the checkout
    checkout = await _ucp.mcp_call(
        merchant_url, "get_checkout", {"checkout": {"id": checkout_id}}
    )
    cart_mandate = _ap2.process_cart_mandate(checkout)
    if not cart_mandate:
        return {"error": "No cart mandate — checkout may have expired"}

    # 2-3. Create and sign the PaymentMandate
    # In production, this call would trigger a user prompt (biometric or device auth)
    # via the AP2 Wallet SDK. In this demo, it just computes a mock SHA-256 hash.
    payment_mandate = _ap2.create_payment_mandate(cart_mandate, payment_method)

    # 4. Send both mandates to complete the purchase
    result = await _ucp.mcp_call(merchant_url, "complete_checkout", {
        "checkout": {
            "id": checkout_id,
            "payment": {
                "instruments": [{
                    "handler_id": f"card_{merchant_url.split(':')[-1]}",
                    "type": "card",
                }],
            },
            "ap2": {"payment_mandate": payment_mandate},
        }
    })
    return json.dumps(result, indent=2)

দুটি ম্যান্ডেট কেন? কার্টম্যান্ডেট নিশ্চিত করে যে, মার্চেন্ট মূল্য উদ্ধৃত করার পর তা পরিবর্তন করতে পারবে না। পেমেন্টম্যান্ডেট নিশ্চিত করে যে, এজেন্ট ব্যবহারকারীর সম্মতি ছাড়া তার থেকে কোনো অর্থ চার্জ করতে পারবে না। কার্যপ্রবাহটি হলো:

Merchant locks price -> User authorizes charge -> Merchant verifies both -> Order completes

চেকপয়েন্ট: সম্পূর্ণ tools.py

আপনার সম্পূর্ণ tools.py এখন ৫টি টুল ফাংশন এবং UCP ও AP2 ক্লায়েন্টদের জন্য মডিউল-স্তরের ইনিশিয়ালাইজেশন থাকা উচিত। যাচাই করুন এটি দেখতে এইরকম:

"""Agent tools — each one wraps a UCP or AP2 operation."""

import asyncio
import json

from ucp import UCPClient
from ap2 import AP2Handler

# Initialize clients directly
_merchant_urls = ["http://localhost:8081", "http://localhost:8082"]
_ucp = UCPClient()
_ap2 = AP2Handler()


async def discover_theaters() -> str:
    """Discover available theater merchants and their capabilities via UCP."""
    theaters = []
    for url in _merchant_urls:
        info = await _ucp.discover(url)
        theaters.append(
            {
                "url": url,
                "name": info["name"],
                "capabilities": info["capabilities"],
                "payment_handlers": info["payment_handlers"],
            }
        )
    return json.dumps(theaters, indent=2)


async def search_movies(query: str = "") -> str:
    """Search for movies across all theaters. Use '' to browse all."""
    all_movies = {}
    for url, merchant in _ucp.merchants.items():
        result = await _ucp.mcp_call(url, "search_catalog", {"query": query})
        for product in result.get("products", []):
            mid = product["id"]
            if mid not in all_movies:
                all_movies[mid] = {
                    "id": mid,
                    "title": product["title"],
                    "categories": product.get("categories", []),
                    "theaters": {},
                }
            showtimes = []
            for v in product.get("variants", []):
                opts = {
                    o["name"]: o["value"]
                    for o in v.get("selected_options", [])
                }
                showtimes.append(
                    {
                        "id": v["id"],
                        "format": opts.get("format", "Standard"),
                        "time": opts.get("time", ""),
                        "price": v.get("price", {}),
                        "seats": v.get("availability", {}).get(
                            "seats_available", 0
                        ),
                    }
                )
            all_movies[mid]["theaters"][url] = {
                "name": merchant["name"],
                "showtimes": showtimes,
            }
    return json.dumps(list(all_movies.values()), indent=2)


async def get_movie_detail(movie_id: str, merchant_url: str) -> str:
    """Get detailed showtimes for a movie at a specific theater."""
    result = await _ucp.mcp_call(
        merchant_url, "lookup_catalog", {"product_id": movie_id}
    )
    return json.dumps(result, indent=2)


async def create_checkout(
    merchant_url: str, showtime_id: str, quantity: int = 1
) -> str:
    """Create a checkout session for tickets at a theater."""
    result = await _ucp.mcp_call(merchant_url, "create_checkout", {
        "checkout": {
            "line_items": [
                {"item": {"id": showtime_id}, "quantity": quantity}
            ],
            "context": {"country": "US", "currency": "USD"},
        }
    })
    return json.dumps(result, indent=2)


async def complete_purchase(
    checkout_id: str, merchant_url: str, payment_method: str = "card"
) -> str:
    """Complete purchase with AP2 payment authorization."""
    # 1. Get the CartMandate from the checkout
    checkout = await _ucp.mcp_call(
        merchant_url, "get_checkout", {"checkout": {"id": checkout_id}}
    )
    cart_mandate = _ap2.process_cart_mandate(checkout)
    if not cart_mandate:
        return {"error": "No cart mandate — checkout may have expired"}

    # 2-3. Create and sign the PaymentMandate
    # In production, this call would trigger a user prompt (biometric or device auth)
    # via the AP2 Wallet SDK. In this demo, it just computes a mock SHA-256 hash.
    payment_mandate = _ap2.create_payment_mandate(cart_mandate, payment_method)

    # 4. Send both mandates to complete the purchase
    result = await _ucp.mcp_call(merchant_url, "complete_checkout", {
        "checkout": {
            "id": checkout_id,
            "payment": {
                "instruments": [{
                    "handler_id": f"card_{merchant_url.split(':')[-1]}",
                    "type": "card",
                }],
            },
            "ap2": {"payment_mandate": payment_mandate},
        }
    })
    return json.dumps(result, indent=2)

৭. এটিকে চালনাযোগ্য করে তুলুন

আসল UCP মার্চেন্টদের ক্ষেত্রে, আপনি এজেন্টকে তাদের URL-গুলো দেখিয়ে দিলেই কাজ শেষ। এই কোডল্যাবের জন্য, আমাদের স্থানীয়ভাবে পরীক্ষা করার জন্য দুটি জিনিস প্রয়োজন:

  1. মক মার্চেন্ট — স্থানীয় সার্ভার যা UCP এন্ডপয়েন্ট অনুকরণ করে, যাতে আপনার কাছে পরীক্ষা করার জন্য একটি ভিত্তি থাকে।
  2. প্রোটোকল হেল্পার — UCP এবং AP2-এর জন্য হালকা HTTP র‍্যাপার (প্রোডাকশনে, অফিসিয়াল SDK-গুলো এগুলোর স্থান নেয়)

দ্রষ্টব্য : আপনার এই কোডটি মনোযোগ দিয়ে পড়ার প্রয়োজন নেই। এই ফাইলগুলো বাস্তব পরিকাঠামো এবং এসডিকে যা প্রদান করে, তার একটি অনুকরণ। এগুলো যেমন আছে তেমনই কপি করুন।

প্রোটোকল সহায়ক

UCP এবং AP2-এর এখনও কোনো ক্লায়েন্ট SDK নেই — এই দুটি ফাইল HTTP-এর মূল সংযোগ ব্যবস্থাটি পরিচালনা করে।

ucp.py তৈরি করুন :

"""UCP client — discovers merchants and calls their MCP tools."""

import uuid
import httpx


class UCPClient:
    def __init__(self):
        self.client = httpx.AsyncClient(timeout=30)
        self.merchants = {}  # url -> merchant info dict

    async def discover(self, merchant_url: str) -> dict:
        """Fetch a merchant's UCP profile from /.well-known/ucp."""
        resp = await self.client.get(f"{merchant_url}/.well-known/ucp")
        resp.raise_for_status()
        profile = resp.json()
        ucp = profile["ucp"]
        info = {
            "name": merchant_url.split("//")[-1],
            "mcp_endpoint": ucp["services"]["dev.ucp.shopping"][0]["endpoint"],
            "capabilities": list(ucp.get("capabilities", {}).keys()),
            "payment_handlers": list(ucp.get("payment_handlers", {}).keys()),
        }
        self.merchants[merchant_url] = info
        return info

    async def mcp_call(
        self, merchant_url: str, tool_name: str, arguments: dict
    ) -> dict:
        """Call a merchant's MCP tool via JSON-RPC 2.0."""
        merchant = self.merchants[merchant_url]
        resp = await self.client.post(
            merchant["mcp_endpoint"],
            json={
                "jsonrpc": "2.0",
                "id": uuid.uuid4().hex,
                "method": "tools/call",
                "params": {"name": tool_name, "arguments": arguments},
            },
        )
        resp.raise_for_status()
        data = resp.json()
        if "error" in data:
            raise Exception(f"MCP error: {data['error']}")
        return data.get("result", {})

    async def close(self):
        await self.client.aclose()

ap2.py তৈরি করুন :

"""AP2 mandate handler — creates and signs payment mandates."""

import uuid
import hashlib


class AP2Handler:
    def process_cart_mandate(self, checkout_response: dict) -> dict | None:
        """Extract the merchant-signed CartMandate from a checkout response.

        The CartMandate is the merchant's cryptographic price guarantee —
        it locks the total so it can't change between checkout and payment.
        """
        return checkout_response.get("ap2", {}).get("cart_mandate")

    def create_payment_mandate(
        self, cart_mandate: dict, payment_method: str = "card"
    ) -> dict:
        """Create and sign a PaymentMandate authorizing payment.

        References the merchant's CartMandate and adds user authorization.
        Together they form a two-party agreement: merchant guarantees price,
        user authorizes charge.
        """
        contents = cart_mandate["contents"]
        mandate_id = uuid.uuid4().hex

        return {
            "mandate_id": mandate_id,
            "cart_reference": contents["id"],
            "merchant": contents["merchant_name"],
            "total": contents["total"],
            "payment_method": payment_method,
            "user_authorization": self._sign(mandate_id, contents["id"]),
        }

    def _sign(self, mandate_id: str, checkout_id: str) -> str:
        """Sign the mandate. Production uses real crypto (sd-jwt-vc)."""
        payload = f"{mandate_id}:{checkout_id}"
        return hashlib.sha256(payload.encode()).hexdigest()

নকল ব্যবসায়ীরা

merchants.py তৈরি করুন:

"""Mock UCP merchant servers — two theaters with different capabilities."""

import uuid
import time
import multiprocessing
from datetime import datetime, timezone, timedelta

import uvicorn
from fastapi import FastAPI

# ── Theater data ────────────────────────────────────────────

THEATERS = {
    8081: {
        "name": "Meridian Cinemas",
        "movies": [
            {
                "id": "opp",
                "title": "Oppenheimer",
                "categories": ["Drama", "History"],
                "showtimes": [
                    {"id": "st_opp_7pm_imax", "format": "IMAX", "time": "7:00 PM", "price": 2200, "seats": 45},
                    {"id": "st_opp_930pm", "format": "Standard", "time": "9:30 PM", "price": 1500, "seats": 80},
                ],
            },
            {
                "id": "dune3",
                "title": "Dune: Part Three",
                "categories": ["Sci-Fi", "Action"],
                "showtimes": [
                    {"id": "st_dune_8pm_imax", "format": "IMAX", "time": "8:00 PM", "price": 2200, "seats": 30},
                ],
            },
        ],
        "discounts": {},
    },
    8082: {
        "name": "StarLight Theaters",
        "movies": [
            {
                "id": "opp",
                "title": "Oppenheimer",
                "categories": ["Drama", "History"],
                "showtimes": [
                    {"id": "st_opp_6pm_atmos", "format": "Dolby Atmos", "time": "6:00 PM", "price": 1800, "seats": 60},
                ],
            },
            {
                "id": "spider",
                "title": "Spider-Verse",
                "categories": ["Animation", "Action"],
                "showtimes": [
                    {"id": "st_spider_4pm", "format": "Standard", "time": "4:00 PM", "price": 1200, "seats": 100},
                ],
            },
        ],
        "discounts": {},
    },
}


def create_app(port):
    theater = THEATERS[port]
    app = FastAPI()
    sessions = {}

    # ── UCP Discovery endpoint ──────────────────────────────
    @app.get("/.well-known/ucp")
    def discovery():
        caps = {
            "dev.ucp.shopping.catalog.search": [{"version": "2026-01-15"}],
            "dev.ucp.shopping.catalog.lookup": [{"version": "2026-01-15"}],
            "dev.ucp.shopping.checkout": [{"version": "2026-01-15"}],
            "dev.ucp.shopping.ap2_mandate": [{"version": "2026-01-15"}],
        }

        return {
            "ucp": {
                "version": "2026-01-15",
                "services": {
                    "dev.ucp.shopping": [
                        {"version": "2026-01-15", "transport": "mcp",
                         "endpoint": f"http://localhost:{port}/mcp"}
                    ]
                },
                "capabilities": caps,
                "payment_handlers": {
                    "com.example.card": [
                        {"id": f"card_{port}", "version": "2026-01-15",
                         "available_instruments": [{"type": "card"}], "config": {}}
                    ]
                },
            }
        }

    # ── MCP JSON-RPC endpoint ───────────────────────────────
    @app.post("/mcp")
    def mcp(body: dict):
        tool = body["params"]["name"]
        args = body["params"].get("arguments", {})
        rid = body.get("id", "1")

        if tool == "search_catalog":
            q = args.get("query", "").lower()
            hits = [m for m in theater["movies"]
                    if not q or q in m["title"].lower()
                    or any(q in c.lower() for c in m["categories"])]
            return _ok(rid, {"products": [_product(m) for m in hits]})

        if tool == "lookup_catalog":
            mid = args.get("product_id") or (args.get("ids", [None])[0])
            movie = next((m for m in theater["movies"] if m["id"] == mid), None)
            if not movie:
                return _err(rid, "Not found")
            return _ok(rid, {"products": [_product(movie)]})

        if tool == "create_checkout":
            co = args.get("checkout", {})
            sid = f"chk_{uuid.uuid4().hex[:12]}"
            items, subtotal = [], 0
            for li in co.get("line_items", []):
                st = _find_showtime(li["item"]["id"])
                if not st:
                    continue
                mv = _find_movie(li["item"]["id"])
                qty = li.get("quantity", 1)
                amt = st["price"] * qty
                subtotal += amt
                items.append({
                    "id": f"li_{uuid.uuid4().hex[:8]}",
                    "item": {
                        "id": st["id"],
                        "title": f"{mv['title']}{st['format']} {st['time']}",
                        "price": st["price"],
                    },
                    "quantity": qty,
                    "totals": [{"type": "subtotal", "amount": amt}],
                })
            tax = int(subtotal * 0.08)
            total = subtotal + tax
            session = {
                "id": sid,
                "status": "ready_for_complete",
                "currency": "USD",
                "line_items": items,
                "totals": [
                    {"type": "subtotal", "display_text": "Subtotal", "amount": subtotal},
                    {"type": "tax", "display_text": "Tax", "amount": tax},
                    {"type": "total", "display_text": "Total", "amount": total},
                ],
                "metadata": {"theater_name": theater["name"]},
                "ap2": {
                    "cart_mandate": {
                        "contents": {
                            "id": sid,
                            "merchant_name": theater["name"],
                            "total": {
                                "label": "Total",
                                "amount": {"currency": "USD", "value": total / 100},
                            },
                            "cart_expiry": (
                                datetime.now(timezone.utc) + timedelta(minutes=10)
                            ).isoformat(),
                        },
                        "merchant_authorization": f"mock_merchant_sig_{sid}",
                    }
                },
            }
            sessions[sid] = session
            return _ok(rid, session)

        if tool == "get_checkout":
            sid = args.get("checkout", {}).get("id") or args.get("id")
            return _ok(rid, sessions.get(sid, {"error": "not_found"}))



        if tool == "complete_checkout":
            co = args.get("checkout", {})
            sid = co.get("id")
            session = sessions.get(sid)
            if not session:
                return _err(rid, "Not found")
            session["status"] = "completed"
            session["order"] = {
                "id": f"ord_{uuid.uuid4().hex[:8]}",
                "created_at": datetime.now(timezone.utc).isoformat(),
                "tickets": [
                    {
                        "movie": li["item"]["title"],
                        "quantity": li["quantity"],
                        "ticket_code": uuid.uuid4().hex[:8].upper(),
                    }
                    for li in session["line_items"]
                ],
            }
            session["ap2"]["payment_mandate_verified"] = True
            return _ok(rid, session)

        return _err(rid, f"Unknown tool: {tool}")

    def _ok(rid, result):
        return {"jsonrpc": "2.0", "id": rid, "result": result}

    def _err(rid, msg):
        return {"jsonrpc": "2.0", "id": rid, "error": {"code": -32000, "message": msg}}

    def _product(movie):
        return {
            "id": movie["id"],
            "title": movie["title"],
            "categories": movie["categories"],
            "variants": [
                {
                    "id": st["id"],
                    "selected_options": [
                        {"name": "format", "value": st["format"]},
                        {"name": "time", "value": st["time"]},
                    ],
                    "price": {"amount": st["price"], "currency": "USD"},
                    "availability": {"available": True, "seats_available": st["seats"]},
                }
                for st in movie["showtimes"]
            ],
        }

    def _find_showtime(sid):
        return next(
            (st for m in theater["movies"] for st in m["showtimes"] if st["id"] == sid),
            None,
        )

    def _find_movie(sid):
        return next(
            (m for m in theater["movies"] for st in m["showtimes"] if st["id"] == sid),
            None,
        )

    return app


def _run(port):
    uvicorn.run(create_app(port), host="0.0.0.0", port=port, log_level="warning")


if __name__ == "__main__":
    for port in THEATERS:
        multiprocessing.Process(target=_run, args=(port,), daemon=True).start()
    print("Merchants running: Meridian (:8081), StarLight (:8082)")
    print("Press Ctrl+C to stop")
    try:
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        pass

এর ফলে দুটি FastAPI সার্ভার তৈরি হয়, যার প্রতিটিতে দুটি করে এন্ডপয়েন্ট থাকে:

  • GET /.well-known/ucp — UCP ডিসকভারি। এটি মার্চেন্টের ক্যাপাবিলিটি, MCP এন্ডপয়েন্ট URL, এবং গৃহীত পেমেন্ট পদ্ধতিগুলো রিটার্ন করে।
  • POST /mcp — MCP (মডেল কনটেক্সট প্রোটোকল) অপারেশন। এটি ক্যাটালগ অনুসন্ধান, চেকআউট, ডিসকাউন্ট এবং পেমেন্টের জন্য JSON-RPC 2.0 কল পরিচালনা করে।

মার্চেন্টদের একটি নতুন টার্মিনালে চালু করুন — তাদের চালু থাকতে হবে:

cd agent_payments
source .venv/bin/activate
python merchants.py

আপনার দেখা উচিত:

Merchants running: Meridian (:8081), StarLight (:8082)

আপনার প্রথম টার্মিনালে ফিরে গিয়ে, UCP ডিসকভারি যাচাই করুন:

curl -s http://localhost:8081/.well-known/ucp | python -m json.tool

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

৮. ADK Web দিয়ে এজেন্টটি চালান

চলুন ADK CLI-এর বিল্ট-ইন ওয়েব UI ব্যবহার করি! এটি ব্রাউজারে একটি চ্যাট ইন্টারফেস প্রদান করে এবং টুল নিশ্চিতকরণের অনুরোধগুলো স্বয়ংক্রিয়ভাবে পরিচালনা করে।

আপনার প্রজেক্টটি এখন দেখতে এইরকম হবে:

agent_payments/
├── merchants.py      # Mock UCP merchants
├── ucp.py            # UCP client helper
├── ap2.py            # AP2 mandate handler
├── tools.py          # Agent tools
├── agent.py          # Agent definition
└── pyproject.toml

চেষ্টা করে দেখুন

বর্তমান টার্মিনালে, প্যারেন্ট ডিরেক্টরিতে ( agent_payments ঠিক উপরের ফোল্ডারটিতে) যান এবং ADK ওয়েব UI চালু করুন:

cd ../
adk web --allow_origins '*'

আপনি এমন একটি আউটপুট দেখতে পাবেন যা থেকে বোঝা যাবে যে সার্ভারটি চালু আছে, এবং এটি আপনাকে একটি URL দেবে (সাধারণত http://localhost:8000 বা এই ধরনের)।

এজেন্টের সাথে কথা বলুন

  1. আপনার ব্রাউজারে adk web কর্তৃক প্রদত্ত URL-টি খুলুন।
  2. আপনি একটি চ্যাট ইন্টারফেস দেখতে পাবেন।
  3. জিজ্ঞাসা করে দেখতে পারেন: "এখন কী কী সিনেমা চলছে?"
  4. এজেন্টটি নেপথ্যে থেকে প্রেক্ষাগৃহ খুঁজে বের করবে এবং ক্যাটালগ অনুসন্ধান করবে, এবং UCP-এর মাধ্যমে উভয় বিক্রেতার কাছ থেকে ফলাফল একত্রিত করবে।
  5. টিকিট বুক করতে বলুন: "সন্ধ্যা ৭টার জন্য ওপেনহাইমারের ২টি টিকিট বুক করুন"
  6. যখন এজেন্ট complete_purchase কল করার চেষ্টা করে, তখন লক্ষ্য করুন কীভাবে ADK ওয়েব UI-তে একটি কনফার্মেশন ডায়ালগ বা কার্ড পপ-আপ হয়!
  7. লেনদেনটি অনুমোদন করতে, চ্যাটে হুবহু এই JSON স্ট্রিংটি লিখে উত্তর দিন: {"confirmed": true}
  8. এজেন্ট ক্রয়টি সম্পন্ন করবে এবং টিকিট কোডসহ আপনার অর্ডার নিশ্চিতকরণ ফেরত পাঠাবে!

নেপথ্যে যা ঘটেছিল তা এখানে দেওয়া হলো:

  1. create_checkout → মার্চেন্ট একটি AP2 CartMandate (স্বাক্ষরিত মূল্য লক) ফেরত দিয়েছে
  2. complete_purchase → একটি পেমেন্টম্যান্ডেট তৈরি করা হয়েছে, তাতে স্বাক্ষর করা হয়েছে (নকল SHA-256), এবং উভয় ম্যান্ডেট মার্চেন্টের কাছে পাঠানো হয়েছে।
  3. বণিক উভয় স্বাক্ষর যাচাই করে টিকিট ইস্যু করেছেন (নকল নমুনায়)।

৯. পরিষ্কার করুন

স্থানীয় সার্ভারগুলো চালু থাকা এড়াতে, রিসোর্সগুলো পরিষ্কার করুন:

  1. টার্মিনালে adk web চালানোর সময়, এজেন্ট সার্ভারটি বন্ধ করতে Ctrl+C চাপুন।
  2. টার্মিনালে python merchants.py চালানোর সময়, মক মার্চেন্টগুলো বন্ধ করতে Ctrl+C চাপুন।
  3. উভয় টার্মিনালে ভার্চুয়াল পরিবেশ নিষ্ক্রিয় করতে নিম্নলিখিত কমান্ডটি চালান:
deactivate
  1. (ঐচ্ছিক) আপনি যদি এই কোডল্যাবের জন্য একটি নতুন গুগল ক্লাউড প্রজেক্ট তৈরি করে থাকেন এবং সেটি মুছে ফেলতে চান, তাহলে চালান:
gcloud projects delete $GOOGLE_CLOUD_PROJECT

১০. অভিনন্দন! 🎉

আপনি একটি ADK এজেন্ট তৈরি করেছেন যা UCP এবং AP2 ব্যবহার করে মার্চেন্ট খুঁজে বের করে, ক্যাটালগ ব্রাউজ করে এবং কেনাকাটা সম্পন্ন করে।

আপনি যা শিখেছেন

এই কোডল্যাবে, আপনি একটি ADK এজেন্ট তৈরি করেছেন যা সুরক্ষিত কমার্স ফ্লো পরিচালনা করে। আপনি যা তৈরি করেছেন এবং যে মূল ধারণাগুলো প্রয়োগ করেছেন তার একটি সারসংক্ষেপ নিচে দেওয়া হলো:

আপনি যা তৈরি করেছেন:

  • UCP এবং AP2 অপারেশনসমূহ— ডিসকভারি, ক্যাটালগ সার্চ, চেকআউট, পেমেন্ট— সমন্বিত ৫টি এজেন্ট টুল
  • AP2 ম্যান্ডেট স্বাক্ষর — কার্টম্যান্ডেট (মার্চেন্টের মূল্য লক) + পেমেন্টম্যান্ডেট (ব্যবহারকারীর অনুমোদন)।
  • একাধিক প্রেক্ষাগৃহে অনুসন্ধান — একজন এজেন্ট একাধিক প্রেক্ষাগৃহে অনুসন্ধান করে ফলাফলগুলো একত্রিত করছেন।

মূল ধারণা:

প্রোটোকল

এটা যা করে

এটি কীভাবে কাজ করে

ইউসিপি ডিসকভারি

এজেন্ট ব্যবসায়ীদের এবং তাদের সক্ষমতা খুঁজে বের করে।

/.well-known/ucp → সক্ষমতা, এমসিপি এন্ডপয়েন্ট, পেমেন্ট পদ্ধতি

ইউসিপি এমসিপি

এজেন্ট ক্যাটালগ ব্রাউজ করে, চেকআউট তৈরি করে।

মার্চেন্টের এমসিপি এন্ডপয়েন্টে JSON-RPC 2.0 কল

এপি২ কার্ট ম্যান্ডেট

বণিক উদ্ধৃত মূল্য স্থির করে।

ব্যবসায়ী কর্তৃক স্বাক্ষরিত, মোট মূল্য ও মেয়াদ অন্তর্ভুক্ত।

এপি২ পেমেন্ট ম্যান্ডেট

ব্যবহারকারী চার্জ অনুমোদন করেন

ব্যবহারকারী দ্বারা স্বাক্ষরিত, রেফারেন্স কার্টম্যান্ডেট

উৎপাদনে কী ভিন্নতা আছে?

এই কোডল্যাবে মক ব্যবহার করা হয়েছে। প্রোডাকশনে:

  • UCP ডিসকভারি হার্ডকোডেড লোকালহোস্ট ইউআরএল-এর পরিবর্তে একটি রেজিস্ট্রিকে ভিত্তি করে সমাধান করে।
  • MCP এন্ডপয়েন্টগুলো প্রকৃত ব্যবসায়ীদের দ্বারা হোস্ট করা হয় — একই JSON-RPC 2.0 প্রোটোকল এবং আসল ইনভেন্টরি।
  • AP2 ম্যান্ডেটগুলো SHA-256 হ্যাশ দিয়ে নয়, বরং sd-jwt-vc দিয়ে স্বাক্ষরিত হয়।
  • পেমেন্ট অনুমোদনে ব্যবহারকারীর সম্মতি চাওয়ার সুবিধাসহ একটি AP2 Wallet SDK ব্যবহার করা হয়।
  • ফ্রন্টএন্ড টুলের ফলাফলগুলোকে একটি সমৃদ্ধ UI (প্রোডাক্ট গ্রিড, চেকআউট সামারি, কনফার্মেশন কার্ড) হিসেবে রেন্ডার করে।

পরবর্তী পদক্ষেপ

রেফারেন্স নথি