mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-07-16 12:47:19 +08:00
### What problem does this PR solve? - OpenTelemetry integration - Checkpoint conformance tests - State inspector API - Callbacks - A series of fault injection tests - Pregel integration tests ### Type of change - [x] Refactoring
274 lines
7.4 KiB
Go
274 lines
7.4 KiB
Go
// Package pregel provides tracing/callback wrappers around the Pregel Engine.
|
|
//
|
|
// TracedEngine wraps Engine.Run with OpenTelemetry spans and lifecycle callbacks
|
|
// without modifying the Engine struct itself.
|
|
package pregel
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"go.opentelemetry.io/otel"
|
|
"go.opentelemetry.io/otel/attribute"
|
|
"go.opentelemetry.io/otel/codes"
|
|
"go.opentelemetry.io/otel/trace"
|
|
|
|
"ragflow/internal/harness/graph/constants"
|
|
"ragflow/internal/harness/graph/types"
|
|
)
|
|
|
|
// tracedEngineTracerName is the OTel tracer name for the traced engine wrapper.
|
|
const tracedEngineTracerName = "ragflow/internal/harness/graph/pregel/traced"
|
|
|
|
// TracedEngineOption configures tracing behavior.
|
|
type TracedEngineOption func(*tracedEngineConfig)
|
|
|
|
type tracedEngineConfig struct {
|
|
enabled bool
|
|
recordArguments bool
|
|
recordResults bool
|
|
eventFilter func(string) bool
|
|
callbacks *CallbackManager
|
|
}
|
|
|
|
func defaultTracingConfig() *tracedEngineConfig {
|
|
return &tracedEngineConfig{
|
|
enabled: true,
|
|
recordArguments: true,
|
|
recordResults: true,
|
|
eventFilter: nil,
|
|
}
|
|
}
|
|
|
|
// WithTracedEngineDisabled disables tracing for a particular engine instance.
|
|
func WithTracedEngineDisabled() TracedEngineOption {
|
|
return func(c *tracedEngineConfig) { c.enabled = false }
|
|
}
|
|
|
|
// WithTracedEngineRecordArgs enables/disables argument size recording.
|
|
func WithTracedEngineRecordArgs(enabled bool) TracedEngineOption {
|
|
return func(c *tracedEngineConfig) { c.recordArguments = enabled }
|
|
}
|
|
|
|
// WithTracedEngineRecordResults enables/disables result size recording.
|
|
func WithTracedEngineRecordResults(enabled bool) TracedEngineOption {
|
|
return func(c *tracedEngineConfig) { c.recordResults = enabled }
|
|
}
|
|
|
|
// TracedEngine wraps an Engine with OpenTelemetry tracing and callbacks.
|
|
// Callbacks are managed separately (not on the Engine struct).
|
|
type TracedEngine struct {
|
|
inner *Engine
|
|
cfg *tracedEngineConfig
|
|
tracer trace.Tracer
|
|
callbacks *CallbackManager
|
|
}
|
|
|
|
// NewTracedEngine creates a new traced engine wrapper.
|
|
// When tracing is disabled, Run/RunSync still dispatch callbacks
|
|
// (if configured via WithEngineCallbacks) but do not create OTel spans.
|
|
func NewTracedEngine(inner *Engine, opts ...TracedEngineOption) *TracedEngine {
|
|
cfg := defaultTracingConfig()
|
|
for _, opt := range opts {
|
|
opt(cfg)
|
|
}
|
|
te := &TracedEngine{
|
|
inner: inner,
|
|
cfg: cfg,
|
|
}
|
|
if cfg.enabled {
|
|
te.tracer = otel.Tracer(tracedEngineTracerName)
|
|
}
|
|
if cfg.callbacks != nil {
|
|
te.callbacks = cfg.callbacks
|
|
}
|
|
return te
|
|
}
|
|
|
|
// WithEngineCallbacks sets the callback manager for the traced engine.
|
|
func WithEngineCallbacks(cb *CallbackManager) TracedEngineOption {
|
|
return func(c *tracedEngineConfig) {
|
|
c.callbacks = cb
|
|
}
|
|
}
|
|
|
|
// SetCallbacks sets the callback manager on an already-created TracedEngine.
|
|
func (te *TracedEngine) SetCallbacks(cb *CallbackManager) {
|
|
te.callbacks = cb
|
|
}
|
|
|
|
// Run executes the graph with tracing and callbacks.
|
|
func (te *TracedEngine) Run(ctx context.Context, input any, mode types.StreamMode) (<-chan any, <-chan error) {
|
|
if !te.cfg.enabled && te.callbacks == nil {
|
|
return te.inner.Run(ctx, input, mode)
|
|
}
|
|
|
|
// Extract thread ID and graph name.
|
|
threadID := extractThreadID(te.inner.config)
|
|
graphName := "state_graph"
|
|
if te.inner.graph != nil {
|
|
nodes := te.inner.graph.GetNodes()
|
|
if len(nodes) > 0 {
|
|
for name := range nodes {
|
|
graphName = "graph:" + name
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
// Start root tracing span.
|
|
var graphSpan trace.Span
|
|
if te.tracer != nil {
|
|
nodeCount := 0
|
|
if te.inner.graph != nil {
|
|
nodeCount = len(te.inner.graph.GetNodes())
|
|
}
|
|
attrs := []attribute.KeyValue{
|
|
attribute.Int(AttrGraphNodes, nodeCount),
|
|
attribute.Int(AttrRecursionLimit, te.inner.recursionLimit),
|
|
attribute.String(AttrStreamMode, string(mode)),
|
|
}
|
|
if threadID != "" {
|
|
attrs = append(attrs, attribute.String(AttrThreadID, threadID))
|
|
}
|
|
ctx, graphSpan = te.tracer.Start(ctx, SpanGraphRun,
|
|
trace.WithSpanKind(trace.SpanKindInternal),
|
|
trace.WithAttributes(attrs...),
|
|
)
|
|
}
|
|
|
|
// Dispatch OnRunStart.
|
|
if te.callbacks != nil {
|
|
te.callbacks.RunStart(ctx, graphName, threadID)
|
|
}
|
|
|
|
// Execute the inner engine.
|
|
outputCh, errCh := te.inner.Run(ctx, input, mode)
|
|
|
|
// Wrap outputCh with tracing.
|
|
if te.tracer == nil {
|
|
return outputCh, wrapErrChWithCallback(errCh, te, graphName, threadID, graphSpan)
|
|
}
|
|
|
|
tracedOutputCh := make(chan any, 100)
|
|
go func() {
|
|
defer close(tracedOutputCh)
|
|
for event := range outputCh {
|
|
te.traceEvent(ctx, event, graphSpan)
|
|
tracedOutputCh <- event
|
|
}
|
|
}()
|
|
|
|
return tracedOutputCh, wrapErrChWithCallback(errCh, te, graphName, threadID, graphSpan)
|
|
}
|
|
|
|
// RunSync executes the graph synchronously with tracing.
|
|
func (te *TracedEngine) RunSync(ctx context.Context, input any) (any, error) {
|
|
outputCh, errCh := te.Run(ctx, input, types.StreamModeValues)
|
|
// Drain outputCh.
|
|
var finalState any
|
|
for result := range outputCh {
|
|
if se, ok := result.(*StreamEvent); ok && se.Type == EventTypeFinal {
|
|
if data, ok := se.Data.(map[string]any); ok {
|
|
if state, ok := data["state"]; ok {
|
|
finalState = state
|
|
}
|
|
}
|
|
}
|
|
}
|
|
err := <-errCh
|
|
return finalState, err
|
|
}
|
|
|
|
// ---- helpers ----
|
|
|
|
// extractThreadID gets the thread ID from the engine config.
|
|
func extractThreadID(cfg *types.RunnableConfig) string {
|
|
if cfg == nil || cfg.Configurable == nil {
|
|
return ""
|
|
}
|
|
if tid, _ := cfg.Configurable[constants.ConfigKeyThreadID].(string); tid != "" {
|
|
return tid
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// traceEvent decorates a stream event with sub-spans.
|
|
func (te *TracedEngine) traceEvent(ctx context.Context, event any, rootSpan trace.Span) {
|
|
if te.tracer == nil || rootSpan == nil {
|
|
return
|
|
}
|
|
se, ok := event.(*StreamEvent)
|
|
if !ok {
|
|
return
|
|
}
|
|
switch se.Type {
|
|
case EventTypeCheckpoint:
|
|
// Checkpoint events under root span.
|
|
_, cpSpan := te.tracer.Start(ctx, SpanCheckpoint,
|
|
trace.WithSpanKind(trace.SpanKindInternal),
|
|
trace.WithAttributes(
|
|
attribute.Int(AttrStepNum, se.Step),
|
|
attribute.String(AttrNodeName, se.Node),
|
|
),
|
|
)
|
|
cpSpan.SetStatus(codes.Ok, "")
|
|
cpSpan.End()
|
|
case EventTypeInterrupt:
|
|
_, intSpan := te.tracer.Start(ctx, SpanInterrupt,
|
|
trace.WithSpanKind(trace.SpanKindInternal),
|
|
trace.WithAttributes(
|
|
attribute.Int(AttrStepNum, se.Step),
|
|
attribute.String(AttrInterruptNode, se.Node),
|
|
),
|
|
)
|
|
intSpan.SetStatus(codes.Ok, "")
|
|
intSpan.End()
|
|
case EventTypeError:
|
|
if rootSpan != nil {
|
|
rootSpan.SetStatus(codes.Error, fmt.Sprintf("%v", se.Error))
|
|
rootSpan.RecordError(se.Error)
|
|
}
|
|
case EventTypeTaskStart:
|
|
_, taskSpan := te.tracer.Start(ctx, SpanNodeExecute,
|
|
trace.WithSpanKind(trace.SpanKindInternal),
|
|
trace.WithAttributes(
|
|
attribute.Int(AttrStepNum, se.Step),
|
|
attribute.String(AttrNodeName, se.Node),
|
|
),
|
|
)
|
|
taskSpan.SetStatus(codes.Ok, "")
|
|
taskSpan.End()
|
|
}
|
|
}
|
|
|
|
// wrapErrChWithCallback wraps the error channel with callback dispatch.
|
|
func wrapErrChWithCallback(errCh <-chan error, te *TracedEngine, graphName, threadID string, graphSpan trace.Span) <-chan error {
|
|
if te.callbacks == nil && (te.tracer == nil || graphSpan == nil) {
|
|
return errCh
|
|
}
|
|
wrapped := make(chan error, 1)
|
|
go func() {
|
|
defer close(wrapped)
|
|
err, ok := <-errCh
|
|
// Dispatch callbacks.
|
|
if te.callbacks != nil {
|
|
te.callbacks.RunEnd(context.Background(), graphName, threadID, err)
|
|
}
|
|
// End root span.
|
|
if graphSpan != nil {
|
|
if err != nil {
|
|
graphSpan.SetStatus(codes.Error, err.Error())
|
|
graphSpan.RecordError(err)
|
|
} else {
|
|
graphSpan.SetStatus(codes.Ok, "")
|
|
}
|
|
graphSpan.End()
|
|
}
|
|
if ok {
|
|
wrapped <- err
|
|
}
|
|
}()
|
|
return wrapped
|
|
}
|