EN 中文
支付 · 泰国 · 2026

在泰国没有信用卡或借记卡,如何在网站收款

一份面向在泰华人的实用指南:三类可用的支付渠道、按签证细分的资格分析、NITMX 跨境二维码捷径、泰国合规要求,以及通过 Omise 和 2C2P 接入微信支付的可运行示例代码。

在泰华人收款全攻略 — 中文图解:签证、支付通道、网关接入与合规
可视化总览(中文)— 由 NotebookLM 基于本指南同源资料生成。涵盖签证资格 → 三类支付通道 → PSP 接入 → 合规要求。
中文语音讲解(9 分 30 秒)— 由 NotebookLM 生成的中文音频解读,与本页内容同源。直接下载

三类可选方案

选择哪条支付通道,取决于你的客户群和你能提供的身份证明。现实中绝大多数商家会把"泰国本地通道"(服务泰国买家)和"中国钱包通道"(服务大陆买家)组合使用。

泰国本地通道

需要泰国银行账户

面向泰国本地买家,以泰铢结算。

PromptPay 二维码、TrueMoney 钱包、银行转账。前端通常通过 Omise、2C2P、GBPrimePay、Beam 等聚合网关接入。

中国钱包通道

面向中国买家

面向中国大陆买家——这是你最自然的客群。

微信支付Alipay+(支付宝+),可通过 Omise、2C2P 或 Airwallex 接入,最终以泰铢结算到泰国银行。

无银行兜底方案

无法开泰国银行

暂时无法开设泰国银行账户时使用。

Wise BusinessAirwallex、PayPal,或者通过 NOWPayments / CoinGate 收 USDT-TRC20。

方案对比表

方式 买家 接入难度 典型费率 结算到 需要泰国银行?
PromptPay 二维码(通过 Opn Payments / Omise)泰国居民 + 在泰中国游客(NITMX 跨境,详见下文)1.65%(Opn)· 0.8% Xendit · 1.0–1.5% Pay Solutions泰国银行,T+1
微信支付(Omise / 2C2P)中国大陆(App 内付款)实体店 1.65% / 线上 3.65%(Opn)泰国银行,T+2是(或用 Airwallex)
Alipay+(Omise / 2C2P)中国及亚太 25 种钱包实体店 1.65% / 线上 3.65%(Opn)泰国银行,T+2
TrueMoney 钱包泰国居民1.5–2.0%TrueMoney → 泰国银行否(仅需泰国手机号)
Wise Business任意,电汇约 0.5% 汇兑多币种 Wise 账户
Airwallex任意,含卡 / 微信2.9% + 笔费多币种 Airwallex
USDT (TRON) via NOWPayments能接受加密币的买家0.5–1.0%你自己的加密钱包
PayPal CN国际买家在泰国较难4.4% + 固定费银联 / 中国银行

开户难度可视化

假设你有护照、能提供泰国居住证明,但没有泰国身份证,下表显示各方案的开户难度。

USDT / NOWPayments
10 / 100
TrueMoney 钱包
25
Wise Business
40
Airwallex
50
Omise(仅 PromptPay)
55
Omise(微信 + 支付宝+)
70
2C2P(微信 + 支付宝+)
80
直接申请泰国商户卡账户
95
轻:护照 + 邮箱 中:+ 居住证明、营业资料 重:泰国公司 / 工作证 / 泰国银行

签证情况——真正的瓶颈

几乎所有方案最终都要求你拥有泰国银行账户或泰国营业登记,而这两者都由你的签证决定。下表说明每种常见签证对在泰华人来说真正解锁了什么。

