Files
go-sip/docs/thirds/v0.5-proposal.md
T

447 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
SaaS Dispatcher 对接文档 v0.5
相关占位说明:
`/internal/v1/dispatcher/xxxxx` 路由按系统当前架构路由进行定义即可
`X-DISPATCHER-ID`/`X-DISPATCHER-SECRET-KEY` HEADER 头KEY 获取忽略大小写
Dispatcher 角色下称 D
触发顺序
1.1 MQ 发布消费规则
RabbitMQ 有发布入口 exchange → 发布时指定的 routing key → 预先绑定的 queue → D 消费四步;
```Plain Text
SaaS→D exchange: agent-call.dispatchers.v1
routing key: d.<dispatcher_id>.t.<tenant_id>.in
binding key: d.<dispatcher_id>.t.<tenant_id>.in
queue: agent-call.d.<dispatcher_id>.t.<tenant_id>.v1
consumer: 对应 Dispatcher
D→SaaS exchange: agent-call.saas.v1
routing key: d.<dispatcher_id>.t.<tenant_id>.out
queue: agent-call.saas.events.v1
consumer: SaaS
```
1.2 本轮任务队列与事件路由
硬边界:所有 exchange/queue/binding 均由 SaaS 创建、维护和退役;D 只消费 SaaS 创建的任务/控制队列,并向 SaaS 创建的结果 exchange 发布,不声明、绑定或删除队列。
```Plain Text
SaaS 创建并绑定:
exchange: agent-call.dispatchers.v1 (topic, durable)
task routing: d.<dispatcher_id>.task.<task_id>.in
task queue: agent-call.d.<dispatcher_id>.task.<task_id>.v1
control route: d.<dispatcher_id>.control.in
control queue: agent-call.d.<dispatcher_id>.control.v1
dead-letter: agent-call.dead-letter.v1
D -> SaaS exchange: agent-call.saas.v1 (topic, durable)
result route: d.<dispatcher_id>.out
SaaS result queue: agent-call.saas.d.<dispatcher_id>.v1
```
HTTP请求
SIP列表配置
```HTTP
GET /internal/v1/dispatcher/sip HTTP/1.1
Host: <SaaS 服务地址>
X-DISPATCHER-id: c046b893-8628-4589-ae50-619d049248a6
X-DISPATCHER-SECRET-KEY: <密钥>
```
响应:
```JSON
{
"resource": "sip_config", // 资源类型:SIP 配置
"dispatcher_id": "c046b893-8628-4589-ae50-619d049248a6", // 这份配置所属的 Dispatcher
"trunks": [ // 该 Dispatcher 获批的线路列表
{
"trunk_id": "trunk-mock", // 线路的唯一标识
"provider_id": "provider-mock", // SIP 服务商标识
"codec": "PCMA", // 线路使用的语音编码
"dial_prefix": "", // 该线路拨号时添加的被叫前缀;空串表示不添加
"enabled": true, // 是否启用这条线路
"server_host": "sip.example.invalid", // SIP 服务端地址;此处是 Mock 地址
"server_port": 5060, // SIP 服务端端口
"transport": null, // 传输方式:udp、tcp、tls;null 表示尚未确认
"auth_mode": null, // 鉴权方式:ip、digest、none;null 表示尚未确认
"registration_required": null, // 是否需要 SIP 注册;null 表示尚未确认
"max_concurrent_calls": null, // 分配给该 Dispatcher 的线路并发上限;null 表示尚未确认
"caller_profiles": [ // 这条线路可用的主叫配置
{
"caller_profile_id": "caller-profile-mock", // 主叫配置标识,供任务引用
"caller_id": "BD00000000" // 实际主叫标识,保留原值及字母
}
],
"schedule": { // 这条线路允许外呼的每周时段
"time_zone": "Asia/Shanghai", // 判断时段使用的时区
"weekly_windows": { // 每天可配置多个时段;空数组表示当天不允许外呼
"monday": [{"start": "09:00", "end": "20:00"}], // 周一
"tuesday": [{"start": "09:00", "end": "20:00"}], // 周二
"wednesday": [{"start": "09:00", "end": "20:00"}], // 周三
"thursday": [{"start": "09:00", "end": "20:00"}], // 周四
"friday": [{"start": "09:00", "end": "20:00"}], // 周五
"saturday": [], // 周六不允许外呼
"sunday": [] // 周日不允许外呼
} }
} ]
}
```
AI 服务商全量读取
`GET /internal/v1/dispatcher/ai-providers`
```JSON
{
"resource": "ai_providers",
"dispatcher_id": "c046b893-8628-4589-ae50-619d049248a6",
"providers": [
{
"provider_ref": "asr-provider-a",
"role": "asr",
"enabled": true,
"adapter": "volcengine_asr",
"endpoint": "https://asr.example.invalid",
"credential": "managed-asr-a"
},
{
"provider_ref": "llm-provider-a",
"role": "llm",
"enabled": true,
"adapter": "openai_compatible",
"endpoint": "https://llm.example.invalid/v1",
"credential": "managed-llm-a"
},
{
"provider_ref": "tts-provider-a",
"role": "tts",
"enabled": true,
"adapter": "volcengine_tts",
"endpoint": "https://tts.example.invalid",
"credential": "managed-tts-a"
}
]
}
```
任务配置
```HTTP
GET /internal/v1/dispatcher/task/{task_id} HTTP/1.1
Host: <SaaS 服务地址>
X-DISPATCHER-id: c046b893-8628-4589-ae50-619d049248a6
X-DISPATCHER-SECRET-KEY: <密钥>
```
响应体:
仅 ASR 模式(Agent配置仅返回 ASR即可)
ASR + LLM + TTS 模式
```JSON
{
**"resource"**: **"task_config"**,
**"dispatcher_id"**: **"c046b893-8628-4589-ae50-619d049248a6"**,
**"tenant_id"**: **"tenant-id-mock"**,
**"task_id"**: **"task-mock"**,
**"task_revision"**: **2**,
**"status"**: **"running"**,
**"name"**: **"Mock task"**,
**"max_concurrent_calls"**: **2**,
**"ring_timeout_ms"**: **30000**,
**"max_call_duration_ms"**: **120000**,
**"route_policy_id"**: **"route-mock"**,
**"caller_profile_id"**: **"caller-profile-mock"**,
**"allowed_trunk_ids"**: [
**"trunk-mock"**
],
**"schedule"**: {
**"time_zone"**: **"Asia/Shanghai"**,
**"starts_at"**: **"2026-09-21T00:00:00+08:00"**,
**"ends_at"**: **null**,
**"weekly_windows"**: {
**"monday"**: [
{
**"start"**: **"09:00"**,
**"end"**: **"11:00"**
},
{
**"start"**: **"14:00"**,
**"end"**: **"18:00"**
}
],
**"tuesday"**: [
{
**"start"**: **"09:00"**,
**"end"**: **"18:00"**
}
],
**"wednesday"**: [
{
**"start"**: **"09:00"**,
**"end"**: **"18:00"**
}
],
**"thursday"**: [
{
**"start"**: **"09:00"**,
**"end"**: **"18:00"**
}
],
**"friday"**: [
{
**"start"**: **"09:00"**,
**"end"**: **"18:00"**
}
],
**"saturday"**: [
],
**"sunday"**: [
]
},
**"excluded_dates"**: [
**"2026-10-01"**,
**"2026-10-02"**
]
},
**"agent"**: {
**"immutable"**: **true**,
**"mode"**: **"full_ai"**,
**"llm"**: {
**"provider_ref"**: **"mock"**,
**"model"**: **"mock-chat-v1"**,
**"temperature"**: **0.2**,
**"max_tokens"**: **256**,
**"timeout_ms"**: **5000**
},
**"prompt"**: {
**"text"**: **"Mock prompt for an isolated test."**,
**"allowed_variables"**: [
],
**"max_bytes"**: **32768**
},
**"tts"**: {
**"provider_ref"**: **"mock"**,
**"model"**: **"mock-tts-v1"**,
**"voice"**: **"mock-neutral"**,
**"speed"**: **1**,
**"format"**: {
**"encoding"**: **"pcm_s16le"**,
**"sample_rate_hz"**: **16000**,
**"channels"**: **1**
},
**"timeout_ms"**: **5000**
},
**"asr"**: {
**"provider_ref"**: **"mock"**,
**"language"**: **"zh-CN"**,
**"input"**: {
**"encoding"**: **"pcm_s16le"**,
**"sample_rate_hz"**: **16000**,
**"channels"**: **1**,
**"sample_width_bytes"**: **2**
},
**"interim"**: **true**,
**"timeout_ms"**: **5000**
},
**"conversation"**: {
**"opening"**: **"开场白"**,
**"hangup_keywords"**: [
**"不用了"**,
**"请挂机"**
],
**"allow_interrupt"**: **true**,
**"silence_timeout_ms"**: **3000**,
**"max_duration_ms"**: **120000**,
**"max_turns"**: **20**,
**"sentence_max_chars"**: **80**,
**"max_pending_audio_chunks"**: **32**
}
}
}
```
任务列表
```HTTP
GET /internal/v1/dispatcher/tasks HTTP/1.1
Host: <SaaS 内网地址>
X-DISPATCHER-id: c046b893-8628-4589-ae50-619d049248a6
X-DISPATCHER-SECRET-KEY: <密钥>
```
增量请求
```HTTP
GET /internal/v1/dispatcher/tasks?after=1042 HTTP/1.1
Host: <SaaS 内网地址>
X-DISPATCHER-id: c046b893-8628-4589-ae50-619d049248a6
X-DISPATCHER-SECRET-KEY: <密钥>
```
响应:
```JSON
{
"schema_version": "task-discovery.v0.2-proposal",
"dispatcher_id": "c046b893-8628-4589-ae50-619d049248a6",
"cursor": "1042",
"tasks": [
{
"task_id": "task-a",
"tenant_id": 1001,
"status": "running",
"task_revision": 1
},
{
"task_id": "task-old",
"tenant_id": 1001,
"status": "stopped",
"task_revision": 3
}
]
}
```
按租户 ID 获取配额信息
```HTTP
GET /internal/v1/dispatcher/tenant/{tenant-id}/quota HTTP/1.1
Host: <SaaS 内网地址>
X-DISPATCHER-id: c046b893-8628-4589-ae50-619d049248a6
X-DISPATCHER-SECRET-KEY: <密钥>
```
```JSON
{
"resource": "tenant_quota",
"dispatcher_id": "c046b893-8628-4589-ae50-619d049248a6",
"tenant_id": "tenant-id-mock",
"quota_revision": 1,
"max_concurrent_calls": 3
}
```
控制事件
> 队列:agent\-call\.d\.\<dispatcher\_id\>\.control\.v1
>
>
SIP 线路变更
```JSON
{
"event_id": "7cd23165-c88e-4f36-8cf2-b5e9ac67ecdb",
"event_type": "sip.config",
"dispatcher_id": "c046b893-8628-4589-ae50-619d049248a6",
"payload": {
"trunk_id": "trunk-mock",
"change_type": "disabled", // enabled/removed/created
"revision": 8
}
}
```
任务控制
```JSON
{
"dispatcher_id": "c046b893-8628-4589-ae50-619d049248a6",
"tenant_id": 1001,
"issued_at": "2026-09-21T00:00:00Z",
"event_id": "7cd23165-c88e-4f36-8cf2-b5e9ac67ecdb",
"event_type":: "task.control",
"payload": {
"task_id": "task-a",
"action": "pause", // resume , stop
"reason": "local-test",
"options": { // 附加控制参数
"active_call_policy": "drain", // 活跃通话策略,仅stop,pause事件生效 [drain|hangup]
}
}
}
```
任务控制处理回执
> 队列 agent\-call\.saas\.v1
>
>
```JSON
{
"event_id": "7cd23165-c88e-4f36-8cf2-b5e9ac67ecdb",
"event_type":: "task.control",
"dispatcher_id": "c046b893-8628-4589-ae50-619d049248a6",
"tenant_id": 1001,
"payload": {
"status": "applied"
}
}
```
外呼任务队列
> 队列:agent\-call\.d\.\<dispatcher\_id\>\.t\.\<tenant\_id\>\.v1
>
>
呼出
```JSON
{
"event_id": "7cd23165-c88e-4f36-8cf2-b5e9ac67ecdb",
"event_type":: "call.execute",
"dispatcher_id": "c046b893-8628-4589-ae50-619d049248a6",
"tenant_id": 1001,
"issued_at": "2026-09-18T10:00:00+08:00"
"payload": {
"task_id": "task-a",
"callee": "15003164745"
}
}
```
回执
> 队列 agent\-call\.saas\.v1
>
>
开始调度执行
```JSON
{
"event_id": "7cd23165-c88e-4f36-8cf2-b5e9ac67ecdb",
"event_type":: "call.execute",
"dispatcher_id": "c046b893-8628-4589-ae50-619d049248a6",
"tenant_id": 1001,
"issued_at": "2026-09-18T10:00:00+08:00"
"payload": {
"status": "dispatched",
}
}
```
完成结果上报
成功
```JSON
{
"event_id": "call-result-001",
"event_type": "call.execute.result",
"dispatcher_id": "c046b893-8628-4589-ae50-619d049248a6",
"tenant_id": 1001,
"issued_at": "2026-09-18T10:10:15+08:00",
"payload": {
"task_id": "task-a",
"caller_profile_id": "caller-a",
"callee": "15003164745",
"trunk_id": "trunk-a",
"started_at": "2026-09-18T10:00:00+08:00",
"ended_at": "2026-09-18T10:10:00+08:00",
"duration_ms": 600000,
"outcome": "answered",
"reason_code": null,
"transcript": [
{
"turn_id": "turn-1",
"segment_id": "segment-1",
"role": "user",
"text": "示例转写内容",
"start_ms": 1000,
"end_ms": 2500
}
],
"opt_out": false,
"recording": {
"status": "uploaded",
"bucket": "example-bucket",
"object_key": "calls/tenant-a/call-a.wav",
"format": "wav",
"channels": 1,
"sample_rate_hz": 8000,
"duration_ms": 600000,
"size_bytes": 9600000,
"checksum_sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
}
}
}
```
无应答
```JSON
{
// ...
"payload": {
//...
"outcome": "no_answer",
"reason_code": 480,
"reason_message": "SIP ring timeout Reason",
"transcript": [],
"opt_out": false,
"recording": {}
}
}
```