50个LLM裸算算术评测:去掉计算器后,谁还靠谱?
2026/8/30 19:36:10 网站建设 项目流程

现在很多开发者在做 LLM 应用时,都会默认给模型接上计算器插件、代码解释器,或者干脆相信模型的“心算”。但当我把计算器从 50 个 LLM 手里拿走,只让它们用最原始的思维链完成加减乘除时,得到的结果比预想中更有意思。这篇文章会完整复盘这次算术评测的测试集设计、评测框架、打分逻辑和避坑方案,也会讨论一个工程问题:在什么场景下,你不应该把算术交给 LLM。

1. 背景:为什么要把计算器从 LLM 手里拿走

1.1 LLM 算术能力为什么是一个工程问题

在 RAG、Agent、Function Calling 这些概念满天飞的今天,LLM 解决复杂问题的方式已经不再是“单模型硬算”。很多应用会在 LLM 外面套一层工具调用层,遇到数学计算就交给计算器插件或代码解释器。这种架构本身没有问题,但它掩盖了一个事实:模型本身的算术能力仍然脆弱。

如果你把一个 LLM 接到一个无法访问外部工具、没有计算器插件的环境里,比如:

  • 纯文本对话机器人只能靠模型自身输出。
  • 某些安全环境中不允许执行外部代码。
  • 模型服务只有基础的 chat/completion 接口,没有 tool calling。
  • Agent 的工具调用失败或超时,系统自动降级到普通对话模式。

这时模型的“裸算能力”就成了兜底。如果它连基本的两位数乘法都算不对,用户的信任度会断崖式下降。

1.2 为什么 LLM 会被“看起来很会算”欺骗

LLM 本质上是下一个 Token 的预测器,它并不像传统计算器那样执行真实的数值运算。它通过学习海量文本,学到了很多算术模式。比如1 + 1 = 27 × 8 = 56这类高频出现的内容,模型可以轻松背出来。但遇到低频率、高位数、需要严格进位的计算时,它的表现就会退化成“模式匹配 + 概率采样”。

这也是为什么很多模型在简单数学题上表现极好,一旦出现多位数乘法、小数除法、分数比较,就会给出一个看起来很有逻辑但数值离谱的结果。

1.3 从“盲目信任”到“基准评测”

为了避免“觉得模型会算”和“模型真的会算”之间的错觉,最直接的办法就是做基准测试。这次评测的做法很明确:把计算器从 LLM 身边拿走,不提供任何外部工具,只给一个题目文本,让模型直接输出答案。然后把 50 个主流和常见的 LLM 放在同一批测试集上跑一遍,按统一标准打分,最后得出一个横向对比结论。

这种做法适合任何想评估模型算术能力的开发者参考,即使你不打算跑 50 个模型,也可以只跑自己负责的那一个,看看它在脱离工具后的真实水平。

2. 测试集设计:如何科学地给 50 个 LLM 出算术题

2.1 题目维度拆解

算术能力不是一个单一指标,我把它拆成以下几个维度:

类别具体题型示例
基础运算整数加减乘除123 + 456789 - 321
多位数运算三位数以上乘除1234 × 5678
混合运算四则混合、括号(12 + 34) × 5 - 18 ÷ 3
小数运算小数的加减乘除0.1 + 0.23.14 × 2.5
分数运算分数加减乘除1/3 + 2/57/8 - 3/4
比较大小小数/分数/大数比较0.3 和 1/3 哪个大
取模与整除余数、整除判断100 % 7256 能否被 8 整除

细分的目的是避免“平均准确率”掩盖单体弱项。一个模型可能整数加减法接近满分,但小数运算一塌糊涂。如果没有维度拆分,最终分数会掩盖这个问题。

2.2 难度梯度设计

测试集不能全出简单的题,也不能全出偏题。我按难度梯度把题目分成三档:

  • 基础题:COT 不需要太多步骤,能直接算出的简单题。
  • 中等题:需要借位、进位、多步混合运算。
  • 困难题:多位小数、超大整数、复杂分数通分比较。

