# AI 中台对接文档

## 1. 概述

本文档描述 Python AI 中台对渠道侧（Java 主框架）暴露的 HTTP 接口规范，用于智能客服对话生成和服务健康检查。

### 1.1 服务信息

- **服务名称**: AI Service (Python AI 中台)
- **服务地址**: `http://ai-service:8080`
- **协议**: HTTP/1.1
- **数据格式**: JSON / SSE (Server-Sent Events)
- **字符编码**: UTF-8
- **契约版本**: v1.1.0

### 1.2 核心能力

- ✅ 智能对话生成（基于 LLM + RAG）
- ✅ 多租户隔离（基于 `X-Tenant-Id`）
- ✅ 会话上下文管理（基于 `sessionId`）
- ✅ 流式/非流式双模式输出
- ✅ 置信度评估与转人工建议
- ✅ 服务健康检查

---

## 2. 认证与租户隔离

### 2.1 API Key 认证（必填）

所有接口请求（除健康检查外）必须在 HTTP Header 中携带 API Key：

```http
X-API-Key: <your_api_key>
```

**说明**：
- API Key 用于身份认证和访问控制
- 缺失或无效的 API Key 将返回 `401 Unauthorized`
- API Key 由 AI 中台管理员分配，请妥善保管
- 以下路径无需 API Key：`/health`、`/ai/health`、`/docs`

### 2.2 租户标识（必填）

所有接口请求必须在 HTTP Header 中携带租户 ID：

```http
X-Tenant-Id: <tenant_id>
```

**租户 ID 格式规范**：`name@ash@year`

示例：
- `szmp@ash@2026` - 深圳某项目 2026 年
- `abc123@ash@2025` - ABC 项目 2025 年

**说明**：
- 租户 ID 用于数据隔离（知识库、会话历史、配置等）
- 缺失或格式错误的租户 ID 将返回 `400 Bad Request`
- 不同租户的数据完全隔离，不可跨租户访问
- 租户不存在时会自动创建

---

## 3. 接口列表

| 接口路径 | 方法 | 功能 | 响应模式 |
|---------|------|------|---------|
| `/ai/chat` | POST | 生成 AI 回复 | JSON / SSE |
| `/ai/health` | GET | 健康检查 | JSON |

---

## 4. 接口详细说明

### 4.1 生成 AI 回复

**接口路径**: `POST /ai/chat`

**功能描述**: 根据用户消息和会话历史生成 AI 回复，支持 RAG 检索增强、上下文管理、置信度评估。

#### 4.1.1 请求参数

**Headers**:
```http
Content-Type: application/json
X-API-Key: <your_api_key>
X-Tenant-Id: <tenant_id>
Accept: application/json  # 或 text/event-stream（流式输出）
```

**Body** (JSON):

| 字段 | 类型 | 必填 | 说明 |
|-----|------|------|------|
| `sessionId` | string | ✅ | 会话 ID，用于关联同一会话的对话历史 |
| `currentMessage` | string | ✅ | 当前用户消息内容 |
| `channelType` | string | ✅ | 渠道类型，枚举值：`wechat`、`douyin`、`jd` |
| `history` | array | ❌ | 历史消息列表（可选，AI 中台会自动管理会话历史） |
| `metadata` | object | ❌ | 扩展元数据（可选） |

**history 数组元素结构**:
```json
{
  "role": "user | assistant",
  "content": "消息内容"
}
```

**请求示例**:
```json
{
  "sessionId": "kf_001_wx123456_1708765432000",
  "currentMessage": "我想了解产品价格",
  "channelType": "wechat",
  "metadata": {
    "channelUserId": "wx123456",
    "extra": "..."
  }
}
```

#### 4.1.2 响应格式

##### 模式 1: JSON 响应（非流式）

**状态码**: `200 OK`

**响应体**:
```json
{
  "reply": "您好，我们的产品价格根据套餐不同有所差异...",
  "confidence": 0.92,
  "shouldTransfer": false,
  "transferReason": null,
  "metadata": {
    "retrieval_count": 3,
    "rag_enabled": true
  }
}
```

**字段说明**:

| 字段 | 类型 | 必填 | 说明 |
|-----|------|------|------|
| `reply` | string | ✅ | AI 生成的回复内容 |
| `confidence` | number | ✅ | 置信度评分（0.0-1.0），越高表示回答越可靠 |
| `shouldTransfer` | boolean | ✅ | 是否建议转人工（true=建议转人工） |
| `transferReason` | string | ❌ | 转人工原因（可选） |
| `metadata` | object | ❌ | 响应元数据（可选） |

##### 模式 2: SSE 流式响应

**触发条件**: 请求头包含 `Accept: text/event-stream`

**响应头**:
```http
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
```

**事件流格式**:

1. **增量消息事件** (可多次发送)
```
event: message
data: {"delta": "您好，"}

event: message
data: {"delta": "我们的产品"}
```

2. **最终结果事件** (发送一次后关闭连接)
```
event: final
data: {"reply": "完整回复内容", "confidence": 0.92, "shouldTransfer": false}
```

