mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-07-16 20:57:21 +08:00
### What problem does this PR solve? - Tools management - Pregel engine wrapper for better usage - UT race - Coding style ### Type of change - [x] Refactoring
285 lines
7.7 KiB
Go
285 lines
7.7 KiB
Go
// 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,
|
|
})
|
|
}
|