建议难度比例控制在3:4:3左右,这样既能反映基础能力,也能区分中高水平模型。

2.3 防止记忆污染

LLM 的训练数据量非常大,一些简单算术题极有可能已经出现在训练语料中。这意味着模型可能不是“算出来”的,而是“背出来”的。为了降低这种影响,测试题应该尽量使用低热度的数字组合,不要全是1+19×9这种高频题。

一种有效做法是在测试集中加入动态随机生成的长数字,确保网络公开语料里几乎不可能出现完全一样的题目。比如735482 × 1849这种组合,模型不太可能直接背过答案。

2.4 生成测试集的代码实现

下面用 Python 写一个可复现的测试集生成器。通过固定随机种子,保证 50 个 LLM 拿到的题目完全一致,这样横向对比才有意义。

import json import random random.seed(42) def generate_integer_arithmetic(num=40): items = [] for _ in range(num): a = random.randint(10, 9999) b = random.randint(10, 999) op = random.choice(['+', '-', '*', '//', '%']) if op == '+': answer = a + b elif op == '-': answer = a - b elif op == '*': answer = a * b elif op == '//': answer = a // b else: answer = a % b expression = f"{a} {op} {b}" items.append({ "problem_id": f"int_{len(items)}", "expression": expression, "answer": str(answer), "category": "integer", }) return items def generate_decimal_arithmetic(num=30): items = [] for _ in range(num): a = round(random.uniform(0.1, 999.9), 2) b = round(random.uniform(0.1, 99.9), 2) op = random.choice(['+', '-', '*']) if op == '+': answer = round(a + b, 2) elif op == '-': answer = round(a - b, 2) else: answer = round(a * b, 2) expression = f"{a} {op} {b}" items.append({ "problem_id": f"dec_{len(items)}", "expression": expression, "answer": str(answer), "category": "decimal", }) return items def generate_mixed_expression(num=30): items = [] for _ in range(num): a = random.randint(2, 50) b = random.randint(2, 50) c = random.randint(2, 50) d = random.randint(2, 9) op1 = random.choice(['+', '-', '*']) op2 = random.choice(['+', '-', '*']) expression = f"({a} {op1} {b}) {op2} {c} - {d}" # 使用 ast 计算精确结果,避免浮点误差干扰 import ast result = eval(expression, {'__builtins__': {}}, {}) items.append({ "problem_id": f"mix_{len(items)}", "expression": expression, "answer": str(result), "category": "mixed", }) return items if __name__ == "__main__": dataset = [] dataset.extend(generate_integer_arithmetic(40)) dataset.extend(generate_decimal_arithmetic(30)) dataset.extend(generate_mixed_expression(30)) with open("arithmetic_testset.json", "w", encoding="utf-8") as f: json.dump(dataset, f, ensure_ascii=False, indent=2) print(f"生成题目总数: {len(dataset)}") for item in dataset[:5]: print(item)

这里说一个注意事项:eval在真实工程中并不安全,上面的代码主要用于本地生成离线测试集,不推荐直接用在线上服务。如果担心表达式求值的可控性,可以改用sympydecimal.Decimal来做精确计算。

2.5 测试集文件结构

生成后的 JSON 文件结构大致如下:

[ { "problem_id": "int_0", "expression": "1234 + 5678", "answer": "6912", "category": "integer" }, { "problem_id": "dec_0", "expression": "123.45 + 67.89", "answer": "191.34", "category": "decimal" } ]

这种结构方便后续评测脚本直接读取,也方便把测试题复用到你自己的评测项目里。

3. 评测环境搭建与代码实现

3.1 统一模型调用接口

要评测 50 个 LLM,第一个要解决的问题是“接口不统一”。不同厂商的 API 参数五花八门,有的支持messages格式,有的支持prompt格式,有的有temperature,有的没有。为了不让接口差异影响评测结果,需要封装一个统一调用层。

