Skip to content

Commit 867ec69

Browse files
committed
feat(api): add GptOss120BNode and GptOss20BNode implementations with integration tests
1 parent f84a855 commit 867ec69

5 files changed

Lines changed: 285 additions & 0 deletions

File tree

apps/api/src/nodes/cloudflare-node-registry.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,8 @@ import { Hermes2ProMistral7BNode } from "./text/hermes-2-pro-mistral-7b-node";
211211
import { InputTextNode } from "./text/input-text-node";
212212
import { Llama318BInstructFastNode } from "./text/llama-3-1-8b-instruct-fast-node";
213213
import { Llama3370BInstructFastNode } from "./text/llama-3-3-70b-instruct-fp8-fast-node";
214+
import { GptOss120BNode } from "./text/gpt-oss-120b-node";
215+
import { GptOss20BNode } from "./text/gpt-oss-20b-node";
214216
import { Llama4Scout17B16EInstructNode } from "./text/llama-4-scout-17b-16e-instruct-node";
215217
import { M2m10012bNode } from "./text/m2m100-1-2b-node";
216218
import { MistralSmall31_24BInstructNode } from "./text/mistral-small-3-1-24b-instruct-node";
@@ -285,6 +287,8 @@ export class CloudflareNodeRegistry extends BaseNodeRegistry {
285287
this.registerImplementation(SliderNode);
286288
this.registerImplementation(Llama318BInstructFastNode);
287289
this.registerImplementation(Llama3370BInstructFastNode);
290+
this.registerImplementation(GptOss120BNode);
291+
this.registerImplementation(GptOss20BNode);
288292
this.registerImplementation(DeepseekR1DistillQwen32BNode);
289293
this.registerImplementation(Hermes2ProMistral7BNode);
290294
this.registerImplementation(Llama4Scout17B16EInstructNode);
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { Node } from "@dafthunk/types";
2+
import { env } from "cloudflare:test";
3+
import { describe, expect, it } from "vitest";
4+
5+
import { NodeContext } from "../types";
6+
import { GptOss120BNode } from "./gpt-oss-120b-node";
7+
8+
describe("GptOss120BNode", () => {
9+
it("should execute manually", async () => {
10+
const nodeId = "gpt-oss-120b";
11+
const node = new GptOss120BNode({
12+
nodeId,
13+
} as unknown as Node);
14+
15+
const input = "What is the capital of France?";
16+
const context = {
17+
nodeId,
18+
inputs: {
19+
input,
20+
},
21+
env: {
22+
AI: env.AI,
23+
},
24+
} as unknown as NodeContext;
25+
26+
const result = await node.execute(context);
27+
expect(result.status).toBe("completed");
28+
expect(result.outputs).toBeDefined();
29+
expect(result.outputs?.response).toBeDefined();
30+
expect(typeof result.outputs?.response).toBe("string");
31+
expect(result.outputs?.response.length).toBeGreaterThan(0);
32+
});
33+
34+
it("should execute with custom instructions", async () => {
35+
const nodeId = "gpt-oss-120b";
36+
const node = new GptOss120BNode({
37+
nodeId,
38+
} as unknown as Node);
39+
40+
const input = "What is the capital of France?";
41+
const instructions = "You are a concise assistant. Provide brief answers.";
42+
const context = {
43+
nodeId,
44+
inputs: {
45+
input,
46+
instructions,
47+
},
48+
env: {
49+
AI: env.AI,
50+
},
51+
} as unknown as NodeContext;
52+
53+
const result = await node.execute(context);
54+
expect(result.status).toBe("completed");
55+
expect(result.outputs).toBeDefined();
56+
expect(result.outputs?.response).toBeDefined();
57+
expect(typeof result.outputs?.response).toBe("string");
58+
expect(result.outputs?.response.length).toBeGreaterThan(0);
59+
});
60+
});
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { NodeExecution, NodeType } from "@dafthunk/types";
2+
3+
import { ExecutableNode } from "../types";
4+
import { NodeContext } from "../types";
5+
6+
/**
7+
* GPT-OSS-120B node implementation following Cloudflare Workers AI API
8+
* OpenAI's open-weight models designed for powerful reasoning, agentic tasks, and versatile developer use cases
9+
*/
10+
export class GptOss120BNode extends ExecutableNode {
11+
public static readonly nodeType: NodeType = {
12+
id: "gpt-oss-120b",
13+
name: "GPT-OSS-120B",
14+
type: "gpt-oss-120b",
15+
description: "OpenAI's open-weight model for powerful reasoning and agentic tasks",
16+
tags: ["Text", "AI"],
17+
icon: "sparkles",
18+
computeCost: 35,
19+
asTool: true,
20+
inputs: [
21+
{
22+
name: "instructions",
23+
type: "string",
24+
description: "System instructions for the model behavior",
25+
required: false,
26+
value: "You are a helpful assistant.",
27+
},
28+
{
29+
name: "input",
30+
type: "string",
31+
description: "The input text or question for the model",
32+
required: true,
33+
},
34+
],
35+
outputs: [
36+
{
37+
name: "response",
38+
type: "string",
39+
description: "Generated text response from GPT-OSS-120B",
40+
},
41+
],
42+
};
43+
44+
async execute(context: NodeContext): Promise<NodeExecution> {
45+
try {
46+
const { instructions, input } = context.inputs;
47+
48+
if (!context.env?.AI) {
49+
return this.createErrorResult("AI service is not available");
50+
}
51+
52+
if (!input) {
53+
return this.createErrorResult("Input is required");
54+
}
55+
56+
const result = await context.env.AI.run(
57+
"@cf/openai/gpt-oss-120b" as any,
58+
{
59+
instructions: instructions || "You are a helpful assistant.",
60+
input,
61+
},
62+
context.env.AI_OPTIONS
63+
);
64+
65+
// Extract the response text from the output structure
66+
// The response is in output[1] (the message with type 'message' and role 'assistant')
67+
const messageOutput = result.output?.find((output: any) => output.type === 'message' && output.role === 'assistant');
68+
const responseText = messageOutput?.content?.[0]?.text || '';
69+
70+
return this.createSuccessResult({
71+
response: responseText,
72+
});
73+
} catch (error) {
74+
console.error(error);
75+
return this.createErrorResult(
76+
error instanceof Error ? error.message : "Unknown error"
77+
);
78+
}
79+
}
80+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { Node } from "@dafthunk/types";
2+
import { env } from "cloudflare:test";
3+
import { describe, expect, it } from "vitest";
4+
5+
import { NodeContext } from "../types";
6+
import { GptOss20BNode } from "./gpt-oss-20b-node";
7+
8+
describe("GptOss20BNode", () => {
9+
it("should execute manually", async () => {
10+
const nodeId = "gpt-oss-20b";
11+
const node = new GptOss20BNode({
12+
nodeId,
13+
} as unknown as Node);
14+
15+
const input = "What is the capital of France?";
16+
const context = {
17+
nodeId,
18+
inputs: {
19+
input,
20+
},
21+
env: {
22+
AI: env.AI,
23+
},
24+
} as unknown as NodeContext;
25+
26+
const result = await node.execute(context);
27+
expect(result.status).toBe("completed");
28+
expect(result.outputs).toBeDefined();
29+
expect(result.outputs?.response).toBeDefined();
30+
expect(typeof result.outputs?.response).toBe("string");
31+
expect(result.outputs?.response.length).toBeGreaterThan(0);
32+
});
33+
34+
it("should execute with custom instructions", async () => {
35+
const nodeId = "gpt-oss-20b";
36+
const node = new GptOss20BNode({
37+
nodeId,
38+
} as unknown as Node);
39+
40+
const input = "What is the capital of France?";
41+
const instructions = "You are a concise assistant. Provide brief answers.";
42+
const context = {
43+
nodeId,
44+
inputs: {
45+
input,
46+
instructions,
47+
},
48+
env: {
49+
AI: env.AI,
50+
},
51+
} as unknown as NodeContext;
52+
53+
const result = await node.execute(context);
54+
expect(result.status).toBe("completed");
55+
expect(result.outputs).toBeDefined();
56+
expect(result.outputs?.response).toBeDefined();
57+
expect(typeof result.outputs?.response).toBe("string");
58+
expect(result.outputs?.response.length).toBeGreaterThan(0);
59+
});
60+
});
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { NodeExecution, NodeType } from "@dafthunk/types";
2+
3+
import { ExecutableNode } from "../types";
4+
import { NodeContext } from "../types";
5+
6+
/**
7+
* GPT-OSS-20B node implementation following Cloudflare Workers AI API
8+
* OpenAI's open-weight models designed for powerful reasoning, agentic tasks, and versatile developer use cases
9+
* GPT-OSS-20B is for lower latency, and local or specialized use-cases
10+
*/
11+
export class GptOss20BNode extends ExecutableNode {
12+
public static readonly nodeType: NodeType = {
13+
id: "gpt-oss-20b",
14+
name: "GPT-OSS-20B",
15+
type: "gpt-oss-20b",
16+
description: "OpenAI's open-weight model for lower latency and specialized use cases",
17+
tags: ["Text", "AI"],
18+
icon: "sparkles",
19+
computeCost: 20,
20+
asTool: true,
21+
inputs: [
22+
{
23+
name: "instructions",
24+
type: "string",
25+
description: "System instructions for the model behavior",
26+
required: false,
27+
value: "You are a helpful assistant.",
28+
},
29+
{
30+
name: "input",
31+
type: "string",
32+
description: "The input text or question for the model",
33+
required: true,
34+
},
35+
],
36+
outputs: [
37+
{
38+
name: "response",
39+
type: "string",
40+
description: "Generated text response from GPT-OSS-20B",
41+
},
42+
],
43+
};
44+
45+
async execute(context: NodeContext): Promise<NodeExecution> {
46+
try {
47+
const { instructions, input } = context.inputs;
48+
49+
if (!context.env?.AI) {
50+
return this.createErrorResult("AI service is not available");
51+
}
52+
53+
if (!input) {
54+
return this.createErrorResult("Input is required");
55+
}
56+
57+
const result = await context.env.AI.run(
58+
"@cf/openai/gpt-oss-20b" as any,
59+
{
60+
instructions: instructions || "You are a helpful assistant.",
61+
input,
62+
},
63+
context.env.AI_OPTIONS
64+
);
65+
66+
// Extract the response text from the output structure
67+
// The response is in output[1] (the message with type 'message' and role 'assistant')
68+
const messageOutput = result.output?.find((output: any) => output.type === 'message' && output.role === 'assistant');
69+
const responseText = messageOutput?.content?.[0]?.text || '';
70+
71+
return this.createSuccessResult({
72+
response: responseText,
73+
});
74+
} catch (error) {
75+
console.error(error);
76+
return this.createErrorResult(
77+
error instanceof Error ? error.message : "Unknown error"
78+
);
79+
}
80+
}
81+
}

0 commit comments

Comments
 (0)