This commit is contained in:
Varun Shenoy
2023-02-10 14:54:20 -08:00
3 changed files with 171 additions and 29 deletions

View File

@@ -9,8 +9,12 @@ Successive queries can update the existing state of the graph or create an entir
The current few-shot prompt guides GPT-3 in accurately understanding the JSON formatting GraphGPT requires for proper rendering. You can see the entire prompt in `public/prompts/main.prompt`. A major issue at the moment is latency. Due to the nature of OpenAI API calls, it takes up to 20 seconds to receive a response.
## Prompting Magic
## Setup
1. Run `npm install` to download required dependencies (currently just [react-graph-vis](https://github.com/crubier/react-graph-vis)).
2. Enter your OPENAI_API_KEY in `src/App.js`.
2. Make sure you have an [OpenAI API key](https://openai.com/api/). You will enter this into the web app when running queries.
3. Run `npm run start`. GraphGPT should open up in a new browser tab.

View File

@@ -2,12 +2,12 @@ Given the current state of a graph and a prompt, extrapolate as many relationshi
Examples:
current state:
{ "counter": 1, "graph": { "nodes": [ { "id": 1, "label": "Bob", "color": "#ffffff" } ], "edges": [] } }
{ "nodes": [ { "id": 1, "label": "Bob", "color": "#ffffff" } ], "edges": [] }
prompt: Alice is Bob's roommate. Make her node green.
new state:
{ "counter": 2, "graph": { "nodes": [ { "id": 1, "label": "Bob", "color": "#ffffff" }, { "id": 2, "label": "Alice", "color": "#ff7675" } ], "edges": [ { "from": 1, "to": 2, "label": "roommate" }, ] } }
{ "nodes": [ { "id": 1, "label": "Bob", "color": "#ffffff" }, { "id": 2, "label": "Alice", "color": "#ff7675" } ], "edges": [ { "from": 1, "to": 2, "label": "roommate" } ] }
current state:
$state

View File

@@ -11,6 +11,8 @@ const DEFAULT_PARAMS = {
"presence_penalty": 0
}
const SELECTED_PROMPT = "STATELESS"
const options = {
layout: {
hierarchical: false
@@ -21,31 +23,157 @@ const options = {
};
function App() {
const [state, setState] = useState(
const [graphState, setGraphState] = useState(
{
counter: 0,
graph: {
nodes: [],
edges: []
}
})
const { graph } = state;
nodes: [],
edges: []
}
);
const clearState = () => {
setState({
counter: 0,
graph: {
nodes: [],
edges: []
}
setGraphState({
nodes: [],
edges: []
})
}
};
const queryPrompt = (prompt, apiKey) => {
fetch('prompts/main.prompt')
const updateGraph = (updates) => {
// updates will be provided as a list of lists
// each list will be of the form [ENTITY1, RELATION, ENTITY2] or [ENTITY1, COLOR]
var current_graph = JSON.parse(JSON.stringify(graphState));
if (updates.length === 0) {
return;
}
// check type of first element in updates
if (typeof updates[0] === "string") {
// updates is a list of strings
updates = [updates]
}
updates.forEach(update => {
if (update.length === 3) {
// update the current graph with a new relation
const [entity1, relation, entity2] = update;
// check if the nodes already exist
var node1 = current_graph.nodes.find(node => node.id === entity1);
var node2 = current_graph.nodes.find(node => node.id === entity2);
if (node1 === undefined) {
current_graph.nodes.push({ id: entity1, label: entity1, color: "#ffffff" });
}
if (node2 === undefined) {
current_graph.nodes.push({ id: entity2, label: entity2, color: "#ffffff" });
}
// check if an edge between the two nodes already exists and if so, update the label
var edge = current_graph.edges.find(edge => edge.from === entity1 && edge.to === entity2);
if (edge !== undefined) {
edge.label = relation;
return;
}
current_graph.edges.push({ from: entity1, to: entity2, label: relation });
} else if (update.length === 2 && update[1].startsWith("#")) {
// update the current graph with a new color
const [entity, color] = update;
// check if the node already exists
var node = current_graph.nodes.find(node => node.id === entity);
if (node === undefined) {
current_graph.nodes.push({ id: entity, label: entity, color: color });
return;
}
// update the color of the node
node.color = color;
} else if (update.length === 2 && update[0] == "DELETE") {
// delete the node at the given index
const [_, index] = update;
// check if the node already exists
var node = current_graph.nodes.find(node => node.id === index);
console.log("HIII");
if (node === undefined) {
return;
}
// delete the node
current_graph.nodes = current_graph.nodes.filter(node => node.id !== index);
// delete all edges that contain the node
current_graph.edges = current_graph.edges.filter(edge => edge.from !== index && edge.to !== index);
}
});
setGraphState(current_graph);
};
const queryStatelessPrompt = (prompt, apiKey) => {
fetch('prompts/stateless.prompt')
.then(response => response.text())
.then(text => text.replace("$prompt", prompt))
.then(text => text.replace("$state", JSON.stringify(state)))
.then(prompt => {
console.log(prompt)
const params = { ...DEFAULT_PARAMS, prompt: prompt, stop: "\n" };
const requestOptions = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + String(apiKey)
},
body: JSON.stringify(params)
};
fetch('https://api.openai.com/v1/completions', requestOptions)
.then(response => {
if (!response.ok) {
switch (response.status) {
case 401: // 401: Unauthorized: API key is wrong
throw new Error('Please double-check your API key.');
case 429: // 429: Too Many Requests: Need to pay
throw new Error('You exceeded your current quota, please check your plan and billing details.');
default:
throw new Error('Something went wrong with the request, please check the Network log');
}
}
return response.json();
})
.then((response) => {
const { choices } = response;
const text = choices[0].text;
console.log(text);
const updates = JSON.parse(text);
console.log(updates);
updateGraph(updates);
document.getElementsByClassName("searchBar")[0].value = "";
document.body.style.cursor = 'default';
document.getElementsByClassName("generateButton")[0].disabled = false;
}).catch((error) => {
console.log(error);
alert(error);
});
})
};
const queryStatefulPrompt = (prompt, apiKey) => {
fetch('prompts/stateful.prompt')
.then(response => response.text())
.then(text => text.replace("$prompt", prompt))
.then(text => text.replace("$state", JSON.stringify(graphState)))
.then(prompt => {
console.log(prompt)
@@ -77,21 +205,31 @@ function App() {
const { choices } = response;
const text = choices[0].text;
console.log(text);
const new_graph = JSON.parse(text);
console.log(new_graph);
setState(new_graph, () => {
console.log(state);
});
setGraphState(new_graph);
document.getElementsByClassName("searchBar")[0].value = "";
document.body.style.cursor = 'default';
document.getElementsByClassName("generateButton")[0].disabled = false;
document.getElementsByClassName("searchBar")[0].value = "";
}).catch((error) => {
console.log(error);
alert(error);
document.body.style.cursor = 'default';
document.getElementsByClassName("generateButton")[0].disabled = false;
});
})
};
const queryPrompt = (prompt, apiKey) => {
if (SELECTED_PROMPT === "STATELESS") {
queryStatelessPrompt(prompt, apiKey);
} else if (SELECTED_PROMPT === "STATEFUL") {
queryStatefulPrompt(prompt, apiKey);
} else {
alert("Please select a prompt");
document.body.style.cursor = 'default';
document.getElementsByClassName("generateButton")[0].disabled = false;
}
}
@@ -119,7 +257,7 @@ function App() {
</div>
</center>
<div className='graphContainer'>
<Graph graph={graph} options={options} style={{ height: "640px" }} />
<Graph graph={graphState} options={options} style={{ height: "640px" }} />
</div>
<p className='footer'>Pro tip: don't take a screenshot! You can right-click and save the graph as a .png 📸</p>
</div>