Cloudflare Workers چیست؟ آموزش ساخت API هوش مصنوعی Serverless با درواره

در این آموزش یاد می‌گیرید Cloudflare Workers و معماری Serverless چیست و چگونه با TypeScript یک API هوش مصنوعی امن، Streaming و قابل استقرار بسازید و آن را به API درواره متصل کنید.

Share
Cloudflare Workers چیست؟ آموزش ساخت API هوش مصنوعی Serverless با درواره

برای ساخت یک API هوش مصنوعی همیشه به خرید سرور، نصب سیستم‌عامل، راه‌اندازی Nginx، مدیریت Process و پیکربندی Docker نیاز ندارید. در بسیاری از پروژه‌های سبک و متوسط می‌توانید منطق Backend را روی یک زیرساخت Serverless اجرا کنید.

Cloudflare Workers یکی از پلتفرم‌های محبوب برای اجرای کد JavaScript و TypeScript در شبکه Edge است. در این روش، شما کد API را می‌نویسید و پلتفرم اجرای آن، مقیاس‌پذیری، دریافت درخواست HTTP و استقرار را مدیریت می‌کند.

در این مقاله یک پروژه واقعی می‌سازیم که قابلیت‌های زیر را دارد:

  • دریافت پیام از Client
  • اعتبارسنجی ورودی
  • محافظت از API با Token
  • نگهداری کلید درواره در Secret
  • اتصال به API هوش مصنوعی درواره
  • دریافت پاسخ کامل
  • دریافت پاسخ Streaming
  • مدیریت CORS
  • ثبت Request ID
  • مدیریت خطا و Timeout
  • تست محلی با Wrangler
  • استقرار روی workers.dev
  • اتصال دامنه اختصاصی
  • آماده‌سازی برای استفاده عملی

Cloudflare Workers چیست؟

Cloudflare Workers یک محیط اجرای Serverless است که به توسعه‌دهندگان اجازه می‌دهد کد Backend را بدون مدیریت مستقیم سرور اجرا کنند.

کد Worker می‌تواند درخواست HTTP دریافت کند، آن را پردازش کند، با یک API خارجی ارتباط برقرار کند و پاسخ را به کاربر برگرداند.

معماری ساده آن به شکل زیر است:

User
  |
  v
Cloudflare Worker
  |
  v
External API

در پروژه این مقاله، API خارجی همان API درواره است:

کاربر یا اپلیکیشن
        |
        v
Cloudflare Worker
        |
        v
https://api.darvareh.ir/v1
        |
        v
مدل هوش مصنوعی

Worker در این معماری نقش Backend واسط را دارد. کلید API درواره داخل Worker نگهداری می‌شود و در اختیار مرورگر یا کاربر نهایی قرار نمی‌گیرد.

Serverless چیست؟

Serverless به این معنی نیست که هیچ سروری وجود ندارد. کد شما همچنان روی سرور اجرا می‌شود، اما مدیریت مستقیم سرور بر عهده ارائه‌دهنده زیرساخت است.

در یک معماری سنتی باید مواردی مانند این‌ها را مدیریت کنید:

  • تهیه VPS
  • نصب Linux
  • نصب Runtime
  • مدیریت Process
  • تنظیم Reverse Proxy
  • به‌روزرسانی سیستم‌عامل
  • مدیریت مقیاس‌پذیری
  • بررسی مصرف CPU و RAM
  • راه‌اندازی مجدد برنامه بعد از Crash

در معماری Serverless، تمرکز اصلی شما روی کد و تنظیمات برنامه است.

مقایسه کلی:

ویژگیسرور سنتیServerless
مدیریت سیستم‌عاملبر عهده شمابر عهده پلتفرم
نصب Runtimeلازم استآماده است
مقیاس‌پذیرینیازمند تنظیمتا حد زیادی مدیریت‌شده
پرداختمعمولاً براساس زمان سرورمعمولاً براساس مصرف
کنترل زیرساختزیادمحدودتر
اجرای پردازش طولانیمناسب‌تروابسته به محدودیت پلتفرم
API سبک و سریعمناسببسیار مناسب
شروع پروژهزمان‌برترسریع‌تر

Edge Computing چیست؟

در Cloudflare Workers کد می‌تواند در زیرساخت Edge اجرا شود. منظور از Edge، نقاطی از شبکه است که نسبت به یک سرور مرکزی به کاربران نزدیک‌تر هستند.

این معماری برای عملیات زیر مفید است:

  • اعتبارسنجی درخواست
  • مسیریابی API
  • افزودن یا حذف Header
  • بررسی Token
  • Cache کردن پاسخ‌های مناسب
  • تبدیل فرمت داده
  • اتصال به سرویس‌های خارجی
  • ساخت API Gateway سبک
  • اجرای منطق کوتاه و Stateless

نزدیک بودن محل اجرای Worker به کاربر، فقط یکی از عوامل Latency است. اگر Worker به یک API خارجی متصل شود، زمان پاسخ آن API و فاصله شبکه با سرویس بالادستی نیز روی زمان نهایی اثر می‌گذارد.

Cloudflare Workers برای چه پروژه‌هایی مناسب است؟

Cloudflare Workers می‌تواند برای این کاربردها مناسب باشد:

  • Backend سبک وب‌سایت
  • API واسط برای مدل هوش مصنوعی
  • فرم تماس و ثبت اطلاعات
  • Webhook Receiver
  • API Gateway کوچک
  • پردازش و تغییر Header
  • تولید پاسخ‌های JSON
  • اتصال چند سرویس به یکدیگر
  • Streaming پاسخ
  • Redirect و URL Rewrite
  • پردازش درخواست در Edge

برای عملیات زیر باید محدودیت‌های پلتفرم را دقیق‌تر بررسی کنید:

  • پردازش CPU بسیار سنگین
  • اجرای طولانی در پس‌زمینه
  • پردازش ویدئو روی Runtime
  • نگهداری فایل‌های بزرگ در حافظه
  • برنامه‌های وابسته به کتابخانه‌های سیستمی خاص
  • اتصال‌های پیچیده و طولانی‌مدت
  • پردازش‌های نیازمند GPU

محدودیت‌ها و امکانات پلن‌ها ممکن است تغییر کنند. پیش از طراحی نسخه Production، صفحه محدودیت‌های Cloudflare Workers را بررسی کنید.

چرا Worker را مستقیماً به API درواره متصل کنیم؟