签证类型 能开泰国银行? 能注册公司? 推荐通道 典型在泰华人画像
旅游签 / 免签 否(极少数:Bangkok Bank 通过中介 + 大额存款) 仅限 Wise / Airwallex / USDT 云南来的短期商人、跑货老板
ED 学生签(语言学校 / 泰拳) 有时——Krungsri、Bangkok Bank 凭学校信函 + ฿20k 存款 否(不允许工作) 仅限个人 PromptPay,不可商用 清迈学语言、年纪较轻的中国学生
DTV 签证(5 年目的地泰国签) 分支行而异,部分要求工作证 个体可申请,但活跃经营在法律上仍属灰色 主用 Wise / Airwallex;条件允许时用 PromptPay 辅助 远程办公的中国自由职业者、数字游民
O 类非移民签(泰国配偶) 是——凭结婚证即可 是——配偶可共有,可办工作证 完整 Omise / 2C2P 方案,开通微信 + 支付宝+ 嫁/娶泰国人的中国配偶
B 类非移民签 + 工作证 是——最容易的路径 是——可在 FBA 限制内独资经营 完整 Omise / 2C2P 方案 曼谷 / EEC 的中资企业员工
泰国精英签证(Privilege / Elite) 是——有专属服务协助开户 可注册,但精英签本身不带工作权——需配合工作证 通过泰国公司用完整方案;筹建期用 Airwallex 富裕的中国投资人、退休族、家办成员
LTR 长期居留签证 是——合作银行快速通道 是——内置数字工作证 完整 Omise / 2C2P,所有路径中阻力最小 高薪科技人才、全球高净值人群
退休签(Non-O / O-A / O-X) 禁止工作——不可办营业登记 仅限个人 PromptPay,被动收款 / 家用 清迈、华欣等地的中国退休人士
Smart 智慧签证(S / T / I / E) 是——BoI 担保 是——Smart-S 含创业工作权 完整方案;Smart-S 创始人可直接申请 Omise 中国科技创业者、BoI 推广投资人
PR 永久居留 是——多数权利与泰国国民相同 完整方案,无任何限制 居住 10 年以上的资深华人

签证 → 支付通道映射

旅游签 / ED / 退休签 DTV / 精英签(无工作证) O 类(配偶) B 类 + 工作证 / Smart / LTR / PR 无银行,无营业登记 可开银行,无工作权 银行 + 配偶名义个体户 银行 + 泰国有限公司 Wise · Airwallex · USDT (TRON) Wise + 个人 PromptPay Omise PromptPay(微信受限) 完整 Omise / 2C2P · 微信 · 支付宝+ 从左往右读:你的签证决定可获得的银行 / 营业资格,再决定能用哪条支付通道。 注意:即使个人银行账户能收到钱,把它用作经营收款会危及你的签证和税务身份。商业流水必须配合正式营业登记。

在泰华人常见的"升签"路径

路径 A · 单人游民

DTV

旅游签 → DTV(5 年),凭远程工作或"软实力"项目(泰拳、泰餐等)资格申请,在昆明或香港领事馆办理。

解锁 5 年多次入境,开户成功率提高,但法律上不可向泰国本地客户经营销售。

路径 B · 婚姻

O 类

与泰国人结婚 → O 类签证。由配偶共同登记个体工商户,再以该营业为基础办工作证。

清迈最常见——开咖啡馆、做旅行、开工作室的中国人多走此路。

路径 C · 投资

精英签 / LTR

资金驱动:精英签(฿90 万–500 万换 5–20 年)或 LTR Wealthy Global Citizen(资产 100 万美元、年收入 8 万美元)。LTR 含工作权,精英签不含。

资金充足时最省事。精英签 + 泰国有限公司 = 完整经营权。

路径 D · 创业

Smart-S / BoI

科技 / 创业路线:通过 dtsc.depa.or.th 背书的 Smart-S,或经 BoI 推广的出口型公司。

Omise 费率最低(BoI 公司常可议价),且自带工作权。

税务提醒。自 2024 年 1 月 1 日起,泰国对税务居民(每年居住 ≥ 180 天)汇入泰国的全球收入征税。微信支付结算到泰国银行就属于"汇入"。请在定价前预留个人所得税(累进,最高 35%)或企业所得税(中小企业 15–20%)。

推荐配置

客户主要是中国大陆买家时:用非移民签证开泰国银行账户,注册个体户(ทะเบียนพาณิชย์),然后申请 Opn Payments(原 Omise),一次接入即可同时开通 PromptPay + 微信支付 + 支付宝+。审核期间用 Airwallex 兜底。
暂无泰国银行账户时:先用 Wise Business 或 Airwallex 开发票收款,再增设 USDT-TRC20 服务能接受加密币的买家。在泰国不要依赖 PayPal——没有泰国银行很难提现。