这里给出一个简化的调用层。它抽象了三种常见模型来源:OpenAI 风格接口、Anthropic 风格接口、本地 OpenAI 兼容接口。

import json import time import requests from typing import Dict, Any, Optional class LLMClient: """统一的 LLM 评测客户端封装""" def __init__(self, model_name: str, api_type: str, base_url: str = None, api_key: str = None): self.model_name = model_name self.api_type = api_type self.base_url = base_url self.api_key = api_key def chat(self, prompt: str, temperature: float = 0.0, max_tokens: int = 512) -> str: if self.api_type == "openai": return self._chat_openai(prompt, temperature, max_tokens) elif self.api_type == "anthropic": return self._chat_anthropic(prompt, temperature, max_tokens) elif self.api_type == "local": return self._chat_local(prompt, temperature, max_tokens) else: raise ValueError(f"不支持的 API 类型: {self.api_type}") def _chat_openai(self, prompt: str, temperature: float, max_tokens: int) -> str: url = f"{self.base_url}/chat/completions" if self.base_url else "https://api.openai.com/v1/chat/completions" headers = { "Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json", } payload = { "model": self.model_name, "messages": [ {"role": "user", "content": prompt} ], "temperature": temperature, "max_tokens": max_tokens, } resp = requests.post(url, headers=headers, json=payload, timeout=60) resp.raise_for_status() return resp.json()["choices"][0]["message"]["content"] def _chat_anthropic(self, prompt: str, temperature: float, max_tokens: int) -> str: url = "https://api.anthropic.com/v1/messages" headers = { "x-api-key": self.api_key, "anthropic-version": "2023-06-01", "Content-Type": "application/json", } payload = { "model": self.model_name, "messages": [ {"role": "user", "content": prompt} ], "temperature": temperature, "max_tokens": max_tokens, } resp = requests.post(url, headers=headers, json=payload, timeout=60) resp.raise_for_status() return resp.json()["content"][0]["text"] def _chat_local(self, prompt: str, temperature: float, max_tokens: int) -> str: # 假设本地服务是 OpenAI 兼容格式 return self._chat_openai(prompt, temperature, max_tokens)

使用示例:

client = LLMClient( model_name="qwen2.5-72b-instruct", api_type="local", base_url="http://localhost:8000/v1", api_key="EMPTY" ) resp = client.chat("请计算 1234 × 5678 的结果,只输出数字。") print(resp)

3.2 评测 Prompt 设计

“去掉计算器”体现在 prompt 上,就是明确要求模型不要使用任何工具、不要编写代码、不要假设外部计算器存在,直接用文字推理完成计算。

这里有一个很关键的细节:是否允许 Chain-of-Thought。标准的 CoT 会让模型把中间的推理步骤写出来,这对算术准确率提升很明显。但不同模型在 verbose 输出能力上有差异。

为了公平,可以设计两套 prompt:

  • prompt_A:直接要求输出答案,禁止解释,模拟“快速响应”场景。
  • prompt_B:允许分步推理,但最终必须给一个唯一的数字答案。

建议用 prompt_B 作为主评测基准,因为它在数学评测中更接近主流实践,也更能反映模型真实推理能力。

示例 Prompt:

你是一个数学计算器评测助手。请完成以下算术题。 要求: 1. 不要使用任何外部工具、计算器、代码执行环境。 2. 只能依靠你自己的推理能力计算。 3. 你可以在思考过程中写推理步骤,但最终一行必须是“最终答案:<数字>”的格式。 4. 不要输出除最终答案外的多余数字。 题目: (12345 + 6789) * 12 - 456 / 3 请开始。

注意,题目里不能出现“请写代码”这种引导,我们要的就是模型裸算。如果模型尝试输出 Python 代码或描述它可以调用计算器,说明它在当前结构下不会乖乖走“纯推理”路径,这种情况会被记录为“违规输出”。

3.3 评测循环主脚本

写一个评测脚本,遍历所有测试题和所有模型,把结果保存为 JSON。