فرض کنید یک صفحه وب دارید که از مدل هوش مصنوعی استفاده می‌کند. قرار دادن کلید API داخل JavaScript مرورگر اشتباه است:

const apiKey = "YOUR_DARVAREH_API_KEY";

هر کاربر می‌تواند Source، درخواست‌های Network یا فایل JavaScript را ببیند و کلید را استخراج کند.

معماری درست‌تر:

Frontend
   |
   | کلید داخلی برنامه
   v
Cloudflare Worker
   |
   | کلید محرمانه درواره
   v
Darvareh API

در این معماری:

  • کلید درواره در مرورگر قرار نمی‌گیرد.
  • مدل از سمت Worker انتخاب می‌شود.
  • طول ورودی محدود می‌شود.
  • تعداد توکن خروجی کنترل می‌شود.
  • پاسخ خطا قابل مدیریت است.
  • دسترسی کاربران قابل کنترل است.
  • امکان ثبت لاگ و Request ID وجود دارد.

پیش‌نیازهای آموزش

برای اجرای پروژه به موارد زیر نیاز دارید:

  • حساب Cloudflare
  • Node.js
  • npm
  • کلید API درواره
  • شناسه یک مدل فعال در درواره
  • آشنایی مقدماتی با JavaScript یا TypeScript
  • Terminal، PowerShell یا Command Prompt

نسخه Node.js را بررسی کنید:

node --version

نسخه npm:

npm --version

ساخت پروژه Cloudflare Worker

دستور زیر را اجرا کنید:

npm create cloudflare@latest -- darvareh-ai-worker

هنگام ساخت پروژه گزینه‌های مشابه زیر را انتخاب کنید:

What would you like to start with?
Hello World example

Which template would you like to use?
Worker only

Which language do you want to use?
TypeScript

Do you want to use git?
Yes

Do you want to deploy?
No

وارد پوشه پروژه شوید:

cd darvareh-ai-worker

طبق راهنمای رسمی، ابزار ایجاد پروژه فایل‌های موردنیاز Worker و Wrangler را می‌سازد. برای اجرای محلی نیز می‌توان از wrangler dev استفاده کرد. راهنمای شروع Cloudflare Workers

ساختار پروژه

ساختار اولیه باید تقریباً مشابه زیر باشد:

darvareh-ai-worker/
├── src/
│   └── index.ts
├── test/
├── package.json
├── package-lock.json
├── tsconfig.json
├── worker-configuration.d.ts
└── wrangler.jsonc

فایل‌های اصلی ما:

  • src/index.ts: منطق Worker
  • wrangler.jsonc: تنظیمات Worker
  • .dev.vars: Secretهای محیط توسعه
  • .gitignore: فایل‌های خارج‌شده از Git

تنظیم فایل wrangler.jsonc

محتوای فایل wrangler.jsonc را به شکل زیر تنظیم کنید:

{
  "$schema": "node_modules/wrangler/config-schema.json",
  "name": "darvareh-ai-api",
  "main": "src/index.ts",
  "compatibility_date": "2026-08-01",

  "vars": {
    "DARVAREH_MODEL_ID": "YOUR_MODEL_ID",
    "ALLOWED_ORIGIN": "http://localhost:3000"
  },

  "secrets": {
    "required": [
      "DARVAREH_API_KEY",
      "APP_TOKEN"
    ]
  }
}

متغیر DARVAREH_MODEL_ID

شناسه مدلی است که Worker اجازه استفاده از آن را دارد:

"DARVAREH_MODEL_ID": "YOUR_MODEL_ID"

شناسه مدل مورد نظر خود را از صفحه مدل‌ها و قیمت‌های درواره بردارید.

بهتر است در نسخه اولیه، کاربر نتواند Model ID دلخواه را در Body درخواست ارسال کند. اگر مدل سمت سرور انتخاب شود، کنترل هزینه و رفتار برنامه ساده‌تر خواهد بود.

متغیر ALLOWED_ORIGIN

دامنه Frontend مجاز را مشخص می‌کند:

"ALLOWED_ORIGIN": "https://app.example.com"

برای توسعه محلی می‌توانید از آدرس زیر استفاده کنید:

"ALLOWED_ORIGIN": "http://localhost:3000"

بخش secrets

این بخش نام Secretهای الزامی را تعریف می‌کند:

"secrets": {
  "required": [
    "DARVAREH_API_KEY",
    "APP_TOKEN"
  ]
}

مقدار واقعی Secretها نباید داخل wrangler.jsonc قرار بگیرد.

ساخت Secretهای محیط توسعه

در Root پروژه فایلی با نام .dev.vars بسازید:

DARVAREH_API_KEY="YOUR_DARVAREH_API_KEY"
APP_TOKEN="یک-توکن-طولانی-و-تصادفی"

برای تولید Token تصادفی در Linux یا macOS:

openssl rand -hex 32

نمونه ساختار خروجی:

4c2f50bd73428183fb30c19f8ed00c06b6825317a79f...

این مقدار را به‌عنوان APP_TOKEN قرار دهید.

فایل .gitignore باید شامل این موارد باشد:

node_modules/
.dev.vars
.dev.vars.*
.env
.env.*
.wrangler/

طبق مستندات Cloudflare، برای اطلاعات محرمانه باید از Secret استفاده شود و فایل‌هایی مانند .dev.vars یا .env نباید داخل Git قرار بگیرند. مستندات Secret در Cloudflare Workers

طراحی API پروژه

دو Endpoint اصلی می‌سازیم:

POST /v1/chat
POST /v1/chat/stream

مسیر اول پاسخ کامل JSON را برمی‌گرداند.

مسیر دوم پاسخ مدل را به‌صورت Streaming منتقل می‌کند.

همچنین دو مسیر کمکی داریم:

GET /
GET /health

ساخت Worker با TypeScript

محتوای فایل src/index.ts را با کد زیر جایگزین کنید:

interface Env {
  DARVAREH_API_KEY: string;
  APP_TOKEN: string;
  DARVAREH_MODEL_ID: string;
  ALLOWED_ORIGIN: string;
}

type ClientRole = "user" | "assistant";

interface ClientMessage {
  role: ClientRole;
  content: string;
}

interface ChatBody {
  messages: ClientMessage[];
  temperature?: number;
  max_tokens?: number;
}

const DARVAREH_CHAT_URL =
  "https://api.darvareh.ir/v1/chat/completions";

