Files
osmedeus/internal/executor/function_executor.go
T
j3ssie 1403d20a4d feat: add LLM step executor with vision and tool support, event workflow system, and inheritance
- 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
2026-01-20 18:23:57 +08:00

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
}