Merge pull request #782 from arc53/feature/add-prompts

Feature/add prompts
This commit is contained in:
Alex
2023-11-23 00:13:02 +00:00
committed by GitHub
7 changed files with 257 additions and 143 deletions

View File

@@ -25,6 +25,7 @@ mongo = MongoClient(settings.MONGO_URI)
db = mongo["docsgpt"]
conversations_collection = db["conversations"]
vectors_collection = db["vectors"]
prompts_collection = db["prompts"]
answer = Blueprint('answer', __name__)
if settings.LLM_NAME == "gpt4":
@@ -43,10 +44,10 @@ with open(os.path.join(current_dir, "prompts", "chat_reduce_prompt.txt"), "r") a
chat_reduce_template = f.read()
with open(os.path.join(current_dir, "prompts", "chat_combine_creative.txt"), "r") as f:
chat_reduce_creative = f.read()
chat_combine_creative = f.read()
with open(os.path.join(current_dir, "prompts", "chat_combine_strict.txt"), "r") as f:
chat_reduce_strict = f.read()
chat_combine_strict = f.read()
api_key_set = settings.API_KEY is not None
embeddings_key_set = settings.EMBEDDINGS_KEY is not None
@@ -90,23 +91,6 @@ def get_vectorstore(data):
return vectorstore
# def get_docsearch(vectorstore, embeddings_key):
# if settings.EMBEDDINGS_NAME == "openai_text-embedding-ada-002":
# if is_azure_configured():
# os.environ["OPENAI_API_TYPE"] = "azure"
# openai_embeddings = OpenAIEmbeddings(model=settings.AZURE_EMBEDDINGS_DEPLOYMENT_NAME)
# else:
# openai_embeddings = OpenAIEmbeddings(openai_api_key=embeddings_key)
# docsearch = FAISS.load_local(vectorstore, openai_embeddings)
# elif settings.EMBEDDINGS_NAME == "huggingface_sentence-transformers/all-mpnet-base-v2":
# docsearch = FAISS.load_local(vectorstore, HuggingFaceHubEmbeddings())
# elif settings.EMBEDDINGS_NAME == "huggingface_hkunlp/instructor-large":
# docsearch = FAISS.load_local(vectorstore, HuggingFaceInstructEmbeddings())
# elif settings.EMBEDDINGS_NAME == "cohere_medium":
# docsearch = FAISS.load_local(vectorstore, CohereEmbeddings(cohere_api_key=embeddings_key))
# return docsearch
def is_azure_configured():
return settings.OPENAI_API_BASE and settings.OPENAI_API_VERSION and settings.AZURE_DEPLOYMENT_NAME
@@ -115,13 +99,16 @@ def complete_stream(question, docsearch, chat_history, api_key, prompt_id, conve
llm = LLMCreator.create_llm(settings.LLM_NAME, api_key=api_key)
if prompt_id == 'default':
prompt = chat_reduce_template
prompt = chat_combine_template
elif prompt_id == 'creative':
prompt = chat_reduce_creative
prompt = chat_combine_creative
elif prompt_id == 'strict':
prompt = chat_reduce_strict
prompt = chat_combine_strict
else:
prompt = chat_reduce_template
prompt = prompts_collection.find_one({"_id": ObjectId(prompt_id)})["content"]
import sys
print(prompt_id, file=sys.stderr)
print(prompt, file=sys.stderr)
docs = docsearch.search(question, k=2)
@@ -253,6 +240,19 @@ def api_answer():
embeddings_key = data["embeddings_key"]
else:
embeddings_key = settings.EMBEDDINGS_KEY
if 'prompt_id' in data:
prompt_id = data["prompt_id"]
else:
prompt_id = 'default'
if prompt_id == 'default':
prompt = chat_combine_template
elif prompt_id == 'creative':
prompt = chat_combine_creative
elif prompt_id == 'strict':
prompt = chat_combine_strict
else:
prompt = prompts_collection.find_one({"_id": ObjectId(prompt_id)})["content"]
# use try and except to check for exception
try:
@@ -270,7 +270,7 @@ def api_answer():
docs = docsearch.search(question, k=2)
# join all page_content together with a newline
docs_together = "\n".join([doc.page_content for doc in docs])
p_chat_combine = chat_combine_template.replace("{summaries}", docs_together)
p_chat_combine = prompt.replace("{summaries}", docs_together)
messages_combine = [{"role": "system", "content": p_chat_combine}]
source_log_docs = []
for doc in docs:

View File

@@ -1,11 +1,9 @@
import os
from flask import Blueprint, request, jsonify
import requests
import json
from pymongo import MongoClient
from bson.objectid import ObjectId
from werkzeug.utils import secure_filename
import http.client
from application.api.user.tasks import ingest
@@ -17,6 +15,7 @@ db = mongo["docsgpt"]
conversations_collection = db["conversations"]
vectors_collection = db["vectors"]
prompts_collection = db["prompts"]
feedback_collection = db["feedback"]
user = Blueprint('user', __name__)
current_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
@@ -71,19 +70,15 @@ def api_feedback():
answer = data["answer"]
feedback = data["feedback"]
print("-" * 5)
print("Question: " + question)
print("Answer: " + answer)
print("Feedback: " + feedback)
print("-" * 5)
response = requests.post(
url="https://86x89umx77.execute-api.eu-west-2.amazonaws.com/docsgpt-feedback",
headers={
"Content-Type": "application/json; charset=utf-8",
},
data=json.dumps({"answer": answer, "question": question, "feedback": feedback}),
feedback_collection.insert_one(
{
"question": question,
"answer": answer,
"feedback": feedback,
}
)
return {"status": http.client.responses.get(response.status_code, "ok")}
return {"status": "ok"}
@user.route("/api/delete_by_ids", methods=["get"])
def delete_by_ids():
@@ -249,18 +244,20 @@ def check_docs():
@user.route("/api/create_prompt", methods=["POST"])
def create_prompt():
data = request.get_json()
prompt = data["prompt"]
content = data["content"]
name = data["name"]
if name == "":
return {"status": "error"}
user = "local"
# write to mongodb
prompts_collection.insert_one(
resp = prompts_collection.insert_one(
{
"name": name,
"prompt": prompt,
"content": content,
"user": user,
}
)
return {"status": "ok"}
new_id = str(resp.inserted_id)
return {"id": new_id}
@user.route("/api/get_prompts", methods=["GET"])
def get_prompts():
@@ -271,32 +268,51 @@ def get_prompts():
list_prompts.append({"id": "creative", "name": "creative", "type": "public"})
list_prompts.append({"id": "precise", "name": "precise", "type": "public"})
for prompt in prompts:
list_prompts.append({"id": str(prompt["_id"]), "name": prompt["name"], type: "private"})
list_prompts.append({"id": str(prompt["_id"]), "name": prompt["name"], "type": "private"})
return jsonify(list_prompts)
@user.route("/api/get_single_prompt", methods=["GET"])
def get_single_prompt():
prompt_id = request.args.get("id")
if prompt_id == 'default':
with open(os.path.join(current_dir, "prompts", "chat_combine_default.txt"), "r") as f:
chat_combine_template = f.read()
return jsonify({"content": chat_combine_template})
elif prompt_id == 'creative':
with open(os.path.join(current_dir, "prompts", "chat_combine_creative.txt"), "r") as f:
chat_reduce_creative = f.read()
return jsonify({"content": chat_reduce_creative})
elif prompt_id == 'strict':
with open(os.path.join(current_dir, "prompts", "chat_combine_strict.txt"), "r") as f:
chat_reduce_strict = f.read()
return jsonify({"content": chat_reduce_strict})
prompt = prompts_collection.find_one({"_id": ObjectId(prompt_id)})
return jsonify(prompt['prompt'])
return jsonify({"content": prompt["content"]})
@user.route("/api/delete_prompt", methods=["POST"])
def delete_prompt():
prompt_id = request.args.get("id")
data = request.get_json()
id = data["id"]
prompts_collection.delete_one(
{
"_id": ObjectId(prompt_id),
"_id": ObjectId(id),
}
)
return {"status": "ok"}
@user.route("/api/update_prompt_name", methods=["POST"])
@user.route("/api/update_prompt", methods=["POST"])
def update_prompt_name():
data = request.get_json()
id = data["id"]
name = data["name"]
prompts_collection.update_one({"_id": ObjectId(id)},{"$set":{"name":name}})
content = data["content"]
# check if name is null
if name == "":
return {"status": "error"}
prompts_collection.update_one({"_id": ObjectId(id)},{"$set":{"name":name, "content": content}})
return {"status": "ok"}

View File

@@ -13,14 +13,10 @@ import { Doc } from './preferences/preferenceApi';
type PromptProps = {
prompts: { name: string; id: string; type: string }[];
selectedPrompt: { name: string; id: string };
onSelectPrompt: (name: string, id: string) => void;
onAddPrompt: (name: string) => void;
newPromptName: string;
onNewPromptNameChange: (name: string) => void;
isAddPromptModalOpen: boolean;
onToggleAddPromptModal: () => void;
onDeletePrompt: (name: string, id: string) => void;
selectedPrompt: { name: string; id: string; type: string };
onSelectPrompt: (name: string, id: string, type: string) => void;
setPrompts: (prompts: { name: string; id: string; type: string }[]) => void;
apiHost: string;
};
const Setting: React.FC = () => {
@@ -32,15 +28,10 @@ const Setting: React.FC = () => {
{ name: string; id: string; type: string }[]
>([]);
const selectedPrompt = useSelector(selectPrompt);
const [newPromptName, setNewPromptName] = useState('');
const [isAddPromptModalOpen, setAddPromptModalOpen] = useState(false);
const documents = useSelector(selectSourceDocs);
const [isAddDocumentModalOpen, setAddDocumentModalOpen] = useState(false);
const [newDocument, setNewDocument] = useState<Document>({
name: '',
vectorDate: '',
vectorLocation: '',
});
const dispatch = useDispatch();
const apiHost = import.meta.env.VITE_API_HOST || 'https://docsapi.arc53.com';
@@ -50,10 +41,6 @@ const Setting: React.FC = () => {
setWidgetScreenshot(screenshot);
};
// Function to toggle the Add Document modal
const toggleAddDocumentModal = () => {
setAddDocumentModalOpen(!isAddDocumentModalOpen);
};
useEffect(() => {
const fetchPrompts = async () => {
try {
@@ -172,19 +159,11 @@ const Setting: React.FC = () => {
<Prompts
prompts={prompts}
selectedPrompt={selectedPrompt}
onSelectPrompt={(name, id) =>
dispatch(setPrompt({ name: name, id: id }))
onSelectPrompt={(name, id, type) =>
dispatch(setPrompt({ name: name, id: id, type: type }))
}
onAddPrompt={addPrompt}
newPromptName={''}
onNewPromptNameChange={function (name: string): void {
throw new Error('Function not implemented.');
}}
isAddPromptModalOpen={false}
onToggleAddPromptModal={function (): void {
throw new Error('Function not implemented.');
}}
onDeletePrompt={onDeletePrompt}
setPrompts={setPrompts}
apiHost={apiHost}
/>
);
case 'Documents':
@@ -205,17 +184,6 @@ const Setting: React.FC = () => {
return null;
}
}
function addPrompt(name: string) {
if (name) {
setNewPromptName('');
toggleAddPromptModal();
}
}
function toggleAddPromptModal() {
setAddPromptModalOpen(!isAddPromptModalOpen);
}
};
const General: React.FC = () => {
@@ -252,65 +220,199 @@ const Prompts: React.FC<PromptProps> = ({
prompts,
selectedPrompt,
onSelectPrompt,
onAddPrompt,
onDeletePrompt,
setPrompts,
apiHost,
}) => {
const [isAddPromptModalOpen, setAddPromptModalOpen] = useState(false);
const [newPromptName, setNewPromptName] = useState('');
const openAddPromptModal = () => {
setAddPromptModalOpen(true);
const handleSelectPrompt = ({
name,
id,
type,
}: {
name: string;
id: string;
type: string;
}) => {
setNewPromptName(name);
onSelectPrompt(name, id, type);
};
const [newPromptName, setNewPromptName] = useState(selectedPrompt.name);
const [newPromptContent, setNewPromptContent] = useState('');
const closeAddPromptModal = () => {
setAddPromptModalOpen(false);
};
const handleSelectPrompt = (name: string) => {
const selected = prompts.find((prompt) => prompt.name === name);
if (selected) {
onSelectPrompt(selected.name, selected.id);
const handleAddPrompt = async () => {
try {
const response = await fetch(`${apiHost}/api/create_prompt`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: newPromptName,
content: newPromptContent,
}),
});
if (!response.ok) {
throw new Error('Failed to add prompt');
}
const newPrompt = await response.json();
if (setPrompts) {
setPrompts([
...prompts,
{ name: newPromptName, id: newPrompt.id, type: 'private' },
]);
}
onSelectPrompt(newPromptName, newPrompt.id, newPromptContent);
setNewPromptName(newPromptName);
} catch (error) {
console.error(error);
}
};
const handleDeletePrompt = (name: string) => {
const selected = prompts.find((prompt) => prompt.name === name);
if (selected) {
onDeletePrompt(selected.name, selected.id);
}
const handleDeletePrompt = () => {
setPrompts(prompts.filter((prompt) => prompt.id !== selectedPrompt.id));
console.log('selectedPrompt.id', selectedPrompt.id);
fetch(`${apiHost}/api/delete_prompt`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ id: selectedPrompt.id }),
})
.then((response) => {
if (!response.ok) {
throw new Error('Failed to delete prompt');
}
// get 1st prompt and set it as selected
if (prompts.length > 0) {
onSelectPrompt(prompts[0].name, prompts[0].id, prompts[0].type);
setNewPromptName(prompts[0].name);
}
})
.catch((error) => {
console.error(error);
});
};
useEffect(() => {
const fetchPromptContent = async () => {
console.log('fetching prompt content');
try {
const response = await fetch(
`${apiHost}/api/get_single_prompt?id=${selectedPrompt.id}`,
{
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
},
);
if (!response.ok) {
throw new Error('Failed to fetch prompt content');
}
const promptContent = await response.json();
setNewPromptContent(promptContent.content);
} catch (error) {
console.error(error);
}
};
fetchPromptContent();
}, [selectedPrompt]);
const handleSaveChanges = () => {
fetch(`${apiHost}/api/update_prompt`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
id: selectedPrompt.id,
name: newPromptName,
content: newPromptContent,
}),
})
.then((response) => {
if (!response.ok) {
throw new Error('Failed to update prompt');
}
onSelectPrompt(newPromptName, selectedPrompt.id, selectedPrompt.type);
setNewPromptName(newPromptName);
})
.catch((error) => {
console.error(error);
});
};
return (
<div className="mt-[59px]">
<div className="mb-4">
<p className="font-bold text-jet">Active Prompt</p>
<p className="font-semibold">Active Prompt</p>
<DropdownPrompt
options={prompts}
selectedValue={selectedPrompt.name}
onSelect={handleSelectPrompt}
showDelete={true}
onDelete={handleDeletePrompt}
/>
</div>
{/* <div>
<div className="mb-4">
<p>Prompt name </p>{' '}
<p className="mb-2 text-xs italic text-eerie-black text-gray-500">
start by editing name
</p>
<input
type="text"
value={newPromptName}
placeholder="Active Prompt Name"
className="w-full rounded-lg border-2 p-2"
onChange={(e) => setNewPromptName(e.target.value)}
/>
</div>
<div className="mb-4">
<p className="mb-2">Prompt content</p>
<textarea
className="h-32 w-full rounded-lg border-2 p-2"
value={newPromptContent}
onChange={(e) => setNewPromptContent(e.target.value)}
placeholder="Active prompt contents"
/>
</div>
<div className="flex justify-between">
<button
onClick={openAddPromptModal}
className="rounded-lg bg-purple-300 px-4 py-2 font-bold text-white transition-all hover:bg-purple-600"
className={`rounded-lg bg-green-500 px-4 py-2 font-bold text-white transition-all hover:bg-green-700 ${
newPromptName === selectedPrompt.name
? 'cursor-not-allowed opacity-50'
: ''
}`}
onClick={handleAddPrompt}
disabled={newPromptName === selectedPrompt.name}
>
Add New Prompt
</button>
</div> */}
{isAddPromptModalOpen && (
<AddPromptModal
newPromptName={newPromptName}
onNewPromptNameChange={setNewPromptName}
onAddPrompt={() => {
onAddPrompt(newPromptName);
closeAddPromptModal();
}}
onClose={closeAddPromptModal}
/>
)}
<button
className={`rounded-lg bg-red-500 px-4 py-2 font-bold text-white transition-all hover:bg-red-700 ${
selectedPrompt.type === 'public'
? 'cursor-not-allowed opacity-50'
: ''
}`}
onClick={handleDeletePrompt}
disabled={selectedPrompt.type === 'public'}
>
Delete Prompt
</button>
<button
className={`rounded-lg bg-blue-500 px-4 py-2 font-bold text-white transition-all hover:bg-blue-700 ${
selectedPrompt.type === 'public'
? 'cursor-not-allowed opacity-50'
: ''
}`}
onClick={handleSaveChanges}
disabled={selectedPrompt.type === 'public'}
>
Save Changes
</button>
</div>
</div>
);
};
@@ -319,14 +421,10 @@ function DropdownPrompt({
options,
selectedValue,
onSelect,
showDelete,
onDelete,
}: {
options: { name: string; id: string; type: string }[];
selectedValue: string;
onSelect: (value: string) => void;
showDelete?: boolean;
onDelete: (value: string) => void;
onSelect: (value: { name: string; id: string; type: string }) => void;
}) {
const [isOpen, setIsOpen] = useState(false);
@@ -356,19 +454,13 @@ function DropdownPrompt({
>
<span
onClick={() => {
onSelect(option.name);
onSelect(option);
setIsOpen(false);
}}
className="ml-2 flex-1 overflow-hidden overflow-ellipsis whitespace-nowrap py-3"
>
{option.name}
</span>
{showDelete && option.type === 'private' && (
<button onClick={() => onDelete(option.name)} className="p-2">
{/* Icon or text for delete button */}
Delete
</button>
)}
</div>
))}
</div>

View File

@@ -9,6 +9,7 @@ export function fetchAnswerApi(
selectedDocs: Doc,
history: Array<any> = [],
conversationId: string | null,
promptId: string | null,
): Promise<
| {
result: any;
@@ -62,6 +63,7 @@ export function fetchAnswerApi(
history: history,
active_docs: docPath,
conversation_id: conversationId,
prompt_id: promptId,
}),
})
.then((response) => {
@@ -89,6 +91,7 @@ export function fetchAnswerSteaming(
selectedDocs: Doc,
history: Array<any> = [],
conversationId: string | null,
promptId: string | null,
onEvent: (event: MessageEvent) => void,
): Promise<Answer> {
let namePath = selectedDocs.name;
@@ -123,6 +126,7 @@ export function fetchAnswerSteaming(
active_docs: docPath,
history: JSON.stringify(history),
conversation_id: conversationId,
prompt_id: promptId,
};
fetch(apiHost + '/stream', {

View File

@@ -25,6 +25,7 @@ export const fetchAnswer = createAsyncThunk<Answer, { question: string }>(
state.preference.selectedDocs!,
state.conversation.queries,
state.conversation.conversationId,
state.preference.prompt.id,
(event) => {
const data = JSON.parse(event.data);
@@ -81,6 +82,7 @@ export const fetchAnswer = createAsyncThunk<Answer, { question: string }>(
state.preference.selectedDocs!,
state.conversation.queries,
state.conversation.conversationId,
state.preference.prompt.id,
);
if (answer) {
let sourcesPrepped = [];

View File

@@ -8,7 +8,7 @@ import { RootState } from '../store';
interface Preference {
apiKey: string;
prompt: { name: string; id: string };
prompt: { name: string; id: string; type: string };
selectedDocs: Doc | null;
sourceDocs: Doc[] | null;
conversations: { name: string; id: string }[] | null;
@@ -16,7 +16,7 @@ interface Preference {
const initialState: Preference = {
apiKey: 'xxx',
prompt: { name: 'default', id: 'default' },
prompt: { name: 'default', id: 'default', type: 'public' },
selectedDocs: {
name: 'default',
language: 'default',

View File

@@ -13,7 +13,7 @@ const store = configureStore({
preference: {
apiKey: key ?? '',
selectedDocs: doc !== null ? JSON.parse(doc) : null,
prompt: { name: 'default', id: 'default' },
prompt: { name: 'default', id: 'default', type: 'private' },
conversations: null,
sourceDocs: [
{