last30days-skill 装完跑不起来 / 是依赖版本还是 API 密钥没配?

文章导读
装完就报错,先别急着换密钥。last30days-skill 这类需要外部请求的技能包,启动失败通常落在三个位置:导入依赖时、进程读配置时、发出第一个请求时。把报错第一行和堆栈最后几层看清楚,基本能判断属于哪一类;只有确认进程已经进入请求阶段且返回 401/403,才值得去查 API 密钥。
📋 目录
  1. 壹 先读报错的第一行与堆栈落点
  2. 贰 核对运行时版本与依赖安装结果
  3. 叁 确认密钥是否被进程真正读到
  4. 肆 用一个最小请求判断鉴权是否通过
  5. 伍 把排查结论固化到启动脚本
A A

装完就报错,先别急着换密钥。last30days-skill 这类需要外部请求的技能包,启动失败通常落在三个位置:导入依赖时、进程读配置时、发出第一个请求时。把报错第一行和堆栈最后几层看清楚,基本能判断属于哪一类;只有确认进程已经进入请求阶段且返回 401/403,才值得去查 API 密钥。

先按堆栈落点分流:导入阶段失败优先查 Python 或 Node 版本、依赖是否装进当前解释器;启动后很快退出并带 ConnectionError、Timeout 或域名解析错误,优先查网络出口和端点地址;返回 401/403 或明确的鉴权错误码,再查密钥是否被进程读到。密钥排查只确认变量是否存在和读取顺序,不打印值;最小请求只验证鉴权链路。具体配置项以 skill 自带说明和代码为准,需要结合环境确认。

先读报错的第一行与堆栈落点

报错第一行给出异常类型,堆栈落点给出失败阶段。导入阶段的典型片段通常长这样:

ModuleNotFoundError: No module named 'xxx'
ImportError: cannot import name 'yyy' from 'zzz'
pydantic.errors.PydanticUserError: ...
TypeError: __init__() got an unexpected keyword argument

请求阶段则会出现网络异常或 HTTP 状态码:

requests.exceptions.ConnectionError
urllib3.exceptions.NameResolutionError
TimeoutError
401 Unauthorized / 403 Forbidden

判断方法不是看报错文字长短,而是看堆栈最后几层落在哪里:如果落在 site-packages 的导入链、importlib 或包初始化文件,且进程没有发出请求,按依赖问题处理;如果堆栈里出现 skill 自己的 client、request 或 fetch 函数,日志里带 URL 或状态码,才进入请求阶段排查。还有一个信号是进程存活时间:启动后立即退出,多数是导入或配置校验失败;运行几秒后退出,才更像请求超时或鉴权被拒。少数包会在 import 时读取密钥并抛 KeyError 或 ValidationError,这仍属于配置阶段,不要误判成网络问题。

last30days-skill 装完跑不起来 / 是依赖版本还是 API 密钥没配?

核对运行时版本与依赖安装结果

先确认当前 shell 用的解释器和 pip 是同一个环境,再谈缺包。常用检查命令如下,按项目实际运行时替换 python 为 python3 或 node:

python -V
which python
pip -V
pip show last30days-skill
pip list | grep -i last30days
node -v
npm ls last30days-skill

如果项目用 requirements.txt、pyproject.toml 或 package.json 声明依赖,先读这些文件里的版本范围,再看 pip show 的输出路径是否和 which python 一致。缺包时的报错特征比较直接:ModuleNotFoundError: No module named 'xxx',或者 ImportError: cannot import name 'yyy'。版本不匹配则常见 AttributeError: module 'xxx' has no attribute 'yyy'、pydantic 的 v1/v2 兼容报错,以及 unexpected keyword argument。遇到这些不要直接 pip install `--upgrade` 全量升级,容易把本来能用的依赖顶掉;建议在虚拟环境里按声明文件重装,并保留更新前的 pip freeze 输出以便回退。

确认密钥是否被进程真正读到