const MAX_BODY_BYTES = 64 * 1024;
const MAX_MESSAGES = 30;
const MAX_MESSAGE_LENGTH = 10_000;
const MAX_TOTAL_CONTENT_LENGTH = 20_000;
const MAX_OUTPUT_TOKENS = 2_000;

function createCorsHeaders(
  request: Request,
  env: Env
): Headers {
  const headers = new Headers();
  const origin = request.headers.get("Origin");

  if (origin && origin === env.ALLOWED_ORIGIN) {
    headers.set("Access-Control-Allow-Origin", origin);
    headers.set("Vary", "Origin");
  }

  headers.set(
    "Access-Control-Allow-Methods",
    "GET, POST, OPTIONS"
  );

  headers.set(
    "Access-Control-Allow-Headers",
    "Content-Type, Authorization"
  );

  headers.set(
    "Access-Control-Max-Age",
    "86400"
  );

  return headers;
}

function jsonResponse(
  request: Request,
  env: Env,
  data: unknown,
  status = 200,
  requestId?: string
): Response {
  const headers = createCorsHeaders(request, env);

  headers.set(
    "Content-Type",
    "application/json; charset=utf-8"
  );

  headers.set(
    "Cache-Control",
    "no-store"
  );

  if (requestId) {
    headers.set("X-Request-ID", requestId);
  }

  return new Response(
    JSON.stringify(data),
    {
      status,
      headers,
    }
  );
}

function isOriginAllowed(
  request: Request,
  env: Env
): boolean {
  const origin = request.headers.get("Origin");

  if (!origin) {
    return true;
  }

  return origin === env.ALLOWED_ORIGIN;
}

function isAuthorized(
  request: Request,
  env: Env
): boolean {
  const authorization =
    request.headers.get("Authorization");

  return authorization === `Bearer ${env.APP_TOKEN}`;
}

function validateChatBody(
  value: unknown
):
  | { ok: true; body: ChatBody }
  | { ok: false; message: string } {
  if (
    typeof value !== "object" ||
    value === null
  ) {
    return {
      ok: false,
      message: "بدنه درخواست معتبر نیست.",
    };
  }

  const body = value as Partial<ChatBody>;

  if (
    !Array.isArray(body.messages) ||
    body.messages.length === 0 ||
    body.messages.length > MAX_MESSAGES
  ) {
    return {
      ok: false,
      message:
        `تعداد پیام‌ها باید بین ۱ تا ${MAX_MESSAGES} باشد.`,
    };
  }

  let totalLength = 0;

  for (const message of body.messages) {
    if (
      typeof message !== "object" ||
      message === null
    ) {
      return {
        ok: false,
        message: "ساختار یکی از پیام‌ها معتبر نیست.",
      };
    }

    if (
      message.role !== "user" &&
      message.role !== "assistant"
    ) {
      return {
        ok: false,
        message:
          "نقش پیام فقط می‌تواند user یا assistant باشد.",
      };
    }

    if (
      typeof message.content !== "string" ||
      message.content.trim().length === 0
    ) {
      return {
        ok: false,
        message: "محتوای پیام نمی‌تواند خالی باشد.",
      };
    }

    if (
      message.content.length >
      MAX_MESSAGE_LENGTH
    ) {
      return {
        ok: false,
        message:
          "طول یکی از پیام‌ها بیشتر از حد مجاز است.",
      };
    }

    totalLength += message.content.length;
  }

  if (
    totalLength >
    MAX_TOTAL_CONTENT_LENGTH
  ) {
    return {
      ok: false,
      message:
        "مجموع طول پیام‌ها بیشتر از حد مجاز است.",
    };
  }

  const temperature =
    body.temperature ?? 0.3;

  if (
    typeof temperature !== "number" ||
    !Number.isFinite(temperature) ||
    temperature < 0 ||
    temperature > 2
  ) {
    return {
      ok: false,
      message:
        "مقدار temperature باید بین ۰ و ۲ باشد.",
    };
  }

  const maxTokens =
    body.max_tokens ?? 800;

  if (
    !Number.isInteger(maxTokens) ||
    maxTokens < 1 ||
    maxTokens > MAX_OUTPUT_TOKENS
  ) {
    return {
      ok: false,
      message:
        `مقدار max_tokens باید بین ۱ و ${MAX_OUTPUT_TOKENS} باشد.`,
    };
  }

  return {
    ok: true,
    body: {
      messages: body.messages,
      temperature,
      max_tokens: maxTokens,
    },
  };
}

async function parseRequestBody(
  request: Request
): Promise<
  | { ok: true; value: unknown }
  | { ok: false; message: string }
> {
  const contentType =
    request.headers.get("Content-Type") ?? "";

  if (
    !contentType
      .toLowerCase()
      .includes("application/json")
  ) {
    return {
      ok: false,
      message:
        "Content-Type باید application/json باشد.",
    };
  }

  const declaredLength =
    request.headers.get("Content-Length");

  if (
    declaredLength &&
    Number(declaredLength) > MAX_BODY_BYTES
  ) {
    return {
      ok: false,
      message: "حجم درخواست بیشتر از حد مجاز است.",
    };
  }

  const rawBody = await request.text();
  const actualSize =
    new TextEncoder().encode(rawBody).byteLength;

  if (actualSize > MAX_BODY_BYTES) {
    return {
      ok: false,
      message: "حجم درخواست بیشتر از حد مجاز است.",
    };
  }

  try {
    return {
      ok: true,
      value: JSON.parse(rawBody),
    };
  } catch {
    return {
      ok: false,
      message: "JSON ارسال‌شده معتبر نیست.",
    };
  }
}

