feat(agent): enhance event emission for final answers and fallback handling
- Updated the AgentEngine to emit final answer events even when the LLM stops without calling the final_answer tool, ensuring the frontend can render responses correctly. - Added fallback logic in the AgentStreamHandler to emit final answers as fallback events when no answer events are streamed, improving robustness in edge cases.
This commit is contained in:
@@ -339,9 +339,24 @@ func (e *AgentEngine) executeLoop(
|
||||
state.IsComplete = true
|
||||
state.RoundSteps = append(state.RoundSteps, step)
|
||||
|
||||
// Emit final answer done marker
|
||||
// When the LLM stops without calling final_answer tool, its response content
|
||||
// was only streamed as thinking events. We must also emit it as an answer event
|
||||
// so the frontend can render it in the main content area (UI depends on answer events).
|
||||
answerID := generateEventID("answer")
|
||||
if response.Content != "" {
|
||||
e.eventBus.Emit(ctx, event.Event{
|
||||
ID: answerID,
|
||||
Type: event.EventAgentFinalAnswer,
|
||||
SessionID: sessionID,
|
||||
Data: event.AgentFinalAnswerData{
|
||||
Content: response.Content,
|
||||
Done: false,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
e.eventBus.Emit(ctx, event.Event{
|
||||
ID: generateEventID("answer-done"),
|
||||
ID: answerID,
|
||||
Type: event.EventAgentFinalAnswer,
|
||||
SessionID: sessionID,
|
||||
Data: event.AgentFinalAnswerData{
|
||||
|
||||
@@ -445,6 +445,42 @@ func (h *AgentStreamHandler) handleComplete(ctx context.Context, evt event.Event
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: if no answer events were streamed but we have a final answer,
|
||||
// emit it as answer events so the frontend can render it properly.
|
||||
// This guards against edge cases where the LLM stops without calling final_answer.
|
||||
if h.finalAnswer == "" && data.FinalAnswer != "" {
|
||||
logger.GetLogger(h.ctx).Warnf(
|
||||
"No answer events were streamed, emitting fallback answer (len=%d)", len(data.FinalAnswer),
|
||||
)
|
||||
fallbackID := fmt.Sprintf("answer-fallback-%d", time.Now().UnixMilli())
|
||||
if err := h.streamManager.AppendEvent(h.ctx, h.sessionID, h.assistantMessageID, interfaces.StreamEvent{
|
||||
ID: fallbackID,
|
||||
Type: types.ResponseTypeAnswer,
|
||||
Content: data.FinalAnswer,
|
||||
Done: false,
|
||||
Timestamp: time.Now(),
|
||||
Data: map[string]interface{}{
|
||||
"event_id": fallbackID,
|
||||
"is_fallback": true,
|
||||
},
|
||||
}); err != nil {
|
||||
logger.GetLogger(h.ctx).Errorf("Append fallback answer event failed: %v", err)
|
||||
}
|
||||
if err := h.streamManager.AppendEvent(h.ctx, h.sessionID, h.assistantMessageID, interfaces.StreamEvent{
|
||||
ID: fallbackID,
|
||||
Type: types.ResponseTypeAnswer,
|
||||
Content: "",
|
||||
Done: true,
|
||||
Timestamp: time.Now(),
|
||||
Data: map[string]interface{}{
|
||||
"event_id": fallbackID,
|
||||
"is_fallback": true,
|
||||
},
|
||||
}); err != nil {
|
||||
logger.GetLogger(h.ctx).Errorf("Append fallback answer done event failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Send completion event to stream manager so SSE can detect completion
|
||||
if err := h.streamManager.AppendEvent(h.ctx, h.sessionID, h.assistantMessageID, interfaces.StreamEvent{
|
||||
ID: evt.ID,
|
||||
|
||||
Reference in New Issue
Block a user