Tài liệu nhà phát triển
Tiếng Việt

Phát triển bot

Bot thoại

Khi ai đó gọi cho bot từ ứng dụng, một mô hình giọng nói AI sẽ trả lời. Cách bật, chỉ dẫn và giọng nói, thông báo cuộc gọi, công cụ, máy chủ MCP, khóa riêng, bản ghi hội thoại, chế độ âm thanh gốc, Apple Watch và lưu cuộc trò chuyện — hướng dẫn dành cho chủ bot.

Bot thoại là bot mà mọi người có thể gọi điện từ ứng dụng. Người nhận cuộc gọi không phải là mã của chủ bot mà là mô hình giọng nói AI của Pabal; chủ bot dùng Bot API để đặt chỉ dẫn, lời chào, giọng nói, ngôn ngữ và giới hạn mà mô hình đó tuân theo. Mỗi khi cuộc gọi bắt đầu và kết thúc, bạn nhận update voice_call, và trong cuộc gọi, mô hình có thể gọi mã của bot (công cụ) để tra đơn hàng hay kiểm tra lịch đặt. Ngoài ra còn có chế độ âm thanh gốc, trong đó máy chủ của bạn nhận trực tiếp âm thanh thay cho mô hình AI. Nếu bạn mới tạo bot lần đầu, hãy xem Tạo bot trước.

Hiện trạng

Bot thoại chỉ dùng được khi được bật trên từng máy chủ, và vào lúc sửa tài liệu này (2026-10-02) tính năng đang tắt trên pabal.me — đang được thử nghiệm trên máy chủ phát triển

Chỉ trên máy chủ đáp ứng tất cả các điều kiện dưới đây mới gọi được cho bot thoại. Các phương thức vẫn có trên máy chủ tắt tính năng thoại nên bạn đọc được cài đặt. Nhưng trước khi tính năng thoại được bật trên máy chủ này và người vận hành cho phép bot đó, không cài đặt nào được lưu ngoài việc tắt (tắt thoại · tắt bản ghi hội thoại · tắt chế độ âm thanh gốc · xóa khóa riêng · xóa máy chủ MCP) (403 VOICE_NOT_AVAILABLE). Bot của bạn hiện có nhận được cuộc gọi hay không thì xem available và unavailable_reason của getVoiceCallSettings.

Tính năngĐể bật được (tất cả)pabal.me hiện nay
Cuộc gọi bot thoạiMáy chủ đã gắn gateway thoại và bộ chuyển tiếp cuộc gọi (TURN) · chính sách quyền riêng tư đã đăng mục cuộc gọi bot (bot-calls) · người vận hành đã cho phép bot đó · trước khi công khai chỉ những người thử nghiệm do người vận hành chọn mới gọi được · ứng dụng của người gọi là bản build có màn hình xác nhận (máy tính pabal-bc2 — khi màn hình xác nhận chỉ có tiếng Hàn và tiếng Anh thì pabal-bc1 cũng được chấp nhận, Android 62151, iPhone bản build 5 trở lên, ứng dụng Apple Watch bản build 1 trở lên) — các bản build đó chưa được phát hànhTắt
Gọi từ Apple WatchCuộc gọi bot thoại + API đồng hồ của máy chủ + điểm vào âm thanh của đồng hồ (pabal-voice-edge) + gateway nhận cuộc gọi trực tiếp (direct) · bản build ứng dụng đồng hồ từ TELEGRAM_VOICE_MIN_BUILD_WATCHOS trở lên · bot dùng công cụ · máy chủ MCP · khóa riêng · chế độ âm thanh gốc · bản ghi hội thoại của chủ bot thì chưa đượcTắt — đang thử nghiệm trên máy chủ phát triển
Lưu cuộc trò chuyện (bản của người gọi)Cuộc gọi bot thoại + mục bot-calls-keep-text trong chính sách + gateway khai báo keep_text (0.9.0) + bản build ứng dụng có hộp chọn · rà soát pháp lý câu chữ trên màn hình xác nhậnTắt — trước rà soát pháp lý
Cuộc gọi từ botMục bot-calls-outbound trong chính sách quyền riêng tư và mục voice-bot-owners-outbound trong điều khoản sử dụng đã được đăng (cùng mục voice-bot-owners, để chủ bot đồng ý điều khoản phiên bản 4) · công tắc chung của người vận hành, sau khi rà soát pháp lý · chủ bot bật trong @BotFather (điều khoản phiên bản 4, mục đích) · người vận hành xét duyệt bot · quyền cho phép của người nhận cuộc gọi (bản ứng dụng có màn hình cho phép) · gateway có bản ghi âm thông báo gọi đi đã được duyệt · ngôn ngữ bot là tiếng Hàn hoặc tiếng AnhTắt — chờ rà soát pháp lý
Ngôn ngữ bot ngoài tiếng Hàn · tiếng Anh (7 ngôn ngữ)Cuộc gọi bot thoại + gateway gọi được bằng ngôn ngữ của bot đó (từ 0.7.0 gọi được cả 9 ngôn ngữ) · người vận hành đã đọc chỉ dẫn và lời chào của bot đó rồi cho phép (kể cả trên máy chủ nhận mọi bot)Tắt — chưa triển khai
Màn hình xác nhận và giọng thông báo ngoài tiếng Hàn · tiếng Anh (8 ngôn ngữ)Với từng ngôn ngữ, chỉ được đưa vào bản build ứng dụng và image gateway sau khi rà soát pháp lý và người bản ngữ kiểm tra xong. Trước đó, người gọi dùng ứng dụng bằng ngôn ngữ ấy vẫn thấy màn hình xác nhận tiếng Anh và nghe thông báo tiếng AnhBản nháp — chưa rà soát
Điều khoản chủ bot (phiên bản 4)Điều khoản sử dụng đã đăng mục chủ bot (voice-bot-owners). Bản ghi hội thoại, khóa riêng, chế độ âm thanh gốc, máy chủ MCP và cuộc gọi từ bot đều cần điều khoản này trướcChưa thể đồng ý
Công cụCuộc gọi bot thoại + gateway nhận công cụTắt
Máy chủ MCPThành phần MCP · địa chỉ đi ra dành riêng (điều kiện vận hành — mã không kiểm tra) · mục bot-calls-mcp trong chính sách và mục chủ bot trong điều khoản · chủ bot đồng ý điều khoản chủ bot · bản build ứng dụng hiển thị nơi nhận MCP của bot trên màn hình xác nhận (trước đó, dù có đăng ký thì cũng không cuộc gọi nào mang theo công cụ MCP)Tắt
Khóa mô hình riêngKhóa chủ trên máy chủ · người vận hành bật sau khi ứng dụng đã phát hành màn hình xác nhận (phiên bản 2) hiển thị nơi nhận của bot dùng khóa riêng · điều khoản chủ bot · gateway chạy khóa của chủ bot (mặc định chỉ khóa Google)Tắt — không nhận khóa
Bản ghi hội thoạiNgười vận hành cho phép trên trang quản trị sau khi rà soát pháp lý (mặc định tắt) · điều khoản chủ bot · công tắc của chủ bot · gateway có giọng thông báo bản ghi hội thoại · cuộc gọi ở chế độ managedTắt
Chế độ âm thanh gốcKhóa chủ trên máy chủ · mục bot-calls-stream trong chính sách và mục chủ bot trong điều khoản · gateway nhận cuộc gọi âm thanh gốc · bản build ứng dụng có màn hình xác nhận âm thanh gốc (chưa có)Tắt
  • Người gọi (người thực hiện cuộc gọi) phải từ 19 tuổi trở lên, sẽ thấy màn hình xác nhận "An AI answers this call" (AI sẽ trả lời cuộc gọi này) trước khi gọi, và khi kết nối thì nghe giọng thông báo AI trước tiên (Những gì người gọi thấy). Ứng dụng không có màn hình xác nhận (kể cả ứng dụng Telegram gốc hay các client như Telethon) sẽ không có nút gọi cho bot.
  • Âm thanh của cuộc gọi ở chế độ managed (do mô hình AI của Pabal trả lời) được gửi tới Google — Gemini Live API của Vertex AI, ở Iowa, Hoa Kỳ (Google Cloud us-central1). Pabal không lưu âm thanh. Google không giữ âm thanh và văn bản sau khi tạo câu trả lời, nhưng nội dung mà bước kiểm tra tự động nghi là lạm dụng có thể được ghi lại tối đa 90 ngày (giống nội dung trên màn hình xác nhận). Khi vận hành chỉ dùng duy nhất engine này, và không âm thầm chuyển sang nhà cung cấp khác (không có chuyển đổi tự động).
  • Khác với cuộc gọi giữa người với người, cuộc gọi bot không được mã hóa đầu cuối: gateway thoại của Pabal giải mã âm thanh rồi chuyển cho mô hình AI. Màn hình xác nhận của người gọi có thông báo điều này.

Một cuộc gọi diễn ra thế nào

Người gọi Pabal (máy chủ của người vận hành) Bên ngoài Pabal Ứng dụng Pabal Xác nhận · 19+ Bản build đã sửa pabal-server MTProto · Bot API Xét duyệt · giới hạn Nhật ký cuộc gọi Chương trình chủ bot Bot API (HTTPS) Mã và máy chủ của bạn coturn Chỉ chuyển tiếp pabal-voice Thông báo AI · chỉ dẫn Không lưu âm thanh Google Vertex AI Gemini Live us-central1 (Hoa Kỳ) ① Gọi ② Liên kết cuộc gọi · cài đặt · công cụ ③ voice_call · truy vấn cài đặt · kết quả · endVoiceCall Âm thanh (chỉ chuyển tiếp) ④ Âm thanh ↔ AI trả lời
Chế độ managed: tín hiệu ở trên, âm thanh ở dưới

Giải thích sơ đồ

  • Ba vùng: ứng dụng của người gọi → máy chủ của người vận hành Pabal (máy chủ Pabal, gateway thoại, máy chủ chuyển tiếp) → bên ngoài Pabal (mô hình AI của Google, chương trình của chủ bot). Mã của chủ bot không nhìn thấy một byte âm thanh nào.
  • ① Gọi: ứng dụng báo cho máy chủ sự đồng ý nhận được từ màn hình xác nhận rồi mới gọi. Máy chủ kiểm tra cả người gọi (đồng ý — tự xác nhận từ 19 tuổi trở lên · bản build ứng dụng · người thử nghiệm · giới hạn) lẫn bot (cho phép · cài đặt · giới hạn), rồi nhận cuộc gọi hoặc kết thúc bằng tín hiệu máy bận.
  • ② Liên kết: máy chủ giao cuộc gọi cho gateway thoại, kèm theo chỉ dẫn, lời chào, giọng nói, ngôn ngữ và công cụ của chủ bot. Gateway luôn đặt các quy tắc cơ bản của Pabal (như tự nhận là AI) lên trước, rồi mới tới chỉ dẫn.
  • ③ Bot API: chương trình của chủ bot đặt cài đặt, nhận voice_call khi cuộc gọi bắt đầu và kết thúc, và nhận voice_call_tool_query khi mô hình gọi công cụ rồi trả lời. Mũi tên xanh ở ③ là thứ máy chủ gửi cho bot, mũi tên xám bên dưới là thứ bot gửi lên máy chủ.
  • ④ Âm thanh (hàng dưới): ứng dụng và gateway không biết địa chỉ của nhau và chỉ trao đổi âm thanh qua máy chủ chuyển tiếp (coturn); gateway chuyển âm thanh cho mô hình của Google và nhận lại câu trả lời. Ô màu đỏ là nơi âm thanh rời khỏi Pabal.
  • Ở chế độ âm thanh gốc, máy chủ của chủ bot thế vào chỗ của Google ở ④ (Chế độ âm thanh gốc).

Cuộc gọi từ Apple Watch (đang chuẩn bị)

Ứng dụng đồng hồ không dùng MTProto. Đồng hồ đăng nhập, nhận danh sách bot và màn hình xác nhận, rồi gọi qua API đồng hồ (HTTPS JSON) của máy chủ. Máy chủ xét duyệt giống như cuộc gọi từ ứng dụng, rồi đưa cho đồng hồ một vé dùng một lần (15 giây) chỉ dành cho cuộc gọi đó và giao cuộc gọi cho gateway. Đồng hồ cầm vé đó kết nối tới điểm vào âm thanh (WSS, pabal-voice-edge), và điểm vào nối âm thanh tới tiến trình gateway đang phụ trách cuộc gọi. Không có trao đổi khóa (DH) và cũng không có máy chủ chuyển tiếp (coturn). Tuy vậy gateway vẫn giải mã âm thanh để chuyển cho mô hình, nên đây không phải mã hóa đầu cuối, và nơi âm thanh đến cũng giống như vậy. Chủ bot nhận voice_call y hệt cuộc gọi từ ứng dụng (chỉ thời điểm started là khác, xem bên dưới).

Bật cuộc gọi thoại

Việc bật cuộc gọi thoại phải do chính con người thực hiện tại @BotFather — vì đồng ý quy tắc bot thoại là cam kết của chủ bot chứ không phải của token. Các cài đặt còn lại thì làm ở @BotFather hay qua Bot API đều được.

  1. Nhờ người vận hành cho phép bot. Trên máy chủ chỉ nhận bot đã cho phép (mặc định), cho đến khi người vận hành cho phép bằng ID bot (dãy số trước dấu : của token) trong tab Thoại của trang quản trị, @BotFather sẽ không có nút cuộc gọi thoại, và thay đổi cài đặt sẽ bị 403 VOICE_NOT_AVAILABLE. Nếu chưa công khai, hãy nhờ thêm tài khoản bạn sẽ dùng để gọi thử vào danh sách người thử nghiệm (ID người dùng của bạn là message.from.id trong getUpdates sau khi bạn nhắn tin cho bot).
  2. Đặt chỉ dẫn. Đây là văn bản mô tả bot làm gì trong cuộc gọi. Đặt bằng setVoiceCallSettings bên dưới, hoặc gửi qua nút Chỉ dẫn (지시문) trên màn hình cuộc gọi thoại của @BotFather. Không có chỉ dẫn thì không bật được (trừ chế độ âm thanh gốc).
  3. Tại @BotFather: /mybots → bot → Cuộc gọi thoại (음성 통화) → Bật (켜기). Lần đầu tiên, quy tắc bot thoại hiện ra; bấm Đồng ý quy tắc và bật (규칙에 동의하고 켜기) là bật. Từ lần sau chỉ hỏi "Bật nhé?" (켤까요?).
  4. Chờ được cho phép lại. Trên máy chủ chỉ nhận bot đã được cho phép, sự cho phép chỉ áp dụng cho chỉ dẫn, lời chào và công cụ tại thời điểm đó, nên nếu bạn viết lại chỉ dẫn sau khi đã được cho phép thì bot sẽ chuyển sang trạng thái unavailable_reason: "awaiting_review". Người vận hành phải xem lại và cho phép lại thì bot mới nhận cuộc gọi. Mỗi lần bạn thay đổi một trong ba thứ đó (ở chế độ âm thanh gốc là địa chỉ âm thanh gốc) đều như vậy. Nếu ngôn ngữ của bot không phải tiếng Hàn hay tiếng Anh, thì kể cả trên máy chủ nhận mọi bot, người vận hành cũng phải đọc chỉ dẫn và lời chào rồi cho phép thì bot mới nhận cuộc gọi (Giọng nói và ngôn ngữ).
  5. Kiểm tra available của getVoiceCallSettings có là true không, rồi thử gọi bằng nút gọi trong hồ sơ bot trên bản build ứng dụng có màn hình xác nhận.
켜기
BotFather음성 통화를 켜기 전에 음성 봇 규칙을 확인해 주세요. 1. 봇은 언제나 AI 라고 밝혀요. 사람인 척하게 할 수 없어요. 2. 실제 기관·회사·사람을 사칭할 수 없어요. 3. 카드 번호·주민등록번호·비밀번호·인증 번호를 묻게 할 수 없어요. 4. 통화 소리는 파발이 저장하지 않고, 대답을 만들기 위해 Google(Vertex AI)로 보내져요. 5. 봇과의 음성 통화는 만 19세 이상만 할 수 있어요. 6. 규칙을 어기면 운영자가 음성 통화나 봇을 멈출 수 있어요.
규칙에 동의하고 켜기
BotFather음성 통화를 켰어요. 음성 통화 · @order_helper_bot 상태: 켜짐 목소리: Kore (단단한) · 언어: 한국어 … 전화를 받으면 먼저 "AI 음성 봇과 연결되었습니다" 안내가 나가고, 그다음 봇이 인사해요.

Hiện tại BotFather trả lời bằng tiếng Hàn. Trong đoạn hội thoại trên, sau khi bạn bấm Bật, BotFather hiện quy tắc bot thoại:

  1. Bot luôn tự nhận là AI. Không được cho bot giả làm người.
  2. Không được mạo danh cơ quan, công ty hay người có thật.
  3. Không được cho bot hỏi số thẻ, số định danh cá nhân, mật khẩu hay mã xác thực.
  4. Pabal không lưu âm thanh cuộc gọi; âm thanh được gửi tới Google (Vertex AI) để tạo câu trả lời.
  5. Chỉ người từ 19 tuổi trở lên mới được gọi thoại với bot.
  6. Nếu vi phạm quy tắc, người vận hành có thể tạm dừng cuộc gọi thoại hoặc bot.

Sau khi bạn bấm Đồng ý quy tắc và bật, BotFather báo đã bật cuộc gọi thoại và hiện màn hình trạng thái (bot @order_helper_bot, trạng thái: bật, giọng: Kore (rắn rỏi) · ngôn ngữ: tiếng Hàn …), kèm lời nhắc rằng khi nhận cuộc gọi, thông báo "AI 음성 봇과 연결되었습니다" (bản tiếng Anh: "You're now talking to an AI voice bot.") được phát trước, sau đó bot mới chào.

Trên máy chủ chỉ nhận bot đã cho phép, trạng thái có thể hiện là "켜짐 — 바뀐 지시문·인사말을 운영자가 확인할 때까지 전화를 받지 않아요" (bật — không nhận cuộc gọi cho đến khi người vận hành xem lại chỉ dẫn và lời chào đã thay đổi) (bước 4 ở trên). Nếu ngôn ngữ của bot không phải tiếng Hàn hay tiếng Anh, thì trên máy chủ nào cũng vậy, cho đến khi người vận hành cho phép, trạng thái sẽ hiện là "켜짐 — 한국어·영어가 아닌 봇은 운영자가 지시문·인사말을 읽고 허용할 때까지 전화를 받지 않아요 (자동 규칙 검사가 한국어와 영어만 읽어요)" (bật — bot không dùng tiếng Hàn hay tiếng Anh sẽ không nhận cuộc gọi cho đến khi người vận hành đọc chỉ dẫn, lời chào và cho phép; bước kiểm tra quy tắc tự động chỉ đọc được tiếng Hàn và tiếng Anh).

