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.
Files changed (70) hide show
  1. 3tears_models-0.14.0/.gitignore +216 -0
  2. 3tears_models-0.14.0/LICENSE +21 -0
  3. 3tears_models-0.14.0/PKG-INFO +62 -0
  4. 3tears_models-0.14.0/README.md +34 -0
  5. 3tears_models-0.14.0/docs/migration-aibots.md +136 -0
  6. 3tears_models-0.14.0/docs/migration-metallm.md +188 -0
  7. 3tears_models-0.14.0/docs/migration-pentest-kit.md +171 -0
  8. 3tears_models-0.14.0/pyproject.toml +42 -0
  9. 3tears_models-0.14.0/src/threetears/models/__init__.py +175 -0
  10. 3tears_models-0.14.0/src/threetears/models/cache.py +99 -0
  11. 3tears_models-0.14.0/src/threetears/models/capabilities.py +406 -0
  12. 3tears_models-0.14.0/src/threetears/models/chunk_merging.py +70 -0
  13. 3tears_models-0.14.0/src/threetears/models/chunk_parsing.py +120 -0
  14. 3tears_models-0.14.0/src/threetears/models/circuit_breaker.py +378 -0
  15. 3tears_models-0.14.0/src/threetears/models/defaults.py +68 -0
  16. 3tears_models-0.14.0/src/threetears/models/enums.py +56 -0
  17. 3tears_models-0.14.0/src/threetears/models/errors.py +138 -0
  18. 3tears_models-0.14.0/src/threetears/models/factory.py +320 -0
  19. 3tears_models-0.14.0/src/threetears/models/preprocessing.py +223 -0
  20. 3tears_models-0.14.0/src/threetears/models/price_lookup.py +614 -0
  21. 3tears_models-0.14.0/src/threetears/models/providers/__init__.py +8 -0
  22. 3tears_models-0.14.0/src/threetears/models/providers/_claude_cli.py +98 -0
  23. 3tears_models-0.14.0/src/threetears/models/providers/_voyageai_compat.py +86 -0
  24. 3tears_models-0.14.0/src/threetears/models/providers/anthropic.py +549 -0
  25. 3tears_models-0.14.0/src/threetears/models/providers/image/__init__.py +22 -0
  26. 3tears_models-0.14.0/src/threetears/models/providers/image/a1111.py +119 -0
  27. 3tears_models-0.14.0/src/threetears/models/providers/image/comfyui.py +218 -0
  28. 3tears_models-0.14.0/src/threetears/models/providers/image/huggingface.py +94 -0
  29. 3tears_models-0.14.0/src/threetears/models/providers/image/modelslab.py +150 -0
  30. 3tears_models-0.14.0/src/threetears/models/providers/image/openai.py +189 -0
  31. 3tears_models-0.14.0/src/threetears/models/providers/openai.py +339 -0
  32. 3tears_models-0.14.0/src/threetears/models/providers/openrouter.py +550 -0
  33. 3tears_models-0.14.0/src/threetears/models/providers/voyageai.py +109 -0
  34. 3tears_models-0.14.0/src/threetears/models/providers/whisper.py +214 -0
  35. 3tears_models-0.14.0/src/threetears/models/registry.py +116 -0
  36. 3tears_models-0.14.0/src/threetears/models/registry_loader.py +210 -0
  37. 3tears_models-0.14.0/src/threetears/models/tool_name_translation.py +311 -0
  38. 3tears_models-0.14.0/src/threetears/models/tool_name_validation.py +186 -0
  39. 3tears_models-0.14.0/src/threetears/models/tracking.py +896 -0
  40. 3tears_models-0.14.0/tests/__init__.py +0 -0
  41. 3tears_models-0.14.0/tests/integration/__init__.py +0 -0
  42. 3tears_models-0.14.0/tests/integration/test_cache_registry_integration.py +118 -0
  43. 3tears_models-0.14.0/tests/integration/test_chat_pipeline.py +49 -0
  44. 3tears_models-0.14.0/tests/integration/test_circuit_breaker_integration.py +117 -0
  45. 3tears_models-0.14.0/tests/integration/test_embedding_pipeline.py +22 -0
  46. 3tears_models-0.14.0/tests/integration/test_error_translation_integration.py +203 -0
  47. 3tears_models-0.14.0/tests/test_import_cost.py +54 -0
  48. 3tears_models-0.14.0/tests/unit/__init__.py +0 -0
  49. 3tears_models-0.14.0/tests/unit/models/__init__.py +0 -0
  50. 3tears_models-0.14.0/tests/unit/models/_voyage_pricing_fixture.html +1 -0
  51. 3tears_models-0.14.0/tests/unit/models/test_cache.py +181 -0
  52. 3tears_models-0.14.0/tests/unit/models/test_callbacks.py +337 -0
  53. 3tears_models-0.14.0/tests/unit/models/test_capabilities.py +595 -0
  54. 3tears_models-0.14.0/tests/unit/models/test_chunk_merging.py +186 -0
  55. 3tears_models-0.14.0/tests/unit/models/test_chunk_parsing.py +234 -0
  56. 3tears_models-0.14.0/tests/unit/models/test_circuit_breaker.py +258 -0
  57. 3tears_models-0.14.0/tests/unit/models/test_enums.py +96 -0
  58. 3tears_models-0.14.0/tests/unit/models/test_errors.py +309 -0
  59. 3tears_models-0.14.0/tests/unit/models/test_factory.py +82 -0
  60. 3tears_models-0.14.0/tests/unit/models/test_preprocessing.py +175 -0
  61. 3tears_models-0.14.0/tests/unit/models/test_price_lookup.py +382 -0
  62. 3tears_models-0.14.0/tests/unit/models/test_provider_anthropic.py +560 -0
  63. 3tears_models-0.14.0/tests/unit/models/test_provider_image.py +519 -0
  64. 3tears_models-0.14.0/tests/unit/models/test_provider_openai.py +256 -0
  65. 3tears_models-0.14.0/tests/unit/models/test_provider_openrouter.py +931 -0
  66. 3tears_models-0.14.0/tests/unit/models/test_provider_voyageai.py +46 -0
  67. 3tears_models-0.14.0/tests/unit/models/test_provider_whisper.py +302 -0
  68. 3tears_models-0.14.0/tests/unit/models/test_registry.py +116 -0
  69. 3tears_models-0.14.0/tests/unit/models/test_tool_name_validation.py +263 -0
  70. 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