mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-11-10 21:32:43 +08:00
Fix: when agentic chat fail, show the real error (#21964)
This commit is contained in:
1 parent
1e3cb2bb29
commit
089e1ac242
2 files changed
+23
-6
No files matched your search
@@ -553,6 +553,9 @@ func Run(ctx context.Context, in Input) (string, error) {
|
||||
inputMessageIDs[adk.GetMessageID(msg)] = true
|
||||
}
|
||||
final, evidence, runErr := consumeAgentEvents(ctx, iter, in.OnDelta, in.ToolCallCounts, in.ToolCallErrors, in.ToolErrorSamples)
|
||||
if runErr != nil {
|
||||
runErr = fmt.Errorf("agentic research failed: %w", withCancellationCause(ctx, runErr))
|
||||
}
|
||||
|
||||
// Recovery, cheapest and most faithful first. A run that ended on a
|
||||
// narration tail or on a bare tool call still holds the model's own answer
|
||||
@@ -586,7 +589,7 @@ func Run(ctx context.Context, in Input) (string, error) {
|
||||
// would reintroduce the failure this call exists to survive. The
|
||||
// unbounded-work risk that WithoutCancel normally carries is covered
|
||||
// by the bounded budget attached here.
|
||||
synthCtx, synthCancel := context.WithTimeout(context.WithoutCancel(ctx), finalizeTimeout)
|
||||
synthCtx, synthCancel := context.WithTimeoutCause(context.WithoutCancel(ctx), finalizeTimeout, fmt.Errorf("final answer synthesis timed out after %s", finalizeTimeout))
|
||||
defer synthCancel()
|
||||
synthCtx = tokenizer.WithUsagePhase(synthCtx, "synth")
|
||||
synthMessages := in.Messages
|
||||
@@ -594,15 +597,17 @@ func Run(ctx context.Context, in Input) (string, error) {
|
||||
synthMessages = append([]*schema.Message{schema.SystemMessage(tmpl.Content)}, synthMessages...)
|
||||
}
|
||||
synth, synthErr := finalizeAnswer(synthCtx, synthModelFor(in), synthMessages, final, evidence)
|
||||
if synthErr == nil && strings.TrimSpace(synth) == "" {
|
||||
synthErr = errors.New("empty answer")
|
||||
}
|
||||
if synthErr != nil {
|
||||
synthErr = fmt.Errorf("final answer synthesis failed: %w", withCancellationCause(synthCtx, synthErr))
|
||||
// Without this an empty final is completely silent: the run looks
|
||||
// healthy in every other log line and the user simply gets nothing.
|
||||
common.WarnCtx(ctx, "agentic_rag: finalizeAnswer failed", zap.Error(synthErr))
|
||||
} else if strings.TrimSpace(synth) == "" {
|
||||
common.WarnCtx(ctx, "agentic_rag: finalizeAnswer returned an empty answer")
|
||||
}
|
||||
switch {
|
||||
case synthErr == nil && strings.TrimSpace(synth) != "":
|
||||
case synthErr == nil:
|
||||
// Replace rather than append: appending leaves the reader with a
|
||||
// narration paragraph followed by the real answer. The single
|
||||
// answer emission at the end of Run carries it.
|
||||
@@ -611,6 +616,8 @@ func Run(ctx context.Context, in Input) (string, error) {
|
||||
case runErr == nil:
|
||||
// Normal termination but synthesis failed — surface the reason.
|
||||
runErr = synthErr
|
||||
default:
|
||||
runErr = fmt.Errorf("%w; %w", runErr, synthErr)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -622,6 +629,16 @@ func Run(ctx context.Context, in Input) (string, error) {
|
||||
return final, runErr
|
||||
}
|
||||
|
||||
// Eino wraps ctx.Err(), which omits cancellation causes such as token exhaustion.
|
||||
func withCancellationCause(ctx context.Context, err error) error {
|
||||
if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
|
||||
if cause := context.Cause(ctx); cause != nil && !errors.Is(err, cause) {
|
||||
return fmt.Errorf("%w: %w", cause, err)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
var modelFailureRe = regexp.MustCompile(`exceeds max retries|failed on every model|short-circuited by failover cooldown|overloaded_error|status 529|insufficient balance`)
|
||||
|
||||
// consumeAgentEvents drains the agent event iterator, streaming assistant
|
||||
|
||||
@@ -107,7 +107,7 @@ func (s *ChatPipelineService) agenticRag(
|
||||
go func() {
|
||||
defer close(out)
|
||||
|
||||
runCtx, cancel := context.WithTimeout(ctx, agenticRunTimeout)
|
||||
runCtx, cancel := context.WithTimeoutCause(ctx, agenticRunTimeout, fmt.Errorf("agentic run timed out after %s", agenticRunTimeout))
|
||||
defer cancel()
|
||||
// The run's evidence registry: every tool result stamps its passages with
|
||||
// the [ID:n] handle the model cites, and buildAgenticReference resolves
|
||||
@@ -319,7 +319,7 @@ func (s *ChatPipelineService) agenticRag(
|
||||
zap.Any("tool_errors", toolErrors),
|
||||
zap.String("error", runErr.Error()),
|
||||
}, usageFields...)...)
|
||||
emitResult(AsyncChatResult{Answer: fmt.Sprintf("**ERROR**: %s", runErr.Error()), Final: true})
|
||||
emitResult(AsyncChatResult{Answer: fmt.Sprintf("**ERROR**: %s", runtime.MarkdownSafeErrorText(runErr)), Final: true})
|
||||
return
|
||||
}
|
||||
common.InfoCtx(ctx, "agentic turn done", append([]zap.Field{
|
||||
|
||||
Reference in new issue
Block a user