Extend square-hole creation flow with visual asset timeout guard
This commit is contained in:
@@ -68,6 +68,7 @@ pub struct LlmTextRequest {
|
||||
pub max_tokens: Option<u32>,
|
||||
pub enable_web_search: bool,
|
||||
pub protocol: LlmTextProtocol,
|
||||
pub request_timeout_ms: Option<u64>,
|
||||
}
|
||||
|
||||
// 文本协议必须由业务请求显式选择,避免全局默认模型把不同场景混到同一上游形态。
|
||||
@@ -421,6 +422,7 @@ impl LlmTextRequest {
|
||||
max_tokens: None,
|
||||
enable_web_search: false,
|
||||
protocol: LlmTextProtocol::ChatCompletions,
|
||||
request_timeout_ms: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -451,6 +453,11 @@ impl LlmTextRequest {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_request_timeout_ms(mut self, request_timeout_ms: u64) -> Self {
|
||||
self.request_timeout_ms = Some(request_timeout_ms);
|
||||
self
|
||||
}
|
||||
|
||||
fn validate(&self) -> Result<(), LlmError> {
|
||||
if self.messages.is_empty() {
|
||||
return Err(LlmError::InvalidRequest(
|
||||
@@ -474,6 +481,14 @@ impl LlmTextRequest {
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(request_timeout_ms) = self.request_timeout_ms
|
||||
&& request_timeout_ms == 0
|
||||
{
|
||||
return Err(LlmError::InvalidRequest(
|
||||
"LLM request_timeout_ms 必须大于 0".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -484,6 +499,12 @@ impl LlmTextRequest {
|
||||
.filter(|value| !value.is_empty())
|
||||
.unwrap_or(fallback_model)
|
||||
}
|
||||
|
||||
fn resolved_request_timeout_ms(&self, fallback_timeout_ms: u64) -> u64 {
|
||||
self.request_timeout_ms
|
||||
.filter(|value| *value > 0)
|
||||
.unwrap_or(fallback_timeout_ms)
|
||||
}
|
||||
}
|
||||
|
||||
impl LlmTextProtocol {
|
||||
@@ -825,7 +846,9 @@ impl LlmClient {
|
||||
.post(url.as_str())
|
||||
.bearer_auth(self.config.api_key())
|
||||
.json(&request_body)
|
||||
.timeout(Duration::from_millis(self.config.request_timeout_ms()))
|
||||
.timeout(Duration::from_millis(
|
||||
request.resolved_request_timeout_ms(self.config.request_timeout_ms()),
|
||||
))
|
||||
.send()
|
||||
.await;
|
||||
|
||||
@@ -1592,6 +1615,48 @@ mod tests {
|
||||
assert_eq!(response.response_id.as_deref(), Some("resp_retry"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn request_text_uses_request_level_timeout_override() {
|
||||
let listener = TcpListener::bind("127.0.0.1:0").expect("listener should bind");
|
||||
let address = listener.local_addr().expect("listener should have addr");
|
||||
thread::spawn(move || {
|
||||
let (mut stream, _) = listener.accept().expect("request should connect");
|
||||
let _ = read_request(&mut stream);
|
||||
thread::sleep(StdDuration::from_millis(200));
|
||||
write_response(
|
||||
&mut stream,
|
||||
MockResponse {
|
||||
status_line: "200 OK",
|
||||
content_type: "application/json; charset=utf-8",
|
||||
body: r#"{"choices":[{"message":{"content":"too late"},"finish_reason":"stop"}]}"#
|
||||
.to_string(),
|
||||
extra_headers: Vec::new(),
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
let config = LlmConfig::new(
|
||||
LlmProvider::Ark,
|
||||
format!("http://{address}"),
|
||||
"test-key".to_string(),
|
||||
"test-model".to_string(),
|
||||
10_000,
|
||||
0,
|
||||
1,
|
||||
)
|
||||
.expect("config should be valid");
|
||||
let client = LlmClient::new(config).expect("client should be created");
|
||||
|
||||
let error = client
|
||||
.request_text(
|
||||
LlmTextRequest::single_turn("系统", "用户").with_request_timeout_ms(20),
|
||||
)
|
||||
.await
|
||||
.expect_err("request override should timeout before the global timeout");
|
||||
|
||||
assert_eq!(error, LlmError::Timeout { attempts: 1 });
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn request_text_sends_web_search_options_when_enabled() {
|
||||
let listener = TcpListener::bind("127.0.0.1:0").expect("listener should bind");
|
||||
|
||||
Reference in New Issue
Block a user