高级 10 分钟多模型

LLM Council:让多个模型进行协商 locaux

LLM Council 是 Andrej Karpathy 于 2025 年底发布的一个小型项目:它不是把问题发送给单个模型,而是提交给多个模型;随后,每个模型在不知道回答作者身份的情况下,对其他模型的回答进行排名,再由一个“主席”模型撰写最终综合结果。该代码仓库面向通过 OpenRouter 提供服务的云端模型,但其核心模式只有约一百行代码,也可迁移到由 Ollama 提供服务的本地模型。本文介绍这三个步骤,展示如何在个人电脑上复现,详细说明其内存和时间成本,以及为什么不能假定委员会的回答一定比优秀的单模型更好。

作者: Léa B.·更新于 2026-10-12·已在 Windows、macOS 和 Linux 上测试

#LLM Council:三步实现核心模式

该仓库描述了一个固定的三阶段流程。每个阶段都是一次普通的模型调用,不使用工具或记忆:唯一的技巧在于,第一阶段的输出如何为后两个阶段提供输入。

  1. 01
    初步意见
    用户的问题会原样分别发送给委员会的每位成员。每个成员在看不到其他回答的情况下独立作答。在原始界面中,这些回答会按模型分别显示在各个标签页中。
  2. 02
    复核与排名
    随后,每位成员都会收到其他成员的回答(已匿名处理,标记为“回答 A”“回答 B”……),并根据准确性和深度从最好到最差进行排名。匿名处理旨在避免模型偏袒或贬低一个已知的竞争模型。
  3. 03
    主席综合结果
    一名被指定为主席的模型会收到问题、全部回答和所有排名,然后撰写最终的唯一答案呈现给用户。

这一架构并不要求使用不同的模型,但这正是它的全部意义:我们依靠训练方式的多样性,让一个模型的错误能够被另一个模型发现。主席可以是成员之一,也可以是单独的模型,通常选择列表中能力最强的模型。

i
一个探索性项目,而非产品
在公告中,Karpathy 将 LLM Council 介绍为一个周末项目,其大部分代码由编程助手编写,并表示他不打算继续维护。关于模型彼此排名好坏的看法属于定性判断,而且具有时效性。代码仓库没有附带任何量化评估:本文也没有添加。

#Karpathy 的代码仓库包含哪些内容

本地智能体套件

在你的机器上执行操作的智能体:具备智能体能力的 Cline、MCP、n8n + Ollama、本地自动化。

  • 在线空间,终身可用
  • PDF + 文件
  • 终身更新

github.com/karpathy/llm-council 仓库是一个本地 Web 应用,看起来像传统的聊天界面。根据撰写本文时的 README 和配置文件,它由以下部分组成:

一个 Python 后端(FastAPI)
在 backend/ 目录中:包含三个步骤的逻辑、一个连接 OpenRouter API 的 HTTP 客户端,以及将对话以 JSON 文件形式存储在 data/ 目录中的功能。依赖项使用 uv 管理。
React 前端(Vite)
在 frontend/ 目录中:用于显示单独回答、排名和综合结果的界面。
一个配置文件
backend/config.py 以 OpenRouter 标识符的形式列出评议会成员(COUNCIL_MODELS)和主席(CHAIRMAN_MODEL),以及所调用的 API 地址。
一个启动脚本
start.sh 会同时启动后端和前端。文件 .env 中应包含一个 OPENROUTER_API_KEY。

默认配置的模型是封闭的云端模型(发布版本中的 GPT、Gemini、Claude 和 Grok 系列)。这是改成本地版本时需要更改的部分:只要 API 使用 OpenAI 推广的“chat completions”格式,其余代码并不关心是谁在回答。在按照本指南操作前,请重新阅读 README 和 config.py;项目自 2026 年 10 月 12 日以来可能已经发生变化。

#本地版本的前置条件