NITMX 捷径——只用 PromptPay 也够

如果你的买家是来泰国旅游的中国游客(不是从大陆远程付款的居民),那你可能根本不需要单独申请微信支付或 Alipay+。NITMX(泰国国家支付清算中心)已与蚂蚁国际(Alipay+)、腾讯(微信支付)和银联建立了网络互联,中国游客可以直接用国内 App 扫描泰国标准 PromptPay 二维码付款。

一码三 App

展示一张普通 PromptPay 二维码即可。中国游客在国内打开支付宝或微信扫码付款——商家无需另外签约支付宝 / 微信收单。

实时汇率

买家以人民币按实时汇率扣款,商家以泰铢入账。结账流程里没有汇兑利差需要管理。

清算银行

跨境清算指定走 Bangkok BankKrungthai Bank。在这两家开收款户路径最顺畅。

不适用的场景

如果中国大陆买家在境内远程付款(购买时人不在泰国),NITMX 不适用——必须走 Omise / 2C2P 的微信支付或 Alipay+。NITMX 要求扫码时人在泰国境内或具备地理位置证明。

面向游客的网站如何取舍:如果 80% 以上交易发生在买家身处泰国时(酒店、旅游、咖啡店扫码点单、市场摊位),仅一个 PromptPay 接入就能同时覆盖泰国本地客户 + 中国游客 + 韩国 / 越南 / 新加坡的 Alipay+ 用户——并完全跳过腾讯 2–4 周的商户审核。

实战接入:通过 Omise / 2C2P 接入微信支付

两家网关都把微信支付实现为"跳转 / QR 源"模式。你的服务端创建 charge,网关返回二维码图片(PC 端)或 weixin:// 深链(移动端),买家在微信内完成支付,服务端通过 webhook 对账。

① 中国买家 浏览你的泰铢网站 手机或电脑均可 ② 你的网站 "使用微信支付" 按钮 POST /api/checkout ③ 你的服务端 用 source=wechat_pay 创建 Omise / 2C2P charge ④ 网关(Omise / 2C2P) 返回二维码图片或 weixin:// 深链 ⑤ 微信支付(腾讯) 买家扫码或打开 App 在微信内以人民币确认 ⑥ Webhook → 服务端 charge.complete 事件 订单标记为已支付 ⑦ 确认页 买家被重定向 "已收到付款" ⑧ 结算 网关 → 你的泰国银行 T+2 到账,泰铢 点击 创建 API 调用 扫码 成功 跳转 结算 延迟:从买家付款到服务端确认平均 8–15 秒。务必以 webhook(步骤 ⑥)为准——买家若提前关闭微信,跳转(步骤 ⑦)可能丢失。 币种:charge 以泰铢创建;微信向买家展示按腾讯当日汇率换算的人民币金额。

实现代码

Omise · 服务端 (Node)
Omise · 浏览器
Omise · webhook
2C2P · 服务端 (Python)
2C2P · webhook
POST /api/checkout — 创建一个微信支付 charge
// npm i omise express
const express = require("express");
const omise   = require("omise")({
  publicKey: process.env.OMISE_PUBLIC_KEY,
  secretKey: process.env.OMISE_SECRET_KEY,
});

const app = express();
app.use(express.json());

app.post("/api/checkout", async (req, res) => {
  const { amountTHB, orderId } = req.body;

  // 1. 创建 wechat_pay 类型的 source;金额单位是 satang(1 THB = 100)
  const source = await omise.sources.create({
    type:     "wechat_pay",
    amount:   Math.round(amountTHB * 100),
    currency: "THB",
  });

  // 2. 基于该 source 创建 charge
  const charge = await omise.charges.create({
    amount:      Math.round(amountTHB * 100),
    currency:    "THB",
    source:      source.id,
    return_uri:  `https://yoursite.com/pay/return?order=${orderId}`,
    metadata:    { orderId },
  });

  // 3. 返回二维码图片地址(PC)和深链(手机)
  res.json({
    chargeId:   charge.id,
    qrImageUri: charge.source.scannable_code?.image?.download_uri,
    deeplink:   charge.authorize_uri,   // 在手机上打开微信
    status:     charge.status,           // 付款前为 "pending"
  });
});