Màn hình cuộc gọi thoại có các nút bật · tắt, chỉ dẫn, lời chào, giọng nói, ngôn ngữ, giới hạn, mức sử dụng, bản ghi hội thoại, khóa riêng, chế độ âm thanh gốc, và (trên máy chủ bật MCP) máy chủ MCP. Ngay cả sau khi tính năng thoại bị tắt hoặc bot bị rút lại sự cho phép, tắt và xóa thì lúc nào cũng làm được — tắt thoại, xóa khóa riêng, tắt chế độ âm thanh gốc, xóa máy chủ MCP.

Khi bật và ở mỗi cuộc gọi, tên bot cũng bị kiểm tra mạo danh. Bot dùng tên của cơ quan hay công ty có thật sẽ không bật được, với lỗi VOICE_CONTENT_REFUSED: name: impersonation. Nếu đó thật sự là bot của cơ quan ấy, hãy báo cho bộ phận hỗ trợ của máy chủ.

Cài đặt — setVoiceCallSettings

Đặt cài đặt thoại của bot. Đây là sửa một phần: chỉ những gì được gửi mới thay đổi; toàn bộ được kiểm tra rồi ghi một lần, hoặc bị từ chối toàn bộ. Nếu không có gì thay đổi thì không ghi gì cả.

curl -s "https://pabal.me/bot$BOT_TOKEN/setVoiceCallSettings" -H 'Content-Type: application/json' -d '{
  "instructions": "Bạn là nhân viên nghe điện thoại của cửa hàng Order Helper. Hãy trả lời ngắn gọn và lịch sự. Cửa hàng mở cửa từ 10 giờ sáng đến 8 giờ tối.",
  "greeting": "Order Helper xin nghe. Tôi có thể giúp gì cho bạn?",
  "voice": "kore",
  "language": "ko-KR",
  "max_call_duration": 180
}'
# {"ok":true,"result":true}
Tham sốKiểuMô tả
enabledBooleanBật · tắt. Muốn bật thì phải đã đồng ý quy tắc tại @BotFather (nếu không: 403 VOICE_RULES_NOT_ACCEPTED), và nếu ở chế độ managed thì phải có chỉ dẫn. Tắt thì lúc nào cũng được — cuộc gọi đang diễn ra vẫn đi đến hết, chỉ không nhận cuộc gọi mới
instructionsStringChỉ dẫn mà mô hình tuân theo. Tối đa 8.192 byte UTF-8 (khoảng 2.700 ký tự tiếng Hàn). "" là xóa — bot ở chế độ managed đang bật thì không để trống được. Càng dài thì phí cuộc gọi càng tăng, và vì người gọi có thể dò ra được nên đừng đưa mật khẩu hay khóa vào
greetingStringCâu đầu tiên bot nói sau thông báo AI. Tối đa 512 byte (khoảng 170 ký tự tiếng Hàn). Nếu là "" thì mô hình tự chào ngắn gọn
voiceStringID giọng nói (danh sách), mặc định kore
languageStringNgôn ngữ bot nói và nghe hiểu, là một trong 9 ID (mặc định ko-KR, danh sách). zh-CN được lưu thành cmn-CN, còn ID chỉ khác nhau ở chữ hoa · chữ thường (ja-jp) được lưu theo dạng trong danh sách. Nếu không phải tiếng Hàn hay tiếng Anh, bot chỉ nhận cuộc gọi sau khi người vận hành đọc chỉ dẫn và lời chào rồi cho phép. Giọng thông báo AI ở đầu cuộc gọi không theo giá trị này mà theo ngôn ngữ của màn hình xác nhận người gọi đã thấy
max_call_durationIntegerĐộ dài tối đa của một cuộc gọi (giây). Từ 60 trở lên, không vượt limits.max_call_duration (giá trị nhỏ hơn giữa mức trần của người vận hành và của máy chủ, mặc định 600). Mặc định của bot là 300
max_concurrent_callsIntegerSố cuộc gọi nhận cùng lúc. Từ 1 trở lên, không vượt limits.max_concurrent_calls (mặc định 2). Mặc định của bot là 2
toolsArrayCông cụ của bot mà mô hình có thể gọi trong cuộc gọi (công cụ). [] là xóa tất cả
transcriptBooleanCông tắc bản ghi hội thoại (bản ghi hội thoại)
mode, stream_url, stream_secretStringChế độ âm thanh gốc (cài đặt chế độ âm thanh gốc)
model, api_keyKhông nhận — VOICE_FIELD_NOT_AVAILABLE. Mô hình do máy chủ quyết định, còn khóa thì chỉ đặt qua setVoiceCallApiKey
  • Quy tắc nội dung: chỉ dẫn, lời chào và mô tả công cụ được kiểm tra lúc lưu và lúc gọi. Bắt bot nói là người (claims_human), bắt bot giấu việc là AI (hides_ai), bảo bỏ qua quy tắc của Pabal (overrides_rules), mạo danh cơ quan hay công ty có thật (impersonation), tự giới thiệu là cơ quan chính thức (official_claim), hỏi số thẻ · số định danh cá nhân · mật khẩu · mã xác thực (asks_secrets), văn bản trông giống khóa bí mật (looks_like_secret), ký tự vô hình (hidden_text) đều bị từ chối với VOICE_CONTENT_REFUSED: <trường>: <mã>. Câu phủ định như "tuyệt đối không hỏi mật khẩu" thì vẫn qua.
  • Quy tắc cơ bản của Pabal luôn đứng trước. Ở mỗi cuộc gọi, mô hình đọc theo thứ tự: quy tắc của Pabal → chỉ dẫn của chủ bot → phần kết của quy tắc Pabal, và chỉ dẫn được đặt trong một khối được đánh dấu là "văn bản của chủ bot". Chỉ dẫn không thể thay thế hay đứng trước quy tắc của Pabal.
  • Có thể thay đổi 30 lần mỗi giờ (tính chung với @BotFather, chỉ đếm những lần thực sự thay đổi). Vượt quá thì nhận 429 Too Many Requests: retry after N và parameters.retry_after.
  • Khi gửi dạng form (-d name=value), tools được đưa vào dưới dạng chuỗi JSON.
  • Bot chỉ đăng nhập bằng MTProto thì gửi cùng JSON đó qua bots.sendCustomRequest (bot MTProto).

Xem cài đặt hiện tại — getVoiceCallSettings

curl -s "https://pabal.me/bot$BOT_TOKEN/getVoiceCallSettings"
{
  "ok": true,
  "result": {
    "enabled": true,
    "available": true,
    "suspended": false,
    "rules_accepted": true,
    "instructions": "Bạn là nhân viên nghe điện thoại của cửa hàng Order Helper. …",
    "greeting": "Order Helper xin nghe. Tôi có thể giúp gì cho bạn?",
    "voice": "kore",
    "language": "ko-KR",
    "max_call_duration": 180,
    "max_concurrent_calls": 2,
    "limits": {"max_call_duration": 600, "max_concurrent_calls": 2, "bot_minutes_per_day": 60, "owner_minutes_per_day": 120},
    "voices": [{"id": "zephyr", "name": "Zephyr", "description": "Bright"}, …],
    "languages": ["ko-KR", "en-US", "ja-JP", "cmn-CN", "fr-FR", "es-US", "id-ID", "vi-VN", "ar-EG"],
    "testers_only": true,
    "mode": "managed",
    "has_stream_secret": false,
    "owner_terms_accepted": false,
    "stream_available": false,
    "updated_date": 1790380712,
    "tools": [],
    "tool_limits": {"max_tools": 16, "max_definition_bytes": 8192, "definition_bytes": 0, "mcp_tools": 0},
    "tool_delivery": "none",
    "transcript": false,
    "transcript_available": false,
    "transcript_delivery": "get_updates",
    "has_api_key": false,
    "own_keys_available": false,
    "mcp_available": false
  }
}
TrườngÝ nghĩa
enabledChủ bot đã bật hay chưa
available, unavailable_reasonHiện bot có nhận được cuộc gọi của bất kỳ ai hay không, và nếu không thì lý do (bảng dưới)
rules_accepted, owner_terms_acceptedĐã đồng ý quy tắc bot thoại, và điều khoản chủ bot hiện hành (phiên bản 4) hay chưa
limitsCác giá trị tối đa có thể đặt trên máy chủ này và giới hạn theo ngày (phút)
voices, languagesGiọng nói và ngôn ngữ có thể chọn
testers_onlytrue nếu chưa công khai nên chỉ người thử nghiệm do người vận hành chọn mới gọi được
tools, tool_limits, tool_delivery, tool_delivery_problemCông cụ và giới hạn của chúng, nơi truy vấn công cụ hiện được gửi tới (webhook·get_updates·mtproto·none) và lý do không gửi được (nhận truy vấn công cụ)
mode, stream_url, stream_secret_fingerprint, has_stream_secret, stream_available, stream_ignoresChế độ âm thanh gốc — bí mật không hiện ra, chỉ có dấu vân tay 8 ký tự
transcript, transcript_available, transcript_unavailable_reason, transcript_delivery, transcript_delivery_problemCông tắc bản ghi hội thoại, và hiện bản ghi có thật sự được chuyển tới hay không
has_api_key, api_key, own_keys_availableKhóa mô hình riêng — khóa không hiện ra, chỉ có nhà cung cấp · dấu vân tay · ngày đặt · có dùng được không
mcp_available, mcp_servers, mcp_unavailable_reasonMáy chủ MCP
outboundTrạng thái cuộc gọi từ bot (Cuộc gọi từ bot — cài đặt và mức dùng)
updated_dateLần thay đổi gần nhất (giây Unix)
unavailable_reasonÝ nghĩa
voice_offTính năng thoại đang tắt trên máy chủ này, hoặc chính sách quyền riêng tư chưa có mục cuộc gọi bot
kill_switchNgười vận hành đã dừng mọi cuộc gọi thoại
bot_voice_offChủ bot chưa bật
not_allowedNgười vận hành chưa cho phép bot này
awaiting_reviewNgười vận hành chưa xem lại chỉ dẫn · lời chào · công cụ đã thay đổi (ở chế độ âm thanh gốc là địa chỉ âm thanh gốc). Nếu ngôn ngữ của bot không phải tiếng Hàn hay tiếng Anh, thì kể cả trên máy chủ nhận mọi bot, người vận hành cũng phải đọc chỉ dẫn và lời chào rồi cho phép
suspendedNgười vận hành đã tạm dừng tính năng thoại của bot này (hoặc của tài khoản chủ bot)
name_refused, text_refusedTên bot, hoặc chỉ dẫn · lời chào · mô tả công cụ đã lưu không còn phù hợp với quy tắc nội dung hiện hành. text_refused cũng xuất hiện khi ngôn ngữ mà bot đã chọn bị gỡ khỏi danh sách và không còn dùng được — hãy chọn lại ngôn ngữ
no_relay, no_gatewayMáy chủ không có bộ chuyển tiếp cuộc gọi hoặc gateway thoại
no_gateway_with_tools, no_own_key_gatewayKhông có gateway nào dùng được công cụ hoặc khóa riêng của bot này
no_gateway_with_languageKhông có gateway nào đang kết nối gọi được bằng ngôn ngữ của bot này (Giọng nói và ngôn ngữ)
own_key_unavailable, stream_unavailableKhóa riêng hay chế độ âm thanh gốc đã đặt hiện không dùng được nên bot không nhận cuộc gọi (không nhận thay bằng khóa của nền tảng)

Giọng nói và ngôn ngữ

Chọn trong 30 giọng dựng sẵn của Gemini Live (mặc định kore). Không có chức năng tải lên hay nhân bản giọng. Nút Giọng nói (목소리) của @BotFather cho phép chọn theo tên và sắc thái.

idSắc tháiidSắc tháiidSắc thái
zephyrTươi sángpuckVui tươicharonRõ ràng
koreRắn rỏifenrirHào hứngledaTrẻ trung
orusRắn rỏiaoedeNhẹ nhàngcallirrhoeThong thả
autonoeTươi sángenceladusNhiều hơiiapetusTrong trẻo
umbrielThong thảalgiebaÊm dịudespinaÊm dịu
erinomeTrong trẻoalgenibKhànrasalgethiRõ ràng
laomedeiaVui tươiachernarMềm mạialnilamRắn rỏi
schedarĐiềm tĩnhgacruxChín chắnpulcherrimaNăng nổ
achirdThân thiệnzubenelgenubiThoải máivindemiatrixÂn cần
sadachbiaSôi nổisadaltagerHiểu biếtsulafatẤm áp

Ngôn ngữ (language) là một trong 9 ngôn ngữ dưới đây. Đây là ngôn ngữ bot nói và nghe hiểu, và bot trả lời bằng ngôn ngữ này. Tại @BotFather, bạn chọn bằng nút Ngôn ngữ (언어) trên màn hình cuộc gọi thoại.

idNgôn ngữLưu ý
ko-KRTiếng HànMặc định
en-USTiếng Anh—
ja-JPTiếng Nhật—
cmn-CNTiếng Trung (Phổ thông)Nằm ngoài danh sách hỗ trợ chính thức của Google nên Google không bảo đảm chất lượng (trong thử nghiệm, cả nghe hiểu lẫn trả lời đều chính xác). Gửi zh-CN cũng được chấp nhận và được lưu thành cmn-CN
fr-FRTiếng Pháp—
es-USTiếng Tây Ban Nha—
id-IDTiếng Indonesia—
vi-VNTiếng Việt—
ar-EGTiếng Ả Rập (chuẩn)Bot nói tiếng Ả Rập chuẩn hiện đại. Mã là ar-EG, mã tiếng Ả Rập duy nhất có trong mô hình thời gian thực của Google, nhưng không cố định phương ngữ — bot chỉ nói bằng một phương ngữ khi chỉ dẫn yêu cầu phương ngữ đó
  • Bot không dùng tiếng Hàn hay tiếng Anh chỉ nhận cuộc gọi sau khi người vận hành đọc chỉ dẫn và lời chào rồi cho phép — kể cả trên máy chủ nhận mọi bot. Lý do là bước kiểm tra quy tắc tự động khi lưu (như giả làm người, mạo danh…) chỉ đọc được tiếng Hàn và tiếng Anh. Trước khi được cho phép, trạng thái là unavailable_reason: "awaiting_review"; sau khi được cho phép, nếu bạn thay đổi chỉ dẫn, lời chào hay công cụ thì bot lại phải chờ được cho phép lại. Ở màn hình chọn ngôn ngữ và sau khi chọn, @BotFather báo "한국어·영어가 아닌 언어는 자동 규칙 검사가 읽지 못해서, 운영자가 지시문·인사말을 읽고 허용한 뒤에 전화를 받아요." (bước kiểm tra quy tắc tự động không đọc được ngôn ngữ ngoài tiếng Hàn và tiếng Anh, nên bot chỉ nhận cuộc gọi sau khi người vận hành đọc chỉ dẫn, lời chào và cho phép). Trong tab Thoại trên trang quản trị của người vận hành, bot đó hiện là "한국어·영어가 아닌 봇 (자동 규칙 검사가 읽지 못함): 지시문·인사말을 읽어 주세요" (bot không dùng tiếng Hàn hay tiếng Anh (kiểm tra quy tắc tự động không đọc được): hãy đọc chỉ dẫn và lời chào).
  • Phải có gateway gọi được bằng ngôn ngữ của bot thì bot mới nhận cuộc gọi. Khi kết nối vào máy chủ, gateway khai báo những ngôn ngữ bot mà nó gọi được (từ 0.7.0 khai báo cả 9), còn gateway không khai báo ngôn ngữ thì được coi là chỉ gọi được tiếng Hàn và tiếng Anh. Nếu không có gateway nào gọi được bằng ngôn ngữ của bot, trạng thái là no_gateway_with_language và nút gọi bị ẩn. Bot không nhận cuộc gọi thay bằng ngôn ngữ khác.
  • ID không phân biệt chữ hoa · chữ thường (ja-jp cũng được nhận) và được lưu theo dạng trong danh sách. Giá trị không có trong danh sách bị từ chối bằng lỗi VOICE_LANGUAGE_INVALID: language is one of ko-KR, en-US, ja-JP, cmn-CN, fr-FR, es-US, id-ID, vi-VN, ar-EG, trong đó liệt kê các ID được chấp nhận.
  • Nếu sau này ngôn ngữ đã chọn bị gỡ khỏi danh sách, bot vẫn giữ nguyên cài đặt nhưng không nhận cuộc gọi (text_refused). Bot không nhận thay bằng ngôn ngữ khác, nên hãy chọn lại ở nút Ngôn ngữ (언어) của @BotFather.
  • Hiện chưa chọn được tiếng Khmer làm ngôn ngữ bot. Mô hình có nghe hiểu tiếng Khmer, nhưng những câu trả lời tự do của nó chưa được người bản ngữ Khmer kiểm tra.

Nếu người gọi cứ nói bằng một ngôn ngữ khác, mô hình sẽ trả lời bằng ngôn ngữ đó. Giọng thông báo AI không theo language của bot mà theo ngôn ngữ của màn hình xác nhận người gọi đã thấy (Những gì người gọi thấy).

Nhận thông báo cuộc gọi — voice_call

Khi có người gọi đến bot, update voice_call sẽ tới. Ba loại update thoại (voice_call, voice_call_tool_query, voice_call_transcript) chỉ được gửi khi bạn yêu cầu bằng cách ghi tên: hãy đưa chúng vào allowed_updates của getUpdates hoặc setWebhook. Nếu danh sách rỗng thì cũng không nhận được chúng (với các loại khác, danh sách rỗng nghĩa là nhận hết).

