| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785 |
- package workflow_test
- import (
- "context"
- "fmt"
- "testing"
- "workflow"
- )
- // TestV310TextOutputResultIsString verifies that in v3.10, _result for text output
- // is a string (content only), not the full result map
- func TestV310TextOutputResultIsString(t *testing.T) {
- wf := &workflow.Workflow{
- Version: "3.10",
- Name: "V310 Text Output Test",
- Registry: workflow.Registry{
- Services: []string{},
- Components: []string{},
- Vars: []string{"$content(STRING)", "$tokens(NUMBER)"},
- Files: workflow.FilesRegistry{},
- },
- Steps: []workflow.Step{
- {
- ID: "LLM_Generate",
- In: workflow.StepInput{
- "messages": []interface{}{
- map[string]interface{}{
- "role": "user",
- "content": "Hello",
- },
- },
- },
- Out: workflow.StepOutput{
- "$content": "=_result",
- "$tokens": "=_meta.usage.total_tokens",
- },
- Next: "Stop_End",
- },
- {
- ID: "Stop_End",
- },
- },
- }
- adapters := createTestAdapters()
- llmAdapter := adapters.LLM.(*workflow.DefaultLLMAdapter)
- llmAdapter.SetHandler(func(ctx context.Context, params map[string]interface{}, stream chan<- string) (map[string]interface{}, error) {
- return map[string]interface{}{
- "content": "Hello! How can I help?",
- "model": "gpt-4",
- "finish_reason": "stop",
- "response_id": "chatcmpl-123",
- "usage": map[string]interface{}{
- "prompt_tokens": 10,
- "completion_tokens": 8,
- "total_tokens": 18,
- },
- }, nil
- })
- engine, err := workflow.NewEngine(wf)
- if err != nil {
- t.Fatalf("Failed to create engine: %v", err)
- }
- result, err := engine.Execute(context.Background(), map[string]interface{}{}, adapters)
- if err != nil {
- t.Fatalf("Failed to execute workflow: %v", err)
- }
- drainEvents(result.RunEventStream)
- // v3.10: _result should be the content string, not the full map
- content := result.Context.Variables["$content"]
- contentStr, ok := content.(string)
- if !ok {
- t.Fatalf("Expected $content to be string, got %T: %v", content, content)
- }
- if contentStr != "Hello! How can I help?" {
- t.Errorf("Expected content 'Hello! How can I help?', got '%s'", contentStr)
- }
- // _meta.usage.total_tokens should be accessible
- tokens := result.Context.Variables["$tokens"]
- if tokens == nil {
- t.Fatal("Expected $tokens to be set from _meta.usage.total_tokens")
- }
- if fmt.Sprintf("%v", tokens) != "18" {
- t.Errorf("Expected tokens 18, got %v", tokens)
- }
- }
- // TestV310StructuredOutputResultIsObject verifies that in v3.10, _result for
- // json_schema output is the parsed JSON object (same as v3.9 for structured)
- func TestV310StructuredOutputResultIsObject(t *testing.T) {
- wf := &workflow.Workflow{
- Version: "3.10",
- Name: "V310 Structured Output Test",
- Registry: workflow.Registry{
- Services: []string{},
- Components: []string{},
- Vars: []string{"$spec(OBJECT)", "$title(STRING)"},
- Files: workflow.FilesRegistry{},
- },
- Steps: []workflow.Step{
- {
- ID: "LLM_GenSpec",
- In: workflow.StepInput{
- "messages": []interface{}{
- map[string]interface{}{
- "role": "user",
- "content": "Generate a spec",
- },
- },
- "output_config": map[string]interface{}{
- "format": map[string]interface{}{
- "type": "json_schema",
- "schema": map[string]interface{}{
- "type": "object",
- "properties": map[string]interface{}{
- "title": map[string]interface{}{"type": "string"},
- "score": map[string]interface{}{"type": "number"},
- },
- },
- },
- },
- },
- Out: workflow.StepOutput{
- "$spec": "=_result",
- "$title": "=_result.title",
- },
- Next: "Stop_End",
- },
- {
- ID: "Stop_End",
- },
- },
- }
- adapters := createTestAdapters()
- llmAdapter := adapters.LLM.(*workflow.DefaultLLMAdapter)
- llmAdapter.SetHandler(func(ctx context.Context, params map[string]interface{}, stream chan<- string) (map[string]interface{}, error) {
- // Adapter returns parsed JSON content (json_schema mode parses in adapter)
- return map[string]interface{}{
- "content": map[string]interface{}{
- "title": "My Spec",
- "score": 95,
- },
- "model": "gpt-4",
- "finish_reason": "stop",
- "response_id": "chatcmpl-456",
- "usage": map[string]interface{}{
- "prompt_tokens": 20,
- "completion_tokens": 15,
- "total_tokens": 35,
- },
- }, nil
- })
- engine, err := workflow.NewEngine(wf)
- if err != nil {
- t.Fatalf("Failed to create engine: %v", err)
- }
- result, err := engine.Execute(context.Background(), map[string]interface{}{}, adapters)
- if err != nil {
- t.Fatalf("Failed to execute workflow: %v", err)
- }
- drainEvents(result.RunEventStream)
- // _result should be the parsed object
- spec, ok := result.Context.Variables["$spec"].(map[string]interface{})
- if !ok {
- t.Fatalf("Expected $spec to be map, got %T: %v", result.Context.Variables["$spec"], result.Context.Variables["$spec"])
- }
- if spec["title"] != "My Spec" {
- t.Errorf("Expected title 'My Spec', got '%v'", spec["title"])
- }
- // _result.title should also work
- title := result.Context.Variables["$title"]
- if title != "My Spec" {
- t.Errorf("Expected $title 'My Spec', got '%v'", title)
- }
- }
- // TestV310MetaFields verifies all _meta fields are populated correctly
- func TestV310MetaFields(t *testing.T) {
- var capturedMeta interface{}
- wf := &workflow.Workflow{
- Version: "3.10",
- Name: "V310 Meta Fields Test",
- Registry: workflow.Registry{
- Services: []string{},
- Components: []string{},
- Vars: []string{"$meta(OBJECT)"},
- Files: workflow.FilesRegistry{},
- },
- Steps: []workflow.Step{
- {
- ID: "LLM_Test",
- In: workflow.StepInput{
- "messages": []interface{}{
- map[string]interface{}{
- "role": "user",
- "content": "Test",
- },
- },
- },
- Out: workflow.StepOutput{
- "$meta": "=_meta",
- },
- Next: "Stop_End",
- },
- {
- ID: "Stop_End",
- },
- },
- }
- adapters := createTestAdapters()
- llmAdapter := adapters.LLM.(*workflow.DefaultLLMAdapter)
- llmAdapter.SetHandler(func(ctx context.Context, params map[string]interface{}, stream chan<- string) (map[string]interface{}, error) {
- return map[string]interface{}{
- "content": "response",
- "model": "claude-3-opus",
- "finish_reason": "stop",
- "response_id": "msg_abc123",
- "usage": map[string]interface{}{
- "prompt_tokens": 100,
- "completion_tokens": 50,
- "total_tokens": 150,
- },
- }, nil
- })
- engine, err := workflow.NewEngine(wf)
- if err != nil {
- t.Fatalf("Failed to create engine: %v", err)
- }
- result, err := engine.Execute(context.Background(), map[string]interface{}{}, adapters)
- if err != nil {
- t.Fatalf("Failed to execute workflow: %v", err)
- }
- drainEvents(result.RunEventStream)
- capturedMeta = result.Context.Variables["$meta"]
- meta, ok := capturedMeta.(map[string]interface{})
- if !ok {
- t.Fatalf("Expected $meta to be map, got %T", capturedMeta)
- }
- // Check model
- if meta["model"] != "claude-3-opus" {
- t.Errorf("Expected model 'claude-3-opus', got '%v'", meta["model"])
- }
- // Check provider inference
- if meta["provider"] != "anthropic" {
- t.Errorf("Expected provider 'anthropic', got '%v'", meta["provider"])
- }
- // Check finish_reason
- if meta["finish_reason"] != "stop" {
- t.Errorf("Expected finish_reason 'stop', got '%v'", meta["finish_reason"])
- }
- // Check response_id
- if meta["response_id"] != "msg_abc123" {
- t.Errorf("Expected response_id 'msg_abc123', got '%v'", meta["response_id"])
- }
- // Check latency_ms exists and is > 0
- latencyMs, ok := meta["latency_ms"].(int64)
- if !ok {
- t.Errorf("Expected latency_ms to be int64, got %T", meta["latency_ms"])
- } else if latencyMs < 0 {
- t.Errorf("Expected latency_ms >= 0, got %d", latencyMs)
- }
- // Check usage with normalized field names
- usage, ok := meta["usage"].(map[string]interface{})
- if !ok {
- t.Fatalf("Expected usage to be map, got %T", meta["usage"])
- }
- if usage["input_tokens"] != 100 {
- t.Errorf("Expected input_tokens 100, got %v", usage["input_tokens"])
- }
- if usage["output_tokens"] != 50 {
- t.Errorf("Expected output_tokens 50, got %v", usage["output_tokens"])
- }
- if usage["total_tokens"] != 150 {
- t.Errorf("Expected total_tokens 150, got %v", usage["total_tokens"])
- }
- // Check raw usage is preserved
- if usage["raw"] == nil {
- t.Error("Expected usage.raw to be set")
- }
- }
- // TestV310OnErrorJumpsToHandler verifies that when an LLM call fails and the step
- // has onError set, the engine jumps to the error handler step
- func TestV310OnErrorJumpsToHandler(t *testing.T) {
- wf := &workflow.Workflow{
- Version: "3.10",
- Name: "V310 OnError Test",
- Registry: workflow.Registry{
- Services: []string{},
- Components: []string{},
- Vars: []string{"$lastError(OBJECT)", "$success(STRING)"},
- Files: workflow.FilesRegistry{},
- },
- Steps: []workflow.Step{
- {
- ID: "LLM_WillFail",
- In: workflow.StepInput{
- "messages": []interface{}{
- map[string]interface{}{
- "role": "user",
- "content": "This will fail",
- },
- },
- },
- Out: workflow.StepOutput{
- "$success": "=_result",
- },
- OnError: "Set_LogError",
- Next: "Stop_End",
- },
- {
- ID: "Set_LogError",
- Target: "$lastError",
- Value: "=_error",
- Next: "Stop_End",
- },
- {
- ID: "Stop_End",
- },
- },
- }
- adapters := createTestAdapters()
- llmAdapter := adapters.LLM.(*workflow.DefaultLLMAdapter)
- llmAdapter.SetHandler(func(ctx context.Context, params map[string]interface{}, stream chan<- string) (map[string]interface{}, error) {
- return nil, &workflow.LLMError{
- Type: "rate_limit",
- Code: "RATE_LIMIT",
- Message: "Rate limit exceeded",
- Retryable: true,
- StatusCode: 429,
- Provider: "openai",
- Model: "gpt-4",
- }
- })
- engine, err := workflow.NewEngine(wf)
- if err != nil {
- t.Fatalf("Failed to create engine: %v", err)
- }
- result, err := engine.Execute(context.Background(), map[string]interface{}{}, adapters)
- if err != nil {
- t.Fatalf("Failed to execute workflow: %v", err)
- }
- drainEvents(result.RunEventStream)
- // Workflow should NOT have failed - onError handled it
- if result.Context.Status == workflow.StatusFailed {
- t.Fatal("Expected workflow to succeed (onError should handle the error)")
- }
- // $success should NOT be set (out mapping is skipped on error)
- if result.Context.Variables["$success"] != nil {
- t.Errorf("Expected $success to be nil (out mapping not applied on error), got %v", result.Context.Variables["$success"])
- }
- // $lastError should be set by the error handler
- lastError, ok := result.Context.Variables["$lastError"].(map[string]interface{})
- if !ok {
- t.Fatalf("Expected $lastError to be map, got %T: %v", result.Context.Variables["$lastError"], result.Context.Variables["$lastError"])
- }
- if lastError["type"] != "rate_limit" {
- t.Errorf("Expected error type 'rate_limit', got '%v'", lastError["type"])
- }
- if lastError["code"] != "RATE_LIMIT" {
- t.Errorf("Expected error code 'RATE_LIMIT', got '%v'", lastError["code"])
- }
- if lastError["message"] != "Rate limit exceeded" {
- t.Errorf("Expected error message 'Rate limit exceeded', got '%v'", lastError["message"])
- }
- if lastError["retryable"] != true {
- t.Errorf("Expected error retryable true, got %v", lastError["retryable"])
- }
- if lastError["status_code"] != 429 {
- t.Errorf("Expected status_code 429, got %v", lastError["status_code"])
- }
- }
- // TestV310ErrorWithoutOnErrorFails verifies that in v3.10, when an LLM call fails
- // and the step has no onError, the workflow still fails (backward-compatible)
- func TestV310ErrorWithoutOnErrorFails(t *testing.T) {
- wf := &workflow.Workflow{
- Version: "3.10",
- Name: "V310 Error Without OnError Test",
- Registry: workflow.Registry{
- Services: []string{},
- Components: []string{},
- Vars: []string{"$content(STRING)"},
- Files: workflow.FilesRegistry{},
- },
- Steps: []workflow.Step{
- {
- ID: "LLM_WillFail",
- In: workflow.StepInput{
- "messages": []interface{}{
- map[string]interface{}{
- "role": "user",
- "content": "This will fail",
- },
- },
- },
- Out: workflow.StepOutput{
- "$content": "=_result",
- },
- // No OnError - error should propagate
- Next: "Stop_End",
- },
- {
- ID: "Stop_End",
- },
- },
- }
- adapters := createTestAdapters()
- llmAdapter := adapters.LLM.(*workflow.DefaultLLMAdapter)
- llmAdapter.SetHandler(func(ctx context.Context, params map[string]interface{}, stream chan<- string) (map[string]interface{}, error) {
- return nil, fmt.Errorf("connection refused")
- })
- engine, err := workflow.NewEngine(wf)
- if err != nil {
- t.Fatalf("Failed to create engine: %v", err)
- }
- result, err := engine.Execute(context.Background(), map[string]interface{}{}, adapters)
- if err != nil {
- t.Fatalf("Failed to execute workflow: %v", err)
- }
- drainEvents(result.RunEventStream)
- // Workflow should have failed
- if result.Context.Status != workflow.StatusFailed {
- t.Errorf("Expected workflow to fail, got status %s", result.Context.Status)
- }
- }
- // TestV310BackwardCompatV39 verifies that a v3.9 workflow still works with old _result semantics
- func TestV310BackwardCompatV39(t *testing.T) {
- wf := &workflow.Workflow{
- Version: "3.9",
- Name: "V39 Backward Compat Test",
- Registry: workflow.Registry{
- Services: []string{},
- Components: []string{},
- Vars: []string{"$content(STRING)", "$model(STRING)", "$tokens(NUMBER)"},
- Files: workflow.FilesRegistry{},
- },
- Steps: []workflow.Step{
- {
- ID: "LLM_Generate",
- In: workflow.StepInput{
- "messages": []interface{}{
- map[string]interface{}{
- "role": "user",
- "content": "Hello",
- },
- },
- },
- Out: workflow.StepOutput{
- // v3.9 semantics: _result is the full map for text output
- "$content": "=_result.content",
- "$model": "=_result.model",
- "$tokens": "=_result.usage.total_tokens",
- },
- Next: "Stop_End",
- },
- {
- ID: "Stop_End",
- },
- },
- }
- adapters := createTestAdapters()
- llmAdapter := adapters.LLM.(*workflow.DefaultLLMAdapter)
- llmAdapter.SetHandler(func(ctx context.Context, params map[string]interface{}, stream chan<- string) (map[string]interface{}, error) {
- return map[string]interface{}{
- "content": "Hello there!",
- "model": "gpt-4",
- "finish_reason": "stop",
- "usage": map[string]interface{}{
- "prompt_tokens": 5,
- "completion_tokens": 3,
- "total_tokens": 8,
- },
- }, nil
- })
- engine, err := workflow.NewEngine(wf)
- if err != nil {
- t.Fatalf("Failed to create engine: %v", err)
- }
- result, err := engine.Execute(context.Background(), map[string]interface{}{}, adapters)
- if err != nil {
- t.Fatalf("Failed to execute workflow: %v", err)
- }
- drainEvents(result.RunEventStream)
- // v3.9: _result.content should work (full map semantics)
- if result.Context.Variables["$content"] != "Hello there!" {
- t.Errorf("Expected content 'Hello there!', got '%v'", result.Context.Variables["$content"])
- }
- if result.Context.Variables["$model"] != "gpt-4" {
- t.Errorf("Expected model 'gpt-4', got '%v'", result.Context.Variables["$model"])
- }
- if fmt.Sprintf("%v", result.Context.Variables["$tokens"]) != "8" {
- t.Errorf("Expected tokens 8, got %v", result.Context.Variables["$tokens"])
- }
- }
- // TestV310ErrorMetaPartiallyPopulated verifies that _meta is set (with latency)
- // even on LLM failure when onError is used
- func TestV310ErrorMetaPartiallyPopulated(t *testing.T) {
- wf := &workflow.Workflow{
- Version: "3.10",
- Name: "V310 Error Meta Test",
- Registry: workflow.Registry{
- Services: []string{},
- Components: []string{},
- Vars: []string{"$errorMeta(OBJECT)", "$errorInfo(OBJECT)"},
- Files: workflow.FilesRegistry{},
- },
- Steps: []workflow.Step{
- {
- ID: "LLM_WillFail",
- In: workflow.StepInput{
- "messages": []interface{}{
- map[string]interface{}{
- "role": "user",
- "content": "This will fail",
- },
- },
- },
- OnError: "Set_CaptureError",
- Next: "Stop_End",
- },
- {
- ID: "Set_CaptureError",
- Target: "$errorInfo",
- Value: "=_error",
- Next: "Stop_End",
- },
- {
- ID: "Stop_End",
- },
- },
- }
- adapters := createTestAdapters()
- llmAdapter := adapters.LLM.(*workflow.DefaultLLMAdapter)
- llmAdapter.SetHandler(func(ctx context.Context, params map[string]interface{}, stream chan<- string) (map[string]interface{}, error) {
- return nil, &workflow.LLMError{
- Type: "timeout",
- Code: "TIMEOUT",
- Message: "Request timed out",
- Retryable: true,
- StatusCode: 408,
- Model: "gpt-4",
- }
- })
- engine, err := workflow.NewEngine(wf)
- if err != nil {
- t.Fatalf("Failed to create engine: %v", err)
- }
- result, err := engine.Execute(context.Background(), map[string]interface{}{}, adapters)
- if err != nil {
- t.Fatalf("Failed to execute workflow: %v", err)
- }
- drainEvents(result.RunEventStream)
- // Workflow should succeed (onError handled)
- if result.Context.Status == workflow.StatusFailed {
- t.Fatal("Expected workflow to succeed with onError handler")
- }
- // $errorInfo should have the error details
- errorInfo, ok := result.Context.Variables["$errorInfo"].(map[string]interface{})
- if !ok {
- t.Fatalf("Expected $errorInfo to be map, got %T: %v", result.Context.Variables["$errorInfo"], result.Context.Variables["$errorInfo"])
- }
- if errorInfo["type"] != "timeout" {
- t.Errorf("Expected error type 'timeout', got '%v'", errorInfo["type"])
- }
- if errorInfo["retryable"] != true {
- t.Errorf("Expected retryable true, got %v", errorInfo["retryable"])
- }
- }
- // TestV310ResultCleanup verifies that _result, _meta, _error are all cleaned up
- // after out evaluation
- func TestV310ResultCleanup(t *testing.T) {
- wf := &workflow.Workflow{
- Version: "3.10",
- Name: "V310 Cleanup Test",
- Registry: workflow.Registry{
- Services: []string{},
- Components: []string{},
- Vars: []string{"$content(STRING)"},
- Files: workflow.FilesRegistry{},
- },
- Steps: []workflow.Step{
- {
- ID: "LLM_First",
- In: workflow.StepInput{
- "messages": []interface{}{
- map[string]interface{}{
- "role": "user",
- "content": "Hello",
- },
- },
- },
- Out: workflow.StepOutput{
- "$content": "=_result",
- },
- Next: "Stop_End",
- },
- {
- ID: "Stop_End",
- },
- },
- }
- adapters := createTestAdapters()
- llmAdapter := adapters.LLM.(*workflow.DefaultLLMAdapter)
- llmAdapter.SetHandler(func(ctx context.Context, params map[string]interface{}, stream chan<- string) (map[string]interface{}, error) {
- return map[string]interface{}{
- "content": "response",
- "model": "gpt-4",
- "finish_reason": "stop",
- }, nil
- })
- engine, err := workflow.NewEngine(wf)
- if err != nil {
- t.Fatalf("Failed to create engine: %v", err)
- }
- result, err := engine.Execute(context.Background(), map[string]interface{}{}, adapters)
- if err != nil {
- t.Fatalf("Failed to execute workflow: %v", err)
- }
- drainEvents(result.RunEventStream)
- // After workflow completes, local vars should be cleaned up
- if _, ok := result.Context.LocalVars["_result"]; ok {
- t.Error("Expected _result to be cleaned up after step execution")
- }
- if _, ok := result.Context.LocalVars["_meta"]; ok {
- t.Error("Expected _meta to be cleaned up after step execution")
- }
- if _, ok := result.Context.LocalVars["_error"]; ok {
- t.Error("Expected _error to be cleaned up after step execution")
- }
- }
- // TestV310OnErrorValidation verifies that onError referencing a non-existent step fails validation
- func TestV310OnErrorValidation(t *testing.T) {
- wf := &workflow.Workflow{
- Version: "3.10",
- Name: "V310 OnError Validation Test",
- Registry: workflow.Registry{
- Services: []string{},
- Components: []string{},
- Vars: []string{},
- Files: workflow.FilesRegistry{},
- },
- Steps: []workflow.Step{
- {
- ID: "LLM_Test",
- In: workflow.StepInput{
- "messages": []interface{}{},
- },
- OnError: "NonExistent_Step",
- Next: "Stop_End",
- },
- {
- ID: "Stop_End",
- },
- },
- }
- _, err := workflow.NewEngine(wf)
- if err == nil {
- t.Fatal("Expected validation error for non-existent onError step")
- }
- t.Logf("Got expected error: %v", err)
- }
- // TestV310ProviderInference verifies provider inference from model names
- func TestV310ProviderInference(t *testing.T) {
- tests := []struct {
- model string
- expected string
- }{
- {"gpt-4", "openai"},
- {"claude-3-opus", "anthropic"},
- {"gemini-pro", "google"},
- {"mistral-large", "mistral"},
- {"some-custom-model", "unknown"},
- }
- for _, tt := range tests {
- t.Run(tt.model, func(t *testing.T) {
- wf := &workflow.Workflow{
- Version: "3.10",
- Name: "Provider Test",
- Registry: workflow.Registry{
- Services: []string{},
- Components: []string{},
- Vars: []string{"$provider(STRING)"},
- Files: workflow.FilesRegistry{},
- },
- Steps: []workflow.Step{
- {
- ID: "LLM_Test",
- In: workflow.StepInput{
- "messages": []interface{}{
- map[string]interface{}{
- "role": "user",
- "content": "test",
- },
- },
- },
- Out: workflow.StepOutput{
- "$provider": "=_meta.provider",
- },
- Next: "Stop_End",
- },
- {
- ID: "Stop_End",
- },
- },
- }
- adapters := createTestAdapters()
- llmAdapter := adapters.LLM.(*workflow.DefaultLLMAdapter)
- llmAdapter.SetHandler(func(ctx context.Context, params map[string]interface{}, stream chan<- string) (map[string]interface{}, error) {
- return map[string]interface{}{
- "content": "ok",
- "model": tt.model,
- }, nil
- })
- engine, err := workflow.NewEngine(wf)
- if err != nil {
- t.Fatalf("Failed to create engine: %v", err)
- }
- result, err := engine.Execute(context.Background(), map[string]interface{}{}, adapters)
- if err != nil {
- t.Fatalf("Failed to execute: %v", err)
- }
- drainEvents(result.RunEventStream)
- provider := result.Context.Variables["$provider"]
- if provider != tt.expected {
- t.Errorf("Expected provider '%s' for model '%s', got '%v'", tt.expected, tt.model, provider)
- }
- })
- }
- }
|