import json import time from datetime import datetime from typing import List, Dict def run_evaluation( models: List[Dict[str, str]], dataset_path: str = "arithmetic_testset.json", output_path: str = "eval_results.json", max_retries: int = 3, ): with open(dataset_path, "r", encoding="utf-8") as f: dataset = json.load(f) all_results = [] for model_config in models: client = LLMClient(**model_config) model_result = { "model": model_config["model_name"], "api_type": model_config["api_type"], "start_time": datetime.now().isoformat(), "items": [], } for item in dataset: is_correct = None raw_output = "" attempts = 0 while attempts < max_retries: try: prompt = build_prompt(item["expression"]) raw_output = client.chat(prompt, temperature=0.0, max_tokens=256) is_correct = check_answer(raw_output, item["answer"]) break except Exception as e: print(f"模型 {model_config['model_name']} 题目 {item['problem_id']} 调用失败: {e}") attempts += 1 time.sleep(2) model_result["items"].append({ "problem_id": item["problem_id"], "category": item["category"], "expression": item["expression"], "expected_answer": item["answer"], "raw_output": raw_output, "is_correct": is_correct, "attempts": attempts, }) time.sleep(0.5) # 防止请求过快 model_result["end_time"] = datetime.now().isoformat() all_results.append(model_result) save_results(all_results, output_path) return all_results def build_prompt(expression: str) -> str: prompt = f"""请完成以下算术题。 要求: 1. 不要使用任何外部工具、计算器、代码执行环境。 2. 只能依靠你自己的推理能力计算。 3. 你可以在思考过程中写推理步骤,但最终一行必须是“最终答案:<数字>”的格式。 4. 不要输出除最终答案外的多余数字。 题目: {expression} """ return prompt def check_answer(raw_output: str, expected_answer: str) -> bool: # 优先从“最终答案:”提取 if "最终答案:" in raw_output: predicted = raw_output.split("最终答案:")[-1].strip() else: # 兜底:取最后一个数字 import re numbers = re.findall(r"-?\d+\.?\d*", raw_output) if not numbers: return False predicted = numbers[-1] return normalize_number(predicted) == normalize_number(expected_answer) def normalize_number(s: str) -> str: s = s.strip().replace(",", "").replace(",", "") # 去掉多余的 .0 if s.endswith(".0"): s = s[:-2] return s def save_results(results, path: str): with open(path, "w", encoding="utf-8") as f: json.dump(results, f, ensure_ascii=False, indent=2)

这个脚本有几点设计值得说明:

  • temperature=0.0是为了减少采样随机性。虽然很多 API 即使设成 0 也不是完全确定,但这是目前最容易保持可复现性的方式。
  • 重试机制只处理“调用失败”,不处理“答错”。答错是模型能力问题,重试不会改善,反而会影响评测公平性。
  • time.sleep(0.5)是为了避免并发过高触发限流。

3.4 结果统计与打分

评测完 50 个模型后,需要汇总出每个模型的准确率,同时按题型维度做交叉分析。

import json from collections import defaultdict def compute_summary(results_path: str): with open(results_path, "r", encoding="utf-8") as f: results = json.load(f) summary = [] for model_result in results: items = model_result["items"] total = len(items) correct = sum(1 for item in items if item["is_correct"]) category_correct = defaultdict(int) category_total = defaultdict(int) for item in items: cat = item["category"] category_total[cat] += 1 if item["is_correct"]: category_correct[cat] += 1 model_summary = { "model": model_result["model"], "total_accuracy": round(correct / total * 100, 2), "total_correct": correct, "total_count": total, "category_accuracy": { cat: round(category_correct[cat] / category_total[cat] * 100, 2) for cat in category_total } } summary.append(model_summary) summary.sort(key=lambda x: x["total_accuracy"], reverse=True) print(f"{'模型':<30} {'总分':<8} {'整数':<8} {'小数':<8} {'混合':<8}") print("-" * 70) for m in summary: cat = m["category_accuracy"] print(f"{m['model']:<30} {m['total_accuracy']:<8} {cat.get('integer', 0):<8} {cat.get('decimal', 0):<8} {cat.get('mixed', 0):<8}") return summary if __name__ == "__main__": compute_summary("eval_results.json")

