Skip to content

Commit c2040e0

Browse files
authored
Merge pull request #81 from Tanzania-AI-Community/feature/tool-calling
Feature/tool calling
2 parents ccc7281 + c8ac324 commit c2040e0

File tree

12 files changed

+582
-407
lines changed

12 files changed

+582
-407
lines changed

.pre-commit-config.yaml

+27-27
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,27 @@
1-
# See https://pre-commit.com for more information
2-
# See https://pre-commit.com/hooks.html for more hooks
3-
fail_fast: true
4-
default_stages: [pre-commit]
5-
6-
repos:
7-
- repo: https://github.com/psf/black
8-
rev: 24.10.0
9-
hooks:
10-
- id: black
11-
args: [--config, pyproject.toml]
12-
types: [python]
13-
14-
- repo: https://github.com/charliermarsh/ruff-pre-commit
15-
rev: "v0.7.3"
16-
hooks:
17-
- id: ruff
18-
args: [--fix, --exit-non-zero-on-fix]
19-
20-
- repo: https://github.com/pre-commit/pre-commit-hooks
21-
rev: v5.0.0
22-
hooks:
23-
- id: check-toml
24-
- id: check-yaml
25-
- id: detect-private-key
26-
- id: end-of-file-fixer
27-
- id: trailing-whitespace
1+
# See https://pre-commit.com for more information
2+
# See https://pre-commit.com/hooks.html for more hooks
3+
fail_fast: true
4+
default_stages: [pre-commit]
5+
6+
repos:
7+
- repo: https://github.com/psf/black
8+
rev: 24.10.0
9+
hooks:
10+
- id: black
11+
args: [--config, pyproject.toml]
12+
types: [python]
13+
14+
- repo: https://github.com/charliermarsh/ruff-pre-commit
15+
rev: "v0.7.3"
16+
hooks:
17+
- id: ruff
18+
args: [--fix, --exit-non-zero-on-fix]
19+
20+
- repo: https://github.com/pre-commit/pre-commit-hooks
21+
rev: v5.0.0
22+
hooks:
23+
- id: check-toml
24+
- id: check-yaml
25+
- id: detect-private-key
26+
- id: end-of-file-fixer
27+
- id: trailing-whitespace
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,8 @@
1-
You are a skilled Tanzanian secondary school teacher that generates questions or exercises for Tanzanian Form 2 geography students based on the request made by the user. Use the provided context from the textbook to ensure that the questions you generate are grounded in the course content. Given the context information and not prior knowledge, follow the query instructions provided by the user. Don't generate questions if the query topic from the user is not related to the course content. Begin your response immediately with the question.
1+
You are a skilled Tanzanian secondary school teacher that generates questions or exercises for students based on the request made by the user. This exercise is for {class_info} students. Use the provided context from the textbook to ensure that the questions you generate are grounded in the course content. Take inspiration from the example exercises from the textbook if they are provided to you. Given the context information and not prior knowledge, follow the query instructions provided by the user. Don't generate questions if the query topic from the user is not related to the course content. Begin your response immediately with the question.
22

