ai-utils-lib 0.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ai_utils_lib-0.0.1/LICENSE +21 -0
- ai_utils_lib-0.0.1/PKG-INFO +106 -0
- ai_utils_lib-0.0.1/README.md +79 -0
- ai_utils_lib-0.0.1/pyproject.toml +134 -0
- ai_utils_lib-0.0.1/src/ai_utils_lib/__init__.py +1 -0
- ai_utils_lib-0.0.1/src/ai_utils_lib/config/__init__.py +1 -0
- ai_utils_lib-0.0.1/src/ai_utils_lib/config/azure/__init__.py +1 -0
- ai_utils_lib-0.0.1/src/ai_utils_lib/config/azure/app_configuration.py +67 -0
- ai_utils_lib-0.0.1/src/ai_utils_lib/config/azure/config.py +106 -0
- ai_utils_lib-0.0.1/src/ai_utils_lib/config/azure/entra_id.py +47 -0
- ai_utils_lib-0.0.1/src/ai_utils_lib/config/azure/key_vault.py +61 -0
- ai_utils_lib-0.0.1/src/ai_utils_lib/config/config.py +49 -0
- ai_utils_lib-0.0.1/src/ai_utils_lib/config/config.yaml +103 -0
- ai_utils_lib-0.0.1/src/ai_utils_lib/config/file/__init__.py +1 -0
- ai_utils_lib-0.0.1/src/ai_utils_lib/config/file/config.py +52 -0
- ai_utils_lib-0.0.1/src/ai_utils_lib/config/models.py +73 -0
- ai_utils_lib-0.0.1/src/ai_utils_lib/constants.py +16 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Rubens Gomes
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ai-utils-lib
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: AI Utils Library.
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Keywords: ai,config,llm,mcp,agentic
|
|
8
|
+
Author: Rubens Gomes
|
|
9
|
+
Author-email: rubens.s.gomes@gmail.com
|
|
10
|
+
Maintainer: Rubens Gomes
|
|
11
|
+
Maintainer-email: rubens.s.gomes@gmail.com
|
|
12
|
+
Requires-Python: >=3.14,<4.0.0
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
15
|
+
Requires-Dist: aiohttp (>=3.13,<4)
|
|
16
|
+
Requires-Dist: azure-appconfiguration-provider (>=2.5,<3)
|
|
17
|
+
Requires-Dist: azure-identity (>=1.26,<2)
|
|
18
|
+
Requires-Dist: azure-keyvault-secrets (>=4.11,<5)
|
|
19
|
+
Requires-Dist: colorlog (>=6.12.0,<7)
|
|
20
|
+
Requires-Dist: pydantic (>=2.13.5,<3)
|
|
21
|
+
Requires-Dist: pyyaml (>=6.0.3,<7)
|
|
22
|
+
Project-URL: Documentation, https://github.com/rubensgomes-org/ai-utils-lib/blob/main/README.md
|
|
23
|
+
Project-URL: Homepage, https://github.com/rubensgomes-org/ai-utils-lib/
|
|
24
|
+
Project-URL: Repository, https://github.com/rubensgomes-org/ai-utils-lib/
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# AI Utilities Library
|
|
28
|
+
|
|
29
|
+
[](https://www.python.org/downloads/release/python-3147/)
|
|
30
|
+
[](https://python-poetry.org/)
|
|
31
|
+
[](https://github.com/openai/openai-python)
|
|
32
|
+
[](https://github.com/features/actions)
|
|
33
|
+
[](https://azure.microsoft.com/en-us)
|
|
34
|
+
[](https://github.com/rubensgomes-org/ai-utils-lib/blob/main/AI_DISCLAIMER.md)
|
|
35
|
+
[](https://github.com/rubensgomes-org/ai-utils-lib/blob/main/LICENSE)
|
|
36
|
+
|
|
37
|
+
Rubens created this Python library of utilities to be shared and re-used across
|
|
38
|
+
some of his software development projects.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Features
|
|
43
|
+
|
|
44
|
+
- **YAML Config** — API function to load and read config.yaml files
|
|
45
|
+
- **Azure App Configuration** — API function to load and read Azure App Configuration settings
|
|
46
|
+
- **Azure Key Vault** — API function to load and read Azure Key Vault secrets
|
|
47
|
+
|
|
48
|
+
## AI Disclaimer
|
|
49
|
+
|
|
50
|
+
This project includes code and documentation created with the assistance of AI
|
|
51
|
+
tools. For details on usage, limits, and review practices, please see the
|
|
52
|
+
[AI Disclaimer](https://github.com/rubensgomes-org/ai-utils-lib/blob/main/AI_DISCLAIMER.md).
|
|
53
|
+
|
|
54
|
+
## Prerequisites
|
|
55
|
+
|
|
56
|
+
- Python 3.14+
|
|
57
|
+
- pip
|
|
58
|
+
|
|
59
|
+
## Installation
|
|
60
|
+
|
|
61
|
+
1. Install using `pip`
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
pip install ai-utils-lib
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Uninstall
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
pip uninstall ai-utils-lib
|
|
71
|
+
pip cache purge
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Configuration
|
|
75
|
+
|
|
76
|
+
- TODO
|
|
77
|
+
|
|
78
|
+
## Usage
|
|
79
|
+
|
|
80
|
+
- TODO
|
|
81
|
+
|
|
82
|
+
## Open Source Project Information
|
|
83
|
+
|
|
84
|
+
This project is open source and publicly hosted on GitHub at
|
|
85
|
+
[rubensgomes-org](https://github.com/rubensgomes-org/). It is
|
|
86
|
+
published under an
|
|
87
|
+
[OSI-approved open-source license](https://opensource.org/licenses).
|
|
88
|
+
|
|
89
|
+
> [!IMPORTANT]
|
|
90
|
+
> Public availability and the use of an OSI-approved license are requirements
|
|
91
|
+
> for eligibility to
|
|
92
|
+
> use [SonarQube Cloud](https://sonarcloud.io/) under its free plan for
|
|
93
|
+
> open-source projects.
|
|
94
|
+
|
|
95
|
+
## License
|
|
96
|
+
|
|
97
|
+
The project is licensed under the
|
|
98
|
+
[MIT License](https://github.com/rubensgomes-org/ai-utils-lib/blob/main/LICENSE).
|
|
99
|
+
|
|
100
|
+
## Links
|
|
101
|
+
|
|
102
|
+
- [GitHub Project](https://github.com/rubensgomes-org/ai-utils-lib)
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
Author: [Rubens Gomes](https://rubensgomes.com/)
|
|
106
|
+
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# AI Utilities Library
|
|
2
|
+
|
|
3
|
+
[](https://www.python.org/downloads/release/python-3147/)
|
|
4
|
+
[](https://python-poetry.org/)
|
|
5
|
+
[](https://github.com/openai/openai-python)
|
|
6
|
+
[](https://github.com/features/actions)
|
|
7
|
+
[](https://azure.microsoft.com/en-us)
|
|
8
|
+
[](https://github.com/rubensgomes-org/ai-utils-lib/blob/main/AI_DISCLAIMER.md)
|
|
9
|
+
[](https://github.com/rubensgomes-org/ai-utils-lib/blob/main/LICENSE)
|
|
10
|
+
|
|
11
|
+
Rubens created this Python library of utilities to be shared and re-used across
|
|
12
|
+
some of his software development projects.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Features
|
|
17
|
+
|
|
18
|
+
- **YAML Config** — API function to load and read config.yaml files
|
|
19
|
+
- **Azure App Configuration** — API function to load and read Azure App Configuration settings
|
|
20
|
+
- **Azure Key Vault** — API function to load and read Azure Key Vault secrets
|
|
21
|
+
|
|
22
|
+
## AI Disclaimer
|
|
23
|
+
|
|
24
|
+
This project includes code and documentation created with the assistance of AI
|
|
25
|
+
tools. For details on usage, limits, and review practices, please see the
|
|
26
|
+
[AI Disclaimer](https://github.com/rubensgomes-org/ai-utils-lib/blob/main/AI_DISCLAIMER.md).
|
|
27
|
+
|
|
28
|
+
## Prerequisites
|
|
29
|
+
|
|
30
|
+
- Python 3.14+
|
|
31
|
+
- pip
|
|
32
|
+
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
1. Install using `pip`
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install ai-utils-lib
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Uninstall
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip uninstall ai-utils-lib
|
|
45
|
+
pip cache purge
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Configuration
|
|
49
|
+
|
|
50
|
+
- TODO
|
|
51
|
+
|
|
52
|
+
## Usage
|
|
53
|
+
|
|
54
|
+
- TODO
|
|
55
|
+
|
|
56
|
+
## Open Source Project Information
|
|
57
|
+
|
|
58
|
+
This project is open source and publicly hosted on GitHub at
|
|
59
|
+
[rubensgomes-org](https://github.com/rubensgomes-org/). It is
|
|
60
|
+
published under an
|
|
61
|
+
[OSI-approved open-source license](https://opensource.org/licenses).
|
|
62
|
+
|
|
63
|
+
> [!IMPORTANT]
|
|
64
|
+
> Public availability and the use of an OSI-approved license are requirements
|
|
65
|
+
> for eligibility to
|
|
66
|
+
> use [SonarQube Cloud](https://sonarcloud.io/) under its free plan for
|
|
67
|
+
> open-source projects.
|
|
68
|
+
|
|
69
|
+
## License
|
|
70
|
+
|
|
71
|
+
The project is licensed under the
|
|
72
|
+
[MIT License](https://github.com/rubensgomes-org/ai-utils-lib/blob/main/LICENSE).
|
|
73
|
+
|
|
74
|
+
## Links
|
|
75
|
+
|
|
76
|
+
- [GitHub Project](https://github.com/rubensgomes-org/ai-utils-lib)
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
Author: [Rubens Gomes](https://rubensgomes.com/)
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "ai-utils-lib"
|
|
3
|
+
keywords = ["ai", "config", "llm", "mcp", "agentic"]
|
|
4
|
+
version = "0.0.1"
|
|
5
|
+
description = "AI Utils Library."
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "Rubens Gomes", email = "rubens.s.gomes@gmail.com" }
|
|
8
|
+
]
|
|
9
|
+
maintainers = [
|
|
10
|
+
{ name = "Rubens Gomes", email = "rubens.s.gomes@gmail.com" }
|
|
11
|
+
]
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
license = "MIT"
|
|
14
|
+
requires-python = ">=3.14,<4.0.0"
|
|
15
|
+
classifiers = [
|
|
16
|
+
# How mature is this project? Common values are
|
|
17
|
+
# 3 - Alpha
|
|
18
|
+
# 4 - Beta
|
|
19
|
+
# 5 - Production/Stable
|
|
20
|
+
"Development Status :: 4 - Beta",
|
|
21
|
+
# Python versions supported.
|
|
22
|
+
"Programming Language :: Python :: 3.14"
|
|
23
|
+
]
|
|
24
|
+
dependencies = [
|
|
25
|
+
"colorlog (>=6.12.0,<7)",
|
|
26
|
+
"pydantic (>=2.13.5,<3)",
|
|
27
|
+
"pyyaml (>=6.0.3,<7)",
|
|
28
|
+
# Azure dependencies
|
|
29
|
+
"azure-appconfiguration-provider (>=2.5,<3)",
|
|
30
|
+
"azure-identity (>=1.26,<2)",
|
|
31
|
+
"azure-keyvault-secrets (>=4.11,<5)",
|
|
32
|
+
"aiohttp (>=3.13,<4)",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
[build-system]
|
|
36
|
+
requires = ["poetry-core>=2.5,<3"]
|
|
37
|
+
build-backend = "poetry.core.masonry.api"
|
|
38
|
+
|
|
39
|
+
[dependency-groups]
|
|
40
|
+
dev = [
|
|
41
|
+
"black (>=26.10,<27)",
|
|
42
|
+
"flake8 (>=7.4,<8)",
|
|
43
|
+
"isort (>=9.0,<10)",
|
|
44
|
+
"mypy (>=2.4,<3)",
|
|
45
|
+
"pip-audit (>=2.10,<3)",
|
|
46
|
+
"poethepoet (>=0.48,<1)",
|
|
47
|
+
"poetry (>=2.5,<3)",
|
|
48
|
+
"pylint (>=4.1,<5)",
|
|
49
|
+
"pysonar (>=1.9,<3)",
|
|
50
|
+
"types-pyyaml (>=6.0,<7)",
|
|
51
|
+
"uv (>=0.13,<1)",
|
|
52
|
+
]
|
|
53
|
+
test = [
|
|
54
|
+
"coverage (>=7.16,<8)",
|
|
55
|
+
"pytest (>=9.1,<10)",
|
|
56
|
+
"pytest-asyncio (>=1.4,<2)",
|
|
57
|
+
"pytest-cov (>=7.1,<8)",
|
|
58
|
+
"pytest-mock (>=3.15,<4)",
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
[project.urls]
|
|
62
|
+
Homepage = "https://github.com/rubensgomes-org/ai-utils-lib/"
|
|
63
|
+
Documentation = "https://github.com/rubensgomes-org/ai-utils-lib/blob/main/README.md"
|
|
64
|
+
Repository = "https://github.com/rubensgomes-org/ai-utils-lib/"
|
|
65
|
+
|
|
66
|
+
################################################################################
|
|
67
|
+
# ---------- >>> Tools <<< -----------------------------------------------------
|
|
68
|
+
################################################################################
|
|
69
|
+
|
|
70
|
+
[tool.black]
|
|
71
|
+
line-length = 80
|
|
72
|
+
target-version = ["py314"]
|
|
73
|
+
|
|
74
|
+
[tool.coverage.report]
|
|
75
|
+
show_missing = true
|
|
76
|
+
fail_under = 90
|
|
77
|
+
exclude_also = ['if __name__ == .__main__.:']
|
|
78
|
+
|
|
79
|
+
[tool.coverage.run]
|
|
80
|
+
source = ["ai_utils_lib"]
|
|
81
|
+
branch = true
|
|
82
|
+
|
|
83
|
+
[tool.isort]
|
|
84
|
+
line_length = 80
|
|
85
|
+
profile = "black"
|
|
86
|
+
|
|
87
|
+
[tool.mypy]
|
|
88
|
+
mypy_path = "src"
|
|
89
|
+
ignore_missing_imports = true
|
|
90
|
+
|
|
91
|
+
[tool.poetry]
|
|
92
|
+
packages = [{ include = "ai_utils_lib", from = "src" }]
|
|
93
|
+
|
|
94
|
+
[tool.pytest.ini_options]
|
|
95
|
+
asyncio_mode = "auto"
|
|
96
|
+
addopts = "--cov --cov-report=term-missing --cov-report=xml"
|
|
97
|
+
|
|
98
|
+
[tool.sonar]
|
|
99
|
+
# SONAR_TOKEN must be defined as an environment variable
|
|
100
|
+
organization = "rubensgomes-org"
|
|
101
|
+
projectKey = "rubensgomes-org_ai-utils-lib"
|
|
102
|
+
projectName = "ai-utils-lib"
|
|
103
|
+
qualitygate.wait = true
|
|
104
|
+
sources = "src/"
|
|
105
|
+
sourceEncoding = "UTF-8"
|
|
106
|
+
python.version = "3.14"
|
|
107
|
+
|
|
108
|
+
# ---------- >>> poethepoet tasks <<< -----------------------------------------
|
|
109
|
+
|
|
110
|
+
# To run a poe task:
|
|
111
|
+
# `poetry run poe <task-name>` OR
|
|
112
|
+
# `poe <task-name` (e.g., poe clean)
|
|
113
|
+
|
|
114
|
+
[tool.poe.tasks]
|
|
115
|
+
# Poe the Poetry "poethepoet" task runner needs to be installed.
|
|
116
|
+
# Define individual cleanup tasks
|
|
117
|
+
_clean_cache = "poetry cache clear --all"
|
|
118
|
+
_clean_misc = "rm -rf .mypy_cache dist build"
|
|
119
|
+
_clean_pyc = "find . -name '*.pyc' -delete"
|
|
120
|
+
_clean_pycache = "find . -name '__pycache__' -type d -exec rm -rf {} +"
|
|
121
|
+
_clean_pytest = "rm -rf .pytest_cache"
|
|
122
|
+
_clean_venv = "poetry env remove --all"
|
|
123
|
+
|
|
124
|
+
# Combine them into one 'clean' command. Similar to `make clean`
|
|
125
|
+
[tool.poe.tasks.clean]
|
|
126
|
+
sequence = [
|
|
127
|
+
"_clean_cache",
|
|
128
|
+
"_clean_misc",
|
|
129
|
+
"_clean_pyc",
|
|
130
|
+
"_clean_pycache",
|
|
131
|
+
"_clean_pytest",
|
|
132
|
+
"_clean_venv",
|
|
133
|
+
]
|
|
134
|
+
help = "Remove all project artifacts"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""AI Utilities package."""
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Configuration package for AI Utilities."""
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Azure Configuration for AI Utilities."""
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""Azure App Configuration settings reader for AI Utilities."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import logging
|
|
5
|
+
|
|
6
|
+
from azure.appconfiguration import ConfigurationSetting
|
|
7
|
+
from azure.appconfiguration.aio import AzureAppConfigurationClient
|
|
8
|
+
from azure.identity.aio import DefaultAzureCredential
|
|
9
|
+
|
|
10
|
+
__all__ = ["list_settings"]
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
# In Azure Container Apps, `appcs_endpoint` is usually the
|
|
15
|
+
# APP_CONFIG_ENDPOINT environment variable, and the app's managed identity
|
|
16
|
+
# needs the "App Configuration Data Reader" role.
|
|
17
|
+
|
|
18
|
+
# How do Azure Container Apps get permission to read the Key Vault secret?
|
|
19
|
+
#
|
|
20
|
+
# All Azure Container Apps provisioned by Rubens' Terraform azure-iac
|
|
21
|
+
# project is assigned a shared UAMI (User-Assigned Managed Identiry), also
|
|
22
|
+
# provisioned by the same project. This UAMI is granted a RBAC role permission
|
|
23
|
+
# ("App Configuration Data Reader ") to read App Configuration store.
|
|
24
|
+
|
|
25
|
+
# Where the appcs_endpoint should come from?
|
|
26
|
+
#
|
|
27
|
+
# APP_CONFIG_ENDPOINT : environment variable set in every Azure Container App
|
|
28
|
+
# by Rubens' Terraform azure-iac project that contains the URI of the
|
|
29
|
+
# shared App Configuration store (e.g., https://appcs-rgomes-lab.azconfig.io)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
async def list_settings(
|
|
33
|
+
appcs_endpoint: str, key_filter: str, label_filter: str
|
|
34
|
+
) -> list[ConfigurationSetting]:
|
|
35
|
+
"""Return the store's settings matching the key and label filters."""
|
|
36
|
+
# DefaultAzureCredential tries, in order:
|
|
37
|
+
# 1. Environment variables (e.g., AZURE_CLIENT_ID, AZURE_TENANT_ID)
|
|
38
|
+
# 2. Managed identity (e.g., the UAMI of an Azure Container App)
|
|
39
|
+
# 3. Your Azure CLI login
|
|
40
|
+
async with (
|
|
41
|
+
DefaultAzureCredential() as credential,
|
|
42
|
+
AzureAppConfigurationClient(appcs_endpoint, credential) as client,
|
|
43
|
+
):
|
|
44
|
+
logger.debug(
|
|
45
|
+
"Listing App Configuration settings with key_filter: %s, "
|
|
46
|
+
"label_filter: %s",
|
|
47
|
+
key_filter,
|
|
48
|
+
label_filter,
|
|
49
|
+
)
|
|
50
|
+
settings = client.list_configuration_settings(
|
|
51
|
+
key_filter=key_filter, label_filter=label_filter
|
|
52
|
+
)
|
|
53
|
+
matching_settings: list[ConfigurationSetting] = []
|
|
54
|
+
async for setting in settings:
|
|
55
|
+
matching_settings.append(setting)
|
|
56
|
+
return matching_settings
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
if __name__ == "__main__":
|
|
60
|
+
# TESTING ONLY!!!
|
|
61
|
+
_APP_CONFIG_ENDPOINT = "https://appcs-rgomes-lab.azconfig.io"
|
|
62
|
+
_KEY_FILTER = "ai-utils-lib/*"
|
|
63
|
+
settings = asyncio.run(
|
|
64
|
+
list_settings(_APP_CONFIG_ENDPOINT, _KEY_FILTER, "lab")
|
|
65
|
+
)
|
|
66
|
+
for setting in settings:
|
|
67
|
+
print(f"{setting.key} = {setting.value}")
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""Azure App Configuration based loader for AI Utilities."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import functools
|
|
5
|
+
import logging
|
|
6
|
+
from importlib.resources.abc import Traversable
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from azure.appconfiguration import ConfigurationSetting
|
|
10
|
+
|
|
11
|
+
from ai_utils_lib.config.azure.app_configuration import list_settings
|
|
12
|
+
from ai_utils_lib.config.file.config import read_yaml, resolve_config_path
|
|
13
|
+
from ai_utils_lib.config.models import AppConfig
|
|
14
|
+
|
|
15
|
+
__all__ = ["load_config"]
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@functools.cache
|
|
21
|
+
def load_config(
|
|
22
|
+
package: str, appcs_endpoint: str, label_filter: str
|
|
23
|
+
) -> AppConfig:
|
|
24
|
+
"""Load ``config.yaml`` from ``package`` overlaid with App Configuration.
|
|
25
|
+
|
|
26
|
+
Settings under ``azure.app_config_prefix`` and labeled ``label_filter``
|
|
27
|
+
are read from the store at ``appcs_endpoint`` and override the
|
|
28
|
+
matching YAML values.
|
|
29
|
+
|
|
30
|
+
Must not be called from a running event loop.
|
|
31
|
+
"""
|
|
32
|
+
config_yaml_file: Traversable = resolve_config_path(package)
|
|
33
|
+
config_dict: dict[str, Any] = read_yaml(config_yaml_file)
|
|
34
|
+
prefix: str = config_dict["azure"]["app_config_prefix"]
|
|
35
|
+
separator: str = config_dict["azure"]["app_config_separator"]
|
|
36
|
+
settings_dict: dict[str, Any] = asyncio.run(
|
|
37
|
+
_convert_to_dict(appcs_endpoint, separator, prefix, label_filter)
|
|
38
|
+
)
|
|
39
|
+
combined_config_dict: dict[str, Any] = _deep_merge(
|
|
40
|
+
config_dict, settings_dict
|
|
41
|
+
)
|
|
42
|
+
return AppConfig.model_validate(combined_config_dict)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
async def _convert_to_dict(
|
|
46
|
+
appcs_endpoint: str, separator: str, prefix: str, label_filter: str
|
|
47
|
+
) -> dict[str, Any]:
|
|
48
|
+
"""Return the settings under ``prefix`` as a nested dictionary.
|
|
49
|
+
|
|
50
|
+
The prefix is removed and the remaining key is split on ``/``, e.g.
|
|
51
|
+
``prefix/model/name = x`` becomes ``{"model": {"name": "x"}}``.
|
|
52
|
+
"""
|
|
53
|
+
key_filter: str = f"{prefix}*"
|
|
54
|
+
settings: list[ConfigurationSetting] = await list_settings(
|
|
55
|
+
appcs_endpoint, key_filter, label_filter
|
|
56
|
+
)
|
|
57
|
+
logger.debug(
|
|
58
|
+
"Read %d App Configuration settings with key_filter: %s",
|
|
59
|
+
len(settings),
|
|
60
|
+
key_filter,
|
|
61
|
+
)
|
|
62
|
+
response: dict[str, Any] = {}
|
|
63
|
+
for setting in settings:
|
|
64
|
+
key_path: str = setting.key.removeprefix(prefix)
|
|
65
|
+
_set_nested(response, key_path.split(separator), setting.value)
|
|
66
|
+
return response
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _set_nested(target: dict[str, Any], keys: list[str], value: Any) -> None:
|
|
70
|
+
"""Set ``value`` in ``target`` at the nested path given by ``keys``."""
|
|
71
|
+
*parents, leaf = keys
|
|
72
|
+
node: dict[str, Any] = target
|
|
73
|
+
for key in parents:
|
|
74
|
+
child = node.setdefault(key, {})
|
|
75
|
+
if not isinstance(child, dict):
|
|
76
|
+
raise ValueError(
|
|
77
|
+
f"Setting path {keys} conflicts with existing value at "
|
|
78
|
+
f"'{key}'"
|
|
79
|
+
)
|
|
80
|
+
node = child
|
|
81
|
+
if isinstance(node.get(leaf), dict):
|
|
82
|
+
raise ValueError(f"Setting path {keys} conflicts with a section")
|
|
83
|
+
node[leaf] = value
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _deep_merge(
|
|
87
|
+
base: dict[str, Any], override: dict[str, Any]
|
|
88
|
+
) -> dict[str, Any]:
|
|
89
|
+
"""Return ``base`` recursively updated with ``override`` values."""
|
|
90
|
+
merged: dict[str, Any] = dict(base)
|
|
91
|
+
for key, value in override.items():
|
|
92
|
+
base_value = merged.get(key)
|
|
93
|
+
if isinstance(base_value, dict) and isinstance(value, dict):
|
|
94
|
+
merged[key] = _deep_merge(base_value, value)
|
|
95
|
+
else:
|
|
96
|
+
merged[key] = value
|
|
97
|
+
return merged
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
if __name__ == "__main__":
|
|
101
|
+
# TESTING ONLY!!!
|
|
102
|
+
_APP_CONFIG_ENDPOINT = "https://appcs-rgomes-lab.azconfig.io"
|
|
103
|
+
config: AppConfig = load_config(
|
|
104
|
+
"ai_utils_lib.config", _APP_CONFIG_ENDPOINT, "lab"
|
|
105
|
+
)
|
|
106
|
+
print(f"AppConfig = {config}")
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Microsoft Entra ID token provider for AI Utilities."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
from collections.abc import Awaitable, Callable
|
|
5
|
+
|
|
6
|
+
from azure.core.credentials_async import AsyncTokenCredential
|
|
7
|
+
from azure.identity.aio import DefaultAzureCredential, get_bearer_token_provider
|
|
8
|
+
|
|
9
|
+
__all__ = ["token_provider"]
|
|
10
|
+
|
|
11
|
+
COGNITIVE_SERVICES_SCOPE = "https://cognitiveservices.azure.com/.default"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# Entra ID tokens expire, so a fixed token passed as an API key stops
|
|
15
|
+
# working. Clients such as openai.AsyncOpenAI also accept a
|
|
16
|
+
# Callable[[], Awaitable[str]] and call it whenever they need a key;
|
|
17
|
+
# the provider returns a cached token and renews it before it expires.
|
|
18
|
+
# This is the reason for the token_provider function below.
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def token_provider(
|
|
22
|
+
credential: AsyncTokenCredential,
|
|
23
|
+
) -> Callable[[], Awaitable[str]]:
|
|
24
|
+
"""Return an async callable yielding a cached, auto-renewed token.
|
|
25
|
+
|
|
26
|
+
``credential`` must stay open while the returned callable is in use.
|
|
27
|
+
"""
|
|
28
|
+
return get_bearer_token_provider(credential, COGNITIVE_SERVICES_SCOPE)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
if __name__ == "__main__":
|
|
32
|
+
# TESTING ONLY!!!
|
|
33
|
+
_VISIBLE_CHARS = 4
|
|
34
|
+
|
|
35
|
+
async def _main() -> None:
|
|
36
|
+
async with DefaultAzureCredential() as credential:
|
|
37
|
+
provider = token_provider(credential)
|
|
38
|
+
token = await provider()
|
|
39
|
+
print(
|
|
40
|
+
f"Token = {token[:_VISIBLE_CHARS]}…{token[-_VISIBLE_CHARS:]}"
|
|
41
|
+
f" ({len(token)} characters)"
|
|
42
|
+
)
|
|
43
|
+
print(
|
|
44
|
+
f"Second call reuses cached token: {await provider() == token}"
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
asyncio.run(_main())
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""Azure Key Vault reader for AI Utilities."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import logging
|
|
5
|
+
|
|
6
|
+
from azure.identity.aio import DefaultAzureCredential
|
|
7
|
+
from azure.keyvault.secrets.aio import SecretClient
|
|
8
|
+
|
|
9
|
+
__all__ = ["read_secret"]
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
# In Azure Container Apps, `vault_url` is usually the KEY_VAULT_URI
|
|
14
|
+
# environment variable, and the app's managed identity needs the
|
|
15
|
+
# "Key Vault Secrets User" role.
|
|
16
|
+
|
|
17
|
+
# How do Azure Container Apps get permission to read the Key Vault secret?
|
|
18
|
+
#
|
|
19
|
+
# All Azure Container Apps provisioned by Rubens' Terraform azure-iac
|
|
20
|
+
# project is assigned a shared UAMI (User-Assigned Managed Identiry),
|
|
21
|
+
# also provisioned by the same project. This UAMI is granted a RBAC role
|
|
22
|
+
# permission ("Key Vault Secrets User") to read Key Vault secrets.
|
|
23
|
+
|
|
24
|
+
# Where the vaulr_url should come from?
|
|
25
|
+
#
|
|
26
|
+
# KEY_VAULT_URI : environment variable set in every Azure Container App
|
|
27
|
+
# by Rubens' Terraform azure-iac project that contains the URI of the
|
|
28
|
+
# shared Key Vault store (e.g., https://kv-rgomes-lab.vault.azure.net/)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
async def read_secret(vault_url: str, secret_name: str) -> str:
|
|
32
|
+
"""Return the non-empty value of secret ``secret_name`` in ``vault_url``."""
|
|
33
|
+
# DefaultAzureCredential tries, in order:
|
|
34
|
+
# 1. Environment variables (e.g., AZURE_CLIENT_ID, AZURE_TENANT_ID)
|
|
35
|
+
# 2. Managed identity (e.g., the UAMI of an Azure Container App)
|
|
36
|
+
# 3. Your Azure CLI login
|
|
37
|
+
async with (
|
|
38
|
+
DefaultAzureCredential() as credential,
|
|
39
|
+
SecretClient(vault_url=vault_url, credential=credential) as client,
|
|
40
|
+
):
|
|
41
|
+
logger.debug(
|
|
42
|
+
"Reading secret: %s from Key Vault: %s", secret_name, vault_url
|
|
43
|
+
)
|
|
44
|
+
secret = await client.get_secret(secret_name)
|
|
45
|
+
if not secret.value:
|
|
46
|
+
raise ValueError(
|
|
47
|
+
f"Key Vault secret {secret_name} in {vault_url} is empty."
|
|
48
|
+
)
|
|
49
|
+
return secret.value
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
if __name__ == "__main__":
|
|
53
|
+
# TESTING ONLY!!!
|
|
54
|
+
_KEY_VAULT_URI = "https://kv-rgomes-lab.vault.azure.net/"
|
|
55
|
+
_SECRET_NAME = "ca-mathagent--FOUNDRY-API-KEY"
|
|
56
|
+
_VISIBLE_CHARS = 4
|
|
57
|
+
secret_value = asyncio.run(read_secret(_KEY_VAULT_URI, _SECRET_NAME))
|
|
58
|
+
print(
|
|
59
|
+
f"{_SECRET_NAME} = {secret_value[:_VISIBLE_CHARS]}…"
|
|
60
|
+
f"{secret_value[-_VISIBLE_CHARS:]} ({len(secret_value)} characters)"
|
|
61
|
+
)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Generic configuration loader for AI Utilities."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
from ai_utils_lib.config.azure.config import load_config as load_azure_config
|
|
6
|
+
from ai_utils_lib.config.file.config import load_config as load_file_config
|
|
7
|
+
from ai_utils_lib.config.models import AppConfig
|
|
8
|
+
|
|
9
|
+
__all__ = ["load_config"]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def load_config(package: str) -> AppConfig:
|
|
13
|
+
"""Load the ``config.yaml`` of ``package`` for the current runtime.
|
|
14
|
+
|
|
15
|
+
When ``APP_CONFIG_ENDPOINT`` is set, the YAML is overlaid with Azure App
|
|
16
|
+
Configuration settings labeled ``APP_ENV``; otherwise only the YAML is
|
|
17
|
+
loaded.
|
|
18
|
+
|
|
19
|
+
Raises:
|
|
20
|
+
KeyError: ``APP_CONFIG_ENDPOINT`` is set but ``APP_ENV`` is not.
|
|
21
|
+
"""
|
|
22
|
+
# APP_CONFIG_ENDPOINT is an environment variable set in every ACA by
|
|
23
|
+
# Rubens' Terraform azure-iac project.
|
|
24
|
+
aca_endpoint: str | None = os.environ.get("APP_CONFIG_ENDPOINT")
|
|
25
|
+
|
|
26
|
+
if aca_endpoint:
|
|
27
|
+
# application is running in Azure Container Apps
|
|
28
|
+
# APP_ENV is an environment variable set in every ACA by
|
|
29
|
+
# Rubens' Terraform azure-iac project.
|
|
30
|
+
label: str = os.environ["APP_ENV"] # KeyError if not defined
|
|
31
|
+
return load_azure_config(package, aca_endpoint, label)
|
|
32
|
+
|
|
33
|
+
# application is running locally
|
|
34
|
+
return load_file_config(package)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
if __name__ == "__main__":
|
|
38
|
+
# TESTING ONLY!!!
|
|
39
|
+
_PACKAGE = "ai_utils_lib.config"
|
|
40
|
+
# load configuration settings from Azure App Configuration
|
|
41
|
+
os.environ["APP_CONFIG_ENDPOINT"] = "https://appcs-rgomes-lab.azconfig.io"
|
|
42
|
+
os.environ["APP_ENV"] = "lab"
|
|
43
|
+
config: AppConfig = load_config(_PACKAGE)
|
|
44
|
+
print(f"Azure AppConfig = {config}")
|
|
45
|
+
|
|
46
|
+
# load configuration settings from local config.yaml
|
|
47
|
+
os.environ.pop("APP_CONFIG_ENDPOINT", None)
|
|
48
|
+
config = load_config(_PACKAGE)
|
|
49
|
+
print(f"Local AppConfig = {config}")
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# NOTE: THIS IS A TEMPLATE DUMMY CONFIGURATION FILE. YOU MUST HAVE YOUR
|
|
2
|
+
# OWN CONFIGURATION FILE SAVED IN YOUR APPLICATION ROOT PACKAGE FOLDER.
|
|
3
|
+
|
|
4
|
+
# ====================================================================
|
|
5
|
+
# Microsoft Azure Configuration
|
|
6
|
+
# ====================================================================
|
|
7
|
+
azure:
|
|
8
|
+
# App Configuration prefix
|
|
9
|
+
app_config_prefix: "ai-utils-lib/"
|
|
10
|
+
# app configuration prefix separator
|
|
11
|
+
app_config_separator: "/"
|
|
12
|
+
# Key Vault prefix
|
|
13
|
+
secret_prefix: "dummy-kv-prefix--"
|
|
14
|
+
|
|
15
|
+
# ====================================================================
|
|
16
|
+
# Microsoft Foundry Configuration
|
|
17
|
+
# ====================================================================
|
|
18
|
+
foundry:
|
|
19
|
+
project_endpoint: "https://dummyurl.change.it/foundry"
|
|
20
|
+
|
|
21
|
+
# ====================================================================
|
|
22
|
+
# MCP Configuration
|
|
23
|
+
# ====================================================================
|
|
24
|
+
mcp:
|
|
25
|
+
calculator:
|
|
26
|
+
#url: "http://127.0.0.1:8080/mcp"
|
|
27
|
+
url: "https://dummyurl.change.it/mcp"
|
|
28
|
+
#is_oauth: false
|
|
29
|
+
is_oauth: true
|
|
30
|
+
# location to store OAuth token in the local machine
|
|
31
|
+
token_dir: "~/.calculator_mcp"
|
|
32
|
+
# local port temporarily used by the OAuth callback
|
|
33
|
+
callback_port: 10000
|
|
34
|
+
|
|
35
|
+
# ====================================================================
|
|
36
|
+
# SLM or LLM Model Configuration
|
|
37
|
+
# ====================================================================
|
|
38
|
+
model:
|
|
39
|
+
# OpenAI API style:
|
|
40
|
+
# "responses" -> POST /v1/responses (newer and preferred)
|
|
41
|
+
# "chat" -> POST /v1/chat/completions (legacy)
|
|
42
|
+
api_style: "responses"
|
|
43
|
+
base_url: "https://dummyurl.change.it/v1"
|
|
44
|
+
name: "dummy-model-name"
|
|
45
|
+
# environment variable holding the API key.
|
|
46
|
+
api_key_env: "DUMMY_API_KEY"
|
|
47
|
+
# system instructions being provided to the model.
|
|
48
|
+
instructions: >-
|
|
49
|
+
Dummy system instructions for the model. Change it to your own instructions.
|
|
50
|
+
# Requires both Responses API and model support. It stores conversaiont history on the model server end, and send only new items each turn via
|
|
51
|
+
# previous_response_id.
|
|
52
|
+
is_stateful: false
|
|
53
|
+
# Concurrent prompts per second processed at once
|
|
54
|
+
max_concurrent_prompts: 10
|
|
55
|
+
# sampling temperature between 0 and 2.
|
|
56
|
+
temperature: 1.0
|
|
57
|
+
# Seconds to wait for each model response before failing.
|
|
58
|
+
timeout_seconds: 30
|
|
59
|
+
|
|
60
|
+
# ====================================================================
|
|
61
|
+
# Web App Configuration (e.g., used by FastAPI web app server)
|
|
62
|
+
# ====================================================================
|
|
63
|
+
web:
|
|
64
|
+
host: "0.0.0.0"
|
|
65
|
+
# ensure below port does not conflict with other services.
|
|
66
|
+
port: 9090
|
|
67
|
+
|
|
68
|
+
# ====================================================================
|
|
69
|
+
# Logging Configuration
|
|
70
|
+
# ====================================================================
|
|
71
|
+
logging:
|
|
72
|
+
version: 1
|
|
73
|
+
disable_existing_loggers: false
|
|
74
|
+
formatters:
|
|
75
|
+
# Colors level name and message when logs are being directly
|
|
76
|
+
# sent to computer TTY console. For plain (no colors) when
|
|
77
|
+
# logs are being sent over the network, set the NO_COLOR
|
|
78
|
+
# environment variable as NO_COLOR=true.
|
|
79
|
+
standard:
|
|
80
|
+
(): colorlog.ColoredFormatter
|
|
81
|
+
fmt: "%(asctime)s %(log_color)s[%(levelname)s]%(reset)s %(fg_147)s%(name)s %(filename)s:%(lineno)d%(reset)s: %(log_color)s%(message)s%(reset)s"
|
|
82
|
+
datefmt: "%H:%M:%S"
|
|
83
|
+
log_colors:
|
|
84
|
+
DEBUG: cyan
|
|
85
|
+
INFO: light_green
|
|
86
|
+
WARNING: yellow
|
|
87
|
+
ERROR: red
|
|
88
|
+
CRITICAL: bold_red
|
|
89
|
+
stream: ext://sys.stderr
|
|
90
|
+
handlers:
|
|
91
|
+
console:
|
|
92
|
+
class: logging.StreamHandler
|
|
93
|
+
formatter: standard
|
|
94
|
+
stream: ext://sys.stderr
|
|
95
|
+
loggers:
|
|
96
|
+
##############################################################
|
|
97
|
+
# ---------- >>> ai_utils_lib <<< ---------------------------#
|
|
98
|
+
##############################################################
|
|
99
|
+
ai_utils_lib:
|
|
100
|
+
level: DEBUG
|
|
101
|
+
handlers:
|
|
102
|
+
- console
|
|
103
|
+
propagate: false
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""File-based configuration loader for AI Utilities."""
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""File-based configuration loader for AI Utilities."""
|
|
2
|
+
|
|
3
|
+
import functools
|
|
4
|
+
import logging.config
|
|
5
|
+
from importlib.resources import files
|
|
6
|
+
from importlib.resources.abc import Traversable
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
import yaml
|
|
10
|
+
|
|
11
|
+
from ai_utils_lib.config.models import AppConfig
|
|
12
|
+
from ai_utils_lib.constants import CONFIG_FILE_NAME
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"load_config",
|
|
16
|
+
"configure_logging",
|
|
17
|
+
"read_yaml",
|
|
18
|
+
"resolve_config_path",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@functools.cache
|
|
23
|
+
def load_config(package: str) -> AppConfig:
|
|
24
|
+
"""Load and validate ``config.yaml`` from the root of ``package``.
|
|
25
|
+
|
|
26
|
+
Applications typically pass their root package name, e.g.
|
|
27
|
+
``load_config(__package__)`` or ``load_config("my_app")``.
|
|
28
|
+
"""
|
|
29
|
+
config_yaml_file: Traversable = resolve_config_path(package)
|
|
30
|
+
config_dict: dict[str, Any] = read_yaml(config_yaml_file)
|
|
31
|
+
return AppConfig.model_validate(config_dict)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def configure_logging(config: AppConfig) -> None:
|
|
35
|
+
"""Apply the ``logging`` section of ``config`` via ``dictConfig``."""
|
|
36
|
+
logging.config.dictConfig(config.logging)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def resolve_config_path(package: str) -> Traversable:
|
|
40
|
+
"""Return ``config.yaml`` located at the root ``package``."""
|
|
41
|
+
return files(package).joinpath(CONFIG_FILE_NAME)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def read_yaml(resource: Traversable) -> dict[str, Any]:
|
|
45
|
+
"""Return the YAML mapping in ``resource``."""
|
|
46
|
+
try:
|
|
47
|
+
content = yaml.safe_load(resource.read_text(encoding="utf-8"))
|
|
48
|
+
except yaml.YAMLError as error:
|
|
49
|
+
raise ValueError(f"Invalid YAML in {resource}") from error
|
|
50
|
+
if not isinstance(content, dict):
|
|
51
|
+
raise ValueError(f"Expected a YAML mapping in {resource}")
|
|
52
|
+
return content
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""Pydantic models for the config.yaml sections."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Literal
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field, HttpUrl
|
|
6
|
+
|
|
7
|
+
from ai_utils_lib.constants import (
|
|
8
|
+
DEFAULT_MAX_CONCURRENT_PROMPTS,
|
|
9
|
+
DEFAULT_MODEL_TIMEOUT_SECONDS,
|
|
10
|
+
DEFAULT_OAUTH_CALLBACK_PORT,
|
|
11
|
+
DEFAULT_WEBAPP_PORT,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
__all__ = ["Azure", "Foundry", "MCP", "Model", "Web", "AppConfig"]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Azure(BaseModel):
|
|
18
|
+
"""The ``azure`` section of config.yaml."""
|
|
19
|
+
|
|
20
|
+
secret_prefix: str
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Foundry(BaseModel):
|
|
24
|
+
"""The ``foundry`` section of config.yaml."""
|
|
25
|
+
|
|
26
|
+
project_endpoint: HttpUrl
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class MCP(BaseModel):
|
|
30
|
+
"""A named MCP server entry in the ``mcp`` section of config.yaml."""
|
|
31
|
+
|
|
32
|
+
url: HttpUrl
|
|
33
|
+
is_oauth: bool = False
|
|
34
|
+
token_dir: str
|
|
35
|
+
callback_port: int = Field(default=DEFAULT_OAUTH_CALLBACK_PORT, gt=0)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class Model(BaseModel):
|
|
39
|
+
"""The ``model`` section of config.yaml."""
|
|
40
|
+
|
|
41
|
+
api_style: Literal["chat", "responses"] = "responses"
|
|
42
|
+
base_url: HttpUrl
|
|
43
|
+
name: str = Field(min_length=1)
|
|
44
|
+
# `api_key_env` may be None if we are using Azure Entra ID credential
|
|
45
|
+
api_key_env: str | None = Field(default=None, min_length=1)
|
|
46
|
+
instructions: str
|
|
47
|
+
is_stateful: bool = False
|
|
48
|
+
max_concurrent_prompts: int = Field(
|
|
49
|
+
default=DEFAULT_MAX_CONCURRENT_PROMPTS, gt=0
|
|
50
|
+
)
|
|
51
|
+
timeout_seconds: int = Field(default=DEFAULT_MODEL_TIMEOUT_SECONDS, gt=0)
|
|
52
|
+
temperature: float | None = Field(default=None, ge=0, le=2)
|
|
53
|
+
# `api_key` if used is read at initializetion time from either a Key
|
|
54
|
+
# Vault or the environment variable defined by `api_key_env`.
|
|
55
|
+
api_key: str | None = Field(default=None, exclude=True, repr=False)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class Web(BaseModel):
|
|
59
|
+
"""The ``web`` section of config.yaml."""
|
|
60
|
+
|
|
61
|
+
host: str = "localhost"
|
|
62
|
+
port: int = Field(default=DEFAULT_WEBAPP_PORT, gt=0)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class AppConfig(BaseModel):
|
|
66
|
+
"""The full config.yaml."""
|
|
67
|
+
|
|
68
|
+
azure: Azure
|
|
69
|
+
foundry: Foundry
|
|
70
|
+
mcp: dict[str, MCP]
|
|
71
|
+
model: Model
|
|
72
|
+
web: Web
|
|
73
|
+
logging: dict[str, Any]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Constants for the AI Utilities config package."""
|
|
2
|
+
|
|
3
|
+
__all__ = [
|
|
4
|
+
"CONFIG_FILE_NAME",
|
|
5
|
+
"DEFAULT_MAX_CONCURRENT_PROMPTS",
|
|
6
|
+
"DEFAULT_MODEL_TIMEOUT_SECONDS",
|
|
7
|
+
"DEFAULT_OAUTH_CALLBACK_PORT",
|
|
8
|
+
"DEFAULT_WEBAPP_PORT",
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
CONFIG_FILE_NAME = "config.yaml"
|
|
12
|
+
|
|
13
|
+
DEFAULT_MAX_CONCURRENT_PROMPTS: int = 10
|
|
14
|
+
DEFAULT_MODEL_TIMEOUT_SECONDS: int = 30
|
|
15
|
+
DEFAULT_OAUTH_CALLBACK_PORT: int = 10000
|
|
16
|
+
DEFAULT_WEBAPP_PORT: int = 8080
|