LLM对抗服务

一个协调多个大语言模型(LLM)进行对抗讨论的服务,通过模型间的挑战和辩论帮助用户优化想法。

  • by jbo-tech
  • Python
  • 本地部署
  • 更新于
在 GitHub 查看 jbo-tech/llm-sparring

使用说明

对练

让你的法学硕士不同意。

一个MCP服务器,负责协调LLM之间的配对会话。查询多个模型,让它们相互挑战,通过摩擦来强化你的想法。

特征

  • 多供应商:OpenAI、Anthropic、谷歌、OpenRouter、Moonshot(Kimi)、z.ai(GLM)、Ollama、自定义端点
  • 推理模型感知:管理的统一解析器 reasoning_content, reasoning (OpenRouter), <think> 内联(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合作进行自由局部推理

安装

快速设置

./install.sh

这将创建配置目录、中央API密钥存储,并安装依赖项。然后在克劳德代码中注册(步骤4)。

手动设置

1.克隆

git clone https://github.com/you/llm-sparring ~/path/to/llm-sparring
cd ~/path/to/llm-sparring

2.依赖关系

uv sync

uv 如果需要,安装Python 3.11+,创建 .venv/ 并从以下位置安装deps uv.lock. 安装uv 如果缺席。

3.设置API密钥

API密钥从 中央商店 跨项目共享:

mkdir -p ~/.config/llm-provider-keys
cp providers.env.example ~/.config/llm-provider-keys/providers.env
chmod 600 ~/.config/llm-provider-keys/providers.env
$EDITOR ~/.config/llm-provider-keys/providers.env

服务器加载 ~/.config/llm-provider-keys/providers.env 首先,然后 .env 在项目根目录中作为覆盖(仅适用于项目特定的值)。只填写您实际使用的提供者——服务器会跳过键为空的模型。

如果你已经有了 ~/.config/llm-provider-keys/providers.env 从另一个项目(jobset&match-v2、claude-code-x)中,您就完成了——不需要额外的密钥设置。

3b。配置模型

mkdir -p ~/.config/mcp/llm-sparring
cp config.yaml ~/.config/mcp/llm-sparring/config.yaml
$EDITOR ~/.config/mcp/llm-sparring/config.yaml

4.添加到克劳德代码

适用于克劳德桌面 (~/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| <think> 内联显示在内容中| |标准模型(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
moonshot MOONSHOT_API_KEY OpenAI同胞 Kimi
zai ZAI_API_KEY OpenAI兼容性 GLM(z.ai)
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种型号,已售出 文学硕士). 级联分辨率:覆盖 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 <path> 另一条路。

脚本

三个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 <name> 所有 enabled 仅测试一个模型(逻辑名称 config.yaml)
--prompt "..." "Dis bonjour en une phrase." 提示自定义(用于测试在prod中失败的情况)
--max-tokens <N> 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 "<le prompt qui a foiré>"

读这行 Diagnostic 然后是块 Détails : content_len, reasoning_len, finish_reason, message_keys 指示文本经过的位置。一个 HTTP 401 对于所有值,表示未加载提供程序的API密钥-检查 ~/.config/llm-provider-keys/providers.env (ou .env 本地si覆盖)。

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 <name> --json | jq '.[0].raw.choices[0].message | keys'

信号映射

症状 检查列 操作
content_len=0 + reasoning_len>0 只讲道理的回应 蒙特尔 default_max_tokens 对于该模型(建议扫描)
finish_reason=length + content_len<50 思考代币耗尽预算 同上-扫描 2000,4000,8000
finish_reason=content_filter 审核提供程序 重新格式化提示符
HTTP 400 max_tokens unsupported GPT-5/O系列124已自动管理(助手 _max_tokens_param)
<think> 在 content_preview 推理模型本地 自动条带,检查是否存在 </think>
HTTP 401/403 API密钥丢失或无效 ~/.config/llm-provider-keys/providers.env
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 ~/.config/llm-provider-keys/providers.env
  • Si覆盖本地: cat .env 在repo
  • echo $OPENAI_API_KEY 在终端中证明不了什么:MCP在 由Claude Code启动的子进程,而不是在shell中。
  • 检查配置: cat ~/.config/mcp/llm-sparring/config.yaml
  • 必须至少有一个模型 enabled: true
  • 更改键或配置后,重新启动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 <name> --sweep 500,1000,2000,4000,8000 (见“诊断提供者”一节)。然后提高 default_max_tokens 对于该模型 config.yaml.
  2. 冠军推理失误 -志浦把答案放在 reasoning_content,OpenRouter in reasoning,phi-4本地丹 <think> 内联。解析器自动处理这三个;如果它坏了,启动 scripts/probe_providers.py --model <name> --json 浇注确认格式。
  3. 断路器开关 -连续3次错误后,将模型放入冰箱5分钟。检查 get_models() → champ breaker计数器在服务器首次成功或重新启动时重置为零。

发展

uv run server.py
LOG_LEVEL=DEBUG uv run server.py

许可证

麻省理工学院