From 6931313f7198057a8c005f278ffc1b608e0343cb Mon Sep 17 00:00:00 2001 From: Matt Miller Date: Thu, 27 Aug 2026 14:40:03 -0700 Subject: [PATCH] docs(router): move the Router pages to the /v2/models namespace (cloud@9e788a8) Comfy Router's model routes moved from /v1/models to /v2/models upstream (cloud #7646) and the idempotency contract is now declared in the spec (cloud #7655). This carries the comfy-pr-bot sync at cloud@9e788a8 (#1535) onto the consolidated Router pages: every route in the quickstart, the reference and the limitations page now reads /v2/models, the quickstart gains the 'Retrying safely with your own key' section, the limitations page reflects that Idempotency-Key, 409 and Idempotent-Replayed are in the contract, and the generated reference picks up the new header, response rows and error-bucket text. ja/zh/ko carry the same changes with their translation hashes re-stamped. --- api-reference/comfy-router/limitations.mdx | 24 ++++-- api-reference/comfy-router/quickstart.mdx | 69 ++++++++++++++-- api-reference/comfy-router/reference.mdx | 38 +++++---- ja/api-reference/comfy-router/limitations.mdx | 28 +++---- ja/api-reference/comfy-router/quickstart.mdx | 82 +++++++++++++++---- ja/api-reference/comfy-router/reference.mdx | 50 ++++++----- ko/api-reference/comfy-router/limitations.mdx | 28 +++---- ko/api-reference/comfy-router/quickstart.mdx | 71 ++++++++++++---- ko/api-reference/comfy-router/reference.mdx | 50 ++++++----- zh/api-reference/comfy-router/limitations.mdx | 28 +++---- zh/api-reference/comfy-router/quickstart.mdx | 71 ++++++++++++---- zh/api-reference/comfy-router/reference.mdx | 50 ++++++----- 12 files changed, 404 insertions(+), 185 deletions(-) diff --git a/api-reference/comfy-router/limitations.mdx b/api-reference/comfy-router/limitations.mdx index 1b5454ba2..5c3ef39bf 100644 --- a/api-reference/comfy-router/limitations.mdx +++ b/api-reference/comfy-router/limitations.mdx @@ -5,7 +5,7 @@ description: "What Comfy Router does not do today, what to use instead where an **Comfy Router is not generally available yet.** The routes referenced below -(`POST /v1/models/{provider}/{model}` and its catalog and schema siblings) are +(`POST /v2/models/{provider}/{model}` and its catalog and schema siblings) are not serving requests yet: an authenticated call answers `404` today. This page describes the contract they will serve, published ahead of that rollout so an integration can be written against a known shape. Everything below is a @@ -31,7 +31,7 @@ Each row links to the section that explains it. **Deliberate** means the limit i ## No queued submission -There is one way to run a model: `POST /v1/models/{provider}/{model}`, which holds the connection until the generation finishes and returns the result in the response. There is no endpoint that accepts a job, hands you an identifier and lets you collect the result later, and no callback or webhook on completion. +There is one way to run a model: `POST /v2/models/{provider}/{model}`, which holds the connection until the generation finishes and returns the result in the response. There is no endpoint that accepts a job, hands you an identifier and lets you collect the result later, and no callback or webhook on completion. **What to do instead.** For most models this is a non-issue: keep the connection open and read the result. A fast image model returns in a few seconds; a long video generation can run for minutes, and Router will hold the connection for it. Set a generous client read timeout, above [Router's own deadline](#calls-are-cut-off-at-a-server-deadline), and treat the call as long-running rather than as a fast request. If your architecture genuinely cannot hold a connection open (a serverless function with a short execution ceiling, a browser tab you expect the user to close), then run the call from a worker you control that can, or use a partner-proxy route for a provider that exposes its own submit-and-poll pair. See [the last section](#router-does-not-cover-every-partner-operation). @@ -49,16 +49,24 @@ A Router response tells you what the model produced, and its contract says nothi Router does not keep a resumable record of an in-flight call. There is no status route, no job identifier, and nothing to reconnect to: if the connection drops mid-call (a client crash, a network partition, a deploy that restarts your process), the response is gone, and the call is not something you can ask about afterwards. Whether the *generation* completed and was charged is a separate question from whether you received it, and losing the connection does not reliably answer either. -**What to do instead.** Send an `Idempotency-Key` header on every call. It does not make a lost call resumable, but it makes retrying one safe. Router reserves the key for the duration of the call, and when the call actually reached you with an answer it records that response against the key for 24 hours; retrying with the **same** key then replays the recorded response instead of dispatching, and re-charging, the provider a second time, marked `Idempotent-Replayed: true` so you can tell a replay from a fresh run. Generate a fresh key per logical call, not per attempt; the same key presented with a *different* request body is a `409` rather than a silent overwrite. +**What to do instead.** Send an `Idempotency-Key` header on every call: the [quickstart](/api-reference/comfy-router/quickstart#retrying-safely-with-your-own-key) has the mechanics, including the step that is easiest to skip: persist the key before you send the request. It does not make a lost call resumable, but it makes retrying one safe. Router reserves the key for the duration of the call, and when the call actually reached you with an answer it records that response against the key for 24 hours; retrying with the **same** key then replays the recorded response instead of dispatching, and re-charging, the provider a second time, marked `Idempotent-Replayed: true` so you can tell a replay from a fresh run. Generate a fresh key per logical call, not per attempt; the same key presented with a *different* request (a different body, model path, query string or method) is a `409` rather than a silent overwrite. -Be precise about what that buys you, because it is a **billing** property and not a delivery one: **a key is charged at most once.** It is not a promise that a key is dispatched to the provider at most once. Router holds a key against an answer you actually received; the outcomes that charged you nothing release it so the call can be made again. A `5xx`, a `408`/`425`/`429`, and (this is the one that matters here) a call where nothing reached you at all: each of those releases the key, and a retry with it genuinely re-runs and re-dispatches the provider. +Be precise about what that buys you, because it is a **billing** property and not a delivery one: **a key is charged at most once.** It is not a promise that a key is dispatched to the provider at most once. Router holds a key against an answer you actually received; the outcomes that charged you nothing release it so the call can be made again. A `5xx`, a `408`/`425`/`429`, and (this is the one that matters here) a call where nothing reached you at all: each of those releases the key, and a retry with it genuinely re-runs and re-dispatches the provider. The one `5xx` that does *not* release is the cut-off with something to collect: a `deadline_exceeded` `504` that carries `Retry-After` means the provider had already accepted the generation when Router stopped waiting, and Router parks the key against that running job instead: re-send the **same** key after `Retry-After` to collect it, because a fresh key there is a second billed generation. A `504` without `Retry-After` had nothing to park and releases like the rest. **So a dropped connection is the case idempotency does *not* rescue.** A connection lost mid-call usually means no response was ever committed to you, which is exactly the release path above: retrying with the same key starts a fresh run rather than handing you the result you missed, and if the original generation had already been dispatched the provider may run it a second time. That is the right default: an unbilled call you never received should be re-runnable, but plan for "retry produces a new run", not "retry collects the lost one". When Router *does* hold something for the key, the retry is answered rather than re-run: either the original response replayed, or a `409` explaining why it cannot be. A retry sent while the original is still in flight is a `409` carrying `Retry-After`, so wait and re-send the same key. A retry against a call that completed but whose response Router could not keep a faithful copy of is also a `409`, and that is not only the oversized-response case: a response past the replay cap, a handler that failed or panicked after answering, and a write to you that failed or came up short all record the key as consumed-but-not-replayable and return the same `409`. Do not go hunting for a size problem when you see it. The guidance in every one of those cases is the same: use a **new** key. The original completed and was charged, and Router will neither invent its response nor re-run it under the old key. -**Not yet in the generated contract.** The `Idempotency-Key` request header, the `409` response and the `Idempotent-Replayed` and `Retry-After` response headers described here are not declared on `POST /v1/models/{provider}/{model}` in the OpenAPI contract the reference is generated from, so they do not appear in the generated API reference and the SDKs do not model them. Send and read them yourself until they do. +**In the contract, with one gap.** The `Idempotency-Key` request header, the +`409` response and the `Idempotent-Replayed` and `Retry-After` response headers +described here are declared on `POST /v2/models/{provider}/{model}`, so they +appear in the generated [API reference](/api-reference/comfy-router/reference) and in the +specification the SDKs vendor, so an SDK picks them up when it regenerates. The +gap: `Retry-After` is +declared on the `409` and the `504` but **not** on the `rate_limited` `429` +described [below](#requests-are-rate-limited-per-caller), which sends it too: +read it there without waiting for the contract to say so. **Status: not yet.** Durable, resumable execution is expected to arrive with the queued path, which is where a request record has somewhere to live. Idempotent retry is the answer today and is not a stopgap; it is worth wiring in regardless. @@ -77,17 +85,17 @@ Do not confuse it with the other `504`. `provider_timeout` is the partner failin ## Requests are rate limited per caller -Router bounds two different things about your traffic, and they answer with two different buckets on the same `429`. The concurrency limit caps how many calls you have **in flight** at once and answers `concurrency_limit_exceeded`; it clears the moment one of your own calls finishes, so retrying in seconds is right. The rate limit caps how **often** you may hit the Router surface at all (`POST /v1/models/{provider}/{model}` and the three catalog reads under `/v1/models` alike, whether the call ran a model or was refused before it could) and answers `rate_limited`. That one is an allowance that refills continuously over a one-minute window, so nothing you do drains it early: the response carries a `Retry-After` header with the seconds to wait, and `detail` names the window. Branch on `X-Comfy-Error-Type`, never on the status alone. +Router bounds two different things about your traffic, and they answer with two different buckets on the same `429`. The concurrency limit caps how many calls you have **in flight** at once and answers `concurrency_limit_exceeded`; it clears the moment one of your own calls finishes, so retrying in seconds is right. The rate limit caps how **often** you may hit the Router surface at all (`POST /v2/models/{provider}/{model}` and the three catalog reads under `/v2/models` alike, whether the call ran a model or was refused before it could) and answers `rate_limited`. That one is an allowance that refills continuously over a one-minute window, so nothing you do drains it early: the response carries a `Retry-After` header with the seconds to wait, and `detail` names the window. Branch on `X-Comfy-Error-Type`, never on the status alone. The limit is keyed on the authenticated caller, not on the source address, so it follows your credential across hosts. A call that runs on your own provider key (bring-your-own-key) is exempt: you own that throughput. The allowance is a server-side configuration value rather than a published constant, and this page deliberately does not quote it; design for backoff, not for a number. -**What to do instead.** Honour `Retry-After`: a retry inside it lands on the same refusal. Fetch `GET /v1/models` and a model's `openapi.json` once and cache them for the life of your process rather than re-reading them ahead of every call; they change only on a deploy. A client that keeps a request identifier from a `429` has the artifact support can trace. +**What to do instead.** Honour `Retry-After`: a retry inside it lands on the same refusal. Fetch `GET /v2/models` and a model's `openapi.json` once and cache them for the life of your process rather than re-reading them ahead of every call; they change only on a deploy. A client that keeps a request identifier from a `429` has the artifact support can trace. **Status: deliberate.** A per-caller bound on request rate has to exist for the same reason the deadline does. The number is tunable; the existence of the limit will not go away. ## No progress while a call runs -`POST /v1/models/{provider}/{model}` returns exactly once, at the end. There is no streaming response, no server-sent events, no percentage, no partial or preview frame. This holds even for partners whose own API is submit-and-poll: Router does that polling internally, inside your one call, and the intermediate states it sees are not forwarded to you. From the outside, a three-second image and a six-minute video are the same shape: one request, one response, nothing in between. +`POST /v2/models/{provider}/{model}` returns exactly once, at the end. There is no streaming response, no server-sent events, no percentage, no partial or preview frame. This holds even for partners whose own API is submit-and-poll: Router does that polling internally, inside your one call, and the intermediate states it sees are not forwarded to you. From the outside, a three-second image and a six-minute video are the same shape: one request, one response, nothing in between. **What to do instead.** On Router today, nothing: show an indeterminate progress state rather than a percentage you cannot source. If progress is a hard requirement for a specific provider, check whether that provider's partner-proxy routes expose their own polling or streaming and use those directly: a few do, and they are unchanged and fully supported. diff --git a/api-reference/comfy-router/quickstart.mdx b/api-reference/comfy-router/quickstart.mdx index 49adc1839..ec706a576 100644 --- a/api-reference/comfy-router/quickstart.mdx +++ b/api-reference/comfy-router/quickstart.mdx @@ -5,7 +5,7 @@ description: "From nothing to a generated image in about five minutes, in Python **Comfy Router is not generally available yet.** The routes below -(`POST /v1/models/{provider}/{model}` and its catalog and schema siblings) are +(`POST /v2/models/{provider}/{model}` and its catalog and schema siblings) are not serving requests yet: an authenticated call answers `404` today. This page documents the contract they will serve, and is published ahead of that rollout so the integration is ready to write against. It is not a description of behaviour @@ -14,7 +14,7 @@ you can exercise right now. Comfy Router runs partner models behind one host, one credential and one route shape. This page is the shortest complete path to a generated image: install a client, set a key, send one request, read the result, and see what the first failure looks like before you hit it. -Base URL: `https://api.comfy.org`. The route is `POST /v1/models/{provider}/{model}`, the request body is the model's own native JSON input, and a `200` carries the model's own native JSON output. Router does not wrap either, so a call you already have written against the partner's API becomes a Router call by changing the host. +Base URL: `https://api.comfy.org`. The route is `POST /v2/models/{provider}/{model}`, the request body is the model's own native JSON input, and a `200` carries the model's own native JSON output. Router does not wrap either, so a call you already have written against the partner's API becomes a Router call by changing the host. ## Why this page uses `bfl/flux-2-pro` @@ -48,7 +48,7 @@ Keys are per workspace and carry that workspace's model entitlements and credit The shortest possible call, for scripts, smoke tests and copy-paste into a terminal: ```bash -curl https://api.comfy.org/v1/models/bfl/flux-2-pro \ +curl https://api.comfy.org/v2/models/bfl/flux-2-pro \ -H "X-API-Key: $COMFY_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ @@ -117,7 +117,7 @@ def run(model: str, arguments: dict, idempotency_key: str) -> dict: # provider a second time. Reuse the SAME key when retrying one logical # call; generate a new one for a new call. response = httpx.post( - f"{BASE_URL}/v1/models/{model}", + f"{BASE_URL}/v2/models/{model}", headers={ "X-API-Key": os.environ["COMFY_API_KEY"], "Idempotency-Key": idempotency_key, @@ -229,7 +229,7 @@ async function run( // Idempotency-Key makes a retry safe on a PAID call: Router replays the // original response for 24h instead of dispatching (and billing) the provider // a second time. Reuse the SAME key when retrying one logical call. - const response = await fetch(`${BASE_URL}/v1/models/${model}`, { + const response = await fetch(`${BASE_URL}/v2/models/${model}`, { method: "POST", headers: { "X-API-Key": API_KEY, @@ -293,13 +293,66 @@ That body carries no `error_type` field of its own, so on a `422` the `X-Comfy-E `X-Comfy-Request-Id` is on every response (success, `4xx` and `5xx` alike) and is the id to quote in a support request. Both samples attach it to the exception rather than making you re-run with header logging on to find it. +## Retrying safely with your own key + +Both samples above send an `Idempotency-Key`. It is worth a section of its own, because the header only does its job if you handle the key correctly, and the step that makes the difference happens before the request is even sent. + +**Bring your own key.** Router does not mint one for you. Generate a fresh key per *logical call* (a UUID is the intended shape) and reuse that same key for every retry *of that call*. A new key per attempt buys you nothing; a key reused across two genuinely different calls is a `409`, because the same key with a different request (a different body, but also a different model path, query string or method) is a conflict rather than a silent overwrite. + +**Persist it before you send.** Write the key somewhere that outlives the request (the row you are generating for, your job record, your queue message) *before* the `POST` goes out, not after the response comes back. A key that only ever existed in the memory of the process that crashed cannot be resent, and the retry that would have been answered from Router's record becomes a fresh, separately charged run instead. This is the one step that is easy to skip and expensive to skip. + +**Retry with it.** On a retry, Router answers rather than re-runs whenever it still holds state for the key: + +| What you get back | What it means | What to do | +| --- | --- | --- | +| `200` with `Idempotent-Replayed: true` | Router replayed the original response. Not billed again. | Use it: it is the original result. | +| `409` / `concurrency_limit_exceeded` | The original call is still running. | Wait `Retry-After` seconds, re-send **the same key**. | +| `409` / `invalid_input` | The key cannot serve this request: a different request (body, model path, query or method) under the same key, or the original completed and its response cannot be replayed. | Use a **new** key. Do not re-send this one. | +| `504` / `deadline_exceeded` with `Retry-After` | Router stopped holding the connection but still holds a handle to a generation the provider is running. | Wait `Retry-After` seconds, re-send **the same key** to collect it. | + +Continuing the Python sample above: a file stands in for whatever durable store +you already have; the ORDERING is the part that matters, not the mechanism. Persist +the whole request next to the key, not the key alone: a retry has to re-send the +*same* model and arguments, and one rebuilt from memory after a restart that differs +by so much as a whitespace is a `409`, while one sent under a fresh key is a second +billed generation. + +```python +import json +import uuid + +# Persist BEFORE the request, so a crash between here and the response still +# leaves a key (and the exact request it belongs to) you can retry with. +request = { + "model": MODEL, + "arguments": {"prompt": "a red teapot on a windowsill, morning light"}, + "idempotency_key": str(uuid.uuid4()), +} +with open("pending-call.json", "w") as f: + json.dump(request, f) + +result = run(request["model"], request["arguments"], idempotency_key=request["idempotency_key"]) +``` + + +**What the key guarantees is billing, not delivery.** A key is charged **at most +once**. It is not a promise that the key is dispatched at most once, and it does +not make a lost call recoverable: if your connection drops mid-call and nothing +was ever committed to you, the key is released and retrying with it starts a +**fresh run** rather than handing you the result you missed. Plan for "retry +produces a new run" and treat the replay as the happy case, not the guarantee. +Durable, resumable "reconnect and collect" is the queued path Router does not +have yet: see +[limitations](/api-reference/comfy-router/limitations#no-way-to-resume-a-call-you-lost). + + ## Find a model -`bfl/flux-2-pro` is one ID; the catalog is the rest. `GET /v1/models` lists every model Router can run, one page at a time, and each entry is exactly what you need to call it: the `id` you put in the path, its `provider` and `model` segments carried separately, and a `billing` block you can branch on before you spend anything. +`bfl/flux-2-pro` is one ID; the catalog is the rest. `GET /v2/models` lists every model Router can run, one page at a time, and each entry is exactly what you need to call it: the `id` you put in the path, its `provider` and `model` segments carried separately, and a `billing` block you can branch on before you spend anything. ```bash curl -H "X-API-Key: $COMFY_API_KEY" \ - "https://api.comfy.org/v1/models?limit=50" + "https://api.comfy.org/v2/models?limit=50" ``` ```json @@ -321,7 +374,7 @@ Walk it with the cursor, not with an offset: pass `next_cursor` back as `?cursor ```bash curl -H "X-API-Key: $COMFY_API_KEY" \ - https://api.comfy.org/v1/models/bfl/flux-2-pro/openapi.json + https://api.comfy.org/v2/models/bfl/flux-2-pro/openapi.json ``` That is the same document the server validates your call against, served as a standalone OpenAPI document, so what is published and what is enforced cannot disagree. Take any `id` from the catalog above, append `/openapi.json` to its invocation path, and generate against what comes back. diff --git a/api-reference/comfy-router/reference.mdx b/api-reference/comfy-router/reference.mdx index e77f4be01..52cf8c5ff 100644 --- a/api-reference/comfy-router/reference.mdx +++ b/api-reference/comfy-router/reference.mdx @@ -19,11 +19,11 @@ Every endpoint below is authenticated. Send `Authorization: Bearer `. ## Endpoints -### `GET /v1/models` +### `GET /v2/models` **List the models Comfy Router can run.** -Comfy Router's model catalog - one page of the canonical model IDs that `POST /v1/models/{provider}/{model}` accepts. An SDK calls this on cold start to discover what is runnable, and the `model_not_found` suggestions come from the same catalog, so an ID listed here that then 404s on invocation would be worse than either failure alone. That agreement is structural rather than a promise: an entry's `provider` and `model` are the two path segments of the invocation route and reference the SAME schema components that route's path parameters do, and `id` is those two segments joined by `/`. +Comfy Router's model catalog - one page of the canonical model IDs that `POST /v2/models/{provider}/{model}` accepts. An SDK calls this on cold start to discover what is runnable, and the `model_not_found` suggestions come from the same catalog, so an ID listed here that then 404s on invocation would be worse than either failure alone. That agreement is structural rather than a promise: an entry's `provider` and `model` are the two path segments of the invocation route and reference the SAME schema components that route's path parameters do, and `id` is those two segments joined by `/`. **Parameters** @@ -42,7 +42,7 @@ Comfy Router's model catalog - one page of the canonical model IDs that `POST /v | `403` | [`RouterErrorResponse`](#routererrorresponse) | `X-Comfy-Error-Type`, `X-Comfy-Request-Id` | A Router request-level failure - the request never reached the model, or failed for a reason the model itself did not report. The body is `RouterErrorResponse` and the bucket is repeated on `X-Comfy-Error-Type`. | | `503` | [`RouterErrorResponse`](#routererrorresponse) | `X-Comfy-Error-Type`, `X-Comfy-Request-Id` | A Router request-level failure - the request never reached the model, or failed for a reason the model itself did not report. The body is `RouterErrorResponse` and the bucket is repeated on `X-Comfy-Error-Type`. | -### `GET /v1/models/{provider}/{model}` +### `GET /v2/models/{provider}/{model}` **Read one partner model's catalog entry by canonical model ID.** @@ -64,7 +64,7 @@ Per-model detail for a single Comfy Router model, so a caller can check one mode | `404` | [`RouterErrorResponse`](#routererrorresponse) | `X-Comfy-Error-Type`, `X-Comfy-Request-Id` | A Router request-level failure - the request never reached the model, or failed for a reason the model itself did not report. The body is `RouterErrorResponse` and the bucket is repeated on `X-Comfy-Error-Type`. | | `503` | [`RouterErrorResponse`](#routererrorresponse) | `X-Comfy-Error-Type`, `X-Comfy-Request-Id` | A Router request-level failure - the request never reached the model, or failed for a reason the model itself did not report. The body is `RouterErrorResponse` and the bucket is repeated on `X-Comfy-Error-Type`. | -### `POST /v1/models/{provider}/{model}` +### `POST /v2/models/{provider}/{model}` **Run a partner model synchronously by canonical model ID.** @@ -76,6 +76,7 @@ Comfy Router's canonical, model-ID-addressed entry point. The request body is th | --- | --- | --- | --- | --- | --- | | `provider` | path | yes | [`RouterProviderSegment`](#routerprovidersegment) | `pattern: ^[a-z0-9]+([._-][a-z0-9]+)*$`, `maxLength: 64` | Lowercase provider segment of the canonical `{provider}/{model}[/{variant}]` model ID - the partner whose model is being run. | | `model` | path | yes | [`RouterModelSegment`](#routermodelsegment) | `pattern: ^[a-z0-9]+([._-][a-z0-9]+)*$`, `maxLength: 128` | Lowercase model segment of the canonical `{provider}/{model}[/{variant}]` model ID - the model to run within that provider. | +| `Idempotency-Key` | header | no | string | `minLength: 1`, `maxLength: 255` | Caller-generated key that makes retrying ONE logical call safe. A call that reached the caller with an answer is recorded against its key for 24 hours, and a retry carrying the same key is answered from that record instead of dispatching - and charging - the provider a second time, marked `Idempotent-Replayed: true`. The guarantee is a BILLING one: a key is charged at most once. It is not a promise that a key is dispatched at most once, and it does not make a lost call resumable. | **Request body** @@ -87,15 +88,19 @@ The partner model's native JSON input, forwarded to the provider unchanged. | Status | Body | Headers | Description | | --- | --- | --- | --- | -| `200` | [`RouterModelOutput`](#routermodeloutput) | `X-Comfy-Request-Id` | OK - the partner model's native JSON output, returned unchanged. | +| `200` | [`RouterModelOutput`](#routermodeloutput) | `X-Comfy-Request-Id`, `Idempotent-Replayed` | OK - the partner model's native JSON output, returned unchanged. | +| `400` | [`RouterErrorResponse`](#routererrorresponse) | `X-Comfy-Error-Type`, `X-Comfy-Request-Id` | A Router request-level failure - the request never reached the model, or failed for a reason the model itself did not report. The body is `RouterErrorResponse` and the bucket is repeated on `X-Comfy-Error-Type`. | +| `401` | [`RouterErrorResponse`](#routererrorresponse) | `X-Comfy-Error-Type`, `X-Comfy-Request-Id` | A Router request-level failure - the request never reached the model, or failed for a reason the model itself did not report. The body is `RouterErrorResponse` and the bucket is repeated on `X-Comfy-Error-Type`. | | `403` | [`RouterErrorResponse`](#routererrorresponse) | `X-Comfy-Error-Type`, `X-Comfy-Request-Id` | A Router request-level failure - the request never reached the model, or failed for a reason the model itself did not report. The body is `RouterErrorResponse` and the bucket is repeated on `X-Comfy-Error-Type`. | | `404` | [`RouterErrorResponse`](#routererrorresponse) | `X-Comfy-Error-Type`, `X-Comfy-Request-Id` | A Router request-level failure - the request never reached the model, or failed for a reason the model itself did not report. The body is `RouterErrorResponse` and the bucket is repeated on `X-Comfy-Error-Type`. | +| `409` | [`RouterErrorResponse`](#routererrorresponse) | `X-Comfy-Error-Type`, `X-Comfy-Request-Id`, `Retry-After` | The `Idempotency-Key` on this request is already held, and this request cannot be answered from its record. Two conditions share the status and `X-Comfy-Error-Type` is what separates them, because they are acted on in opposite ways. `concurrency_limit_exceeded` means the original call for this key is still running: wait `Retry-After` seconds and re-send THE SAME key, which collects that call's result rather than starting a second one. `invalid_input` means the key cannot serve this request at all - it was already used for a different request (the method, the path and query, or the body differ from the original), or the original completed (and, if it succeeded, was charged) and Router holds no faithful copy of its response to replay - and the answer is always a NEW key, never a re-send of this one. `detail` says which case it is. The body is `RouterErrorResponse` and the bucket is repeated on `X-Comfy-Error-Type`. | +| `413` | [`RouterErrorResponse`](#routererrorresponse) | `X-Comfy-Error-Type`, `X-Comfy-Request-Id` | A Router request-level failure - the request never reached the model, or failed for a reason the model itself did not report. The body is `RouterErrorResponse` and the bucket is repeated on `X-Comfy-Error-Type`. | | `422` | [`RouterValidationErrorResponse`](#routervalidationerrorresponse) | `X-Comfy-Error-Type`, `X-Comfy-Request-Id` | The request reached the model and the model rejected its contents. The body is `RouterValidationErrorResponse`, the FastAPI `detail[]` shape, so each offending field keeps its own specific `type` and `ctx`. `X-Comfy-Error-Type` carries the coarse bucket for the whole response. | | `429` | [`RouterErrorResponse`](#routererrorresponse) | `X-Comfy-Error-Type`, `X-Comfy-Request-Id`, `X-Committed-Spend-Limit`, `X-Committed-Spend-Current`, `X-Committed-Spend-Remaining` | The caller is holding as much in-flight capacity as they are allowed and the request was refused before it reached the model. The bucket is `concurrency_limit_exceeded` in either case and `detail` says which bound was hit: the number of concurrent calls, or the committed spend of the calls still in flight, whose refusal also carries the `X-Committed-Spend-Limit`, `X-Committed-Spend-Current` and `X-Committed-Spend-Remaining` headers (USD cents). Retry once one of the caller's own in-flight calls finishes. The body is `RouterErrorResponse` and the bucket is repeated on `X-Comfy-Error-Type`. | | `503` | [`RouterErrorResponse`](#routererrorresponse) | `X-Comfy-Error-Type`, `X-Comfy-Request-Id` | A Router request-level failure - the request never reached the model, or failed for a reason the model itself did not report. The body is `RouterErrorResponse` and the bucket is repeated on `X-Comfy-Error-Type`. | -| `504` | [`RouterErrorResponse`](#routererrorresponse) | `X-Comfy-Error-Type`, `X-Comfy-Request-Id`, `Retry-After` | Comfy stopped holding the connection at its own configured bound (`deadline_exceeded`). The body and the two headers are exactly `RouterRequestError`'s; what this adds is the optional `Retry-After`, present when a retry with the same `Idempotency-Key` will collect the generation that is still running rather than dispatch a new one. See the `504` on `POST /v1/models/{provider}/{model}`. | +| `504` | [`RouterErrorResponse`](#routererrorresponse) | `X-Comfy-Error-Type`, `X-Comfy-Request-Id`, `Retry-After` | Comfy stopped holding the connection at its own configured bound (`deadline_exceeded`). The body and the two headers are exactly `RouterRequestError`'s; what this adds is the optional `Retry-After`, present when a retry with the same `Idempotency-Key` will collect the generation that is still running rather than dispatch a new one. See the `504` on `POST /v2/models/{provider}/{model}`. | -### `GET /v1/models/{provider}/{model}/openapi.json` +### `GET /v2/models/{provider}/{model}/openapi.json` **Read one partner model's input schema as an OpenAPI document.** @@ -131,7 +136,7 @@ Raised for a request Router accepted and then could not complete. | `error_type` | Meaning | | --- | --- | -| `invalid_input` | The request was rejected before it reached the model - a malformed body, a malformed or expired pagination cursor, or an input the model's own schema does not accept. | +| `invalid_input` | The request was rejected before it reached the model - a malformed body, a malformed or expired pagination cursor, an input the model's own schema does not accept, or an `Idempotency-Key` that cannot serve this request (already used for a different request - the method, the path and query, or the body differ - or already consumed by a call whose response cannot be replayed). Sent with `409` in the key cases and with `400`/`422` in the others; the status says which, and the key cases are the ones answered by using a NEW key rather than by editing the request. | | `content_policy_violation` | The provider refused the request on content-policy grounds. The refusal is deterministic: re-sending the same input will be refused again. | | `provider_error` | The partner provider reported a failure of its own, or returned a response Router could not interpret as a result. | | `provider_timeout` | The partner provider did not answer within its deadline. This bucket is the PROVIDER timing out and never Router's own server deadline, which is reported as `deadline_exceeded` - the two share `504` and are separated because they name different causes: this one says the partner failed, that one says Comfy stopped holding the connection. | @@ -146,7 +151,7 @@ Raised by Router itself, before or around the call to the model. | --- | --- | | `unauthorized` | The request carried no usable credential. | | `forbidden` | The credential is valid but is not entitled to this model or this operation. | -| `concurrency_limit_exceeded` | The workspace already has as many calls in flight as it is allowed; retry once one of them finishes. | +| `concurrency_limit_exceeded` | The workspace already has as many calls in flight as it is allowed; retry once one of them finishes. It carries one further condition on the run route, on a `409` rather than the `429` above: another call is already in flight for the `Idempotency-Key` this request presented. Re-send the SAME key after `Retry-After` seconds to collect that call's result. | | `client_disconnected` | The caller closed the connection before Router could return a result. It is logged rather than delivered - there is no socket left to write it to - and it is an attribution, not a billing outcome: a provider generation that completed is billed regardless of whether the caller received the response. | | `internal_error` | Router itself failed. It is also the value a client should treat any UNRECOGNIZED bucket as, so a later addition to the set does not break a client generated before it. | | `deadline_exceeded` | Comfy stopped holding the connection at its own configured bound before an answer arrived. It shares `504` with `provider_timeout` and the pair says which side ran out of time; this one is Comfy's own bound, so nothing about the request was rejected and the same request may be retried. It says nothing about the charge: a provider generation that completed is billed regardless of whether the caller received the response. Retry it with the SAME `Idempotency-Key`: when the provider had already accepted the generation, the retry collects that generation rather than dispatching another, and a `Retry-After` on the `504` says when to ask. | @@ -159,8 +164,9 @@ Raised by Router itself, before or around the call to the model. | Header | Type | Description | | --- | --- | --- | | `Cache-Control` | string | Freshness directives for the served schema document. `private` because the route is authenticated - the document itself is not caller-specific, but a shared cache must not hold a response to an authenticated request - and `must-revalidate` so a stale copy is revalidated against the `ETag` rather than served on. | -| `ETag` | string | Strong entity tag over the served document's bytes, for `GET /v1/models/{provider}/{model}/openapi.json`. A per-model schema changes rarely and an SDK re-fetches it often, so a caller should store this value and send it back as `If-None-Match` to get a `304` instead of the document. | -| `Retry-After` | integer | Seconds to wait before retrying the SAME request with the SAME `Idempotency-Key`. Present on a `deadline_exceeded` `504` only when Comfy holds a handle to a generation the provider is still running; the value is Router's own poll interval, which is the one honest number this route has for "ask again later". Absent when there is nothing to collect: an unkeyed call, or a bound that expired before the provider accepted anything. | +| `ETag` | string | Strong entity tag over the served document's bytes, for `GET /v2/models/{provider}/{model}/openapi.json`. A per-model schema changes rarely and an SDK re-fetches it often, so a caller should store this value and send it back as `If-None-Match` to get a `304` instead of the document. | +| `Idempotent-Replayed` | boolean | Present and `true` when this response was served from an `Idempotency-Key`'s record rather than by running the model again. It carries the original call's status, body and content type, and it is not billed a second time - the charge settled when the original completed. The header is ABSENT on a fresh run rather than sent as `false`, so branch on its presence. | +| `Retry-After` | integer | Seconds to wait before retrying the SAME request with the SAME `Idempotency-Key`. It is set on the two answers such a retry can actually collect from: a `409` carrying `error_type: concurrency_limit_exceeded`, where the original call for that key is still running, and a `deadline_exceeded` `504`, where Comfy stopped holding the connection but still holds a handle to a generation the provider is running. In both cases the value is the interval Router itself would wait before asking again, which is the one honest number this route has for "ask again later". Absent when there is nothing to collect: an unkeyed call, a bound that expired before the provider accepted anything, or a `409` that refuses the key outright instead of asking the caller to wait. | | `X-Comfy-Error-Type` | [`RouterErrorType`](#routererrortype) | Coarse, machine-readable bucket for the failure, set by Router on every error response. It carries the same value as `RouterErrorResponse.error_type`, and on the `422` it is the ONLY machine-readable bucket, because that body is the FastAPI `detail[]` shape and has no `error_type` field of its own. A client can therefore branch on this header alone, before deciding which of the two Router error bodies it received. | | `X-Comfy-Request-Id` | string | Server-generated identifier for this call, present on EVERY Router response - success, 4xx and 5xx alike, because an error response is exactly when a user needs an id to quote in a support request. The SAME value is written into the call's usage/audit event, which is what lets a complaint about a charge be joined to the charge itself instead of searched for by timestamp. | | `X-Committed-Spend-Current` | integer | The USD cents the caller currently has committed to calls still in flight, not counting the refused call. Present alongside `X-Committed-Spend-Limit`. | @@ -169,7 +175,7 @@ Raised by Router itself, before or around the call to the model. ## Per-model input schemas -A model's own input fields are not reproduced here. Read them live from `GET /v1/models/{provider}/{model}/openapi.json`, which serves the same document the server validates the call against, so what is published and what is enforced cannot drift apart. Take a model ID from `GET /v1/models`, append `/openapi.json` to its invocation path, and generate against the document you get back. +A model's own input fields are not reproduced here. Read them live from `GET /v2/models/{provider}/{model}/openapi.json`, which serves the same document the server validates the call against, so what is published and what is enforced cannot drift apart. Take a model ID from `GET /v2/models`, append `/openapi.json` to its invocation path, and generate against the document you get back. ## Schemas @@ -216,11 +222,11 @@ The half of `RouterModelDetail` the catalog listing does NOT carry: per-model fi | Field | Type | Required | Constraints | Description | | --- | --- | --- | --- | --- | -| `input_schema_url` | string | no | `format: uri`, `pattern: ^https://`, `maxLength: 2048` | Pointer to this model's input schema document - the description of the body `POST /v1/models/{provider}/{model}` accepts for this model. Only the POINTER is part of this contract: the document it addresses is authored separately. Absent when no schema has been authored for the model. | +| `input_schema_url` | string | no | `format: uri`, `pattern: ^https://`, `maxLength: 2048` | Pointer to this model's input schema document - the description of the body `POST /v2/models/{provider}/{model}` accepts for this model. Only the POINTER is part of this contract: the document it addresses is authored separately. Absent when no schema has been authored for the model. | ### RouterModelId -A canonical Comfy Router model ID, `{provider}/{model}` - exactly the value that addresses the model on `POST /v1/models/{provider}/{model}`, so a caller can interpolate it into that path without re-deriving it from anything. Its `pattern` is `RouterProviderSegment` and `RouterModelSegment` joined by a single `/`, and `maxLength` is their sum plus that separator. +A canonical Comfy Router model ID, `{provider}/{model}` - exactly the value that addresses the model on `POST /v2/models/{provider}/{model}`, so a caller can interpolate it into that path without re-deriving it from anything. Its `pattern` is `RouterProviderSegment` and `RouterModelSegment` joined by a single `/`, and `maxLength` is their sum plus that separator. Type: `string` -- `pattern: ^[a-z0-9]+([._-][a-z0-9]+)*/[a-z0-9]+([._-][a-z0-9]+)*$`, `maxLength: 193` @@ -232,7 +238,7 @@ Type: `object` ### RouterModelInputSchemaDocument -A standalone OpenAPI document describing ONE Comfy Router model's input - the body `POST /v1/models/{provider}/{model}` accepts for that model. It is what `GET /v1/models/{provider}/{model}/openapi.json` returns. +A standalone OpenAPI document describing ONE Comfy Router model's input - the body `POST /v2/models/{provider}/{model}` accepts for that model. It is what `GET /v2/models/{provider}/{model}/openapi.json` returns. Type: `object` @@ -242,7 +248,7 @@ One entry in the Router model catalog: the identity of a runnable model, and not | Field | Type | Required | Constraints | Description | | --- | --- | --- | --- | --- | -| `id` | [`RouterModelId`](#routermodelid) | yes | `pattern: ^[a-z0-9]+([._-][a-z0-9]+)*/[a-z0-9]+([._-][a-z0-9]+)*$`, `maxLength: 193` | A canonical Comfy Router model ID, `{provider}/{model}` - exactly the value that addresses the model on `POST /v1/models/{provider}/{model}`, so a caller can interpolate it into that path without re-deriving it from anything. Its `pattern` is `RouterProviderSegment` and `RouterModelSegment` joined by a single `/`, and `maxLength` is their sum plus that separator. | +| `id` | [`RouterModelId`](#routermodelid) | yes | `pattern: ^[a-z0-9]+([._-][a-z0-9]+)*/[a-z0-9]+([._-][a-z0-9]+)*$`, `maxLength: 193` | A canonical Comfy Router model ID, `{provider}/{model}` - exactly the value that addresses the model on `POST /v2/models/{provider}/{model}`, so a caller can interpolate it into that path without re-deriving it from anything. Its `pattern` is `RouterProviderSegment` and `RouterModelSegment` joined by a single `/`, and `maxLength` is their sum plus that separator. | | `provider` | [`RouterProviderSegment`](#routerprovidersegment) | yes | `pattern: ^[a-z0-9]+([._-][a-z0-9]+)*$`, `maxLength: 64` | Lowercase `provider` segment of the canonical `{provider}/{model}[/{variant}]` model ID - the partner whose model is being addressed. The invocation route's `provider` path parameter and a catalog entry's `provider` field both reference this one schema, which is what keeps the listed IDs and the accepted IDs from drifting apart. | | `model` | [`RouterModelSegment`](#routermodelsegment) | yes | `pattern: ^[a-z0-9]+([._-][a-z0-9]+)*$`, `maxLength: 128` | Lowercase `model` segment of the canonical `{provider}/{model}[/{variant}]` model ID - the model to run within that provider. Shared by the invocation route's `model` path parameter and a catalog entry's `model` field, for the same no-drift reason as `RouterProviderSegment`. | | `billing` | [`RouterModelBilling`](#routermodelbilling) | yes | - | Per-model billing FACTS a caller needs before invoking - not prices. Usage and cost figures never appear here. | diff --git a/ja/api-reference/comfy-router/limitations.mdx b/ja/api-reference/comfy-router/limitations.mdx index de814000e..a2c936cff 100644 --- a/ja/api-reference/comfy-router/limitations.mdx +++ b/ja/api-reference/comfy-router/limitations.mdx @@ -1,23 +1,23 @@ --- title: "Comfy Router の制限事項" description: "Comfy Router が現在できないこと、代替手段が存在する場合に代わりに使用すべきもの、およびこれらの制限のうち変更される見込みのあるものについて説明します。" -translationSourceHash: b9e78b52 +translationSourceHash: b7db80c8 translationFrom: api-reference/comfy-router/limitations.mdx translationBlockHashes: - "_intro": 9eea7f43 + "_intro": 21bb3a18 "At a glance": fc2ff613 - "No queued submission": f72af0fd + "No queued submission": e529f3ab "No cost or credit figures on a response": ba168aa9 - "No way to resume a call you lost": a783a987 + "No way to resume a call you lost": 3911a303 "Calls are cut off at a server deadline": 45bacfca - "Requests are rate limited per caller": 01f2a8c8 - "No progress while a call runs": be9de68d + "Requests are rate limited per caller": 12eb3edd + "No progress while a call runs": d9c528a1 "Three forecast buckets are not in the vocabulary": f85a2589 "Router does not cover every partner operation": 9ecc10d1 "Next": 2d3bb742 --- -**Comfy Router はまだ一般提供されていません。** 以下で参照されるルート(`POST /v1/models/{provider}/{model}` と、そのカタログおよびスキーマ関連ルート)は、まだリクエストを処理していません。現時点では、認証済みの呼び出しでも `404` が返ります。このページは、それらのルートが提供する予定の契約について説明したものであり、既知の形状に対して統合を記述できるよう、その展開に先立って公開されています。以下の内容はすべて、その契約に関する記述であり、現在実際に試すことができる動作に関するものではありません。 +**Comfy Router はまだ一般提供されていません。** 以下で参照されるルート(`POST /v2/models/{provider}/{model}` と、そのカタログおよびスキーマ関連ルート)は、まだリクエストを処理していません。現時点では、認証済みの呼び出しでも `404` が返ります。このページは、それらのルートが提供する予定の契約について説明したものであり、既知の形状に対して統合を記述できるよう、その展開に先立って公開されています。以下の内容はすべて、その契約に関する記述であり、現在実際に試すことができる動作に関するものではありません。 Comfy Router は1回の同期呼び出しです。パートナーモデルのネイティブな入力を、1つの認証情報で1つのホストに送信し、接続は開いたままになり、`200` がそのモデルのネイティブな出力を運びます。この形状こそが最初の統合を短くするものであり、また、このページに記載されたすべての制限の由来でもあります。Router を中心に設計する前に、このページを読んでください。後ではなく前です。以下に続く内容のほとんどには単純明快な代替手段があり、代替手段のないものは、Router が保持しない前提に基づいて構築する前に知っておく価値があります。 @@ -39,7 +39,7 @@ Comfy Router は1回の同期呼び出しです。パートナーモデルのネ ## キュー投入はありません -モデルの実行方法は1つだけです。`POST /v1/models/{provider}/{model}` は、生成が完了するまで接続を保持し、レスポンスで結果を返します。ジョブを受け付けて識別子を返し、後で結果を取得できるようにするエンドポイントはなく、完了時のコールバックやウェブフックもありません。 +モデルの実行方法は1つだけです。`POST /v2/models/{provider}/{model}` は、生成が完了するまで接続を保持し、レスポンスで結果を返します。ジョブを受け付けて識別子を返し、後で結果を取得できるようにするエンドポイントはなく、完了時のコールバックやウェブフックもありません。 **代わりにすべきこと。** ほとんどのモデルではこれは問題になりません。接続を開いたままにして結果を読み取ってください。高速な画像モデルは数秒で結果を返します。長時間のビデオ生成は数分かかることもありますが、Routerはその間接続を保持します。クライアントの読み取りタイムアウトは、[Router自身の期限](#呼び出しはサーバーの期限で打ち切られる) よりも長い余裕のある値に設定し、この呼び出しを高速なリクエストではなく長時間実行として扱ってください。アーキテクチャ上、どうしても接続を開いたままにできない場合(実行時間の上限が短いサーバーレス関数や、ユーザーが閉じることを想定しているブラウザタブなど)は、接続を保持できる自分が管理するワーカーから呼び出しを実行するか、独自の送信・ポーリングのペアを備えたプロバイダー向けのパートナープロキシルートを使用してください。[最後のセクション](#routerはすべてのパートナー操作をカバーしていない) を参照してください。 @@ -57,16 +57,16 @@ Router のレスポンスはモデルが生成したものを伝えるだけで Router は実行中の呼び出しの再開可能な記録を保持しません。ステータスルートもジョブ識別子も再接続先もありません。呼び出しの途中で接続が切れた場合(クライアントのクラッシュ、ネットワークの分断、プロセスを再起動するデプロイ)はレスポンスが失われ、その呼び出しについて後から問い合わせることもできません。*生成*が完了して課金されたかどうかは、それを受け取ったかどうかとは別の問いであり、接続が切れただけではどちらも確実には分かりません。 -**代わりにすべきこと。** すべての呼び出しに `Idempotency-Key` ヘッダーを送ってください。失われた呼び出しを再開可能にはしませんが、再試行を安全にします。Router は呼び出しの間キーを予約し、呼び出しが実際に回答を届けた場合、そのレスポンスをキーに対応付けて24時間記録します。**同じ**キーで再試行すると、記録されたレスポンスが再生され、プロバイダーへの2回目のディスパッチ(と再課金)は行われません。`Idempotent-Replayed: true` が付くので、再生と新しい実行を区別できます。論理的な呼び出しごとに新しいキーを生成してください。試行ごとではなく。*異なる*リクエストボディで同じキーを提示すると、静かな上書きではなく `409` になります。 +**代わりにすべきこと。** すべての呼び出しに `Idempotency-Key` ヘッダーを送ってください。その仕組みは [クイックスタート](/ja/api-reference/comfy-router/quickstart#自分のキーで安全に再試行する) にまとめてあり、最も飛ばしやすい手順、つまりリクエストを送信する前にキーを永続化することも含まれています。失われた呼び出しを再開可能にはしませんが、再試行を安全にします。Router は呼び出しの間キーを予約し、呼び出しが実際に回答を届けた場合、そのレスポンスをキーに対応付けて24時間記録します。**同じ**キーで再試行すると、記録されたレスポンスが再生され、プロバイダーへの2回目のディスパッチ(と再課金)は行われません。`Idempotent-Replayed: true` が付くので、再生と新しい実行を区別できます。論理的な呼び出しごとに新しいキーを生成してください。試行ごとではなく。*異なる*リクエスト(ボディ、モデルパス、クエリ文字列、メソッドのいずれかが異なるもの)で同じキーを提示すると、静かな上書きではなく `409` になります。 -これが何をもたらすかを正確に理解してください。これは**課金**の性質であり、配信の性質ではありません。**キーは最大でも1回しか課金されません。** キーがプロバイダーに最大でも1回しかディスパッチされないという約束でもありません。Router は、実際に回答を受け取った場合にキーを保持します。課金されなかった結果はキーを解放し、呼び出しを再度行えるようにします。`5xx`、`408`/`425`/`429`、そして(ここが重要なケースです)何も届かなかった呼び出し。これらはすべてキーを解放し、そのキーでの再試行は本当に再実行され、プロバイダーに再ディスパッチされます。 +これが何をもたらすかを正確に理解してください。これは**課金**の性質であり、配信の性質ではありません。**キーは最大でも1回しか課金されません。** キーがプロバイダーに最大でも1回しかディスパッチされないという約束でもありません。Router は、実際に回答を受け取った場合にキーを保持します。課金されなかった結果はキーを解放し、呼び出しを再度行えるようにします。`5xx`、`408`/`425`/`429`、そして(ここが重要なケースです)何も届かなかった呼び出し。これらはすべてキーを解放し、そのキーでの再試行は本当に再実行され、プロバイダーに再ディスパッチされます。解放され*ない*唯一の `5xx` は、回収できるものが残っている打ち切りです。`Retry-After` を伴う `deadline_exceeded` の `504` は、Router が待機を止めた時点でプロバイダーがすでに生成を受け付けていたことを意味し、Router はキーを解放する代わりに、その実行中のジョブに紐付けて保持します。`Retry-After` の後に**同じ**キーを再送して回収してください。ここで新しいキーを使うと、2 回目の課金対象の生成になります。`Retry-After` のない `504` には保持すべきものがなく、他と同様に解放されます。 **つまり、接続が切れた場合は、べき等性が*救えない*ケースです。** 呼び出し途中の接続喪失は通常、レスポンスが一度もコミットされなかったことを意味し、それはまさに上記の解放経路です。同じキーでの再試行は、失った結果を渡すのではなく、新しい実行を開始します。元の生成がすでにディスパッチされていた場合、プロバイダーは2回目に実行するかもしれません。これは正しいデフォルトです。受け取っていない未課金の呼び出しは再実行可能であるべきです。ただし「再試行は新しい実行を生む」と計画してください。「再試行が失った実行を回収する」のではありません。 Router がキーに対して何かを*保持*している場合、再試行は再実行ではなく回答として扱われます。元のレスポンスが再生されるか、できない理由を説明する `409` が返ります。元の呼び出しがまだ実行中のときに送られた再試行は `Retry-After` 付きの `409` になるので、待ってから同じキーを再送してください。完了したが Router が忠実なコピーを保持できなかった呼び出しへの再試行も `409` です。これはレスポンスが大きすぎる場合だけではありません。再生上限を超えたレスポンス、回答後に失敗またはパニックしたハンドラー、あなたへの書き込みが失敗または不足した場合。これらはすべてキーを「消費済みだが再生不可」として記録し、同じ `409` を返します。サイズの問題を探しに行かないでください。これらのすべての場合の指針は同じです。**新しい**キーを使ってください。元の呼び出しは完了して課金されており、Router はそのレスポンスを捏造もせず、古いキーで再実行もさせません。 -**生成された契約にはまだありません。** ここで説明する `Idempotency-Key` リクエストヘッダー、`409` レスポンス、`Idempotent-Replayed` と `Retry-After` レスポンスヘッダーは、リファレンスの生成元である OpenAPI 契約の `POST /v1/models/{provider}/{model}` には宣言されていません。そのため、生成された API リファレンスには登場せず、SDK もこれらをモデル化しません。正式にサポートされるまで、これらを自分で送受信してください。 +**契約に含まれています。ただし 1 つ抜けがあります。** ここで説明する `Idempotency-Key` リクエストヘッダー、`409` レスポンス、`Idempotent-Replayed` と `Retry-After` レスポンスヘッダーは `POST /v2/models/{provider}/{model}` に宣言されているため、生成された [API リファレンス](/ja/api-reference/comfy-router/reference) と、SDK が取り込む仕様に登場します。したがって SDK は再生成時にこれらを取り込みます。抜けているのは次の点です。`Retry-After` は `409` と `504` には宣言されていますが、[後述](#リクエストは呼び出し元ごとにレート制限される) の `rate_limited` `429` には宣言されて**いません**。この `429` も `Retry-After` を送るので、契約に記載されるのを待たずにそこで読み取ってください。 **ステータス: 未対応。** 永続的で再開可能な実行は、キュー投入パスとともに登場する見込みです。そこならリクエスト記録の置き場所があります。べき等リトライは今日の答えであり、一時しのぎではありません。いずれにせよ組み込む価値があります。 @@ -85,17 +85,17 @@ Router の1回の呼び出しは、接続を **10分間** 保持することが ## リクエストは呼び出し元ごとにレート制限される -Router はトラフィックについて 2 つの異なるものを制限しており、同じ `429` に対して 2 つの異なるバケットで応答します。並行数制限は同時に**実行中**にできる呼び出しの数を制限し、`concurrency_limit_exceeded` で応答します。これは自分の呼び出しのいずれかが完了した瞬間に解消されるため、数秒後に再試行するのが正解です。レート制限は、Router のサーフェスにそもそもどれだけ**頻繁に**アクセスできるかを制限し(`POST /v1/models/{provider}/{model}` と `/v1/models` 配下の 3 つのカタログ読み取りのいずれも対象で、呼び出しがモデルを実行したか、その前に拒否されたかは問いません)、`rate_limited` で応答します。こちらは 1 分間のウィンドウで継続的に補充される割り当てなので、何をしても早く消化することはできません。レスポンスには待機すべき秒数を示す `Retry-After` ヘッダーが含まれ、`detail` にウィンドウが記載されます。分岐はステータスだけではなく、必ず `X-Comfy-Error-Type` に基づいて行ってください。 +Router はトラフィックについて 2 つの異なるものを制限しており、同じ `429` に対して 2 つの異なるバケットで応答します。並行数制限は同時に**実行中**にできる呼び出しの数を制限し、`concurrency_limit_exceeded` で応答します。これは自分の呼び出しのいずれかが完了した瞬間に解消されるため、数秒後に再試行するのが正解です。レート制限は、Router のサーフェスにそもそもどれだけ**頻繁に**アクセスできるかを制限し(`POST /v2/models/{provider}/{model}` と `/v2/models` 配下の 3 つのカタログ読み取りのいずれも対象で、呼び出しがモデルを実行したか、その前に拒否されたかは問いません)、`rate_limited` で応答します。こちらは 1 分間のウィンドウで継続的に補充される割り当てなので、何をしても早く消化することはできません。レスポンスには待機すべき秒数を示す `Retry-After` ヘッダーが含まれ、`detail` にウィンドウが記載されます。分岐はステータスだけではなく、必ず `X-Comfy-Error-Type` に基づいて行ってください。 制限は送信元アドレスではなく、認証された呼び出し元をキーにしているため、ホストをまたいでも資格情報に付いて回ります。自分のプロバイダーキー(bring-your-own-key)で実行される呼び出しは対象外です。そのスループットはあなた自身のものだからです。割り当て量は公開された定数ではなくサーバー側の設定値であり、このページでは意図的に数値を記載していません。数値ではなくバックオフを前提に設計してください。 -**代わりにすべきこと。** `Retry-After` を尊重してください。その時間内の再試行は同じ拒否に当たります。`GET /v1/models` とモデルの `openapi.json` は一度取得してプロセスの生存期間中キャッシュし、呼び出しのたびに読み直さないようにしてください。これらはデプロイ時にしか変わりません。`429` からリクエスト識別子を保持しておくクライアントは、サポートが追跡できる証跡を持つことになります。 +**代わりにすべきこと。** `Retry-After` を尊重してください。その時間内の再試行は同じ拒否に当たります。`GET /v2/models` とモデルの `openapi.json` は一度取得してプロセスの生存期間中キャッシュし、呼び出しのたびに読み直さないようにしてください。これらはデプロイ時にしか変わりません。`429` からリクエスト識別子を保持しておくクライアントは、サポートが追跡できる証跡を持つことになります。 **ステータス: 意図的な設計。** 呼び出し元ごとのリクエストレートの上限は、期限と同じ理由で存在しなければなりません。数値は調整可能ですが、制限の存在自体がなくなることはありません。 ## 呼び出し実行中は進捗がありません -`POST /v1/models/{provider}/{model}` は、最後に一度だけ応答を返します。ストリーミング応答も、サーバー送信イベントも、進捗率も、部分的なフレームやプレビューフレームもありません。これは、自社のAPIが送信とポーリング方式であるパートナーについても当てはまります。Routerはそのポーリングを、あなたの1回の呼び出しの中で内部的に実行し、そこで見られる中間状態はあなたには転送されません。外から見ると、3秒の画像と6分のビデオは同じ形状です。つまり、1つのリクエスト、1つのレスポンス、その間に何もない、ということです。 +`POST /v2/models/{provider}/{model}` は、最後に一度だけ応答を返します。ストリーミング応答も、サーバー送信イベントも、進捗率も、部分的なフレームやプレビューフレームもありません。これは、自社のAPIが送信とポーリング方式であるパートナーについても当てはまります。Routerはそのポーリングを、あなたの1回の呼び出しの中で内部的に実行し、そこで見られる中間状態はあなたには転送されません。外から見ると、3秒の画像と6分のビデオは同じ形状です。つまり、1つのリクエスト、1つのレスポンス、その間に何もない、ということです。 **代わりにできること。** 現在のRouterでは、何もできません。取得できないパーセンテージの代わりに、不確定な進捗状態を表示してください。特定のプロバイダーで進捗が必須要件である場合は、そのプロバイダーのパートナープロキシルートが独自のポーリングやストリーミングを公開しているかどうかを確認し、それらを直接使用してください。実際にいくつかのプロバイダーは対応しており、それらは変更されておらず、完全にサポートされています。 diff --git a/ja/api-reference/comfy-router/quickstart.mdx b/ja/api-reference/comfy-router/quickstart.mdx index 7c93c564c..7a57bc70d 100644 --- a/ja/api-reference/comfy-router/quickstart.mdx +++ b/ja/api-reference/comfy-router/quickstart.mdx @@ -1,23 +1,24 @@ --- title: "Comfy Router クイックスタート" description: "Comfy Routerに対して、PythonとTypeScriptで、ゼロから約5分で生成済み画像まで到達する手順を説明します。" -translationSourceHash: 50fc5524 +translationSourceHash: ead13a74 translationFrom: api-reference/comfy-router/quickstart.mdx translationBlockHashes: - "_intro": 46050c92 + "_intro": d9a651bf "Why this page uses `bfl/flux-2-pro`": 0eaf0bb3 "Get a key": 843d281f - "cURL": bc3e1e4c - "Python": 059e95b8 - "TypeScript": 1053430f + "cURL": 8ea9cb9f + "Python": f892ccc3 + "TypeScript": d0117cbc "Reading the `422`": 602cd505 - "Find a model": 9b7855c1 - "Where the model's fields come from": 7861bd52 + "Retrying safely with your own key": d1e75ba8 + "Find a model": a4caa991 + "Where the model's fields come from": 9cf8930c "Next": 6aa641e1 --- **Comfy Router はまだ一般提供されていません。** 以下のルート -`POST /v1/models/{provider}/{model}` と、そのカタログおよびスキーマの関連ルートは、 +`POST /v2/models/{provider}/{model}` と、そのカタログおよびスキーマの関連ルートは、 まだリクエストを処理していません。現在、認証付きの呼び出しは `404` を返します。このページは、 これらのルートが将来提供する契約を文書化したものであり、ロールアウトに先立って公開されているため、 統合をその契約に合わせて作成する準備ができます。これは、現在実際に試すことができる動作の説明ではありません。 @@ -25,7 +26,7 @@ translationBlockHashes: Comfy Router は、パートナーモデルを1つのホスト、1つの資格情報、1つのルート形状の背後で実行します。このページは、生成済み画像への最短の完全なパスです。クライアントをインストールし、キーを設定し、1つのリクエストを送信し、結果を読み取り、そして最初の失敗に遭遇する前に、その失敗がどのようなものかを確認できます。 -ベース URL: `https://api.comfy.org`。ルートは `POST /v1/models/{provider}/{model}` です。リクエストボディはモデル独自のネイティブ JSON 入力であり、`200` 応答にはモデル独自のネイティブ JSON 出力が含まれます。Router は入力と出力のどちらもラップしないため、パートナーの API に対して既に作成した呼び出しは、ホストを変更するだけで Router の呼び出しになります。 +ベース URL: `https://api.comfy.org`。ルートは `POST /v2/models/{provider}/{model}` です。リクエストボディはモデル独自のネイティブ JSON 入力であり、`200` 応答にはモデル独自のネイティブ JSON 出力が含まれます。Router は入力と出力のどちらもラップしないため、パートナーの API に対して既に作成した呼び出しは、ホストを変更するだけで Router の呼び出しになります。 ## このページで `bfl/flux-2-pro` を使用する理由 @@ -52,7 +53,7 @@ export COMFY_API_KEY="comfyui-..." スクリプト、スモークテスト、ターミナルへのコピー&貼り付けに最適な、最短の呼び出し方法です: ```bash -curl https://api.comfy.org/v1/models/bfl/flux-2-pro \ +curl https://api.comfy.org/v2/models/bfl/flux-2-pro \ -H "X-API-Key: $COMFY_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ @@ -121,7 +122,7 @@ def run(model: str, arguments: dict, idempotency_key: str) -> dict: # provider a second time. Reuse the SAME key when retrying one logical # call; generate a new one for a new call. response = httpx.post( - f"{BASE_URL}/v1/models/{model}", + f"{BASE_URL}/v2/models/{model}", headers={ "X-API-Key": os.environ["COMFY_API_KEY"], "Idempotency-Key": idempotency_key, @@ -233,7 +234,7 @@ async function run( // Idempotency-Key makes a retry safe on a PAID call: Router replays the // original response for 24h instead of dispatching (and billing) the provider // a second time. Reuse the SAME key when retrying one logical call. - const response = await fetch(`${BASE_URL}/v1/models/${model}`, { + const response = await fetch(`${BASE_URL}/v2/models/${model}`, { method: "POST", headers: { "X-API-Key": API_KEY, @@ -296,13 +297,64 @@ invalid_input (HTTP 422), request id 6f1c... `X-Comfy-Request-Id` は、成功、`4xx`、`5xx` を問わずすべてのレスポンスに含まれており、サポートリクエストで引用する ID です。両方のサンプルは、ヘッダーロギングを有効にして再実行する代わりに、例外に ID を添付します。 +## 自分のキーで安全に再試行する + +上の 2 つのサンプルはどちらも `Idempotency-Key` を送信しています。これは独立したセクションを設ける価値があります。このヘッダーはキーを正しく扱った場合にのみ役割を果たし、決定的な違いを生む手順は、リクエストが送信される前に行われるからです。 + +**キーは自分で用意する。** Router がキーを発行してくれることはありません。*論理的な呼び出し*ごとに新しいキーを生成し(想定されている形式は UUID です)、*その呼び出し*の再試行では毎回同じキーを再利用してください。試行ごとに新しいキーを使っても何も得られません。まったく異なる 2 つの呼び出しでキーを再利用すると `409` になります。同じキーで異なるリクエスト(ボディが異なる場合だけでなく、モデルパス、クエリ文字列、メソッドが異なる場合も含む)を送ることは、静かな上書きではなく競合として扱われるからです。 + +**送信する前に永続化する。** キーは、レスポンスが返ってきた後ではなく、`POST` が送出される*前*に、リクエストより長く生き残る場所(生成対象の行、ジョブレコード、キューメッセージなど)に書き込んでください。クラッシュしたプロセスのメモリ上にしか存在しなかったキーは再送できず、Router の記録から回答されるはずだった再試行は、別途課金される新しい実行になってしまいます。これは飛ばしやすく、飛ばすと高くつく唯一の手順です。 + +**そのキーで再試行する。** 再試行時、Router はそのキーの状態をまだ保持している限り、再実行ではなく回答を返します。 + +| 返ってくるもの | 意味 | 対応 | +| --- | --- | --- | +| `Idempotent-Replayed: true` 付きの `200` | Router が元のレスポンスを再生しました。再課金はされません。 | そのまま使ってください。元の結果です。 | +| `409` / `concurrency_limit_exceeded` | 元の呼び出しがまだ実行中です。 | `Retry-After` 秒待ってから、**同じキー**を再送してください。 | +| `409` / `invalid_input` | このキーではこのリクエストに応答できません。同じキーで異なるリクエスト(ボディ、モデルパス、クエリ、メソッド)が送られたか、元の呼び出しが完了していてそのレスポンスを再生できない場合です。 | **新しい**キーを使ってください。このキーを再送しないでください。 | +| `Retry-After` 付きの `504` / `deadline_exceeded` | Router は接続の保持を停止しましたが、プロバイダーが実行中の生成へのハンドルはまだ保持しています。 | `Retry-After` 秒待ってから、**同じキー**を再送して回収してください。 | + +上の Python サンプルの続きです。ここでのファイルは、すでに手元にある任意の永続ストアの代わりです。 +重要なのは仕組みではなく順序です。キーだけでなく、リクエスト全体をキーと並べて +永続化してください。再試行では*同じ*モデルと引数を再送する必要があり、再起動後に +メモリから組み直したリクエストは、空白 1 つ分でも違えば `409` になります。一方、 +新しいキーで送れば、2 回目の課金対象の生成になります。 + +```python +import json +import uuid + +# リクエストの前に永続化する。ここからレスポンスまでの間にクラッシュしても、 +# 再試行に使えるキー(と、それが属する正確なリクエスト)が残るようにする。 +request = { + "model": MODEL, + "arguments": {"prompt": "a red teapot on a windowsill, morning light"}, + "idempotency_key": str(uuid.uuid4()), +} +with open("pending-call.json", "w") as f: + json.dump(request, f) + +result = run(request["model"], request["arguments"], idempotency_key=request["idempotency_key"]) +``` + + +**キーが保証するのは課金であり、配信ではありません。** キーが課金されるのは**最大 1 回**です。 +キーが最大 1 回しかディスパッチされないという約束ではなく、失った呼び出しを回収可能にする +ものでもありません。呼び出し途中で接続が切れ、何もコミットされなかった場合、キーは解放され、 +そのキーでの再試行は、取り逃した結果を渡すのではなく**新しい実行**を開始します。 +「再試行は新しい実行を生む」ことを前提に計画し、再生は保証ではなく、うまくいった場合の +ケースとして扱ってください。永続的で再開可能な「再接続して回収する」は、Router がまだ +持っていないキュー投入パスの機能です。 +[制限事項](/ja/api-reference/comfy-router/limitations#失った呼び出しを再開する方法がない) を参照してください。 + + ## モデルを探す -`bfl/flux-2-pro` は 1 つの ID にすぎず、残りはカタログにあります。`GET /v1/models` は Router が実行できるすべてのモデルを 1 ページずつ一覧表示し、各エントリには呼び出しに必要な情報がそのまま含まれています。パスに入れる `id`、個別に保持される `provider` と `model` のセグメント、そして何かを消費する前に分岐判断に使える `billing` ブロックです。 +`bfl/flux-2-pro` は 1 つの ID にすぎず、残りはカタログにあります。`GET /v2/models` は Router が実行できるすべてのモデルを 1 ページずつ一覧表示し、各エントリには呼び出しに必要な情報がそのまま含まれています。パスに入れる `id`、個別に保持される `provider` と `model` のセグメント、そして何かを消費する前に分岐判断に使える `billing` ブロックです。 ```bash curl -H "X-API-Key: $COMFY_API_KEY" \ - "https://api.comfy.org/v1/models?limit=50" + "https://api.comfy.org/v2/models?limit=50" ``` ```json @@ -324,7 +376,7 @@ curl -H "X-API-Key: $COMFY_API_KEY" \ ```bash curl -H "X-API-Key: $COMFY_API_KEY" \ - https://api.comfy.org/v1/models/bfl/flux-2-pro/openapi.json + https://api.comfy.org/v2/models/bfl/flux-2-pro/openapi.json ``` これは、サーバーがあなたの呼び出しの検証に使用するものと同じドキュメントで、スタンドアロンのOpenAPIドキュメントとして提供されます。そのため、公開されている仕様と実際に強制される仕様が食い違うことはありません。上のカタログから任意の `id` を選び、その呼び出しパスに `/openapi.json` を追加すれば、返ってきた内容に基づいて生成できます。 diff --git a/ja/api-reference/comfy-router/reference.mdx b/ja/api-reference/comfy-router/reference.mdx index e9644807d..f70614a3a 100644 --- a/ja/api-reference/comfy-router/reference.mdx +++ b/ja/api-reference/comfy-router/reference.mdx @@ -1,15 +1,15 @@ --- title: "Comfy Router API リファレンス" description: "Comfy API 契約から生成済みの、Comfy Router のすべてのエンドポイント、パラメータ、レスポンスボディ、エラーバケット。" -translationSourceHash: 5b914bce +translationSourceHash: 7d6b2b62 translationFrom: api-reference/comfy-router/reference.mdx translationBlockHashes: "_intro": 0114a881 - "Endpoints": 93fdf96a - "Error buckets": bfbc4524 - "Response headers": 8f8b3475 - "Per-model input schemas": ae73e63b - "Schemas": 8ce93415 + "Endpoints": e00ef646 + "Error buckets": ec90b196 + "Response headers": 07b7f334 + "Per-model input schemas": 978b7612 + "Schemas": 1062b7df --- {/* @@ -28,11 +28,11 @@ translationBlockHashes: ## エンドポイント -### `GET /v1/models` +### `GET /v2/models` **Comfy Router が実行できるモデルを一覧表示します。** -Comfy Router のモデルカタログ。`POST /v1/models/{provider}/{model}` が受け付ける正規モデル ID の 1 ページ分です。SDK はコールドスタート時にこの API を呼び出して実行可能なモデルを検出し、`model_not_found` の提案も同じカタログから取得されます。したがって、ここに掲載されている ID が呼び出し時に 404 になる場合は、どちらか一方の失敗だけよりも悪い結果になります。この一致は約束ではなく構造上のものです。エントリの `provider` と `model` は、呼び出しルートの2つのパスセグメントであり、そのルートのパスパラメータと同じスキーマコンポーネントを参照します。また、`id` はそれらの2つのセグメントを `/` で連結したものです。 +Comfy Router のモデルカタログ。`POST /v2/models/{provider}/{model}` が受け付ける正規モデル ID の 1 ページ分です。SDK はコールドスタート時にこの API を呼び出して実行可能なモデルを検出し、`model_not_found` の提案も同じカタログから取得されます。したがって、ここに掲載されている ID が呼び出し時に 404 になる場合は、どちらか一方の失敗だけよりも悪い結果になります。この一致は約束ではなく構造上のものです。エントリの `provider` と `model` は、呼び出しルートの2つのパスセグメントであり、そのルートのパスパラメータと同じスキーマコンポーネントを参照します。また、`id` はそれらの2つのセグメントを `/` で連結したものです。 **パラメータ** @@ -51,7 +51,7 @@ Comfy Router のモデルカタログ。`POST /v1/models/{provider}/{model}` が | `403` | [`RouterErrorResponse`](#routererrorresponse) | `X-Comfy-Error-Type`, `X-Comfy-Request-Id` | Router のリクエストレベルの失敗。リクエストがモデルに到達しなかったか、モデル自身が報告しない理由で失敗しました。ボディは `RouterErrorResponse` で、バケットは `X-Comfy-Error-Type` に繰り返されます。 | | `503` | [`RouterErrorResponse`](#routererrorresponse) | `X-Comfy-Error-Type`, `X-Comfy-Request-Id` | Router のリクエストレベルの失敗。リクエストがモデルに到達しなかったか、モデル自身が報告しない理由で失敗しました。ボディは `RouterErrorResponse` で、バケットは `X-Comfy-Error-Type` に繰り返されます。 | -### `GET /v1/models/{provider}/{model}` +### `GET /v2/models/{provider}/{model}` **正規のモデル ID で、パートナーモデルのカタログエントリを 1 件読み取ります。** @@ -73,7 +73,7 @@ Comfy Router のモデルカタログ。`POST /v1/models/{provider}/{model}` が | `404` | [`RouterErrorResponse`](#routererrorresponse) | `X-Comfy-Error-Type`, `X-Comfy-Request-Id` | Router のリクエストレベルでの失敗です。リクエストがモデルに到達しなかったか、モデル自身が報告しなかった理由で失敗したことを示します。ボディは `RouterErrorResponse` で、バケットは `X-Comfy-Error-Type` ヘッダーにも繰り返し含まれます。 | | `503` | [`RouterErrorResponse`](#routererrorresponse) | `X-Comfy-Error-Type`, `X-Comfy-Request-Id` | Router のリクエストレベルでの失敗です。リクエストがモデルに到達しなかったか、モデル自身が報告しなかった理由で失敗したことを示します。ボディは `RouterErrorResponse` で、バケットは `X-Comfy-Error-Type` ヘッダーにも繰り返し含まれます。 | -### `POST /v1/models/{provider}/{model}` +### `POST /v2/models/{provider}/{model}` **正規モデルIDでパートナーモデルを同期的に実行します。** @@ -85,6 +85,7 @@ Comfy Routerの正規のエントリポイントであり、モデルIDでアド | --- | --- | --- | --- | --- | --- | | `provider` | path | yes | [`RouterProviderSegment`](#routerprovidersegment) | `pattern: ^[a-z0-9]+([._-][a-z0-9]+)*$`, `maxLength: 64` | 正規の `{provider}/{model}[/{variant}]` モデルIDの小文字のプロバイダーセグメント。モデルが実行されるパートナーを示します。 | | `model` | path | yes | [`RouterModelSegment`](#routermodelsegment) | `pattern: ^[a-z0-9]+([._-][a-z0-9]+)*$`, `maxLength: 128` | 正規の `{provider}/{model}[/{variant}]` モデルIDの小文字のモデルセグメント。そのプロバイダー内で実行するモデルを示します。 | +| `Idempotency-Key` | header | no | string | `minLength: 1`, `maxLength: 255` | 呼び出し元が生成するキーで、1 つの論理的な呼び出しの再試行を安全にします。回答とともに呼び出し元に到達した呼び出しは、そのキーに対応付けて 24 時間記録され、同じキーを伴う再試行は、プロバイダーに 2 回目のディスパッチ(と課金)を行う代わりにその記録から回答され、`Idempotent-Replayed: true` が付きます。この保証は課金に関するものです。キーが課金されるのは最大 1 回です。キーが最大 1 回しかディスパッチされないという約束ではなく、失われた呼び出しを再開可能にするものでもありません。 | **リクエストボディ** @@ -96,15 +97,19 @@ Comfy Routerの正規のエントリポイントであり、モデルIDでアド | ステータス | ボディ | ヘッダー | 説明 | | --- | --- | --- | --- | -| `200` | [`RouterModelOutput`](#routermodeloutput) | `X-Comfy-Request-Id` | OK。パートナーモデルのネイティブなJSON出力がそのまま返されます。 | +| `200` | [`RouterModelOutput`](#routermodeloutput) | `X-Comfy-Request-Id`, `Idempotent-Replayed` | OK。パートナーモデルのネイティブなJSON出力がそのまま返されます。 | +| `400` | [`RouterErrorResponse`](#routererrorresponse) | `X-Comfy-Error-Type`, `X-Comfy-Request-Id` | Routerのリクエストレベルの失敗です。リクエストがモデルに到達しなかったか、モデル自身が報告しない理由で失敗しました。ボディは `RouterErrorResponse` であり、そのバケットは `X-Comfy-Error-Type` にも繰り返し記載されます。 | +| `401` | [`RouterErrorResponse`](#routererrorresponse) | `X-Comfy-Error-Type`, `X-Comfy-Request-Id` | Routerのリクエストレベルの失敗です。リクエストがモデルに到達しなかったか、モデル自身が報告しない理由で失敗しました。ボディは `RouterErrorResponse` であり、そのバケットは `X-Comfy-Error-Type` にも繰り返し記載されます。 | | `403` | [`RouterErrorResponse`](#routererrorresponse) | `X-Comfy-Error-Type`, `X-Comfy-Request-Id` | Routerのリクエストレベルの失敗です。リクエストがモデルに到達しなかったか、モデル自身が報告しない理由で失敗しました。ボディは `RouterErrorResponse` であり、そのバケットは `X-Comfy-Error-Type` にも繰り返し記載されます。 | | `404` | [`RouterErrorResponse`](#routererrorresponse) | `X-Comfy-Error-Type`, `X-Comfy-Request-Id` | Routerのリクエストレベルの失敗です。リクエストがモデルに到達しなかったか、モデル自身が報告しない理由で失敗しました。ボディは `RouterErrorResponse` であり、そのバケットは `X-Comfy-Error-Type` にも繰り返し記載されます。 | +| `409` | [`RouterErrorResponse`](#routererrorresponse) | `X-Comfy-Error-Type`, `X-Comfy-Request-Id`, `Retry-After` | このリクエストの `Idempotency-Key` はすでに保持されており、このリクエストにはその記録から回答できません。2 つの状況がこのステータスを共有し、`X-Comfy-Error-Type` がそれらを区別します。両者への対応は正反対だからです。`concurrency_limit_exceeded` は、このキーに対する元の呼び出しがまだ実行中であることを意味します。`Retry-After` 秒待ってから同じキーを再送してください。そうすれば、2 回目の呼び出しを開始するのではなく、その呼び出しの結果を回収できます。`invalid_input` は、このキーではこのリクエストにまったく応答できないことを意味します。すでに異なるリクエスト(メソッド、パスとクエリ、またはボディが元のものと異なる)に使われたか、元の呼び出しが完了しており(成功していれば課金済みで)、Router が再生できる忠実なレスポンスのコピーを保持していない場合です。この場合の答えは常に新しいキーであり、このキーの再送ではありません。`detail` がどちらのケースかを示します。ボディは `RouterErrorResponse` であり、そのバケットは `X-Comfy-Error-Type` にも繰り返し記載されます。 | +| `413` | [`RouterErrorResponse`](#routererrorresponse) | `X-Comfy-Error-Type`, `X-Comfy-Request-Id` | Routerのリクエストレベルの失敗です。リクエストがモデルに到達しなかったか、モデル自身が報告しない理由で失敗しました。ボディは `RouterErrorResponse` であり、そのバケットは `X-Comfy-Error-Type` にも繰り返し記載されます。 | | `422` | [`RouterValidationErrorResponse`](#routervalidationerrorresponse) | `X-Comfy-Error-Type`, `X-Comfy-Request-Id` | リクエストはモデルに到達しましたが、モデルはその内容を拒否しました。ボディは `RouterValidationErrorResponse` であり、FastAPIの `detail[]` 形状です。そのため、各問題のあるフィールドは独自の `type` と `ctx` を保持します。`X-Comfy-Error-Type` はレスポンス全体の大まかなバケットを伝えます。 | | `429` | [`RouterErrorResponse`](#routererrorresponse) | `X-Comfy-Error-Type`, `X-Comfy-Request-Id`, `X-Committed-Spend-Limit`, `X-Committed-Spend-Current`, `X-Committed-Spend-Remaining` | 呼び出し元が許可された上限いっぱいの実行中キャパシティを保持しており、リクエストはモデルに到達する前に拒否されました。どちらの場合もバケットは `concurrency_limit_exceeded` で、`detail` がどの上限に達したかを示します。同時呼び出し数か、実行中の呼び出しのコミット済み支出かのいずれかで、後者による拒否には `X-Committed-Spend-Limit`、`X-Committed-Spend-Current`、`X-Committed-Spend-Remaining` ヘッダー(米ドルのセント単位)も付きます。呼び出し元自身の実行中の呼び出しのいずれかが完了したら再試行してください。ボディは `RouterErrorResponse` であり、そのバケットは `X-Comfy-Error-Type` にも繰り返し記載されます。 | | `503` | [`RouterErrorResponse`](#routererrorresponse) | `X-Comfy-Error-Type`, `X-Comfy-Request-Id` | Routerのリクエストレベルの失敗です。リクエストがモデルに到達しなかったか、モデル自身が報告しない理由で失敗しました。ボディは `RouterErrorResponse` であり、そのバケットは `X-Comfy-Error-Type` にも繰り返し記載されます。 | -| `504` | [`RouterErrorResponse`](#routererrorresponse) | `X-Comfy-Error-Type`, `X-Comfy-Request-Id`, `Retry-After` | Comfy が自身の設定済みの上限で接続の維持を停止しました(`deadline_exceeded`)。ボディと 2 つのヘッダーは `RouterRequestError` のものとまったく同じで、これに加わるのがオプションの `Retry-After` です。同じ `Idempotency-Key` での再試行が、新たな生成をディスパッチするのではなく、まだ実行中の生成を回収する場合に付与されます。`POST /v1/models/{provider}/{model}` の `504` を参照してください。 | +| `504` | [`RouterErrorResponse`](#routererrorresponse) | `X-Comfy-Error-Type`, `X-Comfy-Request-Id`, `Retry-After` | Comfy が自身の設定済みの上限で接続の維持を停止しました(`deadline_exceeded`)。ボディと 2 つのヘッダーは `RouterRequestError` のものとまったく同じで、これに加わるのがオプションの `Retry-After` です。同じ `Idempotency-Key` での再試行が、新たな生成をディスパッチするのではなく、まだ実行中の生成を回収する場合に付与されます。`POST /v2/models/{provider}/{model}` の `504` を参照してください。 | -### `GET /v1/models/{provider}/{model}/openapi.json` +### `GET /v2/models/{provider}/{model}/openapi.json` **1つのパートナーモデルの入力スキーマをOpenAPIドキュメントとして読み取ります。** @@ -140,7 +145,7 @@ Router が受け付けたものの完了できなかったリクエストに対 | `error_type` | 意味 | | --- | --- | -| `invalid_input` | リクエストがモデルに到達する前に拒否されました。不正な形式のボディ、不正または期限切れのページネーションカーソル、またはモデル自身のスキーマが受け付けない入力が原因です。 | +| `invalid_input` | リクエストがモデルに到達する前に拒否されました。不正な形式のボディ、不正または期限切れのページネーションカーソル、モデル自身のスキーマが受け付けない入力、またはこのリクエストに応答できない `Idempotency-Key`(すでに異なるリクエスト、つまりメソッド、パスとクエリ、またはボディが異なるものに使われたか、レスポンスを再生できない呼び出しによってすでに消費されている)が原因です。キーに関するケースでは `409` とともに、それ以外では `400`/`422` とともに送られます。ステータスがどちらかを示し、キーに関するケースは、リクエストを修正するのではなく新しいキーを使うことで対処するものです。 | | `content_policy_violation` | プロバイダーがコンテンツポリシーを理由にリクエストを拒否しました。この拒否は決定的です。同じ入力を再送信しても再び拒否されます。 | | `provider_error` | パートナープロバイダーが自身の障害を報告したか、Router が結果として解釈できないレスポンスを返しました。 | | `provider_timeout` | パートナープロバイダーが期限までに応答しませんでした。このバケットはプロバイダーのタイムアウトであり、Router 自身のサーバー側の期限ではありません。サーバー側の期限は `deadline_exceeded` として報告されます。両者は `504` を共有しますが、原因が異なるため区別されます。こちらはパートナーが失敗したことを示し、あちらは Comfy が接続の維持を停止したことを示します。 | @@ -155,7 +160,7 @@ Router が受け付けたものの完了できなかったリクエストに対 | --- | --- | | `unauthorized` | リクエストに利用可能な認証情報が含まれていませんでした。 | | `forbidden` | 認証情報は有効ですが、このモデルまたはこの操作に対する権限がありません。 | -| `concurrency_limit_exceeded` | ワークスペースはすでに許可された数の呼び出しを実行中です。いずれかが完了したら再試行してください。 | +| `concurrency_limit_exceeded` | ワークスペースはすでに許可された数の呼び出しを実行中です。いずれかが完了したら再試行してください。実行ルートでは、上記の `429` ではなく `409` で、もう 1 つの状況を示します。このリクエストが提示した `Idempotency-Key` に対して、別の呼び出しがすでに実行中である場合です。`Retry-After` 秒後に同じキーを再送して、その呼び出しの結果を回収してください。 | | `client_disconnected` | 呼び出し側が、Router が結果を返す前に接続を閉じました。これは配信ではなくログに記録されます。書き込むソケットが残っていないためです。また、これは課金結果ではなく原因の帰属を示すものです。完了したプロバイダーによる生成は、呼び出し側がレスポンスを受信したかどうかに関係なく請求されます。 | | `internal_error` | Router 自体が失敗しました。これは、クライアントが認識できないバケットを扱う際の値でもあります。これにより、後でセットに追加が行われても、それ以前に生成されたクライアントが壊れることはありません。 | | `deadline_exceeded` | 回答が到着する前に、Comfy が自身の設定済みの上限で接続の維持を停止しました。`provider_timeout` と `504` を共有し、このペアはどちらの側が時間切れになったかを示します。こちらは Comfy 自身の上限であるため、リクエストのいかなる部分も拒否されておらず、同じリクエストを再試行できます。課金については何も示しません。完了したプロバイダーによる生成は、呼び出し側がレスポンスを受信したかどうかに関係なく請求されます。同じ `Idempotency-Key` で再試行してください。プロバイダーがすでに生成を受け付けていた場合、再試行は新たな生成をディスパッチするのではなくその生成を回収し、`504` の `Retry-After` がいつ問い合わせるべきかを示します。 | @@ -168,8 +173,9 @@ Router が受け付けたものの完了できなかったリクエストに対 | ヘッダー | 型 | 説明 | | --- | --- | --- | | `Cache-Control` | `string` | 提供されるスキーマドキュメントの鮮度ディレクティブ。ルートが認証済みのため `private` です。ドキュメント自体は呼び出し元固有ではありませんが、共有キャッシュは認証済みリクエストへのレスポンスを保持してはなりません。また、`must-revalidate` により、古いコピーはそのまま提供されるのではなく `ETag` に対して再検証されます。 | -| `ETag` | `string` | `GET /v1/models/{provider}/{model}/openapi.json` で提供されるドキュメントのバイト列に対する強力なエンティティタグ。モデルごとのスキーマはほとんど変更されず、SDK が頻繁に再取得するため、呼び出し元はこの値を保存し、`If-None-Match` として送り返すことで、ドキュメントの代わりに `304` を受け取るべきです。 | -| `Retry-After` | `integer` | 同じ `Idempotency-Key` で同じリクエストを再試行するまでに待つべき秒数。`deadline_exceeded` の `504` において、プロバイダーがまだ実行中の生成へのハンドルを Comfy が保持している場合にのみ存在します。値は Router 自身のポーリング間隔で、このルートが「後でもう一度問い合わせる」ために示せる唯一の正直な数値です。回収できるものがない場合、つまりキーなしの呼び出しや、プロバイダーが何かを受け付ける前に上限が切れた場合には存在しません。 | +| `ETag` | `string` | `GET /v2/models/{provider}/{model}/openapi.json` で提供されるドキュメントのバイト列に対する強力なエンティティタグ。モデルごとのスキーマはほとんど変更されず、SDK が頻繁に再取得するため、呼び出し元はこの値を保存し、`If-None-Match` として送り返すことで、ドキュメントの代わりに `304` を受け取るべきです。 | +| `Idempotent-Replayed` | `boolean` | このレスポンスがモデルを再実行するのではなく、`Idempotency-Key` の記録から提供された場合に存在し、値は `true` です。元の呼び出しのステータス、ボディ、コンテンツタイプを運び、2 回目の課金は行われません。課金は元の呼び出しが完了した時点で確定しています。新しい実行ではこのヘッダーは `false` として送られるのではなく存在しないため、その有無で分岐してください。 | +| `Retry-After` | `integer` | 同じ `Idempotency-Key` で同じリクエストを再試行するまでに待つべき秒数。そのような再試行が実際に結果を回収できる 2 種類の応答に設定されます。1 つは `error_type: concurrency_limit_exceeded` を伴う `409` で、そのキーに対する元の呼び出しがまだ実行中の場合です。もう 1 つは `deadline_exceeded` の `504` で、Comfy は接続の保持を停止したものの、プロバイダーが実行中の生成へのハンドルをまだ保持している場合です。どちらの場合も、値は Router 自身が再度問い合わせる前に待つ間隔であり、このルートが「後でもう一度問い合わせる」ために示せる唯一の正直な数値です。回収できるものがない場合、つまりキーなしの呼び出し、プロバイダーが何かを受け付ける前に上限が切れた場合、または呼び出し元に待機を求めるのではなくキーを即座に拒否する `409` の場合には存在しません。 | | `X-Comfy-Error-Type` | [`RouterErrorType`](#routererrortype) | 障害の大まかな機械可読バケットで、Router がすべてのエラーレスポンスに設定します。`RouterErrorResponse.error_type` と同じ値を保持し、`422` では唯一の機械可読バケットです。これは、そのボディが FastAPI の `detail[]` 形状であり、独自の `error_type` フィールドを持たないためです。したがって、クライアントは、受信した 2 つの Router エラーボディのどちらであるかを判断する前に、このヘッダーだけで分岐できます。 | | `X-Comfy-Request-Id` | `string` | この呼び出しのサーバー生成識別子で、成功、4xx、5xx を問わず、すべての Router レスポンスに存在します。エラーレスポンスこそ、ユーザーがサポートリクエストで引用する ID を必要とするタイミングだからです。同じ値が呼び出しの使用状況/監査イベントにも書き込まれるため、課金に関する苦情をタイムスタンプで検索する代わりに、課金自体に結び付けることができます。 | | `X-Committed-Spend-Current` | `integer` | 呼び出し元が現在、実行中の呼び出しにコミットしている米ドルのセント数。拒否された呼び出しは含みません。`X-Committed-Spend-Limit` と併せて存在します。 | @@ -178,7 +184,7 @@ Router が受け付けたものの完了できなかったリクエストに対 ## モデルごとの入力スキーマ -モデル独自の入力フィールドはここでは再掲しません。それらは `GET /v1/models/{provider}/{model}/openapi.json` から直接取得できます。このエンドポイントは、サーバーが呼び出しの検証に使用するのと同じドキュメントを提供するため、公開されている内容と実際に適用される内容が乖離することはありません。`GET /v1/models` からモデルIDを取得し、その呼び出しパスに `/openapi.json` を追加して、返されたドキュメントに基づいて生成します。 +モデル独自の入力フィールドはここでは再掲しません。それらは `GET /v2/models/{provider}/{model}/openapi.json` から直接取得できます。このエンドポイントは、サーバーが呼び出しの検証に使用するのと同じドキュメントを提供するため、公開されている内容と実際に適用される内容が乖離することはありません。`GET /v2/models` からモデルIDを取得し、その呼び出しパスに `/openapi.json` を追加して、返されたドキュメントに基づいて生成します。 ## スキーマ @@ -225,11 +231,11 @@ Router障害の大まかで機械可読なバケットであり、`X-Comfy-Error | フィールド | 型 | 必須 | 制約 | 説明 | | --- | --- | --- | --- | --- | -| `input_schema_url` | 文字列 | いいえ | `format: uri`, `pattern: ^https://`, `maxLength: 2048` | このモデルの入力スキーマドキュメントへのポインタ: このモデルに対して `POST /v1/models/{provider}/{model}` が受け付けるボディの説明です。この契約の一部となるのはポインタのみです。ポインタが指すドキュメントは別途作成されます。モデル用のスキーマが作成されていない場合は存在しません。 | +| `input_schema_url` | 文字列 | いいえ | `format: uri`, `pattern: ^https://`, `maxLength: 2048` | このモデルの入力スキーマドキュメントへのポインタ: このモデルに対して `POST /v2/models/{provider}/{model}` が受け付けるボディの説明です。この契約の一部となるのはポインタのみです。ポインタが指すドキュメントは別途作成されます。モデル用のスキーマが作成されていない場合は存在しません。 | ### RouterModelId -正規の Comfy Router モデル ID です。`{provider}/{model}` は、`POST /v1/models/{provider}/{model}` でモデルを指定する際に使用する正確な値です。そのため、呼び出し元はこの値をそのままパスに埋め込むことができ、他の情報から再導出する必要はありません。`pattern` は `RouterProviderSegment` と `RouterModelSegment` を単一の `/` で連結したもので、`maxLength` はそれらの合計にそのセパレータを加えた長さです。 +正規の Comfy Router モデル ID です。`{provider}/{model}` は、`POST /v2/models/{provider}/{model}` でモデルを指定する際に使用する正確な値です。そのため、呼び出し元はこの値をそのままパスに埋め込むことができ、他の情報から再導出する必要はありません。`pattern` は `RouterProviderSegment` と `RouterModelSegment` を単一の `/` で連結したもので、`maxLength` はそれらの合計にそのセパレータを加えた長さです。 型: `string`、`pattern: ^[a-z0-9]+([._-][a-z0-9]+)*/[a-z0-9]+([._-][a-z0-9]+)*$`、`maxLength: 193` @@ -241,7 +247,7 @@ Router障害の大まかで機械可読なバケットであり、`X-Comfy-Error ### RouterModelInputSchemaDocument -単一の Comfy Router モデルの入力を説明するスタンドアロンの OpenAPI ドキュメントです。これは、そのモデルに対して `POST /v1/models/{provider}/{model}` が受け付けるボディです。`GET /v1/models/{provider}/{model}/openapi.json` が返すのはこのドキュメントです。 +単一の Comfy Router モデルの入力を説明するスタンドアロンの OpenAPI ドキュメントです。これは、そのモデルに対して `POST /v2/models/{provider}/{model}` が受け付けるボディです。`GET /v2/models/{provider}/{model}/openapi.json` が返すのはこのドキュメントです。 型: `object` @@ -251,7 +257,7 @@ Routerモデルカタログの1エントリです。実行可能なモデルの | フィールド | 型 | 必須 | 制約 | 説明 | | --- | --- | --- | --- | --- | -| `id` | [`RouterModelId`](#routermodelid) | はい | `pattern: ^[a-z0-9]+([._-][a-z0-9]+)*/[a-z0-9]+([._-][a-z0-9]+)*$`, `maxLength: 193` | 正規のComfy RouterモデルID、`{provider}/{model}`です。`POST /v1/models/{provider}/{model}`でモデルを指定する正確な値であり、呼び出し元は何かから再導出することなく、この値をそのパスに挿入できます。その`pattern`は`RouterProviderSegment`と`RouterModelSegment`を単一の`/`で連結したものであり、`maxLength`はそれらの合計にその区切り文字を加えたものです。 | +| `id` | [`RouterModelId`](#routermodelid) | はい | `pattern: ^[a-z0-9]+([._-][a-z0-9]+)*/[a-z0-9]+([._-][a-z0-9]+)*$`, `maxLength: 193` | 正規のComfy RouterモデルID、`{provider}/{model}`です。`POST /v2/models/{provider}/{model}`でモデルを指定する正確な値であり、呼び出し元は何かから再導出することなく、この値をそのパスに挿入できます。その`pattern`は`RouterProviderSegment`と`RouterModelSegment`を単一の`/`で連結したものであり、`maxLength`はそれらの合計にその区切り文字を加えたものです。 | | `provider` | [`RouterProviderSegment`](#routerprovidersegment) | はい | `pattern: ^[a-z0-9]+([._-][a-z0-9]+)*$`, `maxLength: 64` | 正規の`{provider}/{model}[/{variant}]`モデルIDの小文字の`provider`セグメントです。つまり、モデルが指定されているパートナーです。呼び出しルートの`provider`パスパラメータとカタログエントリの`provider`フィールドは、どちらもこの1つのスキーマを参照しており、これにより、リストされたIDと受け入れられるIDが乖離しないようになっています。 | | `model` | [`RouterModelSegment`](#routermodelsegment) | はい | `pattern: ^[a-z0-9]+([._-][a-z0-9]+)*$`, `maxLength: 128` | 正規の`{provider}/{model}[/{variant}]`モデルIDの小文字の`model`セグメントです。つまり、そのプロバイダー内で実行するモデルです。`RouterProviderSegment`と同じ乖離防止の理由により、呼び出しルートの`model`パスパラメータとカタログエントリの`model`フィールドで共有されています。 | | `billing` | [`RouterModelBilling`](#routermodelbilling) | はい | - | 呼び出し元が呼び出し前に必要とするモデルごとの請求の事実です。価格ではありません。使用量やコストの数値がここに現れることはありません。 | diff --git a/ko/api-reference/comfy-router/limitations.mdx b/ko/api-reference/comfy-router/limitations.mdx index ea9303d4e..f3123c4f6 100644 --- a/ko/api-reference/comfy-router/limitations.mdx +++ b/ko/api-reference/comfy-router/limitations.mdx @@ -1,23 +1,23 @@ --- title: "Comfy Router 제한 사항" description: "현재 Comfy Router가 지원하지 않는 기능, 대안이 존재하는 경우 대신 사용할 도구, 그리고 변경될 것으로 예상되는 제한 사항을 설명합니다." -translationSourceHash: b9e78b52 +translationSourceHash: b7db80c8 translationFrom: api-reference/comfy-router/limitations.mdx translationBlockHashes: - "_intro": 9eea7f43 + "_intro": 21bb3a18 "At a glance": fc2ff613 - "No queued submission": f72af0fd + "No queued submission": e529f3ab "No cost or credit figures on a response": ba168aa9 - "No way to resume a call you lost": a783a987 + "No way to resume a call you lost": 3911a303 "Calls are cut off at a server deadline": 45bacfca - "Requests are rate limited per caller": 01f2a8c8 - "No progress while a call runs": be9de68d + "Requests are rate limited per caller": 12eb3edd + "No progress while a call runs": d9c528a1 "Three forecast buckets are not in the vocabulary": f85a2589 "Router does not cover every partner operation": 9ecc10d1 "Next": 2d3bb742 --- -**Comfy Router는 아직 일반에 공개되지 않았습니다.** 아래에서 언급하는 라우트(`POST /v1/models/{provider}/{model}` 및 카탈로그·스키마 관련 라우트)는 아직 요청을 처리하지 않습니다. 현재 인증된 호출은 `404`를 반환합니다. 이 페이지는 해당 라우트들이 제공할 계약을 설명하며, 통합이 알려진 형태를 기준으로 작성될 수 있도록 해당 롤아웃 이전에 게시되었습니다. 아래의 모든 내용은 해당 계약에 대한 설명이지, 지금 당장 실행할 수 있는 동작에 대한 설명이 아닙니다. +**Comfy Router는 아직 일반에 공개되지 않았습니다.** 아래에서 언급하는 라우트(`POST /v2/models/{provider}/{model}` 및 카탈로그·스키마 관련 라우트)는 아직 요청을 처리하지 않습니다. 현재 인증된 호출은 `404`를 반환합니다. 이 페이지는 해당 라우트들이 제공할 계약을 설명하며, 통합이 알려진 형태를 기준으로 작성될 수 있도록 해당 롤아웃 이전에 게시되었습니다. 아래의 모든 내용은 해당 계약에 대한 설명이지, 지금 당장 실행할 수 있는 동작에 대한 설명이 아닙니다. Comfy Router는 하나의 동기식 호출입니다. 파트너 모델의 네이티브 입력을 하나의 자격 증명으로 하나의 호스트에 전송하면 연결이 유지되고, `200` 응답이 해당 모델의 네이티브 출력을 전달합니다. 이러한 형태 덕분에 첫 번째 통합이 짧아지며, 이 페이지의 모든 제한 사항도 바로 여기서 비롯됩니다. Router를 중심으로 설계하기 전에, 설계 이후가 아니라 이 페이지를 읽으십시오. 아래 내용의 대부분은 간단한 대안이 있으며, 그렇지 않은 항목은 Router에 대해 성립하지 않는 가정을 기반으로 구축하기 전에 알아둘 가치가 있습니다. @@ -39,7 +39,7 @@ Comfy Router는 하나의 동기식 호출입니다. 파트너 모델의 네이 ## 대기 중 제출 없음 -모델을 실행하는 방법은 하나뿐입니다. `POST /v1/models/{provider}/{model}`은 생성이 끝날 때까지 연결을 유지한 뒤 응답으로 결과를 반환합니다. 작업(job)을 받아 식별자를 돌려주고 나중에 결과를 가져갈 수 있게 해주는 엔드포인트는 없으며, 완료를 알리는 콜백이나 웹훅도 없습니다. +모델을 실행하는 방법은 하나뿐입니다. `POST /v2/models/{provider}/{model}`은 생성이 끝날 때까지 연결을 유지한 뒤 응답으로 결과를 반환합니다. 작업(job)을 받아 식별자를 돌려주고 나중에 결과를 가져갈 수 있게 해주는 엔드포인트는 없으며, 완료를 알리는 콜백이나 웹훅도 없습니다. **대신 할 수 있는 방법.** 대부분의 모델에서는 이는 문제가 되지 않습니다. 연결을 유지한 채 결과를 읽으면 됩니다. 빠른 이미지 모델은 몇 초 안에 결과를 반환하고, 긴 비디오 생성은 수 분 동안 실행될 수 있지만 Router가 그동안 연결을 유지합니다. 클라이언트 읽기 타임아웃을 넉넉하게, [Router 자체의 데드라인](#호출이-서버-마감-시간에-중단됨) 이상으로 설정하고, 이 호출을 빠른 요청이 아닌 장기 실행으로 취급하세요. 아키텍처상 정말로 연결을 유지할 수 없는 경우, 즉 실행 시간 상한이 짧은 서버리스 함수나 사용자가 닫을 것으로 예상되는 브라우저 탭이라면, 연결을 유지할 수 있는 여러분이 제어하는 워커에서 호출을 실행하거나, 자체 제출(submit) 및 폴링(poll) 쌍을 제공하는 공급자의 파트너 프록시 경로를 사용하세요. [마지막 섹션](#router는-모든-파트너-작업을-다루지-않습니다)을 참조하세요. @@ -57,16 +57,16 @@ Router 응답은 모델이 무엇을 생성했는지 알려주며, 그 계약에 Router는 진행 중인 호출의 재개 가능한 기록을 보관하지 않습니다. 상태 라우트도, 작업 식별자도, 다시 연결할 대상도 없습니다. 호출 도중 연결이 끊기면(클라이언트 충돌, 네트워크 분할, 프로세스를 재시작하는 배포) 응답은 사라지며, 이후에 그 호출에 대해 문의할 수도 없습니다. *생성*이 완료되어 청구되었는지는 응답을 받았는지와는 별개의 문제이며, 연결이 끊어졌다는 사실만으로는 어느 쪽도 확실히 알 수 없습니다. -**대신 이렇게 하세요.** 모든 호출에 `Idempotency-Key` 헤더를 보내세요. 잃어버린 호출을 재개할 수 있게 해주지는 않지만, 재시도를 안전하게 만듭니다. Router는 호출이 진행되는 동안 키를 예약하며, 호출이 실제로 답변을 전달한 경우 해당 응답을 키에 연결해 24시간 동안 기록합니다. **같은** 키로 재시도하면 기록된 응답이 재생되고, 공급자에게 두 번째로 디스패치(및 재청구)되지 않습니다. `Idempotent-Replayed: true`가 표시되므로 재생과 새 실행을 구분할 수 있습니다. 논리적 호출마다 새 키를 생성하세요. 시도마다가 아니라요. *다른* 요청 본문으로 같은 키를 제시하면 조용한 덮어쓰기가 아니라 `409`가 반환됩니다. +**대신 이렇게 하세요.** 모든 호출에 `Idempotency-Key` 헤더를 보내세요. 구체적인 방법은 [빠른 시작](/ko/api-reference/comfy-router/quickstart#자신의-키로-안전하게-재시도하기)에 있으며, 가장 건너뛰기 쉬운 단계도 함께 다룹니다. 요청을 보내기 전에 키를 먼저 저장하는 것입니다. 잃어버린 호출을 재개할 수 있게 해주지는 않지만, 재시도를 안전하게 만듭니다. Router는 호출이 진행되는 동안 키를 예약하며, 호출이 실제로 답변을 전달한 경우 해당 응답을 키에 연결해 24시간 동안 기록합니다. **같은** 키로 재시도하면 기록된 응답이 재생되고, 공급자에게 두 번째로 디스패치(및 재청구)되지 않습니다. `Idempotent-Replayed: true`가 표시되므로 재생과 새 실행을 구분할 수 있습니다. 논리적 호출마다 새 키를 생성하세요. 시도마다가 아니라요. *다른* 요청(다른 본문, 모델 경로, 쿼리 문자열 또는 메서드)으로 같은 키를 제시하면 조용한 덮어쓰기가 아니라 `409`가 반환됩니다. -이것이 무엇을 보장하는지 정확히 이해하세요. 이는 **청구** 속성이지 전달 속성이 아닙니다. **키는 최대 한 번만 청구됩니다.** 키가 공급자에게 최대 한 번만 디스패치된다는 약속이 아닙니다. Router는 실제로 답변을 받은 경우에만 키를 보유합니다. 청구되지 않은 결과는 키를 해제하여 호출을 다시 할 수 있게 합니다. `5xx`, `408`/`425`/`429`, 그리고 (여기서 중요한 경우) 아무것도 도달하지 않은 호출. 이 모든 경우가 키를 해제하며, 해당 키로 재시도하면 실제로 다시 실행되어 공급자에게 다시 디스패치됩니다. +이것이 무엇을 보장하는지 정확히 이해하세요. 이는 **청구** 속성이지 전달 속성이 아닙니다. **키는 최대 한 번만 청구됩니다.** 키가 공급자에게 최대 한 번만 디스패치된다는 약속이 아닙니다. Router는 실제로 답변을 받은 경우에만 키를 보유합니다. 청구되지 않은 결과는 키를 해제하여 호출을 다시 할 수 있게 합니다. `5xx`, `408`/`425`/`429`, 그리고 (여기서 중요한 경우) 아무것도 도달하지 않은 호출. 이 모든 경우가 키를 해제하며, 해당 키로 재시도하면 실제로 다시 실행되어 공급자에게 다시 디스패치됩니다. 키를 해제하지 *않는* 유일한 `5xx`는 회수할 것이 남아 있는 중단, 즉 `Retry-After`가 포함된 `deadline_exceeded` `504`입니다. 이는 Router가 대기를 멈춘 시점에 공급자가 이미 생성을 수락했다는 뜻이며, Router는 키를 해제하는 대신 실행 중인 그 작업에 키를 묶어 둡니다. `Retry-After` 이후에 **같은** 키를 다시 보내 결과를 회수하세요. 이 경우 새 키를 쓰면 두 번째로 청구되는 생성이 됩니다. `Retry-After`가 없는 `504`는 묶어 둘 것이 없었으므로 나머지처럼 키를 해제합니다. **즉, 연결 끊김은 멱등성이 *구해주지 못하는* 경우입니다.** 호출 도중 연결이 끊어지면 대개 어떤 응답도 커밋되지 않았다는 뜻이며, 이는 정확히 위의 해제 경로입니다. 같은 키로 재시도하면 놓친 결과를 건네주는 대신 새 실행이 시작됩니다. 원래 생성이 이미 디스패치되었다면 공급자가 두 번째로 실행할 수도 있습니다. 이것이 올바른 기본값입니다. 받지 못한 미청구 호출은 다시 실행할 수 있어야 합니다. 다만 "재시도는 새 실행을 만든다"고 계획하세요. "재시도가 잃어버린 실행을 회수한다"가 아니라요. Router가 키에 대해 무언가를 *보유*하고 있는 경우, 재시도는 재실행이 아니라 응답으로 처리됩니다. 원래 응답이 재생되거나, 그럴 수 없는 이유를 설명하는 `409`가 반환됩니다. 원래 호출이 아직 진행 중일 때 보낸 재시도는 `Retry-After`가 포함된 `409`가 되므로, 기다렸다가 같은 키를 다시 보내세요. 완료되었지만 Router가 충실한 사본을 보관하지 못한 호출에 대한 재시도도 `409`입니다. 이는 응답이 너무 큰 경우만 해당하지 않습니다. 재생 한도를 넘은 응답, 응답 후 실패하거나 패닉한 핸들러, 전송이 실패하거나 부족했던 쓰기. 이 모든 경우 키가 "소비되었지만 재생 불가"로 기록되고 같은 `409`가 반환됩니다. 크기 문제를 찾으러 가지 마세요. 이러한 모든 경우의 지침은 동일합니다. **새** 키를 사용하세요. 원래 호출은 완료되어 청구되었으며, Router는 그 응답을 지어내지도 않고 이전 키로 다시 실행하지도 않습니다. -**생성된 계약에는 아직 없습니다.** 여기서 설명하는 `Idempotency-Key` 요청 헤더, `409` 응답, `Idempotent-Replayed` 및 `Retry-After` 응답 헤더는 레퍼런스가 생성되는 기반인 OpenAPI 계약의 `POST /v1/models/{provider}/{model}`에 선언되어 있지 않습니다. 따라서 생성된 API 레퍼런스에는 나타나지 않으며 SDK도 이를 모델링하지 않습니다. 지원될 때까지 직접 보내고 읽으세요. +**계약에 포함되어 있으며, 한 가지 공백이 있습니다.** 여기서 설명하는 `Idempotency-Key` 요청 헤더, `409` 응답, `Idempotent-Replayed` 및 `Retry-After` 응답 헤더는 `POST /v2/models/{provider}/{model}`에 선언되어 있으므로, 생성된 [API 레퍼런스](/ko/api-reference/comfy-router/reference)와 SDK가 벤더링하는 명세에 나타나며, SDK는 재생성 시 이를 반영합니다. 공백은 이것입니다. `Retry-After`는 `409`와 `504`에는 선언되어 있지만, [아래](#요청은-호출자별로-속도-제한됨)에서 설명하는 `rate_limited` `429`에는 선언되어 있지 **않습니다**. 그 `429`도 이 헤더를 보내므로, 계약에 명시되기를 기다리지 말고 거기서도 읽으세요. **상태: 아직 아님.** 영구적이고 재개 가능한 실행은 대기 중 경로와 함께 제공될 예정이며, 그곳이 요청 기록이 보관될 곳입니다. 멱등 재시도는 오늘의 답이며 임시방편이 아닙니다. 어쨌든 통합할 가치가 있습니다. @@ -85,17 +85,17 @@ Router 호출 하나는 연결을 **10분** 동안 유지할 수 있습니다. ## 요청은 호출자별로 속도 제한됨 -Router는 트래픽에 대해 서로 다른 두 가지를 제한하며, 같은 `429`에 서로 다른 두 버킷으로 응답합니다. 동시성 제한은 한 번에 **진행 중**일 수 있는 호출 수를 제한하고 `concurrency_limit_exceeded`로 응답합니다. 이는 여러분의 호출 중 하나가 끝나는 순간 해소되므로 몇 초 뒤 재시도하는 것이 옳습니다. 속도 제한은 Router 표면을 **얼마나 자주** 호출할 수 있는지를 제한하며(`POST /v1/models/{provider}/{model}`와 `/v1/models` 하위의 세 가지 카탈로그 읽기 모두 해당하고, 호출이 모델을 실행했는지 그 전에 거부되었는지는 상관없습니다) `rate_limited`로 응답합니다. 이쪽은 1분 창(window) 동안 지속적으로 다시 채워지는 할당량이므로 무엇을 해도 더 빨리 비울 수 없습니다. 응답에는 기다려야 할 초 수를 담은 `Retry-After` 헤더가 포함되고, `detail`에 창이 명시됩니다. 상태 코드만 보지 말고 반드시 `X-Comfy-Error-Type`으로 분기하세요. +Router는 트래픽에 대해 서로 다른 두 가지를 제한하며, 같은 `429`에 서로 다른 두 버킷으로 응답합니다. 동시성 제한은 한 번에 **진행 중**일 수 있는 호출 수를 제한하고 `concurrency_limit_exceeded`로 응답합니다. 이는 여러분의 호출 중 하나가 끝나는 순간 해소되므로 몇 초 뒤 재시도하는 것이 옳습니다. 속도 제한은 Router 표면을 **얼마나 자주** 호출할 수 있는지를 제한하며(`POST /v2/models/{provider}/{model}`와 `/v2/models` 하위의 세 가지 카탈로그 읽기 모두 해당하고, 호출이 모델을 실행했는지 그 전에 거부되었는지는 상관없습니다) `rate_limited`로 응답합니다. 이쪽은 1분 창(window) 동안 지속적으로 다시 채워지는 할당량이므로 무엇을 해도 더 빨리 비울 수 없습니다. 응답에는 기다려야 할 초 수를 담은 `Retry-After` 헤더가 포함되고, `detail`에 창이 명시됩니다. 상태 코드만 보지 말고 반드시 `X-Comfy-Error-Type`으로 분기하세요. 이 제한은 출발지 주소가 아니라 인증된 호출자를 키로 삼으므로, 호스트를 넘어가도 자격 증명을 따라갑니다. 여러분 자신의 공급자 키(bring-your-own-key)로 실행되는 호출은 제외됩니다. 그 처리량은 여러분의 것이기 때문입니다. 할당량은 공개된 상수가 아니라 서버 측 구성 값이며, 이 페이지는 의도적으로 그 수치를 밝히지 않습니다. 숫자가 아니라 백오프를 전제로 설계하세요. -**대신 이렇게 하세요.** `Retry-After`를 지키세요. 그 시간 안의 재시도는 같은 거부에 부딪힙니다. `GET /v1/models`와 모델의 `openapi.json`은 한 번 가져와 프로세스 수명 동안 캐시하고, 호출마다 다시 읽지 마세요. 이들은 배포 시에만 바뀝니다. `429`에서 받은 요청 식별자를 보관하는 클라이언트는 지원팀이 추적할 수 있는 증거를 갖게 됩니다. +**대신 이렇게 하세요.** `Retry-After`를 지키세요. 그 시간 안의 재시도는 같은 거부에 부딪힙니다. `GET /v2/models`와 모델의 `openapi.json`은 한 번 가져와 프로세스 수명 동안 캐시하고, 호출마다 다시 읽지 마세요. 이들은 배포 시에만 바뀝니다. `429`에서 받은 요청 식별자를 보관하는 클라이언트는 지원팀이 추적할 수 있는 증거를 갖게 됩니다. **상태: 의도적.** 호출자별 요청 속도 상한은 마감 시간과 같은 이유로 존재해야 합니다. 숫자는 조정될 수 있지만 제한의 존재는 사라지지 않습니다. ## 호출이 실행되는 동안에는 진행률이 없음 -`POST /v1/models/{provider}/{model}`는 종료 시점에 정확히 한 번만 응답을 반환합니다. 스트리밍 응답, 서버 전송 이벤트, 백분율, 부분 또는 미리보기 프레임이 없습니다. 이는 파트너의 자체 API가 제출 후 폴링 방식인 경우에도 마찬가지입니다. Router는 해당 폴링을 내부적으로, 즉 여러분의 단일 호출 안에서 처리하며, 그 과정에서 확인되는 중간 상태는 여러분에게 전달되지 않습니다. 외부에서 보면 3초짜리 이미지와 6분짜리 비디오는 같은 형태입니다. 요청 하나, 응답 하나, 그 사이에 아무것도 없습니다. +`POST /v2/models/{provider}/{model}`는 종료 시점에 정확히 한 번만 응답을 반환합니다. 스트리밍 응답, 서버 전송 이벤트, 백분율, 부분 또는 미리보기 프레임이 없습니다. 이는 파트너의 자체 API가 제출 후 폴링 방식인 경우에도 마찬가지입니다. Router는 해당 폴링을 내부적으로, 즉 여러분의 단일 호출 안에서 처리하며, 그 과정에서 확인되는 중간 상태는 여러분에게 전달되지 않습니다. 외부에서 보면 3초짜리 이미지와 6분짜리 비디오는 같은 형태입니다. 요청 하나, 응답 하나, 그 사이에 아무것도 없습니다. **대신 해야 할 일.** 현재 Router에서는 할 수 있는 일이 없습니다. 출처를 알 수 없는 백분율 대신 불확정 진행 상태를 표시하세요. 특정 공급자에게 진행률이 필수 요구 사항이라면, 해당 공급자의 파트너 프록시 라우트가 자체 폴링이나 스트리밍을 제공하는지 확인하고 그 라우트를 직접 사용하세요. 일부는 해당 기능을 제공하며, 그 라우트는 변경되지 않았고 완전히 지원됩니다. diff --git a/ko/api-reference/comfy-router/quickstart.mdx b/ko/api-reference/comfy-router/quickstart.mdx index fd8c1bb55..bef104369 100644 --- a/ko/api-reference/comfy-router/quickstart.mdx +++ b/ko/api-reference/comfy-router/quickstart.mdx @@ -1,27 +1,28 @@ --- title: "Comfy Router 빠른 시작" description: "아무것도 없는 상태에서 Python과 TypeScript로 Comfy Router를 사용해 약 5분 만에 생성된 이미지를 얻는 방법." -translationSourceHash: 50fc5524 +translationSourceHash: ead13a74 translationFrom: api-reference/comfy-router/quickstart.mdx translationBlockHashes: - "_intro": 46050c92 + "_intro": d9a651bf "Why this page uses `bfl/flux-2-pro`": 0eaf0bb3 "Get a key": 843d281f - "cURL": bc3e1e4c - "Python": 059e95b8 - "TypeScript": 1053430f + "cURL": 8ea9cb9f + "Python": f892ccc3 + "TypeScript": d0117cbc "Reading the `422`": 602cd505 - "Find a model": 9b7855c1 - "Where the model's fields come from": 7861bd52 + "Retrying safely with your own key": d1e75ba8 + "Find a model": a4caa991 + "Where the model's fields come from": 9cf8930c "Next": 6aa641e1 --- -**Comfy Router는 아직 일반에 공개되지 않았습니다.** 아래의 라우트, 즉 `POST /v1/models/{provider}/{model}` 및 해당 카탈로그와 스키마 관련 라우트는 아직 요청을 처리하지 않습니다. 현재 인증된 호출은 `404`를 반환합니다. 이 페이지는 이 라우트가 제공할 계약을 문서화하며, 해당 출시에 앞서 게시되어 통합 코드를 미리 작성할 수 있도록 합니다. 지금 바로 사용할 수 있는 동작에 대한 설명은 아닙니다. +**Comfy Router는 아직 일반에 공개되지 않았습니다.** 아래의 라우트, 즉 `POST /v2/models/{provider}/{model}` 및 해당 카탈로그와 스키마 관련 라우트는 아직 요청을 처리하지 않습니다. 현재 인증된 호출은 `404`를 반환합니다. 이 페이지는 이 라우트가 제공할 계약을 문서화하며, 해당 출시에 앞서 게시되어 통합 코드를 미리 작성할 수 있도록 합니다. 지금 바로 사용할 수 있는 동작에 대한 설명은 아닙니다. Comfy Router는 파트너 모델을 하나의 호스트, 하나의 자격 증명, 하나의 라우트 형태 뒤에서 실행합니다. 이 페이지는 생성된 이미지에 도달하는 가장 짧은 완전한 경로입니다. 클라이언트를 설치하고, 키를 설정하고, 요청을 하나 보내고, 결과를 읽고, 실제로 마주하기 이전에 첫 번째 실패가 어떤 모습인지 확인하는 것입니다. -Base URL은 `https://api.comfy.org`입니다. 라우트는 `POST /v1/models/{provider}/{model}`이며, 요청 본문은 모델 자체의 네이티브 JSON 입력이고, `200`은 모델 자체의 네이티브 JSON 출력을 전달합니다. Router는 입력도 출력도 래핑하지 않으므로, 이미 파트너 API에 대해 작성한 호출은 호스트만 변경하면 Router 호출이 됩니다. +Base URL은 `https://api.comfy.org`입니다. 라우트는 `POST /v2/models/{provider}/{model}`이며, 요청 본문은 모델 자체의 네이티브 JSON 입력이고, `200`은 모델 자체의 네이티브 JSON 출력을 전달합니다. Router는 입력도 출력도 래핑하지 않으므로, 이미 파트너 API에 대해 작성한 호출은 호스트만 변경하면 Router 호출이 됩니다. ## 이 페이지에서 `bfl/flux-2-pro`를 사용하는 이유 @@ -48,7 +49,7 @@ export COMFY_API_KEY="comfyui-..." 가장 짧은 호출로, 스크립트, 스모크 테스트, 터미널에 복사하여 붙여넣기용입니다: ```bash -curl https://api.comfy.org/v1/models/bfl/flux-2-pro \ +curl https://api.comfy.org/v2/models/bfl/flux-2-pro \ -H "X-API-Key: $COMFY_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ @@ -117,7 +118,7 @@ def run(model: str, arguments: dict, idempotency_key: str) -> dict: # provider a second time. Reuse the SAME key when retrying one logical # call; generate a new one for a new call. response = httpx.post( - f"{BASE_URL}/v1/models/{model}", + f"{BASE_URL}/v2/models/{model}", headers={ "X-API-Key": os.environ["COMFY_API_KEY"], "Idempotency-Key": idempotency_key, @@ -229,7 +230,7 @@ async function run( // Idempotency-Key makes a retry safe on a PAID call: Router replays the // original response for 24h instead of dispatching (and billing) the provider // a second time. Reuse the SAME key when retrying one logical call. - const response = await fetch(`${BASE_URL}/v1/models/${model}`, { + const response = await fetch(`${BASE_URL}/v2/models/${model}`, { method: "POST", headers: { "X-API-Key": API_KEY, @@ -291,13 +292,53 @@ invalid_input (HTTP 422), request id 6f1c... `X-Comfy-Request-Id`는 성공, `4xx`, `5xx` 할 것 없이 모든 응답에 포함되며 지원팀 요청에서 인용할 ID입니다. 두 샘플 모두 헤더 로깅을 켜고 다시 실행하여 찾도록 하는 대신 이 ID를 예외에 첨부합니다. +## 자신의 키로 안전하게 재시도하기 + +위의 두 샘플은 모두 `Idempotency-Key`를 보냅니다. 이 헤더는 별도의 섹션으로 다룰 가치가 있습니다. 키를 올바르게 다룰 때에만 제 역할을 하고, 결정적인 차이를 만드는 단계가 요청을 보내기도 전에 일어나기 때문입니다. + +**키는 직접 준비하세요.** Router가 키를 발급해 주지 않습니다. *논리적 호출*마다 새 키를 생성하고(UUID가 의도된 형태입니다), *그 호출의* 모든 재시도에는 같은 키를 재사용하세요. 시도마다 새 키를 쓰면 얻는 것이 없고, 실제로 서로 다른 두 호출에 같은 키를 재사용하면 `409`가 됩니다. 같은 키에 다른 요청(본문이 다른 경우뿐 아니라 모델 경로, 쿼리 문자열, 메서드가 다른 경우도 포함)이 오면 조용한 덮어쓰기가 아니라 충돌로 처리되기 때문입니다. + +**보내기 전에 저장하세요.** 응답이 돌아온 뒤가 아니라 `POST`가 나가기 *전에*, 요청보다 오래 남는 곳(생성 대상이 되는 행, 작업 레코드, 큐 메시지)에 키를 기록하세요. 충돌한 프로세스의 메모리에만 존재했던 키는 다시 보낼 수 없고, Router의 기록으로 응답받았을 재시도는 대신 별도로 청구되는 새 실행이 됩니다. 이 단계는 건너뛰기 쉽지만 건너뛰면 비용이 큰 단계입니다. + +**그 키로 재시도하세요.** 재시도 시 Router는 해당 키에 대한 상태를 아직 보유하고 있는 한 재실행하지 않고 응답합니다: + +| 받는 응답 | 의미 | 할 일 | +| --- | --- | --- | +| `Idempotent-Replayed: true`가 포함된 `200` | Router가 원래 응답을 재생했습니다. 다시 청구되지 않습니다. | 그대로 사용하세요. 원래 결과입니다. | +| `409` / `concurrency_limit_exceeded` | 원래 호출이 아직 실행 중입니다. | `Retry-After` 초만큼 기다린 뒤 **같은 키**를 다시 보내세요. | +| `409` / `invalid_input` | 이 키로는 이 요청을 처리할 수 없습니다. 같은 키로 다른 요청(본문, 모델 경로, 쿼리 또는 메서드)이 왔거나, 원래 호출이 완료되었지만 그 응답을 재생할 수 없는 경우입니다. | **새** 키를 사용하세요. 이 키를 다시 보내지 마세요. | +| `Retry-After`가 포함된 `504` / `deadline_exceeded` | Router는 연결 유지를 중단했지만, 공급자가 실행 중인 생성에 대한 핸들은 아직 보유하고 있습니다. | `Retry-After` 초만큼 기다린 뒤 **같은 키**를 다시 보내 결과를 회수하세요. | + +위의 Python 샘플을 이어서 보겠습니다. 여기서는 파일이 여러분이 이미 갖고 있는 영구 저장소를 대신하며, 중요한 것은 메커니즘이 아니라 순서입니다. 키만 저장하지 말고 요청 전체를 키 옆에 함께 저장하세요. 재시도는 *같은* 모델과 인수를 다시 보내야 하며, 재시작 후 메모리에서 다시 조립한 요청이 공백 하나라도 다르면 `409`가 되고, 새 키로 보내면 두 번째로 청구되는 생성이 됩니다. + +```python +import json +import uuid + +# Persist BEFORE the request, so a crash between here and the response still +# leaves a key (and the exact request it belongs to) you can retry with. +request = { + "model": MODEL, + "arguments": {"prompt": "a red teapot on a windowsill, morning light"}, + "idempotency_key": str(uuid.uuid4()), +} +with open("pending-call.json", "w") as f: + json.dump(request, f) + +result = run(request["model"], request["arguments"], idempotency_key=request["idempotency_key"]) +``` + + +**키가 보장하는 것은 청구이지 전달이 아닙니다.** 키는 **최대 한 번** 청구됩니다. 키가 최대 한 번만 디스패치된다는 약속이 아니며, 잃어버린 호출을 복구할 수 있게 해주지도 않습니다. 호출 도중 연결이 끊기고 아무것도 여러분에게 커밋되지 않았다면 키는 해제되며, 그 키로 재시도하면 놓친 결과를 건네주는 대신 **새 실행**이 시작됩니다. "재시도는 새 실행을 만든다"고 계획하고, 재생은 보장이 아니라 잘 풀린 경우로 취급하세요. 영구적이고 재개 가능한 "다시 연결해 회수하기"는 Router에 아직 없는 대기 중 경로입니다. [제한 사항](/ko/api-reference/comfy-router/limitations#손실된-호출을-재개할-방법-없음)을 참조하세요. + + ## 모델 찾기 -`bfl/flux-2-pro`는 ID 하나일 뿐이고, 나머지는 카탈로그에 있습니다. `GET /v1/models`는 Router가 실행할 수 있는 모든 모델을 한 페이지씩 나열하며, 각 항목은 호출에 필요한 정보 그 자체입니다. 경로에 넣을 `id`, 따로 담긴 `provider`와 `model` 세그먼트, 그리고 비용을 쓰기 전에 분기 판단에 쓸 수 있는 `billing` 블록입니다. +`bfl/flux-2-pro`는 ID 하나일 뿐이고, 나머지는 카탈로그에 있습니다. `GET /v2/models`는 Router가 실행할 수 있는 모든 모델을 한 페이지씩 나열하며, 각 항목은 호출에 필요한 정보 그 자체입니다. 경로에 넣을 `id`, 따로 담긴 `provider`와 `model` 세그먼트, 그리고 비용을 쓰기 전에 분기 판단에 쓸 수 있는 `billing` 블록입니다. ```bash curl -H "X-API-Key: $COMFY_API_KEY" \ - "https://api.comfy.org/v1/models?limit=50" + "https://api.comfy.org/v2/models?limit=50" ``` ```json @@ -319,7 +360,7 @@ curl -H "X-API-Key: $COMFY_API_KEY" \ ```bash curl -H "X-API-Key: $COMFY_API_KEY" \ - https://api.comfy.org/v1/models/bfl/flux-2-pro/openapi.json + https://api.comfy.org/v2/models/bfl/flux-2-pro/openapi.json ``` 이 문서는 서버가 호출을 검증할 때 사용하는 바로 그 문서로, 독립형 OpenAPI 문서로 제공됩니다. 따라서 게시된 내용과 실제로 적용되는 내용이 서로 어긋날 수 없습니다. 위 카탈로그에서 아무 `id`나 선택한 뒤 해당 호출 경로에 `/openapi.json`을 붙이면, 반환된 결과를 기준으로 생성을 진행할 수 있습니다. diff --git a/ko/api-reference/comfy-router/reference.mdx b/ko/api-reference/comfy-router/reference.mdx index 9bc28d448..fef6e4dde 100644 --- a/ko/api-reference/comfy-router/reference.mdx +++ b/ko/api-reference/comfy-router/reference.mdx @@ -1,15 +1,15 @@ --- title: "Comfy Router API 레퍼런스" description: "Comfy API 계약에서 생성된 모든 Comfy Router 엔드포인트, 매개변수, 응답 본문 및 오류 버킷." -translationSourceHash: 5b914bce +translationSourceHash: 7d6b2b62 translationFrom: api-reference/comfy-router/reference.mdx translationBlockHashes: "_intro": 0114a881 - "Endpoints": 93fdf96a - "Error buckets": bfbc4524 - "Response headers": 8f8b3475 - "Per-model input schemas": ae73e63b - "Schemas": 8ce93415 + "Endpoints": e00ef646 + "Error buckets": ec90b196 + "Response headers": 07b7f334 + "Per-model input schemas": 978b7612 + "Schemas": 1062b7df --- @@ -29,11 +29,11 @@ Comfy Router의 정식 라우트로, 모델 ID로 주소가 지정됩니다. ## 엔드포인트 -### `GET /v1/models` +### `GET /v2/models` **Comfy Router가 실행할 수 있는 모델을 나열합니다.** -Comfy Router의 모델 카탈로그 - `POST /v1/models/{provider}/{model}`가 허용하는 표준 모델 ID의 한 페이지입니다. SDK는 콜드 스타트 시 이 엔드포인트를 호출하여 실행 가능한 모델을 발견하며, `model_not_found` 제안도 동일한 카탈로그에서 제공됩니다. 따라서 여기에 나열된 ID가 호출 시 404를 반환한다면 어느 한쪽만 실패하는 것보다 더 나쁩니다. 이러한 일관성은 약속이 아니라 구조적인 것입니다. 항목의 `provider`와 `model`은 호출 경로의 두 경로 세그먼트이며 해당 경로의 경로 파라미터가 참조하는 동일한 스키마 구성 요소를 참조하고, `id`는 이 두 세그먼트를 `/`로 연결한 것입니다. +Comfy Router의 모델 카탈로그 - `POST /v2/models/{provider}/{model}`가 허용하는 표준 모델 ID의 한 페이지입니다. SDK는 콜드 스타트 시 이 엔드포인트를 호출하여 실행 가능한 모델을 발견하며, `model_not_found` 제안도 동일한 카탈로그에서 제공됩니다. 따라서 여기에 나열된 ID가 호출 시 404를 반환한다면 어느 한쪽만 실패하는 것보다 더 나쁩니다. 이러한 일관성은 약속이 아니라 구조적인 것입니다. 항목의 `provider`와 `model`은 호출 경로의 두 경로 세그먼트이며 해당 경로의 경로 파라미터가 참조하는 동일한 스키마 구성 요소를 참조하고, `id`는 이 두 세그먼트를 `/`로 연결한 것입니다. **파라미터** @@ -52,7 +52,7 @@ Comfy Router의 모델 카탈로그 - `POST /v1/models/{provider}/{model}`가 | `403` | [`RouterErrorResponse`](#routererrorresponse) | `X-Comfy-Error-Type`, `X-Comfy-Request-Id` | Router 요청 수준 실패: 요청이 모델에 도달하지 못했거나, 모델 자체가 신고하지 않은 이유로 실패했습니다. 본문은 `RouterErrorResponse`이며, 버킷은 `X-Comfy-Error-Type`에 반복됩니다. | | `503` | [`RouterErrorResponse`](#routererrorresponse) | `X-Comfy-Error-Type`, `X-Comfy-Request-Id` | Router 요청 수준 실패: 요청이 모델에 도달하지 못했거나, 모델 자체가 신고하지 않은 이유로 실패했습니다. 본문은 `RouterErrorResponse`이며, 버킷은 `X-Comfy-Error-Type`에 반복됩니다. | -### `GET /v1/models/{provider}/{model}` +### `GET /v2/models/{provider}/{model}` **정규 모델 ID로 파트너 모델 하나의 카탈로그 항목을 조회합니다.** @@ -74,7 +74,7 @@ Comfy Router의 모델 카탈로그 - `POST /v1/models/{provider}/{model}`가 | `404` | [`RouterErrorResponse`](#routererrorresponse) | `X-Comfy-Error-Type`, `X-Comfy-Request-Id` | 라우터 요청 수준의 실패입니다. 요청이 모델에 도달하지 못했거나, 모델 자체가 신고하지 않은 이유로 실패했습니다. 본문은 `RouterErrorResponse`이며, 버킷은 `X-Comfy-Error-Type` 헤더에도 동일하게 포함됩니다. | | `503` | [`RouterErrorResponse`](#routererrorresponse) | `X-Comfy-Error-Type`, `X-Comfy-Request-Id` | 라우터 요청 수준의 실패입니다. 요청이 모델에 도달하지 못했거나, 모델 자체가 신고하지 않은 이유로 실패했습니다. 본문은 `RouterErrorResponse`이며, 버킷은 `X-Comfy-Error-Type` 헤더에도 동일하게 포함됩니다. | -### `POST /v1/models/{provider}/{model}` +### `POST /v2/models/{provider}/{model}` **표준 모델 ID로 파트너 모델을 동기식으로 실행합니다.** @@ -86,6 +86,7 @@ Comfy Router의 표준 진입점으로, 모델 ID로 주소가 지정됩니다. | --- | --- | --- | --- | --- | --- | | `provider` | path | yes | [`RouterProviderSegment`](#routerprovidersegment) | `pattern: ^[a-z0-9]+([._-][a-z0-9]+)*$`, `maxLength: 64` | 표준 `{provider}/{model}[/{variant}]` 모델 ID의 소문자 공급자 세그먼트입니다. 실행할 모델을 보유한 파트너를 나타냅니다. | | `model` | path | yes | [`RouterModelSegment`](#routermodelsegment) | `pattern: ^[a-z0-9]+([._-][a-z0-9]+)*$`, `maxLength: 128` | 표준 `{provider}/{model}[/{variant}]` 모델 ID의 소문자 모델 세그먼트입니다. 해당 공급자 내에서 실행할 모델을 나타냅니다. | +| `Idempotency-Key` | header | no | string | `minLength: 1`, `maxLength: 255` | 호출자가 생성하는 키로, 하나의 논리적 호출에 대한 재시도를 안전하게 만듭니다. 호출자에게 답변이 도달한 호출은 그 키에 연결되어 24시간 동안 기록되며, 같은 키를 담은 재시도는 공급자에게 두 번째로 디스패치(및 청구)하는 대신 그 기록으로 응답하고 `Idempotent-Replayed: true`로 표시됩니다. 이 보장은 청구에 관한 것입니다. 키는 최대 한 번만 청구됩니다. 키가 최대 한 번만 디스패치된다는 약속이 아니며, 잃어버린 호출을 재개할 수 있게 해주지도 않습니다. | **요청 본문** @@ -97,15 +98,19 @@ Comfy Router의 표준 진입점으로, 모델 ID로 주소가 지정됩니다. | 상태 | 본문 | 헤더 | 설명 | | --- | --- | --- | --- | -| `200` | [`RouterModelOutput`](#routermodeloutput) | `X-Comfy-Request-Id` | OK: 파트너 모델의 네이티브 JSON 출력이 변경 없이 반환됩니다. | +| `200` | [`RouterModelOutput`](#routermodeloutput) | `X-Comfy-Request-Id`, `Idempotent-Replayed` | OK: 파트너 모델의 네이티브 JSON 출력이 변경 없이 반환됩니다. | +| `400` | [`RouterErrorResponse`](#routererrorresponse) | `X-Comfy-Error-Type`, `X-Comfy-Request-Id` | Router 요청 수준의 실패입니다. 요청이 모델에 도달하지 못했거나, 모델 자체가 보고하지 않은 이유로 실패했습니다. 본문은 `RouterErrorResponse`이며, 버킷은 `X-Comfy-Error-Type` 헤더에 동일하게 표시됩니다. | +| `401` | [`RouterErrorResponse`](#routererrorresponse) | `X-Comfy-Error-Type`, `X-Comfy-Request-Id` | Router 요청 수준의 실패입니다. 요청이 모델에 도달하지 못했거나, 모델 자체가 보고하지 않은 이유로 실패했습니다. 본문은 `RouterErrorResponse`이며, 버킷은 `X-Comfy-Error-Type` 헤더에 동일하게 표시됩니다. | | `403` | [`RouterErrorResponse`](#routererrorresponse) | `X-Comfy-Error-Type`, `X-Comfy-Request-Id` | Router 요청 수준의 실패입니다. 요청이 모델에 도달하지 못했거나, 모델 자체가 보고하지 않은 이유로 실패했습니다. 본문은 `RouterErrorResponse`이며, 버킷은 `X-Comfy-Error-Type` 헤더에 동일하게 표시됩니다. | | `404` | [`RouterErrorResponse`](#routererrorresponse) | `X-Comfy-Error-Type`, `X-Comfy-Request-Id` | Router 요청 수준의 실패입니다. 요청이 모델에 도달하지 못했거나, 모델 자체가 보고하지 않은 이유로 실패했습니다. 본문은 `RouterErrorResponse`이며, 버킷은 `X-Comfy-Error-Type` 헤더에 동일하게 표시됩니다. | +| `409` | [`RouterErrorResponse`](#routererrorresponse) | `X-Comfy-Error-Type`, `X-Comfy-Request-Id`, `Retry-After` | 이 요청의 `Idempotency-Key`가 이미 보유되어 있고, 이 요청은 그 기록으로 응답할 수 없습니다. 두 가지 조건이 이 상태 코드를 공유하며, 대응 방식이 정반대이므로 `X-Comfy-Error-Type`으로 구분합니다. `concurrency_limit_exceeded`는 이 키에 대한 원래 호출이 아직 실행 중이라는 뜻입니다. `Retry-After` 초만큼 기다린 뒤 같은 키를 다시 보내면 두 번째 호출을 시작하는 대신 그 호출의 결과를 회수합니다. `invalid_input`은 이 키로는 이 요청을 전혀 처리할 수 없다는 뜻입니다. 이미 다른 요청에 사용되었거나(메서드, 경로와 쿼리, 또는 본문이 원래와 다름), 원래 호출이 완료되었고(성공했다면 청구되었으며) Router가 재생할 충실한 응답 사본을 보유하지 않은 경우이며, 답은 항상 새 키이지 이 키의 재전송이 아닙니다. `detail`이 어느 경우인지 알려줍니다. 본문은 `RouterErrorResponse`이며, 버킷은 `X-Comfy-Error-Type` 헤더에 동일하게 표시됩니다. | +| `413` | [`RouterErrorResponse`](#routererrorresponse) | `X-Comfy-Error-Type`, `X-Comfy-Request-Id` | Router 요청 수준의 실패입니다. 요청이 모델에 도달하지 못했거나, 모델 자체가 보고하지 않은 이유로 실패했습니다. 본문은 `RouterErrorResponse`이며, 버킷은 `X-Comfy-Error-Type` 헤더에 동일하게 표시됩니다. | | `422` | [`RouterValidationErrorResponse`](#routervalidationerrorresponse) | `X-Comfy-Error-Type`, `X-Comfy-Request-Id` | 요청이 모델에 도달했지만 모델이 내용을 거부했습니다. 본문은 FastAPI `detail[]` 형태의 `RouterValidationErrorResponse`이므로, 각 오류 필드는 고유한 `type`과 `ctx`를 유지합니다. `X-Comfy-Error-Type`은 전체 응답에 대한 포괄적인 버킷을 전달합니다. | | `429` | [`RouterErrorResponse`](#routererrorresponse) | `X-Comfy-Error-Type`, `X-Comfy-Request-Id`, `X-Committed-Spend-Limit`, `X-Committed-Spend-Current`, `X-Committed-Spend-Remaining` | 호출자가 허용된 진행 중 용량을 모두 점유하고 있어 요청이 모델에 도달하기 전에 거부되었습니다. 어느 경우든 버킷은 `concurrency_limit_exceeded`이며, `detail`이 어떤 한도에 걸렸는지 알려줍니다. 동시 호출 수이거나, 아직 진행 중인 호출의 확정 지출(committed spend)이며, 후자에 의한 거부에는 `X-Committed-Spend-Limit`, `X-Committed-Spend-Current`, `X-Committed-Spend-Remaining` 헤더(미국 달러 센트 단위)도 함께 담깁니다. 호출자 자신의 진행 중인 호출 중 하나가 끝나면 재시도하세요. 본문은 `RouterErrorResponse`이며, 버킷은 `X-Comfy-Error-Type` 헤더에 동일하게 표시됩니다. | | `503` | [`RouterErrorResponse`](#routererrorresponse) | `X-Comfy-Error-Type`, `X-Comfy-Request-Id` | Router 요청 수준의 실패입니다. 요청이 모델에 도달하지 못했거나, 모델 자체가 보고하지 않은 이유로 실패했습니다. 본문은 `RouterErrorResponse`이며, 버킷은 `X-Comfy-Error-Type` 헤더에 동일하게 표시됩니다. | -| `504` | [`RouterErrorResponse`](#routererrorresponse) | `X-Comfy-Error-Type`, `X-Comfy-Request-Id`, `Retry-After` | Comfy가 자체 구성된 한계에서 연결 유지를 중단했습니다(`deadline_exceeded`). 본문과 두 헤더는 `RouterRequestError`의 것과 정확히 같으며, 여기에 추가되는 것은 선택적 `Retry-After`입니다. 같은 `Idempotency-Key`로 재시도하면 새 생성을 보내는 대신 아직 실행 중인 생성을 회수하게 되는 경우에 포함됩니다. `POST /v1/models/{provider}/{model}`의 `504`를 참조하세요. | +| `504` | [`RouterErrorResponse`](#routererrorresponse) | `X-Comfy-Error-Type`, `X-Comfy-Request-Id`, `Retry-After` | Comfy가 자체 구성된 한계에서 연결 유지를 중단했습니다(`deadline_exceeded`). 본문과 두 헤더는 `RouterRequestError`의 것과 정확히 같으며, 여기에 추가되는 것은 선택적 `Retry-After`입니다. 같은 `Idempotency-Key`로 재시도하면 새 생성을 보내는 대신 아직 실행 중인 생성을 회수하게 되는 경우에 포함됩니다. `POST /v2/models/{provider}/{model}`의 `504`를 참조하세요. | -### `GET /v1/models/{provider}/{model}/openapi.json` +### `GET /v2/models/{provider}/{model}/openapi.json` **파트너 모델 하나의 입력 스키마를 OpenAPI 문서로 읽습니다.** @@ -141,7 +146,7 @@ Raised for a request Router accepted and then could not complete. | `error_type` | Meaning | | --- | --- | -| `invalid_input` | The request was rejected before it reached the model - a malformed body, a malformed or expired pagination cursor, or an input the model's own schema does not accept. | +| `invalid_input` | The request was rejected before it reached the model - a malformed body, a malformed or expired pagination cursor, an input the model's own schema does not accept, or an `Idempotency-Key` that cannot serve this request (already used for a different request - the method, the path and query, or the body differ - or already consumed by a call whose response cannot be replayed). Sent with `409` in the key cases and with `400`/`422` in the others; the status says which, and the key cases are the ones answered by using a NEW key rather than by editing the request. | | `content_policy_violation` | The provider refused the request on content-policy grounds. The refusal is deterministic: re-sending the same input will be refused again. | | `provider_error` | The partner provider reported a failure of its own, or returned a response Router could not interpret as a result. | | `provider_timeout` | The partner provider did not answer within its deadline. This bucket is the PROVIDER timing out and never Router's own server deadline, which is reported as `deadline_exceeded` - the two share `504` and are separated because they name different causes: this one says the partner failed, that one says Comfy stopped holding the connection. | @@ -156,7 +161,7 @@ Raised by Router itself, before or around the call to the model. | --- | --- | | `unauthorized` | The request carried no usable credential. | | `forbidden` | The credential is valid but is not entitled to this model or this operation. | -| `concurrency_limit_exceeded` | The workspace already has as many calls in flight as it is allowed; retry once one of them finishes. | +| `concurrency_limit_exceeded` | The workspace already has as many calls in flight as it is allowed; retry once one of them finishes. It carries one further condition on the run route, on a `409` rather than the `429` above: another call is already in flight for the `Idempotency-Key` this request presented. Re-send the SAME key after `Retry-After` seconds to collect that call's result. | | `client_disconnected` | The caller closed the connection before Router could return a result. It is logged rather than delivered - there is no socket left to write it to - and it is an attribution, not a billing outcome: a provider generation that completed is billed regardless of whether the caller received the response. | | `internal_error` | Router itself failed. It is also the value a client should treat any UNRECOGNIZED bucket as, so a later addition to the set does not break a client generated before it. | | `deadline_exceeded` | Comfy stopped holding the connection at its own configured bound before an answer arrived. It shares `504` with `provider_timeout` and the pair says which side ran out of time; this one is Comfy's own bound, so nothing about the request was rejected and the same request may be retried. It says nothing about the charge: a provider generation that completed is billed regardless of whether the caller received the response. Retry it with the SAME `Idempotency-Key`: when the provider had already accepted the generation, the retry collects that generation rather than dispatching another, and a `Retry-After` on the `504` says when to ask. | @@ -169,8 +174,9 @@ Raised by Router itself, before or around the call to the model. | 헤더 | 유형 | 설명 | | --- | --- | --- | | `Cache-Control` | 문자열 | 제공되는 스키마 문서에 대한 신선도 지시문입니다. `private`은 경로가 인증되어 있기 때문입니다. 문서 자체는 호출자별로 다르지 않지만, 공유 캐시는 인증된 요청에 대한 응답을 보유해서는 안 됩니다. `must-revalidate`는 오래된 복사본이 그대로 제공되는 대신 `ETag`에 대해 재검증되도록 하기 위함입니다. | -| `ETag` | 문자열 | `GET /v1/models/{provider}/{model}/openapi.json`에 대해 제공되는 문서 바이트에 대한 강력한 엔티티 태그입니다. 모델별 스키마는 거의 변경되지 않지만 SDK가 자주 다시 가져오므로, 호출자는 이 값을 저장한 뒤 `If-None-Match`로 다시 보내 문서 대신 `304`를 받을 수 있습니다. | -| `Retry-After` | 정수 | 같은 `Idempotency-Key`로 같은 요청을 재시도하기 전에 기다려야 할 초 수입니다. `deadline_exceeded` `504`에서 공급자가 아직 실행 중인 생성에 대한 핸들을 Comfy가 보유한 경우에만 존재합니다. 값은 Router 자체의 폴링 간격으로, 이 경로가 "나중에 다시 물어보라"에 대해 제시할 수 있는 유일하게 정직한 숫자입니다. 회수할 것이 없을 때, 즉 키 없는 호출이거나 공급자가 무엇이든 수락하기 전에 한계가 만료된 경우에는 존재하지 않습니다. | +| `ETag` | 문자열 | `GET /v2/models/{provider}/{model}/openapi.json`에 대해 제공되는 문서 바이트에 대한 강력한 엔티티 태그입니다. 모델별 스키마는 거의 변경되지 않지만 SDK가 자주 다시 가져오므로, 호출자는 이 값을 저장한 뒤 `If-None-Match`로 다시 보내 문서 대신 `304`를 받을 수 있습니다. | +| `Idempotent-Replayed` | 논리값 | 이 응답이 모델을 다시 실행하는 대신 `Idempotency-Key`의 기록으로 제공되었을 때 존재하며 값은 `true`입니다. 원래 호출의 상태 코드, 본문, 콘텐츠 타입을 그대로 전달하며, 두 번째로 청구되지 않습니다. 요금은 원래 호출이 완료된 시점에 정산되었습니다. 새 실행에서는 `false`로 보내지는 것이 아니라 헤더 자체가 존재하지 않으므로, 존재 여부로 분기하세요. | +| `Retry-After` | 정수 | 같은 `Idempotency-Key`로 같은 요청을 재시도하기 전에 기다려야 할 초 수입니다. 그러한 재시도로 실제로 결과를 회수할 수 있는 두 가지 응답에 설정됩니다. 하나는 `error_type: concurrency_limit_exceeded`를 담은 `409`로, 해당 키의 원래 호출이 아직 실행 중인 경우입니다. 다른 하나는 `deadline_exceeded` `504`로, Comfy가 연결 유지를 중단했지만 공급자가 실행 중인 생성에 대한 핸들은 아직 보유한 경우입니다. 두 경우 모두 값은 Router 자신이 다시 물어보기 전에 기다릴 간격으로, 이 경로가 "나중에 다시 물어보라"에 대해 제시할 수 있는 유일하게 정직한 숫자입니다. 회수할 것이 없을 때, 즉 키 없는 호출, 공급자가 무엇이든 수락하기 전에 한계가 만료된 경우, 또는 기다리라고 하는 대신 키를 곧바로 거부하는 `409`에는 존재하지 않습니다. | | `X-Comfy-Error-Type` | [`RouterErrorType`](#routererrortype) | Router가 모든 오류 응답에 설정하는, 오류에 대한 대략적인 기계 판독 가능 버킷입니다. `RouterErrorResponse.error_type`과 동일한 값을 가지며, `422`에서는 이것이 유일한 기계 판독 가능 버킷입니다. 해당 본문이 FastAPI `detail[]` 형태이고 자체 `error_type` 필드가 없기 때문입니다. 따라서 클라이언트는 수신한 두 Router 오류 본문 중 어느 것인지 결정하기 이전에 이 헤더만으로 분기할 수 있습니다. | | `X-Comfy-Request-Id` | 문자열 | 이 호출에 대해 서버에서 생성된 식별자로, 모든 Router 응답(성공, 4xx, 5xx 모두)에 존재합니다. 오류 응답이 바로 사용자가 지원 요청에 인용할 id가 필요한 때이기 때문입니다. 동일한 값이 호출의 사용량/감사 이벤트에 기록되므로, 요금에 대한 불만을 타임스탬프로 검색하는 대신 요금 자체에 연결할 수 있습니다. | | `X-Committed-Spend-Current` | 정수 | 호출자가 현재 진행 중인 호출에 확정한 금액(미국 달러 센트)으로, 거부된 호출은 포함하지 않습니다. `X-Committed-Spend-Limit`과 함께 존재합니다. | @@ -179,7 +185,7 @@ Raised by Router itself, before or around the call to the model. ## 모델별 입력 스키마 -모델의 자체 입력 필드는 여기에 다시 수록하지 않습니다. `GET /v1/models/{provider}/{model}/openapi.json`에서 실시간으로 확인하세요. 이 엔드포인트는 서버가 호출을 검증할 때 사용하는 문서와 동일한 문서를 제공하므로, 게시된 내용과 실제로 강제 적용되는 내용이 서로 어긋날 수 없습니다. `GET /v1/models`에서 모델 ID를 가져와 해당 호출 경로에 `/openapi.json`을 추가하고, 반환된 문서를 기준으로 생성을 진행하세요. +모델의 자체 입력 필드는 여기에 다시 수록하지 않습니다. `GET /v2/models/{provider}/{model}/openapi.json`에서 실시간으로 확인하세요. 이 엔드포인트는 서버가 호출을 검증할 때 사용하는 문서와 동일한 문서를 제공하므로, 게시된 내용과 실제로 강제 적용되는 내용이 서로 어긋날 수 없습니다. `GET /v2/models`에서 모델 ID를 가져와 해당 호출 경로에 `/openapi.json`을 추가하고, 반환된 문서를 기준으로 생성을 진행하세요. ## 스키마 @@ -226,11 +232,11 @@ Comfy Router 모델 하나에 대한 모델별 세부 정보: 카탈로그 목 | 필드 | 타입 | 필수 | 제약 조건 | 설명 | | --- | --- | --- | --- | --- | -| `input_schema_url` | 문자열 | 아니요 | `format: uri`, `pattern: ^https://`, `maxLength: 2048` | 이 모델의 입력 스키마 문서를 가리키는 포인터입니다. 입력 스키마 문서는 이 모델에 대해 `POST /v1/models/{provider}/{model}`이 받아들이는 본문(body)의 설명입니다. 오직 포인터만이 이 계약의 일부입니다. 포인터가 가리키는 문서는 별도로 작성됩니다. 모델에 대한 스키마가 작성되지 않은 경우에는 이 필드가 존재하지 않습니다. | +| `input_schema_url` | 문자열 | 아니요 | `format: uri`, `pattern: ^https://`, `maxLength: 2048` | 이 모델의 입력 스키마 문서를 가리키는 포인터입니다. 입력 스키마 문서는 이 모델에 대해 `POST /v2/models/{provider}/{model}`이 받아들이는 본문(body)의 설명입니다. 오직 포인터만이 이 계약의 일부입니다. 포인터가 가리키는 문서는 별도로 작성됩니다. 모델에 대한 스키마가 작성되지 않은 경우에는 이 필드가 존재하지 않습니다. | ### RouterModelId -`{provider}/{model}` 형식의 표준 Comfy Router 모델 ID입니다. 이 값은 `POST /v1/models/{provider}/{model}`에서 모델을 주소 지정하는 값과 정확히 일치하므로, 호출자는 다른 곳에서 다시 파생할 필요 없이 해당 경로에 바로 삽입할 수 있습니다. `pattern`은 단일 `/`로 연결된 `RouterProviderSegment`와 `RouterModelSegment`이며, `maxLength`는 두 값의 합에 해당 구분자를 더한 값입니다. +`{provider}/{model}` 형식의 표준 Comfy Router 모델 ID입니다. 이 값은 `POST /v2/models/{provider}/{model}`에서 모델을 주소 지정하는 값과 정확히 일치하므로, 호출자는 다른 곳에서 다시 파생할 필요 없이 해당 경로에 바로 삽입할 수 있습니다. `pattern`은 단일 `/`로 연결된 `RouterProviderSegment`와 `RouterModelSegment`이며, `maxLength`는 두 값의 합에 해당 구분자를 더한 값입니다. 유형: `string`. `pattern: ^[a-z0-9]+([._-][a-z0-9]+)*/[a-z0-9]+([._-][a-z0-9]+)*$`, `maxLength: 193` @@ -242,7 +248,7 @@ Comfy Router 모델 하나에 대한 모델별 세부 정보: 카탈로그 목 ### RouterModelInputSchemaDocument -단일 Comfy Router 모델의 입력을 설명하는 독립 OpenAPI 문서로, 해당 모델에 대해 `POST /v1/models/{provider}/{model}`가 허용하는 요청 본문입니다. `GET /v1/models/{provider}/{model}/openapi.json`이 반환하는 내용이기도 합니다. +단일 Comfy Router 모델의 입력을 설명하는 독립 OpenAPI 문서로, 해당 모델에 대해 `POST /v2/models/{provider}/{model}`가 허용하는 요청 본문입니다. `GET /v2/models/{provider}/{model}/openapi.json`이 반환하는 내용이기도 합니다. 유형: `object` @@ -252,7 +258,7 @@ Router 모델 카탈로그의 한 항목입니다. 실행 가능한 모델의 | 필드 | 타입 | 필수 | 제약 조건 | 설명 | | --- | --- | --- | --- | --- | -| `id` | [`RouterModelId`](#routermodelid) | 예 | `pattern: ^[a-z0-9]+([._-][a-z0-9]+)*/[a-z0-9]+([._-][a-z0-9]+)*$`, `maxLength: 193` | 정규 Comfy Router 모델 ID인 `{provider}/{model}`입니다. `POST /v1/models/{provider}/{model}`에서 모델을 주소 지정하는 값과 정확히 일치하므로, 호출자는 다른 어떤 값에서도 다시 파생할 필요 없이 해당 경로에 그대로 삽입할 수 있습니다. 이 `pattern`은 `RouterProviderSegment`와 `RouterModelSegment`를 단일 `/`로 연결한 것이며, `maxLength`는 두 값의 합에 해당 구분자를 더한 값입니다. | +| `id` | [`RouterModelId`](#routermodelid) | 예 | `pattern: ^[a-z0-9]+([._-][a-z0-9]+)*/[a-z0-9]+([._-][a-z0-9]+)*$`, `maxLength: 193` | 정규 Comfy Router 모델 ID인 `{provider}/{model}`입니다. `POST /v2/models/{provider}/{model}`에서 모델을 주소 지정하는 값과 정확히 일치하므로, 호출자는 다른 어떤 값에서도 다시 파생할 필요 없이 해당 경로에 그대로 삽입할 수 있습니다. 이 `pattern`은 `RouterProviderSegment`와 `RouterModelSegment`를 단일 `/`로 연결한 것이며, `maxLength`는 두 값의 합에 해당 구분자를 더한 값입니다. | | `provider` | [`RouterProviderSegment`](#routerprovidersegment) | 예 | `pattern: ^[a-z0-9]+([._-][a-z0-9]+)*$`, `maxLength: 64` | 정규 `{provider}/{model}[/{variant}]` 모델 ID의 소문자 `provider` 세그먼트입니다. 모델이 주소 지정되는 파트너를 나타냅니다. 호출 라우트의 `provider` 경로 매개변수와 카탈로그 항목의 `provider` 필드는 모두 이 하나의 스키마를 참조하므로, 목록의 ID와 허용되는 ID가 서로 어긋나지 않습니다. | | `model` | [`RouterModelSegment`](#routermodelsegment) | 예 | `pattern: ^[a-z0-9]+([._-][a-z0-9]+)*$`, `maxLength: 128` | 정규 `{provider}/{model}[/{variant}]` 모델 ID의 소문자 `model` 세그먼트입니다. 해당 공급자 내에서 실행할 모델을 나타냅니다. 호출 라우트의 `model` 경로 매개변수와 카탈로그 항목의 `model` 필드는 `RouterProviderSegment`와 동일한 불일치 방지 이유로 이 스키마를 공유합니다. | | `billing` | [`RouterModelBilling`](#routermodelbilling) | 예 | - | 호출자가 호출 이전에 알아야 하는 모델별 청구 정보입니다. 가격이 아닙니다. 사용량 및 비용 수치는 여기에 절대 나타나지 않습니다. | diff --git a/zh/api-reference/comfy-router/limitations.mdx b/zh/api-reference/comfy-router/limitations.mdx index 74bb5460b..d5a575e15 100644 --- a/zh/api-reference/comfy-router/limitations.mdx +++ b/zh/api-reference/comfy-router/limitations.mdx @@ -1,24 +1,24 @@ --- title: "Comfy Router 的局限性" description: "Comfy Router 目前无法做到的事、存在替代方案时该改用何种方案,以及其中哪些限制预计将会改变。" -translationSourceHash: b9e78b52 +translationSourceHash: b7db80c8 translationFrom: api-reference/comfy-router/limitations.mdx translationBlockHashes: - "_intro": 9eea7f43 + "_intro": 21bb3a18 "At a glance": fc2ff613 - "No queued submission": f72af0fd + "No queued submission": e529f3ab "No cost or credit figures on a response": ba168aa9 - "No way to resume a call you lost": a783a987 + "No way to resume a call you lost": 3911a303 "Calls are cut off at a server deadline": 45bacfca - "Requests are rate limited per caller": 01f2a8c8 - "No progress while a call runs": be9de68d + "Requests are rate limited per caller": 12eb3edd + "No progress while a call runs": d9c528a1 "Three forecast buckets are not in the vocabulary": f85a2589 "Router does not cover every partner operation": 9ecc10d1 "Next": 2d3bb742 --- **Comfy Router 尚未全面可用。** 下文引用的路由: -`POST /v1/models/{provider}/{model}` 及其目录和架构相关端点,目前尚未提供服务请求:经过身份验证的调用现在会返回 `404`。本页描述的是它们将要提供的契约,并在该上线之前发布,以便集成可以针对已知形状进行编写。以下所有内容都是关于该契约的说明,而不是你现在可以实践的行为。 +`POST /v2/models/{provider}/{model}` 及其目录和架构相关端点,目前尚未提供服务请求:经过身份验证的调用现在会返回 `404`。本页描述的是它们将要提供的契约,并在该上线之前发布,以便集成可以针对已知形状进行编写。以下所有内容都是关于该契约的说明,而不是你现在可以实践的行为。 Comfy Router 是一次同步调用:你使用一个凭据,将合作伙伴模型的原生输入发送到一个主机,连接保持打开,`200` 响应携带该模型的原生输出。这种形状正是让首次集成变得简短的原因,也是本页所有限制的来源。在围绕 Router 进行设计之前阅读本页,而不是之后:下文大部分内容都有直接的替代方案,而那些没有替代方案的内容,值得你在基于 Router 并不成立的假设进行构建之前了解。 @@ -40,7 +40,7 @@ Comfy Router 是一次同步调用:你使用一个凭据,将合作伙伴模 ## 不支持排队提交 -运行模型只有一种方式:`POST /v1/models/{provider}/{model}`,该端点会保持连接,直到生成完成并在响应中返回结果。没有接受任务后返回标识符、让你稍后再来获取结果的端点,也没有完成时的回调或 webhook。 +运行模型只有一种方式:`POST /v2/models/{provider}/{model}`,该端点会保持连接,直到生成完成并在响应中返回结果。没有接受任务后返回标识符、让你稍后再来获取结果的端点,也没有完成时的回调或 webhook。 **替代做法。** 对大多数模型来说这不成问题:保持连接打开并读取结果即可。快速的图像模型几秒钟即可返回;长时间的视频生成可能运行数分钟,Router 会为其一直保持连接。设置一个宽裕的客户端读取超时,要高于 [Router 自身的截止时间](#调用会在服务器截止时间被切断),并将该调用视为长时间运行的请求,而非快速请求。如果你的架构确实无法保持连接打开,例如执行上限很短的 serverless 函数,或你预期用户会关闭的浏览器标签页,那么就从你能控制且能够保持连接的 worker 发起调用,或者使用合作伙伴代理路由,选择提供自身提交和轮询机制的提供商。参见[最后一节](#router-并不覆盖每个合作伙伴操作)。 @@ -58,16 +58,16 @@ Router 响应会告诉你模型生成了什么,但它的契约不涉及任何 Router 不会保留进行中调用的可恢复记录。没有状态路由,没有任务标识符,也没有任何可重新连接的对象。如果连接在调用中途断开(客户端崩溃、网络分区、重启进程的部署),响应便不复存在,之后你也无从查询这次调用。*生成*是否已完成并被计费,与你是否收到它,是两个不同的问题,而丢失连接并不能可靠地回答其中任何一个。 -**应该怎么做。** 在每次调用中发送 `Idempotency-Key` 请求头。它不能让已丢失的调用恢复,但能让重试变得安全。Router 会在调用时长内保留该密钥;当调用确实将答案送达你时,Router 会将该响应记在该密钥下并保留 24 小时。使用**相同**密钥重试时,便会重放已记录的响应,而不是第二次向提供商分派请求(并再次计费),并标记为 `Idempotent-Replayed: true`,以便你区分重放与全新运行。请为每个逻辑调用生成一个新密钥,而不是每次尝试都生成新密钥。以*不同*请求体出示相同密钥会返回 `409`,而不是静默覆盖。 +**应该怎么做。** 在每次调用中发送 `Idempotency-Key` 请求头:[快速入门](/zh/api-reference/comfy-router/quickstart#用自己的密钥安全地重试)介绍了具体做法,包括最容易被跳过的那一步:在发送请求之前先持久化密钥。它不能让已丢失的调用恢复,但能让重试变得安全。Router 会在调用时长内保留该密钥;当调用确实将答案送达你时,Router 会将该响应记在该密钥下并保留 24 小时。使用**相同**密钥重试时,便会重放已记录的响应,而不是第二次向提供商分派请求(并再次计费),并标记为 `Idempotent-Replayed: true`,以便你区分重放与全新运行。请为每个逻辑调用生成一个新密钥,而不是每次尝试都生成新密钥。以*不同*的请求(请求体、模型路径、查询字符串或方法不同)出示相同密钥会返回 `409`,而不是静默覆盖。 -请准确理解这能给你带来什么,因为这是**计费**属性,而不是投递属性:**一个密钥最多只计费一次。** 它并不是承诺一个密钥最多只向提供商分派一次。Router 会为你实际收到的答案保留密钥;任何未向你收费的结果都会释放密钥,使调用可以再次进行。`5xx`、`408`/`425`/`429`,以及(这里最关键的一种情况)完全没有内容到达你的调用:这些情况都会释放密钥,使用该密钥重试会真正重新执行,并重新分派给提供商。 +请准确理解这能给你带来什么,因为这是**计费**属性,而不是投递属性:**一个密钥最多只计费一次。** 它并不是承诺一个密钥最多只向提供商分派一次。Router 会为你实际收到的答案保留密钥;任何未向你收费的结果都会释放密钥,使调用可以再次进行。`5xx`、`408`/`425`/`429`,以及(这里最关键的一种情况)完全没有内容到达你的调用:这些情况都会释放密钥,使用该密钥重试会真正重新执行,并重新分派给提供商。唯一*不会*释放密钥的 `5xx` 是仍有内容可收取的切断:携带 `Retry-After` 的 `deadline_exceeded` `504` 表示在 Router 停止等待时提供商已经接受了该次生成,此时 Router 会把密钥停放在那个正在运行的任务上:请在 `Retry-After` 之后重新发送**同一个**密钥以收取结果,因为在这种情况下换用新密钥就是第二次计费的生成。不携带 `Retry-After` 的 `504` 没有可停放的内容,会像其他情况一样释放密钥。 **因此,连接断开正是幂等性*无法*挽救的情况。** 调用中途丢失连接通常意味着从未有任何响应真正交付给你,这正是上述的释放路径:使用相同密钥重试会开启全新运行,而不是把你错过的结果交给你。如果原始生成已经被分派,提供商可能会第二次运行它。这是正确的默认行为:你从未收到且未被计费的调用应当可以重新运行。但请按“重试会产生新运行”来规划,而不是“重试会找回丢失的运行”。 当 Router *确实*为密钥保留了内容时,重试会得到应答而不是重新执行:要么重放原始响应,要么返回 `409` 说明无法重放的原因。在原始调用仍在进行中时发送的重试会返回携带 `Retry-After` 的 `409`,因此请等待后再重新发送相同的密钥。针对已完成但其响应 Router 无法保留忠实副本的调用进行重试,也会返回 `409`。这不仅限于响应过大的情况:超过重放上限的响应、应答后失败或崩溃的处理器,以及向你写入时失败或写入不足,都会将密钥记录为已消费但不可重放,并返回相同的 `409`。看到这个错误时,不要去找大小问题。上述所有情况下的引导都是一样的:使用**新**密钥。原始调用已完成并被计费,Router 既不会凭空捏造其响应,也不会在旧密钥下重新运行它。 -**已生成的合同中尚无这些内容。** 此处描述的 `Idempotency-Key` 请求头、`409` 响应以及 `Idempotent-Replayed` 和 `Retry-After` 响应头,并未在生成参考文档所依据的 OpenAPI 合同中的 `POST /v1/models/{provider}/{model}` 上声明,因此它们不会出现在生成的 API 参考中,SDK 也不会对它们进行建模。在获得支持之前,请自行发送和读取这些内容。 +**已在契约中,但有一处缺口。** 此处描述的 `Idempotency-Key` 请求头、`409` 响应以及 `Idempotent-Replayed` 和 `Retry-After` 响应头,均已在 `POST /v2/models/{provider}/{model}` 上声明,因此它们会出现在生成的 [API 参考](/zh/api-reference/comfy-router/reference)中,也会出现在各 SDK 所内置的规范里,SDK 在重新生成时便会获得它们。缺口在于:`Retry-After` 声明在 `409` 和 `504` 上,但**没有**声明在[下文](#请求按调用方进行速率限制)描述的 `rate_limited` `429` 上,而后者同样会发送它:请直接在那里读取它,不必等契约明确说明。 **状态:尚未支持。** 持久化、可恢复的执行预计将随队列式路径一同推出,届时请求记录将有处可存。幂等重试目前就是答案,而且它不是临时方案:无论如何都值得集成。 @@ -86,17 +86,17 @@ Router 不会保留进行中调用的可恢复记录。没有状态路由,没 ## 请求按调用方进行速率限制 -Router 对你的流量限制的是两件不同的事情,并且在同一个 `429` 上用两个不同的分桶来回应。并发限制约束的是你同时**在途**的调用数量,回应 `concurrency_limit_exceeded`;只要你自己的某个调用一完成它就会解除,因此几秒后重试是正确的做法。速率限制约束的是你**多频繁**地访问 Router 这一层(`POST /v1/models/{provider}/{model}` 和 `/v1/models` 下的三个目录读取一律计入,无论该调用运行了模型还是在此之前就被拒绝),回应 `rate_limited`。后者是一个在一分钟窗口内持续补充的额度,你做什么都无法让它提前消耗完:响应携带 `Retry-After` 头给出需要等待的秒数,`detail` 中说明了窗口。请根据 `X-Comfy-Error-Type` 分支判断,切勿只看状态码。 +Router 对你的流量限制的是两件不同的事情,并且在同一个 `429` 上用两个不同的分桶来回应。并发限制约束的是你同时**在途**的调用数量,回应 `concurrency_limit_exceeded`;只要你自己的某个调用一完成它就会解除,因此几秒后重试是正确的做法。速率限制约束的是你**多频繁**地访问 Router 这一层(`POST /v2/models/{provider}/{model}` 和 `/v2/models` 下的三个目录读取一律计入,无论该调用运行了模型还是在此之前就被拒绝),回应 `rate_limited`。后者是一个在一分钟窗口内持续补充的额度,你做什么都无法让它提前消耗完:响应携带 `Retry-After` 头给出需要等待的秒数,`detail` 中说明了窗口。请根据 `X-Comfy-Error-Type` 分支判断,切勿只看状态码。 该限制以经过身份验证的调用方为键,而不是来源地址,因此它会跟随你的凭证跨越主机。使用你自己的提供商密钥(bring-your-own-key)运行的调用不受限制:那份吞吐量属于你自己。该额度是服务器端配置值而不是公开常量,本页有意不给出具体数字;请围绕退避而不是某个数字来设计。 -**替代做法。** 遵守 `Retry-After`:在此期限内重试只会得到同样的拒绝。`GET /v1/models` 和模型的 `openapi.json` 只需获取一次并在进程生命周期内缓存,不要在每次调用前重新读取;它们只会在部署时变化。保留了 `429` 中请求标识符的客户端,就拥有了支持团队可以追踪的线索。 +**替代做法。** 遵守 `Retry-After`:在此期限内重试只会得到同样的拒绝。`GET /v2/models` 和模型的 `openapi.json` 只需获取一次并在进程生命周期内缓存,不要在每次调用前重新读取;它们只会在部署时变化。保留了 `429` 中请求标识符的客户端,就拥有了支持团队可以追踪的线索。 **状态:有意为之。** 按调用方限制请求速率的上限必须存在,理由与截止时间相同。数字可以调整,但限制的存在不会消失。 ## 调用运行期间无进度 -`POST /v1/models/{provider}/{model}` 只在调用结束时返回一次。没有流式响应、没有服务器发送事件、没有百分比、没有部分帧或预览帧。即使对于自身 API 为“提交并轮询”(submit-and-poll)的合作伙伴,情况也是如此:Router 会在您的这一次调用内部完成该轮询,但它看到的中间状态不会转发给您。从外部来看,耗时三秒的图像与耗时六分钟的视频形状相同:一个请求、一个响应,中间什么也没有。 +`POST /v2/models/{provider}/{model}` 只在调用结束时返回一次。没有流式响应、没有服务器发送事件、没有百分比、没有部分帧或预览帧。即使对于自身 API 为“提交并轮询”(submit-and-poll)的合作伙伴,情况也是如此:Router 会在您的这一次调用内部完成该轮询,但它看到的中间状态不会转发给您。从外部来看,耗时三秒的图像与耗时六分钟的视频形状相同:一个请求、一个响应,中间什么也没有。 **替代做法。** 就目前的 Router 而言,没有任何办法:请显示不确定的进度状态,而不是一个您无法获取的百分比。如果进度是某个特定提供商的硬性要求,请检查该提供商的合作伙伴代理路由是否公开了它们自己的轮询或流式接口,并直接使用这些路由:少数提供商确实如此,这些路由未做改动且完全受支持。 diff --git a/zh/api-reference/comfy-router/quickstart.mdx b/zh/api-reference/comfy-router/quickstart.mdx index d0e337ecb..61e410583 100644 --- a/zh/api-reference/comfy-router/quickstart.mdx +++ b/zh/api-reference/comfy-router/quickstart.mdx @@ -1,27 +1,28 @@ --- title: "Comfy Router 快速入门" description: "从零开始,大约五分钟内,使用 Python 和 TypeScript,在 Comfy Router 上生成一张图像。" -translationSourceHash: 50fc5524 +translationSourceHash: ead13a74 translationFrom: api-reference/comfy-router/quickstart.mdx translationBlockHashes: - "_intro": 46050c92 + "_intro": d9a651bf "Why this page uses `bfl/flux-2-pro`": 0eaf0bb3 "Get a key": 843d281f - "cURL": bc3e1e4c - "Python": 059e95b8 - "TypeScript": 1053430f + "cURL": 8ea9cb9f + "Python": f892ccc3 + "TypeScript": d0117cbc "Reading the `422`": 602cd505 - "Find a model": 9b7855c1 - "Where the model's fields come from": 7861bd52 + "Retrying safely with your own key": d1e75ba8 + "Find a model": a4caa991 + "Where the model's fields come from": 9cf8930c "Next": 6aa641e1 --- -**Comfy Router 尚未正式发布。** 以下路由:`POST /v1/models/{provider}/{model}` 及其目录与 schema 兄弟路由,目前均尚未处理请求:经过身份验证的调用目前会返回 `404`。本页面记录的是这些路由未来将提供的契约,并提前于该发布公开,以便集成可以据此进行编写。这不是对当前可执行行为的描述。 +**Comfy Router 尚未正式发布。** 以下路由:`POST /v2/models/{provider}/{model}` 及其目录与 schema 兄弟路由,目前均尚未处理请求:经过身份验证的调用目前会返回 `404`。本页面记录的是这些路由未来将提供的契约,并提前于该发布公开,以便集成可以据此进行编写。这不是对当前可执行行为的描述。 Comfy Router 通过单一主机、单一凭据和单一路由形状来运行合作伙伴模型。本页面是通往已生成图像的最短完整路径:安装客户端、设置密钥、发送一个请求、读取结果,并在真正遇到第一次失败之前,先看清失败的样子。 -Base URL 为 `https://api.comfy.org`。路由为 `POST /v1/models/{provider}/{model}`,请求体是模型自身的原生 JSON 输入,`200` 响应携带模型自身的原生 JSON 输出。Router 不会对两者进行包装,因此,只需更改主机,您已针对合作伙伴 API 写好的调用即可变成 Router 调用。 +Base URL 为 `https://api.comfy.org`。路由为 `POST /v2/models/{provider}/{model}`,请求体是模型自身的原生 JSON 输入,`200` 响应携带模型自身的原生 JSON 输出。Router 不会对两者进行包装,因此,只需更改主机,您已针对合作伙伴 API 写好的调用即可变成 Router 调用。 ## 为什么本页使用 `bfl/flux-2-pro` @@ -48,7 +49,7 @@ export COMFY_API_KEY="comfyui-..." 最短的调用方式,适用于脚本、冒烟测试以及直接复制粘贴到终端: ```bash -curl https://api.comfy.org/v1/models/bfl/flux-2-pro \ +curl https://api.comfy.org/v2/models/bfl/flux-2-pro \ -H "X-API-Key: $COMFY_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ @@ -117,7 +118,7 @@ def run(model: str, arguments: dict, idempotency_key: str) -> dict: # provider a second time. Reuse the SAME key when retrying one logical # call; generate a new one for a new call. response = httpx.post( - f"{BASE_URL}/v1/models/{model}", + f"{BASE_URL}/v2/models/{model}", headers={ "X-API-Key": os.environ["COMFY_API_KEY"], "Idempotency-Key": idempotency_key, @@ -229,7 +230,7 @@ async function run( // Idempotency-Key makes a retry safe on a PAID call: Router replays the // original response for 24h instead of dispatching (and billing) the provider // a second time. Reuse the SAME key when retrying one logical call. - const response = await fetch(`${BASE_URL}/v1/models/${model}`, { + const response = await fetch(`${BASE_URL}/v2/models/${model}`, { method: "POST", headers: { "X-API-Key": API_KEY, @@ -289,13 +290,53 @@ invalid_input (HTTP 422), request id 6f1c... `X-Comfy-Request-Id` 出现在每个响应上,成功、`4xx` 和 `5xx` 均如此,并且是在支持请求中引用的 ID。两个示例都将其附加到异常中,而不是让你在启用响应头日志的情况下重新运行来找到它。 +## 用自己的密钥安全地重试 + +上面的两个示例都发送了 `Idempotency-Key`。它值得单独用一节来说明,因为只有正确处理密钥,这个请求头才能发挥作用,而真正起决定作用的那一步发生在请求发出之前。 + +**自带密钥。** Router 不会替你生成密钥。请为每个*逻辑调用*生成一个新密钥(预期形式是 UUID),并在*该调用*的每一次重试中复用同一个密钥。每次尝试都换新密钥不会带来任何好处;而把同一个密钥用于两次确实不同的调用会得到 `409`,因为同一密钥搭配不同的请求(请求体不同,也包括模型路径、查询字符串或方法不同)属于冲突,而不是静默覆盖。 + +**发送前先持久化。** 在 `POST` 发出*之前*,而不是响应返回之后,把密钥写到一个比请求本身存活更久的地方(你正在为其生成内容的那行记录、你的任务记录、你的队列消息)。只存在于已崩溃进程内存中的密钥无法再次发送,本可以由 Router 的记录直接应答的那次重试,就变成了一次全新的、单独计费的运行。这一步很容易被跳过,而跳过的代价很高。 + +**带着它重试。** 重试时,只要 Router 仍持有该密钥的状态,就会直接应答而不是重新运行: + +| 你得到什么 | 含义 | 该怎么做 | +| --- | --- | --- | +| 带 `Idempotent-Replayed: true` 的 `200` | Router 重放了原始响应。不会再次计费。 | 直接使用:它就是原始结果。 | +| `409` / `concurrency_limit_exceeded` | 原始调用仍在运行。 | 等待 `Retry-After` 秒,再用**同一个密钥**重新发送。 | +| `409` / `invalid_input` | 该密钥无法服务这个请求:同一密钥下出现了不同的请求(请求体、模型路径、查询或方法不同),或者原始调用已完成但其响应无法重放。 | 使用**新**密钥。不要重新发送这个密钥。 | +| 带 `Retry-After` 的 `504` / `deadline_exceeded` | Router 已停止保持连接,但仍持有提供商正在运行的那次生成的句柄。 | 等待 `Retry-After` 秒,再用**同一个密钥**重新发送以收取结果。 | + +接着上面的 Python 示例:这里用一个文件代替你已有的任何持久化存储;重要的是顺序,而不是具体机制。请把整个请求和密钥一起持久化,而不是只保存密钥:重试必须重新发送*相同的*模型和参数,重启后凭记忆重建的请求哪怕只差一个空格也会得到 `409`,而用新密钥发送的请求则是第二次计费的生成。 + +```python +import json +import uuid + +# 在发出请求之前先持久化,这样即使在这里和收到响应之间发生崩溃, +# 也仍然留有一个密钥(以及它所属的确切请求)可供重试。 +request = { + "model": MODEL, + "arguments": {"prompt": "a red teapot on a windowsill, morning light"}, + "idempotency_key": str(uuid.uuid4()), +} +with open("pending-call.json", "w") as f: + json.dump(request, f) + +result = run(request["model"], request["arguments"], idempotency_key=request["idempotency_key"]) +``` + + +**密钥保证的是计费,而不是投递。** 一个密钥**最多计费一次**。它并不承诺该密钥最多只被分派一次,也不能让丢失的调用变得可恢复:如果连接在调用中途断开,且从未有任何内容提交给你,密钥就会被释放,用它重试会开启一次**全新运行**,而不是把你错过的结果交给你。请按“重试会产生新运行”来规划,把重放当作理想情况,而不是保证。持久、可恢复的“重新连接并收取”属于 Router 尚不具备的排队路径:参见[局限性](/zh/api-reference/comfy-router/limitations#无法恢复已丢失的调用)。 + + ## 查找模型 -`bfl/flux-2-pro` 只是其中一个 ID;其余的都在目录里。`GET /v1/models` 会逐页列出 Router 能运行的每一个模型,每个条目正好就是调用它所需的全部信息:放进路径的 `id`、分别给出的 `provider` 和 `model` 段,以及一个可以在花费任何费用之前据以分支判断的 `billing` 块。 +`bfl/flux-2-pro` 只是其中一个 ID;其余的都在目录里。`GET /v2/models` 会逐页列出 Router 能运行的每一个模型,每个条目正好就是调用它所需的全部信息:放进路径的 `id`、分别给出的 `provider` 和 `model` 段,以及一个可以在花费任何费用之前据以分支判断的 `billing` 块。 ```bash curl -H "X-API-Key: $COMFY_API_KEY" \ - "https://api.comfy.org/v1/models?limit=50" + "https://api.comfy.org/v2/models?limit=50" ``` ```json @@ -317,7 +358,7 @@ curl -H "X-API-Key: $COMFY_API_KEY" \ ```bash curl -H "X-API-Key: $COMFY_API_KEY" \ - https://api.comfy.org/v1/models/bfl/flux-2-pro/openapi.json + https://api.comfy.org/v2/models/bfl/flux-2-pro/openapi.json ``` 这份文档与服务器校验您的请求时所依据的文档是同一份,以独立的 OpenAPI 文档形式提供,因此所发布的内容与所执行的内容不可能不一致。从上面的目录中取任意 `id`,在其调用路径后追加 `/openapi.json`,然后根据返回的内容进行生成。 diff --git a/zh/api-reference/comfy-router/reference.mdx b/zh/api-reference/comfy-router/reference.mdx index d7cc5c7c4..a122dab12 100644 --- a/zh/api-reference/comfy-router/reference.mdx +++ b/zh/api-reference/comfy-router/reference.mdx @@ -1,15 +1,15 @@ --- title: "Comfy Router API 参考" description: "每个 Comfy Router 端点、参数、响应体和错误分类,均由 Comfy API 契约生成。" -translationSourceHash: 5b914bce +translationSourceHash: 7d6b2b62 translationFrom: api-reference/comfy-router/reference.mdx translationBlockHashes: "_intro": 0114a881 - "Endpoints": 93fdf96a - "Error buckets": bfbc4524 - "Response headers": 8f8b3475 - "Per-model input schemas": ae73e63b - "Schemas": 8ce93415 + "Endpoints": e00ef646 + "Error buckets": ec90b196 + "Response headers": 07b7f334 + "Per-model input schemas": 978b7612 + "Schemas": 1062b7df --- {/* @@ -28,11 +28,11 @@ Comfy Router 的规范路由,以模型 ID 寻址。 ## 端点 -### `GET /v1/models` +### `GET /v2/models` **列出 Comfy Router 可以运行的模型。** -Comfy Router 的模型目录:`POST /v1/models/{provider}/{model}` 所接受的规范模型 ID 的一页。SDK 在冷启动时调用此接口以发现可运行的模型,`model_not_found` 的建议也来自同一目录,因此,此处列出的 ID 在调用时返回 404 会比单独任一失败更糟糕。这种一致是结构性的,而非承诺:条目的 `provider` 和 `model` 是调用路由的两个路径段,引用与该路由路径参数相同的 schema 组件,而 `id` 是这两个段用 `/` 连接的结果。 +Comfy Router 的模型目录:`POST /v2/models/{provider}/{model}` 所接受的规范模型 ID 的一页。SDK 在冷启动时调用此接口以发现可运行的模型,`model_not_found` 的建议也来自同一目录,因此,此处列出的 ID 在调用时返回 404 会比单独任一失败更糟糕。这种一致是结构性的,而非承诺:条目的 `provider` 和 `model` 是调用路由的两个路径段,引用与该路由路径参数相同的 schema 组件,而 `id` 是这两个段用 `/` 连接的结果。 **参数** @@ -51,7 +51,7 @@ Comfy Router 的模型目录:`POST /v1/models/{provider}/{model}` 所接受的 | `403` | [`RouterErrorResponse`](#routererrorresponse) | `X-Comfy-Error-Type`, `X-Comfy-Request-Id` | Router 请求级失败:请求从未到达模型,或因模型本身未反馈的原因而失败。响应体为 `RouterErrorResponse`,错误类别会在 `X-Comfy-Error-Type` 中重复。 | | `503` | [`RouterErrorResponse`](#routererrorresponse) | `X-Comfy-Error-Type`, `X-Comfy-Request-Id` | Router 请求级失败:请求从未到达模型,或因模型本身未反馈的原因而失败。响应体为 `RouterErrorResponse`,错误类别会在 `X-Comfy-Error-Type` 中重复。 | -### `GET /v1/models/{provider}/{model}` +### `GET /v2/models/{provider}/{model}` **按规范模型 ID 读取单个合作伙伴模型的目录条目。** @@ -73,7 +73,7 @@ Comfy Router 的模型目录:`POST /v1/models/{provider}/{model}` 所接受的 | `404` | [`RouterErrorResponse`](#routererrorresponse) | `X-Comfy-Error-Type`, `X-Comfy-Request-Id` | Router 请求级错误:请求从未到达模型,或因模型自身未反馈的原因而失败。响应体为 `RouterErrorResponse`,错误类别在 `X-Comfy-Error-Type` 中重复。 | | `503` | [`RouterErrorResponse`](#routererrorresponse) | `X-Comfy-Error-Type`, `X-Comfy-Request-Id` | Router 请求级错误:请求从未到达模型,或因模型自身未反馈的原因而失败。响应体为 `RouterErrorResponse`,错误类别在 `X-Comfy-Error-Type` 中重复。 | -### `POST /v1/models/{provider}/{model}` +### `POST /v2/models/{provider}/{model}` **通过规范模型 ID 同步运行合作伙伴模型。** @@ -85,6 +85,7 @@ Comfy Router 的规范入口点,以模型 ID 寻址。请求体是合作伙伴 | --- | --- | --- | --- | --- | --- | | `provider` | path | 是 | [`RouterProviderSegment`](#routerprovidersegment) | `pattern: ^[a-z0-9]+([._-][a-z0-9]+)*$`, `maxLength: 64` | 规范 `{provider}/{model}[/{variant}]` 模型 ID 的小写提供商段:即正在运行其模型的合作伙伴。 | | `model` | path | 是 | [`RouterModelSegment`](#routermodelsegment) | `pattern: ^[a-z0-9]+([._-][a-z0-9]+)*$`, `maxLength: 128` | 规范 `{provider}/{model}[/{variant}]` 模型 ID 的小写模型段:即该提供商内要运行的模型。 | +| `Idempotency-Key` | header | 否 | string | `minLength: 1`, `maxLength: 255` | 由调用方生成的密钥,使对同一个逻辑调用的重试变得安全。已将答案送达调用方的调用会记在其密钥下并保留 24 小时,携带相同密钥的重试会从该记录中得到应答,而不是第二次分派并向提供商计费,并标记为 `Idempotent-Replayed: true`。该保证是计费层面的:一个密钥最多计费一次。它并不承诺一个密钥最多只被分派一次,也不能让丢失的调用变得可恢复。 | **请求体** @@ -96,15 +97,19 @@ Comfy Router 的规范入口点,以模型 ID 寻址。请求体是合作伙伴 | 状态 | 响应体 | 响应头 | 描述 | | --- | --- | --- | --- | -| `200` | [`RouterModelOutput`](#routermodeloutput) | `X-Comfy-Request-Id` | OK:合作伙伴模型的原生 JSON 输出,原样返回。 | +| `200` | [`RouterModelOutput`](#routermodeloutput) | `X-Comfy-Request-Id`, `Idempotent-Replayed` | OK:合作伙伴模型的原生 JSON 输出,原样返回。 | +| `400` | [`RouterErrorResponse`](#routererrorresponse) | `X-Comfy-Error-Type`, `X-Comfy-Request-Id` | Router 请求级失败:请求从未到达模型,或因模型自身未反馈的原因而失败。响应体为 `RouterErrorResponse`,错误分类会重复出现在 `X-Comfy-Error-Type` 中。 | +| `401` | [`RouterErrorResponse`](#routererrorresponse) | `X-Comfy-Error-Type`, `X-Comfy-Request-Id` | Router 请求级失败:请求从未到达模型,或因模型自身未反馈的原因而失败。响应体为 `RouterErrorResponse`,错误分类会重复出现在 `X-Comfy-Error-Type` 中。 | | `403` | [`RouterErrorResponse`](#routererrorresponse) | `X-Comfy-Error-Type`, `X-Comfy-Request-Id` | Router 请求级失败:请求从未到达模型,或因模型自身未反馈的原因而失败。响应体为 `RouterErrorResponse`,错误分类会重复出现在 `X-Comfy-Error-Type` 中。 | | `404` | [`RouterErrorResponse`](#routererrorresponse) | `X-Comfy-Error-Type`, `X-Comfy-Request-Id` | Router 请求级失败:请求从未到达模型,或因模型自身未反馈的原因而失败。响应体为 `RouterErrorResponse`,错误分类会重复出现在 `X-Comfy-Error-Type` 中。 | +| `409` | [`RouterErrorResponse`](#routererrorresponse) | `X-Comfy-Error-Type`, `X-Comfy-Request-Id`, `Retry-After` | 此请求携带的 `Idempotency-Key` 已被占用,且无法从其记录中应答此请求。两种情况共用该状态码,由 `X-Comfy-Error-Type` 加以区分,因为二者的处理方式恰好相反。`concurrency_limit_exceeded` 表示该密钥的原始调用仍在运行:等待 `Retry-After` 秒后重新发送同一个密钥,即可收取那次调用的结果,而不会开启第二次调用。`invalid_input` 表示该密钥根本无法服务此请求:它已被用于另一个不同的请求(方法、路径与查询或请求体与原始请求不同),或者原始调用已完成(若成功则已计费)而 Router 没有可供重放的忠实响应副本;此时的答案永远是换用新密钥,绝不是重新发送这个密钥。`detail` 会说明属于哪种情况。响应体为 `RouterErrorResponse`,错误分类会重复出现在 `X-Comfy-Error-Type` 中。 | +| `413` | [`RouterErrorResponse`](#routererrorresponse) | `X-Comfy-Error-Type`, `X-Comfy-Request-Id` | Router 请求级失败:请求从未到达模型,或因模型自身未反馈的原因而失败。响应体为 `RouterErrorResponse`,错误分类会重复出现在 `X-Comfy-Error-Type` 中。 | | `422` | [`RouterValidationErrorResponse`](#routervalidationerrorresponse) | `X-Comfy-Error-Type`, `X-Comfy-Request-Id` | 请求已到达模型,但模型拒绝了其内容。响应体为 `RouterValidationErrorResponse`,即 FastAPI 的 `detail[]` 形状,因此每个有问题的字段都保留自己的特定 `type` 和 `ctx`。`X-Comfy-Error-Type` 携带整个响应的粗粒度错误分类。 | | `429` | [`RouterErrorResponse`](#routererrorresponse) | `X-Comfy-Error-Type`, `X-Comfy-Request-Id`, `X-Committed-Spend-Limit`, `X-Committed-Spend-Current`, `X-Committed-Spend-Remaining` | 调用方已占满其被允许的在途容量,请求在到达模型之前就被拒绝。两种情况下分桶都是 `concurrency_limit_exceeded`,`detail` 会说明触及的是哪一个上限:并发调用数,或仍在途调用的已承诺支出;后者的拒绝还会携带 `X-Committed-Spend-Limit`、`X-Committed-Spend-Current` 和 `X-Committed-Spend-Remaining` 响应头(单位为美分)。待调用方自己的某个在途调用完成后再重试。响应体为 `RouterErrorResponse`,错误分类会重复出现在 `X-Comfy-Error-Type` 中。 | | `503` | [`RouterErrorResponse`](#routererrorresponse) | `X-Comfy-Error-Type`, `X-Comfy-Request-Id` | Router 请求级失败:请求从未到达模型,或因模型自身未反馈的原因而失败。响应体为 `RouterErrorResponse`,错误分类会重复出现在 `X-Comfy-Error-Type` 中。 | -| `504` | [`RouterErrorResponse`](#routererrorresponse) | `X-Comfy-Error-Type`, `X-Comfy-Request-Id`, `Retry-After` | Comfy 在自身配置的时限处停止了保持连接(`deadline_exceeded`)。响应体和那两个响应头与 `RouterRequestError` 完全相同;额外增加的是可选的 `Retry-After`,当使用相同 `Idempotency-Key` 重试会收取仍在运行的那次生成而不是派发新的生成时出现。参见 `POST /v1/models/{provider}/{model}` 上的 `504`。 | +| `504` | [`RouterErrorResponse`](#routererrorresponse) | `X-Comfy-Error-Type`, `X-Comfy-Request-Id`, `Retry-After` | Comfy 在自身配置的时限处停止了保持连接(`deadline_exceeded`)。响应体和那两个响应头与 `RouterRequestError` 完全相同;额外增加的是可选的 `Retry-After`,当使用相同 `Idempotency-Key` 重试会收取仍在运行的那次生成而不是派发新的生成时出现。参见 `POST /v2/models/{provider}/{model}` 上的 `504`。 | -### `GET /v1/models/{provider}/{model}/openapi.json` +### `GET /v2/models/{provider}/{model}/openapi.json` **以 OpenAPI 文档形式读取单个合作伙伴模型的输入模式。** @@ -140,7 +145,7 @@ Router 失败的粗粒度机器可读分类桶,同时反映在 `X-Comfy-Error- | `error_type` | 含义 | | --- | --- | -| `invalid_input` | 请求在到达模型之前就被拒绝:请求体格式错误、分页游标格式错误或已过期,或包含模型自身 schema 不接受的输入。 | +| `invalid_input` | 请求在到达模型之前就被拒绝:请求体格式错误、分页游标格式错误或已过期、包含模型自身 schema 不接受的输入,或者携带了无法服务此请求的 `Idempotency-Key`(已被用于另一个不同的请求,即方法、路径与查询或请求体不同;或已被某次响应无法重放的调用消费)。密钥相关的情况随 `409` 发送,其余情况随 `400`/`422` 发送;状态码说明属于哪一类,而密钥相关的情况要靠换用新密钥来解决,而不是修改请求。 | | `content_policy_violation` | 提供商基于内容政策理由拒绝了请求。该拒绝是确定性的:重新发送相同的输入仍会被拒绝。 | | `provider_error` | 合作伙伴提供商报告了其自身的故障,或返回了 Router 无法解释为结果的响应。 | | `provider_timeout` | 合作伙伴提供商未在其截止时间内应答。此分类桶表示提供商(PROVIDER)超时,绝不是 Router 自身的服务器截止时间,后者报告为 `deadline_exceeded`。二者同为 `504`,但被分开,因为它们指代不同的原因:前者表示合作伙伴失败,后者表示 Comfy 停止保持连接。 | @@ -155,7 +160,7 @@ Router 失败的粗粒度机器可读分类桶,同时反映在 `X-Comfy-Error- | --- | --- | | `unauthorized` | 请求未携带可用的凭据。 | | `forbidden` | 凭据有效,但无权访问此模型或执行此操作。 | -| `concurrency_limit_exceeded` | 工作区已在进行中的调用数量已达到允许的上限;等待其中一项调用完成后重试。 | +| `concurrency_limit_exceeded` | 工作区已在进行中的调用数量已达到允许的上限;等待其中一项调用完成后重试。在运行路由上它还有另一种情形,随 `409` 而不是上述 `429` 出现:此请求出示的 `Idempotency-Key` 已有另一个调用在途。请在 `Retry-After` 秒后重新发送同一个密钥以收取那次调用的结果。 | | `client_disconnected` | 调用方在 Router 返回结果之前关闭了连接。该错误会被记录而非投递,因为已没有可写入的 socket。它属于归因,而非计费结果:提供商已完成的生成任务都会计费,无论调用方是否收到响应。 | | `internal_error` | Router 自身失败。客户端也应当将任何无法识别的分类桶视为该值,这样以后再向集合中新增分类也不会破坏早先生成的客户端。 | | `deadline_exceeded` | 在答案到达之前,Comfy 在自身配置的时限处停止了保持连接。它与 `provider_timeout` 同为 `504`,这一对值表明是哪一方超时;此值是 Comfy 自身的时限,因此请求没有任何部分被拒绝,可以重试同一请求。它不涉及费用:提供商已完成的生成任务都会计费,无论调用方是否收到响应。请使用相同的 `Idempotency-Key` 重试:如果提供商已经接受了该次生成,重试会收取那次生成而不是再派发一次,`504` 上的 `Retry-After` 会告诉你何时再询问。 | @@ -168,8 +173,9 @@ Router 失败的粗粒度机器可读分类桶,同时反映在 `X-Comfy-Error- | 响应头 | 类型 | 描述 | | --- | --- | --- | | `Cache-Control` | 字符串 | 所提供的架构文档的新鲜度指令。`private` 是因为该路由经过身份验证:文档本身并不特定于调用方,但共享缓存不得保存对已认证请求的响应;`must-revalidate` 则用于让过期副本根据 `ETag` 重新验证,而不是继续将其提供出去。 | -| `ETag` | 字符串 | 基于所提供文档字节的强实体标签,用于 `GET /v1/models/{provider}/{model}/openapi.json`。每个模型的架构很少变更,而 SDK 会频繁重新获取它,因此调用方应存储此值,并将其作为 `If-None-Match` 发送回去,以获得 `304` 而不是整个文档。 | -| `Retry-After` | 整数 | 使用相同 `Idempotency-Key` 重试同一请求之前需要等待的秒数。仅当 Comfy 持有提供商仍在运行的某次生成的句柄时,才会出现在 `deadline_exceeded` 的 `504` 上;该值是 Router 自身的轮询间隔,也是这条路由对“稍后再问”能给出的唯一诚实数字。没有可收取的内容时则不出现:未带键的调用,或在提供商接受任何内容之前就已到期的时限。 | +| `ETag` | 字符串 | 基于所提供文档字节的强实体标签,用于 `GET /v2/models/{provider}/{model}/openapi.json`。每个模型的架构很少变更,而 SDK 会频繁重新获取它,因此调用方应存储此值,并将其作为 `If-None-Match` 发送回去,以获得 `304` 而不是整个文档。 | +| `Idempotent-Replayed` | 布尔值 | 当此响应是从某个 `Idempotency-Key` 的记录中提供、而不是再次运行模型得到时,出现且为 `true`。它携带原始调用的状态码、响应体和内容类型,并且不会第二次计费:费用已在原始调用完成时结清。全新运行时该响应头是不存在的,而不是以 `false` 发送,因此请根据它是否存在来分支判断。 | +| `Retry-After` | 整数 | 使用相同 `Idempotency-Key` 重试同一请求之前需要等待的秒数。它出现在这样的重试真正能够收取结果的两种应答上:携带 `error_type: concurrency_limit_exceeded` 的 `409`,此时该密钥的原始调用仍在运行;以及 `deadline_exceeded` 的 `504`,此时 Comfy 已停止保持连接,但仍持有提供商正在运行的那次生成的句柄。两种情况下,该值都是 Router 自身在再次询问之前会等待的间隔,也是这条路由对“稍后再问”能给出的唯一诚实数字。没有可收取的内容时则不出现:未带键的调用、在提供商接受任何内容之前就已到期的时限,或直接拒绝该密钥而不是让调用方等待的 `409`。 | | `X-Comfy-Error-Type` | [`RouterErrorType`](#routererrortype) | 失败原因的粗粒度、机器可读分类,由 Router 在每个错误响应上设置。其值与 `RouterErrorResponse.error_type` 相同;在 `422` 响应上,它是唯一的机器可读分类,因为该响应体是 FastAPI 的 `detail[]` 形状,本身没有 `error_type` 字段。因此,客户端可以仅根据此响应头进行分支判断,再决定收到的是两种 Router 错误体中的哪一种。 | | `X-Comfy-Request-Id` | 字符串 | 服务器为此次调用生成的标识符,存在于每个 Router 响应上:成功、4xx 和 5xx 响应均如此,因为错误响应恰恰是用户需要在支持请求中引用该 ID 的时候。相同的值会写入调用的使用/审计事件中,这使得关于费用的投诉可以直接关联到该费用本身,而无需按时间戳搜索。 | | `X-Committed-Spend-Current` | 整数 | 调用方当前已承诺给仍在途调用的美分数,不计被拒绝的那次调用。与 `X-Committed-Spend-Limit` 一同出现。 | @@ -178,7 +184,7 @@ Router 失败的粗粒度机器可读分类桶,同时反映在 `X-Comfy-Error- ## 各模型的输入模式 -模型自身的输入字段不在此处重复列出。请通过 `GET /v1/models/{provider}/{model}/openapi.json` 实时读取这些字段,该端点提供与服务器校验调用时所依据的同一份文档,因此对外发布的内容与强制执行的内容不会出现偏差。从 `GET /v1/models` 获取模型 ID,在其调用路径后附加 `/openapi.json`,然后根据返回的文档进行生成。 +模型自身的输入字段不在此处重复列出。请通过 `GET /v2/models/{provider}/{model}/openapi.json` 实时读取这些字段,该端点提供与服务器校验调用时所依据的同一份文档,因此对外发布的内容与强制执行的内容不会出现偏差。从 `GET /v2/models` 获取模型 ID,在其调用路径后附加 `/openapi.json`,然后根据返回的文档进行生成。 ## 模式 @@ -225,11 +231,11 @@ Router 的请求级错误体:当请求从未到达模型,或因模型本身 | 字段 | 类型 | 必填 | 约束 | 描述 | | --- | --- | --- | --- | --- | -| `input_schema_url` | 字符串 | 否 | `format: uri`, `pattern: ^https://`, `maxLength: 2048` | 指向此模型输入模式文档的指针,该文档描述 `POST /v1/models/{provider}/{model}` 为此模型接受的请求正文。只有指针属于此契约的一部分:其指向的文档是单独编写的。当模型尚未编写任何模式时,此字段不存在。 | +| `input_schema_url` | 字符串 | 否 | `format: uri`, `pattern: ^https://`, `maxLength: 2048` | 指向此模型输入模式文档的指针,该文档描述 `POST /v2/models/{provider}/{model}` 为此模型接受的请求正文。只有指针属于此契约的一部分:其指向的文档是单独编写的。当模型尚未编写任何模式时,此字段不存在。 | ### RouterModelId -Comfy Router 模型的规范 ID 为 `{provider}/{model}`,这正是 `POST /v1/models/{provider}/{model}` 上用于寻址该模型的值,因此调用方可以将其直接插入该路径,而无需从任何其他内容重新推导。其 `pattern` 由 `RouterProviderSegment` 和 `RouterModelSegment` 通过单个 `/` 连接而成,`maxLength` 为两者之和再加上该分隔符。 +Comfy Router 模型的规范 ID 为 `{provider}/{model}`,这正是 `POST /v2/models/{provider}/{model}` 上用于寻址该模型的值,因此调用方可以将其直接插入该路径,而无需从任何其他内容重新推导。其 `pattern` 由 `RouterProviderSegment` 和 `RouterModelSegment` 通过单个 `/` 连接而成,`maxLength` 为两者之和再加上该分隔符。 类型:`string`,`pattern: ^[a-z0-9]+([._-][a-z0-9]+)*/[a-z0-9]+([._-][a-z0-9]+)*$`,`maxLength: 193` @@ -241,7 +247,7 @@ Comfy Router 模型的规范 ID 为 `{provider}/{model}`,这正是 `POST /v1/m ### RouterModelInputSchemaDocument -一个独立的 OpenAPI 文档,描述单个 Comfy Router 模型的输入:即 `POST /v1/models/{provider}/{model}` 针对该模型接受的请求体。它正是 `GET /v1/models/{provider}/{model}/openapi.json` 所返回的内容。 +一个独立的 OpenAPI 文档,描述单个 Comfy Router 模型的输入:即 `POST /v2/models/{provider}/{model}` 针对该模型接受的请求体。它正是 `GET /v2/models/{provider}/{model}/openapi.json` 所返回的内容。 类型:`object` @@ -251,7 +257,7 @@ Router 模型目录中的一条条目:可运行模型的身份标识,仅此 | 字段 | 类型 | 必填 | 约束 | 描述 | | --- | --- | --- | --- | --- | -| `id` | [`RouterModelId`](#routermodelid) | 是 | `pattern: ^[a-z0-9]+([._-][a-z0-9]+)*/[a-z0-9]+([._-][a-z0-9]+)*$`, `maxLength: 193` | 一个规范的 Comfy Router 模型 ID,`{provider}/{model}`:正是 `POST /v1/models/{provider}/{model}` 上寻址该模型所使用的值,因此调用方可以将其直接插入该路径,而无需从任何内容重新推导。其 `pattern` 是 `RouterProviderSegment` 和 `RouterModelSegment` 以单个 `/` 连接而成,`maxLength` 是两者之和再加上该分隔符。 | +| `id` | [`RouterModelId`](#routermodelid) | 是 | `pattern: ^[a-z0-9]+([._-][a-z0-9]+)*/[a-z0-9]+([._-][a-z0-9]+)*$`, `maxLength: 193` | 一个规范的 Comfy Router 模型 ID,`{provider}/{model}`:正是 `POST /v2/models/{provider}/{model}` 上寻址该模型所使用的值,因此调用方可以将其直接插入该路径,而无需从任何内容重新推导。其 `pattern` 是 `RouterProviderSegment` 和 `RouterModelSegment` 以单个 `/` 连接而成,`maxLength` 是两者之和再加上该分隔符。 | | `provider` | [`RouterProviderSegment`](#routerprovidersegment) | 是 | `pattern: ^[a-z0-9]+([._-][a-z0-9]+)*$`, `maxLength: 64` | 规范的 `{provider}/{model}[/{variant}]` 模型 ID 中的小写 `provider` 段:也就是其模型正被寻址的那个合作伙伴。调用路由的 `provider` 路径参数和目录条目的 `provider` 字段都引用这同一个模式(schema),这正是保证所列出的 ID 与所接受的 ID 不会发生偏离的原因。 | | `model` | [`RouterModelSegment`](#routermodelsegment) | 是 | `pattern: ^[a-z0-9]+([._-][a-z0-9]+)*$`, `maxLength: 128` | 规范的 `{provider}/{model}[/{variant}]` 模型 ID 中的小写 `model` 段:即在该提供商内要运行的模型。该模式(schema)同时被调用路由的 `model` 路径参数和目录条目的 `model` 字段引用,出于与 `RouterProviderSegment` 相同的防偏离原因。 | | `billing` | [`RouterModelBilling`](#routermodelbilling) | 是 | - | 调用方在调用之前所需的按模型计费事实,而非价格。使用量和成本数字绝不会出现在此处。 |