curl -s "https://pabal.me/bot$BOT_TOKEN/getUpdates" -H 'Content-Type: application/json' \
  -d '{"allowed_updates": ["message", "voice_call", "voice_call_tool_query"], "timeout": 30}'
{
  "update_id": 7,
  "voice_call": {
    "id": "4611686018427387905",
    "direction": "incoming",
    "event": "started",
    "from": {"id": 100002, "is_bot": false, "first_name": "Mai", "language_code": "ko"},
    "date": 1790380803,
    "max_duration": 180
  }
}
{
  "update_id": 9,
  "voice_call": {
    "id": "4611686018427387905",
    "direction": "incoming",
    "event": "ended",
    "from": {"id": 100002, "is_bot": false, "first_name": "Mai", "language_code": "ko"},
    "date": 1790380952,
    "start_date": 1790380803,
    "duration": 149,
    "max_duration": 180,
    "end_reason": "caller"
  }
}
  • direction: "incoming" với cuộc gọi đến bot, "outgoing" với cuộc gọi do bot thực hiện (update của cuộc gọi từ bot). Hiểu trường bị thiếu là cuộc gọi đến bot, như trước đây, vẫn đúng.
  • allowed_updates là một cài đặt duy nhất cho mỗi bot, và giá trị mà getUpdates hoặc setWebhook truyền gần nhất sẽ được giữ lại (nếu không truyền thì giữ nguyên giá trị trước). Vì vậy khi bạn truyền danh sách như ví dụ trên, những loại mặc định không có trong danh sách (edited_message, callback_query) sẽ không còn được gửi nữa — hãy ghi tất cả các loại mà bot dùng. Thông báo về các cuộc gọi xảy ra trước khi bạn yêu cầu sẽ không được giữ lại, nên dù có yêu cầu sau đó cũng không nhận được.
  • Người gọi (from) chỉ gồm ID, tên và ngôn ngữ ứng dụng. Tên người dùng và số điện thoại không được gửi (màn hình xác nhận đã hứa như vậy). Tài khoản đã xóa sẽ có first_name: "Deleted Account".
  • Nếu dùng webhook, chúng được gửi bằng POST theo đúng thứ tự như các update khác. Hàng đợi của getUpdates giữ trong bộ nhớ tối đa 1.000 update gần nhất cho mỗi bot và mất đi khi máy chủ khởi động lại.
  • Bot bị tạm dừng hay đã bị xóa sẽ không nhận được gì.
eventKhi nàoTrường đi kèm
startedKhi ứng dụng của người gọi đã xác nhận cuộc gọi (trao đổi khóa xong) và máy chủ đã báo cho gateway rằng cuộc gọi đã kết nối. Máy chủ không nhìn thấy âm thanh, nên đây là "đã kết nối" theo hiểu biết của máy chủ. Cuộc gọi từ Apple Watch không có trao đổi khóa, nên đó là lúc gateway báo rằng âm thanh của đồng hồ đã kết nốimax_duration
endedKhi một cuộc gọi đã kết nối kết thúc. Nếu phải chờ bản ghi hội thoại thì có thể đến muộn tối đa 10 giây. Trên máy chủ bật tính năng lưu cuộc trò chuyện, mọi cuộc gọi không ở chế độ âm thanh gốc đều đến muộn tối đa 10 giây bất kể người gọi chọn gì — để chủ bot không thể biết lựa chọn của người gọistart_date, duration (số giây đã kết nối theo đồng hồ máy chủ), max_duration, end_reason, (nếu bản ghi hội thoại đang bật) transcript
missedCuộc gọi kết thúc trước khi kết nối (bị ngắt khi đang đổ chuông, v.v.)end_reason
refusedChỉ khi người gọi đã qua mọi bước kiểm tra nhưng không nhận được vì lý do phía bot (cuộc gọi đồng thời, thời lượng trong ngày, gateway) (các trường hợp từ chối khác không báo cho chủ bot)refused_reason: busy (đã đầy số cuộc gọi đồng thời) · budget (hết thời lượng trong ngày của bot · chủ bot · máy chủ) · unavailable (gateway có thể nhận đã đầy)

end_reason: caller (người gọi gác máy) · bot (bot gác máy, endVoiceCall) · max_length (đạt độ dài tối đa) · failed (kết nối thất bại) · operator (người vận hành · công tắc dừng khẩn cấp) · suspended (bot bị tạm dừng · bị xóa).

Với mỗi cuộc gọi đã được giao cho gateway, cuộc trò chuyện giữa người gọi và bot sẽ có một nhật ký cuộc gọi (thời lượng cuộc gọi hoặc "cuộc gọi đã hủy") (cuộc gọi bị từ chối trước khi nhận thì không có). Bản sao phía bot không được gửi dưới dạng update message — nhật ký của bot là voice_call. Nếu người gọi chọn lưu cuộc trò chuyện, văn bản hội thoại được để lại chỉ trong cuộc trò chuyện của người gọi, ngay dưới nhật ký cuộc gọi, còn phía bot không có gì. Trên máy chủ bật tính năng lưu cuộc trò chuyện, 30 số tin nhắn sau nhật ký cuộc gọi được giữ trước, nên số của tin nhắn tiếp theo sau nhật ký lớn hơn số của nhật ký ít nhất 31 (kể cả cuộc gọi không lưu).

Trong cuộc gọi

Gác máy — endVoiceCall

curl -s "https://pabal.me/bot$BOT_TOKEN/endVoiceCall" -d voice_call_id=4611686018427387905
  • farewell (Boolean, mặc định true): nếu cuộc gọi đã kết nối, mô hình chào tạm biệt ngắn rồi gác máy. Nếu sau 10 giây chưa gác thì máy chủ kết thúc. false thì gác ngay. Cuộc gọi ở chế độ âm thanh gốc luôn được gác ngay.
  • voice_call.end_reason của cuộc gọi đã kết thúc là bot. Nếu không phải cuộc gọi đang diễn ra của bot này thì 400 VOICE_CALL_NOT_FOUND.

Gửi ghi chú cho mô hình — sendVoiceCallContext

Cho mô hình biết điều nó cần biết trong cuộc gọi. Mô hình đọc nội dung này như văn bản của chủ bot chứ không phải lời của người gọi, và quy tắc của Pabal vẫn luôn đứng trước.

curl -s "https://pabal.me/bot$BOT_TOKEN/sendVoiceCallContext" -d voice_call_id=4611686018427387905 \
  --data-urlencode "text=Người gọi này vừa hủy đơn hàng A-1042. Nếu họ hỏi thì hãy báo như vậy." -d respond=false
  • text: tối đa 2.000 ký tự (6.144 byte), có kiểm tra theo quy tắc nội dung. Nếu respond=true thì mô hình phản hồi ngay; mặc định false thì dùng vào lượt tiếp theo.
  • Ghi chú cũng được gửi tới mô hình (với cuộc gọi ở chế độ managed là Google), và mô hình có thể nói lại cho người gọi — chỉ ghi những gì có thể cho người gọi biết.
  • Trước khi kết nối: 400 VOICE_CALL_NOT_ACTIVE; cuộc gọi âm thanh gốc hoặc gateway không nhận ghi chú: 400 VOICE_CONTEXT_NOT_SUPPORTED; quá 10 ghi chú mỗi phút cho một cuộc gọi: 429 (retry_after 60).

Mức sử dụng — getVoiceCallUsage

curl -s "https://pabal.me/bot$BOT_TOKEN/getVoiceCallUsage?period=today"
{"ok": true, "result": {"period": "today", "from_date": 1790348400, "to_date": 1790434799,
 "calls": 3, "seconds": 412, "missed": 1,
 "refused": {"busy": 0, "budget": 0, "unavailable": 0},
 "platform_seconds": 412, "own_key_seconds": 0,
 "remaining_seconds_today": {"bot": 3188, "owner": 6788}}}
  • period: today (mặc định) hoặc month. Với month bạn có thể chọn tháng như month=2026-09 (tháng trong tương lai thì VOICE_USAGE_PERIOD_INVALID).
  • Mốc của ngày và tháng là múi giờ của máy chủ (mặc định Asia/Seoul). remaining_seconds_today là giới hạn ngày trừ đi các cuộc gọi đã kết thúc và thời lượng mà các cuộc gọi đang diễn ra đang giữ.
  • Nhật ký sử dụng của từng cuộc gọi (không ghi người gọi) được giữ 180 ngày sau khi tháng đó kết thúc, còn tổng theo tháng thì còn chừng nào bot còn tồn tại.

Công cụ — gọi mã của bot trong cuộc gọi

Khi người gọi hỏi "Đơn A-1042 đang tới đâu rồi?", mô hình gọi công cụ check_order của bot, máy chủ chuyển truy vấn đó cho bot, và mô hình nói lại câu trả lời của bot. Chủ bot định nghĩa công cụ, còn mã của bot trả lời.

Mô hình · gateway Máy chủ Pabal Mã của bot ① check_order {"order_id": "A-1042"} Kiểm tra đối số · bỏ khóa lạ Số thẻ · CCCD: dừng ở đây ② voice_call_tool_query · budget_ms Lặng: "One moment, let me check." >5 s: "Just a moment longer, please." ③ answerVoiceCallToolQuery(result) Kiểm tra · chặn bí mật, số thẻ Tối đa 16.384 byte ④ tool_result (chỉ là dữ liệu) Mô hình nói lại kết quả Muộn: "The tool did not answer in time."
Một truy vấn công cụ: mô hình → máy chủ → bot → máy chủ → mô hình

Giải thích sơ đồ

  • Ba cột: bên trái là mô hình và gateway thoại, ở giữa là máy chủ Pabal, bên phải là mã của chủ bot. Đường chấm dọc là thời gian. Mô hình không nói chuyện trực tiếp với bot; mọi truy vấn và câu trả lời đều đi qua máy chủ.
  • Ô xám ở ① — máy chủ kiểm tra đối số trước: có khớp với parameters của công cụ không, kích thước và độ sâu, các khóa không được khai báo (bị bỏ đi rồi mới chuyển), những giá trị trông giống số thẻ hay số định danh cá nhân (không tới bot, mô hình nghe lời từ chối).
  • Thứ bot nhận ở ② gồm ID truy vấn, ID cuộc gọi, người gọi, tên công cụ, đối số (chuỗi JSON), và budget_ms — số mili giây còn lại cho tới lúc câu trả lời phải tới máy chủ. Trong khi chờ, gateway lấp khoảng lặng (ô bên trái).
  • Câu trả lời ở ③ cũng được kiểm tra: những thứ trông như bí mật, lời lẽ khiến mô hình vi phạm quy tắc, số thẻ hay số định danh cá nhân đều bị từ chối và mô hình nghe thông báo lỗi. Câu trả lời đầu tiên là câu cuối cùng, nên cả câu trả lời bị từ chối cũng đóng truy vấn lại.
  • Ở ④, đường liền màu xanh là bình thường, đường chấm màu đỏ là khi quá hạn. Mô hình nhận kết quả của công cụ chỉ như dữ liệu chứ không phải mệnh lệnh; nếu thất bại, mô hình nghe một câu ngắn của Pabal rồi giải thích tình hình cho người gọi.

Định nghĩa công cụ

curl -s "https://pabal.me/bot$BOT_TOKEN/setVoiceCallSettings" -H 'Content-Type: application/json' -d '{
  "instructions": "Bạn là nhân viên nghe điện thoại của cửa hàng Order Helper. Hãy trả lời ngắn gọn và lịch sự. Khi người gọi đọc mã đơn hàng, hãy dùng công cụ check_order để tra trạng thái giao hàng rồi báo cho họ.",
  "tools": [{
    "name": "check_order",
    "description": "Tra trạng thái giao hàng theo mã đơn hàng. Gọi khi người gọi đọc mã đơn hàng.",
    "parameters": {"type": "object",
                   "properties": {"order_id": {"type": "string", "description": "Mã đơn hàng, ví dụ: A-1042"}},
                   "required": ["order_id"]},
    "timeout_ms": 5000
  }]
}'
MụcQuy tắc
KhóaChỉ bốn khóa: name, description, parameters (tùy chọn), timeout_ms (tùy chọn)
Số lượng · kích thướcCông cụ của bot cộng với công cụ MCP được ghim tổng cộng 16, tổng UTF-8 của định nghĩa (name·description·parameters) 8.192 byte. Bot dùng khóa riêng cũng vậy
name[A-Za-z_][A-Za-z0-9_-]{0,63}. Không được chứa __ (dành cho tên công cụ MCP), không được bắt đầu bằng pabal, và không được trùng nhau khi bỏ qua chữ hoa chữ thường
descriptionBắt buộc, tối đa 1.024 byte, có kiểm tra theo quy tắc nội dung. Mô hình dựa vào đây để quyết định khi nào gọi, nên hãy viết cụ thể
parametersCấp trên cùng là {"type": "object"}. Bỏ đi thì nghĩa là "không có đối số"
timeout_ms1.000–15.000, mặc định 5.000. Thời gian tối đa mô hình chờ câu trả lời

parameters chỉ dùng một phần của JSON Schema mà cả hai engine đều nhận.

  • Chỉ có mười từ khóa: type, description, enum, properties, required, items, minimum, maximum, minItems, maxItems. type chỉ một giá trị (string·number·integer·boolean·array·object).
  • properties·required dành cho object, items·minItems·maxItems cho array (items là bắt buộc), minimum·maximum cho số, và enum chỉ cho string (1–32 giá trị, mỗi giá trị 64 byte).
  • Tên thuộc tính theo [A-Za-z_][A-Za-z0-9_]{0,63}, mỗi công cụ 24 thuộc tính, object tối đa 3 cấp · object và array cộng lại tối đa 5 cấp. Mô tả tham số tối đa 512 byte.
  • Tham số hỏi thông tin bí mật sẽ bị từ chối: nếu tên có các từ như pin·cvv·password·otp·ssn·rrn·cardnumber, hoặc mô tả nói tới số thẻ · mật khẩu · mã xác thực · số định danh cá nhân, thì bị asks_secrets.
  • Lỗi báo vấn đề đầu tiên kèm đường dẫn: VOICE_TOOL_INVALID: tools[0].parameters.properties.tags: items_missing, VOICE_TOOL_INVALID: tools[0].name: reserved, VOICE_CONTENT_REFUSED: tools[0].description: impersonation.
  • Công cụ chỉ định nghĩa được qua Bot API. Màn hình cuộc gọi thoại của @BotFather chỉ hiển thị tên công cụ.
  • Trên máy chủ chỉ nhận bot đã cho phép, khi bạn thêm hay đổi công cụ, bot sẽ không nhận cuộc gọi cho đến khi người vận hành cho phép lại (awaiting_review, bước 4 ở Bật).

Nhận truy vấn công cụ

{
  "update_id": 813,
  "voice_call_tool_query": {
    "id": "8190448532776044545",
    "voice_call_id": "4611686018427387905",
    "from": {"id": 100002, "is_bot": false, "first_name": "Mai", "language_code": "ko"},
    "name": "check_order",
    "arguments": "{\"order_id\":\"A-1042\"}",
    "budget_ms": 4700
  }
}
  • arguments là văn bản của một đối tượng JSON (tối đa 4.096 byte · độ sâu 8, máy chủ đã kiểm tra). Dù vậy, đây là giá trị mô hình tạo ra từ lời người gọi, nên bot hãy kiểm tra lại. Không được để ai chỉ cần biết mã đơn hàng là nghe được đơn của người khác — chỉ báo thông tin thuộc về from.id (người gọi mà Pabal đã xác nhận).
  • budget_ms là số mili giây tính từ lúc bot nhận được cho tới lúc câu trả lời phải tới máy chủ. Đây là timeout_ms của công cụ trừ đi đường về tới mô hình (200 ms) và đường đi tới bot (100 ms), nên thường ít hơn timeout_ms khoảng 300 ms. Không so sánh đồng hồ giữa hai bên.

Đường mà truy vấn tới bot được quyết định theo thứ tự sau (getVoiceCallSettings.tool_delivery).

  1. Nếu có webhook thì qua webhook. allowed_updates của webhook phải có voice_call_tool_query; nếu không có thì truy vấn không chuyển sang đường khác mà sẽ không tới được bot (tool_delivery_problem: "not_in_allowed_updates"). Truy vấn là một POST gửi riêng nên không bị kẹt trong hàng đợi thứ tự, và không được gửi lại.
  2. Nếu không có webhook và bạn đã gọi getUpdates trong 15 giây gần đây, truy vấn nằm ở đầu lần getUpdates tiếp theo. Cũng phải có trong allowed_updates. Mỗi bot tối đa 8 truy vấn chờ.
  3. Ngoài ra, nếu bot đang kết nối qua MTProto thì qua updateBotWebhookJSONQuery (không phụ thuộc allowed_updates).
  4. Nếu không thuộc trường hợp nào, truy vấn không tới được (tool_delivery_problem: "not_listening"), và mô hình nghe "The tool could not be reached." (Không thể kết nối tới công cụ).

Trả lời — answerVoiceCallToolQuery

Tham sốMô tả
tool_query_idvoice_call_tool_query.id của update
resultKết quả. Là văn bản, hoặc đối tượng · mảng nếu là thân JSON (gửi dạng văn bản mà đọc được như đối tượng · mảng JSON thì cũng được xử lý như JSON). Tối đa 16.384 byte UTF-8 (người vận hành có thể hạ thấp hơn)
errorCâu báo thất bại của chính bot, tối đa 512 byte. Mô hình chuyển câu này tới người gọi
  • Đúng một trong hai result và error (không có cả hai thì TOOL_RESULT_EMPTY, có cả hai thì TOOL_RESULT_INVALID). Trả về True.
  • Câu trả lời của công cụ được gửi tới mô hình (với cuộc gọi ở chế độ managed là Google), và mô hình nói nội dung đó cho người gọi — chỉ đưa vào những gì có thể cho người gọi biết.
  • Câu trả lời đầu tiên là câu cuối cùng. Truy vấn đã muộn hoặc đã kết thúc sẽ nhận 400 TOOL_QUERY_EXPIRED: <lý do> — answered (đã trả lời rồi), timeout, cancelled (người gọi ngắt lời nên mô hình rút lại), call_ended, unreachable. ID không xác định hoặc đã quá 10 phút thì TOOL_QUERY_ID_INVALID.
  • Câu trả lời cũng bị kiểm tra: ký tự vô hình bị bỏ, còn những thứ trông như bí mật (looks_like_secret), lời lẽ khiến mô hình vi phạm quy tắc (overrides_rules·hides_ai·claims_human·asks_secrets), số thẻ · số định danh cá nhân (sensitive_data) sẽ bị TOOL_RESULT_REFUSED: <mã>. Dữ liệu của chủ bot như địa chỉ · số điện thoại · URL của cửa hàng thì vẫn được chấp nhận. Vượt kích thước thì TOOL_RESULT_TOO_LONG·TOOL_ERROR_TOO_LONG.
  • Nếu dùng webhook, bạn có thể trả lời ngay trong thân phản hồi — chỉ cần đưa nội dung này vào phản hồi 200 của POST truy vấn công cụ (các phương thức khác không được thực thi). Trước khi trả lời, nhất định phải kiểm tra header X-Telegram-Bot-Api-Secret-Token. Nếu không kiểm tra mà vẫn đưa dữ liệu khách hàng vào phản hồi, bất kỳ ai gửi truy vấn giả tới địa chỉ đó cũng lấy được câu trả lời.
{"method": "answerVoiceCallToolQuery", "tool_query_id": "8190448532776044545",
 "result": {"order_id": "A-1042", "status": "Đang giao", "eta": "Chiều mai sẽ tới"}}

