Sunil Surendra Singh commited on
Commit
c803eb6
β€’
1 Parent(s): 9b9203a

added gradio client code

Browse files
.gitattributes ADDED
@@ -0,0 +1 @@
 
 
1
+ *.png filter=lfs diff=lfs merge=lfs -text
.gitignore CHANGED
@@ -2,6 +2,7 @@
2
  .env
3
  notebook*/
4
  venv*/
 
5
  *.excalidraw
6
  .vscode*
7
  .vscode*/
 
2
  .env
3
  notebook*/
4
  venv*/
5
+ pyvenv.cfg
6
  *.excalidraw
7
  .vscode*
8
  .vscode*/
README.md CHANGED
@@ -2,7 +2,10 @@
2
  > Where Math Meets Marvel with AI Wizardry for Primary School Prowess! πŸ§™β™‚πŸ“šβœ¨
3
 
4
  ## Introduction
5
- GuruZee, your go-to guru for primary school math (for now). Picture this. GuruZee whips up question papers with answers and detailed explanations, all based on those elusive course chapter images. Not just that, it's a math-solving maestro, tackling problems presented as images with ease.
 
 
 
6
 
7
  ## Objectives
8
  1. Given just an image of primary school math's problem, GuruZee shall produce the
@@ -10,8 +13,22 @@ solution and clear explanation of the solution.
10
  2. Given the images of a chapter from primary school maths GuruZee will generate the
11
  question paper for various difficulty level based on the content's in the chapter.
12
 
13
- ## Proposed Architecture
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  [TODO]
15
 
16
- ## How to use
17
- [TODO]
 
2
  > Where Math Meets Marvel with AI Wizardry for Primary School Prowess! πŸ§™β™‚πŸ“šβœ¨
3
 
4
  ## Introduction
5
+ GuruZee, your go-to guru for primary school math (for now).
6
+ Picture this. GuruZee whips up question papers with answers and detailed explanations,
7
+ all based on those elusive course chapter images. Not just that, it's a math-solving
8
+ maestro, tackling problems presented as images with ease.
9
 
10
  ## Objectives
11
  1. Given just an image of primary school math's problem, GuruZee shall produce the
 
13
  2. Given the images of a chapter from primary school maths GuruZee will generate the
14
  question paper for various difficulty level based on the content's in the chapter.
15
 
16
+ ## How to use
17
+ Solve a problem
18
+ ---------------
19
+ - Select/upload an `Image` containing the problem Then hit `GO!` button. Alternatively,
20
+ just select one of the pre-configured `Example` image from Example section at the
21
+ bottom. The answer and explanation will appear on the right.
22
+
23
+ <img src="https://github.com/sssingh/guruzee/blob/main/assets/aap=eample.png?raw=true" width="1000" height="450"/><br><br>
24
+
25
+ Prepare questions and answers
26
+ -----------------------------
27
+ WIP (work in progress)
28
+ - Select/upload an `Image` containing the problem Then hit `GO!` button. Alternatively,
29
+ just select one the pre-configured `Example` image from Example section in the bottom.
30
+ The answer and explanation will appear on the right.
31
+
32
+ ## App Architecture
33
  [TODO]
34
 
 
 
assets/app-example.png ADDED

Git LFS Details

  • SHA256: 45476a0ddbb498770a95e84e8d3c00c6717a24ed8d701fb944739e6b9f8c6f87
  • Pointer size: 131 Bytes
  • Size of remote file: 322 kB
assets/examples/1.png ADDED

Git LFS Details

  • SHA256: de0caa2b422b1c3ff0c476256c0f1810c2bd53263f48731cc50dae3a227dde4f
  • Pointer size: 131 Bytes
  • Size of remote file: 447 kB
assets/examples/10.png ADDED

Git LFS Details

  • SHA256: 09db92a6e5c40a5df41028a60a9837a12364bb3f08d0325087642dffae10bea0
  • Pointer size: 131 Bytes
  • Size of remote file: 439 kB
assets/examples/2.png ADDED

Git LFS Details

  • SHA256: de026b18fb68115ad25eca6fbf140bd2b7ca7f3793d492d8a143bf75753113f0
  • Pointer size: 131 Bytes
  • Size of remote file: 527 kB
