mirror of
https://github.com/j3ssie/osmedeus.git
synced 2026-08-17 21:25:49 +02:00
- Add LLM executor supporting OpenAI vision, tool calling, embeddings, and structured outputs - Introduce event emitter/receiver workflows with deduplication and filtering (generate_event functions) - Add workflow extends/override system enabling inheritance chains and step merge modes - Update function naming to snake_case across all testdata (fileExists→file_exists, etc.) - Add comprehensive test fixtures for linter, events, CDN, step dependencies, and extends workflows
152 lines
3.8 KiB
Go
152 lines
3.8 KiB
Go
package executor
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/j3ssie/osmedeus/v5/internal/core"
|
|
"github.com/j3ssie/osmedeus/v5/internal/functions"
|
|
"github.com/j3ssie/osmedeus/v5/internal/template"
|
|
)
|
|
|
|
// FunctionExecutor executes function steps
|
|
type FunctionExecutor struct {
|
|
templateEngine template.TemplateEngine
|
|
functionRegistry *functions.Registry
|
|
}
|
|
|
|
// NewFunctionExecutor creates a new function executor
|
|
func NewFunctionExecutor(engine template.TemplateEngine, registry *functions.Registry) *FunctionExecutor {
|
|
return &FunctionExecutor{
|
|
templateEngine: engine,
|
|
functionRegistry: registry,
|
|
}
|
|
}
|
|
|
|
// Name returns the executor name for logging/debugging
|
|
func (e *FunctionExecutor) Name() string {
|
|
return "function"
|
|
}
|
|
|
|
// StepTypes returns the step types this executor handles
|
|
func (e *FunctionExecutor) StepTypes() []core.StepType {
|
|
return []core.StepType{core.StepTypeFunction}
|
|
}
|
|
|
|
// Execute executes a function step
|
|
func (e *FunctionExecutor) Execute(ctx context.Context, step *core.Step, execCtx *core.ExecutionContext) (*core.StepResult, error) {
|
|
result := &core.StepResult{
|
|
StepName: step.Name,
|
|
Status: core.StepStatusRunning,
|
|
StartTime: time.Now(),
|
|
Exports: make(map[string]interface{}),
|
|
}
|
|
|
|
vars := execCtx.GetVariables()
|
|
var outputs []interface{}
|
|
var err error
|
|
|
|
// Determine execution mode
|
|
if len(step.ParallelFunctions) > 0 {
|
|
outputs, err = e.executeParallel(ctx, step.ParallelFunctions, vars)
|
|
} else if len(step.Functions) > 0 {
|
|
outputs, err = e.executeSequential(ctx, step.Functions, vars)
|
|
} else if step.Function != "" {
|
|
var output interface{}
|
|
output, err = e.executeFunction(ctx, step.Function, vars)
|
|
outputs = []interface{}{output}
|
|
} else {
|
|
err = fmt.Errorf("no function specified")
|
|
}
|
|
|
|
result.EndTime = time.Now()
|
|
result.Duration = result.EndTime.Sub(result.StartTime)
|
|
|
|
if err != nil {
|
|
result.Status = core.StepStatusFailed
|
|
result.Error = err
|
|
return result, err
|
|
}
|
|
|
|
// Convert outputs to string
|
|
if len(outputs) > 0 {
|
|
result.Output = fmt.Sprintf("%v", outputs[0])
|
|
}
|
|
|
|
result.Status = core.StepStatusSuccess
|
|
return result, nil
|
|
}
|
|
|
|
// executeFunction executes a single function
|
|
func (e *FunctionExecutor) executeFunction(ctx context.Context, expr string, vars map[string]interface{}) (interface{}, error) {
|
|
return e.functionRegistry.Execute(expr, vars)
|
|
}
|
|
|
|
// executeSequential executes functions sequentially
|
|
func (e *FunctionExecutor) executeSequential(ctx context.Context, funcs []string, vars map[string]interface{}) ([]interface{}, error) {
|
|
var outputs []interface{}
|
|
|
|
for _, fn := range funcs {
|
|
select {
|
|
case <-ctx.Done():
|
|
return outputs, ctx.Err()
|
|
default:
|
|
}
|
|
|
|
output, err := e.executeFunction(ctx, fn, vars)
|
|
if err != nil {
|
|
return outputs, err
|
|
}
|
|
outputs = append(outputs, output)
|
|
}
|
|
|
|
return outputs, nil
|
|
}
|
|
|
|
// executeParallel executes functions in parallel
|
|
func (e *FunctionExecutor) executeParallel(ctx context.Context, funcs []string, vars map[string]interface{}) ([]interface{}, error) {
|
|
type result struct {
|
|
index int
|
|
output interface{}
|
|
err error
|
|
}
|
|
|
|
results := make(chan result, len(funcs))
|
|
var wg sync.WaitGroup
|
|
|
|
for i, fn := range funcs {
|
|
wg.Add(1)
|
|
go func(idx int, expr string) {
|
|
defer wg.Done()
|
|
output, err := e.executeFunction(ctx, expr, vars)
|
|
results <- result{index: idx, output: output, err: err}
|
|
}(i, fn)
|
|
}
|
|
|
|
// Wait for all functions to complete
|
|
go func() {
|
|
wg.Wait()
|
|
close(results)
|
|
}()
|
|
|
|
// Collect results
|
|
outputs := make([]interface{}, len(funcs))
|
|
var firstError error
|
|
|
|
for r := range results {
|
|
outputs[r.index] = r.output
|
|
if r.err != nil && firstError == nil {
|
|
firstError = r.err
|
|
}
|
|
}
|
|
|
|
return outputs, firstError
|
|
}
|
|
|
|
// CanHandle returns true if this executor can handle the given step type
|
|
func (e *FunctionExecutor) CanHandle(stepType core.StepType) bool {
|
|
return stepType == core.StepTypeFunction
|
|
}
|