audatron 0.1.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.
- audatron-0.1.0/PKG-INFO +165 -0
- audatron-0.1.0/README.md +146 -0
- audatron-0.1.0/pyproject.toml +34 -0
- audatron-0.1.0/setup.cfg +4 -0
- audatron-0.1.0/src/audatron/__init__.py +0 -0
- audatron-0.1.0/src/audatron/agent.py +142 -0
- audatron-0.1.0/src/audatron/audacity.py +454 -0
- audatron-0.1.0/src/audatron/aup3.py +413 -0
- audatron-0.1.0/src/audatron/backends.py +305 -0
- audatron-0.1.0/src/audatron/cli.py +274 -0
- audatron-0.1.0/src/audatron/dsp.py +312 -0
- audatron-0.1.0/src/audatron/live.py +210 -0
- audatron-0.1.0/src/audatron/mcp_server.py +162 -0
- audatron-0.1.0/src/audatron/session.py +173 -0
- audatron-0.1.0/src/audatron/tools.py +205 -0
- audatron-0.1.0/src/audatron.egg-info/PKG-INFO +165 -0
- audatron-0.1.0/src/audatron.egg-info/SOURCES.txt +30 -0
- audatron-0.1.0/src/audatron.egg-info/dependency_links.txt +1 -0
- audatron-0.1.0/src/audatron.egg-info/entry_points.txt +3 -0
- audatron-0.1.0/src/audatron.egg-info/requires.txt +12 -0
- audatron-0.1.0/src/audatron.egg-info/top_level.txt +1 -0
- audatron-0.1.0/tests/test_agent.py +93 -0
- audatron-0.1.0/tests/test_audacity.py +109 -0
- audatron-0.1.0/tests/test_audacity_launch.py +77 -0
- audatron-0.1.0/tests/test_aup3.py +416 -0
- audatron-0.1.0/tests/test_cli.py +52 -0
- audatron-0.1.0/tests/test_dsp.py +168 -0
- audatron-0.1.0/tests/test_e2e.py +75 -0
- audatron-0.1.0/tests/test_live.py +116 -0
- audatron-0.1.0/tests/test_llm_smoke.py +74 -0
- audatron-0.1.0/tests/test_mcp.py +89 -0
- audatron-0.1.0/tests/test_tools.py +110 -0
audatron-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: audatron
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: audatron — LLM-driven realtime Audacity: natural language drives track editing, noise removal, and mastering, with a headless DSP engine plus auto-generated Audacity TCL scripts, an agent chat REPL, and an MCP stdio server.
|
|
5
|
+
Author: DREAM PIXELS FORGE
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: numpy>=1.26
|
|
10
|
+
Requires-Dist: soundfile>=0.12
|
|
11
|
+
Requires-Dist: typer>=0.12
|
|
12
|
+
Requires-Dist: rich>=13
|
|
13
|
+
Requires-Dist: httpx>=0.27
|
|
14
|
+
Requires-Dist: pydub>=0.25
|
|
15
|
+
Provides-Extra: noisereduce
|
|
16
|
+
Requires-Dist: noisereduce>=3.0; extra == "noisereduce"
|
|
17
|
+
Provides-Extra: test
|
|
18
|
+
Requires-Dist: pytest>=8; extra == "test"
|
|
19
|
+
|
|
20
|
+
# audatron
|
|
21
|
+
|
|
22
|
+
**Natural-language audio production, mirrored live into Audacity.**
|
|
23
|
+
|
|
24
|
+
audatron is a realtime, LLM-driven toolchain: you prompt an agent (or any
|
|
25
|
+
coding tool — Claude Code, Codex, opencode, kimi, pi, qwen code —) in plain
|
|
26
|
+
English, and it drives a real Audacity workflow in your browser: inspect the
|
|
27
|
+
waveform, trim tracks, strip noise, master to a target loudness, and export —
|
|
28
|
+
while the matching Audacity project + TCL/Lua scripts are written so you can
|
|
29
|
+
watch the edits land on the open project.
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
you (natural language)
|
|
33
|
+
│
|
|
34
|
+
▼
|
|
35
|
+
┌──────────────────────────────┐
|
|
36
|
+
│ audatron agent (LLM loop) │ ← Anthropic / OpenAI-compatible / Ollama
|
|
37
|
+
└──────────────────────────────┘
|
|
38
|
+
│ tool calls
|
|
39
|
+
▼
|
|
40
|
+
┌──────────────────────────────┐
|
|
41
|
+
│ DSP engine (headless) │ ← trim / gain / normalize / noise / master / export
|
|
42
|
+
└──────────────────────────────┘
|
|
43
|
+
│ every step
|
|
44
|
+
▼
|
|
45
|
+
┌──────────────────────────────┐
|
|
46
|
+
│ Audacity bridge │ ← .aup3 project + generated TCL + Lua scripts
|
|
47
|
+
└──────────────────────────────┘
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Install
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pip install -e .
|
|
54
|
+
audatron doctor # verify numpy, ffmpeg, Audacity, LLM backends
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Quickstart
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
# 1. generate a test clip
|
|
61
|
+
audatron demo demo.wav --freq 440 --seconds 3
|
|
62
|
+
|
|
63
|
+
# 2. one-shot: natural language in, mastered wav out
|
|
64
|
+
audatron run "trim to 2s, remove hiss, master to -14 LUFS, export wav" demo.wav --backend anthropic
|
|
65
|
+
|
|
66
|
+
# 3. interactive REPL
|
|
67
|
+
audatron chat demo.wav --backend anthropic
|
|
68
|
+
# you › make it sound like a radio
|
|
69
|
+
# you › sync ← pushes the deck into a live Audacity project
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### The LLM agent is optional
|
|
73
|
+
|
|
74
|
+
Every operation the agent can perform is also available without an LLM:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
audatron inspect demo.wav # stats + spectral analysis
|
|
78
|
+
audatron sync demo.wav # Audacity project + TCL/Lua, no LLM
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Audacity integration
|
|
82
|
+
|
|
83
|
+
`audatron sync` writes:
|
|
84
|
+
|
|
85
|
+
| File | Purpose |
|
|
86
|
+
|---------------------------|----------------------------------------------------|
|
|
87
|
+
| `deck.wav` | clean copy of the current deck |
|
|
88
|
+
| `<name>.aup3` | Audacity 3.x XML project (fallback) |
|
|
89
|
+
| `<name>.native.aup3` | **native Audacity 4 SQLite project** (primary) |
|
|
90
|
+
| `audatron.tcl` | generated TCL script (run via Tools → Macros) |
|
|
91
|
+
| `audatron.lua` | generated Lua script (Audacity 2.x scripting) |
|
|
92
|
+
| `audatron.json` | machine-readable manifest of the ops |
|
|
93
|
+
|
|
94
|
+
The `.native.aup3` is a real SQLite database matching Audacity 4's
|
|
95
|
+
on-disk format: four tables (`project`, `sampleblocks`, `autosave`,
|
|
96
|
+
`project_history`), float32 sample blocks sized the way the app writes
|
|
97
|
+
them (up to 262144 samples / 1 MiB), and the app's `dict`/`doc`
|
|
98
|
+
record streams. It opens natively with the waveform + track layout
|
|
99
|
+
intact, no external WAV side-file needed. The XML `.aup3` is kept as
|
|
100
|
+
a fallback for older Audacity builds.
|
|
101
|
+
|
|
102
|
+
Open it in your running Audacity 4:
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
"C:\Program Files\Audacity 4\bin\Audacity4.exe" <project>\river_master-proj.native.aup3
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
or let audatron launch it for you:
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
audatron sync river_master.wav --open-audacity # opens the native project
|
|
112
|
+
audatron sync river_master.wav --run-tcl # + auto-runs the TCL macro
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## MCP server
|
|
116
|
+
|
|
117
|
+
audatron ships an MCP stdio server so **any** MCP-capable coding agent can
|
|
118
|
+
drive it. The tools are the same DSP surface the CLI agent uses.
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
audatron-mcp # newline-delimited JSON-RPC 2.0 over stdio
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Wiring it into a coding agent — see **[dev-notes/MCP-SETUP.md](dev-notes/MCP-SETUP.md)** for
|
|
125
|
+
Claude Code, Codex, opencode, kimi, qwen-code, and generic MCP clients.
|
|
126
|
+
|
|
127
|
+
## Tool surface
|
|
128
|
+
|
|
129
|
+
| Tool | What it does |
|
|
130
|
+
|----------------|--------------|
|
|
131
|
+
| `inspect` | sr, duration, peak, loudness dB, silence ratio |
|
|
132
|
+
| `analyze` | spectral centroid, onsets, silence ranges |
|
|
133
|
+
| `trim` | keep a `[start, end]` window |
|
|
134
|
+
| `gain` | apply ± dB |
|
|
135
|
+
| `normalize` | peak-normalize to a target dBFS |
|
|
136
|
+
| `noise_reduce` | spectral-subtraction denoise (profile from head) |
|
|
137
|
+
| `master` | loudness-normalize to target LUFS + peak ceiling |
|
|
138
|
+
| `export` | write wav / flac / mp3 / ogg |
|
|
139
|
+
| `sync` | generate the Audacity project + scripts |
|
|
140
|
+
| `history` / `undo` | deck state |
|
|
141
|
+
| `load` | replace the deck with a new file |
|
|
142
|
+
|
|
143
|
+
## Development
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
pip install -e ".[test]"
|
|
147
|
+
python -m pytest -q # 102 tests, ~45s
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Layout:
|
|
151
|
+
|
|
152
|
+
```
|
|
153
|
+
src/audatron/
|
|
154
|
+
dsp.py # headless DSP engine (numpy + soundfile)
|
|
155
|
+
session.py # stateful deck + op history
|
|
156
|
+
tools.py # tool surface shared by CLI agent + MCP
|
|
157
|
+
agent.py # LLM agent loop (Anthropic/OpenAI/Ollama)
|
|
158
|
+
backends.py # LLM provider clients (+ streaming)
|
|
159
|
+
live.py # realtime foreground view (token stream + waveform)
|
|
160
|
+
audacity.py # .aup3 + TCL/Lua generation + Audacity lookup
|
|
161
|
+
aup3.py # native Audacity-4 SQLite .aup3 writer
|
|
162
|
+
mcp_server.py # MCP stdio JSON-RPC server
|
|
163
|
+
cli.py # typer CLI
|
|
164
|
+
tests/ # pytest: DSP, tools, agent (mocked), MCP, e2e, CLI, aup3, live
|
|
165
|
+
```
|
audatron-0.1.0/README.md
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# audatron
|
|
2
|
+
|
|
3
|
+
**Natural-language audio production, mirrored live into Audacity.**
|
|
4
|
+
|
|
5
|
+
audatron is a realtime, LLM-driven toolchain: you prompt an agent (or any
|
|
6
|
+
coding tool — Claude Code, Codex, opencode, kimi, pi, qwen code —) in plain
|
|
7
|
+
English, and it drives a real Audacity workflow in your browser: inspect the
|
|
8
|
+
waveform, trim tracks, strip noise, master to a target loudness, and export —
|
|
9
|
+
while the matching Audacity project + TCL/Lua scripts are written so you can
|
|
10
|
+
watch the edits land on the open project.
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
you (natural language)
|
|
14
|
+
│
|
|
15
|
+
▼
|
|
16
|
+
┌──────────────────────────────┐
|
|
17
|
+
│ audatron agent (LLM loop) │ ← Anthropic / OpenAI-compatible / Ollama
|
|
18
|
+
└──────────────────────────────┘
|
|
19
|
+
│ tool calls
|
|
20
|
+
▼
|
|
21
|
+
┌──────────────────────────────┐
|
|
22
|
+
│ DSP engine (headless) │ ← trim / gain / normalize / noise / master / export
|
|
23
|
+
└──────────────────────────────┘
|
|
24
|
+
│ every step
|
|
25
|
+
▼
|
|
26
|
+
┌──────────────────────────────┐
|
|
27
|
+
│ Audacity bridge │ ← .aup3 project + generated TCL + Lua scripts
|
|
28
|
+
└──────────────────────────────┘
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Install
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install -e .
|
|
35
|
+
audatron doctor # verify numpy, ffmpeg, Audacity, LLM backends
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Quickstart
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# 1. generate a test clip
|
|
42
|
+
audatron demo demo.wav --freq 440 --seconds 3
|
|
43
|
+
|
|
44
|
+
# 2. one-shot: natural language in, mastered wav out
|
|
45
|
+
audatron run "trim to 2s, remove hiss, master to -14 LUFS, export wav" demo.wav --backend anthropic
|
|
46
|
+
|
|
47
|
+
# 3. interactive REPL
|
|
48
|
+
audatron chat demo.wav --backend anthropic
|
|
49
|
+
# you › make it sound like a radio
|
|
50
|
+
# you › sync ← pushes the deck into a live Audacity project
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### The LLM agent is optional
|
|
54
|
+
|
|
55
|
+
Every operation the agent can perform is also available without an LLM:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
audatron inspect demo.wav # stats + spectral analysis
|
|
59
|
+
audatron sync demo.wav # Audacity project + TCL/Lua, no LLM
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Audacity integration
|
|
63
|
+
|
|
64
|
+
`audatron sync` writes:
|
|
65
|
+
|
|
66
|
+
| File | Purpose |
|
|
67
|
+
|---------------------------|----------------------------------------------------|
|
|
68
|
+
| `deck.wav` | clean copy of the current deck |
|
|
69
|
+
| `<name>.aup3` | Audacity 3.x XML project (fallback) |
|
|
70
|
+
| `<name>.native.aup3` | **native Audacity 4 SQLite project** (primary) |
|
|
71
|
+
| `audatron.tcl` | generated TCL script (run via Tools → Macros) |
|
|
72
|
+
| `audatron.lua` | generated Lua script (Audacity 2.x scripting) |
|
|
73
|
+
| `audatron.json` | machine-readable manifest of the ops |
|
|
74
|
+
|
|
75
|
+
The `.native.aup3` is a real SQLite database matching Audacity 4's
|
|
76
|
+
on-disk format: four tables (`project`, `sampleblocks`, `autosave`,
|
|
77
|
+
`project_history`), float32 sample blocks sized the way the app writes
|
|
78
|
+
them (up to 262144 samples / 1 MiB), and the app's `dict`/`doc`
|
|
79
|
+
record streams. It opens natively with the waveform + track layout
|
|
80
|
+
intact, no external WAV side-file needed. The XML `.aup3` is kept as
|
|
81
|
+
a fallback for older Audacity builds.
|
|
82
|
+
|
|
83
|
+
Open it in your running Audacity 4:
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
"C:\Program Files\Audacity 4\bin\Audacity4.exe" <project>\river_master-proj.native.aup3
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
or let audatron launch it for you:
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
audatron sync river_master.wav --open-audacity # opens the native project
|
|
93
|
+
audatron sync river_master.wav --run-tcl # + auto-runs the TCL macro
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## MCP server
|
|
97
|
+
|
|
98
|
+
audatron ships an MCP stdio server so **any** MCP-capable coding agent can
|
|
99
|
+
drive it. The tools are the same DSP surface the CLI agent uses.
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
audatron-mcp # newline-delimited JSON-RPC 2.0 over stdio
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Wiring it into a coding agent — see **[dev-notes/MCP-SETUP.md](dev-notes/MCP-SETUP.md)** for
|
|
106
|
+
Claude Code, Codex, opencode, kimi, qwen-code, and generic MCP clients.
|
|
107
|
+
|
|
108
|
+
## Tool surface
|
|
109
|
+
|
|
110
|
+
| Tool | What it does |
|
|
111
|
+
|----------------|--------------|
|
|
112
|
+
| `inspect` | sr, duration, peak, loudness dB, silence ratio |
|
|
113
|
+
| `analyze` | spectral centroid, onsets, silence ranges |
|
|
114
|
+
| `trim` | keep a `[start, end]` window |
|
|
115
|
+
| `gain` | apply ± dB |
|
|
116
|
+
| `normalize` | peak-normalize to a target dBFS |
|
|
117
|
+
| `noise_reduce` | spectral-subtraction denoise (profile from head) |
|
|
118
|
+
| `master` | loudness-normalize to target LUFS + peak ceiling |
|
|
119
|
+
| `export` | write wav / flac / mp3 / ogg |
|
|
120
|
+
| `sync` | generate the Audacity project + scripts |
|
|
121
|
+
| `history` / `undo` | deck state |
|
|
122
|
+
| `load` | replace the deck with a new file |
|
|
123
|
+
|
|
124
|
+
## Development
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
pip install -e ".[test]"
|
|
128
|
+
python -m pytest -q # 102 tests, ~45s
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Layout:
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
src/audatron/
|
|
135
|
+
dsp.py # headless DSP engine (numpy + soundfile)
|
|
136
|
+
session.py # stateful deck + op history
|
|
137
|
+
tools.py # tool surface shared by CLI agent + MCP
|
|
138
|
+
agent.py # LLM agent loop (Anthropic/OpenAI/Ollama)
|
|
139
|
+
backends.py # LLM provider clients (+ streaming)
|
|
140
|
+
live.py # realtime foreground view (token stream + waveform)
|
|
141
|
+
audacity.py # .aup3 + TCL/Lua generation + Audacity lookup
|
|
142
|
+
aup3.py # native Audacity-4 SQLite .aup3 writer
|
|
143
|
+
mcp_server.py # MCP stdio JSON-RPC server
|
|
144
|
+
cli.py # typer CLI
|
|
145
|
+
tests/ # pytest: DSP, tools, agent (mocked), MCP, e2e, CLI, aup3, live
|
|
146
|
+
```
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "audatron"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "audatron — LLM-driven realtime Audacity: natural language drives track editing, noise removal, and mastering, with a headless DSP engine plus auto-generated Audacity TCL scripts, an agent chat REPL, and an MCP stdio server."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "DREAM PIXELS FORGE" }]
|
|
13
|
+
dependencies = [
|
|
14
|
+
"numpy>=1.26",
|
|
15
|
+
"soundfile>=0.12",
|
|
16
|
+
"typer>=0.12",
|
|
17
|
+
"rich>=13",
|
|
18
|
+
"httpx>=0.27",
|
|
19
|
+
"pydub>=0.25",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
[project.optional-dependencies]
|
|
23
|
+
noisereduce = ["noisereduce>=3.0"]
|
|
24
|
+
test = ["pytest>=8"]
|
|
25
|
+
|
|
26
|
+
[project.scripts]
|
|
27
|
+
audatron = "audatron.cli:app"
|
|
28
|
+
audatron-mcp = "audatron.mcp_server:main"
|
|
29
|
+
|
|
30
|
+
[tool.setuptools.packages.find]
|
|
31
|
+
where = ["src"]
|
|
32
|
+
|
|
33
|
+
[tool.pytest.ini_options]
|
|
34
|
+
testpaths = ["tests"]
|
audatron-0.1.0/setup.cfg
ADDED
|
File without changes
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"""audatron.agent — the natural-language agent loop.
|
|
2
|
+
|
|
3
|
+
One `Agent` owns:
|
|
4
|
+
* a LLM backend (Anthropic / OpenAI / Ollama)
|
|
5
|
+
* a Session (the audio deck)
|
|
6
|
+
* a conversation history
|
|
7
|
+
* a trace log (every user turn, assistant turn, tool call, and tool result)
|
|
8
|
+
|
|
9
|
+
`Agent.run(prompt)` executes one user turn to completion: it may call
|
|
10
|
+
tools, observe results, and keep calling until the model stops.
|
|
11
|
+
The trace is returned alongside the final assistant text so the CLI
|
|
12
|
+
and MCP surfaces can render / inspect it.
|
|
13
|
+
|
|
14
|
+
The conversation is kept in one provider-native `messages` list that grows
|
|
15
|
+
as the loop runs — each backend's tool-call / tool-result wire format is
|
|
16
|
+
handled by per-branch appends, so the same Agent works across all three.
|
|
17
|
+
"""
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import json
|
|
21
|
+
from dataclasses import dataclass, field
|
|
22
|
+
from typing import Any
|
|
23
|
+
|
|
24
|
+
from .backends import AnthropicBackend, OllamaBackend, OpenAIBackend
|
|
25
|
+
from .session import Session
|
|
26
|
+
from .tools import TOOL_SCHEMAS, dispatch
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
SYSTEM_PROMPT = """You are audatron, a realtime audio-production agent.
|
|
30
|
+
|
|
31
|
+
You control a headless DSP deck (the "deck") that mirrors a live Audacity
|
|
32
|
+
project. The deck is ALREADY LOADED with the audio the user pointed you at —
|
|
33
|
+
do NOT call `load` unless the user explicitly names a different file. Work
|
|
34
|
+
directly on the current deck.
|
|
35
|
+
|
|
36
|
+
Use the tools to inspect, edit, clean, master, and export audio. After each
|
|
37
|
+
tool call, reason from the result. Keep tool calls minimal: one op per step,
|
|
38
|
+
verify with `inspect` when in doubt. When the user asks for a sync, call
|
|
39
|
+
`sync` to generate the matching Audacity TCL/Lua scripts.
|
|
40
|
+
|
|
41
|
+
Tool results are JSON. Do not invent audio properties — read them from
|
|
42
|
+
`inspect`/`analyze`.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass
|
|
47
|
+
class TraceEntry:
|
|
48
|
+
role: str # "user" | "assistant" | "tool"
|
|
49
|
+
name: str = "" # tool name when role == "tool"
|
|
50
|
+
text: str = "" # assistant/user text or tool-result JSON
|
|
51
|
+
calls: list[dict] = field(default_factory=list) # tool_calls when role == "assistant"
|
|
52
|
+
|
|
53
|
+
def to_dict(self) -> dict:
|
|
54
|
+
return {"role": self.role, "name": self.name, "text": self.text, "calls": self.calls}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class Agent:
|
|
58
|
+
def __init__(
|
|
59
|
+
self,
|
|
60
|
+
session: Session,
|
|
61
|
+
backend: AnthropicBackend | OpenAIBackend | OllamaBackend,
|
|
62
|
+
max_steps: int = 8,
|
|
63
|
+
):
|
|
64
|
+
self.session = session
|
|
65
|
+
self.backend = backend
|
|
66
|
+
self.max_steps = max_steps
|
|
67
|
+
self.trace: list[TraceEntry] = []
|
|
68
|
+
|
|
69
|
+
# ------------------------------------------------------------------
|
|
70
|
+
# public API
|
|
71
|
+
# ------------------------------------------------------------------
|
|
72
|
+
|
|
73
|
+
def run(self, prompt: str) -> dict:
|
|
74
|
+
"""Execute one user turn. Returns {"text": ..., "trace": [...], "tool_calls": N}."""
|
|
75
|
+
self.trace.append(TraceEntry(role="user", text=prompt))
|
|
76
|
+
messages: list[dict] = [{"role": "user", "content": SYSTEM_PROMPT}, {"role": "user", "content": prompt}]
|
|
77
|
+
tool_calls_total = 0
|
|
78
|
+
|
|
79
|
+
for _step in range(self.max_steps):
|
|
80
|
+
text, calls = self.backend.complete(messages, TOOL_SCHEMAS)
|
|
81
|
+
if not calls:
|
|
82
|
+
self.trace.append(TraceEntry(role="assistant", text=text))
|
|
83
|
+
return {"text": text, "trace": self.trace, "tool_calls": tool_calls_total}
|
|
84
|
+
|
|
85
|
+
self.trace.append(TraceEntry(role="assistant", text=text, calls=calls))
|
|
86
|
+
tool_calls_total += len(calls)
|
|
87
|
+
|
|
88
|
+
if self.backend.name == "anthropic":
|
|
89
|
+
results = []
|
|
90
|
+
for c in calls:
|
|
91
|
+
out = dispatch(self.session, c["name"], c.get("arguments") or {})
|
|
92
|
+
results.append({"type": "tool_result", "tool_use_id": c["id"], "content": json.dumps(out)})
|
|
93
|
+
self.trace.append(TraceEntry(role="tool", name=c["name"], text=json.dumps(out)))
|
|
94
|
+
messages.append({"role": "assistant", "content": _anthropic_blocks(text, calls)})
|
|
95
|
+
messages.append({"role": "user", "content": results})
|
|
96
|
+
elif self.backend.name == "ollama":
|
|
97
|
+
# Ollama chat format: assistant message carries tool_calls (OpenAI-ish,
|
|
98
|
+
# args as dict), tool results use role "tool" with the tool name.
|
|
99
|
+
messages.append({
|
|
100
|
+
"role": "assistant",
|
|
101
|
+
"content": text,
|
|
102
|
+
"tool_calls": [
|
|
103
|
+
{"id": c["id"], "function": {"name": c["name"], "arguments": c.get("arguments") or {}}}
|
|
104
|
+
for c in calls
|
|
105
|
+
],
|
|
106
|
+
})
|
|
107
|
+
for c in calls:
|
|
108
|
+
out = dispatch(self.session, c["name"], c.get("arguments") or {})
|
|
109
|
+
self.trace.append(TraceEntry(role="tool", name=c["name"], text=json.dumps(out)))
|
|
110
|
+
messages.append({"role": "tool", "name": c["name"], "content": json.dumps(out)})
|
|
111
|
+
else: # openai-compatible
|
|
112
|
+
messages.append({
|
|
113
|
+
"role": "assistant",
|
|
114
|
+
"content": text,
|
|
115
|
+
"tool_calls": [
|
|
116
|
+
{"id": c["id"], "type": "function", "function": {"name": c["name"], "arguments": json.dumps(c.get("arguments") or {})}}
|
|
117
|
+
for c in calls
|
|
118
|
+
],
|
|
119
|
+
})
|
|
120
|
+
for c in calls:
|
|
121
|
+
out = dispatch(self.session, c["name"], c.get("arguments") or {})
|
|
122
|
+
self.trace.append(TraceEntry(role="tool", name=c["name"], text=json.dumps(out)))
|
|
123
|
+
messages.append({"role": "tool", "tool_call_id": c["id"], "content": json.dumps(out)})
|
|
124
|
+
|
|
125
|
+
# ran out of steps
|
|
126
|
+
return {"text": "stopped at max_steps", "trace": self.trace, "tool_calls": tool_calls_total}
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
# ---------------------------------------------------------------------------
|
|
130
|
+
# Wire-format helpers
|
|
131
|
+
# ---------------------------------------------------------------------------
|
|
132
|
+
|
|
133
|
+
def _anthropic_blocks(text: str, calls: list[dict]) -> list[dict]:
|
|
134
|
+
blocks: list[dict] = []
|
|
135
|
+
if text:
|
|
136
|
+
blocks.append({"type": "text", "text": text})
|
|
137
|
+
for c in calls:
|
|
138
|
+
blocks.append({"type": "tool_use", "id": c["id"], "name": c["name"], "input": c.get("arguments") or {}})
|
|
139
|
+
return blocks
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
__all__ = ["Agent", "SYSTEM_PROMPT", "TraceEntry"]
|