# 最佳实践和常见问题

## 最佳实践

### 1. 环境变量加载顺序

**⚠️ 最重要的规则：必须在导入 SDK 之前加载环境变量**

```python
# ✅ 正确
from dotenv import load_dotenv
load_dotenv()  # 先加载环境变量
from hezor_common.transfer.datahub_sdk import DatahubSDK  # 再导入 SDK

# ❌ 错误
from hezor_common.transfer.datahub_sdk import DatahubSDK
from dotenv import load_dotenv
load_dotenv()  # 太晚了，SDK 已经使用了默认值
```

**原因：** SDK 在模块加载时会读取环境变量创建默认值，如果此时环境变量还没有设置，就会使用硬编码的默认值。

---

### 2. 使用上下文管理器

**✅ 推荐：** 使用 `async with` 自动管理资源

```python
async with DatahubSDK() as sdk:
    result = await sdk.make_call("query")
    # 自动清理资源
```

**❌ 不推荐：** 手动管理资源

```python
sdk = DatahubSDK()
await sdk.__aenter__()
try:
    result = await sdk.make_call("query")
finally:
    await sdk.__aexit__(None, None, None)
```

---

### 3. 错误处理

始终处理可能的异常：

```python
async with DatahubSDK() as sdk:
    try:
        result = await sdk.make_call("query")
    except ValueError as e:
        # 未找到匹配的工具
        print(f"工具搜索失败: {e}")
    except Exception as e:
        # 其他错误（网络、参数构建、执行失败等）
        print(f"执行失败: {e}")
```

---

### 4. 参数提示的编写

**✅ 清晰具体：**

```python
result = await sdk.make_call(
    query="query_daily_avg_received_amount",
    context="查询 2024年10月 的日均实收金额，门店代码为 S001"
)
```

**❌ 过于模糊：**

```python
result = await sdk.make_call(
    query="query_daily_avg_received_amount",
    context="查询数据"
)
```

**提示编写原则：**
- 包含所有必要的参数信息
- 使用明确的值而不是模糊的描述
- 包含时间范围、筛选条件等关键信息

---

### 5. 配置管理

**✅ 推荐：** 集中管理配置

```python
from dotenv import load_dotenv
from hezor_common.transfer.datahub_sdk.env_config import DataHubEnvConfig

load_dotenv()
config = DataHubEnvConfig()

async with DatahubSDK(
    base_url=config.datahub_api_base_url,
    api_key=config.datahub_api_key
) as sdk:
    # 使用配置
    pass
```

**❌ 不推荐：** 硬编码配置

```python
async with DatahubSDK(
    base_url="http://10.8.98.9:12580",  # 硬编码
    api_key="test-api-key"  # 硬编码
) as sdk:
    pass
```

---

### 6. 私钥文件路径处理

处理相对路径和绝对路径：

```python
from pathlib import Path

# 获取私钥路径
private_key_path = Path(config.datahub_header_pk_filepath)

# 处理相对路径
if not private_key_path.is_absolute():
    private_key_path = Path.cwd() / private_key_path

# 检查文件是否存在
if private_key_path.exists():
    async with DatahubSDK(
        private_key_path=str(private_key_path),
        password=config.datahub_header_pk_password.encode()
    ) as sdk:
        result = await sdk.make_call("query")
else:
    print(f"私钥文件不存在: {private_key_path}")
```

---

### 7. 日志配置

配置日志以便调试：

```python
import logging

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)

# SDK 会自动输出 INFO 级别的日志
async with DatahubSDK() as sdk:
    result = await sdk.make_call("query")
```

---

### 8. 重试策略

根据场景选择合适的重试次数：

```python
# 关键业务：增加重试次数
result = await sdk.make_call(
    query="critical_query",
    max_retries=5
)

# 非关键业务：减少重试次数
result = await sdk.make_call(
    query="optional_query",
    max_retries=1
)
```

---

## 常见问题

### Q1: 为什么 SDK 没有使用 .env 文件中的配置？

**A:** 必须在导入 SDK 之前调用 `load_dotenv()`：

```python
# 正确的顺序
from dotenv import load_dotenv
load_dotenv()  # 1. 先加载
from hezor_common.transfer.datahub_sdk import DatahubSDK  # 2. 再导入
```

**原因：** SDK 的 `constants.py` 模块在导入时会读取环境变量。如果此时环境变量还没加载，就会使用默认值。

---

### Q2: 如何查看 Agent 构建参数的过程？

**A:** 启用调试模式：

```python
result = await sdk.make_call(
    query="query",
    agent_kwargs={"debug_mode": True}
)
```

这会输出：
- Agent 的推理过程
- 构建的参数
- 参数验证结果
- 重试信息

---

### Q3: make_call 失败后如何重试？

**A:** 使用 `max_retries` 参数：

```python
result = await sdk.make_call(
    query="query",
    max_retries=5  # 失败后最多重试 5 次
)
```

注意：`max_retries` 控制整个工作流程（搜索 → 构建参数 → 执行）的重试。

---

