1use super::WorkflowTemplate;
11
12pub const SHOWCASE_01_URL_STATUS: &str = r##"# ═══════════════════════════════════════════════════════════════════
17# Pattern 01: Multi-URL Status Checker
18# ═══════════════════════════════════════════════════════════════════
19#
20# for_each over 10 URLs, fetch each, produce structured status report.
21# Demonstrates: for_each array literal + fetch + structured output.
22#
23# Prerequisites: None (public endpoints)
24# Run: nika run workflows/showcase/01-url-status.nika.yaml
25
26schema: "nika/workflow@0.12"
27workflow: url-status-checker
28description: "Check status of multiple URLs in parallel"
29
30tasks:
31 - id: check_urls
32 for_each:
33 - "https://httpbin.org/status/200"
34 - "https://httpbin.org/status/201"
35 - "https://httpbin.org/status/301"
36 - "https://httpbin.org/status/404"
37 - "https://httpbin.org/status/500"
38 - "https://httpbin.org/ip"
39 - "https://httpbin.org/uuid"
40 - "https://httpbin.org/user-agent"
41 - "https://httpbin.org/headers"
42 - "https://httpbin.org/get"
43 as: target_url
44 concurrency: 5
45 fail_fast: false
46 fetch:
47 url: "{{with.target_url}}"
48 method: GET
49 response: full
50 timeout: 15
51
52 - id: report
53 depends_on: [check_urls]
54 with:
55 results: $check_urls
56 exec:
57 command: |
58 echo 'Status check complete. {{with.results | length}} URLs checked.'
59 shell: true
60"##;
61
62pub const SHOWCASE_02_LANG_DETECT: &str = r##"# ═══════════════════════════════════════════════════════════════════
67# Pattern 02: Language Detector
68# ═══════════════════════════════════════════════════════════════════
69#
70# for_each over 5 multilingual texts, detect language via LLM,
71# return structured {text_snippet, language, confidence}.
72#
73# Prerequisites: LLM provider
74# Run: nika run workflows/showcase/02-lang-detect.nika.yaml
75
76schema: "nika/workflow@0.12"
77workflow: language-detector
78description: "Detect language of multiple texts with structured output"
79
80provider: "{{PROVIDER}}"
81model: "{{MODEL}}"
82
83tasks:
84 - id: detect
85 for_each:
86 - "Bonjour, comment allez-vous aujourd'hui?"
87 - "The quick brown fox jumps over the lazy dog."
88 - "Heute ist ein wunderschoener Tag zum Programmieren."
89 - "El desarrollo de software es un arte y una ciencia."
90 - "Konnichiwa, kyou wa ii tenki desu ne."
91 as: text
92 concurrency: 3
93 structured:
94 schema:
95 type: object
96 properties:
97 text_snippet:
98 type: string
99 description: "First 40 characters of the input"
100 language:
101 type: string
102 enum: ["english", "french", "german", "spanish", "japanese", "other"]
103 confidence:
104 type: number
105 minimum: 0
106 maximum: 1
107 required: [text_snippet, language, confidence]
108 infer:
109 prompt: |
110 Detect the language of this text. Return the first 40 characters
111 as text_snippet, the detected language, and a confidence score 0-1.
112
113 Text: "{{with.text}}"
114
115 - id: summary
116 depends_on: [detect]
117 with:
118 detections: $detect
119 exec:
120 command: |
121 echo 'Language detection complete. {{with.detections | length}} texts processed.'
122 shell: true
123"##;
124
125pub const SHOWCASE_03_SENTIMENT: &str = r##"# ═══════════════════════════════════════════════════════════════════
130# Pattern 03: Batch Sentiment Analysis
131# ═══════════════════════════════════════════════════════════════════
132#
133# for_each over product reviews, analyze sentiment with structured output.
134# Demonstrates: for_each + structured with enum + number constraints.
135#
136# Prerequisites: LLM provider
137# Run: nika run workflows/showcase/03-sentiment.nika.yaml
138
139schema: "nika/workflow@0.12"
140workflow: batch-sentiment
141description: "Analyze sentiment of product reviews in batch"
142
143provider: "{{PROVIDER}}"
144model: "{{MODEL}}"
145
146tasks:
147 - id: analyze
148 for_each:
149 - "Absolutely love this product! Best purchase I've made all year."
150 - "Decent quality but the shipping took forever. Not great, not terrible."
151 - "Complete waste of money. Broke after two days. Would NOT recommend."
152 - "It works as advertised. Nothing special but gets the job done."
153 - "Mind-blowing performance! This exceeded all my expectations."
154 - "Meh. It's okay I guess. Expected more for the price."
155 as: review
156 concurrency: 3
157 structured:
158 schema:
159 type: object
160 properties:
161 review_excerpt:
162 type: string
163 maxLength: 60
164 description: "First 60 chars of the review"
165 sentiment:
166 type: string
167 enum: ["positive", "neutral", "negative"]
168 score:
169 type: number
170 minimum: -1
171 maximum: 1
172 description: "Sentiment score from -1 (negative) to 1 (positive)"
173 key_phrases:
174 type: array
175 items:
176 type: string
177 maxItems: 3
178 description: "Key sentiment-bearing phrases"
179 required: [review_excerpt, sentiment, score, key_phrases]
180 infer:
181 prompt: |
182 Analyze the sentiment of this product review.
183 Return: excerpt (first 60 chars), sentiment label,
184 score (-1 to 1), and up to 3 key phrases.
185
186 Review: "{{with.review}}"
187 temperature: 0.2
188
189 - id: aggregate
190 depends_on: [analyze]
191 with:
192 results: $analyze
193 exec:
194 command: |
195 echo 'Sentiment analysis complete. {{with.results | length}} reviews analyzed.'
196 shell: true
197"##;
198
199pub const SHOWCASE_04_TRANSLATION: &str = r##"# ═══════════════════════════════════════════════════════════════════
204# Pattern 04: Parallel Translation
205# ═══════════════════════════════════════════════════════════════════
206#
207# Translate a source text into 5 languages in parallel.
208# Each translation is saved as a separate artifact.
209#
210# Prerequisites: LLM provider
211# Run: nika run workflows/showcase/04-translation.nika.yaml
212
213schema: "nika/workflow@0.12"
214workflow: parallel-translation
215description: "Translate text into 5 languages with per-language artifacts"
216
217provider: "{{PROVIDER}}"
218model: "{{MODEL}}"
219
220tasks:
221 - id: source_text
222 exec:
223 command: |
224 echo 'Open source software is not just about code. It is about community, collaboration, and the belief that knowledge should be free. Every contribution, no matter how small, moves us forward together.'
225 shell: true
226
227 - id: translate
228 depends_on: [source_text]
229 with:
230 source: $source_text
231 for_each: ["french", "german", "spanish", "japanese", "portuguese"]
232 as: lang
233 concurrency: 5
234 structured:
235 schema:
236 type: object
237 properties:
238 language:
239 type: string
240 translation:
241 type: string
242 minLength: 10
243 word_count:
244 type: integer
245 minimum: 1
246 required: [language, translation, word_count]
247 infer:
248 prompt: |
249 Translate the following text into {{with.lang}}.
250 Return the target language name, the translation, and the word count.
251
252 Source text: "{{with.source}}"
253 temperature: 0.3
254 artifact:
255 path: "output/translations/{{with.lang}}.json"
256 format: json
257
258 - id: done
259 depends_on: [translate]
260 exec:
261 command: "echo 'All 5 translations complete.'"
262 shell: true
263"##;
264
265pub const SHOWCASE_05_API_TESTER: &str = r##"# ═══════════════════════════════════════════════════════════════════
270# Pattern 05: API Endpoint Tester
271# ═══════════════════════════════════════════════════════════════════
272#
273# Test multiple API endpoints with full response inspection.
274# Demonstrates: for_each + fetch response:full + structured report.
275#
276# Prerequisites: None (public endpoints)
277# Run: nika run workflows/showcase/05-api-tester.nika.yaml
278
279schema: "nika/workflow@0.12"
280workflow: api-endpoint-tester
281description: "Test API endpoints and report structured results"
282
283provider: "{{PROVIDER}}"
284model: "{{MODEL}}"
285
286tasks:
287 - id: test_endpoints
288 for_each:
289 - "https://httpbin.org/get"
290 - "https://httpbin.org/ip"
291 - "https://httpbin.org/uuid"
292 - "https://httpbin.org/user-agent"
293 - "https://httpbin.org/headers"
294 - "https://httpbin.org/delay/1"
295 as: endpoint
296 concurrency: 3
297 fail_fast: false
298 fetch:
299 url: "{{with.endpoint}}"
300 method: GET
301 response: full
302 timeout: 10
303
304 - id: analyze_results
305 depends_on: [test_endpoints]
306 with:
307 raw_results: $test_endpoints
308 structured:
309 schema:
310 type: object
311 properties:
312 total_endpoints:
313 type: integer
314 healthy_count:
315 type: integer
316 slow_count:
317 type: integer
318 description: "Endpoints with response time > 2 seconds"
319 summary:
320 type: string
321 description: "Brief health summary"
322 required: [total_endpoints, healthy_count, slow_count, summary]
323 infer:
324 prompt: |
325 Analyze these API test results and produce a health report.
326 Count total endpoints, healthy ones (2xx), and slow ones (>2s).
327
328 Results: {{with.raw_results}}
329 temperature: 0.1
330"##;
331
332pub const SHOWCASE_06_LINK_CHECKER: &str = r##"# ═══════════════════════════════════════════════════════════════════
337# Pattern 06: Markdown Link Checker
338# ═══════════════════════════════════════════════════════════════════
339#
340# Extract links via exec, then for_each to verify each link.
341# Demonstrates: exec output → for_each binding → fetch → structured.
342#
343# Prerequisites: None
344# Run: nika run workflows/showcase/06-link-checker.nika.yaml
345
346schema: "nika/workflow@0.12"
347workflow: link-checker
348description: "Extract and verify links from a list"
349
350tasks:
351 - id: extract_links
352 exec:
353 command: |
354 echo '["https://httpbin.org/status/200", "https://httpbin.org/status/404", "https://httpbin.org/status/301", "https://httpbin.org/get", "https://httpbin.org/status/500"]'
355 shell: true
356
357 - id: check_each
358 depends_on: [extract_links]
359 with:
360 links: $extract_links | parse_json
361 for_each: "{{with.links}}"
362 as: url
363 concurrency: 3
364 fail_fast: false
365 fetch:
366 url: "{{with.url}}"
367 method: GET
368 response: full
369 timeout: 10
370
371 - id: report
372 depends_on: [check_each]
373 with:
374 results: $check_each
375 exec:
376 command: |
377 echo 'Link check complete. Results: {{with.results}}'
378 shell: true
379"##;
380
381pub const SHOWCASE_07_RSS_AGGREGATOR: &str = r##"# ═══════════════════════════════════════════════════════════════════
386# Pattern 07: RSS Multi-Feed Aggregator
387# ═══════════════════════════════════════════════════════════════════
388#
389# Fetch 5 RSS feeds in parallel with extract:feed, then merge.
390# Demonstrates: for_each + fetch extract:feed + aggregation.
391#
392# Prerequisites: None (public feeds)
393# Run: nika run workflows/showcase/07-rss-aggregator.nika.yaml
394
395schema: "nika/workflow@0.12"
396workflow: rss-aggregator
397description: "Aggregate multiple RSS feeds in parallel"
398
399tasks:
400 - id: fetch_feeds
401 for_each:
402 - "https://hnrss.org/newest?count=3"
403 - "https://www.reddit.com/r/rust/.rss?limit=3"
404 - "https://www.reddit.com/r/programming/.rss?limit=3"
405 - "https://lobste.rs/rss"
406 - "https://blog.rust-lang.org/feed.xml"
407 as: feed_url
408 concurrency: 5
409 fail_fast: false
410 fetch:
411 url: "{{with.feed_url}}"
412 extract: feed
413 timeout: 15
414
415 - id: merge
416 depends_on: [fetch_feeds]
417 with:
418 feeds: $fetch_feeds
419 exec:
420 command: |
421 echo 'Aggregated {{with.feeds | length}} feeds.'
422 shell: true
423 artifact:
424 path: output/feeds/aggregated.json
425 format: json
426"##;
427
428pub const SHOWCASE_08_GIT_ANALYZER: &str = r##"# ═══════════════════════════════════════════════════════════════════
433# Pattern 08: Git Branch Analyzer
434# ═══════════════════════════════════════════════════════════════════
435#
436# List git branches, then for_each branch get last commit info.
437# Demonstrates: exec → for_each binding → exec per item → structured.
438#
439# Prerequisites: Must run inside a git repository, LLM provider
440# Run: nika run workflows/showcase/08-git-analyzer.nika.yaml
441
442schema: "nika/workflow@0.12"
443workflow: git-branch-analyzer
444description: "Analyze git branches with structured summary"
445
446provider: "{{PROVIDER}}"
447model: "{{MODEL}}"
448
449tasks:
450 - id: list_branches
451 exec:
452 command: |
453 git branch --format='%(refname:short)' | head -5 | tr '\n' ',' | sed 's/,$//' | xargs -I{} echo '["{}"' | sed 's/,/","/g' | sed 's/\["/["/' | sed 's/$/"]/'
454 shell: true
455
456 - id: get_branch_info
457 depends_on: [list_branches]
458 with:
459 branches: $list_branches | parse_json
460 for_each: "{{with.branches}}"
461 as: branch
462 concurrency: 3
463 exec:
464 command: |
465 echo "Branch: {{with.branch}} | Last commit: $(git log -1 --format='%s (%ar)' {{with.branch}} 2>/dev/null || echo 'unknown')"
466 shell: true
467
468 - id: summarize
469 depends_on: [get_branch_info]
470 with:
471 branch_data: $get_branch_info
472 structured:
473 schema:
474 type: object
475 properties:
476 total_branches:
477 type: integer
478 branch_summaries:
479 type: array
480 items:
481 type: object
482 properties:
483 name:
484 type: string
485 last_activity:
486 type: string
487 required: [name, last_activity]
488 recommendation:
489 type: string
490 description: "Suggestion for branch cleanup"
491 required: [total_branches, branch_summaries, recommendation]
492 infer:
493 prompt: |
494 Analyze this git branch information and produce a summary.
495 Include total branches, a summary for each, and cleanup recommendations.
496
497 Branch data: {{with.branch_data}}
498 temperature: 0.2
499"##;
500
501pub const SHOWCASE_09_PKG_VERSIONS: &str = r##"# ═══════════════════════════════════════════════════════════════════
506# Pattern 09: Package Version Checker
507# ═══════════════════════════════════════════════════════════════════
508#
509# Check latest versions of npm/crates packages via API.
510# Demonstrates: for_each + fetch JSON API + extract:jsonpath + structured.
511#
512# Prerequisites: LLM provider (for summary)
513# Run: nika run workflows/showcase/09-pkg-versions.nika.yaml
514
515schema: "nika/workflow@0.12"
516workflow: package-version-checker
517description: "Check latest package versions from crates.io"
518
519provider: "{{PROVIDER}}"
520model: "{{MODEL}}"
521
522tasks:
523 - id: check_crates
524 for_each: ["serde", "tokio", "anyhow", "clap", "tracing"]
525 as: crate_name
526 concurrency: 3
527 retry:
528 max_attempts: 3
529 delay_ms: 1000
530 backoff: 2.0
531 fetch:
532 url: "https://crates.io/api/v1/crates/{{with.crate_name}}"
533 method: GET
534 headers:
535 User-Agent: "nika-workflow/0.1"
536 extract: jsonpath
537 selector: "$.crate.newest_version"
538 timeout: 10
539
540 - id: version_report
541 depends_on: [check_crates]
542 with:
543 versions: $check_crates
544 structured:
545 schema:
546 type: object
547 properties:
548 packages:
549 type: array
550 items:
551 type: object
552 properties:
553 name:
554 type: string
555 latest_version:
556 type: string
557 status:
558 type: string
559 enum: ["up-to-date", "outdated", "unknown"]
560 required: [name, latest_version, status]
561 summary:
562 type: string
563 required: [packages, summary]
564 infer:
565 prompt: |
566 Based on these crate version check results, produce a dependency report.
567 List each package with its latest version and status.
568
569 Version data: {{with.versions}}
570 temperature: 0.1
571"##;
572
573pub const SHOWCASE_10_WEB_SCRAPER: &str = r##"# ═══════════════════════════════════════════════════════════════════
578# Pattern 10: Concurrent Web Scraper
579# ═══════════════════════════════════════════════════════════════════
580#
581# Scrape multiple URLs with rate-limited concurrency (max 3).
582# Each page extracted as markdown and saved as artifact.
583#
584# Prerequisites: None
585# Run: nika run workflows/showcase/10-web-scraper.nika.yaml
586
587schema: "nika/workflow@0.12"
588workflow: concurrent-scraper
589description: "Scrape multiple pages with rate-limited concurrency"
590
591tasks:
592 - id: scrape
593 for_each:
594 - "https://httpbin.org/html"
595 - "https://httpbin.org/robots.txt"
596 - "https://httpbin.org/forms/post"
597 - "https://httpbin.org/links/5"
598 - "https://httpbin.org/xml"
599 as: page_url
600 concurrency: 3
601 fail_fast: false
602 fetch:
603 url: "{{with.page_url}}"
604 extract: markdown
605 timeout: 15
606
607 - id: done
608 depends_on: [scrape]
609 with:
610 pages: $scrape
611 exec:
612 command: |
613 echo 'Scraping complete. {{with.pages | length}} pages collected.'
614 shell: true
615"##;
616
617pub const SHOWCASE_11_MULTI_FORMAT: &str = r##"# ═══════════════════════════════════════════════════════════════════
622# Pattern 11: Multi-Format Export
623# ═══════════════════════════════════════════════════════════════════
624#
625# Generate content once, then export to 4 formats via for_each.
626# Each format produces a separate artifact.
627#
628# Prerequisites: LLM provider
629# Run: nika run workflows/showcase/11-multi-format.nika.yaml
630
631schema: "nika/workflow@0.12"
632workflow: multi-format-export
633description: "Generate content and export to multiple formats"
634
635provider: "{{PROVIDER}}"
636model: "{{MODEL}}"
637
638tasks:
639 - id: generate
640 structured:
641 schema:
642 type: object
643 properties:
644 title:
645 type: string
646 summary:
647 type: string
648 maxLength: 200
649 sections:
650 type: array
651 items:
652 type: object
653 properties:
654 heading:
655 type: string
656 body:
657 type: string
658 required: [heading, body]
659 minItems: 2
660 maxItems: 4
661 required: [title, summary, sections]
662 infer:
663 prompt: |
664 Create a short technical document about "DAG-based workflow engines".
665 Include a title, summary (max 200 chars), and 2-4 sections.
666 temperature: 0.5
667 max_tokens: 800
668
669 - id: export
670 depends_on: [generate]
671 with:
672 content: $generate
673 for_each: ["text", "json", "yaml", "html"]
674 as: fmt
675 concurrency: 4
676 exec:
677 command: |
678 echo 'Exporting as {{with.fmt}}: {{with.content}}'
679 shell: true
680 artifact:
681 path: "output/exports/document.{{with.fmt}}"
682
683 - id: complete
684 depends_on: [export]
685 exec:
686 command: "echo 'All 4 format exports complete.'"
687 shell: true
688"##;
689
690pub const SHOWCASE_12_IMAGE_ANALYSIS: &str = r##"# ═══════════════════════════════════════════════════════════════════
695# Pattern 12: Batch Image Analysis
696# ═══════════════════════════════════════════════════════════════════
697#
698# Download images, then for_each run nika:dimensions + nika:thumbhash.
699# Demonstrates: for_each + fetch binary + invoke builtins + structured.
700#
701# Prerequisites: LLM provider (for summary)
702# Run: nika run workflows/showcase/12-image-analysis.nika.yaml
703
704schema: "nika/workflow@0.12"
705workflow: batch-image-analysis
706description: "Analyze multiple images with builtin tools"
707
708provider: "{{PROVIDER}}"
709model: "{{MODEL}}"
710
711tasks:
712 - id: download_images
713 for_each:
714 - "https://httpbin.org/image/png"
715 - "https://httpbin.org/image/jpeg"
716 - "https://httpbin.org/image/svg"
717 as: img_url
718 concurrency: 3
719 fetch:
720 url: "{{with.img_url}}"
721 response: binary
722 timeout: 15
723
724 - id: get_dimensions
725 depends_on: [download_images]
726 with:
727 images: $download_images
728 for_each: "{{with.images}}"
729 as: img_hash
730 concurrency: 3
731 invoke:
732 tool: "nika:dimensions"
733 params:
734 hash: "{{with.img_hash}}"
735
736 - id: summarize
737 depends_on: [get_dimensions]
738 with:
739 dimension_data: $get_dimensions
740 structured:
741 schema:
742 type: object
743 properties:
744 images_analyzed:
745 type: integer
746 results:
747 type: array
748 items:
749 type: object
750 properties:
751 format:
752 type: string
753 width:
754 type: integer
755 height:
756 type: integer
757 required: [format, width, height]
758 total_pixels:
759 type: integer
760 required: [images_analyzed, results, total_pixels]
761 infer:
762 prompt: |
763 Summarize the image dimension data into a structured report.
764 Include per-image format/width/height and total pixel count.
765
766 Dimension data: {{with.dimension_data}}
767 temperature: 0.1
768"##;
769
770pub const SHOWCASE_13_NESTED_FOREACH: &str = r##"# ═══════════════════════════════════════════════════════════════════
775# Pattern 13: Nested For Each (categories x items)
776# ═══════════════════════════════════════════════════════════════════
777#
778# Outer for_each over categories, each producing items.
779# Inner task iterates over produced items. Simulates nested iteration
780# via chained for_each tasks.
781#
782# Prerequisites: LLM provider
783# Run: nika run workflows/showcase/13-nested-foreach.nika.yaml
784
785schema: "nika/workflow@0.12"
786workflow: nested-foreach
787description: "Nested iteration via chained for_each tasks"
788
789provider: "{{PROVIDER}}"
790model: "{{MODEL}}"
791
792tasks:
793 - id: generate_items
794 for_each: ["technology", "science", "art"]
795 as: category
796 concurrency: 3
797 structured:
798 schema:
799 type: object
800 properties:
801 category:
802 type: string
803 items:
804 type: array
805 items:
806 type: string
807 minItems: 2
808 maxItems: 3
809 required: [category, items]
810 infer:
811 prompt: |
812 For the category "{{with.category}}", generate 2-3 trending topic names.
813 Return the category and the list of items.
814 temperature: 0.7
815 max_tokens: 200
816
817 - id: describe_items
818 depends_on: [generate_items]
819 with:
820 categories: $generate_items
821 for_each: "{{with.categories}}"
822 as: cat_data
823 concurrency: 3
824 structured:
825 schema:
826 type: object
827 properties:
828 category:
829 type: string
830 descriptions:
831 type: array
832 items:
833 type: object
834 properties:
835 topic:
836 type: string
837 description:
838 type: string
839 maxLength: 100
840 required: [topic, description]
841 required: [category, descriptions]
842 infer:
843 prompt: |
844 For each topic in this category, write a one-line description (max 100 chars).
845
846 Category data: {{with.cat_data}}
847 temperature: 0.5
848 max_tokens: 400
849
850 - id: final_report
851 depends_on: [describe_items]
852 with:
853 all_descriptions: $describe_items
854 exec:
855 command: |
856 echo 'Nested for_each complete: {{with.all_descriptions}}'
857 shell: true
858"##;
859
860pub const SHOWCASE_14_FAN_OUT_FAN_IN: &str = r##"# ═══════════════════════════════════════════════════════════════════
865# Pattern 14: Fan-Out Fan-In
866# ═══════════════════════════════════════════════════════════════════
867#
868# Single source → for_each with 5 analyses → merge → synthesize.
869# The classic MapReduce pattern in a workflow.
870#
871# Prerequisites: LLM provider
872# Run: nika run workflows/showcase/14-fan-out-fan-in.nika.yaml
873
874schema: "nika/workflow@0.12"
875workflow: fan-out-fan-in
876description: "MapReduce pattern: fan-out analyses, fan-in synthesis"
877
878provider: "{{PROVIDER}}"
879model: "{{MODEL}}"
880
881tasks:
882 - id: source
883 exec:
884 command: |
885 echo 'Nika is a semantic YAML workflow engine for AI tasks. It supports 5 verbs (infer, exec, fetch, invoke, agent), a DAG scheduler for parallel execution, structured output with JSON Schema validation, and content-addressable storage for media. It connects to knowledge graphs via MCP protocol.'
886 shell: true
887
888 - id: analyze
889 depends_on: [source]
890 with:
891 text: $source
892 for_each:
893 - "technical_accuracy"
894 - "readability"
895 - "completeness"
896 - "market_positioning"
897 - "developer_appeal"
898 as: lens
899 concurrency: 5
900 structured:
901 schema:
902 type: object
903 properties:
904 lens:
905 type: string
906 score:
907 type: number
908 minimum: 0
909 maximum: 10
910 strengths:
911 type: array
912 items:
913 type: string
914 maxItems: 3
915 weaknesses:
916 type: array
917 items:
918 type: string
919 maxItems: 3
920 recommendation:
921 type: string
922 maxLength: 200
923 required: [lens, score, strengths, weaknesses, recommendation]
924 infer:
925 prompt: |
926 Analyze this product description through the lens of "{{with.lens}}".
927 Score 0-10, list up to 3 strengths and 3 weaknesses,
928 and give a recommendation (max 200 chars).
929
930 Text: "{{with.text}}"
931 temperature: 0.4
932 max_tokens: 400
933
934 - id: synthesize
935 depends_on: [analyze]
936 with:
937 analyses: $analyze
938 structured:
939 schema:
940 type: object
941 properties:
942 overall_score:
943 type: number
944 minimum: 0
945 maximum: 10
946 top_strength:
947 type: string
948 top_weakness:
949 type: string
950 action_items:
951 type: array
952 items:
953 type: string
954 minItems: 1
955 maxItems: 5
956 executive_summary:
957 type: string
958 maxLength: 300
959 required: [overall_score, top_strength, top_weakness, action_items, executive_summary]
960 infer:
961 prompt: |
962 Synthesize these 5 analyses into an executive summary.
963 Compute an overall score, identify the top strength and weakness,
964 and list 1-5 action items.
965
966 All analyses: {{with.analyses}}
967 temperature: 0.3
968 max_tokens: 500
969 artifact:
970 path: output/analysis/executive-summary.json
971 format: json
972"##;
973
974pub const SHOWCASE_15_RETRY_PIPELINE: &str = r##"# ═══════════════════════════════════════════════════════════════════
979# Pattern 15: Pipeline with Retry
980# ═══════════════════════════════════════════════════════════════════
981#
982# for_each over tasks with retry on fetch failures + structured results.
983# Demonstrates: for_each + retry + fail_fast:false + structured output.
984#
985# Prerequisites: LLM provider (for summary)
986# Run: nika run workflows/showcase/15-retry-pipeline.nika.yaml
987
988schema: "nika/workflow@0.12"
989workflow: retry-pipeline
990description: "Resilient pipeline with retry on each iteration"
991
992provider: "{{PROVIDER}}"
993model: "{{MODEL}}"
994
995tasks:
996 - id: fetch_with_retry
997 for_each:
998 - "https://httpbin.org/get"
999 - "https://httpbin.org/uuid"
1000 - "https://httpbin.org/delay/1"
1001 - "https://httpbin.org/ip"
1002 - "https://httpbin.org/headers"
1003 as: api_url
1004 concurrency: 3
1005 fail_fast: false
1006 retry:
1007 max_attempts: 3
1008 delay_ms: 500
1009 backoff: 2.0
1010 fetch:
1011 url: "{{with.api_url}}"
1012 method: GET
1013 response: full
1014 timeout: 10
1015
1016 - id: summarize_results
1017 depends_on: [fetch_with_retry]
1018 with:
1019 raw: $fetch_with_retry
1020 structured:
1021 schema:
1022 type: object
1023 properties:
1024 total_requests:
1025 type: integer
1026 successful:
1027 type: integer
1028 failed:
1029 type: integer
1030 results:
1031 type: array
1032 items:
1033 type: object
1034 properties:
1035 url:
1036 type: string
1037 status:
1038 type: string
1039 enum: ["success", "failed", "timeout"]
1040 attempts:
1041 type: integer
1042 minimum: 1
1043 required: [url, status, attempts]
1044 required: [total_requests, successful, failed, results]
1045 max_retries: 2
1046 infer:
1047 prompt: |
1048 Analyze these API call results. For each URL, report its status
1049 and how many attempts were needed. Include totals.
1050
1051 Raw results: {{with.raw}}
1052 temperature: 0.1
1053"##;
1054
1055pub fn get_showcase_workflows() -> Vec<WorkflowTemplate> {
1061 vec![
1062 WorkflowTemplate {
1063 filename: "01-url-status.nika.yaml",
1064 tier_dir: "showcase",
1065 content: SHOWCASE_01_URL_STATUS,
1066 },
1067 WorkflowTemplate {
1068 filename: "02-lang-detect.nika.yaml",
1069 tier_dir: "showcase",
1070 content: SHOWCASE_02_LANG_DETECT,
1071 },
1072 WorkflowTemplate {
1073 filename: "03-sentiment.nika.yaml",
1074 tier_dir: "showcase",
1075 content: SHOWCASE_03_SENTIMENT,
1076 },
1077 WorkflowTemplate {
1078 filename: "04-translation.nika.yaml",
1079 tier_dir: "showcase",
1080 content: SHOWCASE_04_TRANSLATION,
1081 },
1082 WorkflowTemplate {
1083 filename: "05-api-tester.nika.yaml",
1084 tier_dir: "showcase",
1085 content: SHOWCASE_05_API_TESTER,
1086 },
1087 WorkflowTemplate {
1088 filename: "06-link-checker.nika.yaml",
1089 tier_dir: "showcase",
1090 content: SHOWCASE_06_LINK_CHECKER,
1091 },
1092 WorkflowTemplate {
1093 filename: "07-rss-aggregator.nika.yaml",
1094 tier_dir: "showcase",
1095 content: SHOWCASE_07_RSS_AGGREGATOR,
1096 },
1097 WorkflowTemplate {
1098 filename: "08-git-analyzer.nika.yaml",
1099 tier_dir: "showcase",
1100 content: SHOWCASE_08_GIT_ANALYZER,
1101 },
1102 WorkflowTemplate {
1103 filename: "09-pkg-versions.nika.yaml",
1104 tier_dir: "showcase",
1105 content: SHOWCASE_09_PKG_VERSIONS,
1106 },
1107 WorkflowTemplate {
1108 filename: "10-web-scraper.nika.yaml",
1109 tier_dir: "showcase",
1110 content: SHOWCASE_10_WEB_SCRAPER,
1111 },
1112 WorkflowTemplate {
1113 filename: "11-multi-format.nika.yaml",
1114 tier_dir: "showcase",
1115 content: SHOWCASE_11_MULTI_FORMAT,
1116 },
1117 WorkflowTemplate {
1118 filename: "12-image-analysis.nika.yaml",
1119 tier_dir: "showcase",
1120 content: SHOWCASE_12_IMAGE_ANALYSIS,
1121 },
1122 WorkflowTemplate {
1123 filename: "13-nested-foreach.nika.yaml",
1124 tier_dir: "showcase",
1125 content: SHOWCASE_13_NESTED_FOREACH,
1126 },
1127 WorkflowTemplate {
1128 filename: "14-fan-out-fan-in.nika.yaml",
1129 tier_dir: "showcase",
1130 content: SHOWCASE_14_FAN_OUT_FAN_IN,
1131 },
1132 WorkflowTemplate {
1133 filename: "15-retry-pipeline.nika.yaml",
1134 tier_dir: "showcase",
1135 content: SHOWCASE_15_RETRY_PIPELINE,
1136 },
1137 ]
1138}
1139
1140#[cfg(test)]
1141mod tests {
1142 use super::*;
1143
1144 #[test]
1145 fn test_showcase_workflow_count() {
1146 let workflows = get_showcase_workflows();
1147 assert_eq!(
1148 workflows.len(),
1149 15,
1150 "Should have exactly 15 showcase workflows"
1151 );
1152 }
1153
1154 #[test]
1155 fn test_showcase_filenames_unique() {
1156 let workflows = get_showcase_workflows();
1157 let mut names: Vec<&str> = workflows.iter().map(|w| w.filename).collect();
1158 let len = names.len();
1159 names.sort();
1160 names.dedup();
1161 assert_eq!(names.len(), len, "All filenames must be unique");
1162 }
1163
1164 #[test]
1165 fn test_showcase_all_have_schema() {
1166 let workflows = get_showcase_workflows();
1167 for w in &workflows {
1168 assert!(
1169 w.content.contains("schema: \"nika/workflow@0.12\""),
1170 "Workflow {} must declare schema",
1171 w.filename
1172 );
1173 }
1174 }
1175
1176 #[test]
1177 fn test_showcase_all_have_tasks() {
1178 let workflows = get_showcase_workflows();
1179 for w in &workflows {
1180 assert!(
1181 w.content.contains("tasks:"),
1182 "Workflow {} must have tasks section",
1183 w.filename
1184 );
1185 }
1186 }
1187
1188 #[test]
1189 fn test_showcase_all_nika_yaml_extension() {
1190 let workflows = get_showcase_workflows();
1191 for w in &workflows {
1192 assert!(
1193 w.filename.ends_with(".nika.yaml"),
1194 "Workflow {} must end with .nika.yaml",
1195 w.filename
1196 );
1197 }
1198 }
1199
1200 #[test]
1201 fn test_showcase_all_have_for_each() {
1202 let workflows = get_showcase_workflows();
1203 for w in &workflows {
1204 assert!(
1205 w.content.contains("for_each:"),
1206 "Workflow {} must use for_each (showcase pattern requirement)",
1207 w.filename
1208 );
1209 }
1210 }
1211
1212 #[test]
1213 fn test_showcase_structured_or_schema_present() {
1214 let workflows = get_showcase_workflows();
1215 let structured_count = workflows
1216 .iter()
1217 .filter(|w| w.content.contains("structured:"))
1218 .count();
1219 assert!(
1221 structured_count >= 10,
1222 "At least 10 workflows should use structured:, found {}",
1223 structured_count
1224 );
1225 }
1226
1227 #[test]
1228 fn test_showcase_concurrency_present() {
1229 let workflows = get_showcase_workflows();
1230 let concurrent_count = workflows
1231 .iter()
1232 .filter(|w| w.content.contains("concurrency:"))
1233 .count();
1234 assert!(
1235 concurrent_count >= 10,
1236 "At least 10 workflows should use concurrency:, found {}",
1237 concurrent_count
1238 );
1239 }
1240
1241 #[test]
1242 fn test_showcase_all_in_showcase_dir() {
1243 let workflows = get_showcase_workflows();
1244 for w in &workflows {
1245 assert_eq!(
1246 w.tier_dir, "showcase",
1247 "Workflow {} must be in showcase directory",
1248 w.filename
1249 );
1250 }
1251 }
1252
1253 #[test]
1254 fn test_showcase_valid_yaml() {
1255 let workflows = get_showcase_workflows();
1256 for w in &workflows {
1257 if w.content.contains("{{PROVIDER}}") || w.content.contains("{{MODEL}}") {
1259 continue;
1260 }
1261 let parsed: Result<serde_json::Value, _> = serde_saphyr::from_str(w.content);
1262 assert!(
1263 parsed.is_ok(),
1264 "Workflow {} must be valid YAML: {:?}",
1265 w.filename,
1266 parsed.err()
1267 );
1268 }
1269 }
1270
1271 #[test]
1272 fn test_showcase_workflow_names_unique() {
1273 let workflows = get_showcase_workflows();
1274 let mut workflow_names: Vec<&str> = Vec::new();
1275 for w in &workflows {
1276 for line in w.content.lines() {
1278 let trimmed = line.trim();
1279 if let Some(name) = trimmed.strip_prefix("workflow: ") {
1280 workflow_names.push(name);
1281 }
1282 }
1283 }
1284 let len = workflow_names.len();
1285 workflow_names.sort();
1286 workflow_names.dedup();
1287 assert_eq!(
1288 workflow_names.len(),
1289 len,
1290 "All workflow: names must be unique"
1291 );
1292 }
1293}