llm-gateway-ops · v0.3.3 · 2026-10-02 · sha256 fd932c223c7e8019
llm-gateway-ops v0.3.3A
Immutable. This exact content never changes and is served at /api/v1/blob/fd932c223c7e8019.
--- name: llm-gateway-ops description: >- Operate the standing configuration of an application's model calls: multi-provider access over openai-compatible endpoints, capability matrices, routing and fallback chains, quota and rate-limit budgets, inference cost attribution, and the seven provider-specific failure classes (rate limits, auth, bad request, content filtering, server error, timeout, response-shape drift). Use when wiring a second provider, choosing a fallback chain, capping spend or throughput, or naming which model tier served a request. NOT for: triaging one reported model call that failed — root-cause tracing, severity grading, shipping a fix, closing it out; prompt design or model evaluation; fine-tuning and training; domain modelling; general observability beyond LLM-specific amounts. Platform model names, endpoints, prices, and timeouts change often — verify against the provider's current documentation. No skill name implied: the boundary is the task. slug: llm-gateway-ops version: 0.3.3 displayName: llm-gateway-ops --- # LLM Gateway Operations Everything about *calling models from an application and keeping those calls predictable*: which provider, which model, what happens when it fails, what it costs, and who is allowed to spend how much. This is not a book about models. > **v0.1.0 is a skeleton on purpose.** The two tables below — the capability matrix and the failure > taxonomy — are left as **shapes with placeholders**, not filled-in values. Model names, endpoints, > prices, context limits, and default timeouts change on the provider's schedule; a table filled in > today is a stale authority source within weeks. Fill a cell only from that provider's current > documentation, and record which document you used. Until then the cell stays `verify` and this > skill tells you to go read — rather than handing you a plausible-looking value. ## 中文速览(Quick Guide) - **做什么**:把应用接到多个模型 provider 上,管路由与降级链、配额限流、推理成本归因,并给每种 provider 特有失败一个可观察出口。 - **何时用**:要接第二个 provider、选主备降级链、控某条调用线的预算/吞吐,或判断某次请求实际由哪一档模型服务。 - **核心步骤**:①查能力矩阵确认目标模型是否支持你要的能力(tool call/JSON mode/流式/上下文)②定降级链并把切换留痕写进代码 ③设配额与超时,重试前先判幂等 ④按调用点归集 token 成本 ⑤失败时按分类走出口,不静默重试。 - **国内可达性**:本技能为本地判据与流程,不依赖境外在线服务;但 provider 端点本身在国内的可达性、速率与价格各不相同,选型时须按目标 provider 当前文档核对,必要时在降级链里把不可达的一档显式列为候选而非默认。 ## Hard Rules 1. **Never hardcode a provider value into prose.** Model names, endpoints, prices, context limits, and default timeouts are `verify` until read off that provider's current documentation. A remembered value is a guess, and a plausible guess is the failure mode this skill exists to prevent. 2. **Judge idempotency before retries.** A read-only lookup may be retried; a call whose result is written somewhere must carry an idempotency key or it must not be retried. "Retry with backoff" is not a policy until that question is answered for *this* call site. 3. **Fallback is never silent.** Every switch records which tier actually served the request and why the higher tier did not. A silent fallback makes production incidents untraceable. 4. **A failure is named before it is handled.** Rate limit, auth, bad request, content filter, server error, timeout, and response-shape drift are seven different problems. "Handle exception" is not an exit. 5. **Never log a credential.** Name the environment variable or secret path; print no value. 6. **Cost attribution stops at inference.** Training and fine-tuning are out of scope for this skill. ## The Two Tables Depth for both tables lives in the references: how to fill the matrix and where the openai-compatible edges actually drift is in [`references/provider-integration.md`](references/provider-integration.md); the per-class exits, the idempotency-to-retry table, and how to keep a fallback switch attributable afterwards are in [`references/failure-handling.md`](references/failure-handling.md). ### Capability matrix (shape — values unverified) One row per model you actually intend to call. Fill `verify` from current docs; leave it at `verify` until you have. | Provider | Model (the identifier your code addresses) | Tool calls | JSON mode | Streaming | Context | Latency tier | Price tier | Verified against | |---|---|---|---|---|---|---|---|---| | _verify_ | _verify_ | _verify_ | _verify_ | _verify_ | _verify_ | _verify_ | _verify_ | _which document + date_ | Rules for this table: - **Every cell must be filled or explicitly `verify`** — "probably yes" is a defect. - `Verified against` names the document, not just "the docs". Prices and context limits move. - Two models that look interchangeable in the table are not interchangeable in production; the difference shows up in the failure taxonomy, not in the happy path. ### Failure taxonomy (closed set) Each row's exit must name **what to read** and **what to do** — and, for at least one row, what *not* to do. | Class | How to recognize it | Exit | |---|---|---| | Rate limited / quota exhausted | Provider signals throttling; a retry-delay hint is present or absent (its name varies by provider — read the response, not this row) | Do not retry immediately. Surface the limit to the caller; decide with the human owning the budget whether to wait, degrade, or fail. | | Auth / credential rejected | Provider rejects the key or its scope | Do not retry — a retry with the same key cannot succeed. Report which credential is missing or under-scoped (name only). | | Bad request / parameter rejected | Provider rejects the request shape | Do not retry unchanged. This is a code bug in the request, not an outage. | | Content filtered | Provider blocks the content | Do not retry with a reworded prompt unless a human asked for a fallback; report the filter, do not smuggle the content past it. | | Server error / unavailable | Provider-side failure | Retry only if the call site is idempotent (Hard Rule 2). | | Timeout | No response within the call's timeout | Retry only if idempotent; otherwise surface partial state rather than assuming nothing happened. | | Response-shape drift | Response parses but a field the caller relies on is missing or differently shaped | This is the failure that silently corrupts downstream code. Treat as a hard failure even when the request "succeeded". | **Not in the closed set**: "model quality is bad" and "the answer was wrong". Those are evaluation concerns, not gateway concerns — this skill does not route them. ## Fallback chains A chain is an ordered list of tiers with a reason for each. The shape: | Tier | Role | Chosen when | Cost | Recorded on every call | |---|---|---|---|---| | primary | default | — | _verify_ | yes | | secondary | degraded quality, still usable | primary unavailable or over budget | _verify_ | yes | | last resort | correctness over quality, or cached answer | both above unavailable | _verify_ | yes | Rules: - Every tier needs a **trigger**, not just an order. "Primary times out" and "primary exceeds the caller's latency budget" are different triggers with different consequences. - Tiers that are simply unreachable from your network belong in the chain **as candidates**, marked as such — not as defaults that quietly fail. - If a caller's quality requirement is met by no tier, the chain does not get a last-resort entry that produces a worse answer silently; the call fails and says why. ## Quota, rate limits, and cost attribution | Concern | Where it is decided | What must be observable | |---|---|---| | Per-call rate ceiling | the call site, as a timeout plus a token budget | which budget applied to a given call | | Per-caller limit | the layer that knows the caller | when a limit was hit, and by whom | | Spend ceiling | whatever enforces the budget | the remaining budget at decision time, not afterwards | | Cost attribution | the point where usage is metered | usage split by call site; a total alone cannot answer "which feature got expensive" | **Attribution needs a call-site label.** If usage is metered without one, the numbers cannot be split later — plan the label at the call site, not at the dashboard. ## Failure exits This table is **not an eighth failure class** — every row is a situation *made of* the seven above. If a new row cannot be traced back to one of them, it probably belongs in the taxonomy instead. | Situation | What to do | |---|---| | Provider unreachable from your network | Record the tier as a candidate in the chain; do not make it the default. | | The chain is exhausted | Fail with the list of tiers already tried and why each was skipped. Never return an empty success. | | Quota exhausted mid-run | Surface it; do not let one caller consume another's budget silently. | | Usage looks anomalous (retry storm, context growth) | Report the call site and the shape of the anomaly; do not silently disable retries to make it go away. | | The capability matrix is stale for a model in use | Treat the affected call as unverified and say so; do not fill the gap from memory. | ## Not for - **Prompt design and model evaluation** — iterating on what you ask or scoring what comes back. - **Fine-tuning and training** — explicitly out of scope; this skill ends at inference. - **Application domain modelling** — `ddd-*` and `architecture-patterns`. - **General observability infrastructure** — metrics, logs, and tracing belong to the project; this skill only owns the LLM-specific amounts (tokens, per-caller limits, fallback tier actually used). ### Boundary with `problem-handling` (settled 2026-10-02 by scenario test, not by guess) An eight-scenario collision test (audits/2026-10-02-llm-gateway-ops-boundary-test.md) found that **5 of 8** realistic LLM scenarios match *both* skills' trigger language: `problem-handling` triggers on 「报错/失败/异常/行为偏差」, and this skill owns the provider-shaped exits. Overlapping trigger words are unavoidable — the line is therefore **not** "whose trigger fires first". **The line is: standing state vs. an event.** | | `problem-handling` | This skill | |---|---|---| | Nature of the work | a **reported event** that needs diagnosing, grading, fixing, and closing out | **standing state** of the model-call layer: which provider, which tier served it, what it costs, what the budget allows | | Entry question | "something broke — what is the root cause?" | "how should this layer behave, and is it behaving that way?" | | Output | a fix, plus a blameless close with captured known errors | a configured behaviour: routing, budgets, failure exits | | Time | one incident, start to close | continuous, revised whenever providers change | **Reading the collision.** A single 401 or one timeout burst is an *event* → `problem-handling`, which may call this skill's failure taxonomy for the provider-shaped part. "This provider silently served the cheap tier for a week", "the bill doubled and nobody knows which feature", "we need a second provider" are *standing state* → this skill, with no incident to diagnose. Neither skill's `NOT for` names the other: they are not exclusive, and pretending otherwise would break both. What is exclusive is the question being asked. ## Wrong → Fix | Mistake | Why it breaks | Fix | |---|---|---| | Retrying every failure with backoff | Non-idempotent calls duplicate side effects; auth and bad-request retries never succeed | Judge idempotency first, then classify, then retry only what is retryable | | Falling back silently | Production looks fine while quality quietly drops | Record the tier that served and the reason for every switch | | One model for everything | Cheap tasks pay premium prices; hard tasks fail on a model that cannot do tool calls | Route by task, then verify the capability matrix cell before routing to it | | Metering usage without a call-site label | Cost cannot be split later, so the bill cannot be attributed | Label at the call site, where the caller is still known | | Filling the matrix from memory | Prices, context limits, and model availability move on the provider's schedule | `verify` until read from the current documentation; name the document and date | | Logging the provider key to "see which one" | Leaks the credential | Name the variable or secret path; print no value |