3. **错误事件** (发生错误时发送)
```
event: error
data: {"code": "INTERNAL_ERROR", "message": "错误描述"}
```

**事件序列保证**:
- `message*` (0 或多次) → `final` (1 次) → 连接关闭
- 或 `message*` (0 或多次) → `error` (1 次) → 连接关闭

#### 4.1.3 错误响应

**401 Unauthorized** - 认证失败
```json
{
  "code": "UNAUTHORIZED",
  "message": "Missing required header: X-API-Key",
  "details": []
}
```

**400 Bad Request** - 请求参数错误
```json
{
  "code": "INVALID_REQUEST",
  "message": "缺少必填字段: sessionId",
  "details": []
}
```

**400 Bad Request** - 租户 ID 格式错误
```json
{
  "code": "INVALID_TENANT_ID",
  "message": "Invalid tenant ID format. Expected: name@ash@year (e.g., szmp@ash@2026)",
  "details": []
}
```

**500 Internal Server Error** - 服务内部错误
```json
{
  "code": "INTERNAL_ERROR",
  "message": "LLM 调用失败",
  "details": []
}
```

**503 Service Unavailable** - 服务不可用
```json
{
  "code": "SERVICE_UNAVAILABLE",
  "message": "向量数据库连接失败",
  "details": []
}
```

---

### 4.2 健康检查

**接口路径**: `GET /ai/health`

**功能描述**: 检查 AI 服务是否正常运行，用于服务监控和负载均衡健康探测。

#### 4.2.1 请求参数

无需请求参数，无需认证头。

#### 4.2.2 响应格式

**200 OK** - 服务正常
```json
{
  "status": "healthy"
}
```

**503 Service Unavailable** - 服务不健康
```json
{
  "status": "unhealthy"
}
```

---

## 5. 调用示例

### 5.1 Java 调用示例（非流式）

```java
import org.springframework.http.*;
import org.springframework.web.client.RestTemplate;

public class AIServiceClient {

    private final RestTemplate restTemplate;
    private final String aiServiceUrl = "http://ai-service:8080";
    private final String apiKey = "your_api_key_here";

    public ChatResponse generateReply(String tenantId, ChatRequest request) {
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        headers.set("X-API-Key", apiKey);
        headers.set("X-Tenant-Id", tenantId);

        HttpEntity<ChatRequest> entity = new HttpEntity<>(request, headers);

        ResponseEntity<ChatResponse> response = restTemplate.postForEntity(
            aiServiceUrl + "/ai/chat",
            entity,
            ChatResponse.class
        );

        return response.getBody();
    }
}
```

### 5.2 Java 调用示例（流式）

```java
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Flux;

public class AIServiceStreamClient {

    private final WebClient webClient;
    private final String apiKey = "your_api_key_here";

    public Flux<ServerSentEvent<String>> generateReplyStream(
        String tenantId,
        ChatRequest request
    ) {
        return webClient.post()
            .uri("/ai/chat")
            .header("X-API-Key", apiKey)
            .header("X-Tenant-Id", tenantId)
            .header("Accept", "text/event-stream")
            .bodyValue(request)
            .retrieve()
            .bodyToFlux(ServerSentEvent.class);
    }
}
```

### 5.3 cURL 调用示例

```bash
# 非流式调用
curl -X POST http://ai-service:8080/ai/chat \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your_api_key_here" \
  -H "X-Tenant-Id: szmp@ash@2026" \
  -d '{
    "sessionId": "kf_001_wx123456_1708765432000",
    "currentMessage": "我想了解产品价格",
    "channelType": "wechat"
  }'

# 流式调用
curl -X POST http://ai-service:8080/ai/chat \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your_api_key_here" \
  -H "X-Tenant-Id: szmp@ash@2026" \
  -H "Accept: text/event-stream" \
  -d '{
    "sessionId": "kf_001_wx123456_1708765432000",
    "currentMessage": "我想了解产品价格",
    "channelType": "wechat"
  }'

# 健康检查（无需认证）
curl http://ai-service:8080/ai/health
```

---

## 6. 业务逻辑说明

### 6.1 会话管理

- **会话标识**: `sessionId` 用于唯一标识一个对话会话
- **自动持久化**: AI 中台会自动保存会话历史，无需调用方每次传递完整历史
- **可选历史**: 调用方可通过 `history` 字段提供外部历史，AI 中台会合并处理
- **租户隔离**: 相同 `sessionId` 在不同 `tenantId` 下视为不同会话

### 6.2 RAG 检索增强

- **自动触发**: AI 中台会根据用户问题自动判断是否需要检索知识库
- **多知识库**: 支持按知识库类型（产品知识、FAQ、话术模板等）分类检索
- **置信度评估**: 检索结果质量会影响 `confidence` 评分
- **兜底策略**: 检索失败或无结果时，AI 会基于通用知识回答并降低置信度

### 6.3 转人工建议

`shouldTransfer` 字段由以下因素决定：

