mirror of
https://github.com/nexu-io/open-design.git
synced 2026-10-27 09:51:12 +01:00
test(web,docs): cover maxTokens lookup + document sync workflow
- apps/web/src/state/maxTokens.test.ts: six vitest cases pinning the three-tier lookup (override → LiteLLM → fallback) and the effectiveMaxTokens user-override path. Guards against a future sync silently dropping the Anthropic 4.5 entries we rely on. - CONTRIBUTING.md / CONTRIBUTING.zh-CN.md: new "Updating model max_tokens metadata" section pointing future maintainers at scripts/sync-litellm-models.ts and explaining when OVERRIDES is appropriate (it's the rare exception, not the default).
This commit is contained in:
@@ -193,6 +193,28 @@ Bar for merging:
|
||||
|
||||
---
|
||||
|
||||
## Updating model `max_tokens` metadata
|
||||
|
||||
API-mode chat sends `max_tokens` to the upstream provider on every request. The web client picks that number from a three-tier lookup in [`apps/web/src/state/maxTokens.ts`](apps/web/src/state/maxTokens.ts):
|
||||
|
||||
1. The user's explicit override in Settings, if set.
|
||||
2. Otherwise, the per-model default in [`apps/web/src/state/litellm-models.json`](apps/web/src/state/litellm-models.json) — a vendored slice of [BerriAI/litellm][litellm]'s `model_prices_and_context_window.json` (MIT). It covers ~2k chat models across Anthropic, OpenAI, DeepSeek, Groq, Together, Mistral, Gemini, Bedrock, Vertex, OpenRouter, and friends.
|
||||
3. Otherwise, `FALLBACK_MAX_TOKENS = 8192`.
|
||||
|
||||
To pick up a newly-launched model, regenerate the vendored JSON:
|
||||
|
||||
```bash
|
||||
node --experimental-strip-types scripts/sync-litellm-models.ts
|
||||
```
|
||||
|
||||
The script fetches LiteLLM's catalog, filters to `mode: 'chat'` entries, projects each to its `max_output_tokens` (or `max_tokens` fallback), and writes a sorted snapshot. Commit the regenerated `litellm-models.json` alongside whatever PR triggered the refresh.
|
||||
|
||||
The OVERRIDES table in `maxTokens.ts` is for the rare case where LiteLLM is missing or wrong for a model id we actually use — for example, `mimo-v2.5-pro` (LiteLLM only ships MiMo via the `openrouter/xiaomi/...` and `novita/xiaomimimo/...` aliases, neither of which matches the canonical id Xiaomi's direct API uses). Keep it small; everything that LiteLLM gets right belongs upstream.
|
||||
|
||||
[litellm]: https://github.com/BerriAI/litellm
|
||||
|
||||
---
|
||||
|
||||
## Localization maintenance
|
||||
|
||||
German uses formal `Sie` because OD speaks to a mixed audience of solo creators, agencies, and engineering teams; until project feedback shows that an informal `du` voice fits better, formal German is the least surprising default. Locale PRs should translate UI chrome, core docs, and display-only gallery metadata in `apps/web/src/i18n/content.ts`, but should not translate `skills/`, `design-systems/`, or prompt bodies that agents execute. Those source prompts are maintained as workflow inputs, and keeping one source language avoids multiplying prompt QA across locales. When adding or renaming a skill, design system, or prompt template, update the German display metadata and run `pnpm --filter @open-design/web test`; `content.test.ts` fails if German display coverage drifts. Daemon errors, export filenames, and agent-generated artifact text are known limitations unless a PR explicitly scopes them.
|
||||
|
||||
@@ -192,6 +192,28 @@ design-systems/your-brand/
|
||||
|
||||
---
|
||||
|
||||
## 更新模型 `max_tokens` 元数据
|
||||
|
||||
API 模式下每次请求都会带 `max_tokens` 给上游。Web 端通过 [`apps/web/src/state/maxTokens.ts`](apps/web/src/state/maxTokens.ts) 的三层 lookup 决定这个数字:
|
||||
|
||||
1. 用户在 Settings 里手填的覆盖值(如果有)。
|
||||
2. 否则用 [`apps/web/src/state/litellm-models.json`](apps/web/src/state/litellm-models.json) 里的 per-model 默认 —— 这是从 [BerriAI/litellm][litellm] 的 `model_prices_and_context_window.json`(MIT)摘的一份切片,覆盖约 2000 个 chat 模型,包括 Anthropic、OpenAI、DeepSeek、Groq、Together、Mistral、Gemini、Bedrock、Vertex、OpenRouter 等。
|
||||
3. 都 miss 就走 `FALLBACK_MAX_TOKENS = 8192`。
|
||||
|
||||
新模型上线想吃到默认值,重新生成 vendored JSON:
|
||||
|
||||
```bash
|
||||
node --experimental-strip-types scripts/sync-litellm-models.ts
|
||||
```
|
||||
|
||||
脚本会拉 LiteLLM 的最新 catalog、过滤 `mode: 'chat'`、把每条投影到 `max_output_tokens`(缺失时 fallback 到 `max_tokens`),写成排好序的快照。把重新生成的 `litellm-models.json` 跟着触发它的 PR 一起提。
|
||||
|
||||
`maxTokens.ts` 里的 OVERRIDES 表只用于 LiteLLM 没收 / 收错的 model id —— 比如 `mimo-v2.5-pro`(LiteLLM 只收了 `openrouter/xiaomi/...` 和 `novita/xiaomimimo/...` 两个 alias,model id 跟小米直接 API 用的不一样)。表要保持小:凡是 LiteLLM 已经对的,**不要**抄进来。
|
||||
|
||||
[litellm]: https://github.com/BerriAI/litellm
|
||||
|
||||
---
|
||||
|
||||
## 代码风格
|
||||
|
||||
格式我们不抠(保存时跑 Prettier 就行),但有两条不能让 —— 因为它们出现在提示词栈和用户可见的 API 里:
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import litellmData from './litellm-models.json';
|
||||
import {
|
||||
effectiveMaxTokens,
|
||||
FALLBACK_MAX_TOKENS,
|
||||
modelMaxTokensDefault,
|
||||
} from './maxTokens';
|
||||
|
||||
describe('modelMaxTokensDefault', () => {
|
||||
it('falls through to LiteLLM data for canonical Anthropic ids', () => {
|
||||
// 64k for the 4.5 line is the upstream value; this guards against the
|
||||
// sync script silently dropping or rewriting these entries.
|
||||
expect(modelMaxTokensDefault('claude-sonnet-4-5')).toBe(64000);
|
||||
expect(modelMaxTokensDefault('claude-opus-4-5')).toBe(64000);
|
||||
expect(modelMaxTokensDefault('claude-haiku-4-5')).toBe(64000);
|
||||
});
|
||||
|
||||
it('lets OVERRIDES win over LiteLLM data', () => {
|
||||
// mimo-v2.5-pro is not in LiteLLM, so this asserts the OVERRIDES path
|
||||
// (not the LiteLLM path) supplied the answer.
|
||||
expect((litellmData.models as Record<string, number>)['mimo-v2.5-pro']).toBeUndefined();
|
||||
expect(modelMaxTokensDefault('mimo-v2.5-pro')).toBe(32768);
|
||||
});
|
||||
|
||||
it('returns FALLBACK_MAX_TOKENS for unknown ids', () => {
|
||||
expect(modelMaxTokensDefault('definitely-not-a-real-model-x9z')).toBe(FALLBACK_MAX_TOKENS);
|
||||
expect(FALLBACK_MAX_TOKENS).toBe(8192);
|
||||
});
|
||||
});
|
||||
|
||||
describe('effectiveMaxTokens', () => {
|
||||
it('honors an explicit user override over the model default', () => {
|
||||
expect(effectiveMaxTokens({ maxTokens: 12345, model: 'claude-sonnet-4-5' })).toBe(12345);
|
||||
});
|
||||
|
||||
it('uses the model default when no override is set', () => {
|
||||
expect(effectiveMaxTokens({ model: 'mimo-v2.5-pro' })).toBe(32768);
|
||||
expect(effectiveMaxTokens({ model: 'claude-sonnet-4-5' })).toBe(64000);
|
||||
});
|
||||
|
||||
it('falls back to FALLBACK_MAX_TOKENS for unknown models with no override', () => {
|
||||
expect(effectiveMaxTokens({ model: 'unknown-model' })).toBe(FALLBACK_MAX_TOKENS);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user