Routing & Orchestration
This guide covers two powerful modules for building dynamic, multi-path prompt applications: PromptRouter for intent-based template selection and PromptWorkflow for DAG-based prompt orchestration with branching, merging, and parallel execution.
Prompt Routing (PromptRouter)
When your application handles multiple types of user requests, hardcoding
template selection becomes brittle. PromptRouter classifies user input and
automatically selects the best template based on keyword matching, regex
patterns, and priority scoring.
Basic Setup
using Prompt;
var library = new PromptLibrary();
library.Register("code-review", new PromptTemplate("Review this code:\n{{code}}"));
library.Register("explain", new PromptTemplate("Explain this concept:\n{{topic}}"));
library.Register("summarize", new PromptTemplate("Summarize:\n{{text}}"));
var router = new PromptRouter(library);
router.AddRoute("code-review", new RouteConfig
{
Keywords = new[] { "review", "code", "bug", "fix", "refactor" },
Patterns = new[] { @"review\s+(this|my)\s+code", @"find\s+bugs?" },
TemplateName = "code-review",
Priority = 1.0
});
router.AddRoute("explain", new RouteConfig
{
Keywords = new[] { "explain", "what", "how", "why", "concept" },
TemplateName = "explain",
Priority = 0.8
});
router.AddRoute("summarize", new RouteConfig
{
Keywords = new[] { "summarize", "summary", "tldr", "brief" },
TemplateName = "summarize",
Priority = 0.9
});
Routing a Request
var match = router.Route("Can you review my code for bugs?");
// match.RouteName == "code-review"
// match.Score > 0
// match.TemplateName == "code-review"
The router scores each route against the input using:
- Keyword matches — case-insensitive word boundary matching, each hit adds to the score
- Regex patterns — bonus score for pattern matches (run with a 2-second timeout to prevent ReDoS)
- Priority weight — multiplied into the final score
The highest-scoring route above the minimum threshold (default 0.1) wins.
Fallback Route
Set a fallback for when no route meets the threshold:
router.SetFallback("explain");
var match = router.Route("hello there");
// If no route scores above 0.1, falls back to "explain"
Adjusting Sensitivity
router.SetMinScore(0.3); // Require stronger matches
Integration with PromptLibrary
When constructed with a PromptLibrary, the router can look up templates
directly:
var match = router.Route(userInput);
if (match != null)
{
var template = library.Get(match.TemplateName);
string rendered = template.Render(new { code = userCode });
string response = await Main.GetResponseAsync(rendered);
}
Prompt Workflows (PromptWorkflow)
For complex applications that need branching logic, parallel execution, and
result merging, PromptWorkflow provides a DAG (directed acyclic graph) engine
for prompt orchestration.
Core Concepts
| Concept | Description |
|---|---|
| Node | A single step — either a prompt template, a branch point, or a merge point |
| Edge | A connection between nodes, optionally with a condition |
| Branch | Conditional fork — routes to different nodes based on the previous output |
| Merge | Combines outputs from multiple parent nodes using a configurable strategy |
| Status | Each node tracks its state: Pending, Running, Completed, Skipped, Failed |
Building a Workflow
using Prompt;
var workflow = new PromptWorkflow();
// Add prompt nodes
workflow.AddNode(new WorkflowNode("classify")
{
Template = "Classify this request as 'technical' or 'general': {{input}}"
});
workflow.AddNode(new WorkflowNode("technical-response")
{
Template = "Provide a detailed technical answer to: {{input}}"
});
workflow.AddNode(new WorkflowNode("general-response")
{
Template = "Provide a friendly, accessible answer to: {{input}}"
});
workflow.AddNode(new WorkflowNode("format-output")
{
Template = "Format this response for the user:\n{{response}}",
MergeStrategy = MergeStrategy.FirstCompleted
});
// Define edges with conditions
workflow.AddEdge("classify", "technical-response",
condition: output => output.Contains("technical"));
workflow.AddEdge("classify", "general-response",
condition: output => output.Contains("general"));
workflow.AddEdge("technical-response", "format-output");
workflow.AddEdge("general-response", "format-output");
// Set entry point
workflow.SetEntryNode("classify");
Running a Workflow
var result = await workflow.ExecuteAsync(new Dictionary<string, string>
{
["input"] = "How does TCP three-way handshake work?"
});
Console.WriteLine(result.FinalOutput);
Console.WriteLine($"Nodes executed: {result.ExecutedNodes.Count}");
Console.WriteLine($"Total duration: {result.Duration}");
Merge Strategies
When a node receives inputs from multiple parents, choose how to combine them:
| Strategy | Behavior |
|---|---|
ConcatenateAll |
Wait for all parents, join outputs with newlines |
JoinWithSeparator |
Wait for all parents, join with a custom separator |
FirstCompleted |
Take the first parent that finishes |
LongestOutput |
Take the longest output among parents |
ShortestOutput |
Take the shortest output among parents |
CustomTemplate |
Use a merge template that references parent outputs by node ID |
Parallel Execution
Nodes without dependencies on each other execute in parallel automatically.
In the example above, technical-response and general-response are
independent branches — only one runs based on the classify output, but if
both edges matched, they would execute concurrently.
Error Handling
Failed nodes record their exception and transition to Failed status.
Downstream nodes that depend on a failed node are automatically Skipped.
Check result.FailedNodes for diagnostics:
var result = await workflow.ExecuteAsync(variables);
if (result.FailedNodes.Any())
{
foreach (var (nodeId, ex) in result.FailedNodes)
{
Console.WriteLine($"Node '{nodeId}' failed: {ex.Message}");
}
}
Serialization
Workflows serialize to JSON for storage and reuse:
string json = workflow.ToJson();
var restored = PromptWorkflow.FromJson(json);
Combining Router + Workflow
A common pattern is using the router for top-level intent classification, then dispatching to different workflows based on the route:
var match = router.Route(userInput);
var workflow = match.RouteName switch
{
"code-review" => codeReviewWorkflow,
"data-analysis" => dataAnalysisWorkflow,
_ => generalWorkflow
};
var result = await workflow.ExecuteAsync(new Dictionary<string, string>
{
["input"] = userInput
});
This gives you keyword-based fast routing at the top level with full DAG orchestration for complex multi-step tasks underneath.