1. “Agent-Reach”不是新概念,而是CLI工具链里被低估的通信枢纽
你可能在GitHub Trending页扫到过agent-reach这个仓库名,也可能在某次调试Python CLI工具时,终端突然弹出一行reach: agent not found — retrying...。它不像click那样被写进每本Python入门书,也不像typer那样自带文档生成光环,但它真实存在于至少17个开源项目依赖树的底层——包括三个主流AI工作流编排器、两个本地LLM沙盒环境,以及一个被某家芯片厂商内部用于设备代理状态同步的私有CLI套件。
“Agent-Reach”本质上是一个轻量级、无状态、面向终端场景的代理发现与可达性协商协议实现。它不托管模型,不调度任务,不管理内存,只做一件事:在当前运行环境中,用最小开销确认“某个逻辑Agent是否就绪、可通信、能响应”。关键词里的CLI和Python并非偶然——它的设计哲学就是“命令行优先”,所有交互通过标准输入/输出完成,不绑定HTTP端口,不监听socket,不依赖Docker或systemd;而MIT License则决定了它被大量嵌入到闭源工具链中却从不声张。
我第一次接触它,是在帮客户排查一个自研RAG CLI工具启动卡顿的问题。现象是:rag-cli serve --port 8080执行后,终端停在Initializing agent context...超过42秒才继续。日志里没有报错,ps aux | grep rag显示进程确实在跑,但就是不响应。后来加-v参数才发现,它其实在反复执行agent-reach --timeout 3 --retry 5 --probe http://localhost:9001/health,而那个9001端口根本没起服务——原来开发误把Agent启动脚本注释掉了,但CLI主程序仍按约定流程调用agent-reach去探测,结果陷入默认重试循环。
这就是它的真实定位:不是功能模块,而是契约守门人。它定义了一套极简的“Agent就绪”语义——只要一个进程能响应--probe指令并返回符合格式的JSON({"status": "ready", "version": "0.4.2", "latency_ms": 12}),它就算“可达”。没有OAuth,没有JWT,没有TLS握手,甚至不需要HTTP——你可以用--probe exec:/usr/local/bin/my-agent-health-check.sh直接调用Shell脚本,只要stdout输出合法JSON就行。
提示:
agent-reach的核心价值不在“它能做什么”,而在“它拒绝做什么”。它不处理认证,不缓存状态,不记录历史,不提供UI。这种克制,恰恰让它成为CLI生态里最可靠的基础设施层组件——就像which之于路径查找,curl之于网络探测,它解决的是“我能不能跟那个东西说话”这个最原始的问题。
它和热搜词里那些codex cli、boos cli、minimax cli的本质区别在于:后者是面向用户的“能力封装体”,而agent-reach是面向开发者的“连接校验器”。当你看到python安装numpy库的方法这类搜索词泛滥时,说明大量新手正卡在环境依赖链上;而agent-reach就是那条链上第一个被触发的“健康检查探针”——它不负责装numpy,但它会告诉你“numpy装好了吗?你的Python环境能跑起来吗?”。
所以,别把它当成一个要“学会使用”的工具,而该视作一种CLI工程思维的具象化表达:任何需要跨进程协作的命令行程序,在设计之初就该思考——我的Agent如何被外部确认可达?agent-reach提供的不是唯一答案,但它是目前最轻、最稳、最易集成的那个答案。
2. 协议设计:三行JSON定义的“可达性契约”
agent-reach的协议极其朴素,朴素到初看会觉得“这也能叫协议?”——但它正是靠这种极致简化,在过去三年里零重大漏洞、零兼容性断裂地支撑了数百个生产环境CLI工具。它的全部语义,浓缩在一次成功的探测响应里:
{ "status": "ready", "version": "0.5.1", "latency_ms": 8 }就这么三字段,却承载了CLI世界里最关键的三项元信息。我们逐字段拆解其设计逻辑,以及为什么少一个都不行、多一个就冗余。
2.1status:二值判断的工程妥协
status字段只接受两个字符串值:"ready"或"unavailable"。注意,它不支持"starting"、"degraded"、"maintenance"等中间态。这是刻意为之的工程选择。
原因很现实:CLI工具的生命周期极短。一个git commit命令执行完就退出,pip install完成后进程销毁。在这种场景下,“启动中”状态毫无意义——用户不会等待,系统也不会重试。如果Agent返回"starting",agent-reach只能要么立刻失败(违背“可达性”本意),要么无限等待(破坏CLI的确定性)。所以协议强制要求:Agent必须在完全就绪、能处理首个请求时,才开始响应agent-reach探测。
实操中,这意味着Agent启动脚本的最后一步,必须是启动一个轻量HTTP server(如Flask的dev server)或监听一个Unix socket,并确保该服务能稳定返回{"status": "ready"}。我见过最典型的错误,是开发者把健康检查端点写在主服务启动前——比如Flask应用还没app.run(),就先起了一个独立的health server。结果agent-reach探测成功,但主服务崩溃,用户执行命令时直接报错。正确做法是:health端点必须与主服务共生死,用同一个进程管理。
注意:
agent-reach自身也遵循此原则。它的二进制文件执行后,若探测成功则退出码为0;若超时或返回非法JSON,则退出码为1。任何调用它的脚本,都应通过$?判断结果,而非解析stdout——这是Unix哲学的铁律。
2.2version:隐式API契约的锚点
version字段看似只是个字符串,实则是整个协议的隐式版本控制锚点。它不参与逻辑判断,但为后续演进留出安全通道。
设想这样一个场景:某天agent-reach新增了一个--strict参数,要求Agent必须返回{"status": "ready", "version": "0.6.0+", "features": ["streaming"]}。旧版Agent仍返回{"status": "ready", "version": "0.4.2"},agent-reach会怎么做?它不会报错,而是静默忽略features字段,仅校验status。因为协议规定:agent-reach永远向后兼容——它只依赖status,其他字段均为可选扩展。
但version字段让调用方有了主动权。比如你的CLI工具明确需要Streaming能力,就可以在启动时执行:
if ! agent-reach --probe http://localhost:8000/health --require-version ">=0.6.0"; then echo "Error: Agent too old, upgrade required" >&2 exit 1 fi这个--require-version参数由调用方实现(agent-reach本身不提供),但version字段的存在,让这种校验成为可能。没有它,你就只能靠status硬扛,一旦协议升级,旧Agent就会无声失效。
我在重构一个金融数据CLI时踩过这个坑。原Agent用0.3.x版本,返回{"status":"ready"};新需求要求支持增量推送,于是升级Agent到0.5.0,新增{"status":"ready","mode":"incremental"}。但调用方没校验version,导致旧版CLI误以为新Agent支持增量,结果解析失败崩溃。后来加了version校验,问题立解。
2.3latency_ms:终端体验的隐形标尺
latency_ms是唯一数值字段,单位毫秒,表示从发送探测请求到收到完整响应的时间。它不参与“可达性”判定(status才是判决依据),却是影响终端用户体验最直接的指标。
为什么重要?CLI工具的响应感,70%取决于首屏时间。用户敲下mytool run --config config.yaml,如果3秒内没输出任何文字,就会觉得“卡了”。而agent-reach的探测,往往是整个命令链的第一环。如果latency_ms高达2000ms,意味着Agent虽“可达”,但响应慢——这时调用方可以决定:是直接报错让用户检查环境,还是降级使用本地缓存,或是启动备用Agent。
更关键的是,latency_ms提供了可量化的性能基线。比如你部署一个本地LLM Agent,首次探测latency_ms是150ms;一周后变成800ms。这未必是Agent故障,但提示你:磁盘IO变慢?内存不足?模型加载策略需优化?它把模糊的“变慢了”转化成可追踪的数字。
实测中我发现一个反直觉现象:某些Agent故意将latency_ms设为固定值(如50),声称“保证响应速度”。这反而暴露了问题——真实延迟不可能恒定。健康的做法是如实上报,哪怕波动大(如{"latency_ms": 12, "status": "ready"}和{"latency_ms": 217, "status": "ready"}交替出现),因为调用方能据此做智能决策(如自动切换到低延迟Agent实例)。
3. 实战集成:从零构建一个可被agent-reach探测的Python Agent
光理解协议不够,你得亲手造一个能被agent-reach认证的Agent。这里以Python为例,演示如何用不到50行代码,构建一个符合协议、生产可用的CLI Agent。重点不是“怎么写”,而是“为什么这样写”——每一行都对应一个真实踩过的坑。
3.1 最小可行Agent:Flask + JSON Response
很多人第一反应是用FastAPI,但对CLI Agent而言,Flask的轻量和确定性更优。FastAPI的异步模型在短命CLI进程中反而增加复杂度,而Flask的同步阻塞模型,恰好匹配agent-reach的简单请求-响应范式。
# agent.py from flask import Flask, jsonify, request import time import os app = Flask(__name__) # 全局状态:模拟Agent真正就绪的标志 AGENT_READY = False @app.before_first_request def init_agent(): """模拟Agent初始化耗时操作""" global AGENT_READY print("Starting agent initialization...") # 这里放你的实际初始化逻辑:加载模型、连接数据库、预热缓存... time.sleep(2) # 模拟2秒加载 AGENT_READY = True print("Agent initialized and ready.") @app.route('/health') def health_check(): if not AGENT_READY: return jsonify({"status": "unavailable"}), 503 # 计算真实延迟:从请求开始到响应生成 start_time = time.time() # 模拟业务逻辑耗时(可选) # time.sleep(0.01) latency_ms = int((time.time() - start_time) * 1000) return jsonify({ "status": "ready", "version": "0.5.1", "latency_ms": latency_ms }) if __name__ == '__main__': port = int(os.environ.get('PORT', 8000)) app.run(host='0.0.0.0', port=port, debug=False)这段代码的关键设计点:
@app.before_first_request而非__main__中初始化:确保Health端点只在首次HTTP请求时才触发初始化。如果放在if __name__ == '__main__':里,Flask启动时就执行初始化,但此时进程可能还未完全就绪(如Gunicorn worker未fork完成),导致探测失败。AGENT_READY全局变量控制状态:避免用time.sleep()硬等。真实Agent可能依赖外部服务(如Redis、PostgreSQL),需主动轮询其可用性,再设AGENT_READY=True。latency_ms在路由内计算:必须包含从request接收到response发送的全链路时间,而非仅业务逻辑时间。agent-reach测量的是端到端延迟,这才是用户感知的真实延迟。
3.2 启动脚本:确保进程与健康检查同生命周期
光有Python脚本不够,你还得一个健壮的启动方式。常见错误是直接python agent.py &,结果Agent进程被shell回收,或信号处理不当导致agent-reach探测时进程已死。
推荐方案:用supervisord或systemd,但对开发调试,一个简单的Bash脚本更实用:
#!/bin/bash # start-agent.sh AGENT_PORT=${1:-8000} AGENT_LOG="agent.log" # 清理残留进程 pkill -f "agent.py" 2>/dev/null sleep 0.5 # 启动Agent,后台运行,重定向日志 nohup python3 agent.py --port $AGENT_PORT > $AGENT_LOG 2>&1 & AGENT_PID=$! # 等待Agent就绪(最多30秒) ATTEMPTS=0 MAX_ATTEMPTS=30 while [ $ATTEMPTS -lt $MAX_ATTEMPTS ]; do if curl -s http://localhost:$AGENT_PORT/health | jq -e '.status == "ready"' >/dev/null 2>&1; then echo "✅ Agent started successfully on port $AGENT_PORT" echo "PID: $AGENT_PID" exit 0 fi ATTEMPTS=$((ATTEMPTS + 1)) sleep 1 done echo "❌ Failed to start Agent after $MAX_ATTEMPTS attempts" echo "Check logs in $AGENT_LOG" exit 1这个脚本的价值在于:它把“Agent就绪”从一个模糊概念,变成了可编程的确定性事件。agent-reach不负责等待,它只探测;而启动脚本负责确保探测时Agent必然就绪。两者分工明确。
提示:
curl探测用jq解析JSON,比用grep更可靠。jq -e '.status == "ready"'返回非零退出码当条件不满足,while循环自然退出。这是Shell脚本里最健壮的状态等待模式。
3.3 集成agent-reach:在你的CLI中嵌入可达性校验
现在,你的Agent跑起来了。下一步,是让你的主CLI工具在执行核心逻辑前,先调用agent-reach确认其可达。以下是一个典型集成模式:
# mycli/main.py import subprocess import sys import json from pathlib import Path def check_agent_reach(agent_url: str, timeout: int = 5) -> dict: """ 调用agent-reach校验Agent可达性 返回解析后的JSON响应,或抛出异常 """ try: result = subprocess.run( ["agent-reach", "--probe", f"http://{agent_url}/health", "--timeout", str(timeout)], capture_output=True, text=True, timeout=timeout + 1 # 给agent-reach自身留1秒缓冲 ) if result.returncode != 0: raise RuntimeError(f"Agent probe failed: {result.stderr.strip()}") # 解析JSON响应 return json.loads(result.stdout.strip()) except subprocess.TimeoutExpired: raise RuntimeError(f"Agent probe timed out after {timeout}s") except json.JSONDecodeError as e: raise RuntimeError(f"Invalid JSON from agent: {e}") except FileNotFoundError: raise RuntimeError("agent-reach not found in PATH. Install with: pip install agent-reach") def main(): # 1. 读取配置,获取Agent地址 agent_url = "localhost:8000" # 或从config.yaml读取 # 2. 校验Agent可达性 try: status = check_agent_reach(agent_url) if status["status"] != "ready": print(f"⚠️ Agent status: {status['status']}") sys.exit(1) print(f"🚀 Agent {status['version']} ready (latency: {status['latency_ms']}ms)") except Exception as e: print(f"❌ Agent check failed: {e}") sys.exit(1) # 3. 执行核心业务逻辑(此时可安全调用Agent) # ... your real work here ... if __name__ == "__main__": main()这个集成的关键细节:
subprocess.run的timeout参数:必须显式设置,否则agent-reach默认3秒超时,而你的CLI可能需要更长等待(如Agent冷启动)。这里设为5秒,且subprocess自身超时设为6秒,避免竞态。capture_output=True, text=True:确保能捕获stdout/stderr,且以字符串而非bytes处理,简化JSON解析。异常分类处理:区分
subprocess.TimeoutExpired(网络问题)、FileNotFoundError(工具未安装)、JSONDecodeError(Agent返回非法格式),给用户提供精准错误信息。
我曾在线上环境遇到一个诡异问题:agent-reach探测成功,但后续调用Agent API失败。排查发现,Agent的/health端点返回{"status":"ready"},但主API端点因SSL配置错误返回500。解决方案是:在check_agent_reach后,追加一次对主API的轻量探测(如GET /api/v1/ping),形成双重校验。agent-reach解决的是“进程级可达”,而你需要的是“服务级可用”。
4. 故障排查:当agent-reach返回“unavailable”时,你在查什么?
agent-reach的输出只有两种:成功({"status":"ready"})或失败({"status":"unavailable"}或超时)。但“unavailable”背后,可能是17种完全不同的原因。作为资深CLI工程师,你不能只看结果,而要建立一套系统性的排查链路。以下是我整理的实战排查手册,按发生概率排序。
4.1 网络层:端口监听与防火墙
这是最常见、最基础的故障点。agent-reach默认探测http://localhost:8000/health,但很多Agent实际监听127.0.0.1:8000或::1:8000,而localhost在某些系统(如WSL2)可能解析为IPv6地址,导致连接失败。
排查步骤:
netstat -tuln | grep :8000或ss -tuln | grep :8000:确认端口是否真在监听- 如果输出为空 → Agent根本没启动
- 如果显示
127.0.0.1:8000→ 检查agent-reach是否用http://127.0.0.1:8000/health - 如果显示
*:8000→ 监听所有接口,问题在其他层
curl -v http://127.0.0.1:8000/health:绕过DNS,直接IP测试- 成功 → DNS或hosts文件问题(检查
/etc/hosts是否有localhost条目被篡改) - 失败 → 端口监听问题或Agent崩溃
- 成功 → DNS或hosts文件问题(检查
telnet 127.0.0.1 8000:测试TCP连通性Connected→ HTTP层问题(Agent返回非200状态码)Connection refused→ Agent未监听该端口,或监听了但bind失败(端口被占)
注意:在Docker容器中,
localhost指向容器自身,而非宿主机。如果你的CLI在宿主机运行,Agent在容器内,agent-reach必须探测容器映射的宿主机端口(如http://localhost:8001/health),而非容器内网IP。
4.2 Agent层:健康检查端点逻辑缺陷
Agent代码里最常见的错误,是健康检查端点与主服务状态脱钩。例如:
# 错误示范:Health端点永远返回ready @app.route('/health') def health_check(): return jsonify({"status": "ready"}) # ❌ 即使主服务崩溃也返回ready排查步骤:
手动访问
/health端点,对比agent-reach输出- 如果手动curl返回
{"status":"ready"},但agent-reach报unavailable→agent-reach参数错误(如URL拼写错、端口错)
- 如果手动curl返回
查看Agent日志,搜索
health或/health- 如果无日志 →
agent-reach请求根本没到达Agent(网络层问题) - 如果有日志但返回
{"status":"unavailable"}→ Agent内部逻辑判定未就绪(检查AGENT_READY变量设置位置)
- 如果无日志 →
在Agent代码中临时添加调试日志:
@app.route('/health') def health_check(): print(f"[DEBUG] Health check called at {time.time()}") # ... rest of logic观察日志是否打印。不打印 → 请求被拦截(如Nginx配置、防火墙规则);打印但返回
unavailable→ 业务逻辑问题。
我曾在一个Kubernetes集群里遇到此问题:Agent Pod日志显示/health被频繁调用,但始终返回unavailable。最终发现,Pod的livenessProbe配置了httpGet到/health,而Agent的健康检查逻辑里,有一个计数器限制每分钟最多10次探测,超过即返回unavailable。agent-reach和K8s Probe同时探测,触发了限流。解决方案:为agent-reach探测添加User-Agent头,Agent端识别后豁免限流。
4.3 环境层:Python依赖与权限冲突
agent-reach本身是Python工具,但它的探测目标(Agent)也是Python进程。当两者Python环境不一致时,极易出问题。
典型场景:
- Agent用
conda环境启动,agent-reach用系统Python安装 - Agent依赖
torch==2.0.0,而agent-reach依赖torch==1.13.1,导致共享库冲突 - Agent需要读取
/var/run/myagent.sock,但CLI进程以普通用户运行,无权限
排查步骤:
which agent-reach和which python:确认agent-reach安装路径- 如果是
~/.local/bin/agent-reach→ 用pip install --user agent-reach - 如果是
/usr/local/bin/agent-reach→ 用sudo pip install agent-reach
- 如果是
python -c "import sys; print(sys.executable)":查看Agent使用的Python解释器路径- 与
agent-reach的Python路径不同 → 环境隔离问题
- 与
strace -e trace=connect,openat -p $(pgrep -f 'agent.py') 2>&1 | head -20:跟踪Agent进程的系统调用- 查看是否尝试打开不存在的文件(如配置文件路径错误)
- 查看是否
connect到错误的地址(如数据库连接串写错)
最有效的预防措施:为Agent和CLI使用同一虚拟环境。在start-agent.sh中,明确指定Python路径:
# start-agent.sh VENV_PATH="/opt/myapp/venv" $VENV_PATH/bin/python agent.py --port 8000并在CLI的setup.py中声明install_requires=['agent-reach'],确保两者共用依赖。
4.4 协议层:JSON格式与HTTP状态码陷阱
agent-reach对响应格式极其严格。一个空格、一个换行、一个多余的逗号,都会导致JSONDecodeError,进而返回unavailable。
常见错误:
- Agent返回
{"status": "ready" }\n(末尾空格+换行) - Agent返回
{"status":"ready",}(末尾逗号,Python dict转JSON时自动添加) - Agent返回
{"status":"ready"}但HTTP状态码是500(agent-reach只读body,不校验status code)
排查步骤:
curl -i http://localhost:8000/health:查看完整HTTP响应(含headers和body)- 状态码不是
200→ Agent框架默认行为(如Flask的abort(500)),需显式设置200 - Body开头有BOM字符(
\ufeff)→ 文件编码问题,用iconv -f utf-8 -t utf-8//IGNORE agent.py修复
- 状态码不是
curl http://localhost:8000/health | python -m json.tool:用Python内置JSON工具验证格式- 如果报错
Expecting property name enclosed in double quotes→ 单引号问题(JSON只认双引号) - 如果报错
Extra data→ Body有多余内容(如日志打印混入)
- 如果报错
在Agent代码中,强制指定JSON参数:
return app.response_class( response=json.dumps({ "status": "ready", "version": "0.5.1", "latency_ms": latency_ms }, separators=(',', ':')), # 移除空格,避免格式差异 status=200, mimetype='application/json' )
记住:agent-reach不是浏览器,它不宽容。它要的是RFC 8259标准的JSON,不多不少,不松不紧。这正是它可靠的原因——没有魔法,只有确定性。
5. 进阶实践:超越HTTP,用exec和file协议探测任意进程
agent-reach的强大之处,在于它不绑定HTTP。官方文档里常被忽略的--probe exec:和--probe file:协议,才是真正释放其潜力的钥匙。它们让你能把“可达性”概念,延伸到任何能产生标准输出的进程,甚至是静态文件。
5.1--probe exec::让Shell脚本成为Agent
想象一个场景:你的Agent不是一个Web服务,而是一个需要定期执行的Python脚本,比如>#!/bin/bash # 检查data-sync.py最近一次执行是否成功(通过检查锁文件和时间戳) LOCK_FILE="/tmp/data-sync.lock" LOG_FILE="/var/log/data-sync.log" if [ ! -f "$LOCK_FILE" ]; then echo '{"status":"unavailable","reason":"lock_file_missing"}' exit 1 fi # 检查锁文件是否过期(10分钟内创建) if [ $(($(date +%s) - $(stat -c %Y "$LOCK_FILE"))) -gt 600 ]; then echo '{"status":"unavailable","reason":"lock_expired"}' exit 1 fi # 检查日志最后10行是否有ERROR if tail -n 10 "$LOG_FILE" | grep -q "ERROR"; then echo '{"status":"unavailable","reason":"log_contains_error"}' exit 1 fi # 计算延迟:从脚本启动到输出JSON的时间 START_TIME=$(date +%s.%N) LATENCY_MS=$(printf "%.0f" $(echo "($START_TIME - $START_TIME) * 1000" | bc)) echo "{\"status\":\"ready\",\"version\":\"1.0\",\"latency_ms\":$LATENCY_MS}"
赋予执行权限:chmod +x health-check.sh
在CLI中调用:
agent-reach --probe "exec:/path/to/health-check.sh" --timeout 10这个方案的优势:零网络开销,纯本地进程间通信。agent-reach直接fork/exec你的脚本,捕获stdout,无需启动HTTP server,适合资源受限的嵌入式环境或CI/CD流水线。
注意:
exec协议下,agent-reach的--timeout参数直接传递给subprocess.run的timeout,因此脚本必须在超时内完成。避免在脚本中做耗时I/O(如远程curl),应只做快速状态检查。
5.2--probe file::用文件存在性定义“就绪”
这是最轻量的协议,适用于“Agent就绪 = 某个文件存在”的场景。比如,一个机器学习训练任务完成后,会生成/model/weights.pth文件。你的推理CLI,可以在执行前确认该文件是否存在、是否非空。
使用方式:
# 探测文件存在且大小>0 agent-reach --probe "file:/model/weights.pth" --require-size ">0" # 探测文件存在且修改时间在5分钟内 agent-reach --probe "file:/tmp/agent-ready.flag" --require-mtime "<300"file协议的参数:
--require-size:支持>0,>=1024,==100等比较--require-mtime:文件修改时间距现在秒数,支持<300,>60--require-content:文件内容必须匹配正则(如--require-content "^READY$")
实战案例:我们为一个边缘计算设备开发CLI,设备启动后需加载固件。固件加载成功,会在/run/firmware-loaded创建一个空文件。CLI启动时执行:
if ! agent-reach --probe "file:/run/firmware-loaded" --require-mtime "<10"; then echo "Firmware not loaded or stale. Aborting." exit 1 fi这比轮询一个HTTP端点更高效,且100%可靠——文件系统操作是原子的,没有网络抖动。
5.3 自定义协议:扩展agent-reach的边界
agent-reach支持通过--probe custom:调用自定义探测器,只要你提供一个符合规范的可执行文件。规范很简单:接收一个参数(探测目标URI),stdout输出标准JSON,exit code 0表示成功,非0表示失败。
示例:探测Docker容器状态
#!/usr/bin/env python3 # docker-probe.py import sys import json import subprocess def main(): if len(sys.argv) != 2: print('{"status":"unavailable","reason":"missing_container_name"}') sys.exit(1) container_name = sys.argv[1] try: # 检查容器是否running result = subprocess.run( ['docker', 'inspect', '--format={{.State.Status}}', container_name], capture_output=True, text=True, timeout=5 ) if result.returncode != 0: print('{"status":"unavailable","reason":"container_not_found"}') sys.exit(1) status = result.stdout.strip() if status != "running": print(f'{{"status":"unavailable","reason":"container_{status}"}}') sys.exit(1) # 获取容器IP(可选) ip_result = subprocess.run( ['docker', 'inspect', '--format={{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}', container_name], capture_output=True, text=True ) ip = ip_result.stdout.strip() if ip_result.returncode == 0 else "unknown" print(f'{{"status":"ready","version":"docker-1.0","latency_ms":0,"ip":"{ip}"}}') except subprocess.TimeoutExpired: print('{"status":"unavailable","reason":"docker_timeout"}') sys.exit(1) except Exception as e: print(f'{{"status":"unavailable","reason":"{str(e).replace(chr(34), chr(39))}"}}') sys.exit(1) if __name__ == "__main__": main()然后这样调用:
agent-reach --probe "custom:/path/to/docker-probe.py:my-llm-container"这个机制的意义在于:agent-reach从不试图定义所有可能的Agent形态,而是提供一个可插拔的探测框架。你负责定义“什么算就绪”,它负责提供标准化的调用、超时、重试和结果解析。这种设计,让它能无缝融入任何技术栈——无论是Kubernetes Pod、Systemd Service、还是裸金属上的Shell进程。
6. 生产建议:在CI/CD和监控中嵌入agent-reach的黄金实践
agent-reach不该只在开发阶段用。把它嵌入到持续集成、部署流水线和线上监控中,才能真正发挥其作为“CLI健康守门人”的价值。以下是经过多个生产环境验证的实践。
6.1 CI流水线:构建后自动验证Agent可达性
在GitHub Actions或GitLab CI中,每次Push代码后,不仅要跑单元测试,还应启动Agent并验证其可达。这能提前捕获“代码能跑,但Agent无法启动”的集成问题。
GitHub Actions 示例:
# .github/workflows/ci.yml name: CI Pipeline on: [push, pull_request] jobs: test-and-validate: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up