OrkaJS
Orka.JS

Workflows en Graphe

Construisez des flux IA complexes avec conditions, branches, exécution parallèle et export de diagrammes Mermaid.

Pourquoi des Workflows en Graphe ?

Alors que les workflows linéaires (Multi-Step) sont parfaits pour les pipelines simples, les Workflows en Graphe vous permettent de construire des flux complexes avec des conditions, des branches, des boucles et une exécution parallèle. Pensez-y comme des organigrammes pour l'IA — vous définissez des nœuds (actions) et des arêtes (connexions), et le moteur de graphe gère l'exécution.

import { createOrka, OpenAIAdapter } from 'orkajs';
import { startNode, endNode, actionNode, conditionNode, llmNode, edge } from 'orkajs/graph';
 
const orka = createOrka({
llm: new OpenAIAdapter({ apiKey: process.env.OPENAI_API_KEY! }),
vectorDB: myVectorDB,
});
 
const graph = orka.graph({
name: 'smart-support',
nodes: [
startNode('start'),
actionNode('classify', async (ctx) => {
const result = await ctx.llm.generate(
`Classify as "technical" or "general": ${ctx.input}`
);
ctx.metadata.category = result.content.trim().toLowerCase();
return ctx;
}),
conditionNode('router', (ctx) => ctx.metadata.category as string),
llmNode('respond', { temperature: 0.3 }),
endNode('end'),
],
edges: [
edge('start', 'classify'),
edge('classify', 'router'),
edge('router', 'respond', 'technical'), // If category === 'technical'
edge('router', 'respond', 'general'), // If category === 'general'
edge('respond', 'end'),
],
});
 
const result = await graph.run('How do I configure SSL?');
 
console.log(result.output); // Final response
console.log(result.path); // ['start', 'classify', 'router', 'respond', 'end']
console.log(result.steps); // Detailed execution trace

# Types de Nœuds

Orka fournit sept types de nœuds pour construire des workflows en graphe :

startNode(id)

Point d'entrée du graphe

endNode(id)

Point de sortie, arrête l'exécution

actionNode(id, fn)

Action personnalisée qui transforme le contexte

conditionNode(id, fn)

Nœud de branchement, retourne un label

llmNode(id, opts)

Appelle le LLM avec options configurables

retrieveNode(id, name)

Recherche sémantique dans une base

parallelNode(id, nodeIds)

Exécuter plusieurs nœuds simultanément

# Détails des Nœuds

startNode(id) / endNode(id)

Points d'entrée et de sortie du graphe. Chaque graphe doit avoir exactement un startNode et au moins un endNode.

startNode('start') // Entry point
endNode('end') // Exit point - stops execution and returns result

actionNode(id, fn)

Exécute une logique personnalisée. Reçoit le contexte du graphe et doit le retourner (possiblement modifié).

actionNode('process', async (ctx) => {
// Access input, output, metadata, llm
const processed = ctx.input.toUpperCase();
ctx.output = processed;
ctx.metadata.processedAt = Date.now();
return ctx;
})

conditionNode(id, fn)

Nœud de branchement qui retourne un label string. Le graphe suit l'arête correspondant à ce label.

conditionNode('router', (ctx) => {
const score = ctx.metadata.score as number;
if (score > 0.8) return 'high';
if (score > 0.5) return 'medium';
return 'low';
})
 
// Edges:
edge('router', 'premium-response', 'high')
edge('router', 'standard-response', 'medium')
edge('router', 'fallback-response', 'low')

llmNode(id, options)

Appelle le LLM avec le contexte actuel. Construit automatiquement un prompt à partir de l'entrée + docs récupérés.

llmNode('generate', {
temperature: 0.7,
maxTokens: 1000,
systemPrompt: 'You are a helpful assistant.'
})

retrieveNode(id, knowledgeName, options)

Effectue une recherche sémantique et ajoute les résultats à ctx.retrievedDocs.

retrieveNode('search', 'documentation', {
topK: 5,
minScore: 0.7
})

parallelNode(id, nodeIds)

Exécute plusieurs nœuds simultanément. Les résultats sont fusionnés dans ctx.parallelResults.

