3tears-agent-tools 0.14.0__py3-none-any.whl
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_agent_tools-0.14.0.dist-info/METADATA +207 -0
- 3tears_agent_tools-0.14.0.dist-info/RECORD +66 -0
- 3tears_agent_tools-0.14.0.dist-info/WHEEL +4 -0
- 3tears_agent_tools-0.14.0.dist-info/licenses/LICENSE +21 -0
- threetears/agent/tools/__init__.py +225 -0
- threetears/agent/tools/_coercion.py +102 -0
- threetears/agent/tools/aliases.py +280 -0
- threetears/agent/tools/base_tool.py +223 -0
- threetears/agent/tools/bootstrap.py +292 -0
- threetears/agent/tools/bridge.py +123 -0
- threetears/agent/tools/builtin/__init__.py +160 -0
- threetears/agent/tools/builtin/analyze_media.py +748 -0
- threetears/agent/tools/builtin/calculator.py +206 -0
- threetears/agent/tools/builtin/context_recall.py +168 -0
- threetears/agent/tools/builtin/current_date.py +156 -0
- threetears/agent/tools/builtin/dictionary.py +183 -0
- threetears/agent/tools/builtin/image_generation.py +298 -0
- threetears/agent/tools/builtin/image_prep.py +229 -0
- threetears/agent/tools/builtin/timezone_converter.py +188 -0
- threetears/agent/tools/builtin/unit_converter.py +141 -0
- threetears/agent/tools/builtin/web_fetch.py +243 -0
- threetears/agent/tools/builtin/web_search.py +159 -0
- threetears/agent/tools/call_scope.py +202 -0
- threetears/agent/tools/chunker.py +235 -0
- threetears/agent/tools/collections.py +521 -0
- threetears/agent/tools/config.py +127 -0
- threetears/agent/tools/consume.py +217 -0
- threetears/agent/tools/context.py +851 -0
- threetears/agent/tools/context_envelope.py +190 -0
- threetears/agent/tools/deliver.py +216 -0
- threetears/agent/tools/document.py +1031 -0
- threetears/agent/tools/engagement.py +139 -0
- threetears/agent/tools/engagement_resolver.py +284 -0
- threetears/agent/tools/entities.py +55 -0
- threetears/agent/tools/events.py +75 -0
- threetears/agent/tools/executor.py +133 -0
- threetears/agent/tools/graph_nodes.py +236 -0
- threetears/agent/tools/langchain_adapter.py +135 -0
- threetears/agent/tools/mcp.py +145 -0
- threetears/agent/tools/migrations/__init__.py +94 -0
- threetears/agent/tools/migrations/v001_create_context_items_table.py +61 -0
- threetears/agent/tools/migrations/v002_datetime_to_datetimetz.py +153 -0
- threetears/agent/tools/migrations/v003_align_context_items_shape.py +166 -0
- threetears/agent/tools/migrations/v004_tool_result_dedup_index.py +67 -0
- threetears/agent/tools/object_resolver.py +256 -0
- threetears/agent/tools/platform_migrations/__init__.py +80 -0
- threetears/agent/tools/platform_migrations/v001_add_tool_eligibility_columns.py +65 -0
- threetears/agent/tools/produce.py +206 -0
- threetears/agent/tools/protocols.py +31 -0
- threetears/agent/tools/py.typed +0 -0
- threetears/agent/tools/registry.py +81 -0
- threetears/agent/tools/report.py +539 -0
- threetears/agent/tools/reports/__init__.py +22 -0
- threetears/agent/tools/reports/markdown_compiler.py +271 -0
- threetears/agent/tools/reports/mermaid_generator.py +190 -0
- threetears/agent/tools/reports/pdf_renderer.py +152 -0
- threetears/agent/tools/reports/severity.py +93 -0
- threetears/agent/tools/router.py +228 -0
- threetears/agent/tools/serve.py +248 -0
- threetears/agent/tools/server.py +2086 -0
- threetears/agent/tools/strategy.py +302 -0
- threetears/agent/tools/todo.py +303 -0
- threetears/agent/tools/tool_llm_dispatch.py +233 -0
- threetears/agent/tools/types.py +18 -0
- threetears/agent/tools/utils.py +12 -0
- threetears/agent/tools/workflow.py +130 -0
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: 3tears-agent-tools
|
|
3
|
+
Version: 0.14.0
|
|
4
|
+
Summary: Tool framework for LLM agents -- routing, execution, and builtin tools
|
|
5
|
+
Project-URL: Repository, https://github.com/pacepace/3tears
|
|
6
|
+
Author: pace
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Framework :: AsyncIO
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
14
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
15
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
16
|
+
Classifier: Typing :: Typed
|
|
17
|
+
Requires-Python: >=3.14
|
|
18
|
+
Requires-Dist: 3tears
|
|
19
|
+
Requires-Dist: 3tears-agent-audit
|
|
20
|
+
Requires-Dist: 3tears-agent-memory
|
|
21
|
+
Requires-Dist: 3tears-langgraph
|
|
22
|
+
Requires-Dist: 3tears-media-contracts
|
|
23
|
+
Requires-Dist: 3tears-nats
|
|
24
|
+
Requires-Dist: 3tears-observe
|
|
25
|
+
Requires-Dist: httpx
|
|
26
|
+
Requires-Dist: langchain-core
|
|
27
|
+
Provides-Extra: all
|
|
28
|
+
Requires-Dist: openpyxl; extra == 'all'
|
|
29
|
+
Requires-Dist: pdf2image; extra == 'all'
|
|
30
|
+
Requires-Dist: pillow; extra == 'all'
|
|
31
|
+
Requires-Dist: pint; extra == 'all'
|
|
32
|
+
Requires-Dist: pymupdf; extra == 'all'
|
|
33
|
+
Requires-Dist: pytesseract; extra == 'all'
|
|
34
|
+
Requires-Dist: python-docx; extra == 'all'
|
|
35
|
+
Requires-Dist: simpleeval; extra == 'all'
|
|
36
|
+
Requires-Dist: trafilatura; extra == 'all'
|
|
37
|
+
Provides-Extra: calculator
|
|
38
|
+
Requires-Dist: simpleeval; extra == 'calculator'
|
|
39
|
+
Provides-Extra: document
|
|
40
|
+
Requires-Dist: openpyxl; extra == 'document'
|
|
41
|
+
Requires-Dist: pymupdf; extra == 'document'
|
|
42
|
+
Requires-Dist: python-docx; extra == 'document'
|
|
43
|
+
Provides-Extra: docx
|
|
44
|
+
Requires-Dist: python-docx; extra == 'docx'
|
|
45
|
+
Provides-Extra: fetch
|
|
46
|
+
Requires-Dist: trafilatura; extra == 'fetch'
|
|
47
|
+
Provides-Extra: ocr
|
|
48
|
+
Requires-Dist: pdf2image; extra == 'ocr'
|
|
49
|
+
Requires-Dist: pytesseract; extra == 'ocr'
|
|
50
|
+
Provides-Extra: pdf
|
|
51
|
+
Requires-Dist: pymupdf; extra == 'pdf'
|
|
52
|
+
Provides-Extra: units
|
|
53
|
+
Requires-Dist: pint; extra == 'units'
|
|
54
|
+
Provides-Extra: vision
|
|
55
|
+
Requires-Dist: pillow; extra == 'vision'
|
|
56
|
+
Provides-Extra: xlsx
|
|
57
|
+
Requires-Dist: openpyxl; extra == 'xlsx'
|
|
58
|
+
Description-Content-Type: text/markdown
|
|
59
|
+
|
|
60
|
+
# 3tears Agent Tools
|
|
61
|
+
|
|
62
|
+
Tool framework for LLM agents. Provides tool routing, execution, context management, MCP integration, and a set of builtin tools.
|
|
63
|
+
|
|
64
|
+
Part of the [3tears](https://github.com/pacepace/3tears) framework.
|
|
65
|
+
|
|
66
|
+
## ToolServer baseline audit
|
|
67
|
+
|
|
68
|
+
`ToolServer.handle_call` stamps every dispatch with a unified `AuditEvent` envelope (`event_type='tool.call'`) via `threetears.agent.audit.publish_audit`. The baseline emission fires in a `finally` block so success, failure (tool returned `success=False`), and error (tool raised) outcomes all produce a row. Identity axes carry from the active `ToolCallScope` (`actor_user_id`, `calling_agent_id`, `owner_agent_id`, `customer_id`, `correlation_id`); `resource_namespace_id` / `resource_namespace_type` stay `None` at the baseline layer since the tool resolves its target inside `execute`. Per-tool additive events (e.g. `workspace.fs_write`) still publish via `publish_audit` and ride alongside the baseline row under the same `correlation_id`. The `(correlation_id, event_type)` partial unique index on `platform_audit.audit_events` keeps them distinct. Emission is fire-and-forget: NATS publish failures log WARN and never taint the tool's response.
|
|
69
|
+
|
|
70
|
+
## Tool-as-namespace emission
|
|
71
|
+
|
|
72
|
+
Tool namespace materialization is platform-owned. `ToolServer.publish_registration` writes the `RegistrationManifest` (carrying `pod_id` + `tools` + the `owner_agent_id` / `customer_id` envelope fields), and a platform-side namespace emitter subscribes to `{ns}.tools.register` and upserts one `platform.namespaces` row of type `tool` per tool. This is the sole writer in the platform.
|
|
73
|
+
|
|
74
|
+
Agent-spun ToolServers stamp `agent_id` + `customer_id` on the `RegistrationManifest` so the emitter lands rows with the right owner scope; platform-built-in pods (admin tool server, datasource tool pod) leave both `None` and the row lands with NULL owner columns (admitted under the widened `namespaces_row_scope_customer_ck` carve-out for `tool` type alongside `system` / `model`).
|
|
75
|
+
|
|
76
|
+
The canonical `name` shape is `tools.<sanitized-mcp>.<sanitized-version>` (per `build_namespace_name`); `metadata` carries the pre-sanitized natural-identity fields `mcp_name` / `mcp_version` / `pod_id` so downstream pattern matching (platform access materializer agent.yaml `access.tools` patterns + registry authorizer canonical-name lookup) does not need to reverse the sanitization rules. Deterministic `uuid5` derived from `(mcp_name, version, owner_agent_id_hex)` keeps concurrent emitters race-safe via `ON CONFLICT (id) DO UPDATE`.
|
|
77
|
+
|
|
78
|
+
The `ToolServer.register_tool` / `deregister_tool` helpers still emit through an injected `namespace_collection` for callers that wire one explicitly, but in production deployments the platform-side emitter is the source of truth and the in-process emission is redundant.
|
|
79
|
+
|
|
80
|
+
## Installation
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
pip install 3tears-agent-tools
|
|
84
|
+
|
|
85
|
+
# Optional extras for builtin tools
|
|
86
|
+
pip install "3tears-agent-tools[calculator]" # simpleeval
|
|
87
|
+
pip install "3tears-agent-tools[units]" # pint
|
|
88
|
+
pip install "3tears-agent-tools[fetch]" # trafilatura
|
|
89
|
+
pip install "3tears-agent-tools[document]" # PyMuPDF, python-docx, openpyxl
|
|
90
|
+
pip install "3tears-agent-tools[all]" # everything
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Components
|
|
94
|
+
|
|
95
|
+
### ToolRouter
|
|
96
|
+
|
|
97
|
+
Routes user messages to the appropriate tool using a lightweight LLM call. Includes recall-intent detection to avoid re-invoking tools when users ask about previous results.
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
from threetears.agent.tools import ToolRouter, is_recall_intent
|
|
101
|
+
|
|
102
|
+
# Quick check -- no LLM call needed
|
|
103
|
+
if is_recall_intent("show me what the calculator said"):
|
|
104
|
+
# User wants to recall, not invoke
|
|
105
|
+
|
|
106
|
+
# Full routing with LLM
|
|
107
|
+
router = ToolRouter(chat_model)
|
|
108
|
+
decision = await router.route(user_message, tool_descriptions)
|
|
109
|
+
# decision.tool_name, decision.reasoning
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### ToolExecutor
|
|
113
|
+
|
|
114
|
+
Invokes a tool-LLM: sends the user message to a secondary model configured for a specific task.
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
from threetears.agent.tools import ToolExecutor
|
|
118
|
+
|
|
119
|
+
executor = ToolExecutor()
|
|
120
|
+
result = await executor.invoke_with_tools(
|
|
121
|
+
chat_model=tool_model,
|
|
122
|
+
user_message="What is 42 * 17?",
|
|
123
|
+
tools=[calculator_tool],
|
|
124
|
+
tool_name="calculator",
|
|
125
|
+
)
|
|
126
|
+
# result.content, result.tool_calls
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### ToolContextManager
|
|
130
|
+
|
|
131
|
+
Tracks tool invocations and results across a conversation for recall support.
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
from threetears.agent.tools import ToolContextManager
|
|
135
|
+
|
|
136
|
+
ctx = ToolContextManager()
|
|
137
|
+
await ctx.record_invocation("calculator", "42 * 17", "714")
|
|
138
|
+
await ctx.get_recall_context("calculator") # Returns formatted recall string
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### McpClient
|
|
142
|
+
|
|
143
|
+
MCP (Model Context Protocol) integration for connecting to external tool servers.
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
from threetears.agent.tools import McpClient
|
|
147
|
+
|
|
148
|
+
async with McpClient(server_config) as client:
|
|
149
|
+
tools = await client.list_tools()
|
|
150
|
+
result = await client.invoke_tool("tool_name", {"param": "value"})
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Builtin Tools
|
|
154
|
+
|
|
155
|
+
Register all builtin tools at once:
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
from threetears.agent.tools import register_builtins, ToolRegistry
|
|
159
|
+
|
|
160
|
+
registry = ToolRegistry()
|
|
161
|
+
register_builtins(registry)
|
|
162
|
+
# Registers: calculator, unit_converter, dice_roller, date_time,
|
|
163
|
+
# random_number, web_fetch, text_transform, parse_document
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Todo Tools
|
|
167
|
+
|
|
168
|
+
Todo list management behind a storage protocol:
|
|
169
|
+
|
|
170
|
+
```python
|
|
171
|
+
from threetears.agent.tools import TodoStorage, load_todo_tools_from_storage
|
|
172
|
+
|
|
173
|
+
class MyTodoStorage(TodoStorage):
|
|
174
|
+
async def add(self, conv_id, user_id, title, list_name, msg_id) -> dict: ...
|
|
175
|
+
async def list_all(self, conv_id) -> list[dict]: ...
|
|
176
|
+
# ... other methods
|
|
177
|
+
|
|
178
|
+
tools = load_todo_tools_from_storage(my_storage, snapshot_callback=on_snapshot)
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Protocols
|
|
182
|
+
|
|
183
|
+
For media-related capabilities, implement these protocols:
|
|
184
|
+
|
|
185
|
+
```python
|
|
186
|
+
from threetears.agent.tools import (
|
|
187
|
+
ImageGenerationBackend,
|
|
188
|
+
MediaStorage,
|
|
189
|
+
VisionProvider,
|
|
190
|
+
TranscriptionProvider,
|
|
191
|
+
)
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Document Parsing
|
|
195
|
+
|
|
196
|
+
Parse PDF, DOCX, XLSX, and plain text with optional OCR:
|
|
197
|
+
|
|
198
|
+
```python
|
|
199
|
+
from threetears.agent.tools import parse_document, OcrConfig
|
|
200
|
+
|
|
201
|
+
result = await parse_document(
|
|
202
|
+
file_bytes=data,
|
|
203
|
+
filename="report.pdf",
|
|
204
|
+
ocr_config=OcrConfig(enabled=True),
|
|
205
|
+
)
|
|
206
|
+
# result.sections -- list of DocumentSection with title, content, page numbers
|
|
207
|
+
```
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
threetears/agent/tools/__init__.py,sha256=z9TTY8-lAB201cGvlRyp2zBAqdWxMJ-qItu2U_g44vQ,10373
|
|
2
|
+
threetears/agent/tools/_coercion.py,sha256=nXtjsmWP9vNJ4aI1GHwPe9kt__RfeFG5QkHgUIFECuE,3863
|
|
3
|
+
threetears/agent/tools/aliases.py,sha256=lCYhlZJUXRlqUHTWBprwI1QpJVxWcPkvJm29MN0RU7U,10622
|
|
4
|
+
threetears/agent/tools/base_tool.py,sha256=GgUQnTSTMMwcowWIjq46GlXWUwpHLNxeglBrBmd8HCs,8260
|
|
5
|
+
threetears/agent/tools/bootstrap.py,sha256=1LIV7PfrcUcESJWiCUYMjBPruR6u9cd2l4cYEpn3FM4,11166
|
|
6
|
+
threetears/agent/tools/bridge.py,sha256=el9iVwFymmCbY6N8aaupTMfSxRu5ZNWVKmFYbxfWbnc,4361
|
|
7
|
+
threetears/agent/tools/call_scope.py,sha256=VauOPb4bGC_rRtd48fsy4zYXjFJiATzZ4JmdZ4UnRdc,8517
|
|
8
|
+
threetears/agent/tools/chunker.py,sha256=0m8DHxAWxYEawEkhS5EDfOVKxGFyaqL60BMOg-OpVes,6793
|
|
9
|
+
threetears/agent/tools/collections.py,sha256=4qKaEtitF8nXxcp4RYkCrT93fkH03qIx-0BhdzTwXR8,20669
|
|
10
|
+
threetears/agent/tools/config.py,sha256=N2zQ1qDnDFtnRi59QJnK7neoKMXbbA9pbrh31Wv8oWU,3878
|
|
11
|
+
threetears/agent/tools/consume.py,sha256=EocFDA7mCJfgAbU6caiOc2TuwDz0cJ21CU5rsfXD1lM,10132
|
|
12
|
+
threetears/agent/tools/context.py,sha256=IV6NFfn4GI0nSZBQhaPrCyh-LzTSW8q2sSmmUoM_P40,34431
|
|
13
|
+
threetears/agent/tools/context_envelope.py,sha256=6ksSLp2WIibAFpzrBiJFfpxpR62OM28qYL4au0ITuhc,9228
|
|
14
|
+
threetears/agent/tools/deliver.py,sha256=13XBGLSyDKiaka1slXBLy7viJUWnMQw0CK0h1DBtd5M,8510
|
|
15
|
+
threetears/agent/tools/document.py,sha256=eo-4tka10bF14ofixS2T8ReICW4l8WxkD7kZxRkvBHE,32425
|
|
16
|
+
threetears/agent/tools/engagement.py,sha256=1Gvb3iMWVRqXrVArDvfNOO5bF1vE-XsUmZHAf4u59FI,6950
|
|
17
|
+
threetears/agent/tools/engagement_resolver.py,sha256=MefjDsmivT8msOXC6wAbOjO7iqCoEz-JT7n29H-2PME,12422
|
|
18
|
+
threetears/agent/tools/entities.py,sha256=zHVoyeXYLg_AP9hMN00JcPNbzHPdZMR-Y1uwPqKQJwQ,1788
|
|
19
|
+
threetears/agent/tools/events.py,sha256=hfhWW2G-V5MkCzPyyy4LV1nSPBb1gRMpxKBhQTaKQ0c,3070
|
|
20
|
+
threetears/agent/tools/executor.py,sha256=ZQyDlhA0TOt0xkv7ZI-f6VYV-_tB5uMhxJMeVhjzzpc,4405
|
|
21
|
+
threetears/agent/tools/graph_nodes.py,sha256=7zAFLYuR3M-oVLAMpU3Z9FcVOrlan02m8NdjWCBGUFY,8538
|
|
22
|
+
threetears/agent/tools/langchain_adapter.py,sha256=YRlPBex2HV1moYYAq8kDmMjLQkKhbuQp2fjl3v5bHoA,6228
|
|
23
|
+
threetears/agent/tools/mcp.py,sha256=bMoqh4gwasiWV07UWM4PlawpLA8ciAAAK2lkJOvt2ng,4507
|
|
24
|
+
threetears/agent/tools/object_resolver.py,sha256=vfIXsQCLHiC7l7edZ1o6a0jO91MOgqvonPw2LTLrRxk,10795
|
|
25
|
+
threetears/agent/tools/produce.py,sha256=pe3C9taoh-FHh55uWIXKIjHPeP17d7m3kRcrj11QafU,8435
|
|
26
|
+
threetears/agent/tools/protocols.py,sha256=HOWu74BuvRyWwHsYBYnSDy62SQXOpEhYgX8kYgJctL8,834
|
|
27
|
+
threetears/agent/tools/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
28
|
+
threetears/agent/tools/registry.py,sha256=MuPn6UObIzqnaIzcwmnr3NB9RJuepfq4wzkyDmb3euU,2670
|
|
29
|
+
threetears/agent/tools/report.py,sha256=9GnrdizPw07WqxZcU_uW7i9BNstsb9IPv-VgZI_InXQ,21136
|
|
30
|
+
threetears/agent/tools/router.py,sha256=3rx_JU-WHZf8h0SHrA9TV2uZhZWfi_lnIKllM_JekSo,8054
|
|
31
|
+
threetears/agent/tools/serve.py,sha256=pSi5Nr7qChGaGwwdcF-YThPgCmTor3NtcQAAL_-eJsI,9054
|
|
32
|
+
threetears/agent/tools/server.py,sha256=HuQUbZ5xx5NB-dRIcSqqJNzPuy0KRkyv0IADNIqi9tU,96435
|
|
33
|
+
threetears/agent/tools/strategy.py,sha256=-jVguOQDBzHfrhc0EsPhBmnqoDxEZZl1VpkNZhV90sE,14719
|
|
34
|
+
threetears/agent/tools/todo.py,sha256=HiFuXw4niioYKq9Rtq1qJFest0miO6cTREIAElMuj84,10823
|
|
35
|
+
threetears/agent/tools/tool_llm_dispatch.py,sha256=ZvbAPHtcxMoHUaB7RXnRF_qC_NkhuuF5_ZPb0dlktcY,8965
|
|
36
|
+
threetears/agent/tools/types.py,sha256=wiaJpmY3fZQH7lkrAY8n8ncbr8cHQUKXr6nZDAqXT8Y,457
|
|
37
|
+
threetears/agent/tools/utils.py,sha256=Ata2Tli2vflXtfArLuG-DzBTNRuc2ZprBkoowksZBds,295
|
|
38
|
+
threetears/agent/tools/workflow.py,sha256=q7fVPxWlO0Istg6bjwuo2rIPq6-KSe9DND4GrVc-TSA,4699
|
|
39
|
+
threetears/agent/tools/builtin/__init__.py,sha256=8zeSdSbnlS_CnTiHCZepkzRGUzKGkl_9fsi8Yhz_ggM,6230
|
|
40
|
+
threetears/agent/tools/builtin/analyze_media.py,sha256=UXeUZp3PpxFvUVDR3_PWxEGWlrh_Q_-wdxxTfsaio4c,27556
|
|
41
|
+
threetears/agent/tools/builtin/calculator.py,sha256=UoUiYD90vt2lM3IsVpeo_4WQplGHzWtQZ0C5BFeqlno,6567
|
|
42
|
+
threetears/agent/tools/builtin/context_recall.py,sha256=sMMCzjVhW6LuSt8SzzyQ6pENaE--hIjchHUfZfDTN1k,6066
|
|
43
|
+
threetears/agent/tools/builtin/current_date.py,sha256=Hl2aW_p2vS6rnFreu_lhUeYxqWwzZH2Zcd_EhdcNFo0,5735
|
|
44
|
+
threetears/agent/tools/builtin/dictionary.py,sha256=P9KrTuhVUO6v3c3vWyou8CoJlCfwJRuzSQNIrMnZeGM,5628
|
|
45
|
+
threetears/agent/tools/builtin/image_generation.py,sha256=pyZjVKPz-SHFH7ZCHfINHjNTj1I7e_LcXPRiEJhibbc,11397
|
|
46
|
+
threetears/agent/tools/builtin/image_prep.py,sha256=qn3f-Ky7MNktVnXq_Lv3m9DcZLLQ18uDvjH8J6A4CIo,7013
|
|
47
|
+
threetears/agent/tools/builtin/timezone_converter.py,sha256=ylTUSs5PHbz0Veea_X6CnXI3t95PW5eYyjxOZ5llrwQ,6444
|
|
48
|
+
threetears/agent/tools/builtin/unit_converter.py,sha256=7aRGm0opsnFMHEE3OtZBWs18BOK8v7sECzDthBAraO0,4530
|
|
49
|
+
threetears/agent/tools/builtin/web_fetch.py,sha256=hH8UEHirtB1nCcYYGvUFSS3h7SIJyxjoBMynCbw7wyY,7796
|
|
50
|
+
threetears/agent/tools/builtin/web_search.py,sha256=BIN_6WWwOOMGSuwyU_wUXc6OMF-kA1qrZqn589ZacKg,4862
|
|
51
|
+
threetears/agent/tools/migrations/__init__.py,sha256=_Hpb6V6jABgT_Vh3edu0C45tUjafcxFbl1_5MXdsCfg,3482
|
|
52
|
+
threetears/agent/tools/migrations/v001_create_context_items_table.py,sha256=BeI3A-wWQblHBSl0s_ryPxgj6EYYb5TEWlEpeEZUFQk,1923
|
|
53
|
+
threetears/agent/tools/migrations/v002_datetime_to_datetimetz.py,sha256=K-kj6H61fbB0SeyKnfiNw7SzXaZ4gVlleIU0YGtFh5U,5184
|
|
54
|
+
threetears/agent/tools/migrations/v003_align_context_items_shape.py,sha256=IvC0zPQC-nZ8Rtp4OE6tvZk40rnuSh3m18anT-yXWGU,7188
|
|
55
|
+
threetears/agent/tools/migrations/v004_tool_result_dedup_index.py,sha256=AoJ7Scy8PvOeHNoITwByB6QApbUZ_uBGcm_l2HEjd1w,2534
|
|
56
|
+
threetears/agent/tools/platform_migrations/__init__.py,sha256=WsXVhpWPLEEyn1TICYhgNyMTm_lGMCN8BztLNWaapUM,2893
|
|
57
|
+
threetears/agent/tools/platform_migrations/v001_add_tool_eligibility_columns.py,sha256=cRPfxB1A5RGCJjg5S9SFnK0YtjkQmHOV2jUjC7PTAFs,2384
|
|
58
|
+
threetears/agent/tools/reports/__init__.py,sha256=myR_uloZkUSbVA-vJzJLrxc9wV9u1fgwPNZ0ZRZJq68,761
|
|
59
|
+
threetears/agent/tools/reports/markdown_compiler.py,sha256=9evSspQ4pxSCVRxTVV2iudzBy8pH1BtFFQaisoZe4MQ,9352
|
|
60
|
+
threetears/agent/tools/reports/mermaid_generator.py,sha256=rao_f423W90kcfQCs6nWmm1LQRwfKBYNxdtM4UyeS2Y,6803
|
|
61
|
+
threetears/agent/tools/reports/pdf_renderer.py,sha256=7CltWlimHyavDtxnjD-rFPZONk9GBS2wGIlMvpOsZRA,4655
|
|
62
|
+
threetears/agent/tools/reports/severity.py,sha256=cK5XRXTgo1M9M31Q7jqIOwJ0UJfz3dT-M3qv-VJkqCY,2614
|
|
63
|
+
3tears_agent_tools-0.14.0.dist-info/METADATA,sha256=5mUBkjY1_h_coWqtl6s-en-j9m1CYN_OMNCpSrJ1IlU,8290
|
|
64
|
+
3tears_agent_tools-0.14.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
65
|
+
3tears_agent_tools-0.14.0.dist-info/licenses/LICENSE,sha256=7GWEoEOcFJenZLt4LDzqH2K7QLxo_2m8rzG7Vv8VGXo,1066
|
|
66
|
+
3tears_agent_tools-0.14.0.dist-info/RECORD,,
|
|
@@ -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,225 @@
|
|
|
1
|
+
"""3tears agent-tools: tool registry, context management, and built-in tools."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
# Version derived from pyproject.toml so the metadata is the single
|
|
6
|
+
# source of truth -- a future release that bumps pyproject without
|
|
7
|
+
# updating ``__init__.py`` can't drift the runtime ``__version__``.
|
|
8
|
+
# The except guard handles the rare case where the package isn't
|
|
9
|
+
# installed via importlib.metadata (e.g. running directly from a
|
|
10
|
+
# checked-out source tree without ``uv sync``); the fallback keeps
|
|
11
|
+
# imports working but reports ``unknown`` rather than crashing.
|
|
12
|
+
from importlib.metadata import PackageNotFoundError as _PackageNotFoundError
|
|
13
|
+
from importlib.metadata import version as _version
|
|
14
|
+
from typing import TYPE_CHECKING
|
|
15
|
+
|
|
16
|
+
try:
|
|
17
|
+
__version__ = _version("3tears-agent-tools")
|
|
18
|
+
except _PackageNotFoundError: # pragma: no cover - dev fallback
|
|
19
|
+
__version__ = "unknown"
|
|
20
|
+
|
|
21
|
+
# lazy public API (PEP 562). the package namespace no longer imports its
|
|
22
|
+
# implementation modules eagerly: importing this package (or any of its
|
|
23
|
+
# submodules) costs only this file, and each public attribute resolves
|
|
24
|
+
# its defining module on first access. the TYPE_CHECKING block carries
|
|
25
|
+
# the real imports so mypy and IDEs see the full statically-typed API;
|
|
26
|
+
# the _LAZY map is the runtime equivalent. the three-way agreement
|
|
27
|
+
# between __all__, _LAZY, and the TYPE_CHECKING block is pinned by the
|
|
28
|
+
# package's lazy-surface consistency test.
|
|
29
|
+
# decision record: docs/separate-concerns-decisions.md (hand-rolled
|
|
30
|
+
# PEP 562 over lazy_loader -- zero added runtime deps, no stub drift).
|
|
31
|
+
if TYPE_CHECKING:
|
|
32
|
+
from threetears.agent.tools.builtin import register_builtins
|
|
33
|
+
from threetears.agent.tools.builtin.analyze_media import AnalyzerConfig, create_analyze_media_tool
|
|
34
|
+
from threetears.agent.tools.builtin.image_generation import (
|
|
35
|
+
ImageGenerationContext,
|
|
36
|
+
ImageGenerationInput,
|
|
37
|
+
create_image_generation_tool,
|
|
38
|
+
)
|
|
39
|
+
from threetears.agent.tools.chunker import (
|
|
40
|
+
ChunkResult,
|
|
41
|
+
ChunkStrategy,
|
|
42
|
+
chunk_by_headers,
|
|
43
|
+
chunk_by_lines,
|
|
44
|
+
chunk_by_sections,
|
|
45
|
+
chunk_content,
|
|
46
|
+
register_chunk_strategy,
|
|
47
|
+
)
|
|
48
|
+
from threetears.agent.tools.collections import (
|
|
49
|
+
ContextItemCollection,
|
|
50
|
+
context_items_table,
|
|
51
|
+
migrate_context_items_schema,
|
|
52
|
+
)
|
|
53
|
+
from threetears.agent.tools.context import ToolContextManager
|
|
54
|
+
from threetears.agent.tools.document import (
|
|
55
|
+
DocumentResult,
|
|
56
|
+
DocumentSection,
|
|
57
|
+
OcrConfig,
|
|
58
|
+
ParseDocumentInput,
|
|
59
|
+
create_parse_document_tool,
|
|
60
|
+
detect_mime_from_filename,
|
|
61
|
+
parse_document,
|
|
62
|
+
)
|
|
63
|
+
from threetears.agent.tools.entities import ContextItemEntity
|
|
64
|
+
from threetears.agent.tools.events import TodosChangedEvent
|
|
65
|
+
from threetears.agent.tools.executor import ToolExecutionResult, ToolExecutor
|
|
66
|
+
from threetears.agent.tools.mcp import McpClient, McpTool, McpToolResult
|
|
67
|
+
from threetears.agent.tools.protocols import (
|
|
68
|
+
GeneratedImage,
|
|
69
|
+
ImageGenerationBackend,
|
|
70
|
+
MediaInfo,
|
|
71
|
+
MediaStorage,
|
|
72
|
+
TextProvider,
|
|
73
|
+
TranscriptionProvider,
|
|
74
|
+
VisionProvider,
|
|
75
|
+
)
|
|
76
|
+
from threetears.agent.tools.registry import ToolRegistry
|
|
77
|
+
from threetears.agent.tools.router import DEFAULT_ROUTING_PROMPT, ToolRouter, ToolRoutingDecision, is_recall_intent
|
|
78
|
+
from threetears.agent.tools.todo import TodoStorage, load_todo_tools as load_todo_tools_from_storage
|
|
79
|
+
from threetears.agent.tools.tool_llm_dispatch import (
|
|
80
|
+
InvokeToolLlmInput,
|
|
81
|
+
ToolLlmInvocation,
|
|
82
|
+
ToolLlmResolver,
|
|
83
|
+
load_tool_llm_dispatch,
|
|
84
|
+
)
|
|
85
|
+
from threetears.agent.tools.types import ChatModelFactory
|
|
86
|
+
from threetears.agent.tools.workflow import load_workflow_tools
|
|
87
|
+
|
|
88
|
+
# public attribute -> (defining module, attribute name in that module)
|
|
89
|
+
_LAZY: dict[str, tuple[str, str]] = {
|
|
90
|
+
"AnalyzerConfig": ("threetears.agent.tools.builtin.analyze_media", "AnalyzerConfig"),
|
|
91
|
+
"ChatModelFactory": ("threetears.agent.tools.types", "ChatModelFactory"),
|
|
92
|
+
"ChunkResult": ("threetears.agent.tools.chunker", "ChunkResult"),
|
|
93
|
+
"ChunkStrategy": ("threetears.agent.tools.chunker", "ChunkStrategy"),
|
|
94
|
+
"ContextItemCollection": ("threetears.agent.tools.collections", "ContextItemCollection"),
|
|
95
|
+
"ContextItemEntity": ("threetears.agent.tools.entities", "ContextItemEntity"),
|
|
96
|
+
"DEFAULT_ROUTING_PROMPT": ("threetears.agent.tools.router", "DEFAULT_ROUTING_PROMPT"),
|
|
97
|
+
"DocumentResult": ("threetears.agent.tools.document", "DocumentResult"),
|
|
98
|
+
"DocumentSection": ("threetears.agent.tools.document", "DocumentSection"),
|
|
99
|
+
"GeneratedImage": ("threetears.agent.tools.protocols", "GeneratedImage"),
|
|
100
|
+
"ImageGenerationBackend": ("threetears.agent.tools.protocols", "ImageGenerationBackend"),
|
|
101
|
+
"ImageGenerationContext": ("threetears.agent.tools.builtin.image_generation", "ImageGenerationContext"),
|
|
102
|
+
"ImageGenerationInput": ("threetears.agent.tools.builtin.image_generation", "ImageGenerationInput"),
|
|
103
|
+
"InvokeToolLlmInput": ("threetears.agent.tools.tool_llm_dispatch", "InvokeToolLlmInput"),
|
|
104
|
+
"McpClient": ("threetears.agent.tools.mcp", "McpClient"),
|
|
105
|
+
"McpTool": ("threetears.agent.tools.mcp", "McpTool"),
|
|
106
|
+
"McpToolResult": ("threetears.agent.tools.mcp", "McpToolResult"),
|
|
107
|
+
"MediaInfo": ("threetears.agent.tools.protocols", "MediaInfo"),
|
|
108
|
+
"MediaStorage": ("threetears.agent.tools.protocols", "MediaStorage"),
|
|
109
|
+
"OcrConfig": ("threetears.agent.tools.document", "OcrConfig"),
|
|
110
|
+
"ParseDocumentInput": ("threetears.agent.tools.document", "ParseDocumentInput"),
|
|
111
|
+
"TextProvider": ("threetears.agent.tools.protocols", "TextProvider"),
|
|
112
|
+
"TodoStorage": ("threetears.agent.tools.todo", "TodoStorage"),
|
|
113
|
+
"TodosChangedEvent": ("threetears.agent.tools.events", "TodosChangedEvent"),
|
|
114
|
+
"ToolContextManager": ("threetears.agent.tools.context", "ToolContextManager"),
|
|
115
|
+
"ToolExecutionResult": ("threetears.agent.tools.executor", "ToolExecutionResult"),
|
|
116
|
+
"ToolExecutor": ("threetears.agent.tools.executor", "ToolExecutor"),
|
|
117
|
+
"ToolLlmInvocation": ("threetears.agent.tools.tool_llm_dispatch", "ToolLlmInvocation"),
|
|
118
|
+
"ToolLlmResolver": ("threetears.agent.tools.tool_llm_dispatch", "ToolLlmResolver"),
|
|
119
|
+
"ToolRegistry": ("threetears.agent.tools.registry", "ToolRegistry"),
|
|
120
|
+
"ToolRouter": ("threetears.agent.tools.router", "ToolRouter"),
|
|
121
|
+
"ToolRoutingDecision": ("threetears.agent.tools.router", "ToolRoutingDecision"),
|
|
122
|
+
"TranscriptionProvider": ("threetears.agent.tools.protocols", "TranscriptionProvider"),
|
|
123
|
+
"VisionProvider": ("threetears.agent.tools.protocols", "VisionProvider"),
|
|
124
|
+
"chunk_by_headers": ("threetears.agent.tools.chunker", "chunk_by_headers"),
|
|
125
|
+
"chunk_by_lines": ("threetears.agent.tools.chunker", "chunk_by_lines"),
|
|
126
|
+
"chunk_by_sections": ("threetears.agent.tools.chunker", "chunk_by_sections"),
|
|
127
|
+
"chunk_content": ("threetears.agent.tools.chunker", "chunk_content"),
|
|
128
|
+
"context_items_table": ("threetears.agent.tools.collections", "context_items_table"),
|
|
129
|
+
"create_analyze_media_tool": ("threetears.agent.tools.builtin.analyze_media", "create_analyze_media_tool"),
|
|
130
|
+
"create_image_generation_tool": ("threetears.agent.tools.builtin.image_generation", "create_image_generation_tool"),
|
|
131
|
+
"create_parse_document_tool": ("threetears.agent.tools.document", "create_parse_document_tool"),
|
|
132
|
+
"detect_mime_from_filename": ("threetears.agent.tools.document", "detect_mime_from_filename"),
|
|
133
|
+
"is_recall_intent": ("threetears.agent.tools.router", "is_recall_intent"),
|
|
134
|
+
"load_todo_tools_from_storage": ("threetears.agent.tools.todo", "load_todo_tools"),
|
|
135
|
+
"load_tool_llm_dispatch": ("threetears.agent.tools.tool_llm_dispatch", "load_tool_llm_dispatch"),
|
|
136
|
+
"load_workflow_tools": ("threetears.agent.tools.workflow", "load_workflow_tools"),
|
|
137
|
+
"migrate_context_items_schema": ("threetears.agent.tools.collections", "migrate_context_items_schema"),
|
|
138
|
+
"parse_document": ("threetears.agent.tools.document", "parse_document"),
|
|
139
|
+
"register_builtins": ("threetears.agent.tools.builtin", "register_builtins"),
|
|
140
|
+
"register_chunk_strategy": ("threetears.agent.tools.chunker", "register_chunk_strategy"),
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
__all__ = [
|
|
144
|
+
"AnalyzerConfig",
|
|
145
|
+
"ChunkResult",
|
|
146
|
+
"ChunkStrategy",
|
|
147
|
+
"ChatModelFactory",
|
|
148
|
+
"ContextItemCollection",
|
|
149
|
+
"ContextItemEntity",
|
|
150
|
+
"context_items_table",
|
|
151
|
+
"migrate_context_items_schema",
|
|
152
|
+
"DEFAULT_ROUTING_PROMPT",
|
|
153
|
+
"DocumentResult",
|
|
154
|
+
"DocumentSection",
|
|
155
|
+
"GeneratedImage",
|
|
156
|
+
"ImageGenerationBackend",
|
|
157
|
+
"ImageGenerationContext",
|
|
158
|
+
"ImageGenerationInput",
|
|
159
|
+
"InvokeToolLlmInput",
|
|
160
|
+
"McpClient",
|
|
161
|
+
"McpTool",
|
|
162
|
+
"McpToolResult",
|
|
163
|
+
"MediaInfo",
|
|
164
|
+
"MediaStorage",
|
|
165
|
+
"OcrConfig",
|
|
166
|
+
"TextProvider",
|
|
167
|
+
"ParseDocumentInput",
|
|
168
|
+
"ToolContextManager",
|
|
169
|
+
"TodoStorage",
|
|
170
|
+
"TodosChangedEvent",
|
|
171
|
+
"ToolExecutionResult",
|
|
172
|
+
"ToolExecutor",
|
|
173
|
+
"ToolLlmInvocation",
|
|
174
|
+
"ToolLlmResolver",
|
|
175
|
+
"ToolRegistry",
|
|
176
|
+
"ToolRouter",
|
|
177
|
+
"ToolRoutingDecision",
|
|
178
|
+
"TranscriptionProvider",
|
|
179
|
+
"VisionProvider",
|
|
180
|
+
"chunk_by_headers",
|
|
181
|
+
"chunk_by_lines",
|
|
182
|
+
"chunk_by_sections",
|
|
183
|
+
"chunk_content",
|
|
184
|
+
"create_analyze_media_tool",
|
|
185
|
+
"create_image_generation_tool",
|
|
186
|
+
"create_parse_document_tool",
|
|
187
|
+
"detect_mime_from_filename",
|
|
188
|
+
"is_recall_intent",
|
|
189
|
+
"load_tool_llm_dispatch",
|
|
190
|
+
"load_todo_tools_from_storage",
|
|
191
|
+
"load_workflow_tools",
|
|
192
|
+
"parse_document",
|
|
193
|
+
"register_builtins",
|
|
194
|
+
"register_chunk_strategy",
|
|
195
|
+
]
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def __getattr__(name: str) -> object:
|
|
199
|
+
"""resolve a public attribute from its defining module on first access.
|
|
200
|
+
|
|
201
|
+
:param name: attribute name being resolved
|
|
202
|
+
:ptype name: str
|
|
203
|
+
:return: the resolved attribute (also cached in module globals so
|
|
204
|
+
``__getattr__`` fires at most once per name)
|
|
205
|
+
:rtype: object
|
|
206
|
+
:raises AttributeError: when ``name`` is not part of the public API
|
|
207
|
+
"""
|
|
208
|
+
entry = _LAZY.get(name)
|
|
209
|
+
if entry is None:
|
|
210
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
211
|
+
from importlib import import_module
|
|
212
|
+
|
|
213
|
+
module_name, attr = entry
|
|
214
|
+
value: object = getattr(import_module(module_name), attr)
|
|
215
|
+
globals()[name] = value
|
|
216
|
+
return value
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def __dir__() -> list[str]:
|
|
220
|
+
"""include lazy attributes in ``dir()`` output.
|
|
221
|
+
|
|
222
|
+
:return: sorted union of materialized globals and lazy names
|
|
223
|
+
:rtype: list[str]
|
|
224
|
+
"""
|
|
225
|
+
return sorted(set(globals()) | set(_LAZY))
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""input-value coercion helpers for TearsTool subclasses.
|
|
2
|
+
|
|
3
|
+
LLMs routinely send loose values for object/array tool parameters:
|
|
4
|
+
empty strings where a dict is expected, JSON-encoded strings where
|
|
5
|
+
a dict/list is expected, arguments omitted entirely. this module
|
|
6
|
+
provides pure functions that rewrite those loose values toward the
|
|
7
|
+
declared JSON schema type before dispatch, so subclasses receive
|
|
8
|
+
the native Python container they expect.
|
|
9
|
+
|
|
10
|
+
the coercion engages only for fields whose declared type is
|
|
11
|
+
``object`` or ``array``. every other field passes through
|
|
12
|
+
untouched. explicit ``None`` is preserved as a "not provided"
|
|
13
|
+
sentinel so required-field checks in subclasses still fire.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import json
|
|
19
|
+
from typing import Any
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"coerce_value",
|
|
23
|
+
"normalize_kwargs",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def coerce_value(value: Any, declared_type: str | None) -> Any:
|
|
28
|
+
"""coerce single MCP input value toward its declared JSON schema type.
|
|
29
|
+
|
|
30
|
+
handles two LLM mistakes: empty string supplied for complex type
|
|
31
|
+
(becomes empty container), and JSON-encoded string supplied where
|
|
32
|
+
native dict/list is expected (gets decoded). values already of
|
|
33
|
+
correct type, or values that cannot be safely coerced, pass
|
|
34
|
+
through unchanged.
|
|
35
|
+
|
|
36
|
+
:param value: raw value supplied by caller
|
|
37
|
+
:ptype value: Any
|
|
38
|
+
:param declared_type: JSON schema ``type`` for this parameter or None
|
|
39
|
+
:ptype declared_type: str | None
|
|
40
|
+
:return: value coerced to declared type when possible, else original
|
|
41
|
+
:rtype: Any
|
|
42
|
+
"""
|
|
43
|
+
result = value
|
|
44
|
+
# only empty *strings* are treated as "give me the empty container" --
|
|
45
|
+
# literal 0, False, or empty tuple must pass through so type-strict
|
|
46
|
+
# subclasses still see the original value (and can reject it).
|
|
47
|
+
if declared_type == "object" and not isinstance(value, dict):
|
|
48
|
+
if value == "":
|
|
49
|
+
result = {}
|
|
50
|
+
elif isinstance(value, str):
|
|
51
|
+
try:
|
|
52
|
+
parsed = json.loads(value)
|
|
53
|
+
except TypeError, ValueError:
|
|
54
|
+
parsed = None
|
|
55
|
+
if isinstance(parsed, dict):
|
|
56
|
+
result = parsed
|
|
57
|
+
elif declared_type == "array" and not isinstance(value, list):
|
|
58
|
+
if value == "":
|
|
59
|
+
result = []
|
|
60
|
+
elif isinstance(value, str):
|
|
61
|
+
try:
|
|
62
|
+
parsed = json.loads(value)
|
|
63
|
+
except TypeError, ValueError:
|
|
64
|
+
parsed = None
|
|
65
|
+
if isinstance(parsed, list):
|
|
66
|
+
result = parsed
|
|
67
|
+
return result
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def normalize_kwargs(
|
|
71
|
+
kwargs: dict[str, Any],
|
|
72
|
+
input_schema: dict[str, Any],
|
|
73
|
+
) -> dict[str, Any]:
|
|
74
|
+
"""coerce loose LLM-supplied kwargs to match declared schema types.
|
|
75
|
+
|
|
76
|
+
inspects ``input_schema['properties']`` and rewrites entries in
|
|
77
|
+
kwargs whose declared type is ``object`` or ``array`` when the
|
|
78
|
+
supplied value is a wrong-shape loose container (empty string
|
|
79
|
+
or JSON-encoded string). values matching their declared type
|
|
80
|
+
are passed through untouched. keys not present in the schema
|
|
81
|
+
are passed through untouched. explicit ``None`` is preserved
|
|
82
|
+
so per-action required-field checks still function.
|
|
83
|
+
|
|
84
|
+
:param kwargs: raw tool input parameters as supplied by caller
|
|
85
|
+
:ptype kwargs: dict[str, Any]
|
|
86
|
+
:param input_schema: tool's JSON schema for input parameters
|
|
87
|
+
:ptype input_schema: dict[str, Any]
|
|
88
|
+
:return: parameters with wrong-type values coerced where possible
|
|
89
|
+
:rtype: dict[str, Any]
|
|
90
|
+
"""
|
|
91
|
+
properties = input_schema.get("properties", {})
|
|
92
|
+
if not properties:
|
|
93
|
+
return dict(kwargs)
|
|
94
|
+
normalized: dict[str, Any] = {}
|
|
95
|
+
for key, value in kwargs.items():
|
|
96
|
+
prop = properties.get(key)
|
|
97
|
+
if prop is None or value is None:
|
|
98
|
+
normalized[key] = value
|
|
99
|
+
continue
|
|
100
|
+
declared_type = prop.get("type")
|
|
101
|
+
normalized[key] = coerce_value(value, declared_type)
|
|
102
|
+
return normalized
|