// Package pregel provides remote execution support for Pregel. package pregel import ( "bytes" "context" "encoding/json" "fmt" "io" "net/http" "time" "ragflow/internal/harness/graph/types" ) // RemoteRunnable executes nodes remotely via HTTP. type RemoteRunnable struct { url string headers map[string]string httpClient *http.Client timeout time.Duration } // RemoteConfig configures a remote runnable. type RemoteConfig struct { URL string Headers map[string]string Timeout time.Duration HTTPClient *http.Client } // NewRemoteRunnable creates a new remote runnable. func NewRemoteRunnable(config *RemoteConfig) *RemoteRunnable { timeout := config.Timeout if timeout == 0 { timeout = 30 * time.Second } client := config.HTTPClient if client == nil { client = &http.Client{ Timeout: timeout, } } headers := make(map[string]string) if config.Headers != nil { for k, v := range config.Headers { headers[k] = v } } return &RemoteRunnable{ url: config.URL, headers: headers, httpClient: client, timeout: timeout, } } // Execute sends a request to the remote server to execute a node. func (r *RemoteRunnable) Execute(ctx context.Context, nodeName string, input any, config *types.RunnableConfig) (any, error) { // Build request reqBody := &RemoteExecuteRequest{ Node: nodeName, Input: input, Config: config, } data, err := json.Marshal(reqBody) if err != nil { return nil, fmt.Errorf("failed to marshal request: %w", err) } req, err := http.NewRequestWithContext(ctx, "POST", r.url+"/execute", bytes.NewReader(data)) if err != nil { return nil, fmt.Errorf("failed to create request: %w", err) } req.Header.Set("Content-Type", "application/json") for k, v := range r.headers { req.Header.Set(k, v) } // Send request resp, err := r.httpClient.Do(req) if err != nil { return nil, fmt.Errorf("failed to send request: %w", err) } defer resp.Body.Close() // Read response body, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("failed to read response: %w", err) } if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("remote execution failed with status %d: %s", resp.StatusCode, string(body)) } // Parse response var result RemoteExecuteResponse if err := json.Unmarshal(body, &result); err != nil { return nil, fmt.Errorf("failed to unmarshal response: %w", err) } if result.Error != "" { return nil, fmt.Errorf("remote execution error: %s", result.Error) } return result.Output, nil } // RemoteExecuteRequest represents a request to execute a node remotely. type RemoteExecuteRequest struct { Node string `json:"node"` Input any `json:"input"` Config *types.RunnableConfig `json:"config,omitempty"` } // RemoteExecuteResponse represents the response from a remote execution. type RemoteExecuteResponse struct { Output any `json:"output,omitempty"` Error string `json:"error,omitempty"` } // PregelProtocol defines the protocol for remote Pregel execution. type PregelProtocol interface { // Send sends a message to the remote peer. Send(ctx context.Context, message *PregelMessage) error // Receive receives a message from the remote peer. Receive(ctx context.Context) (*PregelMessage, error) // Close closes the protocol connection. Close() error } // PregelMessage represents a message in the Pregel protocol. type PregelMessage struct { Type MessageType `json:"type"` ID string `json:"id"` NodeName string `json:"node_name,omitempty"` Input any `json:"input,omitempty"` Output any `json:"output,omitempty"` Error string `json:"error,omitempty"` Metadata map[string]any `json:"metadata,omitempty"` Timestamp time.Time `json:"timestamp"` } // MessageType represents the type of a Pregel message. type MessageType string const ( // MessageTypeExecute requests node execution. MessageTypeExecute MessageType = "execute" // MessageTypeExecuteResponse is the response to an execute request. MessageTypeExecuteResponse MessageType = "execute_response" // MessageTypeCheckpoint sends checkpoint data. MessageTypeCheckpoint MessageType = "checkpoint" // MessageTypeStateUpdate sends state updates. MessageTypeStateUpdate MessageType = "state_update" // MessageTypeInterrupt sends interrupt information. MessageTypeInterrupt MessageType = "interrupt" // MessageTypeResume resumes execution from interrupt. MessageTypeResume MessageType = "resume" // MessageTypePing is a heartbeat. MessageTypePing MessageType = "ping" // MessageTypePong is a heartbeat response. MessageTypePong MessageType = "pong" ) // HTTPPregelProtocol implements PregelProtocol over HTTP. type HTTPPregelProtocol struct { baseURL string httpClient *http.Client headers map[string]string } // NewHTTPPregelProtocol creates a new HTTP Pregel protocol. func NewHTTPPregelProtocol(baseURL string, headers map[string]string) *HTTPPregelProtocol { return &HTTPPregelProtocol{ baseURL: baseURL, httpClient: &http.Client{Timeout: 30 * time.Second}, headers: headers, } } // Send sends a message to the remote peer. func (p *HTTPPregelProtocol) Send(ctx context.Context, message *PregelMessage) error { data, err := json.Marshal(message) if err != nil { return fmt.Errorf("failed to marshal message: %w", err) } req, err := http.NewRequestWithContext(ctx, "POST", p.baseURL+"/pregel/message", bytes.NewReader(data)) if err != nil { return fmt.Errorf("failed to create request: %w", err) } req.Header.Set("Content-Type", "application/json") for k, v := range p.headers { req.Header.Set(k, v) } resp, err := p.httpClient.Do(req) if err != nil { return fmt.Errorf("failed to send message: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) return fmt.Errorf("send failed with status %d: %s", resp.StatusCode, string(body)) } return nil } // Receive receives a message from the remote peer. func (p *HTTPPregelProtocol) Receive(ctx context.Context) (*PregelMessage, error) { req, err := http.NewRequestWithContext(ctx, "GET", p.baseURL+"/pregel/message", nil) if err != nil { return nil, fmt.Errorf("failed to create request: %w", err) } for k, v := range p.headers { req.Header.Set(k, v) } resp, err := p.httpClient.Do(req) if err != nil { return nil, fmt.Errorf("failed to receive message: %w", err) } defer resp.Body.Close() if resp.StatusCode == http.StatusNoContent { return nil, nil // No message available } if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) return nil, fmt.Errorf("receive failed with status %d: %s", resp.StatusCode, string(body)) } var message PregelMessage if err := json.NewDecoder(resp.Body).Decode(&message); err != nil { return nil, fmt.Errorf("failed to decode message: %w", err) } return &message, nil } // Close closes the protocol connection. func (p *HTTPPregelProtocol) Close() error { return nil } // RemoteNode wraps a remote execution capability as a node function. type RemoteNode struct { runnable *RemoteRunnable nodeName string } // NewRemoteNode creates a new remote node. func NewRemoteNode(runnable *RemoteRunnable, nodeName string) *RemoteNode { return &RemoteNode{ runnable: runnable, nodeName: nodeName, } } // Execute executes the remote node. func (n *RemoteNode) Execute(ctx context.Context, input any) (any, error) { return n.runnable.Execute(ctx, n.nodeName, input, nil) } // NodeToRemoteRunnable converts a local node to a remote runnable. func NodeToRemoteRunnable(node types.NodeFunc, url string) *RemoteRunnable { // This would register the node locally and expose it via HTTP // Implementation depends on the HTTP server setup return NewRemoteRunnable(&RemoteConfig{ URL: url, }) }