The info that LiteLLM serves about the available models through gateway.inference.coop/v1/models/ or /model_group/info/ is sometimes incomplete or incorrect (see appendix). For example: context length, capabilities.
That info currently will need to be manually updated when upstream changes their options.
Proposal.
Automatically pull full model info from upstreams that provide it over API (currently Tinfoil, Regolo)
Maintain a manual file for other upstreams (currently GreenPT)
Ensure all of this info is served from our own endpoint /v1/models and perhaps /model/info
Not changed. How pricing information is set.
--> Thoughts? If it sounds good, I'll start working on a PR for this.
Appendix
Warning: LLM-researched and generated.
1) Missing from LiteLLM discovery
#
Missing
Where it is absent
Effect on OMP
1
Prompt/context and output caps
max_input_tokens, max_output_tokens are null on 11/11 groups in both /model_group/info and /v2/model/info
Limits are substituted locally: bundled-reference rows supply 1M for the GreenPT/Tinfoil DeepSeek and GLM rows, 131K for gpt-oss-120b; both Regolo rows fall to the generic 128K context / 32K output defaults (apertus-70b actually serves 65K, qwen3.5-122b 262K)
2
Reasoning effort ladder
supported_reasoning_efforts: null on 11/11
The real ladders differ and are only discoverable by brute force: DeepSeek minimal…xhigh; glm-5.3minimal…high (xhigh → 400); gpt-oss-120blow…high (minimal → 400); apertus-70b accepts every value including xhigh. off → 400 everywhere, so thinking cannot be disabled on any group
3
Task-mode classification
mode: null on 6/11 groups — every GreenPT and Tinfoil chat row
OMP's mode filter can only classify the 5 rows that carry mode (chat, image_generation, audio_transcription, audio_speech); the remaining 6 are assumed to be chat (correct today, unverified by the proxy)
4
One model entirely
tinfoil/nomic-embed-text (mode embedding) appears only in /v2/model/info — 12 rows there vs 11 in /model_group/info and /v1/models
Because the first usable roster wins, the embedding model is undiscoverable regardless of OMP configuration
5
Display names
No name / display_name field on any surface; ids only
OMP synthesizes names ("DeepSeek V4.1 Flash"); human/marketing names and multi-word aliases are lost
6
Real upstream attribution
providers: ["openai"] on every group; owned_by: "openai" on every /v1/models row. The true hosts (api.greenpt.ai, 127.0.0.1:3301, api.regolo.ai) exist only in /v2/model/info → litellm_params.api_base, on the surface OMP never reaches
OMP reads providers to choose the wire and therefore assigns openai-responses to all 8 discovered chat rows. It works, but the field describes the LiteLLM adapter, not the backend
7
Quotas and health
tpm, rpm, itpm, otpm, health_status, health_response_time, health_checked_at are null on 11/11
No rate-limit awareness, no health signal in listings
8
Tiered / batch / peak pricing
Every *_above_*k_tokens, *_flex, *_priority, *_batches cost field is null
Only flat per-token prices are representable; a long-context tier or off-peak tariff would be invisible
9
Cache-write price
cache_creation_input_token_cost is null on 11/11; only cache_read_input_token_cost is populated
OMP's cacheWrite estimate is 0 for every model
10
Audio and image specifics
No voice list (valid speakers — ar_male, casual_female, casual_male, cheerful_female, de_female, …, neutral_male — surface only inside a 400 body), no output-format list, no pixel or duration caps. TTS cost is entirely null (tinfoil/voxtral-tts: both input and output null); the image row carries a per-pixel price (1.5e-08) that OMP's cost model cannot express
TTS is priced at $0, image generation is priced at $0, and the speech role has no way to select a valid voice
11
Identity and tokenizer
No canonical id, alias, or tokenizer field. The same weights appear as deepseek-v4.1-flash (GreenPT) and deepseek-v4-1-flash (Tinfoil)
Reference-metadata matching and token accounting are guesswork
2) Wrong in LiteLLM discovery
Capability flags as reported (false for all four, all models) versus what the endpoint does.
Probe: a 64×64 red PNG for vision, a get_weather function for tool calling, a two-city request
with parallel_tool_calls: true, and a plain 17*23 prompt with noreasoning_effort
parameter for reasoning.
model
function calling
vision
reasoning
parallel FC
greenpt/deepseek-v4.1-flash
false → tool_calls returned
false → answers "Red"
false → reasoning_content present
false → 2 calls
tinfoil/deepseek-v4-1-flash
false → tool_calls returned
false → answers "Red"
false → reasoning_content present
false → 2 calls
greenpt/glm-5.3
false → tool_calls returned
false → 200, but "I don't see an image attached"
false → reasoning_content present
false → **2
calls**
tinfoil/glm-5.3
false → tool_calls returned
false → 400 "not a multimodal model" (correct)
false → reasoning_content present
false → **2
calls**
greenpt/glm-5.3-flash
false → tool_calls returned
false → answers "Red"
false → reasoning_content present
false → 2 calls
tinfoil/gpt-oss-120b
false → tool_calls returned
false → 200, but "I can't view images"
false → reasoning_content present
false → 1 call
(inconclusive)
regolo/qwen3.5-122b
false → tool_calls returned
false → answers "Red"
false → none (correct)
not probed
regolo/apertus-70b
false → tool_calls returned
false → answers "Red"
false → none (correct)
not probed
supports_function_calling: false on 11/11 — wrong for at least 8. Every chat model returns
a valid tool_calls payload. OMP's tool use works only because it advertises tools regardless;
the metadata asserts the opposite.
supports_parallel_function_calling: false on 11/11 — wrong for 5 of 5 measured. One request
asking for Paris and Tokyo returned two tool_calls on both DeepSeek rows, both glm-5.3 rows,
and glm-5.3-flash.
supports_vision: false on 11/11 — wrong for 5 of 8 chat models. greenpt/deepseek-v4.1-flash, tinfoil/deepseek-v4-1-flash, greenpt/glm-5.3-flash, regolo/qwen3.5-122b and regolo/apertus-70b all identify the red square. The flag is correct
only for tinfoil/glm-5.3 (hard 400). greenpt/glm-5.3 is effectively text-only (200, image
dropped). Note: the cooperative's own published note lists greenpt/glm-5.3-flash as vision: false; that is also wrong.
supports_reasoning: false on 11/11 — wrong for 6 of 8. With no reasoning_effort supplied,
both DeepSeek rows, both glm-5.3 rows, glm-5.3-flash and gpt-oss-120b all return non-empty reasoning_content, i.e. thinking is on by default. Only the two Regolo rows are genuinely
non-thinking.
supported_openai_params is byte-identical across all 11 groups. The same 32-entry chat list
(tools, tool_choice, logprobs, audio, prediction, service_tier, prompt_cache_retention, …) is attached to tinfoil/voxtral-tts, tinfoil/whisper-large-v3-turbo and regolo/qwen-image, which are not chat endpoints. On four
chat models the list holds up under spot checks (n=2, logprobs, store all accepted), so the
defect is structural rather than per-model.
/v1/models is false on two fields for every row.created: 1677610602 (2023-02-28) for all
11 models, and owned_by: "openai" for all 11 — Tinfoil, GreenPT and Regolo models are reported
as OpenAI's. Every OpenAI-SDK consumer and OMP's generic openai-models-list path inherits both.
Two groups accept images and silently drop them.greenpt/glm-5.3 ("I don't see an image
attached") and tinfoil/gpt-oss-120b ("I can't view images") return 200 with the image discarded.
Capability probing by HTTP status therefore yields false positives; only checking the answer text
reveals it. (Proxy behaviour, not a discovery field — but it defeats discovery-by-probe.)
**Problems.**
1. The info that LiteLLM serves about the available models through `gateway.inference.coop/v1/models/` or `/model_group/info/` is sometimes incomplete or incorrect (see appendix). For example: context length, capabilities.
2. That info currently will need to be manually updated when upstream changes their options.
**Proposal.**
1. Automatically pull full model info from upstreams that provide it over API (currently Tinfoil, Regolo)
2. Maintain a manual file for other upstreams (currently GreenPT)
3. Ensure all of this info is served from our own endpoint /v1/models and perhaps /model/info
*Not changed.* How pricing information is set.
--> Thoughts? If it sounds good, I'll start working on a PR for this.
----
# Appendix
Warning: LLM-researched and generated.
## 1) Missing from LiteLLM discovery
| # | Missing | Where it is absent | Effect on OMP |
|---|---------|--------------------|---------------|
| 1 | Prompt/context and output caps | `max_input_tokens`, `max_output_tokens` are `null` on **11/11** groups in both `/model_group/info` and `/v2/model/info` | Limits are substituted locally: bundled-reference rows supply 1M for the GreenPT/Tinfoil DeepSeek and GLM rows, 131K for `gpt-oss-120b`; both Regolo rows fall to the generic 128K context / 32K output defaults (`apertus-70b` actually serves 65K, `qwen3.5-122b` 262K) |
| 2 | Reasoning effort ladder | `supported_reasoning_efforts: null` on 11/11 | The real ladders differ and are only discoverable by brute force: DeepSeek `minimal…xhigh`; `glm-5.3` `minimal…high` (`xhigh` → 400); `gpt-oss-120b` `low…high` (`minimal` → 400); `apertus-70b` accepts every value including `xhigh`. `off` → 400 everywhere, so thinking cannot be disabled on any group |
| 3 | Task-mode classification | `mode: null` on **6/11** groups — every GreenPT and Tinfoil chat row | OMP's mode filter can only classify the 5 rows that carry `mode` (`chat`, `image_generation`, `audio_transcription`, `audio_speech`); the remaining 6 are assumed to be chat (correct today, unverified by the proxy) |
| 4 | One model entirely | `tinfoil/nomic-embed-text` (mode `embedding`) appears **only** in `/v2/model/info` — 12 rows there vs 11 in `/model_group/info` and `/v1/models` | Because the first usable roster wins, the embedding model is undiscoverable regardless of OMP configuration |
| 5 | Display names | No `name` / `display_name` field on any surface; ids only | OMP synthesizes names ("DeepSeek V4.1 Flash"); human/marketing names and multi-word aliases are lost |
| 6 | Real upstream attribution | `providers: ["openai"]` on every group; `owned_by: "openai"` on every `/v1/models` row. The true hosts (`api.greenpt.ai`, `127.0.0.1:3301`, `api.regolo.ai`) exist only in `/v2/model/info` → `litellm_params.api_base`, on the surface OMP never reaches | OMP reads `providers` to choose the wire and therefore assigns `openai-responses` to all 8 discovered chat rows. It works, but the field describes the LiteLLM adapter, not the backend |
| 7 | Quotas and health | `tpm`, `rpm`, `itpm`, `otpm`, `health_status`, `health_response_time`, `health_checked_at` are `null` on 11/11 | No rate-limit awareness, no health signal in listings |
| 8 | Tiered / batch / peak pricing | Every `*_above_*k_tokens`, `*_flex`, `*_priority`, `*_batches` cost field is `null` | Only flat per-token prices are representable; a long-context tier or off-peak tariff would be invisible |
| 9 | Cache-write price | `cache_creation_input_token_cost` is `null` on 11/11; only `cache_read_input_token_cost` is populated | OMP's `cacheWrite` estimate is 0 for every model |
| 10 | Audio and image specifics | No voice list (valid speakers — `ar_male`, `casual_female`, `casual_male`, `cheerful_female`, `de_female`, …, `neutral_male` — surface only inside a 400 body), no output-format list, no pixel or duration caps. TTS cost is entirely `null` (`tinfoil/voxtral-tts`: both input and output null); the image row carries a per-pixel price (`1.5e-08`) that OMP's cost model cannot express | TTS is priced at $0, image generation is priced at $0, and the `speech` role has no way to select a valid voice |
| 11 | Identity and tokenizer | No canonical id, alias, or tokenizer field. The same weights appear as `deepseek-v4.1-flash` (GreenPT) and `deepseek-v4-1-flash` (Tinfoil) | Reference-metadata matching and token accounting are guesswork |
---
## 2) Wrong in LiteLLM discovery
Capability flags as reported (`false` for all four, all models) versus what the endpoint does.
Probe: a 64×64 red PNG for vision, a `get_weather` function for tool calling, a two-city request
with `parallel_tool_calls: true`, and a plain `17*23` prompt with **no** `reasoning_effort`
parameter for reasoning.
| model | function calling | vision | reasoning | parallel FC |
|---|---|---|---|---|
| `greenpt/deepseek-v4.1-flash` | false → **tool_calls returned** | false → **answers "Red"** | false → **`reasoning_content` present** | false → **2 calls** |
| `tinfoil/deepseek-v4-1-flash` | false → **tool_calls returned** | false → **answers "Red"** | false → **`reasoning_content` present** | false → **2 calls** |
| `greenpt/glm-5.3` | false → **tool_calls returned** | false → 200, but "I don't see an image attached" | false → **`reasoning_content` present** | false → **2
calls** |
| `tinfoil/glm-5.3` | false → **tool_calls returned** | false → 400 "not a multimodal model" (correct) | false → **`reasoning_content` present** | false → **2
calls** |
| `greenpt/glm-5.3-flash` | false → **tool_calls returned** | false → **answers "Red"** | false → **`reasoning_content` present** | false → **2 calls** |
| `tinfoil/gpt-oss-120b` | false → **tool_calls returned** | false → 200, but "I can't view images" | false → **`reasoning_content` present** | false → 1 call
(inconclusive) |
| `regolo/qwen3.5-122b` | false → **tool_calls returned** | false → **answers "Red"** | false → none (correct) | not probed |
| `regolo/apertus-70b` | false → **tool_calls returned** | false → **answers "Red"** | false → none (correct) | not probed |
1. **`supports_function_calling: false` on 11/11 — wrong for at least 8.** Every chat model returns
a valid `tool_calls` payload. OMP's tool use works only because it advertises tools regardless;
the metadata asserts the opposite.
2. **`supports_parallel_function_calling: false` on 11/11 — wrong for 5 of 5 measured.** One request
asking for Paris and Tokyo returned two `tool_calls` on both DeepSeek rows, both `glm-5.3` rows,
and `glm-5.3-flash`.
3. **`supports_vision: false` on 11/11 — wrong for 5 of 8 chat models.**
`greenpt/deepseek-v4.1-flash`, `tinfoil/deepseek-v4-1-flash`, `greenpt/glm-5.3-flash`,
`regolo/qwen3.5-122b` and `regolo/apertus-70b` all identify the red square. The flag is correct
only for `tinfoil/glm-5.3` (hard 400). `greenpt/glm-5.3` is effectively text-only (200, image
dropped). Note: the cooperative's own published note lists `greenpt/glm-5.3-flash` as
`vision: false`; that is also wrong.
4. **`supports_reasoning: false` on 11/11 — wrong for 6 of 8.** With no `reasoning_effort` supplied,
both DeepSeek rows, both `glm-5.3` rows, `glm-5.3-flash` and `gpt-oss-120b` all return non-empty
`reasoning_content`, i.e. thinking is on by default. Only the two Regolo rows are genuinely
non-thinking.
5. **`supported_openai_params` is byte-identical across all 11 groups.** The same 32-entry chat list
(`tools`, `tool_choice`, `logprobs`, `audio`, `prediction`, `service_tier`,
`prompt_cache_retention`, …) is attached to `tinfoil/voxtral-tts`,
`tinfoil/whisper-large-v3-turbo` and `regolo/qwen-image`, which are not chat endpoints. On four
chat models the list holds up under spot checks (`n=2`, `logprobs`, `store` all accepted), so the
defect is structural rather than per-model.
6. **`/v1/models` is false on two fields for every row.** `created: 1677610602` (2023-02-28) for all
11 models, and `owned_by: "openai"` for all 11 — Tinfoil, GreenPT and Regolo models are reported
as OpenAI's. Every OpenAI-SDK consumer and OMP's generic `openai-models-list` path inherits both.
7. **Two groups accept images and silently drop them.** `greenpt/glm-5.3` ("I don't see an image
attached") and `tinfoil/gpt-oss-120b` ("I can't view images") return 200 with the image discarded.
Capability probing by HTTP status therefore yields false positives; only checking the answer text
reveals it. (Proxy behaviour, not a discovery field — but it defeats discovery-by-probe.)
Thanks Bo — I reproduced all of it against the live gateway just now, and I think the diagnosis points somewhere a bit different than the proposal does. Numbers from gateway.inference.coop (LiteLLM 1.103.2, admin key, 2026-10-26):
/v1/models: 11 rows, owned_by: "openai" and created: 1677610602 on every one.
/model_group/info: max_input_tokens, max_output_tokens, supported_reasoning_efforts, display_name null on 11/11; supports_function_calling, supports_vision, supports_reasoningfalse on 11/11; mode null on 6/11 (present: chat x2, image_generation, audio_transcription, audio_speech).
/v2/model/info: 12 rows against 11 elsewhere.
Root cause: our config declares only pricing
Every entry in this repo's config.yaml has a model_info: block, and those blocks contain input_cost_per_token, output_cost_per_token, cache_read_input_token_cost and, on the image row, input_cost_per_pixel — pricing and nothing else. mode appears on five entries only.
LiteLLM serves what it is told. With no max_input_tokens, no mode, no supports_*, and no registry entry for custom vendor names like greenpt/deepseek-v4.1-flash, nulls and false are the honest answer. So the missing fields in section 1 — and much of section 2 — are a declaration gap in our own config, closable now without any upstream integration. The auto-pull you propose is the right hygiene for keeping the values fresh afterwards; it isn't the fix for what is absent today.
Three cautions before the PR
No generator may touch pricing. Cost keys live in the same model_info block as the discovery fields. LiteLLM computes member spend, allowance consumption and budget caps from them, so a generated file that rewrites model_info wholesale would silently reprice every model and drift every member's budget. Keep the generator strictly additive — discovery fields only, never a cost key — and add a test asserting the pricing keys are unchanged before/after generation. That test is the thing that makes this safe to automate.
Upstream APIs cannot supply what you measured. Tools, vision, reasoning and the effort ladders came from your probes, not vendor metadata — and status codes lie: greenpt/glm-5.3 and tinfoil/gpt-oss-120b return 200 with the image quietly discarded. So the shape is auto-pull for what upstreams publish plus a maintained empirical layer for capabilities. That is a larger manual surface than "GreenPT only" implies — worth deciding deliberately rather than discovering halfway.
Shipping it means a gateway restart. This repo's config.yaml mirrors the live deployment, so a new field is only served after the app restarts. That's a member-facing inference blip: batch it, don't slip it in.
Two notes
Section 2 corroborates an earlier investigation of ours (co-op/docs#8): supports_function_calling: false is served while tool calls return normally — 6/6 modes pass on greenpt/glm-5.3-flash, with the call arriving in tool_calls and an empty content.
The published models note claims greenpt/glm-5.3-flash is vision: false; your probe contradicts it. I'll correct the docs either way.
Suggested split
Declare the missing fields in config.yaml now — unblocks the client, no automation, and the pricing invariant is untouched by construction.
Then the pull/generate step as a follow-up, with the pricing guard and the empirical capability layer.
Happy to review the PR when it lands, and I'll keep an eye on this thread.
Thanks Bo — I reproduced all of it against the live gateway just now, and I think the diagnosis points somewhere a bit different than the proposal does. Numbers from `gateway.inference.coop` (LiteLLM 1.103.2, admin key, 2026-10-26):
- `/v1/models`: 11 rows, `owned_by: "openai"` and `created: 1677610602` on every one.
- `/model_group/info`: `max_input_tokens`, `max_output_tokens`, `supported_reasoning_efforts`, `display_name` null on **11/11**; `supports_function_calling`, `supports_vision`, `supports_reasoning` **false on 11/11**; `mode` null on **6/11** (present: chat x2, image_generation, audio_transcription, audio_speech).
- `/v2/model/info`: 12 rows against 11 elsewhere.
## Root cause: our config declares only pricing
Every entry in this repo's `config.yaml` has a `model_info:` block, and those blocks contain `input_cost_per_token`, `output_cost_per_token`, `cache_read_input_token_cost` and, on the image row, `input_cost_per_pixel` — pricing and nothing else. `mode` appears on five entries only.
LiteLLM serves what it is told. With no `max_input_tokens`, no `mode`, no `supports_*`, and no registry entry for custom vendor names like `greenpt/deepseek-v4.1-flash`, nulls and `false` are the honest answer. So the missing fields in section 1 — and much of section 2 — are a **declaration gap in our own config**, closable now without any upstream integration. The auto-pull you propose is the right *hygiene* for keeping the values fresh afterwards; it isn't the fix for what is absent today.
## Three cautions before the PR
1. **No generator may touch pricing.** Cost keys live in the same `model_info` block as the discovery fields. LiteLLM computes member spend, allowance consumption and budget caps from them, so a generated file that rewrites `model_info` wholesale would silently reprice every model and drift every member's budget. Keep the generator strictly additive — discovery fields only, never a cost key — and add a test asserting the pricing keys are unchanged before/after generation. That test is the thing that makes this safe to automate.
2. **Upstream APIs cannot supply what you measured.** Tools, vision, reasoning and the effort ladders came from your probes, not vendor metadata — and status codes lie: `greenpt/glm-5.3` and `tinfoil/gpt-oss-120b` return 200 with the image quietly discarded. So the shape is auto-pull for what upstreams publish *plus* a maintained empirical layer for capabilities. That is a larger manual surface than "GreenPT only" implies — worth deciding deliberately rather than discovering halfway.
3. **Shipping it means a gateway restart.** This repo's `config.yaml` mirrors the live deployment, so a new field is only served after the app restarts. That's a member-facing inference blip: batch it, don't slip it in.
## Two notes
- Section 2 corroborates an earlier investigation of ours (co-op/docs#8): `supports_function_calling: false` is served while tool calls return normally — 6/6 modes pass on `greenpt/glm-5.3-flash`, with the call arriving in `tool_calls` and an empty `content`.
- The published models note claims `greenpt/glm-5.3-flash` is `vision: false`; your probe contradicts it. I'll correct the docs either way.
## Suggested split
1. Declare the missing fields in `config.yaml` now — unblocks the client, no automation, and the pricing invariant is untouched by construction.
2. Then the pull/generate step as a follow-up, with the pricing guard and the empirical capability layer.
Happy to review the PR when it lands, and I'll keep an eye on this thread.
We could declare the missing fields now, but it's a one-time fix. We'd need to manually update config.yaml any time an upstream provider changes what models they offer or we add a new provider.
We could declare the missing fields now, but it's a one-time fix. We'd need to manually update config.yaml any time an upstream provider changes what models they offer or we add a new provider.
Checking in on this, since Bo asked whether it is ready to go.
Where it stands — measured against the live gateway just now, via /model_group/info (11 entries):
max_input_tokens null: 11/11
max_output_tokens null: 11/11
mode null: 6/11
supports_function_calling true: 0/11
supports_vision true: 0/11
So nothing has changed since the measurements above: the fields are still simply absent, which is why clients substitute their own numbers for us rather than reading ours.
On the proposal — the shape is right, and the split you suggest is the correct one: pull from upstreams that expose it (Tinfoil, Regolo), keep a manual file for GreenPT. Two things I can hand over so you do not have to rediscover them:
Read Tinfoil the way our traffic does. Our model-info watcher authenticates to Tinfoil through the in-TEE proxy inside the gateway container, not a side door. If the sync reads Tinfoil directly, it can report a model list that does not match what we actually serve.
Key the GreenPT file on model_group, not model. Our spend logs carry the upstream name (openai/deepseek-v4.1-flash) while everything member-facing is keyed on our alias (greenpt/deepseek-v4.1-flash). Matching on the wrong one fails silently — it just matches nothing. I lost an afternoon to exactly that mistake in a different script.
Bar for "done", in my view: every served group reports real caps and capabilities, and a re-run after an upstream change needs no hand editing. Pricing stays as-is, per your "not changed".
I am not taking this from you — it is your proposal and your PR. But when it lands I will extend the existing watcher to verify it: assert every served group has non-null caps, and alert when one goes null again. That is the part that keeps it fixed, and it is squarely mine.
Checking in on this, since Bo asked whether it is ready to go.
**Where it stands** — measured against the live gateway just now, via `/model_group/info` (11 entries):
- `max_input_tokens` null: **11/11**
- `max_output_tokens` null: **11/11**
- `mode` null: **6/11**
- `supports_function_calling` true: **0/11**
- `supports_vision` true: **0/11**
So nothing has changed since the measurements above: the fields are still simply absent, which is why clients substitute their own numbers for us rather than reading ours.
**On the proposal** — the shape is right, and the split you suggest is the correct one: pull from upstreams that expose it (Tinfoil, Regolo), keep a manual file for GreenPT. Two things I can hand over so you do not have to rediscover them:
1. **Read Tinfoil the way our traffic does.** Our model-info watcher authenticates to Tinfoil through the in-TEE proxy inside the gateway container, not a side door. If the sync reads Tinfoil directly, it can report a model list that does not match what we actually serve.
2. **Key the GreenPT file on `model_group`, not `model`.** Our spend logs carry the upstream name (`openai/deepseek-v4.1-flash`) while everything member-facing is keyed on our alias (`greenpt/deepseek-v4.1-flash`). Matching on the wrong one fails *silently* — it just matches nothing. I lost an afternoon to exactly that mistake in a different script.
**Bar for "done", in my view:** every served group reports real caps and capabilities, and a re-run after an upstream change needs no hand editing. Pricing stays as-is, per your "not changed".
I am not taking this from you — it is your proposal and your PR. But when it lands I will extend the existing watcher to *verify* it: assert every served group has non-null caps, and alert when one goes null again. That is the part that keeps it fixed, and it is squarely mine.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Problems.
gateway.inference.coop/v1/models/or/model_group/info/is sometimes incomplete or incorrect (see appendix). For example: context length, capabilities.Proposal.
Not changed. How pricing information is set.
--> Thoughts? If it sounds good, I'll start working on a PR for this.
Appendix
Warning: LLM-researched and generated.
1) Missing from LiteLLM discovery
max_input_tokens,max_output_tokensarenullon 11/11 groups in both/model_group/infoand/v2/model/infogpt-oss-120b; both Regolo rows fall to the generic 128K context / 32K output defaults (apertus-70bactually serves 65K,qwen3.5-122b262K)supported_reasoning_efforts: nullon 11/11minimal…xhigh;glm-5.3minimal…high(xhigh→ 400);gpt-oss-120blow…high(minimal→ 400);apertus-70baccepts every value includingxhigh.off→ 400 everywhere, so thinking cannot be disabled on any groupmode: nullon 6/11 groups — every GreenPT and Tinfoil chat rowmode(chat,image_generation,audio_transcription,audio_speech); the remaining 6 are assumed to be chat (correct today, unverified by the proxy)tinfoil/nomic-embed-text(modeembedding) appears only in/v2/model/info— 12 rows there vs 11 in/model_group/infoand/v1/modelsname/display_namefield on any surface; ids onlyproviders: ["openai"]on every group;owned_by: "openai"on every/v1/modelsrow. The true hosts (api.greenpt.ai,127.0.0.1:3301,api.regolo.ai) exist only in/v2/model/info→litellm_params.api_base, on the surface OMP never reachesprovidersto choose the wire and therefore assignsopenai-responsesto all 8 discovered chat rows. It works, but the field describes the LiteLLM adapter, not the backendtpm,rpm,itpm,otpm,health_status,health_response_time,health_checked_atarenullon 11/11*_above_*k_tokens,*_flex,*_priority,*_batchescost field isnullcache_creation_input_token_costisnullon 11/11; onlycache_read_input_token_costis populatedcacheWriteestimate is 0 for every modelar_male,casual_female,casual_male,cheerful_female,de_female, …,neutral_male— surface only inside a 400 body), no output-format list, no pixel or duration caps. TTS cost is entirelynull(tinfoil/voxtral-tts: both input and output null); the image row carries a per-pixel price (1.5e-08) that OMP's cost model cannot expressspeechrole has no way to select a valid voicedeepseek-v4.1-flash(GreenPT) anddeepseek-v4-1-flash(Tinfoil)2) Wrong in LiteLLM discovery
Capability flags as reported (
falsefor all four, all models) versus what the endpoint does.Probe: a 64×64 red PNG for vision, a
get_weatherfunction for tool calling, a two-city requestwith
parallel_tool_calls: true, and a plain17*23prompt with noreasoning_effortparameter for reasoning.
greenpt/deepseek-v4.1-flashreasoning_contentpresenttinfoil/deepseek-v4-1-flashreasoning_contentpresentgreenpt/glm-5.3reasoning_contentpresenttinfoil/glm-5.3reasoning_contentpresentgreenpt/glm-5.3-flashreasoning_contentpresenttinfoil/gpt-oss-120breasoning_contentpresentregolo/qwen3.5-122bregolo/apertus-70bsupports_function_calling: falseon 11/11 — wrong for at least 8. Every chat model returnsa valid
tool_callspayload. OMP's tool use works only because it advertises tools regardless;the metadata asserts the opposite.
supports_parallel_function_calling: falseon 11/11 — wrong for 5 of 5 measured. One requestasking for Paris and Tokyo returned two
tool_callson both DeepSeek rows, bothglm-5.3rows,and
glm-5.3-flash.supports_vision: falseon 11/11 — wrong for 5 of 8 chat models.greenpt/deepseek-v4.1-flash,tinfoil/deepseek-v4-1-flash,greenpt/glm-5.3-flash,regolo/qwen3.5-122bandregolo/apertus-70ball identify the red square. The flag is correctonly for
tinfoil/glm-5.3(hard 400).greenpt/glm-5.3is effectively text-only (200, imagedropped). Note: the cooperative's own published note lists
greenpt/glm-5.3-flashasvision: false; that is also wrong.supports_reasoning: falseon 11/11 — wrong for 6 of 8. With noreasoning_effortsupplied,both DeepSeek rows, both
glm-5.3rows,glm-5.3-flashandgpt-oss-120ball return non-emptyreasoning_content, i.e. thinking is on by default. Only the two Regolo rows are genuinelynon-thinking.
supported_openai_paramsis byte-identical across all 11 groups. The same 32-entry chat list(
tools,tool_choice,logprobs,audio,prediction,service_tier,prompt_cache_retention, …) is attached totinfoil/voxtral-tts,tinfoil/whisper-large-v3-turboandregolo/qwen-image, which are not chat endpoints. On fourchat models the list holds up under spot checks (
n=2,logprobs,storeall accepted), so thedefect is structural rather than per-model.
/v1/modelsis false on two fields for every row.created: 1677610602(2023-02-28) for all11 models, and
owned_by: "openai"for all 11 — Tinfoil, GreenPT and Regolo models are reportedas OpenAI's. Every OpenAI-SDK consumer and OMP's generic
openai-models-listpath inherits both.greenpt/glm-5.3("I don't see an imageattached") and
tinfoil/gpt-oss-120b("I can't view images") return 200 with the image discarded.Capability probing by HTTP status therefore yields false positives; only checking the answer text
reveals it. (Proxy behaviour, not a discovery field — but it defeats discovery-by-probe.)
Thanks Bo — I reproduced all of it against the live gateway just now, and I think the diagnosis points somewhere a bit different than the proposal does. Numbers from
gateway.inference.coop(LiteLLM 1.103.2, admin key, 2026-10-26):/v1/models: 11 rows,owned_by: "openai"andcreated: 1677610602on every one./model_group/info:max_input_tokens,max_output_tokens,supported_reasoning_efforts,display_namenull on 11/11;supports_function_calling,supports_vision,supports_reasoningfalse on 11/11;modenull on 6/11 (present: chat x2, image_generation, audio_transcription, audio_speech)./v2/model/info: 12 rows against 11 elsewhere.Root cause: our config declares only pricing
Every entry in this repo's
config.yamlhas amodel_info:block, and those blocks containinput_cost_per_token,output_cost_per_token,cache_read_input_token_costand, on the image row,input_cost_per_pixel— pricing and nothing else.modeappears on five entries only.LiteLLM serves what it is told. With no
max_input_tokens, nomode, nosupports_*, and no registry entry for custom vendor names likegreenpt/deepseek-v4.1-flash, nulls andfalseare the honest answer. So the missing fields in section 1 — and much of section 2 — are a declaration gap in our own config, closable now without any upstream integration. The auto-pull you propose is the right hygiene for keeping the values fresh afterwards; it isn't the fix for what is absent today.Three cautions before the PR
model_infoblock as the discovery fields. LiteLLM computes member spend, allowance consumption and budget caps from them, so a generated file that rewritesmodel_infowholesale would silently reprice every model and drift every member's budget. Keep the generator strictly additive — discovery fields only, never a cost key — and add a test asserting the pricing keys are unchanged before/after generation. That test is the thing that makes this safe to automate.greenpt/glm-5.3andtinfoil/gpt-oss-120breturn 200 with the image quietly discarded. So the shape is auto-pull for what upstreams publish plus a maintained empirical layer for capabilities. That is a larger manual surface than "GreenPT only" implies — worth deciding deliberately rather than discovering halfway.config.yamlmirrors the live deployment, so a new field is only served after the app restarts. That's a member-facing inference blip: batch it, don't slip it in.Two notes
supports_function_calling: falseis served while tool calls return normally — 6/6 modes pass ongreenpt/glm-5.3-flash, with the call arriving intool_callsand an emptycontent.greenpt/glm-5.3-flashisvision: false; your probe contradicts it. I'll correct the docs either way.Suggested split
config.yamlnow — unblocks the client, no automation, and the pricing invariant is untouched by construction.Happy to review the PR when it lands, and I'll keep an eye on this thread.
We could declare the missing fields now, but it's a one-time fix. We'd need to manually update config.yaml any time an upstream provider changes what models they offer or we add a new provider.
Checking in on this, since Bo asked whether it is ready to go.
Where it stands — measured against the live gateway just now, via
/model_group/info(11 entries):max_input_tokensnull: 11/11max_output_tokensnull: 11/11modenull: 6/11supports_function_callingtrue: 0/11supports_visiontrue: 0/11So nothing has changed since the measurements above: the fields are still simply absent, which is why clients substitute their own numbers for us rather than reading ours.
On the proposal — the shape is right, and the split you suggest is the correct one: pull from upstreams that expose it (Tinfoil, Regolo), keep a manual file for GreenPT. Two things I can hand over so you do not have to rediscover them:
Read Tinfoil the way our traffic does. Our model-info watcher authenticates to Tinfoil through the in-TEE proxy inside the gateway container, not a side door. If the sync reads Tinfoil directly, it can report a model list that does not match what we actually serve.
Key the GreenPT file on
model_group, notmodel. Our spend logs carry the upstream name (openai/deepseek-v4.1-flash) while everything member-facing is keyed on our alias (greenpt/deepseek-v4.1-flash). Matching on the wrong one fails silently — it just matches nothing. I lost an afternoon to exactly that mistake in a different script.Bar for "done", in my view: every served group reports real caps and capabilities, and a re-run after an upstream change needs no hand editing. Pricing stays as-is, per your "not changed".
I am not taking this from you — it is your proposal and your PR. But when it lands I will extend the existing watcher to verify it: assert every served group has non-null caps, and alert when one goes null again. That is the part that keeps it fixed, and it is squarely mine.