async function handleChat(
  request: Request,
  env: Env,
  stream: boolean
): Promise<Response> {
  const requestId = crypto.randomUUID();

  const parsed = await parseRequestBody(request);

  if (!parsed.ok) {
    return jsonResponse(
      request,
      env,
      {
        error: parsed.message,
        request_id: requestId,
      },
      400,
      requestId
    );
  }

  const validation =
    validateChatBody(parsed.value);

  if (!validation.ok) {
    return jsonResponse(
      request,
      env,
      {
        error: validation.message,
        request_id: requestId,
      },
      422,
      requestId
    );
  }

  const controller = new AbortController();

  const timeoutId = setTimeout(
    () => controller.abort(),
    90_000
  );

  try {
    const upstreamResponse = await fetch(
      DARVAREH_CHAT_URL,
      {
        method: "POST",
        headers: {
          "Authorization":
            `Bearer ${env.DARVAREH_API_KEY}`,
          "Content-Type": "application/json",
          "Accept": stream
            ? "text/event-stream"
            : "application/json",
          "X-Request-ID": requestId,
        },
        body: JSON.stringify({
          model: env.DARVAREH_MODEL_ID,
          messages: [
            {
              role: "system",
              content:
                "شما یک دستیار فارسی دقیق و کاربردی هستید. پاسخ را شفاف، مسئولانه و بدون اطلاعات ساختگی ارائه کنید.",
            },
            ...validation.body.messages,
          ],
          temperature:
            validation.body.temperature,
          max_tokens:
            validation.body.max_tokens,
          stream,
        }),
        signal: controller.signal,
      }
    );

    clearTimeout(timeoutId);

    if (!upstreamResponse.ok) {
      const upstreamError =
        await upstreamResponse.text();

      console.error(
        JSON.stringify({
          event: "darvareh_upstream_error",
          request_id: requestId,
          upstream_status:
            upstreamResponse.status,
          detail: upstreamError.slice(0, 1000),
        })
      );

      return jsonResponse(
        request,
        env,
        {
          error:
            "سرویس هوش مصنوعی در حال حاضر پاسخ مناسبی برنگرداند.",
          request_id: requestId,
        },
        502,
        requestId
      );
    }

    const responseHeaders =
      createCorsHeaders(request, env);

    responseHeaders.set(
      "Content-Type",
      upstreamResponse.headers.get(
        "Content-Type"
      ) ??
        (
          stream
            ? "text/event-stream; charset=utf-8"
            : "application/json; charset=utf-8"
        )
    );

    responseHeaders.set(
      "Cache-Control",
      "no-store"
    );

    responseHeaders.set(
      "X-Request-ID",
      requestId
    );

    if (stream) {
      responseHeaders.set(
        "X-Accel-Buffering",
        "no"
      );
    }

    return new Response(
      upstreamResponse.body,
      {
        status: upstreamResponse.status,
        headers: responseHeaders,
      }
    );
  } catch (error) {
    clearTimeout(timeoutId);

    const isTimeout =
      error instanceof Error &&
      error.name === "AbortError";

    console.error(
      JSON.stringify({
        event: "darvareh_request_failed",
        request_id: requestId,
        timeout: isTimeout,
        message:
          error instanceof Error
            ? error.message
            : "Unknown error",
      })
    );

    return jsonResponse(
      request,
      env,
      {
        error: isTimeout
          ? "زمان پردازش درخواست بیش از حد مجاز شد."
          : "ارتباط با سرویس هوش مصنوعی برقرار نشد.",
        request_id: requestId,
      },
      isTimeout ? 504 : 502,
      requestId
    );
  }
}

export default {
  async fetch(
    request: Request,
    env: Env
  ): Promise<Response> {
    const url = new URL(request.url);

    if (!isOriginAllowed(request, env)) {
      return jsonResponse(
        request,
        env,
        {
          error: "Origin درخواست مجاز نیست.",
        },
        403
      );
    }

    if (request.method === "OPTIONS") {
      return new Response(
        null,
        {
          status: 204,
          headers: createCorsHeaders(
            request,
            env
          ),
        }
      );
    }

    if (
      request.method === "GET" &&
      url.pathname === "/"
    ) {
      return jsonResponse(
        request,
        env,
        {
          name: "Darvareh AI Worker",
          status: "running",
          endpoints: [
            "POST /v1/chat",
            "POST /v1/chat/stream",
            "GET /health",
          ],
        }
      );
    }

    if (
      request.method === "GET" &&
      url.pathname === "/health"
    ) {
      return jsonResponse(
        request,
        env,
        {
          status: "ok",
          service: "darvareh-ai-worker",
        }
      );
    }

    if (!isAuthorized(request, env)) {
      return jsonResponse(
        request,
        env,
        {
          error: "دسترسی غیرمجاز است.",
        },
        401
      );
    }

    if (
      request.method === "POST" &&
      url.pathname === "/v1/chat"
    ) {
      return handleChat(
        request,
        env,
        false
      );
    }

    if (
      request.method === "POST" &&
      url.pathname === "/v1/chat/stream"
    ) {
      return handleChat(
        request,
        env,
        true
      );
    }

    return jsonResponse(
      request,
      env,
      {
        error: "مسیر مورد نظر پیدا نشد.",
      },
      404
    );
  },
} satisfies ExportedHandler<Env>;

کد Worker چگونه کار می‌کند؟

دریافت درخواست

تابع اصلی Worker درخواست ورودی را دریافت می‌کند:

async fetch(
  request: Request,
  env: Env
): Promise<Response>

پارامتر request شامل اطلاعات HTTP است و env متغیرها و Secretهای Worker را در اختیار کد قرار می‌دهد.

بررسی مسیر

مسیر درخواست با URL API مقایسه می‌شود:

const url = new URL(request.url);

سپس براساس Method و Path، Handler مناسب اجرا می‌شود.

احراز دسترسی برنامه

Worker مقدار Header زیر را بررسی می‌کند:

Authorization: Bearer YOUR_APP_TOKEN

این Token با کلید درواره متفاوت است:

مقدارکاربرد
APP_TOKENدسترسی Client مورد اعتماد به Worker
DARVAREH_API_KEYدسترسی Worker به API درواره

کلید درواره هرگز نباید برای Client ارسال شود.

اعتبارسنجی ورودی

Worker محدودیت‌های زیر را بررسی می‌کند:

  • نوع Content-Type
  • حجم Body
  • معتبر بودن JSON
  • تعداد پیام‌ها
  • Role پیام
  • طول هر پیام
  • مجموع طول پیام‌ها
  • محدوده temperature
  • محدوده max_tokens

این کنترل‌ها باعث می‌شوند Client نتواند بدون محدودیت، درخواست دلخواه و پرهزینه به API بالادستی ارسال کند.

انتخاب مدل سمت سرور

Model ID از Environment خوانده می‌شود:

model: env.DARVAREH_MODEL_ID

در نتیجه کاربر نهایی نمی‌تواند با تغییر Body، مدل دیگری را انتخاب کند.

اگر محصول شما باید چند مدل داشته باشد، بهتر است یک Allowlist داخلی تعریف کنید:

