这次我们来看一个很实用的方向:在 macOS 菜单栏或状态栏里直接显示 LLM 用量的小扩展。项目名里写得很清楚,它用面板(panel)、胶囊条(pill)、小圆点(nub)三种形态,把 LLM 的用量信息塞进菜单栏,核心就干一件事——不用切到浏览器后台,抬眼看一眼状态栏,就知道本地模型服务是不是还活着、API 这轮大约花了多少 token、批量任务跑到什么程度。
如果你属于下面任意一类用户,这篇文章可以收藏:
- 重度调用 OpenAI、Anthropic、国内大模型 API 的开发者,想知道每天跑了多少 token、大概花了多少钱;
- 本机用 Ollama、LM Studio、OpenWebUI 跑本地模型的用户,想快速确认服务端是否在线、加载了哪些模型、有没有端口冲突;
- 打算自己写一个 macOS 菜单栏小工具的人,需要一个从架构到测试再到排错的完整参考。
这类扩展的通用逻辑并不复杂:一个常驻菜单栏的轻量 UI,加上一个定时拉取用量数据的轮询器,再加上一个或多个「数据源」适配层。本文会从核心能力、环境准备、安装启动、功能测试、接口接入、性能观察、常见排查几个维度完整展开。项目本身的具体实现可能因版本迭代有差异,但下面的思路和验证方法可以直接复用。
1. 核心能力速览
| 能力项 | 说明 |
|---|---|
| 项目定位 | macOS 菜单栏 / 状态栏 LLM 用量展示扩展 |
| 常见实现形态 | MenuBarExtra 应用、xbar/SwiftBar 脚本、Raycast 扩展 |
| 显示样式 | 面板(panel)、胶囊条(pill)、小圆点(nub)三种 |
| 主要展示内容 | 服务在线状态、模型列表、token 用量、API 调用次数、成本估算 |
| 数据来源 | 本地 LLM 服务接口、商业 API 用量接口、自定义统计服务 |
| 系统要求 | 通常要求 macOS 13 及以上(MenuBarExtra),脚本类插件可兼容旧版本,具体以项目说明为准 |
| 安装方式 | 直接安装 App / 插件脚本导入 / Homebrew 安装 / 源码构建 |
| API 能力 | 支持通过 HTTP 接口接入本地或远程 LLM 服务 |
| 批量任务 | 刷新周期可配置,适合持续轮询多个数据源 |
| 显存需求 | 不涉及显存;作为菜单栏工具,更应关注内存、CPU 占用是否足够低 |
这表里最关键的一行是「菜单栏工具更看重内存和 CPU」。它和跑模型的本体不一样,一个合格的用量监控扩展,应该在你完全没注意它的情况下工作,而不是自己变成一个吃资源的进程。
2. 适用场景与使用边界
2.1 适合谁
- API 重度用户:每天几十上百次请求,需要实时掌握 token 消耗和成本趋势,而不是等月底看账单。
- 本地 LLM 使用者:本机同时跑着 Ollama、LM Studio、OpenAI-compatible 代理等多个端口,需要一个统一状态入口。
- 自动化脚本作者:批量调用模型时,希望有一个可见指标确认「任务真的在推进」,比如每次轮询对应的 token 增量。
- Mac 菜单栏应用开发者:想了解如何把 SwiftUI 的 MenuBarExtra、脚本插件、Raycast 扩展串成一套完整方案。
2.2 使用边界与合规提醒
这类扩展本质是一个「用量显示器」,它不负责计算准确性,最终以服务商后台或本地服务日志为准。使用时要特别注意几点:
- API Key 安全:扩展要访问用量接口,就必然持有密钥。建议只授予「读取用量」的最小权限,不要把可计费、可删除的完整密钥塞进一个全局配置文件。
- 隐私边界:用量数据如果走远程统计服务,会涉及 token 元数据外发。公司内部项目、研发数据敏感的场景,建议优先用本地服务地址,数据不出本机。
- 版权与授权:如果展示的是第三方模型服务的用量,请确认服务条款允许通过第三方工具查询;如果是本地模型,模型文件的许可证也要自己核对。
- 不要本末倒置:扩展只是监控层,别让它承担计费、审计、越权操作这类高风险功能。
3. 环境准备与前置条件
无论你用的是现成扩展还是自己写,环境准备都围绕下面几项展开。
3.1 操作系统与基础工具
- macOS:至少 macOS 13(Ventura)以上,因为 SwiftUI 的
MenuBarExtra从这一版本开始可用;脚本类扩展(xbar、SwiftBar)对系统版本要求更宽松。 - Xcode Command Line Tools:从源码构建时必须安装。
xcode-select --install- Homebrew:用来安装 xbar、SwiftBar、Node.js、Python 等依赖。
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"3.2 运行时
根据扩展实现方式不同,需要准备:
- Swift/SwiftUI 应用:需要 Swift 工具链,macOS 自带即可。
- xbar / SwiftBar 脚本:脚本本身用 Bash、Python 或 Node 都行,需要对应解释器。
- Raycast 扩展:需要安装 Raycast,并用
npm初始化扩展工程。
3.3 LLM 服务地址
- 本地服务:Ollama 默认监听
http://127.0.0.1:11434,LM Studio 默认监听http://127.0.0.1:1234,OpenWebUI 默认监听http://127.0.0.1:8080。 - 远程 API:需要记录服务商提供的 Base URL、API Key、模型名。
- 代理环境:如果你本机有 HTTP 代理,需要确认扩展进程能读到代理配置,否则可能报连接超时。
3.4 磁盘与网络
这类扩展体积很小,磁盘占用通常在几 MB 到几十 MB 之间。网络方面,本地服务走 loopback 即可,远程 API 需要出网权限。
4. 安装部署与启动方式
安装方式取决于项目发布形态。下面是四种常见路径,按需选择。
4.1 直接安装 App
如果项目提供已编译的.app或.dmg,下载后拖入「应用程序」目录即可。首次启动如果遇到「无法打开,因为无法验证开发者」提示,需要到「系统设置 -> 隐私与安全性」中手动允许。菜单栏图标出现即启动成功。
4.2 通过 Homebrew 安装
如果项目已发布到 Homebrew,可尝试:
brew install --cask <package-name>注意:<package-name>需要替换成项目实际发布的 cask 名称。安装后从启动台打开,授权通知权限即可。
4.3 以 xbar / SwiftBar 脚本方式
这是最轻量的接入方式,适合不想装完整 App、只想要一个状态栏文本条的用户。安装 xbar 或 SwiftBar 后,把插件脚本放到对应插件目录,设置执行权限:
# xbar 插件目录通常在 ~/Library/Application Support/xbar/plugins/ chmod +x ~/Library/Application\ Support/xbar/plugins/llm-usage.1m.sh脚本内容示意(以本地 Ollama 为数据源):
#!/bin/bash # llm-usage.1m.sh # 按 1 分钟刷新,xbar/SwiftBar 可识别文件名中的间隔 OLLAMA_HOST="${OLLAMA_HOST:-http://127.0.0.1:11434}" curl -s --max-time 3 "$OLLAMA_HOST/api/tags" | python3 -c " import json, sys try: data = json.load(sys.stdin) models = data.get('models', []) print(f'LLM: {len(models)} models') for m in models: print(f'-- {m.get(\"name\", \"\")}') except Exception: print('LLM: offline') " || echo "LLM: offline"在 xbar 插件列表里刷新后,菜单栏会出现一条类似LLM: 3 models的文本,点击展开能看到具体模型名。这种方式的优点是不需要编译、改一行脚本就能换数据源。
4.4 从源码构建 SwiftUI 应用
如果项目提供 Swift 源码,可以用 Xcode 直接打开工程,选择签名目标后运行。核心入口一般是这样的结构:
import SwiftUI @main struct LLMUsageBarApp: App { var body: some Scene { MenuBarExtra("LLM Usage") { UsagePanelView() } .menuBarExtraStyle(.window) } }上面代码中MenuBarExtra构建菜单栏常驻入口,.menuBarExtraStyle(.window)对应「面板」形态。如果你是开发者,后续想做成「胶囊条」或「小圆点」,改的是UsagePanelView内部布局和menuBarExtraStyle,整体架构不需要动。
4.5 启动后第一件事
服务起来后,第一件事不是看界面,而是确认三个东西:
- 菜单栏出现图标 / 文本;
- 日志窗口没有报「API key 缺失」;
- 扩展能连到目标 LLM 服务。
如果这三步都过了,再谈样式和体验优化。
5. 功能测试与效果验证
功能测试建议按「从简到繁」的顺序做,不要一上来就接一堆数据源。
5.1 测试数据源连通性
先不打开扩展,直接用 curl 验证服务是否可达:
curl -s --max-time 5 http://127.0.0.1:11434/api/tags | head -c 500如果返回 JSON 且包含models字段,说明本地服务正常。如果报错或超时,扩展大概率也会显示 offline。
判断成功的标准:返回内容字段结构清晰,能解析出模型名列表。
5.2 验证菜单栏显示
进入扩展界面,确认:
- 菜单栏出现预期图标或文本;
- 展开后有至少一项数据(例如模型数量或 token 用量);
- 数据刷新周期符合配置(例如 1 分钟刷新一次)。
如果什么都不显示,先看扩展日志,通常问题出在数据源地址或权限。
5.3 验证「服务离线」场景
把本地服务停掉,观察扩展行为:
- 是否显示「offline」而不是卡在旧数据?
- 图标是否变化(例如变为空心点)?
- 服务恢复后,扩展是否能自动恢复显示,还是需要手动刷新?
这一项很关键。一个合格的用量监控扩展,必须能优雅处理数据源宕机,而不是把「最后一次成功的数据」一直挂在那里,让用户误以为服务正常。
5.4 验证 token 增量
接好商业 API 后,可以连续调用几次模型,观察扩展里的 token 数值是否相应增加。这一步是验证「用量统计」核心功能的重点。如果数值不变,优先检查:
- 用的是不是真实的用量接口;
- 接口返回的字段与扩展解析逻辑是否匹配;
- 刷新周期是不是太长。
5.5 长周期稳定性测试
让扩展持续运行 24 小时以上,观察:
- 菜单栏图标是否偶发消失;
- 内存占用是否持续上涨;
- 日志里是否有反复重试报错。
稳定性测试建议配合第 7 章的资源占用观察一起做。
6. 接口 API 与批量任务
LLM 用量扩展的价值,很大程度体现在它的 API 接入灵活性上。
6.1 数据源接口的常见结构
可以分三类理解:
- 本地模型服务:Ollama 的
/api/tags、/api/ps,LM Studio 的/v1/models,OpenAI-compatible 的/v1/models。 - 商业 API 用量接口:各服务商提供的用量查询接口,多数需要鉴权,参数结构变化较快,以服务商文档为准。
- 自定义统计服务:自己搭的 token 计数服务,返回任意字段,扩展按配置文件解析。
6.2 curl 调用示例
以 OpenAI-compatible 接口为例,结构通常是:
curl -s http://127.0.0.1:1234/v1/models \ -H "Authorization: Bearer $LLM_API_KEY" \ --max-time 5本地 LM Studio 默认端口是1234,不需要鉴权也可以先试。如果返回401,再确认 API Key 是否正确。
6.3 Python 轮询脚本示例
当扩展需要同时监控多个数据源时,用一个 Python 脚本统一拉取、再交给菜单栏展示,是更工程化的做法:
import time import requests OLLAMA_URL = "http://127.0.0.1:11434/api/tags" REMOTE_URL = "https://api.example.com/v1/models" API_KEY = "YOUR_READONLY_KEY" REFRESH_SECONDS = 60 def fetch(url, headers=None): try: resp = requests.get(url, headers=headers, timeout=5) resp.raise_for_status() return resp.json() except Exception as exc: return {"error": str(exc)} if __name__ == "__main__": while True: local = fetch(OLLAMA_URL) remote = fetch(REMOTE_URL, {"Authorization": f"Bearer {API_KEY}"}) print("local:", local.get("error") or f"{len(local.get('models', []))} models") print("remote:", remote.get("error") or f"{len(remote.get('data', []))} models") time.sleep(REFRESH_SECONDS)注意:上面的REMOTE_URL和API_KEY只是示例,实际请求路径、鉴权头、返回字段必须以服务商文档为准。真实项目里不要把密钥硬编码在脚本里,建议读取环境变量。
6.4 批量任务与失败重试
如果扩展负责监控的不止一个服务,而是多个服务器上的模型实例,推荐设计一份任务清单:
{ "refresh_interval_seconds": 60, "tasks": [ { "name": "local-ollama", "type": "ollama", "url": "http://127.0.0.1:11434/api/tags" }, { "name": "remote-api", "type": "openai-compatible", "url": "http://127.0.0.1:1234/v1/models" }, { "name": "usage-api", "type": "custom", "url": "http://127.0.0.1:9000/api/usage" } ] }批量任务设计上要遵守三条原则:
- 每个任务独立超时,一个任务失败不影响其他任务;
- 失败重试要加退避策略,不要每秒重试把服务打挂;
- 输出要带时间戳,方便回查。
7. 资源占用与性能观察
菜单栏工具最容易被吐槽的就是「装了之后风扇狂转」。所以资源占用必须单独观察。
7.1 观察方法
打开「活动监视器」,按内存或 CPU 排序,找到扩展进程,观察这几个指标:
- CPU 占用:空闲时应接近 0%,刷新瞬间可以短暂升高,但不应持续超过 5%。
- 内存占用:纯脚本插件通常 < 50 MB;完整 SwiftUI App 几十到一百多 MB 都算正常,持续上涨则需要警惕。
- 网络请求频率:如果 1 分钟刷新一次,请求密度很低;如果刷新频率调到秒级,就要考虑是否并发拉取。
7.2 刷新频率对性能的影响
刷新频率直接决定资源占用:
| 刷新间隔 | 适合场景 | 注意事项 |
|---|---|---|
| 5-10 秒 | 本地调试、关注服务在线状态 | 注意本地服务日志会有大量访问记录 |
| 30-60 秒 | 日常用量监控 | 最推荐,兼顾实时性和资源占用 |
| 5 分钟以上 | 只看每日成本汇总 | 几乎无压力 |
7.3 如何降低占用
- 拉取远程 API 时,本地不缓存大响应,只解析需要的字段;
- 不要在每次刷新时重建视图,视图更新逻辑用 diff 判断;
- 远程 API 失败时,设置指数退避,而不是每次都全量请求。
总体判断原则:扩展应该「感知不到存在」。如果它让你产生了明显的卡顿、发热、网络占用,说明配置或实现需要优化。
8. 常见问题与排查方法
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 菜单栏不显示图标 | 扩展未启动、系统菜单栏被折叠 | 在「菜单栏」看是否有隐藏箭头;查看扩展日志 | 重新启动扩展,或到系统设置里允许菜单栏项目 |
| 一直显示 offline | 本地 LLM 服务未启动、端口错误 | 用 curl 直接访问数据源地址 | 确认服务端口,修正扩展配置 |
| 端口 11434 报 bind 冲突 | 本机已有 Ollama 实例占用了端口 | lsof -i :11434查看占用进程 | 关闭旧进程,或让新服务换端口 |
| 用量数值不变 | 刷新周期太长、接口字段解析错误 | 手动调用接口,对比返回字段 | 调整刷新周期,修正解析逻辑 |
| 远程 API 调用失败 | API Key 失效、代理未配置、防火墙拦截 | 先 curl 验证,再查扩展日志 | 更新密钥、配置代理、检查出网 |
| 安装提示无法验证开发者 | Gatekeeper 拦截未签名应用 | 查看「系统设置 -> 隐私与安全性」 | 手动允许,或使用签名版本 |
| 扩展 CPU 占用高 | 刷新过频、脚本有死循环 | 在活动监视器确认占用进程 | 调大刷新间隔,检查脚本逻辑 |
| 菜单栏文字与 UI 重叠 | 菜单栏宽度不够 | 看是否为小圆点样式被遮挡 | 换成面板样式或缩短显示文案 |
这里的核心排查思路是「先接口后界面」。遇到任何显示问题,都先绕过扩展、用 curl 或浏览器直接访问数据源,把问题定位在「扩展自身」还是「数据源」上,再决定下一步。
9. 最佳实践与使用建议
9.1 先小规模验证
第一次接入时,只接一个本地数据源,刷新间隔设 60 秒,确认基础链路没问题,再接入商业 API 和自定义统计服务。不要一上来就配五个任务,出问题很难定位。
9.2 密钥和配置分离
把 API Key、Base URL、刷新间隔做成独立配置文件,用环境变量注入:
export LLM_API_KEY="readonly-key" export LLM_BASE_URL="http://127.0.0.1:11434"并把配置文件加入.gitignore,避免误提交到仓库。
9.3 输出带时间戳
无论是脚本还是日志,输出统一带上时间戳。批量任务回查时,没有时间戳的日志基本等于没有日志。
9.4 数据源尽量本地优先
对于公司内部、研发环境,优先用本地 Ollama 或内网代理地址。用量数据不出本机,隐私风险最低。
9.5 不要承担超出「展示」的职责
扩展定位是展示用量,不要让它承担计费、自动扩容、权限管理等高危操作。高风险动作应该由独立的、有完整审计的后台任务负责。
9.6 涉及人脸、声音、版权素材的内容
如果这个 MaC 扩展接入的 LLM 服务涉及图像识别、声音克隆或数字人相关 API 用量展示,一定在测试环境验证,并确认相关素材、肖像、声音已获得授权。这类合规要求与工具本身的监控能力无关,但实际操作中很容易被忽略。
10. 总结与下一步
这个项目方向最值得尝试的点是:用极小的成本,把 LLM 用量从「事后查账单」变成「实时可见」。尤其是本地 Ollama 加商业 API 混用的用户,一个菜单栏小工具就能统一掌握全部模型服务的状态。
拿到项目后,建议先做的事:
- 确认 macOS 版本,挑一个安装路径(独立 App 或脚本插件);
- 用 curl 把数据源连通性测试做一遍;
- 跑通基础显示和刷新,再做多个数据源和批量任务;
- 最后才调样式:面板、胶囊条、小圆点各试一遍,挑一个不遮挡菜单栏的。
最容易踩的坑有三个:端口冲突导致服务起不来、API Key 直接硬编码在配置里、刷新频率调得太高把菜单栏工具变成性能杀手。按本文第 8 章的排查思路,绝大多数问题都能快速收敛。
后续可以扩展的方向包括:多模型服务的统一成本统计、按月按天的用量曲线、超预算阈值时自动推送系统通知、把数据导出到本地 InfluxDB 或 Grafana 做长周期可视化。先从「把用量显示出来」开始,一步步往完整监控体系上靠。