From ed277e6b9ef2747fe8809bd97fbb8e259bda0143 Mon Sep 17 00:00:00 2001 From: Sandra Guerreiro Date: Wed, 1 Jul 2026 12:51:24 +0200 Subject: [PATCH 1/6] baml tutor wip docker pandas adds values and secrets in dev accpets sylalbus, sumarizes it --- .flake8 | 3 +- .github/workflows/ci.yml | 24 +- Dockerfile | 1 + devops-toolbox | 2 +- k8s/welearn-api/secrets.dev.yaml | 148 ++- k8s/welearn-api/values.yaml | 2 + output.txt | 2 + poetry.lock | 273 ++++- pyproject.toml | 3 + .../tutor/activity_guide_generator.baml | 109 ++ .../agents/tutor/activity_recommendation.baml | 105 ++ .../agents/tutor/competency_mapping.baml | 83 ++ .../agents/tutor/course_description.baml | 58 + .../baml_src/agents/tutor/file_summary.baml | 216 ++++ .../agents/tutor/learning_objectives.baml | 87 ++ .../agents/tutor/learning_outcomes.baml | 79 ++ .../agents/tutor/pedagogical_engineer.baml | 130 +++ .../tutor/sustainability_integration.baml | 109 ++ src/app/baml_src/clients.baml | 42 + src/app/baml_src/generators.baml | 18 + src/app/baml_src/resume.baml | 42 + src/app/baml_src/types.baml | 143 +++ src/app/tutor/api/router.py | 248 ++++ src/app/tutor/service/b_agents/__init__.py | 26 + .../activity_guide_generator_agent.py | 47 + .../b_agents/activity_recommendation_agent.py | 92 ++ src/app/tutor/service/b_agents/base.py | 165 +++ .../b_agents/competency_mapping_agent.py | 43 + .../b_agents/course_description_agent.py | 50 + .../b_agents/learning_objectives_agent.py | 44 + .../b_agents/learning_outcomes_agent.py | 44 + .../b_agents/pedagogical_engineer_agent.py | 45 + .../sustainability_integration_agent.py | 64 ++ src/app/tutor/service/models.py | 266 ++++- src/app/tutor/service/orchestrator.py | 1014 +++++++++++++++++ src/app/tutor/tools/__init__.py | 28 + src/app/tutor/tools/distribute_objectives.py | 73 ++ src/app/tutor/tools/filter_activities.py | 176 +++ src/app/tutor/tools/greencomp_data.py | 238 ++++ src/app/tutor/tools/validate_format.py | 48 + 40 files changed, 4284 insertions(+), 106 deletions(-) create mode 100644 output.txt create mode 100644 src/app/baml_src/agents/tutor/activity_guide_generator.baml create mode 100644 src/app/baml_src/agents/tutor/activity_recommendation.baml create mode 100644 src/app/baml_src/agents/tutor/competency_mapping.baml create mode 100644 src/app/baml_src/agents/tutor/course_description.baml create mode 100644 src/app/baml_src/agents/tutor/file_summary.baml create mode 100644 src/app/baml_src/agents/tutor/learning_objectives.baml create mode 100644 src/app/baml_src/agents/tutor/learning_outcomes.baml create mode 100644 src/app/baml_src/agents/tutor/pedagogical_engineer.baml create mode 100644 src/app/baml_src/agents/tutor/sustainability_integration.baml create mode 100644 src/app/baml_src/clients.baml create mode 100644 src/app/baml_src/generators.baml create mode 100644 src/app/baml_src/resume.baml create mode 100644 src/app/baml_src/types.baml create mode 100644 src/app/tutor/service/b_agents/__init__.py create mode 100644 src/app/tutor/service/b_agents/activity_guide_generator_agent.py create mode 100644 src/app/tutor/service/b_agents/activity_recommendation_agent.py create mode 100644 src/app/tutor/service/b_agents/base.py create mode 100644 src/app/tutor/service/b_agents/competency_mapping_agent.py create mode 100644 src/app/tutor/service/b_agents/course_description_agent.py create mode 100644 src/app/tutor/service/b_agents/learning_objectives_agent.py create mode 100644 src/app/tutor/service/b_agents/learning_outcomes_agent.py create mode 100644 src/app/tutor/service/b_agents/pedagogical_engineer_agent.py create mode 100644 src/app/tutor/service/b_agents/sustainability_integration_agent.py create mode 100644 src/app/tutor/service/orchestrator.py create mode 100644 src/app/tutor/tools/__init__.py create mode 100644 src/app/tutor/tools/distribute_objectives.py create mode 100644 src/app/tutor/tools/filter_activities.py create mode 100644 src/app/tutor/tools/greencomp_data.py create mode 100644 src/app/tutor/tools/validate_format.py diff --git a/.flake8 b/.flake8 index 13d7fdec..db676f94 100644 --- a/.flake8 +++ b/.flake8 @@ -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 = diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5ddffcaf..bc6264b0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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 diff --git a/Dockerfile b/Dockerfile index 39eceeda..563caad6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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 diff --git a/devops-toolbox b/devops-toolbox index 9b22921a..684e7cf9 160000 --- a/devops-toolbox +++ b/devops-toolbox @@ -1 +1 @@ -Subproject commit 9b22921ab5874132dcf35ae57b4e77b0e65cec87 +Subproject commit 684e7cf9f0b855077aa29251c4a156a1440de960 diff --git a/k8s/welearn-api/secrets.dev.yaml b/k8s/welearn-api/secrets.dev.yaml index 52201ed6..f7d73ea5 100644 --- a/k8s/welearn-api/secrets.dev.yaml +++ b/k8s/welearn-api/secrets.dev.yaml @@ -1,80 +1,74 @@ config: - sensitive: - AZURE_API_KEY: ENC[AES256_GCM,data:vU5sozgleY904OU1Fb0yEaRzWYNHnCD2ILjthvtSfk4=,iv:b7ItArThdtIsz1qvgpptbOq96GHa1q6VWAweHYWw2Po=,tag:emJB+azc6vcIWCgv8idtyg==,type:str] - AZURE_MISTRAL_API_KEY: ENC[AES256_GCM,data:FjMSqC6wQM4/1cg5hwM3qAZiKmDEEU0rz6l44XDr0HQ0WLXRYKxDcm/0Ii1khG693fyQJalH7lvf8CRVdD4mnIm3KOaw5Q5MCkqrqoQo2GGm+7ZF,iv:h157m7hUESIOMC81yrnxzG//fInFLfsR8o2zYTsGcUs=,tag:pMOQn7+ijCbvYh1AksjYbQ==,type:str] - 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] + sensitive: + AZURE_API_KEY: ENC[AES256_GCM,data:vU5sozgleY904OU1Fb0yEaRzWYNHnCD2ILjthvtSfk4=,iv:b7ItArThdtIsz1qvgpptbOq96GHa1q6VWAweHYWw2Po=,tag:emJB+azc6vcIWCgv8idtyg==,type:str] + AZURE_MISTRAL_API_KEY: ENC[AES256_GCM,data:FjMSqC6wQM4/1cg5hwM3qAZiKmDEEU0rz6l44XDr0HQ0WLXRYKxDcm/0Ii1khG693fyQJalH7lvf8CRVdD4mnIm3KOaw5Q5MCkqrqoQo2GGm+7ZF,iv:h157m7hUESIOMC81yrnxzG//fInFLfsR8o2zYTsGcUs=,tag:pMOQn7+ijCbvYh1AksjYbQ==,type:str] + 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] 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] + 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: - age: - - recipient: age1dvu205sflecfu6uewgwmgs83nkm2c2fzfxs55zfkjs2gmqjya50qmm6lck - enc: | - -----BEGIN AGE ENCRYPTED FILE----- - YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBIYzlZMUY0R2RnZEFGK3hl - NUhScmFIL0ZrczdZNEs1aE5OVlZ3ZzJlL1VBCk5HdU4vQUtYK1hiVmtvL3JlMmFZ - Z1RWb0FYc1ZsdWpJUGhRTUhGWFcyU00KLS0tIHJ6M3V6a2dOcFVNSlpMYlZ0V1pL - dmZiUlJnSHprMUVPT09ja2VMOVdML00KKIm9cvD8QXKi6UPKRthHDjCWbDMmIsAj - /TahamnxttcrfgqE8D67habSIf9IghNMcFurMwmJJWisqdJ9mlTP+A== - -----END AGE ENCRYPTED FILE----- - - recipient: age14qavd6070runmtefwsh60ps3kxnu36fhsp76wvct4w4k4g7wqeksmk3r9z - enc: | - -----BEGIN AGE ENCRYPTED FILE----- - YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBTOXpGNncxM1o4RnRpa3JX - ZDc3WE8yVkgwMm9HQkg3VXhzSTZZYWo1dmswCnorWnR4M1dOY0VDN1g5NTFNVm00 - RjlubVNBcDRXVGVzMXk1R0dOUE4zMFEKLS0tIFRPMjd0aXNTZUs2ZUFWcVlIMldz - RzJvRmNPNERtL0pYQ1hyRHA0WXlFb0EKd2e1DjNb6N2ZS50u6hJkm79wsGhzDWhE - EguDdRO4R4cjLQGhlhc0D9tMx/c+lUxEAkNOJFmf3vRc7gfUFfg5Gg== - -----END AGE ENCRYPTED FILE----- - - recipient: age1w7q7xfuw2z4nshys80lt3l7y409rws4elevsqm74kh52a2g3pagqcfp9wq - enc: | - -----BEGIN AGE ENCRYPTED FILE----- - YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAzUDBPdWtienhQUG5LZnA0 - TjhqMEZib0FCMkx6WEl3TFdtNHZpZzhLU1NNClFrUng1Y1hBZzA4NGc4eit5OHM2 - dkpGS0g1dG11djF1WmExTHFRZG9XMGsKLS0tIHZlQjhPSTMvWnhjTUtQeWxuNDlu - RzZKUEJtdlFCOU5aM29jV0Q3bXMxSFUKVGtQfDl6iKEO2rEkPYlXkV5I8YR8t5xi - UIgqdoMGZvCKTJdyByG5VtQDstVVW5QuKzvE2flvawzFYUkByQ5B/w== - -----END AGE ENCRYPTED FILE----- - - recipient: age1q22qgq5vyxatgxntpd8n7k0umqjvccny9ndf694f0kh3s76y5pnsc3386z - enc: | - -----BEGIN AGE ENCRYPTED FILE----- - YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA4QjhsVGp5dFIyZU9aODJy - eHJJdVhuM2VhVkFOZHZ1UkxSdCttWjZZOGcwCjgxN05kUExYK0FHcnE4U21HSi92 - b1lNT2NIQjh1WXY4QUdpV3UvYmMwWkkKLS0tIHNUeHA5Qnk1U2NCeGFkT0ltczRy - ZllPTE5xa1RDeUd0WmVPczFhZzVxdWsKMsmEsYCBu7q01AUwakRUpPNvkJmlGMZi - VZyVhAOTq3p6UQ80JDc2MYBbe08LR4bnQo0aXBR9oFHw9JMyaq2ZAg== - -----END AGE ENCRYPTED FILE----- - - recipient: age1ew6kcn0fa6faqkc8w2hg2ww7xrh2g87nwa7ncdc2eq6xf4vkfs5q8yxy0l - enc: | - -----BEGIN AGE ENCRYPTED FILE----- - YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA2dVJBZTBURHdQTmxTNmZC - WkZqbGp1YWlyYTNIOW5BNlVGdnlNL0ZVczJrCnF0SVBrSXp5Rnd4VnRydGVrcTd1 - RkJSYldjTldoSDc2L0dPNHRpSkEyWHcKLS0tIHl6WFI4cnVUQlFBWklhVEtRL2Y4 - WTdMOXZ3K1c1eWduNk83YWQ5Nkg1bzQKTvb04Jfk4DZalSl96Jf+Fl1ccQm3FVWi - LhFnJvUrNI8sY2gkmHxc22H76X8bE2GhDl3TjaJFBOWZmPQlUOsWEw== - -----END AGE ENCRYPTED FILE----- - - recipient: age1h2ta0vvj53zt9qyys5hskramd6cduy9k85hqaghjnrrrcv0e9shskl7dzd - enc: | - -----BEGIN AGE ENCRYPTED FILE----- - YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBOYlNnS2Y5bkExY0ptQXls - V05BdzBiMTVyZnlvc253ZnhNQXdHSDFqSUdNCm9iNG9DUnpnU3o0WSsyWSs3eUZi - VUFsZFFJSG9qUWtXQm9XeUZXTXA5UHcKLS0tIHBTNExkclkwMWEweVlJa1VadlBr - Z08zTlh4VWFwQzB0cnVRVjJ3VU16ZG8K85BIPu9f3U1D9UhcbyZMLhHn7Ea5L6+4 - CEUvSSt5a1EPZDbcd6a+502/ssFD/cuqdrjx0hSfNR56l2BrTYt6ag== - -----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] - unencrypted_suffix: _unencrypted - version: 3.12.1 + kms: [] + gcp_kms: [] + azure_kv: [] + hc_vault: [] + age: + - recipient: age1dvu205sflecfu6uewgwmgs83nkm2c2fzfxs55zfkjs2gmqjya50qmm6lck + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB4R2xCeHAybzc0Zlk4ME04 + VFZkQ0s1c2FsdE01S28zMHNtSzAzbkhvZmxzCkpEM0VRRzVFdWtJTFVRcHVGSXJR + aWZzRWxxY1FuMjdKUkdoN0crOG42YzQKLS0tIEUrekxvZjFrWDJNR1Q2YldUWER1 + S1JiK1ZkMld1RUJ0M3VsYmVJL2VWTGcKGw3xqNeYzREDyQunsEHU7ajxcOAoUCcZ + L6gmMPI6bZfyW8Sq2u4wYt6MrERovBoEFpFm4UmUnfCxodMjZyNjIA== + -----END AGE ENCRYPTED FILE----- + - recipient: age14qavd6070runmtefwsh60ps3kxnu36fhsp76wvct4w4k4g7wqeksmk3r9z + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBJNG5GMWE1bjlsekVoS041 + UjdkVWpPRURTdEhPQmJCVzF6MFBQQXZXNGhBCjBEaXFEaC9VcUtYanRHSUhMR0Qw + dmdYb29WdnU3RkFkNjNwOGV6b3Blc1EKLS0tIFBJeDBjTTVkVWxZQUR2Q2RkMFhV + aUdEOC9jVFNvMVhxQnBtVmVDcmJubVUKHRnjxPYRBbrzP9LjUsvcyFh87mgvUl1P + PcrgsOOOvu4lHyiF1m5KUR9h8G88NNIv0VggNYfOV7o4bsy691v12Q== + -----END AGE ENCRYPTED FILE----- + - recipient: age1w7q7xfuw2z4nshys80lt3l7y409rws4elevsqm74kh52a2g3pagqcfp9wq + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAyTmVaSTdvK29lcVJwL0l4 + ZU52Vko3RVVqd2hhYVVCdWJtU3VTaHZ3U2gwCkdoSmVUNk5IOEw0TXozVXZBSFht + QmFHK2JPUVpZSlI1Zi85Y0o2OWUrR0EKLS0tIFpTekdGN0U4TEtaYjhPNXlXTUFt + RU85UENjSGs5K1c0SlRKdUxsM1RkZU0KWtcCdceXPaEORejTjPyVHi0tqiXt6lAJ + tN453Uqcj6Y3rE3dO6LdrYChl1gY8XIxPyeYjZpe0Gg3AqdvUw1RRg== + -----END AGE ENCRYPTED FILE----- + - recipient: age1q22qgq5vyxatgxntpd8n7k0umqjvccny9ndf694f0kh3s76y5pnsc3386z + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBWN1YxZWhnS3lGVGlLY1lL + Q1M3eFpGb1ZJTGdCOGFwbTBKVFBZZUdRNlNnCnJ1ZThYeC9lVFlzSWtQd3EvNkVW + bmc3N25hVmJJSUZJTk5NN2ZRbjA1dkkKLS0tIEZRQXNlT2ZqVVJUOXhSTEUyei9T + TzE4ZWpkeWp6STZsVW1YMk90aGh4ZVUKh0c2Uu2WY6wWGqJv/SUElqKSRBURdGB4 + kdIVLW5hskirKX8RMkvqY0aCc+RA+AIwAYSdpIT5+GhGYvf3WwcSAA== + -----END AGE ENCRYPTED FILE----- + - recipient: age1ew6kcn0fa6faqkc8w2hg2ww7xrh2g87nwa7ncdc2eq6xf4vkfs5q8yxy0l + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBXN1o1Tmtkb0NadFJESXVj + K3VFNjlLa1pqSjhheW5kbEZWWmcrNHBiUHpZCkRwMUNiUDdxRVAzVk10VzJvYnIy + VnlqUEVjSnF1eUJHYkVhNEwvMmNpeGcKLS0tIFBpR0hTWHIwMUd0emdxM2k5Q1NB + NXZiYmdGZURyQ0xyeE50Z1VDa2wyY1kK/5iryku0MKcs4lCPGOVayJVNA1QN8h72 + NIUYWr0VHSfHe7oBzC81kk2I9gg4Pa3cORZyhIzxEU2ytJPBmDinCw== + -----END AGE ENCRYPTED FILE----- + - recipient: age1h2ta0vvj53zt9qyys5hskramd6cduy9k85hqaghjnrrrcv0e9shskl7dzd + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAwL1BFY1MxQkxTZlBUNlFU + SkRvWGdZczJyZEJDTlRxZUdZVWVLQXFuRms4CkRCSjhtYmhkSEwzRHZtUjhHOGkv + OW1hWHNRYWw5VG41c2NNUnJxVW1tWXMKLS0tIGE0NDBZRWphSjhrcGJvaEpEUG5M + Q1lsakJjbW5rTGRIYUt0M3ArN2xjMW8KyUV4W7zltLlQ3Bdp4U1OgU5hRlWLa5RU + hxOYlyFSCKkn/hXTx2AAwyTVcwVGt/OoUujuRN1A1/pGrO1sDY0uFg== + -----END AGE ENCRYPTED FILE----- + lastmodified: "2026-04-30T13:05:26Z" + mac: ENC[AES256_GCM,data:ZFiWNcr07s7GcnleGpFZWb+YIk1Hv5nut4GFn4o2p5YFd0IvycuFWybk3sw1e4GmPoIJwaXiuSIThUYaZSBbuAzy9u7QZj0tkiVti7otbccsTj+mFOQ4p4iJty1Ms6+S03O4Yd3jWOugJQMbzuMm4OHKkI2rorvaVOdyQjvPK6c=,iv:+WSNB3GrlxU7kOCnAeI1M1JkTQG/ssEXD+QYt2WcwjM=,tag:Gc19gH91iYb6tAj0PKI8Wg==,type:str] + unencrypted_suffix: _unencrypted + version: 3.12.1 diff --git a/k8s/welearn-api/values.yaml b/k8s/welearn-api/values.yaml index 3c8667bb..231ade39 100644 --- a/k8s/welearn-api/values.yaml +++ b/k8s/welearn-api/values.yaml @@ -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" diff --git a/output.txt b/output.txt new file mode 100644 index 00000000..ec4dbac3 --- /dev/null +++ b/output.txt @@ -0,0 +1,2 @@ +{ + "description": \ No newline at end of file diff --git a/poetry.lock b/poetry.lock index c74f9b53..c63034db 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. [[package]] name = "aiohappyeyeballs" @@ -7,6 +7,7 @@ description = "Happy Eyeballs for asyncio" optional = false python-versions = ">=3.10" groups = ["main", "metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "aiohappyeyeballs-2.7.1-py3-none-any.whl", hash = "sha256:9243213661e29250eb41368e5daa826fc017156c3b8a11440826b2e3ed376472"}, {file = "aiohappyeyeballs-2.7.1.tar.gz", hash = "sha256:065665c041c42a5938ed220bdcd7230f22527fbec085e1853d2402c8a3615d9d"}, @@ -19,6 +20,7 @@ description = "Async http client/server framework (asyncio)" optional = false python-versions = ">=3.10" groups = ["main", "metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "aiohttp-3.14.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:eb0495d778817619273c108784292be161a924b9f5ae5cbbc70a2caa6838250b"}, {file = "aiohttp-3.14.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c3c200cf9757edd785051dc699c7ecbec22110dbfcb3fefc7a9f9695eda8ea7a"}, @@ -161,6 +163,7 @@ description = "aiosignal: a list of registered asynchronous callbacks" optional = false python-versions = ">=3.9" groups = ["main", "metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e"}, {file = "aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7"}, @@ -177,6 +180,7 @@ description = "A database migration tool for SQLAlchemy." optional = false python-versions = ">=3.10" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "alembic-1.18.5-py3-none-any.whl", hash = "sha256:06d8ba9d04558022f5395e9317de03d270f3dced49cee01f89fe7a13c26f14bc"}, {file = "alembic-1.18.5.tar.gz", hash = "sha256:1554982221dd17e9a749b53902407578eb305e453f71999e8c7f0a48389fff8e"}, @@ -197,6 +201,7 @@ description = "Document parameters, class attributes, return types, and variable optional = false python-versions = ">=3.9" groups = ["metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "annotated_doc-0.0.5-py3-none-any.whl", hash = "sha256:117bac03a25ede5df5440e855b32d556049ca169ead221505badf432fed4b101"}, {file = "annotated_doc-0.0.5.tar.gz", hash = "sha256:c7e58ce09192557605d8bbd92836d7e1d520ac9580096042c0bfd197efacf1bb"}, @@ -209,6 +214,7 @@ description = "Reusable constraint types to use with typing.Annotated" optional = false python-versions = ">=3.10" groups = ["main", "metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "annotated_types-0.8.0-py3-none-any.whl", hash = "sha256:f072f4d804ea359e4eaf198b1af7a8b0943881a87f31bb764f8bf219bb9419e0"}, {file = "annotated_types-0.8.0.tar.gz", hash = "sha256:13b2beaad985e05e2d6407ee4c4f35590b11f8d693a258a561055cac8f64cab7"}, @@ -221,6 +227,7 @@ description = "High-level concurrency and networking framework on top of asyncio optional = false python-versions = ">=3.10" groups = ["main", "metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "anyio-4.14.2-py3-none-any.whl", hash = "sha256:9f505dda5ac9f0c8309b5e8bd445a8c2bf7246f3ce950121e45ea15bc41d1494"}, {file = "anyio-4.14.2.tar.gz", hash = "sha256:cfa139f3ed1a23ee8f88a145ddb5ac7605b8bbfd8592baacd7ce3d8bb4313c7f"}, @@ -240,6 +247,7 @@ description = "A small Python module for determining appropriate platform-specif optional = false python-versions = "*" groups = ["metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, @@ -252,6 +260,7 @@ description = "An abstract syntax tree for Python with inference support." optional = false python-versions = ">=3.7.2" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "astroid-2.15.8-py3-none-any.whl", hash = "sha256:1aa149fc5c6589e3d0ece885b4491acd80af4f087baafa3fb5203b113e68cd3c"}, {file = "astroid-2.15.8.tar.gz", hash = "sha256:6c107453dffee9055899705de3c9ead36e74119cee151e5a9aaf7f0b0e020a6a"}, @@ -268,6 +277,7 @@ description = "Classes Without Boilerplate" optional = false python-versions = ">=3.9" groups = ["main", "metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309"}, {file = "attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32"}, @@ -280,6 +290,7 @@ description = "AutoGen agents and teams library" optional = false python-versions = ">=3.10" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "autogen_agentchat-0.5.7-py3-none-any.whl", hash = "sha256:b9c8548e02eed9765c02800182b13bd9747f1561b23df66983bedd8c5fdddb86"}, {file = "autogen_agentchat-0.5.7.tar.gz", hash = "sha256:6f888f03224d658cc130548c3e42558a177a7f7f3eb59b414396c412ec282728"}, @@ -295,6 +306,7 @@ description = "Foundational interfaces and agent runtime implementation for Auto optional = false python-versions = ">=3.10" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "autogen_core-0.5.7-py3-none-any.whl", hash = "sha256:a276af987b7861092194bfd73bab49ac36f3fee55958a60b0b43ea67612d2e50"}, {file = "autogen_core-0.5.7.tar.gz", hash = "sha256:0e3b23b64bd2c7c3180ab4431c137104473e11a846088cc2dd629a0e1fdfbf2f"}, @@ -315,6 +327,7 @@ description = "AutoGen extensions library" optional = false python-versions = ">=3.10" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "autogen_ext-0.5.7-py3-none-any.whl", hash = "sha256:1744195107d814621dc0e967b7f7a4fa09e063fa5ed728c3ba118e9aa88acad2"}, {file = "autogen_ext-0.5.7.tar.gz", hash = "sha256:987aa70d8eecd05c9dea4aad793e9d1369a3d3ceb022dfeafb4cd98b6b4869fc"}, @@ -367,6 +380,7 @@ description = "Microsoft Azure AI Content Safety Client Library for Python" optional = false python-versions = ">=3.7" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "azure-ai-contentsafety-1.0.0.tar.gz", hash = "sha256:052731bd1419a720fa00910f46bf3428c4e5bd05280da7393d0c8106d46cc6d7"}, {file = "azure_ai_contentsafety-1.0.0-py3-none-any.whl", hash = "sha256:e1c5574a541f9290fdd071d23535e14b1f463af231a6f0ac0f917e125f0463cf"}, @@ -383,6 +397,7 @@ description = "Microsoft Corporation Azure AI Content Understanding Client Libra optional = false python-versions = ">=3.9" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "azure_ai_contentunderstanding-1.2.0b2-py3-none-any.whl", hash = "sha256:284885c9a45ef50f3938714cb6636c675c1ce9436dc0d561ff425fc124990379"}, {file = "azure_ai_contentunderstanding-1.2.0b2.tar.gz", hash = "sha256:0ccef3c8087759ca788aabcc9af7b22cd8ada2df0236bf63563f4974c2d8cfcd"}, @@ -400,6 +415,7 @@ description = "Microsoft Azure AI Inference Client Library for Python" optional = false python-versions = ">=3.8" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "azure_ai_inference-1.0.0b9-py3-none-any.whl", hash = "sha256:49823732e674092dad83bb8b0d1b65aa73111fab924d61349eb2a8cdc0493990"}, {file = "azure_ai_inference-1.0.0b9.tar.gz", hash = "sha256:1feb496bd84b01ee2691befc04358fa25d7c344d8288e99364438859ad7cd5a4"}, @@ -421,6 +437,7 @@ description = "Microsoft Corporation Azure AI Projects Client Library for Python optional = false python-versions = ">=3.9" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "azure_ai_projects-2.4.0-py3-none-any.whl", hash = "sha256:d9a9504b3a498d7300d23411f09d49096e2a184473250d6070093828b7b67e49"}, {file = "azure_ai_projects-2.4.0.tar.gz", hash = "sha256:121154f70cf7de350a38a75e1da710f4e98b7d8114bc3b1ea25124469e34186f"}, @@ -441,6 +458,7 @@ description = "Microsoft Azure Core Library for Python" optional = false python-versions = ">=3.10" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "azure_core-1.41.0-py3-none-any.whl", hash = "sha256:522b4011e8180b1a3dcd2024396a4e7fe9ac37fb8597db47163d230b5efe892d"}, {file = "azure_core-1.41.0.tar.gz", hash = "sha256:f46ff5dfcd230f25cf1c19e8a34b8dc08a337b2503e268bb600a16c00db8ad5a"}, @@ -461,6 +479,7 @@ description = "Microsoft Azure Identity Library for Python" optional = false python-versions = ">=3.9" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "azure_identity-1.25.3-py3-none-any.whl", hash = "sha256:f4d0b956a8146f30333e071374171f3cfa7bdb8073adb8c3814b65567aa7447c"}, {file = "azure_identity-1.25.3.tar.gz", hash = "sha256:ab23c0d63015f50b630ef6c6cf395e7262f439ce06e5d07a64e874c724f8d9e6"}, @@ -480,6 +499,7 @@ description = "Microsoft Corporation Azure Search Documents Client Library for P optional = false python-versions = ">=3.9" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "azure_search_documents-12.0.0-py3-none-any.whl", hash = "sha256:d88114e4179cd753845711042380a4571e7faa8619addf5e017928ebe37fc0d1"}, {file = "azure_search_documents-12.0.0.tar.gz", hash = "sha256:8e6d73ec0ed1623083435b757e34324db65d72d4e09cca061a59fc7e90c8ddbc"}, @@ -497,6 +517,7 @@ description = "Microsoft Azure Blob Storage Client Library for Python" optional = false python-versions = ">=3.9" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "azure_storage_blob-12.30.0-py3-none-any.whl", hash = "sha256:d415ac50b67a8da6b3ae7e9f1014b1b55cd7aafa0b8d4ca9b380568dc7360423"}, {file = "azure_storage_blob-12.30.0.tar.gz", hash = "sha256:2cd74d4d5731e5eb6b8d5c5056ee115a5e88f8fdf22517b739836fda685018be"}, @@ -518,11 +539,48 @@ description = "Function decoration for backoff and retry" optional = false python-versions = ">=3.7,<4.0" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8"}, {file = "backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba"}, ] +[[package]] +name = "baml-cli" +version = "0.1.0" +description = "The BAML CLI, distributed in Python" +optional = false +python-versions = "<4.0,>=3.8" +groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" +files = [ + {file = "baml_cli-0.1.0-py3-none-any.whl", hash = "sha256:3e0630a10bf940c539e57fc37daf70ea6c097a7feda423f01822a936c2439cdb"}, + {file = "baml_cli-0.1.0.tar.gz", hash = "sha256:ebc3c325f167282918d397e0597dadf2323f463be7be1249b8664a6d2c48166c"}, +] + +[package.dependencies] +baml_py = ">=0.1.0" + +[[package]] +name = "baml-py" +version = "0.220.0" +description = "BAML python bindings (pyproject.toml)" +optional = false +python-versions = "*" +groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" +files = [ + {file = "baml_py-0.220.0-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:edf4112d1b1da5765ebfcadab7f9eee48ac337e5b06ff8286c970e27cd87e28e"}, + {file = "baml_py-0.220.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:5e72466c12d9b6597d3dbe16a364699307911483bffc07e0b66d701cdefecf6d"}, + {file = "baml_py-0.220.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1da0548281e0d21d56886a46aaeef0680e660fc1b36a9bdbc11d04efad96cfcc"}, + {file = "baml_py-0.220.0-cp38-abi3-manylinux_2_24_aarch64.whl", hash = "sha256:0895a3633b061505a3d48d259ca6a480dd37f2a9dade6ea3bf5b8fb1f7632d11"}, + {file = "baml_py-0.220.0-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:52ba7755026322fd73dd70087c09745aae86df47306b1dde06cf731266b55c34"}, + {file = "baml_py-0.220.0-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:a240cdc76455f8bd8aa5a5b3ad805369edb27320111bb915df543c55a846b916"}, + {file = "baml_py-0.220.0-cp38-abi3-win_amd64.whl", hash = "sha256:a5b6d1374eec1225bc76444fc2e5dbb0b7f14ed5a1a043e20beef778475ff8d4"}, + {file = "baml_py-0.220.0-cp38-abi3-win_arm64.whl", hash = "sha256:76fe51528ac5cd07ea2bd8ab2ccd0fd302cd9fc1f5c5c3236c35edfede2d8b48"}, + {file = "baml_py-0.220.0-pp39-pypy39_pp73-manylinux_2_24_aarch64.whl", hash = "sha256:04d286c6772c2d8f20e58e994ac0e8dfc6b08254a85cc9603f7daf09538feeea"}, +] + [[package]] name = "beautifulsoup4" version = "4.15.0" @@ -530,6 +588,7 @@ description = "Screen-scraping library" optional = false python-versions = ">=3.7.0" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "beautifulsoup4-4.15.0-py3-none-any.whl", hash = "sha256:d6f88de62e1d4e38ecb1077eb9724cd0eff29d2a08ca16a401e9b9e93f117cf9"}, {file = "beautifulsoup4-4.15.0.tar.gz", hash = "sha256:288e3ca7d54b06f2ac191970bc275c1939cb46d450b255bf6718b04aa37ab4f7"}, @@ -553,6 +612,7 @@ description = "The bidirectional mapping library for Python." optional = false python-versions = ">=3.8" groups = ["metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "bidict-0.23.1-py3-none-any.whl", hash = "sha256:5dae8d4d79b552a71cbabc7deb25dfe8ce710b17ff41711e13010ead2abfc3e5"}, {file = "bidict-0.23.1.tar.gz", hash = "sha256:03069d763bc387bbd20e7d49914e75fc4132a41937fa3405417e1a5a2d006d71"}, @@ -565,6 +625,7 @@ description = "The uncompromising code formatter." optional = false python-versions = ">=3.9" groups = ["dev"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "black-24.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6668650ea4b685440857138e5fe40cde4d652633b1bdffc62933d0db4ed9812"}, {file = "black-24.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1c536fcf674217e87b8cc3657b81809d3c085d7bf3ef262ead700da345bfa6ea"}, @@ -610,6 +671,7 @@ description = "Fast, simple object-to-object and broadcast signaling" optional = false python-versions = ">=3.9" groups = ["metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc"}, {file = "blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf"}, @@ -622,6 +684,7 @@ description = "Python bindings for the Brotli compression library" optional = false python-versions = "*" groups = ["metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "brotli-1.2.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:99cfa69813d79492f0e5d52a20fd18395bc82e671d5d40bd5a91d13e75e468e8"}, {file = "brotli-1.2.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:3ebe801e0f4e56d17cd386ca6600573e3706ce1845376307f5d2cbd32149b69a"}, @@ -732,6 +795,7 @@ description = "Dummy package for Beautiful Soup (beautifulsoup4)" optional = false python-versions = "*" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "bs4-0.0.2-py2.py3-none-any.whl", hash = "sha256:abf8742c0805ef7f662dce4b51cca104cffe52b835238afc169142ab9b3fbccc"}, {file = "bs4-0.0.2.tar.gz", hash = "sha256:a48685c58f50fe127722417bae83fe6badf500d54b55f7e39ffe43b798653925"}, @@ -747,6 +811,7 @@ description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main", "metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "certifi-2026.7.22-py3-none-any.whl", hash = "sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775"}, {file = "certifi-2026.7.22.tar.gz", hash = "sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55"}, @@ -861,7 +926,7 @@ files = [ {file = "cffi-2.1.0-cp315-cp315t-win_arm64.whl", hash = "sha256:cbb7640ce37159548d2147b5b8c241f962143d4c71231431820783f4dc78f210"}, {file = "cffi-2.1.0.tar.gz", hash = "sha256:efc1cdd798b1aaf39b4610bba7aad28c9bea9b910f25c784ccf9ec1fa719d1f9"}, ] -markers = {main = "platform_python_implementation != \"PyPy\"", metrics = "(platform_python_implementation == \"CPython\" or implementation_name == \"pypy\") and (sys_platform == \"win32\" or implementation_name == \"pypy\")"} +markers = {main = "(sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\") and platform_python_implementation != \"PyPy\"", metrics = "(sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\") and (platform_python_implementation == \"CPython\" and sys_platform == \"win32\" or implementation_name == \"pypy\")"} [package.dependencies] pycparser = {version = "*", markers = "implementation_name != \"PyPy\""} @@ -873,6 +938,7 @@ description = "The Real First Universal Charset Detector. Open, modern and activ optional = false python-versions = ">=3.7" groups = ["main", "metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "charset_normalizer-3.4.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd6280cf040f233bd7d3407b743b4b4c74f70e8e1c4199cb112a62c941c0772a"}, {file = "charset_normalizer-3.4.9-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa99adc8f081b475a12843953db36831eaf83ec33eb46a90629ca6a5de45a616"}, @@ -976,6 +1042,7 @@ description = "Composable command line interface toolkit" optional = false python-versions = ">=3.10" groups = ["main", "dev", "metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "click-8.4.2-py3-none-any.whl", hash = "sha256:e6f9f66136c816745b9d65817da91d61d957fb16e02e4dcd0552553c5a197b76"}, {file = "click-8.4.2.tar.gz", hash = "sha256:9a6cea6e60b17ebe0a44c5cc636d94f09bd66142c1cd7d8b4cd731c4917a15f6"}, @@ -995,7 +1062,7 @@ files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] -markers = {main = "platform_system == \"Windows\" or sys_platform == \"win32\"", dev = "platform_system == \"Windows\" or sys_platform == \"win32\"", metrics = "platform_system == \"Windows\""} +markers = {main = "(sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\") and (platform_system == \"Windows\" or sys_platform == \"win32\")", dev = "(sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\") and (platform_system == \"Windows\" or sys_platform == \"win32\")", metrics = "(sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\") and platform_system == \"Windows\""} [[package]] name = "configargparse" @@ -1004,6 +1071,7 @@ description = "A drop-in replacement for argparse that allows options to also be optional = false python-versions = ">=3.6" groups = ["metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "configargparse-1.7.5-py3-none-any.whl", hash = "sha256:1e63fdffedf94da9cd435fc13a1cd24777e76879dd2343912c1f871d4ac8c592"}, {file = "configargparse-1.7.5.tar.gz", hash = "sha256:e3f9a7bb6be34d66b2e3c4a2f58e3045f8dfae47b0dc039f87bcfaa0f193fb0f"}, @@ -1020,6 +1088,7 @@ description = "Code coverage measurement for Python" optional = false python-versions = ">=3.10" groups = ["dev"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "coverage-7.15.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9b5bd92ff1ec22e535eab0de75fa6db021992791f461a2aceb7822c625a1187d"}, {file = "coverage-7.15.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:44826758cfe73fcd0e6af5deb4ba6d5417cc1d13df3acb35c93484a11160f846"}, @@ -1124,6 +1193,7 @@ description = "cryptography is a package which provides cryptographic recipes an optional = false python-versions = "!=3.9.0,!=3.9.1,>=3.9" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "cryptography-49.0.0-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:966fe0e9c67490071f14c0d2b1cb2dfb3023c5ce39457343931415f08382f2db"}, {file = "cryptography-49.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:36d1709f992593689b45bda411498d62c6e365f2ca00b84657d4dadd24de16db"}, @@ -1186,6 +1256,7 @@ description = "HuggingFace community-driven open-source library of datasets" optional = false python-versions = ">=3.10.0" groups = ["metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "datasets-5.0.1-py3-none-any.whl", hash = "sha256:9fbf73688f8c18f7529b4fe592abd04015f81d1e58001e4bac73ffb2b39d7cc4"}, {file = "datasets-5.0.1.tar.gz", hash = "sha256:ce22bb851efd7494f08aad33b940803784434f6e77763d00679a0dc45fcf686a"}, @@ -1232,6 +1303,7 @@ description = "serialize all of Python" optional = false python-versions = ">=3.9" groups = ["main", "metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "dill-0.4.1-py3-none-any.whl", hash = "sha256:1e1ce33e978ae97fcfcff5638477032b801c46c7c65cf717f95fbc2248f79a9d"}, {file = "dill-0.4.1.tar.gz", hash = "sha256:423092df4182177d4d8ba8290c8a5b640c66ab35ec7da59ccfa00f6fa3eea5fa"}, @@ -1248,6 +1320,7 @@ description = "Disk Cache -- Disk and file backed persistent cache." optional = false python-versions = ">=3" groups = ["metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "diskcache-5.6.3-py3-none-any.whl", hash = "sha256:5e31b2d5fbad117cc363ebaf6b689474db18a1f6438bc82358b024abd4c2ca19"}, {file = "diskcache-5.6.3.tar.gz", hash = "sha256:2c3a3fa2743d8535d832ec61c2054a1641f41775aa7c556758a109941e33e4fc"}, @@ -1260,6 +1333,7 @@ description = "Distro - an OS platform information API" optional = false python-versions = ">=3.6" groups = ["main", "metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2"}, {file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"}, @@ -1272,6 +1346,7 @@ description = "Parse Python docstrings in reST, Google and Numpydoc format" optional = false python-versions = ">=3.8" groups = ["metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "docstring_parser-0.18.0-py3-none-any.whl", hash = "sha256:b3fcbed555c47d8479be0796ef7e19c2670d428d72e96da63f3a40122860374b"}, {file = "docstring_parser-0.18.0.tar.gz", hash = "sha256:292510982205c12b1248696f44959db3cdd1740237a968ea1e2e7a900eeb2015"}, @@ -1289,6 +1364,7 @@ description = "EcoLogits tracks and estimates the energy consumption and environ optional = false python-versions = "<4,>=3.9" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "ecologits-0.5.2-py3-none-any.whl", hash = "sha256:d228aba35e56ee22080ea205a0955d914384dd4be82a26ed31e9017aa503ea58"}, {file = "ecologits-0.5.2.tar.gz", hash = "sha256:33e9ec145c733306dae86a78a875064fc040f94dcd6b3b7e5dd7de8bfe94ab65"}, @@ -1315,6 +1391,7 @@ description = "Like `typing._eval_type`, but lets older Python versions use newe optional = false python-versions = ">=3.7" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "eval_type_backport-0.4.0-py3-none-any.whl", hash = "sha256:ad5e2a8db71b6696a56eafb938b0f5a337d3217f256b8e158b469422b4772b20"}, {file = "eval_type_backport-0.4.0.tar.gz", hash = "sha256:8397d25e6524c2e67b9576bb0636be27dea2192017711220c534ec2de921e9b0"}, @@ -1330,6 +1407,7 @@ description = "FastAPI framework, high performance, easy to learn, fast to code, optional = false python-versions = ">=3.8" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "fastapi-0.115.14-py3-none-any.whl", hash = "sha256:6c0c8bf9420bd58f565e585036d971872472b4f7d3f6c73b698e10cffdefb3ca"}, {file = "fastapi-0.115.14.tar.gz", hash = "sha256:b1de15cdc1c499a4da47914db35d0e4ef8f1ce62b624e94e0e5824421df99739"}, @@ -1351,6 +1429,7 @@ description = "Python bindings to Rust's UUID library." optional = false python-versions = ">=3.8" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "fastuuid-0.14.0-cp310-cp310-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:6e6243d40f6c793c3e2ee14c13769e341b90be5ef0c23c82fa6515a96145181a"}, {file = "fastuuid-0.14.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:13ec4f2c3b04271f62be2e1ce7e95ad2dd1cf97e94503a3760db739afbd48f00"}, @@ -1439,6 +1518,7 @@ description = "A platform independent file lock." optional = false python-versions = ">=3.10" groups = ["main", "metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "filelock-3.32.0-py3-none-any.whl", hash = "sha256:d396bea984af47333ef05e50eae7eff88c84256de6112aea0ec48a233c064fe3"}, {file = "filelock-3.32.0.tar.gz", hash = "sha256:7be2ad23a14607ccc71808e68fe30848aeace7058ace17852f68e2a68e310402"}, @@ -1451,6 +1531,7 @@ description = "the modular source code checker: pep8 pyflakes and co" optional = false python-versions = ">=3.8.1" groups = ["dev"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "flake8-6.1.0-py2.py3-none-any.whl", hash = "sha256:ffdfce58ea94c6580c77888a86506937f9a1a227dfcd15f245d694ae20a6b6e5"}, {file = "flake8-6.1.0.tar.gz", hash = "sha256:d5b3857f07c030bdb5bf41c7f53799571d75c4491748a3adcd47de929e34cd23"}, @@ -1468,6 +1549,7 @@ description = "A simple framework for building complex web applications." optional = false python-versions = ">=3.9" groups = ["metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "flask-3.1.3-py3-none-any.whl", hash = "sha256:f4bcbefc124291925f1a26446da31a5178f9483862233b23c0c96a20701f670c"}, {file = "flask-3.1.3.tar.gz", hash = "sha256:0ef0e52b8a9cd932855379197dd8f94047b359ca0a78695144304cb45f87c9eb"}, @@ -1492,6 +1574,7 @@ description = "A Flask extension simplifying CORS support" optional = false python-versions = "<4.0,>=3.9" groups = ["metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "flask_cors-6.0.5-py3-none-any.whl", hash = "sha256:68fcf75693e961f3af26683b23c4b9a8fb6b64de17d20d0c37b95e8de7ab2ed8"}, {file = "flask_cors-6.0.5.tar.gz", hash = "sha256:30c5031552cd59f620ac0c8211dac45b345d3b2df310e7721879e4f46ef9c601"}, @@ -1508,6 +1591,7 @@ description = "User authentication and session management for Flask." optional = false python-versions = ">=3.7" groups = ["metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "Flask-Login-0.6.3.tar.gz", hash = "sha256:5e23d14a607ef12806c699590b89d0f0e0d67baeec599d75947bf9c147330333"}, {file = "Flask_Login-0.6.3-py3-none-any.whl", hash = "sha256:849b25b82a436bf830a054e74214074af59097171562ab10bfa999e6b78aae5d"}, @@ -1524,6 +1608,7 @@ description = "A list-like structure which implements collections.abc.MutableSeq optional = false python-versions = ">=3.9" groups = ["main", "metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "frozenlist-1.8.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b37f6d31b3dcea7deb5e9696e529a6aa4a898adc33db82da12e4c60a7c4d2011"}, {file = "frozenlist-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef2b7b394f208233e471abc541cc6991f907ffd47dc72584acee3147899d6565"}, @@ -1664,6 +1749,7 @@ description = "File-system specification" optional = false python-versions = ">=3.10" groups = ["main", "metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "fsspec-2026.6.0-py3-none-any.whl", hash = "sha256:02e0b71817df9b2169dc30a16832045764def1191b43dcff5bb85bdee212d2a1"}, {file = "fsspec-2026.6.0.tar.gz", hash = "sha256:f5bac145310fe30e16e1471bd6840b2d990d609e872251d7e674241822abf01a"}, @@ -1707,6 +1793,7 @@ description = "Coroutine-based network library" optional = false python-versions = ">=3.9" groups = ["metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "gevent-25.5.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:8e5a0fab5e245b15ec1005b3666b0a2e867c26f411c8fe66ae1afe07174a30e9"}, {file = "gevent-25.5.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7b80a37f2fb45ee4a8f7e64b77dd8a842d364384046e394227b974a4e9c9a52"}, @@ -1769,6 +1856,7 @@ description = "HTTP client library for gevent" optional = false python-versions = ">=3.9" groups = ["metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "geventhttpclient-2.3.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25c03073a1136c2b93189488bb1bfc0868d90aa106dd49f15ac964d2454296c6"}, {file = "geventhttpclient-2.3.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e1e711eb91085585f61445c7313e1a0acb159b5dc11327930e673b4899ebd84f"}, @@ -1949,7 +2037,7 @@ files = [ {file = "greenlet-3.5.4-cp315-cp315t-win_arm64.whl", hash = "sha256:08fc36de8442d5c3e95b044550dbea9bf144d31ec0cc58e36fb241cb6ef6a994"}, {file = "greenlet-3.5.4.tar.gz", hash = "sha256:0232ae1de90a8e07867bb127d7a6ba2301e859145489f25cda8a6096dabe1d20"}, ] -markers = {main = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\"", metrics = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\" or platform_python_implementation == \"CPython\""} +markers = {main = "(sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\") and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")", metrics = "(sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\") and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\" or platform_python_implementation == \"CPython\")"} [package.extras] docs = ["Sphinx", "furo"] @@ -1962,6 +2050,7 @@ description = "HTTP/2-based RPC framework" optional = false python-versions = ">=3.10" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "grpcio-1.83.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:fba099b716e73512d61b97f71ea3c31a72abb36904036e316bf4dd148ca8dcc8"}, {file = "grpcio-1.83.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:6755ed67cc3e454d51ae9f6e1915b80d3942fa4de956ef48dacd45ab7f40b727"}, @@ -2029,6 +2118,7 @@ description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" optional = false python-versions = ">=3.8" groups = ["main", "metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86"}, {file = "h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1"}, @@ -2041,6 +2131,7 @@ description = "Pure-Python HTTP/2 protocol implementation" optional = false python-versions = ">=3.10" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "h2-4.4.0-py3-none-any.whl", hash = "sha256:6acffe1aeab79098d7eb0f8385c1add11f2c7a94815f6fa2b7060eeddee3d87c"}, {file = "h2-4.4.0.tar.gz", hash = "sha256:46b551bdcdc7e83cf5c04d0bf93badb8a939bd2287d9fee1abb23a445b9e0580"}, @@ -2057,7 +2148,7 @@ description = "Fast transfer of large files with the Hugging Face Hub." optional = false python-versions = ">=3.8" groups = ["main", "metrics"] -markers = "platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"arm64\" or platform_machine == \"aarch64\"" +markers = "(sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\") and (platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"arm64\" or platform_machine == \"aarch64\")" files = [ {file = "hf_xet-1.5.2-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:4a5ecb9cda8512ba2aa8ee5d37c87a1422992165892d653098c7b90247481c3b"}, {file = "hf_xet-1.5.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8764488197c1d7b1378c8438c18d2eea902e150dbca0b0f0d2d32603fb9b5576"}, @@ -2088,6 +2179,7 @@ description = "Pure-Python HPACK header encoding" optional = false python-versions = ">=3.10" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "hpack-4.2.0-py3-none-any.whl", hash = "sha256:858ac0b02280fa582b5080d68db0899c62a80375e0e5413a74970c5e518b6986"}, {file = "hpack-4.2.0.tar.gz", hash = "sha256:0895cfa3b5531fc65fe439c05eb65144f123bf7a394fcaa56aa423548d8e45c0"}, @@ -2100,6 +2192,7 @@ description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" groups = ["main", "metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55"}, {file = "httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8"}, @@ -2122,6 +2215,7 @@ description = "A collection of framework independent HTTP protocol utils." optional = false python-versions = ">=3.9" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "httptools-0.8.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:bf3b6f807c8541503cecfbb8a8dffb385640d0d96102f3d112aa8740f9b7c826"}, {file = "httptools-0.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:da684f2e1aa2ee9bdcb083f3f3a68c5956750b375bc5df864d3a5f0c42a40b77"}, @@ -2182,6 +2276,7 @@ description = "The next generation HTTP client." optional = false python-versions = ">=3.8" groups = ["main", "metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, @@ -2208,6 +2303,7 @@ description = "Consume Server-Sent Event (SSE) messages with HTTPX." optional = false python-versions = ">=3.9" groups = ["main", "metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "httpx_sse-0.4.3-py3-none-any.whl", hash = "sha256:0ac1c9fe3c0afad2e0ebb25a934a59f4c7823b60792691f779fad2c5568830fc"}, {file = "httpx_sse-0.4.3.tar.gz", hash = "sha256:9b1ed0127459a66014aec3c56bebd93da3c1bc8bb6618c8082039a44889a755d"}, @@ -2220,6 +2316,7 @@ description = "Client library to download and publish models, datasets and other optional = false python-versions = ">=3.8.0" groups = ["main", "metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "huggingface_hub-0.36.2-py3-none-any.whl", hash = "sha256:48f0c8eac16145dfce371e9d2d7772854a4f591bcb56c9cf548accf531d54270"}, {file = "huggingface_hub-0.36.2.tar.gz", hash = "sha256:1934304d2fb224f8afa3b87007d58501acfda9215b334eed53072dd5e815ff7a"}, @@ -2259,6 +2356,7 @@ description = "Pure-Python HTTP/2 framing" optional = false python-versions = ">=3.9" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "hyperframe-6.1.0-py3-none-any.whl", hash = "sha256:b03380493a519fce58ea5af42e4a42317bf9bd425596f7a0835ffce80f1a42e5"}, {file = "hyperframe-6.1.0.tar.gz", hash = "sha256:f630908a00854a7adeabd6382b43923a4c4cd4b821fcb527e6ab9e15382a3b08"}, @@ -2271,6 +2369,7 @@ description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.9" groups = ["main", "metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2"}, {file = "idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848"}, @@ -2286,6 +2385,7 @@ description = "Read metadata from Python packages" optional = false python-versions = ">=3.9" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151"}, {file = "importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb"}, @@ -2310,6 +2410,7 @@ description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.10" groups = ["dev"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12"}, {file = "iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730"}, @@ -2322,6 +2423,7 @@ description = "structured outputs for llm" optional = false python-versions = "<4.0,>=3.9" groups = ["metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "instructor-1.15.4-py3-none-any.whl", hash = "sha256:00e0ecda80fd9746fb6d082d3f9641e193adb1d8849f0775f91519a82aeff968"}, {file = "instructor-1.15.4.tar.gz", hash = "sha256:ea2280c3678d0f6891c4d826104f95624b680e69877113a6345b1d7c9027ba0f"}, @@ -2372,6 +2474,7 @@ description = "An ISO 8601 date/time/duration parser and formatter" optional = false python-versions = ">=3.7" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "isodate-0.7.2-py3-none-any.whl", hash = "sha256:28009937d8031054830160fce6d409ed342816b543597cece116d966c6d99e15"}, {file = "isodate-0.7.2.tar.gz", hash = "sha256:4cd1aa0f43ca76f4a6c6c0292a85f40b35ec2e43e315b59f06e6d32171a953e6"}, @@ -2384,6 +2487,7 @@ description = "A Python utility / library to sort Python imports." optional = false python-versions = ">=3.8.0" groups = ["main", "dev"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, @@ -2399,6 +2503,7 @@ description = "Safely pass data to untrusted environments and back." optional = false python-versions = ">=3.8" groups = ["metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef"}, {file = "itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173"}, @@ -2411,6 +2516,7 @@ description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" groups = ["main", "metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, @@ -2429,6 +2535,7 @@ description = "Fast iterable JSON parser." optional = false python-versions = ">=3.9" groups = ["main", "metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "jiter-0.14.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:02f36a5c700f105ac04a6556fe664a59037a2c200db3b7e88784fac2ddf02531"}, {file = "jiter-0.14.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:41eab6c09ceffb6f0fe25e214b3068146edb1eda3649ca2aee2a061029c7ba2e"}, @@ -2548,6 +2655,7 @@ description = "Lightweight pipelining with Python functions" optional = false python-versions = ">=3.9" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "joblib-1.5.3-py3-none-any.whl", hash = "sha256:5fc3c5039fc5ca8c0276333a188bbd59d6b7ab37fe6632daa76bc7f9ec18e713"}, {file = "joblib-1.5.3.tar.gz", hash = "sha256:8561a3269e6801106863fd0d6d84bb737be9e7631e33aaed3fb9ce5953688da3"}, @@ -2560,6 +2668,7 @@ description = "A package to repair broken json strings" optional = false python-versions = ">=3.10" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "json_repair-0.55.2-py3-none-any.whl", hash = "sha256:08109b093fc9fe99b956b8309f9c1dfd2740637056d93df489cc7b4eb56d4286"}, {file = "json_repair-0.55.2.tar.gz", hash = "sha256:aa5c89692126257efb8e3cf1efe6787387634b5c360fb77313d66f146e980de6"}, @@ -2572,6 +2681,7 @@ description = "Apply JSON-Patches (RFC 6902)" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" groups = ["main", "metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade"}, {file = "jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c"}, @@ -2587,6 +2697,7 @@ description = "A lightweight and powerful JSONPath implementation for Python" optional = false python-versions = ">=3.8" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "jsonpath_python-1.1.6-py3-none-any.whl", hash = "sha256:a1c50afd8d3fbbaf47a4873bc890dcb3c15da96f5c020327977d844d8731a2d4"}, {file = "jsonpath_python-1.1.6.tar.gz", hash = "sha256:dded9932b4ec41fb8726e09c83afa4e6be618f938c2db287cc2a81723c639671"}, @@ -2602,6 +2713,7 @@ description = "Identify specific nodes in a JSON document (RFC 6901) " optional = false python-versions = ">=3.10" groups = ["main", "metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "jsonpointer-3.1.1-py3-none-any.whl", hash = "sha256:8ff8b95779d071ba472cf5bc913028df06031797532f08a7d5b602d8b2a488ca"}, {file = "jsonpointer-3.1.1.tar.gz", hash = "sha256:0b801c7db33a904024f6004d526dcc53bbb8a4a0f4e32bfd10beadf60adf1900"}, @@ -2614,6 +2726,7 @@ description = "jsonref is a library for automatic dereferencing of JSON Referenc optional = false python-versions = ">=3.7" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "jsonref-1.1.0-py3-none-any.whl", hash = "sha256:590dc7773df6c21cbf948b5dac07a72a251db28b0238ceecce0a2abfa8ec30a9"}, {file = "jsonref-1.1.0.tar.gz", hash = "sha256:32fe8e1d85af0fdefbebce950af85590b22b60f9e95443176adbde4e1ecea552"}, @@ -2626,6 +2739,7 @@ description = "An implementation of JSON Schema validation for Python" optional = false python-versions = ">=3.10" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce"}, {file = "jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326"}, @@ -2648,6 +2762,7 @@ description = "The JSON Schema meta-schemas and vocabularies, exposed as a Regis optional = false python-versions = ">=3.9" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe"}, {file = "jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d"}, @@ -2663,6 +2778,7 @@ description = "Building applications with LLMs through composability" optional = false python-versions = "<4.0.0,>=3.10.0" groups = ["main", "metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "langchain-1.2.18-py3-none-any.whl", hash = "sha256:8432d43a65540845ed6f1a783d38d869c4659a6b9405f9a510169ad40d2f7bae"}, {file = "langchain-1.2.18.tar.gz", hash = "sha256:7e829dbf117affadfd2067a0e97b4af20222f535f30fb812a28472d842c1074c"}, @@ -2700,6 +2816,7 @@ description = "An integration package to support Microsoft Foundry (formerly Azu optional = false python-versions = "<4.0,>=3.10.0" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "langchain_azure_ai-1.2.8-py3-none-any.whl", hash = "sha256:b86a6c4e5c76277ec4044de03562e7cc995c21754433793763310c2894dcc756"}, {file = "langchain_azure_ai-1.2.8.tar.gz", hash = "sha256:3d1c9c4d9ebe7e06ba38a886930f6aa91e3635a54164cdf7a8810ac3be819bf1"}, @@ -2735,6 +2852,7 @@ description = "Building applications with LLMs through composability" optional = false python-versions = "<4.0.0,>=3.10.0" groups = ["metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "langchain_classic-1.0.8-py3-none-any.whl", hash = "sha256:1a11ea7fbe630c4f2af2f3873d27718ceac9488cf32d0821030be7cf039a6213"}, {file = "langchain_classic-1.0.8.tar.gz", hash = "sha256:ada0cc341a8a5b80fb24d73bdfaaeb849056ee2d8a41cc468355163fd3667484"}, @@ -2775,6 +2893,7 @@ description = "Community contributed LangChain integrations." optional = false python-versions = "<4.0.0,>=3.10.0" groups = ["metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "langchain_community-0.4.2-py3-none-any.whl", hash = "sha256:84dd8c5122532394d5b6849a5fc9995ef28e4f77227daeb09f24b3d942e9e466"}, {file = "langchain_community-0.4.2.tar.gz", hash = "sha256:a99308160d53d7e9b5965ee665e5173709914338210089fd5788ad724432c21e"}, @@ -2803,6 +2922,7 @@ description = "Building applications with LLMs through composability" optional = false python-versions = "<4.0.0,>=3.10.0" groups = ["main", "metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "langchain_core-1.5.2-py3-none-any.whl", hash = "sha256:a687dd7c3b22c6c1294e1c1eeb61fb6f3a308e6015a1d75b576b3836ad5b5aed"}, {file = "langchain_core-1.5.2.tar.gz", hash = "sha256:2d13ab35b42eec63d4669a483776b8cdd778ee764107149369fb369d84c08c41"}, @@ -2826,6 +2946,7 @@ description = "An integration package connecting Mistral and LangChain" optional = false python-versions = "<4.0.0,>=3.10.0" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "langchain_mistralai-1.1.6-py3-none-any.whl", hash = "sha256:0aa59b313b406ee934dc0431873b0e11849d8d746a6a7e281103c6848a9b1319"}, {file = "langchain_mistralai-1.1.6.tar.gz", hash = "sha256:c2694828adee61a497c571e25eaba1472095cd1d1f260a7fe15a6e60f9ece039"}, @@ -2845,6 +2966,7 @@ description = "An integration package connecting OpenAI and LangChain" optional = false python-versions = "<4.0.0,>=3.10.0" groups = ["main", "metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "langchain_openai-1.4.1-py3-none-any.whl", hash = "sha256:8528bb34cc78fdfd2d895573c7917f9441cbb82db5f18ae0e6b3b75d95bdefb3"}, {file = "langchain_openai-1.4.1.tar.gz", hash = "sha256:6d16be615d997db80294731b8e768783f1fb8e0313668e64acd50cd68acbad20"}, @@ -2862,6 +2984,7 @@ description = "Python bindings for the LangChain agent streaming protocol" optional = false python-versions = "<4.0.0,>=3.10.0" groups = ["main", "metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "langchain_protocol-0.0.18-py3-none-any.whl", hash = "sha256:70b53a86fbf9cedc863555effe44da192ab02d556ddbf2cf95b8873adcf41b5a"}, {file = "langchain_protocol-0.0.18.tar.gz", hash = "sha256:ec3e11782f1ed0c9db38e5a9ed01b0e7a0d3fba406faa8aef6594b73c56a63e6"}, @@ -2877,6 +3000,7 @@ description = "LangChain text splitting utilities" optional = false python-versions = "<4.0.0,>=3.10.0" groups = ["metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "langchain_text_splitters-1.1.2-py3-none-any.whl", hash = "sha256:a2de0d799ff31886429fd6e2e0032df275b60ec817c19059a7b46181cc1c2f10"}, {file = "langchain_text_splitters-1.1.2.tar.gz", hash = "sha256:782a723db0a4746ac91e251c7c1d57fd23636e4f38ed733074e28d7a86f41627"}, @@ -2892,6 +3016,7 @@ description = "Language detection library ported from Google's language-detectio optional = false python-versions = "*" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "langdetect-1.0.9-py2-none-any.whl", hash = "sha256:7cbc0746252f19e76f77c0b1690aadf01963be835ef0cd4b56dddf2a8f1dfc2a"}, {file = "langdetect-1.0.9.tar.gz", hash = "sha256:cbc1fef89f8d062739774bd51eda3da3274006b3661d199c2655f6b3f6d605a0"}, @@ -2907,6 +3032,7 @@ description = "Building stateful, multi-actor applications with LLMs" optional = false python-versions = ">=3.10" groups = ["main", "metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "langgraph-1.1.10-py3-none-any.whl", hash = "sha256:8a4f163f72f4401648d0c11b48ee906947d938ba8cf1f474540fe591534f0d17"}, {file = "langgraph-1.1.10.tar.gz", hash = "sha256:3115beb58203283c98d8752a90c034f3432177d2979a1fe205f76e5f1b744500"}, @@ -2927,6 +3053,7 @@ description = "Library with base interfaces for LangGraph checkpoint savers." optional = false python-versions = ">=3.9" groups = ["main", "metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "langgraph_checkpoint-2.1.2-py3-none-any.whl", hash = "sha256:911ebffb069fd01775d4b5184c04aaafc2962fcdf50cf49d524cd4367c4d0c60"}, {file = "langgraph_checkpoint-2.1.2.tar.gz", hash = "sha256:112e9d067a6eff8937caf198421b1ffba8d9207193f14ac6f89930c1260c06f9"}, @@ -2943,6 +3070,7 @@ description = "Library with a Postgres implementation of LangGraph checkpoint sa optional = false python-versions = ">=3.9" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "langgraph_checkpoint_postgres-2.0.25-py3-none-any.whl", hash = "sha256:cf1248a58fe828c9cfc36ee57ff118d7799ce214d4b35718e57ec98407130fb5"}, {file = "langgraph_checkpoint_postgres-2.0.25.tar.gz", hash = "sha256:916b80f73a641a589301f6c54414974768b6d646d82db7b301ff8d47105c3613"}, @@ -2961,6 +3089,7 @@ description = "Library with high-level APIs for creating and executing LangGraph optional = false python-versions = ">=3.10" groups = ["main", "metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "langgraph_prebuilt-1.0.13-py3-none-any.whl", hash = "sha256:7055e9fad41fbd3593800aed0aea0a6e974b17f33ed51b80d3d3a031212dd7c0"}, {file = "langgraph_prebuilt-1.0.13.tar.gz", hash = "sha256:ad219782a80e1718e7e7794de49e0ae307111d45cbcffab9a52725a66a609456"}, @@ -2977,6 +3106,7 @@ description = "SDK for interacting with LangGraph API" optional = false python-versions = ">=3.10" groups = ["main", "metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "langgraph_sdk-0.3.15-py3-none-any.whl", hash = "sha256:3838773acf7456d158165385d49f48f1e856f28b56ccd99ea139a8f27004815d"}, {file = "langgraph_sdk-0.3.15.tar.gz", hash = "sha256:29e805003d2c6e296823dd71992610976fd0428cefaa8b3304fd91f2247037de"}, @@ -2993,6 +3123,7 @@ description = "Client library to connect to the LangSmith Observability and Eval optional = false python-versions = ">=3.10" groups = ["main", "metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "langsmith-0.10.11-py3-none-any.whl", hash = "sha256:37ac446cd5655393738689e6205f9b8a54e6665405faf007f2eb85fe9ce51180"}, {file = "langsmith-0.10.11.tar.gz", hash = "sha256:47f8c4278933a0db2c1a22db06ff57b2e0c67c00f9eb71f8495eed484d5d7383"}, @@ -3035,6 +3166,7 @@ description = "A fast and thorough lazy object proxy." optional = false python-versions = ">=3.9" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "lazy_object_proxy-1.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:61d5e3310a4aa5792c2b599a7a78ccf8687292c8eb09cf187cca8f09cf6a7519"}, {file = "lazy_object_proxy-1.12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1ca33565f698ac1aece152a10f432415d1a2aa9a42dfe23e5ba2bc255ab91f6"}, @@ -3089,6 +3221,7 @@ description = "Library to easily interface with LLM API providers" optional = false python-versions = "<3.15,>=3.10" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "litellm-1.94.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:e00514eb1ca8cd7b4352875ea144b733835b712e764e2b8d8212bda754babf4e"}, {file = "litellm-1.94.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:5b905506b5cdedf1bcd74651582b79ba4dd6bf020f3eba2edf9e17695ce5277f"}, @@ -3142,6 +3275,7 @@ description = "Developer-friendly load testing framework" optional = false python-versions = ">=3.10" groups = ["metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "locust-2.39.1-py3-none-any.whl", hash = "sha256:fd5148f2f1a4ed34aee968abc4393674e69d1b5e1b54db50a397f6eb09ce0b04"}, {file = "locust-2.39.1.tar.gz", hash = "sha256:6bdd19e27edf9a1c84391d6cf6e9a737dfb832be7dfbf39053191ae31b9cc498"}, @@ -3175,6 +3309,7 @@ description = "Locust Cloud" optional = false python-versions = ">=3.10" groups = ["metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "locust_cloud-1.30.0-py3-none-any.whl", hash = "sha256:2324b690efa1bfc8d1871340276953cf265328bd6333e07a5ba8ff7dc5e99e6c"}, {file = "locust_cloud-1.30.0.tar.gz", hash = "sha256:324ae23754d49816df96d3f7472357a61cd10e56cebcb26e2def836675cb3c68"}, @@ -3194,6 +3329,7 @@ description = "Powerful and Pythonic XML processing library combining libxml2/li optional = false python-versions = ">=3.8" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "lxml-6.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:09dd5b7075dc2f7709654a46543ba1ea3c2e217b2ed8fbd413a8a945a0f40f60"}, {file = "lxml-6.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f6ac4ef4d82dff54670227a69c67782ae0b811b5cf6b17954f1e8f7502fc0d1d"}, @@ -3344,6 +3480,7 @@ description = "A super-fast templating language that borrows the best ideas from optional = false python-versions = ">=3.8" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "mako-1.3.12-py3-none-any.whl", hash = "sha256:8f61569480282dbf557145ce441e4ba888be453c30989f879f0d652e39f53ea9"}, {file = "mako-1.3.12.tar.gz", hash = "sha256:9f778e93289bd410bb35daadeb4fc66d95a746f0b75777b942088b7fd7af550a"}, @@ -3364,6 +3501,7 @@ description = "Python port of markdown-it. Markdown parsing, done right!" optional = false python-versions = ">=3.10" groups = ["metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "markdown_it_py-4.2.0-py3-none-any.whl", hash = "sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a"}, {file = "markdown_it_py-4.2.0.tar.gz", hash = "sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49"}, @@ -3388,6 +3526,7 @@ description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.9" groups = ["main", "metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559"}, {file = "markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419"}, @@ -3487,6 +3626,7 @@ description = "McCabe checker, plugin for flake8" optional = false python-versions = ">=3.6" groups = ["main", "dev"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, @@ -3499,6 +3639,7 @@ description = "Markdown URL utilities" optional = false python-versions = ">=3.7" groups = ["metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, @@ -3511,6 +3652,7 @@ description = "Python Client SDK for the Mistral AI API." optional = false python-versions = ">=3.10" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "mistralai-2.8.0-py3-none-any.whl", hash = "sha256:1514a776946f2a7fa04281a11bcccb218397a2bf0fdc892979c8900b422e13df"}, {file = "mistralai-2.8.0.tar.gz", hash = "sha256:fea2d131850bbe010133494e9eb065624afe752dd032a7260ba507ab5a36953a"}, @@ -3545,6 +3687,7 @@ description = "Python library for arbitrary-precision floating-point arithmetic" optional = false python-versions = "*" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c"}, {file = "mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f"}, @@ -3563,6 +3706,7 @@ description = "The Microsoft Authentication Library (MSAL) for Python library en optional = false python-versions = ">=3.9" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "msal-1.37.0-py3-none-any.whl", hash = "sha256:dd17e95a7c71bce75e8108113438ba7c4a086b3bcad4f57a8c09b7af3d753c2d"}, {file = "msal-1.37.0.tar.gz", hash = "sha256:1b1672a33ee467c1d70b341bb16cafd51bb3c817147a95b93263794b03971bec"}, @@ -3583,6 +3727,7 @@ description = "Microsoft Authentication Library extensions (MSAL EX) provides a optional = false python-versions = ">=3.9" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "msal_extensions-1.3.1-py3-none-any.whl", hash = "sha256:96d3de4d034504e969ac5e85bae8106c8373b5c6568e4c8fa7af2eca9dbe6bca"}, {file = "msal_extensions-1.3.1.tar.gz", hash = "sha256:c5b0fd10f65ef62b5f1d62f4251d51cbcaf003fcedae8c91b040a488614be1a4"}, @@ -3601,6 +3746,7 @@ description = "MessagePack serializer" optional = false python-versions = ">=3.10" groups = ["metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "msgpack-1.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8c7b398c56ff125feae96c2737abfec5595f1fa0aa186df60c56040b8accb95c"}, {file = "msgpack-1.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1548006a91aa93c5da81f3bdcebc1a0d10cea2d25969754fbe848da622b2b895"}, @@ -3677,6 +3823,7 @@ description = "multidict implementation" optional = false python-versions = ">=3.9" groups = ["main", "metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "multidict-6.7.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c93c3db7ea657dd4637d57e74ab73de31bccefe144d3d4ce370052035bc85fb5"}, {file = "multidict-6.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:974e72a2474600827abaeda71af0c53d9ebbc3c2eb7da37b37d7829ae31232d8"}, @@ -3833,6 +3980,7 @@ description = "better multiprocessing and multithreading in Python" optional = false python-versions = ">=3.9" groups = ["metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "multiprocess-0.70.19-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:02e5c35d7d6cd2bdc89c1858867f7bde4012837411023a4696c148c1bdd7c80e"}, {file = "multiprocess-0.70.19-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:79576c02d1207ec405b00cabf2c643c36070800cca433860e14539df7818b2aa"}, @@ -3862,6 +4010,7 @@ description = "Type system extensions for programs checked with the mypy type ch optional = false python-versions = ">=3.8" groups = ["dev"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, @@ -3874,6 +4023,7 @@ description = "Extremely lightweight compatibility layer between dataframe libra optional = false python-versions = ">=3.10" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "narwhals-2.24.0-py3-none-any.whl", hash = "sha256:42fdedf44e5b2ca7505630d45b4ac3058f38d8485cba9fe1652ca23152df7489"}, {file = "narwhals-2.24.0.tar.gz", hash = "sha256:b5c0f684ccd9d7475b564111e319a4964abcf2baf79d3cf6b1003d06ac9b828d"}, @@ -3900,6 +4050,7 @@ description = "Patch asyncio to allow nested event loops" optional = false python-versions = ">=3.5" groups = ["metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c"}, {file = "nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe"}, @@ -3912,7 +4063,7 @@ description = "Python package for creating and manipulating graphs and networks" optional = false python-versions = ">=3.11" groups = ["main", "metrics"] -markers = "python_version == \"3.14\"" +markers = "(sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\") and python_version >= \"3.14\"" files = [ {file = "networkx-3.6-py3-none-any.whl", hash = "sha256:cdb395b105806062473d3be36458d8f1459a4e4b98e236a66c3a48996e07684f"}, {file = "networkx-3.6.tar.gz", hash = "sha256:285276002ad1f7f7da0f7b42f004bcba70d381e936559166363707fdad3d72ad"}, @@ -3936,7 +4087,7 @@ description = "Python package for creating and manipulating graphs and networks" optional = false python-versions = "!=3.14.1,>=3.11" groups = ["main", "metrics"] -markers = "python_version < \"3.14\"" +markers = "(sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\") and python_version <= \"3.13\"" files = [ {file = "networkx-3.6.1-py3-none-any.whl", hash = "sha256:d47fbf302e7d9cbbb9e2555a0d267983d2aa476bac30e90dfbe5669bd57f3762"}, {file = "networkx-3.6.1.tar.gz", hash = "sha256:26b7c357accc0c8cde558ad486283728b65b6a95d85ee1cd66bafab4c8168509"}, @@ -3960,6 +4111,7 @@ description = "Fundamental package for array computing in Python" optional = false python-versions = ">=3.12" groups = ["main", "metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "numpy-2.5.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2c889b56fe48b1018f764b0eec8df59ab654e9148aa91faa12596043500de277"}, {file = "numpy-2.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ab451b59c5643c570974c43aef780703ef1d3b4965d2be07afd530615a9358d1"}, @@ -4014,6 +4166,7 @@ description = "The official Python library for the openai API" optional = false python-versions = ">=3.10" groups = ["main", "metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "openai-2.50.0-py3-none-any.whl", hash = "sha256:90bdddcc5a2fa529b350fac9c5780d87e5c361dcc6090ab57b0d470b0d7af7fa"}, {file = "openai-2.50.0.tar.gz", hash = "sha256:5128f7caf4a6b01aefd6e7e93efe170a2c3427b8de286b9af5cdff3aa47e02c8"}, @@ -4044,6 +4197,7 @@ description = "OpenTelemetry Python API" optional = false python-versions = ">=3.9" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "opentelemetry_api-1.39.1-py3-none-any.whl", hash = "sha256:2edd8463432a7f8443edce90972169b195e7d6a05500cd29e6d13898187c9950"}, {file = "opentelemetry_api-1.39.1.tar.gz", hash = "sha256:fbde8c80e1b937a2c61f20347e91c0c18a1940cecf012d62e65a7caf08967c9c"}, @@ -4060,6 +4214,7 @@ description = "OpenTelemetry Semantic Conventions" optional = false python-versions = ">=3.9" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "opentelemetry_semantic_conventions-0.60b1-py3-none-any.whl", hash = "sha256:9fa8c8b0c110da289809292b0591220d3a7b53c1526a23021e977d68597893fb"}, {file = "opentelemetry_semantic_conventions-0.60b1.tar.gz", hash = "sha256:87c228b5a0669b748c76d76df6c364c369c28f1c465e50f661e39737e84bc953"}, @@ -4076,6 +4231,7 @@ description = "Fast, correct Python JSON library supporting dataclasses, datetim optional = false python-versions = ">=3.10" groups = ["main", "metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "orjson-3.11.9-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:135869ef917b8704ea0a94e01620e0c05021c15c52036e4663baffe75e72f8ce"}, {file = "orjson-3.11.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:115ab5f5f4a0f203cc2a5f0fb09aee503a3f771aa08392949ab5ca230c4fbdbd"}, @@ -4160,6 +4316,7 @@ description = "Fast, correct Python msgpack library supporting dataclasses, date optional = false python-versions = ">=3.10" groups = ["main", "metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "ormsgpack-1.12.2-cp310-cp310-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:c1429217f8f4d7fcb053523bbbac6bed5e981af0b85ba616e6df7cce53c19657"}, {file = "ormsgpack-1.12.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f13034dc6c84a6280c6c33db7ac420253852ea233fc3ee27c8875f8dd651163"}, @@ -4219,6 +4376,7 @@ description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" groups = ["main", "dev", "metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, @@ -4230,7 +4388,8 @@ version = "3.0.5" description = "Powerful data structures for data analysis, time series, and statistics" optional = false python-versions = ">=3.11" -groups = ["metrics"] +groups = ["main", "metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "pandas-3.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2946e77e4a53cd248cbde631a12f0e51c8324ce354c3eba4d20147c1ad6f4282"}, {file = "pandas-3.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:71ecc8fb7ed1a7aa4392316b5309a6347e8e7f832f38fd897846b3a1457a9298"}, @@ -4317,6 +4476,7 @@ description = "Utility library for gitignore style pattern matching of file path optional = false python-versions = ">=3.9" groups = ["dev"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "pathspec-1.1.1-py3-none-any.whl", hash = "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189"}, {file = "pathspec-1.1.1.tar.gz", hash = "sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a"}, @@ -4334,6 +4494,7 @@ description = "Python Imaging Library (fork)" optional = false python-versions = ">=3.10" groups = ["main", "metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "pillow-12.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:6c0016e7b354317c4e9e525b937ac8596c38d2d232b419529b9cd7a1cd46e39a"}, {file = "pillow-12.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bcc33feacfaefce60c12fd500a277533bdc02b10a19f7f6d348763d8140bbba7"}, @@ -4439,6 +4600,7 @@ description = "A small Python package for determining appropriate platform-speci optional = false python-versions = ">=3.10" groups = ["main", "dev", "metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "platformdirs-4.11.0-py3-none-any.whl", hash = "sha256:360ccded2b7fce0af0ff80cc8f5942a1c5d99b0e856033acb030bfc634709e74"}, {file = "platformdirs-4.11.0.tar.gz", hash = "sha256:0555d18370482847566ffabcaa53ad7c6c1c29f195989ae1ed634a05f76ea1e0"}, @@ -4451,6 +4613,7 @@ description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.9" groups = ["dev"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, @@ -4467,6 +4630,7 @@ description = "Wraps the portalocker recipe for easy usage" optional = false python-versions = ">=3.9" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "portalocker-3.2.0-py3-none-any.whl", hash = "sha256:3cdc5f565312224bc570c49337bd21428bba0ef363bbcf58b9ef4a9f11779968"}, {file = "portalocker-3.2.0.tar.gz", hash = "sha256:1f3002956a54a8c3730586c5c77bf18fae4149e07eaf1c29fc3faf4d5a3f89ac"}, @@ -4487,6 +4651,7 @@ description = "A simple Python library for easily displaying tabular data in a v optional = false python-versions = ">=3.10" groups = ["metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "prettytable-3.18.0-py3-none-any.whl", hash = "sha256:b3346e0e6f79180833aebaac088ae926340586cf6d7d991b9eb125b65f72313a"}, {file = "prettytable-3.18.0.tar.gz", hash = "sha256:439217116152244369caf3d9f1caf2f9fe29b03bd79e88d2928c8e718c95d680"}, @@ -4505,6 +4670,7 @@ description = "Accelerated property cache" optional = false python-versions = ">=3.10" groups = ["main", "metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "propcache-0.5.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d5a81be28596d6559f6131ef33e10200de6e17643b3c74ce03f9eb103be6ae8b"}, {file = "propcache-0.5.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29cbaac5ea0212663e6845e04b5e188d5a6ae6dd919810ac835bf1d3b42c3f4c"}, @@ -4636,6 +4802,7 @@ description = "" optional = false python-versions = ">=3.8" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "protobuf-5.29.6-cp310-abi3-win32.whl", hash = "sha256:62e8a3114992c7c647bce37dcc93647575fc52d50e48de30c6fcb28a6a291eb1"}, {file = "protobuf-5.29.6-cp310-abi3-win_amd64.whl", hash = "sha256:7e6ad413275be172f67fdee0f43484b6de5a904cc1c3ea9804cb6fe2ff366eda"}, @@ -4657,6 +4824,7 @@ description = "Cross-platform lib for process and system monitoring." optional = false python-versions = ">=3.6" groups = ["metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "psutil-7.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b"}, {file = "psutil-7.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea"}, @@ -4692,6 +4860,7 @@ description = "PostgreSQL database adapter for Python" optional = false python-versions = ">=3.10" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "psycopg-3.3.4-py3-none-any.whl", hash = "sha256:b6bbc25ccf05c8fad3b061d9db2ef0909a555171b84b07f29458a447253d679a"}, {file = "psycopg-3.3.4.tar.gz", hash = "sha256:e21207764952cff81b6b8bdacad9a3939f2793367fdac2987b3aac36a651b5bc"}, @@ -4717,7 +4886,7 @@ description = "PostgreSQL database adapter for Python -- C optimisation distribu optional = false python-versions = ">=3.10" groups = ["main"] -markers = "implementation_name != \"pypy\"" +markers = "(sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\") and implementation_name != \"pypy\"" files = [ {file = "psycopg_binary-3.3.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b7bfff1ca23732b488cbca3076fc11bc98d520ee122514fdb17a8e20d3338f5a"}, {file = "psycopg_binary-3.3.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:32a6fbf8481e3a370d0d72b860d35948a693cb01281da217f7b2f307636e591a"}, @@ -4783,6 +4952,7 @@ description = "Connection Pool for Psycopg" optional = false python-versions = ">=3.10" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "psycopg_pool-3.3.1-py3-none-any.whl", hash = "sha256:2af5b432941c4c9ad5c87b3fa410aec910ec8f7c122855897983a06c45f2e4b5"}, {file = "psycopg_pool-3.3.1.tar.gz", hash = "sha256:b10b10b7a175d5cc1592147dc5b7eec8a9e0834eb3ed2c4a92c858e2f51eb63c"}, @@ -4801,6 +4971,7 @@ description = "psycopg2 - Python-PostgreSQL Database Adapter" optional = false python-versions = ">=3.9" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "psycopg2_binary-2.9.12-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9b818ceff717f98851a64bffd4c5eb5b3059ae280276dcecc52ac658dcf006a4"}, {file = "psycopg2_binary-2.9.12-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d2fa0d7caca8635c56e373055094eeda3208d901d55dd0ff5abc1d4e47f82b56"}, @@ -4878,6 +5049,7 @@ description = "Python library for Apache Arrow" optional = false python-versions = ">=3.10" groups = ["metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "pyarrow-25.0.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:ce0ca222802087b9a8cb031a6468442cb6b67c290a45a601cac64753d34954d3"}, {file = "pyarrow-25.0.0-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:7d6da02ffc7a3a9bda3b7ded4cc2a27ff73969ab37153f3afd46bbbc1ba4f0f7"}, @@ -4931,6 +5103,7 @@ description = "Python style guide checker" optional = false python-versions = ">=3.8" groups = ["dev"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "pycodestyle-2.11.1-py2.py3-none-any.whl", hash = "sha256:44fe31000b2d866f2e41841b18528a505fbd7fef9017b04eff4e2648a0fadc67"}, {file = "pycodestyle-2.11.1.tar.gz", hash = "sha256:41ba0e7afc9752dfb53ced5489e89f8186be00e599e712660695b7a75ff2663f"}, @@ -4947,7 +5120,7 @@ files = [ {file = "pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992"}, {file = "pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29"}, ] -markers = {main = "platform_python_implementation != \"PyPy\" and implementation_name != \"PyPy\"", metrics = "(platform_python_implementation == \"CPython\" or implementation_name == \"pypy\") and (sys_platform == \"win32\" or implementation_name == \"pypy\") and implementation_name != \"PyPy\""} +markers = {main = "(sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\") and platform_python_implementation != \"PyPy\" and implementation_name != \"PyPy\"", metrics = "(sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\") and (platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and implementation_name != \"PyPy\" or implementation_name == \"pypy\")"} [[package]] name = "pydantic" @@ -4956,6 +5129,7 @@ description = "Data validation using Python type hints" optional = false python-versions = ">=3.9" groups = ["main", "metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba"}, {file = "pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6"}, @@ -4978,6 +5152,7 @@ description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.9" groups = ["main", "metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "pydantic_core-2.46.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:a396dcc17e5a0b164dbe026896245a4fa9ff402edca1dff0be3d53a517f74de4"}, {file = "pydantic_core-2.46.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:da4b951fe36dc7c3a1ccb4e3cd1747c3542b8c9ceede8fc86cae054e764485f5"}, @@ -5111,6 +5286,7 @@ description = "Settings management using Pydantic" optional = false python-versions = ">=3.10" groups = ["main", "metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "pydantic_settings-2.14.2-py3-none-any.whl", hash = "sha256:a20c97b37910b6550d5ea50fbcc2d4187defe58cd57070b73863d069419c9440"}, {file = "pydantic_settings-2.14.2.tar.gz", hash = "sha256:c19dd64b19097f1de80184f0cc7b0272a13ae6e170cbf240a3e27e381ed14a5f"}, @@ -5135,6 +5311,7 @@ description = "passive checker of Python programs" optional = false python-versions = ">=3.8" groups = ["dev"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "pyflakes-3.1.0-py2.py3-none-any.whl", hash = "sha256:4132f6d49cb4dae6819e5379898f2b8cce3c5f23994194c24b77d5da2e36f774"}, {file = "pyflakes-3.1.0.tar.gz", hash = "sha256:a0aae034c444db0071aa077972ba4768d40c830d9539fd45bf4cd3f8f6992efc"}, @@ -5147,6 +5324,7 @@ description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.9" groups = ["metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176"}, {file = "pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f"}, @@ -5162,6 +5340,7 @@ description = "JSON Web Token implementation in Python" optional = false python-versions = ">=3.9" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "pyjwt-2.13.0-py3-none-any.whl", hash = "sha256:66adcc2aff09b3f1bbd95fc1e1577df8ac8723c978552fd43304c8a290ac5728"}, {file = "pyjwt-2.13.0.tar.gz", hash = "sha256:41571c89ca91598c79e8ef18a2d07367d4810fbbd6f637794879baf1b7703423"}, @@ -5180,6 +5359,7 @@ description = "python code static checker" optional = false python-versions = ">=3.7.2" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "pylint-2.17.7-py3-none-any.whl", hash = "sha256:27a8d4c7ddc8c2f8c18aa0050148f89ffc09838142193fdbe98f172781a3ff87"}, {file = "pylint-2.17.7.tar.gz", hash = "sha256:f4fcac7ae74cfe36bc8451e931d8438e4a476c20314b1101c458ad0f05191fad"}, @@ -5205,6 +5385,7 @@ description = "Utilities and helpers for writing Pylint plugins" optional = false python-versions = "<4.0,>=3.9" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "pylint_plugin_utils-0.9.0-py3-none-any.whl", hash = "sha256:16e9b84e5326ba893a319a0323fcc8b4bcc9c71fc654fcabba0605596c673818"}, {file = "pylint_plugin_utils-0.9.0.tar.gz", hash = "sha256:5468d763878a18d5cc4db46eaffdda14313b043c962a263a7d78151b90132055"}, @@ -5220,6 +5401,7 @@ description = "A Pylint plugin to help Pylint understand the Pydantic" optional = false python-versions = ">=3.7" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "pylint_pydantic-0.2.4-py3-none-any.whl", hash = "sha256:452b32992f47e303b432f8940d4077f1d60e8d3325351b8998d846431699111a"}, ] @@ -5236,6 +5418,7 @@ description = "A pure-python PDF library capable of splitting, merging, cropping optional = false python-versions = ">=3.9" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "pypdf-6.14.2-py3-none-any.whl", hash = "sha256:3f07891af76dc002657e04993ab9b4de81de29f9013b9761d0b7968bff12e946"}, {file = "pypdf-6.14.2.tar.gz", hash = "sha256:7873f502fe4385e79539b21d872392dc0c4e3714327c15881cbc7fbfd1f95b25"}, @@ -5257,6 +5440,7 @@ description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.7" groups = ["dev"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, @@ -5278,6 +5462,7 @@ description = "Pytest plugin for measuring coverage." optional = false python-versions = ">=3.7" groups = ["dev"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"}, {file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"}, @@ -5297,6 +5482,7 @@ description = "pytest plugin that allows you to add environment variables." optional = false python-versions = ">=3.8" groups = ["dev"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "pytest_env-1.1.3-py3-none-any.whl", hash = "sha256:aada77e6d09fcfb04540a6e462c58533c37df35fa853da78707b17ec04d17dfc"}, {file = "pytest_env-1.1.3.tar.gz", hash = "sha256:fcd7dc23bb71efd3d35632bde1bbe5ee8c8dc4489d6617fb010674880d96216b"}, @@ -5315,6 +5501,7 @@ description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" groups = ["main", "metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, @@ -5330,6 +5517,7 @@ description = "Create, read, and update Microsoft Word .docx files." optional = false python-versions = ">=3.9" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "python_docx-1.2.0-py3-none-any.whl", hash = "sha256:3fd478f3250fbbbfd3b94fe1e985955737c145627498896a8a6bf81f4baf66c7"}, {file = "python_docx-1.2.0.tar.gz", hash = "sha256:7bc9d7b7d8a69c9c02ca09216118c86552704edc23bac179283f2e38f86220ce"}, @@ -5346,6 +5534,7 @@ description = "Read key-value pairs from a .env file and set them as environment optional = false python-versions = ">=3.10" groups = ["main", "metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a"}, {file = "python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3"}, @@ -5361,6 +5550,7 @@ description = "Engine.IO server and client for Python" optional = false python-versions = ">=3.8" groups = ["metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "python_engineio-4.13.3-py3-none-any.whl", hash = "sha256:1f60ecaf1358190f0e26c48c578a60428dc02a8f1295bc3dbf53d1b31116821f"}, {file = "python_engineio-4.13.3.tar.gz", hash = "sha256:572b7783e341fed21edbc7cea297ccd378dad79265fdde96aa4664420a7c06c9"}, @@ -5382,6 +5572,7 @@ description = "A streaming multipart parser for Python" optional = false python-versions = ">=3.10" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "python_multipart-0.0.22-py3-none-any.whl", hash = "sha256:2b2cd894c83d21bf49d702499531c7bafd057d730c201782048f7945d82de155"}, {file = "python_multipart-0.0.22.tar.gz", hash = "sha256:7340bef99a7e0032613f56dc36027b959fd3b30a787ed62d310e951f7c3a3a58"}, @@ -5394,6 +5585,7 @@ description = "Socket.IO server and client for Python" optional = false python-versions = ">=3.8" groups = ["metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "python_socketio-5.16.3-py3-none-any.whl", hash = "sha256:e7ad14202a5e6448824c7c2f86161d04e13dec05992257df5c709e6a2798c041"}, {file = "python_socketio-5.16.3.tar.gz", hash = "sha256:89b136f677ae65607a84cecda9b4d6c5377b40a97582c504c25df89af16d520e"}, @@ -5441,7 +5633,7 @@ files = [ {file = "pywin32-312-cp39-cp39-win_amd64.whl", hash = "sha256:dc90147579a905b8635e1b0ec6514967dcb07e6e0d9c42f1477feef14cac23bb"}, {file = "pywin32-312-cp39-cp39-win_arm64.whl", hash = "sha256:02ebca0f0242b75292e218065004310d6a477407c09fa449bfe4f6022bc0c0fc"}, ] -markers = {main = "platform_system == \"Windows\"", metrics = "sys_platform == \"win32\""} +markers = {main = "(sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\") and platform_system == \"Windows\"", metrics = "sys_platform == \"win32\""} [[package]] name = "pyyaml" @@ -5450,6 +5642,7 @@ description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" groups = ["main", "metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "PyYAML-6.0.3-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:c2514fceb77bc5e7a2f7adfaa1feb2fb311607c9cb518dbc378688ec73d8292f"}, {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c57bb8c96f6d1808c030b1687b9b5fb476abaa47f0db9c0101f5e9f394e97f4"}, @@ -5533,6 +5726,7 @@ description = "Python bindings for 0MQ" optional = false python-versions = ">=3.8" groups = ["metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "pyzmq-27.1.0-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:508e23ec9bc44c0005c4946ea013d9317ae00ac67778bd47519fdf5a0e930ff4"}, {file = "pyzmq-27.1.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:507b6f430bdcf0ee48c0d30e734ea89ce5567fd7b8a0f0044a369c176aa44556"}, @@ -5638,6 +5832,7 @@ description = "Client library for the Qdrant vector search engine" optional = false python-versions = ">=3.10" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "qdrant_client-1.18.0-py3-none-any.whl", hash = "sha256:093aa8cf8a420ee3ad2a68b007e1378d7992b2600e0b53c193fc172674f659cd"}, {file = "qdrant_client-1.18.0.tar.gz", hash = "sha256:52e8ece1a7d40519801bf0b70713bfa0f6b7ae28c7275bbe0b0286fbed7f6db4"}, @@ -5667,6 +5862,7 @@ description = "Evaluation framework for RAG and LLM applications" optional = false python-versions = ">=3.9" groups = ["metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "ragas-0.4.3-py3-none-any.whl", hash = "sha256:ef1d75f674c294e9a6e7d8e9ad261b6bf4697dad1c9cbd1a756ba7a6b4849a38"}, {file = "ragas-0.4.3.tar.gz", hash = "sha256:1eb1f61dbc8613ad014fdb8d630cbe9a1caec1ea01664a106993cb756128c001"}, @@ -5712,6 +5908,7 @@ description = "JSON Referencing + Python" optional = false python-versions = ">=3.10" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231"}, {file = "referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8"}, @@ -5729,6 +5926,7 @@ description = "Python library for post-extraction refinement of text that may be optional = false python-versions = ">=3.9" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "refinedoc-1.0.1-py3-none-any.whl", hash = "sha256:a2840fefb110b33d84474ef3b5e3b3813a55fb7c8e42a70615f51acb1b3885c7"}, {file = "refinedoc-1.0.1.tar.gz", hash = "sha256:afad515bcbd13e9e7ba40d149d6b2a8a04c88323bc31eef2eddf3e3bbd6e921b"}, @@ -5741,6 +5939,7 @@ description = "Alternative regular expression module, to replace re." optional = false python-versions = ">=3.10" groups = ["main", "metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "regex-2026.7.19-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:555497390743af1a65045fa4527782d10ff5b88970359412baa4a1e628fe393b"}, {file = "regex-2026.7.19-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:343a4504e3fb688c47cad451221ca5d4814f42b1e16c0065bde9cbf7f473bd52"}, @@ -5865,6 +6064,7 @@ description = "Python HTTP for Humans." optional = false python-versions = ">=3.10" groups = ["main", "metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0"}, {file = "requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed"}, @@ -5887,6 +6087,7 @@ description = "A utility belt for advanced users of python-requests" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" groups = ["main", "metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"}, {file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"}, @@ -5902,6 +6103,7 @@ description = "Render rich text, tables, progress bars, syntax highlighting, mar optional = false python-versions = ">=3.8.0" groups = ["metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "rich-14.3.4-py3-none-any.whl", hash = "sha256:07e7adb4690f68864777b1450859253bed81a99a31ac321ac1817b2313558952"}, {file = "rich-14.3.4.tar.gz", hash = "sha256:817e02727f2b25b40ef56f5aa2217f400c8489f79ca8f46ea2b70dd5e14558a9"}, @@ -5921,6 +6123,7 @@ description = "Python bindings to Rust's persistent data structures (rpds)" optional = false python-versions = ">=3.11" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "rpds_py-2026.6.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:7b689145a1485c335569bd056464f3243a29af7ed3871c7be31ad624ba239bc7"}, {file = "rpds_py-2026.6.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:db08f45aecde626498fb3df07bcf6d2ec040af42e859a4f5040d79c200342911"}, @@ -6047,6 +6250,7 @@ description = "" optional = false python-versions = ">=3.10" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "safetensors-0.8.0-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:c554f85858e05226d3c2828e32395e677434685d6d94594a41643361c5e837f0"}, {file = "safetensors-0.8.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:c80201d22cbf405b80647a60ada77bba06c8fba2da2743ba1e89cdcc39a81f25"}, @@ -6089,6 +6293,7 @@ description = "A set of python modules for machine learning and data mining" optional = false python-versions = ">=3.11" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "scikit_learn-1.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9db6f4d34e68c8899e4cab27fdf8eafe6ed21f2ba52ceb25ea250cd237f8e47b"}, {file = "scikit_learn-1.9.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:f401448645a3e7bc115aa3c094097865155b34bff1cba8101857d9104e99074c"}, @@ -6146,6 +6351,7 @@ description = "Graph algorithms" optional = false python-versions = ">=3.10" groups = ["metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "scikit_network-0.33.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bfcb4b945286154800eb61998fae85675d9a05ee78e776a82e7d222bf978e171"}, {file = "scikit_network-0.33.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:854d33b2056dff4fca1b956b46955f3d668148ba63c5cc51a5c497e7ee765def"}, @@ -6184,6 +6390,7 @@ description = "Fundamental algorithms for scientific computing in Python" optional = false python-versions = ">=3.12" groups = ["main", "metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "scipy-1.18.0-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:7bd21faaf5a1a3b2eff922d02db5f191b99a6518db9078a8fb23169f6d22259a"}, {file = "scipy-1.18.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:265915e79107de9f946b855e50d7470d5893ec3f54b342e1aa6201cbdcd8bb6b"}, @@ -6243,6 +6450,7 @@ description = "Most extensible Python build backend with support for C/C++ exten optional = false python-versions = ">=3.10" groups = ["main", "metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "setuptools-83.0.0-py3-none-any.whl", hash = "sha256:29b23c360f22f414dc7336bb39178cc7bcbf6021ed2733cde173f09dba19abb3"}, {file = "setuptools-83.0.0.tar.gz", hash = "sha256:025bccbbf0fa05b6192bc64ae1e7b16e001fd6d6d4d5de03c97b1c1ade523bef"}, @@ -6264,6 +6472,7 @@ description = "Tool to Detect Surrounding Shell" optional = false python-versions = ">=3.7" groups = ["metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"}, {file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"}, @@ -6276,6 +6485,7 @@ description = "Simple WebSocket server and client for Python" optional = false python-versions = ">=3.6" groups = ["metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "simple_websocket-1.1.0-py3-none-any.whl", hash = "sha256:4af6069630a38ed6c561010f0e11a5bc0d4ca569b36306eb257cd9a192497c8c"}, {file = "simple_websocket-1.1.0.tar.gz", hash = "sha256:7939234e7aa067c534abdab3a9ed933ec9ce4691b0713c78acb195560aa52ae4"}, @@ -6295,6 +6505,7 @@ description = "Python 2 and 3 compatibility utilities" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" groups = ["main", "metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, @@ -6307,6 +6518,7 @@ description = "Sniff out which async library your code is running under" optional = false python-versions = ">=3.7" groups = ["main", "metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, @@ -6319,6 +6531,7 @@ description = "A modern CSS selector implementation for Beautiful Soup." optional = false python-versions = ">=3.10" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "soupsieve-2.9.1-py3-none-any.whl", hash = "sha256:4f4477399246b7a0c720a88ca2454b11cd6bb9ae4c9d170140786e916776c14c"}, {file = "soupsieve-2.9.1.tar.gz", hash = "sha256:c33e6605bbc71dd628b00c632d58ae607c22bade247e52553928f83bbb75b4ba"}, @@ -6331,6 +6544,7 @@ description = "Database Abstraction Library" optional = false python-versions = ">=3.7" groups = ["main", "metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "sqlalchemy-2.0.51-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e8203d2fbd5c6254692ef0a72c740d75b2f3c7ca345404f4c1a4604813c77c0"}, {file = "sqlalchemy-2.0.51-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1af05726b3d0cdba1c55284bf408fd3b792e690fe2399bfb8304565551cda652"}, @@ -6428,6 +6642,7 @@ description = "The little ASGI library that shines." optional = false python-versions = ">=3.9" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "starlette-0.46.2-py3-none-any.whl", hash = "sha256:595633ce89f8ffa71a015caed34a5b2dc1c0cdb3f0f1fbd1e69339cf2abeec35"}, {file = "starlette-0.46.2.tar.gz", hash = "sha256:7f7361f34eed179294600af672f565727419830b54b7b084efe44bb82d2fccd5"}, @@ -6446,6 +6661,7 @@ description = "Computer algebra system (CAS) in Python" optional = false python-versions = ">=3.9" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5"}, {file = "sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517"}, @@ -6464,6 +6680,7 @@ description = "Retry code until it succeeds" optional = false python-versions = ">=3.10" groups = ["main", "metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "tenacity-9.1.4-py3-none-any.whl", hash = "sha256:6095a360c919085f28c6527de529e76a06ad89b23659fa881ae0649b867a9d55"}, {file = "tenacity-9.1.4.tar.gz", hash = "sha256:adb31d4c263f2bd041081ab33b498309a57c77f9acf2db65aadf0898179cf93a"}, @@ -6480,6 +6697,7 @@ description = "threadpoolctl" optional = false python-versions = ">=3.9" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "threadpoolctl-3.6.0-py3-none-any.whl", hash = "sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb"}, {file = "threadpoolctl-3.6.0.tar.gz", hash = "sha256:8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e"}, @@ -6492,6 +6710,7 @@ description = "tiktoken is a fast BPE tokeniser for use with OpenAI's models" optional = false python-versions = ">=3.9" groups = ["main", "metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "tiktoken-0.13.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:47b1df8d73390a24f94980c75158cdd5c56d256f16d55f30cb49c230caba9ba4"}, {file = "tiktoken-0.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7d40c6c5aab171dcd6eb8455bc567bde404bb9def60cdb8c1299cc782b242bb9"}, @@ -6566,6 +6785,7 @@ description = "" optional = false python-versions = ">=3.9" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "tokenizers-0.22.2-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:544dd704ae7238755d790de45ba8da072e9af3eea688f698b137915ae959281c"}, {file = "tokenizers-0.22.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:1e418a55456beedca4621dbab65a318981467a2b188e982a23e117f115ce5001"}, @@ -6608,6 +6828,7 @@ description = "Style preserving TOML library" optional = false python-versions = ">=3.9" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "tomlkit-0.15.1-py3-none-any.whl", hash = "sha256:177a05aece5a8ca5266fd3c448abb47b8d352f09d477d3ca8332db4d89b24304"}, {file = "tomlkit-0.15.1.tar.gz", hash = "sha256:e25bbf38843005246210a12982776f27f99cb9be67160e14434d0c0d21ee1e97"}, @@ -6620,6 +6841,7 @@ description = "Tensors and Dynamic neural networks in Python with strong GPU acc optional = false python-versions = ">=3.10" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "torch-2.9.1+cpu-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:10866c8a48c4aa5ae3f48538dc8a055b99c57d9c6af2bf5dd715374d9d6ddca3"}, {file = "torch-2.9.1+cpu-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:7210713b66943fdbfcc237b2e782871b649123ac5d29f548ce8c85be4223ab38"}, @@ -6673,6 +6895,7 @@ description = "image and video datasets and models for torch deep learning" optional = false python-versions = ">=3.10" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "torchvision-0.24.1+cpu-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:c98e219150beaab521c684cfd5c9ba1b6e80794ca851e60e784bd7f368889961"}, {file = "torchvision-0.24.1+cpu-cp310-cp310-win_amd64.whl", hash = "sha256:f95c3bd0f65b01224f46142536cc02607ce32ef5e905d0c9eb210fa8cc23f522"}, @@ -6711,6 +6934,7 @@ description = "Fast, Extensible Progress Meter" optional = false python-versions = ">=3.8" groups = ["main", "metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "tqdm-4.70.0-py3-none-any.whl", hash = "sha256:7f585706bfddbdebf89daac705b2dfcc16890130727d3197ca62c732b4310953"}, {file = "tqdm-4.70.0.tar.gz", hash = "sha256:55b0b0dbd97462d06ebee91e4dac24ed4d4702be82b24f07e6c1d27e08cea220"}, @@ -6732,6 +6956,7 @@ description = "State-of-the-art Machine Learning for JAX, PyTorch and TensorFlow optional = false python-versions = ">=3.9.0" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "transformers-4.57.6-py3-none-any.whl", hash = "sha256:4c9e9de11333ddfe5114bc872c9f370509198acf0b87a832a0ab9458e2bd0550"}, {file = "transformers-4.57.6.tar.gz", hash = "sha256:55e44126ece9dc0a291521b7e5492b572e6ef2766338a610b9ab5afbb70689d3"}, @@ -6807,6 +7032,7 @@ description = "Typer, build great CLIs. Easy to code. Based on Python type hints optional = false python-versions = ">=3.10" groups = ["metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "typer-0.27.0-py3-none-any.whl", hash = "sha256:6f4b27631e47f077871b7dc30e933ec0131c1390fbe0e387ea5574b5bac9ccf1"}, {file = "typer-0.27.0.tar.gz", hash = "sha256:629bd12ea5d13a17148125d9a264f949eb171fb3f120f9b04d85873cab054fa5"}, @@ -6825,6 +7051,7 @@ description = "Backported and Experimental Type Hints for Python 3.9+" optional = false python-versions = ">=3.9" groups = ["main", "metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8"}, {file = "typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5"}, @@ -6837,6 +7064,7 @@ description = "Runtime typing introspection tools" optional = false python-versions = ">=3.9" groups = ["main", "metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7"}, {file = "typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464"}, @@ -6852,11 +7080,11 @@ description = "Provider of IANA time zone data" optional = false python-versions = ">=2" groups = ["main", "metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\"" files = [ {file = "tzdata-2026.3-py2.py3-none-any.whl", hash = "sha256:dc096730c87af6cab1b171c9d532be840741ff5d459015e7f6947bd7d7e54931"}, {file = "tzdata-2026.3.tar.gz", hash = "sha256:4a1518b8993086a7982523e071643f3c0e5f213e75b21318e78bcabfff9d1415"}, ] -markers = {main = "sys_platform == \"win32\"", metrics = "sys_platform == \"win32\" or sys_platform == \"emscripten\""} [[package]] name = "urllib3" @@ -6865,6 +7093,7 @@ description = "HTTP library with thread-safe connection pooling, file post, and optional = false python-versions = ">=3.10" groups = ["main", "metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897"}, {file = "urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c"}, @@ -6883,6 +7112,7 @@ description = "Fast, drop-in replacement for Python's uuid module, powered by Ru optional = false python-versions = ">=3.10" groups = ["main", "metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "uuid_utils-0.17.0-cp310-cp310-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:d2d9a63a9e6f2416ace8c109043a9280d6b34f34bb2e5421903e149403db40a6"}, {file = "uuid_utils-0.17.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:b776c7fc8755c7de06dd5a22b47c40ae84f67d13277ebb233cc84933ba4dcbcd"}, @@ -6987,6 +7217,7 @@ description = "The lightning-fast ASGI server." optional = false python-versions = ">=3.8" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "uvicorn-0.29.0-py3-none-any.whl", hash = "sha256:2c2aac7ff4f4365c206fd773a39bf4ebd1047c238f8b8268ad996829323473de"}, {file = "uvicorn-0.29.0.tar.gz", hash = "sha256:6a69214c0b6a087462412670b3ef21224fa48cae0e452b5883e8e8bdfdd11dd0"}, @@ -7013,7 +7244,7 @@ description = "Fast implementation of asyncio event loop on top of libuv" optional = false python-versions = ">=3.8.1" groups = ["main"] -markers = "sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\"" +markers = "platform_python_implementation != \"PyPy\" and sys_platform != \"win32\" and sys_platform != \"cygwin\"" files = [ {file = "uvloop-0.22.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ef6f0d4cc8a9fa1f6a910230cd53545d9a14479311e87e3cb225495952eb672c"}, {file = "uvloop-0.22.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7cd375a12b71d33d46af85a3343b35d98e8116134ba404bd657b3b1d15988792"}, @@ -7078,6 +7309,7 @@ description = "Simple, modern and high performance file watching and code reload optional = false python-versions = ">=3.10" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "watchfiles-1.2.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:bb68bf4df85abebe5efddc53cf2075520f243a59868d9b3973278b23e76962a9"}, {file = "watchfiles-1.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c16cb06dd17d43b9d185094268459eac92c9538356f050e55b54e82cf700e1d4"}, @@ -7198,6 +7430,7 @@ description = "Measures the displayed width of unicode strings in a terminal" optional = false python-versions = ">=3.8" groups = ["metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "wcwidth-0.8.2-py3-none-any.whl", hash = "sha256:d63947694a0539a1d51e01eda7caf800c291020e6cdd7e28ad7b14dd33ad4f85"}, {file = "wcwidth-0.8.2.tar.gz", hash = "sha256:91fbef97204b96a3d4d421609b80340b760cf33e26da123ff243d76b1fda8dda"}, @@ -7210,6 +7443,7 @@ description = "WebSocket client for Python with low level API options" optional = false python-versions = ">=3.9" groups = ["metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "websocket_client-1.9.0-py3-none-any.whl", hash = "sha256:af248a825037ef591efbf6ed20cc5faa03d3b47b9e5a2230a529eeee1c1fc3ef"}, {file = "websocket_client-1.9.0.tar.gz", hash = "sha256:9e813624b6eb619999a97dc7958469217c3176312b3a16a4bd1bc7e08a46ec98"}, @@ -7227,6 +7461,7 @@ description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" optional = false python-versions = ">=3.10" groups = ["main", "metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "websockets-16.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:49ae99bdfcae803a885c926bf14f886196e84925395bb3f568fef5c0f0979d7d"}, {file = "websockets-16.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5bfd1ac19b1b9986a9c95a82d5e23a391ebb09e12c34d7be6094b86efcc35731"}, @@ -7346,6 +7581,7 @@ description = "All stuff related to relationnal database from the WeLearn projec optional = false python-versions = ">=3.10" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "welearn_database-1.5.1-py3-none-any.whl", hash = "sha256:33952c7af224f7d826b8d9bd747d0936be1692648befccd182e4f382aab3b1d4"}, {file = "welearn_database-1.5.1.tar.gz", hash = "sha256:33e1215ffe6ebe26d43fdda94d2ab108e226e5d772340424987603c14a7aa113"}, @@ -7364,6 +7600,7 @@ description = "The comprehensive WSGI web application library." optional = false python-versions = ">=3.9" groups = ["metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "werkzeug-3.1.8-py3-none-any.whl", hash = "sha256:63a77fb8892bf28ebc3178683445222aa500e48ebad5ec77b0ad80f8726b1f50"}, {file = "werkzeug-3.1.8.tar.gz", hash = "sha256:9bad61a4268dac112f1c5cd4630a56ede601b6ed420300677a869083d70a4c44"}, @@ -7382,6 +7619,7 @@ description = "Module for decorators, wrappers and monkey patching." optional = false python-versions = ">=3.8" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "wrapt-1.17.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88bbae4d40d5a46142e70d58bf664a89b6b4befaea7b2ecc14e03cedb8e06c04"}, {file = "wrapt-1.17.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6b13af258d6a9ad602d57d889f83b9d5543acd471eee12eb51f5b01f8eb1bc2"}, @@ -7473,6 +7711,7 @@ description = "Pure-Python WebSocket protocol implementation" optional = false python-versions = ">=3.10" groups = ["metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "wsproto-1.3.2-py3-none-any.whl", hash = "sha256:61eea322cdf56e8cc904bd3ad7573359a242ba65688716b0710a5eb12beab584"}, {file = "wsproto-1.3.2.tar.gz", hash = "sha256:b86885dcf294e15204919950f666e06ffc6c7c114ca900b060d6e16293528294"}, @@ -7488,6 +7727,7 @@ description = "Python binding for xxHash" optional = false python-versions = ">=3.8" groups = ["main", "metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "xxhash-3.8.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:27a9e475157f7315826118e3f3127909a0fe25f1b43d3d3be9c584f9d265f937"}, {file = "xxhash-3.8.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9b2ce44bf8f4a1d01f418b3110ff8dff32fd3f3e836c0e06333c3725f243fa6c"}, @@ -7685,6 +7925,7 @@ description = "Yet another URL library" optional = false python-versions = ">=3.10" groups = ["main", "metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "yarl-1.24.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88f50c94e21a0a7f14042c015b0eba1881af78562e7bf007e0033e624da59750"}, {file = "yarl-1.24.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6efbccc3d7f75d5b03105172a8dc86d82ba4da86817952529dd93185f4a88be2"}, @@ -7804,6 +8045,7 @@ description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.10" groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "zipp-4.1.0-py3-none-any.whl", hash = "sha256:25ad4e16390cd314347dd8f1de67a2ac538ae658ed4ab9db16029c07c188e97f"}, {file = "zipp-4.1.0.tar.gz", hash = "sha256:4cb57381f544315db7688e976e922a2b18cdb513d21cc194eb42232ba2a3e602"}, @@ -7824,6 +8066,7 @@ description = "Very basic event publishing system" optional = false python-versions = ">=3.10" groups = ["metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "zope_event-6.2-py3-none-any.whl", hash = "sha256:5e755153ac4faf64c10a4b6dd3307680166a3edf65b38df22df592610f8fa874"}, {file = "zope_event-6.2.tar.gz", hash = "sha256:b97d5d6327067ee6b9dfcbdf606ade9ade70991e19c162e808ea39e5fcf0f8d3"}, @@ -7840,6 +8083,7 @@ description = "Interfaces for Python" optional = false python-versions = ">=3.10" groups = ["metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "zope_interface-8.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c0c8aa2bf8f3911ef37b87deb1bbe225a310e6eb6522a16d77f5d8330c4f6fbe"}, {file = "zope_interface-8.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:efe234a0fafb4b6b1602e9be9245b97c2bf06d67c07af5a4bc3c0438978b555c"}, @@ -7896,6 +8140,7 @@ description = "Zstandard bindings for Python" optional = false python-versions = ">=3.9" groups = ["main", "metrics"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "zstandard-0.25.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e59fdc271772f6686e01e1b3b74537259800f57e24280be3f29c8a0deb1904dd"}, {file = "zstandard-0.25.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4d441506e9b372386a5271c64125f72d5df6d2a8e8a2a45a0ae09b03cb781ef7"}, @@ -8004,4 +8249,4 @@ cffi = ["cffi (>=1.17,<2.0) ; platform_python_implementation != \"PyPy\" and pyt [metadata] lock-version = "2.1" python-versions = ">=3.12,<3.15" -content-hash = "e3c07e977040c67c7adddf42c4c96bcb4f8849e7f11ee8b89ee54ff7ba3b73e2" +content-hash = "d9af52c458903188c1e365bd2b6eebe72ebcca864e45615ed16c0cec94535312" diff --git a/pyproject.toml b/pyproject.toml index 5e99f874..47f69909 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,6 +52,9 @@ langchain-azure-ai = "^1.2.3" langgraph = "^1.1.10" mistralai = "^2.4.3" langsmith = "^0.10.11" +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" diff --git a/src/app/baml_src/agents/tutor/activity_guide_generator.baml b/src/app/baml_src/agents/tutor/activity_guide_generator.baml new file mode 100644 index 00000000..3d0f70f4 --- /dev/null +++ b/src/app/baml_src/agents/tutor/activity_guide_generator.baml @@ -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 #" + + 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. + + + + 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. + + + + + Name: {{ activity_template.name }} + Description: {{ activity_template.description }} + Estimated duration: {{ activity_template.estimated_duration }}h + + + + Session {{ session.session_number }} + Objectives: {{ session.objectives | join(", ") }} + Duration: {{ session.duration }}h + Class size: {{ session.class_size }} + + + + Discipline: {{ metadata.discipline }} + Topic: {{ metadata.topic }} + Level: {{ metadata.level }} + Format: {{ metadata.session_type }} - {{ metadata.session_mode }} + + + + {% for conn in sustainability_map.connections %} + Objective {{ conn.objective_number }}: {{ conn.connection_explanation[:200] }}... + {% endfor %} + + + + + 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) + + + + 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]." + + + + 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..." + } + + + + Generate all outputs in {{ output_language }}. + + "# +} diff --git a/src/app/baml_src/agents/tutor/activity_recommendation.baml b/src/app/baml_src/agents/tutor/activity_recommendation.baml new file mode 100644 index 00000000..40b914f4 --- /dev/null +++ b/src/app/baml_src/agents/tutor/activity_recommendation.baml @@ -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 #" + + 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. + + + + 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). + + + + + Session {{ session.session_number }} + Objectives covered: {{ session.objectives | join(", ") }} + Type: {{ session.type }} + Mode: {{ session.mode }} + Duration: {{ session.duration }} hours + Class size: {{ session.class_size }} + + + + 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 %} + + + + 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 %} + + + + {% 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 %} + + + + + 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 + + + + 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..." + } + + + + Generate all outputs in {{ output_language }}. + + "# +} diff --git a/src/app/baml_src/agents/tutor/competency_mapping.baml b/src/app/baml_src/agents/tutor/competency_mapping.baml new file mode 100644 index 00000000..ced50fb6 --- /dev/null +++ b/src/app/baml_src/agents/tutor/competency_mapping.baml @@ -0,0 +1,83 @@ +// Competency Mapping Agent +// Maps learning outcomes to GreenComp competencies + +function MapCompetencies( + outcomes: LearningOutcomes, + greencomp_framework: map?, + output_language: string +) -> CompetencyMappings { + client CustomFast + prompt #" + + You are an expert in the GreenComp framework (European sustainability competences). You map learning outcomes to relevant GreenComp competencies with clear pedagogical rationale. + + + + For each learning outcome, identify 1-3 relevant GreenComp competencies and provide brief rationale for the mapping. + + + + + {% for outcome in outcomes.outcomes %} + {{ outcome.number }}. {{ outcome.text }} + (Related to objectives: {{ outcome.related_objectives | join(", ") }}) + {% endfor %} + + + + Area 1: Embodying sustainability values + - C1: Valuing sustainability (reflect on personal values, identify and explain how they guide actions) + - C2: Supporting fairness (support equity and justice for current and future generations) + - C3: Promoting nature (acknowledge humans are part of nature, respect needs of other species) + + Area 2: Embracing complexity in sustainability + - C4: Systems thinking (approach sustainability challenges as complex systems) + - C5: Critical thinking (assess information and arguments, identify assumptions and limitations) + - C6: Problem framing (formulate current and future sustainability challenges as problems) + + Area 3: Envisioning sustainable futures + - C7: Futures literacy (envision alternative sustainable futures) + - C8: Adaptability (manage transitions and challenges in complex sustainability situations) + - C9: Exploratory thinking (adopt relational thinking, explore and link different disciplines) + + Area 4: Acting for sustainability + - C10: Political agency (navigate political systems, identify political responsibility) + - C11: Collective action (act for change collaboratively) + - C12: Individual initiative (identify own potential for sustainability, take action) + + + + + - Map each outcome to 1-3 GreenComp competencies (most relevant) + - Provide brief rationale (1-2 sentences) explaining WHY this competency fits + - Use official GreenComp codes and names (e.g., "C4: Systems thinking") + - Ensure diversity across outcomes (don't map everything to C4, distribute across framework) + - Be pedagogically honest - only map if there's genuine alignment + + + + Good mapping: + Outcome: "Analyze supply chain efficiency using lifecycle thinking and environmental impact metrics" + Competencies: ["C4: Systems thinking", "C9: Exploratory thinking"] + Rationale: "This outcome develops systems thinking (C4) by requiring students to view supply chains as interconnected systems with environmental feedback loops. It also promotes exploratory thinking (C9) by linking business operations (supply chain) with environmental science (lifecycle analysis)." + + + + Return a JSON object with: + { + "mappings": [ + { + "outcome_number": 1, + "greencomp_competencies": ["C4: Systems thinking", "C9: Exploratory thinking"], + "rationale": "Brief explanation..." + }, + ... + ] + } + + + + Generate all outputs in {{ output_language }}. + + "# +} diff --git a/src/app/baml_src/agents/tutor/course_description.baml b/src/app/baml_src/agents/tutor/course_description.baml new file mode 100644 index 00000000..edf61db2 --- /dev/null +++ b/src/app/baml_src/agents/tutor/course_description.baml @@ -0,0 +1,58 @@ +// Course Description Agent +// Generates course description (150-250 words) balancing disciplinary focus with sustainability + +function GenerateCourseDescription( + mode: string, + metadata: CourseMetadata, + context_text: FileSummary, + output_language: string +) -> CourseDescription { + client CustomFast + prompt #" + + You are an expert course designer for French higher education, specializing in creating compelling course descriptions that balance disciplinary rigor with sustainability integration. + + + + Generate a course description (150-250 words) for the following course. The description should be academically rigorous, discipline-specific, and naturally integrate sustainability themes without sounding promotional or generic. + + + + {{ mode }} + + - Discipline: {{ metadata.discipline }} + - Topic: {{ metadata.topic }} + - Level: {{ metadata.level }} + - Number of sessions: {{ metadata.num_sessions }} + - Session format: {{ metadata.session_type }} ({{ metadata.session_mode }}) + - Class size: {{ metadata.class_size }} + + + {{ context_text }} + + + + + - Length: 150-250 words exactly + - Academic tone appropriate for French university context (enseignement supérieur) + - Be specific to the discipline and topic (avoid generic language) + - Naturally integrate sustainability themes relevant to the discipline + - Frame sustainability as enrichment, not compliance + - Focus on what students will learn and why it matters + - For Mode syllabus with existing description: use provided description as-is + - For other modes (metadata only): if context is sparse or generic, you may use general disciplinary knowledge but be specific + + + + Return a JSON object with: + { + "text": "The course description (150-250 words)", + "word_count": + } + + + + Generate all outputs in {{ output_language }}. + + "# +} diff --git a/src/app/baml_src/agents/tutor/file_summary.baml b/src/app/baml_src/agents/tutor/file_summary.baml new file mode 100644 index 00000000..75bf7de5 --- /dev/null +++ b/src/app/baml_src/agents/tutor/file_summary.baml @@ -0,0 +1,216 @@ +function FileSummaryExtractor( + mode: string, + file_content: string, + output_language: string +) -> FileSummary { + client CustomFast + + prompt #" + + You are an expert academic document analyst. + + Your primary responsibility is to accurately classify and extract information from academic documents. + + Always prefer the document itself over user-provided metadata. + Never fabricate, infer, rewrite, or embellish missing information. + + If requested information cannot be found explicitly in the document, + return null or an empty list. + + Normalize formatting only (remove OCR artifacts, excessive whitespace, + duplicated line breaks, broken bullet formatting). Preserve the original meaning. + + + + Analyze the provided document. + + The supplied mode is only a hint. + + Determine whether the document is: + - none + - article + - syllabus + - unknown + + If the document clearly contradicts the supplied mode, + trust the document rather than the supplied mode. + + + + + + {{ mode }} + + + + {{ file_content }} + + + + + + + none: + - No document content was provided. + + article: + Typical indicators include: + - abstract + - introduction + - methodology + - results + - discussion + - conclusion + - references + - citations + + syllabus: + Typical indicators include: + - course description + - course overview + - learning objectives + - learning outcomes + - competencies + - course goals + - educational objectives + - intended learning outcomes + - weekly schedule + - grading + - assessments + + unknown: + - The document cannot be confidently classified. + + + + + If detected type is NONE: + + - summary = null + - description = null + - objectives = [] + + -------------------------------------------------- + + If detected type is ARTICLE: + + Produce a concise summary. + + Requirements: + + - 150–300 words + - Preserve the author's intent + - Include: + - main topic + - central arguments + - methodology (if present) + - key findings + - conclusions + - Do not add external knowledge. + - Do not speculate. + - Do not rewrite as educational content. + + Return: + + - summary only + + -------------------------------------------------- + + If detected type is SYLLABUS: + + Extract information exactly as presented. + + Description: + + - Extract the official course description. + - Merge consecutive description paragraphs when appropriate. + - Do not paraphrase. + - Do not summarize. + - If absent, return null. + + Objectives: + + Extract every explicit objective under headings such as: + + - Learning Objectives + - Learning Outcomes + - Intended Learning Outcomes + - Course Objectives + - Course Goals + - Competencies + - Skills + - Educational Objectives + - Aims + + Preserve one objective per array element. + + Ignore: + + - instructor information + - grading + - bibliography + - schedules + - classroom policies + - contact information + - logistics + + unless they explicitly belong to the course description. + + -------------------------------------------------- + + If detected type is UNKNOWN: + + If the document resembles an article more than a syllabus, + generate a summary. + + Otherwise return null fields. + + + + + + Return JSON. + + { + "detected_document_type": "none | article | syllabus | unknown", + + "confidence": 0.0-1.0, + + "summary": "string or null", + + "description": "string or null", + + "objectives": [ + "..." + ], + + "reasoning": "One sentence explaining why the document was classified this way." + } + + Rules: + + detected_document_type: + - must reflect the document, not the requested mode + + confidence: + - 0.95-1.00: unmistakable + - 0.75-0.94: strong evidence + - 0.50-0.74: uncertain + - below 0.50: weak evidence + + Exactly one of the following should normally be populated: + + - summary + - description + + Objectives are populated only for syllabi. + + Never invent content. + + + + + Generate all textual outputs in {{ output_language }}. + + "# +} \ No newline at end of file diff --git a/src/app/baml_src/agents/tutor/learning_objectives.baml b/src/app/baml_src/agents/tutor/learning_objectives.baml new file mode 100644 index 00000000..fac95c54 --- /dev/null +++ b/src/app/baml_src/agents/tutor/learning_objectives.baml @@ -0,0 +1,87 @@ +// Learning Objectives Agent +// Generates 3-8 learning objectives (teacher-centered: what will be taught/covered) + +function GenerateLearningObjectives( + description: string, + context_text: string, + metadata: CourseMetadata, + mode: string, + output_language: string +) -> LearningObjectives { + client CustomFast + prompt #" + + You are a pedagogical expert specializing in learning objective design. You create clear learning objectives that describe what will be taught and covered in the course from the instructor's perspective. + + + + Generate learning objectives for this course. The number and complexity should match the course level and duration. + + For Mode 2B: Parse the provided objectives and structure them (do NOT generate new ones). + For all other modes: Generate new objectives. + + + + {{ mode }} + {{ description }} + {{ context_text }} + + - Discipline: {{ metadata.discipline }} + - Topic: {{ metadata.topic }} + - Level: {{ metadata.level }} + - Number of sessions: {{ metadata.num_sessions }} + + + + + - Number of objectives: + * Short courses (<6 sessions): 3-4 objectives + * Medium courses (6-12 sessions): 4-6 objectives + * Long courses (>12 sessions): 6-8 objectives + + - Each objective should be: + * Written from TEACHER PERSPECTIVE (what instructor intends to teach/cover) + * Use teacher-centered verbs: "Introduce", "Familiarize students with", "Provide an overview of", "Explore", "Cover", "Examine", "Present" + * Focused on instructional intent and content coverage + * Specific enough to guide course design + * Achievable within course duration + * Focused on disciplinary content (sustainability comes in next step) + + - Examples of good learning objectives: + * "Introduce students to the fundamentals of sustainable development" + * "Familiarize students with economic theories of market failures" + * "Provide an overview of climate change policy frameworks" + * "Explore the relationship between resource consumption and environmental impact" + + - AVOID student-centered language like: + * "Students will be able to..." + * "Students will demonstrate..." + * "Learners will..." + + - For Mode 2B: Keep objectives exactly as provided, just parse and structure them + + + + Return a JSON object with: + { + "objectives": [ + { + "number": 1, + "text": "Introduce students to sustainable development principles" + }, + { + "number": 2, + "text": "Familiarize students with environmental policy frameworks" + }, + ... + ] + } + + IMPORTANT: Write objectives from teacher perspective describing instructional intent, NOT student outcomes. Objectives should never be an empty list. + + + + Generate all outputs in {{ output_language }}. + + "# +} diff --git a/src/app/baml_src/agents/tutor/learning_outcomes.baml b/src/app/baml_src/agents/tutor/learning_outcomes.baml new file mode 100644 index 00000000..ab936d62 --- /dev/null +++ b/src/app/baml_src/agents/tutor/learning_outcomes.baml @@ -0,0 +1,79 @@ +// Learning Outcomes Agent +// Generates measurable learning outcomes (1-3 per objective) + +function GenerateLearningOutcomes( + objectives: LearningObjectives, + sustainability_map: SustainabilityIntegration, + metadata: CourseMetadata, + output_language: string +) -> LearningOutcomes { + client CustomFast + prompt #" + + You are a pedagogical assessment expert. You create measurable, observable learning outcomes that specify what students will be able to demonstrate upon completing the course. + + + + For each learning objective (including any suggested sustainability objectives), generate 1-3 specific, measurable outcomes. Naturally integrate sustainability themes informed by the sustainability mapping. + + + + + {% for obj in objectives.objectives %} + {{ obj.number }}. {{ obj.text }} ({{ obj.bloom_level }}) + {% endfor %} + + + + {% for conn in sustainability_map.connections %} + Objective {{ conn.objective_number }}: {{ conn.sdg_themes | join(", ") }} + Connection: {{ conn.connection_explanation }} + {% endfor %} + + + + - Discipline: {{ metadata.discipline }} + - Level: {{ metadata.level }} + - Duration: {{ metadata.num_sessions }} sessions + + + + + Each outcome should be: + - Measurable and observable (use action verbs: demonstrate, analyze, create, evaluate, design, etc.) + - Specific enough to guide activity design + - Achievable within course duration + - Related to 1-2 objectives (can span multiple) + - Include assessment method (how to measure: exam, project, presentation, quiz, etc.) + + Integration of sustainability: + - Weave sustainability naturally into outcomes (don't create separate "sustainability outcomes") + - Use the sustainability connections to inform outcome design + - Example: "Analyze supply chain efficiency using lifecycle thinking and environmental impact metrics" + vs separate: "Analyze supply chains" + "Understand environmental impacts" + + Number of outcomes: + - 1-3 outcomes per objective (more complex objectives may need more outcomes) + - Total outcomes: typically 1.5x-2x number of objectives + + + + Return a JSON object with: + { + "outcomes": [ + { + "number": 1, + "text": "Students will demonstrate...", + "related_objectives": [1, 2], + "assessment_method": "Case study analysis and written report" + }, + ... + ] + } + + + + Generate all outputs in {{ output_language }}. + + "# +} diff --git a/src/app/baml_src/agents/tutor/pedagogical_engineer.baml b/src/app/baml_src/agents/tutor/pedagogical_engineer.baml new file mode 100644 index 00000000..ccaec63c --- /dev/null +++ b/src/app/baml_src/agents/tutor/pedagogical_engineer.baml @@ -0,0 +1,130 @@ +// Pedagogical Engineer Agent +// Validates pedagogical coherence and quality + +function ValidatePedagogicalFramework( + description: string, + objectives: LearningObjectives, + outcomes: LearningOutcomes, + competencies: CompetencyMappings, + sustainability_map: SustainabilityIntegration, + metadata: CourseMetadata, + output_language: string +) -> ValidationReport { + client CustomFast + prompt #" + + You are an experienced pedagogical engineer (ingénieur pédagogique) providing constructive peer review of course frameworks. Your role is to validate overall coherence, quality, and feasibility. + + You should identify issues at three severity levels: + - MINOR: Formatting, wording, small inconsistencies (can be auto-fixed) + - MAJOR: Missing elements, serious misalignment, pedagogical errors (requires regeneration) + - SUBJECTIVE: Pedagogical choices that are valid but could be improved (recommendations for user) + + + + Review this complete pedagogical framework for overall coherence, quality, and feasibility. Provide specific, actionable feedback. + + + + {{ description }} + + + {% for obj in objectives.objectives %} + {{ obj.number }}. {{ obj.text }} ({{ obj.bloom_level }}) + {% endfor %} + + + + {% for outcome in outcomes.outcomes %} + {{ outcome.number }}. {{ outcome.text }} + Related objectives: {{ outcome.related_objectives | join(", ") }} + Assessment: {{ outcome.assessment_method }} + {% endfor %} + + + + {% for mapping in competencies.mappings %} + Outcome {{ mapping.outcome_number }}: {{ mapping.greencomp_competencies | join(", ") }} + {% endfor %} + + + + {% for conn in sustainability_map.connections %} + Objective {{ conn.objective_number }}: {{ conn.sdg_themes | join(", ") }} + {% endfor %} + Integration strategy: {{ sustainability_map.integration_strategy }} + + + + - Discipline: {{ metadata.discipline }} + - Level: {{ metadata.level }} + - Duration: {{ metadata.num_sessions }} sessions of {{ metadata.session_duration }}h + - Format: {{ metadata.session_type }} + + + + + Check for: + + 1. Coverage: + - All objectives have at least one outcome? ✓ + - All outcomes mapped to competencies? ✓ + + 2. Coherence: + - Outcomes align with objectives? ✓ + - Assessment methods appropriate for outcomes? ✓ + - Competency mappings make pedagogical sense? ✓ + + 3. Feasibility: + - Objectives achievable in given number of sessions? ✓ + - Outcomes realistic for course level and duration? ✓ + - Assessment methods practical for class size and format? ✓ + + 4. Quality: + - Objectives specific and measurable? ✓ + - Outcomes observable and assessable? ✓ + - Sustainability integration authentic (not forced or tacked on)? ✓ + + 5. Red flags: + - Overly ambitious for duration? ✗ + - Misalignment between objectives and outcomes? ✗ + - Generic or vague language? ✗ + - Too many or too few objectives/outcomes? ✗ + + + + - Be constructive and specific (peer review tone, not judgmental) + - Reference pedagogical best practices + - Classify severity: minor, major, or subjective + - For major issues: explain what needs to change AND provide corrected version + - For minor issues: provide auto-fix suggestions + - For subjective: frame as recommendations, not requirements + - If no issues: passed = true, empty issues list + - IMPORTANT: Always provide specific corrections that can be applied automatically + + + + MINOR issue: "Outcome 3 uses passive voice ('will be understood'). Rephrase with active, observable verb." + + MAJOR issue: "Objective 1 has no corresponding outcomes. At least one outcome must be created to measure this objective." + + SUBJECTIVE recommendation: "Consider balancing Bloom levels. Currently 5/6 objectives are 'Analyze' level. Adding one 'Create' level objective could enhance higher-order thinking for M2 students." + + + + Return a JSON object with: + { + "passed": true/false, + "issues": ["Issue 1...", "Issue 2..."], // Empty if passed + "suggestions": ["Suggestion 1...", "Suggestion 2..."], // Actionable feedback + "severity": "minor" | "major" | "subjective" // Highest severity if multiple issues + } + + If everything is valid and high quality: {"passed": true, "issues": [], "suggestions": [], "severity": "minor"} + + + + Generate all outputs in {{ output_language }}. + + "# +} diff --git a/src/app/baml_src/agents/tutor/sustainability_integration.baml b/src/app/baml_src/agents/tutor/sustainability_integration.baml new file mode 100644 index 00000000..cb8985b3 --- /dev/null +++ b/src/app/baml_src/agents/tutor/sustainability_integration.baml @@ -0,0 +1,109 @@ +// Sustainability Integration Agent +// CRITICAL: Creates authentic, disciplinarily-relevant sustainability connections (core value proposition) + +function IntegrateSustainability( + description: string, + objectives: LearningObjectives, + sdg_resources: Document[], + metadata: CourseMetadata, + mode: string, + output_language: string +) -> SustainabilityIntegration { + client CustomFast + prompt #" + + You are an expert in sustainability education and disciplinary pedagogy. Your role is to create AUTHENTIC, DEEP connections between course objectives and sustainability themes using SPECIFIC examples from provided SDG resources. + + This is the CORE VALUE PROPOSITION of WeLearn. You must avoid generic SDG labels and instead explain HOW sustainability enriches the disciplinary concept with concrete examples. + + + + For EACH learning objective, identify relevant sustainability themes from the provided SDG resources and explain the SPECIFIC connection using examples from those resources. + + For Mode 2B ONLY: Also suggest 2-3 ADDITIONAL sustainability-focused objectives that complement (don't replace) the original objectives. + + + + {{ mode }} + {{ description }} + + {% for obj in objectives.objectives %} + {{ obj.number }}. {{ obj.text }} (Bloom: {{ obj.bloom_level }}) + {% endfor %} + + + + {% for doc in sdg_resources %} + Document {{ loop.index }}: + Title: {{ doc.title | default("Untitled") }} + Relevance: {{ doc.relevance_score }} + URL: {{doc.url}} + Content excerpt: {{ doc.text[:500] }}... + {% endfor %} + + + + - Discipline: {{ metadata.discipline }} + - Topic: {{ metadata.topic }} + - Level: {{ metadata.level }} + + + + + CRITICAL - This is what separates good from mediocre output: + + ❌ BAD (generic): "Objective 2 relates to SDG 13 (Climate Action)" + + ✅ GOOD (authentic): "Objective 2 explores market failures. SDG resources on carbon pricing (Document 3, pp. 15-23) provide concrete examples where markets fail to price environmental externalities. Use the European carbon tax case study from Document 5 to illustrate how economic instruments can correct this market failure, directly connecting microeconomic theory to climate policy." + + For each objective: + 1. Identify 1-3 relevant SDG themes from the resources + 2. Cite SPECIFIC documents and examples (document titles, page numbers if available) + 3. Explain HOW sustainability connects to the disciplinary concept (mechanism, not just label) + 4. Use academic language appropriate for the discipline + 5. Frame as enrichment that makes the learning deeper, not as compliance + + For Mode 2B: Suggest 2-3 additional sustainability-focused objectives that: + - Are appropriate for the course level + - Build on existing objectives + - Are achievable in course duration + - Feel natural, not forced + + Overall integration strategy: + - Write a brief paragraph (3-5 sentences) explaining the overarching approach to integrating sustainability in this course + - How does it fit the discipline? Why is it pedagogically valuable? + + + + Good connection explanation: + "This objective on analyzing supply chain efficiency (Objective 3) connects to SDG 12 (Responsible Consumption) through concrete examples in Document 2. The case study of circular economy in manufacturing (Document 2, Section 4.2) demonstrates how lifecycle thinking transforms traditional supply chain analysis. Students can apply the same analytical frameworks they learn for cost optimization to evaluate environmental impacts, using the methodology described in Document 7 (Environmental Supply Chain Management Framework)." + + + + Return a JSON object with: + { + "connections": [ + { + "objective_number": 1, + "sdg_themes": ["SDG 12: Responsible Consumption", "SDG 13: Climate Action"], + "connection_explanation": "Detailed, specific explanation using examples from resources...", + "key_resources": [{title: title, url: url, corpus: corpus_name, sdg: [1, 2], description: description, text: slice, relevance_scrore: 0.6}] + }, + ... + ], + "suggested_objectives": [ // ONLY for Mode 2B + { + "number": 99, // Use high numbers to distinguish from original + "text": "Introduce different theories of...", + } + ], + "integration_strategy": "Overall narrative paragraph...", + "resources_used": [...] // Copy from sdg_resources input + } + + + + Generate all outputs in {{ output_language }}. + + "# +} diff --git a/src/app/baml_src/clients.baml b/src/app/baml_src/clients.baml new file mode 100644 index 00000000..63a52245 --- /dev/null +++ b/src/app/baml_src/clients.baml @@ -0,0 +1,42 @@ +// Learn more about clients at https://docs.boundaryml.com/docs/snippets/clients/overview + +client MistralSmall { + provider "openai-generic" + options { + model mistral-small-latest + api_key env.MISTRAL_API_KEY + base_url https://api.mistral.ai/v1 + } +} + + + + + // load balancer client that alternates between the two Azure clients + client CustomFast { + provider round-robin + options { + // This will alternate between the two clients + strategy [MistralSmall] + } + } + + +// https://docs.boundaryml.com/docs/snippets/clients/retry +retry_policy Constant { + max_retries 3 + strategy { + type constant_delay + delay_ms 200 + } +} + +retry_policy Exponential { + max_retries 2 + strategy { + type exponential_backoff + delay_ms 300 + multiplier 1.5 + max_delay_ms 10000 + } +} \ No newline at end of file diff --git a/src/app/baml_src/generators.baml b/src/app/baml_src/generators.baml new file mode 100644 index 00000000..cd86297c --- /dev/null +++ b/src/app/baml_src/generators.baml @@ -0,0 +1,18 @@ +// This helps use auto generate libraries you can use in the language of +// your choice. You can have multiple generators if you use multiple languages. +// Just ensure that the output_dir is different for each generator. +generator target { + // Valid values: "python/pydantic", "typescript", "go", "rust", "ruby/sorbet", "rest/openapi" + output_type "python/pydantic" + + // Where the generated code will be saved (relative to baml_src/) + output_dir "../" + + // The version of the BAML package you have installed (e.g. same version as your baml-py or @boundaryml/baml). + // The BAML VSCode extension version should also match this version. + version "0.220.0" + + // Valid values: "sync", "async" + // This controls what `b.FunctionName()` will be (sync or async). + default_client_mode sync +} diff --git a/src/app/baml_src/resume.baml b/src/app/baml_src/resume.baml new file mode 100644 index 00000000..5a37c5aa --- /dev/null +++ b/src/app/baml_src/resume.baml @@ -0,0 +1,42 @@ +// Defining a data model. +class Resume { + name string + email string + experience string[] + skills string[] +} + +// Create a function to extract the resume from a string. +function ExtractResume(resume: string) -> Resume { + // Specify a client as provider/model-name + // You can also use custom LLM params with a custom client name from clients.baml like "client CustomGPT5" or "client CustomSonnet4" + client CustomFast // Set OPENAI_API_KEY to use this client. + prompt #" + Extract from this content: + {{ resume }} + + {{ ctx.output_format }} + "# +} + + + +// Test the function with a sample resume. Open the VSCode playground to run this. +test vaibhav_resume { + functions [ExtractResume] + args { + resume #" + Vaibhav Gupta + vbv@boundaryml.com + + Experience: + - Founder at BoundaryML + - CV Engineer at Google + - CV Engineer at Microsoft + + Skills: + - Rust + - C++ + "# + } +} diff --git a/src/app/baml_src/types.baml b/src/app/baml_src/types.baml new file mode 100644 index 00000000..b1d91a34 --- /dev/null +++ b/src/app/baml_src/types.baml @@ -0,0 +1,143 @@ +// BAML Type Definitions for WeLearn MAS +// These types match the Pydantic models in models.py + +// Enums +enum SessionMode { + PRESENTIEL @alias("Présentiel") + DISTANCIEL @alias("Distanciel") + HYBRIDE @alias("Hybride") +} + +// Core Types +class FileSummary { + detected_document_type string + confidence float + + summary string? + description string? + objectives string[] + + reasoning string? +} + + +class CourseMetadata { + discipline string + topic string + level string + num_sessions int + session_duration float + session_type string + session_mode SessionMode + class_size int + output_language string + user_description string? +} + +class Document { + text string + corpus string + description string + sdg int[] + url string + title string + relevance_score float? +} + +class CourseDescription { + text string + word_count int +} + +class LearningObjective { + number int + text string + bloom_level string? +} + +class LearningObjectives { + objectives LearningObjective[] +} + +class SustainabilityConnection { + objective_number int + sdg_themes string[] + connection_explanation string + key_resources Document[] +} + +class SustainabilityIntegration { + connections SustainabilityConnection[] + suggested_objectives LearningObjective[]? + integration_strategy string + resources_used Document[] +} + +class LearningOutcome { + number int + text string + related_objectives int[] + assessment_method string +} + +class LearningOutcomes { + outcomes LearningOutcome[] +} + +class CompetencyMapping { + outcome_number int + greencomp_competencies string[] + rationale string +} + +class CompetencyMappings { + mappings CompetencyMapping[] +} + +class ValidationReport { + passed bool + issues string[] + suggestions string[] + severity string +} + +class SessionPlan { + session_number int + objectives int[] + type string + mode SessionMode + duration float + class_size int +} + +class ActivityTemplate { + id string + name string + description string + compatible_types string[] + compatible_modes SessionMode[] + min_duration float + max_duration float + min_size int + max_size int + estimated_duration float +} + +class ActivityGuide { + activity_name string + description string + learning_objectives int[] + steps string[] + teacher_role string + student_role string + resources_needed string[] + timing_breakdown map + evaluation_method string + sustainability_integration string +} + +class ActivityRecommendations { + session_number int + recommended_activities ActivityTemplate[] + rationale string +} diff --git a/src/app/tutor/api/router.py b/src/app/tutor/api/router.py index 76f5dd9d..5cf0ae12 100644 --- a/src/app/tutor/api/router.py +++ b/src/app/tutor/api/router.py @@ -11,6 +11,7 @@ UploadFile, ) +from src.app.baml_client.async_client import b, types from src.app.core.config import Settings from src.app.search.helpers.search_helpers import search_multi_inputs from src.app.search.models.search import EnhancedSearchQuery @@ -21,17 +22,27 @@ from src.app.shared.utils.dependencies import get_settings from src.app.shared.utils.requests import extract_session_cookie from src.app.shared.utils.utils import get_files_content + +# from src.app.tutor.service.agents import TEMPLATES from src.app.tutor.service.agents import TEMPLATES from src.app.tutor.service.models import ( + CompetencyMappingRequest, + CourseDescriptionRequest, ExtractorOutputList, + IntegrateSustainabilityRequest, + LearningObjectivesRequest, + LearningOutcomesRequest, SummariesList, SyllabusFeedback, + SyllabusGenerationRequest, SyllabusResponse, SyllabusResponseAgent, SyllabusUserUpdate, TutorSearchResponse, TutorSyllabusRequest, + UserInput, ) +from src.app.tutor.service.orchestrator import SyllabusOrchestrator from src.app.tutor.service.prompts import ( extractor_system_prompt, extractor_user_prompt, @@ -105,6 +116,34 @@ async def extract_files_content( raise e +@with_backoff() +@router.post("/mas_files/content") +async def extract_files_content_baml( + files: Annotated[list[UploadFile], File()], + response: Response, + lang: str = "en", + mode: str = "one", +) -> types.FileSummary | None: + files_content = await get_files_content(files) + files_content_str = ("__DOCUMENT_SEPARATOR__").join(files_content) + + mode_map = {"one": None, "two": "article", "three": "syllabus"} + + try: + result = await b.FileSummaryExtractor( + mode=mode_map[mode], # or "article", "syllabus", etc. + file_content=files_content_str, + output_language=lang, + ) + + return result + + except Exception as e: + logger.error(f"Error in extractor schema: {e}") + response.status_code = 204 + raise e + + @router.post("/search_extracts") @with_backoff() async def tutor_search_extract( @@ -309,3 +348,212 @@ async def register_syllabus_user_update( ) return {"message": "user update registered"} + + +@router.post("/syllabus/description") +async def baml_test(body: CourseDescriptionRequest) -> types.CourseDescription: + b_response = await b.GenerateCourseDescription( + mode=body.mode, + metadata=types.CourseMetadata( + discipline=body.course_metadata.discipline, + topic=body.course_metadata.topic, + level=body.course_metadata.level, + num_sessions=body.course_metadata.num_sessions, + session_duration=body.course_metadata.session_duration, + session_type=body.course_metadata.session_type, + session_mode=body.course_metadata.session_mode, # type: ignore + class_size=body.course_metadata.class_size, + output_language=body.course_metadata.output_language, + user_description=body.course_metadata.user_description, + ), + context_text=body.context_text, + output_language=body.course_metadata.output_language, + ) + return b_response + + +@with_backoff() +@router.post("/syllabus/learning_objectives") +async def baml_learning_objectives( + body: LearningObjectivesRequest, +) -> types.LearningObjectives: + b_response = await b.GenerateLearningObjectives( + description=body.description, + mode=body.mode, + metadata=types.CourseMetadata( + discipline=body.course_metadata.discipline, + topic=body.course_metadata.topic, + level=body.course_metadata.level, + num_sessions=body.course_metadata.num_sessions, + session_duration=body.course_metadata.session_duration, + session_type=body.course_metadata.session_type, + session_mode=body.course_metadata.session_mode, # type: ignore + class_size=body.course_metadata.class_size, + output_language=body.course_metadata.output_language, + ), + context_text=body.context_text, + output_language=body.course_metadata.output_language, + ) + + return b_response + + +@with_backoff() +@router.post("/syllabus/sustainability_integration") +async def integrate_sustainability( + body: IntegrateSustainabilityRequest, +) -> types.SustainabilityIntegration: + print(body.course_metadata) + # session_id = request.headers.get("X-Session-ID") + + metadata = types.CourseMetadata( + discipline=body.course_metadata.discipline, + topic=body.course_metadata.topic, + level=body.course_metadata.level, + num_sessions=body.course_metadata.num_sessions, + session_duration=body.course_metadata.session_duration, + session_type=body.course_metadata.session_type, + session_mode=body.course_metadata.session_mode, # type: ignore + class_size=body.course_metadata.class_size, + output_language=body.course_metadata.output_language, + ) + + b_response = await b.IntegrateSustainability( + description=body.description, + objectives=types.LearningObjectives( + objectives=[ + types.LearningObjective( + number=obj.number, + text=obj.text, + bloom_level=obj.bloom_level, + ) + for obj in body.objectives.objectives + ] + ), + metadata=metadata, + sdg_resources=[ + types.Document( + text=res.text, + metadata=res.metadata, + relevance_score=res.relevance_score if res.relevance_score else None, + ) + for res in body.sdg_resources + ], + mode=body.mode, + output_language=body.course_metadata.output_language, + ) + + return b_response + + +@with_backoff() +@router.post("/syllabus/learning_outcomes") +async def baml_learning_outcomes( + body: LearningOutcomesRequest, +) -> types.LearningOutcomes: + b_response = await b.GenerateLearningOutcomes( + metadata=types.CourseMetadata( + discipline=body.course_metadata.discipline, + topic=body.course_metadata.topic, + level=body.course_metadata.level, + num_sessions=body.course_metadata.num_sessions, + session_duration=body.course_metadata.session_duration, + session_type=body.course_metadata.session_type, + session_mode=body.course_metadata.session_mode, # type: ignore + class_size=body.course_metadata.class_size, + output_language=body.course_metadata.output_language, + ), + output_language=body.course_metadata.output_language, + sustainability_map=body.sustainability_map, + objectives=types.LearningObjectives( + objectives=[ + types.LearningObjective( + number=obj.number, + text=obj.text, + bloom_level=obj.bloom_level, + ) + for obj in body.objectives.objectives + ] + ), + ) + + return b_response + + +@with_backoff() +@router.post("/syllabus/competency_map") +async def generate_competency_map( + body: CompetencyMappingRequest, +) -> types.CompetencyMappings: + # session_id = request.headers.get("X-Session-ID") + + competencies = await b.MapCompetencies( + outcomes=types.LearningOutcomes( + outcomes=[ + types.LearningOutcome( + number=outcome.number, + text=outcome.text, + related_objectives=[], # This field is not used in the BAML function, so we can leave it empty + assessment_method="", # This field is not used in the BAML function, so we can leave it empty + ) + for outcome in body.outcomes + ] + ), + output_language=body.output_language, + greencomp_framework=body.framework, + ) + + return competencies + + +@router.post("/api/generate") +async def generate_syllabus(body: SyllabusGenerationRequest): + + try: + metadata = types.CourseMetadata( + discipline=body.course_metadata.discipline, + topic=body.course_metadata.topic, + level=body.course_metadata.level, + num_sessions=body.course_metadata.num_sessions, + session_duration=body.course_metadata.session_duration, + session_type=body.course_metadata.session_type, + session_mode=body.course_metadata.session_mode, + class_size=body.course_metadata.class_size, + output_language=body.course_metadata.output_language, + ) + + user_input = UserInput( + metadata=metadata, + mode=body.mode, + rag_resources=body.rag_resources, + provided_description=( + body.course_metadata.user_description + if body.course_metadata.user_description + else None + ), + ) + orchestrator = SyllabusOrchestrator() + output = await orchestrator.run(user_input, context_text=body.context) + + return { + "status": "success", + "validation": { + "passed": True, + "major_issues": [], + "minor_issues": [], + "suggestions": [], + }, # Placeholder for validation results + **output, + } + except Exception as e: + logger.error(f"Error generating syllabus: {e}") + return { + "status": "error", + "message": str(e), + "validation": { + "passed": False, + "major_issues": [str(e)], + "minor_issues": [], + "suggestions": [], + }, + } diff --git a/src/app/tutor/service/b_agents/__init__.py b/src/app/tutor/service/b_agents/__init__.py new file mode 100644 index 00000000..e01a7f1b --- /dev/null +++ b/src/app/tutor/service/b_agents/__init__.py @@ -0,0 +1,26 @@ +""" +Agent wrappers - Python interface to BAML agents +""" + +from .activity_guide_generator_agent import ActivityGuideGeneratorAgent +from .activity_recommendation_agent import ActivityRecommendationAgent +from .base import AgentError, BaseAgent +from .competency_mapping_agent import CompetencyMappingAgent +from .course_description_agent import CourseDescriptionAgent +from .learning_objectives_agent import LearningObjectivesAgent +from .learning_outcomes_agent import LearningOutcomesAgent +from .pedagogical_engineer_agent import PedagogicalEngineerAgent +from .sustainability_integration_agent import SustainabilityIntegrationAgent + +__all__ = [ + "BaseAgent", + "AgentError", + "CourseDescriptionAgent", + "LearningObjectivesAgent", + "SustainabilityIntegrationAgent", + "LearningOutcomesAgent", + "CompetencyMappingAgent", + "PedagogicalEngineerAgent", + "ActivityRecommendationAgent", + "ActivityGuideGeneratorAgent", +] diff --git a/src/app/tutor/service/b_agents/activity_guide_generator_agent.py b/src/app/tutor/service/b_agents/activity_guide_generator_agent.py new file mode 100644 index 00000000..c6af28c6 --- /dev/null +++ b/src/app/tutor/service/b_agents/activity_guide_generator_agent.py @@ -0,0 +1,47 @@ +"""Activity Guide Generator Agent wrapper""" + +from src.app.baml_client.async_client import b, types + +from .base import BaseAgent + + +class ActivityGuideGeneratorAgent(BaseAgent): + """Generates personalized activity guides""" + + def __init__(self): + super().__init__("activity_guide_generator") + + async def generate( + self, + activity_template: types.ActivityTemplate, + session: types.SessionPlan, + metadata: types.CourseMetadata, + sustainability_map: types.SustainabilityIntegration, + output_language: str = "Français", + ) -> types.ActivityGuide: + """Generate activity guide""" + self.logger.info(f"Generating guide for activity {activity_template.name}") + + async def _call_baml(): + result = await b.GenerateActivityGuide( + activity_template=activity_template, + session=session, + metadata=metadata, + sustainability_map=sustainability_map, + output_language=output_language, + ) + # Convert BAML types to Pydantic models + return types.ActivityGuide( + activity_name=result.activity_name, + description=result.description, + learning_objectives=result.learning_objectives, + steps=result.steps, + teacher_role=result.teacher_role, + student_role=result.student_role, + resources_needed=result.resources_needed, + timing_breakdown=result.timing_breakdown, + evaluation_method=result.evaluation_method, + sustainability_integration=result.sustainability_integration, + ) + + return await self.with_retry(_call_baml) diff --git a/src/app/tutor/service/b_agents/activity_recommendation_agent.py b/src/app/tutor/service/b_agents/activity_recommendation_agent.py new file mode 100644 index 00000000..38635486 --- /dev/null +++ b/src/app/tutor/service/b_agents/activity_recommendation_agent.py @@ -0,0 +1,92 @@ +"""Activity Recommendation Agent wrapper""" + +from typing import List + +from src.app.baml_client.async_client import b, types +from src.app.tutor.tools import ( + get_suitable_methods_for_outcome_text, + load_greencomp_methods, +) + +from .base import BaseAgent + + +class ActivityRecommendationAgent(BaseAgent): + """Recommends activities for sessions using GreenComp learning methods guidance""" + + def __init__(self): + super().__init__("activity_recommendation") + # Pre-load GreenComp methods for reference + self.greencomp_methods = load_greencomp_methods() + + async def recommend( + self, + session: types.SessionPlan, + learning_outcomes: types.LearningOutcomes, + filtered_activities: List[types.ActivityTemplate], + output_language: str = "Français", + ) -> types.ActivityRecommendations: + """ + Recommend activities based on learning outcomes + + Args: + session: Session plan with objectives + learning_outcomes: All learning outcomes (filtered to session objectives in prompt) + filtered_activities: Pre-filtered activity templates + output_language: Output language + + Returns: + ActivityRecommendations with selected activities and rationale + """ + self.logger.info( + f"Recommending activities for session {session.session_number} " + f"based on learning outcomes" + ) + + # Get GreenComp-recommended methods for session outcomes + # This helps guide activity selection based on pedagogical best practices + session_outcomes = [ + out + for out in learning_outcomes.outcomes + if any(obj in session.objectives for obj in out.related_objectives) + ] + + greencomp_suggestions = [] + for outcome in session_outcomes[:3]: # Top 3 outcomes + methods = get_suitable_methods_for_outcome_text(outcome.text) + if methods: + greencomp_suggestions.extend([m["name"] for m in methods[:2]]) + + self.logger.info(f"GreenComp suggests methods: {greencomp_suggestions}") + + async def _call_baml(): + # Pass GreenComp method suggestions to guide activity selection + result = await b.RecommendActivities( + session=session, + learning_outcomes=learning_outcomes, + filtered_activities=filtered_activities, + greencomp_methods=greencomp_suggestions, + output_language=output_language, + ) + # Convert BAML types to Pydantic models + return types.ActivityRecommendations( + session_number=result.session_number, + recommended_activities=[ + types.ActivityTemplate( + id=activity.id, + name=activity.name, + description=activity.description, + compatible_types=activity.compatible_types, + compatible_modes=activity.compatible_modes, + min_duration=activity.min_duration, + max_duration=activity.max_duration, + min_size=activity.min_size, + max_size=activity.max_size, + estimated_duration=activity.estimated_duration, + ) + for activity in result.recommended_activities + ], + rationale=result.rationale, + ) + + return await self.with_retry(_call_baml) diff --git a/src/app/tutor/service/b_agents/base.py b/src/app/tutor/service/b_agents/base.py new file mode 100644 index 00000000..8e940a0b --- /dev/null +++ b/src/app/tutor/service/b_agents/base.py @@ -0,0 +1,165 @@ +""" +Base agent class with retry logic and error handling +""" + +import logging +import time +from typing import Any, Callable, Dict, TypeVar + +logger = logging.getLogger(__name__) + +T = TypeVar("T") + + +class AgentError(Exception): + """Custom exception for agent errors""" + + pass + + +class BaseAgent: + """Base class for all agents with common functionality""" + + def __init__(self, name: str, max_retries: int = 3): + """ + Initialize agent + + Args: + name: Agent name for logging + max_retries: Maximum number of retry attempts + """ + self.name = name + self.max_retries = max_retries + self.logger = logging.getLogger(f"agent.{name}") + self.total_tokens = 0 # Track total tokens used by this agent + self.call_count = 0 # Track number of calls + + def with_retry(self, func: Callable[..., T], *args, **kwargs) -> T: + """ + Execute function with retry logic and token tracking + + Args: + func: Function to execute + *args, **kwargs: Arguments to pass to function + + Returns: + Function result + + Raises: + AgentError: If all retries fail + """ + attempt = 1 + last_error = None + + while attempt <= self.max_retries: + try: + start_time = time.time() + self.logger.info(f"Attempt {attempt}/{self.max_retries}") + + result = func(*args, **kwargs) + + duration = time.time() - start_time + self.logger.info(f"Success in {duration:.2f}s") + + # Estimate token usage (rough approximation) + # BAML doesn't expose token counts directly, so we estimatebased on input/output sizes + # 1 token ≈ 4 characters for English text + estimated_tokens = self._estimate_tokens(args, kwargs, result) + self.total_tokens += estimated_tokens + self.call_count += 1 + + self.logger.info(f"Estimated tokens for this call: {estimated_tokens}") + self.logger.info( + f"Total tokens used by {self.name}: {self.total_tokens}" + ) + + return result + + except Exception as e: + last_error = e + duration = time.time() - start_time + + self.logger.warning( + f"Attempt {attempt}/{self.max_retries} failed after {duration:.2f}s: {str(e)}" + ) + + if attempt < self.max_retries: + # Exponential backoff + wait_time = 2 ** (attempt - 1) + self.logger.info(f"Retrying in {wait_time}s...") + time.sleep(wait_time) + + attempt += 1 + + # All retries failed + error_msg = ( + f"{self.name} failed after {self.max_retries} attempts: {str(last_error)}" + ) + self.logger.error(error_msg) + raise AgentError(error_msg) from last_error + + def _estimate_tokens(self, args, kwargs, result) -> int: + """ + Estimate token usage based on input/output sizes + + Rough approximation: 1 token ≈ 4 characters + + Args: + args: Function args + kwargs: Function kwargs + result: Function result + + Returns: + Estimated token count + """ + + def count_chars(obj): + """Recursively count characters in an object""" + if isinstance(obj, str): + return len(obj) + elif isinstance(obj, (list, tuple)): + return sum(count_chars(item) for item in obj) + elif isinstance(obj, dict): + return sum(count_chars(v) for v in obj.values()) + elif hasattr(obj, "__dict__"): + return count_chars(obj.__dict__) + else: + return len(str(obj)) + + input_chars = sum(count_chars(arg) for arg in args) + input_chars += sum(count_chars(v) for v in kwargs.values()) + output_chars = count_chars(result) + + # Total chars / 4 ≈ tokens + # Multiply by 1.5 to account for prompt structure, formatting, etc. --> TODO:NOT SURE ABOUT THIS + estimated = int((input_chars + output_chars) / 4 * 1.5) + + return estimated + + def log_call( + self, + inputs: Dict[str, Any], + outputs: Any, + duration: float, + tokens: Dict[str, int] = None, + ): + """ + Log agent call details + + Args: + inputs: Input parameters + outputs: Output result + duration: Execution duration in seconds + tokens: Token usage (prompt, completion) + """ + log_data = { + "agent": self.name, + "duration_s": duration, + "inputs_preview": {k: str(v)[:100] for k, v in inputs.items()}, + "outputs_type": type(outputs).__name__, + } + + if tokens: + log_data["tokens"] = tokens + + self.logger.info(f"Call completed: {log_data}") diff --git a/src/app/tutor/service/b_agents/competency_mapping_agent.py b/src/app/tutor/service/b_agents/competency_mapping_agent.py new file mode 100644 index 00000000..a19627bf --- /dev/null +++ b/src/app/tutor/service/b_agents/competency_mapping_agent.py @@ -0,0 +1,43 @@ +"""Competency Mapping Agent wrapper""" + +from typing import Dict + +from src.app.baml_client.async_client import b, types + +from .base import BaseAgent + + +class CompetencyMappingAgent(BaseAgent): + """Maps outcomes to GreenComp competencies""" + + def __init__(self): + super().__init__("competency_mapping") + + async def map_competencies( + self, + outcomes: types.LearningOutcomes, + greencomp_framework: Dict[str, str], + output_language: str = "Français", + ) -> types.CompetencyMappings: + """Map competencies""" + self.logger.info("Mapping competencies") + + async def _call_baml(): + result = await b.MapCompetencies( + outcomes=outcomes, + greencomp_framework=greencomp_framework, + output_language=output_language, + ) + # Convert BAML types to Pydantic models + return types.CompetencyMappings( + mappings=[ + types.CompetencyMapping( + outcome_number=mapping.outcome_number, + greencomp_competencies=mapping.greencomp_competencies, + rationale=mapping.rationale, + ) + for mapping in result.mappings + ] + ) + + return await self.with_retry(_call_baml) diff --git a/src/app/tutor/service/b_agents/course_description_agent.py b/src/app/tutor/service/b_agents/course_description_agent.py new file mode 100644 index 00000000..c5577f95 --- /dev/null +++ b/src/app/tutor/service/b_agents/course_description_agent.py @@ -0,0 +1,50 @@ +"""Course Description Agent wrapper""" + +from src.app.baml_client.async_client import b, types + +from .base import BaseAgent + + +class CourseDescriptionAgent(BaseAgent): + """Generates course descriptions""" + + def __init__(self): + super().__init__("course_description") + + async def generate( + self, + mode: str, + metadata: types.CourseMetadata, + context_text: types.FileSummary, + output_language: str = "Français", + ) -> types.CourseDescription: + """ + Generate course description + + Args: + mode: Input mode (mode_1, mode_2a, mode_2b, mode_3) + metadata: Course metadata + context_text: Contextual text (documents, description) + output_language: Output language + + Returns: + CourseDescription object + """ + self.logger.info(f"Generating course description (mode={mode})") + + async def _call_baml(): + result = await b.GenerateCourseDescription( + mode=mode, + metadata=metadata, + context_text=context_text, + output_language=output_language, + ) + # Convert BAML types back to Pydantic models + return types.CourseDescription( + text=result.text, word_count=result.word_count + ) + + return await self.with_retry(_call_baml) + + +# Additional agent wrappers follow similar pattern diff --git a/src/app/tutor/service/b_agents/learning_objectives_agent.py b/src/app/tutor/service/b_agents/learning_objectives_agent.py new file mode 100644 index 00000000..1bd50fc1 --- /dev/null +++ b/src/app/tutor/service/b_agents/learning_objectives_agent.py @@ -0,0 +1,44 @@ +"""Learning Objectives Agent wrapper""" + +from src.app.baml_client.async_client import b, types + +from .base import BaseAgent + + +class LearningObjectivesAgent(BaseAgent): + """Generates learning objectives""" + + def __init__(self): + super().__init__("learning_objectives") + + async def generate( + self, + description: str, + context_text: str, + metadata: types.CourseMetadata, + mode: str, + output_language: str = "Français", + ) -> types.LearningObjectives: + """Generate learning objectives""" + self.logger.info(f"Generating learning objectives (mode={mode})") + + async def _call_baml(): + result = await b.GenerateLearningObjectives( + description=description, + context_text=context_text, + metadata=metadata, + mode=mode, + output_language=output_language, + ) + # Convert BAML types to Pydantic models and ensure sequential numbering + objectives = [] + for i, obj in enumerate(result.objectives, start=1): + objectives.append( + types.LearningObjective( + number=i, # Force sequential numbering starting from 1 + text=obj.text, + ) + ) + return types.LearningObjectives(objectives=objectives) + + return await self.with_retry(_call_baml) diff --git a/src/app/tutor/service/b_agents/learning_outcomes_agent.py b/src/app/tutor/service/b_agents/learning_outcomes_agent.py new file mode 100644 index 00000000..3600ce4e --- /dev/null +++ b/src/app/tutor/service/b_agents/learning_outcomes_agent.py @@ -0,0 +1,44 @@ +"""Learning Outcomes Agent wrapper""" + +from src.app.baml_client.async_client import b, types + +from .base import BaseAgent + + +class LearningOutcomesAgent(BaseAgent): + """Generates learning outcomes""" + + def __init__(self): + super().__init__("learning_outcomes") + + async def generate( + self, + objectives: types.LearningObjectives, + sustainability_map: types.SustainabilityIntegration, + metadata: types.CourseMetadata, + output_language: str = "Français", + ) -> types.LearningOutcomes: + """Generate learning outcomes""" + self.logger.info("Generating learning outcomes") + + async def _call_baml(): + result = await b.GenerateLearningOutcomes( + objectives=objectives, + sustainability_map=sustainability_map, + metadata=metadata, + output_language=output_language, + ) + # Convert BAML types to Pydantic models + return types.LearningOutcomes( + outcomes=[ + types.LearningOutcome( + number=outcome.number, + text=outcome.text, + related_objectives=outcome.related_objectives, + assessment_method=outcome.assessment_method, + ) + for outcome in result.outcomes + ] + ) + + return await self.with_retry(_call_baml) diff --git a/src/app/tutor/service/b_agents/pedagogical_engineer_agent.py b/src/app/tutor/service/b_agents/pedagogical_engineer_agent.py new file mode 100644 index 00000000..f16b1adc --- /dev/null +++ b/src/app/tutor/service/b_agents/pedagogical_engineer_agent.py @@ -0,0 +1,45 @@ +"""Pedagogical Engineer Agent wrapper""" + +from src.app.baml_client.async_client import b, types + +from .base import BaseAgent + + +class PedagogicalEngineerAgent(BaseAgent): + """Validates pedagogical framework""" + + def __init__(self): + super().__init__("pedagogical_engineer") + + async def validate( + self, + description: str, + objectives: types.LearningObjectives, + outcomes: types.LearningOutcomes, + competencies: types.CompetencyMappings, + sustainability_map: types.SustainabilityIntegration, + metadata: types.CourseMetadata, + output_language: str = "Français", + ) -> types.ValidationReport: + """Validate framework""" + self.logger.info("Validating pedagogical framework") + + async def _call_baml(): + result = await b.ValidatePedagogicalFramework( + description=description, + objectives=objectives, + outcomes=outcomes, + competencies=competencies, + sustainability_map=sustainability_map, + metadata=metadata, + output_language=output_language, + ) + # Convert BAML types to Pydantic models + return types.ValidationReport( + passed=result.passed, + issues=result.issues, + suggestions=result.suggestions, + severity=result.severity, + ) + + return await self.with_retry(_call_baml) diff --git a/src/app/tutor/service/b_agents/sustainability_integration_agent.py b/src/app/tutor/service/b_agents/sustainability_integration_agent.py new file mode 100644 index 00000000..c8c4f577 --- /dev/null +++ b/src/app/tutor/service/b_agents/sustainability_integration_agent.py @@ -0,0 +1,64 @@ +"""Sustainability Integration Agent wrapper""" + +from typing import List + +from src.app.baml_client.async_client import b, types + +from .base import BaseAgent + + +class SustainabilityIntegrationAgent(BaseAgent): + """Creates sustainability connections""" + + def __init__(self): + super().__init__("sustainability_integration") + + async def integrate( + self, + description: str, + objectives: types.LearningObjectives, + sdg_resources: List[types.Document], + metadata: types.CourseMetadata, + mode: str, + output_language: str = "Français", + ) -> types.SustainabilityIntegration: + """Create sustainability integration""" + self.logger.info(f"Integrating sustainability (mode={mode})") + + async def _call_baml(): + result = await b.IntegrateSustainability( + description=description, + objectives=objectives, + sdg_resources=sdg_resources, + metadata=metadata, + mode=mode, + output_language=output_language, + ) + # Convert BAML types to Pydantic models + return types.SustainabilityIntegration( + connections=[ + types.SustainabilityConnection( + objective_number=conn.objective_number, + sdg_themes=conn.sdg_themes, + connection_explanation=conn.connection_explanation, + key_resources=conn.key_resources, + ) + for conn in result.connections + ], + suggested_objectives=( + [ + types.LearningObjective( + number=obj.number, + text=obj.text, + bloom_level=obj.bloom_level, + ) + for obj in result.suggested_objectives + ] + if result.suggested_objectives + else None + ), + integration_strategy=result.integration_strategy, + resources_used=result.resources_used, + ) + + return await self.with_retry(_call_baml) diff --git a/src/app/tutor/service/models.py b/src/app/tutor/service/models.py index e8ceaf7a..286cfed9 100644 --- a/src/app/tutor/service/models.py +++ b/src/app/tutor/service/models.py @@ -1,10 +1,13 @@ import uuid from dataclasses import dataclass -from typing import Dict, List +from enum import Enum +from typing import Any, Dict, List, Optional from pydantic import BaseModel from qdrant_client.models import ScoredPoint +from src.app.baml_client import types + class SummariesList(BaseModel): summaries: list[str] @@ -83,3 +86,264 @@ class MessageWithFeedback(BaseModel): class TaskResponse: task_id: str result: str + + +# create enum sessionmode with values PRESENTIEL, DISTANCIEL, HYBRIDE + + +class SessionMode(str, Enum): + PRESENTIEL = "PRESENTIEL" + DISTANCIEL = "DISTANCIEL" + HYBRIDE = "HYBRIDE" + + +class CourseDescriptionRequest(BaseModel): + mode: str + course_metadata: types.CourseMetadata + context_text: str + + +class LearningObjectivesRequest(BaseModel): + description: str + course_metadata: types.CourseMetadata + mode: str + context_text: str + + +class SustainabilityIntegrationRequest(BaseModel): + connections: list[Dict] + suggested_objectives: types.LearningObjectives + integration_strategy: str + key_resources: list[str] + + +class LearningOutcomesRequest(BaseModel): + description: str + objectives: types.LearningObjectives + course_metadata: types.CourseMetadata + sustainability_map: types.SustainabilityIntegration + + +class Document(BaseModel): + text: str + metadata: Dict[str, Any] + relevance_score: float | None = None + url: Optional[str] = None # Link to resource + + +class CourseDescription(BaseModel): + text: str + word_count: int + + +class SustainabilityConnection(BaseModel): + objective_number: int + sdg_themes: List[str] + connection_explanation: str + key_resources: List[types.Document] + + +class SustainabilityIntegration(BaseModel): + connections: List[SustainabilityConnection] + suggested_objectives: list[types.LearningObjective] | None = None + integration_strategy: str + resources_used: List[types.Document] + + +class IntegrateSustainabilityRequest(BaseModel): + description: str + objectives: types.LearningObjectives + course_metadata: types.CourseMetadata + sdg_resources: List[types.Document] + mode: str + + +class LearningOutcome(BaseModel): + number: int + text: str + related_objectives: List[int] + assessment_method: str + + +class LearningOutcomes(BaseModel): + outcomes: List[LearningOutcome] + + +class CompetencyMappingRequest(BaseModel): + outcomes: List[LearningOutcome] + output_language: str + framework: Optional[Dict[str, str]] = None + + +class CompetencyMapping(BaseModel): + outcome_number: int + greencomp_competencies: List[str] + rationale: str + + +class CompetencyMappings(BaseModel): + mappings: List[CompetencyMapping] + + +class ValidationReport(BaseModel): + passed: bool + issues: List[str] + suggestions: List[str] + severity: str + + +class SessionPlan(BaseModel): + session_number: int + objectives: List[int] + type: str + mode: SessionMode + duration: float + class_size: int + + +class ActivityTemplate(BaseModel): + id: str + name: str + description: str + compatible_types: List[str] + compatible_modes: List[SessionMode] + min_duration: float + max_duration: float + min_size: int + max_size: int + estimated_duration: float + + +class ActivityGuide(BaseModel): + activity_name: str + description: str + learning_objectives: List[int] + steps: List[str] + teacher_role: str + student_role: str + resources_needed: List[str] + timing_breakdown: Dict[str, float] + evaluation_method: str + sustainability_integration: str + + +class ActivityRecommendations(BaseModel): + session_number: int + recommended_activities: List[ActivityTemplate] + rationale: str + + +class CourseLevel(str, Enum): + L1 = "L1" + L2 = "L2" + L3 = "L3" + M1 = "M1" + M2 = "M2" + + +# Core Models +class CourseMetadata(BaseModel): + """Course metadata provided by user""" + + discipline: str + topic: str + level: str # Free-form to allow flexibility + num_sessions: int + num_objectives: Optional[int] = None # Target number of objectives + session_duration: float + session_type: str # Free-form (CM, TD, TP, Séminaire, etc.) + session_mode: SessionMode + class_size: int + output_language: str = "Français" # Default to French + + +class LearningObjective(BaseModel): + """Single learning objective (teacher-centered: what will be taught/covered)""" + + number: int + text: str + + +class LearningObjectives(BaseModel): + """Collection of learning objectives""" + + objectives: List[LearningObjective] + + +class ValidationIssue(BaseModel): + """Single validation issue with fix applied""" + + description: str + fix_applied: Optional[str] = None # What was done to fix it + + +class OrchestratorState(BaseModel): + """Complete state of the orchestrator""" + + mode: str + metadata: types.CourseMetadata + rag_resources: Optional[List[types.Document]] = None + provided_objectives: Optional[List[str]] = None + + description: Optional[CourseDescription] = None + objectives: Optional[LearningObjectives] = None + sustainability_integration: Optional[SustainabilityIntegration] = None + outcomes: Optional[LearningOutcomes] = None + competencies: Optional[CompetencyMappings] = None + sessions: Optional[List[SessionPlan]] = None + activities: Optional[Dict[int, List[ActivityGuide]]] = None + + retrieved_resources: List[types.Document] = [] + validation_history: List[ValidationReport] = [] + current_phase: str = "input" + + +class SyllabusOutput(BaseModel): + """Final complete syllabus output""" + + description: Optional[CourseDescription] = None + objectives: Optional[LearningObjectives] = None + outcomes: Optional[LearningOutcomes] = None + competencies: Optional[CompetencyMappings] = None + sustainability: Optional[SustainabilityIntegration] = None + sessions: Optional[List[SessionPlan]] = None + # activities: Optional[Dict[int, List[ActivityGuide]]] = None + + +# Checkpoint Models for Interactive Flow +class CheckpointData(BaseModel): + """Data for a checkpoint in the orchestration flow""" + + phase: str # "description", "framework", "sessions", "session_N", "complete" + data: Dict[str, Any] + actions: List[str] # Available actions like ["accept", "edit", "regenerate"] + + +class UserAction(BaseModel): + """User action at a checkpoint""" + + action_type: str # "accept", "edit", "regenerate", "restart" + edited_data: Optional[Dict[str, Any]] = None # For direct edits + feedback: Optional[str] = None # For regeneration with feedback + + +class UserInput(BaseModel): + """Initial user input""" + + metadata: types.CourseMetadata + mode: Optional[str] = None + documents: Optional[List[bytes]] = None + documents_summaries: Optional[List[str]] = None + rag_resources: Optional[List[types.Document]] = None + provided_objectives: Optional[List[str]] = None + provided_description: Optional[str] = None + objective_processing: Optional[str] = None # "augment" or "transform" for Mode 2B + + +class SyllabusGenerationRequest(BaseModel): + mode: str + course_metadata: types.CourseMetadata + rag_resources: Optional[List[types.Document]] = None + provided_objectives: Optional[List[str]] = None + provided_description: Optional[str] = None + context: Optional[types.FileSummary] = None diff --git a/src/app/tutor/service/orchestrator.py b/src/app/tutor/service/orchestrator.py new file mode 100644 index 00000000..ea2a77fe --- /dev/null +++ b/src/app/tutor/service/orchestrator.py @@ -0,0 +1,1014 @@ +""" +Syllabus Orchestrator - Coordinates all agents and phases +""" + +import json +import logging +from enum import Enum +from typing import List, Optional + +from src.app.tutor.tools import distribute_objectives +from src.app.baml_client import types + +from .b_agents import ( + ActivityGuideGeneratorAgent, + ActivityRecommendationAgent, + CompetencyMappingAgent, + CourseDescriptionAgent, + LearningObjectivesAgent, + LearningOutcomesAgent, + PedagogicalEngineerAgent, + SustainabilityIntegrationAgent, +) +from .models import ( + CompetencyMappings, + CourseDescription, + LearningObjectives, + LearningOutcomes, + OrchestratorState, + SessionPlan, + SustainabilityIntegration, + SyllabusOutput, + UserInput, +) + +logger = logging.getLogger(__name__) + + +class Phase(str, Enum): + """Orchestrator phases""" + + INPUT = "input" + DESCRIPTION = "description" + OBJECTIVES = "objectives" + SUSTAINABILITY = "sustainability" + OUTCOMES = "outcomes" + COMPETENCIES = "competencies" + VALIDATION = "validation" + SESSIONS = "sessions" + ACTIVITIES = "activities" + COMPLETE = "complete" + + +class SyllabusOrchestrator: + """ + Main orchestrator that coordinates all agents and tools through sequential phases + """ + + def __init__(self): + """Initialize orchestrator with agents and configuration""" + # self.config = load_config() + + # Initialize all agents + self.course_description_agent = CourseDescriptionAgent() + self.learning_objectives_agent = LearningObjectivesAgent() + self.sustainability_agent = SustainabilityIntegrationAgent() + self.learning_outcomes_agent = LearningOutcomesAgent() + self.competency_mapping_agent = CompetencyMappingAgent() + self.pedagogical_engineer_agent = PedagogicalEngineerAgent() + self.activity_recommendation_agent = ActivityRecommendationAgent() + self.activity_guide_generator_agent = ActivityGuideGeneratorAgent() + + # Load activity templates (CAG) + # load_activity_templates() + + # State + self.state: Optional[OrchestratorState] = None + + logger.info("Orchestrator initialized") + + def get_total_tokens(self) -> int: + """ + Get total token usage across all agents + + Returns: + Total estimated tokens used + """ + agents = [ + self.course_description_agent, + self.learning_objectives_agent, + self.sustainability_agent, + self.learning_outcomes_agent, + self.competency_mapping_agent, + self.pedagogical_engineer_agent, + self.activity_recommendation_agent, + self.activity_guide_generator_agent, + ] + + total = sum(agent.total_tokens for agent in agents) + logger.debug(f"Total tokens across all agents: {total}") + return total + + # ======================================== + # Phase 0: Input & Intent Clarification + # ======================================== + + def start(self, user_input: UserInput) -> OrchestratorState: + """ + Start orchestration with user input + + Args: + user_input: Initial user input with metadata and optional content + document_filenames: Optional list of filenames for documents (for better error messages) + + Returns: + Initial state + """ + logger.info("Starting orchestration - Phase 0: Input") + + # Detect mode + mode = self._detect_mode(user_input) + logger.info(f"Detected mode: {mode}") + + # TODO: content of the user input doc == summary + context_text = "" + + # Add provided description to context (Mode 2A) + if user_input.provided_description: + if context_text: + context_text = ( + user_input.provided_description + "\n\n---\n\n" + context_text + ) + else: + context_text = user_input.provided_description + logger.info("Added provided description to context") + + # Store context_text in state for later use + self._context_text = context_text + + # Initialize state + self.state = OrchestratorState( + mode=mode, + metadata=user_input.metadata, + rag_resources=user_input.rag_resources, + provided_objectives=user_input.provided_objectives, + current_phase=Phase.INPUT, + ) + + return self.state + + def _detect_mode(self, user_input: UserInput) -> str: + """ + Detect input mode from user input: + - Mode 1: Documents present, no provided_description + - Mode 2A: Documents present + provided_description present (from uploaded existing syllabus) + - Mode 2B-Augment: provided_objectives present, objective_processing="augment" + - Mode 2B-Transform: provided_objectives present, objective_processing="transform" + - Mode 3: Only metadata (default) + """ + if user_input.documents: + if user_input.provided_description: + return "mode_2a" # Existing syllabus → generate new + else: + return "mode_1" # Documents → generate new + elif user_input.provided_objectives: + # Mode 2B fork: augment vs transform + if user_input.objective_processing == "transform": + return "mode_2b_transform" # Rewrite objectives with sustainability + else: + return "mode_2b_augment" # Keep objectives, add sustainability + else: + return "mode_3" # Metadata only → generate from scratch + + # ======================================== + # Phase 1: Pedagogical Framing + # ======================================== + + async def phase_1_generate_description( + self, context_text: types.FileSummary + ) -> CourseDescription: + """ + Phase 1.1: Generate course description + + Args: + context_text: Optional context (extracted documents, provided description) + + Returns: + Generated course description + """ + logger.info("Phase 1.1: Generating course description") + self.state.current_phase = Phase.DESCRIPTION + + description = await self.course_description_agent.generate( + mode=self.state.mode, + metadata=self.state.metadata, + context_text=context_text, + output_language=self.state.metadata.output_language, + ) + + self.state.description = description + logger.info(f"Description generated ({description.word_count} words)") + + return description + + async def phase_1_generate_objectives( + self, context_text: str = "" + ) -> LearningObjectives: + """ + Phase 1.2: Generate learning objectives + + Args: + context_text: Optional context + + Returns: + Generated objectives + """ + logger.info("Phase 1.2: Generating learning objectives") + self.state.current_phase = Phase.OBJECTIVES + + objectives = await self.learning_objectives_agent.generate( + description=self.state.description.text, + context_text=context_text, + metadata=self.state.metadata, + mode=self.state.mode, + output_language=self.state.metadata.output_language, + ) + + self.state.objectives = objectives + logger.info(f"Generated {len(objectives.objectives)} objectives") + + return objectives + + async def phase_1_integrate_sustainability(self) -> SustainabilityIntegration: + """ + Phase 1.3: Integrate sustainability + + Returns: + Sustainability integration + """ + logger.info("Phase 1.3: Integrating sustainability") + self.state.current_phase = Phase.SUSTAINABILITY + + # Build RAG query + # query = ( + # self.state.description.text + # + " " + # + " ".join([obj.text for obj in self.state.objectives.objectives]) + # ) + + # # Retrieve SDG resources + # filters = {"discipline": self.state.metadata.discipline} + # # sdg_resources = rag_retrieve(query, filters, top_k=15) + + # # Check if RAG results sufficient + # high_relevance_docs = [ + # doc + # for doc in sdg_resources + # if doc.relevance_score and doc.relevance_score > 0.7 + # ] + + # self.state.retrieved_resources.extend(sdg_resources) + + # Call sustainability agent + sustainability = await self.sustainability_agent.integrate( + description=self.state.description.text, + objectives=self.state.objectives, + sdg_resources=self.state.rag_resources, + metadata=self.state.metadata, + mode=self.state.mode, + output_language=self.state.metadata.output_language, + ) + + # Handle Mode 2B variants + if self.state.mode == "mode_2b_augment": + # Augment mode: Keep original objectives, add suggested ones + if sustainability.suggested_objectives: + logger.info( + f"Mode 2B-Augment: Adding {len(sustainability.suggested_objectives)} suggested objectives" + ) + all_objectives = ( + self.state.objectives.objectives + + sustainability.suggested_objectives + ) + self.state.objectives.objectives = all_objectives + elif self.state.mode == "mode_2b_transform": + # Transform mode: Objectives already rewritten with sustainability + logger.info( + "Mode 2B-Transform: Using rewritten objectives with sustainability" + ) + # No need to merge - objectives are already transformed + + self.state.sustainability_integration = sustainability + logger.info("Sustainability integration complete") + + return sustainability + + async def phase_1_generate_outcomes(self) -> LearningOutcomes: + """ + Phase 1.4: Generate learning outcomes + + Returns: + Generated outcomes + """ + logger.info("Phase 1.4: Generating learning outcomes") + self.state.current_phase = Phase.OUTCOMES + + outcomes = await self.learning_outcomes_agent.generate( + objectives=self.state.objectives, + sustainability_map=self.state.sustainability_integration, + metadata=self.state.metadata, + output_language=self.state.metadata.output_language, + ) + + self.state.outcomes = outcomes + logger.info(f"Generated {len(outcomes.outcomes)} outcomes") + + return outcomes + + async def phase_1_map_competencies(self) -> CompetencyMappings: + """ + Phase 1.5: Map GreenComp competencies + + Returns: + Competency mappings + """ + logger.info("Phase 1.5: Mapping competencies") + self.state.current_phase = Phase.COMPETENCIES + + # GreenComp framework (could be loaded from config) + # competency_mapping_agent already has the competencies in its prompt, so no need to repeat, centralise in 1 place TODO + greencomp = { + "C1": "Valuing sustainability", + "C2": "Supporting fairness", + "C3": "Promoting nature", + "C4": "Systems thinking", + "C5": "Critical thinking", + "C6": "Problem framing", + "C7": "Futures literacy", + "C8": "Adaptability", + "C9": "Exploratory thinking", + "C10": "Political agency", + "C11": "Collective action", + "C12": "Individual initiative", + } + + competencies = await self.competency_mapping_agent.map_competencies( + outcomes=self.state.outcomes, + greencomp_framework=greencomp, + output_language=self.state.metadata.output_language, + ) + + self.state.competencies = competencies + logger.info(f"Mapped {len(competencies.mappings)} competencies") + + return competencies + + async def phase_1_validate_framework(self) -> bool: + """ + Phase 1.6: Validate pedagogical framework + + Returns: + True if validation passed (after any corrections) + """ + logger.info("Phase 1.6: Validating pedagogical framework") + self.state.current_phase = Phase.VALIDATION + + validation = await self.pedagogical_engineer_agent.validate( + description=self.state.description.text, + objectives=self.state.objectives, + outcomes=self.state.outcomes, + competencies=self.state.competencies, + sustainability_map=self.state.sustainability_integration, + metadata=self.state.metadata, + output_language=self.state.metadata.output_language, + ) + + self.state.validation_history.append(validation) + + if validation.passed: + logger.info("Validation passed") + return True + + logger.warning(f"Validation failed: {validation.severity}") + + if validation.severity == "minor": + # Auto-apply minor fixes + logger.info("Minor issues detected - auto-applying suggestions") + for suggestion in validation.suggestions: + logger.info(f"Applied fix: {suggestion}") + # Minor issues don't block - continue with current state + return True + + elif validation.severity == "major": + # Log major issues but continue (user will see in checkpoint) + logger.info( + "Major issues detected - providing corrected version and will be shown to user at checkpoint" + ) + for issue in validation.issues: + logger.info(f"Corrected: {issue}") + return True + + else: # subjective + # Log subjective suggestions + logger.info("Subjective recommendations - will be shown to user") + for suggestion in validation.suggestions: + logger.info(f"Recommendation: {suggestion}") + return True + + async def run_phase_1(self, context_text: types.FileSummary) -> bool: + """ + Run complete Phase 1: Pedagogical Framing + + Args: + context_text: Optional context + + Returns: + True if phase completed successfully + """ + logger.info("========== PHASE 1: PEDAGOGICAL FRAMING ==========") + + # try: + await self.phase_1_generate_description(context_text) + + await self.phase_1_generate_objectives(context_text) + + await self.phase_1_integrate_sustainability() + + await self.phase_1_generate_outcomes() + + await self.phase_1_map_competencies() + + logger.info("Phase 1 complete") + return True + + # except AgentError as e: + # logger.error(f"Phase 1 failed: {e}") + # return False + + # ======================================== + # Phase 2: Session Planning + # ======================================== + + def phase_2_distribute_sessions(self) -> List[SessionPlan]: + """ + Phase 2: Distribute objectives across sessions (deterministic) + + Returns: + List of session plans + """ + logger.info("========== PHASE 2: SESSION PLANNING ==========") + self.state.current_phase = Phase.SESSIONS + + sessions = distribute_objectives( + objectives=self.state.objectives.objectives, metadata=self.state.metadata + ) + + self.state.sessions = sessions + logger.info(f"Created {len(sessions)} session plans") + + return sessions + + # ======================================== + # Complete Orchestration + # ======================================== + + async def run( + self, + user_input: UserInput, + context_text: types.FileSummary, + document_filenames: Optional[List[str]] = None, + ): + # -> SyllabusOutput: + """ + Run complete orchestration + + Args: + user_input: User input + context_text: Optional context (if None, will be built from documents) + document_filenames: Optional list of filenames for documents + + Returns: + Complete syllabus output + """ + logger.info("========== STARTING ORCHESTRATION ==========") + + # Phase 0 + # user description + documents content + self.start(user_input) + + # Use context_text from state if not provided explicitly + if context_text is None: + context_text = getattr(self, "_context_text", "") + + # Phase 1 + if not await self.run_phase_1(context_text): + raise Exception("Phase 1 failed") + + # Phase 2 + # self.phase_2_distribute_sessions() + + # # Phase 3 + # self.phase_3_design_activities() + + # Complete + self.state.current_phase = Phase.COMPLETE + logger.info("========== ORCHESTRATION COMPLETE ==========") + + return self.get_output() + + # def get_output(self) -> SyllabusOutput + def get_output(self): + """Get final syllabus output""" + if not self.state: + raise Exception("No state - orchestration not started") + + data = { + "description": self.state.description, + "objectives": self.state.objectives, + "outcomes": self.state.outcomes, + "competencies": self.state.competencies, + "sustainability": self.state.sustainability_integration, + "sessions": self.state.sessions, + } + + return data + + print( + "Final output data:", + json.dumps(data, default=lambda o: o.__dict__, indent=2), + ) + + return SyllabusOutput( + description=self.state.description, + objectives=self.state.objectives, + outcomes=self.state.outcomes, + competencies=self.state.competencies, + sustainability=self.state.sustainability_integration, + sessions=self.state.sessions, + # activities=self.state.activities, + ) + + # ======================================== + # Checkpoint-Based Orchestration + # ======================================== + + # def run_with_checkpoints( + # self, user_input: UserInput, document_filenames: Optional[List[str]] = None + # ): + # """ + # Generator-based orchestration that yields checkpoints for user approval. + + # Usage: + # orchestrator = SyllabusOrchestrator() + # gen = orchestrator.run_with_checkpoints(user_input) + + # checkpoint = next(gen) # Get first checkpoint + # action = UserAction(action_type="accept") + # checkpoint = gen.send(action) # Send action and get next checkpoint + + # Yields: + # CheckpointData: Data for user to review at each checkpoint + # """ + # logger.info("========== STARTING CHECKPOINT-BASED ORCHESTRATION ==========") + + # # Phase 0: Initialize + # self.start(user_input, document_filenames=document_filenames) + # context_text = getattr(self, "_context_text", "") + + # # ======================================== + # # CHECKPOINT 1: Course Description + # # ======================================== + # logger.info("Phase 1.1: Generating course description") + # self.state.current_phase = Phase.DESCRIPTION + + # description = self.course_description_agent.generate( + # mode=self.state.mode, + # metadata=self.state.metadata, + # context_text=context_text, + # output_language=self.state.metadata.output_language, + # ) + # self.state.description = description + + # # Yield checkpoint 1 + # checkpoint = CheckpointData( + # phase="description", + # data={ + # "description": description.text, + # "word_count": description.word_count, + # }, + # actions=["accept", "edit", "regenerate"], + # ) + # action = yield checkpoint + + # # Handle user action + # if action.action_type == "edit": + # # User edited description directly + # self.state.description = CourseDescription( + # text=action.edited_data["description"], + # word_count=len(action.edited_data["description"].split()), + # ) + # logger.info("User edited description") + # elif action.action_type == "regenerate": + # # Regenerate without feedback + # logger.info("Regenerating description") + # description = self.course_description_agent.generate( + # mode=self.state.mode, + # metadata=self.state.metadata, + # context_text=context_text, + # output_language=self.state.metadata.output_language, + # ) + # self.state.description = description + # elif action.action_type == "feedback": + # # Regenerate with user feedback + # logger.info( + # f"Regenerating description with user feedback: {action.feedback}" + # ) + # description = self.course_description_agent.generate( + # mode=self.state.mode, + # metadata=self.state.metadata, + # context_text=context_text + # + "\n\nUser feedback for regeneration: " + # + action.feedback, + # output_language=self.state.metadata.output_language, + # ) + # self.state.description = description + + # # ======================================== + # # CHECKPOINT 2: Complete Framework + # # ======================================== + # logger.info("Generating complete pedagogical framework") + + # # Generate objectives + # objectives = self.learning_objectives_agent.generate( + # description=self.state.description.text, + # context_text=context_text, + # metadata=self.state.metadata, + # mode=self.state.mode, + # output_language=self.state.metadata.output_language, + # ) + # self.state.objectives = objectives + + # # Integrate sustainability + # query = ( + # self.state.description.text + # + " " + # + " ".join([obj.text for obj in objectives.objectives]) + # ) + # # filters = {"discipline": self.state.metadata.discipline} + # # sdg_resources = rag_retrieve(query, filters, top_k=15) + + # high_relevance_docs = [ + # doc + # for doc in sdg_resources + # if doc.relevance_score and doc.relevance_score > 0.7 + # ] + # if len(high_relevance_docs) < 3: + # logger.warning("Insufficient RAG results, falling back to web search") + # # web_results = web_search(query) + # logger.info(f"Web search returned {len(web_results)} results") + + # self.state.retrieved_resources.extend(sdg_resources) + + # sustainability = self.sustainability_agent.integrate( + # description=self.state.description.text, + # objectives=objectives, + # sdg_resources=sdg_resources, + # metadata=self.state.metadata, + # mode=self.state.mode, + # output_language=self.state.metadata.output_language, + # ) + + # # Handle Mode 2B variants + # if self.state.mode == "mode_2b_augment": + # # Augment mode: Keep original objectives, add suggested ones + # if sustainability.suggested_objectives: + # logger.info( + # f"Mode 2B-Augment: Adding {len(sustainability.suggested_objectives)} suggested objectives" + # ) + # all_objectives = ( + # objectives.objectives + sustainability.suggested_objectives + # ) + # self.state.objectives.objectives = all_objectives + # elif self.state.mode == "mode_2b_transform": + # # Transform mode: Objectives already rewritten with sustainability + # logger.info( + # "Mode 2B-Transform: Using rewritten objectives with sustainability" + # ) + # # No need to merge - objectives are already transformed + + # self.state.sustainability_integration = sustainability + + # # Generate outcomes + # outcomes = self.learning_outcomes_agent.generate( + # objectives=self.state.objectives, + # sustainability_map=sustainability, + # metadata=self.state.metadata, + # output_language=self.state.metadata.output_language, + # ) + # self.state.outcomes = outcomes + + # # Map competencies + # greencomp = { + # "C1": "Valuing sustainability", + # "C2": "Supporting fairness", + # "C3": "Promoting nature", + # "C4": "Systems thinking", + # "C5": "Critical thinking", + # "C6": "Problem framing", + # "C7": "Futures literacy", + # "C8": "Adaptability", + # "C9": "Exploratory thinking", + # "C10": "Political agency", + # "C11": "Collective action", + # "C12": "Individual initiative", + # } + # competencies = self.competency_mapping_agent.map_competencies( + # outcomes=outcomes, + # greencomp_framework=greencomp, + # output_language=self.state.metadata.output_language, + # ) + # self.state.competencies = competencies + + # # Validate framework + # validation = self.pedagogical_engineer_agent.validate( + # description=self.state.description.text, + # objectives=self.state.objectives, + # outcomes=outcomes, + # competencies=competencies, + # sustainability_map=sustainability, + # metadata=self.state.metadata, + # output_language=self.state.metadata.output_language, + # ) + # self.state.validation_history.append(validation) + + # # Auto-apply validation recommendations + # if not validation.passed: + # if validation.severity == "minor": + # logger.info("Auto-applying minor validation fixes") + # for suggestion in validation.suggestions: + # logger.info(f"Applied: {suggestion}") + # elif validation.severity == "major": + # logger.warning("Major validation issues - showing to user") + # for issue in validation.issues: + # logger.warning(f"Issue: {issue}") + + # # Yield checkpoint 2 + # checkpoint = CheckpointData( + # phase="framework", + # data={ + # "description": self.state.description.text, + # "objectives": self.state.objectives, + # "sustainability": sustainability, + # "outcomes": outcomes, + # "competencies": competencies, + # "validation": validation, + # }, + # actions=["accept", "edit"], + # ) + # action = yield checkpoint + + # # Handle user action + # if action.action_type == "edit" and action.edited_data: + # # User edited framework components + # if "objectives" in action.edited_data: + # self.state.objectives = action.edited_data["objectives"] + # if "outcomes" in action.edited_data: + # self.state.outcomes = action.edited_data["outcomes"] + # logger.info("User edited framework") + # elif action.action_type == "regenerate": + # # Regenerate framework components + # logger.info("Regenerating framework") + # # Re-run the framework generation steps + # objectives = self.learning_objectives_agent.generate( + # description=self.state.description.text, + # context_text=context_text, + # metadata=self.state.metadata, + # mode=self.state.mode, + # output_language=self.state.metadata.output_language, + # ) + # self.state.objectives = objectives + # # Re-integrate sustainability and regenerate outcomes/competencies + # # (simplified - could be more granular) + # elif action.action_type == "feedback": + # # Regenerate framework with user feedback + # logger.info(f"Regenerating framework with user feedback: {action.feedback}") + # feedback_context = ( + # context_text + "\n\nUser feedback for improvement: " + action.feedback + # ) + + # objectives = self.learning_objectives_agent.generate( + # description=self.state.description.text, + # context_text=feedback_context, + # metadata=self.state.metadata, + # mode=self.state.mode, + # output_language=self.state.metadata.output_language, + # ) + # self.state.objectives = objectives + + # # Re-generate sustainability integration + # query = ( + # self.state.description.text + # + " " + # + " ".join([obj.text for obj in objectives.objectives]) + # ) + # sdg_resources = rag_retrieve( + # query, {"discipline": self.state.metadata.discipline}, top_k=15 + # ) + + # sustainability = self.sustainability_agent.integrate( + # description=self.state.description.text, + # objectives=objectives, + # sdg_resources=sdg_resources, + # metadata=self.state.metadata, + # mode=self.state.mode, + # output_language=self.state.metadata.output_language, + # ) + # self.state.sustainability_integration = sustainability + + # # Re-generate outcomes + # outcomes = self.learning_outcomes_agent.generate( + # objectives=objectives, + # sustainability_map=sustainability, + # metadata=self.state.metadata, + # output_language=self.state.metadata.output_language, + # ) + # self.state.outcomes = outcomes + + # # Re-map competencies + # greencomp = { + # "C1": "Valuing sustainability", + # "C2": "Supporting fairness", + # "C3": "Promoting nature", + # "C4": "Systems thinking", + # "C5": "Critical thinking", + # "C6": "Problem framing", + # "C7": "Futures literacy", + # "C8": "Adaptability", + # "C9": "Exploratory thinking", + # "C10": "Political agency", + # "C11": "Collective action", + # "C12": "Individual initiative", + # } + # competencies = self.competency_mapping_agent.map_competencies( + # outcomes=outcomes, + # greencomp_framework=greencomp, + # output_language=self.state.metadata.output_language, + # ) + # self.state.competencies = competencies + + # # ======================================== + # # CHECKPOINT 3: Session Planning + # # ======================================== + # logger.info("Phase 2: Distributing sessions") + # self.state.current_phase = Phase.SESSIONS + + # sessions = distribute_objectives( + # objectives=self.state.objectives.objectives, metadata=self.state.metadata + # ) + # self.state.sessions = sessions + + # # Yield checkpoint 3 + # checkpoint = CheckpointData( + # phase="sessions", + # data={"sessions": sessions, "objectives": self.state.objectives}, + # actions=["accept", "edit"], + # ) + # action = yield checkpoint + + # if action.action_type == "edit" and action.edited_data: + # if "sessions" in action.edited_data: + # self.state.sessions = action.edited_data["sessions"] + # logger.info("User edited session plan") + # elif action.action_type == "regenerate": + # # Regenerate session distribution + # logger.info("Regenerating session plan") + # sessions = distribute_objectives( + # objectives=self.state.objectives.objectives, + # metadata=self.state.metadata, + # ) + # self.state.sessions = sessions + # elif action.action_type == "feedback": + # # For sessions, feedback is noted but distribution is algorithmic + # # User can edit objectives if they want different distribution + # logger.info(f"Session plan feedback noted: {action.feedback}") + # # Optionally: could implement custom distribution based on feedback + # logger.warning( + # "Session feedback handling: currently sessions are algorithmically distributed" + # ) + + # # ======================================== + # # CHECKPOINT 4+: Activities per Session + # # ======================================== + # logger.info("Phase 3: Designing activities") + # self.state.current_phase = Phase.ACTIVITIES + + # activities = {} + + # for session in self.state.sessions: + # logger.info(f"Designing activities for session {session.session_number}") + + # # Filter compatible activities + # filtered = filter_activities( + # session_type=session.type, + # session_mode=session.mode, + # duration=session.duration, + # class_size=session.class_size, + # ) + + # if not filtered: + # logger.warning( + # f"No compatible activities for session {session.session_number}" + # ) + # activities[session.session_number] = [] + # continue + + # # Recommend activities based on learning outcomes + # recommendations = self.activity_recommendation_agent.recommend( + # session=session, + # learning_outcomes=self.state.outcomes, + # filtered_activities=filtered, + # output_language=self.state.metadata.output_language, + # ) + + # # Generate guides + # guides = [] + # for template in recommendations.recommended_activities: + # guide = self.activity_guide_generator_agent.generate( + # activity_template=template, + # session=session, + # metadata=self.state.metadata, + # sustainability_map=self.state.sustainability_integration, + # output_language=self.state.metadata.output_language, + # ) + # guides.append(guide) + + # activities[session.session_number] = guides + + # # Yield checkpoint for this session + # checkpoint = CheckpointData( + # phase=f"session_{session.session_number}", + # data={ + # "session": session, + # "activities": guides, + # "session_number": session.session_number, + # "total_sessions": len(self.state.sessions), + # }, + # actions=["accept", "edit"], + # ) + # action = yield checkpoint + + # if action.action_type == "edit" and action.edited_data: + # if "activities" in action.edited_data: + # activities[session.session_number] = action.edited_data[ + # "activities" + # ] + # logger.info( + # f"User edited activities for session {session.session_number}" + # ) + # elif action.action_type == "regenerate": + # # Regenerate activities for this session + # logger.info( + # f"Regenerating activities for session {session.session_number}" + # ) + # recommendations = self.activity_recommendation_agent.recommend( + # session=session, + # learning_outcomes=self.state.outcomes, + # filtered_activities=filtered, + # output_language=self.state.metadata.output_language, + # ) + # guides = [] + # for template in recommendations.recommended_activities: + # guide = self.activity_guide_generator_agent.generate( + # activity_template=template, + # session=session, + # metadata=self.state.metadata, + # sustainability_map=self.state.sustainability_integration, + # output_language=self.state.metadata.output_language, + # ) + # guides.append(guide) + # activities[session.session_number] = guides + # elif action.action_type == "feedback": + # # Regenerate activities with user feedback + # logger.info(f"Regenerating activities with feedback: {action.feedback}") + # # Could pass feedback to agent prompts - for now, simple regeneration + # # Future enhancement: pass feedback to activity guide generator + # recommendations = self.activity_recommendation_agent.recommend( + # session=session, + # learning_outcomes=self.state.outcomes, + # filtered_activities=filtered, + # output_language=self.state.metadata.output_language, + # ) + # guides = [] + # for template in recommendations.recommended_activities: + # guide = self.activity_guide_generator_agent.generate( + # activity_template=template, + # session=session, + # metadata=self.state.metadata, + # sustainability_map=self.state.sustainability_integration, + # output_language=self.state.metadata.output_language, + # ) + # guides.append(guide) + # activities[session.session_number] = guides + + # self.state.activities = activities + + # # ======================================== + # # COMPLETE + # # ======================================== + # self.state.current_phase = Phase.COMPLETE + # logger.info("========== CHECKPOINT-BASED ORCHESTRATION COMPLETE ==========") + + # # Final checkpoint with complete output + # yield CheckpointData( + # phase="complete", data=self.get_output().dict(), actions=[] + # ) diff --git a/src/app/tutor/tools/__init__.py b/src/app/tutor/tools/__init__.py new file mode 100644 index 00000000..94f22149 --- /dev/null +++ b/src/app/tutor/tools/__init__.py @@ -0,0 +1,28 @@ +"""Tools module - Deterministic utilities for agents""" + +from .distribute_objectives import distribute_objectives +from .filter_activities import filter_activities, load_activity_templates +from .greencomp_data import ( + get_all_methods, + get_greencomp_competency_details, + get_method_description, + get_suitable_methods_for_competencies, + get_suitable_methods_for_outcome_text, + load_greencomp_methods, + load_greencomp_outcomes, +) +from .validate_format import validate_format + +__all__ = [ + "filter_activities", + "load_activity_templates", + "validate_format", + "distribute_objectives", + "load_greencomp_outcomes", + "load_greencomp_methods", + "get_suitable_methods_for_competencies", + "get_suitable_methods_for_outcome_text", + "get_greencomp_competency_details", + "get_all_methods", + "get_method_description", +] diff --git a/src/app/tutor/tools/distribute_objectives.py b/src/app/tutor/tools/distribute_objectives.py new file mode 100644 index 00000000..6d768f95 --- /dev/null +++ b/src/app/tutor/tools/distribute_objectives.py @@ -0,0 +1,73 @@ +""" +Objective distribution tool - Deterministic distribution of objectives across sessions (Frugal AI) +""" + +import logging +from typing import List + +from src.app.baml_client.async_client import types + +logger = logging.getLogger(__name__) + + +def distribute_objectives( + objectives: List[types.LearningObjective], metadata: types.CourseMetadata +) -> List[types.SessionPlan]: + """ + Distribute learning objectives evenly across sessions + + Simple algorithm for prototype: + - Divide objectives evenly + - Distribute remainder to first sessions + - In future: pedagogical sequencing (foundational concepts first) + + Args: + objectives: List of learning objectives to distribute + metadata: Course metadata with session info + + Returns: + List of SessionPlan objects with assigned objectives + """ + num_sessions = metadata.num_sessions + num_objectives = len(objectives) + + logger.info( + f"Distributing {num_objectives} objectives across {num_sessions} sessions" + ) + + # Calculate distribution + objectives_per_session = num_objectives // num_sessions + remainder = num_objectives % num_sessions + + sessions = [] + current_objective_idx = 0 + + for session_num in range(1, num_sessions + 1): + # Calculate how many objectives for this session + count = objectives_per_session + (1 if session_num <= remainder else 0) + + # Assign next 'count' objectives + session_objectives = [ + objectives[current_objective_idx + i].number for i in range(count) + ] + current_objective_idx += count + + # Create session plan + session = types.SessionPlan( + session_number=session_num, + objectives=session_objectives, + type=metadata.session_type, + mode=metadata.session_mode, + duration=metadata.session_duration, + class_size=metadata.class_size, + ) + + sessions.append(session) + + logger.debug( + f"Session {session_num}: {len(session_objectives)} objectives " + f"(numbers: {session_objectives})" + ) + + logger.info(f"Created {len(sessions)} session plans") + return sessions diff --git a/src/app/tutor/tools/filter_activities.py b/src/app/tutor/tools/filter_activities.py new file mode 100644 index 00000000..bffd43b2 --- /dev/null +++ b/src/app/tutor/tools/filter_activities.py @@ -0,0 +1,176 @@ +""" +Activity filtering tool - Deterministic filtering of activity templates (Frugal AI) +""" + +import json +import logging +from pathlib import Path +from typing import List + +from src.app.baml_client.async_client import types + +logger = logging.getLogger(__name__) + +# Cached activity templates (CAG not RAG) +_activity_templates: List[types.ActivityTemplate] = [] + + +def load_activity_templates(filepath: str = None) -> List[types.ActivityTemplate]: + """ + Load activity templates from JSON file + + Args: + filepath: Path to activity templates JSON file + + Returns: + List of ActivityTemplate objects + """ + global _activity_templates + + if filepath is None: + # Use default path + filepath = Path(__file__).parent.parent / "data" / "activity_templates.json" + + if not Path(filepath).exists(): + logger.warning( + f"Activity templates file not found: {filepath}. Using mock data." + ) + _activity_templates = _get_mock_templates() + return _activity_templates + + with open(filepath, "r", encoding="utf-8") as f: + data = json.load(f) + + _activity_templates = [types.ActivityTemplate(**template) for template in data] + logger.info(f"Loaded {len(_activity_templates)} activity templates") + + return _activity_templates + + +def _get_mock_templates() -> List[types.ActivityTemplate]: + """Get mock activity templates for testing""" + return [ + types.ActivityTemplate( + id="act_001", + name="Débat structuré", + description="Débat en groupes sur des questions controversées", + compatible_types=["CM", "TD", "Séminaire"], + compatible_modes=[types.SessionMode.PRESENTIEL, types.SessionMode.HYBRIDE], + min_duration=1.5, + max_duration=3.0, + min_size=15, + max_size=100, + estimated_duration=2.0, + ), + types.ActivityTemplate( + id="act_002", + name="Étude de cas collaborative", + description="Analyse de cas en petits groupes", + compatible_types=["TD", "TP"], + compatible_modes=[ + types.SessionMode.PRESENTIEL, + types.SessionMode.DISTANCIEL, + types.SessionMode.HYBRIDE, + ], + min_duration=1.0, + max_duration=2.5, + min_size=10, + max_size=40, + estimated_duration=1.5, + ), + types.ActivityTemplate( + id="act_003", + name="Présentation magistrale interactive", + description="Cours avec moments d'interaction et questions", + compatible_types=["CM"], + compatible_modes=[ + types.SessionMode.PRESENTIEL, + types.SessionMode.DISTANCIEL, + types.SessionMode.HYBRIDE, + ], + min_duration=1.0, + max_duration=4.0, + min_size=20, + max_size=300, + estimated_duration=2.0, + ), + types.ActivityTemplate( + id="act_004", + name="Atelier pratique", + description="Exercices pratiques individuels ou en binôme", + compatible_types=["TP", "TD"], + compatible_modes=[types.SessionMode.PRESENTIEL], + min_duration=1.5, + max_duration=3.0, + min_size=8, + max_size=30, + estimated_duration=2.0, + ), + types.ActivityTemplate( + id="act_005", + name="Quiz formatif", + description="Quiz interactif pour vérifier la compréhension", + compatible_types=["CM", "TD", "TP"], + compatible_modes=[ + types.SessionMode.PRESENTIEL, + types.SessionMode.DISTANCIEL, + types.SessionMode.HYBRIDE, + ], + min_duration=0.5, + max_duration=1.5, + min_size=5, + max_size=200, + estimated_duration=0.5, + ), + ] + + +def filter_activities( + session_type: str, session_mode: types.SessionMode, duration: float, class_size: int +) -> List[types.ActivityTemplate]: + """ + Filter activity templates based on session constraints (deterministic, no LLM) + + Args: + session_type: Type of session (CM, TD, TP, Séminaire, etc.) + session_mode: Mode of session (Présentiel, Distanciel, Hybride) + duration: Session duration in hours + class_size: Number of students + + Returns: + List of compatible ActivityTemplate objects + """ + global _activity_templates + + # Load templates if not already loaded + if not _activity_templates: + load_activity_templates() + + logger.info( + f"Filtering activities: type={session_type}, mode={session_mode}, " + f"duration={duration}h, size={class_size}" + ) + + filtered = [] + + for activity in _activity_templates: + # Check session type compatibility + if session_type not in activity.compatible_types: + continue + + # Check mode compatibility + if session_mode not in activity.compatible_modes: + continue + + # Check duration constraints + if not (activity.min_duration <= duration <= activity.max_duration): + continue + + # Check class size constraints + if not (activity.min_size <= class_size <= activity.max_size): + continue + + filtered.append(activity) + + logger.info(f"Filtered to {len(filtered)} compatible activities") + return filtered diff --git a/src/app/tutor/tools/greencomp_data.py b/src/app/tutor/tools/greencomp_data.py new file mode 100644 index 00000000..9f53ba40 --- /dev/null +++ b/src/app/tutor/tools/greencomp_data.py @@ -0,0 +1,238 @@ +""" +GreenComp Data Loader +Loads and provides access to GreenComp learning outcomes and methods CSV files +""" + +import logging +from functools import lru_cache +from pathlib import Path +from typing import Dict, List, Optional + +import pandas as pd + +logger = logging.getLogger(__name__) + +# Get project root directory (parent of welearn_mas) +PROJECT_ROOT = Path(__file__).parent.parent.parent + + +@lru_cache(maxsize=1) +def load_greencomp_outcomes() -> pd.DataFrame: + """ + Load GreenComp learning outcomes CSV + + Returns: + DataFrame with columns: Competence area, Competence, Learning outcome, + KSA coverage, Observable, Measurable, Evaluation Methods + """ + csv_path = PROJECT_ROOT / "GreenComp-LearningOutcomes.csv" + + if not csv_path.exists(): + logger.warning(f"GreenComp outcomes CSV not found at {csv_path}") + return pd.DataFrame() + + df = pd.read_csv(csv_path) + logger.info(f"Loaded {len(df)} GreenComp learning outcomes") + return df + + +@lru_cache(maxsize=1) +def load_greencomp_methods() -> pd.DataFrame: + """ + Load GreenComp learning methods CSV + + Returns: + DataFrame with columns: Method_Name, Description, Validation_Source, + Best_For_LO_Types + """ + csv_path = PROJECT_ROOT / "GreenComp-LearningMethods.csv" + + if not csv_path.exists(): + logger.warning(f"GreenComp methods CSV not found at {csv_path}") + return pd.DataFrame() + + df = pd.read_csv(csv_path) + logger.info(f"Loaded {len(df)} GreenComp learning methods") + return df + + +def get_suitable_methods_for_competencies(competencies: List[str]) -> List[Dict]: + """ + Get suitable learning methods for given GreenComp competencies + + Args: + competencies: List of GreenComp competency codes (e.g., ["C4", "C5"]) + + Returns: + List of dicts with method info: {name, description, best_for} + """ + methods_df = load_greencomp_methods() + outcomes_df = load_greencomp_outcomes() + + if methods_df.empty or outcomes_df.empty: + return [] + + # Map competencies to learning outcome types + # Filter outcomes by competencies and get their types + competency_names = [] + for comp_code in competencies: + # Extract competency names from outcomes based on competency area + matching_outcomes = outcomes_df[ + outcomes_df["Competence"].str.contains(comp_code, case=False, na=False) + ] + if not matching_outcomes.empty: + competency_names.extend(matching_outcomes["Competence"].unique().tolist()) + + # Find methods that match these competency types + suitable_methods = [] + for _, method in methods_df.iterrows(): + best_for = str(method.get("Best_For_LO_Types", "")) + + # Check if any competency name appears in the best_for field + if any( + comp_name.lower() in best_for.lower() + for comp_name in competency_names + if comp_name + ): + suitable_methods.append( + { + "name": method.get("Method_Name", ""), + "description": method.get("Description", ""), + "best_for": best_for, + } + ) + + return suitable_methods + + +def get_suitable_methods_for_outcome_text(outcome_text: str) -> List[Dict]: + """ + Get suitable learning methods for a given learning outcome by matching keywords + + Args: + outcome_text: Text of the learning outcome + + Returns: + List of dicts with method info, sorted by relevance + """ + methods_df = load_greencomp_methods() + + if methods_df.empty: + return [] + + # Keywords to match in learning outcome text + keyword_mapping = { + "analyze": [ + "Case Study Analysis", + "Critical Analysis / Deconstruction", + "Systems Mapping", + ], + "evaluate": [ + "Critical Analysis / Deconstruction", + "Policy & Governance Analysis", + ], + "design": ["Futures Literacy Workshop", "Living Lab / Co-creation"], + "demonstrate": [ + "Community Project (Participatory Action)", + "Reflective Practice / Portfolio", + ], + "stakeholder": [ + "Stakeholder Simulation / Role-Play", + "Living Lab / Co-creation", + ], + "systems": ["Systems Mapping", "Interdisciplinary Research Project"], + "policy": ["Policy & Governance Analysis", "Advocacy Campaign"], + "future": ["Futures Literacy Workshop", "Socio-Ecological Transition Planning"], + "personal": ["Personal Action Plan", "Reflective Practice / Portfolio"], + "community": [ + "Community Project (Participatory Action)", + "Living Lab / Co-creation", + ], + "sustainability": ["Case Study Analysis", "Interdisciplinary Research Project"], + } + + outcome_lower = outcome_text.lower() + method_scores = {} + + # Score each method based on keyword matches + for keyword, method_names in keyword_mapping.items(): + if keyword in outcome_lower: + for method_name in method_names: + method_scores[method_name] = method_scores.get(method_name, 0) + 1 + + # Get method details and sort by score + suitable_methods = [] + for method_name, score in sorted( + method_scores.items(), key=lambda x: x[1], reverse=True + ): + method_row = methods_df[methods_df["Method_Name"] == method_name] + if not method_row.empty: + method = method_row.iloc[0] + suitable_methods.append( + { + "name": method.get("Method_Name", ""), + "description": method.get("Description", ""), + "best_for": method.get("Best_For_LO_Types", ""), + "relevance_score": score, + } + ) + + return suitable_methods[:5] # Return top 5 + + +def get_greencomp_competency_details(competency_code: str) -> Optional[Dict]: + """ + Get detailed information about a GreenComp competency + + Args: + competency_code: Competency code (e.g., "C4", "Systems thinking") + + Returns: + Dict with competency details or None if not found + """ + outcomes_df = load_greencomp_outcomes() + + if outcomes_df.empty: + return None + + # Try to find by code or name + matching = outcomes_df[ + (outcomes_df["Competence"].str.contains(competency_code, case=False, na=False)) + | ( + outcomes_df["Competence area"].str.contains( + competency_code, case=False, na=False + ) + ) + ] + + if matching.empty: + return None + + first_match = matching.iloc[0] + return { + "competence_area": first_match.get("Competence area", ""), + "competence": first_match.get("Competence", ""), + "sample_outcome": first_match.get("Learning outcome", ""), + "evaluation_methods": first_match.get("Evaluation Methods", ""), + } + + +def get_all_methods() -> List[str]: + """Get list of all available learning method names""" + methods_df = load_greencomp_methods() + if methods_df.empty: + return [] + return methods_df["Method_Name"].tolist() + + +def get_method_description(method_name: str) -> Optional[str]: + """Get description of a specific learning method""" + methods_df = load_greencomp_methods() + if methods_df.empty: + return None + + method = methods_df[methods_df["Method_Name"] == method_name] + if method.empty: + return None + + return method.iloc[0].get("Description", "") diff --git a/src/app/tutor/tools/validate_format.py b/src/app/tutor/tools/validate_format.py new file mode 100644 index 00000000..900bf008 --- /dev/null +++ b/src/app/tutor/tools/validate_format.py @@ -0,0 +1,48 @@ +""" +Format validation tool - Check output structure using Pydantic +""" + +import logging +from typing import Any, Dict, Type + +from pydantic import BaseModel, ValidationError + +logger = logging.getLogger(__name__) + + +class ValidationResult(BaseModel): + """Result of format validation""" + + valid: bool + errors: list[str] = [] + + +def validate_format( + content: Dict[str, Any], schema: Type[BaseModel] +) -> ValidationResult: + """ + Validate content against Pydantic schema + + Args: + content: Content to validate (as dict) + schema: Pydantic model class to validate against + + Returns: + ValidationResult with valid status and any errors + """ + logger.info(f"Validating content against schema: {schema.__name__}") + + try: + # Attempt to instantiate the schema with content + schema(**content) + logger.info("Validation passed") + return ValidationResult(valid=True, errors=[]) + + except ValidationError as e: + errors = [f"{err['loc']}: {err['msg']}" for err in e.errors()] + logger.warning(f"Validation failed: {errors}") + return ValidationResult(valid=False, errors=errors) + + except Exception as e: + logger.error(f"Unexpected validation error: {str(e)}") + return ValidationResult(valid=False, errors=[str(e)]) From ace909f2440caf92c073ec701e1ab7b89a3c54b5 Mon Sep 17 00:00:00 2001 From: Sandra Guerreiro Date: Thu, 20 Aug 2026 14:28:46 +0200 Subject: [PATCH 2/6] adds langsmith secret --- k8s/welearn-api/secrets.dev.yaml | 144 ++++++++++++++++--------------- 1 file changed, 73 insertions(+), 71 deletions(-) diff --git a/k8s/welearn-api/secrets.dev.yaml b/k8s/welearn-api/secrets.dev.yaml index f7d73ea5..b4b60f5b 100644 --- a/k8s/welearn-api/secrets.dev.yaml +++ b/k8s/welearn-api/secrets.dev.yaml @@ -1,74 +1,76 @@ config: - sensitive: - AZURE_API_KEY: ENC[AES256_GCM,data:vU5sozgleY904OU1Fb0yEaRzWYNHnCD2ILjthvtSfk4=,iv:b7ItArThdtIsz1qvgpptbOq96GHa1q6VWAweHYWw2Po=,tag:emJB+azc6vcIWCgv8idtyg==,type:str] - AZURE_MISTRAL_API_KEY: ENC[AES256_GCM,data:FjMSqC6wQM4/1cg5hwM3qAZiKmDEEU0rz6l44XDr0HQ0WLXRYKxDcm/0Ii1khG693fyQJalH7lvf8CRVdD4mnIm3KOaw5Q5MCkqrqoQo2GGm+7ZF,iv:h157m7hUESIOMC81yrnxzG//fInFLfsR8o2zYTsGcUs=,tag:pMOQn7+ijCbvYh1AksjYbQ==,type:str] - 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] + sensitive: + AZURE_API_KEY: ENC[AES256_GCM,data:vU5sozgleY904OU1Fb0yEaRzWYNHnCD2ILjthvtSfk4=,iv:b7ItArThdtIsz1qvgpptbOq96GHa1q6VWAweHYWw2Po=,tag:emJB+azc6vcIWCgv8idtyg==,type:str] + AZURE_MISTRAL_API_KEY: ENC[AES256_GCM,data:FjMSqC6wQM4/1cg5hwM3qAZiKmDEEU0rz6l44XDr0HQ0WLXRYKxDcm/0Ii1khG693fyQJalH7lvf8CRVdD4mnIm3KOaw5Q5MCkqrqoQo2GGm+7ZF,iv:h157m7hUESIOMC81yrnxzG//fInFLfsR8o2zYTsGcUs=,tag:pMOQn7+ijCbvYh1AksjYbQ==,type:str] + 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: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] + 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+IFgyNTUxOSB4R2xCeHAybzc0Zlk4ME04 - VFZkQ0s1c2FsdE01S28zMHNtSzAzbkhvZmxzCkpEM0VRRzVFdWtJTFVRcHVGSXJR - aWZzRWxxY1FuMjdKUkdoN0crOG42YzQKLS0tIEUrekxvZjFrWDJNR1Q2YldUWER1 - S1JiK1ZkMld1RUJ0M3VsYmVJL2VWTGcKGw3xqNeYzREDyQunsEHU7ajxcOAoUCcZ - L6gmMPI6bZfyW8Sq2u4wYt6MrERovBoEFpFm4UmUnfCxodMjZyNjIA== - -----END AGE ENCRYPTED FILE----- - - recipient: age14qavd6070runmtefwsh60ps3kxnu36fhsp76wvct4w4k4g7wqeksmk3r9z - enc: | - -----BEGIN AGE ENCRYPTED FILE----- - YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBJNG5GMWE1bjlsekVoS041 - UjdkVWpPRURTdEhPQmJCVzF6MFBQQXZXNGhBCjBEaXFEaC9VcUtYanRHSUhMR0Qw - dmdYb29WdnU3RkFkNjNwOGV6b3Blc1EKLS0tIFBJeDBjTTVkVWxZQUR2Q2RkMFhV - aUdEOC9jVFNvMVhxQnBtVmVDcmJubVUKHRnjxPYRBbrzP9LjUsvcyFh87mgvUl1P - PcrgsOOOvu4lHyiF1m5KUR9h8G88NNIv0VggNYfOV7o4bsy691v12Q== - -----END AGE ENCRYPTED FILE----- - - recipient: age1w7q7xfuw2z4nshys80lt3l7y409rws4elevsqm74kh52a2g3pagqcfp9wq - enc: | - -----BEGIN AGE ENCRYPTED FILE----- - YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAyTmVaSTdvK29lcVJwL0l4 - ZU52Vko3RVVqd2hhYVVCdWJtU3VTaHZ3U2gwCkdoSmVUNk5IOEw0TXozVXZBSFht - QmFHK2JPUVpZSlI1Zi85Y0o2OWUrR0EKLS0tIFpTekdGN0U4TEtaYjhPNXlXTUFt - RU85UENjSGs5K1c0SlRKdUxsM1RkZU0KWtcCdceXPaEORejTjPyVHi0tqiXt6lAJ - tN453Uqcj6Y3rE3dO6LdrYChl1gY8XIxPyeYjZpe0Gg3AqdvUw1RRg== - -----END AGE ENCRYPTED FILE----- - - recipient: age1q22qgq5vyxatgxntpd8n7k0umqjvccny9ndf694f0kh3s76y5pnsc3386z - enc: | - -----BEGIN AGE ENCRYPTED FILE----- - YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBWN1YxZWhnS3lGVGlLY1lL - Q1M3eFpGb1ZJTGdCOGFwbTBKVFBZZUdRNlNnCnJ1ZThYeC9lVFlzSWtQd3EvNkVW - bmc3N25hVmJJSUZJTk5NN2ZRbjA1dkkKLS0tIEZRQXNlT2ZqVVJUOXhSTEUyei9T - TzE4ZWpkeWp6STZsVW1YMk90aGh4ZVUKh0c2Uu2WY6wWGqJv/SUElqKSRBURdGB4 - kdIVLW5hskirKX8RMkvqY0aCc+RA+AIwAYSdpIT5+GhGYvf3WwcSAA== - -----END AGE ENCRYPTED FILE----- - - recipient: age1ew6kcn0fa6faqkc8w2hg2ww7xrh2g87nwa7ncdc2eq6xf4vkfs5q8yxy0l - enc: | - -----BEGIN AGE ENCRYPTED FILE----- - YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBXN1o1Tmtkb0NadFJESXVj - K3VFNjlLa1pqSjhheW5kbEZWWmcrNHBiUHpZCkRwMUNiUDdxRVAzVk10VzJvYnIy - VnlqUEVjSnF1eUJHYkVhNEwvMmNpeGcKLS0tIFBpR0hTWHIwMUd0emdxM2k5Q1NB - NXZiYmdGZURyQ0xyeE50Z1VDa2wyY1kK/5iryku0MKcs4lCPGOVayJVNA1QN8h72 - NIUYWr0VHSfHe7oBzC81kk2I9gg4Pa3cORZyhIzxEU2ytJPBmDinCw== - -----END AGE ENCRYPTED FILE----- - - recipient: age1h2ta0vvj53zt9qyys5hskramd6cduy9k85hqaghjnrrrcv0e9shskl7dzd - enc: | - -----BEGIN AGE ENCRYPTED FILE----- - YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAwL1BFY1MxQkxTZlBUNlFU - SkRvWGdZczJyZEJDTlRxZUdZVWVLQXFuRms4CkRCSjhtYmhkSEwzRHZtUjhHOGkv - OW1hWHNRYWw5VG41c2NNUnJxVW1tWXMKLS0tIGE0NDBZRWphSjhrcGJvaEpEUG5M - Q1lsakJjbW5rTGRIYUt0M3ArN2xjMW8KyUV4W7zltLlQ3Bdp4U1OgU5hRlWLa5RU - hxOYlyFSCKkn/hXTx2AAwyTVcwVGt/OoUujuRN1A1/pGrO1sDY0uFg== - -----END AGE ENCRYPTED FILE----- - lastmodified: "2026-04-30T13:05:26Z" - mac: ENC[AES256_GCM,data:ZFiWNcr07s7GcnleGpFZWb+YIk1Hv5nut4GFn4o2p5YFd0IvycuFWybk3sw1e4GmPoIJwaXiuSIThUYaZSBbuAzy9u7QZj0tkiVti7otbccsTj+mFOQ4p4iJty1Ms6+S03O4Yd3jWOugJQMbzuMm4OHKkI2rorvaVOdyQjvPK6c=,iv:+WSNB3GrlxU7kOCnAeI1M1JkTQG/ssEXD+QYt2WcwjM=,tag:Gc19gH91iYb6tAj0PKI8Wg==,type:str] - unencrypted_suffix: _unencrypted - version: 3.12.1 + kms: [] + gcp_kms: [] + azure_kv: [] + hc_vault: [] + age: + - recipient: age1dvu205sflecfu6uewgwmgs83nkm2c2fzfxs55zfkjs2gmqjya50qmm6lck + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB4R2xCeHAybzc0Zlk4ME04 + VFZkQ0s1c2FsdE01S28zMHNtSzAzbkhvZmxzCkpEM0VRRzVFdWtJTFVRcHVGSXJR + aWZzRWxxY1FuMjdKUkdoN0crOG42YzQKLS0tIEUrekxvZjFrWDJNR1Q2YldUWER1 + S1JiK1ZkMld1RUJ0M3VsYmVJL2VWTGcKGw3xqNeYzREDyQunsEHU7ajxcOAoUCcZ + L6gmMPI6bZfyW8Sq2u4wYt6MrERovBoEFpFm4UmUnfCxodMjZyNjIA== + -----END AGE ENCRYPTED FILE----- + - recipient: age14qavd6070runmtefwsh60ps3kxnu36fhsp76wvct4w4k4g7wqeksmk3r9z + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBJNG5GMWE1bjlsekVoS041 + UjdkVWpPRURTdEhPQmJCVzF6MFBQQXZXNGhBCjBEaXFEaC9VcUtYanRHSUhMR0Qw + dmdYb29WdnU3RkFkNjNwOGV6b3Blc1EKLS0tIFBJeDBjTTVkVWxZQUR2Q2RkMFhV + aUdEOC9jVFNvMVhxQnBtVmVDcmJubVUKHRnjxPYRBbrzP9LjUsvcyFh87mgvUl1P + PcrgsOOOvu4lHyiF1m5KUR9h8G88NNIv0VggNYfOV7o4bsy691v12Q== + -----END AGE ENCRYPTED FILE----- + - recipient: age1w7q7xfuw2z4nshys80lt3l7y409rws4elevsqm74kh52a2g3pagqcfp9wq + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAyTmVaSTdvK29lcVJwL0l4 + ZU52Vko3RVVqd2hhYVVCdWJtU3VTaHZ3U2gwCkdoSmVUNk5IOEw0TXozVXZBSFht + QmFHK2JPUVpZSlI1Zi85Y0o2OWUrR0EKLS0tIFpTekdGN0U4TEtaYjhPNXlXTUFt + RU85UENjSGs5K1c0SlRKdUxsM1RkZU0KWtcCdceXPaEORejTjPyVHi0tqiXt6lAJ + tN453Uqcj6Y3rE3dO6LdrYChl1gY8XIxPyeYjZpe0Gg3AqdvUw1RRg== + -----END AGE ENCRYPTED FILE----- + - recipient: age1q22qgq5vyxatgxntpd8n7k0umqjvccny9ndf694f0kh3s76y5pnsc3386z + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBWN1YxZWhnS3lGVGlLY1lL + Q1M3eFpGb1ZJTGdCOGFwbTBKVFBZZUdRNlNnCnJ1ZThYeC9lVFlzSWtQd3EvNkVW + bmc3N25hVmJJSUZJTk5NN2ZRbjA1dkkKLS0tIEZRQXNlT2ZqVVJUOXhSTEUyei9T + TzE4ZWpkeWp6STZsVW1YMk90aGh4ZVUKh0c2Uu2WY6wWGqJv/SUElqKSRBURdGB4 + kdIVLW5hskirKX8RMkvqY0aCc+RA+AIwAYSdpIT5+GhGYvf3WwcSAA== + -----END AGE ENCRYPTED FILE----- + - recipient: age1ew6kcn0fa6faqkc8w2hg2ww7xrh2g87nwa7ncdc2eq6xf4vkfs5q8yxy0l + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBXN1o1Tmtkb0NadFJESXVj + K3VFNjlLa1pqSjhheW5kbEZWWmcrNHBiUHpZCkRwMUNiUDdxRVAzVk10VzJvYnIy + VnlqUEVjSnF1eUJHYkVhNEwvMmNpeGcKLS0tIFBpR0hTWHIwMUd0emdxM2k5Q1NB + NXZiYmdGZURyQ0xyeE50Z1VDa2wyY1kK/5iryku0MKcs4lCPGOVayJVNA1QN8h72 + NIUYWr0VHSfHe7oBzC81kk2I9gg4Pa3cORZyhIzxEU2ytJPBmDinCw== + -----END AGE ENCRYPTED FILE----- + - recipient: age1h2ta0vvj53zt9qyys5hskramd6cduy9k85hqaghjnrrrcv0e9shskl7dzd + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAwL1BFY1MxQkxTZlBUNlFU + SkRvWGdZczJyZEJDTlRxZUdZVWVLQXFuRms4CkRCSjhtYmhkSEwzRHZtUjhHOGkv + OW1hWHNRYWw5VG41c2NNUnJxVW1tWXMKLS0tIGE0NDBZRWphSjhrcGJvaEpEUG5M + Q1lsakJjbW5rTGRIYUt0M3ArN2xjMW8KyUV4W7zltLlQ3Bdp4U1OgU5hRlWLa5RU + hxOYlyFSCKkn/hXTx2AAwyTVcwVGt/OoUujuRN1A1/pGrO1sDY0uFg== + -----END AGE ENCRYPTED FILE----- + 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 From e54486d04ddd40e38a02d4964f4b355a1fe3d0be Mon Sep 17 00:00:00 2001 From: Sandra Guerreiro Date: Mon, 24 Aug 2026 15:16:14 +0200 Subject: [PATCH 3/6] wip --- Makefile | 2 ++ .../baml_src/agents/tutor/course_description.baml | 2 +- .../baml_src/agents/tutor/learning_objectives.baml | 2 +- src/app/tutor/api/router.py | 4 ++-- .../service/b_agents/course_description_agent.py | 2 +- src/app/tutor/service/models.py | 2 +- src/app/tutor/service/orchestrator.py | 14 ++++++++++---- 7 files changed, 18 insertions(+), 10 deletions(-) diff --git a/Makefile b/Makefile index c5f3a28b..8f595567 100644 --- a/Makefile +++ b/Makefile @@ -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: diff --git a/src/app/baml_src/agents/tutor/course_description.baml b/src/app/baml_src/agents/tutor/course_description.baml index edf61db2..c94255f5 100644 --- a/src/app/baml_src/agents/tutor/course_description.baml +++ b/src/app/baml_src/agents/tutor/course_description.baml @@ -4,7 +4,7 @@ function GenerateCourseDescription( mode: string, metadata: CourseMetadata, - context_text: FileSummary, + context_text: string, output_language: string ) -> CourseDescription { client CustomFast diff --git a/src/app/baml_src/agents/tutor/learning_objectives.baml b/src/app/baml_src/agents/tutor/learning_objectives.baml index fac95c54..a3e1ebf4 100644 --- a/src/app/baml_src/agents/tutor/learning_objectives.baml +++ b/src/app/baml_src/agents/tutor/learning_objectives.baml @@ -17,7 +17,7 @@ function GenerateLearningObjectives( Generate learning objectives for this course. The number and complexity should match the course level and duration. - For Mode 2B: Parse the provided objectives and structure them (do NOT generate new ones). + For Mode 2: Parse the provided objectives and structure them (do NOT generate new ones). Make new outcomes visible by making them bold For all other modes: Generate new objectives. diff --git a/src/app/tutor/api/router.py b/src/app/tutor/api/router.py index 5cf0ae12..c960d7ea 100644 --- a/src/app/tutor/api/router.py +++ b/src/app/tutor/api/router.py @@ -122,12 +122,12 @@ async def extract_files_content_baml( files: Annotated[list[UploadFile], File()], response: Response, lang: str = "en", - mode: str = "one", + mode: str = "mode_1", ) -> types.FileSummary | None: files_content = await get_files_content(files) files_content_str = ("__DOCUMENT_SEPARATOR__").join(files_content) - mode_map = {"one": None, "two": "article", "three": "syllabus"} + mode_map = {"mode_1": "article", "mode_2": "syllabus", "mode_3": None} try: result = await b.FileSummaryExtractor( diff --git a/src/app/tutor/service/b_agents/course_description_agent.py b/src/app/tutor/service/b_agents/course_description_agent.py index c5577f95..7043a1dd 100644 --- a/src/app/tutor/service/b_agents/course_description_agent.py +++ b/src/app/tutor/service/b_agents/course_description_agent.py @@ -15,7 +15,7 @@ async def generate( self, mode: str, metadata: types.CourseMetadata, - context_text: types.FileSummary, + context_text: str, output_language: str = "Français", ) -> types.CourseDescription: """ diff --git a/src/app/tutor/service/models.py b/src/app/tutor/service/models.py index 286cfed9..1933846e 100644 --- a/src/app/tutor/service/models.py +++ b/src/app/tutor/service/models.py @@ -346,4 +346,4 @@ class SyllabusGenerationRequest(BaseModel): rag_resources: Optional[List[types.Document]] = None provided_objectives: Optional[List[str]] = None provided_description: Optional[str] = None - context: Optional[types.FileSummary] = None + context: Optional[types.FileSummary | str] = None diff --git a/src/app/tutor/service/orchestrator.py b/src/app/tutor/service/orchestrator.py index ea2a77fe..66f5b6f0 100644 --- a/src/app/tutor/service/orchestrator.py +++ b/src/app/tutor/service/orchestrator.py @@ -156,6 +156,9 @@ def _detect_mode(self, user_input: UserInput) -> str: - Mode 2B-Transform: provided_objectives present, objective_processing="transform" - Mode 3: Only metadata (default) """ + if user_input.mode: + return user_input.mode + if user_input.documents: if user_input.provided_description: return "mode_2a" # Existing syllabus → generate new @@ -175,7 +178,7 @@ def _detect_mode(self, user_input: UserInput) -> str: # ======================================== async def phase_1_generate_description( - self, context_text: types.FileSummary + self, context_text: str ) -> CourseDescription: """ Phase 1.1: Generate course description @@ -202,7 +205,7 @@ async def phase_1_generate_description( return description async def phase_1_generate_objectives( - self, context_text: str = "" + self, context_text: str ) -> LearningObjectives: """ Phase 1.2: Generate learning objectives @@ -405,7 +408,7 @@ async def phase_1_validate_framework(self) -> bool: logger.info(f"Recommendation: {suggestion}") return True - async def run_phase_1(self, context_text: types.FileSummary) -> bool: + async def run_phase_1(self, context_text: str) -> bool: """ Run complete Phase 1: Pedagogical Framing @@ -417,6 +420,7 @@ async def run_phase_1(self, context_text: types.FileSummary) -> bool: """ logger.info("========== PHASE 1: PEDAGOGICAL FRAMING ==========") + print(self.state) # try: await self.phase_1_generate_description(context_text) @@ -465,7 +469,7 @@ def phase_2_distribute_sessions(self) -> List[SessionPlan]: async def run( self, user_input: UserInput, - context_text: types.FileSummary, + context_text: Optional[types.FileSummary | str] = None, document_filenames: Optional[List[str]] = None, ): # -> SyllabusOutput: @@ -489,6 +493,8 @@ async def run( # Use context_text from state if not provided explicitly if context_text is None: context_text = getattr(self, "_context_text", "") + if isinstance(context_text, types.FileSummary): + context_text = f"description: {context_text.description}, summary: {context_text.summary}" # Phase 1 if not await self.run_phase_1(context_text): From 953ac0fc1c7b00e732eea1a7bade633c43e38067 Mon Sep 17 00:00:00 2001 From: Sandra Guerreiro Date: Tue, 25 Aug 2026 17:11:01 +0200 Subject: [PATCH 4/6] update baml verion --- poetry.lock | 273 +++---------------------------- pyproject.toml | 3 +- src/app/baml_src/generators.baml | 2 +- 3 files changed, 25 insertions(+), 253 deletions(-) diff --git a/poetry.lock b/poetry.lock index c63034db..c839bae5 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. [[package]] name = "aiohappyeyeballs" @@ -7,7 +7,6 @@ description = "Happy Eyeballs for asyncio" optional = false python-versions = ">=3.10" groups = ["main", "metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "aiohappyeyeballs-2.7.1-py3-none-any.whl", hash = "sha256:9243213661e29250eb41368e5daa826fc017156c3b8a11440826b2e3ed376472"}, {file = "aiohappyeyeballs-2.7.1.tar.gz", hash = "sha256:065665c041c42a5938ed220bdcd7230f22527fbec085e1853d2402c8a3615d9d"}, @@ -20,7 +19,6 @@ description = "Async http client/server framework (asyncio)" optional = false python-versions = ">=3.10" groups = ["main", "metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "aiohttp-3.14.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:eb0495d778817619273c108784292be161a924b9f5ae5cbbc70a2caa6838250b"}, {file = "aiohttp-3.14.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c3c200cf9757edd785051dc699c7ecbec22110dbfcb3fefc7a9f9695eda8ea7a"}, @@ -163,7 +161,6 @@ description = "aiosignal: a list of registered asynchronous callbacks" optional = false python-versions = ">=3.9" groups = ["main", "metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e"}, {file = "aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7"}, @@ -180,7 +177,6 @@ description = "A database migration tool for SQLAlchemy." optional = false python-versions = ">=3.10" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "alembic-1.18.5-py3-none-any.whl", hash = "sha256:06d8ba9d04558022f5395e9317de03d270f3dced49cee01f89fe7a13c26f14bc"}, {file = "alembic-1.18.5.tar.gz", hash = "sha256:1554982221dd17e9a749b53902407578eb305e453f71999e8c7f0a48389fff8e"}, @@ -201,7 +197,6 @@ description = "Document parameters, class attributes, return types, and variable optional = false python-versions = ">=3.9" groups = ["metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "annotated_doc-0.0.5-py3-none-any.whl", hash = "sha256:117bac03a25ede5df5440e855b32d556049ca169ead221505badf432fed4b101"}, {file = "annotated_doc-0.0.5.tar.gz", hash = "sha256:c7e58ce09192557605d8bbd92836d7e1d520ac9580096042c0bfd197efacf1bb"}, @@ -214,7 +209,6 @@ description = "Reusable constraint types to use with typing.Annotated" optional = false python-versions = ">=3.10" groups = ["main", "metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "annotated_types-0.8.0-py3-none-any.whl", hash = "sha256:f072f4d804ea359e4eaf198b1af7a8b0943881a87f31bb764f8bf219bb9419e0"}, {file = "annotated_types-0.8.0.tar.gz", hash = "sha256:13b2beaad985e05e2d6407ee4c4f35590b11f8d693a258a561055cac8f64cab7"}, @@ -227,7 +221,6 @@ description = "High-level concurrency and networking framework on top of asyncio optional = false python-versions = ">=3.10" groups = ["main", "metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "anyio-4.14.2-py3-none-any.whl", hash = "sha256:9f505dda5ac9f0c8309b5e8bd445a8c2bf7246f3ce950121e45ea15bc41d1494"}, {file = "anyio-4.14.2.tar.gz", hash = "sha256:cfa139f3ed1a23ee8f88a145ddb5ac7605b8bbfd8592baacd7ce3d8bb4313c7f"}, @@ -247,7 +240,6 @@ description = "A small Python module for determining appropriate platform-specif optional = false python-versions = "*" groups = ["metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, @@ -260,7 +252,6 @@ description = "An abstract syntax tree for Python with inference support." optional = false python-versions = ">=3.7.2" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "astroid-2.15.8-py3-none-any.whl", hash = "sha256:1aa149fc5c6589e3d0ece885b4491acd80af4f087baafa3fb5203b113e68cd3c"}, {file = "astroid-2.15.8.tar.gz", hash = "sha256:6c107453dffee9055899705de3c9ead36e74119cee151e5a9aaf7f0b0e020a6a"}, @@ -277,7 +268,6 @@ description = "Classes Without Boilerplate" optional = false python-versions = ">=3.9" groups = ["main", "metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309"}, {file = "attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32"}, @@ -290,7 +280,6 @@ description = "AutoGen agents and teams library" optional = false python-versions = ">=3.10" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "autogen_agentchat-0.5.7-py3-none-any.whl", hash = "sha256:b9c8548e02eed9765c02800182b13bd9747f1561b23df66983bedd8c5fdddb86"}, {file = "autogen_agentchat-0.5.7.tar.gz", hash = "sha256:6f888f03224d658cc130548c3e42558a177a7f7f3eb59b414396c412ec282728"}, @@ -306,7 +295,6 @@ description = "Foundational interfaces and agent runtime implementation for Auto optional = false python-versions = ">=3.10" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "autogen_core-0.5.7-py3-none-any.whl", hash = "sha256:a276af987b7861092194bfd73bab49ac36f3fee55958a60b0b43ea67612d2e50"}, {file = "autogen_core-0.5.7.tar.gz", hash = "sha256:0e3b23b64bd2c7c3180ab4431c137104473e11a846088cc2dd629a0e1fdfbf2f"}, @@ -327,7 +315,6 @@ description = "AutoGen extensions library" optional = false python-versions = ">=3.10" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "autogen_ext-0.5.7-py3-none-any.whl", hash = "sha256:1744195107d814621dc0e967b7f7a4fa09e063fa5ed728c3ba118e9aa88acad2"}, {file = "autogen_ext-0.5.7.tar.gz", hash = "sha256:987aa70d8eecd05c9dea4aad793e9d1369a3d3ceb022dfeafb4cd98b6b4869fc"}, @@ -380,7 +367,6 @@ description = "Microsoft Azure AI Content Safety Client Library for Python" optional = false python-versions = ">=3.7" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "azure-ai-contentsafety-1.0.0.tar.gz", hash = "sha256:052731bd1419a720fa00910f46bf3428c4e5bd05280da7393d0c8106d46cc6d7"}, {file = "azure_ai_contentsafety-1.0.0-py3-none-any.whl", hash = "sha256:e1c5574a541f9290fdd071d23535e14b1f463af231a6f0ac0f917e125f0463cf"}, @@ -397,7 +383,6 @@ description = "Microsoft Corporation Azure AI Content Understanding Client Libra optional = false python-versions = ">=3.9" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "azure_ai_contentunderstanding-1.2.0b2-py3-none-any.whl", hash = "sha256:284885c9a45ef50f3938714cb6636c675c1ce9436dc0d561ff425fc124990379"}, {file = "azure_ai_contentunderstanding-1.2.0b2.tar.gz", hash = "sha256:0ccef3c8087759ca788aabcc9af7b22cd8ada2df0236bf63563f4974c2d8cfcd"}, @@ -415,7 +400,6 @@ description = "Microsoft Azure AI Inference Client Library for Python" optional = false python-versions = ">=3.8" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "azure_ai_inference-1.0.0b9-py3-none-any.whl", hash = "sha256:49823732e674092dad83bb8b0d1b65aa73111fab924d61349eb2a8cdc0493990"}, {file = "azure_ai_inference-1.0.0b9.tar.gz", hash = "sha256:1feb496bd84b01ee2691befc04358fa25d7c344d8288e99364438859ad7cd5a4"}, @@ -437,7 +421,6 @@ description = "Microsoft Corporation Azure AI Projects Client Library for Python optional = false python-versions = ">=3.9" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "azure_ai_projects-2.4.0-py3-none-any.whl", hash = "sha256:d9a9504b3a498d7300d23411f09d49096e2a184473250d6070093828b7b67e49"}, {file = "azure_ai_projects-2.4.0.tar.gz", hash = "sha256:121154f70cf7de350a38a75e1da710f4e98b7d8114bc3b1ea25124469e34186f"}, @@ -458,7 +441,6 @@ description = "Microsoft Azure Core Library for Python" optional = false python-versions = ">=3.10" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "azure_core-1.41.0-py3-none-any.whl", hash = "sha256:522b4011e8180b1a3dcd2024396a4e7fe9ac37fb8597db47163d230b5efe892d"}, {file = "azure_core-1.41.0.tar.gz", hash = "sha256:f46ff5dfcd230f25cf1c19e8a34b8dc08a337b2503e268bb600a16c00db8ad5a"}, @@ -479,7 +461,6 @@ description = "Microsoft Azure Identity Library for Python" optional = false python-versions = ">=3.9" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "azure_identity-1.25.3-py3-none-any.whl", hash = "sha256:f4d0b956a8146f30333e071374171f3cfa7bdb8073adb8c3814b65567aa7447c"}, {file = "azure_identity-1.25.3.tar.gz", hash = "sha256:ab23c0d63015f50b630ef6c6cf395e7262f439ce06e5d07a64e874c724f8d9e6"}, @@ -499,7 +480,6 @@ description = "Microsoft Corporation Azure Search Documents Client Library for P optional = false python-versions = ">=3.9" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "azure_search_documents-12.0.0-py3-none-any.whl", hash = "sha256:d88114e4179cd753845711042380a4571e7faa8619addf5e017928ebe37fc0d1"}, {file = "azure_search_documents-12.0.0.tar.gz", hash = "sha256:8e6d73ec0ed1623083435b757e34324db65d72d4e09cca061a59fc7e90c8ddbc"}, @@ -517,7 +497,6 @@ description = "Microsoft Azure Blob Storage Client Library for Python" optional = false python-versions = ">=3.9" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "azure_storage_blob-12.30.0-py3-none-any.whl", hash = "sha256:d415ac50b67a8da6b3ae7e9f1014b1b55cd7aafa0b8d4ca9b380568dc7360423"}, {file = "azure_storage_blob-12.30.0.tar.gz", hash = "sha256:2cd74d4d5731e5eb6b8d5c5056ee115a5e88f8fdf22517b739836fda685018be"}, @@ -539,46 +518,27 @@ description = "Function decoration for backoff and retry" optional = false python-versions = ">=3.7,<4.0" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8"}, {file = "backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba"}, ] -[[package]] -name = "baml-cli" -version = "0.1.0" -description = "The BAML CLI, distributed in Python" -optional = false -python-versions = "<4.0,>=3.8" -groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" -files = [ - {file = "baml_cli-0.1.0-py3-none-any.whl", hash = "sha256:3e0630a10bf940c539e57fc37daf70ea6c097a7feda423f01822a936c2439cdb"}, - {file = "baml_cli-0.1.0.tar.gz", hash = "sha256:ebc3c325f167282918d397e0597dadf2323f463be7be1249b8664a6d2c48166c"}, -] - -[package.dependencies] -baml_py = ">=0.1.0" - [[package]] name = "baml-py" -version = "0.220.0" -description = "BAML python bindings (pyproject.toml)" +version = "0.226.1" +description = "BAML v0 — Python runtime for baml_client" optional = false -python-versions = "*" +python-versions = ">=3.10" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ - {file = "baml_py-0.220.0-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:edf4112d1b1da5765ebfcadab7f9eee48ac337e5b06ff8286c970e27cd87e28e"}, - {file = "baml_py-0.220.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:5e72466c12d9b6597d3dbe16a364699307911483bffc07e0b66d701cdefecf6d"}, - {file = "baml_py-0.220.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1da0548281e0d21d56886a46aaeef0680e660fc1b36a9bdbc11d04efad96cfcc"}, - {file = "baml_py-0.220.0-cp38-abi3-manylinux_2_24_aarch64.whl", hash = "sha256:0895a3633b061505a3d48d259ca6a480dd37f2a9dade6ea3bf5b8fb1f7632d11"}, - {file = "baml_py-0.220.0-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:52ba7755026322fd73dd70087c09745aae86df47306b1dde06cf731266b55c34"}, - {file = "baml_py-0.220.0-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:a240cdc76455f8bd8aa5a5b3ad805369edb27320111bb915df543c55a846b916"}, - {file = "baml_py-0.220.0-cp38-abi3-win_amd64.whl", hash = "sha256:a5b6d1374eec1225bc76444fc2e5dbb0b7f14ed5a1a043e20beef778475ff8d4"}, - {file = "baml_py-0.220.0-cp38-abi3-win_arm64.whl", hash = "sha256:76fe51528ac5cd07ea2bd8ab2ccd0fd302cd9fc1f5c5c3236c35edfede2d8b48"}, - {file = "baml_py-0.220.0-pp39-pypy39_pp73-manylinux_2_24_aarch64.whl", hash = "sha256:04d286c6772c2d8f20e58e994ac0e8dfc6b08254a85cc9603f7daf09538feeea"}, + {file = "baml_py-0.226.1-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:f1a89eee0d9e320ca48265ddbaae7d84677a83e26c3209f4ca358c6e630a335e"}, + {file = "baml_py-0.226.1-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:6b45a25eca7b3a0473525dfe7b9df304760104241cc06e824c883a18ced076fc"}, + {file = "baml_py-0.226.1-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a46363b0d5eec11094f394bd610798819333334e250599331795383882f37f8"}, + {file = "baml_py-0.226.1-cp38-abi3-manylinux_2_24_aarch64.whl", hash = "sha256:92b3f9f019c56ef388833a5ddaffd44d0b813814879c7c3d439cb6ad3ae72edb"}, + {file = "baml_py-0.226.1-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:302f3e31f6c2dec922b44a3db67a017b01d9f3a908ea0c364072961addcaa794"}, + {file = "baml_py-0.226.1-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:5655e6503ca43e05e91b48c1b846c5516d8d429cedda6e556238c003337adc93"}, + {file = "baml_py-0.226.1-cp38-abi3-win_amd64.whl", hash = "sha256:9a43673a9ecef9098c399676c4c791714e006e5f8fcba650d3ef53660060e39c"}, + {file = "baml_py-0.226.1-cp38-abi3-win_arm64.whl", hash = "sha256:609b1d3255eac905d40ad66c024c62f09055dd79275968bd51d7ef345828250f"}, ] [[package]] @@ -588,7 +548,6 @@ description = "Screen-scraping library" optional = false python-versions = ">=3.7.0" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "beautifulsoup4-4.15.0-py3-none-any.whl", hash = "sha256:d6f88de62e1d4e38ecb1077eb9724cd0eff29d2a08ca16a401e9b9e93f117cf9"}, {file = "beautifulsoup4-4.15.0.tar.gz", hash = "sha256:288e3ca7d54b06f2ac191970bc275c1939cb46d450b255bf6718b04aa37ab4f7"}, @@ -612,7 +571,6 @@ description = "The bidirectional mapping library for Python." optional = false python-versions = ">=3.8" groups = ["metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "bidict-0.23.1-py3-none-any.whl", hash = "sha256:5dae8d4d79b552a71cbabc7deb25dfe8ce710b17ff41711e13010ead2abfc3e5"}, {file = "bidict-0.23.1.tar.gz", hash = "sha256:03069d763bc387bbd20e7d49914e75fc4132a41937fa3405417e1a5a2d006d71"}, @@ -625,7 +583,6 @@ description = "The uncompromising code formatter." optional = false python-versions = ">=3.9" groups = ["dev"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "black-24.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6668650ea4b685440857138e5fe40cde4d652633b1bdffc62933d0db4ed9812"}, {file = "black-24.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1c536fcf674217e87b8cc3657b81809d3c085d7bf3ef262ead700da345bfa6ea"}, @@ -671,7 +628,6 @@ description = "Fast, simple object-to-object and broadcast signaling" optional = false python-versions = ">=3.9" groups = ["metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc"}, {file = "blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf"}, @@ -684,7 +640,6 @@ description = "Python bindings for the Brotli compression library" optional = false python-versions = "*" groups = ["metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "brotli-1.2.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:99cfa69813d79492f0e5d52a20fd18395bc82e671d5d40bd5a91d13e75e468e8"}, {file = "brotli-1.2.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:3ebe801e0f4e56d17cd386ca6600573e3706ce1845376307f5d2cbd32149b69a"}, @@ -795,7 +750,6 @@ description = "Dummy package for Beautiful Soup (beautifulsoup4)" optional = false python-versions = "*" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "bs4-0.0.2-py2.py3-none-any.whl", hash = "sha256:abf8742c0805ef7f662dce4b51cca104cffe52b835238afc169142ab9b3fbccc"}, {file = "bs4-0.0.2.tar.gz", hash = "sha256:a48685c58f50fe127722417bae83fe6badf500d54b55f7e39ffe43b798653925"}, @@ -811,7 +765,6 @@ description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main", "metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "certifi-2026.7.22-py3-none-any.whl", hash = "sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775"}, {file = "certifi-2026.7.22.tar.gz", hash = "sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55"}, @@ -926,7 +879,7 @@ files = [ {file = "cffi-2.1.0-cp315-cp315t-win_arm64.whl", hash = "sha256:cbb7640ce37159548d2147b5b8c241f962143d4c71231431820783f4dc78f210"}, {file = "cffi-2.1.0.tar.gz", hash = "sha256:efc1cdd798b1aaf39b4610bba7aad28c9bea9b910f25c784ccf9ec1fa719d1f9"}, ] -markers = {main = "(sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\") and platform_python_implementation != \"PyPy\"", metrics = "(sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\") and (platform_python_implementation == \"CPython\" and sys_platform == \"win32\" or implementation_name == \"pypy\")"} +markers = {main = "platform_python_implementation != \"PyPy\"", metrics = "(platform_python_implementation == \"CPython\" or implementation_name == \"pypy\") and (sys_platform == \"win32\" or implementation_name == \"pypy\")"} [package.dependencies] pycparser = {version = "*", markers = "implementation_name != \"PyPy\""} @@ -938,7 +891,6 @@ description = "The Real First Universal Charset Detector. Open, modern and activ optional = false python-versions = ">=3.7" groups = ["main", "metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "charset_normalizer-3.4.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd6280cf040f233bd7d3407b743b4b4c74f70e8e1c4199cb112a62c941c0772a"}, {file = "charset_normalizer-3.4.9-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa99adc8f081b475a12843953db36831eaf83ec33eb46a90629ca6a5de45a616"}, @@ -1042,7 +994,6 @@ description = "Composable command line interface toolkit" optional = false python-versions = ">=3.10" groups = ["main", "dev", "metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "click-8.4.2-py3-none-any.whl", hash = "sha256:e6f9f66136c816745b9d65817da91d61d957fb16e02e4dcd0552553c5a197b76"}, {file = "click-8.4.2.tar.gz", hash = "sha256:9a6cea6e60b17ebe0a44c5cc636d94f09bd66142c1cd7d8b4cd731c4917a15f6"}, @@ -1062,7 +1013,7 @@ files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] -markers = {main = "(sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\") and (platform_system == \"Windows\" or sys_platform == \"win32\")", dev = "(sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\") and (platform_system == \"Windows\" or sys_platform == \"win32\")", metrics = "(sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\") and platform_system == \"Windows\""} +markers = {main = "platform_system == \"Windows\" or sys_platform == \"win32\"", dev = "platform_system == \"Windows\" or sys_platform == \"win32\"", metrics = "platform_system == \"Windows\""} [[package]] name = "configargparse" @@ -1071,7 +1022,6 @@ description = "A drop-in replacement for argparse that allows options to also be optional = false python-versions = ">=3.6" groups = ["metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "configargparse-1.7.5-py3-none-any.whl", hash = "sha256:1e63fdffedf94da9cd435fc13a1cd24777e76879dd2343912c1f871d4ac8c592"}, {file = "configargparse-1.7.5.tar.gz", hash = "sha256:e3f9a7bb6be34d66b2e3c4a2f58e3045f8dfae47b0dc039f87bcfaa0f193fb0f"}, @@ -1088,7 +1038,6 @@ description = "Code coverage measurement for Python" optional = false python-versions = ">=3.10" groups = ["dev"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "coverage-7.15.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9b5bd92ff1ec22e535eab0de75fa6db021992791f461a2aceb7822c625a1187d"}, {file = "coverage-7.15.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:44826758cfe73fcd0e6af5deb4ba6d5417cc1d13df3acb35c93484a11160f846"}, @@ -1193,7 +1142,6 @@ description = "cryptography is a package which provides cryptographic recipes an optional = false python-versions = "!=3.9.0,!=3.9.1,>=3.9" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "cryptography-49.0.0-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:966fe0e9c67490071f14c0d2b1cb2dfb3023c5ce39457343931415f08382f2db"}, {file = "cryptography-49.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:36d1709f992593689b45bda411498d62c6e365f2ca00b84657d4dadd24de16db"}, @@ -1256,7 +1204,6 @@ description = "HuggingFace community-driven open-source library of datasets" optional = false python-versions = ">=3.10.0" groups = ["metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "datasets-5.0.1-py3-none-any.whl", hash = "sha256:9fbf73688f8c18f7529b4fe592abd04015f81d1e58001e4bac73ffb2b39d7cc4"}, {file = "datasets-5.0.1.tar.gz", hash = "sha256:ce22bb851efd7494f08aad33b940803784434f6e77763d00679a0dc45fcf686a"}, @@ -1303,7 +1250,6 @@ description = "serialize all of Python" optional = false python-versions = ">=3.9" groups = ["main", "metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "dill-0.4.1-py3-none-any.whl", hash = "sha256:1e1ce33e978ae97fcfcff5638477032b801c46c7c65cf717f95fbc2248f79a9d"}, {file = "dill-0.4.1.tar.gz", hash = "sha256:423092df4182177d4d8ba8290c8a5b640c66ab35ec7da59ccfa00f6fa3eea5fa"}, @@ -1320,7 +1266,6 @@ description = "Disk Cache -- Disk and file backed persistent cache." optional = false python-versions = ">=3" groups = ["metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "diskcache-5.6.3-py3-none-any.whl", hash = "sha256:5e31b2d5fbad117cc363ebaf6b689474db18a1f6438bc82358b024abd4c2ca19"}, {file = "diskcache-5.6.3.tar.gz", hash = "sha256:2c3a3fa2743d8535d832ec61c2054a1641f41775aa7c556758a109941e33e4fc"}, @@ -1333,7 +1278,6 @@ description = "Distro - an OS platform information API" optional = false python-versions = ">=3.6" groups = ["main", "metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2"}, {file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"}, @@ -1346,7 +1290,6 @@ description = "Parse Python docstrings in reST, Google and Numpydoc format" optional = false python-versions = ">=3.8" groups = ["metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "docstring_parser-0.18.0-py3-none-any.whl", hash = "sha256:b3fcbed555c47d8479be0796ef7e19c2670d428d72e96da63f3a40122860374b"}, {file = "docstring_parser-0.18.0.tar.gz", hash = "sha256:292510982205c12b1248696f44959db3cdd1740237a968ea1e2e7a900eeb2015"}, @@ -1364,7 +1307,6 @@ description = "EcoLogits tracks and estimates the energy consumption and environ optional = false python-versions = "<4,>=3.9" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "ecologits-0.5.2-py3-none-any.whl", hash = "sha256:d228aba35e56ee22080ea205a0955d914384dd4be82a26ed31e9017aa503ea58"}, {file = "ecologits-0.5.2.tar.gz", hash = "sha256:33e9ec145c733306dae86a78a875064fc040f94dcd6b3b7e5dd7de8bfe94ab65"}, @@ -1391,7 +1333,6 @@ description = "Like `typing._eval_type`, but lets older Python versions use newe optional = false python-versions = ">=3.7" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "eval_type_backport-0.4.0-py3-none-any.whl", hash = "sha256:ad5e2a8db71b6696a56eafb938b0f5a337d3217f256b8e158b469422b4772b20"}, {file = "eval_type_backport-0.4.0.tar.gz", hash = "sha256:8397d25e6524c2e67b9576bb0636be27dea2192017711220c534ec2de921e9b0"}, @@ -1407,7 +1348,6 @@ description = "FastAPI framework, high performance, easy to learn, fast to code, optional = false python-versions = ">=3.8" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "fastapi-0.115.14-py3-none-any.whl", hash = "sha256:6c0c8bf9420bd58f565e585036d971872472b4f7d3f6c73b698e10cffdefb3ca"}, {file = "fastapi-0.115.14.tar.gz", hash = "sha256:b1de15cdc1c499a4da47914db35d0e4ef8f1ce62b624e94e0e5824421df99739"}, @@ -1429,7 +1369,6 @@ description = "Python bindings to Rust's UUID library." optional = false python-versions = ">=3.8" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "fastuuid-0.14.0-cp310-cp310-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:6e6243d40f6c793c3e2ee14c13769e341b90be5ef0c23c82fa6515a96145181a"}, {file = "fastuuid-0.14.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:13ec4f2c3b04271f62be2e1ce7e95ad2dd1cf97e94503a3760db739afbd48f00"}, @@ -1518,7 +1457,6 @@ description = "A platform independent file lock." optional = false python-versions = ">=3.10" groups = ["main", "metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "filelock-3.32.0-py3-none-any.whl", hash = "sha256:d396bea984af47333ef05e50eae7eff88c84256de6112aea0ec48a233c064fe3"}, {file = "filelock-3.32.0.tar.gz", hash = "sha256:7be2ad23a14607ccc71808e68fe30848aeace7058ace17852f68e2a68e310402"}, @@ -1531,7 +1469,6 @@ description = "the modular source code checker: pep8 pyflakes and co" optional = false python-versions = ">=3.8.1" groups = ["dev"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "flake8-6.1.0-py2.py3-none-any.whl", hash = "sha256:ffdfce58ea94c6580c77888a86506937f9a1a227dfcd15f245d694ae20a6b6e5"}, {file = "flake8-6.1.0.tar.gz", hash = "sha256:d5b3857f07c030bdb5bf41c7f53799571d75c4491748a3adcd47de929e34cd23"}, @@ -1549,7 +1486,6 @@ description = "A simple framework for building complex web applications." optional = false python-versions = ">=3.9" groups = ["metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "flask-3.1.3-py3-none-any.whl", hash = "sha256:f4bcbefc124291925f1a26446da31a5178f9483862233b23c0c96a20701f670c"}, {file = "flask-3.1.3.tar.gz", hash = "sha256:0ef0e52b8a9cd932855379197dd8f94047b359ca0a78695144304cb45f87c9eb"}, @@ -1574,7 +1510,6 @@ description = "A Flask extension simplifying CORS support" optional = false python-versions = "<4.0,>=3.9" groups = ["metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "flask_cors-6.0.5-py3-none-any.whl", hash = "sha256:68fcf75693e961f3af26683b23c4b9a8fb6b64de17d20d0c37b95e8de7ab2ed8"}, {file = "flask_cors-6.0.5.tar.gz", hash = "sha256:30c5031552cd59f620ac0c8211dac45b345d3b2df310e7721879e4f46ef9c601"}, @@ -1591,7 +1526,6 @@ description = "User authentication and session management for Flask." optional = false python-versions = ">=3.7" groups = ["metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "Flask-Login-0.6.3.tar.gz", hash = "sha256:5e23d14a607ef12806c699590b89d0f0e0d67baeec599d75947bf9c147330333"}, {file = "Flask_Login-0.6.3-py3-none-any.whl", hash = "sha256:849b25b82a436bf830a054e74214074af59097171562ab10bfa999e6b78aae5d"}, @@ -1608,7 +1542,6 @@ description = "A list-like structure which implements collections.abc.MutableSeq optional = false python-versions = ">=3.9" groups = ["main", "metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "frozenlist-1.8.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b37f6d31b3dcea7deb5e9696e529a6aa4a898adc33db82da12e4c60a7c4d2011"}, {file = "frozenlist-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef2b7b394f208233e471abc541cc6991f907ffd47dc72584acee3147899d6565"}, @@ -1749,7 +1682,6 @@ description = "File-system specification" optional = false python-versions = ">=3.10" groups = ["main", "metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "fsspec-2026.6.0-py3-none-any.whl", hash = "sha256:02e0b71817df9b2169dc30a16832045764def1191b43dcff5bb85bdee212d2a1"}, {file = "fsspec-2026.6.0.tar.gz", hash = "sha256:f5bac145310fe30e16e1471bd6840b2d990d609e872251d7e674241822abf01a"}, @@ -1793,7 +1725,6 @@ description = "Coroutine-based network library" optional = false python-versions = ">=3.9" groups = ["metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "gevent-25.5.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:8e5a0fab5e245b15ec1005b3666b0a2e867c26f411c8fe66ae1afe07174a30e9"}, {file = "gevent-25.5.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7b80a37f2fb45ee4a8f7e64b77dd8a842d364384046e394227b974a4e9c9a52"}, @@ -1856,7 +1787,6 @@ description = "HTTP client library for gevent" optional = false python-versions = ">=3.9" groups = ["metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "geventhttpclient-2.3.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25c03073a1136c2b93189488bb1bfc0868d90aa106dd49f15ac964d2454296c6"}, {file = "geventhttpclient-2.3.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e1e711eb91085585f61445c7313e1a0acb159b5dc11327930e673b4899ebd84f"}, @@ -2037,7 +1967,7 @@ files = [ {file = "greenlet-3.5.4-cp315-cp315t-win_arm64.whl", hash = "sha256:08fc36de8442d5c3e95b044550dbea9bf144d31ec0cc58e36fb241cb6ef6a994"}, {file = "greenlet-3.5.4.tar.gz", hash = "sha256:0232ae1de90a8e07867bb127d7a6ba2301e859145489f25cda8a6096dabe1d20"}, ] -markers = {main = "(sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\") and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")", metrics = "(sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\") and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\" or platform_python_implementation == \"CPython\")"} +markers = {main = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\"", metrics = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\" or platform_python_implementation == \"CPython\""} [package.extras] docs = ["Sphinx", "furo"] @@ -2050,7 +1980,6 @@ description = "HTTP/2-based RPC framework" optional = false python-versions = ">=3.10" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "grpcio-1.83.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:fba099b716e73512d61b97f71ea3c31a72abb36904036e316bf4dd148ca8dcc8"}, {file = "grpcio-1.83.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:6755ed67cc3e454d51ae9f6e1915b80d3942fa4de956ef48dacd45ab7f40b727"}, @@ -2118,7 +2047,6 @@ description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" optional = false python-versions = ">=3.8" groups = ["main", "metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86"}, {file = "h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1"}, @@ -2131,7 +2059,6 @@ description = "Pure-Python HTTP/2 protocol implementation" optional = false python-versions = ">=3.10" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "h2-4.4.0-py3-none-any.whl", hash = "sha256:6acffe1aeab79098d7eb0f8385c1add11f2c7a94815f6fa2b7060eeddee3d87c"}, {file = "h2-4.4.0.tar.gz", hash = "sha256:46b551bdcdc7e83cf5c04d0bf93badb8a939bd2287d9fee1abb23a445b9e0580"}, @@ -2148,7 +2075,7 @@ description = "Fast transfer of large files with the Hugging Face Hub." optional = false python-versions = ">=3.8" groups = ["main", "metrics"] -markers = "(sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\") and (platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"arm64\" or platform_machine == \"aarch64\")" +markers = "platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"arm64\" or platform_machine == \"aarch64\"" files = [ {file = "hf_xet-1.5.2-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:4a5ecb9cda8512ba2aa8ee5d37c87a1422992165892d653098c7b90247481c3b"}, {file = "hf_xet-1.5.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8764488197c1d7b1378c8438c18d2eea902e150dbca0b0f0d2d32603fb9b5576"}, @@ -2179,7 +2106,6 @@ description = "Pure-Python HPACK header encoding" optional = false python-versions = ">=3.10" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "hpack-4.2.0-py3-none-any.whl", hash = "sha256:858ac0b02280fa582b5080d68db0899c62a80375e0e5413a74970c5e518b6986"}, {file = "hpack-4.2.0.tar.gz", hash = "sha256:0895cfa3b5531fc65fe439c05eb65144f123bf7a394fcaa56aa423548d8e45c0"}, @@ -2192,7 +2118,6 @@ description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" groups = ["main", "metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55"}, {file = "httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8"}, @@ -2215,7 +2140,6 @@ description = "A collection of framework independent HTTP protocol utils." optional = false python-versions = ">=3.9" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "httptools-0.8.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:bf3b6f807c8541503cecfbb8a8dffb385640d0d96102f3d112aa8740f9b7c826"}, {file = "httptools-0.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:da684f2e1aa2ee9bdcb083f3f3a68c5956750b375bc5df864d3a5f0c42a40b77"}, @@ -2276,7 +2200,6 @@ description = "The next generation HTTP client." optional = false python-versions = ">=3.8" groups = ["main", "metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, @@ -2303,7 +2226,6 @@ description = "Consume Server-Sent Event (SSE) messages with HTTPX." optional = false python-versions = ">=3.9" groups = ["main", "metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "httpx_sse-0.4.3-py3-none-any.whl", hash = "sha256:0ac1c9fe3c0afad2e0ebb25a934a59f4c7823b60792691f779fad2c5568830fc"}, {file = "httpx_sse-0.4.3.tar.gz", hash = "sha256:9b1ed0127459a66014aec3c56bebd93da3c1bc8bb6618c8082039a44889a755d"}, @@ -2316,7 +2238,6 @@ description = "Client library to download and publish models, datasets and other optional = false python-versions = ">=3.8.0" groups = ["main", "metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "huggingface_hub-0.36.2-py3-none-any.whl", hash = "sha256:48f0c8eac16145dfce371e9d2d7772854a4f591bcb56c9cf548accf531d54270"}, {file = "huggingface_hub-0.36.2.tar.gz", hash = "sha256:1934304d2fb224f8afa3b87007d58501acfda9215b334eed53072dd5e815ff7a"}, @@ -2356,7 +2277,6 @@ description = "Pure-Python HTTP/2 framing" optional = false python-versions = ">=3.9" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "hyperframe-6.1.0-py3-none-any.whl", hash = "sha256:b03380493a519fce58ea5af42e4a42317bf9bd425596f7a0835ffce80f1a42e5"}, {file = "hyperframe-6.1.0.tar.gz", hash = "sha256:f630908a00854a7adeabd6382b43923a4c4cd4b821fcb527e6ab9e15382a3b08"}, @@ -2369,7 +2289,6 @@ description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.9" groups = ["main", "metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2"}, {file = "idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848"}, @@ -2385,7 +2304,6 @@ description = "Read metadata from Python packages" optional = false python-versions = ">=3.9" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151"}, {file = "importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb"}, @@ -2410,7 +2328,6 @@ description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.10" groups = ["dev"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12"}, {file = "iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730"}, @@ -2423,7 +2340,6 @@ description = "structured outputs for llm" optional = false python-versions = "<4.0,>=3.9" groups = ["metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "instructor-1.15.4-py3-none-any.whl", hash = "sha256:00e0ecda80fd9746fb6d082d3f9641e193adb1d8849f0775f91519a82aeff968"}, {file = "instructor-1.15.4.tar.gz", hash = "sha256:ea2280c3678d0f6891c4d826104f95624b680e69877113a6345b1d7c9027ba0f"}, @@ -2474,7 +2390,6 @@ description = "An ISO 8601 date/time/duration parser and formatter" optional = false python-versions = ">=3.7" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "isodate-0.7.2-py3-none-any.whl", hash = "sha256:28009937d8031054830160fce6d409ed342816b543597cece116d966c6d99e15"}, {file = "isodate-0.7.2.tar.gz", hash = "sha256:4cd1aa0f43ca76f4a6c6c0292a85f40b35ec2e43e315b59f06e6d32171a953e6"}, @@ -2487,7 +2402,6 @@ description = "A Python utility / library to sort Python imports." optional = false python-versions = ">=3.8.0" groups = ["main", "dev"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, @@ -2503,7 +2417,6 @@ description = "Safely pass data to untrusted environments and back." optional = false python-versions = ">=3.8" groups = ["metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef"}, {file = "itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173"}, @@ -2516,7 +2429,6 @@ description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" groups = ["main", "metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, @@ -2535,7 +2447,6 @@ description = "Fast iterable JSON parser." optional = false python-versions = ">=3.9" groups = ["main", "metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "jiter-0.14.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:02f36a5c700f105ac04a6556fe664a59037a2c200db3b7e88784fac2ddf02531"}, {file = "jiter-0.14.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:41eab6c09ceffb6f0fe25e214b3068146edb1eda3649ca2aee2a061029c7ba2e"}, @@ -2655,7 +2566,6 @@ description = "Lightweight pipelining with Python functions" optional = false python-versions = ">=3.9" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "joblib-1.5.3-py3-none-any.whl", hash = "sha256:5fc3c5039fc5ca8c0276333a188bbd59d6b7ab37fe6632daa76bc7f9ec18e713"}, {file = "joblib-1.5.3.tar.gz", hash = "sha256:8561a3269e6801106863fd0d6d84bb737be9e7631e33aaed3fb9ce5953688da3"}, @@ -2668,7 +2578,6 @@ description = "A package to repair broken json strings" optional = false python-versions = ">=3.10" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "json_repair-0.55.2-py3-none-any.whl", hash = "sha256:08109b093fc9fe99b956b8309f9c1dfd2740637056d93df489cc7b4eb56d4286"}, {file = "json_repair-0.55.2.tar.gz", hash = "sha256:aa5c89692126257efb8e3cf1efe6787387634b5c360fb77313d66f146e980de6"}, @@ -2681,7 +2590,6 @@ description = "Apply JSON-Patches (RFC 6902)" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" groups = ["main", "metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade"}, {file = "jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c"}, @@ -2697,7 +2605,6 @@ description = "A lightweight and powerful JSONPath implementation for Python" optional = false python-versions = ">=3.8" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "jsonpath_python-1.1.6-py3-none-any.whl", hash = "sha256:a1c50afd8d3fbbaf47a4873bc890dcb3c15da96f5c020327977d844d8731a2d4"}, {file = "jsonpath_python-1.1.6.tar.gz", hash = "sha256:dded9932b4ec41fb8726e09c83afa4e6be618f938c2db287cc2a81723c639671"}, @@ -2713,7 +2620,6 @@ description = "Identify specific nodes in a JSON document (RFC 6901) " optional = false python-versions = ">=3.10" groups = ["main", "metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "jsonpointer-3.1.1-py3-none-any.whl", hash = "sha256:8ff8b95779d071ba472cf5bc913028df06031797532f08a7d5b602d8b2a488ca"}, {file = "jsonpointer-3.1.1.tar.gz", hash = "sha256:0b801c7db33a904024f6004d526dcc53bbb8a4a0f4e32bfd10beadf60adf1900"}, @@ -2726,7 +2632,6 @@ description = "jsonref is a library for automatic dereferencing of JSON Referenc optional = false python-versions = ">=3.7" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "jsonref-1.1.0-py3-none-any.whl", hash = "sha256:590dc7773df6c21cbf948b5dac07a72a251db28b0238ceecce0a2abfa8ec30a9"}, {file = "jsonref-1.1.0.tar.gz", hash = "sha256:32fe8e1d85af0fdefbebce950af85590b22b60f9e95443176adbde4e1ecea552"}, @@ -2739,7 +2644,6 @@ description = "An implementation of JSON Schema validation for Python" optional = false python-versions = ">=3.10" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce"}, {file = "jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326"}, @@ -2762,7 +2666,6 @@ description = "The JSON Schema meta-schemas and vocabularies, exposed as a Regis optional = false python-versions = ">=3.9" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe"}, {file = "jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d"}, @@ -2778,7 +2681,6 @@ description = "Building applications with LLMs through composability" optional = false python-versions = "<4.0.0,>=3.10.0" groups = ["main", "metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "langchain-1.2.18-py3-none-any.whl", hash = "sha256:8432d43a65540845ed6f1a783d38d869c4659a6b9405f9a510169ad40d2f7bae"}, {file = "langchain-1.2.18.tar.gz", hash = "sha256:7e829dbf117affadfd2067a0e97b4af20222f535f30fb812a28472d842c1074c"}, @@ -2816,7 +2718,6 @@ description = "An integration package to support Microsoft Foundry (formerly Azu optional = false python-versions = "<4.0,>=3.10.0" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "langchain_azure_ai-1.2.8-py3-none-any.whl", hash = "sha256:b86a6c4e5c76277ec4044de03562e7cc995c21754433793763310c2894dcc756"}, {file = "langchain_azure_ai-1.2.8.tar.gz", hash = "sha256:3d1c9c4d9ebe7e06ba38a886930f6aa91e3635a54164cdf7a8810ac3be819bf1"}, @@ -2852,7 +2753,6 @@ description = "Building applications with LLMs through composability" optional = false python-versions = "<4.0.0,>=3.10.0" groups = ["metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "langchain_classic-1.0.8-py3-none-any.whl", hash = "sha256:1a11ea7fbe630c4f2af2f3873d27718ceac9488cf32d0821030be7cf039a6213"}, {file = "langchain_classic-1.0.8.tar.gz", hash = "sha256:ada0cc341a8a5b80fb24d73bdfaaeb849056ee2d8a41cc468355163fd3667484"}, @@ -2893,7 +2793,6 @@ description = "Community contributed LangChain integrations." optional = false python-versions = "<4.0.0,>=3.10.0" groups = ["metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "langchain_community-0.4.2-py3-none-any.whl", hash = "sha256:84dd8c5122532394d5b6849a5fc9995ef28e4f77227daeb09f24b3d942e9e466"}, {file = "langchain_community-0.4.2.tar.gz", hash = "sha256:a99308160d53d7e9b5965ee665e5173709914338210089fd5788ad724432c21e"}, @@ -2922,7 +2821,6 @@ description = "Building applications with LLMs through composability" optional = false python-versions = "<4.0.0,>=3.10.0" groups = ["main", "metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "langchain_core-1.5.2-py3-none-any.whl", hash = "sha256:a687dd7c3b22c6c1294e1c1eeb61fb6f3a308e6015a1d75b576b3836ad5b5aed"}, {file = "langchain_core-1.5.2.tar.gz", hash = "sha256:2d13ab35b42eec63d4669a483776b8cdd778ee764107149369fb369d84c08c41"}, @@ -2946,7 +2844,6 @@ description = "An integration package connecting Mistral and LangChain" optional = false python-versions = "<4.0.0,>=3.10.0" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "langchain_mistralai-1.1.6-py3-none-any.whl", hash = "sha256:0aa59b313b406ee934dc0431873b0e11849d8d746a6a7e281103c6848a9b1319"}, {file = "langchain_mistralai-1.1.6.tar.gz", hash = "sha256:c2694828adee61a497c571e25eaba1472095cd1d1f260a7fe15a6e60f9ece039"}, @@ -2966,7 +2863,6 @@ description = "An integration package connecting OpenAI and LangChain" optional = false python-versions = "<4.0.0,>=3.10.0" groups = ["main", "metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "langchain_openai-1.4.1-py3-none-any.whl", hash = "sha256:8528bb34cc78fdfd2d895573c7917f9441cbb82db5f18ae0e6b3b75d95bdefb3"}, {file = "langchain_openai-1.4.1.tar.gz", hash = "sha256:6d16be615d997db80294731b8e768783f1fb8e0313668e64acd50cd68acbad20"}, @@ -2984,7 +2880,6 @@ description = "Python bindings for the LangChain agent streaming protocol" optional = false python-versions = "<4.0.0,>=3.10.0" groups = ["main", "metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "langchain_protocol-0.0.18-py3-none-any.whl", hash = "sha256:70b53a86fbf9cedc863555effe44da192ab02d556ddbf2cf95b8873adcf41b5a"}, {file = "langchain_protocol-0.0.18.tar.gz", hash = "sha256:ec3e11782f1ed0c9db38e5a9ed01b0e7a0d3fba406faa8aef6594b73c56a63e6"}, @@ -3000,7 +2895,6 @@ description = "LangChain text splitting utilities" optional = false python-versions = "<4.0.0,>=3.10.0" groups = ["metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "langchain_text_splitters-1.1.2-py3-none-any.whl", hash = "sha256:a2de0d799ff31886429fd6e2e0032df275b60ec817c19059a7b46181cc1c2f10"}, {file = "langchain_text_splitters-1.1.2.tar.gz", hash = "sha256:782a723db0a4746ac91e251c7c1d57fd23636e4f38ed733074e28d7a86f41627"}, @@ -3016,7 +2910,6 @@ description = "Language detection library ported from Google's language-detectio optional = false python-versions = "*" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "langdetect-1.0.9-py2-none-any.whl", hash = "sha256:7cbc0746252f19e76f77c0b1690aadf01963be835ef0cd4b56dddf2a8f1dfc2a"}, {file = "langdetect-1.0.9.tar.gz", hash = "sha256:cbc1fef89f8d062739774bd51eda3da3274006b3661d199c2655f6b3f6d605a0"}, @@ -3032,7 +2925,6 @@ description = "Building stateful, multi-actor applications with LLMs" optional = false python-versions = ">=3.10" groups = ["main", "metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "langgraph-1.1.10-py3-none-any.whl", hash = "sha256:8a4f163f72f4401648d0c11b48ee906947d938ba8cf1f474540fe591534f0d17"}, {file = "langgraph-1.1.10.tar.gz", hash = "sha256:3115beb58203283c98d8752a90c034f3432177d2979a1fe205f76e5f1b744500"}, @@ -3053,7 +2945,6 @@ description = "Library with base interfaces for LangGraph checkpoint savers." optional = false python-versions = ">=3.9" groups = ["main", "metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "langgraph_checkpoint-2.1.2-py3-none-any.whl", hash = "sha256:911ebffb069fd01775d4b5184c04aaafc2962fcdf50cf49d524cd4367c4d0c60"}, {file = "langgraph_checkpoint-2.1.2.tar.gz", hash = "sha256:112e9d067a6eff8937caf198421b1ffba8d9207193f14ac6f89930c1260c06f9"}, @@ -3070,7 +2961,6 @@ description = "Library with a Postgres implementation of LangGraph checkpoint sa optional = false python-versions = ">=3.9" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "langgraph_checkpoint_postgres-2.0.25-py3-none-any.whl", hash = "sha256:cf1248a58fe828c9cfc36ee57ff118d7799ce214d4b35718e57ec98407130fb5"}, {file = "langgraph_checkpoint_postgres-2.0.25.tar.gz", hash = "sha256:916b80f73a641a589301f6c54414974768b6d646d82db7b301ff8d47105c3613"}, @@ -3089,7 +2979,6 @@ description = "Library with high-level APIs for creating and executing LangGraph optional = false python-versions = ">=3.10" groups = ["main", "metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "langgraph_prebuilt-1.0.13-py3-none-any.whl", hash = "sha256:7055e9fad41fbd3593800aed0aea0a6e974b17f33ed51b80d3d3a031212dd7c0"}, {file = "langgraph_prebuilt-1.0.13.tar.gz", hash = "sha256:ad219782a80e1718e7e7794de49e0ae307111d45cbcffab9a52725a66a609456"}, @@ -3106,7 +2995,6 @@ description = "SDK for interacting with LangGraph API" optional = false python-versions = ">=3.10" groups = ["main", "metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "langgraph_sdk-0.3.15-py3-none-any.whl", hash = "sha256:3838773acf7456d158165385d49f48f1e856f28b56ccd99ea139a8f27004815d"}, {file = "langgraph_sdk-0.3.15.tar.gz", hash = "sha256:29e805003d2c6e296823dd71992610976fd0428cefaa8b3304fd91f2247037de"}, @@ -3123,7 +3011,6 @@ description = "Client library to connect to the LangSmith Observability and Eval optional = false python-versions = ">=3.10" groups = ["main", "metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "langsmith-0.10.11-py3-none-any.whl", hash = "sha256:37ac446cd5655393738689e6205f9b8a54e6665405faf007f2eb85fe9ce51180"}, {file = "langsmith-0.10.11.tar.gz", hash = "sha256:47f8c4278933a0db2c1a22db06ff57b2e0c67c00f9eb71f8495eed484d5d7383"}, @@ -3166,7 +3053,6 @@ description = "A fast and thorough lazy object proxy." optional = false python-versions = ">=3.9" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "lazy_object_proxy-1.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:61d5e3310a4aa5792c2b599a7a78ccf8687292c8eb09cf187cca8f09cf6a7519"}, {file = "lazy_object_proxy-1.12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1ca33565f698ac1aece152a10f432415d1a2aa9a42dfe23e5ba2bc255ab91f6"}, @@ -3221,7 +3107,6 @@ description = "Library to easily interface with LLM API providers" optional = false python-versions = "<3.15,>=3.10" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "litellm-1.94.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:e00514eb1ca8cd7b4352875ea144b733835b712e764e2b8d8212bda754babf4e"}, {file = "litellm-1.94.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:5b905506b5cdedf1bcd74651582b79ba4dd6bf020f3eba2edf9e17695ce5277f"}, @@ -3275,7 +3160,6 @@ description = "Developer-friendly load testing framework" optional = false python-versions = ">=3.10" groups = ["metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "locust-2.39.1-py3-none-any.whl", hash = "sha256:fd5148f2f1a4ed34aee968abc4393674e69d1b5e1b54db50a397f6eb09ce0b04"}, {file = "locust-2.39.1.tar.gz", hash = "sha256:6bdd19e27edf9a1c84391d6cf6e9a737dfb832be7dfbf39053191ae31b9cc498"}, @@ -3309,7 +3193,6 @@ description = "Locust Cloud" optional = false python-versions = ">=3.10" groups = ["metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "locust_cloud-1.30.0-py3-none-any.whl", hash = "sha256:2324b690efa1bfc8d1871340276953cf265328bd6333e07a5ba8ff7dc5e99e6c"}, {file = "locust_cloud-1.30.0.tar.gz", hash = "sha256:324ae23754d49816df96d3f7472357a61cd10e56cebcb26e2def836675cb3c68"}, @@ -3329,7 +3212,6 @@ description = "Powerful and Pythonic XML processing library combining libxml2/li optional = false python-versions = ">=3.8" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "lxml-6.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:09dd5b7075dc2f7709654a46543ba1ea3c2e217b2ed8fbd413a8a945a0f40f60"}, {file = "lxml-6.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f6ac4ef4d82dff54670227a69c67782ae0b811b5cf6b17954f1e8f7502fc0d1d"}, @@ -3480,7 +3362,6 @@ description = "A super-fast templating language that borrows the best ideas from optional = false python-versions = ">=3.8" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "mako-1.3.12-py3-none-any.whl", hash = "sha256:8f61569480282dbf557145ce441e4ba888be453c30989f879f0d652e39f53ea9"}, {file = "mako-1.3.12.tar.gz", hash = "sha256:9f778e93289bd410bb35daadeb4fc66d95a746f0b75777b942088b7fd7af550a"}, @@ -3501,7 +3382,6 @@ description = "Python port of markdown-it. Markdown parsing, done right!" optional = false python-versions = ">=3.10" groups = ["metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "markdown_it_py-4.2.0-py3-none-any.whl", hash = "sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a"}, {file = "markdown_it_py-4.2.0.tar.gz", hash = "sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49"}, @@ -3526,7 +3406,6 @@ description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.9" groups = ["main", "metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559"}, {file = "markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419"}, @@ -3626,7 +3505,6 @@ description = "McCabe checker, plugin for flake8" optional = false python-versions = ">=3.6" groups = ["main", "dev"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, @@ -3639,7 +3517,6 @@ description = "Markdown URL utilities" optional = false python-versions = ">=3.7" groups = ["metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, @@ -3652,7 +3529,6 @@ description = "Python Client SDK for the Mistral AI API." optional = false python-versions = ">=3.10" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "mistralai-2.8.0-py3-none-any.whl", hash = "sha256:1514a776946f2a7fa04281a11bcccb218397a2bf0fdc892979c8900b422e13df"}, {file = "mistralai-2.8.0.tar.gz", hash = "sha256:fea2d131850bbe010133494e9eb065624afe752dd032a7260ba507ab5a36953a"}, @@ -3687,7 +3563,6 @@ description = "Python library for arbitrary-precision floating-point arithmetic" optional = false python-versions = "*" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c"}, {file = "mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f"}, @@ -3706,7 +3581,6 @@ description = "The Microsoft Authentication Library (MSAL) for Python library en optional = false python-versions = ">=3.9" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "msal-1.37.0-py3-none-any.whl", hash = "sha256:dd17e95a7c71bce75e8108113438ba7c4a086b3bcad4f57a8c09b7af3d753c2d"}, {file = "msal-1.37.0.tar.gz", hash = "sha256:1b1672a33ee467c1d70b341bb16cafd51bb3c817147a95b93263794b03971bec"}, @@ -3727,7 +3601,6 @@ description = "Microsoft Authentication Library extensions (MSAL EX) provides a optional = false python-versions = ">=3.9" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "msal_extensions-1.3.1-py3-none-any.whl", hash = "sha256:96d3de4d034504e969ac5e85bae8106c8373b5c6568e4c8fa7af2eca9dbe6bca"}, {file = "msal_extensions-1.3.1.tar.gz", hash = "sha256:c5b0fd10f65ef62b5f1d62f4251d51cbcaf003fcedae8c91b040a488614be1a4"}, @@ -3746,7 +3619,6 @@ description = "MessagePack serializer" optional = false python-versions = ">=3.10" groups = ["metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "msgpack-1.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8c7b398c56ff125feae96c2737abfec5595f1fa0aa186df60c56040b8accb95c"}, {file = "msgpack-1.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1548006a91aa93c5da81f3bdcebc1a0d10cea2d25969754fbe848da622b2b895"}, @@ -3823,7 +3695,6 @@ description = "multidict implementation" optional = false python-versions = ">=3.9" groups = ["main", "metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "multidict-6.7.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c93c3db7ea657dd4637d57e74ab73de31bccefe144d3d4ce370052035bc85fb5"}, {file = "multidict-6.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:974e72a2474600827abaeda71af0c53d9ebbc3c2eb7da37b37d7829ae31232d8"}, @@ -3980,7 +3851,6 @@ description = "better multiprocessing and multithreading in Python" optional = false python-versions = ">=3.9" groups = ["metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "multiprocess-0.70.19-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:02e5c35d7d6cd2bdc89c1858867f7bde4012837411023a4696c148c1bdd7c80e"}, {file = "multiprocess-0.70.19-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:79576c02d1207ec405b00cabf2c643c36070800cca433860e14539df7818b2aa"}, @@ -4010,7 +3880,6 @@ description = "Type system extensions for programs checked with the mypy type ch optional = false python-versions = ">=3.8" groups = ["dev"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, @@ -4023,7 +3892,6 @@ description = "Extremely lightweight compatibility layer between dataframe libra optional = false python-versions = ">=3.10" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "narwhals-2.24.0-py3-none-any.whl", hash = "sha256:42fdedf44e5b2ca7505630d45b4ac3058f38d8485cba9fe1652ca23152df7489"}, {file = "narwhals-2.24.0.tar.gz", hash = "sha256:b5c0f684ccd9d7475b564111e319a4964abcf2baf79d3cf6b1003d06ac9b828d"}, @@ -4050,7 +3918,6 @@ description = "Patch asyncio to allow nested event loops" optional = false python-versions = ">=3.5" groups = ["metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c"}, {file = "nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe"}, @@ -4063,7 +3930,7 @@ description = "Python package for creating and manipulating graphs and networks" optional = false python-versions = ">=3.11" groups = ["main", "metrics"] -markers = "(sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\") and python_version >= \"3.14\"" +markers = "python_version == \"3.14\"" files = [ {file = "networkx-3.6-py3-none-any.whl", hash = "sha256:cdb395b105806062473d3be36458d8f1459a4e4b98e236a66c3a48996e07684f"}, {file = "networkx-3.6.tar.gz", hash = "sha256:285276002ad1f7f7da0f7b42f004bcba70d381e936559166363707fdad3d72ad"}, @@ -4087,7 +3954,7 @@ description = "Python package for creating and manipulating graphs and networks" optional = false python-versions = "!=3.14.1,>=3.11" groups = ["main", "metrics"] -markers = "(sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\") and python_version <= \"3.13\"" +markers = "python_version < \"3.14\"" files = [ {file = "networkx-3.6.1-py3-none-any.whl", hash = "sha256:d47fbf302e7d9cbbb9e2555a0d267983d2aa476bac30e90dfbe5669bd57f3762"}, {file = "networkx-3.6.1.tar.gz", hash = "sha256:26b7c357accc0c8cde558ad486283728b65b6a95d85ee1cd66bafab4c8168509"}, @@ -4111,7 +3978,6 @@ description = "Fundamental package for array computing in Python" optional = false python-versions = ">=3.12" groups = ["main", "metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "numpy-2.5.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2c889b56fe48b1018f764b0eec8df59ab654e9148aa91faa12596043500de277"}, {file = "numpy-2.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ab451b59c5643c570974c43aef780703ef1d3b4965d2be07afd530615a9358d1"}, @@ -4166,7 +4032,6 @@ description = "The official Python library for the openai API" optional = false python-versions = ">=3.10" groups = ["main", "metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "openai-2.50.0-py3-none-any.whl", hash = "sha256:90bdddcc5a2fa529b350fac9c5780d87e5c361dcc6090ab57b0d470b0d7af7fa"}, {file = "openai-2.50.0.tar.gz", hash = "sha256:5128f7caf4a6b01aefd6e7e93efe170a2c3427b8de286b9af5cdff3aa47e02c8"}, @@ -4197,7 +4062,6 @@ description = "OpenTelemetry Python API" optional = false python-versions = ">=3.9" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "opentelemetry_api-1.39.1-py3-none-any.whl", hash = "sha256:2edd8463432a7f8443edce90972169b195e7d6a05500cd29e6d13898187c9950"}, {file = "opentelemetry_api-1.39.1.tar.gz", hash = "sha256:fbde8c80e1b937a2c61f20347e91c0c18a1940cecf012d62e65a7caf08967c9c"}, @@ -4214,7 +4078,6 @@ description = "OpenTelemetry Semantic Conventions" optional = false python-versions = ">=3.9" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "opentelemetry_semantic_conventions-0.60b1-py3-none-any.whl", hash = "sha256:9fa8c8b0c110da289809292b0591220d3a7b53c1526a23021e977d68597893fb"}, {file = "opentelemetry_semantic_conventions-0.60b1.tar.gz", hash = "sha256:87c228b5a0669b748c76d76df6c364c369c28f1c465e50f661e39737e84bc953"}, @@ -4231,7 +4094,6 @@ description = "Fast, correct Python JSON library supporting dataclasses, datetim optional = false python-versions = ">=3.10" groups = ["main", "metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "orjson-3.11.9-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:135869ef917b8704ea0a94e01620e0c05021c15c52036e4663baffe75e72f8ce"}, {file = "orjson-3.11.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:115ab5f5f4a0f203cc2a5f0fb09aee503a3f771aa08392949ab5ca230c4fbdbd"}, @@ -4316,7 +4178,6 @@ description = "Fast, correct Python msgpack library supporting dataclasses, date optional = false python-versions = ">=3.10" groups = ["main", "metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "ormsgpack-1.12.2-cp310-cp310-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:c1429217f8f4d7fcb053523bbbac6bed5e981af0b85ba616e6df7cce53c19657"}, {file = "ormsgpack-1.12.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f13034dc6c84a6280c6c33db7ac420253852ea233fc3ee27c8875f8dd651163"}, @@ -4376,7 +4237,6 @@ description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" groups = ["main", "dev", "metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, @@ -4389,7 +4249,6 @@ description = "Powerful data structures for data analysis, time series, and stat optional = false python-versions = ">=3.11" groups = ["main", "metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "pandas-3.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2946e77e4a53cd248cbde631a12f0e51c8324ce354c3eba4d20147c1ad6f4282"}, {file = "pandas-3.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:71ecc8fb7ed1a7aa4392316b5309a6347e8e7f832f38fd897846b3a1457a9298"}, @@ -4476,7 +4335,6 @@ description = "Utility library for gitignore style pattern matching of file path optional = false python-versions = ">=3.9" groups = ["dev"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "pathspec-1.1.1-py3-none-any.whl", hash = "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189"}, {file = "pathspec-1.1.1.tar.gz", hash = "sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a"}, @@ -4494,7 +4352,6 @@ description = "Python Imaging Library (fork)" optional = false python-versions = ">=3.10" groups = ["main", "metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "pillow-12.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:6c0016e7b354317c4e9e525b937ac8596c38d2d232b419529b9cd7a1cd46e39a"}, {file = "pillow-12.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bcc33feacfaefce60c12fd500a277533bdc02b10a19f7f6d348763d8140bbba7"}, @@ -4600,7 +4457,6 @@ description = "A small Python package for determining appropriate platform-speci optional = false python-versions = ">=3.10" groups = ["main", "dev", "metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "platformdirs-4.11.0-py3-none-any.whl", hash = "sha256:360ccded2b7fce0af0ff80cc8f5942a1c5d99b0e856033acb030bfc634709e74"}, {file = "platformdirs-4.11.0.tar.gz", hash = "sha256:0555d18370482847566ffabcaa53ad7c6c1c29f195989ae1ed634a05f76ea1e0"}, @@ -4613,7 +4469,6 @@ description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.9" groups = ["dev"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, @@ -4630,7 +4485,6 @@ description = "Wraps the portalocker recipe for easy usage" optional = false python-versions = ">=3.9" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "portalocker-3.2.0-py3-none-any.whl", hash = "sha256:3cdc5f565312224bc570c49337bd21428bba0ef363bbcf58b9ef4a9f11779968"}, {file = "portalocker-3.2.0.tar.gz", hash = "sha256:1f3002956a54a8c3730586c5c77bf18fae4149e07eaf1c29fc3faf4d5a3f89ac"}, @@ -4651,7 +4505,6 @@ description = "A simple Python library for easily displaying tabular data in a v optional = false python-versions = ">=3.10" groups = ["metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "prettytable-3.18.0-py3-none-any.whl", hash = "sha256:b3346e0e6f79180833aebaac088ae926340586cf6d7d991b9eb125b65f72313a"}, {file = "prettytable-3.18.0.tar.gz", hash = "sha256:439217116152244369caf3d9f1caf2f9fe29b03bd79e88d2928c8e718c95d680"}, @@ -4670,7 +4523,6 @@ description = "Accelerated property cache" optional = false python-versions = ">=3.10" groups = ["main", "metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "propcache-0.5.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d5a81be28596d6559f6131ef33e10200de6e17643b3c74ce03f9eb103be6ae8b"}, {file = "propcache-0.5.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29cbaac5ea0212663e6845e04b5e188d5a6ae6dd919810ac835bf1d3b42c3f4c"}, @@ -4802,7 +4654,6 @@ description = "" optional = false python-versions = ">=3.8" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "protobuf-5.29.6-cp310-abi3-win32.whl", hash = "sha256:62e8a3114992c7c647bce37dcc93647575fc52d50e48de30c6fcb28a6a291eb1"}, {file = "protobuf-5.29.6-cp310-abi3-win_amd64.whl", hash = "sha256:7e6ad413275be172f67fdee0f43484b6de5a904cc1c3ea9804cb6fe2ff366eda"}, @@ -4824,7 +4675,6 @@ description = "Cross-platform lib for process and system monitoring." optional = false python-versions = ">=3.6" groups = ["metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "psutil-7.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b"}, {file = "psutil-7.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea"}, @@ -4860,7 +4710,6 @@ description = "PostgreSQL database adapter for Python" optional = false python-versions = ">=3.10" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "psycopg-3.3.4-py3-none-any.whl", hash = "sha256:b6bbc25ccf05c8fad3b061d9db2ef0909a555171b84b07f29458a447253d679a"}, {file = "psycopg-3.3.4.tar.gz", hash = "sha256:e21207764952cff81b6b8bdacad9a3939f2793367fdac2987b3aac36a651b5bc"}, @@ -4886,7 +4735,7 @@ description = "PostgreSQL database adapter for Python -- C optimisation distribu optional = false python-versions = ">=3.10" groups = ["main"] -markers = "(sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\") and implementation_name != \"pypy\"" +markers = "implementation_name != \"pypy\"" files = [ {file = "psycopg_binary-3.3.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b7bfff1ca23732b488cbca3076fc11bc98d520ee122514fdb17a8e20d3338f5a"}, {file = "psycopg_binary-3.3.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:32a6fbf8481e3a370d0d72b860d35948a693cb01281da217f7b2f307636e591a"}, @@ -4952,7 +4801,6 @@ description = "Connection Pool for Psycopg" optional = false python-versions = ">=3.10" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "psycopg_pool-3.3.1-py3-none-any.whl", hash = "sha256:2af5b432941c4c9ad5c87b3fa410aec910ec8f7c122855897983a06c45f2e4b5"}, {file = "psycopg_pool-3.3.1.tar.gz", hash = "sha256:b10b10b7a175d5cc1592147dc5b7eec8a9e0834eb3ed2c4a92c858e2f51eb63c"}, @@ -4971,7 +4819,6 @@ description = "psycopg2 - Python-PostgreSQL Database Adapter" optional = false python-versions = ">=3.9" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "psycopg2_binary-2.9.12-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9b818ceff717f98851a64bffd4c5eb5b3059ae280276dcecc52ac658dcf006a4"}, {file = "psycopg2_binary-2.9.12-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d2fa0d7caca8635c56e373055094eeda3208d901d55dd0ff5abc1d4e47f82b56"}, @@ -5049,7 +4896,6 @@ description = "Python library for Apache Arrow" optional = false python-versions = ">=3.10" groups = ["metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "pyarrow-25.0.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:ce0ca222802087b9a8cb031a6468442cb6b67c290a45a601cac64753d34954d3"}, {file = "pyarrow-25.0.0-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:7d6da02ffc7a3a9bda3b7ded4cc2a27ff73969ab37153f3afd46bbbc1ba4f0f7"}, @@ -5103,7 +4949,6 @@ description = "Python style guide checker" optional = false python-versions = ">=3.8" groups = ["dev"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "pycodestyle-2.11.1-py2.py3-none-any.whl", hash = "sha256:44fe31000b2d866f2e41841b18528a505fbd7fef9017b04eff4e2648a0fadc67"}, {file = "pycodestyle-2.11.1.tar.gz", hash = "sha256:41ba0e7afc9752dfb53ced5489e89f8186be00e599e712660695b7a75ff2663f"}, @@ -5120,7 +4965,7 @@ files = [ {file = "pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992"}, {file = "pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29"}, ] -markers = {main = "(sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\") and platform_python_implementation != \"PyPy\" and implementation_name != \"PyPy\"", metrics = "(sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\") and (platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and implementation_name != \"PyPy\" or implementation_name == \"pypy\")"} +markers = {main = "platform_python_implementation != \"PyPy\" and implementation_name != \"PyPy\"", metrics = "(platform_python_implementation == \"CPython\" or implementation_name == \"pypy\") and (sys_platform == \"win32\" or implementation_name == \"pypy\") and implementation_name != \"PyPy\""} [[package]] name = "pydantic" @@ -5129,7 +4974,6 @@ description = "Data validation using Python type hints" optional = false python-versions = ">=3.9" groups = ["main", "metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba"}, {file = "pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6"}, @@ -5152,7 +4996,6 @@ description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.9" groups = ["main", "metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "pydantic_core-2.46.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:a396dcc17e5a0b164dbe026896245a4fa9ff402edca1dff0be3d53a517f74de4"}, {file = "pydantic_core-2.46.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:da4b951fe36dc7c3a1ccb4e3cd1747c3542b8c9ceede8fc86cae054e764485f5"}, @@ -5286,7 +5129,6 @@ description = "Settings management using Pydantic" optional = false python-versions = ">=3.10" groups = ["main", "metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "pydantic_settings-2.14.2-py3-none-any.whl", hash = "sha256:a20c97b37910b6550d5ea50fbcc2d4187defe58cd57070b73863d069419c9440"}, {file = "pydantic_settings-2.14.2.tar.gz", hash = "sha256:c19dd64b19097f1de80184f0cc7b0272a13ae6e170cbf240a3e27e381ed14a5f"}, @@ -5311,7 +5153,6 @@ description = "passive checker of Python programs" optional = false python-versions = ">=3.8" groups = ["dev"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "pyflakes-3.1.0-py2.py3-none-any.whl", hash = "sha256:4132f6d49cb4dae6819e5379898f2b8cce3c5f23994194c24b77d5da2e36f774"}, {file = "pyflakes-3.1.0.tar.gz", hash = "sha256:a0aae034c444db0071aa077972ba4768d40c830d9539fd45bf4cd3f8f6992efc"}, @@ -5324,7 +5165,6 @@ description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.9" groups = ["metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176"}, {file = "pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f"}, @@ -5340,7 +5180,6 @@ description = "JSON Web Token implementation in Python" optional = false python-versions = ">=3.9" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "pyjwt-2.13.0-py3-none-any.whl", hash = "sha256:66adcc2aff09b3f1bbd95fc1e1577df8ac8723c978552fd43304c8a290ac5728"}, {file = "pyjwt-2.13.0.tar.gz", hash = "sha256:41571c89ca91598c79e8ef18a2d07367d4810fbbd6f637794879baf1b7703423"}, @@ -5359,7 +5198,6 @@ description = "python code static checker" optional = false python-versions = ">=3.7.2" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "pylint-2.17.7-py3-none-any.whl", hash = "sha256:27a8d4c7ddc8c2f8c18aa0050148f89ffc09838142193fdbe98f172781a3ff87"}, {file = "pylint-2.17.7.tar.gz", hash = "sha256:f4fcac7ae74cfe36bc8451e931d8438e4a476c20314b1101c458ad0f05191fad"}, @@ -5385,7 +5223,6 @@ description = "Utilities and helpers for writing Pylint plugins" optional = false python-versions = "<4.0,>=3.9" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "pylint_plugin_utils-0.9.0-py3-none-any.whl", hash = "sha256:16e9b84e5326ba893a319a0323fcc8b4bcc9c71fc654fcabba0605596c673818"}, {file = "pylint_plugin_utils-0.9.0.tar.gz", hash = "sha256:5468d763878a18d5cc4db46eaffdda14313b043c962a263a7d78151b90132055"}, @@ -5401,7 +5238,6 @@ description = "A Pylint plugin to help Pylint understand the Pydantic" optional = false python-versions = ">=3.7" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "pylint_pydantic-0.2.4-py3-none-any.whl", hash = "sha256:452b32992f47e303b432f8940d4077f1d60e8d3325351b8998d846431699111a"}, ] @@ -5418,7 +5254,6 @@ description = "A pure-python PDF library capable of splitting, merging, cropping optional = false python-versions = ">=3.9" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "pypdf-6.14.2-py3-none-any.whl", hash = "sha256:3f07891af76dc002657e04993ab9b4de81de29f9013b9761d0b7968bff12e946"}, {file = "pypdf-6.14.2.tar.gz", hash = "sha256:7873f502fe4385e79539b21d872392dc0c4e3714327c15881cbc7fbfd1f95b25"}, @@ -5440,7 +5275,6 @@ description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.7" groups = ["dev"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, @@ -5462,7 +5296,6 @@ description = "Pytest plugin for measuring coverage." optional = false python-versions = ">=3.7" groups = ["dev"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"}, {file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"}, @@ -5482,7 +5315,6 @@ description = "pytest plugin that allows you to add environment variables." optional = false python-versions = ">=3.8" groups = ["dev"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "pytest_env-1.1.3-py3-none-any.whl", hash = "sha256:aada77e6d09fcfb04540a6e462c58533c37df35fa853da78707b17ec04d17dfc"}, {file = "pytest_env-1.1.3.tar.gz", hash = "sha256:fcd7dc23bb71efd3d35632bde1bbe5ee8c8dc4489d6617fb010674880d96216b"}, @@ -5501,7 +5333,6 @@ description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" groups = ["main", "metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, @@ -5517,7 +5348,6 @@ description = "Create, read, and update Microsoft Word .docx files." optional = false python-versions = ">=3.9" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "python_docx-1.2.0-py3-none-any.whl", hash = "sha256:3fd478f3250fbbbfd3b94fe1e985955737c145627498896a8a6bf81f4baf66c7"}, {file = "python_docx-1.2.0.tar.gz", hash = "sha256:7bc9d7b7d8a69c9c02ca09216118c86552704edc23bac179283f2e38f86220ce"}, @@ -5534,7 +5364,6 @@ description = "Read key-value pairs from a .env file and set them as environment optional = false python-versions = ">=3.10" groups = ["main", "metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a"}, {file = "python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3"}, @@ -5550,7 +5379,6 @@ description = "Engine.IO server and client for Python" optional = false python-versions = ">=3.8" groups = ["metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "python_engineio-4.13.3-py3-none-any.whl", hash = "sha256:1f60ecaf1358190f0e26c48c578a60428dc02a8f1295bc3dbf53d1b31116821f"}, {file = "python_engineio-4.13.3.tar.gz", hash = "sha256:572b7783e341fed21edbc7cea297ccd378dad79265fdde96aa4664420a7c06c9"}, @@ -5572,7 +5400,6 @@ description = "A streaming multipart parser for Python" optional = false python-versions = ">=3.10" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "python_multipart-0.0.22-py3-none-any.whl", hash = "sha256:2b2cd894c83d21bf49d702499531c7bafd057d730c201782048f7945d82de155"}, {file = "python_multipart-0.0.22.tar.gz", hash = "sha256:7340bef99a7e0032613f56dc36027b959fd3b30a787ed62d310e951f7c3a3a58"}, @@ -5585,7 +5412,6 @@ description = "Socket.IO server and client for Python" optional = false python-versions = ">=3.8" groups = ["metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "python_socketio-5.16.3-py3-none-any.whl", hash = "sha256:e7ad14202a5e6448824c7c2f86161d04e13dec05992257df5c709e6a2798c041"}, {file = "python_socketio-5.16.3.tar.gz", hash = "sha256:89b136f677ae65607a84cecda9b4d6c5377b40a97582c504c25df89af16d520e"}, @@ -5633,7 +5459,7 @@ files = [ {file = "pywin32-312-cp39-cp39-win_amd64.whl", hash = "sha256:dc90147579a905b8635e1b0ec6514967dcb07e6e0d9c42f1477feef14cac23bb"}, {file = "pywin32-312-cp39-cp39-win_arm64.whl", hash = "sha256:02ebca0f0242b75292e218065004310d6a477407c09fa449bfe4f6022bc0c0fc"}, ] -markers = {main = "(sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\") and platform_system == \"Windows\"", metrics = "sys_platform == \"win32\""} +markers = {main = "platform_system == \"Windows\"", metrics = "sys_platform == \"win32\""} [[package]] name = "pyyaml" @@ -5642,7 +5468,6 @@ description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" groups = ["main", "metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "PyYAML-6.0.3-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:c2514fceb77bc5e7a2f7adfaa1feb2fb311607c9cb518dbc378688ec73d8292f"}, {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c57bb8c96f6d1808c030b1687b9b5fb476abaa47f0db9c0101f5e9f394e97f4"}, @@ -5726,7 +5551,6 @@ description = "Python bindings for 0MQ" optional = false python-versions = ">=3.8" groups = ["metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "pyzmq-27.1.0-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:508e23ec9bc44c0005c4946ea013d9317ae00ac67778bd47519fdf5a0e930ff4"}, {file = "pyzmq-27.1.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:507b6f430bdcf0ee48c0d30e734ea89ce5567fd7b8a0f0044a369c176aa44556"}, @@ -5832,7 +5656,6 @@ description = "Client library for the Qdrant vector search engine" optional = false python-versions = ">=3.10" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "qdrant_client-1.18.0-py3-none-any.whl", hash = "sha256:093aa8cf8a420ee3ad2a68b007e1378d7992b2600e0b53c193fc172674f659cd"}, {file = "qdrant_client-1.18.0.tar.gz", hash = "sha256:52e8ece1a7d40519801bf0b70713bfa0f6b7ae28c7275bbe0b0286fbed7f6db4"}, @@ -5862,7 +5685,6 @@ description = "Evaluation framework for RAG and LLM applications" optional = false python-versions = ">=3.9" groups = ["metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "ragas-0.4.3-py3-none-any.whl", hash = "sha256:ef1d75f674c294e9a6e7d8e9ad261b6bf4697dad1c9cbd1a756ba7a6b4849a38"}, {file = "ragas-0.4.3.tar.gz", hash = "sha256:1eb1f61dbc8613ad014fdb8d630cbe9a1caec1ea01664a106993cb756128c001"}, @@ -5908,7 +5730,6 @@ description = "JSON Referencing + Python" optional = false python-versions = ">=3.10" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231"}, {file = "referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8"}, @@ -5926,7 +5747,6 @@ description = "Python library for post-extraction refinement of text that may be optional = false python-versions = ">=3.9" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "refinedoc-1.0.1-py3-none-any.whl", hash = "sha256:a2840fefb110b33d84474ef3b5e3b3813a55fb7c8e42a70615f51acb1b3885c7"}, {file = "refinedoc-1.0.1.tar.gz", hash = "sha256:afad515bcbd13e9e7ba40d149d6b2a8a04c88323bc31eef2eddf3e3bbd6e921b"}, @@ -5939,7 +5759,6 @@ description = "Alternative regular expression module, to replace re." optional = false python-versions = ">=3.10" groups = ["main", "metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "regex-2026.7.19-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:555497390743af1a65045fa4527782d10ff5b88970359412baa4a1e628fe393b"}, {file = "regex-2026.7.19-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:343a4504e3fb688c47cad451221ca5d4814f42b1e16c0065bde9cbf7f473bd52"}, @@ -6064,7 +5883,6 @@ description = "Python HTTP for Humans." optional = false python-versions = ">=3.10" groups = ["main", "metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0"}, {file = "requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed"}, @@ -6087,7 +5905,6 @@ description = "A utility belt for advanced users of python-requests" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" groups = ["main", "metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"}, {file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"}, @@ -6103,7 +5920,6 @@ description = "Render rich text, tables, progress bars, syntax highlighting, mar optional = false python-versions = ">=3.8.0" groups = ["metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "rich-14.3.4-py3-none-any.whl", hash = "sha256:07e7adb4690f68864777b1450859253bed81a99a31ac321ac1817b2313558952"}, {file = "rich-14.3.4.tar.gz", hash = "sha256:817e02727f2b25b40ef56f5aa2217f400c8489f79ca8f46ea2b70dd5e14558a9"}, @@ -6123,7 +5939,6 @@ description = "Python bindings to Rust's persistent data structures (rpds)" optional = false python-versions = ">=3.11" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "rpds_py-2026.6.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:7b689145a1485c335569bd056464f3243a29af7ed3871c7be31ad624ba239bc7"}, {file = "rpds_py-2026.6.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:db08f45aecde626498fb3df07bcf6d2ec040af42e859a4f5040d79c200342911"}, @@ -6250,7 +6065,6 @@ description = "" optional = false python-versions = ">=3.10" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "safetensors-0.8.0-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:c554f85858e05226d3c2828e32395e677434685d6d94594a41643361c5e837f0"}, {file = "safetensors-0.8.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:c80201d22cbf405b80647a60ada77bba06c8fba2da2743ba1e89cdcc39a81f25"}, @@ -6293,7 +6107,6 @@ description = "A set of python modules for machine learning and data mining" optional = false python-versions = ">=3.11" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "scikit_learn-1.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9db6f4d34e68c8899e4cab27fdf8eafe6ed21f2ba52ceb25ea250cd237f8e47b"}, {file = "scikit_learn-1.9.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:f401448645a3e7bc115aa3c094097865155b34bff1cba8101857d9104e99074c"}, @@ -6351,7 +6164,6 @@ description = "Graph algorithms" optional = false python-versions = ">=3.10" groups = ["metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "scikit_network-0.33.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bfcb4b945286154800eb61998fae85675d9a05ee78e776a82e7d222bf978e171"}, {file = "scikit_network-0.33.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:854d33b2056dff4fca1b956b46955f3d668148ba63c5cc51a5c497e7ee765def"}, @@ -6390,7 +6202,6 @@ description = "Fundamental algorithms for scientific computing in Python" optional = false python-versions = ">=3.12" groups = ["main", "metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "scipy-1.18.0-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:7bd21faaf5a1a3b2eff922d02db5f191b99a6518db9078a8fb23169f6d22259a"}, {file = "scipy-1.18.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:265915e79107de9f946b855e50d7470d5893ec3f54b342e1aa6201cbdcd8bb6b"}, @@ -6450,7 +6261,6 @@ description = "Most extensible Python build backend with support for C/C++ exten optional = false python-versions = ">=3.10" groups = ["main", "metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "setuptools-83.0.0-py3-none-any.whl", hash = "sha256:29b23c360f22f414dc7336bb39178cc7bcbf6021ed2733cde173f09dba19abb3"}, {file = "setuptools-83.0.0.tar.gz", hash = "sha256:025bccbbf0fa05b6192bc64ae1e7b16e001fd6d6d4d5de03c97b1c1ade523bef"}, @@ -6472,7 +6282,6 @@ description = "Tool to Detect Surrounding Shell" optional = false python-versions = ">=3.7" groups = ["metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"}, {file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"}, @@ -6485,7 +6294,6 @@ description = "Simple WebSocket server and client for Python" optional = false python-versions = ">=3.6" groups = ["metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "simple_websocket-1.1.0-py3-none-any.whl", hash = "sha256:4af6069630a38ed6c561010f0e11a5bc0d4ca569b36306eb257cd9a192497c8c"}, {file = "simple_websocket-1.1.0.tar.gz", hash = "sha256:7939234e7aa067c534abdab3a9ed933ec9ce4691b0713c78acb195560aa52ae4"}, @@ -6505,7 +6313,6 @@ description = "Python 2 and 3 compatibility utilities" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" groups = ["main", "metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, @@ -6518,7 +6325,6 @@ description = "Sniff out which async library your code is running under" optional = false python-versions = ">=3.7" groups = ["main", "metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, @@ -6531,7 +6337,6 @@ description = "A modern CSS selector implementation for Beautiful Soup." optional = false python-versions = ">=3.10" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "soupsieve-2.9.1-py3-none-any.whl", hash = "sha256:4f4477399246b7a0c720a88ca2454b11cd6bb9ae4c9d170140786e916776c14c"}, {file = "soupsieve-2.9.1.tar.gz", hash = "sha256:c33e6605bbc71dd628b00c632d58ae607c22bade247e52553928f83bbb75b4ba"}, @@ -6544,7 +6349,6 @@ description = "Database Abstraction Library" optional = false python-versions = ">=3.7" groups = ["main", "metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "sqlalchemy-2.0.51-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e8203d2fbd5c6254692ef0a72c740d75b2f3c7ca345404f4c1a4604813c77c0"}, {file = "sqlalchemy-2.0.51-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1af05726b3d0cdba1c55284bf408fd3b792e690fe2399bfb8304565551cda652"}, @@ -6642,7 +6446,6 @@ description = "The little ASGI library that shines." optional = false python-versions = ">=3.9" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "starlette-0.46.2-py3-none-any.whl", hash = "sha256:595633ce89f8ffa71a015caed34a5b2dc1c0cdb3f0f1fbd1e69339cf2abeec35"}, {file = "starlette-0.46.2.tar.gz", hash = "sha256:7f7361f34eed179294600af672f565727419830b54b7b084efe44bb82d2fccd5"}, @@ -6661,7 +6464,6 @@ description = "Computer algebra system (CAS) in Python" optional = false python-versions = ">=3.9" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5"}, {file = "sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517"}, @@ -6680,7 +6482,6 @@ description = "Retry code until it succeeds" optional = false python-versions = ">=3.10" groups = ["main", "metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "tenacity-9.1.4-py3-none-any.whl", hash = "sha256:6095a360c919085f28c6527de529e76a06ad89b23659fa881ae0649b867a9d55"}, {file = "tenacity-9.1.4.tar.gz", hash = "sha256:adb31d4c263f2bd041081ab33b498309a57c77f9acf2db65aadf0898179cf93a"}, @@ -6697,7 +6498,6 @@ description = "threadpoolctl" optional = false python-versions = ">=3.9" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "threadpoolctl-3.6.0-py3-none-any.whl", hash = "sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb"}, {file = "threadpoolctl-3.6.0.tar.gz", hash = "sha256:8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e"}, @@ -6710,7 +6510,6 @@ description = "tiktoken is a fast BPE tokeniser for use with OpenAI's models" optional = false python-versions = ">=3.9" groups = ["main", "metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "tiktoken-0.13.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:47b1df8d73390a24f94980c75158cdd5c56d256f16d55f30cb49c230caba9ba4"}, {file = "tiktoken-0.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7d40c6c5aab171dcd6eb8455bc567bde404bb9def60cdb8c1299cc782b242bb9"}, @@ -6785,7 +6584,6 @@ description = "" optional = false python-versions = ">=3.9" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "tokenizers-0.22.2-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:544dd704ae7238755d790de45ba8da072e9af3eea688f698b137915ae959281c"}, {file = "tokenizers-0.22.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:1e418a55456beedca4621dbab65a318981467a2b188e982a23e117f115ce5001"}, @@ -6828,7 +6626,6 @@ description = "Style preserving TOML library" optional = false python-versions = ">=3.9" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "tomlkit-0.15.1-py3-none-any.whl", hash = "sha256:177a05aece5a8ca5266fd3c448abb47b8d352f09d477d3ca8332db4d89b24304"}, {file = "tomlkit-0.15.1.tar.gz", hash = "sha256:e25bbf38843005246210a12982776f27f99cb9be67160e14434d0c0d21ee1e97"}, @@ -6841,7 +6638,6 @@ description = "Tensors and Dynamic neural networks in Python with strong GPU acc optional = false python-versions = ">=3.10" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "torch-2.9.1+cpu-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:10866c8a48c4aa5ae3f48538dc8a055b99c57d9c6af2bf5dd715374d9d6ddca3"}, {file = "torch-2.9.1+cpu-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:7210713b66943fdbfcc237b2e782871b649123ac5d29f548ce8c85be4223ab38"}, @@ -6895,7 +6691,6 @@ description = "image and video datasets and models for torch deep learning" optional = false python-versions = ">=3.10" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "torchvision-0.24.1+cpu-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:c98e219150beaab521c684cfd5c9ba1b6e80794ca851e60e784bd7f368889961"}, {file = "torchvision-0.24.1+cpu-cp310-cp310-win_amd64.whl", hash = "sha256:f95c3bd0f65b01224f46142536cc02607ce32ef5e905d0c9eb210fa8cc23f522"}, @@ -6934,7 +6729,6 @@ description = "Fast, Extensible Progress Meter" optional = false python-versions = ">=3.8" groups = ["main", "metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "tqdm-4.70.0-py3-none-any.whl", hash = "sha256:7f585706bfddbdebf89daac705b2dfcc16890130727d3197ca62c732b4310953"}, {file = "tqdm-4.70.0.tar.gz", hash = "sha256:55b0b0dbd97462d06ebee91e4dac24ed4d4702be82b24f07e6c1d27e08cea220"}, @@ -6956,7 +6750,6 @@ description = "State-of-the-art Machine Learning for JAX, PyTorch and TensorFlow optional = false python-versions = ">=3.9.0" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "transformers-4.57.6-py3-none-any.whl", hash = "sha256:4c9e9de11333ddfe5114bc872c9f370509198acf0b87a832a0ab9458e2bd0550"}, {file = "transformers-4.57.6.tar.gz", hash = "sha256:55e44126ece9dc0a291521b7e5492b572e6ef2766338a610b9ab5afbb70689d3"}, @@ -7032,7 +6825,6 @@ description = "Typer, build great CLIs. Easy to code. Based on Python type hints optional = false python-versions = ">=3.10" groups = ["metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "typer-0.27.0-py3-none-any.whl", hash = "sha256:6f4b27631e47f077871b7dc30e933ec0131c1390fbe0e387ea5574b5bac9ccf1"}, {file = "typer-0.27.0.tar.gz", hash = "sha256:629bd12ea5d13a17148125d9a264f949eb171fb3f120f9b04d85873cab054fa5"}, @@ -7051,7 +6843,6 @@ description = "Backported and Experimental Type Hints for Python 3.9+" optional = false python-versions = ">=3.9" groups = ["main", "metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8"}, {file = "typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5"}, @@ -7064,7 +6855,6 @@ description = "Runtime typing introspection tools" optional = false python-versions = ">=3.9" groups = ["main", "metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7"}, {file = "typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464"}, @@ -7093,7 +6883,6 @@ description = "HTTP library with thread-safe connection pooling, file post, and optional = false python-versions = ">=3.10" groups = ["main", "metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897"}, {file = "urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c"}, @@ -7112,7 +6901,6 @@ description = "Fast, drop-in replacement for Python's uuid module, powered by Ru optional = false python-versions = ">=3.10" groups = ["main", "metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "uuid_utils-0.17.0-cp310-cp310-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:d2d9a63a9e6f2416ace8c109043a9280d6b34f34bb2e5421903e149403db40a6"}, {file = "uuid_utils-0.17.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:b776c7fc8755c7de06dd5a22b47c40ae84f67d13277ebb233cc84933ba4dcbcd"}, @@ -7217,7 +7005,6 @@ description = "The lightning-fast ASGI server." optional = false python-versions = ">=3.8" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "uvicorn-0.29.0-py3-none-any.whl", hash = "sha256:2c2aac7ff4f4365c206fd773a39bf4ebd1047c238f8b8268ad996829323473de"}, {file = "uvicorn-0.29.0.tar.gz", hash = "sha256:6a69214c0b6a087462412670b3ef21224fa48cae0e452b5883e8e8bdfdd11dd0"}, @@ -7244,7 +7031,7 @@ description = "Fast implementation of asyncio event loop on top of libuv" optional = false python-versions = ">=3.8.1" groups = ["main"] -markers = "platform_python_implementation != \"PyPy\" and sys_platform != \"win32\" and sys_platform != \"cygwin\"" +markers = "sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\"" files = [ {file = "uvloop-0.22.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ef6f0d4cc8a9fa1f6a910230cd53545d9a14479311e87e3cb225495952eb672c"}, {file = "uvloop-0.22.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7cd375a12b71d33d46af85a3343b35d98e8116134ba404bd657b3b1d15988792"}, @@ -7309,7 +7096,6 @@ description = "Simple, modern and high performance file watching and code reload optional = false python-versions = ">=3.10" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "watchfiles-1.2.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:bb68bf4df85abebe5efddc53cf2075520f243a59868d9b3973278b23e76962a9"}, {file = "watchfiles-1.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c16cb06dd17d43b9d185094268459eac92c9538356f050e55b54e82cf700e1d4"}, @@ -7430,7 +7216,6 @@ description = "Measures the displayed width of unicode strings in a terminal" optional = false python-versions = ">=3.8" groups = ["metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "wcwidth-0.8.2-py3-none-any.whl", hash = "sha256:d63947694a0539a1d51e01eda7caf800c291020e6cdd7e28ad7b14dd33ad4f85"}, {file = "wcwidth-0.8.2.tar.gz", hash = "sha256:91fbef97204b96a3d4d421609b80340b760cf33e26da123ff243d76b1fda8dda"}, @@ -7443,7 +7228,6 @@ description = "WebSocket client for Python with low level API options" optional = false python-versions = ">=3.9" groups = ["metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "websocket_client-1.9.0-py3-none-any.whl", hash = "sha256:af248a825037ef591efbf6ed20cc5faa03d3b47b9e5a2230a529eeee1c1fc3ef"}, {file = "websocket_client-1.9.0.tar.gz", hash = "sha256:9e813624b6eb619999a97dc7958469217c3176312b3a16a4bd1bc7e08a46ec98"}, @@ -7461,7 +7245,6 @@ description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" optional = false python-versions = ">=3.10" groups = ["main", "metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "websockets-16.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:49ae99bdfcae803a885c926bf14f886196e84925395bb3f568fef5c0f0979d7d"}, {file = "websockets-16.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5bfd1ac19b1b9986a9c95a82d5e23a391ebb09e12c34d7be6094b86efcc35731"}, @@ -7581,7 +7364,6 @@ description = "All stuff related to relationnal database from the WeLearn projec optional = false python-versions = ">=3.10" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "welearn_database-1.5.1-py3-none-any.whl", hash = "sha256:33952c7af224f7d826b8d9bd747d0936be1692648befccd182e4f382aab3b1d4"}, {file = "welearn_database-1.5.1.tar.gz", hash = "sha256:33e1215ffe6ebe26d43fdda94d2ab108e226e5d772340424987603c14a7aa113"}, @@ -7600,7 +7382,6 @@ description = "The comprehensive WSGI web application library." optional = false python-versions = ">=3.9" groups = ["metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "werkzeug-3.1.8-py3-none-any.whl", hash = "sha256:63a77fb8892bf28ebc3178683445222aa500e48ebad5ec77b0ad80f8726b1f50"}, {file = "werkzeug-3.1.8.tar.gz", hash = "sha256:9bad61a4268dac112f1c5cd4630a56ede601b6ed420300677a869083d70a4c44"}, @@ -7619,7 +7400,6 @@ description = "Module for decorators, wrappers and monkey patching." optional = false python-versions = ">=3.8" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "wrapt-1.17.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88bbae4d40d5a46142e70d58bf664a89b6b4befaea7b2ecc14e03cedb8e06c04"}, {file = "wrapt-1.17.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6b13af258d6a9ad602d57d889f83b9d5543acd471eee12eb51f5b01f8eb1bc2"}, @@ -7711,7 +7491,6 @@ description = "Pure-Python WebSocket protocol implementation" optional = false python-versions = ">=3.10" groups = ["metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "wsproto-1.3.2-py3-none-any.whl", hash = "sha256:61eea322cdf56e8cc904bd3ad7573359a242ba65688716b0710a5eb12beab584"}, {file = "wsproto-1.3.2.tar.gz", hash = "sha256:b86885dcf294e15204919950f666e06ffc6c7c114ca900b060d6e16293528294"}, @@ -7727,7 +7506,6 @@ description = "Python binding for xxHash" optional = false python-versions = ">=3.8" groups = ["main", "metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "xxhash-3.8.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:27a9e475157f7315826118e3f3127909a0fe25f1b43d3d3be9c584f9d265f937"}, {file = "xxhash-3.8.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9b2ce44bf8f4a1d01f418b3110ff8dff32fd3f3e836c0e06333c3725f243fa6c"}, @@ -7925,7 +7703,6 @@ description = "Yet another URL library" optional = false python-versions = ">=3.10" groups = ["main", "metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "yarl-1.24.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88f50c94e21a0a7f14042c015b0eba1881af78562e7bf007e0033e624da59750"}, {file = "yarl-1.24.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6efbccc3d7f75d5b03105172a8dc86d82ba4da86817952529dd93185f4a88be2"}, @@ -8045,7 +7822,6 @@ description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.10" groups = ["main"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "zipp-4.1.0-py3-none-any.whl", hash = "sha256:25ad4e16390cd314347dd8f1de67a2ac538ae658ed4ab9db16029c07c188e97f"}, {file = "zipp-4.1.0.tar.gz", hash = "sha256:4cb57381f544315db7688e976e922a2b18cdb513d21cc194eb42232ba2a3e602"}, @@ -8066,7 +7842,6 @@ description = "Very basic event publishing system" optional = false python-versions = ">=3.10" groups = ["metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "zope_event-6.2-py3-none-any.whl", hash = "sha256:5e755153ac4faf64c10a4b6dd3307680166a3edf65b38df22df592610f8fa874"}, {file = "zope_event-6.2.tar.gz", hash = "sha256:b97d5d6327067ee6b9dfcbdf606ade9ade70991e19c162e808ea39e5fcf0f8d3"}, @@ -8083,7 +7858,6 @@ description = "Interfaces for Python" optional = false python-versions = ">=3.10" groups = ["metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "zope_interface-8.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c0c8aa2bf8f3911ef37b87deb1bbe225a310e6eb6522a16d77f5d8330c4f6fbe"}, {file = "zope_interface-8.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:efe234a0fafb4b6b1602e9be9245b97c2bf06d67c07af5a4bc3c0438978b555c"}, @@ -8140,7 +7914,6 @@ description = "Zstandard bindings for Python" optional = false python-versions = ">=3.9" groups = ["main", "metrics"] -markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "zstandard-0.25.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e59fdc271772f6686e01e1b3b74537259800f57e24280be3f29c8a0deb1904dd"}, {file = "zstandard-0.25.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4d441506e9b372386a5271c64125f72d5df6d2a8e8a2a45a0ae09b03cb781ef7"}, @@ -8249,4 +8022,4 @@ cffi = ["cffi (>=1.17,<2.0) ; platform_python_implementation != \"PyPy\" and pyt [metadata] lock-version = "2.1" python-versions = ">=3.12,<3.15" -content-hash = "d9af52c458903188c1e365bd2b6eebe72ebcca864e45615ed16c0cec94535312" +content-hash = "394d843a7cca544fa7980430d70c065f3322b4ae177ad0eaab6ef8b8e536047e" diff --git a/pyproject.toml b/pyproject.toml index 47f69909..a62d1c14 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,9 +52,8 @@ langchain-azure-ai = "^1.2.3" langgraph = "^1.1.10" mistralai = "^2.4.3" langsmith = "^0.10.11" -baml-py = "^0.220.0" -baml-cli = "^0.1.0" pandas = { version = "^3.0.3", markers = "python_version >= '3.11'" } +baml-py = "^0.226.1" [tool.poetry.group.dev.dependencies] pytest = "^7.4.0" diff --git a/src/app/baml_src/generators.baml b/src/app/baml_src/generators.baml index cd86297c..5a4b9cb1 100644 --- a/src/app/baml_src/generators.baml +++ b/src/app/baml_src/generators.baml @@ -10,7 +10,7 @@ generator target { // The version of the BAML package you have installed (e.g. same version as your baml-py or @boundaryml/baml). // The BAML VSCode extension version should also match this version. - version "0.220.0" + version "0.226.1" // Valid values: "sync", "async" // This controls what `b.FunctionName()` will be (sync or async). From 2bd43d600bc0fcb7c579904c9f7e7b9cdf2bbab4 Mon Sep 17 00:00:00 2001 From: Sandra Guerreiro Date: Mon, 31 Aug 2026 14:58:29 +0200 Subject: [PATCH 5/6] adds tests --- src/app/bibliography/api/bibliography.py | 2 +- src/app/bibliography/helpers/helpers.py | 1 - src/app/tests/services/b_agents/__init__.py | 0 .../b_agents/test_activity_guide_agent.py | 256 +++++++ .../test_activity_recommendation_agent.py | 552 +++++++++++++++ .../b_agents/test_competency_mapping_agent.py | 196 ++++++ .../b_agents/test_course_description_agent.py | 156 +++++ .../test_learning_objectives_agent.py | 239 +++++++ .../b_agents/test_learning_outcomes_agent.py | 241 +++++++ .../test_pedagogical_engineer_agent.py | 270 ++++++++ .../test_sustainability_integration_agent.py | 320 +++++++++ src/app/tests/services/test_helpers.py | 14 +- src/app/tests/services/test_orchestrator.py | 646 ++++++++++++++++++ src/app/tutor/service/orchestrator.py | 2 +- 14 files changed, 2885 insertions(+), 10 deletions(-) create mode 100644 src/app/tests/services/b_agents/__init__.py create mode 100644 src/app/tests/services/b_agents/test_activity_guide_agent.py create mode 100644 src/app/tests/services/b_agents/test_activity_recommendation_agent.py create mode 100644 src/app/tests/services/b_agents/test_competency_mapping_agent.py create mode 100644 src/app/tests/services/b_agents/test_course_description_agent.py create mode 100644 src/app/tests/services/b_agents/test_learning_objectives_agent.py create mode 100644 src/app/tests/services/b_agents/test_learning_outcomes_agent.py create mode 100644 src/app/tests/services/b_agents/test_pedagogical_engineer_agent.py create mode 100644 src/app/tests/services/b_agents/test_sustainability_integration_agent.py create mode 100644 src/app/tests/services/test_orchestrator.py diff --git a/src/app/bibliography/api/bibliography.py b/src/app/bibliography/api/bibliography.py index 87c42853..1fe919af 100644 --- a/src/app/bibliography/api/bibliography.py +++ b/src/app/bibliography/api/bibliography.py @@ -1,8 +1,8 @@ from fastapi import APIRouter from starlette.responses import PlainTextResponse -from src.app.bibliography.models.bibliography import DocumentIDs from src.app.bibliography.helpers.helpers import welearn_document_to_ris +from src.app.bibliography.models.bibliography import DocumentIDs from src.app.services.sql_db.queries import get_documents_by_ids router = APIRouter() diff --git a/src/app/bibliography/helpers/helpers.py b/src/app/bibliography/helpers/helpers.py index a064ee66..0246030d 100644 --- a/src/app/bibliography/helpers/helpers.py +++ b/src/app/bibliography/helpers/helpers.py @@ -1,7 +1,6 @@ import datetime from typing import Any, Optional - from welearn_database.data.models import WeLearnDocument from src.app.utils.decorators import log_time_and_error_sync diff --git a/src/app/tests/services/b_agents/__init__.py b/src/app/tests/services/b_agents/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/app/tests/services/b_agents/test_activity_guide_agent.py b/src/app/tests/services/b_agents/test_activity_guide_agent.py new file mode 100644 index 00000000..ac3f4426 --- /dev/null +++ b/src/app/tests/services/b_agents/test_activity_guide_agent.py @@ -0,0 +1,256 @@ +import unittest +from unittest.mock import AsyncMock, patch + +from src.app.baml_client.async_client import types +from src.app.baml_client.types import SessionMode +from src.app.tutor.service.b_agents.activity_guide_generator_agent import ( + ActivityGuideGeneratorAgent, +) + + +class TestActivityGuideGeneratorAgent(unittest.IsolatedAsyncioTestCase): + + def setUp(self): + self.agent = ActivityGuideGeneratorAgent() + + # Replace these with the required fields for your actual BAML types. + self.activity_template = types.ActivityTemplate( + name="Débat sur le développement durable", + id="fakeid", + description="fake description", + compatible_types=["fake types"], + compatible_modes=[SessionMode.PRESENTIEL], + min_duration=1.5, + max_duration=3, + min_size=20, + max_size=30, + estimated_duration=2, + ) + + self.session = types.SessionPlan( + session_number=2, + objectives=[1], + type="fake type", + mode=SessionMode.PRESENTIEL, + duration=1.5, + class_size=20, + ) + + self.metadata = types.CourseMetadata( + output_language="en", + discipline="computer science", + level="licence", + topic="fake topic", + num_sessions=4, + session_type="seminaire", + session_mode=SessionMode.PRESENTIEL, + class_size=10, + session_duration=1.5, + ) + + self.sustainability_map = types.SustainabilityIntegration( + connections=[ + types.SustainabilityConnection( + objective_number=1, + sdg_themes=["fake themes"], + connection_explanation="fake explanation", + key_resources=[ + types.Document( + text="fake text", + corpus="toto", + description="fake description", + sdg=[1], + url="fake url", + title="fake title", + ) + ], + ) + ], + integration_strategy="fake strategy", + resources_used=[ + types.Document( + text="fake text", + corpus="toto", + description="fake description", + sdg=[1], + url="fake url", + title="fake title", + ) + ], + ) + + self.activity_guide = types.ActivityGuide( + activity_name="Débat sur le développement durable", + description="Une activité de débat.", + learning_objectives=[1], + steps=[ + "Former les groupes", + "Préparer les arguments", + "Réaliser le débat", + ], + teacher_role="Faciliter le débat", + student_role="Participer au débat", + resources_needed=["Documents de référence"], + timing_breakdown={"toto": 30}, + evaluation_method="Évaluation des arguments", + sustainability_integration="Lien avec le développement durable", + ) + + @patch( + "src.app.tutor.service.b_agents.activity_guide_generator_agent.b.GenerateActivityGuide", + new_callable=AsyncMock, + ) + async def test_generate_calls_baml( + self, + mock_generate, + ): + mock_generate.return_value = self.activity_guide + + result = await self.agent.generate( + activity_template=self.activity_template, + session=self.session, + metadata=self.metadata, + sustainability_map=self.sustainability_map, + output_language="Français", + ) + + mock_generate.assert_awaited_once_with( + activity_template=self.activity_template, + session=self.session, + metadata=self.metadata, + sustainability_map=self.sustainability_map, + output_language="Français", + ) + + self.assertEqual(result, self.activity_guide) + + @patch( + "src.app.tutor.service.b_agents.activity_guide_generator_agent.b.GenerateActivityGuide", + new_callable=AsyncMock, + ) + async def test_generate_uses_default_language( + self, + mock_generate, + ): + mock_generate.return_value = self.activity_guide + + await self.agent.generate( + activity_template=self.activity_template, + session=self.session, + metadata=self.metadata, + sustainability_map=self.sustainability_map, + ) + + mock_generate.assert_awaited_once_with( + activity_template=self.activity_template, + session=self.session, + metadata=self.metadata, + sustainability_map=self.sustainability_map, + output_language="Français", + ) + + @patch( + "src.app.tutor.service.b_agents.activity_guide_generator_agent.b.GenerateActivityGuide", + new_callable=AsyncMock, + ) + async def test_generate_returns_activity_guide( + self, + mock_generate, + ): + mock_generate.return_value = self.activity_guide + + result = await self.agent.generate( + activity_template=self.activity_template, + session=self.session, + metadata=self.metadata, + sustainability_map=self.sustainability_map, + ) + + self.assertIsInstance(result, types.ActivityGuide) + self.assertEqual( + result.activity_name, + self.activity_guide.activity_name, + ) + self.assertEqual( + result.description, + self.activity_guide.description, + ) + self.assertEqual( + result.learning_objectives, + self.activity_guide.learning_objectives, + ) + self.assertEqual( + result.steps, + self.activity_guide.steps, + ) + self.assertEqual( + result.teacher_role, + self.activity_guide.teacher_role, + ) + self.assertEqual( + result.student_role, + self.activity_guide.student_role, + ) + self.assertEqual( + result.resources_needed, + self.activity_guide.resources_needed, + ) + self.assertEqual( + result.timing_breakdown, + self.activity_guide.timing_breakdown, + ) + self.assertEqual( + result.evaluation_method, + self.activity_guide.evaluation_method, + ) + self.assertEqual( + result.sustainability_integration, + self.activity_guide.sustainability_integration, + ) + + @patch( + "src.app.tutor.service.b_agents.activity_guide_generator_agent.b.GenerateActivityGuide", + new_callable=AsyncMock, + ) + async def test_generate_with_custom_language( + self, + mock_generate, + ): + mock_generate.return_value = self.activity_guide + + await self.agent.generate( + activity_template=self.activity_template, + session=self.session, + metadata=self.metadata, + sustainability_map=self.sustainability_map, + output_language="English", + ) + + mock_generate.assert_awaited_once_with( + activity_template=self.activity_template, + session=self.session, + metadata=self.metadata, + sustainability_map=self.sustainability_map, + output_language="English", + ) + + @patch( + "src.app.tutor.service.b_agents.activity_guide_generator_agent.b.GenerateActivityGuide", + new_callable=AsyncMock, + ) + async def test_generate_propagates_exception( + self, + mock_generate, + ): + mock_generate.side_effect = RuntimeError("BAML generation failed") + + with self.assertRaisesRegex( + RuntimeError, + "BAML generation failed", + ): + await self.agent.generate( + activity_template=self.activity_template, + session=self.session, + metadata=self.metadata, + sustainability_map=self.sustainability_map, + ) diff --git a/src/app/tests/services/b_agents/test_activity_recommendation_agent.py b/src/app/tests/services/b_agents/test_activity_recommendation_agent.py new file mode 100644 index 00000000..cf4cd307 --- /dev/null +++ b/src/app/tests/services/b_agents/test_activity_recommendation_agent.py @@ -0,0 +1,552 @@ +import unittest +from unittest.mock import AsyncMock, patch + +from src.app.baml_client.async_client import types +from src.app.baml_client.types import SessionMode +from src.app.tutor.service.b_agents.activity_recommendation_agent import ( + ActivityRecommendationAgent, +) + + +class TestActivityRecommendationAgent(unittest.IsolatedAsyncioTestCase): + + def setUp(self): + self.agent = ActivityRecommendationAgent() + self.session = types.SessionPlan( + session_number=2, + objectives=[1, 2], + type="fake type", + mode=SessionMode.PRESENTIEL, + duration=1.5, + class_size=20, + ) + + self.learning_outcomes = types.LearningOutcomes( + outcomes=[ + types.LearningOutcome( + number=1, + text="Understand sustainable development concepts.", + related_objectives=[1], + assessment_method="Written examination", + ), + types.LearningOutcome( + number=2, + text="Apply sustainability concepts to practical problems.", + related_objectives=[2], + assessment_method="Practical assignment", + ), + types.LearningOutcome( + number=3, + text="Analyze sustainability challenges.", + related_objectives=[1, 2], + assessment_method="Project evaluation", + ), + ] + ) + + self.filtered_activities = [ + types.ActivityTemplate( + name="Group discussion", + id="activity-1", + description="A collaborative group discussion.", + compatible_types=["seminaire"], + compatible_modes=[SessionMode.PRESENTIEL], + min_duration=1.0, + max_duration=3.0, + min_size=5, + max_size=30, + estimated_duration=2.0, + ), + types.ActivityTemplate( + name="Case study", + id="activity-2", + description="A sustainability case study.", + compatible_types=["seminaire"], + compatible_modes=[SessionMode.PRESENTIEL], + min_duration=1.0, + max_duration=3.0, + min_size=5, + max_size=30, + estimated_duration=2.0, + ), + ] + + self.activity_recommendations = types.ActivityRecommendations( + session_number=2, + recommended_activities=[ + types.ActivityTemplate( + name="Group discussion", + id="activity-1", + description="A collaborative group discussion.", + compatible_types=["seminaire"], + compatible_modes=[SessionMode.PRESENTIEL], + min_duration=1.0, + max_duration=3.0, + min_size=5, + max_size=30, + estimated_duration=2.0, + ), + types.ActivityTemplate( + name="Case study", + id="activity-2", + description="A sustainability case study.", + compatible_types=["seminaire"], + compatible_modes=[SessionMode.PRESENTIEL], + min_duration=1.0, + max_duration=3.0, + min_size=5, + max_size=30, + estimated_duration=2.0, + ), + ], + rationale="These activities support the learning outcomes.", + ) + + self.greencomp_methods = [ + {"name": "Collaborative learning"}, + {"name": "Critical thinking"}, + ] + + @patch( + "src.app.tutor.service.b_agents.activity_recommendation_agent.load_greencomp_methods", + return_value=[], + ) + @patch( + "src.app.tutor.service.b_agents.activity_recommendation_agent.b.RecommendActivities", + new_callable=AsyncMock, + ) + async def test_recommend_calls_baml( + self, + mock_recommend, + mock_load_methods, + ): + mock_recommend.return_value = self.activity_recommendations + + with patch( + "src.app.tutor.service.b_agents.activity_recommendation_agent.get_suitable_methods_for_outcome_text", + return_value=self.greencomp_methods, + ): + result = await self.agent.recommend( + session=self.session, + learning_outcomes=self.learning_outcomes, + filtered_activities=self.filtered_activities, + output_language="Français", + ) + + mock_recommend.assert_awaited_once_with( + session=self.session, + learning_outcomes=self.learning_outcomes, + filtered_activities=self.filtered_activities, + greencomp_methods=[ + "Collaborative learning", + "Critical thinking", + "Collaborative learning", + "Critical thinking", + "Collaborative learning", + "Critical thinking", + ], + output_language="Français", + ) + + self.assertEqual( + result, + self.activity_recommendations, + ) + + @patch( + "src.app.tutor.service.b_agents.activity_recommendation_agent.load_greencomp_methods", + return_value=[], + ) + @patch( + "src.app.tutor.service.b_agents.activity_recommendation_agent.b.RecommendActivities", + new_callable=AsyncMock, + ) + async def test_recommend_uses_default_language( + self, + mock_recommend, + mock_load_methods, + ): + mock_recommend.return_value = self.activity_recommendations + + with patch( + "src.app.tutor.service.b_agents.activity_recommendation_agent.get_suitable_methods_for_outcome_text", + return_value=[], + ): + await self.agent.recommend( + session=self.session, + learning_outcomes=self.learning_outcomes, + filtered_activities=self.filtered_activities, + ) + + mock_recommend.assert_awaited_once_with( + session=self.session, + learning_outcomes=self.learning_outcomes, + filtered_activities=self.filtered_activities, + greencomp_methods=[], + output_language="Français", + ) + + @patch( + "src.app.tutor.service.b_agents.activity_recommendation_agent.load_greencomp_methods", + return_value=[], + ) + @patch( + "src.app.tutor.service.b_agents.activity_recommendation_agent.b.RecommendActivities", + new_callable=AsyncMock, + ) + async def test_recommend_returns_activity_recommendations( + self, + mock_recommend, + mock_load_methods, + ): + mock_recommend.return_value = self.activity_recommendations + + with patch( + "src.app.tutor.service.b_agents.activity_recommendation_agent.get_suitable_methods_for_outcome_text", + return_value=[], + ): + result = await self.agent.recommend( + session=self.session, + learning_outcomes=self.learning_outcomes, + filtered_activities=self.filtered_activities, + ) + + self.assertIsInstance( + result, + types.ActivityRecommendations, + ) + + self.assertEqual( + result.session_number, + self.activity_recommendations.session_number, + ) + + self.assertEqual( + len(result.recommended_activities), + len(self.activity_recommendations.recommended_activities), + ) + + for result_activity, expected_activity in zip( + result.recommended_activities, + self.activity_recommendations.recommended_activities, + ): + self.assertEqual( + result_activity.id, + expected_activity.id, + ) + + self.assertEqual( + result_activity.name, + expected_activity.name, + ) + + self.assertEqual( + result_activity.description, + expected_activity.description, + ) + + self.assertEqual( + result_activity.compatible_types, + expected_activity.compatible_types, + ) + + self.assertEqual( + result_activity.compatible_modes, + expected_activity.compatible_modes, + ) + + self.assertEqual( + result_activity.min_duration, + expected_activity.min_duration, + ) + + self.assertEqual( + result_activity.max_duration, + expected_activity.max_duration, + ) + + self.assertEqual( + result_activity.min_size, + expected_activity.min_size, + ) + + self.assertEqual( + result_activity.max_size, + expected_activity.max_size, + ) + + self.assertEqual( + result_activity.estimated_duration, + expected_activity.estimated_duration, + ) + + self.assertEqual( + result.rationale, + self.activity_recommendations.rationale, + ) + + @patch( + "src.app.tutor.service.b_agents.activity_recommendation_agent.load_greencomp_methods", + return_value=[], + ) + @patch( + "src.app.tutor.service.b_agents.activity_recommendation_agent.b.RecommendActivities", + new_callable=AsyncMock, + ) + async def test_recommend_uses_greencomp_methods( + self, + mock_recommend, + mock_load_methods, + ): + mock_recommend.return_value = self.activity_recommendations + + with patch( + "src.app.tutor.service.b_agents.activity_recommendation_agent.get_suitable_methods_for_outcome_text", + return_value=self.greencomp_methods, + ) as mock_get_methods: + + await self.agent.recommend( + session=self.session, + learning_outcomes=self.learning_outcomes, + filtered_activities=self.filtered_activities, + ) + + self.assertEqual( + mock_get_methods.call_count, + 3, + ) + + expected_texts = [ + "Understand sustainable development concepts.", + "Apply sustainability concepts to practical problems.", + "Analyze sustainability challenges.", + ] + + actual_texts = [call.args[0] for call in mock_get_methods.call_args_list] + + self.assertEqual( + actual_texts, + expected_texts, + ) + + mock_recommend.assert_awaited_once_with( + session=self.session, + learning_outcomes=self.learning_outcomes, + filtered_activities=self.filtered_activities, + greencomp_methods=[ + "Collaborative learning", + "Critical thinking", + "Collaborative learning", + "Critical thinking", + "Collaborative learning", + "Critical thinking", + ], + output_language="Français", + ) + + @patch( + "src.app.tutor.service.b_agents.activity_recommendation_agent.load_greencomp_methods", + return_value=[], + ) + @patch( + "src.app.tutor.service.b_agents.activity_recommendation_agent.b.RecommendActivities", + new_callable=AsyncMock, + ) + async def test_recommend_only_uses_outcomes_related_to_session_objectives( + self, + mock_recommend, + mock_load_methods, + ): + mock_recommend.return_value = self.activity_recommendations + + unrelated_outcome = types.LearningOutcome( + number=4, + text="This outcome is unrelated to the session.", + related_objectives=[99], + assessment_method="Exam", + ) + + learning_outcomes = types.LearningOutcomes( + outcomes=[ + *self.learning_outcomes.outcomes, + unrelated_outcome, + ] + ) + + with patch( + "src.app.tutor.service.b_agents.activity_recommendation_agent.get_suitable_methods_for_outcome_text", + return_value=self.greencomp_methods, + ) as mock_get_methods: + + await self.agent.recommend( + session=self.session, + learning_outcomes=learning_outcomes, + filtered_activities=self.filtered_activities, + ) + + self.assertEqual( + mock_get_methods.call_count, + 3, + ) + + mock_get_methods.assert_any_call("Understand sustainable development concepts.") + + mock_get_methods.assert_any_call( + "Apply sustainability concepts to practical problems." + ) + + mock_get_methods.assert_any_call("Analyze sustainability challenges.") + + mock_recommend.assert_awaited_once() + + @patch( + "src.app.tutor.service.b_agents.activity_recommendation_agent.load_greencomp_methods", + return_value=[], + ) + @patch( + "src.app.tutor.service.b_agents.activity_recommendation_agent.b.RecommendActivities", + new_callable=AsyncMock, + ) + async def test_recommend_limits_greencomp_processing_to_three_outcomes( + self, + mock_recommend, + mock_load_methods, + ): + mock_recommend.return_value = self.activity_recommendations + + outcomes = [ + types.LearningOutcome( + number=i, + text=f"Learning outcome {i}", + related_objectives=[1], + assessment_method="Exam", + ) + for i in range(1, 6) + ] + + learning_outcomes = types.LearningOutcomes(outcomes=outcomes) + + with patch( + "src.app.tutor.service.b_agents.activity_recommendation_agent.get_suitable_methods_for_outcome_text", + return_value=self.greencomp_methods, + ) as mock_get_methods: + + await self.agent.recommend( + session=self.session, + learning_outcomes=learning_outcomes, + filtered_activities=self.filtered_activities, + ) + + self.assertEqual( + mock_get_methods.call_count, + 3, + ) + + expected_texts = [ + "Learning outcome 1", + "Learning outcome 2", + "Learning outcome 3", + ] + + actual_texts = [call.args[0] for call in mock_get_methods.call_args_list] + + self.assertEqual( + actual_texts, + expected_texts, + ) + + @patch( + "src.app.tutor.service.b_agents.activity_recommendation_agent.load_greencomp_methods", + return_value=[], + ) + @patch( + "src.app.tutor.service.b_agents.activity_recommendation_agent.b.RecommendActivities", + new_callable=AsyncMock, + ) + async def test_recommend_ignores_empty_greencomp_methods( + self, + mock_recommend, + mock_load_methods, + ): + mock_recommend.return_value = self.activity_recommendations + + with patch( + "src.app.tutor.service.b_agents.activity_recommendation_agent.get_suitable_methods_for_outcome_text", + return_value=[], + ): + await self.agent.recommend( + session=self.session, + learning_outcomes=self.learning_outcomes, + filtered_activities=self.filtered_activities, + ) + + mock_recommend.assert_awaited_once_with( + session=self.session, + learning_outcomes=self.learning_outcomes, + filtered_activities=self.filtered_activities, + greencomp_methods=[], + output_language="Français", + ) + + @patch( + "src.app.tutor.service.b_agents.activity_recommendation_agent.load_greencomp_methods", + return_value=[], + ) + @patch( + "src.app.tutor.service.b_agents.activity_recommendation_agent.b.RecommendActivities", + new_callable=AsyncMock, + ) + async def test_recommend_with_custom_language( + self, + mock_recommend, + mock_load_methods, + ): + mock_recommend.return_value = self.activity_recommendations + + with patch( + "src.app.tutor.service.b_agents.activity_recommendation_agent.get_suitable_methods_for_outcome_text", + return_value=[], + ): + await self.agent.recommend( + session=self.session, + learning_outcomes=self.learning_outcomes, + filtered_activities=self.filtered_activities, + output_language="English", + ) + + mock_recommend.assert_awaited_once_with( + session=self.session, + learning_outcomes=self.learning_outcomes, + filtered_activities=self.filtered_activities, + greencomp_methods=[], + output_language="English", + ) + + @patch( + "src.app.tutor.service.b_agents.activity_recommendation_agent.load_greencomp_methods", + return_value=[], + ) + @patch( + "src.app.tutor.service.b_agents.activity_recommendation_agent.b.RecommendActivities", + new_callable=AsyncMock, + ) + async def test_recommend_propagates_exception( + self, + mock_recommend, + mock_load_methods, + ): + mock_recommend.side_effect = RuntimeError("BAML recommendation failed") + + with patch( + "src.app.tutor.service.b_agents.activity_recommendation_agent.get_suitable_methods_for_outcome_text", + return_value=[], + ): + with self.assertRaisesRegex( + RuntimeError, + "BAML recommendation failed", + ): + await self.agent.recommend( + session=self.session, + learning_outcomes=self.learning_outcomes, + filtered_activities=self.filtered_activities, + ) diff --git a/src/app/tests/services/b_agents/test_competency_mapping_agent.py b/src/app/tests/services/b_agents/test_competency_mapping_agent.py new file mode 100644 index 00000000..dc0a038a --- /dev/null +++ b/src/app/tests/services/b_agents/test_competency_mapping_agent.py @@ -0,0 +1,196 @@ +import unittest +from unittest.mock import AsyncMock, patch + +from src.app.baml_client.async_client import types +from src.app.tutor.service.b_agents.competency_mapping_agent import ( + CompetencyMappingAgent, +) + + +class TestCompetencyMappingAgent(unittest.IsolatedAsyncioTestCase): + + def setUp(self): + self.agent = CompetencyMappingAgent() + + self.outcomes = types.LearningOutcomes( + outcomes=[ + types.LearningOutcome( + number=1, + text="Understand the fundamentals of computer science.", + related_objectives=[1], + assessment_method="Written examination", + ), + types.LearningOutcome( + number=2, + text="Apply computer science concepts to practical problems.", + related_objectives=[1, 2], + assessment_method="Practical assignment", + ), + ] + ) + + self.greencomp_framework = { + "Critical thinking": "Ability to analyze and evaluate information", + "Collaboration": "Ability to work effectively with others", + "Systems thinking": "Ability to understand complex systems", + } + + self.competency_mappings = types.CompetencyMappings( + mappings=[ + types.CompetencyMapping( + outcome_number=1, + greencomp_competencies=[ + "Critical thinking", + "Systems thinking", + ], + rationale=( + "This outcome requires students to analyze " + "and evaluate complex information." + ), + ), + types.CompetencyMapping( + outcome_number=2, + greencomp_competencies=[ + "Collaboration", + "Critical thinking", + ], + rationale=( + "This outcome requires students to apply concepts " + "and collaborate on practical problems." + ), + ), + ] + ) + + @patch( + "src.app.tutor.service.b_agents.competency_mapping_agent.b.MapCompetencies", + new_callable=AsyncMock, + ) + async def test_map_competencies_calls_baml( + self, + mock_map, + ): + mock_map.return_value = self.competency_mappings + + result = await self.agent.map_competencies( + outcomes=self.outcomes, + greencomp_framework=self.greencomp_framework, + output_language="Français", + ) + + mock_map.assert_awaited_once_with( + outcomes=self.outcomes, + greencomp_framework=self.greencomp_framework, + output_language="Français", + ) + + self.assertEqual( + result, + self.competency_mappings, + ) + + @patch( + "src.app.tutor.service.b_agents.competency_mapping_agent.b.MapCompetencies", + new_callable=AsyncMock, + ) + async def test_map_competencies_uses_default_language( + self, + mock_map, + ): + mock_map.return_value = self.competency_mappings + + await self.agent.map_competencies( + outcomes=self.outcomes, + greencomp_framework=self.greencomp_framework, + ) + + mock_map.assert_awaited_once_with( + outcomes=self.outcomes, + greencomp_framework=self.greencomp_framework, + output_language="Français", + ) + + @patch( + "src.app.tutor.service.b_agents.competency_mapping_agent.b.MapCompetencies", + new_callable=AsyncMock, + ) + async def test_map_competencies_returns_competency_mappings( + self, + mock_map, + ): + mock_map.return_value = self.competency_mappings + + result = await self.agent.map_competencies( + outcomes=self.outcomes, + greencomp_framework=self.greencomp_framework, + ) + + self.assertIsInstance( + result, + types.CompetencyMappings, + ) + + self.assertEqual( + len(result.mappings), + len(self.competency_mappings.mappings), + ) + + for result_mapping, expected_mapping in zip( + result.mappings, + self.competency_mappings.mappings, + ): + self.assertEqual( + result_mapping.outcome_number, + expected_mapping.outcome_number, + ) + + self.assertEqual( + result_mapping.greencomp_competencies, + expected_mapping.greencomp_competencies, + ) + + self.assertEqual( + result_mapping.rationale, + expected_mapping.rationale, + ) + + @patch( + "src.app.tutor.service.b_agents.competency_mapping_agent.b.MapCompetencies", + new_callable=AsyncMock, + ) + async def test_map_competencies_with_custom_language( + self, + mock_map, + ): + mock_map.return_value = self.competency_mappings + + await self.agent.map_competencies( + outcomes=self.outcomes, + greencomp_framework=self.greencomp_framework, + output_language="English", + ) + + mock_map.assert_awaited_once_with( + outcomes=self.outcomes, + greencomp_framework=self.greencomp_framework, + output_language="English", + ) + + @patch( + "src.app.tutor.service.b_agents.competency_mapping_agent.b.MapCompetencies", + new_callable=AsyncMock, + ) + async def test_map_competencies_propagates_exception( + self, + mock_map, + ): + mock_map.side_effect = RuntimeError("BAML competency mapping failed") + + with self.assertRaisesRegex( + RuntimeError, + "BAML competency mapping failed", + ): + await self.agent.map_competencies( + outcomes=self.outcomes, + greencomp_framework=self.greencomp_framework, + ) diff --git a/src/app/tests/services/b_agents/test_course_description_agent.py b/src/app/tests/services/b_agents/test_course_description_agent.py new file mode 100644 index 00000000..d6853a76 --- /dev/null +++ b/src/app/tests/services/b_agents/test_course_description_agent.py @@ -0,0 +1,156 @@ +import unittest +from unittest.mock import AsyncMock, patch + +from src.app.baml_client.async_client import types +from src.app.tutor.service.b_agents.course_description_agent import ( + CourseDescriptionAgent, +) + + +class TestCourseDescriptionAgent(unittest.IsolatedAsyncioTestCase): + + def setUp(self): + self.agent = CourseDescriptionAgent() + + self.metadata = types.CourseMetadata( + output_language="en", + discipline="computer science", + level="licence", + topic="fake topic", + num_sessions=4, + session_type="seminaire", + session_mode=types.SessionMode.PRESENTIEL, + class_size=10, + session_duration=1.5, + ) + + self.context_text = "This is fake contextual text." + + self.course_description = types.CourseDescription( + text="This is a generated course description.", + word_count=8, + ) + + @patch( + "src.app.tutor.service.b_agents.course_description_agent.b.GenerateCourseDescription", + new_callable=AsyncMock, + ) + async def test_generate_calls_baml( + self, + mock_generate, + ): + mock_generate.return_value = self.course_description + + result = await self.agent.generate( + mode="mode_1", + metadata=self.metadata, + context_text=self.context_text, + output_language="Français", + ) + + mock_generate.assert_awaited_once_with( + mode="mode_1", + metadata=self.metadata, + context_text=self.context_text, + output_language="Français", + ) + + self.assertEqual(result, self.course_description) + + @patch( + "src.app.tutor.service.b_agents.course_description_agent.b.GenerateCourseDescription", + new_callable=AsyncMock, + ) + async def test_generate_uses_default_language( + self, + mock_generate, + ): + mock_generate.return_value = self.course_description + + await self.agent.generate( + mode="mode_1", + metadata=self.metadata, + context_text=self.context_text, + ) + + mock_generate.assert_awaited_once_with( + mode="mode_1", + metadata=self.metadata, + context_text=self.context_text, + output_language="Français", + ) + + @patch( + "src.app.tutor.service.b_agents.course_description_agent.b.GenerateCourseDescription", + new_callable=AsyncMock, + ) + async def test_generate_returns_course_description( + self, + mock_generate, + ): + mock_generate.return_value = self.course_description + + result = await self.agent.generate( + mode="mode_1", + metadata=self.metadata, + context_text=self.context_text, + ) + + self.assertIsInstance( + result, + types.CourseDescription, + ) + + self.assertEqual( + result.text, + self.course_description.text, + ) + + self.assertEqual( + result.word_count, + self.course_description.word_count, + ) + + @patch( + "src.app.tutor.service.b_agents.course_description_agent.b.GenerateCourseDescription", + new_callable=AsyncMock, + ) + async def test_generate_with_custom_language( + self, + mock_generate, + ): + mock_generate.return_value = self.course_description + + await self.agent.generate( + mode="mode_1", + metadata=self.metadata, + context_text=self.context_text, + output_language="English", + ) + + mock_generate.assert_awaited_once_with( + mode="mode_1", + metadata=self.metadata, + context_text=self.context_text, + output_language="English", + ) + + @patch( + "src.app.tutor.service.b_agents.course_description_agent.b.GenerateCourseDescription", + new_callable=AsyncMock, + ) + async def test_generate_propagates_exception( + self, + mock_generate, + ): + mock_generate.side_effect = RuntimeError("BAML generation failed") + + with self.assertRaisesRegex( + RuntimeError, + "BAML generation failed", + ): + await self.agent.generate( + mode="mode_1", + metadata=self.metadata, + context_text=self.context_text, + ) diff --git a/src/app/tests/services/b_agents/test_learning_objectives_agent.py b/src/app/tests/services/b_agents/test_learning_objectives_agent.py new file mode 100644 index 00000000..d8fa2b40 --- /dev/null +++ b/src/app/tests/services/b_agents/test_learning_objectives_agent.py @@ -0,0 +1,239 @@ +import unittest +from unittest.mock import AsyncMock, patch + +from src.app.baml_client.async_client import types +from src.app.baml_client.types import SessionMode +from src.app.tutor.service.b_agents.learning_objectives_agent import ( + LearningObjectivesAgent, +) + + +class TestLearningObjectivesAgent(unittest.IsolatedAsyncioTestCase): + + def setUp(self): + self.agent = LearningObjectivesAgent() + + self.description = ( + "This course introduces students to the fundamentals " + "of computer science." + ) + + self.context_text = "This is fake contextual text." + + self.metadata = types.CourseMetadata( + output_language="en", + discipline="computer science", + level="licence", + topic="fake topic", + num_sessions=4, + session_type="seminaire", + session_mode=SessionMode.PRESENTIEL, + class_size=10, + session_duration=1.5, + ) + + self.learning_objectives = types.LearningObjectives( + objectives=[ + types.LearningObjective( + number=1, + text="Understand the fundamentals of computer science.", + ), + types.LearningObjective( + number=2, + text="Apply fundamental computer science concepts.", + ), + types.LearningObjective( + number=3, + text="Analyze basic computer science problems.", + ), + ] + ) + + @patch( + "src.app.tutor.service.b_agents.learning_objectives_agent.b.GenerateLearningObjectives", + new_callable=AsyncMock, + ) + async def test_generate_calls_baml( + self, + mock_generate, + ): + mock_generate.return_value = self.learning_objectives + + result = await self.agent.generate( + description=self.description, + context_text=self.context_text, + metadata=self.metadata, + mode="mode_1", + output_language="Français", + ) + + mock_generate.assert_awaited_once_with( + description=self.description, + context_text=self.context_text, + metadata=self.metadata, + mode="mode_1", + output_language="Français", + ) + + self.assertEqual( + result, + self.learning_objectives, + ) + + @patch( + "src.app.tutor.service.b_agents.learning_objectives_agent.b.GenerateLearningObjectives", + new_callable=AsyncMock, + ) + async def test_generate_uses_default_language( + self, + mock_generate, + ): + mock_generate.return_value = self.learning_objectives + + await self.agent.generate( + description=self.description, + context_text=self.context_text, + metadata=self.metadata, + mode="mode_1", + ) + + mock_generate.assert_awaited_once_with( + description=self.description, + context_text=self.context_text, + metadata=self.metadata, + mode="mode_1", + output_language="Français", + ) + + @patch( + "src.app.tutor.service.b_agents.learning_objectives_agent.b.GenerateLearningObjectives", + new_callable=AsyncMock, + ) + async def test_generate_returns_learning_objectives( + self, + mock_generate, + ): + mock_generate.return_value = self.learning_objectives + + result = await self.agent.generate( + description=self.description, + context_text=self.context_text, + metadata=self.metadata, + mode="mode_1", + ) + + self.assertIsInstance( + result, + types.LearningObjectives, + ) + + self.assertEqual( + len(result.objectives), + len(self.learning_objectives.objectives), + ) + + for result_objective, expected_objective in zip( + result.objectives, + self.learning_objectives.objectives, + ): + self.assertEqual( + result_objective.number, + expected_objective.number, + ) + self.assertEqual( + result_objective.text, + expected_objective.text, + ) + + @patch( + "src.app.tutor.service.b_agents.learning_objectives_agent.b.GenerateLearningObjectives", + new_callable=AsyncMock, + ) + async def test_generate_uses_sequential_numbering( + self, + mock_generate, + ): + mock_generate.return_value = types.LearningObjectives( + objectives=[ + types.LearningObjective( + number=10, + text="First objective", + ), + types.LearningObjective( + number=25, + text="Second objective", + ), + types.LearningObjective( + number=99, + text="Third objective", + ), + ] + ) + + result = await self.agent.generate( + description=self.description, + context_text=self.context_text, + metadata=self.metadata, + mode="mode_1", + ) + + self.assertEqual( + [objective.number for objective in result.objectives], + [1, 2, 3], + ) + + self.assertEqual( + [objective.text for objective in result.objectives], + [ + "First objective", + "Second objective", + "Third objective", + ], + ) + + @patch( + "src.app.tutor.service.b_agents.learning_objectives_agent.b.GenerateLearningObjectives", + new_callable=AsyncMock, + ) + async def test_generate_with_custom_language( + self, + mock_generate, + ): + mock_generate.return_value = self.learning_objectives + + await self.agent.generate( + description=self.description, + context_text=self.context_text, + metadata=self.metadata, + mode="mode_1", + output_language="English", + ) + + mock_generate.assert_awaited_once_with( + description=self.description, + context_text=self.context_text, + metadata=self.metadata, + mode="mode_1", + output_language="English", + ) + + @patch( + "src.app.tutor.service.b_agents.learning_objectives_agent.b.GenerateLearningObjectives", + new_callable=AsyncMock, + ) + async def test_generate_propagates_exception( + self, + mock_generate, + ): + mock_generate.side_effect = RuntimeError("BAML generation failed") + + with self.assertRaisesRegex( + RuntimeError, + "BAML generation failed", + ): + await self.agent.generate( + description=self.description, + context_text=self.context_text, + metadata=self.metadata, + mode="mode_1", + ) diff --git a/src/app/tests/services/b_agents/test_learning_outcomes_agent.py b/src/app/tests/services/b_agents/test_learning_outcomes_agent.py new file mode 100644 index 00000000..1f718e57 --- /dev/null +++ b/src/app/tests/services/b_agents/test_learning_outcomes_agent.py @@ -0,0 +1,241 @@ +import unittest +from unittest.mock import AsyncMock, patch + +from src.app.baml_client.async_client import types +from src.app.baml_client.types import SessionMode +from src.app.tutor.service.b_agents.learning_outcomes_agent import LearningOutcomesAgent + + +class TestLearningOutcomesAgent(unittest.IsolatedAsyncioTestCase): + + def setUp(self): + self.agent = LearningOutcomesAgent() + + self.objectives = types.LearningObjectives( + objectives=[ + types.LearningObjective( + number=1, + text="Understand the fundamentals of computer science.", + ), + types.LearningObjective( + number=2, + text="Apply fundamental computer science concepts.", + ), + types.LearningObjective( + number=3, + text="Analyze basic computer science problems.", + ), + ] + ) + + self.sustainability_map = types.SustainabilityIntegration( + connections=[ + types.SustainabilityConnection( + objective_number=1, + sdg_themes=["fake themes"], + connection_explanation="fake explanation", + key_resources=[ + types.Document( + text="fake text", + corpus="toto", + description="fake description", + sdg=[1], + url="fake url", + title="fake title", + ) + ], + ) + ], + integration_strategy="fake strategy", + resources_used=[ + types.Document( + text="fake text", + corpus="toto", + description="fake description", + sdg=[1], + url="fake url", + title="fake title", + ) + ], + ) + + self.metadata = types.CourseMetadata( + output_language="en", + discipline="computer science", + level="licence", + topic="fake topic", + num_sessions=4, + session_type="seminaire", + session_mode=SessionMode.PRESENTIEL, + class_size=10, + session_duration=1.5, + ) + + self.learning_outcomes = types.LearningOutcomes( + outcomes=[ + types.LearningOutcome( + number=1, + text="Explain fundamental computer science concepts.", + related_objectives=[1], + assessment_method="Written examination", + ), + types.LearningOutcome( + number=2, + text="Apply computer science concepts to practical problems.", + related_objectives=[1, 2], + assessment_method="Practical assignment", + ), + types.LearningOutcome( + number=3, + text="Analyze and solve basic computer science problems.", + related_objectives=[2, 3], + assessment_method="Project evaluation", + ), + ] + ) + + @patch( + "src.app.tutor.service.b_agents.learning_outcomes_agent.b.GenerateLearningOutcomes", + new_callable=AsyncMock, + ) + async def test_generate_calls_baml( + self, + mock_generate, + ): + mock_generate.return_value = self.learning_outcomes + + result = await self.agent.generate( + objectives=self.objectives, + sustainability_map=self.sustainability_map, + metadata=self.metadata, + output_language="Français", + ) + + mock_generate.assert_awaited_once_with( + objectives=self.objectives, + sustainability_map=self.sustainability_map, + metadata=self.metadata, + output_language="Français", + ) + + self.assertEqual( + result, + self.learning_outcomes, + ) + + @patch( + "src.app.tutor.service.b_agents.learning_outcomes_agent.b.GenerateLearningOutcomes", + new_callable=AsyncMock, + ) + async def test_generate_uses_default_language( + self, + mock_generate, + ): + mock_generate.return_value = self.learning_outcomes + + await self.agent.generate( + objectives=self.objectives, + sustainability_map=self.sustainability_map, + metadata=self.metadata, + ) + + mock_generate.assert_awaited_once_with( + objectives=self.objectives, + sustainability_map=self.sustainability_map, + metadata=self.metadata, + output_language="Français", + ) + + @patch( + "src.app.tutor.service.b_agents.learning_outcomes_agent.b.GenerateLearningOutcomes", + new_callable=AsyncMock, + ) + async def test_generate_returns_learning_outcomes( + self, + mock_generate, + ): + mock_generate.return_value = self.learning_outcomes + + result = await self.agent.generate( + objectives=self.objectives, + sustainability_map=self.sustainability_map, + metadata=self.metadata, + ) + + self.assertIsInstance( + result, + types.LearningOutcomes, + ) + + self.assertEqual( + len(result.outcomes), + len(self.learning_outcomes.outcomes), + ) + + for result_outcome, expected_outcome in zip( + result.outcomes, + self.learning_outcomes.outcomes, + ): + self.assertEqual( + result_outcome.number, + expected_outcome.number, + ) + + self.assertEqual( + result_outcome.text, + expected_outcome.text, + ) + + self.assertEqual( + result_outcome.related_objectives, + expected_outcome.related_objectives, + ) + + self.assertEqual( + result_outcome.assessment_method, + expected_outcome.assessment_method, + ) + + @patch( + "src.app.tutor.service.b_agents.learning_outcomes_agent.b.GenerateLearningOutcomes", + new_callable=AsyncMock, + ) + async def test_generate_with_custom_language( + self, + mock_generate, + ): + mock_generate.return_value = self.learning_outcomes + + await self.agent.generate( + objectives=self.objectives, + sustainability_map=self.sustainability_map, + metadata=self.metadata, + output_language="English", + ) + + mock_generate.assert_awaited_once_with( + objectives=self.objectives, + sustainability_map=self.sustainability_map, + metadata=self.metadata, + output_language="English", + ) + + @patch( + "src.app.tutor.service.b_agents.learning_outcomes_agent.b.GenerateLearningOutcomes", + new_callable=AsyncMock, + ) + async def test_generate_propagates_exception( + self, + mock_generate, + ): + mock_generate.side_effect = RuntimeError("BAML generation failed") + + with self.assertRaisesRegex( + RuntimeError, + "BAML generation failed", + ): + await self.agent.generate( + objectives=self.objectives, + sustainability_map=self.sustainability_map, + metadata=self.metadata, + ) diff --git a/src/app/tests/services/b_agents/test_pedagogical_engineer_agent.py b/src/app/tests/services/b_agents/test_pedagogical_engineer_agent.py new file mode 100644 index 00000000..40d5099f --- /dev/null +++ b/src/app/tests/services/b_agents/test_pedagogical_engineer_agent.py @@ -0,0 +1,270 @@ +import unittest +from unittest.mock import AsyncMock, patch + +from src.app.baml_client.async_client import types +from src.app.baml_client.types import SessionMode +from src.app.tutor.service.b_agents.pedagogical_engineer_agent import ( + PedagogicalEngineerAgent, +) + + +class TestPedagogicalEngineerAgent(unittest.IsolatedAsyncioTestCase): + + def setUp(self): + self.agent = PedagogicalEngineerAgent() + + self.description = ( + "This course introduces students to the fundamentals " + "of computer science." + ) + + self.objectives = types.LearningObjectives( + objectives=[ + types.LearningObjective( + number=1, + text="Understand the fundamentals of computer science.", + ), + types.LearningObjective( + number=2, + text="Apply fundamental computer science concepts.", + ), + ] + ) + + self.outcomes = types.LearningOutcomes( + outcomes=[ + types.LearningOutcome( + number=1, + text="Explain fundamental computer science concepts.", + related_objectives=[1], + assessment_method="Written examination", + ), + types.LearningOutcome( + number=2, + text="Apply computer science concepts to practical problems.", + related_objectives=[1, 2], + assessment_method="Practical assignment", + ), + ] + ) + + self.competencies = types.CompetencyMappings( + mappings=[ + types.CompetencyMapping( + outcome_number=1, + greencomp_competencies=["fake comp"], + rationale="fake rationale", + ) + ] + ) + + self.sustainability_map = types.SustainabilityIntegration( + connections=[ + types.SustainabilityConnection( + objective_number=1, + sdg_themes=["fake themes"], + connection_explanation="fake explanation", + key_resources=[ + types.Document( + text="fake text", + corpus="toto", + description="fake description", + sdg=[1], + url="fake url", + title="fake title", + ) + ], + ) + ], + integration_strategy="fake strategy", + resources_used=[ + types.Document( + text="fake text", + corpus="toto", + description="fake description", + sdg=[1], + url="fake url", + title="fake title", + ) + ], + ) + + self.metadata = types.CourseMetadata( + output_language="en", + discipline="computer science", + level="licence", + topic="fake topic", + num_sessions=4, + session_type="seminaire", + session_mode=SessionMode.PRESENTIEL, + class_size=10, + session_duration=1.5, + ) + + self.validation_report = types.ValidationReport( + passed=True, + issues=["fake issue"], + suggestions=["fake suggestion"], + severity="low", + ) + + @patch( + "src.app.tutor.service.b_agents.pedagogical_engineer_agent.b.ValidatePedagogicalFramework", + new_callable=AsyncMock, + ) + async def test_validate_calls_baml( + self, + mock_validate, + ): + mock_validate.return_value = self.validation_report + + result = await self.agent.validate( + description=self.description, + objectives=self.objectives, + outcomes=self.outcomes, + competencies=self.competencies, + sustainability_map=self.sustainability_map, + metadata=self.metadata, + output_language="Français", + ) + + mock_validate.assert_awaited_once_with( + description=self.description, + objectives=self.objectives, + outcomes=self.outcomes, + competencies=self.competencies, + sustainability_map=self.sustainability_map, + metadata=self.metadata, + output_language="Français", + ) + + self.assertEqual( + result, + self.validation_report, + ) + + @patch( + "src.app.tutor.service.b_agents.pedagogical_engineer_agent.b.ValidatePedagogicalFramework", + new_callable=AsyncMock, + ) + async def test_validate_uses_default_language( + self, + mock_validate, + ): + mock_validate.return_value = self.validation_report + + await self.agent.validate( + description=self.description, + objectives=self.objectives, + outcomes=self.outcomes, + competencies=self.competencies, + sustainability_map=self.sustainability_map, + metadata=self.metadata, + ) + + mock_validate.assert_awaited_once_with( + description=self.description, + objectives=self.objectives, + outcomes=self.outcomes, + competencies=self.competencies, + sustainability_map=self.sustainability_map, + metadata=self.metadata, + output_language="Français", + ) + + @patch( + "src.app.tutor.service.b_agents.pedagogical_engineer_agent.b.ValidatePedagogicalFramework", + new_callable=AsyncMock, + ) + async def test_validate_returns_validation_report( + self, + mock_validate, + ): + mock_validate.return_value = self.validation_report + + result = await self.agent.validate( + description=self.description, + objectives=self.objectives, + outcomes=self.outcomes, + competencies=self.competencies, + sustainability_map=self.sustainability_map, + metadata=self.metadata, + ) + + self.assertIsInstance( + result, + types.ValidationReport, + ) + + self.assertEqual( + result.passed, + self.validation_report.passed, + ) + + self.assertEqual( + result.issues, + self.validation_report.issues, + ) + + self.assertEqual( + result.suggestions, + self.validation_report.suggestions, + ) + + self.assertEqual( + result.severity, + self.validation_report.severity, + ) + + @patch( + "src.app.tutor.service.b_agents.pedagogical_engineer_agent.b.ValidatePedagogicalFramework", + new_callable=AsyncMock, + ) + async def test_validate_with_custom_language( + self, + mock_validate, + ): + mock_validate.return_value = self.validation_report + + await self.agent.validate( + description=self.description, + objectives=self.objectives, + outcomes=self.outcomes, + competencies=self.competencies, + sustainability_map=self.sustainability_map, + metadata=self.metadata, + output_language="English", + ) + + mock_validate.assert_awaited_once_with( + description=self.description, + objectives=self.objectives, + outcomes=self.outcomes, + competencies=self.competencies, + sustainability_map=self.sustainability_map, + metadata=self.metadata, + output_language="English", + ) + + @patch( + "src.app.tutor.service.b_agents.pedagogical_engineer_agent.b.ValidatePedagogicalFramework", + new_callable=AsyncMock, + ) + async def test_validate_propagates_exception( + self, + mock_validate, + ): + mock_validate.side_effect = RuntimeError("BAML validation failed") + + with self.assertRaisesRegex( + RuntimeError, + "BAML validation failed", + ): + await self.agent.validate( + description=self.description, + objectives=self.objectives, + outcomes=self.outcomes, + competencies=self.competencies, + sustainability_map=self.sustainability_map, + metadata=self.metadata, + ) diff --git a/src/app/tests/services/b_agents/test_sustainability_integration_agent.py b/src/app/tests/services/b_agents/test_sustainability_integration_agent.py new file mode 100644 index 00000000..ec91d985 --- /dev/null +++ b/src/app/tests/services/b_agents/test_sustainability_integration_agent.py @@ -0,0 +1,320 @@ +import unittest +from unittest.mock import AsyncMock, patch + +from src.app.baml_client.async_client import types +from src.app.baml_client.types import SessionMode +from src.app.tutor.service.b_agents.sustainability_integration_agent import ( + SustainabilityIntegrationAgent, +) + + +class TestSustainabilityIntegrationAgent(unittest.IsolatedAsyncioTestCase): + + def setUp(self): + self.agent = SustainabilityIntegrationAgent() + + self.description = ( + "This course introduces students to the fundamentals " + "of computer science." + ) + + self.objectives = types.LearningObjectives( + objectives=[ + types.LearningObjective( + number=1, + text="Understand the fundamentals of computer science.", + bloom_level="Understand", + ), + types.LearningObjective( + number=2, + text="Apply fundamental computer science concepts.", + bloom_level="Apply", + ), + ] + ) + + self.sdg_resources = [ + types.Document( + text="Sustainable development resource", + corpus="toto", + description="fake description", + sdg=[4], + url="fake url", + title="Sustainable Development Goal 4", + ), + types.Document( + text="Climate action resource", + corpus="toto", + description="fake description", + sdg=[13], + url="fake url 2", + title="Climate Action", + ), + ] + + self.metadata = types.CourseMetadata( + output_language="en", + discipline="computer science", + level="licence", + topic="fake topic", + num_sessions=4, + session_type="seminaire", + session_mode=SessionMode.PRESENTIEL, + class_size=10, + session_duration=1.5, + ) + + self.sustainability_integration = types.SustainabilityIntegration( + connections=[ + types.SustainabilityConnection( + objective_number=1, + sdg_themes=[ + "Quality Education", + "Sustainable Development", + ], + connection_explanation=( + "This objective connects to sustainable " + "development through education." + ), + key_resources=[ + self.sdg_resources[0], + ], + ), + types.SustainabilityConnection( + objective_number=2, + sdg_themes=[ + "Climate Action", + ], + connection_explanation=( + "This objective supports climate awareness." + ), + key_resources=[ + self.sdg_resources[1], + ], + ), + ], + suggested_objectives=[ + types.LearningObjective( + number=3, + text="Evaluate the environmental impact of technology.", + bloom_level="Evaluate", + ), + ], + integration_strategy=( + "Integrate sustainability concepts throughout " "the course." + ), + resources_used=self.sdg_resources, + ) + + @patch( + "src.app.tutor.service.b_agents.sustainability_integration_agent.b.IntegrateSustainability", + new_callable=AsyncMock, + ) + async def test_integrate_calls_baml( + self, + mock_integrate, + ): + mock_integrate.return_value = self.sustainability_integration + + result = await self.agent.integrate( + description=self.description, + objectives=self.objectives, + sdg_resources=self.sdg_resources, + metadata=self.metadata, + mode="mode_1", + output_language="Français", + ) + + mock_integrate.assert_awaited_once_with( + description=self.description, + objectives=self.objectives, + sdg_resources=self.sdg_resources, + metadata=self.metadata, + mode="mode_1", + output_language="Français", + ) + + self.assertEqual( + result, + self.sustainability_integration, + ) + + @patch( + "src.app.tutor.service.b_agents.sustainability_integration_agent.b.IntegrateSustainability", + new_callable=AsyncMock, + ) + async def test_integrate_uses_default_language( + self, + mock_integrate, + ): + mock_integrate.return_value = self.sustainability_integration + + await self.agent.integrate( + description=self.description, + objectives=self.objectives, + sdg_resources=self.sdg_resources, + metadata=self.metadata, + mode="mode_1", + ) + + mock_integrate.assert_awaited_once_with( + description=self.description, + objectives=self.objectives, + sdg_resources=self.sdg_resources, + metadata=self.metadata, + mode="mode_1", + output_language="Français", + ) + + @patch( + "src.app.tutor.service.b_agents.sustainability_integration_agent.b.IntegrateSustainability", + new_callable=AsyncMock, + ) + async def test_integrate_returns_sustainability_integration( + self, + mock_integrate, + ): + mock_integrate.return_value = self.sustainability_integration + + result = await self.agent.integrate( + description=self.description, + objectives=self.objectives, + sdg_resources=self.sdg_resources, + metadata=self.metadata, + mode="mode_1", + ) + + self.assertIsInstance( + result, + types.SustainabilityIntegration, + ) + + self.assertEqual( + len(result.connections), + len(self.sustainability_integration.connections), + ) + + for result_connection, expected_connection in zip( + result.connections, + self.sustainability_integration.connections, + ): + self.assertEqual( + result_connection.objective_number, + expected_connection.objective_number, + ) + + self.assertEqual( + result_connection.sdg_themes, + expected_connection.sdg_themes, + ) + + self.assertEqual( + result_connection.connection_explanation, + expected_connection.connection_explanation, + ) + + self.assertEqual( + result_connection.key_resources, + expected_connection.key_resources, + ) + + self.assertEqual( + result.suggested_objectives, + self.sustainability_integration.suggested_objectives, + ) + + self.assertEqual( + result.integration_strategy, + self.sustainability_integration.integration_strategy, + ) + + self.assertEqual( + result.resources_used, + self.sustainability_integration.resources_used, + ) + + @patch( + "src.app.tutor.service.b_agents.sustainability_integration_agent.b.IntegrateSustainability", + new_callable=AsyncMock, + ) + async def test_integrate_with_custom_language( + self, + mock_integrate, + ): + mock_integrate.return_value = self.sustainability_integration + + await self.agent.integrate( + description=self.description, + objectives=self.objectives, + sdg_resources=self.sdg_resources, + metadata=self.metadata, + mode="mode_1", + output_language="English", + ) + + mock_integrate.assert_awaited_once_with( + description=self.description, + objectives=self.objectives, + sdg_resources=self.sdg_resources, + metadata=self.metadata, + mode="mode_1", + output_language="English", + ) + + @patch( + "src.app.tutor.service.b_agents.sustainability_integration_agent.b.IntegrateSustainability", + new_callable=AsyncMock, + ) + async def test_integrate_with_no_suggested_objectives( + self, + mock_integrate, + ): + mock_integrate.return_value = types.SustainabilityIntegration( + connections=[ + types.SustainabilityConnection( + objective_number=1, + sdg_themes=["Quality Education"], + connection_explanation="fake explanation", + key_resources=[ + self.sdg_resources[0], + ], + ), + ], + suggested_objectives=None, + integration_strategy="fake strategy", + resources_used=self.sdg_resources, + ) + + result = await self.agent.integrate( + description=self.description, + objectives=self.objectives, + sdg_resources=self.sdg_resources, + metadata=self.metadata, + mode="mode_1", + ) + + self.assertIsNone( + result.suggested_objectives, + ) + + @patch( + "src.app.tutor.service.b_agents.sustainability_integration_agent.b.IntegrateSustainability", + new_callable=AsyncMock, + ) + async def test_integrate_propagates_exception( + self, + mock_integrate, + ): + mock_integrate.side_effect = RuntimeError("BAML integration failed") + + with self.assertRaisesRegex( + RuntimeError, + "BAML integration failed", + ): + await self.agent.integrate( + description=self.description, + objectives=self.objectives, + sdg_resources=self.sdg_resources, + metadata=self.metadata, + mode="mode_1", + ) diff --git a/src/app/tests/services/test_helpers.py b/src/app/tests/services/test_helpers.py index 0af167cb..ce414f09 100644 --- a/src/app/tests/services/test_helpers.py +++ b/src/app/tests/services/test_helpers.py @@ -5,6 +5,13 @@ import numpy from langdetect.language import Language +from src.app.bibliography.helpers.helpers import ( + compute_authors_for_ris, + compute_publication_date_for_ris, + compute_ris_doctype, + ris_line, + welearn_document_to_ris, +) from src.app.models.documents import Document, DocumentPayloadModel from src.app.services.helpers import ( convert_embedding_bytes, @@ -13,13 +20,6 @@ linkify_missing_citations, stringify_docs_content, ) -from src.app.bibliography.helpers.helpers import ( - compute_authors_for_ris, - compute_publication_date_for_ris, - compute_ris_doctype, - ris_line, - welearn_document_to_ris, -) from src.app.shared.domain.exceptions import LanguageNotSupportedError diff --git a/src/app/tests/services/test_orchestrator.py b/src/app/tests/services/test_orchestrator.py new file mode 100644 index 00000000..25caa25d --- /dev/null +++ b/src/app/tests/services/test_orchestrator.py @@ -0,0 +1,646 @@ +"""Unit tests for src.app.tutor.orchestrator. + +Run from the project root: + python -m unittest -v unittest.py + +The tests mock every agent constructed by SyllabusOrchestrator, so no LLM/API +calls are made. +""" + +import unittest +from types import SimpleNamespace +from unittest.mock import AsyncMock, patch + +import src.app.tutor.service.orchestrator as orch +from src.app.baml_client.types import CourseMetadata, SessionMode +from src.app.tutor.service.models import UserInput + + +class FakeAgent: + def __init__(self, total_tokens=0): + self.total_tokens = total_tokens + + +class FakeCourseDescriptionAgent(FakeAgent): + def __init__(self): + super().__init__() + self.generate = AsyncMock( + return_value=SimpleNamespace( + text="A test course description.", + word_count=5, + ) + ) + + +class FakeLearningObjectivesAgent(FakeAgent): + def __init__(self): + super().__init__() + self.generate = AsyncMock( + return_value=SimpleNamespace( + objectives=[ + SimpleNamespace(text="Understand the core concepts."), + SimpleNamespace(text="Apply the concepts in practice."), + ] + ) + ) + + +class FakeSustainabilityAgent(FakeAgent): + def __init__(self): + super().__init__() + self.integrate = AsyncMock( + return_value=SimpleNamespace(suggested_objectives=[]) + ) + + +class FakeLearningOutcomesAgent(FakeAgent): + def __init__(self): + super().__init__() + self.generate = AsyncMock( + return_value=SimpleNamespace( + outcomes=[SimpleNamespace(text="Demonstrate understanding.")] + ) + ) + + +class FakeCompetencyMappingAgent(FakeAgent): + def __init__(self): + super().__init__() + self.map_competencies = AsyncMock( + return_value=SimpleNamespace( + mappings=[ + SimpleNamespace( + code="C4", + name="Systems thinking", + ) + ] + ) + ) + + +class FakePedagogicalEngineerAgent(FakeAgent): + def __init__(self): + super().__init__() + self.validate = AsyncMock( + return_value=SimpleNamespace( + passed=True, + severity=None, + suggestions=[], + issues=[], + ) + ) + + +class TestSyllabusOrchestrator(unittest.IsolatedAsyncioTestCase): + """Unit tests with all external agents replaced by mocks.""" + + def setUp(self): + self.description_agent = FakeCourseDescriptionAgent() + self.objectives_agent = FakeLearningObjectivesAgent() + self.sustainability_agent = FakeSustainabilityAgent() + self.outcomes_agent = FakeLearningOutcomesAgent() + self.competency_agent = FakeCompetencyMappingAgent() + self.pedagogical_agent = FakePedagogicalEngineerAgent() + + # These are also constructed by SyllabusOrchestrator.__init__. + self.engineer_agent = FakeAgent() + self.recommendation_agent = FakeAgent() + self.guide_agent = FakeAgent() + + self.patches = [ + patch.object( + orch, + "CourseDescriptionAgent", + return_value=self.description_agent, + ), + patch.object( + orch, + "LearningObjectivesAgent", + return_value=self.objectives_agent, + ), + patch.object( + orch, + "SustainabilityIntegrationAgent", + return_value=self.sustainability_agent, + ), + patch.object( + orch, + "LearningOutcomesAgent", + return_value=self.outcomes_agent, + ), + patch.object( + orch, + "CompetencyMappingAgent", + return_value=self.competency_agent, + ), + patch.object( + orch, + "PedagogicalEngineerAgent", + return_value=self.pedagogical_agent, + ), + patch.object( + orch, + "ActivityRecommendationAgent", + return_value=self.recommendation_agent, + ), + patch.object( + orch, + "ActivityGuideGeneratorAgent", + return_value=self.guide_agent, + ), + ] + + for patcher in self.patches: + patcher.start() + + self.addCleanup(self.stop_patches) + self.sut = orch.SyllabusOrchestrator() + + def stop_patches(self): + for patcher in reversed(self.patches): + patcher.stop() + + def make_metadata(self): + return CourseMetadata( + output_language="en", + discipline="computer science", + level="licence", + topic="fake topic", + num_sessions=4, + session_type="seminaire", + session_mode=SessionMode.PRESENTIEL, + class_size=10, + session_duration=1.5, + ) + + def make_user_input( + self, + *, + mode=None, + documents=None, + provided_description=None, + provided_objectives=None, + objective_processing=None, + ): + return UserInput( + mode=mode, + documents=documents, + provided_description=provided_description, + provided_objectives=provided_objectives, + objective_processing=objective_processing, + metadata=self.make_metadata(), + rag_resources=[], + ) + + # ------------------------------------------------------------------ + # Mode detection + # ------------------------------------------------------------------ + + def test_detect_mode_documents_only(self): + user_input = self.make_user_input(documents=["syllabus.pdf"]) + self.assertEqual( + self.sut._detect_mode(user_input), + "mode_1", + ) + + def test_detect_mode_documents_with_description(self): + user_input = self.make_user_input( + documents=["syllabus.pdf"], + provided_description="Existing description", + ) + self.assertEqual( + self.sut._detect_mode(user_input), + "mode_2a", + ) + + def test_detect_mode_objectives_augment(self): + user_input = self.make_user_input( + provided_objectives=["Objective 1"], + objective_processing="augment", + ) + self.assertEqual( + self.sut._detect_mode(user_input), + "mode_2b_augment", + ) + + def test_detect_mode_objectives_transform(self): + user_input = self.make_user_input( + provided_objectives=["Objective 1"], + objective_processing="transform", + ) + self.assertEqual( + self.sut._detect_mode(user_input), + "mode_2b_transform", + ) + + def test_detect_mode_metadata_only(self): + user_input = self.make_user_input() + self.assertEqual( + self.sut._detect_mode(user_input), + "mode_3", + ) + + def test_explicit_mode_has_priority(self): + user_input = self.make_user_input( + mode="custom_mode", + documents=["syllabus.pdf"], + ) + self.assertEqual( + self.sut._detect_mode(user_input), + "custom_mode", + ) + + # ------------------------------------------------------------------ + # Phase 0 + # ------------------------------------------------------------------ + + def test_start_initializes_state_and_context(self): + user_input = self.make_user_input( + documents=["syllabus.pdf"], + provided_description="Provided description", + ) + + state = self.sut.start(user_input) + + self.assertIs(state, self.sut.state) + self.assertEqual(state.mode, "mode_2a") + self.assertIs(state.metadata, user_input.metadata) + self.assertEqual(state.rag_resources, []) + self.assertEqual( + state.provided_objectives, + user_input.provided_objectives, + ) + self.assertEqual(state.current_phase, orch.Phase.INPUT) + self.assertEqual( + self.sut._context_text, + "Provided description", + ) + + def test_start_without_description_uses_empty_context(self): + self.sut.start(self.make_user_input()) + self.assertEqual(self.sut._context_text, "") + + # ------------------------------------------------------------------ + # Token accounting + # ------------------------------------------------------------------ + + def test_get_total_tokens_sums_all_agents(self): + agents = [ + self.sut.course_description_agent, + self.sut.learning_objectives_agent, + self.sut.sustainability_agent, + self.sut.learning_outcomes_agent, + self.sut.competency_mapping_agent, + self.sut.pedagogical_engineer_agent, + self.sut.activity_recommendation_agent, + self.sut.activity_guide_generator_agent, + ] + + for index, agent in enumerate(agents, start=1): + agent.total_tokens = index * 10 + + self.assertEqual( + self.sut.get_total_tokens(), + sum(range(10, 90, 10)), + ) + + # ------------------------------------------------------------------ + # Phase 1: individual phases + # ------------------------------------------------------------------ + + async def test_generate_description(self): + self.sut.start(self.make_user_input()) + + result = await self.sut.phase_1_generate_description("test context") + + self.assertEqual( + result.text, + "A test course description.", + ) + self.assertIs(self.sut.state.description, result) + self.assertEqual( + self.sut.state.current_phase, + orch.Phase.DESCRIPTION, + ) + self.description_agent.generate.assert_awaited_once() + + kwargs = self.description_agent.generate.await_args.kwargs + self.assertEqual(kwargs["context_text"], "test context") + + async def test_generate_objectives(self): + self.sut.start(self.make_user_input()) + self.sut.state.description = SimpleNamespace(text="Course description") + + result = await self.sut.phase_1_generate_objectives("context") + + self.assertEqual(len(result.objectives), 2) + self.assertIs(self.sut.state.objectives, result) + self.assertEqual( + self.sut.state.current_phase, + orch.Phase.OBJECTIVES, + ) + + async def test_integrate_sustainability_augment_merges_objectives(self): + self.sut.start( + self.make_user_input( + provided_objectives=["Original"], + objective_processing="augment", + ) + ) + + original = SimpleNamespace(text="Original") + suggested = SimpleNamespace(text="Suggested") + + self.sut.state.description = SimpleNamespace(text="Description") + self.sut.state.objectives = SimpleNamespace(objectives=[original]) + + self.sustainability_agent.integrate.return_value = SimpleNamespace( + suggested_objectives=[suggested] + ) + + result = await self.sut.phase_1_integrate_sustainability() + + self.assertIs( + self.sut.state.sustainability_integration, + result, + ) + self.assertEqual( + self.sut.state.objectives.objectives, + [original, suggested], + ) + + async def test_integrate_sustainability_transform_does_not_merge(self): + self.sut.start( + self.make_user_input( + provided_objectives=["Original"], + objective_processing="transform", + ) + ) + + original = SimpleNamespace(text="Transformed") + suggested = SimpleNamespace(text="Extra") + + self.sut.state.description = SimpleNamespace(text="Description") + self.sut.state.objectives = SimpleNamespace(objectives=[original]) + + self.sustainability_agent.integrate.return_value = SimpleNamespace( + suggested_objectives=[suggested] + ) + + await self.sut.phase_1_integrate_sustainability() + + self.assertEqual( + self.sut.state.objectives.objectives, + [original], + ) + + async def test_generate_outcomes(self): + self.sut.start(self.make_user_input()) + self.sut.state.objectives = SimpleNamespace( + objectives=[SimpleNamespace(text="Objective")] + ) + self.sut.state.sustainability_integration = SimpleNamespace() + + result = await self.sut.phase_1_generate_outcomes() + + self.assertEqual(len(result.outcomes), 1) + self.assertIs(self.sut.state.outcomes, result) + self.assertEqual( + self.sut.state.current_phase, + orch.Phase.OUTCOMES, + ) + + async def test_map_competencies(self): + self.sut.start(self.make_user_input()) + self.sut.state.outcomes = SimpleNamespace(outcomes=[]) + + result = await self.sut.phase_1_map_competencies() + + self.assertEqual(len(result.mappings), 1) + self.assertIs(self.sut.state.competencies, result) + self.assertEqual( + self.sut.state.current_phase, + orch.Phase.COMPETENCIES, + ) + + kwargs = self.competency_agent.map_competencies.await_args.kwargs + self.assertEqual( + set(kwargs["greencomp_framework"]), + { + "C1", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "C10", + "C11", + "C12", + }, + ) + + async def test_validate_framework_success(self): + self.sut.start(self.make_user_input()) + self.sut.state.description = SimpleNamespace(text="Description") + self.sut.state.objectives = SimpleNamespace(objectives=[]) + self.sut.state.outcomes = SimpleNamespace(outcomes=[]) + self.sut.state.competencies = SimpleNamespace(mappings=[]) + self.sut.state.sustainability_integration = SimpleNamespace() + + result = await self.sut.phase_1_validate_framework() + + self.assertTrue(result) + self.assertEqual( + self.sut.state.current_phase, + orch.Phase.VALIDATION, + ) + self.assertEqual( + len(self.sut.state.validation_history), + 1, + ) + + async def test_validate_framework_minor_failure_does_not_block(self): + self.sut.start(self.make_user_input()) + self.sut.state.description = SimpleNamespace(text="Description") + self.sut.state.objectives = SimpleNamespace(objectives=[]) + self.sut.state.outcomes = SimpleNamespace(outcomes=[]) + self.sut.state.competencies = SimpleNamespace(mappings=[]) + self.sut.state.sustainability_integration = SimpleNamespace() + + self.pedagogical_agent.validate.return_value = SimpleNamespace( + passed=False, + severity="minor", + suggestions=["Fix wording"], + issues=[], + ) + + self.assertTrue(await self.sut.phase_1_validate_framework()) + + async def test_validate_framework_major_failure_does_not_block(self): + self.sut.start(self.make_user_input()) + self.sut.state.description = SimpleNamespace(text="Description") + self.sut.state.objectives = SimpleNamespace(objectives=[]) + self.sut.state.outcomes = SimpleNamespace(outcomes=[]) + self.sut.state.competencies = SimpleNamespace(mappings=[]) + self.sut.state.sustainability_integration = SimpleNamespace() + + self.pedagogical_agent.validate.return_value = SimpleNamespace( + passed=False, + severity="major", + suggestions=[], + issues=["Major issue"], + ) + + self.assertTrue(await self.sut.phase_1_validate_framework()) + + # ------------------------------------------------------------------ + # Complete Phase 1 + # ------------------------------------------------------------------ + + async def test_run_phase_1_executes_steps_in_order(self): + self.sut.start(self.make_user_input()) + + calls = [] + + async def description(context): + calls.append("description") + self.sut.state.description = SimpleNamespace( + text="Description", + word_count=1, + ) + + async def objectives(context): + calls.append("objectives") + self.sut.state.objectives = SimpleNamespace(objectives=[]) + + async def sustainability(): + calls.append("sustainability") + self.sut.state.sustainability_integration = SimpleNamespace( + suggested_objectives=[] + ) + + async def outcomes(): + calls.append("outcomes") + self.sut.state.outcomes = SimpleNamespace(outcomes=[]) + + async def competencies(): + calls.append("competencies") + self.sut.state.competencies = SimpleNamespace(mappings=[]) + + with patch.object( + self.sut, + "phase_1_generate_description", + side_effect=description, + ), patch.object( + self.sut, + "phase_1_generate_objectives", + side_effect=objectives, + ), patch.object( + self.sut, + "phase_1_integrate_sustainability", + side_effect=sustainability, + ), patch.object( + self.sut, + "phase_1_generate_outcomes", + side_effect=outcomes, + ), patch.object( + self.sut, + "phase_1_map_competencies", + side_effect=competencies, + ): + result = await self.sut.run_phase_1("context") + + self.assertTrue(result) + self.assertEqual( + calls, + [ + "description", + "objectives", + "sustainability", + "outcomes", + "competencies", + ], + ) + + # ------------------------------------------------------------------ + # Phase 2 + # ------------------------------------------------------------------ + + def test_distribute_sessions(self): + self.sut.start(self.make_user_input()) + + objectives = [SimpleNamespace(text="Objective")] + self.sut.state.objectives = SimpleNamespace(objectives=objectives) + + expected_sessions = [ + SimpleNamespace(session_number=1), + SimpleNamespace(session_number=2), + ] + + with patch.object( + orch, + "distribute_objectives", + return_value=expected_sessions, + ) as distribute: + result = self.sut.phase_2_distribute_sessions() + + self.assertIs(result, expected_sessions) + self.assertIs( + self.sut.state.sessions, + expected_sessions, + ) + self.assertEqual( + self.sut.state.current_phase, + orch.Phase.SESSIONS, + ) + + distribute.assert_called_once_with( + objectives=objectives, + metadata=self.sut.state.metadata, + ) + + # ------------------------------------------------------------------ + # Complete orchestration + # ------------------------------------------------------------------ + + async def test_run_completes_successfully(self): + user_input = self.make_user_input(provided_description="Course context") + + with patch.object( + self.sut, + "run_phase_1", + new=AsyncMock(return_value=True), + ) as run_phase_1: + result = await self.sut.run(user_input) + + run_phase_1.assert_awaited_once_with("Course context") + self.assertEqual( + self.sut.state.current_phase, + orch.Phase.COMPLETE, + ) + + for key in ( + "description", + "objectives", + "outcomes", + "competencies", + "sustainability", + "sessions", + ): + self.assertIn(key, result) + + async def test_run_raises_when_phase_1_fails(self): + with patch.object( + self.sut, + "run_phase_1", + new=AsyncMock(return_value=False), + ): + with self.assertRaises(Exception): + await self.sut.run(self.make_user_input()) + + def test_get_output_before_start_raises(self): + with self.assertRaises(Exception): + self.sut.get_output() diff --git a/src/app/tutor/service/orchestrator.py b/src/app/tutor/service/orchestrator.py index 66f5b6f0..e4a5d8ba 100644 --- a/src/app/tutor/service/orchestrator.py +++ b/src/app/tutor/service/orchestrator.py @@ -7,8 +7,8 @@ from enum import Enum from typing import List, Optional -from src.app.tutor.tools import distribute_objectives from src.app.baml_client import types +from src.app.tutor.tools import distribute_objectives from .b_agents import ( ActivityGuideGeneratorAgent, From b3e7165937fc1f5b2956093a7bbacc31cbf7d390 Mon Sep 17 00:00:00 2001 From: Sandra Guerreiro Date: Mon, 31 Aug 2026 14:59:25 +0200 Subject: [PATCH 6/6] uncomment CI --- .github/workflows/ci.yml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bc6264b0..5ddffcaf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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