// Define nodes to run in parallel
actionNode('fetch-weather', async (ctx) => { ... }),
actionNode('fetch-news', async (ctx) => { ... }),
actionNode('fetch-stocks', async (ctx) => { ... }),
 
// Parallel node runs all three simultaneously
parallelNode('gather-data', ['fetch-weather', 'fetch-news', 'fetch-stocks'])
 
// After execution:
// ctx.parallelResults = {
// 'fetch-weather': { ... },
// 'fetch-news': { ... },
// 'fetch-stocks': { ... }
// }

# Arêtes

Les arêtes connectent les nœuds et définissent le flux. Pour les nœuds de condition, vous pouvez spécifier un label à faire correspondre.

import { edge } from 'orkajs/graph';
 
// Simple edge: from -> to
edge('start', 'process')
 
// Conditional edge: from -> to (when condition returns 'label')
edge('router', 'premium-path', 'premium')
edge('router', 'standard-path', 'standard')
 
// Multiple edges from one node
edges: [
edge('start', 'classify'),
edge('classify', 'router'),
edge('router', 'technical-support', 'technical'),
edge('router', 'general-support', 'general'),
edge('router', 'sales', 'sales'),
edge('technical-support', 'end'),
edge('general-support', 'end'),
edge('sales', 'end'),
]

# Export Mermaid

Visualisez votre graphe sous forme de diagramme Mermaid pour la documentation ou le débogage :

console.log(graph.toMermaid());
 
// Output:
// graph TD
// start((start))
// classify[classify]
// router{router}
// respond[respond]
// end((end))
// start --> classify
// classify --> router
// router -->|technical| respond
// router -->|general| respond
// respond --> end

💡 Astuce

Collez la sortie Mermaid dans mermaid.live ou n'importe quel renderer Markdown qui supporte Mermaid pour visualiser votre graphe.

Exemple Complet

import { createOrka, OpenAIAdapter, PineconeAdapter } from 'orkajs';
import {
startNode, endNode, actionNode, conditionNode,
llmNode, retrieveNode, parallelNode, edge
} from 'orkajs/graph';
 
const orka = createOrka({
llm: new OpenAIAdapter({ apiKey: process.env.OPENAI_API_KEY! }),
vectorDB: new PineconeAdapter({ apiKey: process.env.PINECONE_API_KEY! }),
});
 
const supportGraph = orka.graph({
name: 'intelligent-support',
nodes: [
startNode('start'),
 
// Classify the query
actionNode('classify', async (ctx) => {
const result = await ctx.llm.generate(
`Classify this query as 'technical', 'billing', or 'general': ${ctx.input}`
);
ctx.metadata.category = result.content.trim().toLowerCase();
return ctx;
}),
 
// Route based on classification
conditionNode('router', (ctx) => ctx.metadata.category as string),
 
// Technical path: search docs first
retrieveNode('search-docs', 'technical-docs', { topK: 5 }),
llmNode('technical-response', {
temperature: 0.3,
systemPrompt: 'You are a technical support expert. Use the provided documentation.'
}),
 
// Billing path: direct response
llmNode('billing-response', {
temperature: 0.5,
systemPrompt: 'You are a billing support agent. Be helpful and clear about policies.'
}),
 
// General path
llmNode('general-response', { temperature: 0.7 }),
 
endNode('end'),
],
edges: [
edge('start', 'classify'),
edge('classify', 'router'),
edge('router', 'search-docs', 'technical'),
edge('router', 'billing-response', 'billing'),
edge('router', 'general-response', 'general'),
edge('search-docs', 'technical-response'),
edge('technical-response', 'end'),
edge('billing-response', 'end'),
edge('general-response', 'end'),
],
});
 
// Run the graph
const result = await supportGraph.run('How do I configure SSL certificates?');
 
console.log('Category:', result.metadata.category); // 'technical'
console.log('Path:', result.path); // ['start', 'classify', 'router', 'search-docs', 'technical-response', 'end']
console.log('Answer:', result.output);

Imports Tree-shakeable

// ✅ Import only what you need
import {
startNode, endNode, actionNode, conditionNode,
llmNode, retrieveNode, parallelNode, edge
} from 'orkajs/graph';
 
// ✅ Or import from main package
import { startNode, endNode, actionNode, edge } from 'orkajs';