33
EXAMPLE INTERACTION:
44
user: Follow these instructions (give me short answer question on Tanzania's mining industry)
55
Context information is below.
6-
7-
START
8-
96
Tanzania has many minerals that it trades with to other countries...etc.
107

11-
END
12-
138
assistant: List three minerals that Tanzania exports.

app/assets/prompts/twiga_system

+13-8
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
1-
You are Twiga, a WhatsApp bot developed by the Tanzania AI Community specifically for secondary school teachers in Tanzania. Your role is to support teachers by providing accurate, curriculum-aligned educational assistance in a professional and supportive tone.
1+
You are Twiga, a WhatsApp bot developed by the Tanzania AI Community for secondary school teachers in Tanzania. The curriculum is the Tanzanian National Curriculum, developed by the Tanzanian Institute of Education (TIE). The students are assessed in NECTA examinations, which cover the curriculum. TIE are also the writers of the textbooks you use. Your role is to support the teachers by providing accurate, curriculum-aligned educational assistance. You are talking to {user_name} who teaches {class_info}.
22

3-
You are talking to {user_name} who teaches {class_info}
3+
Follow these instructions:
44

5-
Note that os2 refers to Ordinary Secondary Level 2, which is equivalent to Form 2 in the Tanzanian education system.
5+
- You only communicate in english
6+
- Use the tools you have available
7+
- Be clear and concise, since your messages are communicated on WhatsApp
8+
- Ask the teacher for additional information or clarification if its needed
9+
- Do not generate educational content if they are not provided by your tools
10+
- If the tool has an error or does not fulfill the user request, tell the user
11+
- Only help the teacher with subject related matter
612

7-
Follow these core guidelines:
13+
Here are your capabilities:
814

9-
Guidelines:
10-
Use Available Tools: For subject-related queries, refer to the tools available to you, unless the query is straightforward or involves general knowledge.
11-
Clarity and Conciseness: Ensure all responses are clear, concise, and easy to understand.
12-
Seek Clarification: If a query is unclear, kindly ask the user for additional details to provide a more accurate response.
15+
1. Searching the textbooks to answer course-related questions
16+
2. Generating example exercises or questions based on a specific course-related topic
17+
3. General tips and support

app/assets/strings/english.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ settings:
2020
class_subject_info: "Classes and Subjects"
2121

2222
tools:
23-
exercise_generator: "🏋️ Generating an exercise using the course literature, please hold..."
24-
search_knowledge: "🔍 Searching for relevant knowledge, please hold..."
23+
generate_exercise: "📑 Generating exercises from the course content, please hold..."
24+
search_knowledge: "📚 Searching the course content, please hold..."
2525

2626
flows:
2727
start_onboarding_header: "Start onboarding to Twiga 🦒"

app/database/db.py

+44
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,24 @@ async def get_or_create_user(wa_id: str, name: Optional[str] = None) -> User:
2626
"""
2727
Get existing user or create new one if they don't exist.
2828
Handles all database operations and error logging.
29+
This uses a lot of eager loading to get the class information from the user.
2930
"""
31+
# TODO: Remove eager loading if too slow
3032
async with get_session() as session:
3133
try:
3234
# First try to get existing user
3335
statement = select(User).where(User.wa_id == wa_id).with_for_update()
36+
# First try to get existing user
37+
statement = (
38+
select(User)
39+
.where(User.wa_id == wa_id)
40+
.options(
41+
selectinload(User.taught_classes) # type: ignore
42+
.selectinload(TeacherClass.class_) # type: ignore
43+
.selectinload(Class.subject_) # type: ignore
44+
)
45+
.with_for_update()
46+
)
3447
result = await session.execute(statement)
3548
user = result.scalar_one_or_none()
3649
if user:
@@ -181,6 +194,37 @@ async def vector_search(query: str, n_results: int, where: dict) -> List[Chunk]:
181194
raise Exception(f"Failed to search for knowledge: {str(e)}")
182195

183196

197+
async def get_class_resources(class_id: int) -> Optional[List[int]]:
198+
"""
199+
Get all resource IDs accessible to a class.
200+
Uses a single optimized SQL query with proper indexing.
201+
"""
202+
async with get_session() as session:
203+
try:
204+
# Use text() for a more efficient raw SQL query
205+
query = text(
206+
"""
207+
SELECT DISTINCT resource_id
208+
FROM classes_resources
209+
WHERE class_id = :class_id
210+
"""
211+
)
212+
213+
result = await session.execute(query, {"class_id": class_id})
214+
resource_ids = [row[0] for row in result.fetchall()]
215+
216+
if not resource_ids:
217+
logger.warning(f"No resources found for class {class_id}")
218+
return None
219+
220+
logger.debug(f"Found resources {resource_ids} for class {class_id}")
221+
return resource_ids
222+
223+
except Exception as e:
224+
logger.error(f"Failed to get resources for class {class_id}: {str(e)}")
225+
raise Exception(f"Failed to get class resources: {str(e)}")
226+
227+
184228
async def get_user_resources(user: User) -> Optional[List[int]]:
185229
"""
186230
Get all resource IDs accessible to a user through their class assignments.

app/database/models.py

+17-1
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,22 @@ def formatted_class_info(self) -> str:
114114

115115
return ClassInfo(classes=self.class_info).format_readable()
116116

117+
@property
118+
def class_name_to_id_map(self) -> Dict[str, int]:
119+
"""
120+
Returns a mapping of class names (with subject and form) to their IDs.
121+
Example: {"Geography Form 2": 123, "Geography Form 3": 456}
122+
"""
123+
124+
if not self.taught_classes:
125+
return {}
126+
127+
# TODO: This can be done in a better way without suppressing the type
128+
return {
129+
f"{class_.class_.subject_.name.capitalize()} {enums.GradeLevel(class_.class_.grade_level).display_format}": class_.class_.id
130+
for class_ in self.taught_classes
131+
} # type: ignore
132+
117133

118134
class Subject(SQLModel, table=True):
119135
__tablename__ = "subjects" # type: ignore
@@ -205,7 +221,7 @@ class Message(SQLModel, table=True):
205221

206222
def to_api_format(self) -> Dict[str, Any]:
207223
"""Convert message to OpenAI API format"""
208-
message: Dict[str, Any] = {"role": self.role}
224+
message: Dict[str, Any] = {"role": self.role.value}
209225
if self.tool_calls and len(self.tool_calls) > 0:
210226
message["tool_calls"] = self.tool_calls
211227
message["content"] = None

0 commit comments

Comments
 (0)