agent-artifact-kit 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.
- agent_artifact_kit-0.1.0/.gitignore +10 -0
- agent_artifact_kit-0.1.0/LICENSE +21 -0
- agent_artifact_kit-0.1.0/PKG-INFO +225 -0
- agent_artifact_kit-0.1.0/PUBLISHING.md +70 -0
- agent_artifact_kit-0.1.0/README.md +177 -0
- agent_artifact_kit-0.1.0/artifactkit/__init__.py +89 -0
- agent_artifact_kit-0.1.0/artifactkit/adapters/__init__.py +0 -0
- agent_artifact_kit-0.1.0/artifactkit/adapters/mcp_server.py +170 -0
- agent_artifact_kit-0.1.0/artifactkit/adapters/strands_tools.py +202 -0
- agent_artifact_kit-0.1.0/artifactkit/backends/__init__.py +0 -0
- agent_artifact_kit-0.1.0/artifactkit/backends/docx_backend.py +189 -0
- agent_artifact_kit-0.1.0/artifactkit/backends/pdf_backend.py +121 -0
- agent_artifact_kit-0.1.0/artifactkit/backends/pptx_backend.py +254 -0
- agent_artifact_kit-0.1.0/artifactkit/backends/xlsx_backend.py +159 -0
- agent_artifact_kit-0.1.0/artifactkit/core/__init__.py +0 -0
- agent_artifact_kit-0.1.0/artifactkit/core/backend.py +35 -0
- agent_artifact_kit-0.1.0/artifactkit/core/destinations.py +153 -0
- agent_artifact_kit-0.1.0/artifactkit/core/errors.py +36 -0
- agent_artifact_kit-0.1.0/artifactkit/core/models.py +416 -0
- agent_artifact_kit-0.1.0/artifactkit/core/observability.py +90 -0
- agent_artifact_kit-0.1.0/artifactkit/core/parsing.py +177 -0
- agent_artifact_kit-0.1.0/artifactkit/core/registry.py +46 -0
- agent_artifact_kit-0.1.0/artifactkit/core/retry.py +54 -0
- agent_artifact_kit-0.1.0/artifactkit/core/service.py +459 -0
- agent_artifact_kit-0.1.0/docs/api-reference.md +90 -0
- agent_artifact_kit-0.1.0/docs/architecture.md +89 -0
- agent_artifact_kit-0.1.0/docs/index.md +47 -0
- agent_artifact_kit-0.1.0/docs/observability.md +115 -0
- agent_artifact_kit-0.1.0/docs/quickstart.md +149 -0
- agent_artifact_kit-0.1.0/mkdocs.yml +44 -0
- agent_artifact_kit-0.1.0/pyproject.toml +53 -0
- agent_artifact_kit-0.1.0/site/404.html +448 -0
- agent_artifact_kit-0.1.0/site/api-reference/index.html +4570 -0
- agent_artifact_kit-0.1.0/site/architecture/index.html +718 -0
- agent_artifact_kit-0.1.0/site/assets/_mkdocstrings.css +237 -0
- agent_artifact_kit-0.1.0/site/assets/images/favicon.png +0 -0
- agent_artifact_kit-0.1.0/site/assets/javascripts/bundle.d7400e89.min.js +16 -0
- agent_artifact_kit-0.1.0/site/assets/javascripts/bundle.d7400e89.min.js.map +7 -0
- agent_artifact_kit-0.1.0/site/assets/javascripts/lunr/min/lunr.ar.min.js +1 -0
- agent_artifact_kit-0.1.0/site/assets/javascripts/lunr/min/lunr.da.min.js +18 -0
- agent_artifact_kit-0.1.0/site/assets/javascripts/lunr/min/lunr.de.min.js +18 -0
- agent_artifact_kit-0.1.0/site/assets/javascripts/lunr/min/lunr.du.min.js +18 -0
- agent_artifact_kit-0.1.0/site/assets/javascripts/lunr/min/lunr.el.min.js +1 -0
- agent_artifact_kit-0.1.0/site/assets/javascripts/lunr/min/lunr.es.min.js +18 -0
- agent_artifact_kit-0.1.0/site/assets/javascripts/lunr/min/lunr.fi.min.js +18 -0
- agent_artifact_kit-0.1.0/site/assets/javascripts/lunr/min/lunr.fr.min.js +18 -0
- agent_artifact_kit-0.1.0/site/assets/javascripts/lunr/min/lunr.he.min.js +1 -0
- agent_artifact_kit-0.1.0/site/assets/javascripts/lunr/min/lunr.hi.min.js +1 -0
- agent_artifact_kit-0.1.0/site/assets/javascripts/lunr/min/lunr.hu.min.js +18 -0
- agent_artifact_kit-0.1.0/site/assets/javascripts/lunr/min/lunr.hy.min.js +1 -0
- agent_artifact_kit-0.1.0/site/assets/javascripts/lunr/min/lunr.it.min.js +18 -0
- agent_artifact_kit-0.1.0/site/assets/javascripts/lunr/min/lunr.ja.min.js +1 -0
- agent_artifact_kit-0.1.0/site/assets/javascripts/lunr/min/lunr.jp.min.js +1 -0
- agent_artifact_kit-0.1.0/site/assets/javascripts/lunr/min/lunr.kn.min.js +1 -0
- agent_artifact_kit-0.1.0/site/assets/javascripts/lunr/min/lunr.ko.min.js +1 -0
- agent_artifact_kit-0.1.0/site/assets/javascripts/lunr/min/lunr.multi.min.js +1 -0
- agent_artifact_kit-0.1.0/site/assets/javascripts/lunr/min/lunr.nl.min.js +18 -0
- agent_artifact_kit-0.1.0/site/assets/javascripts/lunr/min/lunr.no.min.js +18 -0
- agent_artifact_kit-0.1.0/site/assets/javascripts/lunr/min/lunr.pt.min.js +18 -0
- agent_artifact_kit-0.1.0/site/assets/javascripts/lunr/min/lunr.ro.min.js +18 -0
- agent_artifact_kit-0.1.0/site/assets/javascripts/lunr/min/lunr.ru.min.js +18 -0
- agent_artifact_kit-0.1.0/site/assets/javascripts/lunr/min/lunr.sa.min.js +1 -0
- agent_artifact_kit-0.1.0/site/assets/javascripts/lunr/min/lunr.stemmer.support.min.js +1 -0
- agent_artifact_kit-0.1.0/site/assets/javascripts/lunr/min/lunr.sv.min.js +18 -0
- agent_artifact_kit-0.1.0/site/assets/javascripts/lunr/min/lunr.ta.min.js +1 -0
- agent_artifact_kit-0.1.0/site/assets/javascripts/lunr/min/lunr.te.min.js +1 -0
- agent_artifact_kit-0.1.0/site/assets/javascripts/lunr/min/lunr.th.min.js +1 -0
- agent_artifact_kit-0.1.0/site/assets/javascripts/lunr/min/lunr.tr.min.js +18 -0
- agent_artifact_kit-0.1.0/site/assets/javascripts/lunr/min/lunr.vi.min.js +1 -0
- agent_artifact_kit-0.1.0/site/assets/javascripts/lunr/min/lunr.zh.min.js +1 -0
- agent_artifact_kit-0.1.0/site/assets/javascripts/lunr/tinyseg.js +206 -0
- agent_artifact_kit-0.1.0/site/assets/javascripts/lunr/wordcut.js +6708 -0
- agent_artifact_kit-0.1.0/site/assets/javascripts/workers/search.2c215733.min.js +42 -0
- agent_artifact_kit-0.1.0/site/assets/javascripts/workers/search.2c215733.min.js.map +7 -0
- agent_artifact_kit-0.1.0/site/assets/stylesheets/main.ec1eaa64.min.css +1 -0
- agent_artifact_kit-0.1.0/site/assets/stylesheets/main.ec1eaa64.min.css.map +1 -0
- agent_artifact_kit-0.1.0/site/assets/stylesheets/palette.ab4e12ef.min.css +1 -0
- agent_artifact_kit-0.1.0/site/assets/stylesheets/palette.ab4e12ef.min.css.map +1 -0
- agent_artifact_kit-0.1.0/site/index.html +665 -0
- agent_artifact_kit-0.1.0/site/objects.inv +0 -0
- agent_artifact_kit-0.1.0/site/observability/index.html +903 -0
- agent_artifact_kit-0.1.0/site/quickstart/index.html +794 -0
- agent_artifact_kit-0.1.0/site/search/search_index.json +1 -0
- agent_artifact_kit-0.1.0/site/sitemap.xml +3 -0
- agent_artifact_kit-0.1.0/site/sitemap.xml.gz +0 -0
- agent_artifact_kit-0.1.0/tests/test_adapters.py +118 -0
- agent_artifact_kit-0.1.0/tests/test_backends.py +359 -0
- agent_artifact_kit-0.1.0/tests/test_destinations.py +145 -0
- agent_artifact_kit-0.1.0/tests/test_models.py +71 -0
- agent_artifact_kit-0.1.0/tests/test_observability.py +209 -0
- agent_artifact_kit-0.1.0/tests/test_parsing.py +109 -0
- agent_artifact_kit-0.1.0/tests/test_registry.py +44 -0
- agent_artifact_kit-0.1.0/tests/test_retry.py +97 -0
- agent_artifact_kit-0.1.0/tests/test_service.py +184 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Dipak Rimal
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: agent-artifact-kit
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Shared artifact-creation harness for agentic workflows: renders docx/pptx/xlsx/pdf or delivers arbitrary files, with pluggable destinations and verified writes. Installs as agent-artifact-kit, imports as artifactkit.
|
|
5
|
+
Project-URL: Repository, https://github.com/drimal/artifactkit
|
|
6
|
+
Project-URL: Issues, https://github.com/drimal/artifactkit/issues
|
|
7
|
+
Author: Dipak Rimal
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: agents,artifacts,docx,mcp,pdf,pptx,s3,strands,xlsx
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Office/Business
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Requires-Dist: openpyxl>=3.1.0
|
|
23
|
+
Requires-Dist: pypdf>=4.0.0
|
|
24
|
+
Requires-Dist: python-docx>=1.1.0
|
|
25
|
+
Requires-Dist: python-pptx>=1.0.0
|
|
26
|
+
Requires-Dist: reportlab>=4.2.0
|
|
27
|
+
Provides-Extra: all
|
|
28
|
+
Requires-Dist: boto3>=1.34.0; extra == 'all'
|
|
29
|
+
Requires-Dist: mcp<2.0.0,>=1.0.0; extra == 'all'
|
|
30
|
+
Requires-Dist: strands-agents; extra == 'all'
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: build; extra == 'dev'
|
|
33
|
+
Requires-Dist: pytest-cov; extra == 'dev'
|
|
34
|
+
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
35
|
+
Requires-Dist: twine; extra == 'dev'
|
|
36
|
+
Provides-Extra: docs
|
|
37
|
+
Requires-Dist: mkdocs; extra == 'docs'
|
|
38
|
+
Requires-Dist: mkdocs-material; extra == 'docs'
|
|
39
|
+
Requires-Dist: mkdocstrings[python]; extra == 'docs'
|
|
40
|
+
Requires-Dist: ruff; extra == 'docs'
|
|
41
|
+
Provides-Extra: mcp
|
|
42
|
+
Requires-Dist: mcp<2.0.0,>=1.0.0; extra == 'mcp'
|
|
43
|
+
Provides-Extra: s3
|
|
44
|
+
Requires-Dist: boto3>=1.34.0; extra == 's3'
|
|
45
|
+
Provides-Extra: strands
|
|
46
|
+
Requires-Dist: strands-agents; extra == 'strands'
|
|
47
|
+
Description-Content-Type: text/markdown
|
|
48
|
+
|
|
49
|
+
# artifactkit
|
|
50
|
+
|
|
51
|
+
Installs as `agent-artifact-kit`, imports as `artifactkit`
|
|
52
|
+
(same split as `beautifulsoup4` → `bs4`: the PyPI name is descriptive
|
|
53
|
+
for discoverability, the import name is short because that's what you
|
|
54
|
+
actually type in code).
|
|
55
|
+
|
|
56
|
+
Generic artifact creation harness for agentic workflows. Agents build a
|
|
57
|
+
declarative content spec; artifactkit renders it to docx/pptx/xlsx/pdf,
|
|
58
|
+
writes it to a destination (local disk or S3), verifies the write, and
|
|
59
|
+
returns the location. No agent workflow reimplements this on its own.
|
|
60
|
+
|
|
61
|
+
## Install
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
pip install agent-artifact-kit[s3] # local + S3
|
|
65
|
+
pip install agent-artifact-kit[all] # + Strands and MCP adapters
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
All code below imports the package as `artifactkit` regardless of
|
|
69
|
+
which extras you installed.
|
|
70
|
+
|
|
71
|
+
## Development
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
pip install -e ".[dev]"
|
|
75
|
+
pytest tests/ -v
|
|
76
|
+
pytest tests/ --cov=artifactkit --cov-report=term-missing # 92% with all extras installed
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
See `PUBLISHING.md` for how to build and release to PyPI, and
|
|
80
|
+
`mkdocs.yml` / `docs/` for the full documentation site (`pip install
|
|
81
|
+
-e ".[docs]"` then `mkdocs serve`).
|
|
82
|
+
|
|
83
|
+
## Observability
|
|
84
|
+
|
|
85
|
+
Every call is logged (structured, correlated by an `operation_id`
|
|
86
|
+
returned on the result) and, if you supply a `MetricsSink`, measured.
|
|
87
|
+
See `docs/observability.md` for the exact log events, fields, and
|
|
88
|
+
metric names, or wire in your own sink:
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
from artifactkit import ArtifactService, MetricsSink
|
|
92
|
+
|
|
93
|
+
class MySink:
|
|
94
|
+
def increment(self, name, tags=None): ...
|
|
95
|
+
def timing(self, name, duration_ms, tags=None): ...
|
|
96
|
+
|
|
97
|
+
service = ArtifactService(metrics=MySink())
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Plain Python
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
from artifactkit import (
|
|
104
|
+
ArtifactService, ArtifactFormat, DocumentSpec, Heading, Paragraph,
|
|
105
|
+
destination_from_uri,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
spec = DocumentSpec(
|
|
109
|
+
title="Q3 Summary",
|
|
110
|
+
blocks=(
|
|
111
|
+
Heading("Overview", level=1),
|
|
112
|
+
Paragraph.of("Revenue grew 12% quarter over quarter."),
|
|
113
|
+
),
|
|
114
|
+
base_filename="q3_summary", # optional — bare name, no extension
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
service = ArtifactService()
|
|
118
|
+
result = service.create(
|
|
119
|
+
spec,
|
|
120
|
+
ArtifactFormat.DOCX,
|
|
121
|
+
destination=destination_from_uri("s3://reports-bucket/q3"),
|
|
122
|
+
include_presigned_url=True,
|
|
123
|
+
# filename omitted: resolves to "q3_summary.docx" from spec.base_filename,
|
|
124
|
+
# adapted to whatever format is requested. Pass filename= explicitly to
|
|
125
|
+
# override it for this call without changing the spec.
|
|
126
|
+
)
|
|
127
|
+
print(result.location, result.presigned_url)
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Strands agent
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
from strands import Agent
|
|
134
|
+
from artifactkit.adapters.strands_tools import create_docx, create_pptx, create_xlsx, create_pdf
|
|
135
|
+
|
|
136
|
+
agent = Agent(tools=[create_docx, create_pptx, create_xlsx, create_pdf])
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## MCP server
|
|
140
|
+
|
|
141
|
+
```
|
|
142
|
+
python -m artifactkit.adapters.mcp_server
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Any MCP-capable agent can then call `create_docx` / `create_pptx` /
|
|
146
|
+
`create_xlsx` / `create_pdf` with a JSON spec, a `destination_uri`, and
|
|
147
|
+
a `filename`.
|
|
148
|
+
|
|
149
|
+
## Spec shape
|
|
150
|
+
|
|
151
|
+
See `artifactkit/core/parsing.py` for the exact dict shape each tool
|
|
152
|
+
expects — it's the single source of truth both adapters parse against.
|
|
153
|
+
|
|
154
|
+
## Styling
|
|
155
|
+
|
|
156
|
+
Bare output from python-docx/pptx/openpyxl looks exactly like what it
|
|
157
|
+
is: unstyled. Set `theme` on any structured spec for a named preset
|
|
158
|
+
(`Theme.VIBRANT`, `Theme.CORPORATE`, `Theme.MINIMAL`) that each backend
|
|
159
|
+
applies in whatever way fits its format — colored headings and an
|
|
160
|
+
accent rule in a doc, a gradient hero slide with a decorative shape in
|
|
161
|
+
a deck, a native Excel Table with banded rows and a colored tab in a
|
|
162
|
+
workbook. See `docs/quickstart.md` for examples of all three.
|
|
163
|
+
|
|
164
|
+
## Arbitrary files (code, images, anything else)
|
|
165
|
+
|
|
166
|
+
`create()` is for structured content that needs rendering (docx/pptx/xlsx/pdf).
|
|
167
|
+
For content you already have, code files, images, data files, whatever,
|
|
168
|
+
use `create_files()` instead. It skips rendering entirely and goes
|
|
169
|
+
straight to write + verify. If you hand it more than 5 files, it
|
|
170
|
+
bundles them into a single `.zip` automatically rather than doing one
|
|
171
|
+
write per file.
|
|
172
|
+
|
|
173
|
+
Each `RawFile` takes its content one of two ways:
|
|
174
|
+
|
|
175
|
+
```python
|
|
176
|
+
from artifactkit import ArtifactService, RawFile, FileBundleSpec, destination_from_uri
|
|
177
|
+
|
|
178
|
+
# In-memory bytes (e.g. code the agent just wrote)
|
|
179
|
+
RawFile("main.py", content=b"print('hello')\n")
|
|
180
|
+
|
|
181
|
+
# A path already on disk (e.g. output from code that ran, a generated
|
|
182
|
+
# image, anything already written somewhere) -- read lazily at write
|
|
183
|
+
# time, never loaded into memory until it's actually needed.
|
|
184
|
+
RawFile("chart.png", source_path="/tmp/agent_run/chart.png")
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
If a piece of code produced a whole directory of output, skip
|
|
188
|
+
building `RawFile`s by hand:
|
|
189
|
+
|
|
190
|
+
```python
|
|
191
|
+
bundle = FileBundleSpec.from_directory("/tmp/agent_run", bundle_filename="run_output")
|
|
192
|
+
service = ArtifactService()
|
|
193
|
+
result = service.create_files(bundle, destination_from_uri("s3://reports-bucket/runs"))
|
|
194
|
+
# 5 or fewer files in the directory -> delivered individually
|
|
195
|
+
# 6+ -> bundled into run_output.zip
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Via Strands or MCP, this is the `deliver_files` tool. Each file entry
|
|
199
|
+
needs exactly one of `content_base64` (for content the agent is
|
|
200
|
+
holding directly) or `source_path` (for content already on disk,
|
|
201
|
+
avoids hauling large payloads through the model's context just to
|
|
202
|
+
hand them back to this tool).
|
|
203
|
+
|
|
204
|
+
**artifactkit never executes code or generates content.** It only
|
|
205
|
+
delivers bytes that already exist. If `app.py` needs to run and
|
|
206
|
+
produce files, that execution happens entirely outside this library,
|
|
207
|
+
with whatever sandboxed runtime the agent already has, and
|
|
208
|
+
`create_files()` picks up only once the output exists on disk.
|
|
209
|
+
|
|
210
|
+
## Design notes
|
|
211
|
+
|
|
212
|
+
- Render, validate, and write/verify are three separate stages. Only
|
|
213
|
+
the write/verify stage retries; render and validate failures are
|
|
214
|
+
deterministic and retrying wastes time.
|
|
215
|
+
- `ArtifactValidationError` (bad spec/render) and `ArtifactWriteError`
|
|
216
|
+
(write couldn't be verified) are distinct exception types so a
|
|
217
|
+
calling agent can choose the right remediation.
|
|
218
|
+
- Destinations are pluggable via the `OutputDestination` protocol.
|
|
219
|
+
Adding GCS/Azure support means one new class, no changes to backends
|
|
220
|
+
or the service.
|
|
221
|
+
- `create()` and `create_files()` share the same write/verify/retry/
|
|
222
|
+
presign machinery (`ArtifactService._write_verified`), the only
|
|
223
|
+
difference is whether there's a render+validate step first. Arbitrary
|
|
224
|
+
files skip it because there's no format-specific structure to check
|
|
225
|
+
on content the caller already fully formed.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Publishing artifactkit to PyPI
|
|
2
|
+
|
|
3
|
+
## First-time setup
|
|
4
|
+
|
|
5
|
+
1. Confirm the name is still free: https://pypi.org/project/agent-artifact-kit/
|
|
6
|
+
(a text search can miss a reserved-but-empty name -- check the URL directly)
|
|
7
|
+
2. Create a PyPI account and, separately, a TestPyPI account: https://pypi.org/account/register/
|
|
8
|
+
3. Fix the placeholder URLs in `pyproject.toml` (`[project.urls]`) once the
|
|
9
|
+
repo has a real home -- PyPI displays these on the project page.
|
|
10
|
+
4. Recommended: use Trusted Publishing (OIDC from GitHub Actions) instead of
|
|
11
|
+
a long-lived API token: https://docs.pypi.org/trusted-publishers/
|
|
12
|
+
If publishing manually instead, generate a scoped API token under
|
|
13
|
+
PyPI account settings and use `__token__` as the username when prompted.
|
|
14
|
+
|
|
15
|
+
Note the name split: the PyPI/distribution name is
|
|
16
|
+
`agent-artifact-kit` (set in `pyproject.toml`'s `[project] name`),
|
|
17
|
+
but the importable package stays `artifactkit` (`[tool.hatch.build.targets.wheel]`
|
|
18
|
+
still points at the `artifactkit/` directory). Don't rename one without
|
|
19
|
+
checking the other -- `pip install agent-artifact-kit` should
|
|
20
|
+
always leave you with `import artifactkit` working, and the `all` extra's
|
|
21
|
+
self-reference (`agent-artifact-kit[s3,strands,mcp]`) must use
|
|
22
|
+
the distribution name, not the import name, or extras resolution breaks.
|
|
23
|
+
|
|
24
|
+
## Documentation
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install -e ".[docs]"
|
|
28
|
+
mkdocs serve # live preview at http://127.0.0.1:8000
|
|
29
|
+
mkdocs build --strict # what CI should run before merging docs changes
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
`--strict` turns warnings (broken `::` autodoc references, bad internal
|
|
33
|
+
links) into build failures — always run it before publishing, not just
|
|
34
|
+
`mkdocs build`.
|
|
35
|
+
|
|
36
|
+
## Every release
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
# from the package root
|
|
40
|
+
pip install --upgrade build twine
|
|
41
|
+
|
|
42
|
+
# 1. bump version in pyproject.toml, then:
|
|
43
|
+
rm -rf dist/
|
|
44
|
+
python -m build
|
|
45
|
+
twine check dist/* # catches metadata/README rendering problems
|
|
46
|
+
# before they reach PyPI
|
|
47
|
+
|
|
48
|
+
# 2. dry run against TestPyPI first
|
|
49
|
+
twine upload --repository testpypi dist/*
|
|
50
|
+
pip install --index-url https://test.pypi.org/simple/ agent-artifact-kit
|
|
51
|
+
|
|
52
|
+
# 3. the real upload
|
|
53
|
+
twine upload dist/*
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Before the first real upload
|
|
57
|
+
|
|
58
|
+
- Run the test suite: `pytest tests/ -v` (87 tests with all extras
|
|
59
|
+
installed; 78 pass + 9 skip cleanly without `[strands]`/`[mcp]`)
|
|
60
|
+
- Run `pytest --cov=artifactkit` if you want a coverage number to publish
|
|
61
|
+
- Confirm `pip install agent-artifact-kit[all]` in a fresh venv pulls everything
|
|
62
|
+
cleanly (this is what a new user's first experience will be)
|
|
63
|
+
- Double check `LICENSE` and the `authors` field in `pyproject.toml` reflect
|
|
64
|
+
who should actually be listed
|
|
65
|
+
|
|
66
|
+
## Versioning
|
|
67
|
+
|
|
68
|
+
`pyproject.toml` version is the single source of truth. There is no
|
|
69
|
+
dynamic versioning configured, so bump it by hand before each release
|
|
70
|
+
and tag the commit to match (`git tag v0.1.1`).
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# artifactkit
|
|
2
|
+
|
|
3
|
+
Installs as `agent-artifact-kit`, imports as `artifactkit`
|
|
4
|
+
(same split as `beautifulsoup4` → `bs4`: the PyPI name is descriptive
|
|
5
|
+
for discoverability, the import name is short because that's what you
|
|
6
|
+
actually type in code).
|
|
7
|
+
|
|
8
|
+
Generic artifact creation harness for agentic workflows. Agents build a
|
|
9
|
+
declarative content spec; artifactkit renders it to docx/pptx/xlsx/pdf,
|
|
10
|
+
writes it to a destination (local disk or S3), verifies the write, and
|
|
11
|
+
returns the location. No agent workflow reimplements this on its own.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
pip install agent-artifact-kit[s3] # local + S3
|
|
17
|
+
pip install agent-artifact-kit[all] # + Strands and MCP adapters
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
All code below imports the package as `artifactkit` regardless of
|
|
21
|
+
which extras you installed.
|
|
22
|
+
|
|
23
|
+
## Development
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install -e ".[dev]"
|
|
27
|
+
pytest tests/ -v
|
|
28
|
+
pytest tests/ --cov=artifactkit --cov-report=term-missing # 92% with all extras installed
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
See `PUBLISHING.md` for how to build and release to PyPI, and
|
|
32
|
+
`mkdocs.yml` / `docs/` for the full documentation site (`pip install
|
|
33
|
+
-e ".[docs]"` then `mkdocs serve`).
|
|
34
|
+
|
|
35
|
+
## Observability
|
|
36
|
+
|
|
37
|
+
Every call is logged (structured, correlated by an `operation_id`
|
|
38
|
+
returned on the result) and, if you supply a `MetricsSink`, measured.
|
|
39
|
+
See `docs/observability.md` for the exact log events, fields, and
|
|
40
|
+
metric names, or wire in your own sink:
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
from artifactkit import ArtifactService, MetricsSink
|
|
44
|
+
|
|
45
|
+
class MySink:
|
|
46
|
+
def increment(self, name, tags=None): ...
|
|
47
|
+
def timing(self, name, duration_ms, tags=None): ...
|
|
48
|
+
|
|
49
|
+
service = ArtifactService(metrics=MySink())
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Plain Python
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
from artifactkit import (
|
|
56
|
+
ArtifactService, ArtifactFormat, DocumentSpec, Heading, Paragraph,
|
|
57
|
+
destination_from_uri,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
spec = DocumentSpec(
|
|
61
|
+
title="Q3 Summary",
|
|
62
|
+
blocks=(
|
|
63
|
+
Heading("Overview", level=1),
|
|
64
|
+
Paragraph.of("Revenue grew 12% quarter over quarter."),
|
|
65
|
+
),
|
|
66
|
+
base_filename="q3_summary", # optional — bare name, no extension
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
service = ArtifactService()
|
|
70
|
+
result = service.create(
|
|
71
|
+
spec,
|
|
72
|
+
ArtifactFormat.DOCX,
|
|
73
|
+
destination=destination_from_uri("s3://reports-bucket/q3"),
|
|
74
|
+
include_presigned_url=True,
|
|
75
|
+
# filename omitted: resolves to "q3_summary.docx" from spec.base_filename,
|
|
76
|
+
# adapted to whatever format is requested. Pass filename= explicitly to
|
|
77
|
+
# override it for this call without changing the spec.
|
|
78
|
+
)
|
|
79
|
+
print(result.location, result.presigned_url)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Strands agent
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
from strands import Agent
|
|
86
|
+
from artifactkit.adapters.strands_tools import create_docx, create_pptx, create_xlsx, create_pdf
|
|
87
|
+
|
|
88
|
+
agent = Agent(tools=[create_docx, create_pptx, create_xlsx, create_pdf])
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## MCP server
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
python -m artifactkit.adapters.mcp_server
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Any MCP-capable agent can then call `create_docx` / `create_pptx` /
|
|
98
|
+
`create_xlsx` / `create_pdf` with a JSON spec, a `destination_uri`, and
|
|
99
|
+
a `filename`.
|
|
100
|
+
|
|
101
|
+
## Spec shape
|
|
102
|
+
|
|
103
|
+
See `artifactkit/core/parsing.py` for the exact dict shape each tool
|
|
104
|
+
expects — it's the single source of truth both adapters parse against.
|
|
105
|
+
|
|
106
|
+
## Styling
|
|
107
|
+
|
|
108
|
+
Bare output from python-docx/pptx/openpyxl looks exactly like what it
|
|
109
|
+
is: unstyled. Set `theme` on any structured spec for a named preset
|
|
110
|
+
(`Theme.VIBRANT`, `Theme.CORPORATE`, `Theme.MINIMAL`) that each backend
|
|
111
|
+
applies in whatever way fits its format — colored headings and an
|
|
112
|
+
accent rule in a doc, a gradient hero slide with a decorative shape in
|
|
113
|
+
a deck, a native Excel Table with banded rows and a colored tab in a
|
|
114
|
+
workbook. See `docs/quickstart.md` for examples of all three.
|
|
115
|
+
|
|
116
|
+
## Arbitrary files (code, images, anything else)
|
|
117
|
+
|
|
118
|
+
`create()` is for structured content that needs rendering (docx/pptx/xlsx/pdf).
|
|
119
|
+
For content you already have, code files, images, data files, whatever,
|
|
120
|
+
use `create_files()` instead. It skips rendering entirely and goes
|
|
121
|
+
straight to write + verify. If you hand it more than 5 files, it
|
|
122
|
+
bundles them into a single `.zip` automatically rather than doing one
|
|
123
|
+
write per file.
|
|
124
|
+
|
|
125
|
+
Each `RawFile` takes its content one of two ways:
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
from artifactkit import ArtifactService, RawFile, FileBundleSpec, destination_from_uri
|
|
129
|
+
|
|
130
|
+
# In-memory bytes (e.g. code the agent just wrote)
|
|
131
|
+
RawFile("main.py", content=b"print('hello')\n")
|
|
132
|
+
|
|
133
|
+
# A path already on disk (e.g. output from code that ran, a generated
|
|
134
|
+
# image, anything already written somewhere) -- read lazily at write
|
|
135
|
+
# time, never loaded into memory until it's actually needed.
|
|
136
|
+
RawFile("chart.png", source_path="/tmp/agent_run/chart.png")
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
If a piece of code produced a whole directory of output, skip
|
|
140
|
+
building `RawFile`s by hand:
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
bundle = FileBundleSpec.from_directory("/tmp/agent_run", bundle_filename="run_output")
|
|
144
|
+
service = ArtifactService()
|
|
145
|
+
result = service.create_files(bundle, destination_from_uri("s3://reports-bucket/runs"))
|
|
146
|
+
# 5 or fewer files in the directory -> delivered individually
|
|
147
|
+
# 6+ -> bundled into run_output.zip
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Via Strands or MCP, this is the `deliver_files` tool. Each file entry
|
|
151
|
+
needs exactly one of `content_base64` (for content the agent is
|
|
152
|
+
holding directly) or `source_path` (for content already on disk,
|
|
153
|
+
avoids hauling large payloads through the model's context just to
|
|
154
|
+
hand them back to this tool).
|
|
155
|
+
|
|
156
|
+
**artifactkit never executes code or generates content.** It only
|
|
157
|
+
delivers bytes that already exist. If `app.py` needs to run and
|
|
158
|
+
produce files, that execution happens entirely outside this library,
|
|
159
|
+
with whatever sandboxed runtime the agent already has, and
|
|
160
|
+
`create_files()` picks up only once the output exists on disk.
|
|
161
|
+
|
|
162
|
+
## Design notes
|
|
163
|
+
|
|
164
|
+
- Render, validate, and write/verify are three separate stages. Only
|
|
165
|
+
the write/verify stage retries; render and validate failures are
|
|
166
|
+
deterministic and retrying wastes time.
|
|
167
|
+
- `ArtifactValidationError` (bad spec/render) and `ArtifactWriteError`
|
|
168
|
+
(write couldn't be verified) are distinct exception types so a
|
|
169
|
+
calling agent can choose the right remediation.
|
|
170
|
+
- Destinations are pluggable via the `OutputDestination` protocol.
|
|
171
|
+
Adding GCS/Azure support means one new class, no changes to backends
|
|
172
|
+
or the service.
|
|
173
|
+
- `create()` and `create_files()` share the same write/verify/retry/
|
|
174
|
+
presign machinery (`ArtifactService._write_verified`), the only
|
|
175
|
+
difference is whether there's a render+validate step first. Arbitrary
|
|
176
|
+
files skip it because there's no format-specific structure to check
|
|
177
|
+
on content the caller already fully formed.
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""artifactkit: a shared artifact-creation harness for agentic workflows.
|
|
2
|
+
|
|
3
|
+
Public API surface — everything an agent or a plain-Python caller
|
|
4
|
+
needs is importable from the top level:
|
|
5
|
+
|
|
6
|
+
from artifactkit import (
|
|
7
|
+
ArtifactService, ArtifactFormat, ArtifactResult,
|
|
8
|
+
DocumentSpec, PresentationSpec, WorkbookSpec,
|
|
9
|
+
Heading, Paragraph, ListBlock, Table, Image, PageBreak, TextRun, TextStyle,
|
|
10
|
+
destination_from_uri,
|
|
11
|
+
ArtifactError, ArtifactValidationError, ArtifactWriteError,
|
|
12
|
+
)
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from artifactkit.core.destinations import (
|
|
16
|
+
LocalDirectoryDestination,
|
|
17
|
+
OutputDestination,
|
|
18
|
+
S3Destination,
|
|
19
|
+
destination_from_uri,
|
|
20
|
+
)
|
|
21
|
+
from artifactkit.core.errors import (
|
|
22
|
+
ArtifactError,
|
|
23
|
+
ArtifactValidationError,
|
|
24
|
+
ArtifactWriteError,
|
|
25
|
+
DestinationError,
|
|
26
|
+
UnsupportedFormatError,
|
|
27
|
+
)
|
|
28
|
+
from artifactkit.core.models import (
|
|
29
|
+
ArtifactFormat,
|
|
30
|
+
ArtifactSpec,
|
|
31
|
+
DocumentSpec,
|
|
32
|
+
FileBundleSpec,
|
|
33
|
+
Heading,
|
|
34
|
+
Image,
|
|
35
|
+
ListBlock,
|
|
36
|
+
PageBreak,
|
|
37
|
+
Paragraph,
|
|
38
|
+
PresentationSpec,
|
|
39
|
+
Theme,
|
|
40
|
+
RawFile,
|
|
41
|
+
Sheet,
|
|
42
|
+
Slide,
|
|
43
|
+
Table,
|
|
44
|
+
TextRun,
|
|
45
|
+
TextStyle,
|
|
46
|
+
WorkbookSpec,
|
|
47
|
+
)
|
|
48
|
+
from artifactkit.core.observability import LoggingMetricsSink, MetricsSink, NoOpMetricsSink
|
|
49
|
+
from artifactkit.core.retry import RetryPolicy
|
|
50
|
+
from artifactkit.core.service import ArtifactResult, ArtifactService, FileDeliveryResult
|
|
51
|
+
|
|
52
|
+
__version__ = "0.1.0"
|
|
53
|
+
|
|
54
|
+
__all__ = [
|
|
55
|
+
"ArtifactService",
|
|
56
|
+
"ArtifactResult",
|
|
57
|
+
"ArtifactFormat",
|
|
58
|
+
"ArtifactSpec",
|
|
59
|
+
"RetryPolicy",
|
|
60
|
+
"DocumentSpec",
|
|
61
|
+
"Heading",
|
|
62
|
+
"Paragraph",
|
|
63
|
+
"ListBlock",
|
|
64
|
+
"Table",
|
|
65
|
+
"Image",
|
|
66
|
+
"PageBreak",
|
|
67
|
+
"TextRun",
|
|
68
|
+
"TextStyle",
|
|
69
|
+
"PresentationSpec",
|
|
70
|
+
"Theme",
|
|
71
|
+
"Slide",
|
|
72
|
+
"WorkbookSpec",
|
|
73
|
+
"Sheet",
|
|
74
|
+
"RawFile",
|
|
75
|
+
"FileBundleSpec",
|
|
76
|
+
"FileDeliveryResult",
|
|
77
|
+
"MetricsSink",
|
|
78
|
+
"NoOpMetricsSink",
|
|
79
|
+
"LoggingMetricsSink",
|
|
80
|
+
"OutputDestination",
|
|
81
|
+
"LocalDirectoryDestination",
|
|
82
|
+
"S3Destination",
|
|
83
|
+
"destination_from_uri",
|
|
84
|
+
"ArtifactError",
|
|
85
|
+
"ArtifactValidationError",
|
|
86
|
+
"ArtifactWriteError",
|
|
87
|
+
"UnsupportedFormatError",
|
|
88
|
+
"DestinationError",
|
|
89
|
+
]
|
|
File without changes
|