by-tiny-agents 0.2.3__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.
- by_tiny_agents-0.2.3/LICENSE +21 -0
- by_tiny_agents-0.2.3/PKG-INFO +389 -0
- by_tiny_agents-0.2.3/Packages/colors.py +144 -0
- by_tiny_agents-0.2.3/Packages/prettyResolver.py +75 -0
- by_tiny_agents-0.2.3/README.md +371 -0
- by_tiny_agents-0.2.3/Tools/hourNow/hourNow.py +5 -0
- by_tiny_agents-0.2.3/Tools/runInShell/runInShell.py +38 -0
- by_tiny_agents-0.2.3/Tools/tools.json +56 -0
- by_tiny_agents-0.2.3/Tools/toolsCall.json +5 -0
- by_tiny_agents-0.2.3/by_tiny_agents.egg-info/PKG-INFO +389 -0
- by_tiny_agents-0.2.3/by_tiny_agents.egg-info/SOURCES.txt +23 -0
- by_tiny_agents-0.2.3/by_tiny_agents.egg-info/dependency_links.txt +1 -0
- by_tiny_agents-0.2.3/by_tiny_agents.egg-info/requires.txt +6 -0
- by_tiny_agents-0.2.3/by_tiny_agents.egg-info/top_level.txt +4 -0
- by_tiny_agents-0.2.3/pyproject.toml +32 -0
- by_tiny_agents-0.2.3/setup.cfg +4 -0
- by_tiny_agents-0.2.3/src/AgentHarness.py +118 -0
- by_tiny_agents-0.2.3/src/Panel/Panel.py +882 -0
- by_tiny_agents-0.2.3/src/Panel/themes.py +37 -0
- by_tiny_agents-0.2.3/src/tinyAgents.py +242 -0
- by_tiny_agents-0.2.3/tinyAgents/colors.py +3 -0
- by_tiny_agents-0.2.3/tinyAgents/core.py +29 -0
- by_tiny_agents-0.2.3/tinyAgents/formatting.py +5 -0
- by_tiny_agents-0.2.3/tinyAgents/harness.py +5 -0
- by_tiny_agents-0.2.3/tinyAgents/panel.py +5 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kauan M.
|
|
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,389 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: by-tiny-agents
|
|
3
|
+
Version: 0.2.3
|
|
4
|
+
Summary: Small building blocks for Python agents and harnesses
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/kauanmezavila/tiny-agents
|
|
7
|
+
Project-URL: Repository, https://github.com/kauanmezavila/tiny-agents
|
|
8
|
+
Project-URL: Issues, https://github.com/kauanmezavila/tiny-agents/issues
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: openai
|
|
13
|
+
Requires-Dist: tiktoken
|
|
14
|
+
Provides-Extra: panel
|
|
15
|
+
Requires-Dist: prompt-toolkit; extra == "panel"
|
|
16
|
+
Requires-Dist: python-dotenv; extra == "panel"
|
|
17
|
+
Dynamic: license-file
|
|
18
|
+
|
|
19
|
+
<div align="center">
|
|
20
|
+
<table bgcolor="#ffffff">
|
|
21
|
+
<tr><td><img src="imgs/logo.png" alt="Tiny Agents" width="180" /></td></tr>
|
|
22
|
+
</table>
|
|
23
|
+
<h1>Tiny Agents</h1>
|
|
24
|
+
<p>Small building blocks for Python agents and harnesses.</p>
|
|
25
|
+
<p><b>v0.2.3: multiple providers, less configuration, less code, more action.</b></p>
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
Tiny Agents is a framework for building custom agent harnesses. Version 0.2.3 adds `AgentHarness`, which connects model/provider selection, clients, tools, conversation context, and panel callbacks. Application configuration and harness creation stay in `main.py`; reusable wiring lives in the framework. The framework also includes async command execution, OpenAI-compatible provider clients, JSON-based tool calling, in-memory conversation context, and terminal formatting utilities.
|
|
29
|
+
|
|
30
|
+
The core works independently of the panel. Applications connect tools to the interface through callbacks, so the same tools can also run in a regular terminal.
|
|
31
|
+
|
|
32
|
+
For step-by-step examples, see the [usage guide](docs/USAGE.md). The guide covers direct model calls, adding tools, context management, provider setup, and assembling the panel harness.
|
|
33
|
+
|
|
34
|
+
## Function organization
|
|
35
|
+
|
|
36
|
+
| Module | Main responsibilities |
|
|
37
|
+
| --------------------- | ------------------------------------------------------------------------------------------------------------ |
|
|
38
|
+
| `tinyAgents/` | Public imports: `tinyAgents`, `tinyAgents.core`, `tinyAgents.harness`, `tinyAgents.panel`, `tinyAgents.colors`, and `tinyAgents.formatting`. |
|
|
39
|
+
| `src/tinyAgents.py` | `startClient`, model request functions, tool loading and execution, context storage and compaction. |
|
|
40
|
+
| `src/AgentHarness.py` | `AgentHarness`: connects provider/model state, tools, requests, context, worker, and panel callbacks. |
|
|
41
|
+
| `src/Panel/Panel.py` | `initPanel`, `Command`, `PanelContext`, `AsyncWorker`, command dispatch, and terminal UI. |
|
|
42
|
+
| `Tools/` | Tool schemas, handler mappings, and tool implementations such as `hourNow` and shell commands. |
|
|
43
|
+
| `Packages/` | Presentation helpers such as `prettyResolve` and terminal colors. |
|
|
44
|
+
| `main.py` | Application configuration and assembly: profiles, prompt, tool setup, commands, panel settings, and startup. |
|
|
45
|
+
|
|
46
|
+
Keep application-specific behavior and configuration in `main.py` or app modules. Reusable request and wiring behavior belongs in the framework modules; tool implementations belong in `Tools/`.
|
|
47
|
+
|
|
48
|
+
The package name used by `pip install` does not determine Python import names. The public imports are available in short or module-specific forms:
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from tinyAgents import AgentHarness, startClient, messageToLLM
|
|
52
|
+
from tinyAgents.core import loadTools
|
|
53
|
+
from tinyAgents.harness import AgentHarness
|
|
54
|
+
from tinyAgents.panel import initPanel, Command, AsyncWorker
|
|
55
|
+
from tinyAgents.colors import BLUE, WHITE
|
|
56
|
+
from tinyAgents.formatting import prettyResolve
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Providers and models
|
|
60
|
+
|
|
61
|
+
| Provider | Models |
|
|
62
|
+
| ------------ | ------------------------------------------------------------------------------- |
|
|
63
|
+
| NVIDIA NIM | `openai/gpt-oss-20b`, `nvidia/nemotron-3-super-120b-a12b`, `z-ai/glm-5-3-flash` |
|
|
64
|
+
| Cloud Ollama | `gpt-oss:20b` |
|
|
65
|
+
|
|
66
|
+
## Installation
|
|
67
|
+
|
|
68
|
+
The framework requires Python 3.10 or newer.
|
|
69
|
+
|
|
70
|
+
The distribution name is `by-tiny-agents`; Python imports use `tinyAgents`.
|
|
71
|
+
|
|
72
|
+
Install the core and panel dependencies from a repository checkout:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
uv sync --extra panel
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Or use pip:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
python -m pip install ".[panel]"
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
To install directly from GitHub with pip, use:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
python -m pip install "by-tiny-agents[panel] @ git+https://github.com/kauanmezavila/tiny-agents.git"
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
The installable package includes the core, harness, panel, tools and their JSON schemas, and `Packages` helpers. `main.py` remains the repository's configurable example harness.
|
|
91
|
+
|
|
92
|
+
Create a `.env` file in the project root:
|
|
93
|
+
|
|
94
|
+
```dotenv
|
|
95
|
+
API_KEY_NVIDIA=your-nvidia-api-key
|
|
96
|
+
API_KEY_OLLAMA=your-ollama-api-key
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
The example reads provider keys from these environment variables. Configure available providers and model IDs in `profiles` in `main.py`. `startClient` uses the NVIDIA and Cloud Ollama endpoints for those known providers; other providers can provide `baseUrl` in their profile. Use models that support function tool calling when enabling tools.
|
|
100
|
+
|
|
101
|
+
## Panel example
|
|
102
|
+
|
|
103
|
+
The repository's `main.py` is the runnable panel example. Keep application-specific settings there, then create an `AgentHarness` to connect them to the framework:
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
import os
|
|
107
|
+
from dotenv import load_dotenv
|
|
108
|
+
from prompt_toolkit.patch_stdout import patch_stdout
|
|
109
|
+
|
|
110
|
+
from tinyAgents import *
|
|
111
|
+
from tinyAgents.panel import initPanel, PanelContext, Command, AsyncWorker
|
|
112
|
+
from tinyAgents.colors import *
|
|
113
|
+
|
|
114
|
+
agent_worker = AsyncWorker()
|
|
115
|
+
load_dotenv()
|
|
116
|
+
|
|
117
|
+
profiles = {
|
|
118
|
+
"nvidia": {
|
|
119
|
+
"models": ["openai/gpt-oss-20b", "nvidia/nemotron-3-super-120b-a12b"],
|
|
120
|
+
"apiKey": os.getenv("API_KEY_NVIDIA"),
|
|
121
|
+
},
|
|
122
|
+
"cloud-ollama": {
|
|
123
|
+
"models": ["gpt-oss:20b"],
|
|
124
|
+
"apiKey": os.getenv("API_KEY_OLLAMA"),
|
|
125
|
+
},
|
|
126
|
+
# For an unmapped provider, supply its OpenAI-compatible URL:
|
|
127
|
+
# "my-provider": {"models": ["model-id"], "apiKey": "...",
|
|
128
|
+
# "baseUrl": "https://api.example.com/v1"},
|
|
129
|
+
}
|
|
130
|
+
selected = ["nvidia", "nvidia/nemotron-3-super-120b-a12b"]
|
|
131
|
+
tools = loadTools("Tools/tools.json")
|
|
132
|
+
tool_handlers = loadToolHandlers("Tools/toolsCall.json")
|
|
133
|
+
agent = AgentHarness(
|
|
134
|
+
profiles=profiles,
|
|
135
|
+
selected=selected,
|
|
136
|
+
tools=tools,
|
|
137
|
+
tool_handlers=tool_handlers,
|
|
138
|
+
pre_prompt="You are a helpful assistant. Use tools when asked.",
|
|
139
|
+
worker=agent_worker,
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def echo(args: list[str], ctx: PanelContext) -> str:
|
|
144
|
+
return " ".join(args)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def clear_command(args: list[str], ctx: PanelContext) -> None:
|
|
148
|
+
ctx.clear()
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def exit_command(args: list[str], ctx: PanelContext) -> None:
|
|
152
|
+
ctx.exit()
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
COMMANDS = [
|
|
156
|
+
Command("/echo", "Repeat the arguments", echo),
|
|
157
|
+
Command("/clear", "Clear the panel", clear_command),
|
|
158
|
+
Command("/exit", "Exit the panel", exit_command),
|
|
159
|
+
Command("/compact", "Compact the context window", agent.compact_context),
|
|
160
|
+
]
|
|
161
|
+
|
|
162
|
+
PANEL_CONFS = {
|
|
163
|
+
"NAME_TEXT": f"{BOLD + BLUE}KML CODE{RESET}",
|
|
164
|
+
"SUBNAME_TEXT": f"{ITALIC}The Tiny Agents Ultimate Framework{RESET}",
|
|
165
|
+
"INPUT_BOX_UPPER_TEXT": f"{DIM}Try typing something{RESET}",
|
|
166
|
+
"THINKING_TEXT": "Thinking...",
|
|
167
|
+
"MODEL_NAME": agent.model,
|
|
168
|
+
"MODEL_PROVIDER": agent.provider,
|
|
169
|
+
"MODEL_OPTION": agent.model_option,
|
|
170
|
+
"CONTEXT_WINDOW_SIZE": contextWindowSize,
|
|
171
|
+
"CONTEXT_WINDOW_MAX": 10000,
|
|
172
|
+
"THEME": "default",
|
|
173
|
+
"MESSAGE_SPACING": 1,
|
|
174
|
+
"STYLE": {
|
|
175
|
+
"title": BOLD + BLUE,
|
|
176
|
+
"user": BG_BLUE + WHITE,
|
|
177
|
+
"model": WHITE,
|
|
178
|
+
"error": RED,
|
|
179
|
+
"separator": DIM + BLACK,
|
|
180
|
+
"hint": DIM,
|
|
181
|
+
"input-field": BG_BLUE + WHITE,
|
|
182
|
+
"completion-menu": WHITE,
|
|
183
|
+
"completion-menu.completion": WHITE,
|
|
184
|
+
"completion-menu.completion.current": BG_BLUE + BOLD + BLUE,
|
|
185
|
+
"scrollbar": BG_BLACK,
|
|
186
|
+
"scrollbar.button": BG_BLACK + WHITE,
|
|
187
|
+
},
|
|
188
|
+
"COMMANDS": COMMANDS,
|
|
189
|
+
"COMMANDS_LIST": [command.name for command in COMMANDS],
|
|
190
|
+
"MODEL_HANDLER": agent.model_handler,
|
|
191
|
+
"MODEL_OPTIONS": agent.model_options(),
|
|
192
|
+
"MODEL_OPTION_LABELS": agent.model_option_labels(),
|
|
193
|
+
"MODEL_SELECT_HANDLER": agent.select_model,
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
panel = initPanel(PANEL_CONFS)
|
|
197
|
+
|
|
198
|
+
with patch_stdout():
|
|
199
|
+
panel.run()
|
|
200
|
+
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Run the example from the project root so the tool configuration files and Python modules can be found:
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
uv run --with prompt-toolkit --with python-dotenv main.py
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Or, after installing the dependencies with pip:
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
python main.py
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
`AgentHarness` runs model requests and tool execution through the supplied `AsyncWorker`, saves each exchange to the shared context, and connects tools that accept a `write` callback to the panel. `main.py` remains responsible for configuration, application-specific commands, and creating the harness.
|
|
216
|
+
|
|
217
|
+
Commands include `/echo`, `/clear`, `/exit`, `/compact`, and `/model`. Run `/model` to open the selector above the input box, move with ↑/↓, choose with Enter, or close it with Esc. The options show `provider / model`; direct selection accepts an option number or its `provider::model` ID. The harness recreates the client when the provider changes and uses that provider's configured key and URL. For a new provider, add its models and key in `profiles` and set `baseUrl` there.
|
|
218
|
+
|
|
219
|
+
`/compact` uses the currently selected client and model. `/clear` clears the visible chat, not the framework's conversation context. Scroll with PgUp/PgDn, or jump to the beginning and end with Ctrl+Home/Ctrl+End.
|
|
220
|
+
|
|
221
|
+
## Panel configuration
|
|
222
|
+
|
|
223
|
+
Pass a dictionary to `initPanel` from `tinyAgents.panel`:
|
|
224
|
+
|
|
225
|
+
| Key | Purpose |
|
|
226
|
+
| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
|
|
227
|
+
| `NAME_TEXT`, `SUBNAME_TEXT` | Title and subtitle; ANSI formatting is supported. |
|
|
228
|
+
| `INPUT_BOX_UPPER_TEXT` | Extra text beside the scrolling hints. |
|
|
229
|
+
| `THINKING_TEXT` | Activity label; defaults to `Thinking...`. |
|
|
230
|
+
| `MODEL_NAME` | Current model name displayed in the panel; updated after selection. |
|
|
231
|
+
| `MODEL_PROVIDER` | Current provider displayed with the model when set. |
|
|
232
|
+
| `MODEL_OPTION` | Current selector ID, usually `provider::model`, used to highlight the selected option. |
|
|
233
|
+
| `MODEL_OPTIONS` | Optional list of model IDs that enables the `/model` selector. |
|
|
234
|
+
| `MODEL_OPTION_LABELS` | Optional mapping from selector IDs to human-readable labels. |
|
|
235
|
+
| `MODEL_SELECT_HANDLER` | Optional callback receiving the selected option ID. |
|
|
236
|
+
| `MODEL_HANDLER` | Callable receiving `(text, ctx)` and returning a response or `None`; synchronous and async handlers are supported. |
|
|
237
|
+
| `COMMANDS` | List of `Command(name, description, handler)` instances. Command handlers receive `(args, ctx)` and may be synchronous or async. |
|
|
238
|
+
| `COMMANDS_LIST` | Completion suggestions; defaults to registered command names. |
|
|
239
|
+
| `CONTEXT_WINDOW_SIZE` | Function taking no arguments and returning the current usage; defaults to zero. |
|
|
240
|
+
| `CONTEXT_WINDOW_MAX` | Positive integer used as the meter's maximum; defaults to `10000`. |
|
|
241
|
+
| `THEME` | `default`, `dark`, `light`, or `ocean`; defaults to `default`. |
|
|
242
|
+
| `MESSAGE_SPACING` | Number of blank lines between messages; defaults to `1`, and accepts `0`. |
|
|
243
|
+
| `STYLE` | Style overrides for the selected theme. |
|
|
244
|
+
|
|
245
|
+
`PanelContext` exposes `write(text)`, `clear()`, and `exit()`. Writes from the agent worker are scheduled on the UI loop and chat updates are batched during bursts of output. The spinner refreshes only during activity, and unchanged ANSI lines are cached.
|
|
246
|
+
|
|
247
|
+
Command handlers are awaited when async; synchronous handlers run in a thread. Async commands are prevented from starting during a model request or another async command. `/clear` and `/exit` remain available. Async handlers that use the synchronous SDK must delegate that work to `AsyncWorker`, as `/compact` does in the example. `dispatch` is now async and direct callers must await it.
|
|
248
|
+
|
|
249
|
+
The activity line stays reserved above the hints. The spinner and activity text appear only while the model handler is running; the context meter remains visible. Its color changes at 70% and 90% usage, and the bar fills completely at or above the maximum.
|
|
250
|
+
|
|
251
|
+
The size callback and maximum must use the same unit. `contextWindowSize` counts saved user and response characters, not model tokens, tool logs, or the full API request. The meter updates when the saved context changes. Its maximum is visual configuration: it does not enforce a limit or compact context automatically.
|
|
252
|
+
|
|
253
|
+
### Themes and message styles
|
|
254
|
+
|
|
255
|
+
Explicit `STYLE` values override the selected theme. To use a complete preset, omit the corresponding overrides from the example:
|
|
256
|
+
|
|
257
|
+
```python
|
|
258
|
+
PANEL_STYLE = {
|
|
259
|
+
"THEME": "ocean",
|
|
260
|
+
"MESSAGE_SPACING": 1,
|
|
261
|
+
"STYLE": {
|
|
262
|
+
"user": "bg:#173f50 #ffffff",
|
|
263
|
+
"model": "#daf4f4",
|
|
264
|
+
"error": "#ff6b6b bold",
|
|
265
|
+
"context-low": "#4caf80",
|
|
266
|
+
"context-warning": "#e9b949",
|
|
267
|
+
"context-full": "#ef6461 bold",
|
|
268
|
+
},
|
|
269
|
+
}
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
Styles accept prompt_toolkit style strings or ANSI constants such as `BG_BLUE + WHITE`. `user` applies to sent messages, `model` to returned model responses, and `error` to messages beginning with `[ERROR]`. Tool output remains independent. ANSI formatting embedded in messages is preserved and can override their text color.
|
|
273
|
+
|
|
274
|
+
The model selector appears directly above the input box and uses the `completion-menu` background style. Its choices use `radio` and `radio-checked` styles. Other style keys include `chat-area`, `hint`, `separator`, `input-field`, `completion-menu.completion`, `completion-menu.completion.current`, `scrollbar`, and `scrollbar.button`.
|
|
275
|
+
|
|
276
|
+
## Tool calling
|
|
277
|
+
|
|
278
|
+
`Tools/tools.json` defines the function schemas offered to the model. `Tools/toolsCall.json` maps their names to Python handlers:
|
|
279
|
+
|
|
280
|
+
```json
|
|
281
|
+
{
|
|
282
|
+
"hourNow": "Tools.hourNow.hourNow:hourNow",
|
|
283
|
+
"runInShell": "Tools.runInShell.runInShell:runInShell",
|
|
284
|
+
"runInShellCool": "Tools.runInShell.runInShell:runInShellCool"
|
|
285
|
+
}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
The included tools are examples and conveniences for applications:
|
|
289
|
+
|
|
290
|
+
| Tool | Behavior |
|
|
291
|
+
| ---------------------------------- | ---------------------------------------------------------------------------------------------------- |
|
|
292
|
+
| `hourNow()` | Returns the current UTC date and time under `utc`. |
|
|
293
|
+
| `runInShell(cmd)` | Collects shell output without displaying it. |
|
|
294
|
+
| `runInShellCool(cmd, write=print)` | Collects shell output and sends the command, output lines, and exit status to the supplied callback. |
|
|
295
|
+
|
|
296
|
+
Both shell tools return `logs` and `exitCode`, combining standard output and standard error. Output is read by line, so subprocess buffering can delay updates. Shell commands execute locally with `shell=True`.
|
|
297
|
+
|
|
298
|
+
To add a tool, implement an importable function, add its schema to `tools.json`, and map its name to `module:function` in `toolsCall.json`. Handler keyword arguments must match the schema's parameter names. Handler mappings import Python modules and should come from trusted configuration.
|
|
299
|
+
|
|
300
|
+
`messageToLLMTools` lets the model choose tools, executes their handlers, sends results back to the model, and returns `(completion, executedTools)`. It allows up to five rounds of tool execution and raises `RuntimeError("Tool call limit exceeded")` if more calls are still requested.
|
|
301
|
+
|
|
302
|
+
Each execution record includes `id`, `name`, `arguments`, and `ok`, with `result` on success or `error` on failure. Unknown tools, invalid arguments, and handler exceptions become failed tool results. Configuration loading and API errors propagate to the caller.
|
|
303
|
+
|
|
304
|
+
### Tools without the panel
|
|
305
|
+
|
|
306
|
+
Tools do not import the panel. The output callback is supplied by the application and is not a model parameter or part of the JSON schema:
|
|
307
|
+
|
|
308
|
+
```python
|
|
309
|
+
from Tools.runInShell.runInShell import runInShellCool
|
|
310
|
+
|
|
311
|
+
result = runInShellCool("echo hello")
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
`AgentHarness` connects tools that accept a `write` parameter to the panel's `ctx.write` callback when handling a request.
|
|
315
|
+
|
|
316
|
+
## Core without the panel
|
|
317
|
+
|
|
318
|
+
Import the public API from `tinyAgents`. The request helpers are async functions and must be awaited:
|
|
319
|
+
|
|
320
|
+
```python
|
|
321
|
+
import asyncio
|
|
322
|
+
import os
|
|
323
|
+
|
|
324
|
+
from tinyAgents import startClient, messageToLLM, addToContextWindow
|
|
325
|
+
|
|
326
|
+
async def run():
|
|
327
|
+
model = "nvidia/nemotron-3-super-120b-a12b"
|
|
328
|
+
profiles = {"nvidia": {"apiKey": os.environ["API_KEY_NVIDIA"]}}
|
|
329
|
+
client = startClient(profiles, ["nvidia", model])
|
|
330
|
+
message = "Hello"
|
|
331
|
+
completion = await messageToLLM(
|
|
332
|
+
client, model, "You are a helpful assistant.", message
|
|
333
|
+
)
|
|
334
|
+
response = completion.choices[0].message.content or ""
|
|
335
|
+
addToContextWindow(message, response, "user input")
|
|
336
|
+
print(response)
|
|
337
|
+
|
|
338
|
+
asyncio.run(run())
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
This example reads environment variables directly. The helpers retain synchronous SDK calls internally, so applications with an interactive event loop should run them in a worker, as the panel example does.
|
|
342
|
+
|
|
343
|
+
## Context and API
|
|
344
|
+
|
|
345
|
+
| API | Purpose |
|
|
346
|
+
| ---------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
|
|
347
|
+
| `startClient(profiles, selected)` | Create an OpenAI-compatible client using the selected provider's key and URL. |
|
|
348
|
+
| `messageToLLM(client, model, prePrompt, message)` | Async request without tool calling; returns the SDK completion. |
|
|
349
|
+
| `loadTools(path)` | Load the `tools` array from JSON. |
|
|
350
|
+
| `loadToolHandlers(path)` | Import and validate configured Python handlers. |
|
|
351
|
+
| `messageToLLMTools(client, model, prePrompt, message, TOOLS, TOOL_HANDLERS)` | Async request with tool execution; returns completion and execution records. |
|
|
352
|
+
| `addToContextWindow(message, response, objectType, toolsRunned=[])` | Save an exchange and optional tool records. |
|
|
353
|
+
| `contextWindow` | Shared in-memory conversation dictionary. |
|
|
354
|
+
| `contextWindowSize()` | Count saved user and response tokens. |
|
|
355
|
+
| `clearContextWindow()` | Clear the saved conversation. |
|
|
356
|
+
| `OldCompactContextWindow(maxSize)` | Remove the oldest exchanges until the size is within a non-negative character limit. |
|
|
357
|
+
| `NewCompactContextWindow(client, model)` | Async request for a summary that replaces the saved conversation; must be awaited. |
|
|
358
|
+
| `prettyResolve(text)` | Format Markdown headings and emphasis for terminal output. |
|
|
359
|
+
| `AgentHarness(...)` | Connect provider/model selection, tool handlers, context, worker, and panel callbacks. |
|
|
360
|
+
|
|
361
|
+
Requests do not automatically save their results. Pass `executedTools` to `addToContextWindow` to retain tool records alongside an exchange. Context is shared within the process and is lost when it exits. Model-based compaction makes an additional API request and may lose details.
|
|
362
|
+
|
|
363
|
+
Compaction follows the same async calling pattern as `messageToLLM`:
|
|
364
|
+
|
|
365
|
+
```python
|
|
366
|
+
await NewCompactContextWindow(client, model)
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
From the panel's UI loop, use the existing worker to keep the synchronous SDK request off the UI thread:
|
|
370
|
+
|
|
371
|
+
```python
|
|
372
|
+
await agent_worker.run(NewCompactContextWindow(client, model))
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
## Project status
|
|
376
|
+
|
|
377
|
+
Version 0.2.3 adds the reusable `AgentHarness` and provider-aware model switching. The framework remains under active development. See [CHANGELOG.md](CHANGELOG.md) for release notes.
|
|
378
|
+
|
|
379
|
+
## Contributing
|
|
380
|
+
|
|
381
|
+
Suggestions and contributions are welcome. Open an issue or submit a pull request with a clear description of the change.
|
|
382
|
+
|
|
383
|
+
## Ownership
|
|
384
|
+
|
|
385
|
+
Developed under [KML Research Labs](https://github.com/KML-Research-Labs) by [Kauan Mezavila](https://github.com/kauanmezavila).
|
|
386
|
+
|
|
387
|
+
## License
|
|
388
|
+
|
|
389
|
+
Released under the [MIT License](LICENSE).
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import time
|
|
4
|
+
import random
|
|
5
|
+
|
|
6
|
+
RESET = "\033[0m"
|
|
7
|
+
BOLD = "\033[1m"
|
|
8
|
+
DIM = "\033[2m"
|
|
9
|
+
ITALIC = "\033[3m"
|
|
10
|
+
UNDERLINE = "\033[4m"
|
|
11
|
+
BLINK = "\033[5m"
|
|
12
|
+
REVERSE = "\033[7m"
|
|
13
|
+
HIDDEN = "\033[8m"
|
|
14
|
+
STRIKE = "\033[9m"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
BLACK = "\033[30m"
|
|
18
|
+
RED = "\033[31m"
|
|
19
|
+
GREEN = "\033[32m"
|
|
20
|
+
YELLOW = "\033[33m"
|
|
21
|
+
BLUE = "\033[34m"
|
|
22
|
+
MAGENTA = "\033[35m"
|
|
23
|
+
CYAN = "\033[36m"
|
|
24
|
+
WHITE = "\033[37m"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
BRIGHT_BLACK = "\033[90m"
|
|
28
|
+
BRIGHT_RED = "\033[91m"
|
|
29
|
+
BRIGHT_GREEN = "\033[92m"
|
|
30
|
+
BRIGHT_YELLOW = "\033[93m"
|
|
31
|
+
BRIGHT_BLUE = "\033[94m"
|
|
32
|
+
BRIGHT_MAGENTA = "\033[95m"
|
|
33
|
+
BRIGHT_CYAN = "\033[96m"
|
|
34
|
+
BRIGHT_WHITE = "\033[97m"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
BG_BLACK = "\033[40m"
|
|
38
|
+
BG_RED = "\033[41m"
|
|
39
|
+
BG_GREEN = "\033[42m"
|
|
40
|
+
BG_YELLOW = "\033[43m"
|
|
41
|
+
BG_BLUE = "\033[44m"
|
|
42
|
+
BG_MAGENTA = "\033[45m"
|
|
43
|
+
BG_CYAN = "\033[46m"
|
|
44
|
+
BG_WHITE = "\033[47m"
|
|
45
|
+
|
|
46
|
+
BG_BRIGHT_BLACK = "\033[100m"
|
|
47
|
+
BG_BRIGHT_RED = "\033[101m"
|
|
48
|
+
BG_BRIGHT_GREEN = "\033[102m"
|
|
49
|
+
BG_BRIGHT_YELLOW = "\033[103m"
|
|
50
|
+
BG_BRIGHT_BLUE = "\033[104m"
|
|
51
|
+
BG_BRIGHT_MAGENTA = "\033[105m"
|
|
52
|
+
BG_BRIGHT_CYAN = "\033[106m"
|
|
53
|
+
BG_BRIGHT_WHITE = "\033[107m"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def rgb(r, g, b):
|
|
57
|
+
return f"\033[38;2;{r};{g};{b}m"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def bg_rgb(r, g, b):
|
|
61
|
+
return f"\033[48;2;{r};{g};{b}m"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def hex_color(value):
|
|
65
|
+
value = value.lstrip("#")
|
|
66
|
+
r, g, b = (int(value[i : i + 2], 16) for i in (0, 2, 4))
|
|
67
|
+
return rgb(r, g, b)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def clear():
|
|
71
|
+
os.system("cls" if os.name == "nt" else "clear")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def slow(text, delay=0.03, color=""):
|
|
75
|
+
for char in text:
|
|
76
|
+
sys.stdout.write(color + char + RESET)
|
|
77
|
+
sys.stdout.flush()
|
|
78
|
+
time.sleep(delay)
|
|
79
|
+
print()
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def loading(text="Loading", duration=2, color=CYAN):
|
|
83
|
+
frames = ["|", "/", "-", "\\"]
|
|
84
|
+
end = time.time() + duration
|
|
85
|
+
|
|
86
|
+
while time.time() < end:
|
|
87
|
+
for frame in frames:
|
|
88
|
+
sys.stdout.write(f"\r{color}{frame} {text}...{RESET}")
|
|
89
|
+
sys.stdout.flush()
|
|
90
|
+
time.sleep(0.1)
|
|
91
|
+
if time.time() >= end:
|
|
92
|
+
break
|
|
93
|
+
|
|
94
|
+
print(f"\r{GREEN}✓ {text} completed!{RESET}")
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def progress(total=20, delay=0.05, color=GREEN):
|
|
98
|
+
for i in range(total + 1):
|
|
99
|
+
percent = int(i / total * 100)
|
|
100
|
+
bar = "█" * i + "░" * (total - i)
|
|
101
|
+
|
|
102
|
+
sys.stdout.write(f"\r{color}[{bar}] {percent}%{RESET}")
|
|
103
|
+
sys.stdout.flush()
|
|
104
|
+
time.sleep(delay)
|
|
105
|
+
|
|
106
|
+
print()
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def rainbow(text):
|
|
110
|
+
colors = [RED, YELLOW, GREEN, CYAN, BLUE, MAGENTA]
|
|
111
|
+
for i, char in enumerate(text):
|
|
112
|
+
sys.stdout.write(colors[i % len(colors)] + char)
|
|
113
|
+
print(RESET, end="")
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def flash(text, times=3, delay=0.2, color=YELLOW):
|
|
117
|
+
for _ in range(times):
|
|
118
|
+
print(f"\r{color}{BOLD}{text}{RESET}", end="", flush=True)
|
|
119
|
+
time.sleep(delay)
|
|
120
|
+
print("\r" + " " * len(text), end="", flush=True)
|
|
121
|
+
time.sleep(delay)
|
|
122
|
+
print(f"\r{color}{BOLD}{text}{RESET}")
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def random_color(text):
|
|
126
|
+
colors = [RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, BRIGHT_WHITE]
|
|
127
|
+
print(f"{random.choice(colors)}{text}{RESET}")
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def box(text, color=CYAN):
|
|
131
|
+
lines = text.splitlines() or [""]
|
|
132
|
+
width = max(map(len, lines))
|
|
133
|
+
border = "+" + "-" * (width + 2) + "+"
|
|
134
|
+
|
|
135
|
+
print(f"{color}{border}{RESET}")
|
|
136
|
+
for line in lines:
|
|
137
|
+
print(f"{color}|{RESET} {line:<{width}} {color}|{RESET}")
|
|
138
|
+
print(f"{color}{border}{RESET}")
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def title(text, color=BRIGHT_CYAN):
|
|
142
|
+
print(f"\n{color}{BOLD}{'=' * 40}")
|
|
143
|
+
print(f" {text}")
|
|
144
|
+
print(f"{'=' * 40}{RESET}\n")
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
from Packages.colors import *
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def prettyResolve(text):
|
|
5
|
+
result = []
|
|
6
|
+
|
|
7
|
+
bold = False
|
|
8
|
+
title_color = ""
|
|
9
|
+
title_active = False
|
|
10
|
+
line_start = True
|
|
11
|
+
i = 0
|
|
12
|
+
|
|
13
|
+
def apply_style():
|
|
14
|
+
result.append((title_color or "") + (BOLD if bold or title_active else ""))
|
|
15
|
+
|
|
16
|
+
while i < len(text):
|
|
17
|
+
if line_start:
|
|
18
|
+
if text[i : i + 4] == "### ":
|
|
19
|
+
title_color = CYAN
|
|
20
|
+
title_active = True
|
|
21
|
+
result.append(title_color + BOLD)
|
|
22
|
+
i += 4
|
|
23
|
+
line_start = False
|
|
24
|
+
continue
|
|
25
|
+
|
|
26
|
+
elif text[i : i + 3] == "## ":
|
|
27
|
+
title_color = BLUE
|
|
28
|
+
title_active = True
|
|
29
|
+
result.append(title_color + BOLD)
|
|
30
|
+
i += 3
|
|
31
|
+
line_start = False
|
|
32
|
+
continue
|
|
33
|
+
|
|
34
|
+
elif text[i : i + 2] == "# ":
|
|
35
|
+
title_color = YELLOW
|
|
36
|
+
title_active = True
|
|
37
|
+
result.append(title_color + BOLD)
|
|
38
|
+
i += 2
|
|
39
|
+
line_start = False
|
|
40
|
+
continue
|
|
41
|
+
|
|
42
|
+
if text[i : i + 2] == "**":
|
|
43
|
+
bold = not bold
|
|
44
|
+
result.append(RESET)
|
|
45
|
+
apply_style()
|
|
46
|
+
i += 2
|
|
47
|
+
line_start = False
|
|
48
|
+
continue
|
|
49
|
+
|
|
50
|
+
if text[i] == "*":
|
|
51
|
+
bold = not bold
|
|
52
|
+
result.append(RESET)
|
|
53
|
+
apply_style()
|
|
54
|
+
i += 1
|
|
55
|
+
line_start = False
|
|
56
|
+
continue
|
|
57
|
+
|
|
58
|
+
char = text[i]
|
|
59
|
+
result.append(char)
|
|
60
|
+
i += 1
|
|
61
|
+
|
|
62
|
+
if char == "\n":
|
|
63
|
+
|
|
64
|
+
result.append(RESET)
|
|
65
|
+
|
|
66
|
+
bold = False
|
|
67
|
+
title_color = ""
|
|
68
|
+
title_active = False
|
|
69
|
+
line_start = True
|
|
70
|
+
else:
|
|
71
|
+
line_start = False
|
|
72
|
+
|
|
73
|
+
result.append(RESET)
|
|
74
|
+
|
|
75
|
+
return "".join(result)
|