1. 项目概述:为什么一个 config.toml 文件值得单独写一篇实战笔记?
“Codex 接中转站”这个说法,最近在某类技术社群里高频出现,但很多人第一次听到时会愣一下——Codex 是什么?中转站又指什么?它和 config.toml 有什么关系?其实这背后反映的是一类非常典型的工程落地断层:上游模型能力(比如 Codex 系列代码生成模型)已经开放或可调用,下游业务系统(比如内部 IDE 插件、低代码平台、自动化脚本调度器)也已就位,但中间缺一个稳定、可控、可审计、可灰度的协议桥接层。这个“中转站”,不是指某个具体产品,而是一种架构角色:它接收原始请求,做身份校验、限流熔断、上下文增强、响应格式标准化,再转发给真正的模型服务;返回后还要做日志归档、错误分类、结果缓存、敏感词过滤等后处理。AgentRouter 就是这样一个轻量但生产就绪的中转站开源实现,它不训练模型,也不托管模型,只专注做好“路由”这件事。
而 config.toml,就是 AgentRouter 的唯一配置入口。它不像 YAML 那样支持复杂嵌套结构,也不像 JSON 那样强制引号包裹字符串,TOML 的设计哲学是“人类可读优先、机器可解析可靠”,特别适合配置类场景。但正因语法简洁,很多关键语义全靠字段名和层级表达,稍有疏忽就会导致整个中转站启动失败、路由错乱、甚至把调试信息直接透传给终端用户。我见过三个真实案例:某团队把timeout_ms = 3000写成timeout_ms = "3000"(字符串类型被解析为 0),导致所有请求超时重试;另一个团队在[upstream.codex]下漏写了base_url,却误以为是模型服务不可用,花了两天排查网络策略;还有一个更隐蔽的问题——retry_policy.max_attempts = 2被设在了全局 section,实际应放在每个 upstream 的子 section 下,结果重试逻辑完全没生效。
所以这篇笔记不讲 Codex 原理,也不展开 AgentRouter 源码,就死磕 config.toml 这个文件。它不是一份说明书,而是一份从零部署、逐行验证、踩坑复盘后的配置手册。适合三类人:一是刚接手中转站运维的工程师,需要快速上线不翻车;二是想把现有模型调用统一收口的架构师,需要理解配置如何支撑治理能力;三是正在评估 AgentRouter 是否适配自己技术栈的决策者,通过配置复杂度反推其成熟度。你不需要懂 Rust(AgentRouter 是 Rust 写的),也不需要会写 TOML 解析器,只要你会复制粘贴、会改数字、会看日志,就能照着跑通。
2. 核心设计思路:config.toml 不是参数列表,而是治理策略的声明式表达
2.1 为什么选 TOML 而不是环境变量或数据库?
很多人第一反应是:“配置放环境变量不更简单?Docker 启动时-e CODEROUTER_TIMEOUT=5000一行搞定。”确实,单机测试时没问题。但一旦进入真实环境,问题立刻浮现:
- 多实例一致性难保障:K8s 部署 5 个副本,每个 pod 的环境变量必须完全一致,CI/CD 流水线稍有分支就可能漏掉某个变量;
- 动态更新成本高:想临时把限流阈值从 100 QPS 调到 200,得滚动重启所有实例,业务必然抖动;
- 审计追溯无记录:谁在什么时候改了哪个参数?改前值是多少?环境变量没有版本历史,只能靠人工记文档。
而 TOML 文件天然支持 Git 版本管理。一次git commit -m "prod: increase codex timeout to 5s for large file analysis",就能锁定变更时间、操作人、上下文。AgentRouter 启动时加载 config.toml,运行中支持SIGHUP信号热重载(需编译时启用reloadfeature),配置变更无需重启,毫秒级生效。更重要的是,TOML 的 section 结构(如[upstream.codex])天然映射“策略域”概念——你可以清晰看到:哪些配置属于流量控制(rate_limit)、哪些属于链路治理(retry_policy,circuit_breaker)、哪些属于安全边界(allowed_headers,blocked_keywords)。这不是参数堆砌,而是把 SRE 关注的可观测性、稳定性、安全性,全部编码进一份文本里。
2.2 AgentRouter 的配置分层逻辑:从全局到局部的策略继承
AgentRouter 的 config.toml 采用三级作用域设计,理解这个分层是写对配置的前提:
| 作用域层级 | TOML Section 示例 | 生效范围 | 是否可被覆盖 | 典型用途 |
|---|---|---|---|---|
| 全局层 | [server],[logging] | 整个进程生命周期 | ❌ 不可覆盖 | HTTP 服务端口、日志级别、健康检查路径 |
| 路由层 | [[route]] | 单条 HTTP 路由规则 | ✅ 可被下游 upstream 覆盖 | 请求路径匹配、HTTP 方法限制、请求头透传规则 |
| 上游层 | [upstream.codex] | 单个后端模型服务 | ✅ 可被 route 层覆盖 | 模型地址、认证方式、超时、重试、熔断 |
关键点在于:下游配置永远覆盖上游配置。例如,你在[upstream.codex]中设了timeout_ms = 3000,但在某个[[route]]里又写了upstream_timeout_ms = 8000,那么走这条路由的所有请求,都会使用 8000ms 超时,而非全局的 3000ms。这种设计让“特殊需求”无需修改全局配置——比如某条分析大文件的 API 路由需要更长超时,你只需在对应 route 块里加一行,不影响其他普通补全请求。
我实测过一个典型场景:某团队有 80% 的请求是短文本补全(<100 token),20% 是长上下文分析(>2000 token)。如果全局 timeout 设为 8000ms,短请求平均耗时 300ms,却要等满 8s 才返回,用户体验极差;若设为 2000ms,长请求又频繁超时。最终方案就是在[[route]]中按 path 匹配:path = "/v1/completions"用 2000ms,path = "/v1/analyze"用 12000ms。配置如下:
[[route]] path = "/v1/completions" method = ["POST"] upstream = "codex" upstream_timeout_ms = 2000 [[route]] path = "/v1/analyze" method = ["POST"] upstream = "codex" upstream_timeout_ms = 12000这种“按需定制”的灵活性,正是 TOML 分层设计的价值所在。
2.3 Codex 接入的核心挑战:不是连得上,而是连得稳、连得准、连得安全
Codex 类模型(泛指代码生成大模型)接入中转站,表面看只是填个 URL 和 API Key,但实际有三大隐性挑战:
第一,上下文长度与请求体膨胀的矛盾。Codex 的输入 prompt 往往包含大量注释、函数签名、错误堆栈,一个典型请求体可能达 15KB。而很多中转站默认max_request_size_bytes = 1048576(1MB),看似够用,但当并发量上来,Nginx 或 Envoy 的 buffer 配置、AgentRouter 自身的内存分配策略、甚至内核 socket buffer 都可能成为瓶颈。我在压测时发现,当并发 50+、平均请求体 12KB 时,AgentRouter 日志开始出现request body too large错误,但错误码是 413,前端无法区分是用户传太大还是中转站配置不足。解决方案是在[server]中显式加大:
[server] max_request_size_bytes = 4194304 # 4MB,留出 3 倍冗余第二,模型响应格式的非标性。官方 Codex API 返回的是标准 OpenAI 格式(含choices[0].text,usage.prompt_tokens等),但很多私有化部署的 Codex 变体,会删减字段、改名、甚至用纯文本返回。AgentRouter 默认按 OpenAI Schema 解析,遇到缺失字段会 panic。此时必须启用response_transform功能,在[upstream.codex]中配置:
[upstream.codex] # ... 其他配置 response_transform = true # 定义自定义解析脚本路径(需提前写好) transform_script = "/etc/agentrouter/transform_codex.py"该脚本需用 Python 编写,接收原始响应 body,输出标准 JSON。我写过一个兼容脚本:当检测到响应是纯文本时,自动包装成{"choices": [{"text": "xxx"}]};当缺少usage字段时,根据输入长度估算 token 数并补全。这比改 AgentRouter 源码快得多。
第三,密钥轮换与多租户隔离的落地难题。Codex 服务通常要求 Bearer Token 认证,但 Token 有效期短(如 24h),手动更新 config.toml 不现实。AgentRouter 支持auth_header+auth_token_env组合,让 Token 从环境变量注入:
[upstream.codex] auth_header = "Authorization" auth_token_env = "CODEROUTER_CODEX_TOKEN" # 启动前 export 此变量更进一步,若需支持多租户(A 团队用 Token A,B 团队用 Token B),则必须结合[[route]]的auth_header_passthrough = true,让中转站透传客户端的Authorization头,自身不做鉴权,由后端 Codex 服务完成。此时 config.toml 的核心任务,就从“填密钥”变成“管透传”。
这三层挑战说明:config.toml 的每一行,都在回答一个工程问题——不是“怎么连”,而是“连出问题时怎么兜住”。
3. config.toml 核心字段详解与实操配置指南
3.1 全局配置块:[server]与[logging]—— 中转站的“操作系统参数”
[server]是 AgentRouter 的根基配置,决定它以什么姿态暴露给外界:
[server] host = "0.0.0.0" # 必须绑定 0.0.0.0,否则容器内无法被外部访问 port = 8080 # HTTP 服务端口,建议避开 80/443(留给反向代理) health_check_path = "/healthz" # K8s liveness probe 路径,必须能快速返回 200 max_connections = 1024 # 最大并发连接数,按服务器规格调整:4C8G 机器建议 512~2048 max_request_size_bytes = 4194304 # 前文提到的 4MB,实测 12KB 平均请求下,50 并发需 ≥3MB read_timeout_ms = 10000 # 读取客户端请求头的超时,防止慢速攻击 write_timeout_ms = 10000 # 写入响应给客户端的超时,避免大响应卡住连接提示:
max_connections不是越大越好。AgentRouter 基于 Tokio 异步运行时,每个连接占用少量内存(约 100KB)。若设为 10000,理论内存占用近 1GB,但实际受系统ulimit -n限制。建议先用ulimit -n查看当前最大文件描述符数,max_connections应 ≤ 该值的 80%。
[logging]控制日志行为,直接影响问题定位效率:
[logging] level = "info" # 生产环境用 info,调试用 debug(但 debug 日志量极大) format = "json" # 强烈推荐 json,方便 ELK/Splunk 解析 output = "stdout" # Docker 场景必须 stdout,日志收集器才能捕获 include_request_id = true # 每条日志带 request_id,跨服务追踪必备 include_upstream_info = true # 记录转发给 Codex 的 URL、状态码、耗时,排障黄金字段实操心得:我在线上环境吃过亏——level = "warn"时,所有info级别日志(包括成功的请求记录)都被过滤,当 Codex 偶发 503 时,日志里只有孤零零一条错误,无法知道之前 10 分钟的请求是否正常。后来改成info,配合include_upstream_info = true,就能在 Kibana 里用upstream_status:503精准筛选,并关联查看同一request_id的完整链路日志。
3.2 上游服务配置块:[upstream.codex]—— Codex 连接的“心脏起搏器”
这是 config.toml 的核心,所有 Codex 相关策略都集中于此:
[upstream.codex] base_url = "https://api.example-codex.com/v1" # Codex 服务地址,必须带 /v1 api_key = "sk-xxxxxx" # 若用固定密钥,直接写这里(不推荐生产) # 更推荐用 auth_token_env,见前文说明 # auth_header = "Authorization" # auth_token_env = "CODEROUTER_CODEX_TOKEN" timeout_ms = 5000 # 连接 Codex 的总超时,含 DNS、TCP、TLS、HTTP connect_timeout_ms = 3000 # 仅 TCP 连接阶段超时,应 < timeout_ms idle_timeout_ms = 60000 # 连接池空闲连接保活时间,60s 合理 pool_max_idle_per_host = 100 # 每个 Codex host 的最大空闲连接数,防连接泄露 # 重试策略:Codex 偶发 503/504 是常态,必须配置 [upstream.codex.retry_policy] max_attempts = 3 # 最多重试 2 次(首次 + 2 次重试 = 3 次) min_delay_ms = 100 # 首次重试前等待 100ms max_delay_ms = 1000 # 指数退避上限 1000ms jitter = true # 启用随机抖动,防重试风暴 # 熔断器:当 Codex 持续失败,主动停止转发,保护自身和下游 [upstream.codex.circuit_breaker] failure_threshold = 5 # 连续 5 次失败触发熔断 failure_window_ms = 60000 # 60s 时间窗口内统计失败数 slow_call_threshold_ms = 3000 # 响应 >3s 视为慢调用(计入失败) open_state_duration_ms = 30000 # 熔断开启后,30s 后尝试半开参数计算过程:failure_threshold = 5不是拍脑袋。我们统计过 Codex 在高峰时段的 P99 响应时间为 2800ms,慢调用阈值设为 3000ms 合理;P999 错误率约 0.3%,即每 1000 次请求有 3 次失败。若窗口设为 60s,按 QPS=50 计算,60s 内约 3000 次请求,期望失败数 9 次。设failure_threshold = 5,能在错误率异常升高(如升至 1%)时快速熔断,避免雪崩。
注意:
circuit_breaker和retry_policy是互补关系,不是替代。重试解决瞬时抖动,熔断解决持续故障。两者必须同时启用,否则重试会加剧故障扩散。
3.3 路由规则配置块:[[route]]—— 流量调度的“交通指挥图”
[[route]]是数组,可定义多条规则,匹配顺序从上到下。每条规则是一个独立的 TOML 表:
[[route]] id = "codex-completion" # 路由唯一 ID,用于日志和监控指标 path = "/v1/completions" # 匹配路径,支持通配符 *(如 /v1/*) method = ["POST"] # 允许的 HTTP 方法 upstream = "codex" # 对应 upstream 名称 upstream_timeout_ms = 3000 # 覆盖 upstream 全局 timeout upstream_rate_limit = "100;60" # 限流:100 次/60 秒,格式 "count;window_seconds" # 请求头控制:哪些头允许透传给 Codex? allow_headers = ["Content-Type", "X-Request-ID", "X-User-ID"] block_headers = ["Cookie", "Authorization"] # 显式禁止,防密钥泄露 # 请求体改写:Codex 需要特定字段,但客户端传的是旧格式 [route.request_transform] enabled = true script = "/etc/agentrouter/rewrite_completion.py" # Python 脚本路径 # 响应体改写:统一错误码,隐藏 Codex 内部细节 [route.response_transform] enabled = true script = "/etc/agentrouter/standardize_error.py"实操要点:upstream_rate_limit = "100;60"的实现原理是令牌桶算法。AgentRouter 会在内存中为每个client IP + route ID维护一个桶,每秒添加 100/60 ≈ 1.67 个令牌。当请求到来,消耗 1 个令牌;桶空则返回 429。注意,此限流是应用层限流,不能替代网关层(如 Nginx)的全局限流,二者应分层设置:网关层限总量(如 10000 QPS),中转站层限单租户(如 100 QPS)。
常见问题:为什么path = "/v1/completions"匹配不到/v1/completions/(结尾带斜杠)?因为 AgentRouter 默认路径匹配是精确匹配,不自动处理尾部斜杠。解决方案有两个:一是在path中明确写path = "/v1/completions/";二是启用normalize_path = true(需在[server]中设置),它会自动标准化路径(移除重复斜杠、处理...)。
3.4 安全与治理配置块:[security]与[observability]—— 生产环境的“防护盾”
虽然 AgentRouter 官方 config.toml 没有预设[security]section,但通过组合已有字段,可构建强安全策略:
# 防敏感信息泄露 [upstream.codex] # 禁止将 Codex 响应中的敏感字段透传给客户端 block_response_fields = ["secret_key", "db_password", "internal_ip"] # 防恶意输入 [[route]] path = "/v1/**" # 匹配所有 v1 接口 # 请求体内容扫描(需提前部署扫描脚本) content_scan_enabled = true scan_script = "/etc/agentrouter/scan_malicious.py" # 扫描到恶意内容(如 SQL 注入特征)立即返回 400[observability]非官方 section,但可通过[[route]]的metrics_labels实现:
[[route]] id = "codex-completion" path = "/v1/completions" # 为 Prometheus 指标添加标签,便于多维分析 metrics_labels = { service = "codex-router", route = "completion", upstream = "codex" }AgentRouter 默认暴露/metrics端点(Prometheus 格式),这些标签会出现在agentrouter_upstream_request_duration_seconds等指标中。例如,查“Codex 补全接口的 P95 延迟”,PromQL 为:
histogram_quantile(0.95, sum(rate(agentrouter_upstream_request_duration_seconds_bucket{route="completion"}[5m])) by (le))4. 实操全流程:从零部署 AgentRouter 到 Codex 接入验证
4.1 环境准备与二进制获取
AgentRouter 是 Rust 编写的静态二进制,无需安装运行时。推荐从 GitHub Release 页面下载预编译包(非源码编译),省去 Rust 工具链配置:
# 下载最新版(以 v0.8.2 为例) curl -L https://github.com/agentrouter/agentrouter/releases/download/v0.8.2/agentrouter-x86_64-unknown-linux-musl.tar.gz | tar xz sudo mv agentrouter /usr/local/bin/ # 验证 agentrouter --version # 输出 agentrouter 0.8.2注意:musl 版本兼容性最好,glibc 版本在某些精简镜像(如 distroless)中可能缺失依赖。若遇
error while loading shared libraries,换 musl 版本。
创建配置目录:
sudo mkdir -p /etc/agentrouter sudo chown -R $USER:$USER /etc/agentrouter4.2 编写完整 config.toml:一份可直接运行的生产级模板
以下是我在线上环境长期使用的 config.toml,已脱敏,字段齐全,注释详尽:
# /etc/agentrouter/config.toml # AgentRouter 生产环境配置模板(Codex 接入专用) [server] host = "0.0.0.0" port = 8080 health_check_path = "/healthz" max_connections = 1024 max_request_size_bytes = 4194304 read_timeout_ms = 10000 write_timeout_ms = 10000 # 启用热重载,支持 SIGHUP 信号 enable_reload = true [logging] level = "info" format = "json" output = "stdout" include_request_id = true include_upstream_info = true # Codex 上游服务定义 [upstream.codex] base_url = "https://api.your-codex-domain.com/v1" # 使用环境变量注入密钥,更安全 auth_header = "Authorization" auth_token_env = "CODEROUTER_CODEX_TOKEN" timeout_ms = 5000 connect_timeout_ms = 3000 idle_timeout_ms = 60000 pool_max_idle_per_host = 100 # 重试策略:指数退避 + 抖动 [upstream.codex.retry_policy] max_attempts = 3 min_delay_ms = 100 max_delay_ms = 1000 jitter = true # 熔断器:保护中转站自身 [upstream.codex.circuit_breaker] failure_threshold = 5 failure_window_ms = 60000 slow_call_threshold_ms = 3000 open_state_duration_ms = 30000 # 路由规则:Codex 补全接口 [[route]] id = "codex-completion" path = "/v1/completions" method = ["POST"] upstream = "codex" upstream_timeout_ms = 3000 upstream_rate_limit = "100;60" allow_headers = ["Content-Type", "X-Request-ID", "X-User-ID"] block_headers = ["Cookie", "Authorization"] # 请求体改写:将客户端传的 "prompt" 字段,映射为 Codex 需要的 "input" [route.request_transform] enabled = true script = "/etc/agentrouter/rewrite_completion.py" # 路由规则:Codex 分析接口(长耗时) [[route]] id = "codex-analyze" path = "/v1/analyze" method = ["POST"] upstream = "codex" upstream_timeout_ms = 12000 upstream_rate_limit = "20;60" # 严格限流,防资源耗尽 allow_headers = ["Content-Type", "X-Request-ID", "X-User-ID", "X-Project-ID"] block_headers = ["Cookie", "Authorization"] # 安全加固:阻止敏感字段透传 [upstream.codex] block_response_fields = ["secret_key", "password", "token"] # 指标标签,用于 Prometheus 监控 [[route]] id = "codex-completion" path = "/v1/completions" metrics_labels = { service = "codex-router", route = "completion", upstream = "codex" } [[route]] id = "codex-analyze" path = "/v1/analyze" metrics_labels = { service = "codex-router", route = "analyze", upstream = "codex" }4.3 辅助脚本编写:rewrite_completion.py与scan_malicious.py
AgentRouter 的 transform 脚本必须是 Python 3.7+,且需满足特定输入输出格式。以下是rewrite_completion.py的完整实现:
#!/usr/bin/env python3 # /etc/agentrouter/rewrite_completion.py import json import sys def main(): # 从 stdin 读取原始请求 body(bytes) raw_input = sys.stdin.buffer.read() try: req_json = json.loads(raw_input.decode('utf-8')) except json.JSONDecodeError: # 非 JSON 请求,原样返回 sys.stdout.buffer.write(raw_input) return # 将客户端的 "prompt" 字段,重命名为 Codex 需要的 "input" if "prompt" in req_json: req_json["input"] = req_json.pop("prompt") # 添加默认参数(Codex 要求) req_json.setdefault("model", "codex-pro") req_json.setdefault("temperature", 0.2) req_json.setdefault("max_tokens", 256) # 写回 stdout(bytes) output = json.dumps(req_json, ensure_ascii=False).encode('utf-8') sys.stdout.buffer.write(output) if __name__ == "__main__": main()赋予执行权限:
sudo chmod +x /etc/agentrouter/rewrite_completion.pyscan_malicious.py示例(简化版,仅检测基础 SQL 注入特征):
#!/usr/bin/env python3 import json import sys import re def contains_sql_injection(text): patterns = [ r"(?i)select.*from", r"(?i)insert.*into", r"(?i)update.*set", r"(?i)delete.*from", r"(?i)union.*select", r"(?i)drop.*table", r"(?i)exec\s+@", r"(?i)declare\s+@" ] return any(re.search(p, text) for p in patterns) def main(): raw_input = sys.stdin.buffer.read() try: req_json = json.loads(raw_input.decode('utf-8')) except json.JSONDecodeError: # 非 JSON,视为安全 print("SAFE") return # 检查 prompt/input 字段 text_to_check = "" if "prompt" in req_json: text_to_check = str(req_json["prompt"]) elif "input" in req_json: text_to_check = str(req_json["input"]) if contains_sql_injection(text_to_check): print("MALICIOUS") # AgentRouter 会返回 400 else: print("SAFE") if __name__ == "__main__": main()4.4 启动与验证:四步确认接入成功
第一步:启动 AgentRouter
# 设置环境变量 export CODEROUTER_CODEX_TOKEN="sk-your-real-token" # 启动,前台运行(便于看日志) agentrouter --config /etc/agentrouter/config.toml # 正常启动日志: # INFO server: listening on 0.0.0.0:8080 # INFO server: health check enabled at /healthz第二步:验证健康检查
curl -v http://localhost:8080/healthz # 应返回 HTTP/1.1 200 OK,body 为 {"status":"ok"}第三步:发送测试请求
curl -X POST http://localhost:8080/v1/completions \ -H "Content-Type: application/json" \ -d '{ "prompt": "def fibonacci(n):", "max_tokens": 50 }' # 成功响应应包含 choices[0].text,且日志中能看到 upstream_status=200第四步:触发重试与熔断验证
- 临时停掉 Codex 服务,连续发 5 次请求,第 5 次后观察日志:应出现
circuit breaker opened; - 等待 30s,再发请求,日志应显示
circuit breaker half-open,然后根据结果决定是否恢复。
5. 常见问题与独家排查技巧实录
5.1 配置加载失败:TOML 语法陷阱与静默错误
问题现象:AgentRouter 启动后立即退出,日志只有Error: failed to load config,无更多细节。
排查技巧:
- TOML 不支持行尾逗号:
method = ["POST",]是非法的,必须写method = ["POST"]; - 字符串值必须用引号:
port = 8080正确,port = "8080"会被解析为字符串,导致类型错误; - 注释符号
#后必须有空格:#this is wrong会报错,# this is right才合法; - 数组元素不能跨行无逗号:以下写法错误:
正确写法:method = [ "POST" "GET" ]method = [ "POST", "GET" ]
终极诊断命令:用toml-cli工具验证语法(需npm install -g toml-cli):
toml-cli validate /etc/agentrouter/config.toml # 输出 "Valid TOML" 或具体错误行号5.2 请求 404:路由匹配失败的 3 个盲区
问题现象:curl http://localhost:8080/v1/completions返回 404,但curl http://localhost:8080/healthz正常。
排查清单:
- 路径大小写:
path = "/v1/completions"不匹配/v1/Completions。AgentRouter 默认区分大小写,若需忽略,需在[server]中加case_sensitive = false; - 尾部斜杠:如前所述,
/v1/completions不匹配/v1/completions/; - Method 不匹配:客户端用
GET,但配置中method = ["POST"]。检查 curl 是否加了-X POST。
快速验证法:临时添加一个通配路由,看是否能捕获:
[[route]] path = "/**" # 匹配所有路径 method = ["GET", "POST", "PUT", "DELETE"] upstream = "codex" # 临时指向 codex若此时能收到请求,说明是原路由规则问题。
5.3 响应延迟高:超时与重试的连锁反应
问题现象:Codex 实际响应 200ms,但客户端收到响应要 5s+。
根因分析:upstream_timeout_ms = 5000是总超时,但重试策略min_delay_ms = 100导致:第一次请求 200ms → 失败(假设网络抖动)→ 等待 100ms → 第二次请求 200ms → 失败 → 等待 200ms(指数退避)→ 第三次请求 200ms → 成功。总耗时 ≈ 200+100+200+200+200 = 900ms,远低于 5s。若达到 5s,说明重试后仍超时,第三次请求本身耗时就接近 5s。
解决方案:
- 降低
upstream_timeout_ms到 2000ms,让单次请求更快失败; - 调小
min_delay_ms到 50ms,加速重试; - 关键:检查 Codex 服务的 P99 延迟,若本身不稳定,应优化 Codex 而非中转站。
5.4 日志无 upstream_info:配置未生效的静默失效
问题现象:日志中看不到upstream_status、upstream_duration_ms等字段。
原因:[logging]中的include_upstream_info = true必须与[upstream.xxx]配置同时存在。若只写include_upstream_info = true,但没定义任何[upstream],AgentRouter 不会报错,但该字段不会输出。
验证方法: