Skip to content

Commit 0c60279

Browse files
committed
Merge remote-tracking branch 'origin/assistants' into react-setup
2 parents 6781a15 + 3869805 commit 0c60279

File tree

9 files changed

+295
-14
lines changed

9 files changed

+295
-14
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ test/
5454
# Miscellaneous
5555
.temp/
5656
.cache/
57+
*.pdf
5758

5859
# Database Files
5960
database.db

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,19 @@ npm run start:dev
5050

5151
This will start a development server with the `ts-node` and `nodemon` packages. This allows for easy development via cold-reloading. `ts-node` allows for running the typescript code without the need for compliation while `nodemon` monitors for changes to any `.ts` or `.js` files.
5252

53+
## Testing
54+
55+
Tests are stored in the `test/` directory and end with `*.test.ts`. Testing is done using jest.js and is setup with npm.
56+
57+
To run the current tests run this command from the main top of the project directory
58+
```sh
59+
npm test
60+
```
61+
or
62+
```sh
63+
npm run test
64+
```
65+
5366
#### React (Front End)
5467

5568
Simply run the below command in the `web` directory

server/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"devDependencies": {
1616
"@types/axios": "^0.14.4",
1717
"@types/jest": "^29.5.14",
18+
"@types/multer": "^1.4.12",
1819
"@types/jsonwebtoken": "^9.0.7",
1920
"@types/node": "^22.7.6",
2021
"@types/sqlite3": "^3.1.11",
@@ -33,6 +34,7 @@
3334
"cors": "^2.8.5",
3435
"dotenv": "^16.4.5",
3536
"express": "^4.21.1",
37+
"multer": "^1.4.5-lts.1",
3638
"jsonwebtoken": "^9.0.2",
3739
"openai": "^4.67.3",
3840
"sqlite": "^5.1.1",

server/src/gpt-controller.ts

Lines changed: 150 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@ import { GPTModel } from "./enums";
33
import { FileObject } from "openai/resources";
44
import path, { resolve } from "path";
55
import fs from "fs";
6+
import { AssistantBody, GPTResponse, ThreadMessage, GPTData, Testing, TestLocation } from "./types";
7+
import { prompt, questions } from "./prompts.data";
8+
import { threadId } from "worker_threads";
69

7-
class GPTController {
10+
export class GPTController {
811
private static client: OpenAI;
912
private model: GPTModel;
1013

@@ -17,15 +20,121 @@ class GPTController {
1720
this.model = model;
1821
}
1922

20-
async StreamReq() {
21-
const stream = await GPTController.client.chat.completions.create({
23+
async runGPTAnalysis(filePaths: string[]): Promise<GPTResponse[]> {
24+
const assistantParams: AssistantBody = {
25+
name: "Radiation Effects Researcher",
26+
instructions:
27+
"You are a radiation effects reasearcher. Use your knowledge to give very concise and numerical answers to the questions. Please do not give citations.",
2228
model: this.model,
23-
messages: [{ role: "user", content: "Say this is a test" }],
24-
stream: true,
29+
tools: [{ type: "file_search" }],
30+
temperature: 0.1,
31+
};
32+
33+
// Perhaps this should be pulled out to another function
34+
const results: GPTResponse[] = [];
35+
36+
// Upload files and create threads concurrently
37+
const fileThreads = filePaths.map(async (filePath: string) => {
38+
// Pretty sure we need an assistant for each thread to keep it separated.
39+
const fileID = await this.uploadFile(filePath);
40+
const threadMessage: ThreadMessage = {
41+
role: "assistant",
42+
content: prompt + questions,
43+
attachments: [{ file_id: fileID, tools: [{ type: "file_search" }] }],
44+
};
45+
//console.log(`Thread Message: ${threadMessage}`)
46+
// Create the three threads for each paper
47+
let threadResults: GPTData[] = [];
48+
const loopPromises = Array.from({ length: 3 }, async (_) => {
49+
const assistant = await this.createAssistant(assistantParams);
50+
const thread = await this.createThread(threadMessage);
51+
// Run the assistant on the thread and get the prompt results. Think non-stream results are better?
52+
let run = await GPTController.client.beta.threads.runs.createAndPoll(
53+
thread.id,
54+
{
55+
assistant_id: assistant.id,
56+
},
57+
);
58+
var result = "";
59+
if (run.status == "completed") {
60+
const messages =
61+
await GPTController.client.beta.threads.messages.list(
62+
run.thread_id,
63+
);
64+
var n = 1;
65+
for (const message of messages.data.reverse()) {
66+
// Need to check if the message content is text before parsing it
67+
if (message.content[0].type == "text") {
68+
result = message.content[0].text.value;
69+
var resvalues: GPTData = { // Initialize GPT data object
70+
paper_name: "",
71+
year: 0,
72+
author: [],
73+
part_no: "",
74+
type: [],
75+
manufacturer: "",
76+
testing_location: "Terrestrial",
77+
testing_type: "TID",
78+
data_type: 0
79+
}
80+
// Every second message has the data values
81+
if(n % 2 == 0) {
82+
// console.log(`${message.role} > ${result}`);
83+
let preres = result.split("ø").map((s) => s.replace("\n", ""));
84+
// console.log(preres)
85+
resvalues = {
86+
paper_name: preres[0],
87+
year: parseInt(preres[1]),
88+
author: preres[2].split(","),
89+
part_no: preres[3],
90+
type: preres[4].split("¶"),
91+
manufacturer: preres[5],
92+
testing_location: <TestLocation>preres[6],
93+
testing_type: <Testing>preres[7], // TODO: this gives a list ("TID, TID, DD") sometimes so the cast may fail
94+
data_type: 0 // TODO: add a prompt to get data_type
95+
};
96+
console.log(resvalues)
97+
threadResults.push(resvalues);
98+
}
99+
n++;
100+
}
101+
}
102+
} else {
103+
console.log(run.status);
104+
}
105+
});
106+
107+
// Wait for all loop iterations to finish
108+
await Promise.all(loopPromises);
109+
110+
const threadFinal: GPTResponse = {
111+
pass_1: threadResults[0],
112+
pass_2: threadResults[1],
113+
pass_3: threadResults[2],
114+
};
115+
116+
//console.log(threadFinal)
117+
results.push(threadFinal);
118+
119+
// TODO: Need to add the stream and and return it, not working yet.
120+
// Will be uncommented to implement
121+
122+
/*
123+
const stream = await GPTController.client.beta.threads.runs.create(
124+
thread.id,
125+
{assistant_id: assistant.id, stream: true}
126+
127+
)
128+
129+
let response = '';
130+
for await (const chunk of stream) {
131+
response += chunk.choices[0]?.delta?.content || "";
132+
}
133+
*/
25134
});
26-
for await (const chunk of stream) {
27-
process.stdout.write(chunk.choices[0]?.delta?.content || "");
28-
}
135+
136+
await Promise.all(fileThreads);
137+
return results;
29138
}
30139

31140
/*
@@ -43,7 +152,39 @@ class GPTController {
43152
file: fileStream,
44153
purpose: "assistants",
45154
});
46-
155+
console.log("uploadFile: ", response);
47156
return response.id; // Return the uploaded file ID
48157
}
158+
159+
/*
160+
* Parameters:
161+
* - assistantDetails: an instance of AssistantBody containing the required info to create an assistant
162+
* Function: Creates a new assistant
163+
* Returns:
164+
* - OpenAI.Beta.Assistants.Assistant: The new assistant instance
165+
*/
166+
private async createAssistant(
167+
assistantDetails: AssistantBody,
168+
): Promise<OpenAI.Beta.Assistants.Assistant> {
169+
const assistant = await GPTController.client.beta.assistants.create(
170+
assistantDetails,
171+
);
172+
return assistant;
173+
}
174+
175+
/*
176+
* Parameters:
177+
* - threadMessage: an instance of ThreadMessage containing the required info to create a new message
178+
* Function: Creates a new thread with an accompanied message
179+
* Returns:
180+
* - OpenAI.Beta.Thread: The new thread
181+
*/
182+
private async createThread(
183+
threadMessage: ThreadMessage,
184+
): Promise<OpenAI.Beta.Thread> {
185+
const thread = await GPTController.client.beta.threads.create({
186+
messages: [threadMessage],
187+
});
188+
return thread;
189+
}
49190
}

server/src/index.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,21 @@ import cors from "cors";
44
import bodyParser from "body-parser";
55
import dotenv from "dotenv";
66
import { open, Database } from "sqlite";
7+
import dotenv from "dotenv"
78

89
// Import routers
910

1011
import exampleRouter from "./routes/example-router";
1112
import cascadeRouter from "./routes/cascade-router";
1213
import { DatabaseController } from "./database-controller";
14+
1315
import adminRouter from "./routes/admin-router";
16+
import { GPTController } from "./gpt-controller";
17+
import { GPTModel } from "./enums";
1418

1519
const app = express();
1620
const PORT = process.env.PORT || 3000; // Use environment variable if available, otherwise default to 3000
17-
dotenv.config();
21+
dotenv.config();dotenv.config();
1822
/* In the future this will be used to ensure that only requests from certain domains are accepted
1923
const corsOptions = {
2024
origin: (origin: string | undefined, callback: (err: Error | null, allowed: boolean) => void) => {
@@ -34,19 +38,19 @@ const corsOptions = {
3438
app.use(cors(corsOptions));
3539
app.use(bodyParser.json());
3640

37-
async function initializeSystem(): Promise<DatabaseController> {
41+
async function initializeSystem(): Promise<{dbController: DatabaseController, gptController: GPTController}> {
3842
const db = await open({
3943
filename: "./database.db",
4044
driver: sqlite3.Database,
4145
});
42-
return new DatabaseController(db);
46+
return {dbController: new DatabaseController(db), gptController: new GPTController(GPTModel.GPT3_5Turbo)};
4347
}
4448

45-
initializeSystem().then((dbController: DatabaseController) => {
49+
initializeSystem().then(({dbController, gptController}) => {
4650
app.use("/", exampleRouter);
4751
//app.use("/getTable", tableRouter)
4852
app.use("/api/dataRequest", cascadeRouter(dbController));
49-
app.use("/api/adminRequest", adminRouter(dbController));
53+
app.use("/api/adminRequest", adminRouter(dbController, gptController));
5054

5155
app.listen(PORT, () => {
5256
console.log(`Server is running on ${PORT}`);

server/src/prompts.data.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
export const questions = [
2+
"What is the title of the paper",
3+
"Which year was the paper published",
4+
"What are all of the author's names, in the format (J. Doe) in a list like this ['J. Doe', 'R. Austin']",
5+
"What is the Part No. or name if that is not available",
6+
'What is the type of part (eg, switching regulator), if there are multiple part numbers listed, list them all and seperate them with a "¶"',
7+
"Who is the manufacturer",
8+
'What is the type of testing location: Respond to this question with "Terrestrial" for a terrestial testing location, or "Flight" for a flight testing location',
9+
'What type of testing was done: Respond to this question with "TID" for Total Ionizing Dose testing, "SEE" for heavy ion, proton, laser, or neutron testing, or "OTHER" if you are not completely 100% sure',
10+
];
11+
12+
export const prompt = `Please answer the following questions, as concisely as possible, and with a heavy emphasis on numbers instead of words.\n
13+
Use standard text and do not provide citations for each of your answers.
14+
Answer each question, and separate the answers with a "ø" character as a delimiter.
15+
If you are unable to answer the question accurately, provide the answer N/A.\n`;
16+
17+
export const Other_targeted_questions = [
18+
"What type was the radiation source",
19+
"Were there any failures, if so, when?",
20+
];
21+
22+
export const TID_targeted_questions = [
23+
"What type was the radiation source",
24+
"What was the total dose",
25+
"Were there any failures, if so, when?",
26+
];
27+
28+
export const SEE_targeted_questions = [
29+
"What type was the radiation source",
30+
"What the energy of the source",
31+
"Were there any failures, if so, when?",
32+
];
33+
34+
export const targeted_prompt = `Please answer the following questions, as concisely as possible, and with a heavy emphasis on numbers instead of words.
35+
Use standard text and do not provide citations for each of your answers.
36+
Answer each question, and separate the answers with a "ø" character as a delimiter.
37+
If you are unable to answer the question accurately, provide the answer N/A.\n`;
38+
39+
export const sort_questions = `There are five types of papers:
40+
The first are \"Laboratory Capabilities/Facility Equipment/Simulator\", which detail the capacities of a location, or university for use in research.
41+
The second are \"Testing Methods\", which detail specific methods of testing, without any devices being tested in the paper.
42+
The third are \"Phenomenons/Theory Papers_Sorted\", which detail theories or phenomenons that occur on a wide variety of devices, without doing specific testing on a device.
43+
The fourth are \"Compendiums\", which are collections of concise but detailed data on a large variety of devices. The devices must have their part numbers listed to be in this category.
44+
The fifth are \"Single/Multiple Device Testing\", which are papers that test one or more devices with one or more types of radiation. The device must have a part number to be in this category.
45+
In the same order, respond with \"LAB\", \"TST\", \"PHE\", \"CMP\", or \"SMD\", for the category that the paper best fits.`;
46+
47+
export const sort_prompts = `Please answer the following question, as concisely as possible, with a single word answer as outlined in the question.
48+
Classify this paper into one of the following categories: """ + questions + """
49+
Use standard text and do not provide citations for each of your answer.
50+
Answer the question with the keyword for one of the 5 papers.
51+
If you are unable to answer the question accurately, provide the answer N/A.`;

server/src/types.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
// TODO: Add proper values to data types
22

3+
import OpenAI from "openai";
4+
import { GPTModel } from "./enums";
5+
36
export type GetQuery = {
47
paper_name?: string;
58
author?: string;
@@ -35,6 +38,40 @@ export type RadData = {
3538
data_type: number;
3639
};
3740

41+
export type AssistantBody = {
42+
name: string;
43+
description?: string;
44+
instructions: string;
45+
model: GPTModel;
46+
tools: OpenAI.Beta.Assistants.AssistantTool[];
47+
temperature: number;
48+
response_format?: OpenAI.Beta.Threads.AssistantResponseFormatOption;
49+
};
50+
51+
export type GPTData = {
52+
paper_name: string;
53+
year: number;
54+
author: string[];
55+
part_no: string;
56+
type: string[];
57+
manufacturer: string;
58+
testing_location: TestLocation;
59+
testing_type: Testing;
60+
data_type: number;
61+
}
62+
63+
export type GPTResponse = {
64+
pass_1: GPTData,
65+
pass_2: GPTData,
66+
pass_3: GPTData
67+
}
68+
69+
export type ThreadMessage = {
70+
role: "user" | "assistant";
71+
content: string;
72+
attachments: OpenAI.Beta.Threads.ThreadCreateParams.Message.Attachment[];
73+
};
74+
3875
// Type of testing done
3976
export type TestLocation = "Terrestrial" | "Flight";
4077

0 commit comments

Comments
 (0)