assets/examples/3.png ADDED

Git LFS Details

  • SHA256: f23dfdac9574d05b961d6d1b7ecc36602ce5c172a1bdabebf7cc95d3cf01c6f2
  • Pointer size: 131 Bytes
  • Size of remote file: 394 kB
assets/examples/4.png ADDED

Git LFS Details

  • SHA256: d170db868c53fbe51796bf25644ba40ea3f40198e58b76c0bd9c1ed4c6516f00
  • Pointer size: 131 Bytes
  • Size of remote file: 113 kB
assets/examples/5.png ADDED

Git LFS Details

  • SHA256: 3b7522d6eb0d9848ad05b7f0e4e8f9895c21952f71fa596500d2629882aaf9b3
  • Pointer size: 131 Bytes
  • Size of remote file: 215 kB
assets/examples/6.png ADDED

Git LFS Details

  • SHA256: ecc94a4f47fbcdd1e8de1b85b53fdbfcf15c3d6da380350f763b592a0835ed2b
  • Pointer size: 131 Bytes
  • Size of remote file: 129 kB
assets/examples/7.png ADDED

Git LFS Details

  • SHA256: 904c72ba045c0cdf5bbe98e316772679fe086bab3bf54853e1c05dd0f137c994
  • Pointer size: 131 Bytes
  • Size of remote file: 118 kB
assets/examples/8.png ADDED

Git LFS Details

  • SHA256: 287c4d817d254d7d590fe2a2441fe5be60987a514d23108d7e6f329c27e01baa
  • Pointer size: 130 Bytes
  • Size of remote file: 88.8 kB
assets/examples/9.png ADDED

Git LFS Details

  • SHA256: 19e2e650f849a3811fe21c37047a8988e1b7adbc631c9eaa9d42e05eb95cc7e2
  • Pointer size: 131 Bytes
  • Size of remote file: 417 kB
requirements.txt CHANGED
@@ -1,2 +1,6 @@
1
  fastapi
2
- requests
 
 
 
 
 
1
  fastapi
2
+ uvicorn
3
+ requests
4
+ pymongo[tls, srv]==4.4.*
5
+ gradio==3.45.*
6
+ python-dotenv
src/app_config.py CHANGED
@@ -1,10 +1,40 @@
 
 
1
  from dataclasses import dataclass
2
 
 
 
 
3
 
4
  @dataclass
5
  class __AppConfig:
6
- openai_api_endpoint = "https://api.openai.com/v1/chat/completions"
7
- openai_api_key = ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
 
9
 
10
  config = __AppConfig()
 
1
+ import os
2
+ from dotenv import load_dotenv
3
  from dataclasses import dataclass
4
 
5
+ # load environment variables from .env (only available in dev environment)
6
+ load_dotenv()
7
+
8
 
9
  @dataclass
10
  class __AppConfig:
11
+ solver_persona = """You an expert primary school maths teacher. Given an
12
+ image of a primary school maths problem you can analyze the problem and
13
+ produce a detailed solution with explanation."""
14
+ teacher_persona = (
15
+ solver_persona
16
+ + """ \nIn addition to this, given a
17
+ collection images from primary school maths text book you can generate
18
+ questions and there answers based on the topic shown in text book images,
19
+ you will provide the detailed answers with explanation"""
20
+ )
21
+ solver_instruction = """Analyze the primary school math problem in the the image.
22
+ Strictly first provide the answer to the problem and then only the solution
23
+ explanation. Put a newline after each 80 chars"""
24
+ openai_max_access_count = 200
25
+ openai_curr_access_count = None
26
+ mongo_client = None
27
+ db = "mydb"
28
+ collection = "guruzee-openai-access-counter"
29
+ key = "current_count"
30
+ OPENAI_API_ENDPOINT = os.getenv("OPENAI_API_ENDPOINT")
31
+ GURUZEE_API_ENDPOINT = os.getenv("GURUZEE_API_ENDPOINT")
32
+ OPENAI_KEY = os.getenv("OPENAI_KEY")
33
+ HF_TOKEN = os.getenv("HF_TOKEN")
34
+ MONGO_CONN_STR = os.getenv("MONGO_CONN_STR")
35
+ title = "GuruZee, your go-to guru for primary school math"
36
+ theme = "freddyaboulton/dracula_revamped"
37
+ css = "style.css"
38
 