密钥问题要先排除“写在另一个 shell 会话里”。在启动进程的同一个终端、同一个用户下执行,只打印是否存在,不打印值:

last30days-skill 装完跑不起来 / 是依赖版本还是 API 密钥没配?
python -c 'import os; k="LAST30DAYS_API_KEY"; print(k, bool(os.environ.get(k)))'
test -n "${LAST30DAYS_API_KEY:-}" || echo missing
env | grep -c '^LAST30DAYS_API_KEY='

bool 为 False 表示变量不存在或为空字符串,两种情况在程序里都可能被当成没配。读取顺序一般是:进程环境变量优先,其次项目目录的 .env,再次用户级配置文件;但 .env 是否自动加载取决于 skill 是否调用 python-dotenv 或框架配置,不能默认它一定会读。用 systemd、docker run、supervisor 或 CI 启动时,变量需要在对应配置里显式传入,父 shell export 不一定会带过去。验证方式是在启动脚本内执行上面的存在性检查,而不是另开终端测试;如果存在性检查通过但程序仍报缺密钥,再去看 skill 的配置加载代码,确认它读的变量名和大小写是否与你设置的一致。

用一个最小请求判断鉴权是否通过

最小请求的目标不是跑通业务,而是把“连不上”和“密钥不对”分开。下面是一个通用骨架,base URL、路径和 header 名需要按 skill 实际接口替换:

last30days-skill 装完跑不起来 / 是依赖版本还是 API 密钥没配?
python -c 'import os, requests; base=os.environ.get("LAST30DAYS_BASE_URL","https://example.invalid").rstrip("/"); key=os.environ.get("LAST30DAYS_API_KEY",""); h=requests.get(base+"/v1/health", timeout=8); print("health", h.status_code); r=requests.get(base+"/v1/me", headers={"Authorization":"Bearer "+key}, timeout=8); print("auth", r.status_code, r.text[:200])'

先跑不需要鉴权的健康检查或根路径。如果这一步就抛 ConnectionError、NameResolutionError、Timeout 或 SSL 证书错误,说明请求没有到达服务端,属于网络出口、DNS、证书或 base URL 写错,此时改密钥没有意义。健康检查能返回 2xx,而带 Authorization 的请求返回 401 或 403,才说明鉴权链路有问题,优先回查密钥是否被进程读到、是否多了空格或换行、header 前缀是否符合接口要求。连接成功但返回 404,通常是路径或版本不对;返回 5xx 则更像服务端或端点选择问题。所有判断都以实际状态码和响应体为准,不要只看 skill 的最终报错文案。

把排查结论固化到启动脚本

把上面几步写进启动前的预检,下一次启动就能直接暴露环境问题,而不是等业务请求失败后再猜。示例脚本放在 skill 的启动入口前执行:

#!/usr/bin/env bash
set -eu
fail() { echo "[preflight] $*" >&2; exit 1; }

command -v python >/dev/null || fail "python not found"
python -c 'import sys; sys.exit(0 if sys.version_info >= (3,9) else 1)' || fail "python version too low"
python -c 'import last30days_skill' || fail "skill import failed"

test -n "${LAST30DAYS_API_KEY:-}" || fail "LAST30DAYS_API_KEY missing or empty"

python -c 'import os, requests; base=os.environ.get("LAST30DAYS_BASE_URL","https://example.invalid").rstrip("/"); r=requests.get(base+"/v1/health", timeout=8); print(r.status_code)' || fail "health check failed"

exec python -m last30days_skill "$@"

预检失败用 exit 1 退出,不要继续启动;错误信息写到 stderr,方便从日志里定位。版本阈值、模块名、健康检查路径和是否在启动时发外部请求,都要按项目实际约束调整;如果部署环境不允许启动阶段访问外部地址,可以只保留解释器版本、导入和密钥存在性检查,把网络检查放到单独的诊断命令里。预检脚本里不要 echo 密钥值,也不要把完整请求头打进日志。