## 场景 02 · 附件上传与引用

上传文件并在智能体对话中引用，获取处理结果。附件必须归属于会话。

> **配置流程：** 先建会话 → 上传附件 → 带 attachmentIds 提交对话。

### Step 1: 预创建空会话

POST /api/session/create

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| agentId | int64 | 智能体 ID（必填） |
| title | string | 会话标题（必填） |

```bash
curl -X POST 'https://router.fis.aliyuncs.com/finx/api/session/create' \
  -H 'Authorization: Bearer <TOKEN>' \
  -H 'X-Principal-Id: <principalId>' \
  -H 'Content-Type: application/json' \
  -d '{"agentId":1001,"title":"Q3财务分析"}'
```

### Step 2: 上传附件

POST /api/agent/attachment/upload（multipart/form-data）

| 响应字段 | 说明 |
| --- | --- |
| attachmentId | 附件 ID（提交对话时使用） |
| fileName | 原始文件名 |
| fileSize | 文件大小（字节） |
| previewUrl | 预览 URL |

```bash
curl -X POST 'https://router.fis.aliyuncs.com/finx/api/agent/attachment/upload?sessionId=<sid>' \
  -H 'Authorization: Bearer <TOKEN>' \
  -H 'X-Principal-Id: <principalId>' \
  -F 'file=@/path/to/report.pdf'
```

### Step 3: 带附件提交对话

POST /api/agent/run/async — 与普通对话相比多了 sessionId + attachmentIds

```bash
curl -X POST 'https://router.fis.aliyuncs.com/finx/api/agent/run/async' \
  -H 'Authorization: Bearer <TOKEN>' \
  -H 'X-Principal-Id: <principalId>' \
  -H 'Content-Type: application/json' \
  -d '{"agentId":1001,"userInput":"帮我分析这份财报","sessionId":<sid>,"attachmentIds":[<attId>]}'
```

### Step 4: 消费结果

同「发起对话」的 SSE 消费流程，用 requestId 订阅事件流。

> **注意事项**
> 
> attachmentIds 必须属于本会话且为当前用户上传；Content-Type 使用 multipart/form-data；附件上传前必须先有 sessionId。

> 文件：`attachment_flow.py`

```python
# 步骤 1：预创建会话 → sessionId
def create_session(principal_id, agent_id, title="附件分析"):
    data = unwrap(requests.post(
        f"{BASE_URL}/api/session/create",
        headers=auth_headers(principal_id),
        json={"agentId": agent_id, "title": title},
    ))
    return data["sessionId"]
```

```python
# 步骤 2：上传附件（multipart）→ attachmentId
def upload_attachment(principal_id, session_id, file_path):
    with open(file_path, "rb") as f:
        resp = requests.post(
            f"{BASE_URL}/api/agent/attachment/upload",
            headers=auth_headers(principal_id, json_body=False),
            params={"sessionId": session_id},
            files={"file": f},
        )
    return unwrap(resp)["attachmentId"]
```

```python
# 步骤 3：带附件提交对话
def run_async_with_attachment(
    principal_id, agent_id, sid, att_id, user_input,
):
    data = unwrap(requests.post(
        f"{BASE_URL}/api/agent/run/async",
        headers=auth_headers(principal_id),
        json={
            "agentId": agent_id,
            "userInput": user_input,
            "sessionId": sid,
            "attachmentIds": [att_id],
            "triggerSource": {"sourceType": "api_call"},
        },
    ))
    return data["requestId"]
```

```python
# 步骤 4：复用 stream_events 消费结果
if __name__ == "__main__":
    pid = create_principal()
    aid = create_agent(pid)
    sid = create_session(pid, aid, "财报分析")
    att = upload_attachment(pid, sid, "/path/to/report.pdf")
    req_id = run_async_with_attachment(
        pid, aid, sid, att, "帮我分析这份财报",
    )
    stream_events(pid, req_id)
```