### Q4: 如何自定义超时时间？

**A:** 在创建 SDK 时设置：

```python
async with DatahubSDK(timeout=60.0) as sdk:  # 60 秒超时
    result = await sdk.make_call("query")
```

---

### Q5: 支持哪些认证方式？

**A:** 支持三种：

1. **API Key 认证**
   ```python
   async with DatahubSDK(api_key="your-api-key") as sdk:
       pass
   ```

2. **MetaInfo JWT 认证**
   ```python
   async with DatahubSDK(
       meta_info=meta_info,
       private_key_path="private_key.pem",
       password=b"password"
   ) as sdk:
       pass
   ```

3. **组合认证**
   ```python
   async with DatahubSDK(
       api_key="your-api-key",
       meta_info=meta_info,
       private_key_path="private_key.pem"
   ) as sdk:
       pass
   ```

详见 [认证方式](authentication.md)

---

### Q6: 如何处理 API 返回的列表数据？

**A:** `ExecuteResponse.data` 支持 `dict` 和 `list`：

```python
result = await sdk.make_call("query")

if isinstance(result.data, list):
    for item in result.data:
        print(item)
elif isinstance(result.data, dict):
    print(result.data["key"])
```

---

### Q7: 如何切换不同的 LLM 模型？

**A:** 传入自定义模型：

```python
from hezor_common.utilities.agentic.deps import OpenAIChat

model = OpenAIChat(
    id="gpt-4o",
    api_key="sk-...",
    base_url="https://api.openai.com/v1"
)

async with DatahubSDK(model=model) as sdk:
    result = await sdk.make_call("query")
```

---

### Q8: 参数构建一直失败怎么办？

**可能原因：**
1. 参数提示不够明确
2. LLM 模型性能不足
3. 工具参数定义不清晰

**解决方案：**

1. **改进参数提示：**
   ```python
   # 提供更详细的上下文
   result = await sdk.make_call(
       query="query_name",
       context="包含所有必要参数值的详细描述"
   )
   ```

2. **使用更强大的模型：**
   ```python
   from hezor_common.utilities.agentic.deps import OpenAIChat
   
   model = OpenAIChat(id="gpt-4", ...)
   async with DatahubSDK(model=model) as sdk:
       result = await sdk.make_call("query")
   ```

3. **启用调试模式查看详情：**
   ```python
   result = await sdk.make_call(
       query="query",
       agent_kwargs={"debug_mode": True}
   )
   ```

---

### Q9: 如何在生产环境中使用？

**生产环境清单：**

- ✅ 使用环境变量配置（不要硬编码）
- ✅ 使用真实的 API Key（不要使用 test-api-key）
- ✅ 配置合适的超时时间
- ✅ 实现完整的错误处理
- ✅ 配置日志记录
- ✅ 使用 HTTPS（如果可用）
- ✅ 保护私钥文件安全
- ✅ 定期轮换 API Key

**示例：**

```python
import logging
from dotenv import load_dotenv
from hezor_common.transfer.datahub_sdk import DatahubSDK
from hezor_common.transfer.datahub_sdk.env_config import DataHubEnvConfig

# 配置日志
logging.basicConfig(level=logging.INFO)

# 加载环境变量
load_dotenv()
config = DataHubEnvConfig()

async def production_call():
    try:
        async with DatahubSDK(
            base_url=config.datahub_api_base_url,
            api_key=config.datahub_api_key,
            timeout=60.0
        ) as sdk:
            result = await sdk.make_call(
                query="query_name",
                context="detailed context",
                max_retries=3
            )
            return result
    except Exception as e:
        logging.error(f"DataHub 调用失败: {e}")
        # 实现降级逻辑或报警
        raise
```

---

### Q10: 私钥密码可以为空吗？

**A:** 可以，但不推荐。

```python
# 私钥没有密码保护
async with DatahubSDK(
    meta_info=meta_info,
    private_key_path="private_key.pem",
    password=None  # 或不传此参数
) as sdk:
    pass
```

**安全建议：**
- ✅ 生产环境应使用密码保护私钥
- ✅ 私钥文件权限设置为 600
- ✅ 不要将私钥提交到版本控制
- ✅ 使用环境变量存储密码

---

## 性能优化

### 1. 复用 SDK 实例

在同一个上下文中多次调用：

```python
async with DatahubSDK() as sdk:
    # 多次调用，复用连接
    result1 = await sdk.make_call("query1")
    result2 = await sdk.make_call("query2")
    result3 = await sdk.make_call("query3")
```

### 2. 批量调用

使用 `make_call_multi()` 而不是多次 `make_call()`：

```python
# ✅ 推荐：批量调用
results = await sdk.make_call_multi(
    query="查询品牌",
    top_k=3
)

# ❌ 不推荐：多次单独调用
for query in queries:
    result = await sdk.make_call(query)
```

---

## 下一步

- 📚 [API 参考](api_reference.md) - 完整的方法文档
- 📖 [基本用法](basic_usage.md) - 学习基本使用方法
- 🔙 [返回文档首页](README.md)
