deerflow2/docs/SKILL_PROXY_MIGRATION_GUIDE.md

6.2 KiB

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):

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

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