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 {
|
for call in &calls {
|
||||||
let start = std::time::Instant::now();
|
let start = std::time::Instant::now();
|
||||||
let executor = crate::tool::ToolExecutor::new();
|
|
||||||
|
|
||||||
// Use select! loop to send heartbeat chunks at 30s intervals
|
// Spawn tool execution in a separate task to avoid blocking the
|
||||||
// during long tool execution, resetting the frontend streaming timer.
|
// tokio worker thread (git2 operations are synchronous).
|
||||||
let fut = executor.execute_batch(vec![call.clone()], &mut ctx);
|
// This allows the heartbeat timer to fire independently.
|
||||||
tokio::pin!(fut);
|
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 {
|
let results = loop {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
result = fut.as_mut() => break result,
|
res = &mut result_rx => {
|
||||||
_ = tokio::time::sleep(std::time::Duration::from_secs(30)) => {
|
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 {
|
on_chunk(AiStreamChunk {
|
||||||
content: String::new(),
|
content: String::new(),
|
||||||
done: false,
|
done: false,
|
||||||
@ -908,6 +921,17 @@ impl ChatService {
|
|||||||
async fn build_messages(&self, request: &AiRequest) -> Result<Vec<ChatRequestMessage>> {
|
async fn build_messages(&self, request: &AiRequest) -> Result<Vec<ChatRequestMessage>> {
|
||||||
let mut messages = Vec::new();
|
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();
|
let mut processed_history = Vec::new();
|
||||||
if let Some(compact_service) = &self.compact_service {
|
if let Some(compact_service) = &self.compact_service {
|
||||||
let config = CompactConfig::default();
|
let config = CompactConfig::default();
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user