Skip to content
Merged
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
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ RUN pip install --upgrade pip
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
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
.PHONY: run-dev

run-poetry:
poetry run baml-cli generate --from ./src/app/baml_src
poetry run uvicorn src.main:app --reload

run-dev:
baml-cli generate --from ./src/app/baml_src
uvicorn src.main:app --reload

lint:
Expand Down
2 changes: 1 addition & 1 deletion devops-toolbox
80 changes: 38 additions & 42 deletions k8s/welearn-api/secrets.dev.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,76 +5,72 @@ config:
AZURE_APIM_API_KEY: ENC[AES256_GCM,data:GKEHlKS5GgzSO+rHYYvrdL0e7WDZDyByD6mYbDmSNZQ=,iv:k/SHzVKSvMr0ibz3O19yQc3Hz4rzFRALxINlNJRLAwk=,tag:9sgSAnfTKEL5SDPyyw2uhA==,type:str]
PG_PASSWORD: ENC[AES256_GCM,data:8GsISVAuJyIh0WUFwb9mpqyWcNT04fnmpH4ru5CA+aw=,iv:oGKLDiadbhRQWVSE5J/4QJbppiTIcHhNr9fFY7XI0jY=,tag:Ksk0uMrB9fmVT8thQODFJA==,type:str]
MISTRAL_API_KEY: ENC[AES256_GCM,data:2om3jEYb/E8bexmXO1LVWMp5fV4WOm51WCiJLiDEF3g=,iv:aYnBjQt3ZmK0DC4VGcqTGFUYRIOKrewkvoZC2Iz1ArY=,tag:eHpoeTdk5QVaKrqUJYueVw==,type:str]
LANGSMITH_API_KEY: ENC[AES256_GCM,data:VVDA9TJSXi0cB87FPeIYW+GPuhBBkHKhn1rNsiEY57VHqDQSsr5PuKB7nDl93VQdZvNU,iv:BdfYVMD6k2Vatb5hw3vpnWIeH4/WjvEstOWqqm1XvRw=,tag:QUZosVqsowceC7aT8uwoUw==,type:str]
LANGSMITH_API_KEY: ENC[AES256_GCM,data:kSwR7C8iUd6EL58nhcxhoBvi4QB8/0e8GCCd8ywrpQ9R4FLndaHIxXSHVdo7whlwz2Tw,iv:cHRxbIYwA2d7Q2gUr/O0BesMdjb+kT9tK2uT5zsajYU=,tag:+GOhrNHQpVtJqEKt/PhRaw==,type:str]
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: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBIYzlZMUY0R2RnZEFGK3hl
NUhScmFIL0ZrczdZNEs1aE5OVlZ3ZzJlL1VBCk5HdU4vQUtYK1hiVmtvL3JlMmFZ
Z1RWb0FYc1ZsdWpJUGhRTUhGWFcyU00KLS0tIHJ6M3V6a2dOcFVNSlpMYlZ0V1pL
dmZiUlJnSHprMUVPT09ja2VMOVdML00KKIm9cvD8QXKi6UPKRthHDjCWbDMmIsAj
/TahamnxttcrfgqE8D67habSIf9IghNMcFurMwmJJWisqdJ9mlTP+A==
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB4R2xCeHAybzc0Zlk4ME04
VFZkQ0s1c2FsdE01S28zMHNtSzAzbkhvZmxzCkpEM0VRRzVFdWtJTFVRcHVGSXJR
aWZzRWxxY1FuMjdKUkdoN0crOG42YzQKLS0tIEUrekxvZjFrWDJNR1Q2YldUWER1
S1JiK1ZkMld1RUJ0M3VsYmVJL2VWTGcKGw3xqNeYzREDyQunsEHU7ajxcOAoUCcZ
L6gmMPI6bZfyW8Sq2u4wYt6MrERovBoEFpFm4UmUnfCxodMjZyNjIA==
-----END AGE ENCRYPTED FILE-----
- recipient: age14qavd6070runmtefwsh60ps3kxnu36fhsp76wvct4w4k4g7wqeksmk3r9z
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBTOXpGNncxM1o4RnRpa3JX
ZDc3WE8yVkgwMm9HQkg3VXhzSTZZYWo1dmswCnorWnR4M1dOY0VDN1g5NTFNVm00
RjlubVNBcDRXVGVzMXk1R0dOUE4zMFEKLS0tIFRPMjd0aXNTZUs2ZUFWcVlIMldz
RzJvRmNPNERtL0pYQ1hyRHA0WXlFb0EKd2e1DjNb6N2ZS50u6hJkm79wsGhzDWhE
EguDdRO4R4cjLQGhlhc0D9tMx/c+lUxEAkNOJFmf3vRc7gfUFfg5Gg==
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBJNG5GMWE1bjlsekVoS041
UjdkVWpPRURTdEhPQmJCVzF6MFBQQXZXNGhBCjBEaXFEaC9VcUtYanRHSUhMR0Qw
dmdYb29WdnU3RkFkNjNwOGV6b3Blc1EKLS0tIFBJeDBjTTVkVWxZQUR2Q2RkMFhV
aUdEOC9jVFNvMVhxQnBtVmVDcmJubVUKHRnjxPYRBbrzP9LjUsvcyFh87mgvUl1P
PcrgsOOOvu4lHyiF1m5KUR9h8G88NNIv0VggNYfOV7o4bsy691v12Q==
-----END AGE ENCRYPTED FILE-----
- recipient: age1w7q7xfuw2z4nshys80lt3l7y409rws4elevsqm74kh52a2g3pagqcfp9wq
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAzUDBPdWtienhQUG5LZnA0
TjhqMEZib0FCMkx6WEl3TFdtNHZpZzhLU1NNClFrUng1Y1hBZzA4NGc4eit5OHM2
dkpGS0g1dG11djF1WmExTHFRZG9XMGsKLS0tIHZlQjhPSTMvWnhjTUtQeWxuNDlu
RzZKUEJtdlFCOU5aM29jV0Q3bXMxSFUKVGtQfDl6iKEO2rEkPYlXkV5I8YR8t5xi
UIgqdoMGZvCKTJdyByG5VtQDstVVW5QuKzvE2flvawzFYUkByQ5B/w==
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAyTmVaSTdvK29lcVJwL0l4
ZU52Vko3RVVqd2hhYVVCdWJtU3VTaHZ3U2gwCkdoSmVUNk5IOEw0TXozVXZBSFht
QmFHK2JPUVpZSlI1Zi85Y0o2OWUrR0EKLS0tIFpTekdGN0U4TEtaYjhPNXlXTUFt
RU85UENjSGs5K1c0SlRKdUxsM1RkZU0KWtcCdceXPaEORejTjPyVHi0tqiXt6lAJ
tN453Uqcj6Y3rE3dO6LdrYChl1gY8XIxPyeYjZpe0Gg3AqdvUw1RRg==
-----END AGE ENCRYPTED FILE-----
- recipient: age1q22qgq5vyxatgxntpd8n7k0umqjvccny9ndf694f0kh3s76y5pnsc3386z
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA4QjhsVGp5dFIyZU9aODJy
eHJJdVhuM2VhVkFOZHZ1UkxSdCttWjZZOGcwCjgxN05kUExYK0FHcnE4U21HSi92
b1lNT2NIQjh1WXY4QUdpV3UvYmMwWkkKLS0tIHNUeHA5Qnk1U2NCeGFkT0ltczRy
ZllPTE5xa1RDeUd0WmVPczFhZzVxdWsKMsmEsYCBu7q01AUwakRUpPNvkJmlGMZi
VZyVhAOTq3p6UQ80JDc2MYBbe08LR4bnQo0aXBR9oFHw9JMyaq2ZAg==
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBWN1YxZWhnS3lGVGlLY1lL
Q1M3eFpGb1ZJTGdCOGFwbTBKVFBZZUdRNlNnCnJ1ZThYeC9lVFlzSWtQd3EvNkVW
bmc3N25hVmJJSUZJTk5NN2ZRbjA1dkkKLS0tIEZRQXNlT2ZqVVJUOXhSTEUyei9T
TzE4ZWpkeWp6STZsVW1YMk90aGh4ZVUKh0c2Uu2WY6wWGqJv/SUElqKSRBURdGB4
kdIVLW5hskirKX8RMkvqY0aCc+RA+AIwAYSdpIT5+GhGYvf3WwcSAA==
-----END AGE ENCRYPTED FILE-----
- recipient: age1ew6kcn0fa6faqkc8w2hg2ww7xrh2g87nwa7ncdc2eq6xf4vkfs5q8yxy0l
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA2dVJBZTBURHdQTmxTNmZC
WkZqbGp1YWlyYTNIOW5BNlVGdnlNL0ZVczJrCnF0SVBrSXp5Rnd4VnRydGVrcTd1
RkJSYldjTldoSDc2L0dPNHRpSkEyWHcKLS0tIHl6WFI4cnVUQlFBWklhVEtRL2Y4
WTdMOXZ3K1c1eWduNk83YWQ5Nkg1bzQKTvb04Jfk4DZalSl96Jf+Fl1ccQm3FVWi
LhFnJvUrNI8sY2gkmHxc22H76X8bE2GhDl3TjaJFBOWZmPQlUOsWEw==
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBXN1o1Tmtkb0NadFJESXVj
K3VFNjlLa1pqSjhheW5kbEZWWmcrNHBiUHpZCkRwMUNiUDdxRVAzVk10VzJvYnIy
VnlqUEVjSnF1eUJHYkVhNEwvMmNpeGcKLS0tIFBpR0hTWHIwMUd0emdxM2k5Q1NB
NXZiYmdGZURyQ0xyeE50Z1VDa2wyY1kK/5iryku0MKcs4lCPGOVayJVNA1QN8h72
NIUYWr0VHSfHe7oBzC81kk2I9gg4Pa3cORZyhIzxEU2ytJPBmDinCw==
-----END AGE ENCRYPTED FILE-----
- recipient: age1h2ta0vvj53zt9qyys5hskramd6cduy9k85hqaghjnrrrcv0e9shskl7dzd
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBOYlNnS2Y5bkExY0ptQXls
V05BdzBiMTVyZnlvc253ZnhNQXdHSDFqSUdNCm9iNG9DUnpnU3o0WSsyWSs3eUZi
VUFsZFFJSG9qUWtXQm9XeUZXTXA5UHcKLS0tIHBTNExkclkwMWEweVlJa1VadlBr
Z08zTlh4VWFwQzB0cnVRVjJ3VU16ZG8K85BIPu9f3U1D9UhcbyZMLhHn7Ea5L6+4
CEUvSSt5a1EPZDbcd6a+502/ssFD/cuqdrjx0hSfNR56l2BrTYt6ag==
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAwL1BFY1MxQkxTZlBUNlFU
SkRvWGdZczJyZEJDTlRxZUdZVWVLQXFuRms4CkRCSjhtYmhkSEwzRHZtUjhHOGkv
OW1hWHNRYWw5VG41c2NNUnJxVW1tWXMKLS0tIGE0NDBZRWphSjhrcGJvaEpEUG5M
Q1lsakJjbW5rTGRIYUt0M3ArN2xjMW8KyUV4W7zltLlQ3Bdp4U1OgU5hRlWLa5RU
hxOYlyFSCKkn/hXTx2AAwyTVcwVGt/OoUujuRN1A1/pGrO1sDY0uFg==
-----END AGE ENCRYPTED FILE-----
- recipient: age12y40q97r2ww94xt66u9uwvnt87a75d03tev7nj6dyrfugjsm9c5qr2te7k
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBZYjZkVE1XWnBLc2srcmg1
YTV5SWVpYUJCY3k3QnVuSjMvUGpvb2VQTnljCjNpcnlqbTVPYytUV1N0ZmdmM3dy
VDRCRGZiakJSVTFWblF5OW9kRjJqbjgKLS0tIGJMWHY4SXF6ZlpwTXdPN21sOWl6
T0k2ekxSNk9DNFlncGpRR0VmdlFhdWsKnr87J47tm8ksBLFio1frJo8rknlchfdl
skOOxxXqlhwmNg0TUSZaqtW6qUTZCExt0zN1QFFK5WPxa23m3O1s5g==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2026-07-31T09:03:22Z"
mac: ENC[AES256_GCM,data:grgrdop/r7SrBf2/0fOil2i9JDw1akPXsCWvzjo9+vO0s84qagiAoiexORaBQPrDhu4scEnZOKG5F30nLNuUdmTsKYygYUi+KvKnA7rJ/BTiTzZcXcLQ8r26li9opkaMKbhwZGV0zKBz3cqZchOXV8hBblafK3q5kVckBMa4Rxc=,iv:DJnliUWxHDhmmRjibOpqJtnzbtHbVCF916LV967MjtA=,tag:W+m8Fhm8wCXI5+mAhX/++Q==,type:str]
lastmodified: "2026-08-20T12:28:21Z"
mac: ENC[AES256_GCM,data:Il8cM7CsQhS7x5DkLFmZJEImF12f53mtM/muqJQujjsJ7gRbADTqjbl0sPZPD56ZNFLTMii73KHkfbpE39anV9Lsoz3+NAp6IHGlAbT1KyyqCI4K6L3Pksy6qOwq31M+KOqAofVD3MYiJ0joGo+KECQnAZzYpU4QvZud/F+zDTc=,iv:MGPqVRF4yANViylxQdhE1UGAZGeLBeAHMCHtvTCPAQs=,tag:2K+041st1IPLnNGzewwmkQ==,type:str]
pgp: []
unencrypted_suffix: _unencrypted
version: 3.12.1
2 changes: 2 additions & 0 deletions k8s/welearn-api/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ config:
LLM_MODEL_NAME: gpt-oss-120b
LLM_TEMPERATURE: 0.2
MISTRAL_LLM_MODEL_NAME: mistral-small-latest
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":
24 changes: 21 additions & 3 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ langchain-azure-ai = "^1.2.3"
langgraph = "^1.1.10"
mistralai = "^2.4.3"
langsmith = "^0.10.11"
pandas = { version = "^3.0.3", markers = "python_version >= '3.11'" }
baml-py = "^0.226.1"

[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>
"#
}
Loading
Loading