2026-05-01 20:56:25 +08:00
|
|
|
|
# AI-Customer-Service 核心接口设计
|
|
|
|
|
|
|
|
|
|
|
|
> 版本:v1.0 | 状态:初稿
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 1. 内部模块间接口
|
|
|
|
|
|
|
|
|
|
|
|
### 1.1 ChannelAdapter
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
type ChannelAdapter interface {
|
|
|
|
|
|
ParseWebhook(r *http.Request) (*UnifiedMessage, error)
|
|
|
|
|
|
SendReply(ctx context.Context, msg *UnifiedMessage, reply string) error
|
|
|
|
|
|
ValidateWebhook(r *http.Request) error
|
|
|
|
|
|
ChannelType() string
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
type UnifiedMessage struct {
|
|
|
|
|
|
MessageID string
|
|
|
|
|
|
Channel string // telegram | discord | wechat | widget
|
|
|
|
|
|
OpenID string
|
|
|
|
|
|
UserID string
|
|
|
|
|
|
Content string
|
|
|
|
|
|
ContentType string // text | image | file | voice
|
|
|
|
|
|
Timestamp time.Time
|
|
|
|
|
|
ReplyTo string
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 1.2 IntentEngine
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
type IntentEngine interface {
|
|
|
|
|
|
Recognize(ctx context.Context, sessionID string, message string, context []MessageContext) (*IntentResult, error)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
type IntentResult struct {
|
|
|
|
|
|
Intent string // 意图类别
|
|
|
|
|
|
Confidence float64 // 0.00 - 1.00
|
|
|
|
|
|
Entities map[string]string // 提取的实体
|
|
|
|
|
|
NeedsHuman bool // 是否需要转人工
|
|
|
|
|
|
Sensitive bool // 是否敏感意图
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
type MessageContext struct {
|
|
|
|
|
|
Direction string
|
|
|
|
|
|
Content string
|
|
|
|
|
|
Timestamp time.Time
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 1.3 RAGEngine
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
type RAGEngine interface {
|
|
|
|
|
|
Retrieve(ctx context.Context, query string, topK int) ([]RetrievalResult, error)
|
|
|
|
|
|
IndexEntry(ctx context.Context, entry KBEntry) error
|
|
|
|
|
|
DeleteIndex(ctx context.Context, entryID string) error
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
type RetrievalResult struct {
|
|
|
|
|
|
EntryID string
|
|
|
|
|
|
Title string
|
|
|
|
|
|
Content string
|
|
|
|
|
|
Score float64
|
|
|
|
|
|
Category string
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 1.4 DialogManager
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
type DialogManager interface {
|
|
|
|
|
|
GetOrCreateSession(ctx context.Context, channel, openID string) (*Session, error)
|
|
|
|
|
|
UpdateSession(ctx context.Context, sessionID string, updates SessionUpdates) error
|
|
|
|
|
|
CloseSession(ctx context.Context, sessionID string, reason string) error
|
|
|
|
|
|
GetContext(ctx context.Context, sessionID string, maxTurns int) ([]MessageContext, error)
|
|
|
|
|
|
AddMessage(ctx context.Context, sessionID string, msg Message) error
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
type Session struct {
|
|
|
|
|
|
ID string
|
|
|
|
|
|
Channel string
|
|
|
|
|
|
OpenID string
|
|
|
|
|
|
UserID string
|
|
|
|
|
|
Status string // idle processing waiting_feedback handoff closed
|
|
|
|
|
|
TurnCount int
|
|
|
|
|
|
LastMessageAt time.Time
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
type SessionUpdates struct {
|
|
|
|
|
|
Status *string
|
|
|
|
|
|
UserID *string
|
|
|
|
|
|
TurnCount *int
|
|
|
|
|
|
LastMessageAt *time.Time
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 1.5 DiagnosisService
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
type DiagnosisService interface {
|
|
|
|
|
|
VerifyIdentity(ctx context.Context, email string, code string) (*IdentityResult, error)
|
|
|
|
|
|
QueryQuota(ctx context.Context, userID string) (*QuotaInfo, error)
|
|
|
|
|
|
QueryTokenUsage(ctx context.Context, userID string, window time.Duration) (*TokenUsage, error)
|
|
|
|
|
|
QueryErrorLogs(ctx context.Context, userID string, limit int) ([]ErrorLog, error)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
type IdentityResult struct {
|
|
|
|
|
|
Matched bool
|
|
|
|
|
|
UserID string
|
|
|
|
|
|
Attempts int
|
|
|
|
|
|
Locked bool
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
type QuotaInfo struct {
|
|
|
|
|
|
TotalQuota int64
|
|
|
|
|
|
UsedQuota int64
|
|
|
|
|
|
RemainingQuota int64
|
|
|
|
|
|
ResetAt time.Time
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 1.6 HandoffService
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
type HandoffService interface {
|
|
|
|
|
|
ShouldHandoff(ctx context.Context, intent *IntentResult, turnCount int, identityFailures int) (*HandoffDecision, error)
|
|
|
|
|
|
CreateTicket(ctx context.Context, sessionID string, reason string, priority string) (*Ticket, error)
|
|
|
|
|
|
AssignTicket(ctx context.Context, ticketID string, agentID string) error
|
|
|
|
|
|
CloseTicket(ctx context.Context, ticketID string, resolution string) error
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
type HandoffDecision struct {
|
|
|
|
|
|
ShouldHandoff bool
|
|
|
|
|
|
Reason string
|
|
|
|
|
|
Priority string // P1 P2 P3
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
type Ticket struct {
|
|
|
|
|
|
ID string
|
|
|
|
|
|
SessionID string
|
|
|
|
|
|
UserID string
|
|
|
|
|
|
Priority string
|
|
|
|
|
|
Status string
|
|
|
|
|
|
HandoffReason string
|
|
|
|
|
|
AssignedTo string
|
|
|
|
|
|
ContextSnapshot string
|
|
|
|
|
|
CreatedAt time.Time
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 1.7 KnowledgeBaseService
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
type KnowledgeBaseService interface {
|
|
|
|
|
|
CreateEntry(ctx context.Context, entry KBEntry) (*KBEntry, error)
|
|
|
|
|
|
UpdateEntry(ctx context.Context, entry KBEntry) (*KBEntry, error)
|
|
|
|
|
|
DeleteEntry(ctx context.Context, entryID string) error
|
|
|
|
|
|
GetEntry(ctx context.Context, entryID string) (*KBEntry, error)
|
|
|
|
|
|
ListEntries(ctx context.Context, filter KBFilter) ([]KBEntry, error)
|
|
|
|
|
|
PublishEntry(ctx context.Context, entryID string) error
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
type KBEntry struct {
|
|
|
|
|
|
ID string
|
|
|
|
|
|
Title string
|
|
|
|
|
|
Content string
|
|
|
|
|
|
Category string
|
|
|
|
|
|
Tags []string
|
|
|
|
|
|
ReferenceCount int
|
|
|
|
|
|
Status string // draft published deprecated
|
|
|
|
|
|
Version int
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 1.8 LLMClient
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
type LLMClient interface {
|
|
|
|
|
|
Generate(ctx context.Context, prompt string, options LLMOptions) (*LLMResponse, error)
|
|
|
|
|
|
GenerateWithRAG(ctx context.Context, prompt string, context []RetrievalResult, options LLMOptions) (*LLMResponse, error)
|
|
|
|
|
|
GetEmbedding(ctx context.Context, text string) ([]float32, error)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
type LLMResponse struct {
|
|
|
|
|
|
Content string
|
|
|
|
|
|
Provider string
|
|
|
|
|
|
Model string
|
|
|
|
|
|
LatencyMs int
|
|
|
|
|
|
TokenUsage TokenUsageInfo
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
type LLMOptions struct {
|
|
|
|
|
|
MaxTokens int
|
|
|
|
|
|
Temperature float64
|
|
|
|
|
|
Timeout time.Duration
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 2. 外部系统集成接口
|
|
|
|
|
|
|
|
|
|
|
|
### 2.1 与 Bridge Gateway 集成
|
|
|
|
|
|
|
|
|
|
|
|
| 方法 | 路径 | 请求 | 响应 | 说明 |
|
|
|
|
|
|
|------|------|------|------|------|
|
|
|
|
|
|
| Webhook 接收 | `POST /api/v1/customer-service/webhook/{channel}` | `UnifiedMessage` | `{"received":true}` | 接收渠道消息 |
|
|
|
|
|
|
| 消息回复 | `POST {gateway_callback_url}` | `{"session_id":"","content":""}` | `{"sent":true}` | 调用 Gateway 发送接口 |
|
|
|
|
|
|
| 状态查询 | `GET /actuator/health` | - | `{"status":"up"}` | Gateway 健康检查 |
|
|
|
|
|
|
|
|
|
|
|
|
### 2.2 与 platform-token-runtime 集成
|
|
|
|
|
|
|
|
|
|
|
|
| 方法 | 路径 | 请求 | 响应 | 说明 |
|
|
|
|
|
|
|------|------|------|------|------|
|
|
|
|
|
|
| 配额查询 | `GET /internal/runtime/quota` | `?user_id={uid}` | `QuotaInfo` | 延迟 < 500ms |
|
|
|
|
|
|
| Token 消耗 | `GET /internal/runtime/token-usage` | `?user_id={uid}&window=1d` | `TokenUsage` | 延迟 < 500ms |
|
|
|
|
|
|
| 错误日志 | `GET /internal/runtime/error-logs` | `?user_id={uid}&limit=5` | `[]ErrorLog` | 延迟 < 3s |
|
|
|
|
|
|
|
|
|
|
|
|
### 2.3 与 supply-api 集成
|
|
|
|
|
|
|
|
|
|
|
|
| 方法 | 路径 | 请求 | 响应 | 说明 |
|
|
|
|
|
|
|------|------|------|------|------|
|
|
|
|
|
|
| 用户身份校验 | `GET /internal/supply/users/verify` | `?email={email}` 或 `?api_key_prefix={prefix}` | `{"matched":true,"user_id":""}` | 延迟 < 2s |
|
|
|
|
|
|
| 审计日志格式 | `GET /internal/supply/audit/schema` | - | `{"schema":{}}` | 格式一致 |
|
|
|
|
|
|
|
|
|
|
|
|
### 2.4 与 NewAPI / Sub2API 集成
|
|
|
|
|
|
|
|
|
|
|
|
| 方法 | 路径 | 请求 | 响应 | 说明 |
|
|
|
|
|
|
|------|------|------|------|------|
|
|
|
|
|
|
| Webhook 接入 | `POST /api/v1/customer-service/webhook/{channel}` | `渠道原生消息格式` | `{"received":true}` | 适配层转换为 UnifiedMessage |
|
|
|
|
|
|
| 工单查询 | `GET /api/v1/customer-service/tickets` | `?status=open&external_system=newapi` | `[]Ticket` | 外部系统获取工单 |
|
|
|
|
|
|
| 知识库查询 | `GET /api/v1/customer-service/kb` | `?query={q}&limit=5` | `[]KBEntry` | 知识库共享 |
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 3. API 接口规范
|
|
|
|
|
|
|
|
|
|
|
|
### 3.1 REST API 基础
|
|
|
|
|
|
|
|
|
|
|
|
- **基础路径** (独立运行): `/api/v1/customer-service/`
|
|
|
|
|
|
- **基础路径** (集成运行): `/internal/customer-service/`
|
|
|
|
|
|
- **内容类型**: `application/json`
|
|
|
|
|
|
- **错误响应格式**:
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"error": {
|
|
|
|
|
|
"code": "CS_SES_4001",
|
|
|
|
|
|
"message": "会话不存在",
|
|
|
|
|
|
"details": {}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 3.2 核心端点
|
|
|
|
|
|
|
|
|
|
|
|
#### 会话管理
|
|
|
|
|
|
|
|
|
|
|
|
| 方法 | 路径 | 描述 |
|
|
|
|
|
|
|------|------|------|
|
|
|
|
|
|
| POST | `/api/v1/customer-service/webhook/{channel}` | 接收渠道 Webhook |
|
|
|
|
|
|
| GET | `/api/v1/customer-service/sessions/{id}` | 获取会话信息 |
|
|
|
|
|
|
| GET | `/api/v1/customer-service/sessions/{id}/messages` | 获取会话消息 |
|
|
|
|
|
|
| POST | `/api/v1/customer-service/sessions/{id}/feedback` | 提交解决/未解决反馈 |
|
|
|
|
|
|
| POST | `/api/v1/customer-service/sessions/{id}/handoff` | 人工触发转人工 |
|
|
|
|
|
|
|
|
|
|
|
|
#### 工单管理
|
|
|
|
|
|
|
|
|
|
|
|
| 方法 | 路径 | 描述 |
|
|
|
|
|
|
|------|------|------|
|
2026-05-06 09:39:33 +08:00
|
|
|
|
| GET | `/api/v1/customer-service/tickets` | 列表 open / assigned / processing 工单 |
|
2026-05-01 20:56:25 +08:00
|
|
|
|
| GET | `/api/v1/customer-service/tickets/{id}` | 获取工单 |
|
2026-05-06 09:39:33 +08:00
|
|
|
|
| POST | `/api/v1/customer-service/tickets/{id}/assign?agent_id={agent_id}` | 将 `open` 工单分配给客服 |
|
|
|
|
|
|
| POST | `/api/v1/customer-service/tickets/{id}/resolve?resolution={resolution}` | 将 `assigned`/`processing` 工单标记为 `resolved` |
|
|
|
|
|
|
| POST | `/api/v1/customer-service/tickets/{id}/close?resolution={resolution}` | 将 `resolved` 工单最终关闭为 `closed` |
|
2026-05-01 20:56:25 +08:00
|
|
|
|
| GET | `/api/v1/customer-service/tickets/stats` | 工单统计 |
|
|
|
|
|
|
|
2026-05-06 09:39:33 +08:00
|
|
|
|
#### 工单状态机
|
|
|
|
|
|
|
|
|
|
|
|
| 当前状态 | 允许动作 | 目标状态 |
|
|
|
|
|
|
|----------|----------|----------|
|
|
|
|
|
|
| `open` | `assign` | `assigned` |
|
|
|
|
|
|
| `assigned` | `resolve` | `resolved` |
|
|
|
|
|
|
| `processing` | `resolve` | `resolved` |
|
|
|
|
|
|
| `resolved` | `close` | `closed` |
|
|
|
|
|
|
| `closed` | 无 | 无 |
|
|
|
|
|
|
|
|
|
|
|
|
受保护工单接口使用请求头鉴权:
|
|
|
|
|
|
|
|
|
|
|
|
- `X-CS-Actor-ID`
|
|
|
|
|
|
- `X-CS-Actor-Role`
|
|
|
|
|
|
|
2026-05-01 20:56:25 +08:00
|
|
|
|
#### 知识库
|
|
|
|
|
|
|
|
|
|
|
|
| 方法 | 路径 | 描述 |
|
|
|
|
|
|
|------|------|------|
|
|
|
|
|
|
| GET | `/api/v1/customer-service/kb` | 列表知识库条目 |
|
|
|
|
|
|
| POST | `/api/v1/customer-service/kb` | 创建条目 |
|
|
|
|
|
|
| GET | `/api/v1/customer-service/kb/{id}` | 获取条目 |
|
|
|
|
|
|
| PUT | `/api/v1/customer-service/kb/{id}` | 更新条目 |
|
|
|
|
|
|
| DELETE | `/api/v1/customer-service/kb/{id}` | 删除条目 |
|
|
|
|
|
|
| POST | `/api/v1/customer-service/kb/{id}/publish` | 发布条目 |
|
|
|
|
|
|
| POST | `/api/v1/customer-service/kb/search` | 检索知识库 |
|
|
|
|
|
|
|
|
|
|
|
|
#### 运营后台
|
|
|
|
|
|
|
|
|
|
|
|
| 方法 | 路径 | 描述 |
|
|
|
|
|
|
|------|------|------|
|
|
|
|
|
|
| GET | `/api/v1/customer-service/admin/dashboard` | 运营大盘 |
|
|
|
|
|
|
| GET | `/api/v1/customer-service/admin/handoff-reasons` | 转人工原因统计 |
|
|
|
|
|
|
| POST | `/api/v1/customer-service/admin/feedback-review` | 提交对话质检结果 |
|
|
|
|
|
|
|
|
|
|
|
|
### 3.3 错误码定义
|
|
|
|
|
|
|
|
|
|
|
|
| 错误码 | HTTP 状态 | 说明 |
|
|
|
|
|
|
|---------|-----------|------|
|
|
|
|
|
|
| `CS_SES_4001` | 404 | 会话不存在 |
|
|
|
|
|
|
| `CS_SES_4002` | 429 | 消息频率过高 |
|
|
|
|
|
|
| `CS_SES_4003` | 403 | 身份校验已锁定 |
|
|
|
|
|
|
| `CS_IDT_4001` | 400 | 身份信息不匹配 |
|
|
|
|
|
|
| `CS_IDT_4002` | 400 | 验证码错误 |
|
2026-05-06 09:39:33 +08:00
|
|
|
|
| `CS_TICKET_4001` | 404 | 工单不存在 |
|
2026-05-01 20:56:25 +08:00
|
|
|
|
| `CS_TKT_4002` | 409 | 工单已被分配 |
|
2026-05-06 09:39:33 +08:00
|
|
|
|
| `CS_TICKET_4092` | 409 | 工单状态不允许 resolve |
|
|
|
|
|
|
| `CS_TICKET_4093` | 409 | 工单状态不允许 close |
|
2026-05-01 20:56:25 +08:00
|
|
|
|
| `CS_KB_4001` | 404 | 知识库条目不存在 |
|
|
|
|
|
|
| `CS_KB_4002` | 409 | 条目名称已存在 |
|
|
|
|
|
|
| `CS_LLM_5001` | 503 | LLM 服务不可用 |
|
|
|
|
|
|
| `CS_LLM_5002` | 504 | LLM 超时 |
|
|
|
|
|
|
| `CS_AUTH_4001` | 403 | 越权访问 |
|
|
|
|
|
|
|
|
|
|
|
|
### 3.4 WebSocket 接口
|
|
|
|
|
|
|
|
|
|
|
|
**路径**: `/ws/v1/customer-service/sessions/{session_id}`
|
|
|
|
|
|
|
|
|
|
|
|
- 网页 Widget 客户端订阅,实时推送机器人回复。
|
|
|
|
|
|
- 心跳间隔 30 秒。
|