Ví dụ — python-telegram-bot

Đây là bot trả lời công cụ check_order ở trên. Nó nhận qua getUpdates và xử lý truy vấn song song với các update khác (concurrent_updates) — nếu bị chậm vì đang xử lý update trước thì budget_ms sẽ trôi qua mất. Đơn hàng được tra theo ID người dùng Pabal của người đặt, và chỉ báo khi khớp với from.id của truy vấn.

# order_voice_bot.py — pip install "python-telegram-bot==22.*"
# Chạy: read -rs BOT_TOKEN && export BOT_TOKEN
#       CUSTOMER_ID=<ID người dùng Pabal của người đặt hàng> python3 order_voice_bot.py
# Khi thử, đặt CUSTOMER_ID bằng ID của bạn — là message.from.id trong getUpdates sau khi bạn nhắn tin cho bot
import json
import os
import re

from telegram import Update
from telegram.error import TelegramError
from telegram.ext import Application, ContextTypes, TypeHandler

SERVER = "https://pabal.me"
CUSTOMER = int(os.environ.get("CUSTOMER_ID", "0"))
ORDERS = {                                    # bảng nhỏ cho ví dụ: thực tế là hệ thống đơn hàng của cửa hàng
    (CUSTOMER, "A-1042"): {"status": "Đang giao", "eta": "Chiều mai sẽ tới"},
}


def check_order(arguments, caller_id):
    """{"result": …} hoặc {"error": …} để chuyển nguyên cho answerVoiceCallToolQuery"""
    order_id = str(json.loads(arguments).get("order_id", "")).strip().upper()
    if not re.fullmatch(r"[A-Z]-\d{4}", order_id):     # đối số do mô hình tạo — kiểm tra lại
        return {"error": "Tôi chưa nghe rõ mã đơn hàng. Ví dụ: A-1042"}
    found = ORDERS.get((caller_id, order_id))          # chỉ đơn của người đang gọi
    if found is None:                                  # đơn của người khác cũng trả lời như đơn không tồn tại
        return {"error": f"Không tìm thấy đơn hàng {order_id}."}
    return {"result": json.dumps({"order_id": order_id, **found}, ensure_ascii=False)}


async def on_update(update: Update, context: ContextTypes.DEFAULT_TYPE):
    q = update.api_kwargs.get("voice_call_tool_query")  # loại mà PTB không biết nằm trong api_kwargs
    if q is None:
        return
    if q["name"] == "check_order":
        answer = check_order(q["arguments"], q["from"]["id"])
    else:
        answer = {"error": "Tôi không biết công cụ này."}
    try:
        await context.bot.do_api_request("answerVoiceCallToolQuery",
                                         api_kwargs={"tool_query_id": q["id"], **answer})
    except TelegramError as e:                         # TOOL_QUERY_EXPIRED: muộn hoặc cuộc gọi đã kết thúc
        print("Câu trả lời không được chấp nhận:", e, flush=True)


app = (Application.builder().token(os.environ["BOT_TOKEN"])
       .base_url(f"{SERVER}/bot").base_file_url(f"{SERVER}/file/bot")
       .concurrent_updates(True).build())
app.add_handler(TypeHandler(Update, on_update))
app.run_polling(allowed_updates=["message", "voice_call", "voice_call_tool_query"], poll_interval=0, timeout=10)

aiogram bỏ qua các loại update mà nó không biết, nên phải nhận voice_call_tool_query trong update.model_extra trước ở middleware ngoài của dispatcher (dp.update.outer_middleware). Kho mã Pabal có ví dụ (scripts/bots/voice_tools_aiogram.py); nếu cần, hãy hỏi người vận hành máy chủ.

Mô hình và người gọi nghe thấy gì

  • Thành công: kết quả trở thành phản hồi hàm của mô hình (nếu là JSON thì dưới dạng giá trị JSON). Văn bản trong kết quả chỉ được đọc như dữ liệu và không trở thành mệnh lệnh.
  • Thất bại: mô hình nghe một câu ngắn của Pabal bằng ngôn ngữ cuộc gọi (tiếng Hàn hoặc tiếng Anh) rồi giải thích tình hình cho người gọi — quá hạn: "The tool did not answer in time." (Công cụ đã không trả lời kịp), không tới được: "The tool could not be reached." (Không thể kết nối tới công cụ), đối số như số thẻ: "Pabal does not pass on card numbers or national ID numbers." (Pabal không chuyển số thẻ hay số định danh cá nhân), còn error của bot thì nguyên văn câu đó. Với đối số sai, mô hình nhận được gợi ý sai ở đâu nên có thể gọi lại.
  • Trong lúc chờ, gateway lấp khoảng lặng: sau 1,5 giây im lặng thì nói "One moment, let me check." một lần (nếu công cụ gần đây trả lời chậm thì nói ngay), qua 5 giây thì nói "Just a moment longer, please." một lần. Hai câu này được nói bằng ngôn ngữ của bot chứ không phải ngôn ngữ của người gọi; nếu ngôn ngữ đó chưa có bản ghi âm đã rà soát xong thì phát bằng tiếng Anh (hiện mới rà soát xong tiếng Hàn và tiếng Anh).
  • Nếu người gọi chen ngang (ngắt lời), gateway có thể rút lại truy vấn đang mở, và câu trả lời sau đó sẽ nhận TOOL_QUERY_EXPIRED: cancelled.

Giới hạn công cụ

Truy vấn mở cùng lúc: 2 mỗi cuộc gọi, 8 mỗi bot. Số lần gọi: 30 lần mỗi phút cho một cuộc gọi, 120 lần mỗi phút cho một bot. Vượt quá thì truy vấn không tới bot và mô hình nghe ngay too_many_open·rate_limited.

Điều khoản chủ bot

Bản ghi hội thoại, khóa mô hình riêng, chế độ âm thanh gốc, máy chủ MCP và cuộc gọi từ bot chỉ bật được sau khi đồng ý điều khoản chủ bot (phiên bản 4). Việc đồng ý phải do con người thực hiện trên màn hình cuộc gọi thoại của @BotFather (ở bất kỳ màn hình nào trong số bản ghi hội thoại · khóa riêng · chế độ âm thanh gốc · máy chủ MCP · cuộc gọi từ bot) — không làm được bằng token. Dù đã đồng ý phiên bản trước, bạn vẫn sẽ được hỏi lại. Trước khi mục chủ bot được đăng trong điều khoản sử dụng của máy chủ, nút đồng ý sẽ không xuất hiện (gọi trước lúc đó sẽ nhận 403 VOICE_OWNER_TERMS_NOT_ACCEPTED).

BotFather봇 주인 약관을 확인해 주세요. 대화 기록, 자기 모델 키, 원음 모드, MCP 서버, 봇이 거는 전화에 필요해요. 1. 대화 기록을 켜면, 통화가 끝날 때 AI가 적은 대화 글을 내가 받아요. 받은 글은 내가 처리하고 지켜요. 2. 대화 기록이 켜진 봇에 전화하면, 통화가 연결될 때 안내 음성이 대화 글이 봇 주인에게 간다고 먼저 알려요. 3. 받은 대화 글은 필요한 만큼만 두고, 발신자가 지워 달라고 하면 지워요. 모델 학습에 쓰지 않아요. 4. 자기 모델 키를 쓰면, 통화 소리와 글이 내 키의 제공자에게 내 계약으로 가요. 그 처리의 책임은 나에게 있고, 내 개인정보 안내에 그렇게 적어요. 5. 원음 모드에서는 전화 소리가 파발의 AI 모델이 아니라 내 서버로 가요. 그 소리를 받아 처리하는 건 나예요. 전화를 건 사람의 소리와 말한 내용을 법이 허락하는 범위에서만 쓰고, 필요 없으면 지워요. 6. 내 서버가 어디에 있는지, 무엇을 하는지 전화를 건 사람이 물으면 알려요. 봇 소개에 적어 두면 좋아요. 7. 봇은 언제나 AI 라고 밝혀요. 대답은 AI 가 만들어요. 사람이 직접 대답하거나, 사람의 녹음을 AI 대답처럼 틀지 않아요. 원음 모드에서는 파발이 통화 맨 앞에 "AI 음성 봇입니다. 통화 소리는 봇 개발자의 서버로 갑니다." 안내를 틀어요. 8. 카드 번호·주민등록번호·비밀번호·인증 번호를 묻지 않아요. 음성 봇 규칙도 그대로 지켜요. 9. 파발은 대화 글을 저장하지 않아요. 전해지면, 늦어도 24시간 뒤에는 서버에서 지워져요. 키, 원음 모드와 MCP 서버의 비밀은 암호화해 두고 어디에도 기록하지 않아요. 10. MCP 서버를 등록하면, 통화 중 모델이 그 서버의 도구를 부를 때 발신자가 말한 값이 그 서버로 가요. 그 서버와 그 서버가 넘기는 곳의 처리 책임은 나에게 있고, 받는 곳은 사실대로 신고해요. 11. 약관을 어기면 운영자가 음성 통화나 봇을 멈출 수 있어요. 봇이 거는 전화를 켜려면 아래도 지켜요. 12. 봇이 거는 전화는 선언하고 운영자가 허락한 목적(알림·깨우기·배달·예약)에만 써요. 광고·판매 권유·홍보를 하지 않아요. 배달·예약 안내 안에서도 마찬가지예요. 광고성 정보를 보내면 정보통신망법 제50조를 어긴 것이고 그 책임은 나에게 있어요. 파발은 그런 발신을 곧바로 멈추고 발신을 해지해요. 진료·채용·대출처럼 사람의 권리나 의무에 관한 판단을 이 전화로 알리지 않아요. 13. 전화를 건 봇은 첫 말에서 봇 이름과 AI 라는 것을 밝히고, 사람·기관·파발 직원을 사칭하지 않아요. 14. context 에 건강·금융(금액·계좌)·주소 같은 민감한 정보를 넣지 않고, 비밀번호·인증 번호·카드 번호를 묻거나 권하지 않아요. 15. 만 19세 미만인 사람에게 전화하려 하지 않아요. 16. 조용한 시간·쉬는 중·하루 한도를 피하려 하지 않아요(여러 봇·계정으로 나눠 걸기 포함). 사람이 그만 받겠다고 하면 다시 걸지 않아요. 17. 내 개인정보 처리방침을 두고, 통화 내용이 AI 제공자에게 간다는 것을 알려요. 봇이 거는 전화는 종단 간 암호화가 아니에요. 18. 사람에게 보이는 허락 창에 봇 주인인 내 이름이 나와요. 19. 어기면 그 봇의 발신이 멈추고, 음성 기능 전체가 멈출 수 있어요. 20. 발신으로 받는 사람 정보(파발 id·허락했는지·받았는지·통화 시각과 길이·누가 끊었는지)는 선언한 목적에만 쓰고, 남에게 주지 않고, 허락이 꺼지거나 그 사람이 지워 달라고 하면 지워요. 받은 시각으로 사람의 일과나 있는 곳을 짐작하지 않아요. 21. 허락을 서비스의 조건으로 걸거나 속여서 받지 않아요(허락 링크 메시지 포함). 22. 전화마다 적는 목적은 그 전화의 실제 목적이에요. 깨우기 창을 다른 용도로 쓰지 않아요(서버는 목적을 봇이 적은 대로 믿어요). 23. 봇 토큰을 지키고, 샌 것을 알면 곧바로 @BotFather 에서 재발급해요. 토큰이 곧 사람에게 전화할 권한이에요. 24. 봇이 건 전화에는 대화 기록(1항)이 오지 않아요. 봇에게 가는 것은 20항의 것이에요.
약관에 동의하기

BotFather trả lời bằng tiếng Hàn. Nội dung điều khoản ở trên như sau, và bạn đồng ý bằng nút Đồng ý điều khoản (약관에 동의하기):

  1. Nếu bật bản ghi hội thoại, khi cuộc gọi kết thúc tôi nhận được văn bản hội thoại do AI ghi lại. Tôi tự xử lý và bảo vệ văn bản nhận được.
  2. Khi có người gọi tới bot bật bản ghi hội thoại, lúc kết nối sẽ có giọng thông báo trước rằng văn bản hội thoại được gửi tới chủ bot.
  3. Tôi chỉ giữ văn bản hội thoại nhận được trong mức cần thiết, và xóa khi người gọi yêu cầu. Tôi không dùng nó để huấn luyện mô hình.
  4. Nếu dùng khóa mô hình riêng, âm thanh và văn bản cuộc gọi đi tới nhà cung cấp của khóa đó theo hợp đồng của tôi. Tôi chịu trách nhiệm về việc xử lý đó và ghi rõ như vậy trong thông báo quyền riêng tư của tôi.
  5. Ở chế độ âm thanh gốc, âm thanh cuộc gọi đi tới máy chủ của tôi chứ không tới mô hình AI của Pabal. Người nhận và xử lý âm thanh đó là tôi. Tôi chỉ dùng giọng nói và nội dung lời nói của người gọi trong phạm vi pháp luật cho phép, và xóa khi không cần nữa.
  6. Nếu người gọi hỏi máy chủ của tôi ở đâu và làm gì, tôi sẽ cho họ biết. Nên ghi điều này trong phần giới thiệu của bot.
  7. Bot luôn tự nhận là AI. Câu trả lời do AI tạo ra. Không có người trực tiếp trả lời, cũng không phát bản ghi âm của người như thể là câu trả lời của AI. Ở chế độ âm thanh gốc, Pabal phát thông báo "This is an AI voice bot. The call's audio goes to the bot developer's server." ở đầu cuộc gọi.
  8. Tôi không hỏi số thẻ, số định danh cá nhân, mật khẩu hay mã xác thực, và vẫn tuân thủ quy tắc bot thoại.
  9. Pabal không lưu văn bản hội thoại. Sau khi chuyển đi, chậm nhất 24 giờ sau văn bản bị xóa khỏi máy chủ. Khóa và bí mật của chế độ âm thanh gốc và máy chủ MCP được mã hóa và không được ghi ở bất kỳ đâu.
  10. Nếu đăng ký máy chủ MCP, khi mô hình gọi công cụ của máy chủ đó trong cuộc gọi, các giá trị người gọi nói ra sẽ được gửi tới máy chủ đó. Tôi chịu trách nhiệm xử lý của máy chủ đó và của những nơi nó chuyển tiếp tới, và khai báo đúng sự thật về nơi nhận.
  11. Nếu vi phạm điều khoản, người vận hành có thể tạm dừng cuộc gọi thoại hoặc bot.

Mục 12–24 là những gì bạn tuân thủ khi bật cuộc gọi từ bot: chỉ dùng cho các mục đích đã khai báo và được người vận hành cho phép, không quảng cáo; nói tên bot và rằng nó là AI ngay trong câu đầu tiên; không đưa thông tin nhạy cảm vào context; không bao giờ tìm cách gọi cho người dưới 19 tuổi; không né giờ yên tĩnh, tạm dừng hay giới hạn; chỉ dùng thông tin về người nhận cuộc gọi cho các mục đích đó và xóa khi quyền cho phép bị tắt; giữ an toàn cho token. Cuộc gọi do bot thực hiện không mang theo bản ghi hội thoại (mục 24). Câu chữ của điều khoản có thể thay đổi sau rà soát pháp lý.

Máy chủ MCP

Máy chủ MCP mặc định là tắt, và trên pabal.me cũng đang tắt

Chỉ bật khi người vận hành đã chuẩn bị thành phần MCP (pabal-mcp, bộ ký, proxy đi ra) và đăng mục bot-calls-mcp trong chính sách quyền riêng tư cùng mục chủ bot trong điều khoản sử dụng (việc dành riêng một địa chỉ đi ra cho yêu cầu MCP là điều kiện vận hành, mã không kiểm tra). Ngay cả khi đã bật, trước khi ứng dụng hiển thị nơi nhận MCP của bot trên màn hình xác nhận thì chỉ đăng ký được và không cuộc gọi nào mang theo công cụ MCP (mcp_unavailable_reason: "sheet"). Trên máy chủ tắt MCP, setVoiceCallMcpServer trả về 403 VOICE_NOT_AVAILABLE: MCP tools are not available on this server, và @BotFather không có nút "Máy chủ MCP" (MCP 서버).

Khi bạn đăng ký một máy chủ MCP do chủ bot vận hành (hoặc đã ký hợp đồng), mô hình có thể gọi công cụ của máy chủ đó trong cuộc gọi. Bên gọi không phải là mã của bot mà là Pabal — client MCP của Pabal gửi yêu cầu tới máy chủ đó qua proxy đi ra của Pabal và đảm bảo thời hạn · kích thước · kiểm tra. Vì vậy những giá trị người gọi nói ra sẽ được gửi tới máy chủ đó (điều 10 của điều khoản chủ bot).

Máy chủ MCP cần có gì

  • Streamable HTTP, POST (phiên bản giao thức 2024-11-05 trở lên). Chứng chỉ công khai https://, không chuyển hướng, cổng chỉ những cổng máy chủ cho phép (pabal.me là 443).
  • Máy chủ dùng thông tin xác thực phải từ chối đúng cách bằng 401 khi không có thông tin xác thực, và không được chấp nhận thông tin xác thực sai. Một thông tin xác thực chỉ dùng được cho một bot.
  • Tài liệu của nhà vận hành: nhà vận hành máy chủ đặt một tài liệu JSON (tối đa 32KB, không chuyển hướng) tại https://<host>/.well-known/pabal-voice-mcp — chủ thể vận hành · quốc gia · liên hệ, mục đích, loại dữ liệu và nơi đặt, nơi nhận và bên được ủy thác lại, thời gian lưu giữ. Đây là thông tin nơi nhận sẽ hiện trên màn hình xác nhận của người gọi; nếu không có, chủ bot khai báo khi đăng ký (bên dưới). Máy chủ công khai mà ai cũng gọi được không cần xác thực (auth.type: "none") phải ghi "accepts": "any" ở mục điểm cuối của tài liệu này để tuyên bố rằng họ chấp nhận lời gọi từ Pabal.
  • Những gì Pabal gắn thêm: _meta của tools/call có thời hạn còn lại me.pabal/budget_ms (mili giây) và giới hạn kích thước kết quả me.pabal/result_budget_bytes, và có thể có một assertion (khẳng định) do Pabal ký (me.pabal/assertion, JWT). Bên phát hành · sub (bot:<ID bot>) · khóa xác minh (JWKS) của assertion được cho biết qua getVoiceCallMcpBinding.
  • tools/list tối đa 2MB · 20 trang. Không được có bước cần người thao tác (như đăng nhập bằng trình duyệt).
  • Mỗi công cụ cho biết phân loại qua _meta["me.pabal/voice"] — data (none·caller·third_party), content (system·third_party_text), effect (none·write·notify·financial), max_ms (tối đa 15.000), max_result_bytes (tối đa 65.536), tùy chọn p95_ms. Nếu máy chủ không cung cấp, chủ bot ghi đè bằng tools khi đăng ký.
  • Đặc tả 1.0 chỉ cho mức L1: chỉ những công cụ có data: "none" và effect: "none" (công cụ chỉ đọc thông tin công khai) mới được ghim vào cuộc gọi. Công cụ xử lý dữ liệu của người gọi (L2) hoặc thực thi một việc gì đó (L3) chỉ được ghi nhận chứ không được dùng.
  • Khi đăng ký, Pabal chạy các bài kiểm tra tuân thủ (địa chỉ · TLS · phiên bản · 401 · danh sách công cụ · cấu trúc công cụ · phạm vi thông tin xác thực · bước cần người). Nếu trượt thì bị từ chối với MCP_CONFORMANCE_FAILED: CT-xx.

