Skip to content

Commit 962cbe5

Browse files
authored
[WIP] 📦 NEW: Add workflows integration (#88)
* 📦 NEW: add workflows integration * 👌 IMPROVE: update the workflow example * 👌 IMPROVE: llmKey -> apiKey
1 parent bc25765 commit 962cbe5

File tree

3 files changed

+183
-1
lines changed

3 files changed

+183
-1
lines changed
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import 'dotenv/config';
2+
import {Langbase, Workflow} from 'langbase';
3+
4+
const langbase = new Langbase({
5+
apiKey: process.env.LANGBASE_API_KEY!,
6+
});
7+
8+
async function main() {
9+
const {step} = new Workflow({debug: true});
10+
11+
const result = await step({
12+
id: 'sumamrize',
13+
run: async () => {
14+
return langbase.llm.run({
15+
model: 'openai:gpt-4o-mini',
16+
apiKey: process.env.OPENAI_API_KEY!,
17+
messages: [
18+
{
19+
role: 'system',
20+
content:
21+
'You are an expert summarizer. Summarize the user input.',
22+
},
23+
{
24+
role: 'user',
25+
content:
26+
'I am testing workflows. I just created an example of summarize workflow. Can you summarize this?',
27+
},
28+
],
29+
stream: false,
30+
});
31+
},
32+
});
33+
34+
console.log(result['completion']);
35+
}
36+
37+
main();

packages/langbase/src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export * from './langbase/langbase';
22
export * from './pipes/pipes';
3-
export * from './lib/helpers'
3+
export * from './lib/helpers';
4+
export * from './langbase/workflows';
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
type WorkflowContext = {
2+
outputs: Record<string, any>;
3+
};
4+
5+
type RetryConfig = {
6+
limit: number;
7+
delay: number;
8+
backoff: 'exponential' | 'linear' | 'fixed';
9+
};
10+
11+
type StepConfig<T = any> = {
12+
id: string;
13+
timeout?: number;
14+
retries?: RetryConfig;
15+
run: () => Promise<T>;
16+
};
17+
18+
class TimeoutError extends Error {
19+
constructor(stepId: string, timeout: number) {
20+
super(`Step "${stepId}" timed out after ${timeout}ms`);
21+
this.name = 'TimeoutError';
22+
}
23+
}
24+
25+
export class Workflow {
26+
private context: WorkflowContext;
27+
private debug: boolean;
28+
public readonly step: <T = any>(config: StepConfig<T>) => Promise<T>;
29+
30+
constructor(config: {debug?: boolean} = {debug: false}) {
31+
this.context = {outputs: {}};
32+
this.debug = config.debug ?? false;
33+
this.step = this._step.bind(this);
34+
}
35+
36+
private async _step<T = any>(config: StepConfig<T>): Promise<T> {
37+
if (this.debug) {
38+
console.log(`\n🔄 Starting step: ${config.id}`);
39+
console.time(`⏱️ Step ${config.id}`);
40+
if (config.timeout) console.log(`⏳ Timeout: ${config.timeout}ms`);
41+
if (config.retries)
42+
console.log(`🔄 Retries: ${JSON.stringify(config.retries)}`);
43+
}
44+
45+
let lastError: Error | null = null;
46+
let attempt = 1;
47+
const maxAttempts = config.retries?.limit
48+
? config.retries.limit + 1
49+
: 1;
50+
51+
while (attempt <= maxAttempts) {
52+
try {
53+
let stepPromise = config.run();
54+
55+
if (config.timeout) {
56+
stepPromise = this.withTimeout({
57+
promise: stepPromise,
58+
timeout: config.timeout,
59+
stepId: config.id,
60+
});
61+
}
62+
63+
const result = await stepPromise;
64+
this.context.outputs[config.id] = result;
65+
66+
if (this.debug) {
67+
console.timeEnd(`⏱️ Step ${config.id}`);
68+
console.log(`📤 Output:`, result);
69+
console.log(`✅ Completed step: ${config.id}\n`);
70+
}
71+
72+
return result;
73+
} catch (error) {
74+
lastError = error as Error;
75+
76+
if (attempt < maxAttempts) {
77+
const delay = config.retries
78+
? this.calculateDelay(
79+
config.retries.delay,
80+
attempt,
81+
config.retries.backoff,
82+
)
83+
: 0;
84+
85+
if (this.debug) {
86+
console.log(
87+
`⚠️ Attempt ${attempt} failed, retrying in ${delay}ms...`,
88+
);
89+
console.error(error);
90+
}
91+
92+
await this.sleep(delay);
93+
attempt++;
94+
} else {
95+
if (this.debug) {
96+
console.timeEnd(`⏱️ Step ${config.id}`);
97+
console.error(`❌ Failed step: ${config.id}`, error);
98+
}
99+
throw lastError;
100+
}
101+
}
102+
}
103+
104+
throw lastError;
105+
}
106+
107+
private async withTimeout<T>({
108+
promise,
109+
timeout,
110+
stepId,
111+
}: {
112+
promise: Promise<T>;
113+
timeout: number;
114+
stepId: string;
115+
}): Promise<T> {
116+
const timeoutPromise = new Promise<never>((_, reject) => {
117+
setTimeout(
118+
() => reject(new TimeoutError(stepId, timeout)),
119+
timeout,
120+
);
121+
});
122+
return Promise.race([promise, timeoutPromise]);
123+
}
124+
125+
private calculateDelay(
126+
baseDelay: number,
127+
attempt: number,
128+
backoff: RetryConfig['backoff'],
129+
): number {
130+
switch (backoff) {
131+
case 'exponential':
132+
return baseDelay * Math.pow(2, attempt - 1);
133+
case 'linear':
134+
return baseDelay * attempt;
135+
case 'fixed':
136+
default:
137+
return baseDelay;
138+
}
139+
}
140+
141+
private async sleep(ms: number): Promise<void> {
142+
return new Promise(resolve => setTimeout(resolve, ms));
143+
}
144+
}

0 commit comments

Comments
 (0)