Ollama 已安装并正常运行
服务器默认监听 http://localhost:11434。使用命令 ollama list 检查你的模型是否已显示。
来自不同系列的三到四个模型
由同一系列模型组成的委员会会失去多样性的价值。例如,可以选择一个 Qwen、一个 Llama、一个 Mistral 和一个 Gemma,并让它们的规模相近。
合适的内存容量
使用 Q4_K_M 时,每个 7B 模型约需 5 GB,每个 14B 模型约需 9 GB。成员不必同时全部驻留内存,但使用体验取决于此(参见成本一节)。
Python 3.10 或更高版本
对于本指南中的最小脚本,需要 requests 包。对于原始仓库,还需要 uv 和 Node.js。
Patience
一轮咨询至少需要成员数量两倍再加一次的生成次数。这不是即时聊天工具。

#第一步:选择委员会和主席

成员的选择需要在多样性和内存之间取得平衡。主席则主要需要良好地处理较长的上下文:它会收到问题、全部回答和所有排名,轻松就可能涉及数千个 token。因此,应选择机器所支持的能力最强的模型担任这一角色,即使它速度较慢也无妨,因为每轮只调用一次。

终端
# Exemple de conseil pour une carte 12 Go (noms à vérifier avec `ollama search` ou sur ollama.com/library)
ollama pull qwen3:8b
ollama pull llama3.1:8b
ollama pull mistral:7b
ollama pull gemma3:12b   # président : plus gros, appelé une seule fois par tour
ollama list

在 12 GB 显卡上,7B 到 8B 的大小适合作为成员:单个模型可以在内存中运行,并为上下文留出余量,而 Ollama 会在它们之间切换。拥有 24 GB 显存或 48 GB 统一内存的 Mac,可以将成员升级到 14B,或同时加载两到三个模型,从而消除重新加载。

→
如果可以,请添加一个推理模型
能够在回答前进行“思考”的成员,往往能带来不同于直接模型的视角。不过,请记得在排名步骤中停用或限制思考阶段,否则这一步会成为整轮中耗时最长的环节。

#步骤 2:为多个模型配置 Ollama

这里有两个 Ollama 设置很重要。第一个是保留在内存中的模型数量,由变量 OLLAMA_MAX_LOADED_MODELS 设置:如果内存允许,让所有成员保持加载状态,可以避免每次从一个成员切换到另一个成员时重新加载。第二个是上下文窗口 num_ctx,在 Ollama 上默认较短:排名步骤和主席综合步骤都需要读取多份完整回答,过小的上下文会静默截断提示词的开头。

终端(Linux,systemd 服务)
sudo systemctl edit ollama
# puis dans le bloc [Service] :
# Environment="OLLAMA_MAX_LOADED_MODELS=2"
# Environment="OLLAMA_KEEP_ALIVE=30m"
sudo systemctl restart ollama

在 macOS 上,可以使用 launchctl setenv 设置相同的变量,然后重新启动应用程序。OLLAMA_MAX_LOADED_MODELS 的值必须与内存容量保持一致:两个采用 Q4 的 8B 模型连同上下文就已经占用大约十几 GB。上下文则可按请求设置(通过 API 的 options.num_ctx 字段),也可以在派生的 Modelfile 中设置;下一节会在每次调用时传入该值。

!
并行处理:默认按顺序执行
原始代码会并行启动各个成员,这对于云端 API 来说很合理。但在本地,同时向四个不同模型发送四个请求,会迫使 Ollama 轮流加载和卸载它们,甚至将部分计算转移到处理器上。除非所有成员都能同时放入内存,否则请依次调用它们。

#第三步:用 Python 实现一个最小委员会

下面的脚本使用 Ollama 的原生 API、无需界面地复现提交的三个步骤。它会在排名前打乱回答顺序,使标签无法泄露作者身份;要求每位成员给出简短排名,然后将全部结果交给主席。请将模型名称调整为您机器上的名称。

council.py
import random
import requests

OLLAMA = "http://localhost:11434/api/chat"
COUNCIL = ["qwen3:8b", "llama3.1:8b", "mistral:7b"]
CHAIRMAN = "gemma3:12b"
NUM_CTX = 16384