汇总输出示例:

模型 总分 整数 小数 混合 ------------------------------------------------------------ gpt-4o 89.2 95.0 85.0 90.0 claude-3.5-sonnet 87.5 92.5 83.3 86.7 qwen2.5-72b-instruct 84.0 90.0 80.0 83.3 ...

这里要注意,由于测试集和模型版本随时间变化,上面的分数只是展示格式,不代表某个模型的固定成绩。实际跑出来的结果可能会有所不同。

4. 50 个 LLM 的分级与评测流程

4.1 模型池设计

“50 个 LLM”不可能全部来自同一家厂商。在实际评测中,模型池通常按这几类划分:

  • 闭源商用 API:OpenAI、Anthropic、Google、阿里云、智谱等。
  • 开源可本地部署模型:Qwen、Llama、DeepSeek、Mistral、Baichuan、Yi 等。
  • 不同尺寸版本:同一个开源模型可能有 7B、14B、72B 等多个尺寸,应分别评测,方便观察参数量对算术能力的影响。
  • 非主流或小众模型:一些在 Hugging Face 上热度较低的模型,也能跑出很有参考性的结果。

在写评测列表时,可以这样组织:

models = [ # 闭源 API {"model_name": "gpt-4o", "api_type": "openai", "base_url": None, "api_key": "YOUR_KEY"}, {"model_name": "claude-3-5-sonnet-latest", "api_type": "anthropic", "base_url": None, "api_key": "YOUR_KEY"}, # 本地部署 {"model_name": "qwen2.5-7b-instruct", "api_type": "local", "base_url": "http://localhost:8000/v1", "api_key": "EMPTY"}, {"model_name": "qwen2.5-72b-instruct", "api_type": "local", "base_url": "http://localhost:8001/v1", "api_key": "EMPTY"}, {"model_name": "llama-3.1-8b-instruct", "api_type": "local", "base_url": "http://localhost:8002/v1", "api_key": "EMPTY"}, ]

4.2 评测批次与顺序

50 个模型逐个跑会比较慢,尤其是每个模型要过 100 道题。建议分批执行,每批 5 到 10 个模型,避免某个厂商的 API 因为并发过高直接限流。

推荐流程:

  1. 先跑 2 个基线模型(一个最强商用 API,一个本地 7B 小模型),确认评测代码没问题。
  2. 再跑同系列不同尺寸模型,观察尺寸变化趋势。
  3. 然后跑其他厂商/开源模型。
  4. 最后跑一些特殊的推理强化模型。

评测过程中需要持续保存中间结果,避免某个模型跑到一半因为网络中断而全部重来。

4.3 超时与异常兜底

LLM 评测最怕的不是答错,而是“挂起”。某些模型在遇到复杂算术题时可能陷入无限生成。因此,必须设置请求级超时和输出长度上限。

前面代码中的max_tokens=256就是一个硬上限。如果模型在思考过程中写太多废话,256 个 token 可能不够。这时有两种处理方式:

  • 增加max_tokens到 512 或 1024,允许更长的 CoT。
  • 缩短 prompt,明确要求“不要写太多推理,直接给答案”。

建议先用 512 跑通全流程。如果发现大量模型因为 token 截断而答错,需要重新设计 prompt 或提高上限,不能直接算作错误。

5. 评测结果的分析维度

5.1 总体准确率排名

最直观的分析是总体准确率排名。它能快速给出一个结论:当前模型池里,谁的算术裸算最强。但这个排名不能完全代表模型好坏,因为算术只是 LLM 能力的一小块。

比较好的做法是把准确率按区间分层:

  • 89% 以上:算术能力很强,可以承担部分无需工具的数值任务。
  • 70% - 89%:中等水平,建议在关键计算场景接工具。
  • 50% - 70%:较弱,不能承担任何精确计算任务。
  • 50% 以下:基本不具备可靠算术能力。

5.2 错误模式分析

只看准确率不够,还要看模型是怎么错的。我总结了 4 种常见错误模式:

错误模式表现说明
位数错误结果差一个数量级模型可能“看到”了计算过程,但最后少数了一位
进位错误中间过程正确,个位/十位出错反映了 token 级注意力不稳定
符号错误减号看成加号,或丢掉负号prompt 理解问题
幻觉式自信给出一个很精确但完全错误的结果最危险,模型会用自信语气掩盖错误

在汇总时,可以把每道错题的人工筛查结果记录到一个 CSV 里,方便后续分析。

5.3 模型尺寸与算术准确率的相关性

如果把同一个开源模型的 7B、14B、72B 版本放在一起对比,通常会看到参数越大,算术能力越强。但差异并不是线性的。小模型可能在某些基础题上达到 80 分,但到复杂题直接崩盘;大模型在复杂题上表现相对稳定,但也不是 100% 正确。

比较有趣的是,有些专门做过数学强化训练的模型,即便参数量不大,也能在算术评测中超过比自己大几倍的通用模型。所以,与其盲目追求大模型,不如先确认当前任务是否需要数学推理能力。

5.4 输出格式规范性

评测时我会额外记录一个指标:模型输出是否包含“最终答案:”格式。有的模型会在结尾写一句“希望这个答案对你有帮助”,导致提取器拿到最后一个数字时取错。这在评测中会被判定为答案错误,但其实模型的“计算过程”是对的。

这说明输出格式规范化,本身也是模型可用性的一个重要维度。工程上,可以通过提示词约束和少量示例来改善,但模型的固有能力仍然很重要。

6. 常见问题与排查思路

6.1 模型总是用代码解释器或工具回答

问题现象:明明提示词要求“不要使用外部工具”,但模型还是会输出 “我可以使用 Python 计算,代码如下:” 之类的文本。

可能原因:模型的系统提示词里被植入了工具调用偏好。比如某些 API 的默认 system prompt 会告诉模型“你是一个可以调用工具的高级助手”。

排查步骤

  1. 检查调用参数里是否误传了tools字段。只要传了 tools,模型就会倾向调用工具。
  2. 检查 system prompt,确保没有“使用代码解释器”的指令。
  3. 检查模型版本,部分模型在指令遵循上较弱,容易忽略“不要使用工具”的约束。

解决方案

在评测时不要传入任何 tools 参数,只传原始 user prompt。如果模型仍然输出代码,直接判定为“违规输出”,记录为错误。

6.2 模型输出一段“思考过程”但没有最终答案

问题现象:模型写了大量 CoT 推理步骤,最后没有按“最终答案:”格式收尾。

可能原因max_tokens不够,导致输出被截断。

排查步骤

  1. 查看 raw_output 是否以“最终答案:”结尾。
  2. 检查输出长度是否接近max_tokens限制。

解决方案

适度提高max_tokens,或者在 prompt 中强调“先给出答案,再简单解释”。评测类任务不需要追求模型的完整推理过程,只需要一个可解析的答案。

6.3 小数运算出现浮点误差式错误

问题现象0.1 + 0.2模型输出0.30000000000000004

可能原因:模型在预训练时接触了大量编程语言中的浮点数表示方式,导致它学到了这种输出。

解决方案

在打分时,对小数结果做容差比较,而不是字符串完全匹配。比如将预测值和标准值都转成浮点数,允许1e-6的误差。但如果模型输出的是一个明显不对的数,比如0.1 + 0.2 = 0.5,那就要判错。

6.4 多个模型 API 并发调用被限流

问题现象:某个模型跑到一半开始返回 429 或 503。

排查步骤

  1. 检查 API 返回的 HTTP 状态码。
  2. 看报错信息中是否包含rate limitquota等字段。

解决方案

  • 增加请求间隔,比如time.sleep(1)
  • 加入指数退避重试机制。
  • 将同一个模型的题目分成多个子任务,在多个 API key 之间轮询。

7. 从算术评测看 LLM 工程落地

7.1 什么时候该给 LLM 配计算器

经过这次评测,我最想强调的一点是:工具增强虽然很流行,但不是所有场景都适合无脑加计算器。

如果任务满足以下条件,建议给 LLM 配置工具:

  • 计算精确度要求高,不能容忍小数点后两位的误差。
  • 计算过程可以通过代码天然完成,不需要模型创造复杂逻辑。
  • 用户有足够的等待时间等待工具执行。

典型场景包括:

  • 财务对账、订单金额折算。
  • 科学计算、物理模拟。
  • 数据分析里的聚合统计。
  • 需要生成精确报表的场景。

7.2 什么时候不该依赖 LLM 计算

如果任务只是对话里顺带一句“那大概多少钱”,LLM 的估算能力可能已经够用。用户不会因为你把123.45 * 2计算成246.9还是246.90而产生强烈不满。

但在这些场景,不建议依赖 LLM 做数值计算:

  • 涉及真实金钱交易。
  • 涉及用户隐私数据,不允许把数据发到外部 API 执行代码。
  • 需要离线处理,无法访问外部工具。
  • 延迟要求极高,不允许等待代码解释器启动。

在这些情况下,正确做法是:LLM 负责理解用户意图、从文本中抽取关键数字,再用传统代码逻辑完成计算,最后把结果交给 LLM 组织语言回复。

7.3 如何评估一个 LLM 是否适合数值密集型任务

如果你正在选型一个 LLM,想判断它能不能承担数值类任务,可以按以下步骤做一次小规模评测:

  1. 抽取 20 道高频业务算术题,不要用模型见过的原题。
  2. 在“无工具模式”下让模型作答。
  3. 同时用 Python 算出标准答案。
  4. 对比准确率和错误模式。
  5. 如果准确率低于 90%,那你需要额外接工具层。

如果不想每次手动跑,可以把上面的评测脚本保存成一个团队内部的基准脚本。以后每接入一个新模型,先跑一遍算术测试,再决定是否对它开放工具调用权限。

7.4 工程上的其他最佳实践

  • 永远不要在业务关键路径上直接信任 LLM 的数值输出。即使模型准确率已经很高,也要在代码层做一层校验。
  • 对大数运算,LLM 容易丢位数。可以用“千位分隔符”的输入格式降低位数识别错误率,比如1,234,567
  • 对小数运算,尽量要求模型输出固定小数位,比如“结果保留两位小数”。
  • 对 Agent 架构,建议把“计算能力”作为独立工具封装,而不是让 LLM 既当裁判又当运动员。
  • 评测时保持temperature=0.0,并且多跑几次取多数答案,能降低随机性带来的误判。
  • 对开源模型,建议在本地起推理服务并统一使用 OpenAI 兼容接口,降低评测工具的适配成本。

8. 总结

把计算器从 LLM 手里拿走,相当于扒掉了它的“外挂”,直接检查它自身的基本功到底怎么样。这次对 50 个 LLM 的算术评测,本质上是一次很朴素但有效的工程摸底:在无法调用外部工具的极端前提下,模型能不能给出正确答案。

从测试集设计、评测框架、模型池分类,再到结果分析和错误模式拆解,这套流程完全可以复用到你自己的模型选型和 Agent 场景当中。我也建议你不要只跑一次准确率排名,而是深入到每个题型、每种错误模式,才能真正理解一个模型的数学能力边界在哪里。

下一步如果你有兴趣,可以做三件事:把测试集扩展到包含更复杂的高等数学符号;在同样测试集上对比“裸算 vs 带计算器”的准确率差距;或者用这道测试题筛选适合做数学教师的微调基座模型。

如果你自己跑完评测,发现某个模型在算术题上表现得特别“离谱”,欢迎在评论区分享你的模型名字和题目类型,我们一起看看它到底是哪里翻了车。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询