3tears-models 0.14.0__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.
- 3tears_models-0.14.0/.gitignore +216 -0
- 3tears_models-0.14.0/LICENSE +21 -0
- 3tears_models-0.14.0/PKG-INFO +62 -0
- 3tears_models-0.14.0/README.md +34 -0
- 3tears_models-0.14.0/docs/migration-aibots.md +136 -0
- 3tears_models-0.14.0/docs/migration-metallm.md +188 -0
- 3tears_models-0.14.0/docs/migration-pentest-kit.md +171 -0
- 3tears_models-0.14.0/pyproject.toml +42 -0
- 3tears_models-0.14.0/src/threetears/models/__init__.py +175 -0
- 3tears_models-0.14.0/src/threetears/models/cache.py +99 -0
- 3tears_models-0.14.0/src/threetears/models/capabilities.py +406 -0
- 3tears_models-0.14.0/src/threetears/models/chunk_merging.py +70 -0
- 3tears_models-0.14.0/src/threetears/models/chunk_parsing.py +120 -0
- 3tears_models-0.14.0/src/threetears/models/circuit_breaker.py +378 -0
- 3tears_models-0.14.0/src/threetears/models/defaults.py +68 -0
- 3tears_models-0.14.0/src/threetears/models/enums.py +56 -0
- 3tears_models-0.14.0/src/threetears/models/errors.py +138 -0
- 3tears_models-0.14.0/src/threetears/models/factory.py +320 -0
- 3tears_models-0.14.0/src/threetears/models/preprocessing.py +223 -0
- 3tears_models-0.14.0/src/threetears/models/price_lookup.py +614 -0
- 3tears_models-0.14.0/src/threetears/models/providers/__init__.py +8 -0
- 3tears_models-0.14.0/src/threetears/models/providers/_claude_cli.py +98 -0
- 3tears_models-0.14.0/src/threetears/models/providers/_voyageai_compat.py +86 -0
- 3tears_models-0.14.0/src/threetears/models/providers/anthropic.py +549 -0
- 3tears_models-0.14.0/src/threetears/models/providers/image/__init__.py +22 -0
- 3tears_models-0.14.0/src/threetears/models/providers/image/a1111.py +119 -0
- 3tears_models-0.14.0/src/threetears/models/providers/image/comfyui.py +218 -0
- 3tears_models-0.14.0/src/threetears/models/providers/image/huggingface.py +94 -0
- 3tears_models-0.14.0/src/threetears/models/providers/image/modelslab.py +150 -0
- 3tears_models-0.14.0/src/threetears/models/providers/image/openai.py +189 -0
- 3tears_models-0.14.0/src/threetears/models/providers/openai.py +339 -0
- 3tears_models-0.14.0/src/threetears/models/providers/openrouter.py +550 -0
- 3tears_models-0.14.0/src/threetears/models/providers/voyageai.py +109 -0
- 3tears_models-0.14.0/src/threetears/models/providers/whisper.py +214 -0
- 3tears_models-0.14.0/src/threetears/models/registry.py +116 -0
- 3tears_models-0.14.0/src/threetears/models/registry_loader.py +210 -0
- 3tears_models-0.14.0/src/threetears/models/tool_name_translation.py +311 -0
- 3tears_models-0.14.0/src/threetears/models/tool_name_validation.py +186 -0
- 3tears_models-0.14.0/src/threetears/models/tracking.py +896 -0
- 3tears_models-0.14.0/tests/__init__.py +0 -0
- 3tears_models-0.14.0/tests/integration/__init__.py +0 -0
- 3tears_models-0.14.0/tests/integration/test_cache_registry_integration.py +118 -0
- 3tears_models-0.14.0/tests/integration/test_chat_pipeline.py +49 -0
- 3tears_models-0.14.0/tests/integration/test_circuit_breaker_integration.py +117 -0
- 3tears_models-0.14.0/tests/integration/test_embedding_pipeline.py +22 -0
- 3tears_models-0.14.0/tests/integration/test_error_translation_integration.py +203 -0
- 3tears_models-0.14.0/tests/test_import_cost.py +54 -0
- 3tears_models-0.14.0/tests/unit/__init__.py +0 -0
- 3tears_models-0.14.0/tests/unit/models/__init__.py +0 -0
- 3tears_models-0.14.0/tests/unit/models/_voyage_pricing_fixture.html +1 -0
- 3tears_models-0.14.0/tests/unit/models/test_cache.py +181 -0
- 3tears_models-0.14.0/tests/unit/models/test_callbacks.py +337 -0
- 3tears_models-0.14.0/tests/unit/models/test_capabilities.py +595 -0
- 3tears_models-0.14.0/tests/unit/models/test_chunk_merging.py +186 -0
- 3tears_models-0.14.0/tests/unit/models/test_chunk_parsing.py +234 -0
- 3tears_models-0.14.0/tests/unit/models/test_circuit_breaker.py +258 -0
- 3tears_models-0.14.0/tests/unit/models/test_enums.py +96 -0
- 3tears_models-0.14.0/tests/unit/models/test_errors.py +309 -0
- 3tears_models-0.14.0/tests/unit/models/test_factory.py +82 -0
- 3tears_models-0.14.0/tests/unit/models/test_preprocessing.py +175 -0
- 3tears_models-0.14.0/tests/unit/models/test_price_lookup.py +382 -0
- 3tears_models-0.14.0/tests/unit/models/test_provider_anthropic.py +560 -0
- 3tears_models-0.14.0/tests/unit/models/test_provider_image.py +519 -0
- 3tears_models-0.14.0/tests/unit/models/test_provider_openai.py +256 -0
- 3tears_models-0.14.0/tests/unit/models/test_provider_openrouter.py +931 -0
- 3tears_models-0.14.0/tests/unit/models/test_provider_voyageai.py +46 -0
- 3tears_models-0.14.0/tests/unit/models/test_provider_whisper.py +302 -0
- 3tears_models-0.14.0/tests/unit/models/test_registry.py +116 -0
- 3tears_models-0.14.0/tests/unit/models/test_tool_name_validation.py +263 -0
- 3tears_models-0.14.0/tests/unit/models/test_tracking.py +807 -0
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[codz]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
share/python-wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
|
|
29
|
+
# PyInstaller
|
|
30
|
+
# Usually these files are written by a python script from a template
|
|
31
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
32
|
+
*.manifest
|
|
33
|
+
*.spec
|
|
34
|
+
|
|
35
|
+
# Installer logs
|
|
36
|
+
pip-log.txt
|
|
37
|
+
pip-delete-this-directory.txt
|
|
38
|
+
|
|
39
|
+
# Unit test / coverage reports
|
|
40
|
+
htmlcov/
|
|
41
|
+
.tox/
|
|
42
|
+
.nox/
|
|
43
|
+
.coverage
|
|
44
|
+
.coverage.*
|
|
45
|
+
.cache
|
|
46
|
+
nosetests.xml
|
|
47
|
+
coverage.xml
|
|
48
|
+
*.cover
|
|
49
|
+
*.py.cover
|
|
50
|
+
.hypothesis/
|
|
51
|
+
.pytest_cache/
|
|
52
|
+
cover/
|
|
53
|
+
|
|
54
|
+
# Translations
|
|
55
|
+
*.mo
|
|
56
|
+
*.pot
|
|
57
|
+
|
|
58
|
+
# Django stuff:
|
|
59
|
+
*.log
|
|
60
|
+
local_settings.py
|
|
61
|
+
db.sqlite3
|
|
62
|
+
db.sqlite3-journal
|
|
63
|
+
|
|
64
|
+
# Flask stuff:
|
|
65
|
+
instance/
|
|
66
|
+
.webassets-cache
|
|
67
|
+
|
|
68
|
+
# Scrapy stuff:
|
|
69
|
+
.scrapy
|
|
70
|
+
|
|
71
|
+
# Sphinx documentation
|
|
72
|
+
docs/_build/
|
|
73
|
+
|
|
74
|
+
# PyBuilder
|
|
75
|
+
.pybuilder/
|
|
76
|
+
target/
|
|
77
|
+
|
|
78
|
+
# Jupyter Notebook
|
|
79
|
+
.ipynb_checkpoints
|
|
80
|
+
|
|
81
|
+
# IPython
|
|
82
|
+
profile_default/
|
|
83
|
+
ipython_config.py
|
|
84
|
+
|
|
85
|
+
# pyenv
|
|
86
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
87
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
88
|
+
# .python-version
|
|
89
|
+
|
|
90
|
+
# pipenv
|
|
91
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
92
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
93
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
94
|
+
# install all needed dependencies.
|
|
95
|
+
#Pipfile.lock
|
|
96
|
+
|
|
97
|
+
# UV
|
|
98
|
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
99
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
100
|
+
# commonly ignored for libraries.
|
|
101
|
+
#uv.lock
|
|
102
|
+
|
|
103
|
+
# poetry
|
|
104
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
105
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
106
|
+
# commonly ignored for libraries.
|
|
107
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
108
|
+
#poetry.lock
|
|
109
|
+
#poetry.toml
|
|
110
|
+
|
|
111
|
+
# pdm
|
|
112
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
113
|
+
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
|
114
|
+
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
|
115
|
+
#pdm.lock
|
|
116
|
+
#pdm.toml
|
|
117
|
+
.pdm-python
|
|
118
|
+
.pdm-build/
|
|
119
|
+
|
|
120
|
+
# pixi
|
|
121
|
+
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
|
122
|
+
#pixi.lock
|
|
123
|
+
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
|
124
|
+
# in the .venv directory. It is recommended not to include this directory in version control.
|
|
125
|
+
.pixi
|
|
126
|
+
|
|
127
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
128
|
+
__pypackages__/
|
|
129
|
+
|
|
130
|
+
# Celery stuff
|
|
131
|
+
celerybeat-schedule
|
|
132
|
+
celerybeat.pid
|
|
133
|
+
|
|
134
|
+
# SageMath parsed files
|
|
135
|
+
*.sage.py
|
|
136
|
+
|
|
137
|
+
# Environments
|
|
138
|
+
.env
|
|
139
|
+
.envrc
|
|
140
|
+
.venv
|
|
141
|
+
env/
|
|
142
|
+
venv/
|
|
143
|
+
ENV/
|
|
144
|
+
env.bak/
|
|
145
|
+
venv.bak/
|
|
146
|
+
|
|
147
|
+
# Spyder project settings
|
|
148
|
+
.spyderproject
|
|
149
|
+
.spyproject
|
|
150
|
+
|
|
151
|
+
# Rope project settings
|
|
152
|
+
.ropeproject
|
|
153
|
+
|
|
154
|
+
# mkdocs documentation
|
|
155
|
+
/site
|
|
156
|
+
|
|
157
|
+
# mypy
|
|
158
|
+
.mypy_cache/
|
|
159
|
+
.dmypy.json
|
|
160
|
+
dmypy.json
|
|
161
|
+
|
|
162
|
+
# Pyre type checker
|
|
163
|
+
.pyre/
|
|
164
|
+
|
|
165
|
+
# pytype static type analyzer
|
|
166
|
+
.pytype/
|
|
167
|
+
|
|
168
|
+
# Cython debug symbols
|
|
169
|
+
cython_debug/
|
|
170
|
+
|
|
171
|
+
# PyCharm
|
|
172
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
173
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
174
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
175
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
176
|
+
#.idea/
|
|
177
|
+
|
|
178
|
+
# Abstra
|
|
179
|
+
# Abstra is an AI-powered process automation framework.
|
|
180
|
+
# Ignore directories containing user credentials, local state, and settings.
|
|
181
|
+
# Learn more at https://abstra.io/docs
|
|
182
|
+
.abstra/
|
|
183
|
+
|
|
184
|
+
# Visual Studio Code
|
|
185
|
+
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
|
186
|
+
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
|
187
|
+
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
|
188
|
+
# you could uncomment the following to ignore the entire vscode folder
|
|
189
|
+
# .vscode/
|
|
190
|
+
|
|
191
|
+
# Ruff stuff:
|
|
192
|
+
.ruff_cache/
|
|
193
|
+
|
|
194
|
+
# PyPI configuration file
|
|
195
|
+
.pypirc
|
|
196
|
+
|
|
197
|
+
# Cursor
|
|
198
|
+
# Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
|
|
199
|
+
# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
|
|
200
|
+
# refer to https://docs.cursor.com/context/ignore-files
|
|
201
|
+
.cursorignore
|
|
202
|
+
.cursorindexingignore
|
|
203
|
+
|
|
204
|
+
# Marimo
|
|
205
|
+
marimo/_static/
|
|
206
|
+
marimo/_lsp/
|
|
207
|
+
__marimo__/
|
|
208
|
+
|
|
209
|
+
# Claude Code local state
|
|
210
|
+
.claude/
|
|
211
|
+
|
|
212
|
+
# prawduct session evidence (local governance artifacts, never shipped)
|
|
213
|
+
.prawduct/
|
|
214
|
+
|
|
215
|
+
# macOS folder metadata
|
|
216
|
+
.DS_Store
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mark Pace
|
|
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,62 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: 3tears-models
|
|
3
|
+
Version: 0.14.0
|
|
4
|
+
Summary: LangChain-native AI model factories with capability metadata, circuit breakers, and usage tracking for the 3tears platform
|
|
5
|
+
Project-URL: Repository, https://github.com/pacepace/3tears
|
|
6
|
+
Author: pace
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Python: >=3.14
|
|
10
|
+
Requires-Dist: 3tears-media-contracts
|
|
11
|
+
Requires-Dist: 3tears-observe
|
|
12
|
+
Requires-Dist: langchain-anthropic>=1.4
|
|
13
|
+
Requires-Dist: langchain-core>=0.3
|
|
14
|
+
Requires-Dist: langchain-openai>=1.1
|
|
15
|
+
Requires-Dist: langchain-openrouter>=0.1
|
|
16
|
+
Requires-Dist: pydantic>=2.0
|
|
17
|
+
Provides-Extra: claude-cli
|
|
18
|
+
Requires-Dist: langchain-claude-code>=0.1.0; extra == 'claude-cli'
|
|
19
|
+
Provides-Extra: image
|
|
20
|
+
Requires-Dist: httpx>=0.27; extra == 'image'
|
|
21
|
+
Provides-Extra: prometheus
|
|
22
|
+
Requires-Dist: prometheus-client>=0.20; extra == 'prometheus'
|
|
23
|
+
Provides-Extra: voyageai
|
|
24
|
+
Requires-Dist: langchain-voyageai>=0.3; extra == 'voyageai'
|
|
25
|
+
Provides-Extra: whisper
|
|
26
|
+
Requires-Dist: httpx>=0.27; extra == 'whisper'
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
# 3tears-models
|
|
30
|
+
|
|
31
|
+
LangChain-native AI model factories for the 3tears framework. Build chat and embedding models from a single call, with capability metadata, circuit breakers, error translation, and usage tracking wired in.
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install 3tears-models
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## What you get
|
|
38
|
+
|
|
39
|
+
- **Factories** -- `create_chat_model()` and `create_embedding_model()` return standard LangChain `BaseChatModel` / `Embeddings` instances. No custom runtime protocols to learn.
|
|
40
|
+
- **Providers** -- Anthropic, OpenAI, OpenRouter, VoyageAI, Whisper, and image backends (OpenAI Images, HuggingFace, A1111, ModelsLab, ComfyUI).
|
|
41
|
+
- **Capability registry** -- `get_capabilities()`, `register_capabilities()`, and per-model overrides describe context windows, vision support, tool support, and tier.
|
|
42
|
+
- **Circuit breakers** -- `CircuitBreaker` and `CircuitBreakerRegistry` trip on repeated provider failures and recover on a timer.
|
|
43
|
+
- **Usage tracking** -- `UsageTracker.record()` emits an OpenTelemetry span plus Prometheus instruments, with optional per-application audit and counter sinks.
|
|
44
|
+
- **Message hygiene** -- `preprocess_messages()`, `enforce_alternating_roles()`, `filter_invalid_tool_calls()`, and streaming chunk helpers (`parse_chunk`, `merge_chunks`).
|
|
45
|
+
|
|
46
|
+
## Quickstart
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from threetears.models import create_chat_model, create_embedding_model
|
|
50
|
+
|
|
51
|
+
chat = create_chat_model("claude-sonnet-4-6")
|
|
52
|
+
reply = await chat.ainvoke("Summarize three-tier caching in one sentence.")
|
|
53
|
+
|
|
54
|
+
embedder = create_embedding_model("voyage-3")
|
|
55
|
+
vectors = await embedder.aembed_documents(["first doc", "second doc"])
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Capabilities, circuit breakers, and usage tracking attach automatically. Reach for the registry and tracker APIs directly when you need to inspect or override them.
|
|
59
|
+
|
|
60
|
+
## License
|
|
61
|
+
|
|
62
|
+
MIT. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# 3tears-models
|
|
2
|
+
|
|
3
|
+
LangChain-native AI model factories for the 3tears framework. Build chat and embedding models from a single call, with capability metadata, circuit breakers, error translation, and usage tracking wired in.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pip install 3tears-models
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## What you get
|
|
10
|
+
|
|
11
|
+
- **Factories** -- `create_chat_model()` and `create_embedding_model()` return standard LangChain `BaseChatModel` / `Embeddings` instances. No custom runtime protocols to learn.
|
|
12
|
+
- **Providers** -- Anthropic, OpenAI, OpenRouter, VoyageAI, Whisper, and image backends (OpenAI Images, HuggingFace, A1111, ModelsLab, ComfyUI).
|
|
13
|
+
- **Capability registry** -- `get_capabilities()`, `register_capabilities()`, and per-model overrides describe context windows, vision support, tool support, and tier.
|
|
14
|
+
- **Circuit breakers** -- `CircuitBreaker` and `CircuitBreakerRegistry` trip on repeated provider failures and recover on a timer.
|
|
15
|
+
- **Usage tracking** -- `UsageTracker.record()` emits an OpenTelemetry span plus Prometheus instruments, with optional per-application audit and counter sinks.
|
|
16
|
+
- **Message hygiene** -- `preprocess_messages()`, `enforce_alternating_roles()`, `filter_invalid_tool_calls()`, and streaming chunk helpers (`parse_chunk`, `merge_chunks`).
|
|
17
|
+
|
|
18
|
+
## Quickstart
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
from threetears.models import create_chat_model, create_embedding_model
|
|
22
|
+
|
|
23
|
+
chat = create_chat_model("claude-sonnet-4-6")
|
|
24
|
+
reply = await chat.ainvoke("Summarize three-tier caching in one sentence.")
|
|
25
|
+
|
|
26
|
+
embedder = create_embedding_model("voyage-3")
|
|
27
|
+
vectors = await embedder.aembed_documents(["first doc", "second doc"])
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Capabilities, circuit breakers, and usage tracking attach automatically. Reach for the registry and tracker APIs directly when you need to inspect or override them.
|
|
31
|
+
|
|
32
|
+
## License
|
|
33
|
+
|
|
34
|
+
MIT. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# Migration Guide: aibots to threetears.models
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The `threetears.models` package extracts provider protocols, circuit breakers, error translation, and caching from the aibots Gateway into a shared library. After migration, the aibots Gateway becomes thinner -- it routes NATS requests, enforces ACLs, and handles billing, but delegates model lifecycle to `threetears.models`.
|
|
6
|
+
|
|
7
|
+
**Moves to threetears.models:**
|
|
8
|
+
- Circuit breaker state machine (`CircuitBreakerRegistry`)
|
|
9
|
+
- Error translation (`friendly_api_error`, `identify_provider`)
|
|
10
|
+
- Provider instance caching (`ModelCache`)
|
|
11
|
+
- Chat/Embedding provider adapters and protocols
|
|
12
|
+
|
|
13
|
+
**Stays in aibots:**
|
|
14
|
+
- Wire error codes (`ERROR_PROVIDER_RATE_LIMITED`, `ERROR_PROVIDER_OVERLOADED`, etc.)
|
|
15
|
+
- Billing and cost recording
|
|
16
|
+
- NATS request routing and subject handling
|
|
17
|
+
- ACL enforcement (customer/agent access control)
|
|
18
|
+
- Agent lifecycle management
|
|
19
|
+
|
|
20
|
+
## Dependency update
|
|
21
|
+
|
|
22
|
+
Add to `pyproject.toml`:
|
|
23
|
+
|
|
24
|
+
```toml
|
|
25
|
+
[project]
|
|
26
|
+
dependencies = [
|
|
27
|
+
"3tears-models[anthropic,openai,voyageai]",
|
|
28
|
+
# ... existing deps
|
|
29
|
+
]
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Files to delete
|
|
33
|
+
|
|
34
|
+
### `gateway/circuit_breaker.py`
|
|
35
|
+
|
|
36
|
+
Replaced entirely by `CircuitBreakerRegistry` from `threetears.models`.
|
|
37
|
+
|
|
38
|
+
**Before (aibots):**
|
|
39
|
+
```python
|
|
40
|
+
from aibots.gateway.circuit_breaker import CircuitBreaker, CIRCUIT_OPEN
|
|
41
|
+
|
|
42
|
+
breaker = CircuitBreaker(provider_name="anthropic", threshold=5, timeout=30)
|
|
43
|
+
if breaker.is_open():
|
|
44
|
+
raise CircuitOpenException(provider_name)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**After (threetears.models):**
|
|
48
|
+
```python
|
|
49
|
+
from threetears.models import CircuitBreakerRegistry, CircuitOpenError
|
|
50
|
+
|
|
51
|
+
registry = CircuitBreakerRegistry(failure_threshold=5, recovery_timeout_seconds=30.0)
|
|
52
|
+
breaker = registry.get("anthropic")
|
|
53
|
+
breaker.check() # raises CircuitOpenError if open
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Files to modify
|
|
57
|
+
|
|
58
|
+
### `gateway/provider_pool.py`
|
|
59
|
+
|
|
60
|
+
Replace hand-rolled provider pool with `ModelCache` and provider adapters.
|
|
61
|
+
|
|
62
|
+
**Before:**
|
|
63
|
+
```python
|
|
64
|
+
from aibots.gateway.provider_pool import ProviderPool
|
|
65
|
+
|
|
66
|
+
pool = ProviderPool()
|
|
67
|
+
model = pool.get_or_create("anthropic", "claude-sonnet-4-6", api_key)
|
|
68
|
+
response = await model.ainvoke(messages)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**After:**
|
|
72
|
+
```python
|
|
73
|
+
from threetears.models import ModelCache
|
|
74
|
+
from threetears.models.providers.anthropic import AnthropicChatProvider
|
|
75
|
+
|
|
76
|
+
cache = ModelCache()
|
|
77
|
+
provider = cache.get("anthropic", "claude-sonnet-4-6")
|
|
78
|
+
if provider is None:
|
|
79
|
+
provider = AnthropicChatProvider("claude-sonnet-4-6", api_key)
|
|
80
|
+
cache.put("anthropic", "claude-sonnet-4-6", provider)
|
|
81
|
+
|
|
82
|
+
messages = [ChatMessage(role=MessageRole.USER, content="Hello")]
|
|
83
|
+
result = await provider.complete(messages)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### `gateway/handlers/completion.py`
|
|
87
|
+
|
|
88
|
+
Add `friendly_api_error()` for user-facing error text in NATS error responses.
|
|
89
|
+
|
|
90
|
+
**Before:**
|
|
91
|
+
```python
|
|
92
|
+
except Exception as exc:
|
|
93
|
+
error_text = f"Provider error: {exc}"
|
|
94
|
+
return NatsErrorResponse(code=ERROR_PROVIDER_UNKNOWN, message=error_text)
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
**After:**
|
|
98
|
+
```python
|
|
99
|
+
from threetears.models import friendly_api_error
|
|
100
|
+
|
|
101
|
+
except Exception as exc:
|
|
102
|
+
error_text = friendly_api_error(exc)
|
|
103
|
+
return NatsErrorResponse(code=ERROR_PROVIDER_UNKNOWN, message=error_text)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### `gateway/handlers/embedding.py`
|
|
107
|
+
|
|
108
|
+
Replace raw LangChain embedding calls with typed providers.
|
|
109
|
+
|
|
110
|
+
**Before:**
|
|
111
|
+
```python
|
|
112
|
+
from langchain_openai import OpenAIEmbeddings
|
|
113
|
+
|
|
114
|
+
model = OpenAIEmbeddings(model=model_name, api_key=api_key)
|
|
115
|
+
vectors = await model.aembed_documents([text])
|
|
116
|
+
result = {"vector": vectors[0], "dimensions": len(vectors[0])}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**After:**
|
|
120
|
+
```python
|
|
121
|
+
from threetears.models.providers.openai import OpenAIEmbeddingProvider
|
|
122
|
+
|
|
123
|
+
provider = OpenAIEmbeddingProvider(model_name, api_key)
|
|
124
|
+
result = await provider.embed(text)
|
|
125
|
+
# result is EmbeddingResult with .vector, .dimensions, .model, .token_count
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## What stays in aibots
|
|
129
|
+
|
|
130
|
+
These concerns remain in the Gateway because they are platform-specific:
|
|
131
|
+
|
|
132
|
+
- **Wire error codes**: `ERROR_PROVIDER_RATE_LIMITED`, `ERROR_PROVIDER_OVERLOADED`, `ERROR_PROVIDER_AUTH`, `ERROR_PROVIDER_UNAVAILABLE` -- these are NATS envelope codes, not model-layer concerns
|
|
133
|
+
- **Billing**: Cost recording, credit deduction, usage aggregation per customer
|
|
134
|
+
- **ACL**: Which agents can access which providers, model-level allowlists
|
|
135
|
+
- **NATS routing**: Subject patterns, queue groups, request/reply envelopes
|
|
136
|
+
- **Agent lifecycle**: Agent-to-provider mapping, hot-swap on key rotation
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# Migration Guide: metallm to threetears.models
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
metallm has the most to migrate. The `threetears.models` package replaces metallm's provider layer, embedding service, whisper service, image backends, circuit breaker, and streaming recovery logic. After migration, metallm focuses on personality, NSFW handling, content pipeline, and frontend API.
|
|
6
|
+
|
|
7
|
+
**Moves to threetears.models:**
|
|
8
|
+
- Chat providers (Anthropic, OpenAI, OpenRouter)
|
|
9
|
+
- Embedding service (OpenAI, VoyageAI)
|
|
10
|
+
- Whisper transcription service
|
|
11
|
+
- Image generation backends (OpenAI, HuggingFace, A1111, ModelsLab, ComfyUI)
|
|
12
|
+
- Circuit breaker
|
|
13
|
+
- Streaming chunk merging and tool call recovery
|
|
14
|
+
- Message preprocessing (alternating roles, vision content)
|
|
15
|
+
|
|
16
|
+
**Stays in metallm:**
|
|
17
|
+
- NSFW content detection and filtering
|
|
18
|
+
- Personality system and persona management
|
|
19
|
+
- Content pipeline (summarization, extraction, formatting)
|
|
20
|
+
- Frontend HTTP API
|
|
21
|
+
- DuckDB local caching layer
|
|
22
|
+
|
|
23
|
+
## Dependency update
|
|
24
|
+
|
|
25
|
+
Add to `pyproject.toml`:
|
|
26
|
+
|
|
27
|
+
```toml
|
|
28
|
+
[project]
|
|
29
|
+
dependencies = [
|
|
30
|
+
"3tears-models[anthropic,openai,openrouter,voyageai,whisper,image]",
|
|
31
|
+
# ... existing deps
|
|
32
|
+
]
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Files to delete
|
|
36
|
+
|
|
37
|
+
### `graph/providers.py`
|
|
38
|
+
|
|
39
|
+
Provider creation and management replaced by `threetears.models` adapters and `ModelCache`.
|
|
40
|
+
|
|
41
|
+
### `services/embedding.py`
|
|
42
|
+
|
|
43
|
+
Replaced by `OpenAIEmbeddingProvider` and `VoyageAIEmbeddingProvider`.
|
|
44
|
+
|
|
45
|
+
**Before:**
|
|
46
|
+
```python
|
|
47
|
+
from metallm.services.embedding import EmbeddingService
|
|
48
|
+
|
|
49
|
+
svc = EmbeddingService(provider="openai", model="text-embedding-3-small", api_key=key)
|
|
50
|
+
vector = await svc.embed(text)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**After:**
|
|
54
|
+
```python
|
|
55
|
+
from threetears.models.providers.openai import OpenAIEmbeddingProvider
|
|
56
|
+
|
|
57
|
+
provider = OpenAIEmbeddingProvider("text-embedding-3-small", api_key)
|
|
58
|
+
result = await provider.embed(text)
|
|
59
|
+
vector = result.vector
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### `services/whisper.py`
|
|
63
|
+
|
|
64
|
+
Replaced by `WhisperTranscriptionProvider`.
|
|
65
|
+
|
|
66
|
+
**Before:**
|
|
67
|
+
```python
|
|
68
|
+
from metallm.services.whisper import WhisperService
|
|
69
|
+
|
|
70
|
+
svc = WhisperService(api_key=key)
|
|
71
|
+
text = await svc.transcribe(audio_bytes, mime_type="audio/mp3")
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**After:**
|
|
75
|
+
```python
|
|
76
|
+
from threetears.models.providers.whisper import WhisperTranscriptionProvider
|
|
77
|
+
|
|
78
|
+
provider = WhisperTranscriptionProvider(api_key=key)
|
|
79
|
+
result = await provider.transcribe(audio_bytes, "audio/mp3")
|
|
80
|
+
text = result.text
|
|
81
|
+
segments = result.segments # optional time-aligned segments
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### `services/image_backends/*.py`
|
|
85
|
+
|
|
86
|
+
All image generation backends replaced by `threetears.models.providers.image` module.
|
|
87
|
+
|
|
88
|
+
**Before:**
|
|
89
|
+
```python
|
|
90
|
+
from metallm.services.image_backends.openai_backend import OpenAIImageBackend
|
|
91
|
+
|
|
92
|
+
backend = OpenAIImageBackend(api_key=key)
|
|
93
|
+
image_bytes = await backend.generate(prompt, size="1024x1024")
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**After:**
|
|
97
|
+
```python
|
|
98
|
+
from threetears.models.providers.image import OpenAIImageProvider
|
|
99
|
+
|
|
100
|
+
provider = OpenAIImageProvider(api_key=key)
|
|
101
|
+
# provider implements ImageGenerationProvider protocol
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### `cache/circuit_breaker.py`
|
|
105
|
+
|
|
106
|
+
Replaced by `CircuitBreakerRegistry`.
|
|
107
|
+
|
|
108
|
+
## Files to modify
|
|
109
|
+
|
|
110
|
+
### `graph/nodes/personality.py`
|
|
111
|
+
|
|
112
|
+
Replace inline streaming recovery with shared helpers.
|
|
113
|
+
|
|
114
|
+
**Before:**
|
|
115
|
+
```python
|
|
116
|
+
# inline chunk accumulation and tool call fixup
|
|
117
|
+
content_parts = []
|
|
118
|
+
tool_calls = []
|
|
119
|
+
for chunk in chunks:
|
|
120
|
+
content_parts.append(chunk.content)
|
|
121
|
+
if chunk.tool_calls:
|
|
122
|
+
tool_calls.extend(chunk.tool_calls)
|
|
123
|
+
# manual split detection
|
|
124
|
+
if len(tool_calls) >= 2:
|
|
125
|
+
fixed = _fix_split_tool_calls(tool_calls)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
**After:**
|
|
129
|
+
```python
|
|
130
|
+
from threetears.models.streaming import merge_chunks, recover_split_tool_calls
|
|
131
|
+
|
|
132
|
+
result = merge_chunks(chunks)
|
|
133
|
+
if result.tool_calls:
|
|
134
|
+
result = ChatResult(
|
|
135
|
+
content=result.content,
|
|
136
|
+
tool_calls=recover_split_tool_calls(result.tool_calls),
|
|
137
|
+
model=result.model,
|
|
138
|
+
usage=result.usage,
|
|
139
|
+
)
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Use `preprocess_messages()` before sending to providers that require alternating roles:
|
|
143
|
+
|
|
144
|
+
```python
|
|
145
|
+
from threetears.models.preprocessing import preprocess_messages
|
|
146
|
+
from threetears.models.capabilities import ModelCapabilities
|
|
147
|
+
|
|
148
|
+
capabilities = ModelCapabilities(
|
|
149
|
+
model_name="claude-sonnet-4-6",
|
|
150
|
+
model_type=ModelType.CHAT,
|
|
151
|
+
model_tier=ModelTier.LARGE,
|
|
152
|
+
model_status=ModelStatus.ACTIVE,
|
|
153
|
+
requires_alternating_roles=True,
|
|
154
|
+
)
|
|
155
|
+
processed = preprocess_messages(messages, capabilities)
|
|
156
|
+
result = await provider.complete(processed)
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### `services/media_adapters.py`
|
|
160
|
+
|
|
161
|
+
Replace inline base64 encoding with `format_vision_content()`.
|
|
162
|
+
|
|
163
|
+
**Before:**
|
|
164
|
+
```python
|
|
165
|
+
import base64
|
|
166
|
+
b64 = base64.b64encode(image_bytes).decode()
|
|
167
|
+
content = [
|
|
168
|
+
{"type": "image_url", "image_url": {"url": f"data:{mime};base64,{b64}"}},
|
|
169
|
+
{"type": "text", "text": prompt},
|
|
170
|
+
]
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
**After:**
|
|
174
|
+
```python
|
|
175
|
+
from threetears.models.preprocessing import format_vision_content
|
|
176
|
+
|
|
177
|
+
content = format_vision_content(image_bytes, mime_type, prompt)
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## What stays in metallm
|
|
181
|
+
|
|
182
|
+
These concerns remain because they are application-specific:
|
|
183
|
+
|
|
184
|
+
- **NSFW handling**: Content detection, filtering, and policy enforcement
|
|
185
|
+
- **Personality system**: Persona definitions, system prompt generation, personality switching
|
|
186
|
+
- **Content pipeline**: Summarization chains, extraction pipelines, output formatting
|
|
187
|
+
- **Frontend API**: HTTP routes, WebSocket handlers, session management
|
|
188
|
+
- **DuckDB caching**: Local query cache, conversation history, analytics aggregation
|