Skip to content

Commit f48513b

Browse files
committed
fix(code): update llm module
1 parent 4d084eb commit f48513b

3 files changed

Lines changed: 93 additions & 6 deletions

File tree

core/src/lib.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,9 @@ pub use config::{CodeConfig, ModelConfig, ModelCost, ModelLimit, ModelModalities
109109
pub use error::{CodeError, Result};
110110
pub use hooks::HookEngine;
111111
pub use llm::{
112-
AnthropicClient, Attachment, ContentBlock, ImageSource, LlmClient, LlmResponse, Message,
113-
OpenAiClient, TokenUsage,
112+
clear_http_metrics_callback, set_http_metrics_callback, AnthropicClient, Attachment,
113+
ContentBlock, HttpMetricsCallback, HttpMetricsRecord, ImageSource, LlmClient, LlmResponse,
114+
Message, OpenAiClient, TokenUsage,
114115
};
115116
pub use orchestrator::AgentSlot;
116117
pub use plugin::{Plugin, PluginContext, PluginManager, SkillPlugin};

core/src/llm/http.rs

Lines changed: 86 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,53 @@ pub struct StreamingHttpResponse {
2525
pub error_body: String,
2626
}
2727

28+
/// Information about an HTTP request for metrics collection.
29+
#[derive(Debug, Clone)]
30+
pub struct HttpMetricsRecord {
31+
/// The target URL
32+
pub url: String,
33+
/// HTTP method (currently only POST is used for LLM calls)
34+
pub method: String,
35+
/// Response status code
36+
pub status: u16,
37+
/// Request duration in milliseconds
38+
pub duration_ms: f64,
39+
/// Number of bytes sent (request body size)
40+
pub request_bytes: u64,
41+
/// Number of bytes received (response body size)
42+
pub response_bytes: u64,
43+
/// Whether this was a streaming request
44+
pub streaming: bool,
45+
}
46+
47+
/// Callback function type for HTTP metrics collection.
48+
/// The callback is called after each HTTP request completes.
49+
pub type HttpMetricsCallback = Arc<dyn Fn(HttpMetricsRecord) + Send + Sync>;
50+
51+
/// Global HTTP metrics callback registry.
52+
///
53+
/// Set this to enable HTTP metrics collection for LLM API calls.
54+
/// The callback will be invoked after each HTTP request completes.
55+
static HTTP_METRICS_CALLBACK: std::sync::RwLock<Option<HttpMetricsCallback>> =
56+
std::sync::RwLock::new(None);
57+
58+
/// Register a global HTTP metrics callback.
59+
/// The callback will be invoked after each HTTP request completes.
60+
pub fn set_http_metrics_callback(callback: HttpMetricsCallback) {
61+
*HTTP_METRICS_CALLBACK.write().unwrap() = Some(callback);
62+
}
63+
64+
/// Clear the global HTTP metrics callback.
65+
pub fn clear_http_metrics_callback() {
66+
*HTTP_METRICS_CALLBACK.write().unwrap() = None;
67+
}
68+
69+
fn maybe_record_metrics(record: HttpMetricsRecord) {
70+
if let Some(callback) = HTTP_METRICS_CALLBACK.read().unwrap().as_ref() {
71+
callback(record);
72+
}
73+
}
74+
2875
/// Abstraction over HTTP POST requests for LLM API calls.
2976
///
3077
/// Enables dependency injection for testing without hitting real HTTP endpoints.
@@ -74,6 +121,10 @@ impl HttpClient for ReqwestHttpClient {
74121
headers: Vec<(&str, &str)>,
75122
body: &serde_json::Value,
76123
) -> Result<HttpResponse> {
124+
let start = std::time::Instant::now();
125+
let request_body = serde_json::to_string(body).unwrap_or_default();
126+
let request_bytes = request_body.len() as u64;
127+
77128
tracing::debug!(
78129
"HTTP POST to {}: {}",
79130
url,
@@ -92,9 +143,24 @@ impl HttpClient for ReqwestHttpClient {
92143
.context(format!("Failed to send request to {}", url))?;
93144

94145
let status = response.status().as_u16();
95-
let body = response.text().await?;
96-
97-
Ok(HttpResponse { status, body })
146+
let response_body = response.text().await?;
147+
let response_bytes = response_body.len() as u64;
148+
let duration_ms = start.elapsed().as_secs_f64() * 1000.0;
149+
150+
maybe_record_metrics(HttpMetricsRecord {
151+
url: url.to_string(),
152+
method: "POST".to_string(),
153+
status,
154+
duration_ms,
155+
request_bytes,
156+
response_bytes,
157+
streaming: false,
158+
});
159+
160+
Ok(HttpResponse {
161+
status,
162+
body: response_body,
163+
})
98164
}
99165

100166
async fn post_streaming(
@@ -103,6 +169,10 @@ impl HttpClient for ReqwestHttpClient {
103169
headers: Vec<(&str, &str)>,
104170
body: &serde_json::Value,
105171
) -> Result<StreamingHttpResponse> {
172+
let start = std::time::Instant::now();
173+
let request_body = serde_json::to_string(body).unwrap_or_default();
174+
let request_bytes = request_body.len() as u64;
175+
106176
let mut request = self.client.post(url);
107177
for (key, value) in headers {
108178
request = request.header(key, value);
@@ -121,6 +191,19 @@ impl HttpClient for ReqwestHttpClient {
121191
.and_then(|v| v.to_str().ok())
122192
.map(String::from);
123193

194+
// For streaming, we record metrics after sending but before consuming the stream
195+
// Note: response_bytes is estimated as we can't know the full stream size upfront
196+
let duration_ms = start.elapsed().as_secs_f64() * 1000.0;
197+
maybe_record_metrics(HttpMetricsRecord {
198+
url: url.to_string(),
199+
method: "POST".to_string(),
200+
status,
201+
duration_ms,
202+
request_bytes,
203+
response_bytes: 0, // Unknown for streaming
204+
streaming: true,
205+
});
206+
124207
if (200..300).contains(&status) {
125208
let byte_stream = response
126209
.bytes_stream()

core/src/llm/mod.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ pub mod zhipu;
1313
// Re-export public types
1414
pub use anthropic::AnthropicClient;
1515
pub use factory::{create_client_with_config, LlmConfig};
16-
pub use http::{default_http_client, HttpClient, HttpResponse, StreamingHttpResponse};
16+
pub use http::{
17+
clear_http_metrics_callback, default_http_client, set_http_metrics_callback, HttpClient,
18+
HttpMetricsCallback, HttpMetricsRecord, HttpResponse, StreamingHttpResponse,
19+
};
1720
pub use openai::OpenAiClient;
1821
pub use types::*;
1922
pub use zhipu::ZhipuClient;

0 commit comments

Comments
 (0)