const ALLOWED_MODELS = {
  fast: "MODEL_ID_FAST",
  quality: "MODEL_ID_QUALITY",
};

سپس کاربر فقط Aliasهایی مانند fast و quality را ارسال کند، نه Model ID آزاد.

اجرای محلی Worker

دستور زیر را اجرا کنید:

npx wrangler dev

Worker معمولاً روی آدرس زیر در دسترس قرار می‌گیرد:

http://localhost:8787

Health Check:

curl http://localhost:8787/health

خروجی:

{
  "status": "ok",
  "service": "darvareh-ai-worker"
}

تست API معمولی

curl -X POST \
  http://localhost:8787/v1/chat \
  -H "Authorization: Bearer YOUR_APP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "messages": [
      {
        "role": "user",
        "content": "Serverless را با یک مثال ساده توضیح بده."
      }
    ],
    "temperature": 0.3,
    "max_tokens": 700
  }'

ساختار پاسخ با پاسخ استاندارد Chat Completions سازگار خواهد بود:

{
  "id": "chatcmpl-example",
  "object": "chat.completion",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "در معماری Serverless..."
      },
      "finish_reason": "stop"
    }
  ]
}

تست Streaming

برای مشاهده پاسخ تدریجی از گزینه -N در curl استفاده کنید:

curl -N -X POST \
  http://localhost:8787/v1/chat/stream \
  -H "Authorization: Bearer YOUR_APP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "messages": [
      {
        "role": "user",
        "content": "مزایا و محدودیت‌های Cloudflare Workers را توضیح بده."
      }
    ],
    "temperature": 0.2,
    "max_tokens": 1000
  }'

داده‌ها به‌تدریج دریافت می‌شوند:

data: {"id":"...","choices":[{"delta":{"content":"Cloudflare"}}]}

data: {"id":"...","choices":[{"delta":{"content":" Workers"}}]}

data: [DONE]

Worker بدون تبدیل پاسخ، Stream دریافتی از درواره را به Client منتقل می‌کند:

return new Response(
  upstreamResponse.body,
  {
    status: upstreamResponse.status,
    headers: responseHeaders,
  }
);

طبق مستندات Cloudflare، اگر Worker پاسخ بالادستی را بدون خواندن کامل Body منتقل کند، جریان پاسخ حفظ می‌شود. همچنین می‌توان برای ساخت Stream سفارشی از ReadableStream استفاده کرد. مستندات Streams در Cloudflare Workers

چرا نباید response.json را در Streaming استفاده کنیم؟

این کد برای Streaming مناسب نیست:

const data =
  await upstreamResponse.json();

return Response.json(data);

متد json() منتظر می‌ماند تا کل Body دریافت و Parse شود. بنابراین مزیت دریافت تدریجی پاسخ از بین می‌رود.

برای حفظ Streaming باید از Body اصلی استفاده شود:

return new Response(
  upstreamResponse.body,
  {
    headers: {
      "Content-Type": "text/event-stream",
    },
  }
);

تنظیم Secretهای محیط Production

Secretها را با Wrangler اضافه کنید:

npx wrangler secret put DARVAREH_API_KEY

پس از اجرای دستور، مقدار کلید را وارد کنید.

برای Token داخلی برنامه:

npx wrangler secret put APP_TOKEN

وجود secrets.required باعث می‌شود Wrangler پیش از Deploy، Secretهای ضروری را بررسی کند.

برای مشاهده نام Secretهای ثبت‌شده:

npx wrangler secret list

مقدار Secret بعد از ثبت نباید در خروجی نمایش داده شود.

استقرار Worker

برای Deploy از دستور زیر استفاده کنید:

npx wrangler deploy

بعد از استقرار، آدرسی مشابه زیر دریافت می‌کنید:

https://darvareh-ai-api.YOUR_SUBDOMAIN.workers.dev

Health Check نسخه منتشرشده:

curl \
  https://darvareh-ai-api.YOUR_SUBDOMAIN.workers.dev/health

تست API:

curl -X POST \
  https://darvareh-ai-api.YOUR_SUBDOMAIN.workers.dev/v1/chat \
  -H "Authorization: Bearer YOUR_APP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "messages": [
      {
        "role": "user",
        "content": "Edge Computing چیست؟"
      }
    ]
  }'

مشاهده لاگ Worker

برای مشاهده لاگ‌های زنده:

npx wrangler tail

نمونه لاگ خطا:

{
  "event": "darvareh_upstream_error",
  "request_id": "5b71b492-...",
  "upstream_status": 429
}

در کد پروژه، کلید API، APP Token و Authorization Header ثبت نمی‌شوند.

در لاگ‌ها از ذخیره اطلاعات زیر خودداری کنید:

  • کلید API
  • Token کاربر
  • محتوای حساس پیام
  • اطلاعات شخصی
  • Header کامل Authorization
  • Secretهای محیطی

Request ID چه کاربردی دارد؟

برای هر درخواست یک شناسه منحصربه‌فرد تولید می‌شود:

const requestId = crypto.randomUUID();

این شناسه در پاسخ قرار می‌گیرد:

X-Request-ID: 5b71b492-...

همچنین در خطاهای ثبت‌شده وجود دارد. بنابراین اگر کاربر خطایی گزارش کند، می‌توانید با Request ID رخداد مربوط به همان درخواست را در لاگ پیدا کنید.

اتصال Frontend به Worker

نمونه درخواست JavaScript:

async function sendMessage(message) {
  const response = await fetch(
    "https://api.example.com/v1/chat",
    {
      method: "POST",
      headers: {
        "Authorization":
          "Bearer YOUR_APP_TOKEN",
        "Content-Type":
          "application/json",
      },
      body: JSON.stringify({
        messages: [
          {
            role: "user",
            content: message,
          },
        ],
        temperature: 0.3,
        max_tokens: 700,
      }),
    }
  );

  const data = await response.json();

  if (!response.ok) {
    throw new Error(
      data.error ?? "Request failed"
    );
  }

  return data.choices[0].message.content;
}

نکته مهم درباره APP_TOKEN در مرورگر

قرار دادن APP_TOKEN ثابت در JavaScript عمومی، آن را محرمانه نگه نمی‌دارد. کاربران می‌توانند Token را در Source یا Network مرورگر مشاهده کنند.

روش APP_TOKEN برای این موارد مناسب‌تر است:

  • ارتباط Server-to-Server
  • ابزار داخلی
  • تست API
  • Backend مورد اعتماد
  • Jobهای خودکار

