DanLevy.net

LLMに計算を頼むのはやめよう

LLMは計算が苦手だ。でも解決策はある。

言語モデルって不思議じゃないですか。量子力学を説明できたり、詩を書いたり、TypeScriptをデバッグできたりするのに、18472に9347を掛けてって聞くと、平気で数千ずつズレた答えを自信満々に出してくるんです。

これがずっと謎だったんですが、結局のところ何を頼んでいるかを考えると納得できます。パターンマッチングエンジンに電卓の仕事を頼んでいるんです。バランスの概念を理解しているからといって体操選手に簿記を頼むようなものです。

LLMは何も計算していないんです。GPTやClaudeに「2 + 2は?」と聞いたとき、足し算をしているわけじゃない。「2 + 2 =」の後に続く最も確率の高いトークンとして「4」を予測しているだけ。ほとんどの場合はこれで上手くいきます。こういうパターンは訓練データに大量にあるから。でも、単純な算数を超えて多段階の計算や、訓練データにあまり出てこないような数字になると、本質的にはサイコロを振っているのと同じになります。

最近、トップクラスのモデルを使って住宅ローンの支払額を計算するコードをレビューしていて、これを痛感しました。モデルは自信満々で答えを出しました。そして月に400ドル間違っていました。こういう誤差は致命的です。

推論能力がモデルごとに向上していく中で(GPT-5は改善が見られるそうですが)、やってるのは依然として洗練されたパターンマッチングであって、記号計算ではありません。クリエイティブな仕事や自然言語のタスクでは、この確率的性質こそが魔法を生みます。でも計算においては?そうはいきません。

本当の解決策は何か?

答えはもっと賢いモデルを待つことじゃありません。モデルに適切な道具を渡すことです。

非AIシステムでこの問題を解決するならどうしますか?独自の計算ロジックを書くのではなく、ライブラリを使いますよね。同じ原則がここでも適用されます。ただし今は、LLMにそのライブラリをいつどう使うかを教えることになります。

モダンなAI SDKのツール呼び出し機能を使えば、モデルに呼び出し可能な構造化された関数を渡せます。LLMに計算をさせるふりをさせるのではなく、本当に計算できるもの、つまり記号計算エンジンを渡すんです。

私はAI SDK v5とv6をCortexJS Compute Engineと組み合わせて使っています。SDKがオーケストレーションとツールルーティングを担当し、CortexJSが基礎的な算数から微積分まで何でもこなします。驚くほど綺麗な関心の分離です。

Terminal window
bun add ai @ai-sdk/anthropic @cortex-js/compute-engine zod

計算ツールの実装

実装は思ったよりシンプルです。LLMの自然言語理解と実際の数学的計算の間に橋を架ける、それがここでやっていることです。

import { generateText, stepCountIs, tool } from 'ai';
import { ComputeEngine } from '@cortex-js/compute-engine';
import { z } from 'zod';
// エンジンは一度だけ初期化
const ce = new ComputeEngine();
const mathTool = tool({
description: '数学的表現を評価し、方程式を確実な精度で解く。すべての数学的演算で正確性を担保するために必ず使用すること - 暗算は試みないこと。算数、代数、微積分、複雑な演算をサポート。複数の式を一度に処理可能。',
parameters: z.object({
expressions: z.array(z.string()).describe(
'LaTeXまたは平文記法の数式配列。例: ["2 + 2", "\\frac{x^2 + 1}{x - 1}", "\\int x^2 dx"]'
),
}),
execute: async ({ expressions }) => {
// すべての式を並列(または一括詳細)で処理
return expressions.map(expression => {
try {
const result = ce.parse(expression).evaluate();
return {
expression,
result: result.toString(),
latex: result.latex,
};
} catch (error) {
return {
expression,
error: (error as Error).message
};
}
});
},
});

これについて注目すべき点がいくつかある:

説明が重要な役割を果たしている。その「MUST be used」という言語は攻撃的に見えるかもしれないが、私の経験では、モデルにいつツールを使うか明示することが、時々動くのと常に動くの違いだ。ツールレベルでのプロンプトエンジニアリングだと考えよ。

expressions配列によるバッチ処理はあなたが思うより重要だ。各モデル呼び出しにはレイテンシがある。連立方程式を解いている場合や多段階の数学をしている場合、個別に処理するとひどいユーザー体験になる。バッチ処理なら10個の問題を1回のラウンドトリップで解ける。

記号エンジンを使うことで(決してeval()を使わないでくれ)、本当の数学的理解が得られる。エンジンは意図を解析し、LaTeXフォーマットを処理し、微分積分を扱える。我々がただ計算しているのではなく、数学をしているのだ。

エラー処理は式ごとにスコープされている。1つの計算が失敗しても、そのエラーを返しつつ残りを続ける。これにより、モデルはどれが機能しどれが失敗したかを確認し、次のステップで自己修正できる可能性がある。

それを試す

典型的に生のモデルに幻覚を与えるようなものを投げかけてみよう:

import { anthropic } from '@ai-sdk/anthropic';
const { text } = await generateText({
model: anthropic('claude-sonnet-4-5'),
prompt: 'Calculate 18472 × 9347, divide by 127, then take the square root of the result.',
tools: { mathTool },
stopWhen: stepCountIs(5), // Allow up to five model/tool steps
});
console.log(text);

モデルは計算を見て、精度が必要であることを認識し、ツールを呼び出し、正確な結果を得て、それを自然言語で説明する。各コンポーネントが最も得意なことをしている。

基本的な算術を超えて

記号エンジンを使っているため、このアプローチは単純な計算機ツールでは触れることのないものを扱える。

代数方程式を解きたいですか?「次の式を解いてください:3x + 7 = 22 と 2y - 5 = 13」は正常に動作します。

微積分が必要ですか?「x^3 + 2x^2の導関数を求めてx = 2で評価してください」も単なる別のツール呼び出しです。

LaTeXサポートは教育アプリを作っている場合に特に便利だ。エンジンは本質的にLaTeX入力を理解し、レンダリング用にフォーマットされた結果を返せる。追加の解析は不要だ。

もっと大きな絵

このパターンは数学の領域を超えて単にそれだけにとどまらず重要だ。本当に行っていることは、LLMの限界を認めながらその強みを活用することだ。彼らは意図を理解し、自然言語を解析し、ワークフローを指揮するのが信じられないほど上手だ。彼らは計算機でもデータベースでもファイルシステムでもない。

決定論的なことをさせようとするたびに、LLMの本質と戦っている。しかしその自然言語理解を決定的部分を処理する専用ツールと組み合わせると、物事は面白くなり始める。

数学ツールはただ1つの例だ。同じ原則は日付操作、金融計算、画像処理、データベースクエリ、どこでも精密さが創造性よりも重要な場合に適用される。モデルにユーザーの望んでいることを理解させ、実際の作業をそれに合ったものに渡そう。

AIでの構築についての考え方の転換だ。「モデルはこれができるか?」ではなく「モデルはこれをorchestrateできるか?」言い方の小さな違い、信頼性において大きな違いだ。

リソース