Fetches a recipe from TheMealDB (or an offline fixture), runs three AI extractors concurrently over the instructions text, then pipes their outputs through a fully deterministic scoring formula to classify difficulty and generate a skill-level-appropriate cooking tip.
What this example teaches
score = ingredients + steps×1.5 + minutes×0.1 without any AI involvementAI extracts signals from unstructured text; deterministic ops turn those signals into a score; the score gates one of three advice lanes.
All three extractors read from the same instructions_text wire. Because they have no dependencies on each other, the dagor engine schedules all three concurrently. The three AI calls complete in roughly the time of one.
// All three share the same "instructions_text" input wire.
// No mutual dependency → the engine runs them concurrently.
Vertex("ingredients").Op("AIExtractStringSliceOp").
Params(map[string]string{"operation": "extract every distinct ingredient ..."}).
Input("Input", "instructions_text").
Output("Result", "ingredients").
Vertex("steps").Op("AIExtractStringSliceOp").
Params(map[string]string{"operation": "extract every discrete cooking step ..."}).
Input("Input", "instructions_text").
Output("Result", "steps").
Vertex("cook_minutes").Op("AIParseNumberOp").
Params(map[string]string{"operation": "estimate total cooking time in minutes"}).
Input("Input", "instructions_text").
Output("Result", "cook_minutes").
Once the AI extractions complete, the rest of the scoring is pure arithmetic. SliceLenOp counts the list items, IntToFloat64Op widens to float64, and MulFloatOp/AddFloatOp implement the formula. None of these steps ever calls an LLM.
Vertex("ingredient_count_int").Op("SliceLenOp").
Input("Input", "ingredients").
Output("Result", "ingredient_count_int").
Vertex("ingredient_count").Op("IntToFloat64Op").
Input("Value", "ingredient_count_int").
Output("Result", "ingredient_count_f").
Vertex("step_weight").Op("ConstFloat64Op").
Params(map[string]string{"Value": "1.5"}).
Output("Result", "step_weight").
Vertex("step_term").Op("MulFloatOp").
Input("A", "step_count_f").
Input("B", "step_weight").
Output("Result", "step_term").
Vertex("difficulty_score").Op("AddFloatOp").
Input("A", "partial_score").
Input("B", "cook_term").
Output("Result", "difficulty_score").
Predicates for continuous scores compare the float wire against thresholds. Here the advice op itself carries the condition rather than a separate gate op — the predicate is evaluated before the vertex runs, and if false, the vertex is skipped along with its output wire.
// Thresholds: easy < 15, medium 15–30, hard ≥ 30.
predicate.Register("score_is_easy", func(in map[string]any) bool {
s, ok := in["difficulty_score"].(*float64)
return ok && s != nil && *s < 15.0
})
// The condition goes on the advice vertex itself — no separate gate.
b.Vertex("easy_advice").Op("AIComputeStringToStringOp").
Condition("score_is_easy").
ConditionInput("difficulty_score").
Params(map[string]string{"operation": "write a one-sentence tip for a beginner cook ..."}).
Input("Input", "meal_name").
Output("Result", "easy_advice").
Test with three fixtures spanning all three difficulty bands: Pancakes (easy/medium boundary), Chicken Curry (medium), Beef Wellington (hard).
export CLAUDE_API_KEY=<your key>
# Live API
go run ./examples/recipe-analyzer --meal "Pancakes"
go run ./examples/recipe-analyzer --meal "Chicken Curry"
go run ./examples/recipe-analyzer --meal "Beef Wellington"
# Offline fixtures
go run ./examples/recipe-analyzer --fixture examples/recipe-analyzer/testdata/recipes/pancakes.json
The same binary is dual-mode. Pass --mcp and instead of running once and exiting it speaks the Model Context Protocol over stdin/stdout, exposing the entire workflow as a single MCP tool, analyze_recipe. The Go SDK derives the tool's input schema from the workflow's UserInput struct and validates every tools/call request against it, so an agent runs the whole lookup → extract → score → advice-lane pipeline with one tool call.
# Speak MCP over stdin/stdout instead of running once
go run ./examples/recipe-analyzer --mcp
Register it with any MCP client by pointing the client at that command:
{
"mcpServers": {
"recipe-analyzer": {
"command": "go",
"args": ["run", "./examples/recipe-analyzer", "--mcp"]
}
}
}
Measured on testdata/recipes/pancakes.json. All four AIComputeOp calls are the three parallel extractors plus the matched difficulty lane's description writer.
| Op | Calls | Input tokens | Output tokens | Total tokens |
|---|---|---|---|---|
| AIComputeOp | 4 | 800 | 177 | 977 |
| Total | 4 | 800 | 177 | 977 |