Alibaba Cloud Marketplace:パートナー向けトークン消費量追跡ガイド
このガイドでは、Alibaba Cloud Marketplace に掲載され、Alibaba Cloud Model Studio (Bailian) API を呼び出す製品に対して、トークン消費量追跡 (メータリング) を実装する方法を説明します。適切に追跡することで、製品を MaaS (Model-as-a-Service) 製品として認定でき、プラットフォームレベルの販売支援、プロモーションリソース、正確な収益アトリビューションを利用できるようになります。
重要な理由:適切な追跡がない製品は MaaS 製品として分類できないため、販売活動が縮小され、掲載内容に割り当てられるプラットフォームリソースも少なくなります。
1. 概要
製品が Bailian API (例:Qwen モデル) を呼び出す場合、すべての API リクエストに特別な HTTP ヘッダー x-dashscope-euid を付与する必要があります。このヘッダーはエンドカスタマーを識別し、トークン消費量が顧客アカウントおよび製品に正しく紐付くようにします。
実装は次の 3 つの要素で構成されます:
顧客が Marketplace で製品を購入した際に、SPI コールバック (または License API) 経由で顧客 ID を受信します。
顧客と productCode + aliUid の対応関係をシステムに保存します。
保存した対応関係から動的に値を設定し、すべての Bailian API 呼び出しに追跡ヘッダーを付与します。
実装工数の目安:開発者 1~2 日。
2. x-dashscope-euid ヘッダー
Bailian API へのすべてのリクエストには、次のヘッダーを含める必要があります:
x-dashscope-euid: <JSON string>JSON ペイロードには厳密に 5 つのフィールドが含まれます。5 つすべてが必須です。
フィールド | 型 | 固定? | 値 |
bizType | String | 固定 | B2B |
moduleType | String | 固定 | Third-partyproducts |
moduleCode | String | 動的 | market_${productCode}:productCode は SPI コールバックから取得します |
accountType | String | 固定 | Aliyun |
accountId | String | 動的 | SPI コールバックから取得した顧客の Alibaba Cloud アカウント ID (aliUid) |
例 (Marketplace 顧客:限定スコープ):
{
"bizType": "B2B",
"moduleType": "Third-partyproducts",
"moduleCode": "market_abc123",
"accountType": "Aliyun",
"accountId": "1234567890"
}例 (非 Marketplace 顧客:広範スコープのフォールバック):
{
"bizType": "B2B",
"moduleType": "Third-partyproducts",
"moduleCode": "market_cmapi00069878",
"accountType": "Aliyun",
"accountId": ""
}広範スコープのフォールバックは、サービスを呼び出しているユーザーが対応表に存在しない場合 (つまり Marketplace 経由で購入していない場合) に使用します。この場合、moduleCode にはハードコードされた productCode (公開後に ISV コンソールで確認可能) を使用し、accountId は空文字列になります。
大文字・小文字の区別ルール
コンポーネント | 大文字・小文字を区別する? | 備考 |
ヘッダーキー名 | いいえ | x-dashscope-euid と X-DashScope-EUID の両方を受け付けます |
JSON フィールド名 (キー) | はい | bizType、moduleType など、正確なキャメルケースを使用する必要があります |
JSON フィールド値 | いいえ | B2B と b2b はどちらも認識されます |
3. SPI コールバックによる動的値の取得
productCode と accountId (aliUid) の値は、顧客が製品を購入した時点で Marketplace から提供されます。仕組みは製品タイプによって異なります。
3A. SaaS 製品:SPI 配信
SaaS 製品を公開する際に、[Production API Notification] を有効化し、SPI コールバック URL を設定します。少なくとも createInstance イベントを選択してください。
顧客が支払いを完了すると、Marketplace はコールバック URL に HTTP GET リクエストを送信します:
GET https://your-domain.com/spi/callback?action=createInstance&aliUid=123456&orderBizId=xxx&productCode=abc123&...取得して保存する必要がある主要パラメーター:
パラメーター | 説明 |
productCode | 製品コード:moduleCode で使用します |
aliUid | 顧客の Alibaba Cloud アカウント ID |
orderBizId | べき等性のための一意のビジネスインスタンス ID |
orderBizId → (productCode, aliUid) の対応関係をデータベースに保存し、システム内の顧客アカウントに紐付けます。
重要: SPI エンドポイントはべき等である必要があります (orderBizId で重複排除)。また、3 秒以内に応答する必要があります。失敗した場合、Marketplace は最大 120 回リトライします。
3B. SaaS 製品:ライセンスコード配信
ライセンスコード製品の場合、動的値は SPI コールバックではなく DescribeLicense API で取得します。
顧客がライセンスを有効化する際に、次を呼び出します:
DescribeLicense(LicenseCode = "<顧客のライセンスコード>")レスポンスから次を抽出します:
License.ProductCode → moduleCode で使用します
License.ExtendInfo.AliUid → accountId で使用します
これらの値を保存し、以降の Bailian API 呼び出しに備えて顧客アカウントに紐付けます。
3C. API 製品:orderPay SPI
API 製品の場合は、ISV コンソールで支払い成功 (orderPay) の SPI コールバックを設定します。顧客が支払うと、Marketplace は次をプッシュします:
パラメーター | 説明 |
action | 固定値:orderPay |
aliUid | 顧客の Alibaba Cloud アカウント ID |
orderBizId | ビジネスインスタンス ID = CaCloudMarketInstanceId |
productCode | 製品コード |
{"success":true} を返し、requestId に基づくべき等性を確保する必要があります。
重要な対応関係: SPI コールバックの orderBizId は、API ゲートウェイがバックエンドに引き渡す CaCloudMarketInstanceId と同じ値です。API ゲートウェイを設定して CaCloudMarketInstanceId をバックエンドヘッダーとして転送し、対応表を参照して該当する productCode と aliUid を取得します。
4. 実行時ロジック
実行時に、顧客が製品を通じて Bailian API 呼び出しをトリガーした場合:
顧客がサービスを呼び出します
↓
対応表で顧客を検索します
↓
┌────┴────┐
│ 存在するか? │
└────┬────┘
はい ↓ いいえ ↓
↓ ↓
限定スコープ 広範スコープ (フォールバック)
moduleCode = moduleCode = market_<hardcoded productCode>
market_<stored productCode>
accountId = accountId = ""
<stored aliUid>
↓ ↓
└──────┬───────┘
↓
x-dashscope-euid ヘッダーを構築します
↓
ヘッダーを付けて Bailian API を呼び出します5. コード例
Python (requests library)
import requests
import json
url = "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions"
headers = {
"Authorization": "Bearer sk-xxx",
"Content-Type": "application/json",
"x-dashscope-euid": json.dumps({
"bizType": "B2B",
"moduleType": "Third-partyproducts",
"moduleCode": "market_abc123",
"accountType": "Aliyun",
"accountId": "1234567890"
})
}
payload = {
"model": "qwen-plus",
"messages": [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Hello"}
]
}
response = requests.post(url, headers=headers, json=payload)
print(response.json())Python (OpenAI SDK)
from openai import OpenAI
client = OpenAI(
api_key="sk-xxx",
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
default_headers={
"x-dashscope-euid": '{"bizType":"B2B","moduleType":"Third-partyproducts","moduleCode":"market_abc123","accountType":"Aliyun","accountId":"1234567890"}'
}
)
response = client.chat.completions.create(
model="qwen-plus",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Hello"}
]
)
print(response.choices[0].message.content)Java (OkHttp)
import okhttp3.*;
public class BailianTrackingExample {
public static void main(String[ ] args) throws Exception {
OkHttpClient client = new OkHttpClient();
String euidJson = "{\"bizType\":\"B2B\",\"moduleType\":\"Third-partyproducts\","
+ "\"moduleCode\":\"market_abc123\",\"accountType\":\"Aliyun\","
+ "\"accountId\":\"1234567890\"}";
String body = "{\"model\":\"qwen-plus\",\"messages\":["
+ "{\"role\":\"system\",\"content\":\"You are a helpful assistant.\"},"
+ "{\"role\":\"user\",\"content\":\"Hello\"}]}";
Request request = new Request.Builder()
.url("https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions")
.post(RequestBody.create(body, MediaType.parse("application/json")))
.addHeader("Authorization", "Bearer sk-xxx")
.addHeader("Content-Type", "application/json")
.addHeader("x-dashscope-euid", euidJson)
.build();
Response response = client.newCall(request).execute();
System.out.println(response.body().string());
}
}Node.js (fetch)
const response = await fetch(
"https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions",
{
method: "POST",
headers: {
"Authorization": "Bearer sk-xxx",
"Content-Type": "application/json",
"x-dashscope-euid": JSON.stringify({
bizType: "B2B",
moduleType: "Third-partyproducts",
moduleCode: "market_abc123",
accountType: "Aliyun",
accountId: "1234567890"
})
},
body: JSON.stringify({
model: "qwen-plus",
messages: [
{ role: "system", content: "You are a helpful assistant." },
{ role: "user", content: "Hello" }
]
})
}
);
const data = await response.json();
console.log(data);Go
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
func main() {
euid := map[string]string{
"bizType": "B2B",
"moduleType": "Third-partyproducts",
"moduleCode": "market_abc123",
"accountType": "Aliyun",
"accountId": "1234567890",
}
euidBytes, _ := json.Marshal(euid)
body := map[string]interface{}{
"model": "qwen-plus",
"messages": []map[string]string{
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Hello"},
},
}
bodyBytes, _ := json.Marshal(body)
req, _ := http.NewRequest("POST",
"https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions",
bytes.NewBuffer(bodyBytes))
req.Header.Set("Authorization", "Bearer sk-xxx")
req.Header.Set("Content-Type", "application/json")
req.Header.Set("x-dashscope-euid", string(euidBytes))
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
fmt.Println(result)
}動的ヘッダー構築 (Java / Spring)
本番環境では、現在の顧客に基づいてヘッダーを動的に組み立てる必要があります:
import org.springframework.http.*;
import org.springframework.web.client.RestTemplate;
public class BailianService {
private final RestTemplate restTemplate = new RestTemplate();
public String callModel(String productCode, String aliUid) {
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "Bearer sk-xxx");
headers.set("Content-Type", "application/json");
String euid = String.format(
"{\"bizType\":\"B2B\",\"moduleType\":\"Third-partyproducts\","
+ "\"moduleCode\":\"market_%s\",\"accountType\":\"Aliyun\","
+ "\"accountId\":\"%s\"}",
productCode, aliUid
);
headers.set("x-dashscope-euid", euid);
String body = "{\"model\":\"qwen-plus\",\"messages\":["
+ "{\"role\":\"system\",\"content\":\"You are a helpful assistant.\"},"
+ "{\"role\":\"user\",\"content\":\"Hello\"}]}";
HttpEntity<String> entity = new HttpEntity<>(body, headers);
ResponseEntity<String> response = restTemplate.postForEntity(
"https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions",
entity, String.class
);
return response.getBody();
}
}6. よくあるミスとトラブルシューティング
# | 症状 | 原因 | 対処方法 |
1 | トークン消費量が紐付かない | API 呼び出しに x-dashscope-euid ヘッダーが付与されていない | すべての Bailian API リクエストに必ずヘッダーを付与してください |
2 | 追跡が無効になる | JSON フィールド名の大文字・小文字が誤っている (例:biztype ではなく bizType) | すべての JSON キーで正確なキャメルケースを使用してください |
3 | 追跡が無効になる | 必須 5 フィールドのうち 1 つ以上が欠落している | 必ず 5 フィールドすべてを含めてください:bizType、moduleType、moduleCode、accountType、accountId |
4 | 追跡が無効になる | moduleCode の形式が誤っている | market_${productCode} である必要があります。market_ プレフィックスを付け忘れないでください |
5 | 誤った顧客にトークンが紐付く | accountId が実際の呼び出し顧客と一致していない | 対応表で各顧客が正しい aliUid に紐付いていることを確認してください |
7. ベストプラクティス
ヘッダー構築の一元化。 x-dashscope-euid ヘッダーを組み立てるユーティリティメソッドを 1 つ用意し、コードベース内でロジックが重複しないようにします。
JSON シリアライズライブラリの使用。 JSON 文字列を手作業で組み立てないでください。言語標準の JSON ライブラリを使用して、正しい形式になるようにします。
顧客対応表の維持。 SPI コールバックまたは DescribeLicense API から取得した値を用いて、顧客 → (productCode, aliUid) をデータベースに保存します。
開発時のログ出力の追加。 デバッグログに x-dashscope-euid の値を出力し、本番反映前に正しさを確認できるようにします。
本番前のテスト。 ステージング環境で追跡実装を検証した後、PDM または PSA に依頼して、バックエンドでデータが正しく報告されていることを確認してください。
8. 検証
実装が完了したら、Alibaba Cloud のプロダクト開発マネージャー (PDM) またはパートナーソリューションアーキテクト (PSA) に連絡し、次を実施してください:
プラットフォーム側で追跡データを受信できていることを確認してください。
トークン消費量が正しい製品および顧客アカウントに正しく紐付けられていることを確認してください。
限定スコープ (Marketplace 顧客) と広範スコープ (非 Marketplace のフォールバック) の両方のパスを検証してください。
9. 前提条件と制約
製品は、ファーストパーティモデル (例:Qwen、HappyHorse) を使用して Bailian API を呼び出す必要があります。追跡は、Bailian 経由でのファーストパーティモデル呼び出しにのみ対応しています。
製品が現在、他プラットフォームのモデルまたは自己デプロイモデルを使用している場合、追跡を実装する前にモデル呼び出しを Bailian に移行する必要があります。
任意のプログラミング言語または SDK を使用できますが、アウトバウンドリクエストにカスタム HTTP ヘッダーを付与できる必要があります。