-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2 from senacor/feature/add-schema-validation
Feature/add schema validation
- Loading branch information
Showing
12 changed files
with
230 additions
and
92 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import "dotenv/config"; | ||
import express from "express"; | ||
import { registerRoutes } from "./index.js"; | ||
|
||
const app = express(); | ||
const port = process.env["PORT"] || 3000; | ||
|
||
app.listen(port, () =>{ | ||
console.log(`Server is running at http://localhost:${port}`); | ||
registerRoutes(); | ||
}) | ||
|
||
|
||
export { app }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import { z } from "zod"; | ||
import { makeGetEndpoint } from "../middleware/validation/makeGetEndpoint.js"; | ||
import { fileReader, parseFileReaderResponse } from "../util/fileReader.js"; | ||
import { OPENAI_MODEL, PROMPT_FILE_NAME, RESPONSE_FORMAT, openai } from "./index.js"; | ||
|
||
//TODO: Rework type inference of fileReader | ||
export const init = makeGetEndpoint(z.any(), async (_request, response) => { | ||
let messages: string[] = [] | ||
const promptFile = await fileReader(PROMPT_FILE_NAME); | ||
let jsonPrompt; | ||
|
||
if(RESPONSE_FORMAT.type === "json_object"){ | ||
|
||
jsonPrompt = await fileReader('json-format-prompt.docx'); | ||
if(parseFileReaderResponse(jsonPrompt)){ | ||
messages.push(jsonPrompt.content); | ||
} | ||
else{ | ||
return response.status(500).send({ | ||
status: 500, | ||
message: jsonPrompt.error | ||
}) | ||
} | ||
} | ||
|
||
if(parseFileReaderResponse(promptFile)){ | ||
messages.push(promptFile.content); | ||
} | ||
else{ | ||
return response.status(500).send({ | ||
status: 500, | ||
message: promptFile.error | ||
}) | ||
} | ||
|
||
|
||
const completion = await openai.chat.completions.create({ | ||
messages: messages.map(message => ({role: "system", content: message})), | ||
model: OPENAI_MODEL, | ||
response_format: RESPONSE_FORMAT | ||
}); | ||
console.log(completion.choices[0]?.message); | ||
return response | ||
.status(200) | ||
.send([ | ||
{ | ||
role: "system", | ||
content: promptFile.content | ||
}, | ||
{ | ||
role: "system", | ||
content: jsonPrompt?.content !== undefined ? jsonPrompt.content : "" | ||
}, | ||
{ | ||
role: "assistant", | ||
content: "Hi, ich bin der virtuelle Assistent von Qonto und kann alles rund um Qonto und unsere Leistungen beantworten. Bitte fragen Sie mich etwas" | ||
} | ||
]); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,59 +1,32 @@ | ||
import express, { Request, Response} from "express"; | ||
import express from "express"; | ||
import "dotenv/config"; | ||
import OpenAI from "openai"; | ||
import { ChatCompletionCreateParamsBase } from "openai/resources/chat/completions.js"; | ||
import { fileReader, hasErrors } from "../util/fileReader.js"; | ||
import { ChatCompletionCreateParams, ChatCompletionCreateParamsBase } from "openai/resources/chat/completions.js"; | ||
import { init } from "./get-init.js"; | ||
import { newMessage } from "./post-new-message.js"; | ||
|
||
/** | ||
* Change the prompt file, the model or the response format here | ||
*/ | ||
const PROMPT_FILE_NAME = "Chatbot Qonto.docx" | ||
const OPENAI_MODEL: ChatCompletionCreateParamsBase["model"] = "gpt-3.5-turbo"; | ||
const RESPONSE_FORMAT: ChatCompletionCreateParams.ResponseFormat = {type: "text"}; | ||
|
||
export const chatRouter = express.Router(); | ||
|
||
export const openai = new OpenAI(); | ||
|
||
|
||
chatRouter.post('/newMessage', async (request: Request, response: Response) => { | ||
const messages = request.body.messages; | ||
const completion = await openai.chat.completions.create({ | ||
messages: messages, | ||
model: OPENAI_MODEL, | ||
}); | ||
const choice = completion.choices[0]; | ||
if(choice === undefined){ | ||
response.status(500).send({ | ||
status: 500, | ||
message: "Expected an answer from the bot but got none." | ||
}) | ||
} | ||
else{ | ||
response.status(200).send(choice.message); | ||
} | ||
}); | ||
|
||
chatRouter.get('/init', async (_request: Request, response: Response) => { | ||
const fileContent = await fileReader(PROMPT_FILE_NAME); | ||
if(hasErrors(fileContent)){ | ||
console.log(fileContent.error.stack); | ||
response.status(500).json({ | ||
status: 500, | ||
message: 'Something went wrong' | ||
}); | ||
} | ||
else{ | ||
await openai.chat.completions.create({ | ||
messages: [{role: "system", content: fileContent.data}], | ||
model: OPENAI_MODEL, | ||
}); | ||
response.status(200).send([ | ||
{ | ||
role: "system", | ||
content: fileContent.data | ||
}, | ||
{ | ||
role: "assistant", | ||
content: "Hi, ich bin der virtuelle Assistent von Qonto und kann alles rund um Qonto und unsere Leistungen beantworten. Bitte fragen Sie mich etwas" | ||
} | ||
]); | ||
} | ||
}); | ||
|
||
const chatRouter = express.Router(); | ||
const openai = new OpenAI(); | ||
|
||
|
||
chatRouter.post('/newMessage', newMessage); | ||
chatRouter.get("/test") | ||
|
||
chatRouter.get('/init', init); | ||
|
||
|
||
export { | ||
PROMPT_FILE_NAME, | ||
OPENAI_MODEL, | ||
RESPONSE_FORMAT, | ||
chatRouter, | ||
openai, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import { z } from "zod"; | ||
import { makePostEndpoint } from "../middleware/validation/makePostEndpoint.js"; | ||
import { OPENAI_MODEL, RESPONSE_FORMAT, openai } from "./index.js"; | ||
|
||
const ChatCompletionRole = z.union([z.literal('user'), z.literal('system'), z.literal('assistant')]); | ||
export type ChatCompletionRole = z.infer<typeof ChatCompletionRole>; | ||
|
||
const MessageHistory = z.object({ | ||
messages: z.array( | ||
z.object({ | ||
role: ChatCompletionRole, | ||
content: z.string(), | ||
}) | ||
).nonempty() | ||
}); | ||
type MessageHistory = z.infer<typeof MessageHistory>; | ||
|
||
export const newMessage = makePostEndpoint(MessageHistory, async (request, response) => { | ||
const messages = request.body.messages; | ||
const completion = await openai.chat.completions.create({ | ||
messages, | ||
model: OPENAI_MODEL, | ||
response_format: RESPONSE_FORMAT | ||
}); | ||
const chatResponse = completion.choices[0]; | ||
if(!chatResponse){ | ||
return response.status(500).send("Got no response from the bot"); | ||
} | ||
console.log(chatResponse); | ||
return response.status(200).send(chatResponse.message); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,26 +1,21 @@ | ||
import "dotenv/config"; | ||
import express, { Request, Response } from "express"; | ||
import cors from "cors"; | ||
import express from "express"; | ||
import { chatRouter } from "./chat/index.js"; | ||
|
||
const app = express(); | ||
const port = process.env["PORT"] || 3000; | ||
import { app } from "./app.js"; | ||
import cors from "cors"; | ||
|
||
|
||
const corsOptions = { | ||
origin: ["http://localhost:4200", "https://chatbot-frontend-csf37hag2a-ey.a.run.app"], | ||
optionsSuccessStatus: 204 | ||
} | ||
|
||
app.options('*', cors(corsOptions)); | ||
app.use(cors(corsOptions)); | ||
app.use(express.json()); | ||
|
||
app.use('/chat', chatRouter); | ||
app.get("/", (_: Request, response: Response) => { | ||
response.status(200).json({message: "Hello World, running in Container"}); | ||
}) | ||
export const registerRoutes = () => { | ||
app.options('*', cors(corsOptions)); | ||
app.use(cors(corsOptions)); | ||
|
||
|
||
app.use(express.json()); | ||
|
||
app.use('/chat', chatRouter); | ||
} | ||
|
||
app.listen(port, () =>{ | ||
console.log(`Server is running at http://localhost:${port}`); | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import { Request, Response } from "express" | ||
import { z } from "zod" | ||
|
||
export const makeGetEndpoint = <TQuery>( | ||
schema: z.Schema<TQuery>, | ||
callback: ( | ||
req: Request<any, any, any, TQuery>, | ||
res: Response | ||
) => void | ||
) => (req:Request, res:Response) => { | ||
|
||
const bodyValidation = schema.safeParse(req.query); | ||
if(!bodyValidation.success){ | ||
return res | ||
.status(400) | ||
.send({ | ||
status: 400, | ||
message: bodyValidation.error.message | ||
}); | ||
} | ||
return callback(req as any, res); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import { Request, Response } from "express" | ||
import { z } from "zod" | ||
|
||
export const makePostEndpoint = <TBody>( | ||
schema: z.Schema<TBody>, | ||
callback: ( | ||
req: Request<any, any, TBody, any>, | ||
res: Response | ||
) => void | ||
) => (req:Request, res:Response) => { | ||
|
||
const bodyValidation = schema.safeParse(req.body); | ||
if(!bodyValidation.success){ | ||
return res | ||
.status(400) | ||
.send({ | ||
status: 400, | ||
message: bodyValidation.error.message | ||
}); | ||
} | ||
return callback(req, res); | ||
} |
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.