app.listen(3000);
浏览器 — 显示二维码或唤起微信
async function payWithWeChat(amountTHB, orderId) {
  const r = await fetch("/api/checkout", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ amountTHB, orderId }),
  });
  const { qrImageUri, deeplink, chargeId } = await r.json();

  const isMobile = /iPhone|Android/i.test(navigator.userAgent);

  if (isMobile) {
    window.location = deeplink;            // → 唤起微信
  } else {
    document.getElementById("qr").src = qrImageUri;
    pollChargeStatus(chargeId);            // 可选:轮询付款状态
  }
}
POST /webhooks/omise — 标记订单已支付
app.post("/webhooks/omise",
  express.raw({ type: "application/json" }),
  async (req, res) => {
    const event = JSON.parse(req.body);

    // 不要相信 webhook body 的金额——务必重新拉取
    if (event.key === "charge.complete") {
      const charge = await omise.charges.retrieve(event.data.id);
      if (charge.status === "successful" && charge.paid) {
        await markOrderPaid(charge.metadata.orderId, charge.amount);
      }
    }
    res.sendStatus(200);
  });
2C2P PGW — 创建 Payment Token (Python / Flask)
# pip install pyjwt requests flask
import jwt, requests, time, uuid
from flask import Flask, request, jsonify

app          = Flask(__name__)
MERCHANT_ID  = "JT01"
SECRET_KEY   = "YOUR_2C2P_SECRET_KEY"
ENDPOINT     = "https://sandbox-pgw.2c2p.com/payment/4.3/paymentToken"

@app.route("/api/checkout", methods=["POST"])
def checkout():
    body = request.json
    payload = {
        "merchantID":    MERCHANT_ID,
        "invoiceNo":     body["orderId"],
        "description":   body["description"],
        "amount":        body["amountTHB"],
        "currencyCode":  "THB",
        "paymentChannel": ["WECHAT"],            # 锁定为微信支付
        "frontendReturnUrl": "https://yoursite.com/pay/return",
        "backendReturnUrl":  "https://yoursite.com/webhooks/2c2p",
        "nonceStr":       uuid.uuid4().hex,
    }
    token = jwt.encode(payload, SECRET_KEY, algorithm="HS256")
    r = requests.post(ENDPOINT, json={"payload": token}).json()
    decoded = jwt.decode(r["payload"], SECRET_KEY, algorithms=["HS256"])

    # 返回托管收银台 URL —— 2C2P 在该页面展示微信二维码
    return jsonify({
        "redirectUrl": decoded["webPaymentUrl"],
        "paymentToken": decoded["paymentToken"],
    })
POST /webhooks/2c2p — 校验 JWT 并对账
@app.route("/webhooks/2c2p", methods=["POST"])
def c2p_webhook():
    token = request.json["payload"]
    try:
        data = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
    except jwt.InvalidSignatureError:
        return ("bad sig", 400)

    # respCode "0000" = 成功(参见 2C2P 规范)
    if data["respCode"] == "0000" and data["channelCode"] == "WECHAT":
        mark_order_paid(data["invoiceNo"], data["amount"])

    return ("", 200)
先用沙箱。Omise 和 2C2P 都提供沙箱微信二维码,用各自的测试 App 即可扫描——上线前不需要真实的腾讯凭证。生产环境的微信支付申请通过网关后台提交,2–4 周指腾讯审核业务真实性所需时间(2C2P / Opn 自身的账户开通快得多)。

开通清单

  1. 用非移民签证开泰国银行账户(清迈最友好的是 Bangkok Bank 和 Kasikorn)。
  2. 注册个体工商户(ทะเบียนพาณิชย์)或泰国有限公司。Omise 必须有此资质;2C2P 对小额商户允许个人申请。
  3. dashboard.omise.co2c2p.com 注册并申请开通微信支付 + 支付宝+。
  4. 提交业务网站,附带可访问的隐私政策、退款政策和中文价格页。
  5. 等待腾讯审核(由网关代办)。审核期间用 Wise / Airwallex 收发票。

合规要求——PSP 实际审核什么

下面是 Opn / 2C2P 在开通微信支付或 Alipay+ 之前会要求的文件 / 政策清单。别当成走过场——一半的拒绝都来自材料不全,而非业务本身有问题。

营业资料

  • 公司注册证明 Affidavit(90 天内开具)
  • 章程 / 公司细则
  • 税号及 VAT 证书 Por Por 20(ภ.พ. 20)
  • 外资公司:需公证或加注的翻译件
  • 近期水电账单或银行对账单作为营业地址证明

KYC / 反洗钱

  • 所有 持股 ≥25% 的最终受益人 (UBO) 的护照 / 泰国身份证(外国人需公证)
  • 所有授权签字人的 ID + 有效签证 / 工作证(需手写签名扫描件)
  • 近 3–4 个月银行对账单,体现交易历史
  • 大额开通需提供资金来源声明

网站政策

  • 隐私政策(须符合 PDPA,详见下条)
  • 退款政策——泰国拒付规则倾向消费者,审核严格
  • 服务条款
  • 开通微信 / Alipay+ 需中文价格页
  • 双语版本(泰文 + 英文)有助于过审

PDPA(泰版 GDPR)

  • 隐私通告须明示:收集什么数据、用途保留期限
  • PDPA 合规的 Cookie 横幅——非必要 Cookie 需用户同意前阻止加载
  • 建立数据泄露应急流程,72 小时内向 PDPC 上报
  • 指定数据主体请求联系人

PCI DSS v4.0.1

  • 所有结账相关接口启用 TLS 1.2+
  • 管理后台启用 MFA
  • 持卡人数据前部署网络隔离 + 防火墙
  • 存储卡片数据:需完整 PCI DSS 证书
  • 使用网关托管页或动态 QR API:合规范围降至 SAQ-A,网关承担大部分负担

实战建议——压缩合规范围

务必通过网关的托管页或重定向 / QR-source 模式接入,而非直接传卡 API。这一架构选择能把数千条 PCI DSS 控制项压缩为一份自评问卷(SAQ-A),网关成为敏感数据的"控制者",开通周期可缩短数月。

把这个页面跑在本地 AI 智能体(Ollama)工作流里

这个静态页面本身是一份说明,但如果它前端挂上一个本地智能体,就能回答"我是 ED 签证、客户来自昆明,应该用哪条通道?"并直接生成测试 charge。整个流程跑在你的笔记本上——不调用云端 LLM,也不会泄露商户密钥。

架构

浏览器(本 HTML 页) 本地智能体 Worker · Python 或 Node 外部 对比页 你正在阅读的静态 HTML + 右下角聊天小窗 智能体聊天面板 "ED 签证的中国学生 应该用哪条通道?" QR / charge 预览 展示 worker 返回的 微信测试二维码 localhost:8080 由 `python -m http.server` 提供 FastAPI / Express /chat · /charge/preview /health (端口 8000) Agent 循环 tool-calling 控制器, JSON in / JSON out 工具注册表 lookup_fee · pick_rail create_test_charge · log Ollama 运行时 localhost:11434 qwen3:8b · llama3.1:8b Omise 沙箱 API 2C2P 沙箱 API POST /chat /charge/preview 流式

两个进程,同一个网络命名空间。浏览器 → 本地 FastAPI Worker → Ollama + 网关沙箱。除测试调用 Omise / 2C2P 外,没有任何数据离开你的电脑。

为什么选 Ollama

原生中英泰三语

qwen3:8b 原生处理中文、泰语、英语——这正契合你的场景:买家说中文,网关文档是英文,银行表单是泰语。

支持工具调用

自 v0.4 起,Ollama 的 /api/chat 支持 OpenAI 格式的 tools 数组。Agent 循环就是个普通 while 循环,无需 LangChain。

密钥不出本机

Omise 密钥、2C2P 商户号、甚至沙箱 webhook 都只在 127.0.0.1 流转。在泰国测试、外网受限时尤其有用。

