AI Workflows
Build AI workflows with your preferred LLM.
Kestra provides plugins for multiple LLM providers and continues to add more with each release. You can design flows that use your chosen model and seamlessly integrate AI into orchestration workflows.
AI workflows
The following examples demonstrate Kestra AI plugins for a variety of workflows. You can adapt each example to your chosen provider. Three key properties are important to understand:
type
: Defines the LLM provider plugin and task (e.g.,ChatCompletion
with OpenAI).apiKey
: Access key for the provider – store this as a key-value pair in Kestra Open Source or as a secret in Enterprise Edition.model
: Specifies the provider model. Models vary in performance, cost, and capabilities, so choose the one that best fits your use case.
Different provider plugins may include additional properties beyond those shown in the examples. Refer to each plugin’s documentation for a complete list. Common properties to be aware of include prompt
, messages
, jsonResponseSchema
, to name a few.
Check the weather is suitable for sports every day using Gemini
This flow checks the daily wind conditions in Cambridgeshire and uses Google Gemini to decide whether it is suitable to go sailing. If the wind speed falls within the preferred range (above 10 knots and below 30 knots), the flow notifies you in Slack with the recommendation and automatically blocks your calendar for the day with an ‘Out of office – gone sailing’ event. It runs every morning at 8
AM on a schedule.id: check_weathernamespace: company.ai
tasks: - id: ask_ai type: io.kestra.plugin.gemini.StructuredOutputCompletion apiKey: "{{ kv('GEMINI_API_KEY') }}" model: "gemini-2.5-flash-preview-05-20" prompt: "I like to go sailing when the wind is above 10 knots but below 30 knots. I sail in Cambridgeshire. If the wind is within that range, I want to know if I should go sailing or not. Also tell me the current wind speed speeds" jsonResponseSchema: | { "type": "object", "properties": { "content": { "type": "string" }, "wind": { "type": "number" }, "go_sailing": { "type": "boolean" } } } }
- id: if type: io.kestra.plugin.core.flow.If condition: "{{ outputs.ask_ai['predictions'] | first | jq('.go_sailing') | first }}" then: - id: notify_me type: io.kestra.plugin.notifications.slack.SlackIncomingWebhook url: "{{ kv('SLACK_WEBHOOK') }}" payload: | { "text": "{{ outputs.ask_ai['predictions'] | first | jq('.content') | first }}" } - id: block_calendar type: io.kestra.plugin.googleworkspace.calendar.InsertEvent calendarId: "{{ kv('CALENDAR_ID') }}" serviceAccount: "{{ kv('GOOGLE_SA') }}" summary: Out of office description: "Gone sailing because the wind is {{ outputs.ask_ai['predictions'] | first | jq('.wind') | first }} knots" startTime: dateTime: "{{ now() | date(\"yyyy-MM-dd'T'09:00:00+01:00\") }}" timeZone: "Europe/London" endTime: dateTime: "{{ now() | date(\"yyyy-MM-dd'T'18:00:00+01:00\") }}" timeZone: "Europe/London" creator: email: wrussell@kestra.io
triggers: - id: check_daily type: io.kestra.plugin.core.trigger.Schedule cron: "* 8 * * *"
Create tasks with natural language prompts using DeepSeek and Todoist
This flow turns natural language prompts into structured Todoist tasks using an AI model. Each item is parsed into a title, description, and due date, then automatically created in your Todoist workspace via the REST API.
id: add_tasks_to_todoistnamespace: company.ai
inputs: - id: prompt type: STRING displayName: What would you like to add to your task list? description: List out all the things you need to get done defaults: I need to get my prescription on Friday afternoon and go shopping afterwards
tasks: - id: create_task_fields type: io.kestra.plugin.deepseek.ChatCompletion apiKey: '{{ kv("DEEPSEEK_API_KEY") }}' modelName: deepseek-chat messages: - type: SYSTEM content: You are going to help to write a todo list inside of Todoist. I need you to return any user messages as tasks in JSON format only. There might be multiple tasks. The current time is '{{ now() }}' - type: USER content: "{{ inputs.prompt }}" jsonResponseSchema: | { type: "object", "properties": { "tasks": { "type": "array", "items": { "type": "object", "properties": { "title": "string", "description": "string", "due_date": { "type": "string", "format": "date-time" "description": "Due date of the task (as a RFC 3339 timestamp).", } } } } } }
- id: create_tasks type: io.kestra.plugin.core.flow.ForEach values: "{{ outputs.create_task_fields.response | jq('.tasks') | first }}" tasks: - id: create_task type: io.kestra.plugin.core.http.Request uri: https://api.todoist.com/rest/v2/tasks method: POST contentType: application/json headers: Authorization: "Bearer {{ kv('TODOIST_API_TOKEN') }}" body: | { "content": "{{ taskrun.value | jq('.title') | first }}", "description": "{{ taskrun.value | jq('.description') | first }}", "due_datetime": "{{ taskrun.value | jq('.due_date') | first }}" }
Generate an image with OpenAI with human approval
This flow generates an image from a user prompt, sends it to a Discord channel for review, and waits for approval. If approved, the image is finalized and logged; if rejected, the user can provide feedback to regenerate a new image, which is then shared again on Discord.
id: gen_img_approvalnamespace: company.ai
inputs: - id: image_prompt type: STRING
variables: discord_webhook: "https://discord.com/api/webhooks/URL"
tasks:
- id: gen_img type: io.kestra.plugin.core.flow.Subflow namespace: demo flowId: generate_image inputs: openai_prompt: "{{ inputs.image_prompt }}"
- id: send_image type: io.kestra.plugin.notifications.discord.DiscordExecution content: "Are you happy with the image: {{ outputs.gen_img.outputs.image }}. Approve it here: http://localhost:8082/ui/executions/{{flow.namespace}}/{{flow.id}}/{{execution.id}} " url: "{{ vars.discord_webhook }}"
- id: wait_for_approval type: io.kestra.plugin.core.flow.Pause onResume: - id: approve description: Are you happy with the photo or not? type: BOOLEAN
- id: feedback description: Write the prompt again with more detail type: STRING
- id: try_again type: io.kestra.plugin.core.flow.If condition: "{{ outputs.wait_for_approval.onResume.approve }}" then: - id: approved type: io.kestra.plugin.core.log.Log message: "Final photo: {{ outputs.gen_img.outputs.image }}" else: - id: retry type: io.kestra.plugin.core.flow.Subflow namespace: demo flowId: generate_image inputs: openai_prompt: "{{ outputs.wait_for_approval.onResume.feedback }}"
- id: send_new_image type: io.kestra.plugin.notifications.discord.DiscordExecution content: "Here's the new image with your feedback: {{ outputs.retry.outputs.image }}" url: "{{ vars.discord_webhook }}"
Summarize Git commits from the past week using Ollama
This flow automatically summarizes Git commits from the past week in a specified repository and branch. Each Friday at 15
UTC, it generates a plain-text summary using Ollama and posts it to Slack, keeping teams updated on project progress.id: ai-summarize-weekly-git-commitsnamespace: company.ai
inputs: - id: repository type: URI defaults: https://github.com/kestra-io/blueprints description: Repository to summarize last week's progress
- id: branch type: STRING defaults: main description: Git branch to summarize last week's progress
tasks: - id: wdir type: io.kestra.plugin.core.flow.WorkingDirectory tasks: - id: clone_repo type: io.kestra.plugin.git.Clone branch: "{{ inputs.branch }}" url: "{{ inputs.repository }}"
- id: fetch_commits type: io.kestra.plugin.scripts.shell.Commands taskRunner: type: io.kestra.plugin.scripts.runner.docker.Docker containerImage: bitnami/git:latest commands: # 0. Set safe.directory for Git to avoid "dubious ownership" errors - git config --global --add safe.directory "$(pwd)"
# 1. Deepen clone if shallow - git fetch --unshallow origin {{ inputs.branch }} || true
# 2. Update main branch - git fetch origin {{ inputs.branch }}
# 3. Fetch commits from the last 7 days (weekly) - git log origin/{{ inputs.branch }} --since="7 days ago" --pretty=format:"%h %ad %s" --date=short > commits.txt
# 4. Show how many were found - echo "Fetched $(wc -l < commits.txt) commits from the last 7 days" outputFiles: - commits.txt
- id: summarize_commits type: io.kestra.plugin.ollama.cli.OllamaCLI enableModelCaching: true modelCachePath: "{{ kv('OLLAMA_CACHE_PATH') }}" commands: - "ollama run gemma3:1b \"Summarize the following Git commits into a clear and concise weekly development update for users. Output plain text for Slack, no markdown or extra formatting. Ensure no markdown syntax like **bold text** in the response — stick to plain text! Here are the commit messages: {{ read(outputs.fetch_commits.outputFiles['commits.txt']) }}\" > output.txt" outputFiles: - output.txt
- id: slack type: io.kestra.plugin.notifications.slack.SlackIncomingWebhook url: "{{ kv('SLACK_WEBHOOK') }}" payload: | {{ { "text": "This week's repository updates for " ~ inputs.repository ~ ". " ~ read(outputs.summarize_commits.outputFiles['output.txt']) } }}
triggers: - id: weekly-trigger type: io.kestra.plugin.core.trigger.Schedule cron: "0 15 * * 5" # Every Friday at 15:00 (3:00 PM) UTC