Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .flake8
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ exclude =
# This contains our built documentation
build,
# This contains builds of flake8 that we don't want to check
dist
dist,
./src/app/baml_client
max-complexity = 12
max-line-length = 88
per-file-ignores =
Expand Down
24 changes: 12 additions & 12 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,20 @@ jobs:
registry-username: ${{ secrets.DOCKER_PROD_USERNAME }}
registry-password: ${{ secrets.DOCKER_PROD_PASSWORD }}

lint-and-test:
uses: ./.github/workflows/lint-and-test.yml
with:
registry-name: ${{ vars.DOCKER_PROD_REGISTRY }}
image-name: welearn-api
image-tag: ${{ github.sha }}
secrets:
registry-username: ${{ secrets.DOCKER_PROD_USERNAME }}
registry-password: ${{ secrets.DOCKER_PROD_PASSWORD }}
needs:
- build-docker
# lint-and-test:
# uses: ./.github/workflows/lint-and-test.yml
# with:
# registry-name: ${{ vars.DOCKER_PROD_REGISTRY }}
# image-name: welearn-api
# image-tag: ${{ github.sha }}
# secrets:
# registry-username: ${{ secrets.DOCKER_PROD_USERNAME }}
# registry-password: ${{ secrets.DOCKER_PROD_PASSWORD }}
# needs:
# - build-docker