برای اپلیکیشن عمومی باید یکی از روش‌های مناسب مدیریت کاربر پیاده‌سازی شود:

  • Session سمت سرور
  • Token کوتاه‌عمر برای هر کاربر
  • احراز هویت کاربران
  • بررسی سطح دسترسی
  • محدودیت مصرف برای هر حساب
  • ذخیره Usage در پایگاه داده

CORS نیز ابزار احراز هویت نیست. CORS رفتار مرورگر را کنترل می‌کند، اما مانع ارسال مستقیم درخواست با curl یا یک سرور دیگر نمی‌شود.

اتصال دامنه اختصاصی

اگر دامنه شما به‌عنوان Zone فعال در Cloudflare مدیریت می‌شود، می‌توانید یک Custom Domain به Worker متصل کنید.

در Dashboard مسیر زیر را باز کنید:

Workers & Pages
Worker
Settings
Domains & Routes
Add
Custom Domain

سپس دامنه‌ای مانند این وارد کنید:

ai-api.example.com

در فایل wrangler.jsonc نیز می‌توان Route تعریف کرد:

{
  "routes": [
    {
      "pattern": "ai-api.example.com",
      "custom_domain": true
    }
  ]
}

Cloudflare برای Custom Domain رکورد موردنیاز و گواهی مرتبط را مدیریت می‌کند. دامنه باید داخل یک Zone فعال Cloudflare باشد و روی همان Hostname رکورد ناسازگار موجود نباشد. مستندات Custom Domains در Cloudflare Workers

اگر DNS دامنه شما در ارائه‌دهنده دیگری مدیریت می‌شود، قبل از طراحی اتصال مستقیم Custom Domain باید محدودیت‌های این روش را بررسی کنید. در چنین شرایطی می‌توانید از آدرس workers.dev استفاده کنید یا یک معماری Reverse Proxy سازگار با DNS و زیرساخت خود بسازید.

مدیریت CORS برای چند دامنه

در پروژه فعلی فقط یک Origin مجاز است:

origin === env.ALLOWED_ORIGIN

اگر چند Frontend دارید، Allowlist تعریف کنید:

const allowedOrigins = new Set([
  "https://app.example.com",
  "https://admin.example.com",
  "http://localhost:3000",
]);

function isAllowedOrigin(
  origin: string | null
): boolean {
  if (!origin) {
    return true;
  }

  return allowedOrigins.has(origin);
}

از بازتاب دادن هر Origin به شکل زیر خودداری کنید:

headers.set(
  "Access-Control-Allow-Origin",
  request.headers.get("Origin")!
);

Origin باید ابتدا با Allowlist مورد اعتماد مقایسه شود.

مدیریت Timeout

در پروژه یک Timeout برای درخواست بالادستی تعریف شده است:

const controller =
  new AbortController();

const timeoutId = setTimeout(
  () => controller.abort(),
  90_000
);

سپس Signal به fetch داده می‌شود:

signal: controller.signal

اگر API در زمان تعیین‌شده پاسخی برنگرداند، درخواست لغو می‌شود و Worker خطای کنترل‌شده برمی‌گرداند:

{
  "error": "زمان پردازش درخواست بیش از حد مجاز شد.",
  "request_id": "..."
}

Timeout مناسب به کاربرد شما وابسته است. برای پاسخ کوتاه ممکن است ۳۰ تا ۶۰ ثانیه کافی باشد، اما بعضی مدل‌ها یا خروجی‌های طولانی به زمان بیشتری نیاز دارند.

Timeout را براساس داده‌های واقعی و محدودیت‌های جاری پلتفرم انتخاب کنید.

جلوگیری از تبدیل Worker به API عمومی پرهزینه

اگر Worker بدون کنترل روی اینترنت منتشر شود، افراد دیگر می‌توانند از اعتبار درواره شما مصرف کنند.

حداقل کنترل‌های پیشنهادی:

  • احراز هویت درخواست
  • محدودیت طول ورودی
  • محدودیت تعداد پیام
  • محدودیت تعداد توکن خروجی
  • انتخاب مدل در Backend
  • ثبت Usage
  • محدودیت درخواست برای هر کاربر
  • بررسی موجودی یا سهمیه
  • ثبت Request ID
  • Timeout
  • محدود کردن Origin برای مرورگر
  • مخفی نگه داشتن کلید درواره

برای نسخه عمومی و چندکاربره، یک Token ثابت کافی نیست. باید هویت و سهمیه هر کاربر به‌صورت مستقل مدیریت شود.

افزودن Rate Limiting

برای یک محصول عمومی بهتر است محدودیت‌هایی مشابه زیر تعریف شوند:

کاربر عادی:
10 درخواست در دقیقه
100 درخواست در روز

کاربر حرفه‌ای:
60 درخواست در دقیقه
2000 درخواست در روز

نباید شمارنده Rate Limit را فقط داخل یک متغیر معمولی Worker ذخیره کنید:

let requestCount = 0;

Instanceهای Worker می‌توانند متعدد و موقتی باشند و حافظه محلی آن‌ها منبع داده پایدار و مشترک نیست.

برای Rate Limiting قابل اتکا باید از قابلیت‌های مناسب پلتفرم یا یک ذخیره‌ساز مشترک استفاده شود.

مدیریت تاریخچه گفتگو

Worker پروژه فعلی Stateless است. Client تاریخچه را در آرایه messages ارسال می‌کند:

{
  "messages": [
    {
      "role": "user",
      "content": "Serverless چیست؟"
    },
    {
      "role": "assistant",
      "content": "Serverless یک مدل اجرای..."
    },
    {
      "role": "user",
      "content": "یک مثال هم بزن."
    }
  ]
}

برای محصول واقعی می‌توانید تاریخچه را در یک پایگاه داده ذخیره کنید و در هر درخواست فقط شناسه Conversation را بفرستید.

نکته مهم این است که با طولانی شدن تاریخچه:

  • تعداد توکن ورودی افزایش پیدا می‌کند.
  • هزینه درخواست بیشتر می‌شود.
  • زمان پاسخ ممکن است افزایش پیدا کند.
  • Context Window بیشتری مصرف می‌شود.
  • احتمال ورود اطلاعات نامرتبط بالا می‌رود.

بهتر است پیام‌های قدیمی خلاصه یا براساس نیاز انتخاب شوند.

