TL;DR
- LLM エージェントは日本固有のデータ(氏名の読み・法人番号・六曜・住所表記)をもっともらしく幻覚する。対策はモデルの賢さではなく、確定値を返す tool を持たせて、モデルには「呼ぶ判断」だけさせること。
- ChatGPT GPTs / Function Calling への統合は前回までに書いた。本記事はコードでエージェントを組む場合=Vercel AI SDK(
ai)/ LangChain に、日本のデータの tool 群を自前の tool 定義ゼロで持たせる実装ガイド。 - 使うのは npm の
shirabe-sdk(v0.2.0)。shirabe-sdk/aiとshirabe-sdk/langchainの subpath import だけで、住所正規化・姓名分割・読み推定・法人番号検証/照会・暦の 7 tool が生える。Python も PyPI の同名パッケージで同じ 7 tool(LangChain / OpenAI Agents SDK 対応)。 - tool の実体は Shirabe の REST API(ほぼ全て API キー不要・匿名で呼出可)。返り値は「確定値 + 確度 + 出典」の構造化 JSON で、エージェントがそのまま回答に載せられる。
この記事の対象読者
- Vercel AI SDK / LangChain で業務エージェント・RAG・自動化パイプラインを組んでいる開発者
- 顧客名簿・取引先データの正規化(住所・ふりがな・法人番号)を LLM にやらせて、静かな事故に気付いた方
- 「tool calling はわかったが、tool 定義(schema・description・fetch・出典管理)を書くのが面倒」という方
問題: エージェントは日本固有の値を「それらしく」作る
日本のデータには、LLM が構造的に間違えるカテゴリがある。
| 入力 | LLM 直接の典型的な誤り |
|---|---|
| 東海林裕子 の読み | 「とうかいりんゆうこ」と字面読み(正しくは「しょうじ」)、読みを 1 つに断定 |
| 法人番号 13 桁 | チェックディジット(mod-9)を計算ミスしたまま「有効」と回答 |
| 2026-07-17 の六曜 | 旧暦変換を誤り、日によって違う答えを返す |
| 住所文字列 | 丁目・番・号の表記ゆれを正規化できず、存在しない町名を補完 |
これらは「賢いモデルに変えれば直る」類の問題ではない。読みは原理的に非一意、法人番号はレジストリ実在照合が必要、六曜は決定的な計算、住所はアドレス・ベース・レジストリとの照合が正解の形だからだ。
tool calling はまさにこのための機構だが、実務では次の boilerplate が残る。
- tool ごとに zod schema + description を書く(モデルが呼ぶ判断を誤らないよう英語で明確に)
- fetch・エラーハンドリング・タイムアウトを書く
- レスポンスの**出典(attribution)**を落とさず引き回す
- しかも Vercel AI SDK と LangChain で tool の型が違うので二重に書く
解決策: shirabe-sdk の subpath import で 7 tool を丸ごと持たせる
shirabe-sdk は上の 1〜4 を全部済ませてある。core はランタイム依存ゼロ(Node 18+ / Workers / Deno / ブラウザ)、framework アダプタは subpath に分離されていて、使う側だけ optional peer dependency を入れる。
# Vercel AI SDK で使う場合
npm install shirabe-sdk ai zod
# LangChain で使う場合
npm install shirabe-sdk @langchain/core zod
Vercel AI SDK(ai v5 以降。現行 v7 系で動作確認済み)
import { generateText, stepCountIs } from "ai";
import { openai } from "@ai-sdk/openai";
import { shirabeAITools } from "shirabe-sdk/ai";
const { text } = await generateText({
model: openai("gpt-4o"),
tools: shirabeAITools(),
stopWhen: stepCountIs(5), // tool 呼出 → 結果を踏まえた回答まで複数ステップを許可
prompt: "「東海林裕子」さんの氏名の読みと、2026-07-17 の六曜を調べて。",
});
shirabeAITools() は generateText / streamText の tools にそのまま渡せる ToolSet を返す。モデルは prompt に応じて shirabe_name_reading と shirabe_calendar を自分で選んで呼び、確定値を踏まえて回答する。
LangChain(@langchain/core v0.3 以降。現行 v1 系で動作確認済み)
import { ChatOpenAI } from "@langchain/openai";
import { shirabeLangChainTools } from "shirabe-sdk/langchain";
const tools = shirabeLangChainTools();
const model = new ChatOpenAI({ model: "gpt-4o" }).bindTools(tools);
const res = await model.invoke("東海林裕子さんの氏名の読みを調べて");
// res.tool_calls に shirabe_name_reading の呼出要求が入る
エージェントループごと任せるなら LangGraph の prebuilt agent にそのまま渡せる。
import { createReactAgent } from "@langchain/langgraph/prebuilt";
const agent = createReactAgent({
llm: new ChatOpenAI({ model: "gpt-4o" }),
tools: shirabeLangChainTools(),
});
const out = await agent.invoke({
messages: [{ role: "user", content: "この名簿の 3 名にふりがなを振って: 東海林裕子、…" }],
});
LangChain 版の各 tool は結果を JSON 文字列で返す(LangChain の tool 出力規約に合わせてある)。
Python(LangChain / OpenAI Agents SDK)
Python 側も PyPI の shirabe-sdk v0.2.0 で同じ 7 tool が使える(import 名は shirabe、core は標準ライブラリのみ)。
pip install "shirabe-sdk[langchain]" # LangChain(langchain-core >= 0.3.40)
pip install "shirabe-sdk[openai-agents]" # OpenAI Agents SDK(Python 3.9+)
LangChain(Python)。LangGraph の create_react_agent にもそのまま渡せる:
from shirabe.langchain import shirabe_langchain_tools
from langchain_openai import ChatOpenAI
tools = shirabe_langchain_tools()
model = ChatOpenAI(model="gpt-4o").bind_tools(tools)
OpenAI Agents SDK:
from agents import Agent, Runner
from shirabe.openai_agents import shirabe_openai_agents_tools
agent = Agent(
name="assistant",
instructions="日本のデータは Shirabe tool で裏取りして答える。",
tools=shirabe_openai_agents_tools(),
)
result = Runner.run_sync(agent, "「東海林裕子」さんの氏名の読みを調べて。")
生える 7 tool と、それぞれが返す確定値
| tool 名 | 何を返すか | API キー |
|---|---|---|
shirabe_normalize_address |
住所をアドレス・ベース・レジストリ照合で正規化(都道府県〜番地 + JIS コード) | 不要 |
shirabe_split_name |
氏名の姓名分割(IPAdic ベース、confidence 付き) | 不要 |
shirabe_name_reading |
読み推定(最頻の読み + 収載読みの全候補 + 出典) | 不要 |
shirabe_validate_corporation |
法人番号の形式 + チェックディジット + 国税庁レジストリ実在検証 | 不要 |
shirabe_lookup_corporation |
法人番号 → 登記上の商号・所在地・法人種別 | 不要 |
shirabe_calendar |
指定日の六曜・暦注・干支・二十四節気・用途別の吉凶 | 不要 |
shirabe_enrich |
住所 + 氏名 + 法人番号 + 暦を 1 呼出でまとめて正規化 | Hub Pro/Enterprise キー(匿名は月 500 回の試用枠) |
description は英語で「いつ呼ぶべきか」「なぜ LLM が自前で答えてはいけないか」まで書いてあるので、モデルの呼ぶ判断が安定する(例: shirabe_name_reading は "Japanese name readings are NOT unique … never assume a single reading")。
返り値の形(実レスポンス)
読み推定はこう返る(2026-07-17 実行の実値)。
{
"reading": "しょうじゆうこ",
"candidates": [
"しょうじひろこ", "しょうじのぶこ", "しょうじひろし", "しょうじひろみ",
"しょうじやすこ", "しょうじゆうね", "しょうじゆこ", "しょうじようこ",
"しょうじよしこ", "しょうしゆうこ"
],
"confidence": 0.97,
"matched_by": "dictionary_both"
}
reading は最頻の読み、candidates は人名辞書(JMnedict)に収載された別読みの全網羅。読みが非一意である事実を隠さず構造化して返すので、エージェントは「最有力は『しょうじゆうこ』、別読みの可能性としてこれら」と正直に答えられる(この設計の背景は前回記事に詳しい)。
住所正規化は出典付きでこう返る。
{
"input": "東京都港区六本木6-10-1",
"result": {
"normalized": "東京都港区六本木6丁目10",
"components": {
"prefecture": "東京都", "city": "港区", "town": "六本木6丁目",
"block": "10番", "jis_code": "13103", "lg_code": "131032"
},
"level": 4,
"confidence": 0.93
},
"attribution": {
"source": "アドレス・ベース・レジストリ(住所データ)",
"provider": "デジタル庁",
"license": "CC BY 4.0"
}
}
LLM を挟まず直接呼ぶ(テスト・バッチ処理)
tool の実体は core の ShirabeClient なので、エージェント経由にする必要がない処理(夜間バッチの名寄せ・CI でのスモークテスト)は直接呼べばいい。
import { ShirabeClient } from "shirabe-sdk";
const shirabe = new ShirabeClient(); // キー不要で開始
const r = await shirabe.nameReading("東海林裕子");
console.log(r.reading, r.candidates);
オプションは 3 つだけ覚えれば足りる。
new ShirabeClient({
apiKey: process.env.SHIRABE_API_KEY, // 有料プランのキー(X-API-Key として送信)
baseUrl: "https://shirabe.dev", // 既定値。変える必要は通常ない
fetch: customFetch, // テストでのモック差し替え用
});
shirabeAITools(options) / shirabeLangChainTools(options) も同じオプションを受け取る。
自前 tool 定義との比較
| 観点 | 自前で tool を書く | shirabe-sdk |
|---|---|---|
| schema / description | tool × framework ごとに手書き | 7 tool 定義済み(呼ぶ判断まで description で誘導) |
| framework 間の移植 | AI SDK ↔ LangChain で書き直し | subpath を替えるだけ(仕様は単一ソース) |
| 出典(attribution) | 自前で引き回す | レスポンスに機械可読で同梱 |
| 依存 | — | core は依存ゼロ、framework は optional peer |
| 利用開始 | エンドポイント調査から |
npm install → 即(キー不要) |
もちろん自前定義が正解の場面もある(社内 API と混ぜて 1 つの ToolSet にする、tool の説明文を自社ドメイン語彙に寄せる等)。その場合も、この SDK の tool-specs の設計(framework 非依存の仕様 1 箇所から各アダプタを生成)はそのまま参考になるはずだ。
ライセンスと出典(組み込む前に)
各 tool の返す値の出典はレスポンスの attribution に機械可読で入っている。剥がさずに引き回すこと。
- 住所: アドレス・ベース・レジストリ(デジタル庁、CC BY 4.0)
- 姓名分割: IPAdic(BSD 3-Clause)
- 読み推定: JMnedict(EDRDG、CC BY-SA 4.0 — 派生データの再配布には ShareAlike 継承が適用)
- 法人番号: 国税庁 法人番号公表サイトのデータ
エージェントの回答にそのまま載せれば帰属義務を満たせる形にしてある。
まとめ
- 日本固有データ(読み・法人番号・六曜・住所)は LLM に断定させず、確定値 tool に委ねてモデルには呼ぶ判断だけさせる。
-
shirabe-sdk/ai(Vercel AI SDK)/shirabe-sdk/langchainの import 1 行で、その tool 群 7 本が生える。tool 定義・fetch・出典管理は書かなくていい。Python(LangChain / OpenAI Agents SDK)も PyPI の同名パッケージで同じ。 - ほぼ全 tool が API キー不要なので、
npm installから動くエージェントまで数分で到達できる。
関連リンク
- shirabe-sdk(npm)
- シリーズ既刊: #12 法人番号の選択肢比較 / #13 住所正規化の選択肢比較 / #14 法人番号 GPTs/Function Calling 統合 / #16 氏名の読み推定 統合ガイド
- Shirabe(各 API のドキュメント・OpenAPI・料金は各製品ページから)
- Zenn 版(同著者): https://zenn.dev/shirabe_dev