feat(flow): AI node — in-flow LLM completion (Claude/OpenAI/Kimi)#18
Merged
Conversation
Adds a 25th flow node that calls an LLM from inside a DAG. Provider, API
keys, and default models resolve from the project secret vault exactly like
the in-app assistant (CLAUDE_API_KEY / OPENAI_API_KEY / KIMI_API_KEY,
*_DEFAULT_MODEL, CLAUDE_DEFAULT_PROVIDER), or per-node overrides.
Engine:
- new internal/llm package — provider-agnostic client (Anthropic Messages
API + OpenAI-compatible chat completions), standard-library only, no
vendor SDK. Endpoints are vars so tests can target httptest.
- executeAINode + dispatch case models.JobTypeAI in flow_executor.go.
Prompts are already ${{...}}-substituted by the time dispatch runs;
optional JSON-object mode parses output to ${{nodeId.json.field}}.
Output carries response/model/provider/stopReason/usage. Runs through
the existing retry + circuit-breaker path.
- 5 new tests (provider request shaping, JSON mode, token cap, upstream
error, validation).
UI:
- AINode + AINodeProperties, registered in nodes/properties indexes.
- new "AI" palette category (Sparkles icon, #8B5CF6), icon/color in BaseNode.
- node type 'ai' follows the existing nodeType.toUpperCase() === JobType
convention, so it serializes to "AI" with no mapping changes.
Docs: README features (24→25 + AI bullet), CHANGELOG [Unreleased],
CLAUDE.md, in-app API docs node list.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reposition the AI node for adoption: lead the hero tagline + paragraph with AI/LLM nodes, add an "AI / LLM node in a step" row to the comparison table (built-in vs none of Cronicle/Dkron/DolphinScheduler; Airflow only via a provider pkg + code), rewrite the feature bullet as benefit-driven with a concrete webhook→classify→route example, and add an "AI triage" use case. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds a 25th flow node — AI — that calls an LLM (Claude / OpenAI / Kimi) from inside a DAG. The engine-side twin of the existing in-app assistant: same provider set, same secret names, same default models.
Why
RunLoop already has a deep node palette but no way to use an LLM inside a flow — only the side-panel assistant. An AI node turns common one-liners (summarize, classify, extract, rewrite, structure-to-JSON) into a single drag-drop node, and slots into the existing variable/retry/DLQ machinery.
Engine
internal/llm— provider-agnostic client (Anthropic Messages API + OpenAI-compatible chat completions for OpenAI/Kimi). Standard-library only, no vendor SDK — we don't want a third-party retry/transport stack underneath the engine's own retry + circuit-breaker. Endpoints are packagevars so tests targethttptest.executeAINode+case models.JobTypeAIinflow_executor.go. Provider/key/model resolution mirrorssrc/app/api/ai/chat/route.ts(config.provider→CLAUDE_DEFAULT_PROVIDER→ first configured key; key from<PROVIDER>_API_KEY; model fromconfig.model→<PROVIDER>_DEFAULT_MODEL→ built-in default). Prompts are${{...}}-substituted before dispatch. Optional JSON-object mode parses output to${{nodeId.json.field}}. Output carriesresponse,model,provider,stopReason,usage. Goes through the existing retry + circuit-breaker path.max_tokenscap, upstream error, input validation.UI
AINode+AINodeProperties, registered in the node/properties indexes.#8B5CF6); icon/color added toBaseNode.'ai'follows the existingnodeType.toUpperCase() === JobTypeconvention → serializes to"AI"with no mapping-table changes.Config / Output
provider(auto/claude/openai/kimi)${{node.response}}model,systemPrompt,prompt(req)${{node.json.*}}(json mode)temperature,maxTokens(cap 4096)${{node.usage.totalTokens}}responseFormat(text/json),timeoutmodel·provider·stopReasonOut of scope (phase 2)
Single-shot completion only. The agent/tool loop (
tools: [http, database]) is designed-for —llm.Requestalready takes aMessages[]slice — but deferred: it needs a tool-schema + execution loop, a meaningfully bigger change.Tests
go build ./...✅,go vet✅,go test ./...✅ (incl. newinternal/llm)tsc --noEmit✅,vitest36/36 ✅🤖 Generated with Claude Code