agenticli 0.1.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.
- agenticli-0.1.0.dist-info/METADATA +264 -0
- agenticli-0.1.0.dist-info/RECORD +12 -0
- agenticli-0.1.0.dist-info/WHEEL +4 -0
- agenticli-0.1.0.dist-info/licenses/LICENSE +21 -0
- llmcli/__init__.py +75 -0
- llmcli/builtin.py +90 -0
- llmcli/core.py +904 -0
- llmcli/decorators.py +138 -0
- llmcli/parser.py +281 -0
- llmcli/tooling.py +313 -0
- llmcli/types.py +271 -0
- llmcli/validation.py +1010 -0
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agenticli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Expose tools as lightweight CLI commands for LLM agents
|
|
5
|
+
License-File: LICENSE
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Provides-Extra: dev
|
|
8
|
+
Requires-Dist: pytest-cov>=7.1.0; extra == 'dev'
|
|
9
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
10
|
+
Provides-Extra: examples
|
|
11
|
+
Requires-Dist: anthropic>=0.34; extra == 'examples'
|
|
12
|
+
Requires-Dist: openai>=2.32.0; extra == 'examples'
|
|
13
|
+
Provides-Extra: pydantic
|
|
14
|
+
Requires-Dist: pydantic<3,>=2; extra == 'pydantic'
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
17
|
+
<div align="center">
|
|
18
|
+
|
|
19
|
+
# β‘ agenticli
|
|
20
|
+
|
|
21
|
+
**Expose tools as lightweight CLI commands for LLM agents** π§β¨
|
|
22
|
+
|
|
23
|
+
[](https://python.org)
|
|
24
|
+
[](LICENSE)
|
|
25
|
+
[](https://pypi.org/project/agenticli/)
|
|
26
|
+
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## β¨ What is this?
|
|
32
|
+
|
|
33
|
+
**llmcli** converts functions, classes, and schema-based tools into a stable CLI semantic layer. Instead of flooding prompts with large schemas, LLMs just output a single command string.
|
|
34
|
+
|
|
35
|
+
> π‘ **Philosophy: bash is everything.** The command string is the most stable, restrained, and observable intermediate representation between LLMs and tool systems.
|
|
36
|
+
|
|
37
|
+
## π― When to use llmcli?
|
|
38
|
+
|
|
39
|
+
| Scenario | llmcli helps? |
|
|
40
|
+
|----------|---------------|
|
|
41
|
+
| You have many tools/functions and need a unified interface for LLMs | β
|
|
|
42
|
+
| You don't want massive schemas injected into prompts | β
|
|
|
43
|
+
| You want models to see minimal hints, expanding via `--help` | β
|
|
|
44
|
+
| You want validation, help, errors, and lifecycle in one place | β
|
|
|
45
|
+
|
|
46
|
+
## π Quick Start
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pip install llmcli
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
from typing import Annotated
|
|
54
|
+
from llmcli import CommandRegistry, Option, command, command_group
|
|
55
|
+
|
|
56
|
+
@command_group(name="calc", description="Calculator commands")
|
|
57
|
+
class Calc:
|
|
58
|
+
@command(name="add", description="Add numbers")
|
|
59
|
+
def add(self,
|
|
60
|
+
values: Annotated[list[float], Option(positional=True, value_name="n")]
|
|
61
|
+
) -> dict:
|
|
62
|
+
return {"result": sum(values)}
|
|
63
|
+
|
|
64
|
+
registry = CommandRegistry()
|
|
65
|
+
registry.register(Calc)
|
|
66
|
+
|
|
67
|
+
# LLM sees this minimal prompt:
|
|
68
|
+
print(registry.get_llm_prompt())
|
|
69
|
+
# -> You can use the following CLI commands:
|
|
70
|
+
# calc: Calculator commands
|
|
71
|
+
|
|
72
|
+
# Execute:
|
|
73
|
+
registry.parse_and_execute("calc add 10 20 30")
|
|
74
|
+
# -> {"result": 60.0}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## ποΈ Core Architecture
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
81
|
+
β LLM Output β
|
|
82
|
+
β "calc add 10 20 30" β
|
|
83
|
+
βββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββ
|
|
84
|
+
β
|
|
85
|
+
βΌ
|
|
86
|
+
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
87
|
+
β CommandRegistry β
|
|
88
|
+
β ββββββββββββ ββββββββββββ ββββββββββββ ββββββββββββββ β
|
|
89
|
+
β β Parse βββΆβ Validate βββΆβ Execute βββΆβ Result β β
|
|
90
|
+
β ββββββββββββ ββββββββββββ ββββββββββββ ββββββββββββββ β
|
|
91
|
+
β β
|
|
92
|
+
β β’ Command hit/matching β’ Lifecycle callbacks β
|
|
93
|
+
β β’ Help generation β’ Error with suggestions β
|
|
94
|
+
β β’ Argument injection β’ Chain execution (&&, ||, ;) β
|
|
95
|
+
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
96
|
+
β
|
|
97
|
+
βΌ
|
|
98
|
+
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
99
|
+
β Your Functions / Tools β
|
|
100
|
+
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## π Key Features
|
|
104
|
+
|
|
105
|
+
| Feature | Description |
|
|
106
|
+
|---------|-------------|
|
|
107
|
+
| π **Multiple Registrations** | Decorators, class inheritance, dataclass, Pydantic, schema wrapping |
|
|
108
|
+
| β‘ **CLI Parsing** | Positional args, `--option value`, `-o value`, `--opt=val`, flags |
|
|
109
|
+
| π **Smart Help** | Auto-generated usage, help text, LLM prompts |
|
|
110
|
+
| β
**Validation** | Type coercion, `requires`/`excludes`, enums |
|
|
111
|
+
| π‘ **Suggestions** | "Did you mean X?" for unknown commands/options/enums |
|
|
112
|
+
| π **Lifecycle Hooks** | `before_execute`, `after_execute`, `on_error` |
|
|
113
|
+
| π **Internal Injection** | Hide callbacks/state from CLI, inject at runtime |
|
|
114
|
+
| π **Chain Execution** | `cmd1 && cmd2 || cmd3 ; cmd4` |
|
|
115
|
+
|
|
116
|
+
## π Registration Patterns
|
|
117
|
+
|
|
118
|
+
### 1οΈβ£ Decorator (Most Common)
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
from typing import Annotated
|
|
122
|
+
from llmcli import command, Option
|
|
123
|
+
|
|
124
|
+
@command(name="ls", description="List directory")
|
|
125
|
+
def ls(
|
|
126
|
+
path: Annotated[str, Option(short='p', description="Directory path")],
|
|
127
|
+
verbose: Annotated[bool, Option(short='v')] = False,
|
|
128
|
+
) -> list[str]:
|
|
129
|
+
import os
|
|
130
|
+
return os.listdir(path)
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### 2οΈβ£ Command Group
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
from llmcli import command_group, command
|
|
137
|
+
|
|
138
|
+
@command_group(name="db", description="Database operations")
|
|
139
|
+
class Database:
|
|
140
|
+
@command(description="Create database")
|
|
141
|
+
def create(self, name: str) -> None: ...
|
|
142
|
+
|
|
143
|
+
@command(description="Drop database")
|
|
144
|
+
def drop(self, name: str) -> None: ...
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### 3οΈβ£ Wrap Existing Tools
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
from llmcli import wrap_tool
|
|
151
|
+
|
|
152
|
+
class MyTool:
|
|
153
|
+
name = "my_tool"
|
|
154
|
+
description = "Does something"
|
|
155
|
+
parameters = {"type": "object", "properties": {"x": {"type": "int"}}}
|
|
156
|
+
async def execute(self, **kwargs): return kwargs
|
|
157
|
+
|
|
158
|
+
registry.register_spec(wrap_tool(MyTool()))
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### 4οΈβ£ Class Inheritance
|
|
162
|
+
|
|
163
|
+
```python
|
|
164
|
+
from llmcli import CliCommand, CommandRegistry
|
|
165
|
+
|
|
166
|
+
class AddCommand(CliCommand):
|
|
167
|
+
name = "add"
|
|
168
|
+
description = "Add numbers"
|
|
169
|
+
args_model = AddArgs
|
|
170
|
+
|
|
171
|
+
async def run(self, **kwargs) -> dict:
|
|
172
|
+
return {"result": sum(kwargs["values"])}
|
|
173
|
+
|
|
174
|
+
registry.register(AddCommand())
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## π€ LLM Integration
|
|
178
|
+
|
|
179
|
+
### Minimal Tool Schema
|
|
180
|
+
|
|
181
|
+
Expose only one `exec` tool to the LLM:
|
|
182
|
+
|
|
183
|
+
```python
|
|
184
|
+
from llmcli import ExecTool
|
|
185
|
+
|
|
186
|
+
exec_tool = ExecTool(callback=registry.parse_and_execute)
|
|
187
|
+
# Tool schema: {name: "exec", params: {command: string, timeout?: int}}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Lifecycle Callbacks
|
|
191
|
+
|
|
192
|
+
```python
|
|
193
|
+
from llmcli import ExecutionCallbacks
|
|
194
|
+
|
|
195
|
+
def on_error(ctx):
|
|
196
|
+
print(f"Error: {ctx.error.code} - {ctx.error.message}")
|
|
197
|
+
|
|
198
|
+
registry = CommandRegistry(
|
|
199
|
+
callbacks=ExecutionCallbacks(on_error=on_error)
|
|
200
|
+
)
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### Internal Parameter Injection
|
|
204
|
+
|
|
205
|
+
```python
|
|
206
|
+
from typing import Annotated
|
|
207
|
+
from llmcli import Callback, State, command
|
|
208
|
+
|
|
209
|
+
@command(name="process")
|
|
210
|
+
def process(
|
|
211
|
+
data: list[str],
|
|
212
|
+
cache: Annotated[object, State(factory=lambda ctx: load_cache())] = None,
|
|
213
|
+
):
|
|
214
|
+
# cache is injected automatically, hidden from CLI
|
|
215
|
+
return cached_transform(data, cache)
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## π¦ Stable API
|
|
219
|
+
|
|
220
|
+
```python
|
|
221
|
+
# Core
|
|
222
|
+
CommandRegistry
|
|
223
|
+
CommandRegistry.register(target)
|
|
224
|
+
CommandRegistry.register_spec(spec)
|
|
225
|
+
CommandRegistry.execute(command_str)
|
|
226
|
+
CommandRegistry.parse_and_execute(command_str)
|
|
227
|
+
CommandRegistry.render_help(command)
|
|
228
|
+
CommandRegistry.get_llm_prompt(detailed=False)
|
|
229
|
+
|
|
230
|
+
# Decorators
|
|
231
|
+
command(name=None, description="", aliases=None, hidden=False, deprecated=None)
|
|
232
|
+
command_group(name, description)
|
|
233
|
+
|
|
234
|
+
# Helpers
|
|
235
|
+
CliCommand
|
|
236
|
+
wrap_tool(tool)
|
|
237
|
+
command_from_model(name, model, handler)
|
|
238
|
+
command_from_method(name, target, method_name)
|
|
239
|
+
Option
|
|
240
|
+
Injected / Callback / State
|
|
241
|
+
ExecutionCallbacks
|
|
242
|
+
ExecTool
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
## π‘ Examples
|
|
246
|
+
|
|
247
|
+
See [example/demo.py](example/demo.py) for a complete calc system with OpenAI/Anthropic integration:
|
|
248
|
+
|
|
249
|
+
```bash
|
|
250
|
+
pip install "llmcli[examples]"
|
|
251
|
+
python -m example.demo --provider openai
|
|
252
|
+
python -m example.demo --provider anthropic
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
## π Documentation
|
|
256
|
+
|
|
257
|
+
| Language | Link |
|
|
258
|
+
|----------|------|
|
|
259
|
+
| πΊπΈ English | [docs/index_en.md](docs/index_en.md) |
|
|
260
|
+
| π¨π³ δΈζ | [docs/index_zh.md](docs/index_zh.md) |
|
|
261
|
+
|
|
262
|
+
## π License
|
|
263
|
+
|
|
264
|
+
MIT
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
llmcli/__init__.py,sha256=Z3xVTfiY9-Bpx4zXk_Z8GKPOUFfdbY2Av3NBc1y3iFI,2835
|
|
2
|
+
llmcli/builtin.py,sha256=PIkGU6PjrIG3lPrtyGJgx3TYQyUzFZ1L_rl3-l0EfJk,2732
|
|
3
|
+
llmcli/core.py,sha256=W_Nygw6KuqWMYqm4Af3-rTi7mrL6D5Yi84fRllVAJFE,32519
|
|
4
|
+
llmcli/decorators.py,sha256=w5i9KkYd8pxM5Bh46sHtlJq_1KYE_oB41jcqjsJTEEo,4260
|
|
5
|
+
llmcli/parser.py,sha256=_2CEqgQhWjwfd_-m3Jo4FhxAgu0uYR5j9WwFCTdtZ28,10826
|
|
6
|
+
llmcli/tooling.py,sha256=oSxCmNutM9IWhaKbQoI2TXZ9PkMbdQXnGBAvqKANxJQ,9895
|
|
7
|
+
llmcli/types.py,sha256=Dl9FWaS124BU8WTyLuLDG-Nt8H_d9ks8gQvHdx80wp0,9741
|
|
8
|
+
llmcli/validation.py,sha256=ka_vDpcjSGHG_MLhF1LzHQV75eJxQDRnoIb5nRJH8AU,38577
|
|
9
|
+
agenticli-0.1.0.dist-info/METADATA,sha256=TihSvi9jZB-TINJ9TVR0f7LKvYCeZ7SVbxD82fmABaA,8684
|
|
10
|
+
agenticli-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
11
|
+
agenticli-0.1.0.dist-info/licenses/LICENSE,sha256=knHFE1wChIbxkrbYlAr02Z7lAxmWeytdNUAqO3nsp9Y,1065
|
|
12
|
+
agenticli-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 agenticli
|
|
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.
|
llmcli/__init__.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""llmcli - expose tools as lightweight CLI commands for LLMs.
|
|
2
|
+
|
|
3
|
+
This package provides a framework for defining CLI commands that can be
|
|
4
|
+
parsed and executed by LLMs. It supports multiple command definition styles
|
|
5
|
+
including decorators, class inheritance, and schema-based wrapping.
|
|
6
|
+
|
|
7
|
+
Typical usage:
|
|
8
|
+
from llmcli import CommandRegistry, command
|
|
9
|
+
|
|
10
|
+
registry = CommandRegistry()
|
|
11
|
+
|
|
12
|
+
@command(description="List directory contents")
|
|
13
|
+
def ls(path: Annotated[str, Option(description="Directory path")]) -> list[str]:
|
|
14
|
+
import os
|
|
15
|
+
return os.listdir(path)
|
|
16
|
+
|
|
17
|
+
registry.register(ls)
|
|
18
|
+
result = registry.execute("ls /tmp")
|
|
19
|
+
|
|
20
|
+
Exports:
|
|
21
|
+
ArgSpec: Argument specification data class.
|
|
22
|
+
CliCommand: Base class for inheritance-based commands.
|
|
23
|
+
CommandError: Structured error with code, message, and hints.
|
|
24
|
+
CommandRegistry: Central registry for command management.
|
|
25
|
+
CommandSpec: Command specification data class.
|
|
26
|
+
ExecutionCallbacks: Lifecycle callbacks for command execution.
|
|
27
|
+
ExecutionContext: Context passed to lifecycle callbacks.
|
|
28
|
+
ExecutionResult: Structured execution result with ok/value/error.
|
|
29
|
+
ExecTool: Built-in command execution tool.
|
|
30
|
+
HitResult: Command matching/detection result.
|
|
31
|
+
Injected: Marker for injected (internal) parameters.
|
|
32
|
+
ParseResult: Parsed command result with arguments.
|
|
33
|
+
Callback: Alias for Injected for callback injection.
|
|
34
|
+
State: Alias for Injected for state injection.
|
|
35
|
+
clear_commands: Clear the global command registry.
|
|
36
|
+
command: Decorator to register a function as a command.
|
|
37
|
+
command_from_method: Create command from a class method.
|
|
38
|
+
command_from_model: Create command from model + handler.
|
|
39
|
+
command_group: Decorator for command groups with subcommands.
|
|
40
|
+
get_registered_commands: Get all registered command specs.
|
|
41
|
+
Option: Per-argument CLI metadata annotation.
|
|
42
|
+
wrap_tool: Wrap a schema-based tool as a command.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
from llmcli.builtin import ExecTool
|
|
46
|
+
from llmcli.core import CommandRegistry
|
|
47
|
+
from llmcli.decorators import clear_commands, command, command_group, get_registered_commands
|
|
48
|
+
from llmcli.tooling import CliCommand, command_from_method, command_from_model, wrap_tool
|
|
49
|
+
from llmcli.types import ArgSpec, CommandError, CommandSpec, ExecutionCallbacks, ExecutionContext, ExecutionResult, HitResult, ParseResult
|
|
50
|
+
from llmcli.validation import Callback, Injected, Option, State
|
|
51
|
+
|
|
52
|
+
__all__ = [
|
|
53
|
+
"ArgSpec",
|
|
54
|
+
"CliCommand",
|
|
55
|
+
"CommandError",
|
|
56
|
+
"CommandRegistry",
|
|
57
|
+
"CommandSpec",
|
|
58
|
+
"ExecutionCallbacks",
|
|
59
|
+
"ExecutionContext",
|
|
60
|
+
"ExecutionResult",
|
|
61
|
+
"ExecTool",
|
|
62
|
+
"HitResult",
|
|
63
|
+
"Injected",
|
|
64
|
+
"ParseResult",
|
|
65
|
+
"Callback",
|
|
66
|
+
"State",
|
|
67
|
+
"clear_commands",
|
|
68
|
+
"command",
|
|
69
|
+
"command_from_method",
|
|
70
|
+
"command_from_model",
|
|
71
|
+
"command_group",
|
|
72
|
+
"get_registered_commands",
|
|
73
|
+
"Option",
|
|
74
|
+
"wrap_tool",
|
|
75
|
+
]
|
llmcli/builtin.py
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""Builtin tools for llmcli.
|
|
2
|
+
|
|
3
|
+
This module provides built-in command tools that ship with llmcli,
|
|
4
|
+
including the ExecTool for executing shell commands through a callback.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import inspect
|
|
10
|
+
from typing import Any, Awaitable, Callable
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ExecTool:
|
|
14
|
+
"""Minimal schema-based exec tool backed by a callback.
|
|
15
|
+
|
|
16
|
+
Provides a command execution tool that delegates to a user-provided
|
|
17
|
+
callback function. The callback receives the command string and any
|
|
18
|
+
additional arguments.
|
|
19
|
+
|
|
20
|
+
Attributes:
|
|
21
|
+
name: Command name, defaults to "exec".
|
|
22
|
+
description: Human-readable description of the command.
|
|
23
|
+
parameters: JSON schema describing the command arguments.
|
|
24
|
+
|
|
25
|
+
Example:
|
|
26
|
+
>>> def execute_cmd(cmd: str, timeout: int = 60):
|
|
27
|
+
... return subprocess.run(cmd, shell=True, timeout=timeout)
|
|
28
|
+
...
|
|
29
|
+
>>> tool = ExecTool(callback=execute_cmd)
|
|
30
|
+
>>> spec = wrap_tool(tool)
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
name = "exec"
|
|
34
|
+
description = "Execute a command string through a callback."
|
|
35
|
+
parameters = {
|
|
36
|
+
"type": "object",
|
|
37
|
+
"properties": {
|
|
38
|
+
"command": {
|
|
39
|
+
"type": "string",
|
|
40
|
+
"description": "Command text to execute",
|
|
41
|
+
},
|
|
42
|
+
"timeout": {
|
|
43
|
+
"type": "integer",
|
|
44
|
+
"description": "Execution timeout in seconds",
|
|
45
|
+
"default": 60,
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
"required": ["command"],
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
def __init__(self, callback: Callable[..., Any] | Callable[..., Awaitable[Any]]):
|
|
52
|
+
"""Initialize ExecTool with execution callback.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
callback: Function to execute with command. Can be sync or async.
|
|
56
|
+
"""
|
|
57
|
+
self._callback = callback
|
|
58
|
+
|
|
59
|
+
async def execute(self, **kwargs: Any) -> Any:
|
|
60
|
+
"""Execute the command via callback.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
**kwargs: Arguments from parsed command, must include 'command'.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
Result from the callback function.
|
|
67
|
+
"""
|
|
68
|
+
command = kwargs.pop("command", None)
|
|
69
|
+
if command is not None:
|
|
70
|
+
result = self._callback(command, **kwargs)
|
|
71
|
+
else:
|
|
72
|
+
result = self._callback(**kwargs)
|
|
73
|
+
if inspect.isawaitable(result):
|
|
74
|
+
return await result
|
|
75
|
+
return result
|
|
76
|
+
|
|
77
|
+
def to_schema(self) -> dict[str, Any]:
|
|
78
|
+
"""Convert tool to OpenAI function schema format.
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
Dictionary with type "function" and function definition.
|
|
82
|
+
"""
|
|
83
|
+
return {
|
|
84
|
+
"type": "function",
|
|
85
|
+
"function": {
|
|
86
|
+
"name": self.name,
|
|
87
|
+
"description": self.description,
|
|
88
|
+
"parameters": self.parameters,
|
|
89
|
+
},
|
|
90
|
+
}
|