Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: updated genkit flow diagrams/genkit compose #1

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1 @@
# genkit-plugins
# genkit-plugins
5 changes: 5 additions & 0 deletions genkit-compose/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
};
11 changes: 8 additions & 3 deletions genkit-flow-diagram/package.json → genkit-compose/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "genkit-flow-diagram",
"version": "1.0.0",
"name": "genkit-compose",
"version": "0.0.1",
"description": "",
"main": "lib/index.js",
"type": "commonjs",
Expand All @@ -18,12 +18,17 @@
"express": "^4.19.2",
"graphology": "^0.25.4",
"graphology-dag": "^0.4.1",
"zod": "^3.23.5",
"js-yaml": "^4.1.0",
"yaml": "^2.4.2",
"zod": "^3.23.8",
"zod-to-json-schema": "^3.23.0"
},
"devDependencies": {
"@types/jest": "^29.5.12",
"@types/js-yaml": "^4.0.9",
"@types/supertest": "^6.0.2",
"jest": "^29.7.0",
"supertest": "^7.0.0",
"ts-jest": "^29.1.2",
"typescript": "^5.4.5"
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
{
"options": {
"type": "mixed",
"multi": false,
"allowSelfLoops": true
},
"attributes": {},
"nodes": [
{
"key": "foo",
"attributes": {
"name": "foo",
"inputValues": {
"hello": "world"
},
"schema": {
"inputSchema": {
"jsonSchema": {
"type": "object",
"properties": {
"input": {
"type": "string"
}
},
"required": [
"input"
],
"additionalProperties": false,
"$schema": "http://json-schema.org/draft-07/schema#"
}
},
"outputSchema": {
"jsonSchema": {
"type": "object",
"properties": {
"output": {
"type": "string"
}
},
"required": [
"output"
],
"additionalProperties": false,
"$schema": "http://json-schema.org/draft-07/schema#"
}
}
}
}
}
],
"edges": []
}
143 changes: 143 additions & 0 deletions genkit-compose/src/genkit-compose/e2e_tests/prod.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import request from "supertest";
import express from "express";
import { defineFlow, getApp } from "..";
import Graph from "graphology";
import { FlowGraph } from "../types";
import z from "zod";
import zodToJsonSchema, { JsonSchema7ObjectType } from "zod-to-json-schema";
import fs from "fs";
import path from "path";

describe("startFlowsComposeServer", () => {
let app: express.Express | undefined;

beforeEach(async () => {
app = undefined as any;
});

test("Create fixture", async () => {
// app = await getApp({});
// const response = await request(app).get("/introspect");
// expect(response.status).toBe(200);
// expect(response.body).toBeDefined();

const testFlow = defineFlow(
{
name: "foo",
inputSchema: z.object({
input: z.string(),
}),
outputSchema: z.object({
output: z.string(),
}),
},
async (input) => {
return {
output: input.input,
};
}
);

const graph: FlowGraph = new Graph();

// const graph: FlowGraph = new Graph()

graph.addNode("foo", {
name: "foo",
inputValues: {
hello: "world",
},
// flow: testFlow,
schema: {
inputSchema: {
// zod: testFlow.inputSchema,
jsonSchema: zodToJsonSchema(
testFlow.inputSchema!
) as JsonSchema7ObjectType,
},
outputSchema: {
// zod: testFlow.outputSchema,
jsonSchema: zodToJsonSchema(
testFlow.outputSchema!
) as JsonSchema7ObjectType,
},
},
});

// const order = ["node1"];

const fixturePath = path.join(__dirname, "fixtures", "genkit-compose.json");

fs.writeFileSync(fixturePath, JSON.stringify(graph.toJSON(), null, 2));
});

test("Get /introspect should return introspection data", async () => {
defineFlow(
{
name: "foo",
inputSchema: z.object({
input: z.string(),
}),
outputSchema: z.object({
output: z.string(),
}),
},
async (input) => {
return {
output: input.input,
};
}
);

const app = await getApp({});

const response = await request(app).get("/introspect");

expect(response.status).toBe(200);

expect(response.body).toBeDefined();

const expectedJsonSchema = zodToJsonSchema(
z.object({
foo: z.object({
input: z.string(),
}),
})
);

expect(response.body).toEqual(expectedJsonSchema);
});

test("POST /runTotalFlow with valid input should process the flow and return outputs", async () => {
defineFlow(
{
name: "foo",
inputSchema: z.object({
input: z.string(),
}),
outputSchema: z.object({
output: z.string(),
}),
},
async (input) => {
return {
output: input.input,
};
}
);
const app = await getApp({});
const validInput = {
foo: {
input: "hello world",
},
}; // Replace with a valid input example based on your totalInputSchema
const response = await request(app).post("/runTotalFlow").send(validInput);
expect(response.status).toBe(200);
expect(response.body).toBeDefined();
expect(response.body).toEqual({
foo: {
output: "hello world",
},
});
});
});
154 changes: 154 additions & 0 deletions genkit-compose/src/genkit-compose/getComposeConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import { promises as fs, readdirSync, readFileSync } from "fs";
import path from "path";
import yaml from "yaml";
import Graph, { DirectedGraph } from "graphology";
import { SerializedGraph } from "graphology-types";
import { SerializedFlowGraphSchema } from "./types";
import { Flow } from "@genkit-ai/flow";
import { FlowGraph, SerializedFlowGraph } from "./types";
// Helper type for the supported extensions

type FileExtension = "yaml" | "yml" | "json";

export async function findGenkitComposeFile(
startDir: string = __dirname
): Promise<string | null> {
try {
// Read all entries in the current directory
const entries = await fs.readdir(startDir, { withFileTypes: true });

const targetFileNames = [
"genkit-compose.yaml",
"genkit-compose.yml",
"genkit-compose.json",
];

// First, search for the target files in the current directory
for (const fileName of targetFileNames) {
if (entries.some((entry) => entry.isFile() && entry.name === fileName)) {
return path.join(startDir, fileName); // Return the full path of the found file
}
}

// If not found, search in subdirectories
for (const entry of entries) {
if (entry.isDirectory()) {
const subdirPath = path.join(startDir, entry.name);
const found = await findGenkitComposeFile(subdirPath);
if (found) return found; // Return as soon as a file is found in any subdirectory
}
}
} catch (error) {
console.error(`Error reading directory ${startDir}:`, error);
// Optionally return null or rethrow the error depending on your error handling strategy
return null;
}

return null; // Return null if the file is not found in the directory tree
}

export function findGenkitComposeFileSync(
startDir: string = __dirname
): string | null {
try {
// Read all entries in the current directory
const entries = readdirSync(startDir, { withFileTypes: true });

const targetFileNames = [
"genkit-compose.yaml",
"genkit-compose.yml",
"genkit-compose.json",
];

// First, search for the target files in the current directory
for (const fileName of targetFileNames) {
if (entries.some((entry) => entry.isFile() && entry.name === fileName)) {
return path.join(startDir, fileName); // Return the full path of the found file
}
}

// If not found, search in subdirectories
for (const entry of entries) {
if (entry.isDirectory()) {
const subdirPath = path.join(startDir, entry.name);
const found = findGenkitComposeFileSync(subdirPath);
if (found) return found; // Return as soon as a file is found in any subdirectory
}
}
} catch (error) {
console.error(`Error reading directory ${startDir}:`, error);
// Optionally return null or rethrow the error depending on your error handling strategy
return null;
}

return null; // Return null if the file is not found in the directory tree
}

// Function to determine the file extension
export function getFileExtension(filePath: string): FileExtension | null {
const ext = path.extname(filePath).substring(1);
if (ext === "yaml" || ext === "yml" || ext === "json") {
return ext as FileExtension;
}
return null;
}

// Function to read and parse the file based on its extension
export async function readAndParseConfigFile(
filePath: string
): Promise<SerializedFlowGraph> {
// Determine the file extension
const extension = getFileExtension(filePath);
if (!extension) {
throw new Error(`Unsupported file extension for file: ${filePath}`);
}

// Read the file content
const fileContent = await fs.readFile(filePath, "utf8");

// Parse the file content based on its extension
switch (extension) {
case "yaml":
case "yml":
return SerializedFlowGraphSchema.parse(
yaml.load(fileContent)
) as SerializedFlowGraph;
case "json":
return SerializedFlowGraphSchema.parse(
JSON.parse(fileContent)
) as SerializedFlowGraph;
default:
throw new Error(`Unsupported file extension: ${extension}`);
}
}

export function readAndParseConfigFileSync(
filePath: string
): SerializedFlowGraph {
// Determine the file extension
const extension = getFileExtension(filePath);
if (!extension) {
throw new Error(`Unsupported file extension for file: ${filePath}`);
}

// Read the file content
const fileContent = readFileSync(filePath, "utf8");

// Parse the file content based on its extension
switch (extension) {
case "yaml":
case "yml":
return SerializedFlowGraphSchema.parse(
yaml.stringify(fileContent)
) as SerializedFlowGraph;
case "json":
return SerializedFlowGraphSchema.parse(
JSON.parse(fileContent)
) as SerializedFlowGraph;
default:
throw new Error(`Unsupported file extension: ${extension}`);
}
}

export const parseAsGraph = (serializedGraph: SerializedFlowGraph) =>
Graph.from(serializedGraph) as FlowGraph;
Loading