Skip to content

Commit cd267c5

Browse files
Allow ability to specify GPT model (#16)
* first stab at uploading file * cleanups * added migration to create a embedding table * added required columns for embeddings * updating module * introducing ORM and cleanups * wip * docs(README.md): add instructions for file upload and embedding creation feat(api.py): refactor FileUploadView to handle multiple pages and validate embeddings fix(api.py): add error handling for invalid files and server errors fix(models.py, 0004_embedding.py): correct dimensions of text_vectors from 1563 to 1536 refactor(urls.py): remove unused create_embeddings endpoint feat(upload_docs.sh): add script to automate document upload and embedding creation * feat(README.md): add detailed instructions for seeding the database and making API requests refactor(README.md): update curl commands to include API key in the header and JSON data in the body refactor(api.py): add error handling for invalid API key and unexpected errors refactor(api.py): update create_chat and set_system_prompt to use organization's API key for authentication refactor(chat.py): update run_chat_chain to include organization_id in retriever refactor(embeddings.py): replace FAISS with PGVector for storing embeddings refactor(functions.py): change model from gpt-4 to gpt-3.5-turbo in detect_languages_chain chore: remove unused embeddings files The changes were made to improve the security by using API keys for authentication, enhance the error handling, and update the way embeddings are stored and retrieved. The README was updated to reflect these changes and provide more detailed instructions. The model used in detect_languages_chain was changed for better performance. Unused embeddings files were removed to clean up the codebase. * include note * feat(README.md, api.py, chat.py, functions.py): add support for custom GPT model selection refactor(api.py, chat.py): rename 'org' to 'organization' for better readability refactor(chat.py): remove unused imports and redundant database query for system prompt fix(chat.py): adjust search_kwargs in run_chat_chain to include score_threshold feat(chat.py): pass gpt_model to run_chat_chain and detect_languages_chain for model flexibility refactor(chat.py): fetch system prompt directly from Organization model in chat_chain_prompt feat(functions.py): modify detect_languages_chain to accept model as parameter for flexibility --------- Co-authored-by: AkhileshNegi <[email protected]>
1 parent 0f2552c commit cd267c5

17 files changed

+868
-703
lines changed

Pipfile

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ pypdf = "*"
1313
openai = "*"
1414
python-dotenv = "*"
1515
psycopg = "*"
16+
pgvector = "*"
1617

1718
[dev-packages]
1819

Pipfile.lock

+580-539
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

+49-31
Original file line numberDiff line numberDiff line change
@@ -69,79 +69,97 @@ python manage.py migrate
6969
python manage.py runserver
7070
```
7171

72+
### Seeding
73+
74+
To seed the database with a sample organization, open a python shell (`python manage.py shell`) and run the following command:
75+
76+
```python
77+
from llm.models import Organization
78+
Organization.objects.create(
79+
name="Myna Mahila",
80+
system_prompt="I want you to act as a chatbot for providing tailored sexual and reproductive health advice to women in India. You represent an organization called The Myna Mahila Foundation (mynamahila.com), an Indian organization which empowers women by encouraging discussion of taboo subjects such as menstruation, and by setting up workshops to produce low-cost sanitary protection to enable girls to stay in school. In India, majority of girls report not knowing about menstruation before their first period. This is because of limited access to unbiased information due to stigma, discrimination, and lack of resources. The information you provide needs to be non-judgmental, confidential, accurate, and tailored to those living in urban slums. Your response should be in the same language as the user's input.",
81+
api_key="sk_EXAMPLE_SECRET_KEY",
82+
)
83+
```
84+
85+
To make requests to the API with the organization's API key, use the following command:
86+
87+
```bash
88+
curl -X POST -H "Authorization: sk_EXAMPLE_SECRET_KEY" -H "Content-Type: application/json" -d '{"system_prompt":"You are a chatbot that formats your responses as poetry."}' http://localhost:8000/api/system_prompt
89+
```
90+
91+
To upload a file from `llm/data/sources/*` and ultimately create embeddings out of it, use the following command:
92+
93+
```bash
94+
curl -X POST -H "Authorization: sk_EXAMPLE_SECRET_KEY" -H "Content-Type: multipart/form-data" -F "file=@llm/data/sources/ANXIETY.docx.pdf" http://localhost:8000/api/upload
95+
```
96+
97+
For testing and convenience, running the `upload_docs.sh` script will upload all the files in `llm/data/sources/*` for embeddings to be created out of them.
98+
99+
```bash
100+
./upload_docs.sh
101+
```
102+
72103
### Query
73104

74105
For testing the LLM, we are using [HTTPie](https://httpie.io) because of its succint syntax. You may alternatively use curl or Postman or your favorite HTTP client for this.
75106

76107
You can query the LLM using the following command:
77108

78109
```bash
79-
curl -X POST -d "prompt=How can I help someone with anxiety" http://127.0.0.1:8000/api/chat
110+
curl -X POST -H "Authorization: sk_EXAMPLE_SECRET_KEY" -H "Content-Type: application/json" -d '{"prompt": "Peshab ki jagah se kharash ho rahi hai"}' http://localhost:8000/api/chat
80111
```
81112

82113
This will return a JSON endpoint with the LLM response as well as a session id.
83114

84115
```json
85116
{
86-
"answer": "If you know someone who is struggling with anxiety, there are several ways you can help:\n\n1. Express concern: Reach out to them and provide support by simply listening to what they have to say. Let them know they can come to you when they feel anxious and that you would like to be there for them.\n\n2. Know what is not helpful: It is important to understand that continuing to say \"don't worry about that because...\" is not actually helping, even if your friend or loved one thinks it is. Avoid forcing activities that you think might be helpful for them.\n\n3. Ask them: Don't assume things. Ask the person what they need and act accordingly. Make them feel that you are there for them and their needs.\n\n4. Listen non-judgmentally: If the person isn't in a crisis, ask how they're feeling and how long they've been feeling that way. Be patient and engaged while they speak. Ask clarifying questions and show that you care.\n\n5. Provide practical help: Offer your loved one practical assistance with tasks like getting groceries, cleaning, or household chores. Be careful not to take over or encourage dependency.\n\n6. Educate yourself: Understanding what helps anxiety takes time and effort. Make yourself aware of what anxiety is so that you don't provide any wrong information or invalid help.\n\nRemember, it's important to be supportive, patient, and understanding. Encourage them to seek professional help if needed.",
117+
"answer": "Aapki samasya ke liye dhanyavaad. Yah peshab ke samay kharash ki samasya ho sakti hai. Isko urinary tract infection (UTI) kaha jata hai. Urinary tract infection utpann hone ka mukhya karan antarik infection ho sakta hai.",
87118
"chat_history": [],
88-
"session_id": "e971aH"
119+
"session_id": "uhh0pq"
89120
}
90121
```
91122

92123
To ask a fellow up question, you can use the session id returned in the previous response:
93124

94125
```bash
95-
curl -X POST -d "prompt=Give me examples of practical help I can offer" session_id="e971aH" http://127.0.0.1:8000/api/chat
126+
curl -X POST -H "Authorization: sk_EXAMPLE_SECRET_KEY" -H "Content-Type: application/json" -d '{"prompt":"Peshab ki jagah kharash hai","session_id":"uhh0pq"}' http://127.0.0.1:8000/api/chat
96127
```
97128

98129
```json
99130
{
100-
"answer": "Here are some examples of practical help you can offer to someone:\n\n1. Offer to run errands or help with household tasks, like getting groceries, cleaning, or cooking.\n2. Provide transportation to appointments or offer to accompany them to medical or therapy appointments.\n3. Help with childcare or offer to babysit so they can have some time for themselves.\n4. Assist with paperwork or administrative tasks, such as filling out forms or organizing documents.\n5. Offer to help with technology-related issues, like setting up a new device or troubleshooting computer problems.\n6. Provide emotional support by being a good listener and offering a shoulder to lean on.\n7. Help them research and connect with local resources or support groups that may be beneficial to their situation.\n8. Offer to help with financial matters, such as budgeting or finding ways to save money.\n9. Assist in finding educational opportunities or job training programs to enhance their skills and improve their employment prospects.\n10. Help them explore and engage in activities that promote self-care and well-being, such as exercising together, practicing mindfulness, or participating in a hobby they enjoy.\n\nRemember, it's important to ask the person what they specifically need and respect their boundaries. Everyone's situation is unique, so offering tailored support can make a meaningful difference.",
131+
"answer": "aapki samasya ke liye dhanyavad. Yah peshaab ki jagah me kharash ho sakti hai. Isko urinary tract infection (UTI) kaha jata hai. UTI utpann hone ka mukhya karan aantarik infection ho sakta hai.",
101132
"chat_history": [
102133
[
103-
["content", "How can I help someone with anxiety"],
134+
["content", "Peshab ki jagah se kharash ho rahi hai"],
104135
["additional_kwargs", {}],
136+
["type", "human"],
105137
["example", false]
106138
],
107139
[
108140
[
109141
"content",
110-
"If you know someone who is struggling with anxiety, there are several ways you can help:\n\n1. Express concern: Reach out to them and provide support by simply listening to what they have to say. Let them know they can come to you when they feel anxious and that you would like to be there for them.\n\n2. Know what is not helpful: It is important to understand that continuing to say \"don't worry about that because...\" is not actually helping, even if your friend or loved one thinks it is. Avoid forcing activities that you think might be helpful for them.\n\n3. Ask them: Don't assume things. Ask the person what they need and act accordingly. Make them feel that you are there for them and their needs.\n\n4. Listen non-judgmentally: If the person isn't in a crisis, ask how they're feeling and how long they've been feeling that way. Be patient and engaged while they speak. Ask clarifying questions and show that you care.\n\n5. Provide practical help: Offer your loved one practical assistance with tasks like getting groceries, cleaning, or household chores. Be careful not to take over or encourage dependency.\n\n6. Educate yourself: Understanding what helps anxiety takes time and effort. Make yourself aware of what anxiety is so that you don't provide any wrong information or invalid help.\n\nRemember, it's important to be supportive, patient, and understanding. Encourage them to seek professional help if needed."
142+
"Aapki samasya ke liye dhanyavaad. Yah peshab ke samay kharash ki samasya ho sakti hai. Isko urinary tract infection (UTI) kaha jata hai. Urinary tract infection utpann hone ka mukhya karan antarik infection ho sakta hai."
111143
],
112144
["additional_kwargs", {}],
145+
["type", "ai"],
113146
["example", false]
114147
]
115148
],
116-
"session_id": "e971aH"
149+
"session_id": "uhh0pq"
117150
}
118151
```
119152

120-
### Embeddings
121-
122-
The source documents for the embeddings are stored locally in the `llm/data/sources` folder. To re-generate the embeddings, run:
153+
The default model used is [`gpt-3.5-turbo`](https://platform.openai.com/docs/models/gpt-3-5) but you can specify a different GPT model by passing a `gpt_model` parameter in the request body.
123154

124155
```bash
125-
curl -X POST http://127.0.0.1:8000/api/embeddings
156+
curl -X POST -H "Authorization: sk_EXAMPLE_SECRET_KEY" -H "Content-Type: application/json" -d '{"prompt":"Mujhe peshab ki jagah pe kharash ho rahi hai","gpt_model":"gpt-3.5-turbo-16k"}' http://127.0.0.1:8000/api/chat
126157
```
127158

128-
If you intend to utilize this with your custom documents, first remove all existing files within the same folder and then add your files. Afterward, rerun the above command.
129-
130-
### Seeding
131-
132-
To seed the database with a sample organization, open a python shell (`python manage.py shell`) and run the following command:
133-
134-
```python
135-
from llm.models import Organization
136-
Organization.objects.create(
137-
name="Myna Mahila",
138-
system_prompt="I want you to act as a chatbot for providing tailored sexual and reproductive health advice to women in India. You represent an organization called The Myna Mahila Foundation (mynamahila.com), an Indian organization which empowers women by encouraging discussion of taboo subjects such as menstruation, and by setting up workshops to produce low-cost sanitary protection to enable girls to stay in school. In India, majority of girls report not knowing about menstruation before their first period. This is because of limited access to unbiased information due to stigma, discrimination, and lack of resources. The information you provide needs to be non-judgmental, confidential, accurate, and tailored to those living in urban slums. Your response should be in the same language as the user's input.",
139-
api_key="sk_EXAMPLE_SECRET_KEY",
140-
)
141-
```
142-
143-
To make requests to the API with the organization's API key, use the following command:
144-
145-
```bash
146-
curl -X POST -H "Authorization: sk_EXAMPLE_SECRET_KEY" -H "Content-Type: application/json" -d '{"system_prompt":"You are a chatbot that formats your responses as poetry."}' http://localhost:8000/api/system_prompt
159+
```json
160+
{
161+
"answer": "Aapki samasya ke liye dhanyavaad. Yah peshaab karne ke samay kharash ki samasya ho sakti hai. Isko urinary tract infection (UTI) kaha jata hai. UTI utpann hone ka mukhya karan aantrik infection ho sakta hai.",
162+
"chat_history": [],
163+
"session_id": "cfbQXg"
164+
}
147165
```

docker-compose.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
services:
22
db:
3-
image: postgres
3+
image: ankane/pgvector # https://hub.docker.com/r/ankane/pgvector
44
environment:
55
POSTGRES_DB: llm_db
66
POSTGRES_USER: llm_agent

llm/api.py

+120-51
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,20 @@
77
from rest_framework.decorators import api_view
88
from rest_framework.response import Response
99

10+
from llm.chains.embeddings import get_pgvector_idx
11+
1012
from .chains.functions import detect_languages_chain
1113
from .chains.chat import run_chat_chain
12-
from .chains import embeddings
1314
from .data import loader
1415
from .models import Organization
1516

17+
from django.http import JsonResponse
18+
from rest_framework.parsers import MultiPartParser
19+
from rest_framework.views import APIView
20+
from pypdf import PdfReader
21+
from llm.models import Embedding
22+
23+
import openai
1624

1725
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "llm.settings")
1826

@@ -21,76 +29,137 @@
2129

2230
@api_view(["POST"])
2331
def create_chat(request):
24-
prompt = request.data.get("prompt").strip()
25-
session_id = (request.data.get("session_id") or generate_short_id()).strip()
26-
27-
language_detector = detect_languages_chain()
28-
languages = language_detector.run(prompt)
29-
30-
print(f"Language detector chain result: {languages}")
32+
try:
33+
organization = current_organization(request)
34+
if not organization:
35+
return Response(
36+
f"Invalid API key",
37+
status=status.HTTP_404_NOT_FOUND,
38+
)
39+
40+
prompt = request.data.get("prompt").strip()
41+
gpt_model = request.data.get("gpt_model", "gpt-3.5-turbo").strip()
42+
session_id = (request.data.get("session_id") or generate_short_id()).strip()
43+
44+
language_detector = detect_languages_chain(gpt_model)
45+
languages = language_detector.run(prompt)
46+
47+
print(f"Language detector chain result: {languages}")
48+
49+
primary_language = languages["primary_detected_language"]
50+
english_translation_prompt = languages["translation_to_english"]
51+
52+
response = run_chat_chain(
53+
prompt=prompt,
54+
session_id=session_id,
55+
primary_language=primary_language,
56+
english_translation_prompt=english_translation_prompt,
57+
organization_id=organization.id,
58+
gpt_model=gpt_model,
59+
)
3160

32-
primary_language = languages["primary_detected_language"]
33-
english_translation_prompt = languages["translation_to_english"]
61+
print(f"Chat chain result: {response}")
3462

35-
response = run_chat_chain(
36-
prompt=prompt,
37-
session_id=session_id,
38-
primary_language=primary_language,
39-
english_translation_prompt=english_translation_prompt,
40-
)
63+
del response["source_documents"]
4164

42-
print(f"Chat chain result: {response}")
65+
return Response(
66+
{
67+
"answer": response["result"],
68+
"chat_history": response["chat_history"],
69+
"session_id": session_id,
70+
},
71+
status=status.HTTP_201_CREATED,
72+
)
73+
except Exception as error:
74+
print(f"Error: {error}")
75+
return Response(
76+
f"Something went wrong",
77+
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
78+
)
4379

44-
# Remove source documents from response since we have verbose logging setup
45-
del response["source_documents"]
4680

47-
return Response(
48-
{
49-
"answer": response["result"],
50-
"chat_history": response["chat_history"],
51-
"session_id": session_id,
52-
},
53-
status=status.HTTP_201_CREATED,
54-
)
81+
class FileUploadView(APIView):
82+
parser_classes = (MultiPartParser,)
83+
84+
def post(self, request, format=None):
85+
try:
86+
org = current_organization(request)
87+
if not org:
88+
return Response(
89+
f"Invalid API key",
90+
status=status.HTTP_404_NOT_FOUND,
91+
)
92+
93+
if "file" not in request.data:
94+
raise ValueError("Empty content")
95+
96+
file = request.data["file"]
97+
98+
pdf_reader = PdfReader(file)
99+
for page in pdf_reader.pages:
100+
page_text = page.extract_text().replace("\n", " ")
101+
102+
response = openai.Embedding.create(
103+
model="text-embedding-ada-002", input=page_text
104+
)
105+
106+
embeddings = response["data"][0]["embedding"]
107+
if len(embeddings) != 1536:
108+
raise ValueError(f"Invalid embedding length: #{len(embeddings)}")
109+
110+
# TODO: uncomment after move away from langchain to simplify code
111+
# Embedding.objects.create(
112+
# source_name=file.name,
113+
# original_text=page_text,
114+
# text_vectors=embeddings,
115+
# organization=org,
116+
# )
117+
118+
pgvector_idx = get_pgvector_idx()
119+
pgvector_idx.add_texts(
120+
texts=[page_text], metadatas=[{"organization_id": org.id}]
121+
)
122+
123+
return JsonResponse({"status": "file upload successful"})
124+
except ValueError as error:
125+
return Response(
126+
f"Invalid file: {error}",
127+
status=status.HTTP_400_BAD_REQUEST,
128+
)
129+
except Exception as error:
130+
print(f"Error: {error}")
131+
return Response(
132+
f"Something went wrong",
133+
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
134+
)
55135

56136

57137
@api_view(["POST"])
58-
def create_embeddings(_):
59-
chunks = loader.load_pdfs()
60-
embeddings.create_embeddings(chunks=chunks, index_name="embeddings")
61-
62-
return Response(
63-
f"Created embeddings index",
64-
status=status.HTTP_201_CREATED,
65-
)
138+
def set_system_prompt(request):
139+
try:
140+
system_prompt = request.data.get("system_prompt").strip()
141+
org = current_organization(request)
142+
if not org:
143+
return Response(
144+
f"Invalid API key",
145+
status=status.HTTP_404_NOT_FOUND,
146+
)
66147

148+
Organization.objects.filter(id=org.id).update(system_prompt=system_prompt)
67149

68-
@api_view(["POST"])
69-
def set_system_prompt(request):
70-
system_prompt = request.data.get("system_prompt").strip()
71-
org = current_org(request)
72-
if not org:
73150
return Response(
74-
f"Invalid API key",
75-
status=status.HTTP_404_NOT_FOUND,
151+
f"Updated System Prompt",
152+
status=status.HTTP_201_CREATED,
76153
)
77-
78-
try:
79-
Organization.objects.filter(id=org.id).update(system_prompt=system_prompt)
80154
except Exception as error:
81155
print(f"Error: {error}")
82156
return Response(
83157
f"Something went wrong",
84158
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
85159
)
86160

87-
return Response(
88-
f"Updated System Prompt",
89-
status=status.HTTP_201_CREATED,
90-
)
91-
92161

93-
def current_org(request):
162+
def current_organization(request):
94163
api_key = request.headers.get("Authorization")
95164
if not api_key:
96165
return None

0 commit comments

Comments
 (0)