39
 
40
  config = __AppConfig()
src/client.py CHANGED
@@ -1 +1,167 @@
1
- ## Test code for testing the server API goes here
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import base64
2
+ import requests
3
+ from app_config import config
4
+ import gradio as gr
5
+ import mongo_utils as mongo
6
+
7
+
8
+ def clear():
9
+ return None, ""
10
+
11
+
12
+ # Function to encode the local image into base64 to be send over HTTP
13
+ def __encode_image(image_path: str) -> bytes:
14
+ """
15
+ Encodes the passed image to base64 byte-stream.
16
+ This is required for passing the image over a HTTP get/post call.
17
+ """
18
+ with open(image_path, "rb") as image_file:
19
+ base64_image = base64.b64encode(image_file.read()).decode("utf-8")
20
+ return base64_image
21
+
22
+
23
+ def solve(image_path: str):
24
+ """
25
+ Invokes the GuruZee API passing the raw image bytes and returns the response
26
+ to client
27
+ """
28
+ print(image_path)
29
+ image_data = __encode_image(image_path=image_path)
30
+ headers = {"Content-Type": "application/json"}
31
+ payload = {"data": image_data}
32
+ response = requests.post(config.GURUZEE_API_ENDPOINT, headers=headers, json=payload)
33
+ return response.json()
34
+
35
+
36
+ def create_interface():
37
+ js_enable_darkmode = """() =>
38
+ {
39
+ document.querySelector('body').classList.add('dark');
40
+ }"""
41
+ js_toggle_darkmode = """() =>
42
+ {
43
+ if (document.querySelectorAll('.dark').length) {
44
+ document.querySelector('body').classList.remove('dark');
45
+ } else {
46
+ document.querySelector('body').classList.add('dark');
47
+ }
48
+ }"""
49
+
50
+ with gr.Blocks(title=config.title, theme=config.theme, css=config.css) as app:
51
+ # enable darkmode
52
+ app.load(fn=None, inputs=None, outputs=None, _js=js_enable_darkmode)
53
+ with gr.Row():
54
+ darkmode_checkbox = gr.Checkbox(
55
+ label="Dark Mode", value=True, interactive=True
56
+ )
57
+ # toggle darkmode on/off when checkbox is checked/unchecked
58
+ darkmode_checkbox.change(
59
+ None, None, None, _js=js_toggle_darkmode, api_name=False
60
+ )
61
+ with gr.Row():
62
+ with gr.Column():
63
+ gr.Markdown(
64
+ """
65
+ # GuruZee
66
+ ***Where Math Meets Marvel with AI Wizardry for Primary School
67
+ Prowess! πŸ§™β™‚πŸ“šβœ¨***
68
+ **GuruZee whips up question papers with answers and detailed
69
+ explanations, all based on those elusive course chapter images.
70
+ Not just that, it's a math-solving maestro, tackling problems
71
+ presented as images with ease..
72
+ <br>
73
+ Select/upload an `Image` containing the problem Then hit
74
+ `GO!` button.
75
+ Alternatively, just select one the pre-configured `Example` image
76
+ from Example section in the bottom**
77
+ <br>
78
+ Visit the [project's repo](https://github.com/sssingh/GuruZee)
79
+ <br>
80
+ ***Please exercise patience, as the models employed are extensive
81
+ and may require a few seconds to load. If you encounter an unrelated
82
+ response, it is likely still loading; wait a moment and try again.***
83
+ """
84
+ )
85
+ with gr.Column():
86
+ max_count = gr.Textbox(
87
+ label="Max allowed OpenAI requests:",
88
+ value=config.openai_max_access_count,
89
+ )
90
+ curr_count = gr.Textbox(
91
+ label="Used up OpenAI requests:",
92
+ value=config.openai_curr_access_count,
93
+ )
94
+ available_count = gr.Textbox(
95
+ label="Available OpenAI requests:",
96
+ value=config.openai_max_access_count
97
+ - config.openai_curr_access_count,
98
+ )
99
+ with gr.Row():
100
+ with gr.Column():
101
+ image = gr.Image(
102
+ type="filepath",
103
+ )
104
+ with gr.Row():
105
+ submit_button = gr.Button(value="GO!", elem_classes="orange-button")
106
+ clear_button = gr.ClearButton(elem_classes="gray-button")
107
+ with gr.Column():
108
+ answer = gr.Textbox(
109
+ label="Answer:",
110
+ placeholder="Answer will appear here.",
111
+ lines=20,
112
+ )
113
+ with gr.Row():
114
+ with gr.Accordion("Expand for examples:", open=False):
115
+ gr.Examples(
116
+ examples=[
117
+ [
118
+ "assets/examples/1.png",
119
+ ],
120
+ [
121
+ "assets/examples/2.png",
122
+ ],
123
+ [
124
+ "assets/examples/3.png",
125
+ ],
126
+ [
127
+ "assets/examples/4.png",
128
+ ],
129
+ [
130
+ "assets/examples/5.png",
131
+ ],
132
+ [
133
+ "assets/examples/6.png",
134
+ ],
135
+ [
136
+ "assets/examples/7.png",
137
+ ],
138
+ [
139
+ "assets/examples/8.png",
140
+ ],
141
+ [
142
+ "assets/examples/9.png",
143
+ ],
144
+ [
145
+ "assets/examples/10.png",
146
+ ],
147
+ ],
148
+ fn=solve,
149
+ inputs=[image],
150
+ outputs=[answer],
151
+ run_on_click=True,
152
+ )
153
+ submit_button.click(
154
+ fn=solve,
155
+ inputs=[image],
156
+ outputs=[answer],
157
+ )
158
+ clear_button.click(fn=clear, inputs=[], outputs=[image, answer])
159
+ image.clear(fn=clear, inputs=[], outputs=[image, answer])
160
+
161
+ return app
162
+
163
+
164
+ if __name__ == "__main__":
165
+ mongo.fetch_curr_access_count()
166
+ app = create_interface()
167
+ app.launch()
src/mongo_utils.py ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pymongo
2
+ from app_config import config
3
+
4
+
5
+ # Generic functions
6
+ def get_db_client():
7
+ """Returns MongoDB client object, connect to MongoDB Atlas instance if required"""
8
+ try:
9
+ if config.mongo_client == None:
10
+ client = pymongo.MongoClient(config.MONGO_CONN_STR)
11
+ config.mongo_client = client
12
+ except Exception as e:
13
+ print(e)
14
+ return config.mongo_client
15
+
16
+
17
+ def fetch_document(client, db, collection):
18
+ """Get a single document from the provided db and collection"""
19
+ try:
20
+ document = client[db][collection].find_one()
21
+ except Exception as e:
22
+ print(e)
23
+ return document
24
+
25
+
26
+ def update_document(client, db, collection, key, value):
27
+ """Update the passed key in the document for provided db and collection"""
28
+ try:
29
+ document = fetch_document(client, db, collection)
30
+ client[db][collection].update_one(
31
+ {"_id": document["_id"]},
32
+ {"$set": {key: value}},
33
+ )
34
+ except Exception as e:
35
+ print(e)
36
+
37
+
38
+ # Use case specific functions
39
+ def fetch_curr_access_count():
40
+ client = get_db_client()
41
+ curr_count = fetch_document(
42
+ client=client, db=config.db, collection=config.collection
43
+ )[config.key]
44
+ config.openai_curr_access_count = curr_count
45
+
46
+
47
+ def increment_curr_access_count():
48
+ client = get_db_client()
49
+ updated_count = config.openai_curr_access_count + 1
50
+ update_document(
51
+ client=client,
52
+ db=config.db,
53
+ collection=config.collection,
54
+ key=config.key,
55
+ value=updated_count,
56
+ )
57
+ config.openai_curr_access_count = updated_count
src/server.py CHANGED
@@ -1,43 +1,37 @@
1
- import base64
2
  import requests
