├── .DS_Store ├── .gitignore ├── .prettierrc.yml ├── .vscode ├── launch_sample.json └── tasks.json ├── chat_ui.png ├── config ├── default.json └── production.json ├── dist ├── api.js ├── api.js.map ├── configTypes.js ├── configTypes.js.map ├── files.js ├── files.js.map ├── graphManager.js ├── graphManager.js.map ├── pluginConfiguration.js ├── pluginConfiguration.js.map ├── textToSpeech.js.map ├── utils.js └── utils.js.map ├── package-lock.json ├── package.json ├── readme.md ├── rivet └── example.rivet-project ├── src ├── api.ts ├── configTypes.ts ├── files.ts ├── graphManager.ts ├── pluginConfiguration.ts └── utils.ts ├── tests ├── modelsEndpoint.test.js └── parallelRequests.test.js └── tsconfig.json /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ai-made-approachable/rivet-chat-api/57c5fc003c76f1ea105dbd92a7b7b247e4cec51c/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.vscode/launch.json 2 | /node_modules/ 3 | /audio/* 4 | config.gypi 5 | /src/archive/ 6 | /dist/archive/ 7 | yarn.lock 8 | /rivet/test* 9 | .DS_Store -------------------------------------------------------------------------------- /.prettierrc.yml: -------------------------------------------------------------------------------- 1 | tabWidth: 4 2 | semi: true 3 | singleQuote: true 4 | printWidth: 120 5 | -------------------------------------------------------------------------------- /.vscode/launch_sample.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "node", 6 | "request": "launch", 7 | "name": "Launch Program", 8 | "preLaunchTask": "build", 9 | "skipFiles": [ 10 | "/**" 11 | ], 12 | "program": "${workspaceFolder}/src/api.ts", 13 | "outFiles": [ 14 | "${workspaceFolder}/dist/*.js" 15 | ], 16 | "env": { 17 | "OPENAI_API_KEY": "ENTER YOUR KEY", 18 | "RIVET_CHAT_API_KEY": "80f946e4-04ba-4687-8c9e-bd33337f0f90" 19 | } 20 | } 21 | ] 22 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "start-and-test", 6 | "type": "shell", 7 | "command": "npm run start-and-test", 8 | "options": { 9 | "cwd": "${workspaceFolder}" 10 | } 11 | }, 12 | { 13 | "label": "build", 14 | "type": "shell", 15 | "command": "npm run build", 16 | "options": { 17 | "cwd": "${workspaceFolder}" 18 | } 19 | } 20 | ] 21 | } -------------------------------------------------------------------------------- /chat_ui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ai-made-approachable/rivet-chat-api/57c5fc003c76f1ea105dbd92a7b7b247e4cec51c/chat_ui.png -------------------------------------------------------------------------------- /config/default.json: -------------------------------------------------------------------------------- 1 | { 2 | "servers": [ 3 | { 4 | "file": "./rivet/example.rivet-project", 5 | "graphName": "chat-history", 6 | "graphInputName": "input", 7 | "streamingOutput": { 8 | "nodeType": "chat", 9 | "nodeName": "Output (Chat)" 10 | }, 11 | "returnGraphOutput": false, 12 | "graphOutputName": "output", 13 | "port": 3100 14 | }, 15 | { 16 | "file": "./rivet/example2.rivet-project", 17 | "graphName": "other_graph", 18 | "graphInputName": "input", 19 | "streamingOutput": { 20 | "nodeType": "chat", 21 | "nodeName": "Output (Chat)" 22 | }, 23 | "returnGraphOutput": false, 24 | "graphOutputName": "output", 25 | "port": 3101 26 | } 27 | ] 28 | } -------------------------------------------------------------------------------- /config/production.json: -------------------------------------------------------------------------------- 1 | { 2 | "servers": [ 3 | { 4 | "file": "./example.rivet-project", 5 | "graphName": "chat-history", 6 | "graphInputName": "input", 7 | "streamingOutput": { 8 | "nodeType": "chat", 9 | "nodeName": "Output (Chat)" 10 | }, 11 | "returnGraphOutput": false, 12 | "graphOutputName": "output", 13 | "port": 3100 14 | }, 15 | { 16 | "file": "./example2.rivet-project", 17 | "graphName": "other_graph", 18 | "graphInputName": "input", 19 | "streamingOutput": { 20 | "nodeType": "chat", 21 | "nodeName": "Output (Chat)" 22 | }, 23 | "returnGraphOutput": false, 24 | "graphOutputName": "output", 25 | "port": 3101 26 | } 27 | ] 28 | } -------------------------------------------------------------------------------- /dist/api.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { GraphManager } from './graphManager.js'; 3 | import fs from 'fs'; 4 | import path from 'path'; 5 | import { authenticateAndGetJWT, listFiles, fetchFileContent } from './files.js'; 6 | import morgan from 'morgan'; 7 | import cors from 'cors'; 8 | const app = express(); 9 | const port = process.env.PORT || 3100; // Default port or environment variable 10 | const environment = process.env.NODE_ENV; 11 | const apiKey = process.env.RIVET_CHAT_API_KEY; 12 | const domain = process.env.FILEBROWSER_DOMAIN; 13 | app.use(express.json()); 14 | app.use(morgan('combined')); 15 | app.use((req, res, next) => { 16 | console.log(`Received ${req.method} request for ${req.url}`); 17 | next(); 18 | }); 19 | app.use(cors({ 20 | origin: process.env.ACCESS_CONTROL_ALLOW_ORIGIN || '*', 21 | methods: ['GET', 'POST', 'OPTIONS'], // Specify the methods you want to allow 22 | allowedHeaders: ['Content-Type', 'Authorization'], 23 | })); 24 | // Middleware for API Key validation in production 25 | app.use((req, res, next) => { 26 | //console.log('Request Headers:', JSON.stringify(req.headers, null, 2)); 27 | //console.log('Request Body:', JSON.stringify(req.body, null, 2)); 28 | if (environment === 'production') { 29 | const authHeader = req.headers.authorization; 30 | // Do not check authentification on non internal domains 31 | if (!req.hostname.endsWith('.internal')) { 32 | if (!authHeader || authHeader !== `Bearer ${apiKey}`) { 33 | return res.status(403).json({ message: 'Forbidden - Invalid API Key' }); 34 | } 35 | } 36 | } 37 | next(); 38 | }); 39 | // Dynamic model loading for chat completions based on the model specified in the request body 40 | app.post('/chat/completions', async (req, res) => { 41 | const modelId = req.body.model; 42 | if (!modelId) { 43 | return res.status(400).json({ message: 'Model identifier is required' }); 44 | } 45 | const messages = req.body.messages; 46 | if (!messages || !Array.isArray(messages)) { 47 | return res.status(400).json({ message: 'Messages are required and must be an array' }); 48 | } 49 | const processedMessages = messages.map(({ role: type, content: message }) => ({ type, message })); 50 | res.setHeader('Content-Type', 'text/event-stream'); 51 | res.setHeader('Transfer-Encoding', 'chunked'); 52 | const commonData = { 53 | id: 'chatcmpl-mockId12345', 54 | object: 'chat.completion.chunk', 55 | created: Date.now(), 56 | model: modelId, 57 | system_fingerprint: null, 58 | }; 59 | // Define a function to process and send chunks 60 | async function processAndSendChunks(graphManager) { 61 | let isFirstChunk = true; 62 | let previousChunk = null; 63 | let accumulatedContent = ""; 64 | for await (const chunk of graphManager.runGraph(processedMessages)) { 65 | if (isFirstChunk) { 66 | isFirstChunk = false; 67 | // Include role only for the first chunk 68 | previousChunk = { role: "assistant", content: chunk }; 69 | } 70 | else { 71 | // Send the previous chunk now that we know it's not the last 72 | if (previousChunk !== null) { 73 | const chunkData = { 74 | ...commonData, 75 | choices: [{ index: 0, delta: previousChunk, logprobs: null, finish_reason: null }], 76 | }; 77 | res.write(`data: ${JSON.stringify(chunkData)}\n\n`); 78 | accumulatedContent += previousChunk.content; 79 | } 80 | previousChunk = { content: chunk }; // Update previousChunk to current chunk 81 | } 82 | } 83 | // Handle the last chunk 84 | if (previousChunk !== null) { 85 | accumulatedContent += previousChunk.content; 86 | const lastChunkData = { 87 | ...commonData, 88 | choices: [{ index: 0, delta: previousChunk, logprobs: null, finish_reason: "stop" }], 89 | }; 90 | res.write(`data: ${JSON.stringify(lastChunkData)}\n\n`); 91 | } 92 | res.write('data: [DONE]\n\n'); 93 | //console.log('Final Output:', accumulatedContent); 94 | res.end(); 95 | } 96 | // Handling for non-production environment or when not the special case 97 | if (environment !== 'production') { 98 | const directoryPath = path.resolve(process.cwd(), './rivet'); 99 | const modelFilePath = path.join(directoryPath, modelId); 100 | if (!fs.existsSync(modelFilePath)) { 101 | return res.status(404).json({ message: 'Model not found' }); 102 | } 103 | const graphManager = new GraphManager({ config: { file: modelFilePath } }); 104 | await processAndSendChunks(graphManager); 105 | return; 106 | } 107 | // Handling for production environment (excluding summarizer.rivet-project) 108 | try { 109 | const token = await authenticateAndGetJWT(); 110 | if (!token) { 111 | return res.status(401).json({ message: 'Failed to authenticate with Filebrowser' }); 112 | } 113 | const filePath = `/${modelId}`; // Adjust based on your Filebrowser structure 114 | const fileContent = await fetchFileContent(filePath, token); 115 | if (!fileContent) { 116 | return res.status(404).json({ message: 'Model not found on Filebrowser' }); 117 | } 118 | const graphManager = new GraphManager({ modelContent: fileContent }); 119 | await processAndSendChunks(graphManager); 120 | } 121 | catch (error) { 122 | console.error('Error processing graph:', error); 123 | res.status(500).json({ message: 'Internal server error' }); 124 | } 125 | }); 126 | app.get('/models', async (req, res) => { 127 | // For prod get the contents from the filebrowser application 128 | if (environment === 'production') { 129 | try { 130 | const token = await authenticateAndGetJWT(); 131 | if (!token) { 132 | return res.status(401).json({ message: 'Failed to authenticate with Filebrowser' }); 133 | } 134 | const files = await listFiles(token); // This should return the array of files 135 | if (!files || !Array.isArray(files)) { 136 | // If files is not an array, log the actual structure for debugging 137 | console.error('Unexpected structure:', files); 138 | return res.status(500).json({ message: 'Unexpected response structure from Filebrowser' }); 139 | } 140 | const models = files.filter(file => file.extension === '.rivet-project').map(file => ({ 141 | id: file.name, 142 | object: "model", 143 | created: new Date(file.modified).getTime() / 1000, // Convert to Unix timestamp if needed 144 | owned_by: "user", 145 | })); 146 | res.setHeader('Content-Type', 'application/json'); 147 | res.json({ object: "list", data: models }); 148 | } 149 | catch (error) { 150 | console.error('Error listing models from Filebrowser:', error); 151 | return res.status(500).json({ message: 'Internal server error', error: error.message }); 152 | } 153 | } 154 | else { 155 | // Local filesystem logic... 156 | const directoryPath = path.resolve(process.cwd(), './rivet'); 157 | fs.readdir(directoryPath, (err, files) => { 158 | if (err) { 159 | console.error('Unable to scan directory:', err); 160 | return res.status(500).json({ message: 'Internal server error' }); 161 | } 162 | const models = files.filter(file => file.endsWith('.rivet-project')).map(file => { 163 | const fullPath = path.join(directoryPath, file); 164 | const stats = fs.statSync(fullPath); 165 | return { 166 | id: file, 167 | object: "model", 168 | created: Math.floor(stats.birthtimeMs / 1000), 169 | owned_by: "user", 170 | }; 171 | }); 172 | res.setHeader('Content-Type', 'application/json'); 173 | res.json({ object: "list", data: models }); 174 | }); 175 | } 176 | }); 177 | const host = environment === 'production' ? '::' : 'localhost'; 178 | app.listen(Number(port), host, () => { 179 | console.log(`Server running at http://${host}:${port}/`); 180 | console.log("-----------------------------------------------------"); 181 | }); 182 | //# sourceMappingURL=api.js.map -------------------------------------------------------------------------------- /dist/api.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /dist/configTypes.js: -------------------------------------------------------------------------------- 1 | export {}; 2 | //# sourceMappingURL=configTypes.js.map -------------------------------------------------------------------------------- /dist/configTypes.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"configTypes.js","sourceRoot":"","sources":["../src/configTypes.ts"],"names":[],"mappings":""} -------------------------------------------------------------------------------- /dist/files.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | // Use internal domain when online and public domain when testing locally 3 | const localDev = process.env.LOCAL_DEV === 'true'; 4 | let filebrowserUrl = process.env.FILEBROWSER_DOMAIN; 5 | export async function authenticateAndGetJWT() { 6 | const loginUrl = `${filebrowserUrl}/api/login`; 7 | const payload = { 8 | username: process.env.FILEBROWSER_USERNAME, 9 | password: process.env.FILEBROWSE_PASSWORD, 10 | recaptcha: "" // 11 | }; 12 | try { 13 | const response = await axios.post(loginUrl, payload); 14 | const token = response.data; 15 | // console.log('JWT Token:', token); 16 | return token; 17 | } 18 | catch (error) { 19 | console.error('Error during authentication:', error.response ? error.response.data : error.message); 20 | return null; 21 | } 22 | } 23 | export async function listFiles(jwtToken) { 24 | try { 25 | const url = `${filebrowserUrl}/api/resources/`; 26 | const response = await axios.get(url, { 27 | headers: { 'X-AUTH': jwtToken } 28 | }); 29 | // Directly return the array assuming response.data is the array 30 | return response.data.items; // Adjust this line if the structure is nested differently 31 | } 32 | catch (error) { 33 | console.error('Error fetching files:', error.response ? error.response.data : error.message); 34 | return []; // Return an empty array on error 35 | } 36 | } 37 | export async function fetchFileContent(filePath, jwtToken) { 38 | try { 39 | // Construct the URL to access the specific file, including the auth token as a query parameter. 40 | const fileUrl = `${filebrowserUrl}/api/raw${filePath}?auth=${jwtToken}`; 41 | const response = await axios.get(fileUrl, { responseType: 'blob' }); 42 | // For binary files, 'blob' is used. For text files, you might use 'text'. 43 | // Assuming you want to process the file content further or send it in a response: 44 | // Note: Depending on your use case, you might handle the response differently. 45 | console.log(`File ${filePath} downloaded successfully`); 46 | return response.data; 47 | } 48 | catch (error) { 49 | console.error('Error downloading file:', error.response ? error.response.data : error.message); 50 | return null; 51 | } 52 | } 53 | //# sourceMappingURL=files.js.map -------------------------------------------------------------------------------- /dist/files.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"files.js","sourceRoot":"","sources":["../src/files.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,yEAAyE;AACzE,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,SAAS,KAAK,MAAM,CAAC;AAClD,IAAI,cAAc,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC;AAEpD,MAAM,CAAC,KAAK,UAAU,qBAAqB;IACzC,MAAM,QAAQ,GAAG,GAAG,cAAc,YAAY,CAAC;IAC/C,MAAM,OAAO,GAAG;QACd,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,oBAAoB;QAC1C,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,mBAAmB;QACzC,SAAS,EAAE,EAAE,CAAC,EAAE;KACjB,CAAC;IAEF,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QACrD,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC;QAC5B,oCAAoC;QACpC,OAAO,KAAK,CAAC;IACf,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,8BAA8B,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACpG,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,QAAQ;IACpC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,GAAG,cAAc,iBAAiB,CAAC;QAC/C,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE;YACpC,OAAO,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE;SAChC,CAAC,CAAC;QACH,gEAAgE;QAChE,OAAO,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,0DAA0D;IACxF,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,uBAAuB,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC7F,OAAO,EAAE,CAAC,CAAC,iCAAiC;IAC9C,CAAC;AACH,CAAC;AAEH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,QAAQ,EAAE,QAAQ;IACrD,IAAI,CAAC;QACH,gGAAgG;QAChG,MAAM,OAAO,GAAG,GAAG,cAAc,WAAW,QAAQ,SAAS,QAAQ,EAAE,CAAC;QAExE,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,OAAO,EAAE,EAAE,YAAY,EAAE,MAAM,EAAE,CAAC,CAAC;QACpE,0EAA0E;QAE1E,kFAAkF;QAClF,+EAA+E;QAC/E,OAAO,CAAC,GAAG,CAAC,QAAQ,QAAQ,0BAA0B,CAAC,CAAC;QACxD,OAAO,QAAQ,CAAC,IAAI,CAAC;IACvB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,yBAAyB,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC/F,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"} -------------------------------------------------------------------------------- /dist/graphManager.js: -------------------------------------------------------------------------------- 1 | import * as Rivet from '@ironclad/rivet-node'; 2 | import fs from 'fs/promises'; 3 | import path from 'path'; 4 | import { setupPlugins, logAvailablePluginsInfo } from './pluginConfiguration.js'; 5 | import { delay } from './utils.js'; 6 | import event from 'events'; 7 | logAvailablePluginsInfo(); 8 | event.setMaxListeners(100); 9 | class DebuggerServer { 10 | constructor() { 11 | this.debuggerServer = null; 12 | } 13 | static getInstance() { 14 | if (!DebuggerServer.instance) { 15 | DebuggerServer.instance = new DebuggerServer(); 16 | } 17 | return DebuggerServer.instance; 18 | } 19 | startDebuggerServerIfNeeded() { 20 | if (!this.debuggerServer) { 21 | this.debuggerServer = Rivet.startDebuggerServer(); 22 | console.log('Debugger server started'); 23 | } 24 | return this.debuggerServer; 25 | } 26 | getDebuggerServer() { 27 | return this.debuggerServer; 28 | } 29 | } 30 | DebuggerServer.instance = null; 31 | export class GraphManager { 32 | constructor(params) { 33 | this.config = params.config || {}; 34 | this.modelContent = params.modelContent; 35 | this.streamedNodeIds = new Set(); 36 | } 37 | async *runGraph(messages) { 38 | console.time('runGraph'); 39 | let projectContent; 40 | // Ensure the DebuggerServer is started 41 | DebuggerServer.getInstance().startDebuggerServerIfNeeded(); 42 | try { 43 | // Dynamically setup plugins and retrieve their settings 44 | const pluginSettings = await setupPlugins(Rivet); 45 | if (this.modelContent) { 46 | // Use direct model content if provided 47 | projectContent = this.modelContent; 48 | } 49 | else { 50 | // Otherwise, read the model file from the filesystem 51 | const modelFilePath = path.resolve(process.cwd(), './rivet', this.config.file); 52 | console.log("-----------------------------------------------------"); 53 | console.log('runGraph called with model file:', modelFilePath); 54 | projectContent = await fs.readFile(modelFilePath, 'utf8'); 55 | } 56 | const project = Rivet.loadProjectFromString(projectContent); 57 | const graphInput = "input"; 58 | const datasetOptions = { 59 | save: true, 60 | // filePath should only be set if you're working with a file, adjust accordingly 61 | filePath: this.modelContent ? undefined : path.resolve(process.cwd(), './rivet', this.config.file), 62 | }; 63 | const datasetProvider = this.modelContent ? undefined : await Rivet.NodeDatasetProvider.fromProjectFile(datasetOptions.filePath, datasetOptions); 64 | const options = { 65 | graph: this.config.graphName, 66 | inputs: { 67 | [graphInput]: { 68 | type: 'chat-message[]', 69 | value: messages.map((message) => ({ 70 | type: message.type, 71 | message: message.message, 72 | })), 73 | }, 74 | }, 75 | openAiKey: process.env.OPENAI_API_KEY, 76 | remoteDebugger: DebuggerServer.getInstance().getDebuggerServer(), 77 | datasetProvider: datasetProvider, 78 | pluginSettings, 79 | context: { 80 | ...Object.entries(process.env).reduce((acc, [key, value]) => { 81 | acc[key] = value; 82 | return acc; 83 | }, {}), 84 | }, 85 | onUserEvent: { 86 | // Add "event" node and with id "debugger" to log data from Rivet to the server logs 87 | debugger: (data) => { 88 | console.log(`Debugging data: ${JSON.stringify(data)}`); 89 | return Promise.resolve(); 90 | } 91 | } 92 | }; 93 | console.log("-----------------------------------------------------"); 94 | console.log('Creating processor'); 95 | const { processor, run } = Rivet.createProcessor(project, options); 96 | const runPromise = run(); 97 | console.log('Starting to process events'); 98 | let lastContent = ''; 99 | for await (const event of processor.events()) { 100 | // Handle 'partialOutput' events 101 | if (event.type === 'partialOutput' && event.node?.title?.toLowerCase() === "output") { 102 | const content = event.outputs.response?.value || event.outputs.output?.value; 103 | if (content && content.startsWith(lastContent)) { 104 | const delta = content.slice(lastContent.length); 105 | yield delta; 106 | lastContent = content; 107 | this.streamedNodeIds.add(event.node.id); // Add node ID to the Set when streaming output 108 | } 109 | } 110 | // Modify 'nodeFinish' handling to check if node ID has already streamed output 111 | else if (event.type === 'nodeFinish' && 112 | event.node?.title?.toLowerCase() === "output" && 113 | !event.node?.type?.includes('chat') && 114 | !this.streamedNodeIds.has(event.node.id) // Check if the node ID is not in the streamedNodeIds Set 115 | ) { 116 | try { 117 | let content = event.outputs.output.value || event.outputs.output.output; 118 | if (content) { 119 | if (typeof content !== 'string') { 120 | content = JSON.stringify(content); 121 | } 122 | // Stream the content character-by-character 123 | for (const char of content) { 124 | await delay(0.5); // Artificial delay to simulate streaming 125 | yield char; 126 | } 127 | } 128 | } 129 | catch (error) { 130 | console.error(`Error: Cannot return output from node of type ${event.node?.type}. This only works with certain nodes (e.g., text or object)`); 131 | } 132 | } 133 | } 134 | console.log('Finished processing events'); 135 | const finalOutputs = await runPromise; 136 | if (finalOutputs && finalOutputs["output"]) { 137 | yield finalOutputs["output"].value; 138 | } 139 | if (finalOutputs["cost"]) { 140 | console.log(`Cost: ${finalOutputs["cost"].value}`); 141 | } 142 | } 143 | catch (error) { 144 | console.error('Error in runGraph:', error); 145 | } 146 | finally { 147 | console.timeEnd('runGraph'); 148 | console.log("-----------------------------------------------------"); 149 | } 150 | } 151 | } 152 | //# sourceMappingURL=graphManager.js.map -------------------------------------------------------------------------------- /dist/graphManager.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"graphManager.js","sourceRoot":"","sources":["../src/graphManager.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /dist/pluginConfiguration.js: -------------------------------------------------------------------------------- 1 | const pluginConfigurations = [ 2 | { 3 | envVar: 'CHROMADB_PLUGIN', 4 | importPath: 'rivet-plugin-chromadb', 5 | isBuiltIn: false, 6 | registerFunction: (plugin, Rivet) => Rivet.globalRivetNodeRegistry.registerPlugin(plugin(Rivet)), 7 | settings: { 8 | envVarPrefix: 'CHROMA', 9 | settingsKey: 'chroma', 10 | settingsStructure: { 11 | databaseUri: 'DATABASE_URI', 12 | }, 13 | }, 14 | }, 15 | { 16 | envVar: 'GOOGLE_PLUGIN', 17 | isBuiltIn: true, 18 | registerFunction: (plugin, Rivet) => Rivet.globalRivetNodeRegistry.registerPlugin(Rivet.googlePlugin), 19 | settings: { 20 | envVarPrefix: 'GOOGLE', 21 | settingsKey: 'google', 22 | settingsStructure: { 23 | googleProjectId: 'PROJECT_ID', 24 | googleRegion: 'REGION', 25 | googleApplicationCredentials: 'APPLICATION_CREDENTIALS', 26 | }, 27 | }, 28 | }, 29 | { 30 | envVar: 'ANTHROPIC_PLUGIN', 31 | isBuiltIn: true, 32 | registerFunction: (plugin, Rivet) => Rivet.globalRivetNodeRegistry.registerPlugin(Rivet.anthropicPlugin), 33 | settings: { 34 | envVarPrefix: 'ANTHROPIC', 35 | settingsKey: 'anthropic', 36 | settingsStructure: { 37 | anthropicApiKey: 'API_KEY', 38 | }, 39 | }, 40 | }, 41 | { 42 | envVar: 'ASSEMBLYAI_PLUGIN', 43 | isBuiltIn: true, 44 | registerFunction: (plugin, Rivet) => Rivet.globalRivetNodeRegistry.registerPlugin(Rivet.assemblyAiPlugin), 45 | settings: { 46 | envVarPrefix: 'ASSEMBLYAI', 47 | settingsKey: 'assemblyAi', 48 | settingsStructure: { 49 | assemblyAiApiKey: 'API_KEY', 50 | }, 51 | }, 52 | }, 53 | { 54 | envVar: 'AUTOEVALS_PLUGIN', 55 | isBuiltIn: true, 56 | registerFunction: (plugin, Rivet) => Rivet.globalRivetNodeRegistry.registerPlugin(Rivet.autoevalsPlugin), 57 | settings: { 58 | envVarPrefix: 'AUTOEVALS', 59 | settingsKey: 'autoevals' 60 | }, 61 | }, 62 | { 63 | envVar: 'HUGGINGFACE_PLUGIN', 64 | isBuiltIn: true, 65 | registerFunction: (plugin, Rivet) => Rivet.globalRivetNodeRegistry.registerPlugin(Rivet.huggingFacePlugin), 66 | settings: { 67 | envVarPrefix: 'HUGGINGFACE', 68 | settingsKey: 'huggingface', 69 | settingsStructure: { 70 | huggingFaceAccessToken: 'ACCESS_TOKEN', 71 | }, 72 | }, 73 | }, 74 | { 75 | envVar: 'OPENAI_PLUGIN', 76 | isBuiltIn: true, 77 | registerFunction: (plugin, Rivet) => Rivet.globalRivetNodeRegistry.registerPlugin(Rivet.openAIPlugin), 78 | settings: { 79 | envVarPrefix: 'OPENAI', 80 | settingsKey: 'openai', 81 | }, 82 | }, 83 | { 84 | envVar: 'PINECONE_PLUGIN', 85 | isBuiltIn: true, 86 | registerFunction: (plugin, Rivet) => Rivet.globalRivetNodeRegistry.registerPlugin(Rivet.pineconePlugin), 87 | settings: { 88 | envVarPrefix: 'PINECONE', 89 | settingsKey: 'pinecone', 90 | settingsStructure: { 91 | pineconeApiKey: 'API_KEY', 92 | }, 93 | }, 94 | }, 95 | { 96 | envVar: 'MONGODB_PLUGIN', 97 | importPath: 'rivet-plugin-mongodb', 98 | isBuiltIn: false, 99 | registerFunction: (plugin, Rivet) => Rivet.globalRivetNodeRegistry.registerPlugin(plugin(Rivet)), 100 | settings: { 101 | envVarPrefix: 'MONGODB', 102 | settingsKey: 'mongoDB', 103 | settingsStructure: { 104 | mongoDBConnectionString: 'CONNECTION_STRING', 105 | }, 106 | }, 107 | }, 108 | { 109 | envVar: 'OLLAMA_PLUGIN', 110 | importPath: 'rivet-plugin-ollama', 111 | isBuiltIn: false, 112 | registerFunction: (plugin, Rivet) => Rivet.globalRivetNodeRegistry.registerPlugin(plugin(Rivet)), 113 | settings: { 114 | envVarPrefix: 'OLLAMA', 115 | settingsKey: 'ollama', 116 | settingsStructure: { 117 | host: 'HOST', 118 | }, 119 | }, 120 | }, 121 | { 122 | envVar: 'PDF2MD_PLUGIN', 123 | importPath: 'rivet-plugin-pdf2md', 124 | isBuiltIn: false, 125 | registerFunction: (plugin, Rivet) => Rivet.globalRivetNodeRegistry.registerPlugin(plugin(Rivet)), 126 | settings: { 127 | envVarPrefix: 'PDF2MD', 128 | settingsKey: 'pdf2md', 129 | }, 130 | }, 131 | { 132 | envVar: 'TRANSFORMERLAB_PLUGIN', 133 | importPath: 'rivet-plugin-transformerlab', 134 | isBuiltIn: false, 135 | registerFunction: (plugin, Rivet) => Rivet.globalRivetNodeRegistry.registerPlugin(plugin(Rivet)), 136 | settings: { 137 | envVarPrefix: 'TRANSFORMERLAB', 138 | settingsKey: 'transformerlab', 139 | settingsStructure: { 140 | host: 'HOST', 141 | }, 142 | }, 143 | }, 144 | ]; 145 | const registeredPlugins = {}; 146 | export async function setupPlugins(Rivet) { 147 | let pluginSettings = {}; 148 | console.log("Starting plugin registration..."); 149 | for (const config of pluginConfigurations) { 150 | if (process.env[config.envVar] === 'true') { 151 | // Skip registration if the plugin has already been registered 152 | if (registeredPlugins[config.settings.settingsKey]) { 153 | console.log(`${config.settings.settingsKey} plugin already registered.`); 154 | } 155 | let plugin = null; 156 | if (!config.isBuiltIn) { 157 | const module = await import(config.importPath); 158 | plugin = module.default ? module.default : module; 159 | } 160 | try { 161 | // Perform registration if the plugin hasn't been registered yet 162 | if (!registeredPlugins[config.settings.settingsKey]) { 163 | config.registerFunction(plugin, Rivet); 164 | console.log(`Successfully registered ${config.settings.settingsKey} plugin.`); 165 | // Mark plugin as registered 166 | registeredPlugins[config.settings.settingsKey] = true; 167 | } 168 | } 169 | catch (error) { 170 | console.warn(`Failed to register ${config.settings.settingsKey} plugin: ${error.message}`); 171 | } 172 | // Prepare plugin-specific settings if needed 173 | const pluginSpecificSettings = {}; 174 | let missingEnvVars = []; // To store missing environment variables 175 | if (config.settings && config.settings.settingsStructure) { 176 | for (const [settingKey, envSuffix] of Object.entries(config.settings.settingsStructure)) { 177 | // Construct the full environment variable name 178 | const fullEnvName = `${config.settings.envVarPrefix}_${envSuffix}`; 179 | // Fetch the value from the environment variables 180 | const value = process.env[fullEnvName]; 181 | if (value !== undefined) { 182 | pluginSpecificSettings[settingKey] = value; 183 | } 184 | else { 185 | missingEnvVars.push(fullEnvName); // Add missing env var to the list 186 | } 187 | } 188 | if (missingEnvVars.length > 0) { 189 | // Log missing environment variables as a warning 190 | console.warn(`[Warning] Missing environment variables for the '${config.settings.settingsKey}' plugin: ${missingEnvVars.join(', ')}.`); 191 | } 192 | // Assign the settings to the appropriate key in pluginSettings 193 | if (Object.keys(pluginSpecificSettings).length > 0) { 194 | pluginSettings[config.settings.settingsKey] = pluginSpecificSettings; 195 | } 196 | } 197 | } 198 | } 199 | // Optionally, log a summary or a positive confirmation message at the end 200 | console.log("Plugin registration complete."); 201 | return pluginSettings; 202 | } 203 | export function logAvailablePluginsInfo() { 204 | console.log("Available Plugins and Required Environment Variables:"); 205 | console.log("-----------------------------------------------------"); 206 | pluginConfigurations.forEach(config => { 207 | // Log the plugin's activation environment variable 208 | console.log(`Plugin: ${config.settings.settingsKey}`); 209 | console.log(` Activate with env var: ${config.envVar} (set to 'true' to enable)`); 210 | // Check and log required environment variables for settings 211 | if (config.settings && config.settings.settingsStructure) { 212 | Object.entries(config.settings.settingsStructure).forEach(([settingKey, envSuffix]) => { 213 | const fullEnvName = `${config.settings.envVarPrefix}_${envSuffix}`; 214 | console.log(` Required env var for ${settingKey}: ${fullEnvName}`); 215 | }); 216 | } 217 | console.log("-----------------------------------------------------"); 218 | }); 219 | } 220 | //# sourceMappingURL=pluginConfiguration.js.map -------------------------------------------------------------------------------- /dist/pluginConfiguration.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"pluginConfiguration.js","sourceRoot":"","sources":["../src/pluginConfiguration.ts"],"names":[],"mappings":"AAAA,MAAM,oBAAoB,GAAG;IACzB;QACI,MAAM,EAAE,iBAAiB;QACzB,UAAU,EAAE,uBAAuB;QACnC,SAAS,EAAE,KAAK;QAChB,gBAAgB,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,uBAAuB,CAAC,cAAc,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAChG,QAAQ,EAAE;YACN,YAAY,EAAE,QAAQ;YACtB,WAAW,EAAE,QAAQ;YACrB,iBAAiB,EAAE;gBACf,WAAW,EAAE,cAAc;aAC9B;SACJ;KACJ;IACD;QACI,MAAM,EAAE,eAAe;QACvB,SAAS,EAAE,IAAI;QACf,gBAAgB,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,uBAAuB,CAAC,cAAc,CAAC,KAAK,CAAC,YAAY,CAAC;QACrG,QAAQ,EAAE;YACN,YAAY,EAAE,QAAQ;YACtB,WAAW,EAAE,QAAQ;YACrB,iBAAiB,EAAE;gBACf,eAAe,EAAE,YAAY;gBAC7B,YAAY,EAAE,QAAQ;gBACtB,4BAA4B,EAAE,yBAAyB;aAC1D;SACJ;KACJ;IACD;QACI,MAAM,EAAE,kBAAkB;QAC1B,SAAS,EAAE,IAAI;QACf,gBAAgB,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,uBAAuB,CAAC,cAAc,CAAC,KAAK,CAAC,eAAe,CAAC;QACxG,QAAQ,EAAE;YACN,YAAY,EAAE,WAAW;YACzB,WAAW,EAAE,WAAW;YACxB,iBAAiB,EAAE;gBACf,eAAe,EAAE,SAAS;aAC7B;SACJ;KACJ;IACD;QACI,MAAM,EAAE,mBAAmB;QAC3B,SAAS,EAAE,IAAI;QACf,gBAAgB,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,uBAAuB,CAAC,cAAc,CAAC,KAAK,CAAC,gBAAgB,CAAC;QACzG,QAAQ,EAAE;YACN,YAAY,EAAE,YAAY;YAC1B,WAAW,EAAE,YAAY;YACzB,iBAAiB,EAAE;gBACf,gBAAgB,EAAE,SAAS;aAC9B;SACJ;KACJ;IACD;QACI,MAAM,EAAE,kBAAkB;QAC1B,SAAS,EAAE,IAAI;QACf,gBAAgB,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,uBAAuB,CAAC,cAAc,CAAC,KAAK,CAAC,eAAe,CAAC;QACxG,QAAQ,EAAE;YACN,YAAY,EAAE,WAAW;YACzB,WAAW,EAAE,WAAW;SAC3B;KACJ;IACD;QACI,MAAM,EAAE,oBAAoB;QAC5B,SAAS,EAAE,IAAI;QACf,gBAAgB,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,uBAAuB,CAAC,cAAc,CAAC,KAAK,CAAC,iBAAiB,CAAC;QAC1G,QAAQ,EAAE;YACN,YAAY,EAAE,aAAa;YAC3B,WAAW,EAAE,aAAa;YAC1B,iBAAiB,EAAE;gBACf,sBAAsB,EAAE,cAAc;aACzC;SACJ;KACJ;IACD;QACI,MAAM,EAAE,eAAe;QACvB,SAAS,EAAE,IAAI;QACf,gBAAgB,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,uBAAuB,CAAC,cAAc,CAAC,KAAK,CAAC,YAAY,CAAC;QACrG,QAAQ,EAAE;YACN,YAAY,EAAE,QAAQ;YACtB,WAAW,EAAE,QAAQ;SACxB;KACJ;IACD;QACI,MAAM,EAAE,iBAAiB;QACzB,SAAS,EAAE,IAAI;QACf,gBAAgB,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,uBAAuB,CAAC,cAAc,CAAC,KAAK,CAAC,cAAc,CAAC;QACvG,QAAQ,EAAE;YACN,YAAY,EAAE,UAAU;YACxB,WAAW,EAAE,UAAU;YACvB,iBAAiB,EAAE;gBACf,cAAc,EAAE,SAAS;aAC5B;SACJ;KACJ;IACD;QACI,MAAM,EAAE,gBAAgB;QACxB,UAAU,EAAE,sBAAsB;QAClC,SAAS,EAAE,KAAK;QAChB,gBAAgB,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,uBAAuB,CAAC,cAAc,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAChG,QAAQ,EAAE;YACN,YAAY,EAAE,SAAS;YACvB,WAAW,EAAE,SAAS;YACtB,iBAAiB,EAAE;gBACf,uBAAuB,EAAE,mBAAmB;aAC/C;SACJ;KACJ;IACD;QACI,MAAM,EAAE,eAAe;QACvB,UAAU,EAAE,qBAAqB;QACjC,SAAS,EAAE,KAAK;QAChB,gBAAgB,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,uBAAuB,CAAC,cAAc,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAChG,QAAQ,EAAE;YACN,YAAY,EAAE,QAAQ;YACtB,WAAW,EAAE,QAAQ;YACrB,iBAAiB,EAAE;gBACf,IAAI,EAAE,MAAM;aACf;SACJ;KACJ;IACD;QACI,MAAM,EAAE,eAAe;QACvB,UAAU,EAAE,qBAAqB;QACjC,SAAS,EAAE,KAAK;QAChB,gBAAgB,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,uBAAuB,CAAC,cAAc,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAChG,QAAQ,EAAE;YACN,YAAY,EAAE,QAAQ;YACtB,WAAW,EAAE,QAAQ;SACxB;KACJ;IACD;QACI,MAAM,EAAE,uBAAuB;QAC/B,UAAU,EAAE,6BAA6B;QACzC,SAAS,EAAE,KAAK;QAChB,gBAAgB,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,uBAAuB,CAAC,cAAc,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAChG,QAAQ,EAAE;YACN,YAAY,EAAE,gBAAgB;YAC9B,WAAW,EAAE,gBAAgB;YAC7B,iBAAiB,EAAE;gBACf,IAAI,EAAE,MAAM;aACf;SACJ;KACJ;CACJ,CAAC;AAEF,MAAM,iBAAiB,GAAG,EAAE,CAAC;AAE7B,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,KAAK;IACpC,IAAI,cAAc,GAAG,EAAE,CAAC;IAExB,OAAO,CAAC,GAAG,CAAC,iCAAiC,CAAC,CAAC;IAE/C,KAAK,MAAM,MAAM,IAAI,oBAAoB,EAAE,CAAC;QACxC,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,MAAM,EAAE,CAAC;YACxC,8DAA8D;YAC9D,IAAI,iBAAiB,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;gBACjD,OAAO,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAC,WAAW,6BAA6B,CAAC,CAAC;YAC7E,CAAC;YAED,IAAI,MAAM,GAAG,IAAI,CAAC;YAClB,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC;gBACpB,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;gBAC/C,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC;YACtD,CAAC;YAED,IAAI,CAAC;gBACD,gEAAgE;gBAChE,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;oBAClD,MAAM,CAAC,gBAAgB,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;oBACvC,OAAO,CAAC,GAAG,CAAC,2BAA2B,MAAM,CAAC,QAAQ,CAAC,WAAW,UAAU,CAAC,CAAC;oBAC9E,4BAA4B;oBAC5B,iBAAiB,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,GAAG,IAAI,CAAC;gBAC1D,CAAC;YACL,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACb,OAAO,CAAC,IAAI,CAAC,sBAAsB,MAAM,CAAC,QAAQ,CAAC,WAAW,YAAY,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;YAC/F,CAAC;YACD,6CAA6C;YAC7C,MAAM,sBAAsB,GAAG,EAAE,CAAC;YAClC,IAAI,cAAc,GAAG,EAAE,CAAC,CAAC,yCAAyC;YAElE,IAAI,MAAM,CAAC,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,iBAAiB,EAAE,CAAC;gBACvD,KAAK,MAAM,CAAC,UAAU,EAAE,SAAS,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,CAAC;oBACtF,+CAA+C;oBAC/C,MAAM,WAAW,GAAG,GAAG,MAAM,CAAC,QAAQ,CAAC,YAAY,IAAI,SAAS,EAAE,CAAC;oBACnE,iDAAiD;oBACjD,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;oBAEvC,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;wBACtB,sBAAsB,CAAC,UAAU,CAAC,GAAG,KAAK,CAAC;oBAC/C,CAAC;yBAAM,CAAC;wBACJ,cAAc,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,kCAAkC;oBACxE,CAAC;gBACL,CAAC;gBAED,IAAI,cAAc,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBAC5B,iDAAiD;oBACjD,OAAO,CAAC,IAAI,CAAC,oDAAoD,MAAM,CAAC,QAAQ,CAAC,WAAW,aAAa,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBAC3I,CAAC;gBAED,+DAA+D;gBAC/D,IAAI,MAAM,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBACjD,cAAc,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,GAAG,sBAAsB,CAAC;gBACzE,CAAC;YACL,CAAC;QACL,CAAC;IACL,CAAC;IAED,0EAA0E;IAC1E,OAAO,CAAC,GAAG,CAAC,+BAA+B,CAAC,CAAC;IAE7C,OAAO,cAAc,CAAC;AAC1B,CAAC;AAGD,MAAM,UAAU,uBAAuB;IACnC,OAAO,CAAC,GAAG,CAAC,uDAAuD,CAAC,CAAC;IACrE,OAAO,CAAC,GAAG,CAAC,uDAAuD,CAAC,CAAC;IACrE,oBAAoB,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE;QAClC,mDAAmD;QACnD,OAAO,CAAC,GAAG,CAAC,WAAW,MAAM,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC;QACtD,OAAO,CAAC,GAAG,CAAC,4BAA4B,MAAM,CAAC,MAAM,4BAA4B,CAAC,CAAC;QAEnF,4DAA4D;QAC5D,IAAI,MAAM,CAAC,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,iBAAiB,EAAE,CAAC;YACvD,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,iBAAiB,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,EAAE,SAAS,CAAC,EAAE,EAAE;gBAClF,MAAM,WAAW,GAAG,GAAG,MAAM,CAAC,QAAQ,CAAC,YAAY,IAAI,SAAS,EAAE,CAAC;gBACnE,OAAO,CAAC,GAAG,CAAC,0BAA0B,UAAU,KAAK,WAAW,EAAE,CAAC,CAAC;YACxE,CAAC,CAAC,CAAC;QACP,CAAC;QACD,OAAO,CAAC,GAAG,CAAC,uDAAuD,CAAC,CAAC;IACzE,CAAC,CAAC,CAAC;AACP,CAAC"} -------------------------------------------------------------------------------- /dist/textToSpeech.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"textToSpeech.js","sourceRoot":"","sources":["../src/textToSpeech.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,OAAO,MAAM,SAAS,CAAC;AAC9B,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAGtC,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC;IACtB,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,YAAY;CACnC,CAAC,CAAC;AAIH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,IAAY,EAAE,KAAgB;IAC7D,MAAM,MAAM,GAA8B;QACtC,KAAK,EAAE,IAAI;QACX,KAAK,EAAE,OAAO;QACd,KAAK,EAAE,KAAK;QACZ,eAAe,EAAE,KAAK;QACtB,KAAK,EAAE,GAAG;KACb,CAAC;IAEF,IAAI;QACA,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAE1D,qCAAqC;QACrC,MAAM,MAAM,GAAG,KAAK,CAAC,QAAQ,EAAE;YAC3B,IAAI,EAAE,QAAQ;YACd,IAAI,EAAE,OAAO;YACb,KAAK,EAAE,OAAO;YACd,KAAK,EAAE,GAAG;YACV,QAAQ,CAAC,mBAAmB;SAC/B,CAAC,CAAC;QAEH,oDAAoD;QACpD,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAEjC,8CAA8C;QAC9C,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC;YACxB,QAAQ,EAAE,CAAC;YACX,QAAQ,EAAE,EAAE;YACZ,UAAU,EAAE,KAAK;SACpB,CAAC,CAAC;QAEH,kCAAkC;QAClC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAE5B,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACnC,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC7B,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;YAC3B,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAChC,CAAC,CAAC,CAAC;KAEN;IAAC,OAAO,KAAK,EAAE;QACZ,OAAO,CAAC,KAAK,CAAC,wBAAwB,EAAE,KAAK,CAAC,CAAC;QAC/C,MAAM,KAAK,CAAC;KACf;AACL,CAAC"} -------------------------------------------------------------------------------- /dist/utils.js: -------------------------------------------------------------------------------- 1 | export function delay(ms) { 2 | return new Promise(resolve => setTimeout(resolve, ms)); 3 | } 4 | //# sourceMappingURL=utils.js.map -------------------------------------------------------------------------------- /dist/utils.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,KAAK,CAAC,EAAU;IAC5B,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;AAC3D,CAAC"} -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rivet-chat-api", 3 | "version": "1.0.0", 4 | "description": "Connects any rivet graph to Chatbot UI", 5 | "main": "rivet.ts", 6 | "scripts": { 7 | "build": "tsc", 8 | "start": "node dist/api.js", 9 | "start:prod": "NODE_ENV=production node dist/api.js", 10 | "test": "jest" 11 | }, 12 | "type": "module", 13 | "author": "", 14 | "license": "ISC", 15 | "dependencies": { 16 | "@ironclad/rivet-node": "^1.14.2", 17 | "cors": "^2.8.5", 18 | "express": "^4.18.2", 19 | "morgan": "^1.10.0", 20 | "remove": "^0.1.5", 21 | "rivet-plugin-chromadb": "latest", 22 | "rivet-plugin-mongodb": "latest", 23 | "rivet-plugin-ollama": "latest" 24 | }, 25 | "devDependencies": { 26 | "@types/config": "^3.3.3", 27 | "@types/express": "^4.17.21", 28 | "axios": "^1.6.7", 29 | "concurrently": "^8.2.2", 30 | "config": "^3.3.10", 31 | "jest": "^29.7.0", 32 | "typescript": "^5.3.3" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | 2 | With this project you can use "Chatbot-UI" as user interface for your Rivet projects! 3 | This allows you to create complex LLM based processes (e.g. a teachable assistant) in a visual programming interface and interact with it via a beautiful chat UI. Chatbot-UI also keeps all the conversation history, so we do not need to care about that! 4 | 5 | Features: 6 | - Creates an OpenAI SDK compatible API for any rivet project 7 | - Captures streaming output from a configured node 8 | - Streams the output 9 | - Transforms messages, before sending them to the rivet graph 10 | - Beautiful Chat-UI (provided by Chatbot-UI) 11 | - Chatbot-UI features: Multiple chats with conversation history, Integrated RAG etc. 12 | 13 | ![Chat UI for Rivet!](/chat_ui.png "Chat UI for Rivet!") 14 | 15 | # Last updates 19. Feb 2024 16 | - Added plugin support as well as access to environment variables (see #3 Additional features) 17 | 18 | # 1. Cloud setup 19 | ## Requirements 20 | - Free Github account (https://github.com/join) 21 | - Free Supabase account (https://supabase.com/) 22 | - Free railway account (https://railway.app/) 23 | 24 | ## Installation 25 | [![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/template/XjMVyQ?referralCode=Bc2Iiw) 26 | 27 | Click the button above or go to https://railway.app/template/XjMVyQ?referralCode=Bc2Iiw and click "Deploy Now" 28 | 29 | Watch my youtube video explaining the process: 30 | 31 | [![Watch the video](https://img.youtube.com/vi/WY2t1wFg50M/default.jpg)](https://youtu.be/WY2t1wFg50M) 32 | 33 | *Note: Written instructions can be found on the lower part of the template page* 34 | 35 | # 2. Local setup 36 | ## Rivet-Chat-API 37 | For simplicity all is explained for Visual Studio Code. You can of course run it in other IDEs. 38 | 39 | ### Software installation (prerequisites) 40 | 1. Install **Visual Studio Code**: https://code.visualstudio.com/download 41 | 1. Install **node.js + node package manager**: https://nodejs.org/en/download/ (in the install process, make sure you also install npm package manager!) 42 | 1. Install **Github**: https://desktop.github.com/ 43 | 44 | ### Clone the repo 45 | 1. Open terminal or command line 46 | 1. Enter ```git clone https://github.com/ai-made-approachable/rivet-chat-api.git``` 47 | 48 | ### Configure the project 49 | 1. Open the folder in Visual Studio Code (File -> Open Folder) 50 | 1. Open "Terminal -> New Terminal" and enter ```npm install``` 51 | 1. Go to /.vscode/ folder 52 | 1. Rename "launch_sample.json" to "launch.json" 53 | 1. Open "launch.json" and replace the value for OPEN_API_KEY with your OpenAI Key 54 | 55 | ### Running the project 56 | Just press "Run -> Start Debugging" in Visual Studio Code. 57 | Continue with the "Chat setup". The URLs displayed are the endpoints to connect the chat to rivet-chat-api. 58 | 59 | ### Use your own project files 60 | - Make sure your project file has an input of type "chat-message" and array checked (Type: chat-message[]) 61 | - Rename all "Chat" nodes you want to have streaming output to "output" 62 | - Select a main graph in "Project" settings 63 | - Add your file into the /rivet folder and remember the filename 64 | 65 | ### Using plugins 66 | If you want to use plugins, you need to import and register them first in graphManager.ts 67 | 68 | Example for mongoDB Plugin 69 | ``` 70 | import rivetMongoDbPlugin from 'rivet-plugin-mongodb'; 71 | import * as Rivet from '@ironclad/rivet-node'; 72 | Rivet.globalRivetNodeRegistry.registerPlugin(rivetMongoDbPlugin(Rivet)); 73 | ``` 74 | 75 | --- 76 | ## Chat setup 77 | We are using "Chatbot-UI" as it is a very user friendly UI (similiar to ChatGPTs UI): https://github.com/mckaywrigley/chatbot-ui 78 | 79 | ### Software installation (prerequisites) 80 | 1. Install **Docker**: https://docs.docker.com/engine/install/ 81 | 82 | ### Install Chatbot-UI 83 | Note: This installation is a bit long, but it is a one time thing! 84 | 85 | 1. Open terminal (MacOs) or command line (Windows) 86 | 1. ```git clone https://github.com/mckaywrigley/chatbot-ui.git``` 87 | 1. Navigate into the folder of the repository ```cd chatbot-ui``` 88 | 1. Install dependencies ```npm install``` 89 | 1. Install superbase: ```npm install supabase``` 90 | 1. Make sure Docker is running 91 | 1. Start Supabase ```supabase start``` 92 | 1. Create file .env.local ```cp .env.local.example .env.local``` 93 | 1. Open ".env.local" in Visual Studio Code (in chatbot-ui root folder) 94 | 1. Get the required values by running ```supabase status``` 95 | 1. Copy "API URL" value and insert it into "NEXT_PUBLIC_SUPABASE_URL" 96 | 1. Copy "anon key" and insert it into "NEXT_PUBLIC_SUPABASE_ANON_KEY" 97 | 1. Open ```supabase/migrations/20240108234540_setup.sql``` file 98 | 1. Replace "service_role_key" with the value from ```supabase status``` and save 99 | 100 | Note: Also see instructions on: https://github.com/mckaywrigley/chatbot-ui 101 | 102 | ### Starting the Chat-UI 103 | 1. Make sure Docker is running 104 | 1. Navigate to your "chatbot-ui" folder 105 | 1. Enter ```npm run chat``` 106 | 1. Navigate to the URL shown to you, usually: http://localhost:3000 107 | 108 | ### Configure the Chat-UI 109 | 1. When you start "chatbot-ui" for the first time enter e-mail + password (don't worry, all stays locally on your pc) 110 | 1. In the sidebar press on "Models" (Stars-Icon) and on "New Model" 111 | 1. Enter any name 112 | 1. Add the name of your graph e.g. "example.rivet-project" as model 113 | 1. Enter ```http://localhost:3100/``` as Base URL 114 | 1. Enter anything as API key 115 | 1. Open the model selection in the top-right corner and select your custom model 116 | 1. Have fun chatting 117 | 118 | # 3 Additional features 119 | 120 | ## 3.1 Accessing environment variables 121 | All environment variables are automatically available via the context node. 122 | 123 | ## 3.2 Using plugins 124 | When you boot up the application, it will tell which plugins are supported and what environment variables need to set to make them work. 125 | As this might change often, this is easier than trying to keep this readme up-to-date. 126 | 127 | Example: 128 | 129 | ``` 130 | Available Plugins and Required Environment Variables: 131 | ----------------------------------------------------- 132 | Plugin: chroma 133 | Activate with env var: USE_CHROMADB_PLUGIN (set to 'true' to enable) 134 | Required env var for databaseUri: CHROMA_DATABASE_URI 135 | ----------------------------------------------------- 136 | ``` 137 | 138 | Note: I did not test every plugin if it works. Please contact me if there are issues. 139 | 140 | ## 3.3 Additional debugging 141 | You can send data to an "raise event" node with id "debugger" to log the data to the console. This is useful if you run the graphs in the cloud. 142 | 143 | ## 3.4 Streaming output to the API 144 | Generally you can potentially use any node as output, but currently only nodes that either stream the output (chat nodes) or provide a clear output value (e.g. text node, object node) are supported. All nodes that shall be made available need to be renamed to "output" 145 | 146 | Non-Streaming output will still be streamed (but very quickly) by the rivet-chat-api for a better user experience. 147 | 148 | ## 3.5 CORS issues 149 | If you are using the API from another domain you might run into CORS errors. To fix this you can add `ACCESS_CONTROL_ALLOW_ORIGIN` to the environment variables (e.g. "*") 150 | -------------------------------------------------------------------------------- /rivet/example.rivet-project: -------------------------------------------------------------------------------- 1 | version: 4 2 | data: 3 | attachedData: 4 | trivet: 5 | testSuites: [] 6 | version: 1 7 | graphs: 8 | 7csOhbXu44Jps1V4znQpl: 9 | metadata: 10 | description: "" 11 | id: 7csOhbXu44Jps1V4znQpl 12 | name: "#main" 13 | nodes: 14 | '[ECtteSLdMxsPGFVFY6oM8]:text "Text"': 15 | data: 16 | text: You are ChatGPT, but running inside a Rivet Graph. Rivet is a visual 17 | programming tool for interactions with LLMs. Let the user know 18 | about that! 19 | outgoingConnections: 20 | - output->"output" t3d6sXbFk0i9EkAgvd37R/systemPrompt 21 | visualData: 354.23413061499997/308.36726447681565/330/66// 22 | '[NOaDQSTgVr8lJoGjPetSo]:graphInput "Graph Input"': 23 | data: 24 | dataType: chat-message[] 25 | id: input 26 | useDefaultValueInput: false 27 | outgoingConnections: 28 | - data->"output" t3d6sXbFk0i9EkAgvd37R/prompt 29 | visualData: 351.95598246174876/489.9382502904898/330/64/var(--node-color-3)/var(--grey-darkish) 30 | '[t3d6sXbFk0i9EkAgvd37R]:chat "output"': 31 | data: 32 | cache: false 33 | enableFunctionUse: false 34 | frequencyPenalty: 0 35 | headers: [] 36 | maxTokens: 1024 37 | model: gpt-3.5-turbo 38 | parallelFunctionCalling: true 39 | presencePenalty: 0 40 | stop: "" 41 | temperature: 0.5 42 | top_p: 1 43 | useAsGraphPartialOutput: true 44 | useFrequencyPenaltyInput: false 45 | useMaxTokensInput: false 46 | useModelInput: false 47 | usePresencePenaltyInput: false 48 | useStop: false 49 | useStopInput: false 50 | useTemperatureInput: false 51 | useTopP: false 52 | useTopPInput: false 53 | useUseTopPInput: false 54 | useUserInput: false 55 | visualData: 835.1097899549993/313.8617381627201/230/67// 56 | metadata: 57 | description: Example to run via rivet-node 58 | id: vHpwmvjUYbE-MQJ7H_sV_ 59 | mainGraphId: 7csOhbXu44Jps1V4znQpl 60 | title: Example project 61 | plugins: [] 62 | -------------------------------------------------------------------------------- /src/api.ts: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { GraphManager } from './graphManager.js'; 3 | import fs from 'fs'; 4 | import path from 'path'; 5 | import { authenticateAndGetJWT, listFiles, fetchFileContent } from './files.js'; 6 | import morgan from 'morgan'; 7 | import cors from 'cors'; 8 | 9 | const app = express(); 10 | const port = process.env.PORT || 3100; // Default port or environment variable 11 | const environment = process.env.NODE_ENV; 12 | const apiKey = process.env.RIVET_CHAT_API_KEY; 13 | const domain = process.env.FILEBROWSER_DOMAIN; 14 | 15 | app.use(express.json()); 16 | app.use(morgan('combined')); 17 | 18 | app.use((req, res, next) => { 19 | console.log(`Received ${req.method} request for ${req.url}`); 20 | next(); 21 | }); 22 | 23 | app.use(cors({ 24 | origin: process.env.ACCESS_CONTROL_ALLOW_ORIGIN || '*', 25 | methods: ['GET', 'POST', 'OPTIONS'], // Specify the methods you want to allow 26 | allowedHeaders: ['Content-Type', 'Authorization'], 27 | })); 28 | 29 | // Middleware for API Key validation in production 30 | app.use((req, res, next) => { 31 | //console.log('Request Headers:', JSON.stringify(req.headers, null, 2)); 32 | //console.log('Request Body:', JSON.stringify(req.body, null, 2)); 33 | if (environment === 'production') { 34 | const authHeader = req.headers.authorization; 35 | // Do not check authentification on non internal domains 36 | if (!req.hostname.endsWith('.internal')) { 37 | if (!authHeader || authHeader !== `Bearer ${apiKey}`) { 38 | return res.status(403).json({ message: 'Forbidden - Invalid API Key' }); 39 | } 40 | } 41 | } 42 | next(); 43 | }); 44 | 45 | // Dynamic model loading for chat completions based on the model specified in the request body 46 | app.post('/chat/completions', async (req, res) => { 47 | const modelId = req.body.model; 48 | if (!modelId) { 49 | return res.status(400).json({ message: 'Model identifier is required' }); 50 | } 51 | 52 | const messages = req.body.messages; 53 | if (!messages || !Array.isArray(messages)) { 54 | return res.status(400).json({ message: 'Messages are required and must be an array' }); 55 | } 56 | 57 | const processedMessages = messages.map(({ role: type, content: message }) => ({ type, message })); 58 | 59 | res.setHeader('Content-Type', 'text/event-stream'); 60 | res.setHeader('Transfer-Encoding', 'chunked'); 61 | 62 | const commonData = { 63 | id: 'chatcmpl-mockId12345', 64 | object: 'chat.completion.chunk', 65 | created: Date.now(), 66 | model: modelId, 67 | system_fingerprint: null, 68 | }; 69 | 70 | // Define a function to process and send chunks 71 | async function processAndSendChunks(graphManager) { 72 | let isFirstChunk = true; 73 | let previousChunk = null; 74 | let accumulatedContent = ""; 75 | 76 | for await (const chunk of graphManager.runGraph(processedMessages)) { 77 | if (isFirstChunk) { 78 | isFirstChunk = false; 79 | // Include role only for the first chunk 80 | previousChunk = { role: "assistant", content: chunk }; 81 | } else { 82 | // Send the previous chunk now that we know it's not the last 83 | if (previousChunk !== null) { 84 | const chunkData = { 85 | ...commonData, 86 | choices: [{ index: 0, delta: previousChunk, logprobs: null, finish_reason: null }], 87 | }; 88 | res.write(`data: ${JSON.stringify(chunkData)}\n\n`); 89 | accumulatedContent += previousChunk.content; 90 | } 91 | previousChunk = { content: chunk }; // Update previousChunk to current chunk 92 | } 93 | } 94 | 95 | // Handle the last chunk 96 | if (previousChunk !== null) { 97 | accumulatedContent += previousChunk.content; 98 | const lastChunkData = { 99 | ...commonData, 100 | choices: [{ index: 0, delta: previousChunk, logprobs: null, finish_reason: "stop" }], 101 | }; 102 | res.write(`data: ${JSON.stringify(lastChunkData)}\n\n`); 103 | } 104 | 105 | res.write('data: [DONE]\n\n'); 106 | //console.log('Final Output:', accumulatedContent); 107 | res.end() 108 | } 109 | 110 | // Handling for non-production environment or when not the special case 111 | if (environment !== 'production') { 112 | const directoryPath = path.resolve(process.cwd(), './rivet'); 113 | const modelFilePath = path.join(directoryPath, modelId); 114 | 115 | if (!fs.existsSync(modelFilePath)) { 116 | return res.status(404).json({ message: 'Model not found' }); 117 | } 118 | 119 | const graphManager = new GraphManager({ config: { file: modelFilePath } }); 120 | await processAndSendChunks(graphManager); 121 | return; 122 | } 123 | 124 | // Handling for production environment (excluding summarizer.rivet-project) 125 | try { 126 | const token = await authenticateAndGetJWT(); 127 | if (!token) { 128 | return res.status(401).json({ message: 'Failed to authenticate with Filebrowser' }); 129 | } 130 | const filePath = `/${modelId}`; // Adjust based on your Filebrowser structure 131 | const fileContent = await fetchFileContent(filePath, token); 132 | if (!fileContent) { 133 | return res.status(404).json({ message: 'Model not found on Filebrowser' }); 134 | } 135 | 136 | const graphManager = new GraphManager({ modelContent: fileContent }); 137 | await processAndSendChunks(graphManager); 138 | } catch (error) { 139 | console.error('Error processing graph:', error); 140 | res.status(500).json({ message: 'Internal server error' }); 141 | } 142 | }); 143 | 144 | 145 | app.get('/models', async (req, res) => { 146 | // For prod get the contents from the filebrowser application 147 | if (environment === 'production') { 148 | try { 149 | const token = await authenticateAndGetJWT(); 150 | if (!token) { 151 | return res.status(401).json({ message: 'Failed to authenticate with Filebrowser' }); 152 | } 153 | const files = await listFiles(token); // This should return the array of files 154 | if (!files || !Array.isArray(files)) { 155 | // If files is not an array, log the actual structure for debugging 156 | console.error('Unexpected structure:', files); 157 | return res.status(500).json({ message: 'Unexpected response structure from Filebrowser' }); 158 | } 159 | const models = files.filter(file => file.extension === '.rivet-project').map(file => ({ 160 | id: file.name, 161 | object: "model", 162 | created: new Date(file.modified).getTime() / 1000, // Convert to Unix timestamp if needed 163 | owned_by: "user", 164 | })); 165 | res.setHeader('Content-Type', 'application/json'); 166 | res.json({ object: "list", data: models }); 167 | } catch (error) { 168 | console.error('Error listing models from Filebrowser:', error); 169 | return res.status(500).json({ message: 'Internal server error', error: error.message }); 170 | } 171 | } else { 172 | // Local filesystem logic... 173 | const directoryPath = path.resolve(process.cwd(), './rivet'); 174 | fs.readdir(directoryPath, (err, files) => { 175 | if (err) { 176 | console.error('Unable to scan directory:', err); 177 | return res.status(500).json({ message: 'Internal server error' }); 178 | } 179 | 180 | const models = files.filter(file => file.endsWith('.rivet-project')).map(file => { 181 | const fullPath = path.join(directoryPath, file); 182 | const stats = fs.statSync(fullPath); 183 | return { 184 | id: file, 185 | object: "model", 186 | created: Math.floor(stats.birthtimeMs / 1000), 187 | owned_by: "user", 188 | }; 189 | }); 190 | res.setHeader('Content-Type', 'application/json'); 191 | res.json({ object: "list", data: models }); 192 | }); 193 | } 194 | }); 195 | 196 | 197 | const host = environment === 'production' ? '::' : 'localhost'; 198 | 199 | app.listen(Number(port), host, () => { 200 | console.log(`Server running at http://${host}:${port}/`); 201 | console.log("-----------------------------------------------------"); 202 | }); -------------------------------------------------------------------------------- /src/configTypes.ts: -------------------------------------------------------------------------------- 1 | export type ServerConfig = { 2 | port: number; 3 | file: string; 4 | graphName: string; 5 | graphInputName: string; 6 | streamingOutput: { 7 | nodeType: string; 8 | nodeName: string; 9 | }; 10 | returnGraphOutput: boolean; 11 | graphOutputName: string; 12 | textToSpeech: boolean; 13 | textToSpeechVoice?: string; 14 | }; -------------------------------------------------------------------------------- /src/files.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | // Use internal domain when online and public domain when testing locally 4 | const localDev = process.env.LOCAL_DEV === 'true'; 5 | let filebrowserUrl = process.env.FILEBROWSER_DOMAIN; 6 | 7 | export async function authenticateAndGetJWT() { 8 | const loginUrl = `${filebrowserUrl}/api/login`; 9 | const payload = { 10 | username: process.env.FILEBROWSER_USERNAME, 11 | password: process.env.FILEBROWSE_PASSWORD, 12 | recaptcha: "" // 13 | }; 14 | 15 | try { 16 | const response = await axios.post(loginUrl, payload); 17 | const token = response.data; 18 | // console.log('JWT Token:', token); 19 | return token; 20 | } catch (error) { 21 | console.error('Error during authentication:', error.response ? error.response.data : error.message); 22 | return null; 23 | } 24 | } 25 | 26 | export async function listFiles(jwtToken) { 27 | try { 28 | const url = `${filebrowserUrl}/api/resources/`; 29 | const response = await axios.get(url, { 30 | headers: { 'X-AUTH': jwtToken } 31 | }); 32 | // Directly return the array assuming response.data is the array 33 | return response.data.items; // Adjust this line if the structure is nested differently 34 | } catch (error) { 35 | console.error('Error fetching files:', error.response ? error.response.data : error.message); 36 | return []; // Return an empty array on error 37 | } 38 | } 39 | 40 | export async function fetchFileContent(filePath, jwtToken) { 41 | try { 42 | // Construct the URL to access the specific file, including the auth token as a query parameter. 43 | const fileUrl = `${filebrowserUrl}/api/raw${filePath}?auth=${jwtToken}`; 44 | 45 | const response = await axios.get(fileUrl, { responseType: 'blob' }); 46 | // For binary files, 'blob' is used. For text files, you might use 'text'. 47 | 48 | // Assuming you want to process the file content further or send it in a response: 49 | // Note: Depending on your use case, you might handle the response differently. 50 | console.log(`File ${filePath} downloaded successfully`); 51 | return response.data; 52 | } catch (error) { 53 | console.error('Error downloading file:', error.response ? error.response.data : error.message); 54 | return null; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/graphManager.ts: -------------------------------------------------------------------------------- 1 | import * as Rivet from '@ironclad/rivet-node'; 2 | import fs from 'fs/promises'; 3 | import path from 'path'; 4 | import { setupPlugins, logAvailablePluginsInfo } from './pluginConfiguration.js'; 5 | import { delay } from './utils.js'; 6 | import event from 'events'; 7 | 8 | logAvailablePluginsInfo(); 9 | event.setMaxListeners(100); 10 | 11 | class DebuggerServer { 12 | private static instance: DebuggerServer | null = null; 13 | private debuggerServer: any = null; 14 | 15 | private constructor() {} 16 | 17 | public static getInstance(): DebuggerServer { 18 | if (!DebuggerServer.instance) { 19 | DebuggerServer.instance = new DebuggerServer(); 20 | } 21 | return DebuggerServer.instance; 22 | } 23 | 24 | public startDebuggerServerIfNeeded() { 25 | if (!this.debuggerServer) { 26 | this.debuggerServer = Rivet.startDebuggerServer(); 27 | console.log('Debugger server started'); 28 | } 29 | return this.debuggerServer; 30 | } 31 | 32 | public getDebuggerServer() { 33 | return this.debuggerServer; 34 | } 35 | } 36 | 37 | export class GraphManager { 38 | config: any; 39 | modelContent?: string; 40 | streamedNodeIds: Set; 41 | 42 | constructor(params: { config?: any; modelContent?: string }) { 43 | this.config = params.config || {}; 44 | this.modelContent = params.modelContent; 45 | this.streamedNodeIds = new Set(); 46 | } 47 | 48 | async *runGraph(messages: Array<{ type: 'user' | 'assistant'; message: string }>) { 49 | console.time('runGraph'); 50 | let projectContent: string; 51 | 52 | // Ensure the DebuggerServer is started 53 | DebuggerServer.getInstance().startDebuggerServerIfNeeded(); 54 | 55 | try { 56 | // Dynamically setup plugins and retrieve their settings 57 | const pluginSettings = await setupPlugins(Rivet); 58 | 59 | if (this.modelContent) { 60 | // Use direct model content if provided 61 | projectContent = this.modelContent; 62 | } else { 63 | // Otherwise, read the model file from the filesystem 64 | const modelFilePath = path.resolve(process.cwd(), './rivet', this.config.file); 65 | console.log("-----------------------------------------------------"); 66 | console.log('runGraph called with model file:', modelFilePath); 67 | projectContent = await fs.readFile(modelFilePath, 'utf8'); 68 | } 69 | 70 | const project = Rivet.loadProjectFromString(projectContent); 71 | const graphInput = "input"; 72 | 73 | const datasetOptions = { 74 | save: true, 75 | // filePath should only be set if you're working with a file, adjust accordingly 76 | filePath: this.modelContent ? undefined : path.resolve(process.cwd(), './rivet', this.config.file), 77 | }; 78 | 79 | const datasetProvider = this.modelContent ? undefined : await Rivet.NodeDatasetProvider.fromProjectFile(datasetOptions.filePath, datasetOptions); 80 | 81 | const options: Rivet.NodeRunGraphOptions = { 82 | graph: this.config.graphName, 83 | inputs: { 84 | [graphInput]: { 85 | type: 'chat-message[]', 86 | value: messages.map( 87 | (message) => ({ 88 | type: message.type, 89 | message: message.message, 90 | } as Rivet.ChatMessage) 91 | ), 92 | }, 93 | }, 94 | openAiKey: process.env.OPENAI_API_KEY, 95 | remoteDebugger: DebuggerServer.getInstance().getDebuggerServer(), 96 | datasetProvider: datasetProvider, 97 | pluginSettings, 98 | context: { 99 | ...Object.entries(process.env).reduce((acc, [key, value]) => { 100 | acc[key] = value; 101 | return acc; 102 | }, {}), 103 | }, 104 | onUserEvent: { 105 | // Add "event" node and with id "debugger" to log data from Rivet to the server logs 106 | debugger: (data: Rivet.DataValue): Promise => { 107 | console.log(`Debugging data: ${JSON.stringify(data)}`); 108 | return Promise.resolve(); 109 | } 110 | } 111 | }; 112 | console.log("-----------------------------------------------------"); 113 | console.log('Creating processor'); 114 | const { processor, run } = Rivet.createProcessor(project, options); 115 | const runPromise = run(); 116 | console.log('Starting to process events'); 117 | 118 | let lastContent = ''; 119 | 120 | for await (const event of processor.events()) { 121 | // Handle 'partialOutput' events 122 | if (event.type === 'partialOutput' && event.node?.title?.toLowerCase() === "output") { 123 | const content = (event.outputs as any).response?.value || (event.outputs as any).output?.value; 124 | if (content && content.startsWith(lastContent)) { 125 | const delta = content.slice(lastContent.length); 126 | yield delta; 127 | lastContent = content; 128 | this.streamedNodeIds.add(event.node.id); // Add node ID to the Set when streaming output 129 | } 130 | } 131 | // Modify 'nodeFinish' handling to check if node ID has already streamed output 132 | else if ( 133 | event.type === 'nodeFinish' && 134 | event.node?.title?.toLowerCase() === "output" && 135 | !event.node?.type?.includes('chat') && 136 | !this.streamedNodeIds.has(event.node.id) // Check if the node ID is not in the streamedNodeIds Set 137 | ) { 138 | try { 139 | let content = (event.outputs as any).output.value || (event.outputs as any).output.output; 140 | if (content) { 141 | if (typeof content !== 'string') { 142 | content = JSON.stringify(content); 143 | } 144 | // Stream the content character-by-character 145 | for (const char of content) { 146 | await delay(0.5); // Artificial delay to simulate streaming 147 | yield char; 148 | } 149 | } 150 | } catch (error) { 151 | console.error(`Error: Cannot return output from node of type ${event.node?.type}. This only works with certain nodes (e.g., text or object)`); 152 | } 153 | } 154 | } 155 | 156 | 157 | console.log('Finished processing events'); 158 | 159 | const finalOutputs = await runPromise; 160 | if (finalOutputs && finalOutputs["output"]) { 161 | yield finalOutputs["output"].value; 162 | } 163 | if (finalOutputs["cost"]) { 164 | console.log(`Cost: ${finalOutputs["cost"].value}`); 165 | } 166 | } catch (error) { 167 | console.error('Error in runGraph:', error); 168 | } finally { 169 | console.timeEnd('runGraph'); 170 | console.log("-----------------------------------------------------"); 171 | } 172 | } 173 | } -------------------------------------------------------------------------------- /src/pluginConfiguration.ts: -------------------------------------------------------------------------------- 1 | const pluginConfigurations = [ 2 | { 3 | envVar: 'CHROMADB_PLUGIN', 4 | importPath: 'rivet-plugin-chromadb', 5 | isBuiltIn: false, 6 | registerFunction: (plugin, Rivet) => Rivet.globalRivetNodeRegistry.registerPlugin(plugin(Rivet)), 7 | settings: { 8 | envVarPrefix: 'CHROMA', 9 | settingsKey: 'chroma', 10 | settingsStructure: { 11 | databaseUri: 'DATABASE_URI', 12 | }, 13 | }, 14 | }, 15 | { 16 | envVar: 'GOOGLE_PLUGIN', 17 | isBuiltIn: true, 18 | registerFunction: (plugin, Rivet) => Rivet.globalRivetNodeRegistry.registerPlugin(Rivet.googlePlugin), 19 | settings: { 20 | envVarPrefix: 'GOOGLE', 21 | settingsKey: 'google', 22 | settingsStructure: { 23 | googleProjectId: 'PROJECT_ID', 24 | googleRegion: 'REGION', 25 | googleApplicationCredentials: 'APPLICATION_CREDENTIALS', 26 | }, 27 | }, 28 | }, 29 | { 30 | envVar: 'ANTHROPIC_PLUGIN', 31 | isBuiltIn: true, 32 | registerFunction: (plugin, Rivet) => Rivet.globalRivetNodeRegistry.registerPlugin(Rivet.anthropicPlugin), 33 | settings: { 34 | envVarPrefix: 'ANTHROPIC', 35 | settingsKey: 'anthropic', 36 | settingsStructure: { 37 | anthropicApiKey: 'API_KEY', 38 | }, 39 | }, 40 | }, 41 | { 42 | envVar: 'ASSEMBLYAI_PLUGIN', 43 | isBuiltIn: true, 44 | registerFunction: (plugin, Rivet) => Rivet.globalRivetNodeRegistry.registerPlugin(Rivet.assemblyAiPlugin), 45 | settings: { 46 | envVarPrefix: 'ASSEMBLYAI', 47 | settingsKey: 'assemblyAi', 48 | settingsStructure: { 49 | assemblyAiApiKey: 'API_KEY', 50 | }, 51 | }, 52 | }, 53 | { 54 | envVar: 'AUTOEVALS_PLUGIN', 55 | isBuiltIn: true, 56 | registerFunction: (plugin, Rivet) => Rivet.globalRivetNodeRegistry.registerPlugin(Rivet.autoevalsPlugin), 57 | settings: { 58 | envVarPrefix: 'AUTOEVALS', 59 | settingsKey: 'autoevals' 60 | }, 61 | }, 62 | { 63 | envVar: 'HUGGINGFACE_PLUGIN', 64 | isBuiltIn: true, 65 | registerFunction: (plugin, Rivet) => Rivet.globalRivetNodeRegistry.registerPlugin(Rivet.huggingFacePlugin), 66 | settings: { 67 | envVarPrefix: 'HUGGINGFACE', 68 | settingsKey: 'huggingface', 69 | settingsStructure: { 70 | huggingFaceAccessToken: 'ACCESS_TOKEN', 71 | }, 72 | }, 73 | }, 74 | { 75 | envVar: 'OPENAI_PLUGIN', 76 | isBuiltIn: true, 77 | registerFunction: (plugin, Rivet) => Rivet.globalRivetNodeRegistry.registerPlugin(Rivet.openAIPlugin), 78 | settings: { 79 | envVarPrefix: 'OPENAI', 80 | settingsKey: 'openai', 81 | }, 82 | }, 83 | { 84 | envVar: 'PINECONE_PLUGIN', 85 | isBuiltIn: true, 86 | registerFunction: (plugin, Rivet) => Rivet.globalRivetNodeRegistry.registerPlugin(Rivet.pineconePlugin), 87 | settings: { 88 | envVarPrefix: 'PINECONE', 89 | settingsKey: 'pinecone', 90 | settingsStructure: { 91 | pineconeApiKey: 'API_KEY', 92 | }, 93 | }, 94 | }, 95 | { 96 | envVar: 'MONGODB_PLUGIN', 97 | importPath: 'rivet-plugin-mongodb', 98 | isBuiltIn: false, 99 | registerFunction: (plugin, Rivet) => Rivet.globalRivetNodeRegistry.registerPlugin(plugin(Rivet)), 100 | settings: { 101 | envVarPrefix: 'MONGODB', 102 | settingsKey: 'mongoDB', 103 | settingsStructure: { 104 | mongoDBConnectionString: 'CONNECTION_STRING', 105 | }, 106 | }, 107 | }, 108 | { 109 | envVar: 'OLLAMA_PLUGIN', 110 | importPath: 'rivet-plugin-ollama', 111 | isBuiltIn: false, 112 | registerFunction: (plugin, Rivet) => Rivet.globalRivetNodeRegistry.registerPlugin(plugin(Rivet)), 113 | settings: { 114 | envVarPrefix: 'OLLAMA', 115 | settingsKey: 'ollama', 116 | settingsStructure: { 117 | host: 'HOST', 118 | }, 119 | }, 120 | }, 121 | { 122 | envVar: 'PDF2MD_PLUGIN', 123 | importPath: 'rivet-plugin-pdf2md', 124 | isBuiltIn: false, 125 | registerFunction: (plugin, Rivet) => Rivet.globalRivetNodeRegistry.registerPlugin(plugin(Rivet)), 126 | settings: { 127 | envVarPrefix: 'PDF2MD', 128 | settingsKey: 'pdf2md', 129 | }, 130 | }, 131 | { 132 | envVar: 'TRANSFORMERLAB_PLUGIN', 133 | importPath: 'rivet-plugin-transformerlab', 134 | isBuiltIn: false, 135 | registerFunction: (plugin, Rivet) => Rivet.globalRivetNodeRegistry.registerPlugin(plugin(Rivet)), 136 | settings: { 137 | envVarPrefix: 'TRANSFORMERLAB', 138 | settingsKey: 'transformerlab', 139 | settingsStructure: { 140 | host: 'HOST', 141 | }, 142 | }, 143 | }, 144 | ]; 145 | 146 | const registeredPlugins = {}; 147 | 148 | export async function setupPlugins(Rivet) { 149 | let pluginSettings = {}; 150 | 151 | console.log("Starting plugin registration..."); 152 | 153 | for (const config of pluginConfigurations) { 154 | if (process.env[config.envVar] === 'true') { 155 | // Skip registration if the plugin has already been registered 156 | if (registeredPlugins[config.settings.settingsKey]) { 157 | console.log(`${config.settings.settingsKey} plugin already registered.`); 158 | } 159 | 160 | let plugin = null; 161 | if (!config.isBuiltIn) { 162 | const module = await import(config.importPath); 163 | plugin = module.default ? module.default : module; 164 | } 165 | 166 | try { 167 | // Perform registration if the plugin hasn't been registered yet 168 | if (!registeredPlugins[config.settings.settingsKey]) { 169 | config.registerFunction(plugin, Rivet); 170 | console.log(`Successfully registered ${config.settings.settingsKey} plugin.`); 171 | // Mark plugin as registered 172 | registeredPlugins[config.settings.settingsKey] = true; 173 | } 174 | } catch (error) { 175 | console.warn(`Failed to register ${config.settings.settingsKey} plugin: ${error.message}`); 176 | } 177 | // Prepare plugin-specific settings if needed 178 | const pluginSpecificSettings = {}; 179 | let missingEnvVars = []; // To store missing environment variables 180 | 181 | if (config.settings && config.settings.settingsStructure) { 182 | for (const [settingKey, envSuffix] of Object.entries(config.settings.settingsStructure)) { 183 | // Construct the full environment variable name 184 | const fullEnvName = `${config.settings.envVarPrefix}_${envSuffix}`; 185 | // Fetch the value from the environment variables 186 | const value = process.env[fullEnvName]; 187 | 188 | if (value !== undefined) { 189 | pluginSpecificSettings[settingKey] = value; 190 | } else { 191 | missingEnvVars.push(fullEnvName); // Add missing env var to the list 192 | } 193 | } 194 | 195 | if (missingEnvVars.length > 0) { 196 | // Log missing environment variables as a warning 197 | console.warn(`[Warning] Missing environment variables for the '${config.settings.settingsKey}' plugin: ${missingEnvVars.join(', ')}.`); 198 | } 199 | 200 | // Assign the settings to the appropriate key in pluginSettings 201 | if (Object.keys(pluginSpecificSettings).length > 0) { 202 | pluginSettings[config.settings.settingsKey] = pluginSpecificSettings; 203 | } 204 | } 205 | } 206 | } 207 | 208 | // Optionally, log a summary or a positive confirmation message at the end 209 | console.log("Plugin registration complete."); 210 | 211 | return pluginSettings; 212 | } 213 | 214 | 215 | export function logAvailablePluginsInfo() { 216 | console.log("Available Plugins and Required Environment Variables:"); 217 | console.log("-----------------------------------------------------"); 218 | pluginConfigurations.forEach(config => { 219 | // Log the plugin's activation environment variable 220 | console.log(`Plugin: ${config.settings.settingsKey}`); 221 | console.log(` Activate with env var: ${config.envVar} (set to 'true' to enable)`); 222 | 223 | // Check and log required environment variables for settings 224 | if (config.settings && config.settings.settingsStructure) { 225 | Object.entries(config.settings.settingsStructure).forEach(([settingKey, envSuffix]) => { 226 | const fullEnvName = `${config.settings.envVarPrefix}_${envSuffix}`; 227 | console.log(` Required env var for ${settingKey}: ${fullEnvName}`); 228 | }); 229 | } 230 | console.log("-----------------------------------------------------"); 231 | }); 232 | } 233 | 234 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | export function delay(ms: number): Promise { 2 | return new Promise(resolve => setTimeout(resolve, ms)); 3 | } -------------------------------------------------------------------------------- /tests/modelsEndpoint.test.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | 3 | describe('GET /v1/models', () => { 4 | it('should respond with a list of models', async () => { 5 | const baseUrl = 'http://localhost:3100/v1/models'; // Adjust as necessary 6 | 7 | // Send a GET request 8 | const response = await axios.get(baseUrl); 9 | 10 | // Check the structure of the response 11 | expect(res.body).toEqual(expect.objectContaining({ 12 | object: 'list', 13 | data: expect.any(Array) 14 | })); 15 | 16 | // Check the structure of the first item in the data array 17 | if (res.body.data.length > 0) { 18 | expect(res.body.data[0]).toEqual(expect.objectContaining({ 19 | id: expect.any(String), 20 | object: expect.any(String), 21 | created: expect.any(Number), 22 | owned_by: 'user' 23 | })); 24 | } 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /tests/parallelRequests.test.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | 3 | describe('Parallel Requests', () => { 4 | it('should handle parallel requests', async () => { 5 | const baseUrl = 'http://localhost:3100/chat/completions'; // Adjust as necessary 6 | const requestData = { messages: [{ role: 'user', content: 'Hello' }] }; // Example request body 7 | 8 | // Send two parallel requests 9 | const requestOne = axios.post(baseUrl, requestData); 10 | const requestTwo = axios.post(baseUrl, requestData); 11 | 12 | // Wait for both requests to complete 13 | const responses = await Promise.all([requestOne, requestTwo]); 14 | 15 | // Check responses 16 | expect(responses[0].status).toBe(200); 17 | expect(responses[1].status).toBe(200); 18 | // Add more assertions as needed 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "NodeNext", 4 | "target": "ES2020", 5 | "moduleResolution": "NodeNext", 6 | "sourceMap": true, 7 | "outDir": "dist", 8 | "baseUrl": "./*", 9 | "skipLibCheck": true, 10 | "paths": { 11 | "@server/*": ["./*"], 12 | "@root/*": ["../src/*"] 13 | } 14 | }, 15 | "include": ["src/*"] 16 | } 17 | --------------------------------------------------------------------------------