From b78daae06e8dc423c5fcad24151f3e0cc278b60b Mon Sep 17 00:00:00 2001 From: nemo Date: Wed, 12 Feb 2025 15:06:52 +0000 Subject: [PATCH] add tests for Opey status and invoke endpoints --- server/controllers/OpeyIIController.ts | 167 ++++++++++++++++--------- server/schema/OpeySchema.ts | 11 +- server/services/OpeyClientService.ts | 57 ++++++++- tests/opey.test.ts | 22 ++++ 4 files changed, 185 insertions(+), 72 deletions(-) diff --git a/server/controllers/OpeyIIController.ts b/server/controllers/OpeyIIController.ts index 642214c..1fb890e 100644 --- a/server/controllers/OpeyIIController.ts +++ b/server/controllers/OpeyIIController.ts @@ -22,7 +22,110 @@ export class OpeyController { async getStatus( @Res() response: Response ): Response { - return response.status(200).json({status: 'Opey is running'}); + + try { + const opeyStatus = await this.opeyClientService.getOpeyStatus() + console.log("Opey status: ", opeyStatus) + return response.status(200).json({status: 'Opey is running'}); + + } catch (error) { + console.error("Error in /opey endpoint: ", error); + return response.status(500).json({ error: 'Internal Server Error' }); + } + + + } + + @Post('/stream') + + async streamOpey( + @Session() session: any, + @Req() request: Request, + @Res() response: Response + ) { + + let user_input: UserInput + try { + user_input = { + "message": request.body.message, + "thread_id": request.body.thread_id, + "is_tool_call_approval": request.body.is_tool_call_approval + } + } catch (error) { + console.error("Error in stream endpoint, could not parse into UserInput: ", error) + return response.status(500).json({ error: 'Internal Server Error' }) + } + + + console.log("Calling OpeyClientService.stream") + + const streamMiddlewareTransform = new Transform({ + transform(chunk, encoding, callback) { + console.log(`Logged Chunk: ${chunk}`) + this.push(chunk); + + callback(); + } + }) + + try { + const nodeStream = await this.opeyClientService.stream(user_input) + console.log(`Stream received from OpeyClientService.stream: ${nodeStream.readable}`) + nodeStream.pipe(streamMiddlewareTransform).pipe(response) + + response.status(200) + response.setHeader('Content-Type', 'text/event-stream') + response.setHeader('Cache-Control', 'no-cache') + response.setHeader('Connection', 'keep-alive') + + // nodeStream.on('data', (chunk) => { + // const data = chunk.toString() + // console.log(`data: ${data}`) + // response.write(`data: ${data}\n\n`) + // }) + // nodeStream.on('end', () => { + // console.log('Stream ended') + // response.end() + // }) + // nodeStream.on('error', (error) => { + // console.error(error) + // response.write(`data: Error reading stream\n\n`) + // response.end() + // }) + } catch (error) { + console.error(error) + response.status(500).json({ error: 'Internal Server Error' }) + } + } + + @Post('/invoke') + async invokeOpey( + @Session() session: any, + @Req() request: Request, + @Res() response: Response + ): Response { + + let user_input: UserInput + try { + user_input = { + "message": request.body.message, + "thread_id": request.body.thread_id, + "is_tool_call_approval": request.body.is_tool_call_approval + } + } catch (error) { + console.error("Error in stream endpoint, could not parse into UserInput: ", error) + return response.status(500).json({ error: 'Internal Server Error' }) + } + + try { + const opey_response = await this.opeyClientService.invoke(user_input) + + console.log("Opey response: ", opey_response) + return response.status(200).json(opey_response) + } catch (error) { + console.error(error) + return response.status(500).json({ error: 'Internal Server Error' }) + } } @Post('/consent') @@ -113,65 +216,5 @@ export class OpeyController { } - @Post('/stream') - - async streamOpey( - @Session() session: any, - @Req() request: Request, - @Res() response: Response - ) { - - let user_input: UserInput - try { - user_input = { - "message": request.body.message, - "thread_id": request.body.thread_id, - "is_tool_call_approval": request.body.is_tool_call_approval - } - } catch (error) { - console.error("Error in stream endpoint, could not parse into UserInput: ", error) - return response.status(500).json({ error: 'Internal Server Error' }) - } - - - console.log("Calling OpeyClientService.stream") - - const streamMiddlewareTransform = new Transform({ - transform(chunk, encoding, callback) { - console.log(`Logged Chunk: ${chunk}`) - this.push(chunk); - - callback(); - } - }) - - try { - const nodeStream = await this.opeyClientService.stream(user_input) - console.log(`Stream received from OpeyClientService.stream: ${nodeStream.readable}`) - nodeStream.pipe(streamMiddlewareTransform).pipe(response) - - response.status(200) - response.setHeader('Content-Type', 'text/event-stream') - response.setHeader('Cache-Control', 'no-cache') - response.setHeader('Connection', 'keep-alive') - - nodeStream.on('data', (chunk) => { - const data = chunk.toString() - console.log(`data: ${data}`) - response.write(`data: ${data}\n\n`) - }) - nodeStream.on('end', () => { - console.log('Stream ended') - response.end() - }) - nodeStream.on('error', (error) => { - console.error(error) - response.write(`data: Error reading stream\n\n`) - response.end() - }) - } catch (error) { - console.error(error) - response.status(500).json({ error: 'Internal Server Error' }) - } - } + } \ No newline at end of file diff --git a/server/schema/OpeySchema.ts b/server/schema/OpeySchema.ts index 7a2ae82..ab1cb03 100644 --- a/server/schema/OpeySchema.ts +++ b/server/schema/OpeySchema.ts @@ -9,15 +9,14 @@ export class StreamInput extends UserInput { stream_tokens: boolean; } +export type OpeyPaths = { + [key: string]: string; +} + export type OpeyConfig = { baseUri: string, authConfig: any, - paths: { - stream: string, - invoke: string, - approve_tool: string, - feedback: string, - } + paths: OpeyPaths, } export type AuthConfig = { diff --git a/server/services/OpeyClientService.ts b/server/services/OpeyClientService.ts index 20941b6..854f80f 100644 --- a/server/services/OpeyClientService.ts +++ b/server/services/OpeyClientService.ts @@ -15,6 +15,7 @@ export default class OpeyClientService { baseUri: process.env.VITE_CHATBOT_URL? process.env.VITE_CHATBOT_URL : 'http://localhost:5000', authConfig: this.authConfig, paths: { + status: '/status', stream: '/stream', invoke: '/invoke', approve_tool: '/approve_tool/{thead_id}', @@ -23,7 +24,31 @@ export default class OpeyClientService { } } + async getOpeyStatus(): Promise { + // Endpoint to check if Opey is running + try { + const url = `${this.opeyConfig.baseUri}${this.opeyConfig.paths.status}` + const response = await fetch(url, { + method: 'GET', + headers: {} + }) + if (response.status === 200) { + const status = await response.json() + return status + } else { + throw new Error(`Error getting status from Opey: ${response.status} ${response.statusText}`) + } + + + + + } catch (error) { + throw new Error(`Error getting status from Opey: ${error}`) + } + } + async stream(user_input: UserInput): Promise { + // Endpoint to post a message to Opey and stream the response tokens/messages try { const url = `${this.opeyConfig.baseUri}${this.opeyConfig.paths.stream}` @@ -31,7 +56,7 @@ export default class OpeyClientService { const stream_input = user_input as StreamInput stream_input.stream_tokens = true - console.log(`Posting to Opey: ${JSON.stringify(stream_input)}\n URL: ${url}`) //DEBUG + console.log(`Posting to Opey with streaming: ${JSON.stringify(stream_input)}\n URL: ${url}`) //DEBUG const response = await fetch(url, { method: 'POST', @@ -47,10 +72,34 @@ export default class OpeyClientService { return response.body as NodeJS.ReadableStream } catch (error) { - throw new Error(`Error streaming to Opey: ${error}`) + throw new Error(`Error streaming from Opey: ${error}`) } - + } - + async invoke(user_input: UserInput): Promise { + // Endpoint to post a message to Opey and get a response without stream + // I.e. a normal REST call + const url = `${this.opeyConfig.baseUri}${this.opeyConfig.paths.invoke}` + + console.log(`Posting to Opey, STREAMING OFF: ${JSON.stringify(user_input)}\n URL: ${url}`) //DEBUG + + try { + const response = await fetch(url, { + method: 'POST', + headers: { + "Authorization": `Bearer ${this.opeyConfig.authConfig.opeyJWT}`, + "Content-Type": "application/json" + }, + body: JSON.stringify(user_input) + }) + if (response.status === 200) { + const opey_response = await response.json() + return opey_response + } else { + throw new Error(`Error invoking Opey: ${response.status} ${response.statusText}`) + } + } catch (error) { + throw new Error(`Error invoking Opey: ${error}`) + } } } \ No newline at end of file diff --git a/tests/opey.test.ts b/tests/opey.test.ts index f9aa0b1..74d9209 100644 --- a/tests/opey.test.ts +++ b/tests/opey.test.ts @@ -23,6 +23,28 @@ describe('GET /api/opey', () => { }); }); +describe('GET /api/opey/invoke', () => { + let response: Response; + + let userInput: UserInput = { + message: "Hello Opey", + thread_id: uuidv4(), + is_tool_call_approval: false + } + + it('Should return 200', async () => { + const response = await request(app) + .post("/api/opey/invoke") + .send(userInput) + .set('Content-Type', 'application/json') + .then(response => { + console.log(`Response: ${response.body}`) + expect(response.status).toBe(200); + }) + + + }); +}) describe('POST /api/opey/stream', () => {