- ✅ 置信度低于阈值（默认 0.6）
- ✅ 检索无结果或结果质量差
- ✅ 用户明确要求人工服务
- ✅ 意图识别命中"转人工"规则

**注意**: `shouldTransfer=true` 仅为建议，最终是否转人工由调用方（Java 主框架）决策。

### 6.4 意图识别与规则引擎

- **前置处理**: 用户消息会先经过意图识别
- **固定回复**: 命中固定规则时直接返回预设话术（跳过 LLM 调用）
- **话术流程**: 命中流程规则时进入多轮引导对话
- **定向检索**: 命中 RAG 规则时使用指定知识库检索

### 6.5 输出护栏

- **禁词过滤**: AI 回复会自动过滤禁词（竞品名称、敏感词等）
- **替换策略**: 支持星号替换、文本替换、整条拦截三种策略
- **行为约束**: Prompt 中注入行为规则（如"不承诺具体赔偿金额"）

---

## 7. 性能与限制

### 7.1 性能指标

| 指标 | 非流式 | 流式 |
|-----|-------|------|
| 首字响应时间 | 1-3 秒 | 200-500 毫秒 |
| 完整响应时间 | 2-5 秒 | 3-8 秒 |
| 并发支持 | 100+ QPS | 50+ QPS |

### 7.2 限制说明

- **消息长度**: 单条消息最大 4000 字符
- **历史长度**: 建议历史消息不超过 20 轮（AI 中台会自动截断）
- **超时设置**: 建议调用方设置 10 秒超时（非流式）、30 秒超时（流式）
- **重试策略**: 503 错误建议指数退避重试，500 错误建议降级处理

---

## 8. 错误码参考

| 错误码 | HTTP 状态码 | 说明 | 处理建议 |
|-------|-----------|------|---------|
| `UNAUTHORIZED` | 401 | 认证失败（缺少或无效 API Key） | 检查 X-API-Key 请求头 |
| `INVALID_REQUEST` | 400 | 请求参数错误 | 检查必填字段和参数格式 |
| `MISSING_TENANT_ID` | 400 | 缺少租户 ID | 添加 X-Tenant-Id 请求头 |
| `INVALID_TENANT_ID` | 400 | 租户 ID 格式错误 | 使用正确格式：name@ash@year |
| `INTERNAL_ERROR` | 500 | 服务内部错误 | 降级处理或重试 |
| `LLM_ERROR` | 500 | LLM 调用失败 | 降级处理或重试 |
| `SERVICE_UNAVAILABLE` | 503 | 服务不可用 | 指数退避重试 |
| `QDRANT_ERROR` | 503 | 向量库不可用 | 指数退避重试 |
| `STREAMING_ERROR` | 200 (SSE) | 流式传输错误 | 关闭连接并重试 |

---

## 9. 最佳实践

### 9.1 API Key 管理

- API Key 由 AI 中台管理员通过管理后台分配
- 建议为不同环境（开发/测试/生产）使用不同的 API Key
- API Key 应存储在配置文件或环境变量中，不要硬编码
- 定期轮换 API Key 以提高安全性

### 9.2 会话 ID 生成规范

建议格式: `{业务前缀}_{租户ID}_{渠道用户ID}_{时间戳}`

示例: `kf_001_wx123456_1708765432000`

### 9.3 流式 vs 非流式选择

- **流式**: 适用于 Web/App 实时对话场景，用户体验更好
- **非流式**: 适用于批量处理、异步任务、API 集成场景

### 9.4 降级策略建议

```java
public ChatResponse generateReplyWithFallback(String tenantId, ChatRequest request) {
    try {
        return aiServiceClient.generateReply(tenantId, request);
    } catch (ServiceUnavailableException e) {
        // 降级策略 1: 返回固定话术
        return ChatResponse.builder()
            .reply("抱歉，当前咨询量较大，请稍后再试或转人工服务。")
            .confidence(0.0)
            .shouldTransfer(true)
            .build();
    } catch (Exception e) {
        // 降级策略 2: 直接转人工
        return ChatResponse.builder()
            .reply("系统繁忙，正在为您转接人工客服...")
            .confidence(0.0)
            .shouldTransfer(true)
            .transferReason("AI 服务异常")
            .build();
    }
}
```

### 9.5 监控指标建议

- ✅ 接口响应时间（P50/P95/P99）
- ✅ 接口成功率
- ✅ 置信度分布
- ✅ 转人工率
- ✅ 错误码分布

---

## 10. 变更日志

| 版本 | 日期 | 变更内容 |
|-----|------|---------|
| v1.1.0 | 2026-02-27 | 新增流式输出支持、意图识别、输出护栏 |
| v1.0.0 | 2026-02-20 | 初始版本，支持基础对话生成和健康检查 |

---

## 11. 联系方式

- **技术支持**: AI 中台开发团队
- **问题反馈**: 提交 Issue 到项目仓库
- **文档更新**: 参考 `spec/ai-service/openapi.provider.yaml`

---

**文档生成时间**: 2026-02-27
**契约版本**: v1.1.0
**维护状态**: ✅ 活跃维护
