FredyHoundayi commited on
Commit
b43c456
·
0 Parent(s):

fastapi hf v0

Browse files
.gitignore ADDED
@@ -0,0 +1,271 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py,cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ #Pipfile.lock
96
+
97
+ # poetry
98
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
102
+ #poetry.lock
103
+
104
+ # pdm
105
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
106
+ #pdm.lock
107
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
108
+ # in version control.
109
+ # https://pdm.fming.dev/#use-with-ide
110
+ .pdm.toml
111
+
112
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
113
+ __pypackages__/
114
+
115
+ # Celery stuff
116
+ celerybeat-schedule
117
+ celerybeat.pid
118
+
119
+ # SageMath parsed files
120
+ *.sage.py
121
+
122
+ # Environments
123
+ .env
124
+ .venv
125
+ env/
126
+ venv/
127
+ ENV/
128
+ env.bak/
129
+ venv.bak/
130
+
131
+ # Spyder project settings
132
+ .spyderproject
133
+ .spyproject
134
+
135
+ # Rope project settings
136
+ .ropeproject
137
+
138
+ # mkdocs documentation
139
+ /site
140
+
141
+ # mypy
142
+ .mypy_cache/
143
+ .dmypy.json
144
+ dmypy.json
145
+
146
+ # Pyre type checker
147
+ .pyre/
148
+
149
+ # pytype static type analyzer
150
+ .pytype/
151
+
152
+ # Cython debug symbols
153
+ cython_debug/
154
+
155
+ # PyCharm
156
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
157
+ # be added to the global gitignore or merged into this project gitignore. For a PyCharm
158
+ # project, it is recommended to include the following files:
159
+ # .idea/
160
+ # *.iml
161
+ # *.ipr
162
+ # *.iws
163
+ .idea/
164
+ *.iml
165
+ *.ipr
166
+ *.iws
167
+
168
+ # VS Code
169
+ .vscode/
170
+ !.vscode/settings.json
171
+ !.vscode/tasks.json
172
+ !.vscode/launch.json
173
+ !.vscode/extensions.json
174
+ !.vscode/*.code-snippets
175
+
176
+ # Local History for Visual Studio Code
177
+ .history/
178
+
179
+ # Built Visual Studio Code Extensions
180
+ *.vsix
181
+
182
+ # Audio files generated by the app
183
+ app/static/audio/*.mp3
184
+ app/static/audio/*.wav
185
+ app/static/audio/*.ogg
186
+
187
+ # Database files
188
+ *.db
189
+ *.sqlite
190
+ *.sqlite3
191
+
192
+ # Logs
193
+ *.log
194
+ logs/
195
+
196
+ # OS generated files
197
+ .DS_Store
198
+ .DS_Store?
199
+ ._*
200
+ .Spotlight-V100
201
+ .Trashes
202
+ ehthumbs.db
203
+ Thumbs.db
204
+
205
+ # Temporary files
206
+ *.tmp
207
+ *.temp
208
+ *~
209
+
210
+ # Node modules (if you add a frontend later)
211
+ node_modules/
212
+ npm-debug.log*
213
+ yarn-debug.log*
214
+ yarn-error.log*
215
+
216
+ # Package files
217
+ *.tgz
218
+ *.tar.gz
219
+
220
+ # Runtime data
221
+ pids
222
+ *.pid
223
+ *.seed
224
+ *.pid.lock
225
+
226
+ # Coverage directory used by tools like istanbul
227
+ coverage/
228
+ *.lcov
229
+
230
+ # nyc test coverage
231
+ .nyc_output
232
+
233
+ # Dependency directories
234
+ jspm_packages/
235
+
236
+ # Optional npm cache directory
237
+ .npm
238
+
239
+ # Optional eslint cache
240
+ .eslintcache
241
+
242
+ # Output of 'npm pack'
243
+ *.tgz
244
+
245
+ # Yarn Integrity file
246
+ .yarn-integrity
247
+
248
+ # dotenv environment variables file
249
+ .env.local
250
+ .env.development.local
251
+ .env.test.local
252
+ .env.production.local
253
+
254
+ # Next.js build output
255
+ .next
256
+
257
+ # Nuxt.js build / generate output
258
+ .nuxt
259
+ dist
260
+
261
+ # Gatsby files
262
+ .cache/
263
+ public
264
+
265
+ # Storybook build outputs
266
+ .out
267
+ .storybook-out
268
+
269
+ # Temporary folders
270
+ tmp/
271
+ temp/
Dockerfile ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use Python 3.12 slim image
2
+ FROM python:3.12-slim
3
+
4
+ # Set working directory0.0.0", "--port", "8000"]
5
+
6
+ WORKDIR /app
7
+
8
+ # Set environment variables
9
+ ENV PYTHONDONTWRITEBYTECODE=1
10
+ ENV PYTHONUNBUFFERED=1
11
+
12
+ # Install system dependencies
13
+ RUN apt-get update \
14
+ && apt-get install -y --no-install-recommends \
15
+ gcc \
16
+ g++ \
17
+ ffmpeg \
18
+ curl \
19
+ && rm -rf /var/lib/apt/lists/*
20
+
21
+ # Copy requirements first for better caching
22
+ COPY requirements.txt .
23
+
24
+ # Install Python dependencies
25
+ RUN pip install --no-cache-dir --upgrade pip \
26
+ && pip install --no-cache-dir -r requirements.txt
27
+
28
+ # Copy application code
29
+ COPY . .
30
+
31
+ # Create static audio directory
32
+ RUN mkdir -p app/static/audio
33
+
34
+ # Create non-root user for security
35
+ RUN adduser --disabled-password --gecos '' appuser \
36
+ && chown -R appuser:appuser /app
37
+ USER appuser
38
+
39
+ # Expose port
40
+ EXPOSE 8000
41
+
42
+ # Run the application
43
+ CMD ["uvicorn", "app.main:app", "--host", "0.
app/__init__.py ADDED
File without changes
app/core/__init__.py ADDED
File without changes
app/core/config.py ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic_settings import BaseSettings
2
+
3
+
4
+ class Settings(BaseSettings):
5
+ GROQ_API_KEY: str
6
+ DATABASE_URL: str = "sqlite:///./brain_platform.db"
7
+
8
+ class Config:
9
+ env_file = ".env"
10
+ extra = "ignore"
11
+
12
+
13
+ settings = Settings()
app/core/llm.py ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ from langchain_groq import ChatGroq
2
+ from app.core.config import settings
3
+
4
+ llm = ChatGroq(
5
+ model="llama-3.1-8b-instant",
6
+ temperature=0.3,
7
+ api_key=settings.GROQ_API_KEY
8
+ )
app/main.py ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI
2
+ from fastapi.staticfiles import StaticFiles
3
+
4
+ from app.routers import chat, quiz, audio
5
+
6
+ app = FastAPI(title="Brain API MVP")
7
+
8
+ app.mount("/static", StaticFiles(directory="app/static"), name="static")
9
+
10
+ app.include_router(chat.router)
11
+ app.include_router(quiz.router)
12
+ app.include_router(audio.router)
app/prompts/__init__.py ADDED
File without changes
app/prompts/course_prompt.py ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ COURSE_TEMPLATE = """
2
+ You are a friendly AI tutor.
3
+
4
+ Generate a {length} course about {topic} for the {sector} sector.
5
+
6
+ Tone: {tone}
7
+ Style: {style}
8
+
9
+ Rules:
10
+ - Use simple explanations
11
+ - Avoid jargon if requested
12
+ - Give practical examples
13
+ - Keep it clear and beginner friendly
14
+
15
+ Return only the course content.
16
+ """
app/prompts/quiz_prompt.py ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ QUIZ_TEMPLATE = """
2
+ Based on this course, generate 5 MCQ questions.
3
+
4
+ Course:
5
+ {course}
6
+
7
+ Return STRICT JSON:
8
+
9
+ [
10
+ {{
11
+ "question": "...",
12
+ "options": ["A","B","C","D"],
13
+ "answer": 0
14
+ }}
15
+ ]
16
+ """
app/routers/__init__.py ADDED
File without changes
app/routers/audio.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, HTTPException
2
+ from pydantic import BaseModel, Field
3
+ from app.services.audio_service import text_to_audio
4
+
5
+ router = APIRouter(prefix="/audio", tags=["Audio"])
6
+
7
+
8
+ class AudioRequest(BaseModel):
9
+ text: str = Field(..., description="Text to convert to audio")
10
+
11
+
12
+ @router.post("/generate")
13
+ def generate_audio(req: AudioRequest):
14
+ try:
15
+ audio_url = text_to_audio(req.text)
16
+ return {
17
+ "audio_url": audio_url,
18
+ "message": "Audio generated successfully"
19
+ }
20
+ except Exception as e:
21
+ raise HTTPException(status_code=500, detail=f"Audio generation failed: {str(e)}")
app/routers/chat.py ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter
2
+ from app.schemas.chat import LearnRequest
3
+ from app.services.learning_agent import generate_course_and_quiz
4
+ from app.services.audio_service import text_to_audio
5
+
6
+ router = APIRouter(prefix="/chat", tags=["Learning"])
7
+
8
+
9
+ @router.post("/learn")
10
+ def learn(req: LearnRequest):
11
+
12
+ course, quiz = generate_course_and_quiz(req.dict())
13
+
14
+ audio_url = text_to_audio(course)
15
+
16
+ return {
17
+ "course": course,
18
+ "quiz": quiz,
19
+ "audio_url": audio_url
20
+ }
app/routers/quiz.py ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter
2
+ from app.schemas.quiz import QuizEvaluation
3
+ from app.services.quiz_service import evaluate_quiz
4
+
5
+ router = APIRouter(prefix="/quiz", tags=["Quiz"])
6
+
7
+
8
+ @router.post("/evaluate")
9
+ def evaluate(req: QuizEvaluation):
10
+
11
+ score, feedback = evaluate_quiz(req.quiz, req.answers)
12
+
13
+ return {
14
+ "score": score,
15
+ "feedback": feedback
16
+ }
app/schemas/__init__.py ADDED
File without changes
app/schemas/chat.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel, Field
2
+
3
+
4
+ class LearnRequest(BaseModel):
5
+ topic: str = Field(..., description="The topic to learn about")
6
+ sector: str = Field(..., description="The target sector (e.g., finance, tech, healthcare)")
7
+ tone: str = Field(..., description="The tone of the content (e.g., friendly, formal, casual)")
8
+ style: str = Field(..., description="The style of explanation (e.g., simple, detailed, technical)")
9
+ length: str = Field(..., description="Length of the course (short or full)")
app/schemas/quiz.py ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel
2
+ from typing import List, Dict
3
+
4
+
5
+ class QuizEvaluation(BaseModel):
6
+ quiz: List[Dict]
7
+ answers: List[int]
app/services/__init__.py ADDED
File without changes
app/services/audio_service.py ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from gtts import gTTS
2
+ import uuid
3
+ import os
4
+
5
+ AUDIO_FOLDER = "app/static/audio"
6
+
7
+
8
+ def text_to_audio(text: str):
9
+
10
+ filename = f"{uuid.uuid4()}.mp3"
11
+ filepath = os.path.join(AUDIO_FOLDER, filename)
12
+
13
+ tts = gTTS(text=text, lang="en")
14
+ tts.save(filepath)
15
+
16
+ return f"/static/audio/{filename}"
app/services/learning_agent.py ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langchain_core.prompts import PromptTemplate
2
+ from app.core.llm import llm
3
+ from app.prompts.course_prompt import COURSE_TEMPLATE
4
+ from app.prompts.quiz_prompt import QUIZ_TEMPLATE
5
+ import json
6
+ import re
7
+
8
+
9
+ def generate_course_and_quiz(params):
10
+
11
+ course_prompt = PromptTemplate.from_template(COURSE_TEMPLATE)
12
+
13
+ course = llm.invoke(
14
+ course_prompt.format(**params)
15
+ ).content
16
+
17
+ quiz_prompt = PromptTemplate.from_template(QUIZ_TEMPLATE)
18
+
19
+ quiz_raw = llm.invoke(
20
+ quiz_prompt.format(course=course)
21
+ ).content
22
+
23
+ # Extract JSON from response
24
+ json_match = re.search(r'\[.*\]', quiz_raw, re.DOTALL)
25
+ if json_match:
26
+ quiz_json = json_match.group(0)
27
+ quiz = json.loads(quiz_json)
28
+ else:
29
+ # Fallback quiz if JSON parsing fails
30
+ quiz = [
31
+ {
32
+ "question": "What is the main topic of this course?",
33
+ "options": ["Option A", "Option B", "Option C", "Option D"],
34
+ "answer": 0
35
+ }
36
+ ]
37
+
38
+ return course, quiz
app/services/quiz_service.py ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ def evaluate_quiz(quiz, answers):
2
+
3
+ score = 0
4
+ feedback = []
5
+
6
+ for q, a in zip(quiz, answers):
7
+ # Handle both 'answer' and 'correct_answer' keys
8
+ correct_answer = q.get("answer") or q.get("correct_answer", 0)
9
+
10
+ if a == correct_answer:
11
+ score += 1
12
+ else:
13
+ feedback.append(f"Review this question: {q.get('question', 'Unknown question')}")
14
+
15
+ if len(quiz) > 0:
16
+ percent = int(score / len(quiz) * 100)
17
+ else:
18
+ percent = 0
19
+
20
+ return percent, feedback
requirements.txt ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ fastapi==0.104.1
2
+ uvicorn[standard]==0.24.0
3
+ python-dotenv==1.0.0
4
+ pydantic==2.5.0
5
+ sqlalchemy==2.0.23
6
+ asyncpg==0.29.0
7
+ alembic==1.13.0
8
+ redis==5.0.1
9
+ openai==1.3.7
10
+ langchain==0.0.350
11
+ langchain-groq==0.0.1
12
+ langchain-community==0.4.1
13
+ crewai==0.1.4
14
+ crewai-tools==0.2.0
15
+ python-jose[cryptography]==3.3.0
16
+ passlib[bcrypt]==1.7.4
17
+ python-multipart==0.0.6
18
+ httpx==0.25.2
19
+ pandas==2.1.4
20
+ numpy==1.25.2
21
+ pytest==7.4.3
22
+ pytest-asyncio==0.21.1
23
+ gtts==2.4.0