kaigo-gap
kaigo_mcp
将日本的介护供需数据,作为AI智能体可用的工具公开的MCP服务器。
从 Claude Code 或 Claude Desktop 连接后,就能基于公开数据回答“这个市的特别养护老人之家够不够”。
状态:服务器・智能体・eval 均已运行。 列A(本地7B)・列B(本地9B)・列D(Claude Haiku 4.5)已测量完毕。
这是做什么的
MCP(Model Context Protocol)是让LLM拥有工具的规范。本仓库构建的是 工具这一侧,不包含LLM本身。服务器单独运行时完全不调用API,也不会产生费用。
[考える側] [このリポジトリ]
Claude Code / 自作エージェント ←stdio→ kaigo-gap サーバー
「どの道具を使うか」を判断 呼ばれたらデータを返すRelated MCP server: Japan Data MCP
工具
工具 | 用途 |
| 全国基准值。是评估个别数字的前提 |
| 按市区町村名・保险者名查询供需 |
| 按特养不足程度(充足程度)排序 |
主要指标是每100名需要介护3级以上的认定者对应的特养定员数(全国24.6人・中位数26.7人)。 定义和出处位于 kaigo_gap_analysis 一侧。
汇总计算不在这里写
数据由案4(kaigo_gap_analysis)通过 export_web.py 导出,
与公开仪表盘 所分发的
相同的 insurers.json 复制后使用。这里不重新编写除法计算。
如果在两个地方持有指标定义,当智能体的回答与仪表盘的数字不一致时,
就无法判定哪一边是正确的。案4的 dataset.py 按照“定义只在一处”的方针
编写,因此跨项目遵守该方针。更新通过 python scripts/sync_data.py 执行。
工具设计上注意的事项
只返回数字无法做出判断。 即使把“12.4”交给LLM,它也不知道是高是低, 因此在全国排名和工具说明文中都包含了全国值。
不把同名自治体硬性固定为一件。 “府中市”在东京都和广岛县都有。 同时返回两个候选,筛选交由调用方决定。
不让特养定员0被解读为“全国最差”。 符合条件的保险者有90个,全部会并列第1名。
必须附带并列数量,并加上“在小规模自治体中并不罕见,居民多使用邻近自治体的设施”这一注释。
如果没有这个注释,就会从 rank_insurers 的结果得出“新乡村是全国最差的地区”这一错误结论。
如果不把领域知识嵌入工具的响应中,每次更换调用方的模型时, 同样的误读就会再次发生。之所以放在工具侧而不是提示词中,正是出于这个原因。
使用方法
pip install -r requirements.txt
python scripts/sync_data.py # 案4からデータを取り込む
python scripts/smoke_test.py # 通信の疎通確認要从 Claude Code 使用,在此目录下启动即可读取 .mcp.json。
注册到其他客户端时的配置:
{
"mcpServers": {
"kaigo-gap": {
"command": "python",
"args": ["-m", "kaigo_mcp"],
"cwd": "/path/to/kaigo_mcp"
}
}
}写 Windows 路径时要用 \ 分隔("C:\projects\kaigo_mcp")。
在 JSON 中 \p 会成为非法转义,导致整个配置文件无法读取。
本仓库的 .mcp.json 实际上就因此坏过。
智能体
python -m kaigo_mcp.agent --list
python -m kaigo_mcp.agent "尼崎市は特養が足りてる?" --verbose工具通过MCP服务器调用(不直接import函数)。如果用import解决, 就不算通过MCP,“创建了MCP服务器”这一主张就无法被验证。
用于比较的列
列 | 模型 | 位置 | 设置 | 每题成本 | 状态 |
A | qwen2.5:7b | 主力机(CPU) | — | 0日元 | 已测量 |
B | qwen3.5:9b | RTX 5050 8GB |
| 0日元 | 已测量 |
B' | qwen3.5:9b | RTX 5050 8GB |
| 0日元 | 已测量 |
C | Qwen3.5-397B-A17B | DeepInfra | — | 约0.8日元 | 保留(注册可能需要信用卡) |
D | Claude Haiku 4.5 | Anthropic | — | 约1.4日元(实测) | 已测量 |
各列之间的差异只对应一个因素。
比较 | 能了解到什么 |
A → B | 硬件和模型世代的效果(已通过 |
B → B' | 仅设置的效果。模型和权重完全相同 |
B' → C | 仅模型规模的效果(同为 Qwen3.5 系列,已对齐) |
C → D | 模型系列的差异 |
同时保留 B 和 B',是因为只保留一边会丢失一些发现。
相同的模型・相同的权重,仅改变 num_ctx,正确率就分为 83% 和 100%。
只放 8k 会显得“本地9B很优秀”,只放 4k 会显得
“本地模型无法实用”。两者都不是真相。
列C处于保留状态。最初指的是 Qwen3-235B-A22B,但世代为3.0,与列B不一致,
而且提供也已终止。免费额度的目录会不断更换,因此使用前
要通过 /models 确认其确实存在。NVIDIA免费额度(C-alt-nvidia)也试过,
但有一次往返耗时168秒,无法用于 eval。
循环只写一条。 如果一边用SDK的工具运行器、另一边用手写, 列间的差异就无法区分是模型的差异还是循环实现的差异。 后端只负责历史记录的转换。
实测(列A・列B)
各6次・通过 python scripts/probe_tool_calling.py 测量。
列A→列B 中硬件和模型世代同时变化, 因此为了区分,将 7B 也在 GPU 机上运行(中间列)。
列A qwen2.5:7b (CPU) | qwen2.5:7b (GPU) | 列B qwen3.5:9b (GPU) | |
工具调用成功率(默认温度) | 4/6 = 67% | 5/6 = 83% | 6/6 = 100% |
工具调用成功率(温度0) | 6/6 = 100% | 6/6 = 100% | 6/6 = 100% |
指令遵循(温度0) | 0/6 = 0% | 0/6 = 0% | 6/6 = 100% |
每次耗时(预热后) | 7.6秒 | 0.4秒 | 3.2秒 |
首次(含模型加载) | 102.6秒 | 36.1秒 | 19.2秒 |
需要温度0是 7B 特有的问题
qwen2.5:7b 在默认温度下 <tool_call> 的开始标签会损坏
(olith pering 等几个字符会出现在开头),ollama 的解析器无法识别,
调用会漏到正文文本中。只留下结束标签 </tool_call> 是标志。温度0可解决。
qwen3.5:9b 在默认温度下也是 6/6。 这种损坏方式不是本地运行的一般问题,
而是该模型的世代・规模所特有的。
即使把同一个 7B 放到 GPU 机上运行,默认温度下仍会漏(5/6),
连漏的方式都相同(brtc {"name": "lookup_insurer", ...} </tool_call> — 几个字符占据开始标签的位置,只留下结束标签)。更换硬件也无法修复。 这是模型侧的习惯。
更大的差异在于“是否遵循指令”
系统提示词指示“回答前要用 get_national_baseline 确认全国基准值”。
9B 有 6/6 遵循,7B 有 0/6 一次都没遵循。
而且 9B 会在一步中并行调用两个工具。
7B 能给出正确答案,是因为拾取了工具说明文中嵌入的全国值(24.6), 并没有按照指示的步骤执行。差异不在于稳定性,而在于指令遵循, 工具越多影响越大。
起作用的是模型世代,GPU只影响速度
即使把 7B 放到 GPU 上,质量指标都没有变化。 默认温度下的标签损坏仍然存在(4/6 → 5/6,n=6 所以在波动范围内), 温度0下的指令遵循仍是 0/6。GPU 机上的 7B 在默认温度下6次中只有2次 查询了基准值,温度0下一次都没查询。
变化的只有秒数,7.6秒 → 0.4秒(约19倍)。 也就是说 A→B 的差异中,成功率和指令遵循是模型世代的效果, 速度是硬件的效果。在同一张表中完成了区分。
顺便一提,9B 比 7B 慢8倍(3.2秒 vs 0.4秒)。 即使在同一 GPU 上,并行调用两个工具并遵循指令,输出也会更长。 速度与指令遵循在这里形成了正面的权衡。
对上次关于秒数修正的进一步验证
列B的“默认 18.9秒 → 温度0 4.4秒”不是温度的效果,而是首次加载。 用改进版脚本重新采集后,首次 19.2秒・之后 3.2秒(温度0下之后为 3.9秒), 预热后温度没有差异。与列A的首次 102.6秒・之后 7.6秒结构相同, 混入首次的平均值看起来既像温度差异又像硬件差异。
完整运行时,先掉的是上下文而不是智能
不是工具调用单独,而是智能体完整跑完一道题的结果。
“尼崎市的特养够吗?” | 列A 7B (CPU) | 列B 9B (4k) | 列B' 9B (8k) |
步骤 | 2 | 2 | 3 |
秒 | 72.31 | 75〜89 | 21.6〜37.2 |
输入 / 输出令牌 | 1,648 / 171 | 1,985 / 3,121 | 3,176 / 601 |
答案 | 正确 | 空白(0/4次) | 正确(3/3次) |
先触顶的不是 VRAM,而是 ollama 的默认 num_ctx = 4096。
原本预计 9B 占用 6.6GB 会导致 KV缓存紧张,
但在到达那一步之前就被默认的上下文长度截停了。
停止方式很难察觉。第2步以 stop_reason='length' 结束,
提示词约1,150 + 输出2,946 = 正好 4,096。输出有3,121个令牌但正文是空的。
触发原因是模型自身传了错误的参数。尼崎市明明在兵库县,
却传了 lookup_insurer({'name': '尼崎市', 'pref': '大阪'}),返回0条匹配,
然后思考了2,946个令牌,在开始写答案之前就用完了。
自我恢复所需的令牌吃光了上下文。
将 num_ctx 设为 8192 可以解决,但这次 ollama ps 显示 12%/88% CPU/GPU。
8GB 装不下。4k 下得不到答案,8k 下装不进 GPU,
这就是在 RTX 5050 8GB 上运行 9B 的实际情况。
难题(在青森县列举3个市町村并与全国比较・--max-steps 8)
在 4k 下也以3步・41.06秒完成。
决定上下文的不是问题的难度,而是是否需要恢复。
是测量本身的缺陷(已修复)
修复前的 loop.py 在无工具调用时将 stopped_by = "end"。
即使被 length 截断且正文为空也会记为 "end",
所以上面的4次在记录上以“2步完成”的形式,与列A的成功相同地留存。
如果空白答案与完成看起来一样,那么排列各列就没有意义。
现在如果 length 或正文为空,则标记 truncated。
汇总查看 RunRecord.answered。CLI 会输出原因并返回退出码1。
这类缺陷,不实际踩到失败的列就发现不了。 如果只跑列A,就不会注意到。
而且这个修复本身还残留了两个漏洞。两者都在跑列C・D的瞬间生效。
表示截断的值各公司不同。 OpenAI兼容是
length,Anthropic 是max_tokens。 由于只看了length,在列D(Claude)中即使被截断也无法检测。 列B中把空白答案计为完成的那种损坏方式,会原样在列D中复发。没有考虑在工具调用中途被截断的情况。 因为判定 位于“无工具调用时”的条件内。如果参数JSON不完整就继续下一步, 只会留下工具失败的记录,原因看起来像是“模型不行”。
将 TRUNCATED_REASONS = {"length", "max_tokens"} 放在工具调用有无的判断之前。
将 max_tokens 设为 8(在工具之前截断)和 40(在工具调用中途截断)实际踩过,
同时确认了正常情况不会变成 truncated。
eval 的结果(列A・列B・列D)
6个案例・通过 python scripts/eval_agent.py 测量。判定全部使用
仅凭程序即可写出的条件(不使用 LLM-as-judge)。列B跑了3轮(18件)。
列A 7B (CPU) | 列B 9B (4k) | 列B' 9B (8k) | 列D Haiku 4.5 | |
正确率 | 5/6 = 83% | 15/18 = 83% | 18/18 = 100% | 15/18 = 83% |
完成率 | 6/6 = 100% | 15/18 = 83% | 18/18 = 100% | 18/18 = 100% |
指令遵循 | 2/6 = 33% | 18/18 = 100% | 18/18 = 100% | 17/18 = 94% |
每题秒数(中位数) | 72〜85秒 | 16.9秒 | 24.0秒 | 6.5〜8.5秒 |
成本 | 0日元 | 0日元 | 0日元 | 25日元 |
num_ctx 不是速度设置,而是正确率设置
4k 的3次失分全部是 amagasaki-basic 的 truncated/length。
机制就是上一节(完整运行时,先掉的是上下文而不是智能)
所看到的原样出现在 eval 中,并不是新的损坏方式。
通过 eval 了解到的是,相同的模型・相同的权重, 仅凭设置就让正确率分为 83% 和 100%。 8k 只是给了恢复所需的余地,模型本身没有任何变化。
不能写“本地超越了 Haiku”
在这6个案例中列B'(8k)更优(正确率 18/18 对 15/18、 指令遵循 18/18 对 17/18)。但这样解读太勉强。
只有6个案例×3轮。 样本量太小
Haiku 的失分并非性能所致。 如下面的“注释效果过强”所述, 包含读取了工具侧嵌入的注释后排除定员0的4件的轮次。 将源于我方设计的失分计为模型的弱点,是错误的
秒数是3倍(24.0秒 对 6.5〜8.5秒)。不在同一舞台上
能说的是“在这6道题中,8k的本地9B够用了”为止。 考虑到0日元且不离开本地的条件,这本身就是足够的结果。
正确率相同但内容不同(列A)
列A・列B・列D 都是正确率 83%,但构成不同。 列A是拾取了工具说明文中嵌入的全国值而偶然答对, 并没有按照指示确认基准值(指令遵循 2/6)。 列B・列D每次都查询。工具越多,这个差异越明显。
列B 的失分是上述的 truncated,不是答不出来,
而是没能写完答案。同样是83%,但没答到的原因三者各不相同。
只排列正确率这一个数字,这个差异就会消失。
工具侧的注释有效,但效果过强
rank_insurers 的响应中嵌入了“定员0在小规模自治体中并不罕见,不要单独下结论”。
列D读取后,将定员0的4件从答案中排除了(3轮中1次)。
数字与真实数据一致,不是编造的。
误读是防住了,但取而代之的是信息被隐藏了。“不要下结论”被解读为“不要提及”。 将领域知识嵌入工具响应的判断是有效的,但有时会效果过强。
今后
列B 决定同时保留 4k / 8k 两者(B 和 B')。因为仅凭设置就会产生差异,这正是主题
列C 保留。DeepInfra 注册可能需要信用卡
NVIDIA免费额度无法用于 eval(速度没有保证。有往返168秒的情况)
相关
This server cannot be deployed
Maintenance
Related MCP Connectors
Search Japanese company credit, financials, subsidies, and procurement via AI agents.
Local government intelligence for AI agents.
Japan data tools for AI agents: calendar (rokuyo), address, name splitting, corporate number lookup
Raw Japanese regulatory data for AI agents: pension, gazette, gBizINFO. x402-metered (USDC).
Related MCP Servers
- AlicenseNot gradedqualityFmaintenanceEnables LLMs to search and retrieve Japanese real estate data through unified access to the MLIT Real Estate Information Library API, supporting over 25 geospatial datasets for interactive queries.189MIT
- AlicenseNot gradedqualityDmaintenanceEnables AI agents to query Japanese public data (laws, corporations, statistics) from official government APIs, returning normalized English metadata with source attribution.1MIT
- FlicenseNot gradedqualityDmaintenanceProvides AI agents with comprehensive Japanese market intelligence through 27 MCP tools, covering corporate data, macroeconomics, financials, and environmental data from 14 integrated sources.-
- AlicenseNot gradedqualityBmaintenanceEnables AI assistants to search Japanese government subsidies and grants, including archived closed calls and recurring application periods based on historical data.10 npm1MIT