llmling-models 0.0.2__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.
- llmling_models-0.0.2/.copier-answers.yml +21 -0
- llmling_models-0.0.2/.github/FUNDING.yml +3 -0
- llmling_models-0.0.2/.github/copilot-instructions.md +19 -0
- llmling_models-0.0.2/.github/dependabot.yml +9 -0
- llmling_models-0.0.2/.github/workflows/build.yml +88 -0
- llmling_models-0.0.2/.github/workflows/documentation.yml +57 -0
- llmling_models-0.0.2/.gitignore +32 -0
- llmling_models-0.0.2/.pre-commit-config.yaml +48 -0
- llmling_models-0.0.2/LICENSE +22 -0
- llmling_models-0.0.2/PKG-INFO +82 -0
- llmling_models-0.0.2/README.md +32 -0
- llmling_models-0.0.2/docs/.empty +0 -0
- llmling_models-0.0.2/duties.py +60 -0
- llmling_models-0.0.2/mkdocs.yml +92 -0
- llmling_models-0.0.2/overrides/_dummy.txt +1 -0
- llmling_models-0.0.2/pyproject.toml +246 -0
- llmling_models-0.0.2/src/llmling_models/__init__.py +11 -0
- llmling_models-0.0.2/src/llmling_models/base.py +16 -0
- llmling_models-0.0.2/src/llmling_models/log.py +17 -0
- llmling_models-0.0.2/src/llmling_models/multi.py +227 -0
- llmling_models-0.0.2/src/llmling_models/py.typed +0 -0
- llmling_models-0.0.2/src/llmling_models/types.py +68 -0
- llmling_models-0.0.2/tests/__init__.py +3 -0
- llmling_models-0.0.2/tests/conftest.py +1 -0
- llmling_models-0.0.2/tests/test_randommodel.py +109 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Changes here will be overwritten by Copier
|
|
2
|
+
_commit: v1.8.1
|
|
3
|
+
_src_path: .\copier-phil65\
|
|
4
|
+
author_email: philipptemminghoff@googlemail.com
|
|
5
|
+
author_fullname: Philipp Temminghoff
|
|
6
|
+
author_username: phil65
|
|
7
|
+
copyright_date: '2024'
|
|
8
|
+
copyright_holder: Philipp Temminghoff
|
|
9
|
+
copyright_holder_email: philipptemminghoff@googlemail.com
|
|
10
|
+
libraries_use_pydantic: true
|
|
11
|
+
libraries_use_qt: false
|
|
12
|
+
project_description: Pydantic-AI models for LLMling-agent
|
|
13
|
+
project_name: LLMling-models
|
|
14
|
+
python_minimum_version: '3.12'
|
|
15
|
+
python_package_command_line_name: llmling-models
|
|
16
|
+
python_package_distribution_name: llmling-models
|
|
17
|
+
python_package_import_name: llmling_models
|
|
18
|
+
repository_name: llmling-models
|
|
19
|
+
repository_namespace: phil65
|
|
20
|
+
repository_provider: github.com
|
|
21
|
+
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
## Code style
|
|
2
|
+
|
|
3
|
+
You can assume Python 3.12.
|
|
4
|
+
Use newest language features if possible
|
|
5
|
+
Consider using pattern matching, walrus operators and other new syntax features.
|
|
6
|
+
Adhere to pep8.
|
|
7
|
+
|
|
8
|
+
## Tests
|
|
9
|
+
|
|
10
|
+
Tests are written using PyTest. Dont put tests into a class.
|
|
11
|
+
|
|
12
|
+
## DocStrings
|
|
13
|
+
|
|
14
|
+
DocStrings are written in google-style.
|
|
15
|
+
Dont add the types to the "Args" section.
|
|
16
|
+
Only add a Return section if a value is returned and the meaning of the returned value
|
|
17
|
+
is not obvious.
|
|
18
|
+
You can use markdown admonitions with python-markdown syntax if neccessary (!!! info, !!! note, !!! warning).
|
|
19
|
+
You can use markdown tables and markdown lists if neccessary.
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
name: Build
|
|
2
|
+
|
|
3
|
+
on: [push, pull_request]
|
|
4
|
+
|
|
5
|
+
jobs:
|
|
6
|
+
test:
|
|
7
|
+
runs-on: ${{ matrix.os }}
|
|
8
|
+
continue-on-error: ${{ matrix.python_version == '3.14' }}
|
|
9
|
+
strategy:
|
|
10
|
+
matrix:
|
|
11
|
+
python_version: ["3.12", "3.13", "3.14"]
|
|
12
|
+
os: ["ubuntu-latest", "macos-latest", "windows-latest"]
|
|
13
|
+
steps:
|
|
14
|
+
- name: Check out repository
|
|
15
|
+
uses: actions/checkout@v4
|
|
16
|
+
|
|
17
|
+
- name: Set up Python ${{ matrix.python_version }}
|
|
18
|
+
uses: actions/setup-python@v5
|
|
19
|
+
with:
|
|
20
|
+
python-version: ${{ matrix.python_version }}
|
|
21
|
+
allow-prereleases: true
|
|
22
|
+
|
|
23
|
+
- name: Install uv
|
|
24
|
+
uses: astral-sh/setup-uv@v4
|
|
25
|
+
with:
|
|
26
|
+
enable-cache: true
|
|
27
|
+
cache-dependency-glob: pyproject.toml
|
|
28
|
+
cache-suffix: py${{ matrix.python_version }}
|
|
29
|
+
|
|
30
|
+
- name: Install dependencies (uv sync)
|
|
31
|
+
run: uv sync --all-extras --no-group docs
|
|
32
|
+
|
|
33
|
+
- name: Check for code issues (ruff check)
|
|
34
|
+
uses: astral-sh/ruff-action@v1
|
|
35
|
+
|
|
36
|
+
- name: Check code format (ruff format)
|
|
37
|
+
uses: astral-sh/ruff-action@v1
|
|
38
|
+
with:
|
|
39
|
+
args: "format --check"
|
|
40
|
+
|
|
41
|
+
- name: Static type checking (MyPy)
|
|
42
|
+
run: uv run --no-group docs mypy src/llmling_models/
|
|
43
|
+
|
|
44
|
+
- name: Run tests
|
|
45
|
+
run: uv run --no-group docs pytest --cov-report=xml --cov=src/llmling_models/ --cov-report=term-missing
|
|
46
|
+
|
|
47
|
+
- name: Upload test results to Codecov
|
|
48
|
+
uses: codecov/codecov-action@v5
|
|
49
|
+
with:
|
|
50
|
+
fail_ci_if_error: false
|
|
51
|
+
verbose: true
|
|
52
|
+
|
|
53
|
+
release:
|
|
54
|
+
runs-on: ubuntu-latest
|
|
55
|
+
needs: test
|
|
56
|
+
if: startsWith(github.ref, 'refs/tags/')
|
|
57
|
+
permissions:
|
|
58
|
+
# this permission is mandatory for trusted publishing
|
|
59
|
+
id-token: write
|
|
60
|
+
contents: write
|
|
61
|
+
steps:
|
|
62
|
+
- name: Check out repository
|
|
63
|
+
uses: actions/checkout@v4
|
|
64
|
+
|
|
65
|
+
- name: Set up Python
|
|
66
|
+
uses: actions/setup-python@v5
|
|
67
|
+
with:
|
|
68
|
+
python-version: "3.12"
|
|
69
|
+
|
|
70
|
+
- name: Install uv
|
|
71
|
+
uses: astral-sh/setup-uv@v4
|
|
72
|
+
with:
|
|
73
|
+
enable-cache: true
|
|
74
|
+
cache-dependency-glob: pyproject.toml
|
|
75
|
+
cache-suffix: py${{ matrix.python_version }}
|
|
76
|
+
|
|
77
|
+
- name: Build package
|
|
78
|
+
run: uv build
|
|
79
|
+
|
|
80
|
+
- name: Publish package distributions to PyPI
|
|
81
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
82
|
+
|
|
83
|
+
- name: Release package on GitHub
|
|
84
|
+
uses: ncipollo/release-action@v1
|
|
85
|
+
with:
|
|
86
|
+
body: ${{ github.event.head_commit.message }}
|
|
87
|
+
artifacts: dist/*.whl,dist/*.tar.gz
|
|
88
|
+
token: ${{ secrets.GITHUB_TOKEN }}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
name: Build documentation
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
|
|
8
|
+
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
pages: write
|
|
12
|
+
id-token: write
|
|
13
|
+
|
|
14
|
+
# Allow one concurrent deployment
|
|
15
|
+
concurrency:
|
|
16
|
+
group: "pages"
|
|
17
|
+
cancel-in-progress: true
|
|
18
|
+
|
|
19
|
+
# Default to bash
|
|
20
|
+
defaults:
|
|
21
|
+
run:
|
|
22
|
+
shell: bash
|
|
23
|
+
|
|
24
|
+
jobs:
|
|
25
|
+
build:
|
|
26
|
+
runs-on: ubuntu-latest
|
|
27
|
+
|
|
28
|
+
steps:
|
|
29
|
+
- uses: actions/checkout@v4
|
|
30
|
+
with:
|
|
31
|
+
fetch-depth: 0
|
|
32
|
+
- name: Set up Python
|
|
33
|
+
uses: actions/setup-python@v5
|
|
34
|
+
with:
|
|
35
|
+
python-version: "3.12"
|
|
36
|
+
- name: Install uv
|
|
37
|
+
uses: astral-sh/setup-uv@v4
|
|
38
|
+
- name: Install dependencies
|
|
39
|
+
run: |
|
|
40
|
+
uv sync --all-extras
|
|
41
|
+
- name: Build
|
|
42
|
+
run: uv run mknodes build
|
|
43
|
+
- name: Upload artifact
|
|
44
|
+
uses: actions/upload-pages-artifact@v3
|
|
45
|
+
with:
|
|
46
|
+
path: ./site
|
|
47
|
+
|
|
48
|
+
deploy:
|
|
49
|
+
environment:
|
|
50
|
+
name: github-pages
|
|
51
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
52
|
+
runs-on: ubuntu-latest
|
|
53
|
+
needs: build
|
|
54
|
+
steps:
|
|
55
|
+
- name: Deploy to GitHub Pages
|
|
56
|
+
id: deployment
|
|
57
|
+
uses: actions/deploy-pages@v4
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
# Distribution / packaging
|
|
5
|
+
*.sublime-workspace
|
|
6
|
+
# Unit test / coverage reports
|
|
7
|
+
.coverage
|
|
8
|
+
coverage.xml
|
|
9
|
+
.cache
|
|
10
|
+
.hatch
|
|
11
|
+
.pytest_cache/
|
|
12
|
+
junit.xml
|
|
13
|
+
# Jupyter Notebook
|
|
14
|
+
.ipynb_checkpoints
|
|
15
|
+
# pyenv
|
|
16
|
+
.python-version
|
|
17
|
+
# dotenv
|
|
18
|
+
.env
|
|
19
|
+
# virtualenv
|
|
20
|
+
.venv
|
|
21
|
+
# mkdocs documentation
|
|
22
|
+
/site
|
|
23
|
+
# mypy
|
|
24
|
+
.mypy_cache/
|
|
25
|
+
# .vscode
|
|
26
|
+
.vscode/
|
|
27
|
+
# OS files
|
|
28
|
+
.DS_Store
|
|
29
|
+
# uv
|
|
30
|
+
uv.lock
|
|
31
|
+
# DiskCache cache file
|
|
32
|
+
./model_cache
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
default_language_version:
|
|
2
|
+
python: python3.12
|
|
3
|
+
default_stages: [pre-commit]
|
|
4
|
+
repos:
|
|
5
|
+
- repo: local
|
|
6
|
+
hooks:
|
|
7
|
+
- id: pytest-check
|
|
8
|
+
name: pytest-check
|
|
9
|
+
entry: uv run pytest
|
|
10
|
+
language: system
|
|
11
|
+
# stages: [push]
|
|
12
|
+
types: [python]
|
|
13
|
+
pass_filenames: false
|
|
14
|
+
always_run: true
|
|
15
|
+
|
|
16
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
17
|
+
# https://pre-commit.com/hooks.html
|
|
18
|
+
rev: v5.0.0
|
|
19
|
+
hooks:
|
|
20
|
+
- id: check-added-large-files
|
|
21
|
+
- id: check-case-conflict
|
|
22
|
+
- id: check-merge-conflict
|
|
23
|
+
- id: check-toml
|
|
24
|
+
- id: check-json
|
|
25
|
+
- id: check-xml
|
|
26
|
+
- id: check-yaml
|
|
27
|
+
args: [--allow-multiple-documents, --unsafe]
|
|
28
|
+
- id: debug-statements
|
|
29
|
+
- id: detect-private-key
|
|
30
|
+
|
|
31
|
+
- repo: https://github.com/pre-commit/mirrors-mypy
|
|
32
|
+
rev: v1.13.0
|
|
33
|
+
hooks:
|
|
34
|
+
- id: mypy
|
|
35
|
+
additional_dependencies: [orjson, pydantic]
|
|
36
|
+
|
|
37
|
+
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
|
38
|
+
rev: v0.8.3
|
|
39
|
+
hooks:
|
|
40
|
+
- id: ruff
|
|
41
|
+
- id: ruff-format
|
|
42
|
+
|
|
43
|
+
- repo: https://github.com/commitizen-tools/commitizen
|
|
44
|
+
rev: v4.1.0
|
|
45
|
+
hooks:
|
|
46
|
+
- id: commitizen
|
|
47
|
+
stages: [commit-msg]
|
|
48
|
+
additional_dependencies: [typing-extensions]
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024, Philipp Temminghoff
|
|
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.
|
|
22
|
+
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: llmling-models
|
|
3
|
+
Version: 0.0.2
|
|
4
|
+
Summary: Pydantic-AI models for LLMling-agent
|
|
5
|
+
Project-URL: Documentation, https://phil65.github.io/llmling-models/
|
|
6
|
+
Project-URL: Source, https://github.com/phil65/llmling-models
|
|
7
|
+
Project-URL: Issues, https://github.com/phil65/llmling-models/issues
|
|
8
|
+
Project-URL: Discussions, https://github.com/phil65/llmling-models/discussions
|
|
9
|
+
Project-URL: Code coverage, https://app.codecov.io/gh/phil65/llmling-models
|
|
10
|
+
Author-email: Philipp Temminghoff <philipptemminghoff@googlemail.com>
|
|
11
|
+
License: MIT License
|
|
12
|
+
|
|
13
|
+
Copyright (c) 2024, Philipp Temminghoff
|
|
14
|
+
|
|
15
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
16
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
17
|
+
in the Software without restriction, including without limitation the rights
|
|
18
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
19
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
20
|
+
furnished to do so, subject to the following conditions:
|
|
21
|
+
|
|
22
|
+
The above copyright notice and this permission notice shall be included in all
|
|
23
|
+
copies or substantial portions of the Software.
|
|
24
|
+
|
|
25
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
26
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
27
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
28
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
29
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
30
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
31
|
+
SOFTWARE.
|
|
32
|
+
|
|
33
|
+
License-File: LICENSE
|
|
34
|
+
Classifier: Development Status :: 4 - Beta
|
|
35
|
+
Classifier: Intended Audience :: Developers
|
|
36
|
+
Classifier: Operating System :: OS Independent
|
|
37
|
+
Classifier: Programming Language :: Python :: 3
|
|
38
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
41
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
42
|
+
Classifier: Topic :: Documentation
|
|
43
|
+
Classifier: Topic :: Software Development
|
|
44
|
+
Classifier: Topic :: Utilities
|
|
45
|
+
Classifier: Typing :: Typed
|
|
46
|
+
Requires-Python: >=3.12
|
|
47
|
+
Requires-Dist: pydantic
|
|
48
|
+
Requires-Dist: pydantic-ai>=0.0.12
|
|
49
|
+
Description-Content-Type: text/markdown
|
|
50
|
+
|
|
51
|
+
# LLMling-models
|
|
52
|
+
|
|
53
|
+
[](https://pypi.org/project/llmling-models/)
|
|
54
|
+
[](https://pypi.org/project/llmling-models/)
|
|
55
|
+
[](https://pypi.org/project/llmling-models/)
|
|
56
|
+
[](https://pypi.org/project/llmling-models/)
|
|
57
|
+
[](https://pypi.org/project/llmling-models/)
|
|
58
|
+
[](https://pypi.org/project/llmling-models/)
|
|
59
|
+
[](https://pypi.org/project/llmling-models/)
|
|
60
|
+
[](https://pypi.org/project/llmling-models/)
|
|
61
|
+
[](https://pypi.org/project/llmling-models/)
|
|
62
|
+
[](https://github.com/phil65/llmling-models/releases)
|
|
63
|
+
[](https://github.com/phil65/llmling-models/graphs/contributors)
|
|
64
|
+
[](https://github.com/phil65/llmling-models/discussions)
|
|
65
|
+
[](https://github.com/phil65/llmling-models/forks)
|
|
66
|
+
[](https://github.com/phil65/llmling-models/issues)
|
|
67
|
+
[](https://github.com/phil65/llmling-models/pulls)
|
|
68
|
+
[](https://github.com/phil65/llmling-models/watchers)
|
|
69
|
+
[](https://github.com/phil65/llmling-models/stars)
|
|
70
|
+
[](https://github.com/phil65/llmling-models)
|
|
71
|
+
[](https://github.com/phil65/llmling-models/commits)
|
|
72
|
+
[](https://github.com/phil65/llmling-models/releases)
|
|
73
|
+
[](https://github.com/phil65/llmling-models)
|
|
74
|
+
[](https://github.com/phil65/llmling-models)
|
|
75
|
+
[](https://github.com/phil65/llmling-models)
|
|
76
|
+
[](https://github.com/phil65/llmling-models)
|
|
77
|
+
[](https://codecov.io/gh/phil65/llmling-models/)
|
|
78
|
+
[](https://github.com/psf/black)
|
|
79
|
+
[](https://pyup.io/repos/github/phil65/llmling-models/)
|
|
80
|
+
|
|
81
|
+
[Read the documentation!](https://phil65.github.io/llmling-models/)
|
|
82
|
+
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# LLMling-models
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/llmling-models/)
|
|
4
|
+
[](https://pypi.org/project/llmling-models/)
|
|
5
|
+
[](https://pypi.org/project/llmling-models/)
|
|
6
|
+
[](https://pypi.org/project/llmling-models/)
|
|
7
|
+
[](https://pypi.org/project/llmling-models/)
|
|
8
|
+
[](https://pypi.org/project/llmling-models/)
|
|
9
|
+
[](https://pypi.org/project/llmling-models/)
|
|
10
|
+
[](https://pypi.org/project/llmling-models/)
|
|
11
|
+
[](https://pypi.org/project/llmling-models/)
|
|
12
|
+
[](https://github.com/phil65/llmling-models/releases)
|
|
13
|
+
[](https://github.com/phil65/llmling-models/graphs/contributors)
|
|
14
|
+
[](https://github.com/phil65/llmling-models/discussions)
|
|
15
|
+
[](https://github.com/phil65/llmling-models/forks)
|
|
16
|
+
[](https://github.com/phil65/llmling-models/issues)
|
|
17
|
+
[](https://github.com/phil65/llmling-models/pulls)
|
|
18
|
+
[](https://github.com/phil65/llmling-models/watchers)
|
|
19
|
+
[](https://github.com/phil65/llmling-models/stars)
|
|
20
|
+
[](https://github.com/phil65/llmling-models)
|
|
21
|
+
[](https://github.com/phil65/llmling-models/commits)
|
|
22
|
+
[](https://github.com/phil65/llmling-models/releases)
|
|
23
|
+
[](https://github.com/phil65/llmling-models)
|
|
24
|
+
[](https://github.com/phil65/llmling-models)
|
|
25
|
+
[](https://github.com/phil65/llmling-models)
|
|
26
|
+
[](https://github.com/phil65/llmling-models)
|
|
27
|
+
[](https://codecov.io/gh/phil65/llmling-models/)
|
|
28
|
+
[](https://github.com/psf/black)
|
|
29
|
+
[](https://pyup.io/repos/github/phil65/llmling-models/)
|
|
30
|
+
|
|
31
|
+
[Read the documentation!](https://phil65.github.io/llmling-models/)
|
|
32
|
+
|
|
File without changes
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from duty import duty
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@duty(capture=False)
|
|
7
|
+
def build(ctx, *args: str):
|
|
8
|
+
"""Build a MkNodes page."""
|
|
9
|
+
args_str = " " + " ".join(args) if args else ""
|
|
10
|
+
ctx.run(f"uv run mknodes build{args_str}")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@duty(capture=False)
|
|
14
|
+
def serve(ctx, *args: str):
|
|
15
|
+
"""Serve a MkNodes page."""
|
|
16
|
+
args_str = " " + " ".join(args) if args else ""
|
|
17
|
+
ctx.run(f"uv run mknodes serve{args_str}")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@duty(capture=False)
|
|
21
|
+
def test(ctx, *args: str):
|
|
22
|
+
"""Serve a MkNodes page."""
|
|
23
|
+
args_str = " " + " ".join(args) if args else ""
|
|
24
|
+
ctx.run(f"uv run pytest{args_str}")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@duty(capture=False)
|
|
28
|
+
def clean(ctx):
|
|
29
|
+
"""Clean all files from the Git directory except checked-in files."""
|
|
30
|
+
ctx.run("git clean -dfX")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@duty(capture=False)
|
|
34
|
+
def update(ctx):
|
|
35
|
+
"""Update all environment packages using pip directly."""
|
|
36
|
+
ctx.run("uv lock --upgrade")
|
|
37
|
+
ctx.run("uv sync --all-extras")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@duty(capture=False)
|
|
41
|
+
def lint(ctx):
|
|
42
|
+
"""Lint the code and fix issues if possible."""
|
|
43
|
+
ctx.run("uv run ruff check --fix --unsafe-fixes .")
|
|
44
|
+
ctx.run("uv run ruff format .")
|
|
45
|
+
ctx.run("uv run mypy src/llmling_models/")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@duty(capture=False)
|
|
49
|
+
def lint_check(ctx):
|
|
50
|
+
"""Lint the code."""
|
|
51
|
+
ctx.run("uv run ruff check .")
|
|
52
|
+
ctx.run("uv run ruff format --check .")
|
|
53
|
+
ctx.run("uv run mypy src/llmling_models/")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@duty(capture=False)
|
|
57
|
+
def version(ctx, *args: str):
|
|
58
|
+
"""Bump package version."""
|
|
59
|
+
args_str = " " + " ".join(args) if args else ""
|
|
60
|
+
ctx.run(f"hatch version{args_str}")
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
site_name: "LLMling-models"
|
|
2
|
+
site_description: "Pydantic-AI models for LLMling-agent"
|
|
3
|
+
repo_url: "https://github.com/phil65/llmling-models/"
|
|
4
|
+
site_url: https://phil65.github.io/llmling-models/
|
|
5
|
+
site_author: "Philipp Temminghoff"
|
|
6
|
+
copyright: Copyright © 2024 Philipp Temminghoff
|
|
7
|
+
|
|
8
|
+
theme:
|
|
9
|
+
name: material
|
|
10
|
+
custom_dir: overrides
|
|
11
|
+
icon:
|
|
12
|
+
logo: material/graph-outline
|
|
13
|
+
palette:
|
|
14
|
+
# Palette toggle for automatic mode
|
|
15
|
+
- media: "(prefers-color-scheme)"
|
|
16
|
+
toggle:
|
|
17
|
+
icon: material/brightness-auto
|
|
18
|
+
name: Switch to light mode
|
|
19
|
+
|
|
20
|
+
# Palette toggle for light mode
|
|
21
|
+
- media: "(prefers-color-scheme: light)"
|
|
22
|
+
scheme: default
|
|
23
|
+
primary: red
|
|
24
|
+
toggle:
|
|
25
|
+
icon: material/brightness-7
|
|
26
|
+
name: Switch to dark mode
|
|
27
|
+
|
|
28
|
+
# Palette toggle for dark mode
|
|
29
|
+
- media: "(prefers-color-scheme: dark)"
|
|
30
|
+
scheme: slate
|
|
31
|
+
primary: red
|
|
32
|
+
toggle:
|
|
33
|
+
icon: material/brightness-4
|
|
34
|
+
name: Switch to system preference
|
|
35
|
+
features:
|
|
36
|
+
- announce.dismiss
|
|
37
|
+
- content.action.edit
|
|
38
|
+
- content.code.copy
|
|
39
|
+
- content.code.select
|
|
40
|
+
- content.code.annotate
|
|
41
|
+
- content.tooltips
|
|
42
|
+
# - content.tabs.link
|
|
43
|
+
- navigation.tracking # update URL based on current item in TOC
|
|
44
|
+
- navigation.path # shows breadcrumbs
|
|
45
|
+
- navigation.tabs # make top level tabs
|
|
46
|
+
- navigation.indexes # documents can be directly attached to sections (overview pages)
|
|
47
|
+
- navigation.footer # next/previous page buttons in footer
|
|
48
|
+
- navigation.top # adds back-to-top button
|
|
49
|
+
# - navigation.sections # top-level sections are rendered as groups
|
|
50
|
+
# - navigation.expand # expand all subsections in left sidebar by default
|
|
51
|
+
- toc.follow # makes toc follow scrolling
|
|
52
|
+
# - toc.integrate # integrates toc into left menu
|
|
53
|
+
- search.highlight
|
|
54
|
+
- search.suggest
|
|
55
|
+
# - search.share
|
|
56
|
+
|
|
57
|
+
plugins:
|
|
58
|
+
- search
|
|
59
|
+
- autorefs
|
|
60
|
+
- mknodes
|
|
61
|
+
- mkdocstrings:
|
|
62
|
+
default_handler: python
|
|
63
|
+
handlers:
|
|
64
|
+
python:
|
|
65
|
+
import:
|
|
66
|
+
- url: https://docs.python.org/3/objects.inv
|
|
67
|
+
domains: [std, py]
|
|
68
|
+
options:
|
|
69
|
+
# https://mkdocstrings.github.io/python/usage/
|
|
70
|
+
enable_inventory: !ENV [CI, false]
|
|
71
|
+
show_signature_annotations: true
|
|
72
|
+
show_symbol_type_toc: true
|
|
73
|
+
show_symbol_type_heading: true
|
|
74
|
+
show_root_toc_entry: false
|
|
75
|
+
# merge_init_into_class: true
|
|
76
|
+
ignore_init_summary: true
|
|
77
|
+
inherited_members: false
|
|
78
|
+
signature_crossrefs: true
|
|
79
|
+
separate_signature: true
|
|
80
|
+
line_length: 90
|
|
81
|
+
markdown_extensions:
|
|
82
|
+
- attr_list
|
|
83
|
+
- pymdownx.emoji
|
|
84
|
+
- toc:
|
|
85
|
+
permalink: true
|
|
86
|
+
|
|
87
|
+
extra:
|
|
88
|
+
social:
|
|
89
|
+
- icon: fontawesome/brands/github
|
|
90
|
+
link: https://github.com/phil65
|
|
91
|
+
- icon: fontawesome/brands/python
|
|
92
|
+
link: https://pypi.org/project/llmling-models/
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
File exists to make sure that folder exists for git
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
[tool.hatch.version]
|
|
2
|
+
source = "regex_commit"
|
|
3
|
+
commit_extra_args = ["-e"]
|
|
4
|
+
path = "src/llmling_models/__init__.py"
|
|
5
|
+
|
|
6
|
+
[build-system]
|
|
7
|
+
requires = ["hatchling", "hatch-regex-commit"]
|
|
8
|
+
build-backend = "hatchling.build"
|
|
9
|
+
|
|
10
|
+
[project]
|
|
11
|
+
name = "llmling-models"
|
|
12
|
+
description = "Pydantic-AI models for LLMling-agent"
|
|
13
|
+
authors = [
|
|
14
|
+
{ name = "Philipp Temminghoff", email = "philipptemminghoff@googlemail.com" },
|
|
15
|
+
]
|
|
16
|
+
readme = "README.md"
|
|
17
|
+
dynamic = ["version"]
|
|
18
|
+
classifiers = [
|
|
19
|
+
"Development Status :: 4 - Beta",
|
|
20
|
+
"Intended Audience :: Developers",
|
|
21
|
+
"Operating System :: OS Independent",
|
|
22
|
+
"Programming Language :: Python :: 3",
|
|
23
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
24
|
+
"Programming Language :: Python :: 3.12",
|
|
25
|
+
"Programming Language :: Python :: 3.13",
|
|
26
|
+
"Programming Language :: Python :: 3.14",
|
|
27
|
+
"Topic :: Documentation",
|
|
28
|
+
"Topic :: Software Development",
|
|
29
|
+
"Topic :: Utilities",
|
|
30
|
+
"Typing :: Typed",
|
|
31
|
+
|
|
32
|
+
]
|
|
33
|
+
keywords = []
|
|
34
|
+
requires-python = ">=3.12"
|
|
35
|
+
license = { file = "LICENSE" }
|
|
36
|
+
dependencies = [
|
|
37
|
+
"pydantic",
|
|
38
|
+
# Only add below (Copier)
|
|
39
|
+
"pydantic-ai>=0.0.12",
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
[tool.uv]
|
|
43
|
+
default-groups = ["dev", "lint", "docs"]
|
|
44
|
+
|
|
45
|
+
[dependency-groups]
|
|
46
|
+
dev = [
|
|
47
|
+
"pytest",
|
|
48
|
+
"pytest-cov",
|
|
49
|
+
"pyreadline3",
|
|
50
|
+
"devtools",
|
|
51
|
+
# Only add below (Copier)
|
|
52
|
+
"pytest-asyncio>=0.25.0",
|
|
53
|
+
]
|
|
54
|
+
benchmark = ["pyinstrument"]
|
|
55
|
+
lint = [
|
|
56
|
+
"ruff",
|
|
57
|
+
"mypy[faster-cache]; python_version < '3.14'",
|
|
58
|
+
"mypy; python_version >= '3.14'",
|
|
59
|
+
# Only add below (Copier)
|
|
60
|
+
]
|
|
61
|
+
docs = [
|
|
62
|
+
"mkdocs-mknodes",
|
|
63
|
+
"mkdocs-material",
|
|
64
|
+
# Only add below (Copier)
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
[project.urls]
|
|
68
|
+
Documentation = "https://phil65.github.io/llmling-models/"
|
|
69
|
+
Source = "https://github.com/phil65/llmling-models"
|
|
70
|
+
Issues = "https://github.com/phil65/llmling-models/issues"
|
|
71
|
+
Discussions = "https://github.com/phil65/llmling-models/discussions"
|
|
72
|
+
"Code coverage" = "https://app.codecov.io/gh/phil65/llmling-models"
|
|
73
|
+
|
|
74
|
+
[tool.pytest.ini_options]
|
|
75
|
+
testpaths = "tests/"
|
|
76
|
+
log_cli = true
|
|
77
|
+
asyncio_default_fixture_loop_scope = "function"
|
|
78
|
+
log_format = "%(asctime)s %(levelname)s %(message)s"
|
|
79
|
+
log_date_format = "%Y-%m-%d %H:%M:%S"
|
|
80
|
+
asyncio_mode = "auto"
|
|
81
|
+
|
|
82
|
+
[tool.coverage.report]
|
|
83
|
+
exclude_lines = [
|
|
84
|
+
"pragma: no cover",
|
|
85
|
+
"if TYPE_CHECKING:",
|
|
86
|
+
"@overload",
|
|
87
|
+
"except ImportError",
|
|
88
|
+
'if __name__ == "__main__":',
|
|
89
|
+
]
|
|
90
|
+
|
|
91
|
+
[tool.mypy]
|
|
92
|
+
python_version = "3.12"
|
|
93
|
+
disable_error_code = ["misc", "import"]
|
|
94
|
+
pretty = true
|
|
95
|
+
check_untyped_defs = true
|
|
96
|
+
exclude = ['venv/', '.venv/', 'tests/']
|
|
97
|
+
plugins = ["pydantic.mypy"]
|
|
98
|
+
|
|
99
|
+
[tool.ruff]
|
|
100
|
+
line-length = 90
|
|
101
|
+
extend-exclude = ['docs']
|
|
102
|
+
target-version = "py312"
|
|
103
|
+
|
|
104
|
+
[tool.ruff.lint]
|
|
105
|
+
select = [
|
|
106
|
+
"A", # Flake8-builtins
|
|
107
|
+
# "ANN", # Flake8-Annotations
|
|
108
|
+
# "ASYNC", # Flake8-Async
|
|
109
|
+
# "ARG", # # Flake8-Unused-Arguments
|
|
110
|
+
"B", # flake8-bugbear
|
|
111
|
+
"BLE", # Flake8-blind-except
|
|
112
|
+
"C",
|
|
113
|
+
"C4", # flake8-comprehensions
|
|
114
|
+
# "C90", # MCCabe
|
|
115
|
+
"COM", # Flake8-commas
|
|
116
|
+
# "CPY", # Copyright-related rules
|
|
117
|
+
"D", # PyDocStyle
|
|
118
|
+
# "DTZ", # Flake8- Datetimez
|
|
119
|
+
"E", # PyCodeStyle Error
|
|
120
|
+
"EM", # flake8-errmsg
|
|
121
|
+
# "ERA", # Eradicate
|
|
122
|
+
"EXE", # flake8-executable
|
|
123
|
+
"F", # PyFlakes
|
|
124
|
+
"FA", # flake8-future-annotations
|
|
125
|
+
# "FBT", # flake8-boolean-trap
|
|
126
|
+
# "FIX", # flake8-fixme
|
|
127
|
+
"FLY", # flynt
|
|
128
|
+
"G", # flake8-logging-format
|
|
129
|
+
"I", # ISort
|
|
130
|
+
"ICN", # Flake8-import-conventions
|
|
131
|
+
"INP", # flake8-no-pep420
|
|
132
|
+
"INT", # flake8-gettext
|
|
133
|
+
"ISC", # flake8-implicit-str-concat
|
|
134
|
+
"N", # pep8-naming
|
|
135
|
+
# "NPY", # numpy-specific rules
|
|
136
|
+
# "PD", # pandas-vet
|
|
137
|
+
"PERF", # perflint
|
|
138
|
+
# "PGH", # pygrep-hooks
|
|
139
|
+
"PIE", # flake8-pie
|
|
140
|
+
"PLE", # PyLint Error
|
|
141
|
+
"PLC", # PyLint convention
|
|
142
|
+
# "PLW", # PyLint Warning
|
|
143
|
+
"PLR", # PyLint refactor
|
|
144
|
+
"PT", # flake8-pytest-style
|
|
145
|
+
"PTH", # flake8-use-pathlib
|
|
146
|
+
"PYI", # flake8-pyi
|
|
147
|
+
"Q", # flake8-quotes
|
|
148
|
+
"RET", # flake8-return
|
|
149
|
+
"RSE", # flake8-raise
|
|
150
|
+
"RUF", # ruff-specific rules
|
|
151
|
+
# "S", # flake8-bandit
|
|
152
|
+
"SIM", # flake8-simplify
|
|
153
|
+
"SLF", # flake8-self
|
|
154
|
+
"SLOT", # flake8-slots
|
|
155
|
+
# "T",
|
|
156
|
+
# "TD", # flake8-todos
|
|
157
|
+
"T10", # flake8-debugger
|
|
158
|
+
# "T20", # flake8-print
|
|
159
|
+
"TC", # flake8-type-checking
|
|
160
|
+
"TID", # flake8-tidy-imports
|
|
161
|
+
"TRY", # tryceratops
|
|
162
|
+
"UP", # PyUpgrade
|
|
163
|
+
"W", # PyCodeStyle warning
|
|
164
|
+
"YTT", # flake8-2020
|
|
165
|
+
]
|
|
166
|
+
ignore = [
|
|
167
|
+
"C408", # Unnecessary {obj_type} call (rewrite as a literal)
|
|
168
|
+
"B905", # zip() without an explicit strict= parameter
|
|
169
|
+
"C901", # {name} is too complex ({complexity} > {max_complexity})
|
|
170
|
+
"COM812",
|
|
171
|
+
# "CPY001", # Missing copyright notice at top of file
|
|
172
|
+
"D100", # Missing docstring in public module
|
|
173
|
+
"D101", # Missing docstring in public class
|
|
174
|
+
"D102", # Missing docstring in public method
|
|
175
|
+
"D103", # Missing docstring in public function
|
|
176
|
+
"D104", # Missing docstring in public package
|
|
177
|
+
"D105", # Missing docstring in magic method
|
|
178
|
+
"D106", # Missing docstring in public nested class
|
|
179
|
+
"D107", # Missing docstring in __init__
|
|
180
|
+
"D203", # 1 blank line required before class docstring
|
|
181
|
+
"D204", # 1 blank line required after class docstring
|
|
182
|
+
"D213", # Multi-line docstring summary should start at the second line
|
|
183
|
+
"D215", # Section underline is over-indented ("{name}")
|
|
184
|
+
"D400", # First line should end with a period
|
|
185
|
+
"D401", # First line of docstring should be in imperative mood: "{first_line}"
|
|
186
|
+
"D404", # First word of the docstring should not be "This"
|
|
187
|
+
"D406", # Section name should end with a newline ("{name}")
|
|
188
|
+
"D407", # Missing dashed underline after section ("{name}")
|
|
189
|
+
"D408", # Section underline should be in the line following the section's name ("{name}")
|
|
190
|
+
"D409", # Section underline should match the length of its name ("{name}")
|
|
191
|
+
"D413", # Missing blank line after last section ("{name}")
|
|
192
|
+
"ISC001",
|
|
193
|
+
"PLR0912", # Too many branches
|
|
194
|
+
"PLR0913", # Too many arguments to function call
|
|
195
|
+
"PLR0915", # Too many statements
|
|
196
|
+
# "PLR2004", # Magic values instead of named consts
|
|
197
|
+
"SLF001", # Private member accessed
|
|
198
|
+
"TRY003", # Avoid specifying long messages outside the exception class
|
|
199
|
+
]
|
|
200
|
+
|
|
201
|
+
[tool.ruff.lint.flake8-quotes]
|
|
202
|
+
docstring-quotes = "double"
|
|
203
|
+
|
|
204
|
+
[tool.ruff.format]
|
|
205
|
+
# Enable preview style formatting.
|
|
206
|
+
preview = true
|
|
207
|
+
|
|
208
|
+
[tool.ruff.lint.isort]
|
|
209
|
+
lines-after-imports = 2
|
|
210
|
+
# lines-between-types = 1
|
|
211
|
+
# atomic = true
|
|
212
|
+
force-sort-within-sections = true
|
|
213
|
+
combine-as-imports = true
|
|
214
|
+
|
|
215
|
+
[tool.ruff.lint.per-file-ignores]
|
|
216
|
+
"__init__.py" = ["E402", "I001"]
|
|
217
|
+
"scripts/*" = ["INP001"]
|
|
218
|
+
|
|
219
|
+
[tool.pyright]
|
|
220
|
+
pythonVersion = "3.12"
|
|
221
|
+
pythonPlatform = "All"
|
|
222
|
+
typeCheckingMode = "basic"
|
|
223
|
+
deprecateTypingAliases = true
|
|
224
|
+
reportMissingTypeStubs = false
|
|
225
|
+
reportUnusedCallResult = false
|
|
226
|
+
reportUnknownVariableType = false
|
|
227
|
+
reportAny = false
|
|
228
|
+
reportImplicitOverride = false
|
|
229
|
+
reportUnusedFunction = false
|
|
230
|
+
reportImplicitStringConcatenation = false
|
|
231
|
+
reportIgnoreCommentWithoutRule = false
|
|
232
|
+
reportUnannotatedClassAttribute = false
|
|
233
|
+
reportSelfClsParameterName = false
|
|
234
|
+
reportPrivateImportUsage = false
|
|
235
|
+
|
|
236
|
+
[tool.mknodes]
|
|
237
|
+
allowed-commit-types = [
|
|
238
|
+
"fix",
|
|
239
|
+
"feat",
|
|
240
|
+
"refactor",
|
|
241
|
+
"docs",
|
|
242
|
+
"test",
|
|
243
|
+
"build",
|
|
244
|
+
"chore",
|
|
245
|
+
]
|
|
246
|
+
docstring-style = "google"
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Base class for YAML-configurable pydantic-ai models."""
|
|
2
|
+
|
|
3
|
+
from abc import ABC
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, ConfigDict
|
|
6
|
+
from pydantic_ai.models import Model
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class PydanticModel(Model, BaseModel, ABC):
|
|
10
|
+
"""Base for models that can be configured via YAML."""
|
|
11
|
+
|
|
12
|
+
model_config = ConfigDict(
|
|
13
|
+
arbitrary_types_allowed=True,
|
|
14
|
+
extra="forbid",
|
|
15
|
+
use_attribute_docstrings=True,
|
|
16
|
+
)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Logging configuration for llmling_models."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def get_logger(name: str) -> logging.Logger:
|
|
9
|
+
"""Get a logger for the given name.
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
name: The name of the logger, will be prefixed with 'llmling_agent.'
|
|
13
|
+
|
|
14
|
+
Returns:
|
|
15
|
+
A logger instance
|
|
16
|
+
"""
|
|
17
|
+
return logging.getLogger(f"llmling_models.{name}")
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"""Multi-model implementations."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import random
|
|
6
|
+
from typing import TYPE_CHECKING, Any, Literal
|
|
7
|
+
|
|
8
|
+
from pydantic import Field, model_validator
|
|
9
|
+
from pydantic_ai.models import AgentModel, KnownModelName, Model, infer_model
|
|
10
|
+
|
|
11
|
+
from llmling_models.base import PydanticModel
|
|
12
|
+
from llmling_models.log import get_logger
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from pydantic_ai.messages import Message, ModelAnyResponse
|
|
17
|
+
from pydantic_ai.tools import ToolDefinition
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
logger = get_logger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class MultiModel(PydanticModel):
|
|
24
|
+
"""Base for model configurations that combine multiple language models.
|
|
25
|
+
|
|
26
|
+
This provides the base interface for YAML-configurable multi-model setups,
|
|
27
|
+
allowing configuration of multiple models through LLMling's config system.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
type: str
|
|
31
|
+
"""Discriminator field for multi-model types"""
|
|
32
|
+
|
|
33
|
+
models: list[KnownModelName | Model] = Field(default_factory=list)
|
|
34
|
+
""""List of models to use"."""
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def available_models(self) -> list[Model]:
|
|
38
|
+
"""Convert model names/instances to pydantic-ai Model instances."""
|
|
39
|
+
return [
|
|
40
|
+
model if isinstance(model, Model) else infer_model(model) # type: ignore[arg-type]
|
|
41
|
+
for model in self.models
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
@model_validator(mode="after")
|
|
45
|
+
def validate_models(self) -> MultiModel:
|
|
46
|
+
"""Validate model configuration."""
|
|
47
|
+
if not self.models:
|
|
48
|
+
msg = "At least one model must be provided"
|
|
49
|
+
raise ValueError(msg)
|
|
50
|
+
return self
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class RandomMultiModel(MultiModel):
|
|
54
|
+
"""Randomly selects from configured models.
|
|
55
|
+
|
|
56
|
+
Example YAML configuration:
|
|
57
|
+
```yaml
|
|
58
|
+
model:
|
|
59
|
+
type: random
|
|
60
|
+
models:
|
|
61
|
+
- openai:gpt-4
|
|
62
|
+
- openai:gpt-3.5-turbo
|
|
63
|
+
```
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
type: Literal["random"] = "random"
|
|
67
|
+
|
|
68
|
+
@model_validator(mode="after")
|
|
69
|
+
def validate_models(self) -> RandomMultiModel:
|
|
70
|
+
"""Validate model configuration."""
|
|
71
|
+
if not self.models:
|
|
72
|
+
msg = "At least one model must be provided"
|
|
73
|
+
raise ValueError(msg)
|
|
74
|
+
return self
|
|
75
|
+
|
|
76
|
+
def name(self) -> str:
|
|
77
|
+
"""Get descriptive model name."""
|
|
78
|
+
return f"multi-random({len(self.models)})"
|
|
79
|
+
|
|
80
|
+
async def agent_model(
|
|
81
|
+
self,
|
|
82
|
+
*,
|
|
83
|
+
function_tools: list[ToolDefinition],
|
|
84
|
+
allow_text_result: bool,
|
|
85
|
+
result_tools: list[ToolDefinition],
|
|
86
|
+
) -> AgentModel:
|
|
87
|
+
"""Create agent model that randomly selects from available models."""
|
|
88
|
+
return RandomAgentModel(
|
|
89
|
+
models=self.available_models,
|
|
90
|
+
function_tools=function_tools,
|
|
91
|
+
allow_text_result=allow_text_result,
|
|
92
|
+
result_tools=result_tools,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class RandomAgentModel(AgentModel):
|
|
97
|
+
"""AgentModel that randomly selects from available models."""
|
|
98
|
+
|
|
99
|
+
def __init__(
|
|
100
|
+
self,
|
|
101
|
+
models: list[Model],
|
|
102
|
+
function_tools: list[ToolDefinition],
|
|
103
|
+
allow_text_result: bool,
|
|
104
|
+
result_tools: list[ToolDefinition],
|
|
105
|
+
) -> None:
|
|
106
|
+
"""Initialize with list of models."""
|
|
107
|
+
if not models:
|
|
108
|
+
msg = "At least one model must be provided"
|
|
109
|
+
raise ValueError(msg)
|
|
110
|
+
self.models = models
|
|
111
|
+
self.function_tools = function_tools
|
|
112
|
+
self.allow_text_result = allow_text_result
|
|
113
|
+
self.result_tools = result_tools
|
|
114
|
+
self._initialized_models: list[AgentModel] | None = None
|
|
115
|
+
|
|
116
|
+
async def _initialize_models(self) -> list[AgentModel]:
|
|
117
|
+
"""Initialize all agent models."""
|
|
118
|
+
if self._initialized_models is None:
|
|
119
|
+
self._initialized_models = []
|
|
120
|
+
for model in self.models:
|
|
121
|
+
agent_model = await model.agent_model(
|
|
122
|
+
function_tools=self.function_tools,
|
|
123
|
+
allow_text_result=self.allow_text_result,
|
|
124
|
+
result_tools=self.result_tools,
|
|
125
|
+
)
|
|
126
|
+
self._initialized_models.append(agent_model)
|
|
127
|
+
return self._initialized_models
|
|
128
|
+
|
|
129
|
+
async def request(
|
|
130
|
+
self,
|
|
131
|
+
messages: list[Message],
|
|
132
|
+
) -> tuple[ModelAnyResponse, Any]:
|
|
133
|
+
"""Make request using randomly selected model."""
|
|
134
|
+
models = await self._initialize_models()
|
|
135
|
+
selected = random.choice(models)
|
|
136
|
+
logger.debug("Selected model: %s", selected)
|
|
137
|
+
return await selected.request(messages)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class FallbackMultiModel(MultiModel):
|
|
141
|
+
"""Tries models in sequence until one succeeds.
|
|
142
|
+
|
|
143
|
+
Example YAML configuration:
|
|
144
|
+
```yaml
|
|
145
|
+
model:
|
|
146
|
+
type: fallback
|
|
147
|
+
models:
|
|
148
|
+
- openai:gpt-4 # Try this first
|
|
149
|
+
- openai:gpt-3.5-turbo # Fall back to this if gpt-4 fails
|
|
150
|
+
- ollama:llama2 # Last resort
|
|
151
|
+
```
|
|
152
|
+
"""
|
|
153
|
+
|
|
154
|
+
type: Literal["fallback"] = "fallback"
|
|
155
|
+
|
|
156
|
+
def name(self) -> str:
|
|
157
|
+
"""Get descriptive model name."""
|
|
158
|
+
return f"multi-fallback({len(self.models)})"
|
|
159
|
+
|
|
160
|
+
async def agent_model(
|
|
161
|
+
self,
|
|
162
|
+
*,
|
|
163
|
+
function_tools: list[ToolDefinition],
|
|
164
|
+
allow_text_result: bool,
|
|
165
|
+
result_tools: list[ToolDefinition],
|
|
166
|
+
) -> AgentModel:
|
|
167
|
+
"""Create agent model that implements fallback strategy."""
|
|
168
|
+
return FallbackAgentModel(
|
|
169
|
+
models=self.available_models,
|
|
170
|
+
function_tools=function_tools,
|
|
171
|
+
allow_text_result=allow_text_result,
|
|
172
|
+
result_tools=result_tools,
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
class FallbackAgentModel(AgentModel):
|
|
177
|
+
"""AgentModel that implements fallback strategy."""
|
|
178
|
+
|
|
179
|
+
def __init__(
|
|
180
|
+
self,
|
|
181
|
+
models: list[Model],
|
|
182
|
+
function_tools: list[ToolDefinition],
|
|
183
|
+
allow_text_result: bool,
|
|
184
|
+
result_tools: list[ToolDefinition],
|
|
185
|
+
) -> None:
|
|
186
|
+
"""Initialize with ordered list of models."""
|
|
187
|
+
if not models:
|
|
188
|
+
msg = "At least one model must be provided"
|
|
189
|
+
raise ValueError(msg)
|
|
190
|
+
self.models = models
|
|
191
|
+
self.function_tools = function_tools
|
|
192
|
+
self.allow_text_result = allow_text_result
|
|
193
|
+
self.result_tools = result_tools
|
|
194
|
+
self._initialized_models: list[AgentModel] | None = None
|
|
195
|
+
|
|
196
|
+
async def _initialize_models(self) -> list[AgentModel]:
|
|
197
|
+
"""Initialize all agent models."""
|
|
198
|
+
if self._initialized_models is None:
|
|
199
|
+
self._initialized_models = []
|
|
200
|
+
for model in self.models:
|
|
201
|
+
agent_model = await model.agent_model(
|
|
202
|
+
function_tools=self.function_tools,
|
|
203
|
+
allow_text_result=self.allow_text_result,
|
|
204
|
+
result_tools=self.result_tools,
|
|
205
|
+
)
|
|
206
|
+
self._initialized_models.append(agent_model)
|
|
207
|
+
return self._initialized_models
|
|
208
|
+
|
|
209
|
+
async def request(
|
|
210
|
+
self,
|
|
211
|
+
messages: list[Message],
|
|
212
|
+
) -> tuple[ModelAnyResponse, Any]:
|
|
213
|
+
"""Try each model in sequence until one succeeds."""
|
|
214
|
+
models = await self._initialize_models()
|
|
215
|
+
last_error = None
|
|
216
|
+
|
|
217
|
+
for model in models:
|
|
218
|
+
try:
|
|
219
|
+
logger.debug("Trying model: %s", model)
|
|
220
|
+
return await model.request(messages)
|
|
221
|
+
except Exception as e: # noqa: BLE001
|
|
222
|
+
last_error = e
|
|
223
|
+
logger.debug("Model %s failed: %s", model, e)
|
|
224
|
+
continue
|
|
225
|
+
|
|
226
|
+
msg = f"All models failed. Last error: {last_error}"
|
|
227
|
+
raise RuntimeError(msg) from last_error
|
|
File without changes
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
from typing import Annotated, Literal
|
|
2
|
+
|
|
3
|
+
from pydantic import Field
|
|
4
|
+
from pydantic_ai.models import AgentModel, KnownModelName, Model, infer_model
|
|
5
|
+
from pydantic_ai.models.test import TestModel
|
|
6
|
+
from pydantic_ai.tools import ToolDefinition
|
|
7
|
+
|
|
8
|
+
from llmling_models.base import PydanticModel
|
|
9
|
+
from llmling_models.multi import RandomMultiModel
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class _TestModelWrapper(PydanticModel):
|
|
13
|
+
"""Wrapper for TestModel."""
|
|
14
|
+
|
|
15
|
+
type: Literal["test"] = "test"
|
|
16
|
+
model: TestModel
|
|
17
|
+
|
|
18
|
+
def name(self) -> str:
|
|
19
|
+
"""Get model name."""
|
|
20
|
+
return "test"
|
|
21
|
+
|
|
22
|
+
async def agent_model(
|
|
23
|
+
self,
|
|
24
|
+
*,
|
|
25
|
+
function_tools: list[ToolDefinition],
|
|
26
|
+
allow_text_result: bool,
|
|
27
|
+
result_tools: list[ToolDefinition],
|
|
28
|
+
) -> AgentModel:
|
|
29
|
+
"""Create agent model implementation."""
|
|
30
|
+
return await self.model.agent_model(
|
|
31
|
+
function_tools=function_tools,
|
|
32
|
+
allow_text_result=allow_text_result,
|
|
33
|
+
result_tools=result_tools,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class StringModel(PydanticModel):
|
|
38
|
+
"""Wrapper for string model names."""
|
|
39
|
+
|
|
40
|
+
type: Literal["string"] = "string"
|
|
41
|
+
identifier: KnownModelName # renamed from name
|
|
42
|
+
|
|
43
|
+
async def agent_model(
|
|
44
|
+
self,
|
|
45
|
+
*,
|
|
46
|
+
function_tools: list[ToolDefinition],
|
|
47
|
+
allow_text_result: bool,
|
|
48
|
+
result_tools: list[ToolDefinition],
|
|
49
|
+
) -> AgentModel:
|
|
50
|
+
"""Create agent model from string name."""
|
|
51
|
+
model = infer_model(self.identifier) # type: ignore
|
|
52
|
+
return await model.agent_model(
|
|
53
|
+
function_tools=function_tools,
|
|
54
|
+
allow_text_result=allow_text_result,
|
|
55
|
+
result_tools=result_tools,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
def name(self) -> str:
|
|
59
|
+
"""Get model name."""
|
|
60
|
+
return str(self.identifier)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
type ModelInput = str | KnownModelName | Model | PydanticModel
|
|
64
|
+
"""Type for internal model handling (after validation)."""
|
|
65
|
+
|
|
66
|
+
AnyModel = Annotated[
|
|
67
|
+
StringModel | RandomMultiModel | _TestModelWrapper, Field(discriminator="type")
|
|
68
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""Tests for MultiModel implementations."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pydantic import ValidationError
|
|
6
|
+
from pydantic_ai import Agent, Tool
|
|
7
|
+
from pydantic_ai.models.test import TestModel
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
from llmling_models.multi import RandomMultiModel
|
|
11
|
+
from llmling_models.types import _TestModelWrapper
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@pytest.fixture
|
|
15
|
+
def test_models() -> tuple[TestModel, TestModel]:
|
|
16
|
+
"""Create two test models with different responses."""
|
|
17
|
+
return (
|
|
18
|
+
TestModel(custom_result_text="Response from Model 1"),
|
|
19
|
+
TestModel(custom_result_text="Response from Model 2"),
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@pytest.mark.asyncio
|
|
24
|
+
async def test_random_model_basic(test_models: tuple[TestModel, TestModel]) -> None:
|
|
25
|
+
"""Test basic RandomMultiModel functionality with pydantic-ai Agent."""
|
|
26
|
+
model1, model2 = test_models
|
|
27
|
+
random_model = RandomMultiModel(
|
|
28
|
+
type="random",
|
|
29
|
+
models=[
|
|
30
|
+
_TestModelWrapper(type="test", model=model1),
|
|
31
|
+
_TestModelWrapper(type="test", model=model2),
|
|
32
|
+
],
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
# Create a simple agent with our random model
|
|
36
|
+
agent = Agent(model=random_model)
|
|
37
|
+
|
|
38
|
+
# Run multiple times to collect different responses
|
|
39
|
+
responses = set()
|
|
40
|
+
for _ in range(10):
|
|
41
|
+
result = await agent.run("Test prompt")
|
|
42
|
+
responses.add(result.data)
|
|
43
|
+
|
|
44
|
+
# Verify we get both responses
|
|
45
|
+
assert len(responses) == 2 # noqa: PLR2004
|
|
46
|
+
assert "Response from Model 1" in responses
|
|
47
|
+
assert "Response from Model 2" in responses
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@pytest.mark.asyncio
|
|
51
|
+
async def test_random_model_with_tools(test_models: tuple[TestModel, TestModel]) -> None:
|
|
52
|
+
"""Test RandomMultiModel with tool usage."""
|
|
53
|
+
model1, model2 = test_models
|
|
54
|
+
random_model = RandomMultiModel(
|
|
55
|
+
type="random",
|
|
56
|
+
models=[
|
|
57
|
+
_TestModelWrapper(type="test", model=model1),
|
|
58
|
+
_TestModelWrapper(type="test", model=model2),
|
|
59
|
+
],
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# Create test tool
|
|
63
|
+
async def test_tool(text: str) -> str:
|
|
64
|
+
return f"Processed: {text}"
|
|
65
|
+
|
|
66
|
+
# Create agent with tool
|
|
67
|
+
agent = Agent(
|
|
68
|
+
model=random_model,
|
|
69
|
+
tools=[Tool(test_tool, takes_ctx=False)],
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
# Run multiple times
|
|
73
|
+
responses = set()
|
|
74
|
+
for _ in range(10):
|
|
75
|
+
result = await agent.run("Use the test tool")
|
|
76
|
+
responses.add(result.data)
|
|
77
|
+
|
|
78
|
+
assert len(responses) == 2 # noqa: PLR2004
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def test_random_model_validation() -> None:
|
|
82
|
+
"""Test RandomMultiModel validation."""
|
|
83
|
+
# Test empty models list
|
|
84
|
+
with pytest.raises(ValueError): # noqa: PT011
|
|
85
|
+
RandomMultiModel(type="random", models=[])
|
|
86
|
+
|
|
87
|
+
# Test invalid model name
|
|
88
|
+
with pytest.raises(ValidationError):
|
|
89
|
+
RandomMultiModel(type="random", models=["invalid_model"]) # type: ignore
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def test_yaml_loading() -> None:
|
|
93
|
+
"""Test loading RandomMultiModel from YAML configuration."""
|
|
94
|
+
import yaml
|
|
95
|
+
|
|
96
|
+
config = """
|
|
97
|
+
type: random
|
|
98
|
+
models:
|
|
99
|
+
- test
|
|
100
|
+
- openai:gpt-4
|
|
101
|
+
"""
|
|
102
|
+
|
|
103
|
+
data = yaml.safe_load(config)
|
|
104
|
+
model = RandomMultiModel.model_validate(data)
|
|
105
|
+
|
|
106
|
+
assert model.type == "random"
|
|
107
|
+
assert len(model.models) == 2 # noqa: PLR2004
|
|
108
|
+
assert "test" in model.models
|
|
109
|
+
assert "openai:gpt-4" in model.models
|