steplight 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.
- steplight-0.1.0/LICENSE +21 -0
- steplight-0.1.0/PKG-INFO +181 -0
- steplight-0.1.0/README.md +145 -0
- steplight-0.1.0/pyproject.toml +85 -0
- steplight-0.1.0/setup.cfg +4 -0
- steplight-0.1.0/steplight/__init__.py +5 -0
- steplight-0.1.0/steplight/adapters/__init__.py +1 -0
- steplight-0.1.0/steplight/adapters/common.py +30 -0
- steplight-0.1.0/steplight/adapters/generic.py +156 -0
- steplight-0.1.0/steplight/adapters/langchain.py +84 -0
- steplight-0.1.0/steplight/adapters/mcp.py +58 -0
- steplight-0.1.0/steplight/adapters/openai.py +102 -0
- steplight-0.1.0/steplight/cli/__init__.py +1 -0
- steplight-0.1.0/steplight/cli/config.py +30 -0
- steplight-0.1.0/steplight/cli/main.py +166 -0
- steplight-0.1.0/steplight/core/__init__.py +1 -0
- steplight-0.1.0/steplight/core/analyzer.py +229 -0
- steplight-0.1.0/steplight/core/models.py +70 -0
- steplight-0.1.0/steplight/core/parser.py +79 -0
- steplight-0.1.0/steplight/core/stats.py +98 -0
- steplight-0.1.0/steplight/export/__init__.py +1 -0
- steplight-0.1.0/steplight/export/html.py +24 -0
- steplight-0.1.0/steplight/export/templates/report.html.jinja +617 -0
- steplight-0.1.0/steplight/tui/__init__.py +1 -0
- steplight-0.1.0/steplight/tui/app.py +95 -0
- steplight-0.1.0/steplight/tui/detail_panel.py +28 -0
- steplight-0.1.0/steplight/tui/diagnostics.py +17 -0
- steplight-0.1.0/steplight/tui/timeline.py +25 -0
- steplight-0.1.0/steplight.egg-info/PKG-INFO +181 -0
- steplight-0.1.0/steplight.egg-info/SOURCES.txt +37 -0
- steplight-0.1.0/steplight.egg-info/dependency_links.txt +1 -0
- steplight-0.1.0/steplight.egg-info/entry_points.txt +3 -0
- steplight-0.1.0/steplight.egg-info/requires.txt +14 -0
- steplight-0.1.0/steplight.egg-info/top_level.txt +1 -0
- steplight-0.1.0/tests/test_analyzer.py +24 -0
- steplight-0.1.0/tests/test_cli.py +51 -0
- steplight-0.1.0/tests/test_export_html.py +63 -0
- steplight-0.1.0/tests/test_parser.py +32 -0
- steplight-0.1.0/tests/test_tui.py +19 -0
steplight-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Riccardo Merenda
|
|
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.
|
steplight-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: steplight
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Local-first trace inspector for LLM agents and tool-driven workflows.
|
|
5
|
+
Author: Riccardo Merenda
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Keywords: llm,agents,tracing,observability,tui,cli,debugging
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Environment :: Console
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
17
|
+
Classifier: Topic :: Software Development :: Testing
|
|
18
|
+
Classifier: Topic :: Terminals
|
|
19
|
+
Requires-Python: >=3.11
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: jinja2>=3.1
|
|
23
|
+
Requires-Dist: pygments>=2.17
|
|
24
|
+
Requires-Dist: pydantic>=2.7
|
|
25
|
+
Requires-Dist: pyyaml>=6.0
|
|
26
|
+
Requires-Dist: rich>=13.7
|
|
27
|
+
Requires-Dist: textual>=0.76
|
|
28
|
+
Requires-Dist: typer>=0.12
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: build>=1.2; extra == "dev"
|
|
31
|
+
Requires-Dist: pytest>=8.2; extra == "dev"
|
|
32
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
33
|
+
Requires-Dist: ruff>=0.6; extra == "dev"
|
|
34
|
+
Requires-Dist: twine>=5.1; extra == "dev"
|
|
35
|
+
Dynamic: license-file
|
|
36
|
+
|
|
37
|
+
# Steplight
|
|
38
|
+
|
|
39
|
+
Local-first trace inspector for LLM agents and tool-driven workflows.
|
|
40
|
+
|
|
41
|
+
> Load a trace. See what happened. Understand why.
|
|
42
|
+
|
|
43
|
+
Steplight is a terminal-native tool for inspecting LLM runs, tool calls, retries, token usage, errors, and execution bottlenecks without sending trace data to a third-party platform.
|
|
44
|
+
|
|
45
|
+
It is designed for developers building with OpenAI-style workflows, LangChain, MCP tooling, or custom JSON/YAML traces who want a fast local debugging loop and a shareable HTML report.
|
|
46
|
+
|
|
47
|
+
## Why Steplight
|
|
48
|
+
|
|
49
|
+
- Understand what your agent actually did, step by step
|
|
50
|
+
- Spot slow tools, retry loops, and cost spikes quickly
|
|
51
|
+
- Keep sensitive traces on your own machine
|
|
52
|
+
- Stay in the terminal instead of digging through raw JSON by hand
|
|
53
|
+
- Share a polished HTML report when you need feedback from teammates
|
|
54
|
+
|
|
55
|
+
## Features
|
|
56
|
+
|
|
57
|
+
- Interactive Textual inspector for timelines, details, and diagnostics
|
|
58
|
+
- Rich terminal summary for quick debugging and CI-friendly output
|
|
59
|
+
- Static HTML export for sharing a run with other people
|
|
60
|
+
- Built-in diagnostics for common agent failure patterns
|
|
61
|
+
- Support for OpenAI-style traces, LangChain callbacks, MCP logs, and generic JSON/YAML
|
|
62
|
+
- Extensible adapters and rules so you can grow it with your workflow
|
|
63
|
+
|
|
64
|
+
## Install
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
pip install steplight
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
The package installs both `steplight` and the shorter `slt` alias. The examples below use `slt` to avoid PowerShell's reserved `sl` alias.
|
|
71
|
+
|
|
72
|
+
## Quick Start
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
slt summary sample_traces/agent_with_tools.json
|
|
76
|
+
slt validate sample_traces/simple_qa.json
|
|
77
|
+
slt export sample_traces/expensive_run.json -o report.html
|
|
78
|
+
slt inspect sample_traces/agent_with_tools.json
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Example Summary Output
|
|
82
|
+
|
|
83
|
+
```text
|
|
84
|
+
+------------------------ Steplight Summary ------------------------+
|
|
85
|
+
| Run: Find compliance gaps in policy |
|
|
86
|
+
| Source: openai |
|
|
87
|
+
| Overview: Duration: 14.0s | Steps: 4 | Tool calls: 2 | Retries: 0 |
|
|
88
|
+
| Tokens: 4,230 in / 670 out | Est. cost: $0.0028 |
|
|
89
|
+
+-------------------------------------------------------------------+
|
|
90
|
+
|
|
91
|
+
Diagnostics
|
|
92
|
+
- WARNING: Step 'web_search' took 68.6% of total runtime.
|
|
93
|
+
- WARNING: Tool 'web_search' took 9.6s. Check timeouts, caching, or external latency.
|
|
94
|
+
- INFO: Input tokens grew 4.2x between 'Draft plan' and 'Final answer'.
|
|
95
|
+
Bottleneck: web_search (68.6% of total runtime)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Supported Trace Formats
|
|
99
|
+
|
|
100
|
+
- OpenAI-style step traces
|
|
101
|
+
- LangChain callback event exports
|
|
102
|
+
- MCP tool call logs
|
|
103
|
+
- Generic JSON or YAML with an optional `steplight.yaml` mapping file
|
|
104
|
+
|
|
105
|
+
## Diagnostics
|
|
106
|
+
|
|
107
|
+
Steplight currently flags:
|
|
108
|
+
|
|
109
|
+
- bottlenecks
|
|
110
|
+
- retry loops
|
|
111
|
+
- context growth
|
|
112
|
+
- repeated tool calls
|
|
113
|
+
- silent errors
|
|
114
|
+
- slow tools
|
|
115
|
+
- high-cost runs
|
|
116
|
+
- empty completions
|
|
117
|
+
|
|
118
|
+
## Custom Mapping Example
|
|
119
|
+
|
|
120
|
+
```yaml
|
|
121
|
+
mapping:
|
|
122
|
+
steps_path: "$.events"
|
|
123
|
+
timestamp_field: "ts"
|
|
124
|
+
type_field: "event_type"
|
|
125
|
+
type_values:
|
|
126
|
+
prompt: "llm_start"
|
|
127
|
+
completion: "llm_end"
|
|
128
|
+
tool_call: "tool_start"
|
|
129
|
+
tool_result: "tool_end"
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Commands
|
|
133
|
+
|
|
134
|
+
- `slt inspect <file>` opens the interactive Textual UI
|
|
135
|
+
- `slt summary <file>` prints a non-interactive terminal summary
|
|
136
|
+
- `slt export <file> -o report.html` creates a static HTML report
|
|
137
|
+
- `slt validate <file>` checks whether a trace can be parsed successfully
|
|
138
|
+
|
|
139
|
+
## Docs
|
|
140
|
+
|
|
141
|
+
- [Roadmap](docs/ROADMAP.md)
|
|
142
|
+
- [Versioning and Releases](docs/VERSIONING.md)
|
|
143
|
+
- [Release Process](RELEASING.md)
|
|
144
|
+
|
|
145
|
+
## Repository Layout
|
|
146
|
+
|
|
147
|
+
```text
|
|
148
|
+
steplight/
|
|
149
|
+
cli/
|
|
150
|
+
core/
|
|
151
|
+
adapters/
|
|
152
|
+
tui/
|
|
153
|
+
export/
|
|
154
|
+
sample_traces/
|
|
155
|
+
tests/
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Development
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
python -m pip install -e .[dev]
|
|
162
|
+
pytest
|
|
163
|
+
python -m steplight.cli.main --help
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## Release Checklist
|
|
167
|
+
|
|
168
|
+
- Run `pytest`
|
|
169
|
+
- Build the distribution with `python -m build`
|
|
170
|
+
- Update `CHANGELOG.md`
|
|
171
|
+
- Review the generated wheel and sdist
|
|
172
|
+
- Publish the repository to GitHub
|
|
173
|
+
- Publish the package to PyPI when ready
|
|
174
|
+
|
|
175
|
+
## Status
|
|
176
|
+
|
|
177
|
+
Steplight is currently an alpha-stage project focused on a clean local-first inspection experience, a solid parser surface, and practical diagnostics.
|
|
178
|
+
|
|
179
|
+
## License
|
|
180
|
+
|
|
181
|
+
MIT
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# Steplight
|
|
2
|
+
|
|
3
|
+
Local-first trace inspector for LLM agents and tool-driven workflows.
|
|
4
|
+
|
|
5
|
+
> Load a trace. See what happened. Understand why.
|
|
6
|
+
|
|
7
|
+
Steplight is a terminal-native tool for inspecting LLM runs, tool calls, retries, token usage, errors, and execution bottlenecks without sending trace data to a third-party platform.
|
|
8
|
+
|
|
9
|
+
It is designed for developers building with OpenAI-style workflows, LangChain, MCP tooling, or custom JSON/YAML traces who want a fast local debugging loop and a shareable HTML report.
|
|
10
|
+
|
|
11
|
+
## Why Steplight
|
|
12
|
+
|
|
13
|
+
- Understand what your agent actually did, step by step
|
|
14
|
+
- Spot slow tools, retry loops, and cost spikes quickly
|
|
15
|
+
- Keep sensitive traces on your own machine
|
|
16
|
+
- Stay in the terminal instead of digging through raw JSON by hand
|
|
17
|
+
- Share a polished HTML report when you need feedback from teammates
|
|
18
|
+
|
|
19
|
+
## Features
|
|
20
|
+
|
|
21
|
+
- Interactive Textual inspector for timelines, details, and diagnostics
|
|
22
|
+
- Rich terminal summary for quick debugging and CI-friendly output
|
|
23
|
+
- Static HTML export for sharing a run with other people
|
|
24
|
+
- Built-in diagnostics for common agent failure patterns
|
|
25
|
+
- Support for OpenAI-style traces, LangChain callbacks, MCP logs, and generic JSON/YAML
|
|
26
|
+
- Extensible adapters and rules so you can grow it with your workflow
|
|
27
|
+
|
|
28
|
+
## Install
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install steplight
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
The package installs both `steplight` and the shorter `slt` alias. The examples below use `slt` to avoid PowerShell's reserved `sl` alias.
|
|
35
|
+
|
|
36
|
+
## Quick Start
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
slt summary sample_traces/agent_with_tools.json
|
|
40
|
+
slt validate sample_traces/simple_qa.json
|
|
41
|
+
slt export sample_traces/expensive_run.json -o report.html
|
|
42
|
+
slt inspect sample_traces/agent_with_tools.json
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Example Summary Output
|
|
46
|
+
|
|
47
|
+
```text
|
|
48
|
+
+------------------------ Steplight Summary ------------------------+
|
|
49
|
+
| Run: Find compliance gaps in policy |
|
|
50
|
+
| Source: openai |
|
|
51
|
+
| Overview: Duration: 14.0s | Steps: 4 | Tool calls: 2 | Retries: 0 |
|
|
52
|
+
| Tokens: 4,230 in / 670 out | Est. cost: $0.0028 |
|
|
53
|
+
+-------------------------------------------------------------------+
|
|
54
|
+
|
|
55
|
+
Diagnostics
|
|
56
|
+
- WARNING: Step 'web_search' took 68.6% of total runtime.
|
|
57
|
+
- WARNING: Tool 'web_search' took 9.6s. Check timeouts, caching, or external latency.
|
|
58
|
+
- INFO: Input tokens grew 4.2x between 'Draft plan' and 'Final answer'.
|
|
59
|
+
Bottleneck: web_search (68.6% of total runtime)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Supported Trace Formats
|
|
63
|
+
|
|
64
|
+
- OpenAI-style step traces
|
|
65
|
+
- LangChain callback event exports
|
|
66
|
+
- MCP tool call logs
|
|
67
|
+
- Generic JSON or YAML with an optional `steplight.yaml` mapping file
|
|
68
|
+
|
|
69
|
+
## Diagnostics
|
|
70
|
+
|
|
71
|
+
Steplight currently flags:
|
|
72
|
+
|
|
73
|
+
- bottlenecks
|
|
74
|
+
- retry loops
|
|
75
|
+
- context growth
|
|
76
|
+
- repeated tool calls
|
|
77
|
+
- silent errors
|
|
78
|
+
- slow tools
|
|
79
|
+
- high-cost runs
|
|
80
|
+
- empty completions
|
|
81
|
+
|
|
82
|
+
## Custom Mapping Example
|
|
83
|
+
|
|
84
|
+
```yaml
|
|
85
|
+
mapping:
|
|
86
|
+
steps_path: "$.events"
|
|
87
|
+
timestamp_field: "ts"
|
|
88
|
+
type_field: "event_type"
|
|
89
|
+
type_values:
|
|
90
|
+
prompt: "llm_start"
|
|
91
|
+
completion: "llm_end"
|
|
92
|
+
tool_call: "tool_start"
|
|
93
|
+
tool_result: "tool_end"
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Commands
|
|
97
|
+
|
|
98
|
+
- `slt inspect <file>` opens the interactive Textual UI
|
|
99
|
+
- `slt summary <file>` prints a non-interactive terminal summary
|
|
100
|
+
- `slt export <file> -o report.html` creates a static HTML report
|
|
101
|
+
- `slt validate <file>` checks whether a trace can be parsed successfully
|
|
102
|
+
|
|
103
|
+
## Docs
|
|
104
|
+
|
|
105
|
+
- [Roadmap](docs/ROADMAP.md)
|
|
106
|
+
- [Versioning and Releases](docs/VERSIONING.md)
|
|
107
|
+
- [Release Process](RELEASING.md)
|
|
108
|
+
|
|
109
|
+
## Repository Layout
|
|
110
|
+
|
|
111
|
+
```text
|
|
112
|
+
steplight/
|
|
113
|
+
cli/
|
|
114
|
+
core/
|
|
115
|
+
adapters/
|
|
116
|
+
tui/
|
|
117
|
+
export/
|
|
118
|
+
sample_traces/
|
|
119
|
+
tests/
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Development
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
python -m pip install -e .[dev]
|
|
126
|
+
pytest
|
|
127
|
+
python -m steplight.cli.main --help
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Release Checklist
|
|
131
|
+
|
|
132
|
+
- Run `pytest`
|
|
133
|
+
- Build the distribution with `python -m build`
|
|
134
|
+
- Update `CHANGELOG.md`
|
|
135
|
+
- Review the generated wheel and sdist
|
|
136
|
+
- Publish the repository to GitHub
|
|
137
|
+
- Publish the package to PyPI when ready
|
|
138
|
+
|
|
139
|
+
## Status
|
|
140
|
+
|
|
141
|
+
Steplight is currently an alpha-stage project focused on a clean local-first inspection experience, a solid parser surface, and practical diagnostics.
|
|
142
|
+
|
|
143
|
+
## License
|
|
144
|
+
|
|
145
|
+
MIT
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77.0.3", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "steplight"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Local-first trace inspector for LLM agents and tool-driven workflows."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
authors = [
|
|
14
|
+
{ name = "Riccardo Merenda" }
|
|
15
|
+
]
|
|
16
|
+
keywords = [
|
|
17
|
+
"llm",
|
|
18
|
+
"agents",
|
|
19
|
+
"tracing",
|
|
20
|
+
"observability",
|
|
21
|
+
"tui",
|
|
22
|
+
"cli",
|
|
23
|
+
"debugging",
|
|
24
|
+
]
|
|
25
|
+
classifiers = [
|
|
26
|
+
"Development Status :: 3 - Alpha",
|
|
27
|
+
"Environment :: Console",
|
|
28
|
+
"Intended Audience :: Developers",
|
|
29
|
+
"Operating System :: OS Independent",
|
|
30
|
+
"Programming Language :: Python :: 3",
|
|
31
|
+
"Programming Language :: Python :: 3.11",
|
|
32
|
+
"Programming Language :: Python :: 3.12",
|
|
33
|
+
"Programming Language :: Python :: 3.13",
|
|
34
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
35
|
+
"Topic :: Software Development :: Testing",
|
|
36
|
+
"Topic :: Terminals",
|
|
37
|
+
]
|
|
38
|
+
dependencies = [
|
|
39
|
+
"jinja2>=3.1",
|
|
40
|
+
"pygments>=2.17",
|
|
41
|
+
"pydantic>=2.7",
|
|
42
|
+
"pyyaml>=6.0",
|
|
43
|
+
"rich>=13.7",
|
|
44
|
+
"textual>=0.76",
|
|
45
|
+
"typer>=0.12"
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
[project.optional-dependencies]
|
|
49
|
+
dev = [
|
|
50
|
+
"build>=1.2",
|
|
51
|
+
"pytest>=8.2",
|
|
52
|
+
"pytest-asyncio>=0.23",
|
|
53
|
+
"ruff>=0.6",
|
|
54
|
+
"twine>=5.1"
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
[project.scripts]
|
|
58
|
+
steplight = "steplight.cli.main:app"
|
|
59
|
+
slt = "steplight.cli.main:app"
|
|
60
|
+
|
|
61
|
+
[tool.pytest.ini_options]
|
|
62
|
+
testpaths = ["tests"]
|
|
63
|
+
addopts = "-ra -p no:cacheprovider"
|
|
64
|
+
|
|
65
|
+
[tool.setuptools]
|
|
66
|
+
include-package-data = true
|
|
67
|
+
|
|
68
|
+
[tool.setuptools.packages.find]
|
|
69
|
+
include = ["steplight*"]
|
|
70
|
+
|
|
71
|
+
[tool.setuptools.package-data]
|
|
72
|
+
steplight = ["export/templates/*.jinja"]
|
|
73
|
+
|
|
74
|
+
[tool.ruff]
|
|
75
|
+
line-length = 100
|
|
76
|
+
target-version = "py311"
|
|
77
|
+
|
|
78
|
+
[tool.ruff.lint]
|
|
79
|
+
select = [
|
|
80
|
+
"E",
|
|
81
|
+
"F",
|
|
82
|
+
"I",
|
|
83
|
+
"B",
|
|
84
|
+
"UP",
|
|
85
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Trace format adapters."""
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from datetime import datetime, timezone
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def parse_dt(value: Any) -> datetime:
|
|
8
|
+
if isinstance(value, datetime):
|
|
9
|
+
return value
|
|
10
|
+
if value is None:
|
|
11
|
+
return datetime.now(timezone.utc)
|
|
12
|
+
if isinstance(value, (int, float)):
|
|
13
|
+
return datetime.fromtimestamp(value, tz=timezone.utc)
|
|
14
|
+
|
|
15
|
+
normalized = str(value).replace("Z", "+00:00")
|
|
16
|
+
return datetime.fromisoformat(normalized)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def compact_text(value: Any, *, limit: int = 240, keep_empty: bool = False) -> str | None:
|
|
20
|
+
if value is None:
|
|
21
|
+
return None
|
|
22
|
+
if isinstance(value, str):
|
|
23
|
+
text = value.strip()
|
|
24
|
+
else:
|
|
25
|
+
text = str(value).strip()
|
|
26
|
+
if not text:
|
|
27
|
+
return "" if keep_empty else None
|
|
28
|
+
if len(text) <= limit:
|
|
29
|
+
return text
|
|
30
|
+
return text[: limit - 3] + "..."
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
import yaml
|
|
7
|
+
|
|
8
|
+
from steplight.adapters.common import compact_text, parse_dt
|
|
9
|
+
from steplight.core.models import Step, StepType, Trace
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
DEFAULT_MAPPING = {
|
|
13
|
+
"steps_path": "$.steps",
|
|
14
|
+
"timestamp_field": "timestamp",
|
|
15
|
+
"type_field": "type",
|
|
16
|
+
"name_field": "name",
|
|
17
|
+
"duration_field": "duration_ms",
|
|
18
|
+
"input_field": "input",
|
|
19
|
+
"output_field": "output",
|
|
20
|
+
"model_field": "model",
|
|
21
|
+
"tokens_in_field": "tokens_in",
|
|
22
|
+
"tokens_out_field": "tokens_out",
|
|
23
|
+
"error_field": "error",
|
|
24
|
+
"metadata_field": "metadata",
|
|
25
|
+
"type_values": {},
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def load_mapping(config_path: Path | None) -> dict[str, Any]:
|
|
30
|
+
mapping = dict(DEFAULT_MAPPING)
|
|
31
|
+
if not config_path or not config_path.exists():
|
|
32
|
+
return mapping
|
|
33
|
+
|
|
34
|
+
payload = yaml.safe_load(config_path.read_text(encoding="utf-8")) or {}
|
|
35
|
+
mapping.update(payload.get("mapping") or {})
|
|
36
|
+
return mapping
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def parse_generic_trace(payload: dict[str, Any], mapping: dict[str, Any] | None = None) -> Trace:
|
|
40
|
+
active_mapping = dict(DEFAULT_MAPPING)
|
|
41
|
+
if mapping:
|
|
42
|
+
active_mapping.update(mapping)
|
|
43
|
+
|
|
44
|
+
raw_steps = _extract_path(payload, active_mapping["steps_path"])
|
|
45
|
+
if not isinstance(raw_steps, list):
|
|
46
|
+
raise ValueError("Generic mapping did not resolve to a list of steps.")
|
|
47
|
+
|
|
48
|
+
steps: list[Step] = []
|
|
49
|
+
type_values = active_mapping.get("type_values") or {}
|
|
50
|
+
|
|
51
|
+
for index, raw_step in enumerate(raw_steps):
|
|
52
|
+
if not isinstance(raw_step, dict):
|
|
53
|
+
raise ValueError(f"Generic step at index {index} is not an object.")
|
|
54
|
+
raw_type = _get_field(raw_step, active_mapping["type_field"])
|
|
55
|
+
normalized_type = _normalize_type(raw_type, type_values)
|
|
56
|
+
metadata = _get_field(raw_step, active_mapping["metadata_field"]) or {}
|
|
57
|
+
steps.append(
|
|
58
|
+
Step(
|
|
59
|
+
id=str(raw_step.get("id") or f"{payload.get('id', 'generic')}:step:{index}"),
|
|
60
|
+
type=normalized_type,
|
|
61
|
+
name=_get_field(raw_step, active_mapping["name_field"]),
|
|
62
|
+
timestamp=parse_dt(_get_field(raw_step, active_mapping["timestamp_field"])),
|
|
63
|
+
duration_ms=_maybe_float(_get_field(raw_step, active_mapping["duration_field"])),
|
|
64
|
+
input=compact_text(_get_field(raw_step, active_mapping["input_field"])),
|
|
65
|
+
output=compact_text(_get_field(raw_step, active_mapping["output_field"]), keep_empty=True),
|
|
66
|
+
model=_get_field(raw_step, active_mapping["model_field"]),
|
|
67
|
+
tokens_in=_maybe_int(_get_field(raw_step, active_mapping["tokens_in_field"])),
|
|
68
|
+
tokens_out=_maybe_int(_get_field(raw_step, active_mapping["tokens_out_field"])),
|
|
69
|
+
error=compact_text(_get_field(raw_step, active_mapping["error_field"])),
|
|
70
|
+
metadata=metadata if isinstance(metadata, dict) else {"value": metadata},
|
|
71
|
+
)
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
started_at = parse_dt(payload.get("started_at") or (steps[0].timestamp if steps else None))
|
|
75
|
+
ended_at = parse_dt(payload.get("ended_at")) if payload.get("ended_at") else _infer_ended_at(steps)
|
|
76
|
+
|
|
77
|
+
return Trace(
|
|
78
|
+
id=str(payload.get("id") or "generic-trace"),
|
|
79
|
+
name=payload.get("name"),
|
|
80
|
+
started_at=started_at,
|
|
81
|
+
ended_at=ended_at,
|
|
82
|
+
steps=steps,
|
|
83
|
+
total_tokens=_maybe_int(payload.get("total_tokens")),
|
|
84
|
+
total_cost_usd=_maybe_float(payload.get("total_cost_usd") or payload.get("cost_usd")),
|
|
85
|
+
source="generic",
|
|
86
|
+
status=payload.get("status"),
|
|
87
|
+
metadata={"raw": payload, "mapping": active_mapping},
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _extract_path(payload: dict[str, Any], json_path: str) -> Any:
|
|
92
|
+
current: Any = payload
|
|
93
|
+
path = json_path.strip()
|
|
94
|
+
if path in {"$", ""}:
|
|
95
|
+
return current
|
|
96
|
+
if path.startswith("$."):
|
|
97
|
+
path = path[2:]
|
|
98
|
+
for part in path.split("."):
|
|
99
|
+
if not part:
|
|
100
|
+
continue
|
|
101
|
+
if "[" in part and part.endswith("]"):
|
|
102
|
+
key, index_text = part[:-1].split("[", maxsplit=1)
|
|
103
|
+
if key:
|
|
104
|
+
current = current[key]
|
|
105
|
+
current = current[int(index_text)]
|
|
106
|
+
else:
|
|
107
|
+
current = current[part]
|
|
108
|
+
return current
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _get_field(item: dict[str, Any], field_name: str | None) -> Any:
|
|
112
|
+
if not field_name:
|
|
113
|
+
return None
|
|
114
|
+
current: Any = item
|
|
115
|
+
for part in str(field_name).split("."):
|
|
116
|
+
if not isinstance(current, dict):
|
|
117
|
+
return None
|
|
118
|
+
current = current.get(part)
|
|
119
|
+
return current
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _normalize_type(raw_type: Any, type_values: dict[str, str]) -> StepType:
|
|
123
|
+
value = str(raw_type or "").strip().lower()
|
|
124
|
+
for normalized, external in type_values.items():
|
|
125
|
+
if value == str(external).strip().lower():
|
|
126
|
+
return StepType(normalized)
|
|
127
|
+
if value in {member.value for member in StepType}:
|
|
128
|
+
return StepType(value)
|
|
129
|
+
if "tool" in value and "result" in value:
|
|
130
|
+
return StepType.TOOL_RESULT
|
|
131
|
+
if "tool" in value:
|
|
132
|
+
return StepType.TOOL_CALL
|
|
133
|
+
if "retry" in value:
|
|
134
|
+
return StepType.RETRY
|
|
135
|
+
if "error" in value:
|
|
136
|
+
return StepType.ERROR
|
|
137
|
+
if "start" in value:
|
|
138
|
+
return StepType.CHAIN_START
|
|
139
|
+
if "end" in value:
|
|
140
|
+
return StepType.CHAIN_END
|
|
141
|
+
return StepType.COMPLETION
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _maybe_int(value: Any) -> int | None:
|
|
145
|
+
return int(value) if value is not None else None
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _maybe_float(value: Any) -> float | None:
|
|
149
|
+
return float(value) if value is not None else None
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _infer_ended_at(steps: list[Step]):
|
|
153
|
+
if not steps:
|
|
154
|
+
return None
|
|
155
|
+
latest = max(steps, key=lambda item: item.timestamp)
|
|
156
|
+
return latest.timestamp
|