def ask(model, prompt):
    r = requests.post(OLLAMA, json={
        "model": model,
        "messages": [{"role": "user", "content": prompt}],
        "stream": False,
        "options": {"num_ctx": NUM_CTX, "temperature": 0.3},
    }, timeout=1800)
    r.raise_for_status()
    return r.json()["message"]["content"].strip()


def council(question):
    # Étape 1 : premières opinions, une par membre, sans se voir
    answers = {m: ask(m, question) for m in COUNCIL}

    # Anonymisation : ordre mélangé, étiquettes A, B, C...
    order = list(COUNCIL)
    random.shuffle(order)
    labels = {m: chr(65 + i) for i, m in enumerate(order)}
    packed = "\n\n".join(
        f"### Réponse {labels[m]}\n{answers[m]}" for m in order
    )

    # Étape 2 : chaque membre classe les réponses (les siennes incluses)
    rankings = {}
    for m in COUNCIL:
        rankings[m] = ask(m,
            f"Question : {question}\n\n{packed}\n\n"
            "Classe ces réponses de la meilleure à la moins bonne selon leur "
            "justesse et leur profondeur. Pour chacune, une ligne de justification. "
            "Termine par la ligne 'CLASSEMENT :' suivie des lettres dans l'ordre.")

    # Étape 3 : le président rédige la synthèse finale
    votes = "\n\n".join(
        f"### Avis du juge {labels[m]}\n{rankings[m]}" for m in COUNCIL
    )
    final = ask(CHAIRMAN,
        f"Question : {question}\n\n{packed}\n\n{votes}\n\n"
        "Tu présides ce conseil. En t'appuyant sur les réponses et les classements, "
        "rédige une réponse finale unique, précise et sans redite. Signale les points "
        "sur lesquels les réponses se contredisent.")
    return answers, labels, rankings, final


if __name__ == "__main__":
    import sys
    q = " ".join(sys.argv[1:]) or "Quelle quantification choisir pour un modèle 14B sur 12 Go de VRAM ?"
    answers, labels, rankings, final = council(q)
    for m in COUNCIL:
        print(f"\n===== {m} (Réponse {labels[m]}) =====\n{answers[m]}")
    for m in COUNCIL:
        print(f"\n===== Classement par {m} =====\n{rankings[m]}")
    print(f"\n===== Synthèse ({CHAIRMAN}) =====\n{final}")
终端
python3 -m venv .venv && source .venv/bin/activate
pip install requests
python council.py "Faut-il un reranker dans un pipeline RAG local ?"

原始代码仓库还更进一步:它会提取每位评审的排名行,为每个回答计算平均名次,并将其显示在标签页旁边。读取每个输出中的“CLASSEMENT:”行,只需几行代码即可添加此功能。但要注意,小模型并不总是遵守这种格式:如果缺少该行,应准备好备用处理逻辑。

→
成员对自己进行评判
在这个脚本和代码仓库中,每位评审都会在其他回答中看到自己的回答,并带有匿名标签。这是有意为之:这样可以观察模型是否认出自己的风格并偏袒自己。如果希望排除自己的回答,请将其从发送给每位评审的内容块中移除;但这样一来,排名所针对的集合就不再相同,平均值也会更难解读。

#第四步:将原始代码仓库适配到 Ollama

如果你想要网页界面,最短路径是使用 Ollama 的 OpenAI 兼容端点,该端点在 http://localhost:11434/v1/chat/completions 提供服务。仓库后端已经以这种格式向 OpenRouter 发送请求:原则上只需在 backend/config.py 中替换 API 地址和模型标识符,然后填写一个虚拟密钥,Ollama 会忽略授权标头。

终端
git clone https://github.com/karpathy/llm-council
cd llm-council
uv sync
cd frontend && npm install && cd ..
echo 'OPENROUTER_API_KEY=ollama' > .env
backend/config.py(需调整的片段,请在你的版本中核对常量名称)
COUNCIL_MODELS = [
    "qwen3:8b",
    "llama3.1:8b",
    "mistral:7b",
]
CHAIRMAN_MODEL = "gemma3:12b"

