A production-ready, event-driven runtime for executing Dify DSL workflows in Go with CEL expression support.
- ✅ CEL Expressions: Dynamic content with Common Expression Language (
${ }syntax) - ✅ Message-Centric Design: Conversation history is the core state
- ✅ Event-Driven: Streaming execution with real-time events
- ✅ Tool Integration: Extensible tool registry with dynamic parameters
- ✅ Type-Safe: Strong typing with Go structs and sealed interfaces
- ✅ LLM Provider Abstraction: Support for OpenAI (with streaming) and mock providers
- ✅ Parallel Execution: Run multiple branches concurrently (planned)
- ✅ High Test Coverage: >80% average, 100% for core memory module
- ✅ Testable: Comprehensive mocking for unit and integration tests
go build -o dsl-run cmd/dsl-run/main.go## Running Examples
### Simple Chat
```bash
go run cmd/dsl-run/main.go examples/simple_chat.yamlTo run the tool use example with a real LLM (OpenAI), you need to set your API key:
export OPENAI_API_KEY=your-key
go run cmd/dsl-run/main.go -input "San Francisco" examples/tool_use.yamlOr using Mock provider:
DSL_EXP_LLM_PROVIDER=mock DSL_EXP_MOCK_RESPONSE="Mock response" go run cmd/dsl-run/main.go -input "San Francisco" examples/tool_use.yaml- CEL Expression Reference - Complete guide to using CEL expressions
- Expression Language Design - Architecture and design decisions
- JSONSchema - Formal schema definition for workflows
- System Prompt Design - Memory and prompt engineering
- Sealed Interfaces - Tagged union pattern in Go
Dynamic expressions can be used in:
- id: get_weather
type: tool
tool_name: weather
parameters:
city: "${ inputs.query }" # CEL expression- id: personalized_greeting
type: generate
model: gpt-4
system_prompt: "Hello ${ inputs.user_name }, the weather in ${ inputs.city } is ${ nodes.weather.data.condition }"- id: score_router
type: selector
cases:
- if: "inputs.score >= 90"
next: excellent
- if: "inputs.score >= 60"
next: passSee CEL_REFERENCE.md for full syntax and examples.
.
├── cmd/dsl-run/ # CLI entrypoint
├── internal/
│ ├── dsl/ # YAML parsing & validation
│ ├── engine/ # Runtime execution engine
│ ├── memory/ # Thread-safe conversation memory
│ ├── node/ # Node executors
│ ├── llm/ # LLM provider abstraction
│ └── integration/ # Integration tests
├── examples/ # Example workflows
└── docs/ # Documentation
name: "Simple Chat"
nodes:
- id: "greeting"
type: "generate"
model: "gpt-4o"
system_prompt: "You are a helpful assistant."See examples/multi_step_conversation.yaml for a complex workflow with multiple generate nodes.
See examples/conditional_routing.yaml for intent-based routing using selectors.
See examples/parallel_research.yaml for concurrent execution with result synthesis.
See examples/nested_workflow.yaml for groups and nested parallel blocks.
go test ./...go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.outgo test -race ./...go test ./internal/integration/... -vThe runtime supports multiple LLM providers through a plugin architecture:
DSL_EXP_LLM_PROVIDER: Choose provider (openaiormock)OPENAI_API_KEY: API key for OpenAI (when using openai provider)DSL_EXP_MOCK_RESPONSE: Response text for mock provider (testing only)
export DSL_EXP_LLM_PROVIDER=mock
export DSL_EXP_MOCK_RESPONSE="Hello from mock LLM!"
./dsl-run examples/simple_chat.yamlimport "github.com/QuantumGhost/dsl-exp/internal/llm"
// Option 1: From environment
provider := llm.NewProviderFromEnv()
// Option 2: Explicit configuration
config := llm.ProviderConfig{
Type: llm.ProviderOpenAI,
APIKey: "your-api-key",
}
provider := llm.NewProvider(config)
// Option 3: Mock for testing
mockProvider := llm.NewMockProvider("Test response")- Define the node struct in
internal/dsl/types.go - Add unmarshaling logic in
internal/dsl/loader.go - Implement executor in
internal/node/ - Register executor in
cmd/dsl-run/main.go
- Implement the
llm.Providerinterface - Add factory case in
internal/llm/factory.go - Add tests in
internal/llm/
- Global Memory: Linear conversation timeline
- Thread-Safe: Uses
sync.RWMutexfor concurrent access - Snapshot: Copy-on-write for parallel branches
NodeStart: Node begins executionNodeEnd: Node completes executionTokenGenerated: LLM streams a tokenToolCall: Tool invocationError: Execution error
- Load and validate YAML workflow
- Initialize global memory with input messages
- Create engine and register executors
- Execute nodes sequentially/parallel
- Stream events to consumer
Current coverage: 67.8%
- Memory System: 100%
- Node Executors: 88.9%
- Engine: 87.1%
- DSL Loader: 84.3%
See TEST_COVERAGE.md for detailed analysis.
MIT
- Fork the repository
- Create your feature branch
- Write tests for your changes
- Ensure all tests pass with
go test -race ./... - Submit a pull request