آیا پاسخ هوش مصنوعی را Cache کنیم؟

Cache کردن پاسخ هوش مصنوعی همیشه مناسب نیست.

پاسخ‌های زیر معمولاً نباید به‌صورت عمومی Cache شوند:

  • پاسخ شخصی کاربر
  • محتوای دارای اطلاعات حساس
  • مکالمات حساب کاربری
  • خروجی وابسته به تاریخچه
  • درخواست‌های متفاوت با متن مشابه
  • پاسخ‌های متغیر

برای بعضی کاربردهای عمومی و قطعی، Cache ممکن است مفید باشد:

  • توضیح ثابت یک اصطلاح
  • دسته‌بندی از پیش محاسبه‌شده
  • خلاصه یک سند عمومی ثابت
  • پاسخ با ورودی Normalized
  • خروجی با Temperature پایین

اگر Cache می‌سازید، Cache Key باید همه پارامترهای مؤثر را در نظر بگیرد:

model
messages
temperature
max_tokens
system_prompt_version

مدیریت خطاهای API درواره

Worker نباید همه جزئیات خطای Upstream را مستقیماً به کاربر نمایش دهد.

در پروژه، جزئیات فنی در Log ثبت و یک پیام عمومی به Client ارسال می‌شود:

{
  "error": "سرویس هوش مصنوعی در حال حاضر پاسخ مناسبی برنگرداند.",
  "request_id": "..."
}

کدهای متداول:

وضعیتمفهوم احتمالی
400ورودی نامعتبر
401Token برنامه نامعتبر
403Origin مجاز نیست
404مسیر پیدا نشده
422ساختار پیام یا پارامتر نامعتبر
502خطا در ارتباط با سرویس بالادستی
504پایان زمان انتظار

جزئیات دقیق خطا باید با مستندات جاری سرویس و لاگ درخواست بررسی شود.

تست با JavaScript

فایل ساده test.mjs بسازید:

const response = await fetch(
  "http://localhost:8787/v1/chat",
  {
    method: "POST",
    headers: {
      "Authorization":
        `Bearer ${process.env.APP_TOKEN}`,
      "Content-Type":
        "application/json",
    },
    body: JSON.stringify({
      messages: [
        {
          role: "user",
          content:
            "سه مزیت معماری Serverless را بنویس.",
        },
      ],
      temperature: 0.2,
      max_tokens: 500,
    }),
  }
);

const data = await response.json();

console.log(
  JSON.stringify(data, null, 2)
);

اجرا در Linux یا macOS:

APP_TOKEN="YOUR_APP_TOKEN" \
node test.mjs

تست ورودی نامعتبر

بدون Authorization:

curl -X POST \
  http://localhost:8787/v1/chat \
  -H "Content-Type: application/json" \
  -d '{"messages":[]}'

خروجی:

{
  "error": "دسترسی غیرمجاز است."
}

پیام خالی:

curl -X POST \
  http://localhost:8787/v1/chat \
  -H "Authorization: Bearer YOUR_APP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "messages": [
      {
        "role": "user",
        "content": ""
      }
    ]
  }'

خروجی:

{
  "error": "محتوای پیام نمی‌تواند خالی باشد.",
  "request_id": "..."
}

تعداد توکن بیشتر از حد مجاز:

curl -X POST \
  http://localhost:8787/v1/chat \
  -H "Authorization: Bearer YOUR_APP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "messages": [
      {
        "role": "user",
        "content": "یک پاسخ بنویس."
      }
    ],
    "max_tokens": 10000
  }'

خروجی:

{
  "error": "مقدار max_tokens باید بین ۱ و ۲۰۰۰ باشد.",
  "request_id": "..."
}

خطاهای رایج Cloudflare Workers

Secret در env وجود ندارد

اگر با خطایی مرتبط با DARVAREH_API_KEY روبه‌رو شدید، بررسی کنید فایل .dev.vars در Root پروژه قرار دارد.

برای Production:

npx wrangler secret list

در صورت نبود Secret:

npx wrangler secret put DARVAREH_API_KEY

خطای 401 از Worker

Header زیر ارسال نشده یا مقدار آن اشتباه است:

Authorization: Bearer YOUR_APP_TOKEN

توجه کنید APP_TOKEN با DARVAREH_API_KEY تفاوت دارد.

خطای 401 از API بالادستی

کلید درواره ممکن است اشتباه، غیرفعال یا به‌درستی در Secret ذخیره نشده باشد.

Secret را دوباره تنظیم کنید:

npx wrangler secret put DARVAREH_API_KEY

خطای CORS در مرورگر

موارد زیر را بررسی کنید:

  • مقدار ALLOWED_ORIGIN
  • وجود https یا http
  • وجود یا نبود پورت
  • پاسخ صحیح به درخواست OPTIONS
  • Headerهای مجاز
  • دامنه واقعی Frontend

این دو Origin با هم متفاوت‌اند:

https://app.example.com
http://app.example.com

این دو نیز متفاوت‌اند:

http://localhost:3000
http://localhost:5173

Streaming یک‌جا نمایش داده می‌شود

مطمئن شوید Body پاسخ بالادستی را با json() یا text() نخوانده‌اید.

روش مناسب:

return new Response(
  upstreamResponse.body,
  {
    headers: {
      "Content-Type": "text/event-stream",
    },
  }
);

برای تست با curl:

curl -N ...

Worker روی دامنه اختصاصی اجرا نمی‌شود

بررسی کنید:

  • دامنه در Zone فعال Cloudflare باشد.
  • Custom Domain به Worker درست متصل شده باشد.
  • رکورد ناسازگار روی همان Hostname وجود نداشته باشد.
  • دامنه دقیقاً با Hostname تعریف‌شده یکسان باشد.
  • Deploy جدید انجام شده باشد.

چک‌لیست انتشار Production

پیش از انتشار Worker، موارد زیر را بررسی کنید:

  • کلید درواره فقط در Secret ذخیره شده باشد.
  • فایل .dev.vars داخل Git نباشد.
  • Model ID سمت Worker انتخاب شود.
  • تعداد توکن خروجی محدود شده باشد.
  • طول پیام‌ها محدود شده باشد.
  • حجم Body محدود شده باشد.
  • Timeout تعریف شده باشد.
  • خطای Upstream مدیریت شود.
  • Request ID تولید شود.
  • اطلاعات محرمانه داخل Log ثبت نشود.
  • CORS فقط برای Originهای مورد اعتماد فعال باشد.
  • API عمومی دارای احراز هویت واقعی باشد.
  • Rate Limit برای هر کاربر تعریف شود.
  • مصرف و هزینه درخواست‌ها پایش شود.
  • Streaming در محیط واقعی آزمایش شود.
  • محدودیت‌های فعلی پلن Cloudflare بررسی شود.
  • قیمت و محدودیت مدل انتخابی بررسی شود.