# Ollama expose une API compatible OpenAI sur /v1
OPENROUTER_API_URL = "http://localhost:11434/v1/chat/completions"
终端
./start.sh
# backend sur le port 8001, interface Vite sur http://localhost:5173 (valeurs du README à vérifier)

还有两点需要手动处理。首先是上下文:兼容 OpenAI 的访问端点没有 num_ctx 字段,因此必须创建使用 Modelfile 固定上下文的模型变体,或者将客户端切换到接受选项的原生 API /api/chat。其次是前面提到的并行性:第一步会同时发起调用;如果您的成员无法全部装入内存,就应改为顺序执行。

终端(固定上下文的变体)
cat > Modelfile.qwen-council <<'EOF'
FROM qwen3:8b
PARAMETER num_ctx 16384
EOF
ollama create qwen3-council -f Modelfile.qwen-council

多个社区分支已经为 Ollama 或其他本地服务器进行了这些修改。在采用其中一个之前,请查看上次提交的日期、与原始仓库相比受影响的文件数量,并阅读调用模型的代码:代码很短,差异就藏在那里。本指南不推荐任何特定分支,因为它们的状态变化太快。


#内存和时间开销

这个计算很简单,开始之前值得先列出来。对于一个包含 N 名成员的咨询,一轮完整流程需要第一阶段 N 次生成、第二阶段 N 次生成,以及主席的一次生成,即 2N + 1 次调用。第二阶段的调用和主席的调用比第一次更难处理,因为提示词包含所有回答:读取提示词的时间会叠加到生成时间上。

三名成员
每个问题生成七次。如果每个回答在您的机器上大约需要半分钟,一轮就会超过三分钟,还不包括重新加载的时间。
最低内存
评议会中最大的模型及其上下文。对于采用 Q4 的 8B 成员和 16 000 个 token 的上下文,一张 12 GB 的显卡可以容纳它,前提是只保留一个已加载的模型。
内存充裕
所有成员同时加载:需要它们大小的总和,再加上每个模型的上下文缓存。三个采用 Q4 的 8B 模型至少需要一块 24 GB 的 GPU 或一台拥有 32 GB 统一内存的 Mac。
Rechargements
当成员无法同时容纳时,每次切换模型都要花费几秒钟从磁盘加载。在一轮中,这种情况最多会发生 2N 次。NVMe SSD 可以让这一成本尚可接受;速度较慢的磁盘则会让它变得令人难受。

这些数量级取决于你的硬件、量化方式和回答长度:本文没有测量每轮耗时,上述数字只是估算规则,并非实测结果。要评估你的具体情况,请分别为每个成员进行一次简单生成计时,然后套用该公式。

#委员会的局限性与偏差

这种模式之所以吸引人,是因为它看起来像同行评审。然而,必须抵制认为它必然比单个优秀模型更准确的想法:该仓库没有提供任何支持这一点的测量结果,而且模型评判者的多种已知偏差也对它不利。

不完善的匿名化
标签隐藏的是名称,而不是风格。模型可能认出自己的写作方式,或认出它所熟悉的某个模型家族的写作方式,并据此进行排序。Karpathy 自己也定性地指出,模型往往更偏好另一个模型的回答,而不是自己的回答:这是观察结果,不是保证。
位置偏差与长度偏差
关于评审模型的研究,例如 Zheng 等人(2023 年)的 MT-Bench 论文,记录了人们偏好先读到的回答以及较长回答的倾向。打乱顺序可以减轻前一种偏差;后一种则很难纠正。
相关错误
在相近语料上训练的模型往往会以相同方式出错。由小模型组成的委员会可能会一致投票支持一个错误答案,而主席也会采纳它。
只剩主席
总结完全取决于主席模型:如果它产生幻觉,评议会也无法改变结果。当主题重要时,请阅读各个单独的回答;这正是原始界面选项卡的意义所在。
小模型,长指令
排名提示词要求很高:需要阅读三或四篇文本、进行判断、说明理由并遵守格式。在 7B 以下,输出会变得不稳定,平均排名也失去意义。
!
没有测量,就没有承诺
如果您想知道评议会是否能改善结果,请构建一组您知道答案的小型问题集,先单独运行最佳模型,再运行评议会,并进行盲测比较。如果不在您自己的案例上进行这项测试,“多个意见胜过一个意见”的直觉就只是一种假设。