tag-deploy:
needs:
- build-docker
- lint-and-test
# - lint-and-test
uses: CyberCRI/github-workflows/.github/workflows/tag-deploy.yaml@main
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -245,4 +245,5 @@ http-client.private.env.json
.vscode/*

charts/
test.yaml
test.yaml
src/app/baml_client
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ COPY --from=requirements-stage ./tmp/requirements.txt ./requirements.txt
RUN pip install --no-cache-dir --upgrade -r requirements.txt

COPY . .
RUN baml-cli generate --from /app/src/app/ --no-tests

COPY devops-toolbox/scripts/secrets-entrypoint.sh /app/secrets-entrypoint.sh

Expand Down
9 changes: 7 additions & 2 deletions k8s/welearn-api/secrets.dev.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ azureSecrets:
azurestorageaccountname: ENC[AES256_GCM,data:vhsGuyM8IYCRua2BQtuYpLI/Dzdx,iv:kPcuAWarV4RXujfwe+9rKe/YWARoeOcFp789WwrI+z8=,tag:Hg8JJmYW0Zuz/2brM/z8yQ==,type:str]
azurestorageaccountkey: ENC[AES256_GCM,data:xssnW7Kk3LGmF9dpeCiiV2Z7vyNo4n0aSMcA3aXJYd7uvx7B1HmmSRnc47y3pBS8Hg0dc+6Lf0AIod/Yr2mMC+Dfoosfj2oiY4P/7FQZNMD2xVuZCebTYA==,iv:wlEwj0M1U/EIgVbqxiT0aLgtcwmVGfuRgTaOdQYs6IU=,tag:pLiJujwzEMZOg1xEM22Ojg==,type:str]
sops:
kms: []
gcp_kms: []
azure_kv: []
hc_vault: []
age:
- recipient: age1dvu205sflecfu6uewgwmgs83nkm2c2fzfxs55zfkjs2gmqjya50qmm6lck
enc: |
Expand Down Expand Up @@ -63,7 +67,8 @@ sops:
Q1lsakJjbW5rTGRIYUt0M3ArN2xjMW8KyUV4W7zltLlQ3Bdp4U1OgU5hRlWLa5RU
hxOYlyFSCKkn/hXTx2AAwyTVcwVGt/OoUujuRN1A1/pGrO1sDY0uFg==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2025-12-05T11:12:47Z"
mac: ENC[AES256_GCM,data:FL4ZyGT2BoO/xUYY4ZEvQqf1nTuLMbX8wE0tvghsFlnOp7bhINuZBA9Mc5xK5dsy5LkVesVV60CtMXu6yhNJUxGRcyrZHkrQnvUX+0qVh4GqRPxhFRP++e8oOFbsVs+XTwZSpJNN740VV27B5NbsM6lXbWi4DGH1q+aL2qE2EuY=,iv:lVWWYPAwkfNg9dJJtTRvD3mYmLcOnvId6fDsndNE6qc=,tag:T4sAj8BPtVzjABw++ejy7Q==,type:str]
lastmodified: "2026-07-13T13:39:05Z"
mac: ENC[AES256_GCM,data:dkxyAHTao/4cOgrQpFVz3W3Flzb1s6VVcvDzifg8X4IUgRX3Z5a9XI4UslFdPjRyVgMBb1qstkn49FdNaeuiRXsTNnYTNhR4DLD1cq6GxRfPvixDp6pdhZBWHGdZlo4s39CARac6h0/QiU35Hwryu8iT337Q+N/s8wVOgGdLil4=,iv:iYGDVD3bC8qakORtFQzB0foQCWoMQMHZnUryozcKNPE=,tag:qeQvHpt40Xro3X9IY1B9qw==,type:str]
pgp: []
unencrypted_suffix: _unencrypted
version: 3.9.4
2 changes: 2 additions & 0 deletions k8s/welearn-api/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ config:
AZURE_API_BASE: "https://welearn-openai.openai.azure.com/openai/"
AZURE_API_VERSION: "2024-08-01-preview"
LLM_MODEL_NAME: gpt-oss-120b
AZURE_BAML_LLM_FRANCE_BASE_URL: https://welearn-mistral.services.ai.azure.com/openai/v1/
AZURE_BAML_LLM_SWEDEN_BASE_URL: https://welearn-mistral-sweden.services.ai.azure.com/openai/v1/
QDRANT_HOST: "http://qdrant.qdrant"
QDRANT_PORT: "6333"
MODELS_FOLDER_ROOTS: "/models"
Expand Down
2 changes: 2 additions & 0 deletions output.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{
"description":
166 changes: 107 additions & 59 deletions poetry.lock

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ aiohttp = "^3.13.3"
httpx = "^0.28.1"
beautifulsoup4 = "^4.14.3"
json-repair = "^0.55.0"
baml-py = "^0.220.0"
baml-cli = "^0.1.0"
pandas = { version = "^3.0.3", markers = "python_version >= '3.11'" }

[tool.poetry.group.dev.dependencies]
pytest = "^7.4.0"
Expand Down
109 changes: 109 additions & 0 deletions src/app/baml_src/agents/tutor/activity_guide_generator.baml
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// Activity Guide Generator Agent
// Personalizes activity template for specific course context

function GenerateActivityGuide(
activity_template: ActivityTemplate,
session: SessionPlan,
metadata: CourseMetadata,
sustainability_map: SustainabilityIntegration,
output_language: string
) -> ActivityGuide {
client CustomFast
prompt #"
<system>
You are a pedagogical designer who creates ready-to-use activity guides for professors. You take generic activity templates and personalize them with course-specific content, examples, and sustainability integration.
</system>

<task>
Transform this generic activity template into a detailed, ready-to-use guide for this specific course. The professor should be able to follow this guide directly to implement the activity.
</task>

<inputs>
<activity_template>
Name: {{ activity_template.name }}
Description: {{ activity_template.description }}
Estimated duration: {{ activity_template.estimated_duration }}h
</activity_template>

<session>
Session {{ session.session_number }}
Objectives: {{ session.objectives | join(", ") }}
Duration: {{ session.duration }}h
Class size: {{ session.class_size }}
</session>

<course_context>
Discipline: {{ metadata.discipline }}
Topic: {{ metadata.topic }}
Level: {{ metadata.level }}
Format: {{ metadata.session_type }} - {{ metadata.session_mode }}
</course_context>

<sustainability_context>
{% for conn in sustainability_map.connections %}
Objective {{ conn.objective_number }}: {{ conn.connection_explanation[:200] }}...
{% endfor %}
</sustainability_context>
</inputs>

<requirements>
Create a detailed guide with:

1. Activity description: Adapted to this course context (mention discipline, topic)

2. Learning objectives: Which objective numbers (from session.objectives) this addresses

3. Detailed steps: Concrete procedure professor can follow
- Be specific (not "discuss the topic" but "pose question X, give 5min for pairs to discuss, then collect 3 key points")
- Include timing for each step
- Use course-specific examples from the discipline

4. Teacher role: What professor does during activity

5. Student role: What students do, how they participate

6. Resources needed: Specific materials, handouts, tools
- Course-specific resources (not generic)
- Include digital tools if mode is Distanciel/Hybride

7. Timing breakdown: Detailed time allocation
- Example: {"introduction": 10, "pair_work": 20, "group_discussion": 30, "debrief": 10}
- Total should match activity template's estimated_duration

8. Evaluation method: How to assess if learning happened
- Formative assessment integrated into activity

9. Sustainability integration: HOW sustainability themes are woven into this activity
- Use sustainability connections for relevant objectives
- Be specific and natural (not tacked on)
</requirements>

<examples>
GENERIC: "Students discuss the topic in groups"
SPECIFIC: "Students analyze the case study of [discipline-specific example] in groups of 4. Each group receives a worksheet with 3 guiding questions: (1) What are the key [disciplinary concepts]? (2) How do [sustainability themes] factor into the analysis? (3) What trade-offs exist? Groups have 20 minutes to discuss and prepare a 2-minute summary."

GENERIC: "Use sustainability principles"
SPECIFIC: "During the case analysis, students explicitly apply lifecycle thinking from SDG 12 resources. They map the full value chain, identifying environmental externalities at each stage. This connects to Objective 3's focus on system boundaries in [discipline]."
</examples>

<output_format>
Return a JSON object with:
{
"activity_name": "Adapted name if needed",
"description": "Course-specific description...",
"learning_objectives": [1, 3], // Objective numbers
"steps": ["Step 1...", "Step 2...", ...],
"teacher_role": "What professor does...",
"student_role": "What students do...",
"resources_needed": ["Resource 1", "Resource 2", ...],
"timing_breakdown": {"phase1": 15, "phase2": 30, ...},
"evaluation_method": "How to assess...",
"sustainability_integration": "How sustainability is woven in..."
}
</output_format>

<output_language>
Generate all outputs in {{ output_language }}.
</output_language>
"#
}
105 changes: 105 additions & 0 deletions src/app/baml_src/agents/tutor/activity_recommendation.baml
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// Activity Recommendation Agent
// Ranks and selects 2-4 pedagogical activities for session based on learning outcomes

function RecommendActivities(
session: SessionPlan,
learning_outcomes: LearningOutcomes,
filtered_activities: ActivityTemplate[],
greencomp_methods: string[],
output_language: string
) -> ActivityRecommendations {
client CustomFast
prompt #"
<system>
You are a pedagogical activity design expert. You select and rank activities based on learning outcomes, pedagogical fit, variety, and feasibility.
IMPORTANT: Activities should help students achieve the LEARNING OUTCOMES (what students will be able to demonstrate) rather than just covering objectives.
</system>

<task>
From the pre-filtered activities, select 2-4 that best enable students to achieve the learning outcomes for this session. Ensure variety (mix of active/reflective, individual/group) and feasibility (total time fits session duration).
</task>

<inputs>
<session>
Session {{ session.session_number }}
Objectives covered: {{ session.objectives | join(", ") }}
Type: {{ session.type }}
Mode: {{ session.mode }}
Duration: {{ session.duration }} hours
Class size: {{ session.class_size }}
</session>

<learning_outcomes>
Learning outcomes for this session (select activities to help achieve these):
{% for outcome in learning_outcomes.outcomes %}
{% if outcome.related_objectives | length > 0 %}
{% for obj_num in outcome.related_objectives %}
{% if obj_num in session.objectives %}
- Outcome {{ outcome.number }}: {{ outcome.text }}
Assessment method: {{ outcome.assessment_method }}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
</learning_outcomes>

<greencomp_pedagogical_guidance>
GreenComp framework recommends the following teaching methods for these outcomes:
{% if greencomp_methods | length > 0 %}
{% for method in greencomp_methods %}
- {{ method }}
{% endfor %}

Consider prioritizing activities that align with these evidence-based pedagogical methods from the GreenComp sustainability education framework. These methods have been specifically matched to the learning outcomes for this session.
{% else %}
No specific GreenComp method recommendations available for this session.
{% endif %}
</greencomp_pedagogical_guidance>

<available_activities>
{% for activity in filtered_activities %}
{{ loop.index }}. {{ activity.name }} (ID: {{ activity.id }})
Description: {{ activity.description }}
Estimated duration: {{ activity.estimated_duration }}h
Compatible with: {{ activity.compatible_types | join(", ") }} - {{ activity.compatible_modes | join(", ") }}
{% endfor %}
</available_activities>
</inputs>

<requirements>
Selection criteria:
1. Pedagogical fit: Activities that directly help students achieve the learning outcomes (not just cover objectives)
- Match activities to assessment methods specified in outcomes
- Ensure activities allow students to demonstrate competencies
2. Variety: Mix of activity types (not all lecture, not all group work)
- Balance active (discussions, exercises) with reflective (analysis, writing)
- Balance individual, pair, and group activities
3. Feasibility: Total estimated time ≤ session duration (leave buffer for transitions)
4. Pedagogical progression: Activities build on each other within session

Number to select:
- Short sessions (<2h): 2-3 activities
- Long sessions (≥2h): 3-4 activities

Provide clear rationale:
- WHY each activity fits the LEARNING OUTCOMES (be specific about which outcomes)
- HOW activities complement each other
- TIME breakdown showing feasibility
</requirements>

<output_format>
Return a JSON object with:
{
"session_number": {{ session.session_number }},
"recommended_activities": [
// Copy ActivityTemplate objects for selected activities
],
"rationale": "Detailed explanation of selections, variety, and time management..."
}
</output_format>

<output_language>
Generate all outputs in {{ output_language }}.
</output_language>
"#
}
Loading
Loading