3
  from app_config import config
 
 
4
 
 
5
 
6
- # Function to encode the local image into base64 to be send over HTTP
7
- def local_image_to_url(image_path):
8
- with open(image_path, "rb") as image_file:
9
- base64_image = base64.b64encode(image_file.read()).decode("utf-8")
10
- return {"url": f"data:image/jpeg;base64,{base64_image}"}
11
 
 
 
12
 
13
- def analyze_single_image(image_path: str, instruction: str, mode="url"): # "local"
14
- if mode == "local":
15
- image_path = local_image_to_url(image_path)
 
 
 
 
16
  headers = {
17
  "Content-Type": "application/json",
18
- "Authorization": f"Bearer {config.openai_api_key}",
19
  }
20
  payload = {
21
  "model": "gpt-4-vision-preview",
22
- "temperature": 0.2,
23
  "messages": [
24
- {
25
- "role": "system",
26
- "contents": """You an expert primary school maths teacher. Given an image
27
- of a primary school maths problem you can analyze the problem and produce
28
- a detailed solution with explanation."""
29
- # In addition to this, given a
30
- # collection images from primary school maths text book you can generate
31
- # questions and there answers based on the topic shown in text book images,
32
- # you will provide the detailed answers with explanation""",
33
- },
34
  {
35
  "role": "user",
36
  "content": [
37
- {"type": "text", "text": instruction},
38
  {
39
  "type": "image_url",
40
- "image_url": image_path,
41
  },
42
  ],
43
  },
@@ -45,16 +39,15 @@ def analyze_single_image(image_path: str, instruction: str, mode="url"): # "loc
45
  "max_tokens": 600,
46
  }
47
 
48
- response = requests.post(config.openai_api_endpoint, headers=headers, json=payload)
49
  return response.json()
50
 
51
 
52
- def solve(problem_image: str, mode="local"):
53
- print("P R O B L E M:\n--------------")
54
- display(Image(filename=problem_image)) # only in notebook
55
- instruction = """Analyze the 4th grade math problem in the image. Strictly first provide the answer to the problem and then only the solution explanation.
56
- Put a newline after each 80 chars"""
57
- output = analyze_image(problem_image, instruction, mode)
58
- print("A N S W E R:\n------------")
59
- print(output["choices"][0]["message"]["content"])
60
  return output["choices"][0]["message"]["content"]
 
 
1
  import requests
2
  from app_config import config
3
+ from fastapi import FastAPI
4
+ from pydantic import BaseModel
5
 
6
+ guruzee = FastAPI()
7
 
 
 
 
 
 
8
 
9
+ class SingleImageData(BaseModel):
10
+ data: str
11
 
12
+
13
+ def __analyze_single_image(image_data: str):
14
+ """
15
+ Sends the user supplied image with system instructions to GPT-4, returns the
16
+ received response in JSON format.
17
+ """
18
+ image_data = {"url": f"data:image/jpeg;base64,{image_data}"}
19
  headers = {
20
  "Content-Type": "application/json",
21
+ "Authorization": f"Bearer {config.OPENAI_KEY}",
22
  }
23
  payload = {
24
  "model": "gpt-4-vision-preview",
25
+ "temperature": 0.2, # low temperature because we want deterministic responses
26
  "messages": [
27
+ {"role": "system", "content": config.solver_persona},
 
 
 
 
 
 
 
 
 
28
  {
29
  "role": "user",
30
  "content": [
31
+ {"type": "text", "text": config.solver_instruction},
32
  {
33
  "type": "image_url",
34
+ "image_url": image_data,
35
  },
36
  ],
37
  },
 
39
  "max_tokens": 600,
40
  }
41
 
42
+ response = requests.post(config.OPENAI_API_ENDPOINT, headers=headers, json=payload)
43
  return response.json()
44
 
45
 
46
+ @guruzee.post("/solve")
47
+ async def solve(image: SingleImageData):
48
+ """
49
+ Invokes the OpenAI API passing the raw image bytes and returns the
50
+ response to client
51
+ """
52
+ output = __analyze_single_image(image.data)
 
53
  return output["choices"][0]["message"]["content"]
src/style.css ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .orange-button {
2
+ background-color: orange !important;
3
+ }
4
+
5
+ .gray-button {
6
+ background-color: #5e6061 !important;
7
+ }
8
+
9
+ footer {
10
+ visibility: hidden;
11
+ }