#本地 LLM Council 的实际使用场景

当问题少见且重要时,2N + 1 次生成所带来的额外开销是合理的;对于闲聊则不值得。在本地采用这一模式的人中,以下三种情况最为突出。

复核敏感文本或代码
一封合同函件、一套流程或一个关键函数:不同成员会发现不同的问题,主席再将它们整合成一份列表。这里,排名不如意见的多样性重要。
有理有据的技术决策
“是否应迁移这个数据库”“这种导出应使用什么格式”:相互矛盾的回答本身就是一种信息。要求主席明确呈现分歧,而不是将其抹平。
单模型质量控制
将评议会用作评审员,以比较两个提示词版本或同一模型的两种量化版本,让它为您在其他地方生成的输出排名。这是步骤 2 的直接应用,不包含步骤 1。

在所有这些情况下,本地版本真正的优势在于隐私:重新阅读的文档不会离开机器,而原始版本会通过 OpenRouter 将其发送给四个不同的供应商。这使得该模式对处理内部数据的团队很有吸引力,但前提是能够接受数分钟的等待。

#故障排除

排名忽略了一个回答
上下文太短,提示词开头已被截断。请增大 num_ctx(或 Modelfile),并检查 Ollama 的日志中是否出现截断警告。
步骤 1 的调用非常慢
成员会并行调用,在内存中相互争抢。请改为顺序执行,或将评议会缩减到能够同时装入显存的规模。
主席答非所问
它的上下文在本轮中最为充实。为它提供一个更大的模型,或者在传递排名之前先将排名总结一下。
缺少排名格式
小型模型会忘记“CLASSEMENT:”这一行。请在提示词末尾重申格式、降低温度,或者接受手动阅读理由。
成员的回答为空或为英文
请确认模型不会只返回其思考阶段的内容,并在问题中明确指定所需语言。
运行几轮后内存已饱和
OLLAMA_MAX_LOADED_MODELS 对这台机器来说设置得太高。将其调回 1,让 Ollama 交替运行。

#深入了解

LLM Council 是一种审议模式,而不是代理框架:它不管理工具、角色或记忆。如果您的需求超出了意见总结,这些网站指南涵盖了相邻的构建模块。

使用 Ollama 的 CrewAI
当模型需要分工并依次完成任务,而不是评判同一个回答时。https://quelllm.fr/guide/crewai-ollama-multi-agents
作为本地网关的 LiteLLM
用于让多个模型服务器通过一个兼容 OpenAI 的 API 对外提供服务,例如将评议会分布在两台机器上。https://quelllm.fr/guide/litellm-proxy-gateway-local
本地 AI 代理的架构
当希望为模型提供工具和记忆时,需要围绕模型组装的组件。https://quelllm.fr/guide/agent-ia-local-architecture
在本地网络上共享 Ollama 服务器
将评议会成员托管在更大的机器上,再从您的工作站调用它们。https://quelllm.fr/guide/ollama-serveur-reseau-local-partage

本指南于 2026 年 10 月 12 日根据 Karpathy 的 GitHub 仓库和 Ollama 的文档撰写。本文未进行任何时间或质量测量;记忆占用参考值是以 Q4_K_M 为基准的数量级估算。常量、端口和模型名称会变化:复制命令前请重新阅读下方的源代码。

这份指南对您有帮助吗?

有反馈、发现了错误,或想补充说明?请告诉我们,让这份指南对每个人都更有帮助。