fix(ai): bound reasoning latency and surface transport causes
A reasoning model with 100k+ token review contexts routinely needs more than the 120s request budget, so every commit_review attempt died on a client timeout and the jobs drained into the DLQ. Add [ai].thinking_budget (0 keeps the endpoint default) to bound the reasoning phase, set a connect timeout so a bad resolved address cannot consume the whole budget, and keep the reqwest source chain in the error text instead of only its Display message. Production values raise request_timeout_secs to 600 and cap thinking at 2048.
ZhenYi committed
Modified chart/templates/_helpers.tpl chart/templates/_helpers.tpl +2 −0 View chart/templates/_helpers.tpl
@@ -96,6 +96,8 @@
9696 value: {{ .Values.ai.maxFindings | quote }}
9797- name: APP_AI__MAX_OUTPUT_TOKENS
9898 value: {{ .Values.ai.maxOutputTokens | quote }}
99- name: APP_AI__THINKING_BUDGET
100 value: {{ .Values.ai.thinkingBudget | quote }}
99101- name: APP_AI__TEMPERATURE
100102 value: {{ .Values.ai.temperature | quote }}
101103- name: APP_AI__SUMMARY_COMMIT_LIMIT
Modified chart/values.yaml chart/values.yaml +1 −0 View chart/values.yaml
@@ -209,6 +209,7 @@
209209 contextMaxBytes: 524288
210210 maxFindings: 20
211211 maxOutputTokens: 4096
212 thinkingBudget: 0
212213 temperature: "0.2"
213214 summaryCommitLimit: 50
214215 botUsername: "ai-reviewer"
Modified config/default.toml config/default.toml +1 −0 View config/default.toml
@@ -350,6 +350,7 @@
350350context_max_bytes = 524288
351351max_findings = 20
352352max_output_tokens = 4096
353thinking_budget = 0
353354temperature = 0.2
354355summary_commit_limit = 50
355356bot_username = "ai-reviewer"
Modified deploy/values/prod.yaml deploy/values/prod.yaml +2 −1 View deploy/values/prod.yaml
@@ -132,10 +132,11 @@
132132 queueTopic: "gitdata:ai"
133133 dlqTopic: "gitdata:ai:dlq"
134134 maxAttempts: 3
135 requestTimeoutSecs: 120
135 requestTimeoutSecs: 600
136136 contextMaxBytes: 524288
137137 maxFindings: 20
138138 maxOutputTokens: 4096
139 thinkingBudget: 2048
139140 temperature: "0.2"
140141 summaryCommitLimit: 50
141142 botUsername: "ai-reviewer"
Modified lib/agent/src/client.rs lib/agent/src/client.rs +30 −2 View lib/agent/src/client.rs
@@ -25,6 +25,28 @@
2525
2626pub type AgentResult<T> = Result<T, AgentError>;
2727
28/// 连接阶段单独设限:端点可能解析出多个地址,坏地址不该吃掉整个请求预算。
29const CONNECT_TIMEOUT: Duration = Duration::from_secs(10);
30
31/// reqwest 的 Display 只剩 "error sending request for url(...)",真正的原因
32/// (超时 / DNS / connect)在 source 链里,这里拼出来,否则线上只剩超时现象。
33fn transport_message(error: &reqwest::Error) -> String {
34 let kind = if error.is_timeout() {
35 "timed out"
36 } else if error.is_connect() {
37 "connect failed"
38 } else {
39 "request failed"
40 };
41 let mut message = format!("{kind}: {error}");
42 let mut cause = std::error::Error::source(error);
43 while let Some(next) = cause {
44 message = format!("{message}: {next}");
45 cause = next.source();
46 }
47 message
48}
49
2850impl AgentError {
2951 /// 该错误是否值得重投。
3052 ///
@@ -47,6 +69,8 @@
4769 max_tokens: u32,
4870 #[serde(skip_serializing_if = "Option::is_none")]
4971 response_format: Option<ResponseFormat>,
72 #[serde(skip_serializing_if = "Option::is_none")]
73 thinking_budget: Option<u32>,
5074}
5175
5276#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -109,6 +133,7 @@
109133 temperature: f32,
110134 max_output_tokens: u32,
111135 max_attempts: i32,
136 thinking_budget: Option<u32>,
112137}
113138
114139impl AgentClient {
@@ -120,6 +145,7 @@
120145 }
121146 let http = reqwest::Client::builder()
122147 .timeout(Duration::from_secs(cfg.request_timeout_secs.max(1)))
148 .connect_timeout(CONNECT_TIMEOUT)
123149 .build()
124150 .map_err(|e| AgentError::Transport(e.to_string()))?;
125151 Ok(Self {
@@ -130,6 +156,7 @@
130156 temperature: cfg.temperature,
131157 max_output_tokens: cfg.max_output_tokens,
132158 max_attempts: cfg.max_attempts.max(1),
159 thinking_budget: (cfg.thinking_budget > 0).then_some(cfg.thinking_budget),
133160 })
134161 }
135162
@@ -161,6 +188,7 @@
161188 response_format: json_mode.then(|| ResponseFormat {
162189 kind: "json_object".to_string(),
163190 }),
191 thinking_budget: self.thinking_budget,
164192 };
165193
166194 let mut last_error: Option<AgentError> = None;
@@ -196,12 +224,12 @@
196224 let response = request
197225 .send()
198226 .await
199 .map_err(|e| AgentError::Transport(e.to_string()))?;
227 .map_err(|e| AgentError::Transport(transport_message(&e)))?;
200228 let status = response.status();
201229 let text = response
202230 .text()
203231 .await
204 .map_err(|e| AgentError::Transport(e.to_string()))?;
232 .map_err(|e| AgentError::Transport(transport_message(&e)))?;
205233 if !status.is_success() {
206234 return Err(AgentError::Status {
207235 status: status.as_u16(),
Modified lib/config/src/ai.rs lib/config/src/ai.rs +3 −0 View lib/config/src/ai.rs
@@ -21,6 +21,8 @@
2121 pub context_max_bytes: usize,
2222 pub max_findings: usize,
2323 pub max_output_tokens: u32,
24 /// 思维链预算(reasoning tokens);0 表示不下发该参数,由端点自行决定。
25 pub thinking_budget: u32,
2426 pub temperature: f32,
2527 pub summary_commit_limit: u64,
2628 pub bot_username: String,
@@ -51,6 +53,7 @@
5153 context_max_bytes: 512 * 1024,
5254 max_findings: 20,
5355 max_output_tokens: 4096,
56 thinking_budget: 0,
5457 temperature: 0.2,
5558 summary_commit_limit: 50,
5659 bot_username: "ai-reviewer".to_string(),