成本:零

用云端模型反复调试 prompt 和工具 schema 烧钱很快。本地推理让快速迭代阶段免费。

接入步骤

1 · Ollama 安装
2 · 工具定义
3 · Agent 循环
4 · FastAPI 服务
5 · 浏览器小窗
一次性配置 — 安装 Ollama 并下载支持工具调用的模型
# Windows / macOS / Linux — ollama.com/download
ollama pull qwen3:8b           # 中文友好,约 5GB,支持工具调用
ollama pull llama3.1:8b        # 仅英文场景的备选

# 验证服务在 11434 端口运行
curl http://localhost:11434/api/tags

# 工具调用快速冒烟测试
curl http://localhost:11434/api/chat -d '{
  "model": "qwen3:8b",
  "messages": [{"role":"user","content":"现在几点?"}],
  "tools": [{"type":"function","function":{
    "name":"clock","description":"返回当前时间","parameters":{"type":"object","properties":{}}
  }}],
  "stream": false
}'
tools.py — 智能体可调用的函数
from typing import Any

FEES = {
    "promptpay":    {"pct": 0.0095, "flat_thb": 2, "settles_to": "thai_bank"},
    "wechat_pay":   {"pct": 0.0290, "flat_thb": 0, "settles_to": "thai_bank"},
    "alipay_plus":  {"pct": 0.0290, "flat_thb": 0, "settles_to": "thai_bank"},
    "truemoney":    {"pct": 0.0175, "flat_thb": 0, "settles_to": "truemoney"},
    "usdt_tron":    {"pct": 0.0080, "flat_thb": 0, "settles_to": "crypto_wallet"},
}

def lookup_fee(rail: str, amount_thb: float) -> dict:
    """计算指定通道的手续费和到账净额。"""
    f = FEES[rail]
    fee = amount_thb * f["pct"] + f["flat_thb"]
    return {"rail": rail, "fee_thb": round(fee, 2), "net_thb": round(amount_thb - fee, 2)}

def pick_rail(visa: str, buyer_country: str, has_thai_bank: bool) -> dict:
    """根据商家签证和买家来源推荐通道。"""
    if not has_thai_bank:
        return {"rail": "wise_or_airwallex", "why": "无泰国银行,走多币种账户"}
    if visa in {"tourist", "ed", "retirement"}:
        return {"rail": "promptpay_personal", "why": "禁止商用——仅个人收款"}
    if buyer_country == "CN":
        return {"rail": "wechat_pay", "why": "中国买家——走 Omise 微信支付"}
    return {"rail": "promptpay", "why": "泰国本地默认通道"}

def create_test_charge(rail: str, amount_thb: float) -> dict:
    # 调用 Omise 沙箱,返回二维码和 charge id(细节略)
    ...

TOOLS: list[dict[str, Any]] = [
    {"type": "function", "function": {
        "name": "lookup_fee",
        "description": "计算指定通道在某金额下的手续费和到账净额。",
        "parameters": {"type": "object", "properties": {
            "rail":       {"type": "string", "enum": list(FEES.keys())},
            "amount_thb": {"type": "number"}
        }, "required": ["rail", "amount_thb"]}
    }},
    {"type": "function", "function": {
        "name": "pick_rail",
        "description": "根据商家签证和目标买家推荐支付通道。",
        "parameters": {"type": "object", "properties": {
            "visa":           {"type": "string"},
            "buyer_country":  {"type": "string"},
            "has_thai_bank":  {"type": "boolean"}
        }, "required": ["visa", "buyer_country", "has_thai_bank"]}
    }},
    {"type": "function", "function": {
        "name": "create_test_charge",
        "description": "在沙箱创建一笔 charge 并返回微信二维码用于预览。",
        "parameters": {"type": "object", "properties": {
            "rail":       {"type": "string"},
            "amount_thb": {"type": "number"}
        }, "required": ["rail", "amount_thb"]}
    }},
]

DISPATCH = {
    "lookup_fee":         lookup_fee,
    "pick_rail":          pick_rail,
    "create_test_charge": create_test_charge,
}
agent.py — 调用 Ollama 的 tool-calling 循环
import json, requests
from tools import TOOLS, DISPATCH