پرسش‌های متداول

آیا Cloudflare Workers رایگان است؟

Cloudflare معمولاً پلن رایگان با محدودیت مشخص ارائه می‌دهد، اما مقدار دقیق محدودیت‌ها و امکانات ممکن است تغییر کند. برای تصمیم‌گیری، صفحه رسمی قیمت‌گذاری و محدودیت‌ها را بررسی کنید.

آیا برای اجرای Worker به سرور نیاز داریم؟

نیازی به تهیه و مدیریت مستقیم VPS ندارید. اجرای Worker توسط زیرساخت Cloudflare مدیریت می‌شود.

آیا می‌توان Python را روی Cloudflare Workers اجرا کرد؟

Cloudflare Workers در درجه اول با JavaScript و TypeScript شناخته می‌شود و قابلیت‌های Runtime آن در حال توسعه است. برای پروژه‌ای که به کتابخانه‌های کامل Python یا وابستگی‌های سیستمی نیاز دارد، باید سازگاری فعلی Runtime را بررسی کنید. در این آموزش TypeScript انتخاب شده چون مسیر ساده و رایجی برای Worker است.

آیا می‌توان از Worker به API درواره وصل شد؟

بله. Worker می‌تواند با fetch به آدرس زیر درخواست ارسال کند:

https://api.darvareh.ir/v1/chat/completions

کلید API باید در Secret نگهداری شود.

آیا Cloudflare Worker جای FastAPI را می‌گیرد؟

همیشه نه. Worker برای APIهای سبک، واسط‌های Serverless و پردازش Edge بسیار مناسب است. FastAPI برای برنامه‌های Python، منطق پیچیده Backend، کتابخانه‌های تخصصی و معماری‌های دارای Processهای طولانی انعطاف بیشتری دارد.

آیا می‌توان تاریخچه چت را داخل Worker ذخیره کرد؟

نه در متغیر معمولی و حافظه موقت Worker. برای ذخیره پایدار تاریخچه باید از یک پایگاه داده یا Storage مناسب استفاده کنید.

آیا می‌توان پاسخ API درواره را Stream کرد؟

بله. کافی است درخواست با stream: true ارسال و upstreamResponse.body بدون خواندن کامل به Client منتقل شود.

آیا APP_TOKEN برای اپلیکیشن عمومی کافی است؟

خیر. Token ثابت در مرورگر قابل مشاهده است. اپلیکیشن عمومی باید احراز هویت، Token کوتاه‌عمر و سهمیه مستقل هر کاربر داشته باشد.

آیا می‌توان چند مدل را از طریق یک Worker ارائه داد؟

بله، اما بهتر است مدل‌ها در یک Allowlist سمت سرور تعریف شوند. اجازه ندهید کاربر هر Model ID دلخواهی ارسال کند.

قیمت استفاده از مدل‌ها چقدر است؟

هزینه به مدل انتخابی، تعداد توکن ورودی، تعداد توکن خروجی و نوع درخواست بستگی دارد. برای مشاهده اطلاعات به‌روز، صفحه مدل‌ها و قیمت‌های درواره را بررسی کنید.

جمع‌بندی

Cloudflare Workers راهی کاربردی برای ساخت APIهای Serverless و اجرای منطق Backend بدون مدیریت مستقیم سرور است. این پلتفرم می‌تواند درخواست کاربر را دریافت کند، ورودی را بررسی کند و آن را به API هوش مصنوعی درواره بفرستد.

در پروژه این مقاله قابلیت‌های زیر را پیاده‌سازی کردیم:

  • ساخت Worker با TypeScript
  • اجرای محلی با Wrangler
  • ساخت API چت
  • اتصال به API درواره
  • نگهداری کلید در Secret
  • محافظت از Endpoint با Token
  • محدود کردن ورودی
  • کنترل Model ID
  • مدیریت CORS
  • افزودن Timeout
  • ثبت Request ID
  • مدیریت خطاهای Upstream
  • انتقال پاسخ Streaming
  • Deploy روی workers.dev
  • اتصال Custom Domain

برای یک نمونه آزمایشی یا API داخلی، این معماری می‌تواند با سرعت و پیچیدگی کم راه‌اندازی شود. برای محصول عمومی باید مدیریت کاربران، Rate Limiting، سهمیه مصرف، ثبت Usage و ذخیره‌سازی پایدار نیز به آن اضافه شود.

برای دریافت کلید API و شروع ساخت سرویس هوش مصنوعی، وارد درواره شوید. برای انتخاب مدل مناسب و مشاهده قیمت‌های فعلی نیز صفحه مدل‌های درواره را ببینید.

منابع

مقالات مرتبط

برای مطالعه شرایط استفاده و محدودیت‌های مسئولیت، صفحه «سلب مسئولیت» را مشاهده کنید.

Read more

اتوماسیون هوش مصنوعی چیست؟ کاربردها و آموزش ساخت AI Automation

اتوماسیون هوش مصنوعی چیست؟ کاربردها و آموزش ساخت AI Automation

اتوماسیون هوش مصنوعی با ترکیب گردش‌کارهای خودکار و مدل‌های هوش مصنوعی، پردازش متن، دسته‌بندی، استخراج اطلاعات و تصمیم‌های پیشنهادی را خودکار می‌کند. در این راهنما، معماری و ساخت نمونه عملی آن با API درواره را می‌آموزید.

Agentic Commerce چیست؟ آینده خرید با ایجنت هوش مصنوعی

Agentic Commerce چیست؟ آینده خرید با ایجنت هوش مصنوعی

Agentic Commerce شیوه‌ای جدید برای خرید اینترنتی است که در آن ایجنت هوش مصنوعی می‌تواند نیاز کاربر را بفهمد، محصولات را جست‌وجو و مقایسه کند و فرایند خرید را پیش ببرد. در این راهنما با معماری، UCP، ACP و پیاده‌سازی آن با API درواره آشنا می‌شوید.