Đăng ký

Bí mật chỉ gửi trong thân yêu cầu (trong chuỗi truy vấn thì 400). Hãy để nó trong tệp rồi gửi, để bí mật không lưu lại trên dòng lệnh.

umask 077
cat > mcp-shop.json <<'EOF'
{"label": "shop",
 "url": "https://mcp.example.com/mcp",
 "auth": {"type": "bearer", "token": "(token do máy chủ MCP cấp)"}}
EOF
curl -s "https://pabal.me/bot$BOT_TOKEN/setVoiceCallMcpServer" -H 'Content-Type: application/json' -d @mcp-shop.json
  • label: [a-z][a-z0-9]{0,11}, không được bắt đầu bằng pabal. Mỗi bot 2 máy chủ. Tên công cụ mà mô hình thấy là label__tên_công_cụ.
  • auth.type: none, bearer (token), headers (tối đa 3 header), client_credentials (token_url, client_id, client_secret, hoặc private_key_jwt: true — trước đó gọi createVoiceCallMcpClientKey). Chưa có authorization_code. none (máy chủ công khai) chỉ đăng ký được khi tài liệu công khai của nhà vận hành máy chủ đó tuyên bố chấp nhận lời gọi từ Pabal (nếu không thì MCP_OPT_IN_REQUIRED).
  • Thay đổi làm đổi nơi nhận (máy chủ mới, địa chỉ hay host của token khác, khai báo mới, mức cao hơn) sẽ nhận câu trả lời 400 MCP_CHANGE_NEEDS_CONFIRMATION, và chỉ được áp dụng khi chủ bot bấm nút Xác nhận (확인 — 이 변경을 적용) hiện ở @BotFather trong vòng 5 phút. Mục đích là để dù token bị lộ, lời người gọi cũng không bị âm thầm gửi tới nơi khác. Việc xoay vòng chỉ đổi bí mật thì có hiệu lực ngay.
  • Nếu nhà vận hành máy chủ không có tài liệu công khai, chủ bot khai báo đúng sự thật nơi nhận (chủ thể vận hành · quốc gia · liên hệ · mục đích · loại dữ liệu · nơi đặt · bên được ủy thác lại · thời gian lưu giữ) bằng declaration. Người gọi thấy thông tin này trên màn hình xác nhận, và khi nó thay đổi thì người gọi được hỏi lại.
  • Đăng ký · kiểm tra: mỗi bot 10 lần mỗi giờ, mỗi chủ bot 20 lần. Các phương thức khác: refreshVoiceCallMcpServer (kiểm tra lại), deleteVoiceCallMcpServer (lúc nào cũng được), getVoiceCallMcpBinding (bên phát hành · sub · JWKS mà máy chủ của bạn dùng để nhận ra lời gọi của Pabal), createVoiceCallMcpClientKey.
  • Trạng thái của máy chủ và công cụ đã đăng ký nằm ở getVoiceCallSettings.mcp_servers (không hiện đường dẫn · truy vấn · bí mật). Toàn văn đặc tả (Pabal Voice MCP Profile 1.0) nằm ở docs/app/voice-bot-mcp-profile.html trong kho mã Pabal; nếu cần, hãy hỏi người vận hành máy chủ.

Khóa mô hình riêng

Khóa riêng hiện chưa dùng được trên bất kỳ máy chủ vận hành nào

Cuộc gọi nhận bằng khóa riêng đòi hỏi người gọi phải thấy màn hình xác nhận (phiên bản 2) nói rằng âm thanh được gửi tới nhà cung cấp của khóa đó, nhưng hiện chưa có ứng dụng nào hiển thị màn hình này. Vì vậy các máy chủ không phải máy chủ thử nghiệm sẽ không nhận khóa cho đến khi người vận hành bật cờ báo ứng dụng đã sẵn sàng (403 VOICE_OWN_KEY_NOT_AVAILABLE). Máy chủ cũng phải có khóa chủ.

Chạy cuộc gọi của bot bằng tài khoản mô hình của chính chủ bot. Phí được tính vào tài khoản nhà cung cấp của chủ bot, và cuộc gọi bằng khóa riêng không dùng giới hạn ngày của bot · chủ bot · máy chủ trên nền tảng (giới hạn của người gọi vẫn áp dụng). Cần có điều khoản chủ bot trước.

curl -s "https://pabal.me/bot$BOT_TOKEN/setVoiceCallApiKey" -d provider=google \
  --data-urlencode "api_key@vertex-sa.json"
  • provider: google là khóa JSON của tài khoản dịch vụ Vertex AI (chỉ giữ các trường cần thiết), openai là khóa API bắt đầu bằng sk-. Giá trị khác thì VOICE_PROVIDER_INVALID. Khóa chỉ gửi trong thân yêu cầu (trong chuỗi truy vấn thì 400) — đọc từ tệp như trên thì không lưu lại trên dòng lệnh. Đừng dán khóa vào cuộc trò chuyện với @BotFather.
  • Trước khi nhận khóa, máy chủ dùng phiên thử của gateway để xem khóa có thật sự mở được không (tối đa 12 giây). Nếu qua thì lưu và trả về True. Nhà cung cấp từ chối thì API_KEY_INVALID, đăng nhập được nhưng không có quyền API thời gian thực thì API_KEY_NO_REALTIME_ACCESS, không có gateway để thử hoặc gateway bận thì 503 API_KEY_CHECK_UNAVAILABLE: …: the key was not kept.
  • Thử: mỗi bot 5 lần mỗi giờ, mỗi chủ bot (tổng mọi bot) 10 lần. Chỉ tính những lần thử đã tới gateway.
  • Khóa không bao giờ hiện ra ở đâu — trong câu trả lời, lỗi, log hay màn hình. getVoiceCallSettings.api_key chỉ có nhà cung cấp · ngày đặt · dấu vân tay (8 ký tự) · có dùng được không.
  • Nếu khóa đã đặt không dùng được (điều khoản thay đổi, máy chủ không mở được khóa, v.v.), bot sẽ không nhận cuộc gọi — không âm thầm đổi sang khóa của nền tảng. Xóa khóa bằng deleteVoiceCallApiKey (lúc nào cũng được) thì bot nhận cuộc gọi bằng khóa của nền tảng, và các cuộc gọi đang chạy bằng khóa đó sẽ kết thúc.
  • Cuộc gọi bằng khóa riêng không có giới hạn ngày của Pabal — chỉ áp dụng độ dài một cuộc gọi, số cuộc gọi đồng thời và giới hạn của người gọi. Phí tính vào tài khoản của bạn, nên hãy đặt cảnh báo ngân sách trên bảng điều khiển của nhà cung cấp.
  • Khóa OpenAI chỉ được dùng khi nhà vận hành gateway bật riêng (mặc định gateway chỉ nhận khóa Google). Nếu không có gateway nào bật, việc thử khóa OpenAI sẽ là 503 … no_gateway. Đường đi của khóa OpenAI chưa được thử với OpenAI thật (chỉ với máy chủ giả), và cuộc gọi nhận bằng khóa OpenAI dùng giọng OpenAI do gateway quy định (mặc định marin) bất kể cài đặt giọng của bot. Khóa Google được dùng ở vùng Vertex của gateway (us-central1).
  • Ở chế độ âm thanh gốc, khóa chỉ được lưu chứ không được dùng.

Bản ghi hội thoại

Bản ghi hội thoại chỉ có trên máy chủ mà người vận hành đã cho phép

Người vận hành phải hoàn tất rà soát pháp lý rồi cho phép trên trang quản trị (mặc định tắt). Trên máy chủ chưa cho phép, dù bạn bật công tắc thì vẫn là transcript_available: false, transcript_unavailable_reason: "not_allowed" và không có văn bản hội thoại nào được gửi tới.

Khi bật, chủ bot nhận văn bản hội thoại của cuộc gọi do AI ghi lại. Hãy đồng ý điều khoản chủ bot rồi bật công tắc.

Bản ghi hội thoại và lưu cuộc trò chuyện là hai việc khác nhau

Bản ghi hội thoại (mục này) do chủ bot bật và chủ bot nhận, còn người gọi nghe giọng thông báo khi kết nối. Lưu cuộc trò chuyện thì do người gọi chọn bằng hộp chọn trên màn hình xác nhận, và sau khi cuộc gọi kết thúc, văn bản chỉ được để lại trong cuộc trò chuyện của chính người đó — không gửi cho bot hay chủ bot, và không có giọng thông báo. Một cuộc gọi có thể có cả hai. Lưu cuộc trò chuyện không có công tắc để chủ bot bật; nó được bật khi chính sách quyền riêng tư của máy chủ có mục bot-calls-keep-text (đang chuẩn bị — Sử dụng ứng dụng — Bot thoại).

curl -s "https://pabal.me/bot$BOT_TOKEN/setVoiceCallSettings" -d transcript=true
  • Cuộc gọi do bot thực hiện không có bản ghi hội thoại (cuộc gọi từ bot) — kể cả khi công tắc đang bật.
  • Khi gọi tới bot bật bản ghi hội thoại, ngay sau thông báo AI, người gọi nghe "What is said on this call is written down and sent to the bot's owner." (Nội dung cuộc gọi này được ghi thành văn bản và gửi cho chủ bot.). Chỉ những lời nói sau khi thông báo kết thúc mới được ghi. Cuộc gọi ở chế độ âm thanh gốc không có bản ghi hội thoại.
  • voice_call của cuộc gọi đã kết thúc (event: "ended") có kèm transcript. Đây là bản đầy đủ duy nhất, nên hãy đưa voice_call vào allowed_updates. Nếu muốn nhận từng lượt trong cuộc gọi, hãy thêm cả voice_call_transcript (chỉ qua HTTP Bot API, không có qua MTProto).
  • Lượt nào chứa số thẻ hay số định danh cá nhân thì mọi chữ số được thay bằng * trước khi gửi. Số được nói tách qua hai lượt hoặc đọc thành chữ thì không bắt được, nên hãy cẩn thận cả với văn bản nhận được.
  • Mỗi lượt 4.096 byte, mỗi cuộc gọi 256KiB · 4.000 lượt. Vượt quá thì phần còn lại bị bỏ và truncated: true.
  • Pabal không lưu văn bản hội thoại: văn bản nằm trong bộ nhớ trong suốt cuộc gọi, được chuyển cho bot một lần, và phần bot chưa lấy sẽ bị xóa sau 24 giờ. Khởi động lại máy chủ thì mất. Việc lưu giữ và xử lý sau khi nhận là trách nhiệm của chủ bot.
"transcript": {
  "turns": [
    {"role": "bot", "text": "Order Helper xin nghe. Tôi có thể giúp gì cho bạn?", "offset_ms": 5200},
    {"role": "caller", "text": "Đơn A-1042 đang tới đâu rồi?", "offset_ms": 9800}
  ],
  "truncated": false
}
{"update_id": 21, "voice_call_transcript": {"voice_call_id": "4611686018427387905", "seq": 2,
 "role": "caller", "text": "Đơn A-1042 đang tới đâu rồi?", "is_final": true, "offset_ms": 9800, "date": 1790380813}}

role là caller (người gọi) hoặc bot (mô hình), offset_ms là số mili giây tính từ lúc âm thanh cuộc gọi bắt đầu.

Chế độ âm thanh gốc

Chế độ âm thanh gốc hiện chưa dùng được

Người gọi chỉ gọi được cho bot âm thanh gốc khi ứng dụng hiển thị màn hình xác nhận âm thanh gốc nói rằng "âm thanh cuộc gọi được gửi tới máy chủ của nhà phát triển bot", nhưng hiện chưa có bản build ứng dụng nào có màn hình đó. Máy chủ cũng phải có khóa chủ, mục bot-calls-stream của chính sách quyền riêng tư, mục chủ bot, và gateway nhận âm thanh gốc. Nếu thiếu, mode=stream sẽ là 403 VOICE_STREAM_NOT_AVAILABLE.

Ở chế độ âm thanh gốc, âm thanh cuộc gọi được gửi tới máy chủ của chủ bot chứ không phải mô hình AI của Pabal. Máy chủ của bạn nghe âm thanh và gửi lại câu trả lời bằng âm thanh — dùng AI nào là do bạn quyết định (mô hình thời gian thực của nhà cung cấp khác, hoặc tổ hợp nhận dạng · tổng hợp giọng nói của riêng bạn). Pabal vẫn phát giọng thông báo AI ở đầu cuộc gọi, và vẫn áp dụng xác nhận của người gọi, các giới hạn, độ dài cuộc gọi và số cuộc gọi đồng thời. Không cần chỉ dẫn, còn công cụ · khóa riêng · bản ghi hội thoại thì chỉ được lưu chứ không được dùng (stream_ignores).

Người gọi Pabal (máy chủ · gateway) Máy chủ wss (chủ bot) ① Xác nhận âm thanh gốc · gọi ② Nâng cấp + chữ ký · pabal.voice.v1 ③ start {call_id, bot_id, format} ④ ready (trong 3 giây) Thông báo AI · trong lúc này tiếng người gọi không tới máy chủ bot ⑤ live Âm thanh (chuyển tiếp) ⑥ PCM16 24 kHz · mảnh 20 ms ⑦ clear · mark · hangup ⑧ stop {reason} → đóng 1000
Chế độ âm thanh gốc: máy chủ của chủ bot thế vào chỗ mô hình

Giải thích sơ đồ

  • Ba cột: bên trái là người gọi, ở giữa là Pabal (máy chủ và gateway thoại), bên phải là máy chủ của chủ bot. Máy chủ bot không nói chuyện trực tiếp với ứng dụng hay với máy chủ Pabal mà chỉ với gateway qua WebSocket.
  • ②–④ xảy ra trong lúc chuông đổ: gateway kết nối TLS tới địa chỉ công khai đã kiểm tra, nâng cấp kèm header chữ ký rồi gửi start. Máy chủ bot phải gửi ready trong vòng 3 giây thì cuộc gọi mới được nhận; nếu không, người gọi nghe tín hiệu máy bận.
  • Ô xám là cửa chắn: trước khi giọng thông báo AI ("This is an AI voice bot. The call's audio goes to the bot developer's server.") phát xong, tiếng người gọi không đi tới máy chủ bot, và âm thanh bot gửi trước cũng không được phát. Ngay khi thông báo kết thúc, ⑤ live được gửi.
  • ⑥ Âm thanh theo cả hai chiều đều là PCM16 · 24 kHz · mono, và phía người gọi luôn là các mảnh 20 ms (960 byte). Mũi tên xanh là thứ Pabal gửi, mũi tên xám là thứ máy chủ bot gửi.
  • ⑦ và ⑧: bot có thể gửi ngắt lời (clear), yêu cầu báo vị trí phát (mark), gác máy (hangup); khi cuộc gọi kết thúc, Pabal gửi stop kèm lý do rồi đóng với mã 1000.

Cài đặt và thăm dò

Trước hết, hãy chạy máy chủ ví dụ bên dưới trên một địa chỉ công khai (tên khớp với chứng chỉ). Thăm dò kết nối tới máy chủ đó ngay khi bạn đặt cài đặt, và cả những lần thất bại vì không tới được cũng bị tính vào giới hạn 5 lần mỗi giờ.

Sau khi đồng ý điều khoản chủ bot (@BotFather → Cuộc gọi thoại (음성 통화) → Chế độ âm thanh gốc (원음 모드) → Đồng ý điều khoản chủ bot (봇 주인 약관에 동의)), hãy báo địa chỉ máy chủ và bí mật. Bí mật là 32–128 ký tự ASCII in được (không có khoảng trắng), và địa chỉ mới luôn phải đi kèm bí mật mới.

umask 077
openssl rand -hex 32 | tr -d '\n' > stream-secret.txt      # cuối tệp không được có ký tự xuống dòng
curl -s "https://pabal.me/bot$BOT_TOKEN/setVoiceCallSettings" -d mode=stream \
  -d stream_url=wss://bot.example.com:8443/voice --data-urlencode "stream_secret@stream-secret.txt"
  1. Định dạng địa chỉ: chỉ wss://, không có tên người dùng · # · khoảng trắng, chỉ ASCII (ký tự khác thì mã hóa phần trăm), tối đa 2.048 ký tự. Host phải là tên có dấu chấm hoặc IP công khai (localhost không được), cổng là 443 hoặc 1024–65535. Nếu không thì 400 STREAM_URL_INVALID: ….
  2. Giới hạn thăm dò được tính ngay lúc yêu cầu: mỗi bot 5 lần mỗi giờ, mỗi chủ bot 10 lần, đồng thời mỗi bot 1 · mỗi chủ bot 2. Vượt quá thì 429. Lần thăm dò nào gateway bận nên không kết nối tới đâu thì không bị tính.
  3. Kiểm tra địa chỉ: nếu tên trỏ tới dù chỉ một địa chỉ riêng · địa chỉ dành riêng hay địa chỉ của chính Pabal thì 400 STREAM_URL_INVALID: the host must resolve only to public addresses.
  4. Thăm dò: gateway thử kết nối một lần (start có "probe": true). Nếu nhận được ready thì chế độ được bật, và bí mật được mã hóa dưới khóa chủ của máy chủ. Nếu thất bại thì 400 STREAM_PROBE_FAILED: <mã> — unreachable (một trong: tên · kết nối · thời gian · TLS), handshake_failed (câu trả lời không phải 101 — thường là từ chối chữ ký), redirect, no_subprotocol, no_ready, bad_ready, address_refused, busy, url v.v. — các mã khác được chuyển nguyên như gateway đưa. Máy chủ chờ tối đa 12 giây.
  5. Nếu chế độ âm thanh gốc được bật hoặc địa chỉ bị đổi qua Bot API (bằng token), @BotFather sẽ gửi cho chủ bot thông báo "địa chỉ âm thanh gốc đã đổi thành … (bí mật …)" (원음 주소가 … 로 바뀌었어요 (비밀 …)) cùng nút Quay về chế độ managed (관리형으로 돌아가기); còn khi quay về chế độ managed thì gửi thông báo đó cùng nút Cuộc gọi thoại (음성 통화). Mục đích là ngăn việc token bị lộ khiến âm thanh bị âm thầm chuyển tới máy chủ khác.
  • Nếu đưa stream_secret vào chuỗi truy vấn thì bị 400 trước mọi thứ khác. Không thể đặt chế độ âm thanh gốc qua phản hồi webhook.
  • Để quay về chế độ managed, dùng mode=managed (xóa địa chỉ và bí mật). Nếu đang bật mà chỉ dẫn trống thì VOICE_INSTRUCTIONS_REQUIRED, nên hãy gửi kèm chỉ dẫn.
  • Trên máy chủ chỉ nhận bot đã cho phép, người vận hành phải xem lại địa chỉ âm thanh gốc và cho phép lại thì bot mới nhận cuộc gọi.
  • getVoiceCallSettings chỉ hiện stream_url và dấu vân tay của bí mật. Với cuộc gọi âm thanh gốc thì sendVoiceCallContext không dùng được (VOICE_CONTEXT_NOT_SUPPORTED), và endVoiceCall gác máy ngay không chào tạm biệt.

