fix(agent): spawn tool execution in separate task for heartbeat
Move tool execution to a spawned task so synchronous git2 operations don't block the tokio worker thread, allowing heartbeat chunks to be sent every 10s during long tool execution. Also add analysis-first reasoning prompt to system messages.
This commit is contained in:
parent
30822bbd7d
commit
03f97c9221
@ -685,17 +685,30 @@ impl ChatService {
|
||||
|
||||
for call in &calls {
|
||||
let start = std::time::Instant::now();
|
||||
let executor = crate::tool::ToolExecutor::new();
|
||||
|
||||
// Use select! loop to send heartbeat chunks at 30s intervals
|
||||
// during long tool execution, resetting the frontend streaming timer.
|
||||
let fut = executor.execute_batch(vec![call.clone()], &mut ctx);
|
||||
tokio::pin!(fut);
|
||||
// Spawn tool execution in a separate task to avoid blocking the
|
||||
// tokio worker thread (git2 operations are synchronous).
|
||||
// This allows the heartbeat timer to fire independently.
|
||||
let call_clone = call.clone();
|
||||
let mut ctx_clone = ctx.clone();
|
||||
let (result_tx, mut result_rx) = tokio::sync::oneshot::channel();
|
||||
tokio::spawn(async move {
|
||||
let executor = crate::tool::ToolExecutor::new();
|
||||
let res = executor.execute_batch(vec![call_clone], &mut ctx_clone).await;
|
||||
let _ = result_tx.send(res);
|
||||
});
|
||||
|
||||
// Send heartbeats every 10s until tool execution completes
|
||||
let heartbeat_dur = std::time::Duration::from_secs(10);
|
||||
let results = loop {
|
||||
tokio::select! {
|
||||
result = fut.as_mut() => break result,
|
||||
_ = tokio::time::sleep(std::time::Duration::from_secs(30)) => {
|
||||
res = &mut result_rx => {
|
||||
match res {
|
||||
Ok(inner) => break inner,
|
||||
Err(_) => break Err(crate::tool::ToolError::ExecutionError("tool task cancelled".into())),
|
||||
}
|
||||
},
|
||||
_ = tokio::time::sleep(heartbeat_dur) => {
|
||||
on_chunk(AiStreamChunk {
|
||||
content: String::new(),
|
||||
done: false,
|
||||
@ -908,6 +921,17 @@ impl ChatService {
|
||||
async fn build_messages(&self, request: &AiRequest) -> Result<Vec<ChatRequestMessage>> {
|
||||
let mut messages = Vec::new();
|
||||
|
||||
// Core reasoning instruction — prioritize analysis before answering.
|
||||
messages.push(ChatRequestMessage::system(
|
||||
"When receiving a question or problem, follow this reasoning process:\n\
|
||||
1. ANALYZE: Break down the question. Identify what is being asked, what context is available, and what information is missing.\n\
|
||||
2. GATHER: Use available tools (repository search, file reading, etc.) to collect relevant information before answering.\n\
|
||||
3. REASON: Synthesize the gathered information. Consider edge cases and potential issues.\n\
|
||||
4. ANSWER: Provide a clear, actionable answer based on your analysis.\n\
|
||||
\n\
|
||||
Do NOT guess or assume when tools can provide concrete answers. Always verify claims against actual code or documentation.".to_string()
|
||||
));
|
||||
|
||||
let mut processed_history = Vec::new();
|
||||
if let Some(compact_service) = &self.compact_service {
|
||||
let config = CompactConfig::default();
|
||||
|
||||
Loading…
Reference in New Issue
Block a user