feat(docs): add Skill Proxy Migration Guide for transitioning to gateway-based API calls
This commit is contained in:
parent
f677c653bd
commit
1ffe32fe00
|
|
@ -0,0 +1,203 @@
|
|||
# Skill Proxy Migration Guide (via Gateway)
|
||||
|
||||
This document explains how to migrate a skill script from directly calling a third-party API to using DeerFlow Gateway's transparent proxy, with unified billing orchestration (reserve/finalize).
|
||||
|
||||
Applicable scenarios:
|
||||
- Async third-party task skills (image/video/audio generation, etc.)
|
||||
- Existing scripts that directly call providers (for example, RunningHub)
|
||||
|
||||
## 1. Migration Goals
|
||||
|
||||
1. The skill no longer calls third-party domains directly.
|
||||
2. The skill no longer manages third-party API keys itself.
|
||||
3. All requests go through `/api/proxy/{provider}/...`.
|
||||
4. Gateway handles:
|
||||
- API key injection
|
||||
- Idempotent submit deduplication
|
||||
- Billing reserve/finalize orchestration
|
||||
- Query terminal-state detection and settlement
|
||||
|
||||
## 2. Core Principles
|
||||
|
||||
1. Keep provider names stable (for example, `runninghub`); do not encode model paths in provider names.
|
||||
2. Only submit requests should carry `X-Idempotency-Key`; query requests should not.
|
||||
3. Use `X-Thread-Id` as a common context header whenever available.
|
||||
4. Use shorthand dot-paths in config extraction fields:
|
||||
- Correct: `taskId`, `status`, `usage.thirdPartyConsumeMoney`
|
||||
- Incorrect: `$.taskId`, `'$'.taskId`
|
||||
|
||||
## 3. Skill Script Migration Steps
|
||||
|
||||
The examples below assume Python + requests.
|
||||
|
||||
### Step 1: Add gateway config loaders
|
||||
|
||||
Add:
|
||||
- `load_skill_env()`: loads skill-local `.env`
|
||||
- `get_gateway_config()`: reads
|
||||
- `DEER_FLOW_GATEWAY_URL` (default `http://host.docker.internal:8001`)
|
||||
- `RUNNINGHUB_PROXY_PROVIDER` (default `runninghub`)
|
||||
|
||||
### Step 2: Centralize proxy headers
|
||||
|
||||
Implement:
|
||||
- `build_proxy_headers(include_idempotency: bool = False)`
|
||||
- always sets `Content-Type: application/json`
|
||||
- optionally sets `X-Thread-Id`
|
||||
- sets `X-Idempotency-Key` only when `include_idempotency=True`
|
||||
|
||||
### Step 3: Route submit calls through gateway
|
||||
|
||||
Replace:
|
||||
- `https://www.runninghub.cn/openapi/v2/<model-path>`
|
||||
|
||||
With:
|
||||
- `{gateway}/api/proxy/{provider}/openapi/v2/<model-path>`
|
||||
|
||||
And use:
|
||||
- `headers=build_proxy_headers(include_idempotency=True)`
|
||||
|
||||
### Step 4: Route query calls through gateway
|
||||
|
||||
Replace:
|
||||
- `https://www.runninghub.cn/openapi/v2/query`
|
||||
|
||||
With:
|
||||
- `{gateway}/api/proxy/{provider}/openapi/v2/query`
|
||||
|
||||
And use:
|
||||
- `headers=build_proxy_headers()`
|
||||
|
||||
### Step 5: Remove third-party API key logic from the skill
|
||||
|
||||
Remove:
|
||||
- Loading `RUNNINGHUB_API_KEY` in the script
|
||||
- Building `Authorization: Bearer ...` in the script
|
||||
|
||||
Reason: third-party credentials are injected by gateway.
|
||||
|
||||
### Step 6: Keep essential error handling
|
||||
|
||||
Recommended checks:
|
||||
- `response.raise_for_status()`
|
||||
- submit fallback when `taskId` is missing
|
||||
- query loop timeout/failure handling
|
||||
|
||||
## 4. Proxy Config Migration (config.yaml)
|
||||
|
||||
Configure submit/query routes under `third_party_proxy.providers.<provider>`.
|
||||
|
||||
Example (RunningHub):
|
||||
|
||||
```yaml
|
||||
third_party_proxy:
|
||||
enabled: true
|
||||
providers:
|
||||
runninghub:
|
||||
base_url: https://www.runninghub.cn
|
||||
api_key_env: RUNNINGHUB_API_KEY
|
||||
api_key_header: Authorization
|
||||
api_key_prefix: "Bearer "
|
||||
timeout_seconds: 30.0
|
||||
frozen_amount: 10.0
|
||||
frozen_type: 2
|
||||
submit_routes:
|
||||
- path_pattern: "/openapi/v2/rhart-image/z-image/turbo-lora"
|
||||
task_id_jsonpath: "taskId"
|
||||
frozen_amount: 0.03
|
||||
frozen_type: 2
|
||||
- path_pattern: "/openapi/v2/vidu/text-to-video-q3-turbo"
|
||||
task_id_jsonpath: "taskId"
|
||||
frozen_amount: 11.2
|
||||
frozen_type: 2
|
||||
query_routes:
|
||||
- path_pattern: "/openapi/v2/query"
|
||||
request_task_id_jsonpath: "taskId"
|
||||
status_jsonpath: "status"
|
||||
success_values: ["SUCCESS"]
|
||||
failure_values: ["FAILED", "CANCELLED"]
|
||||
usage_jsonpath: "usage.thirdPartyConsumeMoney"
|
||||
```
|
||||
|
||||
Notes:
|
||||
- Provider-level `frozen_amount`/`frozen_type` are defaults.
|
||||
- Submit-route values can override defaults per model endpoint.
|
||||
|
||||
## 5. Reusable Function Template
|
||||
|
||||
```python
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from dotenv import dotenv_values
|
||||
|
||||
|
||||
def load_skill_env() -> dict[str, str]:
|
||||
"""Load skill-local .env values."""
|
||||
env_path = Path(__file__).parent.parent / ".env"
|
||||
return {
|
||||
key: value
|
||||
for key, value in dotenv_values(env_path).items()
|
||||
if isinstance(key, str) and isinstance(value, str)
|
||||
}
|
||||
|
||||
|
||||
def get_gateway_config() -> tuple[str, str]:
|
||||
"""Get DeerFlow gateway base URL and proxy provider name."""
|
||||
env_vars = load_skill_env()
|
||||
gateway_url = os.getenv("DEER_FLOW_GATEWAY_URL") or env_vars.get(
|
||||
"DEER_FLOW_GATEWAY_URL",
|
||||
"http://host.docker.internal:8001",
|
||||
)
|
||||
provider = os.getenv("RUNNINGHUB_PROXY_PROVIDER") or env_vars.get(
|
||||
"RUNNINGHUB_PROXY_PROVIDER",
|
||||
"runninghub",
|
||||
)
|
||||
return gateway_url.rstrip("/"), provider
|
||||
|
||||
|
||||
def build_proxy_headers(*, include_idempotency: bool = False) -> dict[str, str]:
|
||||
headers = {"Content-Type": "application/json"}
|
||||
thread_id = os.getenv("THREAD_ID")
|
||||
if thread_id:
|
||||
headers["X-Thread-Id"] = thread_id
|
||||
if include_idempotency:
|
||||
from uuid import uuid4
|
||||
headers["X-Idempotency-Key"] = str(uuid4())
|
||||
return headers
|
||||
```
|
||||
|
||||
## 6. Common Pitfalls
|
||||
|
||||
### 6.1 Response contains taskId but extraction fails
|
||||
|
||||
Usually caused by wrong config path syntax:
|
||||
- Wrong: `$.taskId` or `'$'.taskId`
|
||||
- Right: `taskId`
|
||||
|
||||
### 6.2 Why query should not include X-Idempotency-Key
|
||||
|
||||
Idempotency keys are for submit deduplication (to avoid duplicate task creation). Query requests are polling and should not generate new idempotency keys.
|
||||
|
||||
### 6.3 Sandbox cannot reach gateway
|
||||
|
||||
For Docker-based sandbox execution, use:
|
||||
- `DEER_FLOW_GATEWAY_URL=http://host.docker.internal:8001`
|
||||
|
||||
## 7. Validation Checklist
|
||||
|
||||
1. No direct third-party domain calls remain in the skill script.
|
||||
2. The skill script no longer reads third-party API keys.
|
||||
3. Submit uses proxy URL + `include_idempotency=True`.
|
||||
4. Query uses proxy URL + `include_idempotency=False`.
|
||||
5. Config extraction fields use shorthand dot-paths only.
|
||||
6. Submit returns `taskId`, then query reaches `RUNNING/SUCCESS`.
|
||||
7. Gateway logs show submit/query route hits and finalize flow.
|
||||
|
||||
## 8. Reference Implementations
|
||||
|
||||
- `skills/public/image-generation/scripts/generate.py`
|
||||
- `skills/public/video-generation/scripts/generate.py`
|
||||
- `backend/app/gateway/routers/third_party.py`
|
||||
- `backend/app/gateway/third_party_proxy/proxy.py`
|
||||
- `third_party_proxy` section in `config.yaml`
|
||||
Loading…
Reference in New Issue