Đặc tả

  • TLS 1.2 trở lên, chứng chỉ là chứng chỉ công khai khớp với tên trong URL. Gateway kết nối tới IP đã kiểm tra, nhưng SNI và việc kiểm tra chứng chỉ dùng tên. Không đi theo chuyển hướng (3xx).
  • Phải chọn subprotocol pabal.voice.v1 (Sec-WebSocket-Protocol). Không nén. Một thông điệp tối đa 65.536 byte, thông điệp văn bản tối đa 4.096 byte (UTF-8).
  • Gateway gửi ping mỗi 15 giây. Nếu không có pong trong 15 giây thì ngắt kết nối (hầu hết thư viện tự trả lời).
  • Thông điệp văn bản là sự kiện JSON, thông điệp nhị phân là âm thanh. Chỉ có một định dạng âm thanh: PCM16 little-endian, mono, 24.000 Hz. call_id·bot_id là chuỗi số thập phân.
ChiềuThông điệpKhi nào · ý nghĩa
Pabal → bot{"event":"start","protocol":1,"call_id":"…","bot_id":"…","probe":false,"caller":{"lang":"ko"},"format":{"encoding":"pcm_s16le","sample_rate":24000,"channels":1,"frame_ms":20},"max_seconds":300}Một lần ngay sau khi nâng cấp. Không có tên · số điện thoại · ID của người gọi (chỉ caller.lang — thông tin về người thì qua update voice_call)
Pabal → bot{"event":"live"}Thông báo AI đã kết thúc. Từ đây âm thanh đi hai chiều
Pabal → botNhị phânÂm thanh người gọi, luôn là 960 byte (20 ms). Chỉ sau live
Pabal → bot{"event":"mark","name":"…"}Âm thanh trước mark mà bot đã gửi đã phát xong
Pabal → bot{"event":"cleared","played_ms":N,"dropped_ms":N,"marks":["…"]}Trả lời cho clear
Pabal → bot{"event":"buffer_full","dropped_ms":N}Âm thanh của bot dồn quá 30 giây nên phần tràn bị bỏ
Pabal → bot{"event":"stop","reason":"…","played_ms":N,"duration_ms":N}Cuộc gọi đã kết thúc. Sắp đóng với mã 1000
Bot → Pabal{"event":"ready"}Trong vòng 3 giây, trước mọi thứ khác sau start
Bot → PabalNhị phânÂm thanh của bot: số byte chẵn, 2–65.536 byte. Những gì gửi trước live bị bỏ
Bot → Pabal{"event":"clear"}Ngắt lời: dừng ngay âm thanh của bot đang xếp hàng
Bot → Pabal{"event":"mark","name":"[A-Za-z0-9_.:-]{1,64}"}"Báo cho tôi khi phát tới đây". Mỗi cuộc gọi tối đa 256 mark chờ
Bot → Pabal{"event":"hangup"}Kết thúc cuộc gọi. Phát trước âm thanh đang xếp hàng tối đa 5 giây rồi gác máy
  • event không biết thì bị bỏ qua. Văn bản không phải JSON, văn bản quá 4KiB, âm thanh có số byte lẻ hay quá 64KiB là vi phạm đặc tả và làm kết thúc cuộc gọi (người gọi nghe lời xin lỗi).
  • Mức gửi tối đa: sự kiện văn bản trung bình 50 mỗi giây (tối đa tức thời 100), âm thanh trung bình gấp 4 lần thời gian thực (tối đa tức thời tương đương 60 giây âm thanh). Nếu socket của bot không đọc khiến âm thanh người gọi dồn quá 30 giây (1.500 mảnh), cuộc gọi cũng kết thúc.

Chữ ký

Gateway gắn header này vào yêu cầu nâng cấp.

X-Pabal-Voice-Signature: t=<giây Unix>,nonce=<16 byte hex>,call=<call_id>,v1=<hex>
v1 = HMAC-SHA256(stream_secret, "pabal-voice-stream/1\n<t>\n<nonce>\n<call>\n<host>\n<path?query>")
  • host là host của URL mà bạn đã báo cho Pabal (chữ thường, không có cổng). Không được tính bằng header Host của yêu cầu — giá trị đó do phía gửi viết nên không có tác dụng ràng buộc với host. path?query là đường dẫn và truy vấn của URL nguyên như vậy (nếu không có thì /). Khi thăm dò, call là số của lần thăm dò.
  • Máy chủ bot bắt buộc phải kiểm tra: |hiện tại − t| ≤ 300 giây, mỗi nonce chỉ dùng một lần, và so sánh HMAC theo từng byte bằng hàm có thời gian không đổi. Nếu sai thì trả lời bằng mã khác 101, chẳng hạn 401.
  • Rồi kiểm tra start.call_id có bằng call của chữ ký hay không. Nếu khác thì đóng với 1008 — để yêu cầu nâng cấp đã ký không bị dùng lại cho cuộc gọi khác. Nếu máy chủ chỉ phục vụ một bot, hãy kiểm tra thêm start.bot_id có phải bot của bạn không.
  • Giá trị để thử: bí mật example-stream-secret-of-the-bot-developer-0001, t=1790000000, nonce 00112233445566778899aabbccddeeff, call 4611686018427387905, host bot.example.com, đường dẫn /voice?bot=100042 → v1=807517ff96ac75223bde35b48c775f49a2eda2fb4832a02c0ea832b914d70d0c.

Âm thanh · ngắt lời · mark · kết thúc

  • Bộ đệm bot → người gọi: gateway phát theo tốc độ phát nên bạn có thể gửi một lúc nhiều, nhưng nếu dồn quá 30 giây thì phần tràn bị bỏ và gateway gửi buffer_full. Nếu âm thanh của bot ngắt quãng 300 ms thì coi như một "đoạn" đã kết thúc (đơn vị của mark·clear).
  • clear: bỏ hết âm thanh của bot chưa phát và im lặng từ 10 ms tiếp theo (đo được dưới 50 ms). Trong câu trả lời cleared, played_ms là tổng âm thanh của bot đã thực sự phát trong cuộc gọi này tới lúc đó, dropped_ms là lượng bị bỏ lần này, marks là tên các mark gắn với các đoạn bị bỏ — dùng để cho mô hình biết "bị ngắt ở đâu".
  • mark: khi âm thanh trước nó đã phát xong, mark được gửi lại với cùng tên. Âm thanh trước mark nên kết thúc ở bội số của 480 byte (10 ms) (phần mảnh dư sẽ được lấp bằng im lặng).
  • Kết thúc: bot gửi hangup hoặc đóng đúng cách với 1000·1001. Các kiểu đóng khác hoặc mất kết nối TCP được xử lý như vi phạm đặc tả (người gọi nghe lời xin lỗi). Khi Pabal kết thúc, stop.reason là một mã ngắn — hangup (người gọi gác máy), max_length, bot_ended, disconnect, drain, kill_switch, operator, link_lost, media_failed … Giá trị không biết thì hãy xử lý như "đã kết thúc".
  • start của thăm dò có "probe": true, caller rỗng, max_seconds: 0. Khi bạn gửi ready, {"event":"stop","reason":"probe"} sẽ tới rồi kết nối đóng lại. Khi thăm dò, đừng mở những thứ tốn phí như phiên mô hình.

Ví dụ — máy chủ âm thanh gốc nhỏ nhất

Đây là máy chủ kiểm tra toàn bộ đặc tả mà không cần AI: phát tiếng chuông khi nhận live, rồi trả lại nguyên tiếng người gọi (echo). Nó kiểm tra đủ chữ ký · thời điểm · nonce · cuộc gọi · bot. Trước hết hãy dùng nó để thấy cuộc gọi chạy trọn vẹn, rồi đặt AI của bạn vào chỗ echo. Kho mã Pabal còn có scripts/bots/stream_echo_bot.py (có minh họa ngắt lời) và scripts/bots/stream_openai_relay.mjs (chuyển tiếp tới Realtime bằng khóa OpenAI của bạn, ví dụ chưa được chạy thử); nếu cần, hãy hỏi người vận hành máy chủ.

# stream_bot.py — pip install "websockets>=13"
# Chạy: STREAM_SECRET_FILE=stream-secret.txt TLS_CERT=fullchain.pem TLS_KEY=privkey.pem \
#       PUBLIC_HOST=bot.example.com BOT_ID=<ID bot — dãy số trước dấu : của token> python3 stream_bot.py
import asyncio
import hashlib
import hmac
import json
import math
import os
import ssl
import struct
import time

from websockets.asyncio.server import serve

