在 openSRE 中接入非交互式 LLM CLI:subprocess 适配层架构与完整接入指南

发布时间:2026/9/15 15:46:54
在 openSRE 中接入非交互式 LLM CLI:subprocess 适配层架构与完整接入指南 在 openSRE 中接入非交互式 LLM CLIsubprocess 适配层架构与完整接入指南【免费下载链接】opensreBuild your own AI SRE agents. The open source toolkit for the AI era.项目地址: https://gitcode.com/GitHub_Trending/op/opensre导读本指南聚焦 openSRE 中integrations/llm_cli/包它负责把 OpenAI Codex、Cursor、Claude Code、OpenCode、Kimi、Copilot、Grok、Pi 等非交互式non-interactive厂商 CLI封装成统一的可编程 LLM 客户端通过subprocess.run代替 HTTP API 完成推理调用。读完本文你将掌握该适配层的文件布局、二进制解析三阶段策略、三态登录探测logged_in协议、子进程环境白名单机制以及把一个新 CLI如未来的gemini接入 openSRE 的完整 checklist包括注册表、配置、向导与测试的接线方式。说明本文事实均来自仓库中的 integrations/llm_cli/AGENTS.md 及对应源码引用的实现细节可回到源码验证。一、为什么需要一层CLI 适配层openSRE 的 LLM 基础设施以 HTTP API 客户端为主但有一类模型服务商只提供命令行工具如codex exec、opencode、pi -p没有公开的、适合无人值守调用的 HTTP 接口或官方 CLI 才是其主推用法。为此openSRE 没有为每个 CLI 各写一套裸subprocess调用而是抽象出一个统一适配层统一探测-构建-执行-解析-失败解释五段式生命周期共享二进制解析、超时解析、环境白名单、语义化版本解析等基础设施通过 registry.py 中CLI_PROVIDER_REGISTRY一张表把LLM_PROVIDER字符串映射到适配器工厂与可选的model_env_key。从源码结构看harness_adapters.py 的_register_cli_llm_adapters()会把get_cli_provider_registration注册进 harness 提供者层任何被CLI_PROVIDER_REGISTRY收录的 key 都会被当作 CLI-backed 处理——因此文档明确要求不要在doctor.py里硬编码 provider ID。二、包布局一张表看懂每个文件文件角色base.pyLLMCLIAdapter协议、CLIProbe、CLIInvocation三个核心类型constants.py共享常量探测缓存 TTL、临时失败重试旋钮、通用超时默认值registry.pyCLI_PROVIDER_REGISTRYLLM_PROVIDER→adapter_factory 可选model_env_keysubprocess_env.py传给子进程的过滤后环境build_cli_subprocess_envvendor 前缀在此扩展env_overrides.py显式 HTTP/API 密钥在build_cli_subprocess_env会丢弃它们时合并进CLIInvocation.envtimeout_utils.py超时环境变量解析默认值 上下限钳制probe_utils.py子进程探测辅助run_version_probe执行binary --versionsemver_utils.py语义化版本解析辅助parse_semver_three_part、semver_to_tuplebinary_resolver.py可执行文件解析辅助env → PATH → 回退路径runner.pyCLIBackedLLMClientguardrails、detect()、subprocess.run、ANSI 剥离、LLMResponseagent_exec.pycoding-agent 后端使用的agentichands角色共享机制text.py把聊天式消息展平为 stdin promptflatten_messages_to_promptcodex.py参考适配器二进制解析、codex exec、--version、仅 opt-in 的login status探测opencode.py多 provider CLI--version后执行opencode auth list见_parse_opencode_auth_list_outputkimi.pykimi --print路径--version、kimi login status再回退 env/config.tomlKIMI_API_KEYcopilot.pycopilot -p路径--version然后 env token、gh auth status非默认 host 时传--hostname否则logged_inNone。不读取明文$COPILOT_HOME/config.json不做 OS keychain 探测pi_cli.pyPi CLIpi.devBYOK 多 providerpi -p打印模式--version后做基于状态的认证判断grok_cli.pyxAI Grok Build CLIgrok -p --output-format plain--version后执行grok models约 0.5 s无 LLM 调用分类认证状态除了上述模块仓库中还有 cursor.py、claude_code.py、gemini_cli.py、antigravity_cli.py、failure_explain.py、output.py、auth_check.py提供check_cli_auth(provider)供opensre命令做 CLI 认证状态检查与 errors.py 等配套文件共同构成完整适配层。三个核心类型base.pybase.py 定义了适配层的基础数据结构CLIProbe一次探测的结果字段为installed、version、logged_in、bin_path、detailCLIInvocation一次非交互子进程调用的完整描述字段为argv、stdin、cwd、env、timeout_secLLMCLIAdapterruntime_checkableProtocol每个适配器必须实现的契约包含name、binary_env_key、install_hint、auth_hint、min_version、default_exec_timeout_sec以及四个方法detect() - CLIProbe不允许抛异常build(prompt, model, workspace, reasoning_effort) - CLIInvocation返回 argv 可选 stdinparse(stdout, stderr, returncode) - str成功时提取模型回答explain_failure(stdout, stderr, returncode) - str非零退出时给出人类可读说明。文档特别强调runner不区分独立的delivery modeCLIInvocation直接携带build()产生的一切parse/explain_failure分别处理成功与非零退出。三、接入新 provider 的两条前置红线文档明确要求在合并代码之前先阅读 Subprocess environment allowlist因为如果你的 CLI 读取 vendor 专属环境变量你必须扩展subprocess_env.py中的_SAFE_SUBPROCESS_ENV_PREFIXES否则子进程看不到它们认证与配置会静默失败。实现LLMCLIAdapter后照抄文档底部的 Provider checklist 依次接线注册表、配置、向导与测试。四、二进制解析推荐模式所有适配器应统一使用binary_resolver.resolve_cli_binary(...)共享同一套解析行为。解析顺序见 binary_resolver.py 源码显式二进制环境变量PROVIDER_BIN如 Codex 的CODEX_BIN——仅当它指向一个可运行文件时才采用shutil.which(...)按平台候选名查找Windows 下candidate_binary_names会展开为.cmd、.exe、.ps1、.bat四种后缀回退安装位置default_cli_fallback_paths(...)——覆盖 npm 全局 bin含NPM_CONFIG_PREFIX与npm config get prefix结果被lru_cache缓存、Volta、pnpm、Homebrew/opt/homebrew/bin、/usr/local/bin、~/.local/bin、~/.npm-global/bin等常见位置。几个关键设计点二进制环境变量默认可选空白/非法/损坏的显式路径会被忽略并继续走 PATH 与回退——resolve_cli_binary会记录 WARNING调用diagnose_binary_path给出broken symlink / does not exist / not a file / not executable等可行动提示Codex 保持此行为用户即使不设CODEX_BIN也能运行。五、适配器约定Conventions文档总结了四条必须遵守的约定均在 runner.py 中有对应实现无 TTY调用必须适合subprocess.run的非交互会话build()不产生任何审批提示探测与运行分离detect()必须廉价CLIBackedLLMClient.invoke在执行前会再次探测走_probe()缓存未认证时快速失败并给出清晰错误。探测结果按PROBE_CACHE_TTL_SEC 45.0见 constants.py缓存避免长时间调查期间每次 invoke 都跑两次子进程探测结构化输出CLIBackedLLMClient.with_structured_output委托给StructuredOutputClientJSON-in-prompt与 API 客户端同款契约可选模型环境变量使用PROVIDER_MODEL始终可选——未设置时依赖厂商 CLI 默认值。临时失败重试与退出码语义runner.py 对子进程失败做了精细分类退出码130SIGINT/CtrlC→ 抛CLIInterruptedError让try/except Exception能捕获KeyboardInterrupt继承自BaseException会绕过普通异常处理器且不会被 Sentry 当作 bug 上报退出码75POSIXEX_TEMPFAIL→ 视为瞬时失败按TEMPFAIL_MAX_RETRIES 2、TEMPFAIL_BACKOFF_SEC 2.0指数退避重试重试耗尽后抛CLITimeoutError失败信息中出现 not logged in / api key invalid / re-authenticate / authentication failed / not authenticated 等短语 → 抛CLIAuthenticationRequired让调用方如reraise_cli_runtime_error、服务端端点获得结构化、可行动的处理而不是一条落入 Sentry 的裸RuntimeError。默认执行超时为DEFAULT_EXEC_TIMEOUT_SEC 300.0调查型 ReAct 会发送大 prompt最小/最大钳制为30.0/600.0timeout_utils.py 的resolve_timeout_from_env提供环境变量覆盖 上下限钳制 非法值回退默认的统一解析。六、Per-provider 环境变量每个新 CLI 必配Codex 是参考实现。每个 subprocess LLM 必须暴露同一形态的两个旋钮环境变量作用PROVIDER_BIN可选的厂商可执行文件显式路径。以同名作为explicit_env_key传给resolve_cli_binary(...)。缺失、空白或非法路径会被忽略PATH 回退仍会执行PROVIDER_MODEL可选模型覆盖。在registry.py的CLIProviderRegistration上注册为model_env_key。为空或未设置 → runner 省略该 flagCLI 使用其默认模型命名规则PROVIDER取自注册表 /LLM_PROVIDER字符串大写后拼接_BIN/_MODEL。示例codex→CODEX_BIN、CODEX_MODEL未来若接入gemini→GEMINI_BIN、GEMINI_MODEL。两个变量都应在适配器模块 docstring 或binary_env_key/注册条目附近的一行注释中说明保证用户与向导复制内容对齐。从 registry.py 可以看到当前注册的全部 10 个 provider 及其model_env_keycodex、cursor、claude-code、gemini-cli、antigravity-cli、opencode、kimi、copilot、grok-cli、pi。Codex 环境变量速查上述约定的实例全部可选CODEX_MODEL CODEX_BIN若CODEX_MODEL未设置codex exec使用其默认模型行为若CODEX_BIN未设置适配器解析回退到 PATH 已知安装位置。七、Subprocess 环境白名单与密钥隔离CLIBackedLLMClient只通过build_cli_subprocess_env把安全子集的环境变量传给子进程见 subprocess_env.py_SAFE_SUBPROCESS_ENV_KEYSHOME、USER/LOGNAME、USERPROFILE、APPDATA、PATH、SHELL、TMP/TEMP/TMPDIR、LANG、TERM、TZ、各类代理*_PROXY、TLS 证书变量SSL_CERT_FILE、SSL_CERT_DIR、REQUESTS_CA_BUNDLE、CURL_CA_BUNDLE、颜色控制、XDG 目录等_SAFE_SUBPROCESS_ENV_PREFIXESLC_、CODEX_、CURSOR_、CLAUDE_、GEMINI_、GOOGLE_、ANTIGRAVITY_、OPENCODE_、KIMI_、PI_。共享的 HTTP/API 覆盖放在 env_overrides.py 中用nonempty_env_values(...)配合以下元组OPENAI_PLATFORM_ENV_KEYSCodexOPENAI_API_KEY、OPENAI_ORG_ID、OPENAI_PROJECT_ID、OPENAI_BASE_URLHTTP_LLM_PROVIDER_ENV_KEYSOpenCode 等 HTTP 后端ANTHROPIC_CLI_ENV_KEYSClaude CodeANTHROPIC_API_KEY、ANTHROPIC_BASE_URL、ANTHROPIC_AUTH_TOKENCURSOR_CLI_ENV_KEYSCursor Agent headless API keyCURSOR_API_KEYXAI_CLI_ENV_KEYSGrokXAI_API_KEY、XAI_BASE_URLCOPILOT_CLI_ENV_KEYSCopilot CLI 凭据COPILOT_GITHUB_TOKEN、GH_TOKEN、GITHUB_TOKEN以及COPILOT_CLI_CONFIG_ENV_KEYSCOPILOT_HOME、COPILOT_MODEL、COPILOT_GH_HOST、GH_HOSTPI_PROVIDER_ENV_KEYSPi CLI 的 BYOK覆盖约 30 个 provider 的 API key。向LLMSettings增加 API-key 环境变量时必须同步扩展这些元组。密钥隔离的两个反例务必理解Kimi目前不使用上述元组OAuth/API 材料通过_SAFE_SUBPROCESS_ENV_PREFIXES中的KIMI_前缀整体放行KimiAdapter.build()使用CLIInvocation(envNone)依赖白名单COPILOT_刻意不是前缀条目COPILOT_GITHUB_TOKEN是 GitHub PAT若用前缀白名单放行会把该 PAT 泄漏给每一个其他 CLI 子进程Codex、Kimi、Claude Code……。Copilot 适配器改为把所有 Copilot 专属环境变量通过自己的CLIInvocation.env转发见COPILOT_CLI_ENV_KEYS/COPILOT_CLI_CONFIG_ENV_KEYS只到达 Copilot 子进程。XAI_API_KEY与 Pi 的 per-provider key 同理一律走CLIInvocation.env绝不用宽泛前缀。如果新 CLI 读取自定义环境变量如GEMINI_*必须把相应前缀加入_SAFE_SUBPROCESS_ENV_PREFIXES否则子进程收不到这些变量、认证/配置会静默失败同时添加一个测试断言所需 key 被正确转发。八、认证探测模式三态logged_indetect()必须返回带logged_in: bool | None的CLIProbe三态语义与向导行为如下值含义向导行为True二进制找到且认证确认直接继续False二进制找到但确定未认证提示用户运行登录命令auth_hintNone二进制找到但认证状态不明确网络错误、输出异常等让用户重试或重选 provider推荐探测序列针对具备安全非交互认证状态命令的 CLI运行binary --version——失败则立即返回installedFalse运行binary auth-status-command——解析 stdout/stderr 分类logged_in编写_classify_name_auth(returncode, stdout, stderr) - tuple[bool | None, str]辅助函数先检查否定短语如先看 not logged in 再看 logged in避免子串误判网络/超时错误映射为None而非False——用户可能处于不稳定连接不应被强迫重新认证。参考实现见codex.py的_classify_codex_authcodex.py依次检查 not logged in/no credentials、logged in、expired/invalid token、rate limit/quota、network/unreachable/dns/connection refused→None其余非零退出也归为None。各 CLI 的认证探测差异Codex 例外默认不运行codex login status——某些 Codex CLI 版本在检查会话时可能弹出浏览器 OAuth因此只有显式设置OPENSRE_CODEX_AUTH_STATUS_PROBE1时才执行该命令正常路径返回logged_inNone让codex exec在请求时暴露认证失败。此外当OPENAI_API_KEY存在且logged_in非True时CodexAdapter会把它提升为已认证Authenticated via OPENAI_API_KEY fallback支持 usage-based API key 认证OpenCode 是多 provider用户可能依赖auth.json、环境 API key 或两者。--version后运行opencode auth list解析报告出的凭据/环境计数让探测与 CLI 一致不要仅凭 JSON 文件推断认证Kimi--version后运行kimi login status当 CLI 未明确确认认证logged_in非True时执行_check_kimi_auth_fallback()依次检查KIMI_API_KEY、~/.kimi/config.toml或KIMI_SHARE_DIR中的 API key。纯 API-key 安装可能不在login status输出中体现 logged in回退正是为了贴合真实用法login status超时或无法生成logged_inNone时同样运行回退因此配置了 API key 仍算已认证Copilot--version后依次检查环境 token、gh auth status当COPILOT_GH_HOST/GH_HOST指向非默认 host 时传--hostname否则logged_inNone不检查明文$COPILOT_HOME/config.json不做 OS keychain 探测Grok--version后运行grok models约 0.5 秒、无 LLM 调用分类认证——在输出中查找 You are logged in/logged in with当探测结果不明确时XAI_API_KEY环境变量可提升为已认证headless/CI 回退Pi--version后基于状态判断provider API key 在 env 中否则~/.pi/agent/auth.json来自/login——Pi 没有非交互的 auth-status 命令。九、Codex 二进制解析参考实现CodexAdapter._resolve_binary的解析顺序现已委托给共享解析器见 codex.pyCODEX_BIN已设置且路径可运行 → 显式覆盖shutil.which(codex)Windows 下还包括codex.cmd/codex.ps1_fallback_codex_paths()→default_cli_fallback_paths(codex)约定的安装位置非法或空白的CODEX_BIN被忽略PATH/回退继续生效。build()构造的 argv 也值得注意codex.pycodex exec --ephemeral -s read-only --color never -C workspace非 git 仓库时追加--skip-git-repo-check模型存在时追加-m modelreasoning effort 通过-c model_reasoning_effortlevel传入最后以-结尾从 stdin 读 prompt工作区由git rev-parse --show-toplevel推断失败则用 cwd 并跳过 git 检查。十、Provider checklist复制粘贴模板新 CLI 接入的完整步骤来源AGENTS.md 底部在integrations/llm_cli/添加适配器按 Per-provider env vars 定义PROVIDER_BINPROVIDER_MODEL_resolve_binary复用resolve_cli_binary(..., explicit_env_key...)实现detect()包含--version 认证状态检查遵循上述三态logged_in模式编写_classify_name_auth——合并前用真实已登录与已登出的会话各测一遍若 CLI 读取自定义环境变量如GEMINI_*把前缀加入subprocess_env.py的_SAFE_SUBPROCESS_ENV_PREFIXES在registry.py注册 provider并在config/llm_settings.py添加相同的LLM_PROVIDER值可选在surfaces/shared/llm_setup/catalog.py添加向导 onboarding 选项在tests/integrations/llm_cli/下为 detect/build/failure 路径添加测试包括环境转发。从 tests/integrations/llm_cli/ 目录可以看到对应测试已覆盖test_codex_adapter.py、test_opencode_adapter.py、test_kimi_adapter.py、test_copilot_adapter.py、test_grok_cli_adapter.py、test_pi_adapter.py、test_cursor_adapter.py、test_claude_code_adapter.py、test_gemini_cli_adapter.py、test_antigravity_cli_adapter.py、test_runner.py、test_env_overrides.py、test_timeout_utils.py、test_probe_utils.py等。十一、测试注意事项适配器与 runner 的单元测试位于tests/integrations/llm_cli/按需 mocksubprocess/shutil.which平台相关断言必须 patchintegrations.llm_cli.binary_resolver.sys.platform而不是codex.sys.platform因为解析逻辑位于binary_resolver.pynpm_prefix_bin_dirs被lru_cache缓存测试中会改变 env 或平台的用例应在每个用例前调用npm_prefix_bin_dirs.cache_clear()或使用共享 autouse fixture避免跨测试的陈旧缓存。十二、运行时接线从注册表到实际调用最后串起整条链路从源码结构可以确认registry.py 的CLI_PROVIDER_REGISTRY维护LLM_PROVIDER → CLIProviderRegistration(adapter_factory, model_env_key)harness_adapters.py 在启动时调用_register_cli_llm_adapters()把get_cli_provider_registration与CLIBackedLLMClient构建器安装到CliLlmAdaptersauth_check.py 的check_cli_auth(provider)通过注册表拿到适配器并执行detect()返回CliAuthState每次调用时 runner.py 的CLIBackedLLMClient依次完成guardrails → 探测45 s 缓存→build()构 argv →build_cli_subprocess_env过滤环境 →subprocess.run含 tempfail 重试与超时→ ANSI 剥离 →parse提取回答 →LLMResponse支持流式时streams_plain_stdout True的适配器走invoke_stream的管道逐字符排水路径。这套设计让厂商 CLI 也能成为一等公民 LLM 后端新增 provider 只需实现一个适配器 一行注册 配置项 测试即可被opensre的 doctor、设置向导与调查型 Agent 统一识别与调用。【免费下载链接】opensreBuild your own AI SRE agents. The open source toolkit for the AI era.项目地址: https://gitcode.com/GitHub_Trending/op/opensre创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考