练习拳击
让你的法学硕士不同意。
一个MCP服务器,负责协调LLM之间的配对会话。查询多个模型,让它们相互挑战,通过摩擦来强化你的想法。
特性
- 多供应商:OpenAI、Anthropic、谷歌、OpenRouter、Ollama、自定义端点
- 推理模型感知:管理的统一解析器
reasoning_content,reasoning(OpenRouter), `内联(phi-4、QwQ),max_completion_tokens(GPT-5/o系列),finish_reason` 暴露于呼叫者 - 预算控制:会话、每日、每次请求限制+仅附加日志
session_id用于事后整合(按模型、按拳击) - 断路器:在N个连续错误(内容空、HTTP 5xx、超时)后自动禁用提供程序
- 简单图元:
ask_model,ask_all,challenge,get_models,get_lenses,get_usage,estimate_cost - 挑战镜片:10个观点(devil_advocate、cynical_dev、安全性、成本、用户、规模、简单性、天真、实用主义、钢铁侠)
- 诊断工具:
scripts/probe_providers.py(原始响应转储),scripts/consolidate_usage.py(JSONL聚合) - 本地优先:与Ollama合作进行自由局部推理
安装
1.克隆/复制
mkdir -p ~/.claude/mcp/llm-sparring
cp -r . ~/.claude/mcp/llm-sparring/
# Or clone from git
# git clone https://github.com/you/llm-sparring ~/.claude/mcp/llm-sparring2.依赖关系
cd ~/.claude/mcp/llm-sparring
uv syncuv 如果需要,管理Python 3.11的安装,创建 .venv/ 并从安装DEP uv.lock. 安装程序uv 我缺席。
3.配置模型
mkdir -p ~/.config/mcp/llm-sparring
cp config.yaml ~/.config/mcp/llm-sparring/config.yaml
nano ~/.config/mcp/llm-sparring/config.yaml4.设置API密钥
按优先顺序排列的三个选项:
A、文件 .env 在服务器文件夹中(推荐)
服务器自动加载 ~/.claude/mcp/llm-sparring/.env 在开始时。 关键仍然在MCP范围内,不会污染全球环境,并且 不依赖于运行Claude代码的shell。
cat > ~/.claude/mcp/llm-sparring/.env ⚠️ 不简单 `export` 在开放终端中是不够的:Claude代码
> 必须从环境中具有这些变量的shell重新启动。
### 5.添加到克劳德代码
**适用于克劳德桌面** (`~/Library/Application Support/Claude/claude_desktop_config.json`):
{ "mcpServers": { "sparring": { "command": "uv", "args": [ "--directory", "/Users/YOU/.claude/mcp/llm-sparring", "run", "server.py" ], "env": { "OPENAI_API_KEY": "sk-...", "GOOGLE_API_KEY": "...", "OPENROUTER_API_KEY": "sk-or-..." } } } }
**适用于Claude Code CLI**:
claude mcp add sparring -- uv --directory ~/.claude/mcp/llm-sparring run server.py
## 工具
### `ask_model`
查询特定模型。
ask_model( model: "gpt-4o", question: "What are the pros/cons of microservices?", context: "Building a SaaS product", # optional max_tokens: 1000, # optional — voir cascade ci-dessous session_id: "20260423-143012" # optional — pour regrouper la facturation )
`max_tokens` (级联,从强到弱):显式arg> `model.default_max_tokens` (配置)> `settings.default_max_tokens` > 2000.提出倒推推理模型(Gemini 3、Qwen、GLM、o系列、phi-4局部)。
### `ask_all`
并行查询所有启用的模型。每个模型都应用自己的级联 `max_tokens`.
ask_all( question: "What database should I use for time-series data?", context: "IoT project with 10M events/day", # optional max_tokens: 1000, # optional — override uniforme session_id: "20260423-143012" # optional )
返回一个 `meta` 按模型 `finish_reason`, `reasoning_len`, `inline_thinking_len` 相关时+a `models_with_errors` 在管弦乐队的最高级别。
### `challenge`
让一个模型评论一个响应(来自另一个模型、文件、片段——任何文本)。
这是拳击的核心。
challenge( challenger_model: "gemini-flash", original_question: "Best database for time-series?", target_response: "PostgreSQL with TimescaleDB because...", target_source: "gpt-4o", # optional: model name, file name, etc. lens: "devil_advocate", # optional: see get_lenses. null = natural critique language: "fr", # optional: "fr" (default) or "en" max_tokens: 2000, # optional — même cascade que ask_model session_id: "20260423-143012" # optional )
### `get_models`
列出所有已配置的型号及其状态和定价。
get_models()
{ "models": [ { "name": "gpt-4o", "provider": "openai", "model_id": "gpt-4o", "status": "available", "enabled": true, "pricing": {"input": 2.50, "output": 10.00}, "breaker": {"errors": 0, "disabled": false} }, { "name": "or-zai-glm", "provider": "openrouter", "model_id": "z-ai/glm-4.6", "status": "available", "enabled": true, "pricing": {"input": 0.5, "output": 2.0}, "breaker": { "errors": 3, "disabled": true, "disabled_until": 1745421012.5, "last_error": "truncated during reasoning (finish=length, reasoning_len=693)" } } ] }
冠军 `breaker` 反映断路器电路的状态: `disabled: true` 表示模型失败≥ `threshold` 连续两次,并放入冰箱,直到 `disabled_until`.
### `get_lenses`
列出可用的挑战镜片及其说明。
get_lenses()
返回10个内置镜头(`devil_advocate`, `steelman`, `pragmatist`, `cynical_dev`, `security`, `cost`, `user`, `scale`, `simplicity`, `naive`)加上默认值和传递说明 `lens: null` 一种没有个性的自然批判。
### `get_usage`
获取当前预算使用情况。无参数:totaux会话(内存中)/每日/每月。和 `session_id` :通过JSONL日志中的聚合模型崩溃。
get_usage() get_usage(session_id: "20260423-143012")
{ "session": {"cost": 0.05, "requests": 3, "limit": 1.00, "remaining": 0.95}, "daily": {"cost": 0.25, "requests": 12, "limit": 5.00, "remaining": 4.75}, "monthly": {"cost": 3.50, "requests": 156}, "sparring_session": { "session_id": "20260423-143012", "total_cost": 0.0172, "requests": 3, "errors": 1, "by_model": { "gpt-4o": {"cost": 0.017, "requests": 2, "errors": 0, "input_tokens": 400, "output_tokens": 600}, "or-zai-glm": {"cost": 0.0002, "requests": 1, "errors": 1, "input_tokens": 100, "output_tokens": 0} } } }
### `estimate_cost`
在提出请求之前估算成本。
estimate_cost( models: ["gpt-4o", "gemini-flash", "llama-local"], input_tokens: 500, output_tokens: 1000 )
## 配置
### 模型
models: - name: "gpt-4o" provider: "openai" model_id: "gpt-4o" enabled: true
- name: "or-zai-glm" provider: "openrouter" model_id: "z-ai/glm-4.6" default_max_tokens: 4000 # reasoning model — besoin de marge pour thinking enabled: true
- name: "llama-local" provider: "ollama" model_id: "llama3.2" base_url: "http://localhost:11434" enabled: true
**`default_max_tokens`** (可选)-此模板的默认代币预算。对于在生成文本之前在思考中消耗100-600个不可见令牌的推理模型:
|建议的模型|为什么|
|--------|---------|----------|
|Gemini 2.5/3 Flash/Pro|6000|~190个即使在琐碎提示上也看不见的思考令牌|
|GPT-5/O系列
|GLM-4.6/Qwen思考|4000|200-600响应令牌|
|phi-4/QwQ/DeepSeek-R1本地|8000| `` 内联显示在内容中|
|标准模型(GPT-4O、Mistral、Claude)|-(回溯设置)|无隐形思维|
### 设置
settings: default_timeout: 30 # secondes max_parallel: 3 # concurrence ask_all default_max_tokens: 2000 # fallback global — voir cascade plus haut
circuit_breaker: # optionnel, défauts indiqués threshold: 3 # erreurs consécutives avant désactivation cooldown_seconds: 300 # durée de mise au frigo
### 提供商
|提供者|环境变量|类型|注释|
|----------|---------|------|-------|
| `openai` | `OPENAI_API_KEY` |OpenAI兼容| GPT-4、GPT-4o|
| `anthropic` | `ANTHROPIC_API_KEY` |OpenAI compat|Claude(终点beta, **无提示缓存**) |
| `google` | `GOOGLE_API_KEY` |OpenAI compat|Gemini(终点测试版)|
| `openrouter` | `OPENROUTER_API_KEY` |OpenAI兼容性| **推荐**:400多种型号|
| `mistral` | `MISTRAL_API_KEY` |OpenAI compat | Mistral大、小|
| `deepseek` | `DEEPSEEK_API_KEY` |OpenAI compat | DeepSeek聊天,推理|
| `groq` | `GROQ_API_KEY` |OpenAI compat |非常快速的推理|
| `together` | `TOGETHER_API_KEY` |OpenAI compat |开源模型|
| `xai` | `XAI_API_KEY` |OpenAI同胞| Grok|
| `custom` |自定义|OpenAI compat|任何兼容的端点|
| `ollama` |--|Ollama|本地型号,免费(专用处理器)|
在中添加与OpenAI兼容的提供程序 `providers.py`:
"newprovider": { "type": "openai_compatible", "base_url": "https://api.newprovider.com/v1", "api_key_env": "NEWPROVIDER_API_KEY", },
### 预算
budget: confirm_threshold: 0.10 # Confirm if request > $0.10 session_limit: 1.00 # Max per session daily_limit: 5.00 # Max per day tracking_file: "~/.config/mcp/llm-sparring/usage.json" journal_file: "~/.config/mcp/llm-sparring/usage.jsonl" # optionnel, défaut à côté de tracking_file
两个文件并行维护:
- **`usage.json`** -每个会话(内存)、日期和月份的汇总总数。实时预算检查的来源。
- **`usage.jsonl`** -仅附加日志,每个请求一行: `ts`, `session_id`, `tool`, `model`, `provider`, `input_tokens`, `output_tokens`, `cost`, `error`, `finish_reason`, `reasoning_len`, `inline_thinking_len`, `duration_ms`.反向审计和会话整合的真实来源。
### 定价
定价数据来自 `pricing.json` (~2600种型号,已售出 [轻量级LLM](https://github.com/BerriAI/litellm)).
级联分辨率:覆盖 `config.yaml` → 查找精确→ `{provider}/{model_id}` →本地规则(ollama/localhost→0)→带警告的保守回退。
建议季度刷新:
uv run scripts/refresh_pricing.py
覆盖JSON中缺少的模板或强制定价,在 `config.yaml` :
pricing: my-custom-model: input: 0.50 # per 1M tokens output: 1.50
## 监控和计费
### 按会话聚合
管弦乐手(`/sparring`)生成 `session_id` 通过Sparring独特,并在每次工具调用时传播。巩固的两种方法:
**来自Claude代码(在线)** :
get_usage(session_id: "20260423-143012")
→返回 `sparring_session.by_model` 每个模型有成本、错误和令牌。
**离线(脚本)** :
Détail d'une session
uv run scripts/consolidate_usage.py --session 20260423-143012
Top sessions de la journée par coût
uv run scripts/consolidate_usage.py --day 2026-04-23 --by-session
Breakdown par outil (ask_model / ask_all / challenge)
uv run scripts/consolidate_usage.py --by-tool
Dump JSON pour traitement externe
uv run scripts/consolidate_usage.py --json | jq '.by_model'
默认情况下,床 `~/.config/mcp/llm-sparring/usage.jsonl` ; `--file
` 另一条路。
## 脚本
三个CLI实用程序 `scripts/`所有通过发射 `uv run` :
|脚本|角色|何时使用|
|--------|------|------------------|
| `probe_providers.py` |向一个或所有模板发送一个普通提示,转储返回空、截断、错误或 `circuit breaker open` |
| `consolidate_usage.py` | 添加 `usage.jsonl` 每个会话/天/模型/工具|回溯会话审核,按成本划分的顶级会话|
| `refresh_pricing.py` |重新下载 `pricing.json` 自Litellm |季度或JSON中缺少的模板以来|
## 诊断提供者
系统返回空、截断或“电路断路器打开”的模型? `scripts/probe_providers.py` 发送一个简单的提示并转储提供程序的原始HTTP响应,而不经过内部解析器,因此不隐藏内容消失的位置。
### 旗帜
|标志|缺陷|效果|
|------|--------|-------|
| `--model ` |所有 `enabled` |仅测试一个模型(逻辑名称 `config.yaml`) |
| `--prompt "..."` | `"Dis bonjour en une phrase."` |提示自定义(用于测试在prod中失败的情况)|
| `--max-tokens ` | `200` |预算代币为您带来回报|
| `--sweep "500,1000,2000,4000"` |-|多个循环 `max_tokens` 以识别有用的阈值。需要 `--model`. |
| `--json` |用于外部分析的原始JSON转储(每个模型的原始)|
### 工作流
**1.全球分类** -所有型号均已启用,人为格式:
uv run scripts/probe_providers.py
**2.测试特定模型** -例如,在deepseek错误之后:
uv run scripts/probe_providers.py --model deepseek-v4-flash uv run scripts/probe_providers.py --model deepseek-v4-flash --max-tokens 4000 uv run scripts/probe_providers.py --model deepseek-v4-flash --prompt ""
读这行 `Diagnostic` 然后是块 `Détails` : `content_len`, `reasoning_len`, `finish_reason`, `message_keys` 指示文本经过的位置。一 `HTTP 401` 对于所有值,表示未加载提供程序的API密钥-检查 `.env`.
**3.找到 `max_tokens` 最低有用** -例如,对于返回的Kimi `null` :
uv run scripts/probe_providers.py --model or-kimi-k2 --sweep 500,1000,2000,4000,8000
分拣类型:
max_tokens content reasoning finish diagnostic 500 0 487 length ⚠️ content vide MAIS reasoning_content présent (487 chars) — thinking model 1000 0 923 length ⚠️ tronqué avant tout content (finish=length) — augmenter max_tokens 2000 42 1450 stop OK — 42 chars (finish=stop) 4000 180 1502 stop OK — 180 chars (finish=stop) 8000 180 1502 stop OK — 180 chars (finish=stop)
→ Seuil utile détecté : 2000 (premier max_tokens avec content non-vide et non tronqué)
然后将阈值传播到 `~/.config/mcp/llm-sparring/config.yaml` :
- name: "or-kimi-k2"
provider: "openrouter" model_id: "moonshotai/kimi-k2" default_max_tokens: 2000 # ou 4000 pour marge de sécurité enabled: true
**4.原始JSON浇注分析精细** -格式未知时有用:
uv run scripts/probe_providers.py --model --json | jq '.[0].raw.choices[0].message | keys'
### 信号映射
|症状|检查列|操作|
|----------|-------------------|--------|
| `content_len=0` + `reasoning_len>0` |只讲道理的回应|蒙特尔 `default_max_tokens` 对于该模型(建议扫描)|
| `finish_reason=length` + `content_len` 在 `content_preview` |推理模型本地|自动条带,检查是否存在 `` |
| `HTTP 401/403` | API密钥丢失或无效 `.env` ou集团 `env` MCP|
| `ERREUR: TimeoutException` |端点速度慢/过载 `settings.default_timeout` |
## 使用/陪练
此MCP旨在与 `/sparring` 克劳德代码中的命令:
/sparring Should I use microservices or monolith for my startup?
→ Claude orchestrates: 1. Framing with you 2. ask_all() to gather perspectives 3. challenge() to have models critique each other 4. Synthesis and recommendation
## 测试
### 1.验证服务器是否启动
cd ~/.claude/mcp/llm-sparring uv run server.py
Ctrl+C to stop
在调试模式下了解更多详细信息:
LOG_LEVEL=DEBUG uv run server.py
### 2.检查型号配置
在Claude Code中,运行:
get_models()
验证您启用的模型是否显示为 `status: "available"`.
### 3.测试单个模型
ask_model(model: "gpt-4o", question: "Say hello in one word")
从便宜/快速的型号开始(例如。 `gpt-4o-mini`, `gemini-flash`或当地Ollama模型)。
### 4.测试并行查询
ask_all(question: "What is 2+2?")
所有启用的模型都应该响应。
### 5.测试挑战
challenge( challenger_model: "gemini-flash", original_question: "What is the best programming language?", target_response: "Python because it's simple", target_source: "gpt-4o", lens: "devil_advocate" )
### 6.检查预算跟踪
get_usage()
验证会话和每日费用是否反映了您的测试查询。
### 7.测试/陪练
/sparring Should I use SQLite or PostgreSQL for a side project?
这将运行完整的编排流程(框架、ask_all、挑战、合成)。
## 故障排除
### “没有可用型号”/ `status: "unavailable"`
- 检查服务器是否看到密钥: `cat ~/.claude/mcp/llm-sparring/.env`
(选项A)整体 `env` 在 `~/.claude.json` (选项B)。
- `echo $OPENAI_API_KEY` 在终端中证明不了什么:MCP在
由Claude Code启动的子进程,而不是在shell中。
- 检查配置: `cat ~/.config/mcp/llm-sparring/config.yaml`
- 必须至少有一个模型 `enabled: true`
- 修改后 `.env` 欧德 `~/.claude.json`,重新启动Claude代码。
### “超时查询模型”
- 增加 `default_timeout` 在配置中
- 对于Ollama: `ollama serve`
- 本地模型需要时间来加载第一个查询
### “超出预算”
- 与核对 `get_usage`
- 调整配置中的限制
- 等待每日重置(午夜)
### 答案为空、截断或 `circuit breaker open`
推理模型的三个常见原因(双子座3、GPT-5、O系列、GLM、Qwen思维、Phi-4本地):
1. **`max_tokens` 托普·巴斯** -无形思维令牌在模型生成文本之前消耗预算。要查找有用的阈值: `uv run scripts/probe_providers.py --model --sweep 500,1000,2000,4000,8000` (见“诊断提供者”一节)。然后提高 `default_max_tokens` 对于该模型 `config.yaml`.
1. **冠军推理失误** -志浦把答案放在 `reasoning_content`,OpenRouter in `reasoning`,phi-4本地丹 `` 内联。解析器自动处理这三个;如果它坏了,启动 `scripts/probe_providers.py --model --json` 浇注确认格式。
1. **断路器开关** -连续3次错误后,将模型放入冰箱5分钟。检查 `get_models()` → champ `breaker`计数器在服务器首次成功或重新启动时重置为零。
## 发展
uv run server.py LOG_LEVEL=DEBUG uv run server.py
## 许可证
麻省理工学院