SECRET = open(os.environ["STREAM_SECRET_FILE"], "rb").read().strip()
PUBLIC_HOST = os.environ["PUBLIC_HOST"].lower()   # host của stream_url đã báo cho Pabal (không phải header Host)
BOT_ID = os.environ.get("BOT_ID", "")             # để trống thì nhận cuộc gọi của bot nào cũng được
PATH = os.environ.get("STREAM_PATH", "/voice")
PORT = int(os.environ.get("PORT", "8443"))
CHIME = b"".join(struct.pack("<h", int(8000 * math.sin(2 * math.pi * 880 * i / 24000)))
                 for i in range(24000 * 6 // 10))  # 880 Hz trong 0,6 giây, PCM16 24 kHz
seen = {}                                         # nonce đã dùng


def signed_call(header, target):
    """Kiểm tra X-Pabal-Voice-Signature và trả về call id đã ký. Sai thì trả về None."""
    try:
        parts = dict(p.split("=", 1) for p in header.split(","))
        t, nonce, call, v1 = int(parts["t"]), parts["nonce"], parts["call"], parts["v1"]
    except (KeyError, ValueError):
        return None
    now = time.time()
    if abs(now - t) > 300 or nonce in seen:
        return None
    text = f"pabal-voice-stream/1\n{t}\n{nonce}\n{call}\n{PUBLIC_HOST}\n{target}"
    expected = hmac.new(SECRET, text.encode("utf-8", "surrogateescape"), hashlib.sha256).hexdigest()
    if not hmac.compare_digest(expected.encode(), v1.encode("utf-8", "surrogateescape")):  # theo từng byte
        return None
    for old in [n for n, at in seen.items() if at < now - 600]:
        del seen[old]
    seen[nonce] = now
    return call


def check(connection, request):
    if request.path.split("?", 1)[0] != PATH:
        return connection.respond(404, "not here\n")
    call = signed_call(request.headers.get("X-Pabal-Voice-Signature", ""), request.path)
    if call is None:
        return connection.respond(401, "bad signature\n")
    connection.signed_call = call                 # start phải nói về đúng cuộc gọi này
    return None


async def handle(ws):
    if ws.subprotocol != "pabal.voice.v1":
        await ws.close(1002, "pabal.voice.v1 only")
        return
    start = json.loads(await asyncio.wait_for(ws.recv(), 5))
    if (start.get("event") != "start" or start.get("call_id") != ws.signed_call
            or (BOT_ID and start.get("bot_id") != BOT_ID)):
        await ws.close(1008, "not this call")
        return
    await ws.send(json.dumps({"event": "ready"}))    # trong vòng 3 giây, trước mọi thứ khác
    live = False
    async for message in ws:
        if isinstance(message, bytes):
            if live:
                await ws.send(message)            # trả lại nguyên 20 ms của người gọi (echo)
            continue
        event = json.loads(message)
        if event.get("event") == "live" and not start.get("probe"):   # thông báo AI đã xong
            live = True
            await ws.send(CHIME)
            await ws.send(json.dumps({"event": "mark", "name": "chime"}))
        elif event.get("event") == "stop":
            print("Kết thúc:", start["call_id"], event.get("reason"), flush=True)
            break


async def main():
    tls = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
    tls.minimum_version = ssl.TLSVersion.TLSv1_2
    tls.load_cert_chain(os.environ["TLS_CERT"], os.environ["TLS_KEY"])
    async with serve(handle, "0.0.0.0", PORT, ssl=tls, subprotocols=["pabal.voice.v1"],
                     process_request=check, max_size=65536, compression=None):
        print(f"Đang lắng nghe: wss://{PUBLIC_HOST}:{PORT}{PATH}", flush=True)
        await asyncio.get_running_loop().create_future()


asyncio.run(main())

Những gì người gọi thấy

  • Nút gọi: phía trên cuộc trò chuyện và trong hồ sơ của bot thoại xuất hiện nút gọi. Nút chỉ hiện khi máy chủ xác định rằng cuộc gọi của người đó · trên thiết bị đó có thể được nhận (trước khi công khai thì chỉ với người thử nghiệm). Không có cuộc gọi video.
  • Màn hình xác nhận (trước cả quyền micro): tiêu đề "An AI answers this call" (AI sẽ trả lời cuộc gọi này); bot không phải người thật và giọng nói cũng do AI tạo; không mã hóa đầu cuối; nơi nhận âm thanh (Google, Gemini Live API của Vertex AI) cùng quốc gia · vùng (United States (Iowa, Google Cloud us-central1)); những gì được gửi (giọng nói trong cuộc gọi, văn bản hội thoại và câu trả lời của AI, ghi chú mà bot đưa vào trong cuộc gọi và kết quả chức năng của bot); việc lưu giữ (Pabal không lưu âm thanh, chỉ giữ nhật ký cuộc gọi 180 ngày; Google không giữ sau khi tạo câu trả lời, nhưng nội dung mà bước kiểm tra tự động nghi là lạm dụng có thể được ghi lại tối đa 90 ngày); những gì chủ bot nhận (tên · số tài khoản · ngôn ngữ, thời điểm · độ dài cuộc gọi, giá trị dùng cho công cụ — không có số điện thoại và âm thanh); từ 19 tuổi trở lên. Các nút là "I'm 19 or older · Call" và hủy. Tuổi không phải là xác minh danh tính mà là tự xác nhận. Nút gọi là nút chính (tô màu nhấn), còn hủy là nút phụ. Trên máy chủ bật tính năng lưu cuộc trò chuyện, hộp chọn "Keep this call's conversation as text in my chat" (lưu cuộc trò chuyện của cuộc gọi này dưới dạng văn bản trong cuộc trò chuyện của tôi; mặc định tắt) được thêm sau phần giải thích.
  • Hỏi bao lâu một lần: trên máy tính · Android thì hỏi lần đầu bạn gọi bot đó (hỏi lại khi nơi nhận hoặc phiên bản màn hình thay đổi), trên iPhone và Apple Watch thì hỏi mỗi lần. Đồng hồ mỗi lần đều hiện toàn bộ màn hình, và giá trị ban đầu của công tắc lưu cuộc trò chuyện là lựa chọn ở cuộc gọi gần nhất tới bot đó. Ứng dụng gửi sự đồng ý lên máy chủ ở mỗi cuộc gọi, và máy chủ dùng một sự đồng ý của cùng thiết bị · cùng bot · trong vòng 2 phút cho cuộc gọi đó đúng một lần.
  • Khi kết nối: giọng thông báo AI "You're now talking to an AI voice bot." (với bot âm thanh gốc là "This is an AI voice bot. The call's audio goes to the bot developer's server.") → (nếu là bot có bản ghi hội thoại) thông báo bản ghi hội thoại → lời chào của bot. Chủ bot không tắt được các thông báo này, và lời người gọi nói trước khi thông báo kết thúc không được gửi tới mô hình. Lưu cuộc trò chuyện không có giọng thông báo — vì người gọi đã tự chọn trên màn hình.
  • Ngôn ngữ của thông báo: giọng thông báo được phát bằng ngôn ngữ của màn hình xác nhận mà người gọi đã đọc, chứ không theo language của bot. Mọi thông báo trong một cuộc gọi đều dùng cùng một ngôn ngữ — thông báo AI, thông báo bản ghi hội thoại, lời xin lỗi khi mất kết nối (với bot âm thanh gốc là thông báo âm thanh gốc và lời xin lỗi). Chỉ cần một trong số đó chưa có bản ghi âm đã rà soát xong bằng ngôn ngữ ấy thì tất cả đều phát bằng tiếng Anh. Hiện chỉ tiếng Hàn và tiếng Anh là đã rà soát xong. Màn hình xác nhận và thông báo của 8 ngôn ngữ còn lại (tiếng Nhật, tiếng Trung, tiếng Pháp, tiếng Tây Ban Nha, tiếng Indonesia, tiếng Việt, tiếng Khmer, tiếng Ả Rập) là bản nháp đang chờ rà soát pháp lý và người bản ngữ kiểm tra, nên hiện nay người gọi dùng ứng dụng bằng các ngôn ngữ đó vẫn thấy màn hình xác nhận tiếng Anh và nghe thông báo tiếng Anh. Bản ghi âm tiếng Khmer và câu tiếng Nhật tương ứng với "Just a moment longer, please." vẫn đang chờ người nghe thử và chọn. Trước thông báo đầu tiên của cuộc gọi và trước thông báo bản ghi hội thoại luôn có ít nhất 300ms im lặng — để đường truyền điện thoại không nuốt mất từ đầu tiên.
  • Màn hình cuộc gọi: dưới tên là "@{username} · AI · Bot" và "AI-generated voice · Call audio is processed by Google". Không có khóa emoji và dòng chữ về mã hóa.
  • Nếu kết nối gặp sự cố, người gọi nghe "Sorry, we've lost the connection. Ending the call now." rồi cuộc gọi bị ngắt. Mọi trường hợp từ chối đều kết thúc bằng tín hiệu máy bận.

Phần dành cho người dùng ứng dụng nằm ở Sử dụng ứng dụng — gọi cho bot thoại.

Cuộc gọi từ bot

Đang tắt trên pabal.me — chờ rà soát pháp lý

Máy chủ đã có tính năng này, nhưng không bot nào gọi được cho ai cho tới khi mục bot-calls-outbound trong chính sách quyền riêng tư và mục voice-bot-owners-outbound trong điều khoản sử dụng qua rà soát pháp lý và được đăng. Cũng chưa có bản ứng dụng nào có màn hình cho phép (Tình trạng hiện tại). Phần dưới đây mô tả cách hoạt động trên máy chủ đã bật tính năng này.

Bot thoại cũng có thể chủ động gọi cho một người: để xác nhận lịch hẹn, đánh thức vào giờ đã chọn, hay báo tin giao hàng. Mã của chủ bot yêu cầu bằng startVoiceCall, máy chủ xét yêu cầu rồi đổ chuông thiết bị của người đó; nếu họ nghe máy, thông báo của Pabal phát trước, sau đó mô hình giọng nói AI nói theo chỉ dẫn của bot. Cuộc gọi kết thúc thế nào sẽ tới trong update voice_call.

  • Chỉ gọi cho người đã cho phép. Người đó phải cho phép bot gọi cho mình trong ứng dụng Pabal (Quyền cho phép của người nhận cuộc gọi) và có thể tắt bất cứ lúc nào.
  • Không phải cuộc gọi quảng cáo. Có bốn mục đích — nhắc việc, đánh thức, giao hàng, lịch hẹn — và không được quảng cáo, chào bán hay khuyến mãi (điều khoản chủ bot, mục 12–24).
  • Cuộc gọi từ bot không được mã hóa đầu cuối — âm thanh đi qua máy chủ Pabal và nhà cung cấp AI. Khi người đó nghe máy, âm thanh được gửi tới Google trong lúc gọi — Gemini Live API của Vertex AI, Iowa, Hoa Kỳ (Google Cloud us-central1). Pabal không lưu âm thanh.
  • Chủ bot không nhận được văn bản cuộc trò chuyện. Cuộc gọi do bot thực hiện không có bản ghi hội thoại. Thứ chủ bot nhận được là năm điều ở Điều người nhận cuộc gọi thấy và nghe.

Luồng hoạt động

Mã của bot Máy chủ Pabal Gateway · mô hình Ứng dụng người nhận ① startVoiceCall Xét duyệt: bot · cho phép · giới hạn giờ yên tĩnh · tạm dừng ② 200 {voice_call_id} ③ dial ④ Chuông: tên (AI) ⑤ Nghe máy ⑥ Thông báo Pabal ⑦ Bot: tên · AI · mục đích Cuộc gọi — âm thanh đi qua gateway và mô hình AI ⑧ voice_call: started · ended Không nghe · không đổ chuông: not_answered
Một cuộc gọi từ bot: yêu cầu → xét duyệt → đổ chuông → nghe máy → thông báo → câu đầu tiên → kết thúc

Giải thích sơ đồ

  • Bốn làn: từ trái sang, mã của chủ bot, máy chủ Pabal, gateway thoại cùng mô hình AI, và ứng dụng của người nhận cuộc gọi. Các đường chấm dọc là thời gian; đường xanh liền là cuộc gọi đang tiến triển, đường xám là câu trả lời cho bot.
  • Ô xám ở ① — ba lần xét duyệt: trước khi trả lời, phía bot (đã bật chưa, người vận hành xét duyệt, điều khoản, giới hạn) và có quyền cho phép hay không; sau 200, phía người nhận (giờ yên tĩnh, đang tạm dừng, giới hạn trong ngày và khoảng cách của người đó, thiết bị để đổ chuông); và thêm một lần ngay trước khi đổ chuông.
  • 200 ở ② chỉ có nghĩa là "đã nhận". Có đổ chuông không, có nghe máy không, chỉ update ở ⑧ cho biết.
  • ④–⑦, phía người nhận: chuông hiện "(AI)" sau tên bot, và khi nghe máy, thông báo của Pabal (⑥) phát trước bot. Trong câu đầu tiên (⑦), bot nói tên mình, rằng mình là AI và mục đích cuộc gọi. Trong suốt ô xanh lá, âm thanh đi qua gateway và mô hình AI — không được mã hóa đầu cuối.
  • Đường chấm đỏ là điểm mấu chốt: một yêu cầu chưa từng đổ chuông vì phía người nhận và một yêu cầu đổ chuông mà không ai nghe kết thúc giống nhau, cùng lúc, bằng not_answered. Bot không thể biết người đó lúc ấy đang ngủ hay đang trong cuộc gọi khác.

Bật — @BotFather

Cuộc gọi từ bot chỉ do chủ bot bật trong @BotFather, không bao giờ bằng token (gửi các trường gọi đi tới setVoiceCallSettings sẽ nhận 400 VOICE_FIELD_NOT_AVAILABLE). Cuộc gọi thoại phải được bật trước. /mybots → bot → Cuộc gọi thoại (음성 통화) → Cuộc gọi từ bot (봇이 거는 전화) → Bật (켜기).

켜기
BotFather@order_helper_bot 이 사람에게 전화를 걸 수 있게 할까요? • 이 봇에게 전화를 허락한 사람에게만 걸어요. 허락은 그 사람이 파발 앱에서 하고, 언제든 끌 수 있어요. • 그 사람의 조용한 시간과 쉬는 중에는 울리지 않아요. • 하루 상한(지금 50번)과 서버의 한도가 있어요. • 운영자가 이 봇의 이름·사진·지시문·목소리·목적을 검토하고 허락해야 걸려요. 무엇이든 바뀌면 다시 검토해요. • 봇이 거는 전화는 종단 간 암호화가 아니에요. 통화 소리는 파발 서버와 AI 제공자를 거쳐요.
켜기
BotFather봇이 거는 전화를 켰어요. 운영자가 이 봇을 검토하고 허락하면 Bot API 의 startVoiceCall 로 걸 수 있어요. 봇이 거는 전화 · @order_helper_bot 상태: 켜짐 운영자 허락: 대기 목적: 배달 하루 상한: 50번 오늘: 요청 0 · 받음 0 · 안 받음 0 이 봇에게 전화를 허락한 사람에게만, 그 사람의 조용한 시간과 쉬는 중을 피해 걸어요. 봇이 거는 전화는 종단 간 암호화가 아니에요 — 통화 소리는 파발 서버와 AI 제공자를 거쳐요.

BotFather trả lời bằng tiếng Hàn. Tin nhắn đầu hỏi có cho bot gọi cho mọi người không: chỉ người đã cho phép, không bao giờ trong giờ yên tĩnh của họ hay khi đang tạm dừng, trong mức trần mỗi ngày (hiện là 50) và giới hạn của máy chủ, và chỉ sau khi người vận hành xét duyệt và cho phép tên, ảnh, chỉ dẫn, giọng nói và mục đích của bot (và xét lại mỗi khi có thứ thay đổi); cuộc gọi từ bot không được mã hóa đầu cuối. Tin nhắn thứ hai là màn hình của bot sau khi bật: đã bật, chờ người vận hành, mục đích giao hàng, mức trần 50, số yêu cầu hôm nay.

  • Điều khoản phiên bản 4: nếu bạn chưa đồng ý, điều khoản chủ bot sẽ hiện ra trước khi bật. Mục 12–24 là cam kết dành cho cuộc gọi từ bot.
  • Mục đích: chỉ bật được khi chọn ít nhất một trong reminder (nhắc việc) · wake_up (đánh thức) · delivery (giao hàng) · appointment (lịch hẹn). Mỗi yêu cầu ghi một trong số đó; mục đích bot không chọn sẽ nhận 400. Đổi mục đích thì người đã cho phép sẽ được hỏi lại và người vận hành xét duyệt lại.
  • Mức trần mỗi ngày: số yêu cầu bot được gửi mỗi ngày — 10, 20 hoặc 50 (mặc định 50).
  • Người vận hành xét duyệt: người vận hành xem tên, @tên người dùng, ảnh, chỉ dẫn, lời chào, công cụ, giọng nói, ngôn ngữ, chế độ, mục đích và tên chủ bot trên trang quản trị rồi cho phép. Nếu bất kỳ thứ nào thay đổi, bot không gọi được cho tới khi được cho phép lại (403 VOICE_OUTBOUND_OFF: review).
  • Token: đổi cài đặt thoại bằng token (trừ việc tắt thoại) sẽ đóng băng cuộc gọi từ bot trong 24 giờ (frozen). Nếu chính bạn đổi, hãy gỡ bằng Tôi đã đổi (내가 바꿨어요, vdk·vdK) trong @BotFather. Cấp lại token cũng đóng băng 24 giờ, kết thúc các cuộc gọi đang diễn ra và xóa webhook của bot đang bật gọi đi.
  • Chỉ bot dùng tiếng Hàn hoặc tiếng Anh (ko-KR·en-US) mới gọi được. Bot ở chế độ âm thanh gốc, dùng khóa riêng, công cụ hay máy chủ MCP thì không (mode).

Quyền cho phép của người nhận cuộc gọi

Bot không tự lấy được quyền cho phép. Người đó mở màn hình cho phép của bot trong ứng dụng Pabal và chạm "I am 19 or older · Allow" (Tôi từ 19 tuổi trở lên · Cho phép). Màn hình cho biết AI là bên gọi, mục đích, tên chủ bot, âm thanh đi đâu và giờ yên tĩnh (Sử dụng ứng dụng — Cho phép bot gọi cho bạn). Có ba cách mở.

  • Menu của bot: "Allow calls…" (Cho phép gọi…) trong cuộc trò chuyện hoặc hồ sơ của bot.
  • Liên kết trong trò chuyện 1:1: bot gửi https://pabal.me/<tên người dùng của bot>?callme=1 vào cuộc trò chuyện 1:1 với người đó, dưới dạng liên kết trong văn bản hoặc nút URL nội tuyến. Màn hình chỉ mở với tin nhắn do chính bot đó gửi trong cuộc trò chuyện đó — liên kết chuyển tiếp sang cuộc trò chuyện khác, bản xem trước hay trang web chỉ mở cuộc trò chuyện. Mỗi người chỉ mở màn hình từ liên kết được một lần mỗi ngày (tính chung mọi bot), và không mở được nếu chưa từng nhắn cho bot.
  • Danh sách trong cài đặt "Bots allowed to call me" (Bot được phép gọi cho tôi): xem các bot đã cho phép, tắt và cho phép lại.
curl -s "https://pabal.me/bot$BOT_TOKEN/sendMessage" -d chat_id=$USER_ID \
  --data-urlencode "text=Bạn có muốn chúng tôi gọi trước khi đơn hàng tới không?" \
  --data-urlencode 'reply_markup={"inline_keyboard": [[{"text": "Cho phép gọi", "url": "https://pabal.me/order_helper_bot?callme=1"}]]}'
  • Máy chủ tự vận hành: ứng dụng chỉ biết liên kết pabal.me. Với máy chủ khác, phải thêm host của máy chủ đó vào quy tắc liên kết của bản ứng dụng thì liên kết mới mở được màn hình (menu và danh sách cài đặt vẫn dùng như cũ).
  • Hiện tại: chỉ tài khoản số +82, trên thiết bị có múi giờ Hàn Quốc (Seoul) và đã đăng nhập ít nhất 24 giờ, mới bật được quyền cho phép (người vận hành quyết định quốc gia). Người trả lời dưới 19 tuổi sẽ không gọi với bot được trong một năm.
  • Khi tên, chủ hoặc mục đích của bot thay đổi, hoặc một năm sau khi cho phép, người đó sẽ được hỏi lại; trong lúc đó bot không gọi được.

Gọi — startVoiceCall

curl -s "https://pabal.me/bot$BOT_TOKEN/startVoiceCall" -d user_id=$USER_ID -d purpose=delivery \
  --data-urlencode "context=Đơn A-1042 sẽ tới hôm nay khoảng 15 giờ. Hãy hỏi xem lúc đó họ có nhận được không." \
  -d max_duration=180 -d ring_timeout=30
{"ok": true, "result": {"voice_call_id": "8812345678901234"}}
Tham sốÝ nghĩa
user_idID của người cần gọi, người này phải đã cho phép bot
purposeMột trong các mục đích bot đã chọn: reminder · wake_up · delivery · appointment
contextThông tin về cuộc gọi chỉ dành cho mô hình: 1–1.000 ký tự, 3.072 byte. Được kiểm tra theo quy tắc nội dung (mạo danh, che giấu AI, hỏi bí mật, quảng cáo). Mô hình dùng nó sau câu đầu tiên (tên, AI, mục đích), khi người đó đã trả lời
max_durationThời lượng tối đa sau khi nghe máy (giây): từ 30 tới giá trị nhỏ hơn giữa 300 và mức trần của bot. Mặc định 180
ring_timeoutThời gian đổ chuông (giây), 30–45. Mặc định 40
Phản hồiÝ nghĩaCần làm
200 {"voice_call_id": "…"}Đã nhận. Có đổ chuông, có nghe máy hay không thì tới qua update—
400 Bad Request: <nội dung>Tham số sai, mục đích bot không chọn (purpose is not declared), ngôn ngữ bot không phải tiếng Hàn hay tiếng AnhSửa lại
400 VOICE_CONTEXT_REFUSED: <mã>context vi phạm quy tắc nội dungSửa lại
403 VOICE_OUTBOUND_OFF: <lý do>Phía bot: owner_off (chưa bật) · terms (điều khoản phiên bản 4) · review (chờ người vận hành xét duyệt) · suspended (tự động dừng) · frozen (đóng băng do token) · owner_name (tên chủ bot không qua kiểm tra mạo danh) · mode, hoặc lý do phía bot từ cài đặt thoại (unavailable_reason): bot_voice_off · not_allowed · suspended · name_refused · text_refused · awaiting_review · stream_unavailable · own_key_unavailable · no_gateway · no_gateway_with_tools · no_gateway_with_language · no_own_key_gateway. Các lý do phía máy chủ voice_off · kill_switch · no_relay · outbound_off đến dưới dạng 503, không phải 403@BotFather · người vận hành
403 USER_UNAVAILABLEKhông gọi được cho người này: chưa cho phép, đã tắt hoặc cần cho phép lại, đã chặn, không phải người, ngoài phạm vi, dưới 19 tuổi, không phải người thử nghiệm. Nội dung phản hồi luôn giống nhau, bất kể lý doXin quyền cho phép
429 retry after NGiới hạn phía bot hoặc chủ bot: tốc độ yêu cầu, số yêu cầu mỗi ngày của bot hoặc chủ bot, số cuộc gọi đồng thời của bot, số phút gọi đi của chủ bot, thời lượng gọi mỗi ngày của bot hoặc chủ bot (dùng chung với cuộc gọi đến bot)Sau N giây
503 VOICE_UNAVAILABLEPhía máy chủ: công tắc gọi đi đang tắt, thoại đang tắt trên máy chủ (kể cả khi chính sách quyền riêng tư chưa có mục cuộc gọi bot), công tắc dừng khẩn cấp, không có máy chủ chuyển tiếp cuộc gọi, giới hạn và phần gọi đi của máy chủ, không có gateway gọi đi được (hoặc tất cả đều đầy), không ghi được yêu cầuĐể sau
{"ok": false, "error_code": 403, "description": "Forbidden: USER_UNAVAILABLE"}

Phía người nhận được xét sau 200. Dù có quyền cho phép, nếu đang trong giờ yên tĩnh của người đó, đang tạm dừng, vượt giới hạn trong ngày với người đó hoặc giới hạn trong ngày và khoảng cách của chính họ, không có thiết bị để đổ chuông, hay họ đang trong cuộc gọi khác, thì không có gì đổ chuông và not_answered tới khoảng ring_timeout + 12 giây sau yêu cầu — giống hệt chuông đổ mà không ai nghe. Quyền cho phép không thay đổi phía bot (403, 429, 503).

Cài đặt và mức dùng

"outbound": {
  "enabled": true,
  "purposes": ["delivery"],
  "daily_cap": 50,
  "reviewed": true,
  "frozen_until": 0,
  "suspended": false,
  "available": true
}
"outbound": {
  "today": {"requests": 4, "answered": 2, "not_answered": 2, "minutes": 3},
  "limits": {"daily": 50, "owner_daily": 100, "owner_minutes": 30}
}
  • outbound trong getVoiceCallSettings luôn có: những gì đặt trong @BotFather (enabled·purposes·daily_cap), việc người vận hành xét duyệt (reviewed), lúc hết đóng băng do token (frozen_until, 0 nếu không bị đóng băng), tự động dừng (suspended) và bot có gọi được ngay bây giờ không (available). unavailable_reason chỉ có khi available là false (phía bot thì cùng giá trị với lý do của 403 VOICE_OUTBOUND_OFF; phía máy chủ thì voice_off · kill_switch · no_relay · outbound_off — bốn giá trị này tương ứng với 503 VOICE_UNAVAILABLE).
  • outbound trong getVoiceCallUsage chỉ có ở bot từng bật gọi đi: số yêu cầu, đã nghe, không nghe và số phút hôm nay, cùng các giới hạn trong ngày áp dụng cho bot này. Không cho biết bao nhiêu lần đã đổ chuông. calls·seconds·missed·refused không bao giờ tính cuộc gọi do bot thực hiện. Nhưng cuộc gọi do bot thực hiện dùng chung thời lượng gọi mỗi ngày của bot và chủ bot với cuộc gọi đến bot: số giây của cuộc gọi đã nghe bị tính, nên bot và owner trong remaining_seconds_today giảm — trong lúc đổ chuông cũng đã giảm theo phần cuộc gọi giữ trước, và phần đó được trả lại nếu không ai nghe.
  • setVoiceCallSettings không đổi được chúng (400 VOICE_FIELD_NOT_AVAILABLE).

Update

Tới dưới dạng update voice_call giống như cuộc gọi đến bot (ghi voice_call trong allowed_updates). direction là "outgoing", và người nhận chỉ xuất hiện bằng ID trong to (không có tên, tên người dùng hay số điện thoại).

{
  "update_id": 83,
  "voice_call": {
    "id": "8812345678901234",
    "direction": "outgoing",
    "event": "not_answered",
    "to": {"id": 100005},
    "purpose": "delivery",
    "date": 1790000123
  }
}
{
  "update_id": 81,
  "voice_call": {
    "id": "8812345678901234",
    "direction": "outgoing",
    "event": "started",
    "to": {"id": 100005},
    "purpose": "delivery",
    "date": 1790000130,
    "max_duration": 180
  }
}
{
  "update_id": 82,
  "voice_call": {
    "id": "8812345678901234",
    "direction": "outgoing",
    "event": "ended",
    "to": {"id": 100005},
    "purpose": "delivery",
    "date": 1790000250,
    "start_date": 1790000130,
    "duration": 120,
    "max_duration": 180,
    "end_reason": "callee"
  }
}
{
  "update_id": 84,
  "voice_call": {
    "id": "8812345678901234",
    "direction": "outgoing",
    "event": "failed",
    "to": {"id": 100005},
    "purpose": "delivery",
    "date": 1790000110
  }
}
eventKhi nàoTrường thêm
startedNgười đó nghe máy và cuộc gọi được kết nối. Trên Apple Watch là khi âm thanh của đồng hồ kết nối (nếu không kết nối được thì là ended 0 giây với failed)max_duration
endedCuộc gọi đã nghe kết thúc. Đến muộn tối đa 10 giây bất kể cài đặt của máy chủ, vì máy chủ chờ mức sử dụng từ gatewaystart_date, duration, max_duration, end_reason
not_answeredYêu cầu không ai nghe — không phân biệt đổ chuông mà không nghe, bị từ chối, hay chưa từng đổ chuông vì phía người nhận. Khoảng ring_timeout + 12 giây sau yêu cầuKhông có
failedMất kết nối với gateway đang giữ yêu cầu trước khi có người ngheKhông có

end_reason: callee (người nhận cúp máy) · bot (bot cúp máy, hoặc token được cấp lại) · max_length (thời lượng tối đa) · failed (kết nối hỏng sau khi nghe) · operator (người vận hành hoặc công tắc dừng khẩn cấp) · suspended (bot hoặc chủ bot bị tạm dừng). Cuộc gọi do bot thực hiện không có missed, refused hay ringing — tốc độ nghe máy sẽ để lộ người đó có ở cạnh điện thoại không — và không có transcript (tắt theo thiết kế).

Trong cuộc gọi

  • endVoiceCall: trước khi bot được báo kết quả, rút lại yêu cầu dù đang đổ chuông hay chưa (true, sau đó là not_answered). Kết thúc cuộc gọi đã nghe (end_reason bot). Rút lại trong vòng 15 giây sau yêu cầu sẽ khiến cặp đó tạm dừng một giờ.
  • sendVoiceCallContext: chỉ sau khi người đó nghe máy (trước đó là VOICE_CALL_NOT_FOUND). Ba lần mỗi cuộc gọi, 1.000 ký tự và 3.072 byte, cùng quy tắc nội dung với context. Nếu gateway không nhận ghi chú thì là VOICE_CONTEXT_NOT_SUPPORTED.

Điều người nhận cuộc gọi thấy và nghe

  • Chuông: "(AI)" sau tên bot, và "AI voice bot · Pabal never asks for codes" (bot thoại AI · Pabal không bao giờ hỏi mã).
  • Khi nghe máy, một thông báo phát trước bot: "This is a Pabal AI voice call. We never ask for passwords, codes or money transfers. To stop these calls, turn off call permission in the bot's chat." (Đây là cuộc gọi thoại AI của Pabal. Chúng tôi không bao giờ hỏi mật khẩu, mã hay chuyển tiền. Để dừng các cuộc gọi này, hãy tắt quyền cho phép gọi trong cuộc trò chuyện với bot.) — bằng ngôn ngữ của màn hình cho phép người đó đã đọc (tiếng Hàn hoặc tiếng Anh); từ ứng dụng bằng ngôn ngữ khác thì bằng tiếng Anh. Chủ bot không tắt được.
  • Câu đầu tiên của bot: tên bot, rằng nó là AI, và mục đích cuộc gọi. context được dùng sau đó.
  • Cách tắt: "Stop calls from this bot" (Dừng cuộc gọi từ bot này) trong menu, chặn bot hoặc báo cáo bot luôn tắt được. Nói rõ trong cuộc gọi kiểu "đừng bao giờ gọi cho tôi nữa" sẽ tắt ngay tại chỗ (chỉ nói "dừng lại" thì tùy mô hình phán đoán).
  • Tạm dừng: sau khi từ chối, để chuông đổ mà không nghe, hoặc cúp máy trong vòng 10 giây sau khi nghe, bot không gọi được cho người đó trong 24 giờ.
  • Ghi nhận cuộc gọi nhỡ: khi một thiết bị thật sự đã đổ chuông, người đó không chặn bot và cuộc trò chuyện với bot không có tin nhắn chưa đọc, sẽ còn lại "Missed AI bot call" (cuộc gọi nhỡ từ bot AI — đã đọc, không thông báo). Nếu có tin nhắn chưa đọc, hoặc không đổ chuông vì giờ yên tĩnh hay tạm dừng, thì không có ghi nhận. Mọi yêu cầu của bot đều được lưu đầy đủ trong bằng chứng báo cáo (30 ngày).
  • Điều chủ bot nhận được (theo thứ tự trên màn hình cho phép):
    1. ID Pabal của người đó
    2. quyền cho phép có đang bật không (biết được qua phản hồi cho yêu cầu gọi)
    3. người đó có nghe máy không
    4. thời điểm bắt đầu và độ dài cuộc gọi
    5. ai cúp máy
    Cuộc gọi nhỡ và cuộc gọi bị từ chối trông như nhau. Nội dung cuộc gọi không được gửi đi.
  • Thiết bị dùng chung: bất kỳ thiết bị nào đang đăng nhập đều nghe được, và bot nói mục đích cùng nội dung được dặn với bất kỳ ai nghe máy. Với máy tính dùng chung, hãy nhắc mọi người đăng xuất.
  • Màn hình khóa: trên Android 14 trở lên, nếu người đó không cho phép thông báo toàn màn hình, cuộc gọi tới dưới dạng thông báo thay vì màn hình cuộc gọi trên màn hình khóa. iPhone và Apple Watch đổ chuông như cuộc gọi điện thoại.
  • Lưu cuộc trò chuyện chỉ lưu vào cuộc trò chuyện của chính người đó, nếu họ chọn trên màn hình cho phép. Khi bật Nghe trên đồng hồ trong ứng dụng Pabal cho Apple Watch, Apple Watch cũng đổ chuông.
  • Cuộc gọi từ bot không được mã hóa đầu cuối — âm thanh đi qua máy chủ Pabal và nhà cung cấp AI.

Giới hạn · giờ yên tĩnh · tạm dừng · tự động dừng

Giới hạnMặc địnhTrên máy chủ vận hành
Một bot gọi một người (mỗi ngày)3 cuộcNgười vận hành chỉ có thể hạ xuống
Gọi một người (mỗi ngày, mọi bot)6 cuộcChỉ có thể hạ xuống
Khoảng cách giữa các cuộc gọi tới một người10 phútChỉ có thể tăng lên
Yêu cầu của một bot (mỗi ngày)50 cuộcChủ bot chọn 10, 20 hoặc 50 trong @BotFather
Một chủ bot (mỗi ngày, mọi bot)100 cuộc · 30 phútSố cuộc chỉ có thể hạ xuống
Tới khi bot được gửi yêu cầu tiếp theo10 giâyChỉ có thể tăng lên
Số phút gọi đi của máy chủ30% số phút mỗi ngày của máy chủChỉ có thể hạ xuống
Giờ yên tĩnh21:00–08:00Người vận hành và người nhận chỉ có thể mở rộng. Theo múi giờ của thiết bị
Khung giờ đánh thức (chỉ wake_up)tối đa 120 phútNgười nhận chọn trong giờ yên tĩnh của mình
Chuông (ring_timeout)30–45 giây40 giây nếu yêu cầu không ghi
Tạm dừng — từ chối, chuông đổ mà không nghe, cúp máy trong vòng 10 giây sau khi nghe24 giờChỉ có thể tăng lên
Tạm dừng — bot rút lại yêu cầu trong vòng 15 giây1 giờChỉ có thể tăng lên
  • "Mỗi ngày" là một ngày theo múi giờ của máy chủ (TELEGRAM_VOICE_TIME_ZONE). Một cuộc gọi không bao giờ dài quá 300 giây.
  • Cuộc gọi từ bot cũng dùng chung thời lượng gọi mỗi ngày với cuộc gọi đến bot (bot 60 phút · chủ bot 120 phút). Khi dùng hết, startVoiceCall nhận 429, và cuộc gọi đến bot đó cũng bị từ chối (refused, budget).
  • Tạm dừng kết thúc khi hết thời gian, khi người đó gọi cho bot, chạm "I can take calls now" (Giờ tôi nghe được rồi) hoặc cho phép lại. Tin nhắn hay nút bấm của bot không kết thúc được.
  • Tự động dừng chỉ dừng cuộc gọi của bot tới mọi người (403 VOICE_OUTBOUND_OFF: suspended) và kéo dài cho tới khi người vận hành kiểm tra và gỡ: tỷ lệ nghe máy dưới 20% trong 20 cuộc gần nhất, 3 báo cáo trong 7 ngày từ những người bot đã đổ chuông, từ 30% trở lên số người tắt quyền cho phép (khi có ít nhất 10 người), 2 cuộc gọi vi phạm quy tắc trong lúc gọi, 5 lần rút lại trong vòng 15 giây, hoặc chỉ 2 người trở xuống nghe máy mà số phút đã nghe từ 60 phút trở lên.

Giới hạn

Giới hạnMặc địnhAi quyết định
Độ dài tối đa một cuộc gọiBot 300 giây (phạm vi có thể đặt: 60 tới mức trần của người vận hành · máy chủ, mặc định 600)Chủ bot, trong mức trần của người vận hành · máy chủ. Nút ở @BotFather là 1 · 3 · 5 · 10 phút
Cuộc gọi đồng thời của bot2 (1 tới mức trần của người vận hành · máy chủ, mặc định 2)Chủ bot, trong mức trần của người vận hành · máy chủ
Thời lượng gọi mỗi ngày của một bot60 phútNgười vận hành
Mỗi ngày của một chủ bot (cộng mọi bot)120 phútNgười vận hành
Mỗi ngày của toàn máy chủ300 phútNgười vận hành
Một người gọiMỗi ngày 60 phút, mỗi giờ 20 cuộcNgười vận hành
Cuộc gọi ngắn nhất được nhận60 giây — nếu thời gian còn lại ít hơn thì không nhậnNgười vận hành
Tài khoản mớiNgười đăng ký chưa đủ 7 ngày thì không gọi được — ngoại lệ: người thử nghiệm, và người mà bot đã từng nhắn tin từ hơn một ngày trướcNgười vận hành
Thay đổi cài đặtMỗi bot 30 lần mỗi giờ—
Ghi chú trong cuộc gọiMỗi cuộc gọi 10 ghi chú mỗi phút—
Công cụ16 công cụ · 8KB; truy vấn mở: 2 mỗi cuộc gọi · 8 mỗi bot; mỗi phút 30 mỗi cuộc gọi · 120 mỗi bot—
Thử khóa riêng · thăm dò âm thanh gốcMỗi bot 5 lần mỗi giờ, mỗi chủ bot 10 lần—
Đăng ký · kiểm tra MCPMỗi bot 10 lần mỗi giờ, mỗi chủ bot 20 lần, mỗi bot 2 máy chủ—

Từ Apple Watch không gọi được bot dùng công cụ · máy chủ MCP · khóa riêng · chế độ âm thanh gốc · bản ghi hội thoại của chủ bot. Lời từ chối này chỉ đến với người gọi (403 WATCH_NOT_YET của ứng dụng đồng hồ), còn bot không nhận update refused.

Độ dài của một cuộc gọi là giá trị nhỏ nhất trong thời gian còn lại hôm nay của người gọi · bot · chủ bot · máy chủ và mức trần của cuộc gọi. Khi nhận cuộc gọi, khoảng đó được giữ trước ở cả bốn giới hạn, và khi kết thúc chỉ số giây thực tế theo đồng hồ máy chủ bị tính. Cuộc gọi bằng khóa riêng không dùng giới hạn của bot · chủ bot · máy chủ (giới hạn của người gọi vẫn áp dụng).

Lỗi

Lỗi có cùng dạng với các phương thức khác: {"ok": false, "error_code": 400, "description": "Bad Request: VOICE_…"}. 403 có dạng Forbidden: <mã>: <mô tả> (trừ USER_UNAVAILABLE, luôn đúng là Forbidden: USER_UNAVAILABLE, không có mô tả), còn 429 kèm parameters.retry_after.

MãÝ nghĩa
403 VOICE_RULES_NOT_ACCEPTEDPhải đồng ý quy tắc bot thoại tại @BotFather trước
403 VOICE_NOT_AVAILABLETrên máy chủ này, bot này không dùng được thoại (thoại đang tắt · người vận hành chưa cho phép) — không thay đổi được cài đặt, chỉ tắt và xóa được
403 VOICE_SUSPENDEDNgười vận hành đã tạm dừng thoại của bot này. Chỉ tắt được
403 VOICE_OWNER_TERMS_NOT_ACCEPTEDPhải đồng ý điều khoản chủ bot (phiên bản 4) trước
403 VOICE_OWN_KEY_NOT_AVAILABLEMáy chủ này không nhận khóa riêng
403 VOICE_STREAM_NOT_AVAILABLETrên máy chủ này hiện không dùng được chế độ âm thanh gốc
403 VOICE_OUTBOUND_OFF: <lý do>Bot này hiện không gọi được cho mọi người: owner_off · terms · review · suspended · frozen · owner_name · mode, hoặc lý do phía bot từ cài đặt thoại (Gọi). voice_off · kill_switch · no_relay · outbound_off là 503
403 USER_UNAVAILABLEKhông gọi được cho người này — nội dung luôn giống nhau bất kể lý do, kể cả khi chưa được cho phép
503 VOICE_UNAVAILABLEMáy chủ hiện không gọi được cho mọi người (công tắc gọi đi, thoại tắt trên máy chủ, công tắc dừng khẩn cấp, không có chuyển tiếp cuộc gọi, giới hạn máy chủ, gateway, không ghi được yêu cầu) — hãy thử lại sau
Bad Request: purpose is not declared, Bad Request: the bot's voice language must be ko or en for outbound callsstartVoiceCall: mục đích bot không chọn, ngôn ngữ bot không phải tiếng Hàn hay tiếng Anh
VOICE_INSTRUCTIONS_REQUIRED, VOICE_INSTRUCTIONS_TOO_LONG, VOICE_GREETING_TOO_LONGCần có chỉ dẫn · chỉ dẫn vượt 8.192 byte · lời chào vượt 512 byte
VOICE_INVALID, VOICE_LANGUAGE_INVALID, VOICE_LIMIT_INVALID, VOICE_MODE_INVALIDGiọng · ngôn ngữ không tồn tại · giới hạn ngoài phạm vi · chế độ không phải managed/stream
VOICE_CONTENT_REFUSED: <trường>: <mã>, VOICE_CONTEXT_REFUSED: <mã>Quy tắc nội dung (cài đặt, context của startVoiceCall)
VOICE_TOOL_INVALID: <đường dẫn>: <lý do>Định nghĩa công cụ (định nghĩa công cụ)
VOICE_FIELD_NOT_AVAILABLE: …Trường không được nhận (model, api_key), trường âm thanh gốc trên bot ở chế độ managed, trường của cuộc gọi từ bot (outbound·purposes·daily_cap — chỉ trong @BotFather)
VOICE_CALL_NOT_FOUND, VOICE_CALL_NOT_ACTIVE, VOICE_CONTEXT_NOT_SUPPORTED, VOICE_CONTEXT_TOO_LONGPhương thức trong cuộc gọi (trong cuộc gọi)
VOICE_USAGE_PERIOD_INVALIDKhoảng thời gian của getVoiceCallUsage
TOOL_QUERY_ID_INVALID, TOOL_QUERY_EXPIRED: …, TOOL_RESULT_…, TOOL_ERROR_TOO_LONGCâu trả lời của công cụ (trả lời)
API_KEY_INVALID, API_KEY_NO_REALTIME_ACCESS, 503 API_KEY_CHECK_UNAVAILABLE, VOICE_PROVIDER_INVALIDKhóa riêng (khóa mô hình riêng)
STREAM_URL_INVALID, STREAM_SECRET_REQUIRED, STREAM_SECRET_INVALID, STREAM_PROBE_FAILED: …Chế độ âm thanh gốc (cài đặt và thăm dò)
MCP_…, MCP_OPT_IN_REQUIREDMáy chủ MCP (đăng ký). 503 VOICE_NOT_AVAILABLE: mcp: … thì thử lại sau một lúc, 409 MCP_CHECK_IN_PROGRESS thì thử lại sau khi kiểm tra xong
429 Too Many Requests: retry after NGiới hạn theo giờ (cài đặt · thử khóa · thăm dò · MCP), giới hạn ghi chú, hoặc giới hạn của cuộc gọi từ bot (giới hạn)

Bot kết nối qua MTProto

  • Bot chỉ đăng nhập bằng MTProto gọi các phương thức thoại qua bots.sendCustomRequest (tên phương thức và JSON như nhau). Dùng được: setVoiceCallSettings, getVoiceCallSettings, answerVoiceCallToolQuery, endVoiceCall, sendVoiceCallContext, getVoiceCallUsage, startVoiceCall, và năm phương thức MCP. Các phương thức khác thì METHOD_INVALID. Phản hồi của startVoiceCall là {"voice_call_id": "…"}, còn từ chối tới dưới dạng lỗi RPC (USER_UNAVAILABLE, VOICE_OUTBOUND_OFF: review, FLOOD_WAIT_n, VOICE_UNAVAILABLE).
  • Phương thức không gọi được từ phản hồi webhook: setVoiceCallApiKey, startVoiceCall, và các phương thức đăng ký và khóa MCP. Đặt trong nội dung phản hồi cho POST của webhook thì chúng không được chạy — để không tin nhắn nào của một người biến thẳng thành cuộc gọi điện thoại qua phản hồi webhook. Hãy gọi chúng bằng yêu cầu riêng.
  • Khóa riêng chỉ qua HTTPS: setVoiceCallApiKey·deleteVoiceCallApiKey không gọi được qua sendCustomRequest. Muốn xóa khóa, hãy dùng HTTP Bot API hoặc @BotFather.
  • Với bot không có webhook và không gọi getUpdates trong 15 giây gần đây, voice_call (kể cả của cuộc gọi do bot thực hiện) được đẩy một lần qua updateBotWebhookJSON (không phụ thuộc allowed_updates, không gửi lại). Truy vấn công cụ tới qua updateBotWebhookJSONQuery, và trả lời bằng {"result": …} hoặc {"error": "…"} qua bots.answerWebhookJSONQuery. voice_call_transcript không tới qua MTProto (còn transcript trong update kết thúc thì có).
  • Với lỗi RPC, 429 là FLOOD_WAIT_n, còn lại là mã chữ hoa nguyên như vậy.