> ## Documentation Index
> Fetch the complete documentation index at: https://docs.profy.cn/llms.txt
> Use this file to discover all available pages before exploring further.

# Python SDK

> 袋袋 Python SDK 安装与使用指南

`profy` 是袋袋平台的官方 Python SDK，提供异步和同步两种客户端，封装了认证、流式解析、错误处理等细节。

## 安装

```bash theme={null}
pip install profy
```

要求 Python 3.8+。SDK 基于 `httpx` 构建。

## 快速开始

### 异步客户端（推荐）

```python theme={null}
import asyncio
from profy import Profy

async def main():
    async with Profy(api_key="sk-pro-your-key") as client:
        result = await client.agents.run("my-expert", "你好")
        print(result.text)

asyncio.run(main())
```

### 同步客户端

```python theme={null}
from profy import ProfySync

with ProfySync(api_key="sk-pro-your-key") as client:
    result = client.agents.run("my-expert", "你好")
    print(result.text)
```

## 客户端配置

```python theme={null}
from profy import Profy
import httpx

client = Profy(
    api_key="sk-pro-your-key",
    base_url="https://api.profy.cn",  # 默认值
    timeout=httpx.Timeout(300.0, connect=10.0),  # 默认值
)
```

| 参数         | 类型                 | 默认值                    | 说明                           |
| ---------- | ------------------ | ---------------------- | ---------------------------- |
| `api_key`  | `str`              | （必填）                   | API Key 或 OAuth Access Token |
| `base_url` | `str`              | `https://api.profy.cn` | API 基础 URL                   |
| `timeout`  | `Timeout \| float` | 300 秒                  | 请求超时时间                       |

## 资源命名空间

SDK 通过资源命名空间组织 API 方法：

| 命名空间                      | 方法                      | 对应端点                        |
| ------------------------- | ----------------------- | --------------------------- |
| `client.agents`           | `run()`, `run_stream()` | `POST /v1/agents/run`       |
| `client.chat.completions` | `create()`              | `POST /v1/chat/completions` |
| `client.models`           | `list()`                | `GET /v1/models`            |
| `client.events`           | `report()`              | `POST /v1/events`           |

## Agents

### `agents.run()` — 调用专家

```python theme={null}
result = await client.agents.run(
    "my-expert",           # 专家标识符
    "帮我分析这段代码",      # 消息内容
    session_id="abc123",   # 可选：延续会话
    model="deepseek-v3",   # 可选：指定模型
)
```

返回 `AgentRunResponse` 对象：

| 属性            | 类型               | 说明            |
| ------------- | ---------------- | ------------- |
| `text`        | `str`            | Agent 的完整文本回复 |
| `model`       | `str \| None`    | 使用的模型名称       |
| `session_id`  | `str \| None`    | 会话 ID（用于多轮对话） |
| `tool_calls`  | `list[ToolCall]` | 工具调用列表        |
| `usage`       | `TokenUsage`     | Token 用量      |
| `error`       | `str \| None`    | 错误信息（如有）      |
| `partial`     | `bool`           | 是否因超时截断       |
| `elapsed_sec` | `float`          | 耗时（秒）         |

### `agents.run_stream()` — 流式调用专家

```python theme={null}
async for chunk in client.agents.run_stream("my-expert", "你好"):
    if chunk.type == "output.text.delta":
        print(chunk.delta, end="", flush=True)
    elif chunk.type == "tool_call.created":
        print(f"\n🔧 {chunk.raw.get('tool_name')}")
    elif chunk.type == "run.completed":
        print("\n完成")
```

每个 `AgentRunChunk` 包含：

| 属性      | 类型            | 说明                          |
| ------- | ------------- | --------------------------- |
| `type`  | `str`         | 事件类型（如 `output.text.delta`） |
| `delta` | `str \| None` | 文本增量（仅 `output.text.delta`） |
| `raw`   | `dict`        | 原始事件数据                      |

## Chat Completions

### `chat.completions.create()` — 对话补全

**非流式**：

```python theme={null}
resp = await client.chat.completions.create(
    model="deepseek-v3",
    messages=[
        {"role": "system", "content": "你是一个有帮助的助手"},
        {"role": "user", "content": "你好"},
    ],
    temperature=0.7,
    max_tokens=1000,
)
print(resp.text)
print(f"Token: {resp.usage.total_tokens}")
```

**流式**：

```python theme={null}
stream = await client.chat.completions.create(
    model="deepseek-v3",
    messages=[{"role": "user", "content": "讲一个故事"}],
    stream=True,
)
async for chunk in stream:
    if chunk.delta:
        print(chunk.delta, end="", flush=True)
```

