agent-first-data 0.1.3__tar.gz → 0.2.1__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.
@@ -0,0 +1,407 @@
1
+ Metadata-Version: 2.4
2
+ Name: agent-first-data
3
+ Version: 0.2.1
4
+ Summary: Agent-First Data (AFD) — suffix-driven output formatting and protocol templates for AI agents
5
+ License-Expression: MIT
6
+ Project-URL: Repository, https://github.com/cmnspore/agent-first-data
7
+ Requires-Python: >=3.9
8
+ Description-Content-Type: text/markdown
9
+
10
+ # agent-first-data
11
+
12
+ **Agent-First Data (AFD)** — Suffix-driven output formatting and protocol templates for AI agents.
13
+
14
+ The field name is the schema. Agents read `latency_ms` and know milliseconds, `api_key_secret` and know to redact, no external schema needed.
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ pip install agent-first-data
20
+ ```
21
+
22
+ ## API Reference
23
+
24
+ Total: **9 public APIs** + **AFD logging** (4 protocol builders + 3 output functions + 1 internal + 1 utility)
25
+
26
+ ### Protocol Builders (returns dict)
27
+
28
+ Build AFD protocol structures. Return dict objects for API responses.
29
+
30
+ ```python
31
+ # Startup (configuration)
32
+ build_json_startup(config: Any, args: Any, env: Any) -> dict
33
+
34
+ # Success (result)
35
+ build_json_ok(result: Any, trace: Any = None) -> dict
36
+
37
+ # Error (simple message)
38
+ build_json_error(message: str, trace: Any = None) -> dict
39
+
40
+ # Generic (any code + fields)
41
+ build_json(code: str, fields: Any, trace: Any = None) -> dict
42
+ ```
43
+
44
+ **Use case:** API responses (frameworks like FastAPI automatically serialize)
45
+
46
+ **Example:**
47
+ ```python
48
+ from agent_first_data import *
49
+
50
+ # Startup
51
+ startup = build_json_startup(
52
+ {"api_key_secret": "sk-123", "timeout_s": 30},
53
+ {"config_path": "config.yml"},
54
+ {"RUST_LOG": "info"},
55
+ )
56
+
57
+ # Success (always include trace)
58
+ response = build_json_ok(
59
+ {"user_id": 123},
60
+ trace={"duration_ms": 150, "source": "db"},
61
+ )
62
+
63
+ # Error
64
+ err = build_json_error("user not found", trace={"duration_ms": 5})
65
+
66
+ # Specific error code
67
+ not_found = build_json(
68
+ "not_found",
69
+ {"resource": "user", "id": 123},
70
+ trace={"duration_ms": 8},
71
+ )
72
+ ```
73
+
74
+ ### CLI/Log Output (returns str)
75
+
76
+ Format values for CLI output and logs. **All formats redact `_secret` fields.** YAML and Plain also strip suffixes from keys and format values for human readability.
77
+
78
+ ```python
79
+ output_json(value: Any) -> str # Single-line JSON, original keys, for programs/logs
80
+ output_yaml(value: Any) -> str # Multi-line YAML, keys stripped, values formatted
81
+ output_plain(value: Any) -> str # Single-line logfmt, keys stripped, values formatted
82
+ ```
83
+
84
+ **Example:**
85
+ ```python
86
+ from agent_first_data import *
87
+
88
+ data = {
89
+ "user_id": 123,
90
+ "api_key_secret": "sk-1234567890abcdef",
91
+ "created_at_epoch_ms": 1738886400000,
92
+ "file_size_bytes": 5242880,
93
+ }
94
+
95
+ # JSON (secrets redacted, original keys, raw values)
96
+ print(output_json(data))
97
+ # {"api_key_secret":"***","created_at_epoch_ms":1738886400000,"file_size_bytes":5242880,"user_id":123}
98
+
99
+ # YAML (keys stripped, values formatted, secrets redacted)
100
+ print(output_yaml(data))
101
+ # ---
102
+ # api_key: "***"
103
+ # created_at: "2025-02-07T00:00:00.000Z"
104
+ # file_size: "5.0MB"
105
+ # user_id: 123
106
+
107
+ # Plain logfmt (keys stripped, values formatted, secrets redacted)
108
+ print(output_plain(data))
109
+ # api_key=*** created_at=2025-02-07T00:00:00.000Z file_size=5.0MB user_id=123
110
+ ```
111
+
112
+ ### Internal Tools
113
+
114
+ ```python
115
+ internal_redact_secrets(value: Any) -> None # Manually redact secrets in-place
116
+ ```
117
+
118
+ Most users don't need this. Output functions automatically protect secrets.
119
+
120
+ ### Utility Functions
121
+
122
+ ```python
123
+ parse_size(s: str) -> int | None # Parse "10M" → bytes
124
+ ```
125
+
126
+ **Example:**
127
+ ```python
128
+ from agent_first_data import *
129
+
130
+ assert parse_size("10M") == 10485760
131
+ assert parse_size("1.5K") == 1536
132
+ assert parse_size("512") == 512
133
+ ```
134
+
135
+ ## Usage Examples
136
+
137
+ ### Example 1: REST API
138
+
139
+ ```python
140
+ from agent_first_data import *
141
+ from fastapi import FastAPI
142
+
143
+ app = FastAPI()
144
+
145
+ @app.get("/users/{user_id}")
146
+ async def get_user(user_id: int):
147
+ response = build_json_ok(
148
+ {"user_id": user_id, "name": "alice"},
149
+ trace={"duration_ms": 150, "source": "db"},
150
+ )
151
+ # API returns raw JSON — no output processing, no key stripping
152
+ return response
153
+ ```
154
+
155
+ ### Example 2: CLI Tool (Complete Lifecycle)
156
+
157
+ ```python
158
+ from agent_first_data import *
159
+
160
+ # 1. Startup
161
+ startup = build_json_startup(
162
+ {"api_key_secret": "sk-sensitive-key", "timeout_s": 30},
163
+ {"input_path": "data.json"},
164
+ {"RUST_LOG": "info"},
165
+ )
166
+ print(output_yaml(startup))
167
+ # ---
168
+ # code: "startup"
169
+ # args:
170
+ # input_path: "data.json"
171
+ # config:
172
+ # api_key: "***"
173
+ # timeout: "30s"
174
+ # env:
175
+ # RUST_LOG: "info"
176
+
177
+ # 2. Progress
178
+ progress = build_json(
179
+ "progress",
180
+ {"current": 3, "total": 10, "message": "processing"},
181
+ trace={"duration_ms": 1500},
182
+ )
183
+ print(output_plain(progress))
184
+ # code=progress current=3 message=processing total=10 trace.duration=1.5s
185
+
186
+ # 3. Result
187
+ result = build_json_ok(
188
+ {
189
+ "records_processed": 10,
190
+ "file_size_bytes": 5242880,
191
+ "created_at_epoch_ms": 1738886400000,
192
+ },
193
+ trace={"duration_ms": 3500, "source": "file"},
194
+ )
195
+ print(output_yaml(result))
196
+ # ---
197
+ # code: "ok"
198
+ # result:
199
+ # created_at: "2025-02-07T00:00:00.000Z"
200
+ # file_size: "5.0MB"
201
+ # records_processed: 10
202
+ # trace:
203
+ # duration: "3.5s"
204
+ # source: "file"
205
+ ```
206
+
207
+ ### Example 3: JSONL Output
208
+
209
+ ```python
210
+ from agent_first_data import *
211
+
212
+ result = build_json_ok(
213
+ {"status": "success"},
214
+ trace={"duration_ms": 250, "api_key_secret": "sk-123"},
215
+ )
216
+
217
+ # Print JSONL to stdout (secrets redacted, one JSON object per line)
218
+ print(output_json(result))
219
+ # {"code":"ok","result":{"status":"success"},"trace":{"api_key_secret":"***","duration_ms":250}}
220
+ ```
221
+
222
+ ## Complete Suffix Example
223
+
224
+ ```python
225
+ from agent_first_data import *
226
+
227
+ data = {
228
+ "created_at_epoch_ms": 1738886400000,
229
+ "request_timeout_ms": 5000,
230
+ "cache_ttl_s": 3600,
231
+ "file_size_bytes": 5242880,
232
+ "payment_msats": 50000000,
233
+ "price_usd_cents": 9999,
234
+ "success_rate_percent": 95.5,
235
+ "api_key_secret": "sk-1234567890abcdef",
236
+ "user_name": "alice",
237
+ "count": 42,
238
+ }
239
+
240
+ # YAML output (keys stripped, values formatted, secrets redacted)
241
+ print(output_yaml(data))
242
+ # ---
243
+ # api_key: "***"
244
+ # cache_ttl: "3600s"
245
+ # count: 42
246
+ # created_at: "2025-02-07T00:00:00.000Z"
247
+ # file_size: "5.0MB"
248
+ # payment: "50000000msats"
249
+ # price: "$99.99"
250
+ # request_timeout: "5.0s"
251
+ # success_rate: "95.5%"
252
+ # user_name: "alice"
253
+
254
+ # Plain logfmt output (same transformations, single line)
255
+ print(output_plain(data))
256
+ # api_key=*** cache_ttl=3600s count=42 created_at=2025-02-07T00:00:00.000Z file_size=5.0MB payment=50000000msats price=$99.99 request_timeout=5.0s success_rate=95.5% user_name=alice
257
+ ```
258
+
259
+ ## AFD Logging
260
+
261
+ AFD-compliant structured logging via Python's `logging` module. Every log line is formatted using the library's own `output_json`/`output_plain`/`output_yaml` functions. Span fields are carried via `contextvars` (async-safe), automatically flattened into each log line.
262
+
263
+ ### API
264
+
265
+ ```python
266
+ from agent_first_data import init_logging_json, init_logging_plain, init_logging_yaml
267
+ from agent_first_data.afd_logging import AfdHandler, get_logger, span
268
+
269
+ # Convenience initializers — set up the root logger with AFD output to stdout
270
+ init_logging_json(level="INFO") # Single-line JSONL (secrets redacted, original keys)
271
+ init_logging_plain(level="INFO") # Single-line logfmt (keys stripped, values formatted)
272
+ init_logging_yaml(level="INFO") # Multi-line YAML (keys stripped, values formatted)
273
+
274
+ # Low-level — create a handler for custom logger stacks
275
+ AfdHandler(format="json") # format: "json" | "plain" | "yaml"
276
+
277
+ # Logger with default fields (returns logging.LoggerAdapter)
278
+ get_logger(name, **fields)
279
+
280
+ # Span context manager — adds fields to all log events within the block
281
+ span(**fields)
282
+ ```
283
+
284
+ ### Setup
285
+
286
+ ```python
287
+ from agent_first_data import init_logging_json, init_logging_plain, init_logging_yaml
288
+
289
+ # JSON output for production (one JSONL line per event, secrets redacted)
290
+ init_logging_json("INFO")
291
+
292
+ # Plain logfmt for development (keys stripped, values formatted)
293
+ init_logging_plain("DEBUG")
294
+
295
+ # YAML for detailed inspection (multi-line, keys stripped, values formatted)
296
+ init_logging_yaml("DEBUG")
297
+ ```
298
+
299
+ ### Log Output
300
+
301
+ Standard `logging` calls work unchanged. Output format depends on the init function used.
302
+
303
+ ```python
304
+ import logging
305
+ logger = logging.getLogger("myapp")
306
+
307
+ logger.info("Server started")
308
+ # JSON: {"timestamp_epoch_ms":1739000000000,"message":"Server started","target":"myapp","code":"info"}
309
+ # Plain: code=info message="Server started" target=myapp timestamp_epoch_ms=1739000000000
310
+ # YAML: ---
311
+ # code: "info"
312
+ # message: "Server started"
313
+ # target: "myapp"
314
+ # timestamp_epoch_ms: 1739000000000
315
+
316
+ logger.warning("DNS lookup failed")
317
+ # JSON: {"timestamp_epoch_ms":...,"message":"DNS lookup failed","target":"myapp","code":"warn"}
318
+ ```
319
+
320
+ ### Span Support
321
+
322
+ Use the `span` context manager to add fields to all log events within the block. Spans nest and work with both sync and async code.
323
+
324
+ ```python
325
+ from agent_first_data import span
326
+
327
+ with span(request_id="abc-123"):
328
+ logger.info("Processing")
329
+ # {"timestamp_epoch_ms":...,"message":"Processing","target":"myapp","request_id":"abc-123","code":"info"}
330
+
331
+ with span(step="validate"):
332
+ logger.info("Validating input")
333
+ # {"timestamp_epoch_ms":...,"message":"Validating input","target":"myapp","request_id":"abc-123","step":"validate","code":"info"}
334
+ ```
335
+
336
+ ### Logger with Default Fields
337
+
338
+ Use `get_logger` for per-component fields that appear on every log line:
339
+
340
+ ```python
341
+ from agent_first_data import get_logger
342
+
343
+ logger = get_logger("myapp.auth", component="auth")
344
+ logger.info("Token verified")
345
+ # {"timestamp_epoch_ms":...,"message":"Token verified","target":"myapp.auth","component":"auth","code":"info"}
346
+ ```
347
+
348
+ ### Custom Code Override
349
+
350
+ The `code` field defaults to the log level. Override with an explicit field:
351
+
352
+ ```python
353
+ from agent_first_data import get_logger
354
+
355
+ logger = get_logger("myapp")
356
+ logger.info("Server ready", extra={"code": "startup"})
357
+ # {"timestamp_epoch_ms":...,"message":"Server ready","target":"myapp","code":"startup"}
358
+ ```
359
+
360
+ ### Output Fields
361
+
362
+ Every log line contains:
363
+
364
+ | Field | Type | Description |
365
+ |:------|:-----|:------------|
366
+ | `timestamp_epoch_ms` | number | Unix milliseconds |
367
+ | `message` | string | Log message |
368
+ | `target` | string | Logger name |
369
+ | `code` | string | Level (debug/info/warn/error) or explicit override |
370
+ | *span fields* | any | From `span()` context manager |
371
+ | *event fields* | any | From `extra=` or `get_logger` fields |
372
+
373
+ ### Log Output Formats
374
+
375
+ All three formats use the library's own output functions, so AFD suffix processing applies to log fields too:
376
+
377
+ | Format | Function | Keys | Values | Use case |
378
+ |:-------|:---------|:-----|:-------|:---------|
379
+ | **JSON** | `init_logging_json` | original (with suffix) | raw | production, log aggregation |
380
+ | **Plain** | `init_logging_plain` | stripped | formatted | development, compact scanning |
381
+ | **YAML** | `init_logging_yaml` | stripped | formatted | debugging, detailed inspection |
382
+
383
+ All formats automatically redact `_secret` fields in log output.
384
+
385
+ ## Output Formats
386
+
387
+ Three output formats for different use cases:
388
+
389
+ | Format | Structure | Keys | Values | Use case |
390
+ |:-------|:----------|:-----|:-------|:---------|
391
+ | **JSON** | single-line | original (with suffix) | raw | programs, logs |
392
+ | **YAML** | multi-line | stripped | formatted | human inspection |
393
+ | **Plain** | single-line logfmt | stripped | formatted | compact scanning |
394
+
395
+ All formats automatically redact `_secret` fields.
396
+
397
+ ## Supported Suffixes
398
+
399
+ - **Duration**: `_ms`, `_s`, `_ns`, `_us`, `_minutes`, `_hours`, `_days`
400
+ - **Timestamps**: `_epoch_ms`, `_epoch_s`, `_epoch_ns`, `_rfc3339`
401
+ - **Size**: `_bytes` (auto-scales to KB/MB/GB/TB), `_size` (config input, pass through)
402
+ - **Currency**: `_msats`, `_sats`, `_btc`, `_usd_cents`, `_eur_cents`, `_jpy`, `_{code}_cents`
403
+ - **Other**: `_percent`, `_secret` (auto-redacted in all formats)
404
+
405
+ ## License
406
+
407
+ MIT