OLLAMA = "http://localhost:11434/api/chat"
MODEL  = "qwen3:8b"
SYSTEM = ("你是一名面向在泰华人的支付顾问。"
          "必须使用提供的工具,而不是凭空猜测费率或通道。"
          "始终用用户的语言(中文、泰语或英文)回答。")

def run_agent(user_msg: str, history: list[dict] | None = None) -> dict:
    msgs = [{"role": "system", "content": SYSTEM}, *(history or []),
            {"role": "user", "content": user_msg}]

    for _ in range(6):                              # 工具调用预算上限
        r = requests.post(OLLAMA, json={
            "model": MODEL, "messages": msgs,
            "tools": TOOLS, "stream": False,
        }, timeout=120).json()

        msg = r["message"]
        msgs.append(msg)

        calls = msg.get("tool_calls") or []
        if not calls:
            return {"reply": msg["content"], "history": msgs}

        for call in calls:                            # 执行所有被请求的工具
            name = call["function"]["name"]
            args = call["function"]["arguments"]
            result = DISPATCH[name](**args)
            msgs.append({"role": "tool", "name": name,
                         "content": json.dumps(result, ensure_ascii=False)})

    return {"reply": "(已超出工具调用预算)", "history": msgs}
server.py — 暴露 /chat 接口给浏览器
# pip install fastapi uvicorn requests
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from agent import run_agent

app = FastAPI()
app.add_middleware(CORSMiddleware, allow_origins=["http://localhost:8080"],
                   allow_methods=["*"], allow_headers=["*"])

class ChatIn(BaseModel):
    message: str
    history: list[dict] = []

@app.post("/chat")
def chat(body: ChatIn):
    return run_agent(body.message, body.history)

@app.get("/health")
def health():
    return {"ok": True, "model": "qwen3:8b"}

# 启动:  uvicorn server:app --reload --port 8000
chat-widget.js — 嵌入到静态页面,调用本地 worker
const WORKER = "http://localhost:8000/chat";
let history = [];

async function ask(message) {
  const r = await fetch(WORKER, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ message, history }),
  });
  const { reply, history: next } = await r.json();
  history = next;                // 保留 tool-call 轨迹便于追溯
  render(reply);
}

// 智能体能通过工具回答的示例提问:
//   "我有 ED 签证,想给云南朋友收 500 baht,应该用哪个?"
//   "12000 泰铢,Omise 微信和 USDT 哪个手续费低?"
//   "帮我生成 99 泰铢的微信沙箱二维码"

每次提问的内部流程

用户消息 中文 / 泰语 / 英文 qwen3:8b 选择要调用的工具 pick_rail() {签证, 国家, 银行} lookup_fee() 在选中通道上算费率 create_test_charge() → Omise 沙箱二维码 最终答案返回浏览器 "ED 签证 + 中国买家 → 用 Omise 微信(费率 2.9%,500 泰铢手续费 ฿14.50)。测试二维码 ↗"

端到端启动

# 终端 1 —— LLM
ollama serve

# 终端 2 —— Agent worker
uvicorn server:app --reload --port 8000

# 终端 3 —— 提供本 HTML 页面
python -m http.server 8080

# 浏览器打开 http://localhost:8080/thailand_china_payments_zh.html
# 右下角聊天小窗 → localhost:8000 → localhost:11434
为什么把 LLM 单独抽成一个进程?Ollama 是模型服务,FastAPI worker 是策略 + 工具层。两者解耦后,你可以把 qwen3 换成 llama3.1 或更小的蒸馏模型而不动 agent 代码;也可以把 worker 装进 Docker,而 Ollama 留在宿主机以便用 GPU。
生产环境提醒。本地 Ollama 智能体适合做原型、演示和单用户工具。要上线公网,请在 worker 前加鉴权代理;多租户场景换用云端 Claude 或 GPT;任何情况下都不要让 LLM 直接持有 Omise 真实密钥——把密钥保留在 worker 环境变量里,仅暴露窄接口的工具函数即可。