`ChatResponse` 对象：

| 属性      | 类型           | 说明       |
| ------- | ------------ | -------- |
| `text`  | `str`        | 回复文本     |
| `model` | `str`        | 模型名称     |
| `usage` | `TokenUsage` | Token 用量 |
| `raw`   | `dict`       | 原始响应数据   |

## Models

### `models.list()` — 获取模型列表

```python theme={null}
models = await client.models.list()

for m in models:
    print(f"{m['id']}: {m['name']}")
    caps = m.get("capabilities", {})
    print(f"  工具: {'✅' if caps.get('supportsTools') else '❌'}")
    print(f"  视觉: {'✅' if caps.get('supportsVision') else '❌'}")
```

## Events

### `events.report()` — 上报自定义事件

```python theme={null}
result = await client.events.report(
    "image_generation",
    idempotency_key="order_123_gen_1",
    metadata={"style": "watercolor"},
)
print(f"扣费: {result['charged']}")
print(f"剩余: {result['balance_remaining']}")
```

| 参数                | 类型     | 必填 | 说明           |
| ----------------- | ------ | -- | ------------ |
| `event_name`      | `str`  | ✅  | 事件名称         |
| `idempotency_key` | `str`  | 否  | 幂等键（不传则自动生成） |
| `metadata`        | `dict` | 否  | 附加元数据        |

<Warning>
  Events API 仅支持 OAuth Token。使用 API Key 调用会返回 401 错误。
</Warning>

## 错误处理

SDK 会在 API 返回 4xx/5xx 状态码时抛出 `ProfyApiError`：

```python theme={null}
from profy import Profy, ProfyApiError

async with Profy(api_key="sk-pro-your-key") as client:
    try:
        result = await client.agents.run("non-existent", "你好")
    except ProfyApiError as e:
        print(f"HTTP 状态码: {e.status_code}")
        print(f"错误信息: {e.body}")
        if e.status_code == 404:
            print("专家 不存在")
        elif e.status_code == 403:
            print("访问被拒绝")
        elif e.status_code == 429:
            print("速率限制，请稍后重试")
```

### 超时处理

```python theme={null}
import httpx
from profy import Profy

async with Profy(
    api_key="sk-pro-your-key",
    timeout=httpx.Timeout(60.0, connect=5.0),
) as client:
    try:
        result = await client.agents.run("my-expert", "复杂任务")
    except httpx.TimeoutException:
        print("请求超时")
```

`agents.run()` 在超时时会尽量返回已收到的部分结果（`result.partial = True`），而不是直接抛出异常。

## 同步客户端

`ProfySync` 提供完全相同的 API，但使用同步方法：

```python theme={null}
from profy import ProfySync

with ProfySync(api_key="sk-pro-your-key") as client:
    # Agents
    result = client.agents.run("my-expert", "你好")
    for chunk in client.agents.run_stream("my-expert", "你好"):
        print(chunk.delta, end="")

    # Chat
    resp = client.chat.completions.create(
        model="deepseek-v3",
        messages=[{"role": "user", "content": "你好"}],
    )

    # Models
    models = client.models.list()
```

<Note>
  `ProfySync` 不包含 `events` 命名空间，因为 Events API 需要 OAuth Token，通常在异步的 Web 应用中使用。
</Note>

## 完整示例：多轮对话 Bot

```python theme={null}
import asyncio
from profy import Profy, ProfyApiError

async def chat_bot():
    async with Profy(api_key="sk-pro-your-key") as client:
        session_id = None
        print("开始对话（输入 'quit' 退出）\n")

        while True:
            user_input = input("你: ")
            if user_input.lower() == "quit":
                break

            try:
                print("AI: ", end="", flush=True)
                async for chunk in client.agents.run_stream(
                    "my-expert",
                    user_input,
                    session_id=session_id,
                ):
                    if chunk.type == "run.in_progress":
                        session_id = chunk.raw.get("session_id")
                    elif chunk.type == "output.text.delta":
                        print(chunk.delta, end="", flush=True)
                print()
            except ProfyApiError as e:
                print(f"\n错误: {e.body}")

asyncio.run(chat_bot())
```

## 下一步

<CardGroup cols={2}>
  <Card title="TypeScript SDK" icon="js" href="/zh/developers/sdk-typescript">
    TypeScript SDK 指南
  </Card>

  <Card title="API 参考" icon="code" href="/zh/developers/api/agents-run">
    完整 API 文档
  </Card>
</CardGroup>
